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.
284 lines
7.6 KiB
284 lines
7.6 KiB
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; |
|
};
|
|
|