| 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, walkReducer) { |
| 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') { |
| var breakWalk = reverse |
| ? ref.reduceRight(walkReducer, false) |
| : ref.reduce(walkReducer, false); |
| |
| if (breakWalk) { |
| return true; |
| } |
| } else if (walk(ref)) { |
| return true; |
| } |
| } |
| } |
| |
| 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, |
| DeclarationList: iterators.DeclarationList |
| } |
| }; |
| } |
| |
| module.exports = function createWalker(config) { |
| var types = getTypesFromConfig(config); |
| var iteratorsNatural = {}; |
| var iteratorsReverse = {}; |
| var breakWalk = Symbol('break-walk'); |
| var skipNode = Symbol('skip-node'); |
| |
| 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) { |
| var enterRet = enter.call(context, node, item, list); |
| |
| if (enterRet === breakWalk) { |
| debugger; |
| return true; |
| } |
| |
| if (enterRet === skipNode) { |
| return false; |
| } |
| |
| if (iterators.hasOwnProperty(node.type)) { |
| if (iterators[node.type](node, context, walkNode, walkReducer)) { |
| return true; |
| } |
| } |
| |
| if (leave.call(context, node, item, list) === breakWalk) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| var walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list); |
| var enter = noop; |
| var leave = noop; |
| var iterators = iteratorsNatural; |
| var context = { |
| break: breakWalk, |
| skip: skipNode, |
| |
| 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'); |
| } |
| |
| walkNode(root); |
| }; |
| |
| walk.break = breakWalk; |
| walk.skip = skipNode; |
| |
| walk.find = function(ast, fn) { |
| var found = null; |
| |
| walk(ast, function(node, item, list) { |
| if (fn.call(this, node, item, list)) { |
| found = node; |
| return breakWalk; |
| } |
| }); |
| |
| return found; |
| }; |
| |
| walk.findLast = function(ast, fn) { |
| var found = null; |
| |
| walk(ast, { |
| reverse: true, |
| enter: function(node, item, list) { |
| if (fn.call(this, node, item, list)) { |
| found = node; |
| return breakWalk; |
| } |
| } |
| }); |
| |
| 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; |
| }; |