export const TRAVERSAL_PATH = { ArrayExpression: (node) => node.elements.map(({expression}) => expression), ArrayPattern: ['elements'], AssignmentExpression: ['left', 'right'], AssignmentPatternProperty: ['key'], AwaitExpression: ['argument'], BinaryExpression: ['left', 'right'], BlockStatement: ['stmts'], BooleanLiteral: [], CallExpression: (node) => ([ node.callee, ...node.arguments.map(({expression}) => expression), ]), Computed: ['expression'], ConditionalExpression: ['alternate', 'consequent', 'test'], DoWhileStatement: ['body', 'test'], ExpressionStatement: ['expression'], ForStatement: ['body', 'init', 'test', 'update'], Identifier: [], IfStatement: ['alternate', 'consequent', 'test'], KeyValuePatternProperty: ['key', 'value'], KeyValueProperty: ['key', 'value'], MemberExpression: ['object', 'property'], Module: ['body'], NullLiteral: [], NumericLiteral: [], ObjectExpression: ['properties'], ObjectPattern: ['properties'], OptionalChainingExpression: ['base'], ParenthesisExpression: ['expression'], RegExpLiteral: [], StringLiteral: [], UnaryExpression: ['argument'], UpdateExpression: ['argument'], VariableDeclaration: ['declarations'], VariableDeclarator: ['id', 'init'], WhileStatement: ['body', 'test'], }; export default function traverse(node, visitor) { /* v8 ignore next 3 */ if (!(node.type in TRAVERSAL_PATH)) { throw new Error(`node type ${node.type} not traversable`); } visitor(node, 'enter'); const path = TRAVERSAL_PATH[node.type]; let children; if (path instanceof Function) { children = path(node); } else if (Array.isArray(path)) { children = []; for (const key of path) { children.push(...(Array.isArray(node[key]) ? node[key] : [node[key]])); } } for (const child of children) { if (child) { traverse(child, visitor); } } visitor(node, 'exit'); }