import type { Node } from 'oxc-parser' /** * Cache of keys that hold child nodes (objects/arrays) per AST node type. * Since oxc-parser produces AST via JSON.parse, every instance of a given * node type has the same set of keys, so we only need to discover them once. */ const childKeysCache = new Map>() function getChildKeys(node: Node): Array { let keys = childKeysCache.get(node.type) if (keys) return keys keys = [] for (const key in node) { if (key === 'type' || key === 'start' || key === 'end') continue // typeof null === 'object', so nullable node fields get cached too if (typeof (node as any)[key] === 'object') { keys.push(key) } } childKeysCache.set(node.type, keys) return keys } /** * Iterate over the direct child nodes of an AST node. * Uses a per-type cache of which keys hold child nodes to avoid * allocating Object.entries() arrays on every call. */ export function forEachChild(node: Node, callback: (child: Node) => void) { const keys = getChildKeys(node) for (const key of keys) { const value = (node as any)[key] if (value === null) continue if (Array.isArray(value)) { for (const item of value) { if (typeof item === 'object' && item !== null && 'type' in item) { callback(item) } } } else if ('type' in value) { callback(value as Node) } } } /** * Recursively walk AST nodes, calling `visitor` for each node with a `type`. */ export function walk( node: Node, visitor: (node: Node, parentNode?: Node) => void, parentNode?: Node, ) { visitor(node, parentNode) forEachChild(node, (child) => walk(child, visitor, node)) }