type Keyof = T extends unknown ? keyof T : never; export class ASTWalkerContext { private _defaultProp: Keyof = 'children' as unknown as Keyof; private _globalContext: Record = Object.create(null); private readonly _stack: { node: TNode; prop: Keyof; context: Record; }[] = []; _skip = false; _skipChildrenNum = 0; setDefaultProp = (parentProp: Keyof) => { this._defaultProp = parentProp; }; get stack() { return this._stack; } private current() { return this._stack[this._stack.length - 1]; } cleanGlobalContextStack(key: string) { if (this._globalContext[key] instanceof Array) { this._globalContext[key] = []; } } closeNode() { const ele = this._stack.pop(); if (!ele) return this; const parent = this._stack.pop(); if (!parent) { this._stack.push(ele); return this; } if (parent.node[ele.prop] instanceof Array) { (parent.node[ele.prop] as Array).push(ele.node); } this._stack.push(parent); return this; } currentNode() { return this.current()?.node; } getGlobalContext(key: string) { return this._globalContext[key]; } getGlobalContextStack(key: string) { const stack = this._globalContext[key]; if (stack instanceof Array) { return stack as StackElement[]; } else { return [] as StackElement[]; } } getNodeContext(key: string) { return this.current().context[key]; } getPreviousNodeContext(key: string) { return this._stack[this._stack.length - 2]?.context[key]; } openNode(node: TNode, parentProp?: Keyof) { this._stack.push({ node, prop: parentProp ?? this._defaultProp, context: Object.create(null), }); return this; } previousNode() { return this._stack[this._stack.length - 2]?.node; } pushGlobalContextStack(key: string, value: StackElement) { const stack = this._globalContext[key]; if (stack instanceof Array) { stack.push(value); } else { this._globalContext[key] = [value]; } } setGlobalContext(key: string, value: unknown) { this._globalContext[key] = value; return this; } setGlobalContextStack(key: string, value: StackElement[]) { this._globalContext[key] = value; } setNodeContext(key: string, value: unknown) { this._stack[this._stack.length - 1].context[key] = value; return this; } skipAllChildren() { this._skip = true; } skipChildren(num = 1) { this._skipChildrenNum = num; } }