tenko
Version:
A "pixel perfect" 100% spec compliant ES2021 JavaScript parser written in JS.
656 lines (637 loc) • 25.7 kB
JavaScript
// Walk the entire AST
// The walker is tested in the test runner to assert it visits every node, and that it visits each once and only once.
// You can call the walker with a `before` or `after` callback. The `before` callback can return `false` to prevent
// visiting this node. The `after` callback can return `true` to re-visit this node. A re-visit will trigger the before
// and after again. In case it matters; the last return value of the visit of the node is propagated back in the walker.
//
// Api (before and after are the same api):
// ```
// walker(ast, function callback(node, parent, prop, index, afterVisit, revisit) {}, callback, $);
// ```
// - node: the node about to or that has been visited
// - parent: parent node that contained this node (or contains the array containing this node)
// - prop: parent[prop]===node or parent[prop][index]===node (if an array contains this node)
// - index: if this node was found in an array, this will be the index of node in that array. Otherwise undefined
// - afterVisit: is this the call before or after visiting node?
// - revisit: how often has this node been re-visited? Starts at 0 (before and after), increments for each re-visit.
// - $: manually visit a sub-tree of the AST. You're on your own here. Certain nvariants may seize to hold.
import {
$$A_61, $$A_UC_41,
$$B_62, $$B_UC_42, $$C_UC_43,
$$D_64, $$D_UC_44,
$$E_65, $$E_UC_45, $$G_67, $$H_68,
$$I_69,
$$I_UC_49,
$$L_6C, $$L_UC_4C,
$$M_6D, $$N_UC_4E,
$$O_6F, $$O_UC_4F,
$$P_70,
$$R_72,
$$S_73, $$S_UC_53, $$T_74,
$$U_75, $$W_77, $$X_78
} from "../charcodes.mjs";
function assert(a, b) {
// This is an assert that can be dropped for a build... It confirms hashing assumptions
// (Will also be an invaluable tool when adding a new node type ;)
if (a !== b) throw new Error('Expected `' + b + '`, got `' + a + '`');
}
function ArrayExpression(node, parent, prop, index) {
assert(node.type, 'ArrayExpression');
node.elements.forEach((n, i) => n !== null && $(n, node, 'elements', i));
}
function ArrayPattern(node, parent, prop, index) {
assert(node.type, 'ArrayPattern');
node.elements.forEach((n, i) => n !== null && $(n, node, 'elements', i));
}
function ArrowFunctionExpression(node, parent, prop, index) {
assert(node.type, 'ArrowFunctionExpression');
node.params.forEach((e, i) => $(e, node, 'params', i));
$(node.body, node, 'body');
}
function AssignmentExpression(node, parent, prop, index) {
assert(node.type, 'AssignmentExpression');
$(node.left, node, 'left');
$(node.right, node, 'right');
}
function AssignmentPattern(node, parent, prop, index) {
assert(node.type, 'AssignmentPattern');
$(node.left, node, 'left');
$(node.right, node, 'right');
}
function AwaitExpression(node, parent, prop, index) {
assert(node.type, 'AwaitExpression');
$(node.argument, node, 'argument');
}
function BigIntLiteral(node, parent, prop, index) {
assert(node.type, 'BigIntLiteral');
}
function BinaryExpression(node, parent, prop, index) {
assert(node.type, 'BinaryExpression');
$(node.left, node, 'left');
$(node.right, node, 'right');
}
function BlockStatement(node, parent, prop, index) {
assert(node.type, 'BlockStatement');
node.body.forEach((e, i) => $(e, node, 'body', i));
}
function BooleanLiteral(node, parent, prop, index) {
assert(node.type, 'BooleanLiteral');
}
function BreakStatement(node, parent, prop, index) {
assert(node.type, 'BreakStatement');
if (node.label) $(node.label, node, 'label');
}
function CallExpression(node, parent, prop, index) {
assert(node.type, 'CallExpression');
node.arguments.forEach((e, i) => $(e, node, 'arguments', i));
$(node.callee, node, 'callee');
}
function CatchClause(node, parent, prop, index) {
assert(node.type, 'CatchClause');
if (node.param) $(node.param, node, 'param');
$(node.body, node, 'body');
}
function ClassBody(node, parent, prop, index) {
assert(node.type, 'ClassBody');
node.body.forEach((e, i) => $(e, node, 'body', i));
}
function ClassDeclaration(node, parent, prop, index) {
assert(node.type, 'ClassDeclaration');
if (node.id) $(node.id, node, 'id'); // Optional for default exports
if (node.superClass) $(node.superClass, node, 'superClass');
$(node.body, node, 'body');
}
function ClassExpression(node, parent, prop, index) {
assert(node.type, 'ClassExpression');
if (node.id) $(node.id, node, 'id');
if (node.superClass) $(node.superClass, node, 'superClass')
$(node.body, node, 'body');
}
function ClassMethod(node, parent, prop, index) {
assert(node.type, 'ClassMethod');
if (node.value === undefined) {
assert('value' in node, false);
// Babel does not have .value and merges the method node with the function node, different from the estree spec
$(node.key, node, 'key');
node.params.forEach((e, i) => $(e, node, 'params', i));
$(node.body, node, 'body');
} else {
$(node.value, node, 'value');
$(node.body, node, 'body');
}
}
function ChainExpression(node, parent, prop, index) {
assert(node.type, 'ChainExpression');
$(node.expression, node, 'expression');
}
function CommentBlock(node, parent, prop, index) {
assert(node.type, 'CommentBlock');
}
function CommentLine(node, parent, prop, index) {
assert(node.type, 'CommentLine');
}
function ConditionalExpression(node, parent, prop, index) {
assert(node.type, 'ConditionalExpression');
$(node.test, node, 'test');
$(node.consequent, node, 'consequent');
$(node.alternate, node, 'alternate');
}
function ContinueStatement(node, parent, prop, index) {
assert(node.type, 'ContinueStatement');
if (node.label) $(node.label, node, 'label');
}
function DebuggerStatement(node, parent, prop, index) {
assert(node.type, 'DebuggerStatement');
}
function Directive(node, parent, prop, index) {
assert(node.type, 'Directive');
$(node.value, node, 'value');
}
function DirectiveLiteral(node, parent, prop, index) {
assert(node.type, 'DirectiveLiteral');
}
function DoWhileStatement(node, parent, prop, index) {
assert(node.type, 'DoWhileStatement');
$(node.body, node, 'body');
$(node.test, node, 'test');
}
function EmptyStatement(node, parent, prop, index) {
assert(node.type, 'EmptyStatement');
}
function ExportAllDeclaration(node, parent, prop, index) {
assert(node.type, 'ExportAllDeclaration');
$(node.source, node, 'source');
}
function ExportDefaultDeclaration(node, parent, prop, index) {
assert(node.type, 'ExportDefaultDeclaration');
$(node.declaration, node, 'declaration');
}
function ExportNamespaceSpecifier(node, parent, prop, index) {
assert(node.type, 'ExportNamespaceSpecifier');
$(node.exported, node, 'exported');
}
function ExportNamedDeclaration(node, parent, prop, index) {
assert(node.type, 'ExportNamedDeclaration');
if (node.specifiers) node.specifiers.forEach((e, i) => $(e, node, 'specifiers', i));
if (node.declaration) $(node.declaration, node, 'declaration');
if (node.source) $(node.source, node, 'source');
}
function ExportSpecifier(node, parent, prop, index) {
assert(node.type, 'ExportSpecifier');
$(node.local, node, 'local');
$(node.exported, node, 'exported');
}
function ExpressionStatement(node, parent, prop, index) {
assert(node.type, 'ExpressionStatement');
$(node.expression, node, 'expression');
}
function ForInStatement(node, parent, prop, index) {
assert(node.type, 'ForInStatement');
$(node.left, node, 'left', undefined, true);
$(node.right, node, 'right');
$(node.body, node, 'body');
}
function ForOfStatement(node, parent, prop, index) {
assert(node.type, 'ForOfStatement');
$(node.left, node, 'left', undefined, true);
$(node.right, node, 'right');
$(node.body, node, 'body');
}
function ForStatement(node, parent, prop, index) {
assert(node.type, 'ForStatement');
if (node.init) $(node.init, node, 'init', undefined, true)
if (node.test) $(node.test, node, 'test')
if (node.update) $(node.update, node, 'update')
$(node.body, node, 'body');
}
function FunctionDeclaration(node, parent, prop, index) {
assert(node.type, 'FunctionDeclaration');
if (node.id) $(node.id, node, 'id'); // Note: export default may cause this id to be undefined
node.params.forEach((e, i) => $(e, node, 'params', i));
$(node.body, node, 'body'); // TODO: may have to clarify that this is the block of a func decl...
}
function FunctionExpression(node, parent, prop, index) {
assert(node.type, 'FunctionExpression');
if (node.id) $(node.id, node, 'id');
node.params.forEach((e, i) => $(e, node, 'params', i));
$(node.body, node, 'body'); // TODO: may have to clarify that this is the block of a func expr...
}
function Identifier(node, parent, prop, index) {
assert(node.type, 'Identifier');
}
function IfStatement(node, parent, prop, index) {
assert(node.type, 'IfStatement');
$(node.test, node, 'test');
$(node.consequent, node, 'consequent');
if (node.alternate) $(node.alternate, node, 'alternate');
}
function Import(node, parent, prop, index) {
assert(node.type, 'Import');
}
function ImportDeclaration(node, parent, prop, index) {
assert(node.type, 'ImportDeclaration');
node.specifiers.forEach((e, i) => $(e, node, 'specifiers', i));
if (node.source) $(node.source, node, 'source');
}
function ImportDefaultSpecifier(node, parent, prop, index) {
assert(node.type, 'ImportDefaultSpecifier');
$(node.local, node, 'local');
}
function ImportExpression(node, parent, prop, index) {
assert(node.type, 'ImportExpression');
node.arguments.forEach((e, i) => $(e, node, 'arguments', i));
}
function ImportNamespaceSpecifier(node, parent, prop, index) {
assert(node.type, 'ImportNamespaceSpecifier');
$(node.local, node, 'local');
}
function ImportSpecifier(node, parent, prop, index) {
assert(node.type, 'ImportSpecifier');
$(node.imported, node, 'imported')
if (node.local) $(node.local, node, 'local');
}
function LabeledStatement(node, parent, prop, index) {
assert(node.type, 'LabeledStatement');
$(node.label, node, 'label');
$(node.body, node, 'body');
}
function Literal(node, parent, prop, index) {
assert(node.type, 'Literal');
switch (typeof node.value) {
case 'boolean':
case 'number':
case 'string':
case 'object': // regex
return
}
throw new Error('fixme; literal type');
}
function LogicalExpression(node, parent, prop, index) {
assert(node.type, 'LogicalExpression');
$(node.left, node, 'left');
$(node.right, node, 'right');
}
function MemberExpression(node, parent, prop, index) {
assert(node.type, 'MemberExpression');
$(node.object, node, 'object');
$(node.property, node, 'property');
}
function MetaProperty(node, parent, prop, index) {
assert(node.type, 'MetaProperty');
$(node.meta, node, 'meta');
$(node.property, node, 'property');
}
function MethodDefinition(node, parent, prop, index) {
assert(node.type, 'MethodDefinition');
$(node.key, node, 'key');
$(node.value, node, 'value');
}
function NewExpression(node, parent, prop, index) {
assert(node.type, 'NewExpression');
$(node.callee, node, 'callee')
node.arguments.forEach((e, i) => $(e, node, 'arguments', i));
}
function NullLiteral(node, parent, prop, index) {
assert(node.type, 'NullLiteral');
}
function NumericLiteral(node, parent, prop, index) {
assert(node.type, 'NumericLiteral');
}
function ObjectExpression(node, parent, prop, index) {
assert(node.type, 'ObjectExpression');
node.properties.forEach((e, i) => $(e, node, 'properties', i));
}
function ObjectMethod(node, parent, prop, index) {
assert(node.type, 'ObjectMethod');
if (node.value === undefined) {
assert('value' in node, false);
// Babel does not have .value and merges the method node with the function node, different from the estree spec
$(node.key, node, 'key');
node.params.forEach((e, i) => $(e, node, 'params', i));
$(node.body, node, 'body');
} else {
$(node.value, node, 'value'); // TODO: clarify that this is coming from 'ObjectMethod'
}
}
function ObjectPattern(node, parent, prop, index) {
assert(node.type, 'ObjectPattern');
node.properties.forEach((e, i) => $(e, node, 'properties', i));
}
function ObjectProperty(node, parent, prop, index) {
assert(node.type, 'ObjectProperty');
if (node.body) {
// Babel
// TODO: aren't there any other properties to visit?
node.body.forEach((e, i) => $(e, node, 'body', i));
} else {
Property(node, parent, prop, index);
}
}
function Program(node, parent, prop, index) {
assert(node.type, 'Program');
node.body.forEach((e, i) => $(e, node, 'body', i));
}
function Property(node, parent, prop, index) {
assert(node.type === 'Property' || node.type === 'ObjectProperty', true);
$(node.key, node, 'key');
$(node.value, node, 'value');
}
function RegExpLiteral(node, parent, prop, index) {
assert(node.type, 'RegExpLiteral');
}
function RestElement(node, parent, prop, index) {
assert(node.type, 'RestElement');
$(node.argument, node, 'argument');
}
function ReturnStatement(node, parent, prop, index) {
assert(node.type, 'ReturnStatement');
if (node.argument) $(node.argument, node, 'argument');
}
function SequenceExpression(node, parent, prop, index) {
assert(node.type, 'SequenceExpression');
node.expressions.forEach((e, i) => $(e, node, 'expressions', i));
}
function SpreadElement(node, parent, prop, index) {
assert(node.type, 'SpreadElement');
$(node.argument, node, 'argument');
}
function StringLiteral(node, parent, prop, index) {
assert(node.type, 'StringLiteral');
}
function Super(node, parent, prop, index) {
assert(node.type, 'Super');
}
function SwitchCase(node, parent, prop, index) {
assert(node.type, 'SwitchCase');
if (node.test) $(node.test, node, 'test');
node.consequent.forEach((e, i) => $(e, node, 'consequent', i));
}
function SwitchStatement(node, parent, prop, index) {
assert(node.type, 'SwitchStatement');
$(node.discriminant, node, 'discriminant');
node.cases.forEach((e, i) => $(e, node, 'cases', i));
}
function TaggedTemplateExpression(node, parent, prop, index) {
assert(node.type, 'TaggedTemplateExpression');
$(node.tag, node, 'tag');
$(node.quasi, node, 'quasi');
}
function TemplateElement(node, parent, prop, index) {
assert(node.type, 'TemplateElement');
}
function TemplateLiteral(node, parent, prop, index) {
assert(node.type, 'TemplateLiteral');
$(node.quasis[0], node, 'quasis', 0);
node.expressions.forEach((e, i) => {
$(e, node, 'expressions', i);
// The number of quasi's (text part of a template) is always one more than the number of expressions, even if empty
// So start with the first quasi, then after every expression also visit the quasi that must follow it
// This basically zips it: quasi [expression quasi [expression quasi [...]]]
$(node.quasis[i + 1], node, 'quasis', i + 1);
});
}
function ThisExpression(node, parent, prop, index) {
assert(node.type, 'ThisExpression');
}
function ThrowStatement(node, parent, prop, index) {
assert(node.type, 'ThrowStatement');
$(node.argument, node, 'argument');
}
function TryStatement(node, parent, prop, index) {
assert(node.type, 'TryStatement');
$(node.block, node, 'block');
if (node.handler) $(node.handler, node, 'handler');
if (node.finalizer) $(node.finalizer, node, 'finalizer');
}
function UnaryExpression(node, parent, prop, index) {
assert(node.type, 'UnaryExpression');
$(node.argument, node, 'argument');
}
function UpdateExpression(node, parent, prop, index) {
assert(node.type, 'UpdateExpression');
$(node.argument, node, 'argument');
}
function VariableDeclaration(node, parent, prop, index) {
assert(node.type, 'VariableDeclaration');
node.declarations.forEach((e, i) => $(e, node, 'declarations', i));
}
function VariableDeclarator(node, parent, prop, index) {
assert(node.type, 'VariableDeclarator');
$(node.id, node, 'id');
if (node.init) $(node.init, node, 'init');
}
function WhileStatement(node, parent, prop, index) {
assert(node.type, 'WhileStatement');
$(node.test, node, 'test');
$(node.body, node, 'body');
}
function WithStatement(node, parent, prop, index) {
assert(node.type, 'WithStatement');
$(node.object, node, 'object');
$(node.body, node, 'body');
}
function YieldExpression(node, parent, prop, index) {
assert(node.type, 'YieldExpression');
if (node.argument) $(node.argument, node, 'argument');
}
let jumpTable = [
(node, parent, prop, index, type, c) => {
if (c === $$I_69) return Directive(node, parent, prop, index);
if (c === $$X_78) {
c = type.charCodeAt(6);
if (c === $$D_UC_44) return ExportDefaultDeclaration(node, parent, prop, index);
return ExportNamespaceSpecifier(node, parent, prop, index);
}
return UpdateExpression(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(3);
if (c === $$L_6C) return BooleanLiteral(node, parent, prop, index);
if (c === $$I_UC_49) return ForInStatement(node, parent, prop, index);
if (c === $$O_UC_4F) return ForOfStatement(node, parent, prop, index);
return UnaryExpression(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(0);
if (c === $$A_UC_41) return AssignmentPattern(node, parent, prop, index);
if (c === $$B_UC_42) return BlockStatement(node, parent, prop, index);
return ImportSpecifier(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(2);
if (c === $$A_61) return ClassExpression(node, parent, prop, index);
if (c === $$M_6D) return CommentBlock(node, parent, prop, index);
if (c === $$P_70) return EmptyStatement(node, parent, prop, index);
return ForStatement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(2);
if (c === $$G_67) return BigIntLiteral(node, parent, prop, index);
if (c === $$M_6D) return CommentLine(node, parent, prop, index);
return WithStatement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$R_72) return ArrowFunctionExpression(node, parent, prop, index);
return ClassBody(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(4);
if (c === $$T_74) return FunctionDeclaration(node, parent, prop, index);
if (c === $$E_UC_45) return ThisExpression(node, parent, prop, index);
if (c === $$W_77) return ThrowStatement(node, parent, prop, index);
if (c === $$E_65) return WhileStatement(node, parent, prop, index);
return YieldExpression(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$S_73) return AssignmentExpression(node, parent, prop, index);
if (c === $$L_6C) return ClassMethod(node, parent, prop, index);
if (c === $$H_68) return ChainExpression(node, parent, prop, index);
return FunctionExpression(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(0);
if (c === $$N_UC_4E) return NewExpression(node, parent, prop, index);
return RegExpLiteral(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
return MetaProperty(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(8);
if (c === $$U_75) return CatchClause(node, parent, prop, index);
if (c === $$A_61) return ReturnStatement(node, parent, prop, index);
if (c === $$E_UC_45) return TemplateElement(node, parent, prop, index);
return TemplateLiteral(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$X_78) return ExpressionStatement(node, parent, prop, index);
return Import(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$X_78) return ExportAllDeclaration(node, parent, prop, index);
return ObjectProperty(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
return IfStatement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$X_78) return ExportNamedDeclaration(node, parent, prop, index);
if (c === $$D_64) return Identifier(node, parent, prop, index);
if (c === $$I_69) return Literal(node, parent, prop, index);
if (c === $$B_62) return ObjectMethod(node, parent, prop, index);
return RestElement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$A_61) return CallExpression(node, parent, prop, index);
return ObjectPattern(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
return Super(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
return LabeledStatement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$B_62) return ObjectExpression(node, parent, prop, index);
return VariableDeclaration(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
return VariableDeclarator(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
return DebuggerStatement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(2);
if (c === $$P_70) return ImportNamespaceSpecifier(node, parent, prop, index);
if (c === $$M_6D) return MemberExpression(node, parent, prop, index);
if (c === $$T_74) return MethodDefinition(node, parent, prop, index);
return Program(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
// (nothing)
},
(node, parent, prop, index, type, c) => {
if (c === $$X_78) return ExportSpecifier(node, parent, prop, index);
return SequenceExpression(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(0);
if (c === $$A_UC_41) return AwaitExpression(node, parent, prop, index);
return SwitchStatement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(0);
if (c === $$B_UC_42) return BinaryExpression(node, parent, prop, index);
if (c === $$D_UC_44) return DirectiveLiteral(node, parent, prop, index);
if (c === $$S_UC_53) return StringLiteral(node, parent, prop, index);
return TaggedTemplateExpression(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$O_6F) return ConditionalExpression(node, parent, prop, index);
return Property(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$M_6D) return ImportDefaultSpecifier(node, parent, prop, index);
return NumericLiteral(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$R_72) return BreakStatement(node, parent, prop, index);
if (c === $$L_6C) return ClassDeclaration(node, parent, prop, index);
return ImportDeclaration(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
if (c === $$R_72) return ArrayExpression(node, parent, prop, index);
if (c === $$M_6D) return ImportExpression(node, parent, prop, index);
if (c === $$P_70) return SpreadElement(node, parent, prop, index);
return SwitchCase(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
c = type.charCodeAt(0);
if (c === $$A_UC_41) return ArrayPattern(node, parent, prop, index);
if (c === $$C_UC_43) return ContinueStatement(node, parent, prop, index);
if (c === $$L_UC_4C) return LogicalExpression(node, parent, prop, index);
if (c === $$N_UC_4E) return NullLiteral(node, parent, prop, index);
return TryStatement(node, parent, prop, index);
},
(node, parent, prop, index, type, c) => {
return DoWhileStatement(node, parent, prop, index);
},
];
function _$(callbackBefore, callbackAfter, node, parentNode, parentProp, parentIndex, revisit = 0) {
// This is a walker that was (manually) built using a simple hash, as follows:
// Input file is a text file with every node name, one per line
// Get distribution
// var x=1; require('fs').readFileSync('nodes.txt', 'utf8').split('\n').filter(Boolean).reduce((map, s) => { let p = s.length^s.charCodeAt(x)-96; if (!map[p]) map[p] = 0; ++map[p]; return map }, {})
// Get node names per bucket
// var x=6; var obj=require('fs').readFileSync('nodes.txt', 'utf8').split('\n').filter(Boolean).reduce((map, s) => { let p = s.length^s.charCodeAt(s.length-x)-96; if (!map[p]) map[p] = []; map[p].push(s); return map }, {});
if (callbackBefore) {
let f = callbackBefore(node, parentNode, parentProp, parentIndex, false, revisit, $)
if (f === false) {
return;
}
}
// This breaks down the 83 node names into an almost perfect 31 case hash, within 5bit, where each bucket has at most 5 elements
let type = node.type;
let c = type.charCodeAt(1); // We can use the second character as a second hash in some of these :)
let hash = type.length ^ c - 96;
let r = jumpTable[hash](node, parentNode, parentProp, parentIndex, type, c);
if (callbackAfter) {
let a = callbackAfter(node, parentNode, parentProp, parentIndex, true, revisit, $)
if (a === true) {
// Resolve node again because it may have changed
let p = parentNode[parentProp];
let newNode = (Array.isArray(p)) ? p[parentIndex] : p;
return _$(callbackBefore, callbackAfter, newNode, parentNode, parentProp, parentIndex, ++revisit);
}
}
return r;
}
let $;
function walker(ast, callbackBefore, callbackAfter) {
$ = _$.bind(undefined, callbackBefore, callbackAfter);
return $(ast);
}
export {
walker,
};