@optave/codegraph 3.11.2 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +8 -8
  2. package/dist/db/migrations.d.ts.map +1 -1
  3. package/dist/db/migrations.js +7 -0
  4. package/dist/db/migrations.js.map +1 -1
  5. package/dist/domain/analysis/module-map.d.ts +2 -0
  6. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  7. package/dist/domain/analysis/module-map.js +24 -2
  8. package/dist/domain/analysis/module-map.js.map +1 -1
  9. package/dist/domain/graph/builder/call-resolver.d.ts +4 -2
  10. package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
  11. package/dist/domain/graph/builder/call-resolver.js +170 -8
  12. package/dist/domain/graph/builder/call-resolver.js.map +1 -1
  13. package/dist/domain/graph/builder/cha.d.ts +61 -0
  14. package/dist/domain/graph/builder/cha.d.ts.map +1 -0
  15. package/dist/domain/graph/builder/cha.js +143 -0
  16. package/dist/domain/graph/builder/cha.js.map +1 -0
  17. package/dist/domain/graph/builder/context.d.ts +3 -0
  18. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  19. package/dist/domain/graph/builder/context.js +2 -0
  20. package/dist/domain/graph/builder/context.js.map +1 -1
  21. package/dist/domain/graph/builder/helpers.d.ts +17 -1
  22. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  23. package/dist/domain/graph/builder/helpers.js +159 -5
  24. package/dist/domain/graph/builder/helpers.js.map +1 -1
  25. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  26. package/dist/domain/graph/builder/incremental.js +73 -1
  27. package/dist/domain/graph/builder/incremental.js.map +1 -1
  28. package/dist/domain/graph/builder/stages/build-edges.d.ts +2 -0
  29. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  30. package/dist/domain/graph/builder/stages/build-edges.js +926 -26
  31. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  32. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  33. package/dist/domain/graph/builder/stages/detect-changes.js +2 -1
  34. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  35. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
  36. package/dist/domain/graph/builder/stages/native-orchestrator.js +501 -14
  37. package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
  38. package/dist/domain/graph/builder/stages/resolve-imports.d.ts +1 -0
  39. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  40. package/dist/domain/graph/builder/stages/resolve-imports.js +9 -0
  41. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  42. package/dist/domain/graph/journal.js +1 -1
  43. package/dist/domain/graph/journal.js.map +1 -1
  44. package/dist/domain/graph/resolver/points-to.d.ts +53 -0
  45. package/dist/domain/graph/resolver/points-to.d.ts.map +1 -0
  46. package/dist/domain/graph/resolver/points-to.js +213 -0
  47. package/dist/domain/graph/resolver/points-to.js.map +1 -0
  48. package/dist/domain/graph/resolver/ts-resolver.d.ts +9 -0
  49. package/dist/domain/graph/resolver/ts-resolver.d.ts.map +1 -0
  50. package/dist/domain/graph/resolver/ts-resolver.js +476 -0
  51. package/dist/domain/graph/resolver/ts-resolver.js.map +1 -0
  52. package/dist/domain/parser.d.ts +10 -1
  53. package/dist/domain/parser.d.ts.map +1 -1
  54. package/dist/domain/parser.js +39 -7
  55. package/dist/domain/parser.js.map +1 -1
  56. package/dist/domain/wasm-worker-entry.js +25 -0
  57. package/dist/domain/wasm-worker-entry.js.map +1 -1
  58. package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
  59. package/dist/domain/wasm-worker-pool.js +32 -0
  60. package/dist/domain/wasm-worker-pool.js.map +1 -1
  61. package/dist/domain/wasm-worker-protocol.d.ts +14 -1
  62. package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
  63. package/dist/extractors/c.js +3 -3
  64. package/dist/extractors/c.js.map +1 -1
  65. package/dist/extractors/clojure.js +1 -1
  66. package/dist/extractors/clojure.js.map +1 -1
  67. package/dist/extractors/cpp.js +3 -3
  68. package/dist/extractors/cpp.js.map +1 -1
  69. package/dist/extractors/csharp.d.ts.map +1 -1
  70. package/dist/extractors/csharp.js +37 -8
  71. package/dist/extractors/csharp.js.map +1 -1
  72. package/dist/extractors/cuda.js +3 -3
  73. package/dist/extractors/cuda.js.map +1 -1
  74. package/dist/extractors/elixir.js +6 -6
  75. package/dist/extractors/elixir.js.map +1 -1
  76. package/dist/extractors/fsharp.js +1 -1
  77. package/dist/extractors/fsharp.js.map +1 -1
  78. package/dist/extractors/go.js +5 -5
  79. package/dist/extractors/go.js.map +1 -1
  80. package/dist/extractors/haskell.js +1 -1
  81. package/dist/extractors/haskell.js.map +1 -1
  82. package/dist/extractors/java.js +2 -2
  83. package/dist/extractors/java.js.map +1 -1
  84. package/dist/extractors/javascript.d.ts +2 -0
  85. package/dist/extractors/javascript.d.ts.map +1 -1
  86. package/dist/extractors/javascript.js +1674 -64
  87. package/dist/extractors/javascript.js.map +1 -1
  88. package/dist/extractors/kotlin.js +5 -5
  89. package/dist/extractors/kotlin.js.map +1 -1
  90. package/dist/extractors/lua.js +1 -1
  91. package/dist/extractors/lua.js.map +1 -1
  92. package/dist/extractors/objc.js +3 -3
  93. package/dist/extractors/objc.js.map +1 -1
  94. package/dist/extractors/ocaml.js +1 -1
  95. package/dist/extractors/ocaml.js.map +1 -1
  96. package/dist/extractors/php.js +2 -2
  97. package/dist/extractors/php.js.map +1 -1
  98. package/dist/extractors/python.js +7 -7
  99. package/dist/extractors/python.js.map +1 -1
  100. package/dist/extractors/ruby.js +2 -2
  101. package/dist/extractors/ruby.js.map +1 -1
  102. package/dist/extractors/scala.js +1 -1
  103. package/dist/extractors/scala.js.map +1 -1
  104. package/dist/extractors/solidity.js +1 -1
  105. package/dist/extractors/solidity.js.map +1 -1
  106. package/dist/extractors/swift.js +4 -4
  107. package/dist/extractors/swift.js.map +1 -1
  108. package/dist/extractors/zig.js +4 -4
  109. package/dist/extractors/zig.js.map +1 -1
  110. package/dist/infrastructure/config.d.ts +10 -0
  111. package/dist/infrastructure/config.d.ts.map +1 -1
  112. package/dist/infrastructure/config.js +15 -0
  113. package/dist/infrastructure/config.js.map +1 -1
  114. package/dist/infrastructure/native.d.ts +11 -0
  115. package/dist/infrastructure/native.d.ts.map +1 -1
  116. package/dist/infrastructure/native.js +78 -5
  117. package/dist/infrastructure/native.js.map +1 -1
  118. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  119. package/dist/presentation/queries-cli/overview.js +5 -0
  120. package/dist/presentation/queries-cli/overview.js.map +1 -1
  121. package/dist/types.d.ts +184 -0
  122. package/dist/types.d.ts.map +1 -1
  123. package/package.json +7 -7
  124. package/src/db/migrations.ts +7 -0
  125. package/src/domain/analysis/module-map.ts +29 -1
  126. package/src/domain/graph/builder/call-resolver.ts +177 -7
  127. package/src/domain/graph/builder/cha.ts +175 -0
  128. package/src/domain/graph/builder/context.ts +3 -0
  129. package/src/domain/graph/builder/helpers.ts +175 -5
  130. package/src/domain/graph/builder/incremental.ts +79 -1
  131. package/src/domain/graph/builder/stages/build-edges.ts +1128 -24
  132. package/src/domain/graph/builder/stages/detect-changes.ts +3 -1
  133. package/src/domain/graph/builder/stages/native-orchestrator.ts +583 -20
  134. package/src/domain/graph/builder/stages/resolve-imports.ts +14 -0
  135. package/src/domain/graph/journal.ts +1 -1
  136. package/src/domain/graph/resolver/points-to.ts +254 -0
  137. package/src/domain/graph/resolver/ts-resolver.ts +536 -0
  138. package/src/domain/parser.ts +43 -5
  139. package/src/domain/wasm-worker-entry.ts +25 -0
  140. package/src/domain/wasm-worker-pool.ts +21 -0
  141. package/src/domain/wasm-worker-protocol.ts +14 -0
  142. package/src/extractors/c.ts +3 -3
  143. package/src/extractors/clojure.ts +1 -1
  144. package/src/extractors/cpp.ts +3 -3
  145. package/src/extractors/csharp.ts +33 -9
  146. package/src/extractors/cuda.ts +3 -3
  147. package/src/extractors/elixir.ts +6 -6
  148. package/src/extractors/fsharp.ts +1 -1
  149. package/src/extractors/go.ts +5 -5
  150. package/src/extractors/haskell.ts +1 -1
  151. package/src/extractors/java.ts +2 -2
  152. package/src/extractors/javascript.ts +1802 -66
  153. package/src/extractors/kotlin.ts +5 -5
  154. package/src/extractors/lua.ts +1 -1
  155. package/src/extractors/objc.ts +3 -3
  156. package/src/extractors/ocaml.ts +1 -1
  157. package/src/extractors/php.ts +2 -2
  158. package/src/extractors/python.ts +7 -7
  159. package/src/extractors/ruby.ts +2 -2
  160. package/src/extractors/scala.ts +1 -1
  161. package/src/extractors/solidity.ts +1 -1
  162. package/src/extractors/swift.ts +4 -4
  163. package/src/extractors/zig.ts +4 -4
  164. package/src/infrastructure/config.ts +15 -0
  165. package/src/infrastructure/native.ts +87 -5
  166. package/src/presentation/queries-cli/overview.ts +15 -1
  167. package/src/types.ts +194 -0
@@ -169,9 +169,23 @@ async function reparseBarrelFiles(
169
169
  return added;
170
170
  }
171
171
 
172
+ export function resolveBarrelExportCached(
173
+ ctx: PipelineContext,
174
+ barrelPath: string,
175
+ symbolName: string,
176
+ ): string | null {
177
+ const cacheKey = `${barrelPath}|${symbolName}`;
178
+ if (ctx.barrelExportCache.has(cacheKey))
179
+ return ctx.barrelExportCache.get(cacheKey) as string | null;
180
+ const result = resolveBarrelExport(ctx, barrelPath, symbolName);
181
+ ctx.barrelExportCache.set(cacheKey, result);
182
+ return result;
183
+ }
184
+
172
185
  export async function resolveImports(ctx: PipelineContext): Promise<void> {
173
186
  const { fileSymbols, rootDir, aliases, allFiles, isFullBuild } = ctx;
174
187
  const t0 = performance.now();
188
+ ctx.barrelExportCache = new Map();
175
189
 
176
190
  const batchInputs: Array<{ fromFile: string; importSource: string }> = [];
177
191
  for (const [relPath, symbols] of fileSymbols) {
@@ -235,7 +235,7 @@ interface JournalResult {
235
235
  }
236
236
 
237
237
  function parseJournalHeader(firstLine: string | undefined): number | null {
238
- if (!firstLine || !firstLine.startsWith(HEADER_PREFIX)) {
238
+ if (!firstLine?.startsWith(HEADER_PREFIX)) {
239
239
  debug('Journal has malformed or missing header');
240
240
  return null;
241
241
  }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Phase 8.3 — Lightweight field-based points-to analysis for JS/TS.
3
+ *
4
+ * Resolves higher-order function calls where a named variable is an alias for
5
+ * a function that the syntactic extractor cannot connect directly. Common
6
+ * patterns resolved:
7
+ *
8
+ * const fn = handler; arr.map(fn) → edge to handler
9
+ * const fn = obj.method; router.use(fn) → edge to obj.method
10
+ * const m = authMiddleware; app.use(m) → edge to authMiddleware
11
+ *
12
+ * Algorithm: Andersen-style inclusion-based analysis with allocation-site
13
+ * abstraction and fixed-point constraint propagation.
14
+ *
15
+ * Field-based (not field-sensitive): all instances of obj.field are treated as
16
+ * a single abstract location, matching ACG's sweet spot of 99% precision.
17
+ *
18
+ * Scope: intra-module + cross-module via importedNames (the importedNames map
19
+ * that build-edges.ts already builds per file is the cross-module link — if
20
+ * a variable aliases an imported name, resolveCallTargets follows it).
21
+ */
22
+ import type {
23
+ ArrayCallbackBinding,
24
+ ArrayElemBinding,
25
+ FnRefBinding,
26
+ ForOfBinding,
27
+ ObjectPropBinding,
28
+ ObjectRestParamBinding,
29
+ ParamBinding,
30
+ SpreadArgBinding,
31
+ } from '../../../types.js';
32
+
33
+ export type PointsToMap = Map<string, Set<string>>;
34
+
35
+ /**
36
+ * Maximum fixed-point iterations before bailing out (prevents divergence).
37
+ * Mirrors `DEFAULTS.analysis.pointsToMaxIterations` in config.ts.
38
+ * TODO(Phase 8.3): thread config through buildPointsToMap so this can be tuned
39
+ * per-repo via `.codegraphrc.json` (tracked alongside typePropagationDepth).
40
+ */
41
+ const MAX_SOLVER_ITERATIONS = 50;
42
+
43
+ /**
44
+ * Build a points-to map for one file.
45
+ *
46
+ * Seeds concrete function names (locally-defined functions + imported names),
47
+ * then propagates assignments through fixed-point iteration until stable.
48
+ *
49
+ * Each "concrete target" in a pts set is a name that `resolveCallTargets` can
50
+ * look up — either a locally-defined function name (found via byNameAndFile) or
51
+ * an imported name (found via importedNames → byNameAndFile in the source file).
52
+ *
53
+ * @param fnRefBindings - identifier/member-expr bindings from the extractor
54
+ * @param definitionNames - locally-defined callable names in this file
55
+ * @param importedNames - names imported into this file (name → resolved file)
56
+ * @param paramBindings - call-site arg→param bindings (Phase 8.3c)
57
+ * @param definitionParams - per-function ordered parameter names (Phase 8.3c)
58
+ * @param arrayElemBindings - array literal element bindings (Phase 8.3e)
59
+ * @param spreadArgBindings - spread-argument bindings (Phase 8.3e)
60
+ * @param forOfBindings - for-of iteration variable bindings (Phase 8.3e)
61
+ * @param arrayCallbackBindings - Array.from/callback bindings (Phase 8.3e)
62
+ */
63
+ export function buildPointsToMap(
64
+ fnRefBindings: readonly FnRefBinding[],
65
+ definitionNames: ReadonlySet<string>,
66
+ importedNames: ReadonlyMap<string, string>,
67
+ paramBindings?: readonly ParamBinding[],
68
+ definitionParams?: ReadonlyMap<string, readonly string[]>,
69
+ arrayElemBindings?: readonly ArrayElemBinding[],
70
+ spreadArgBindings?: readonly SpreadArgBinding[],
71
+ forOfBindings?: readonly ForOfBinding[],
72
+ arrayCallbackBindings?: readonly ArrayCallbackBinding[],
73
+ objectRestParamBindings?: readonly ObjectRestParamBinding[],
74
+ objectPropBindings?: readonly ObjectPropBinding[],
75
+ ): PointsToMap {
76
+ const pts: PointsToMap = new Map();
77
+
78
+ // Seed: each locally-defined function points to itself.
79
+ for (const name of definitionNames) {
80
+ pts.set(name, new Set([name]));
81
+ }
82
+
83
+ // Seed: each imported name points to itself (importedNames resolves it to
84
+ // the source file when resolveCallTargets is called with that name).
85
+ for (const name of importedNames.keys()) {
86
+ if (!pts.has(name)) pts.set(name, new Set([name]));
87
+ }
88
+
89
+ // Build constraint list: pts(lhs) ⊇ pts(rhsKey).
90
+ // For member expressions (const fn = obj.method), key is "obj.method".
91
+ // These composite keys won't be in pts unless a prior statement seeded them
92
+ // (e.g. handlers.auth = authMiddleware); they produce no flow otherwise,
93
+ // which is safe — no false edges.
94
+ const constraints: Array<{ lhs: string; rhsKey: string }> = fnRefBindings.map((b) => ({
95
+ lhs: b.lhs,
96
+ rhsKey: b.rhsReceiver ? `${b.rhsReceiver}.${b.rhs}` : b.rhs,
97
+ }));
98
+
99
+ // Phase 8.3c: parameter-flow constraints.
100
+ // For each call f(x) at argIndex i where f is locally defined, add
101
+ // constraint: pts(f::paramName_i) ⊇ pts(x). This makes the pts solver
102
+ // inter-procedural within the module so that `fn()` inside `f` resolves
103
+ // to the concrete function passed at each call site.
104
+ //
105
+ // Keys are scoped as "callee::paramName" to prevent name collisions: bare
106
+ // parameter names like `fn`, `cb`, and `callback` appear in many functions
107
+ // within the same file. Without scoping, pts(fn) from runA and runB would
108
+ // merge into a single set, producing spurious call edges. The scoped key is
109
+ // resolved in buildFileCallEdges by combining the enclosing caller's name
110
+ // with the call's name (see callerName::call.name lookup there).
111
+ //
112
+ // Scope: intra-module only (definitionParams contains local defs only).
113
+ if (paramBindings && definitionParams) {
114
+ for (const { callee, argIndex, argName } of paramBindings) {
115
+ const params = definitionParams.get(callee);
116
+ if (!params || argIndex >= params.length) continue;
117
+ const paramName = params[argIndex];
118
+ if (paramName) constraints.push({ lhs: `${callee}::${paramName}`, rhsKey: argName });
119
+ }
120
+ }
121
+
122
+ // Phase 8.3e: array-element bindings — seed concrete elements and wildcard.
123
+ // `arr[0]` etc. are seeded from literal arrays; `arr[*]` collects all elements.
124
+ if (arrayElemBindings && arrayElemBindings.length > 0) {
125
+ for (const { arrayName, index, elemName } of arrayElemBindings) {
126
+ const elemKey = `${arrayName}[${index}]`;
127
+ const wildcardKey = `${arrayName}[*]`;
128
+ // Seed the per-index entry if the elemName is a concrete function.
129
+ if (!pts.has(elemKey)) pts.set(elemKey, new Set());
130
+ pts.get(elemKey)!.add(elemName);
131
+ // Wildcard: array[*] collects all element targets for imprecise spread/for-of.
132
+ constraints.push({ lhs: wildcardKey, rhsKey: elemKey });
133
+ }
134
+ }
135
+
136
+ // Phase 8.3e: spread-argument constraints.
137
+ // f(...arr) → pts[f::param_i] ⊇ pts[arr[i]] for each known element.
138
+ if (spreadArgBindings && spreadArgBindings.length > 0 && definitionParams) {
139
+ // Build a per-array index count from arrayElemBindings for precise per-index constraints.
140
+ const arrayMaxIndex = new Map<string, number>();
141
+ for (const { arrayName, index } of arrayElemBindings ?? []) {
142
+ const cur = arrayMaxIndex.get(arrayName) ?? -1;
143
+ if (index > cur) arrayMaxIndex.set(arrayName, index);
144
+ }
145
+
146
+ for (const { callee, arrayName, startIndex } of spreadArgBindings) {
147
+ const params = definitionParams.get(callee);
148
+ if (!params) continue;
149
+ const maxIdx = arrayMaxIndex.get(arrayName) ?? -1;
150
+ if (maxIdx >= 0) {
151
+ // Precise: per-element constraints.
152
+ for (let i = 0; i <= maxIdx; i++) {
153
+ const paramIdx = startIndex + i;
154
+ if (paramIdx >= params.length) break;
155
+ constraints.push({ lhs: `${callee}::${params[paramIdx]}`, rhsKey: `${arrayName}[${i}]` });
156
+ }
157
+ } else {
158
+ // Unknown array size: all params at/after startIndex get the wildcard.
159
+ for (let j = startIndex; j < params.length; j++) {
160
+ constraints.push({ lhs: `${callee}::${params[j]}`, rhsKey: `${arrayName}[*]` });
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ // Phase 8.3e: for-of iteration constraints.
167
+ // `for (const x of arr)` inside `outer` → pts[outer::x] ⊇ pts[arr[*]]
168
+ if (forOfBindings) {
169
+ for (const { varName, sourceName, enclosingFunc } of forOfBindings) {
170
+ constraints.push({ lhs: `${enclosingFunc}::${varName}`, rhsKey: `${sourceName}[*]` });
171
+ }
172
+ }
173
+
174
+ // Phase 8.3e: Array.from / callback constraints.
175
+ // Array.from(source, cb) → pts[cb::param0] ⊇ pts[source[*]]
176
+ if (arrayCallbackBindings && definitionParams) {
177
+ for (const { sourceName, calleeName } of arrayCallbackBindings) {
178
+ const params = definitionParams.get(calleeName);
179
+ if (!params || params.length === 0) continue;
180
+ constraints.push({ lhs: `${calleeName}::${params[0]}`, rhsKey: `${sourceName}[*]` });
181
+ }
182
+ }
183
+
184
+ // Phase 8.3f: object-rest parameter dispatch.
185
+ // `function f({ ...rest }) {}` + `f(obj)` + `const obj = { prop: fn }` →
186
+ // seed pts["rest.prop"] = {"fn"} so that `rest.prop()` resolves to `fn`.
187
+ if (objectRestParamBindings && objectPropBindings && paramBindings) {
188
+ // Index paramBindings: "callee::argIndex" → argName[] (O(|paramBindings|) build,
189
+ // O(1) lookup — avoids scanning paramBindings for each rest binding).
190
+ const paramByCalleeIdx = new Map<string, string[]>();
191
+ for (const { callee, argIndex, argName } of paramBindings) {
192
+ const k = `${callee}::${argIndex}`;
193
+ const list = paramByCalleeIdx.get(k);
194
+ if (list) list.push(argName);
195
+ else paramByCalleeIdx.set(k, [argName]);
196
+ }
197
+
198
+ // Index objectPropBindings: objectName → {propName, valueName}[]
199
+ const propsByObject = new Map<string, Array<{ propName: string; valueName: string }>>();
200
+ for (const { objectName, propName, valueName } of objectPropBindings) {
201
+ const list = propsByObject.get(objectName);
202
+ if (list) list.push({ propName, valueName });
203
+ else propsByObject.set(objectName, [{ propName, valueName }]);
204
+ }
205
+
206
+ for (const { callee, restName, argIndex } of objectRestParamBindings) {
207
+ const argNames = paramByCalleeIdx.get(`${callee}::${argIndex}`) ?? [];
208
+ for (const argName of argNames) {
209
+ const props = propsByObject.get(argName) ?? [];
210
+ for (const { propName, valueName } of props) {
211
+ if (!definitionNames.has(valueName) && !importedNames.has(valueName)) continue;
212
+ const key = `${restName}.${propName}`;
213
+ if (!pts.has(key)) pts.set(key, new Set());
214
+ pts.get(key)!.add(valueName);
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ if (constraints.length === 0) return pts;
221
+
222
+ // Fixed-point iteration: propagate pts sets until no new information flows.
223
+ for (let iter = 0; iter < MAX_SOLVER_ITERATIONS; iter++) {
224
+ let changed = false;
225
+ for (const { lhs, rhsKey } of constraints) {
226
+ const rhsPts = pts.get(rhsKey);
227
+ if (!rhsPts || rhsPts.size === 0) continue;
228
+ let lhsPts = pts.get(lhs);
229
+ if (!lhsPts) {
230
+ lhsPts = new Set();
231
+ pts.set(lhs, lhsPts);
232
+ }
233
+ const before = lhsPts.size;
234
+ for (const target of rhsPts) lhsPts.add(target);
235
+ if (lhsPts.size !== before) changed = true;
236
+ }
237
+ if (!changed) break;
238
+ }
239
+
240
+ return pts;
241
+ }
242
+
243
+ /**
244
+ * Return the concrete function names that `callName` flows to, excluding
245
+ * itself to prevent circular self-reference edges.
246
+ *
247
+ * Returns an empty array when callName is not in the pts map (i.e., it is
248
+ * not an alias — the caller should fall back to normal resolution failure).
249
+ */
250
+ export function resolveViaPointsTo(callName: string, pts: PointsToMap): string[] {
251
+ const targets = pts.get(callName);
252
+ if (!targets) return [];
253
+ return [...targets].filter((t) => t !== callName);
254
+ }