1import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/types'; 2import { visitorKeys, VisitorKeys } from '@typescript-eslint/visitor-keys'; 3 4interface VisitorOptions { 5 childVisitorKeys?: VisitorKeys | null; 6} 7 8function isObject(obj: unknown): obj is Record<string, unknown> { 9 return typeof obj === 'object' && obj != null; 10} 11function isNode(node: unknown): node is TSESTree.Node { 12 return isObject(node) && typeof node.type === 'string'; 13} 14 15type NodeVisitor = { 16 [K in AST_NODE_TYPES]?: (node: TSESTree.Node) => void; 17}; 18 19abstract class VisitorBase { 20 readonly #childVisitorKeys: VisitorKeys; 21 constructor(options: VisitorOptions) { 22 this.#childVisitorKeys = options.childVisitorKeys ?? visitorKeys; 23 } 24 25 /** 26 * Default method for visiting children. 27 * @param node the node whose children should be visited 28 * @param exclude a list of keys to not visit 29 */ 30 visitChildren<T extends TSESTree.Node>( 31 node: T | null | undefined, 32 excludeArr?: (keyof T)[], 33 ): void { 34 if (node == null || node.type == null) { 35 return; 36 } 37 38 const exclude = new Set(excludeArr) as Set<string>; 39 const children = this.#childVisitorKeys[node.type] ?? Object.keys(node); 40 for (const key of children) { 41 if (exclude.has(key)) { 42 continue; 43 } 44 45 const child = node[key as keyof TSESTree.Node] as unknown; 46 if (!child) { 47 continue; 48 } 49 50 if (Array.isArray(child)) { 51 for (const subChild of child) { 52 if (isNode(subChild)) { 53 this.visit(subChild); 54 } 55 } 56 } else if (isNode(child)) { 57 this.visit(child); 58 } 59 } 60 } 61 62 /** 63 * Dispatching node. 64 */ 65 visit(node: TSESTree.Node | null | undefined): void { 66 if (node == null || node.type == null) { 67 return; 68 } 69 70 const visitor = (this as NodeVisitor)[node.type]; 71 if (visitor) { 72 return visitor.call(this, node); 73 } 74 75 this.visitChildren(node); 76 } 77} 78 79export { VisitorBase, VisitorOptions, VisitorKeys }; 80