You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					217 lines
				
				6.0 KiB
			
		
		
			
		
	
	
					217 lines
				
				6.0 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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);
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								};
							 |