285 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
		
			7.6 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 = 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;
 | |
| };
 |