fork of hey-api/openapi-ts because I need some additional things
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}