215 lines
6.0 KiB
JavaScript
215 lines
6.0 KiB
JavaScript
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
var noop = function() {};
|
|
|
|
function ensureFunction(value) {
|
|
return typeof value === 'function' ? value : noop;
|
|
}
|
|
|
|
function invokeForType(fn, type) {
|
|
return function(node, item, list) {
|
|
if (node.type === type) {
|
|
fn.call(this, node, item, list);
|
|
}
|
|
};
|
|
}
|
|
|
|
function getWalkersFromStructure(name, nodeType) {
|
|
var structure = nodeType.structure;
|
|
var walkers = [];
|
|
|
|
for (var key in structure) {
|
|
if (hasOwnProperty.call(structure, key) === false) {
|
|
continue;
|
|
}
|
|
|
|
var fieldTypes = structure[key];
|
|
var walker = {
|
|
name: key,
|
|
type: false,
|
|
nullable: false
|
|
};
|
|
|
|
if (!Array.isArray(structure[key])) {
|
|
fieldTypes = [structure[key]];
|
|
}
|
|
|
|
for (var i = 0; i < fieldTypes.length; i++) {
|
|
var fieldType = fieldTypes[i];
|
|
if (fieldType === null) {
|
|
walker.nullable = true;
|
|
} else if (typeof fieldType === 'string') {
|
|
walker.type = 'node';
|
|
} else if (Array.isArray(fieldType)) {
|
|
walker.type = 'list';
|
|
}
|
|
}
|
|
|
|
if (walker.type) {
|
|
walkers.push(walker);
|
|
}
|
|
}
|
|
|
|
if (walkers.length) {
|
|
return {
|
|
context: nodeType.walkContext,
|
|
fields: walkers
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getTypesFromConfig(config) {
|
|
var types = {};
|
|
|
|
for (var name in config.node) {
|
|
if (hasOwnProperty.call(config.node, name)) {
|
|
var nodeType = config.node[name];
|
|
|
|
if (!nodeType.structure) {
|
|
throw new Error('Missed `structure` field in `' + name + '` node type definition');
|
|
}
|
|
|
|
types[name] = getWalkersFromStructure(name, nodeType);
|
|
}
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
function createTypeIterator(config, reverse) {
|
|
var fields = reverse ? config.fields.slice().reverse() : config.fields;
|
|
var body = fields.map(function(field) {
|
|
var ref = 'node.' + field.name;
|
|
var line;
|
|
|
|
if (field.type === 'list') {
|
|
line = reverse
|
|
? ref + '.forEachRight(walk);'
|
|
: ref + '.forEach(walk);';
|
|
} else {
|
|
line = 'walk(' + ref + ');';
|
|
}
|
|
|
|
if (field.nullable) {
|
|
line = 'if (' + ref + ') {\n ' + line + '}';
|
|
}
|
|
|
|
return line;
|
|
});
|
|
|
|
if (config.context) {
|
|
body = [].concat(
|
|
'var old = context.' + config.context + ';',
|
|
'context.' + config.context + ' = node;',
|
|
body,
|
|
'context.' + config.context + ' = old;'
|
|
);
|
|
}
|
|
|
|
return new Function('node', 'context', 'walk', body.join('\n'));
|
|
}
|
|
|
|
function createFastTraveralMap(iterators) {
|
|
return {
|
|
Atrule: {
|
|
StyleSheet: iterators.StyleSheet,
|
|
Atrule: iterators.Atrule,
|
|
Rule: iterators.Rule,
|
|
Block: iterators.Block
|
|
},
|
|
Rule: {
|
|
StyleSheet: iterators.StyleSheet,
|
|
Atrule: iterators.Atrule,
|
|
Rule: iterators.Rule,
|
|
Block: iterators.Block
|
|
},
|
|
Declaration: {
|
|
StyleSheet: iterators.StyleSheet,
|
|
Atrule: iterators.Atrule,
|
|
Rule: iterators.Rule,
|
|
Block: iterators.Block
|
|
}
|
|
};
|
|
}
|
|
|
|
module.exports = function createWalker(config) {
|
|
var types = getTypesFromConfig(config);
|
|
var iteratorsNatural = {};
|
|
var iteratorsReverse = {};
|
|
|
|
for (var name in types) {
|
|
if (hasOwnProperty.call(types, name) && types[name] !== null) {
|
|
iteratorsNatural[name] = createTypeIterator(types[name], false);
|
|
iteratorsReverse[name] = createTypeIterator(types[name], true);
|
|
}
|
|
}
|
|
|
|
var fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
|
|
var fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
|
|
|
|
return function walk(root, options) {
|
|
function walkNode(node, item, list) {
|
|
enter.call(context, node, item, list);
|
|
|
|
if (iterators.hasOwnProperty(node.type)) {
|
|
iterators[node.type](node, context, walkNode);
|
|
}
|
|
|
|
leave.call(context, node, item, list);
|
|
}
|
|
|
|
var enter = noop;
|
|
var leave = noop;
|
|
var iterators = iteratorsNatural;
|
|
var context = {
|
|
root: root,
|
|
stylesheet: null,
|
|
atrule: null,
|
|
atrulePrelude: null,
|
|
rule: null,
|
|
selector: null,
|
|
block: null,
|
|
declaration: null,
|
|
function: null
|
|
};
|
|
|
|
if (typeof options === 'function') {
|
|
enter = options;
|
|
} else if (options) {
|
|
enter = ensureFunction(options.enter);
|
|
leave = ensureFunction(options.leave);
|
|
|
|
if (options.reverse) {
|
|
iterators = iteratorsReverse;
|
|
}
|
|
|
|
if (options.visit) {
|
|
if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
|
|
iterators = options.reverse
|
|
? fastTraversalIteratorsReverse[options.visit]
|
|
: fastTraversalIteratorsNatural[options.visit];
|
|
} else if (!types.hasOwnProperty(options.visit)) {
|
|
throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).join(', ') + ')');
|
|
}
|
|
|
|
enter = invokeForType(enter, options.visit);
|
|
leave = invokeForType(leave, options.visit);
|
|
}
|
|
}
|
|
|
|
if (enter === noop && leave === noop) {
|
|
throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
|
|
}
|
|
|
|
// swap handlers in reverse mode to invert visit order
|
|
if (options.reverse) {
|
|
var tmp = enter;
|
|
enter = leave;
|
|
leave = tmp;
|
|
}
|
|
|
|
walkNode(root);
|
|
};
|
|
};
|