|
|
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 = config.fields.slice(); var contextName = config.context; var useContext = typeof contextName === 'string';
if (reverse) { fields.reverse(); }
return function(node, context, walk) { var prevContextValue;
if (useContext) { prevContextValue = context[contextName]; context[contextName] = node; }
for (var i = 0; i < fields.length; i++) { var field = fields[i]; var ref = node[field.name];
if (!field.nullable || ref) { if (field.type === 'list') { if (reverse) { ref.forEachRight(walk); } else { ref.forEach(walk); } } else { walk(ref); } } }
if (useContext) { context[contextName] = prevContextValue; } }; }
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);
var walk = function(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); };
walk.find = function(ast, fn) { var found = null;
walk(ast, function(node, item, list) { if (found === null && fn.call(this, node, item, list)) { found = node; } });
return found; };
walk.findLast = function(ast, fn) { var found = null;
walk(ast, { reverse: true, enter: function(node, item, list) { if (found === null && fn.call(this, node, item, list)) { found = node; } } });
return found; };
walk.findAll = function(ast, fn) { var found = [];
walk(ast, function(node, item, list) { if (fn.call(this, node, item, list)) { found.push(node); } });
return found; };
return walk; };
|