fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 160 lines 4.2 kB view raw
1import { isNodeRef, isSymbolRef } from '../guards'; 2import type { INode, NodeRelationship } from '../nodes/node'; 3import { fromRef, isRef, ref } from '../refs/refs'; 4import type { Ref } from '../refs/types'; 5import type { Symbol } from '../symbols/symbol'; 6import type { NameScopes, Scope } from './scope'; 7import { createScope } from './scope'; 8import type { IAnalysisContext, Input } from './types'; 9 10export class AnalysisContext implements IAnalysisContext { 11 /** 12 * Stack of parent nodes during analysis. 13 * 14 * The top of the stack is the current semantic container. 15 */ 16 private _parentStack: Array<INode> = []; 17 18 scope: Scope; 19 scopes: Scope = createScope(); 20 symbol?: Symbol; 21 22 constructor(node: INode) { 23 this._parentStack.push(node); 24 this.scope = this.scopes; 25 this.symbol = node.symbol; 26 } 27 28 /** 29 * Get the current semantic parent (top of stack). 30 */ 31 get currentParent(): INode | undefined { 32 return this._parentStack[this._parentStack.length - 1]; 33 } 34 35 /** 36 * Register a child node under the current parent. 37 */ 38 addChild(child: INode, relationship: NodeRelationship = 'container'): void { 39 const parent = this.currentParent; 40 if (!parent) return; 41 42 if (!parent.structuralChildren) { 43 parent.structuralChildren = new Map(); 44 } 45 parent.structuralChildren.set(child, relationship); 46 47 if (!child.structuralParents) { 48 child.structuralParents = new Map(); 49 } 50 child.structuralParents.set(parent, relationship); 51 } 52 53 addDependency(symbol: Ref<Symbol>): void { 54 if (this.symbol !== fromRef(symbol)) { 55 this.scope.symbols.push(symbol); 56 } 57 } 58 59 analyze(input: Input): void { 60 const value = isRef(input) ? input : ref(input); 61 if (isSymbolRef(value)) { 62 const symbol = fromRef(value); 63 // avoid adding self as child 64 if (symbol.node && this.currentParent !== symbol.node) { 65 this.addChild(symbol.node, 'reference'); 66 } 67 this.addDependency(value); 68 } else if (isNodeRef(value)) { 69 const node = fromRef(value); 70 this.addChild(node, 'container'); 71 this.pushParent(node); 72 node.analyze(this); 73 this.popParent(); 74 } 75 } 76 77 localNames(scope: Scope): NameScopes { 78 const names: NameScopes = new Map(); 79 for (const [name, kinds] of scope.localNames) { 80 names.set(name, new Set(kinds)); 81 } 82 if (scope.parent) { 83 const parentNames = this.localNames(scope.parent); 84 for (const [name, kinds] of parentNames) { 85 if (!names.has(name)) { 86 names.set(name, kinds); 87 } else { 88 const existingKinds = names.get(name)!; 89 for (const kind of kinds) { 90 existingKinds.add(kind); 91 } 92 } 93 } 94 } 95 return names; 96 } 97 98 /** 99 * Pop the current semantic parent. 100 * Call this when exiting a container node. 101 */ 102 popParent(): void { 103 this._parentStack.pop(); 104 } 105 106 popScope(): void { 107 this.scope = this.scope.parent ?? this.scope; 108 } 109 110 /** 111 * Push a node as the current semantic parent. 112 */ 113 pushParent(node: INode): void { 114 this._parentStack.push(node); 115 } 116 117 pushScope(): void { 118 const scope = createScope({ parent: this.scope }); 119 this.scope.children.push(scope); 120 this.scope = scope; 121 } 122 123 walkScopes( 124 callback: (symbol: Ref<Symbol>, scope: Scope) => void, 125 scope: Scope = this.scopes, 126 ): void { 127 this.scope = scope; 128 for (const symbol of scope.symbols) { 129 callback(symbol, scope); 130 } 131 for (const child of scope.children) { 132 scope = child; 133 this.walkScopes(callback, scope); 134 } 135 this.scope = this.scopes; 136 } 137} 138 139export class Analyzer { 140 private nodeCache = new WeakMap<INode, AnalysisContext>(); 141 142 analyzeNode(node: INode): AnalysisContext { 143 const cached = this.nodeCache.get(node); 144 if (cached) return cached; 145 146 node.root = true; 147 const ctx = new AnalysisContext(node); 148 node.analyze(ctx); 149 150 this.nodeCache.set(node, ctx); 151 return ctx; 152 } 153 154 analyze(nodes: Iterable<INode>, callback?: (ctx: AnalysisContext, node: INode) => void): void { 155 for (const node of nodes) { 156 const ctx = this.analyzeNode(node); 157 callback?.(ctx, node); 158 } 159 } 160}