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;
 | 
						|
};
 |