Spaces:
Runtime error
Runtime error
const { hasOwnProperty } = Object.prototype; | |
const 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) { | |
const structure = nodeType.structure; | |
const walkers = []; | |
for (const key in structure) { | |
if (hasOwnProperty.call(structure, key) === false) { | |
continue; | |
} | |
let fieldTypes = structure[key]; | |
const walker = { | |
name: key, | |
type: false, | |
nullable: false | |
}; | |
if (!Array.isArray(fieldTypes)) { | |
fieldTypes = [fieldTypes]; | |
} | |
for (const fieldType of fieldTypes) { | |
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) { | |
const types = {}; | |
for (const name in config.node) { | |
if (hasOwnProperty.call(config.node, name)) { | |
const 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) { | |
const fields = config.fields.slice(); | |
const contextName = config.context; | |
const useContext = typeof contextName === 'string'; | |
if (reverse) { | |
fields.reverse(); | |
} | |
return function(node, context, walk, walkReducer) { | |
let prevContextValue; | |
if (useContext) { | |
prevContextValue = context[contextName]; | |
context[contextName] = node; | |
} | |
for (const field of fields) { | |
const ref = node[field.name]; | |
if (!field.nullable || ref) { | |
if (field.type === 'list') { | |
const 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({ | |
StyleSheet, | |
Atrule, | |
Rule, | |
Block, | |
DeclarationList | |
}) { | |
return { | |
Atrule: { | |
StyleSheet, | |
Atrule, | |
Rule, | |
Block | |
}, | |
Rule: { | |
StyleSheet, | |
Atrule, | |
Rule, | |
Block | |
}, | |
Declaration: { | |
StyleSheet, | |
Atrule, | |
Rule, | |
Block, | |
DeclarationList | |
} | |
}; | |
} | |
export function createWalker(config) { | |
const types = getTypesFromConfig(config); | |
const iteratorsNatural = {}; | |
const iteratorsReverse = {}; | |
const breakWalk = Symbol('break-walk'); | |
const skipNode = Symbol('skip-node'); | |
for (const name in types) { | |
if (hasOwnProperty.call(types, name) && types[name] !== null) { | |
iteratorsNatural[name] = createTypeIterator(types[name], false); | |
iteratorsReverse[name] = createTypeIterator(types[name], true); | |
} | |
} | |
const fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural); | |
const fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse); | |
const walk = function(root, options) { | |
function walkNode(node, item, list) { | |
const enterRet = enter.call(context, node, item, list); | |
if (enterRet === breakWalk) { | |
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; | |
} | |
let enter = noop; | |
let leave = noop; | |
let iterators = iteratorsNatural; | |
let walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list); | |
const context = { | |
break: breakWalk, | |
skip: skipNode, | |
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).sort().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) { | |
let 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) { | |
let found = null; | |
walk(ast, { | |
reverse: true, | |
enter(node, item, list) { | |
if (fn.call(this, node, item, list)) { | |
found = node; | |
return breakWalk; | |
} | |
} | |
}); | |
return found; | |
}; | |
walk.findAll = function(ast, fn) { | |
const found = []; | |
walk(ast, function(node, item, list) { | |
if (fn.call(this, node, item, list)) { | |
found.push(node); | |
} | |
}); | |
return found; | |
}; | |
return walk; | |
}; | |