@optave/codegraph 3.11.2 → 3.13.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 (236) hide show
  1. package/README.md +73 -37
  2. package/dist/cli/commands/audit.d.ts.map +1 -1
  3. package/dist/cli/commands/audit.js +2 -1
  4. package/dist/cli/commands/audit.js.map +1 -1
  5. package/dist/cli/commands/batch.d.ts.map +1 -1
  6. package/dist/cli/commands/batch.js +1 -0
  7. package/dist/cli/commands/batch.js.map +1 -1
  8. package/dist/cli/commands/build.d.ts.map +1 -1
  9. package/dist/cli/commands/build.js +6 -1
  10. package/dist/cli/commands/build.js.map +1 -1
  11. package/dist/cli/commands/config.d.ts +3 -0
  12. package/dist/cli/commands/config.d.ts.map +1 -0
  13. package/dist/cli/commands/config.js +272 -0
  14. package/dist/cli/commands/config.js.map +1 -0
  15. package/dist/cli/commands/triage.js +1 -1
  16. package/dist/cli/commands/triage.js.map +1 -1
  17. package/dist/cli/index.d.ts.map +1 -1
  18. package/dist/cli/index.js +10 -0
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/shared/options.d.ts +2 -1
  21. package/dist/cli/shared/options.d.ts.map +1 -1
  22. package/dist/cli/shared/options.js +11 -1
  23. package/dist/cli/shared/options.js.map +1 -1
  24. package/dist/cli/types.d.ts +2 -0
  25. package/dist/cli/types.d.ts.map +1 -1
  26. package/dist/db/migrations.d.ts.map +1 -1
  27. package/dist/db/migrations.js +8 -1
  28. package/dist/db/migrations.js.map +1 -1
  29. package/dist/domain/analysis/module-map.d.ts +2 -0
  30. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  31. package/dist/domain/analysis/module-map.js +24 -2
  32. package/dist/domain/analysis/module-map.js.map +1 -1
  33. package/dist/domain/graph/builder/call-resolver.d.ts +16 -10
  34. package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
  35. package/dist/domain/graph/builder/call-resolver.js +251 -34
  36. package/dist/domain/graph/builder/call-resolver.js.map +1 -1
  37. package/dist/domain/graph/builder/cha.d.ts +69 -0
  38. package/dist/domain/graph/builder/cha.d.ts.map +1 -0
  39. package/dist/domain/graph/builder/cha.js +158 -0
  40. package/dist/domain/graph/builder/cha.js.map +1 -0
  41. package/dist/domain/graph/builder/context.d.ts +3 -0
  42. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  43. package/dist/domain/graph/builder/context.js +2 -0
  44. package/dist/domain/graph/builder/context.js.map +1 -1
  45. package/dist/domain/graph/builder/helpers.d.ts +25 -1
  46. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  47. package/dist/domain/graph/builder/helpers.js +178 -5
  48. package/dist/domain/graph/builder/helpers.js.map +1 -1
  49. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  50. package/dist/domain/graph/builder/incremental.js +74 -2
  51. package/dist/domain/graph/builder/incremental.js.map +1 -1
  52. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  53. package/dist/domain/graph/builder/pipeline.js +37 -2
  54. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  55. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  56. package/dist/domain/graph/builder/stages/build-edges.js +704 -34
  57. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  58. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  59. package/dist/domain/graph/builder/stages/detect-changes.js +3 -2
  60. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/finalize.js +4 -0
  63. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  64. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
  65. package/dist/domain/graph/builder/stages/native-orchestrator.js +783 -37
  66. package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
  67. package/dist/domain/graph/builder/stages/resolve-imports.d.ts +1 -0
  68. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  69. package/dist/domain/graph/builder/stages/resolve-imports.js +10 -1
  70. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  71. package/dist/domain/graph/journal.js +1 -1
  72. package/dist/domain/graph/journal.js.map +1 -1
  73. package/dist/domain/graph/resolver/points-to.d.ts +53 -0
  74. package/dist/domain/graph/resolver/points-to.d.ts.map +1 -0
  75. package/dist/domain/graph/resolver/points-to.js +213 -0
  76. package/dist/domain/graph/resolver/points-to.js.map +1 -0
  77. package/dist/domain/graph/resolver/ts-resolver.d.ts +9 -0
  78. package/dist/domain/graph/resolver/ts-resolver.d.ts.map +1 -0
  79. package/dist/domain/graph/resolver/ts-resolver.js +476 -0
  80. package/dist/domain/graph/resolver/ts-resolver.js.map +1 -0
  81. package/dist/domain/parser.d.ts +12 -4
  82. package/dist/domain/parser.d.ts.map +1 -1
  83. package/dist/domain/parser.js +83 -20
  84. package/dist/domain/parser.js.map +1 -1
  85. package/dist/domain/wasm-worker-entry.js +35 -2
  86. package/dist/domain/wasm-worker-entry.js.map +1 -1
  87. package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
  88. package/dist/domain/wasm-worker-pool.js +34 -0
  89. package/dist/domain/wasm-worker-pool.js.map +1 -1
  90. package/dist/domain/wasm-worker-protocol.d.ts +15 -1
  91. package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
  92. package/dist/extractors/c.js +3 -3
  93. package/dist/extractors/c.js.map +1 -1
  94. package/dist/extractors/clojure.js +1 -1
  95. package/dist/extractors/clojure.js.map +1 -1
  96. package/dist/extractors/cpp.d.ts.map +1 -1
  97. package/dist/extractors/cpp.js +45 -4
  98. package/dist/extractors/cpp.js.map +1 -1
  99. package/dist/extractors/csharp.d.ts.map +1 -1
  100. package/dist/extractors/csharp.js +37 -8
  101. package/dist/extractors/csharp.js.map +1 -1
  102. package/dist/extractors/cuda.d.ts.map +1 -1
  103. package/dist/extractors/cuda.js +45 -4
  104. package/dist/extractors/cuda.js.map +1 -1
  105. package/dist/extractors/elixir.js +6 -6
  106. package/dist/extractors/elixir.js.map +1 -1
  107. package/dist/extractors/fsharp.js +1 -1
  108. package/dist/extractors/fsharp.js.map +1 -1
  109. package/dist/extractors/go.js +5 -5
  110. package/dist/extractors/go.js.map +1 -1
  111. package/dist/extractors/haskell.js +1 -1
  112. package/dist/extractors/haskell.js.map +1 -1
  113. package/dist/extractors/helpers.d.ts +11 -0
  114. package/dist/extractors/helpers.d.ts.map +1 -1
  115. package/dist/extractors/helpers.js +40 -0
  116. package/dist/extractors/helpers.js.map +1 -1
  117. package/dist/extractors/java.d.ts.map +1 -1
  118. package/dist/extractors/java.js +10 -9
  119. package/dist/extractors/java.js.map +1 -1
  120. package/dist/extractors/javascript.d.ts +2 -0
  121. package/dist/extractors/javascript.d.ts.map +1 -1
  122. package/dist/extractors/javascript.js +1812 -71
  123. package/dist/extractors/javascript.js.map +1 -1
  124. package/dist/extractors/kotlin.js +5 -5
  125. package/dist/extractors/kotlin.js.map +1 -1
  126. package/dist/extractors/lua.js +1 -1
  127. package/dist/extractors/lua.js.map +1 -1
  128. package/dist/extractors/objc.js +3 -3
  129. package/dist/extractors/objc.js.map +1 -1
  130. package/dist/extractors/ocaml.js +1 -1
  131. package/dist/extractors/ocaml.js.map +1 -1
  132. package/dist/extractors/php.js +2 -2
  133. package/dist/extractors/php.js.map +1 -1
  134. package/dist/extractors/python.js +7 -7
  135. package/dist/extractors/python.js.map +1 -1
  136. package/dist/extractors/ruby.js +2 -2
  137. package/dist/extractors/ruby.js.map +1 -1
  138. package/dist/extractors/scala.js +1 -1
  139. package/dist/extractors/scala.js.map +1 -1
  140. package/dist/extractors/solidity.js +1 -1
  141. package/dist/extractors/solidity.js.map +1 -1
  142. package/dist/extractors/swift.js +4 -4
  143. package/dist/extractors/swift.js.map +1 -1
  144. package/dist/extractors/zig.js +4 -4
  145. package/dist/extractors/zig.js.map +1 -1
  146. package/dist/features/structure-query.d.ts +1 -1
  147. package/dist/features/structure-query.d.ts.map +1 -1
  148. package/dist/features/structure-query.js +6 -6
  149. package/dist/features/structure-query.js.map +1 -1
  150. package/dist/index.d.ts +1 -1
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/index.js +1 -1
  153. package/dist/index.js.map +1 -1
  154. package/dist/infrastructure/config.d.ts +85 -2
  155. package/dist/infrastructure/config.d.ts.map +1 -1
  156. package/dist/infrastructure/config.js +408 -19
  157. package/dist/infrastructure/config.js.map +1 -1
  158. package/dist/infrastructure/native.d.ts +11 -0
  159. package/dist/infrastructure/native.d.ts.map +1 -1
  160. package/dist/infrastructure/native.js +78 -5
  161. package/dist/infrastructure/native.js.map +1 -1
  162. package/dist/infrastructure/registry.d.ts +27 -0
  163. package/dist/infrastructure/registry.d.ts.map +1 -1
  164. package/dist/infrastructure/registry.js +59 -1
  165. package/dist/infrastructure/registry.js.map +1 -1
  166. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  167. package/dist/presentation/queries-cli/overview.js +5 -0
  168. package/dist/presentation/queries-cli/overview.js.map +1 -1
  169. package/dist/presentation/structure.d.ts +1 -1
  170. package/dist/presentation/structure.d.ts.map +1 -1
  171. package/dist/presentation/structure.js +2 -2
  172. package/dist/presentation/structure.js.map +1 -1
  173. package/dist/types.d.ts +221 -0
  174. package/dist/types.d.ts.map +1 -1
  175. package/grammars/tree-sitter-gleam.wasm +0 -0
  176. package/package.json +7 -8
  177. package/src/cli/commands/audit.ts +2 -1
  178. package/src/cli/commands/batch.ts +1 -0
  179. package/src/cli/commands/build.ts +6 -1
  180. package/src/cli/commands/config.ts +353 -0
  181. package/src/cli/commands/triage.ts +1 -1
  182. package/src/cli/index.ts +10 -0
  183. package/src/cli/shared/options.ts +11 -1
  184. package/src/cli/types.ts +2 -0
  185. package/src/db/migrations.ts +8 -1
  186. package/src/domain/analysis/module-map.ts +29 -1
  187. package/src/domain/graph/builder/call-resolver.ts +263 -35
  188. package/src/domain/graph/builder/cha.ts +192 -0
  189. package/src/domain/graph/builder/context.ts +3 -0
  190. package/src/domain/graph/builder/helpers.ts +195 -5
  191. package/src/domain/graph/builder/incremental.ts +80 -1
  192. package/src/domain/graph/builder/pipeline.ts +49 -2
  193. package/src/domain/graph/builder/stages/build-edges.ts +867 -32
  194. package/src/domain/graph/builder/stages/detect-changes.ts +4 -2
  195. package/src/domain/graph/builder/stages/finalize.ts +4 -0
  196. package/src/domain/graph/builder/stages/native-orchestrator.ts +910 -43
  197. package/src/domain/graph/builder/stages/resolve-imports.ts +15 -1
  198. package/src/domain/graph/journal.ts +1 -1
  199. package/src/domain/graph/resolver/points-to.ts +254 -0
  200. package/src/domain/graph/resolver/ts-resolver.ts +536 -0
  201. package/src/domain/parser.ts +86 -17
  202. package/src/domain/wasm-worker-entry.ts +35 -2
  203. package/src/domain/wasm-worker-pool.ts +22 -0
  204. package/src/domain/wasm-worker-protocol.ts +15 -0
  205. package/src/extractors/c.ts +3 -3
  206. package/src/extractors/clojure.ts +1 -1
  207. package/src/extractors/cpp.ts +47 -4
  208. package/src/extractors/csharp.ts +33 -9
  209. package/src/extractors/cuda.ts +47 -4
  210. package/src/extractors/elixir.ts +6 -6
  211. package/src/extractors/fsharp.ts +1 -1
  212. package/src/extractors/go.ts +5 -5
  213. package/src/extractors/haskell.ts +1 -1
  214. package/src/extractors/helpers.ts +43 -0
  215. package/src/extractors/java.ts +10 -9
  216. package/src/extractors/javascript.ts +1929 -72
  217. package/src/extractors/kotlin.ts +5 -5
  218. package/src/extractors/lua.ts +1 -1
  219. package/src/extractors/objc.ts +3 -3
  220. package/src/extractors/ocaml.ts +1 -1
  221. package/src/extractors/php.ts +2 -2
  222. package/src/extractors/python.ts +7 -7
  223. package/src/extractors/ruby.ts +2 -2
  224. package/src/extractors/scala.ts +1 -1
  225. package/src/extractors/solidity.ts +1 -1
  226. package/src/extractors/swift.ts +4 -4
  227. package/src/extractors/zig.ts +4 -4
  228. package/src/features/structure-query.ts +7 -7
  229. package/src/index.ts +5 -1
  230. package/src/infrastructure/config.ts +494 -20
  231. package/src/infrastructure/native.ts +87 -5
  232. package/src/infrastructure/registry.ts +82 -1
  233. package/src/presentation/queries-cli/overview.ts +15 -1
  234. package/src/presentation/structure.ts +3 -3
  235. package/src/types.ts +235 -0
  236. package/grammars/tree-sitter-erlang.wasm +0 -0
@@ -55,7 +55,15 @@ const BUILTIN_GLOBALS = new Set([
55
55
  'Buffer',
56
56
  'EventEmitter',
57
57
  'Stream',
58
+ 'process',
59
+ 'window',
60
+ 'document',
61
+ 'globalThis',
58
62
  ]);
63
+ /** Maximum chain depth for inter-procedural return-type propagation (Phase 8.2). */
64
+ const MAX_PROPAGATION_DEPTH = 3;
65
+ /** Confidence penalty applied per propagation hop (1.0 → 0.9 → 0.8 → 0.7). */
66
+ export const PROPAGATION_HOP_PENALTY = 0.1;
59
67
  /**
60
68
  * Extract symbols from a JS/TS parsed AST.
61
69
  * When a compiled tree-sitter Query is provided (from parser.js),
@@ -116,7 +124,22 @@ function handleClassCapture(c, definitions, classes) {
116
124
  }
117
125
  /** Handle method_definition capture. */
118
126
  function handleMethodCapture(c, definitions) {
119
- const methName = c.meth_name.text;
127
+ const methNameNode = c.meth_name;
128
+ let methName;
129
+ if (methNameNode.type === 'computed_property_name') {
130
+ // Extract the inner string literal from `['methodName']` or `["methodName"]`.
131
+ // Non-string computed keys (e.g. `[Symbol.iterator]`) cannot be resolved at
132
+ // dot-notation call sites, so skip them entirely.
133
+ const inner = methNameNode.child(1); // child(0)='[', child(1)=string, child(2)=']'
134
+ if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment'))
135
+ return;
136
+ methName = inner.text.replace(/^['"]|['"]$/g, '');
137
+ if (!methName)
138
+ return;
139
+ }
140
+ else {
141
+ methName = methNameNode.text;
142
+ }
120
143
  const parentClass = findParentClass(c.meth_node);
121
144
  const fullName = parentClass ? `${parentClass}.${methName}` : methName;
122
145
  const methChildren = extractParameters(c.meth_node);
@@ -138,7 +161,9 @@ function handleExportCapture(c, exps, imports) {
138
161
  const declType = decl.type;
139
162
  const kindMap = {
140
163
  function_declaration: 'function',
164
+ generator_function_declaration: 'function',
141
165
  class_declaration: 'class',
166
+ abstract_class_declaration: 'class',
142
167
  interface_declaration: 'interface',
143
168
  type_alias_declaration: 'type',
144
169
  };
@@ -261,6 +286,7 @@ function dispatchQueryMatch(c, definitions, calls, imports, classes, exps) {
261
286
  }
262
287
  else if (c.assign_node) {
263
288
  handleCommonJSAssignment(c.assign_left, c.assign_right, c.assign_node, imports);
289
+ handleFuncPropAssignment(c.assign_left, c.assign_right, definitions);
264
290
  }
265
291
  }
266
292
  function extractSymbolsQuery(tree, query) {
@@ -270,6 +296,17 @@ function extractSymbolsQuery(tree, query) {
270
296
  const classes = [];
271
297
  const exps = [];
272
298
  const typeMap = new Map();
299
+ const returnTypeMap = new Map();
300
+ const callAssignments = [];
301
+ const fnRefBindings = [];
302
+ const paramBindings = [];
303
+ const arrayElemBindings = [];
304
+ const spreadArgBindings = [];
305
+ const forOfBindings = [];
306
+ const arrayCallbackBindings = [];
307
+ const objectRestParamBindings = [];
308
+ const objectPropBindings = [];
309
+ const thisCallBindings = [];
273
310
  const matches = query.matches(tree.rootNode);
274
311
  for (const match of matches) {
275
312
  // Build capture lookup for this match (1-3 captures each, very fast)
@@ -280,13 +317,65 @@ function extractSymbolsQuery(tree, query) {
280
317
  }
281
318
  // Extract top-level constants via targeted walk (query patterns don't cover these)
282
319
  extractConstantsWalk(tree.rootNode, definitions);
283
- // Extract dynamic import() calls via targeted walk (query patterns don't match `import` function type)
284
- extractDynamicImportsWalk(tree.rootNode, imports);
285
- // Extract typeMap from type annotations and new expressions
286
- extractTypeMapWalk(tree.rootNode, typeMap);
320
+ // Phase 8.2: Extract function return types first runContextCollectorWalk's
321
+ // declarator handler reads the *complete* per-file map for inter-procedural
322
+ // propagation, so this cannot be folded into that pass.
323
+ extractReturnTypeMapWalk(tree.rootNode, returnTypeMap);
324
+ // Context-tracking collector pass: typeMap (with return-type propagation),
325
+ // object-rest param bindings, and spread/for-of/Array.from bindings.
326
+ runContextCollectorWalk(tree.rootNode, {
327
+ typeMap,
328
+ returnTypeMap,
329
+ callAssignments,
330
+ fnRefBindings,
331
+ objectRestParamBindings,
332
+ spreadArgBindings,
333
+ forOfBindings,
334
+ arrayCallbackBindings,
335
+ });
287
336
  // Extract definitions from destructured bindings (query patterns don't match object_pattern)
288
337
  extractDestructuredBindingsWalk(tree.rootNode, definitions);
289
- return { definitions, calls, imports, classes, exports: exps, typeMap };
338
+ // Everything without bespoke traversal semantics is collected in ONE pass:
339
+ // dynamic import() calls, prototype-method definitions, param bindings,
340
+ // array-element bindings, object-prop bindings, `new X()` names,
341
+ // Object.defineProperty receivers, class members (fields/static blocks,
342
+ // which query patterns don't capture), and this()/call/apply bindings.
343
+ const newExpressions = [];
344
+ const definePropertyReceivers = new Map();
345
+ runCollectorWalk(tree.rootNode, {
346
+ definitions,
347
+ typeMap,
348
+ paramBindings,
349
+ arrayElemBindings,
350
+ objectPropBindings,
351
+ newExpressions,
352
+ definePropertyReceivers,
353
+ imports,
354
+ calls,
355
+ thisCallBindings,
356
+ classMemberDefs: definitions,
357
+ });
358
+ return {
359
+ definitions,
360
+ calls,
361
+ imports,
362
+ classes,
363
+ exports: exps,
364
+ typeMap,
365
+ returnTypeMap,
366
+ callAssignments,
367
+ fnRefBindings,
368
+ paramBindings,
369
+ arrayElemBindings,
370
+ spreadArgBindings,
371
+ forOfBindings,
372
+ arrayCallbackBindings,
373
+ objectRestParamBindings,
374
+ objectPropBindings,
375
+ thisCallBindings,
376
+ newExpressions,
377
+ ...(definePropertyReceivers.size > 0 ? { definePropertyReceivers } : {}),
378
+ };
290
379
  }
291
380
  /** Node types that define a function scope — constants inside these are skipped. */
292
381
  const FUNCTION_SCOPE_TYPES = new Set([
@@ -338,6 +427,10 @@ function extractConstantsWalk(node, definitions) {
338
427
  }
339
428
  }
340
429
  }
430
+ // Class field definitions and static initializer blocks (which query patterns
431
+ // don't capture) are collected inline in runCollectorWalk's field_definition /
432
+ // class_static_block cases when `classMemberDefs` is set. The walk-based path
433
+ // (extractSymbolsWalk) handles these node types via walkJavaScriptNode instead.
341
434
  /**
342
435
  * Walk the AST to find destructured const bindings (query patterns don't match object_pattern).
343
436
  * e.g. `const { handleToken, checkPermissions } = initAuth(config)`
@@ -360,12 +453,22 @@ function extractDestructuredBindingsWalk(node, definitions) {
360
453
  declNode.text.startsWith('const ')) {
361
454
  for (let j = 0; j < declNode.childCount; j++) {
362
455
  const declarator = declNode.child(j);
363
- if (!declarator || declarator.type !== 'variable_declarator')
456
+ if (declarator?.type !== 'variable_declarator')
364
457
  continue;
365
458
  const nameN = declarator.childForFieldName('name');
366
459
  if (nameN && nameN.type === 'object_pattern') {
367
460
  extractDestructuredBindings(nameN, nodeStartLine(declNode), nodeEndLine(declNode), definitions);
368
461
  }
462
+ else if (nameN && nameN.type === 'array_pattern') {
463
+ // `const [x, y] = ...` — emit a single constant node whose name is the
464
+ // full array pattern text (e.g. `[x, y]`), matching native engine behaviour.
465
+ definitions.push({
466
+ name: nameN.text,
467
+ kind: 'constant',
468
+ line: nodeStartLine(declNode),
469
+ endLine: nodeEndLine(declNode),
470
+ });
471
+ }
369
472
  }
370
473
  }
371
474
  if (child.type !== 'export_statement') {
@@ -382,15 +485,18 @@ function extractConstDeclarators(declNode, definitions) {
382
485
  return;
383
486
  for (let j = 0; j < declNode.childCount; j++) {
384
487
  const declarator = declNode.child(j);
385
- if (!declarator || declarator.type !== 'variable_declarator')
488
+ if (declarator?.type !== 'variable_declarator')
386
489
  continue;
387
490
  const nameN = declarator.childForFieldName('name');
388
491
  const valueN = declarator.childForFieldName('value');
389
- if (!nameN || nameN.type !== 'identifier' || !valueN)
492
+ if (nameN?.type !== 'identifier' || !valueN)
390
493
  continue;
391
494
  // Skip functions — already captured by query patterns
392
495
  const valType = valueN.type;
393
- if (valType === 'arrow_function' || valType === 'function_expression' || valType === 'function')
496
+ if (valType === 'arrow_function' ||
497
+ valType === 'function_expression' ||
498
+ valType === 'function' ||
499
+ valType === 'generator_function')
394
500
  continue;
395
501
  if (isConstantValue(valueN)) {
396
502
  definitions.push({
@@ -399,6 +505,14 @@ function extractConstDeclarators(declNode, definitions) {
399
505
  line: nodeStartLine(declNode),
400
506
  endLine: nodeEndLine(declNode),
401
507
  });
508
+ // Phase 8.3f: extract function/arrow properties from object literals.
509
+ // Scope guard: extractConstDeclarators is only called from extractConstantsWalk, which
510
+ // already skips const declarations inside function scopes (line ~412). So these definitions
511
+ // are always top-level. Any new call site must add a hasFunctionScopeAncestor guard
512
+ // (the walk path at handleVariableDecl does this).
513
+ if (valueN.type === 'object') {
514
+ extractObjectLiteralFunctions(valueN, nameN.text, definitions);
515
+ }
402
516
  }
403
517
  }
404
518
  }
@@ -407,33 +521,36 @@ function extractConstDeclarators(declNode, definitions) {
407
521
  * Query patterns match call_expression with identifier/member_expression/subscript_expression
408
522
  * functions, but import() has function type `import` which none of those patterns cover.
409
523
  */
410
- function extractDynamicImportsWalk(node, imports) {
411
- if (node.type === 'call_expression') {
412
- const fn = node.childForFieldName('function');
413
- if (fn && fn.type === 'import') {
414
- const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
415
- if (args) {
416
- const strArg = findChild(args, 'string');
417
- if (strArg) {
418
- const modPath = strArg.text.replace(/['"]/g, '');
419
- const names = extractDynamicImportNames(node);
420
- imports.push({
421
- source: modPath,
422
- names,
423
- line: nodeStartLine(node),
424
- dynamicImport: true,
425
- });
426
- }
427
- else {
428
- debug(`Skipping non-static dynamic import() at line ${nodeStartLine(node)} (template literal or variable)`);
429
- }
430
- }
431
- return; // no need to recurse into import() children
524
+ /**
525
+ * Collect a dynamic `import()` call at `node` (a call_expression).
526
+ * Returns true when the node *is* an import() call — the collector walk uses
527
+ * this to suppress dynamic-import collection inside the import's own argument
528
+ * subtree, preserving the former standalone walk's "don't recurse into
529
+ * import() children" behaviour without hiding those children from the other
530
+ * collectors.
531
+ */
532
+ function collectDynamicImport(node, imports) {
533
+ const fn = node.childForFieldName('function');
534
+ if (fn?.type !== 'import')
535
+ return false;
536
+ const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
537
+ if (args) {
538
+ const strArg = findChild(args, 'string');
539
+ if (strArg) {
540
+ const modPath = strArg.text.replace(/['"]/g, '');
541
+ const names = extractDynamicImportNames(node);
542
+ imports.push({
543
+ source: modPath,
544
+ names,
545
+ line: nodeStartLine(node),
546
+ dynamicImport: true,
547
+ });
548
+ }
549
+ else {
550
+ debug(`Skipping non-static dynamic import() at line ${nodeStartLine(node)} (template literal or variable)`);
432
551
  }
433
552
  }
434
- for (let i = 0; i < node.childCount; i++) {
435
- extractDynamicImportsWalk(node.child(i), imports);
436
- }
553
+ return true;
437
554
  }
438
555
  function handleCommonJSAssignment(left, right, node, imports) {
439
556
  if (!left || !right)
@@ -489,20 +606,76 @@ function extractSymbolsWalk(tree) {
489
606
  classes: [],
490
607
  exports: [],
491
608
  typeMap: new Map(),
609
+ returnTypeMap: new Map(),
610
+ callAssignments: [],
611
+ fnRefBindings: [],
612
+ paramBindings: [],
613
+ arrayElemBindings: [],
614
+ spreadArgBindings: [],
615
+ forOfBindings: [],
616
+ arrayCallbackBindings: [],
617
+ objectRestParamBindings: [],
618
+ objectPropBindings: [],
619
+ thisCallBindings: [],
492
620
  };
493
621
  walkJavaScriptNode(tree.rootNode, ctx);
494
- // Populate typeMap for variables and parameter type annotations
495
- extractTypeMapWalk(tree.rootNode, ctx.typeMap);
622
+ // Phase 8.2: Extract function return types first — runContextCollectorWalk's
623
+ // declarator handler reads the *complete* per-file map for inter-procedural
624
+ // propagation, so this cannot be folded into that pass.
625
+ extractReturnTypeMapWalk(tree.rootNode, ctx.returnTypeMap);
626
+ // Context-tracking collector pass: typeMap (with return-type propagation),
627
+ // object-rest param bindings, and spread/for-of/Array.from bindings.
628
+ runContextCollectorWalk(tree.rootNode, {
629
+ typeMap: ctx.typeMap,
630
+ returnTypeMap: ctx.returnTypeMap,
631
+ callAssignments: ctx.callAssignments,
632
+ fnRefBindings: ctx.fnRefBindings,
633
+ objectRestParamBindings: ctx.objectRestParamBindings,
634
+ spreadArgBindings: ctx.spreadArgBindings,
635
+ forOfBindings: ctx.forOfBindings,
636
+ arrayCallbackBindings: ctx.arrayCallbackBindings,
637
+ });
638
+ // Single collector pass for everything else: prototype-method and func-prop
639
+ // definitions, param bindings, array-element bindings, object-prop bindings,
640
+ // `new X()` names, and Object.defineProperty receivers. Dynamic imports,
641
+ // this()/call/apply bindings, and class members are omitted here —
642
+ // walkJavaScriptNode already covers those node types on this path.
643
+ const newExpressions = [];
644
+ const definePropertyReceivers = new Map();
645
+ runCollectorWalk(tree.rootNode, {
646
+ definitions: ctx.definitions,
647
+ typeMap: ctx.typeMap,
648
+ paramBindings: ctx.paramBindings,
649
+ arrayElemBindings: ctx.arrayElemBindings,
650
+ objectPropBindings: ctx.objectPropBindings,
651
+ newExpressions,
652
+ definePropertyReceivers,
653
+ funcPropDefs: ctx.definitions,
654
+ });
655
+ ctx.newExpressions = newExpressions;
656
+ if (definePropertyReceivers.size > 0)
657
+ ctx.definePropertyReceivers = definePropertyReceivers;
496
658
  return ctx;
497
659
  }
498
660
  function walkJavaScriptNode(node, ctx) {
499
661
  switch (node.type) {
500
662
  case 'function_declaration':
663
+ case 'generator_function_declaration':
501
664
  handleFunctionDecl(node, ctx);
502
665
  break;
503
666
  case 'class_declaration':
667
+ case 'abstract_class_declaration':
668
+ // class expressions: `return class Foo extends Bar { ... }` or `const X = class Foo { ... }`
669
+ case 'class':
504
670
  handleClassDecl(node, ctx);
505
671
  break;
672
+ case 'class_static_block':
673
+ handleStaticBlock(node, ctx.definitions);
674
+ break;
675
+ case 'field_definition':
676
+ case 'public_field_definition':
677
+ handleFieldDef(node, ctx.definitions);
678
+ break;
506
679
  case 'method_definition':
507
680
  handleMethodDef(node, ctx);
508
681
  break;
@@ -582,8 +755,23 @@ function handleClassDecl(node, ctx) {
582
755
  function handleMethodDef(node, ctx) {
583
756
  const nameNode = node.childForFieldName('name');
584
757
  if (nameNode) {
758
+ let methName;
759
+ if (nameNode.type === 'computed_property_name') {
760
+ // Extract the inner string literal from `['methodName']` or `["methodName"]`.
761
+ // Non-string computed keys (e.g. `[Symbol.iterator]`) cannot be resolved at
762
+ // dot-notation call sites, so skip them entirely.
763
+ const inner = nameNode.child(1); // child(0)='[', child(1)=string, child(2)=']'
764
+ if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment'))
765
+ return;
766
+ methName = inner.text.replace(/^['"]|['"]$/g, '');
767
+ if (!methName)
768
+ return;
769
+ }
770
+ else {
771
+ methName = nameNode.text;
772
+ }
585
773
  const parentClass = findParentClass(node);
586
- const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
774
+ const fullName = parentClass ? `${parentClass}.${methName}` : methName;
587
775
  const methChildren = extractParameters(node);
588
776
  const methVis = extractVisibility(node);
589
777
  ctx.definitions.push({
@@ -596,6 +784,71 @@ function handleMethodDef(node, ctx) {
596
784
  });
597
785
  }
598
786
  }
787
+ /**
788
+ * Create a synthetic `ClassName.<static:L:C>` definition for a class static block
789
+ * so that calls inside the block can be attributed to a method-kind node and
790
+ * `resolveThisDispatch` can walk up to the parent class for `super.method()`.
791
+ *
792
+ * The start line and column are appended to the name to ensure uniqueness when a
793
+ * class has multiple `static { }` blocks (each has a distinct start position even
794
+ * if on the same line).
795
+ *
796
+ * Tree-sitter uses `class_static_block` (not `static_block`) for `static { ... }`.
797
+ */
798
+ function handleStaticBlock(node, definitions) {
799
+ const parentClass = findParentClass(node);
800
+ if (!parentClass)
801
+ return;
802
+ const line = nodeStartLine(node);
803
+ const col = node.startPosition.column;
804
+ definitions.push({
805
+ name: `${parentClass}.<static:${line}:${col}>`,
806
+ kind: 'method',
807
+ line,
808
+ endLine: nodeEndLine(node),
809
+ });
810
+ }
811
+ /**
812
+ * Emit a `ClassName.fieldName` definition for class fields that have an initializer.
813
+ * This lets `findCaller` attribute calls inside field initializers (e.g. static field
814
+ * side-effects) to the field rather than the enclosing class.
815
+ *
816
+ * JS `field_definition` uses the `'property'` field name; TS
817
+ * `public_field_definition` uses `'name'`. As a third fallback (Rust/TS parity) we
818
+ * also check for a positional `property_identifier` child.
819
+ */
820
+ const CALLABLE_FIELD_TYPES = new Set([
821
+ 'arrow_function',
822
+ 'function_expression',
823
+ 'generator_function',
824
+ ]);
825
+ function handleFieldDef(node, definitions) {
826
+ // JS field_definition uses 'property' field; TS public_field_definition uses 'name' field
827
+ const nameNode = node.childForFieldName('name') ||
828
+ node.childForFieldName('property') ||
829
+ findChild(node, 'property_identifier');
830
+ const valueNode = node.childForFieldName('value');
831
+ if (!nameNode || !valueNode)
832
+ return;
833
+ if (nameNode.type === 'computed_property_name')
834
+ return;
835
+ // Only emit a callable definition when the initializer is a function/arrow expression.
836
+ // Scalar fields like `static x = 42` should not appear as method-kind nodes.
837
+ if (!CALLABLE_FIELD_TYPES.has(valueNode.type))
838
+ return;
839
+ const fieldName = nameNode.text;
840
+ if (!fieldName)
841
+ return;
842
+ const parentClass = findParentClass(node);
843
+ if (!parentClass)
844
+ return;
845
+ definitions.push({
846
+ name: `${parentClass}.${fieldName}`,
847
+ kind: 'method',
848
+ line: nodeStartLine(node),
849
+ endLine: nodeEndLine(node),
850
+ });
851
+ }
599
852
  function handleInterfaceDecl(node, ctx) {
600
853
  const nameNode = node.childForFieldName('name');
601
854
  if (!nameNode)
@@ -660,7 +913,8 @@ function handleVariableDecl(node, ctx) {
660
913
  const valType = valueN.type;
661
914
  if (valType === 'arrow_function' ||
662
915
  valType === 'function_expression' ||
663
- valType === 'function') {
916
+ valType === 'function' ||
917
+ valType === 'generator_function') {
664
918
  const varFnChildren = extractParameters(valueN);
665
919
  ctx.definitions.push({
666
920
  name: nameN.text,
@@ -670,13 +924,26 @@ function handleVariableDecl(node, ctx) {
670
924
  children: varFnChildren.length > 0 ? varFnChildren : undefined,
671
925
  });
672
926
  }
673
- else if (isConst && nameN.type === 'identifier' && isConstantValue(valueN)) {
927
+ else if (isConst &&
928
+ nameN.type === 'identifier' &&
929
+ isConstantValue(valueN) &&
930
+ !hasFunctionScopeAncestor(node)) {
674
931
  ctx.definitions.push({
675
932
  name: nameN.text,
676
933
  kind: 'constant',
677
934
  line: nodeStartLine(node),
678
935
  endLine: nodeEndLine(node),
679
936
  });
937
+ // Phase 8.3f: extract function/arrow properties from object literals so that
938
+ // this.method() calls inside Object.defineProperty accessors can resolve them.
939
+ // Scope guard: hasFunctionScopeAncestor mirrors the Rust path's find_parent_of_types
940
+ // check and the sibling destructured-binding branch below — skips object literals
941
+ // inside function bodies to avoid polluting the global definition index with
942
+ // local variable properties (e.g. `localObj.fn` from `const localObj = { fn: ... }`
943
+ // inside a function).
944
+ if (valueN.type === 'object') {
945
+ extractObjectLiteralFunctions(valueN, nameN.text, ctx.definitions);
946
+ }
680
947
  }
681
948
  else if (isConst && nameN.type === 'object_pattern' && !hasFunctionScopeAncestor(node)) {
682
949
  // Destructured bindings: const { handleToken, checkPermissions } = initAuth(...)
@@ -688,6 +955,80 @@ function handleVariableDecl(node, ctx) {
688
955
  // handle_var_decl (Rust path) — skips bindings inside function bodies.
689
956
  extractDestructuredBindings(nameN, nodeStartLine(node), nodeEndLine(node), ctx.definitions);
690
957
  }
958
+ else if (isConst && nameN.type === 'array_pattern' && !hasFunctionScopeAncestor(node)) {
959
+ // Array destructuring: `const [x, y] = ...` — emit a single constant node
960
+ // whose name is the full array pattern text (e.g. `[x, y]`), matching
961
+ // native engine behaviour. Scope guard mirrors the object_pattern branch above.
962
+ ctx.definitions.push({
963
+ name: nameN.text,
964
+ kind: 'constant',
965
+ line: nodeStartLine(node),
966
+ endLine: nodeEndLine(node),
967
+ });
968
+ }
969
+ }
970
+ }
971
+ }
972
+ }
973
+ /**
974
+ * Phase 8.3f: extract function/arrow function properties from an object literal as standalone
975
+ * definitions so that `this.method()` calls inside Object.defineProperty accessor functions can
976
+ * resolve them via the same-file definition lookup.
977
+ *
978
+ * Definitions are emitted as qualified names (`obj.baz` rather than bare `baz`) to avoid
979
+ * polluting the global definition index with common property names like `init`, `run`, or
980
+ * `render`. The typeMap value stored by the caller also uses the qualified name so the resolver
981
+ * looks up `lookup.byName('obj.baz')` rather than `lookup.byName('baz')`.
982
+ *
983
+ * `const obj = { baz: () => {} }` → emits Definition { name: 'obj.baz', kind: 'function' }
984
+ */
985
+ function extractObjectLiteralFunctions(objNode, varName, definitions) {
986
+ for (let i = 0; i < objNode.childCount; i++) {
987
+ const child = objNode.child(i);
988
+ if (!child)
989
+ continue;
990
+ if (child.type === 'pair') {
991
+ const keyNode = child.childForFieldName('key');
992
+ const valueNode = child.childForFieldName('value');
993
+ if (!keyNode || !valueNode)
994
+ continue;
995
+ const keyName = keyNode.type === 'string' ? keyNode.text.replace(/^['"]|['"]$/g, '') : keyNode.text;
996
+ if (!keyName)
997
+ continue;
998
+ if (valueNode.type === 'arrow_function' ||
999
+ valueNode.type === 'function_expression' ||
1000
+ valueNode.type === 'function') {
1001
+ definitions.push({
1002
+ name: `${varName}.${keyName}`,
1003
+ kind: 'function',
1004
+ line: nodeStartLine(child),
1005
+ endLine: nodeEndLine(valueNode),
1006
+ });
1007
+ }
1008
+ }
1009
+ else if (child.type === 'method_definition') {
1010
+ const nameNode = child.childForFieldName('name');
1011
+ if (nameNode) {
1012
+ let methodName;
1013
+ if (nameNode.type === 'computed_property_name') {
1014
+ // Strip brackets+quotes from `['methodName']` to get a resolvable name.
1015
+ // Skip non-string computed keys (e.g. [Symbol.iterator]).
1016
+ const inner = nameNode.child(1);
1017
+ if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment'))
1018
+ continue;
1019
+ methodName = inner.text.replace(/^['"]|['"]$/g, '');
1020
+ if (!methodName)
1021
+ continue;
1022
+ }
1023
+ else {
1024
+ methodName = nameNode.text;
1025
+ }
1026
+ definitions.push({
1027
+ name: `${varName}.${methodName}`,
1028
+ kind: 'function',
1029
+ line: nodeStartLine(child),
1030
+ endLine: nodeEndLine(child),
1031
+ });
691
1032
  }
692
1033
  }
693
1034
  }
@@ -731,6 +1072,11 @@ function handleCallExpr(node, ctx) {
731
1072
  handleDynamicImportCall(node, ctx.imports);
732
1073
  }
733
1074
  else {
1075
+ // this() calls: `this` used as a function (not as a receiver).
1076
+ if (fn.type === 'this') {
1077
+ ctx.calls.push({ name: 'this', line: nodeStartLine(node) });
1078
+ return; // no further processing needed for this()-style calls
1079
+ }
734
1080
  const callInfo = extractCallInfo(fn, node);
735
1081
  if (callInfo)
736
1082
  ctx.calls.push(callInfo);
@@ -738,6 +1084,32 @@ function handleCallExpr(node, ctx) {
738
1084
  const cbDef = extractCallbackDefinition(node, fn);
739
1085
  if (cbDef)
740
1086
  ctx.definitions.push(cbDef);
1087
+ // this-call bindings: `fn.call(namedCtx, ...)` / `fn.apply(namedCtx, ...)`
1088
+ const obj = fn.childForFieldName('object');
1089
+ const prop = fn.childForFieldName('property');
1090
+ if (obj?.type === 'identifier' &&
1091
+ prop &&
1092
+ (prop.text === 'call' || prop.text === 'apply') &&
1093
+ !BUILTIN_GLOBALS.has(obj.text)) {
1094
+ const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
1095
+ if (args) {
1096
+ for (let i = 0; i < args.childCount; i++) {
1097
+ const child = args.child(i);
1098
+ if (!child)
1099
+ continue;
1100
+ const t = child.type;
1101
+ if (t === '(' || t === ')' || t === ',')
1102
+ continue;
1103
+ if (t === 'identifier' &&
1104
+ !BUILTIN_GLOBALS.has(child.text) &&
1105
+ child.text !== 'undefined' &&
1106
+ child.text !== 'null') {
1107
+ ctx.thisCallBindings.push({ callee: obj.text, thisArg: child.text });
1108
+ }
1109
+ break;
1110
+ }
1111
+ }
1112
+ }
741
1113
  }
742
1114
  ctx.calls.push(...extractCallbackReferenceCalls(node));
743
1115
  }
@@ -791,7 +1163,9 @@ function handleExportStmt(node, ctx) {
791
1163
  const declType = decl.type;
792
1164
  const kindMap = {
793
1165
  function_declaration: 'function',
1166
+ generator_function_declaration: 'function',
794
1167
  class_declaration: 'class',
1168
+ abstract_class_declaration: 'class',
795
1169
  interface_declaration: 'interface',
796
1170
  type_alias_declaration: 'type',
797
1171
  };
@@ -1010,7 +1384,7 @@ function extractSimpleTypeName(typeAnnotationNode) {
1010
1384
  return null;
1011
1385
  }
1012
1386
  function extractNewExprTypeName(newExprNode) {
1013
- if (!newExprNode || newExprNode.type !== 'new_expression')
1387
+ if (newExprNode?.type !== 'new_expression')
1014
1388
  return null;
1015
1389
  const ctor = newExprNode.childForFieldName('constructor') || newExprNode.child(1);
1016
1390
  if (!ctor)
@@ -1023,40 +1397,477 @@ function extractNewExprTypeName(newExprNode) {
1023
1397
  }
1024
1398
  return null;
1025
1399
  }
1400
+ // ── Phase 8.2: Inter-Procedural Return Type Propagation ─────────────────────
1401
+ /**
1402
+ * Walk the AST and record the return type of every function/method definition.
1403
+ *
1404
+ * Keys: plain name (e.g. "createUser") or "ClassName.methodName" for methods.
1405
+ * Confidence:
1406
+ * - 1.0: explicit TypeScript return type annotation
1407
+ * - 0.85: inferred from the first `return new Constructor()` in the body
1408
+ */
1409
+ function extractReturnTypeMapWalk(rootNode, returnTypeMap) {
1410
+ function walk(node, depth, currentClass) {
1411
+ if (depth >= MAX_WALK_DEPTH)
1412
+ return;
1413
+ const t = node.type;
1414
+ if (t === 'class_declaration' || t === 'abstract_class_declaration' || t === 'class') {
1415
+ const nameNode = node.childForFieldName('name');
1416
+ const className = nameNode?.text ?? null;
1417
+ for (let i = 0; i < node.childCount; i++) {
1418
+ walk(node.child(i), depth + 1, className);
1419
+ }
1420
+ return;
1421
+ }
1422
+ if (t === 'function_declaration' || t === 'generator_function_declaration') {
1423
+ const nameNode = node.childForFieldName('name');
1424
+ if (nameNode?.type === 'identifier' && nameNode.text !== 'constructor') {
1425
+ const fnName = currentClass ? `${currentClass}.${nameNode.text}` : nameNode.text;
1426
+ storeReturnType(node, fnName, returnTypeMap);
1427
+ }
1428
+ // Recurse into the function body with null currentClass so nested
1429
+ // function declarations are not stored under the enclosing class name.
1430
+ for (let i = 0; i < node.childCount; i++) {
1431
+ walk(node.child(i), depth + 1, null);
1432
+ }
1433
+ return;
1434
+ }
1435
+ else if (t === 'method_definition') {
1436
+ const nameNode = node.childForFieldName('name');
1437
+ if (nameNode && currentClass && nameNode.text !== 'constructor') {
1438
+ storeReturnType(node, `${currentClass}.${nameNode.text}`, returnTypeMap);
1439
+ }
1440
+ // Recurse into the method body with null currentClass so nested
1441
+ // function declarations are not stored under the enclosing class name.
1442
+ for (let i = 0; i < node.childCount; i++) {
1443
+ walk(node.child(i), depth + 1, null);
1444
+ }
1445
+ return;
1446
+ }
1447
+ else if (t === 'variable_declarator') {
1448
+ // const foo = (): ReturnType => … or const foo = function(): ReturnType { … }
1449
+ const nameN = node.childForFieldName('name');
1450
+ const valueN = node.childForFieldName('value');
1451
+ if (nameN?.type === 'identifier' && valueN) {
1452
+ const vt = valueN.type;
1453
+ if (vt === 'arrow_function' ||
1454
+ vt === 'function_expression' ||
1455
+ vt === 'generator_function') {
1456
+ const fnName = currentClass ? `${currentClass}.${nameN.text}` : nameN.text;
1457
+ storeReturnType(valueN, fnName, returnTypeMap);
1458
+ }
1459
+ }
1460
+ }
1461
+ for (let i = 0; i < node.childCount; i++) {
1462
+ walk(node.child(i), depth + 1, currentClass);
1463
+ }
1464
+ }
1465
+ walk(rootNode, 0, null);
1466
+ }
1467
+ /** Extract the return type of a function node and store it in the returnTypeMap. */
1468
+ function storeReturnType(fnNode, fnName, returnTypeMap) {
1469
+ const returnTypeNode = fnNode.childForFieldName('return_type');
1470
+ if (returnTypeNode) {
1471
+ const typeName = extractSimpleTypeName(returnTypeNode);
1472
+ if (typeName) {
1473
+ const existing = returnTypeMap.get(fnName);
1474
+ if (!existing || existing.confidence < 1.0)
1475
+ returnTypeMap.set(fnName, { type: typeName, confidence: 1.0 });
1476
+ return;
1477
+ }
1478
+ }
1479
+ // Infer from first `return new Constructor()` in the function body
1480
+ const body = fnNode.childForFieldName('body');
1481
+ if (body) {
1482
+ const inferred = findReturnNewExprType(body);
1483
+ if (inferred) {
1484
+ const existing = returnTypeMap.get(fnName);
1485
+ if (!existing || 0.85 > existing.confidence)
1486
+ returnTypeMap.set(fnName, { type: inferred, confidence: 0.85 });
1487
+ }
1488
+ }
1489
+ }
1490
+ /** Return the constructor name from the first `return new Constructor()` in a body, or null. */
1491
+ function findReturnNewExprType(bodyNode) {
1492
+ for (let i = 0; i < bodyNode.childCount; i++) {
1493
+ const child = bodyNode.child(i);
1494
+ if (child?.type !== 'return_statement')
1495
+ continue;
1496
+ for (let j = 0; j < child.childCount; j++) {
1497
+ const expr = child.child(j);
1498
+ if (expr?.type === 'new_expression')
1499
+ return extractNewExprTypeName(expr);
1500
+ }
1501
+ }
1502
+ return null;
1503
+ }
1504
+ /**
1505
+ * Resolve the return type of a call_expression node using returnTypeMap.
1506
+ * Handles: createUser() (identifier), service.getRepo() (member), and
1507
+ * getService().getRepo() (chained call) up to MAX_PROPAGATION_DEPTH hops.
1508
+ *
1509
+ * `depth` tracks total chain hops consumed so far. Each call boundary — both
1510
+ * resolving the receiver and resolving the final return type — costs one hop.
1511
+ * Confidence = annotated return type confidence − 0.1 × (depth + 1).
1512
+ *
1513
+ * Examples (annotated sources → confidence 1.0):
1514
+ * createUser() depth=0 → 1.0 − 0.1 = 0.9 (1 hop)
1515
+ * svc.getUser() depth=0 → 1.0 − 0.1 = 0.9 (1 hop; receiver from typeMap)
1516
+ * getService().getRepo() depth=0 → inner resolved at depth=1, outer at depth+1 → 0.8 (2 hops)
1517
+ */
1518
+ function resolveCallExprReturnType(callNode, typeMap, returnTypeMap, depth) {
1519
+ if (depth >= MAX_PROPAGATION_DEPTH)
1520
+ return null;
1521
+ const fn = callNode.childForFieldName('function');
1522
+ if (!fn)
1523
+ return null;
1524
+ if (fn.type === 'identifier') {
1525
+ const entry = returnTypeMap.get(fn.text);
1526
+ if (!entry)
1527
+ return null;
1528
+ const confidence = entry.confidence - PROPAGATION_HOP_PENALTY * (depth + 1);
1529
+ return confidence > 0 ? { type: entry.type, confidence } : null;
1530
+ }
1531
+ if (fn.type === 'member_expression') {
1532
+ const obj = fn.childForFieldName('object');
1533
+ const prop = fn.childForFieldName('property');
1534
+ if (!obj || !prop)
1535
+ return null;
1536
+ let receiverType = null;
1537
+ // effectiveDepth tracks the depth at which THIS call's return type is charged.
1538
+ // When the receiver is itself a call expression (chain), we've already consumed
1539
+ // a hop resolving it, so charge this call at depth+1.
1540
+ let effectiveDepth = depth;
1541
+ if (obj.type === 'identifier') {
1542
+ const typeEntry = typeMap.get(obj.text);
1543
+ receiverType = typeEntry ? typeEntry.type : null;
1544
+ }
1545
+ else if (obj.type === 'call_expression') {
1546
+ // Each link in a call chain costs an extra hop.
1547
+ const innerResult = resolveCallExprReturnType(obj, typeMap, returnTypeMap, depth + 1);
1548
+ receiverType = innerResult ? innerResult.type : null;
1549
+ effectiveDepth = depth + 1;
1550
+ }
1551
+ if (receiverType) {
1552
+ const entry = returnTypeMap.get(`${receiverType}.${prop.text}`);
1553
+ if (entry) {
1554
+ const confidence = entry.confidence - PROPAGATION_HOP_PENALTY * (effectiveDepth + 1);
1555
+ return confidence > 0 ? { type: entry.type, confidence } : null;
1556
+ }
1557
+ }
1558
+ }
1559
+ return null;
1560
+ }
1561
+ /**
1562
+ * Record a call assignment into callAssignments for cross-file propagation.
1563
+ * Only records cases where the callee is a simple identifier or a method call
1564
+ * on a known-typed variable — chain expressions are skipped (handled locally).
1565
+ */
1566
+ function recordCallAssignment(callNode, varName, typeMap, callAssignments) {
1567
+ const fn = callNode.childForFieldName('function');
1568
+ if (!fn)
1569
+ return;
1570
+ if (fn.type === 'identifier') {
1571
+ callAssignments.push({ varName, calleeName: fn.text });
1572
+ }
1573
+ else if (fn.type === 'member_expression') {
1574
+ const obj = fn.childForFieldName('object');
1575
+ const prop = fn.childForFieldName('property');
1576
+ if (obj?.type === 'identifier' && prop) {
1577
+ const receiverEntry = typeMap.get(obj.text);
1578
+ callAssignments.push({
1579
+ varName,
1580
+ calleeName: prop.text,
1581
+ receiverTypeName: receiverEntry?.type,
1582
+ });
1583
+ }
1584
+ }
1585
+ }
1586
+ /**
1587
+ * Phase 8.5 (RTA): collect all constructor names from `new X()` expressions
1588
+ * in the file. Captures both assigned (`const x = new Foo()`) and unassigned
1589
+ * (`doSomething(new Foo())`) usages that the typeMap-based approach would miss.
1590
+ */
1591
+ // `new X()` constructor-name collection (Phase 8.5 RTA instantiation tracking)
1592
+ // happens inline in runCollectorWalk's new_expression case.
1593
+ /**
1594
+ * Walk the AST to find `Object.defineProperty(obj, "bar", { get: getter })` patterns
1595
+ * and record which functions are used as getter/setter accessors for which objects.
1596
+ *
1597
+ * Result is stored in the provided map as `funcName → receiverVarName`.
1598
+ */
1599
+ function collectDefinePropertyReceiver(node, out) {
1600
+ const fn = node.childForFieldName('function');
1601
+ // Match `Object.defineProperty`
1602
+ if (fn?.type !== 'member_expression')
1603
+ return;
1604
+ const obj = fn.childForFieldName('object');
1605
+ const prop = fn.childForFieldName('property');
1606
+ if (obj?.type !== 'identifier' || obj.text !== 'Object' || prop?.text !== 'defineProperty') {
1607
+ return;
1608
+ }
1609
+ const argsNode = node.childForFieldName('arguments') ?? findChild(node, 'arguments');
1610
+ if (!argsNode)
1611
+ return;
1612
+ // Collect non-punctuation children: arg0 (target obj), arg1 (prop name string), arg2 (descriptor)
1613
+ const argChildren = [];
1614
+ for (let i = 0; i < argsNode.childCount; i++) {
1615
+ const c = argsNode.child(i);
1616
+ if (!c)
1617
+ continue;
1618
+ if (c.type === ',' || c.type === '(' || c.type === ')')
1619
+ continue;
1620
+ argChildren.push(c);
1621
+ }
1622
+ if (argChildren.length < 3)
1623
+ return;
1624
+ const targetObj = argChildren[0];
1625
+ const descriptor = argChildren[2];
1626
+ if (targetObj?.type !== 'identifier' || descriptor?.type !== 'object')
1627
+ return;
1628
+ const targetName = targetObj.text;
1629
+ // Walk the descriptor object's pair children looking for get/set
1630
+ for (let i = 0; i < descriptor.childCount; i++) {
1631
+ const pair = descriptor.child(i);
1632
+ if (pair?.type !== 'pair')
1633
+ continue;
1634
+ const key = pair.childForFieldName('key');
1635
+ const val = pair.childForFieldName('value');
1636
+ if (key &&
1637
+ (key.text === 'get' || key.text === 'set') &&
1638
+ val?.type === 'identifier' &&
1639
+ !BUILTIN_GLOBALS.has(val.text)) {
1640
+ // Known limitation: if the same function is registered as an
1641
+ // accessor on multiple objects, last-write-wins — only the
1642
+ // last target object is retained. This is an unusual pattern
1643
+ // (sharing one function across multiple defineProperty calls)
1644
+ // and covering it would require Map<string, string[]> which
1645
+ // changes the consumer API. Tracked as a known edge case.
1646
+ out.set(val.text, targetName);
1647
+ }
1648
+ }
1649
+ }
1026
1650
  /**
1027
- * Extract variable-to-type assignments into a per-file type map.
1651
+ * Single context-tracking pass combining what were three separate full-tree
1652
+ * walks (typeMap, object-rest params, spread/for-of) — see runCollectorWalk
1653
+ * for why traversal count dominates extraction cost on WASM trees.
1654
+ *
1655
+ * Each concern keeps its own enclosing-class register because their reset
1656
+ * rules intentionally differ:
1028
1657
  *
1029
- * Values are `{ type: string, confidence: number }`:
1030
- * - 1.0: explicit constructor (`new Foo()`)
1031
- * - 0.9: type annotation (`: Foo`) or typed parameter
1032
- * - 0.7: factory method call (`Foo.create()` uppercase-first heuristic)
1658
+ * - typeMap (`typeMapClass`): extracts variable-to-type assignments.
1659
+ * Values are `{ type: string, confidence: number }`:
1660
+ * - 1.0: explicit constructor (`new Foo()`)
1661
+ * - 0.9: type annotation (`: Foo`) or typed parameter
1662
+ * - 0.85: property write (`obj.prop = fn` — Phase 8.3d pts tracking)
1663
+ * - 0.7–0.9: inter-procedural propagation from return-type map (Phase 8.2)
1664
+ * - 0.7: factory method call (`Foo.create()` — uppercase-first heuristic)
1665
+ * Higher-confidence entries take priority when the same variable is seen
1666
+ * twice. Class declarations propagate their name into the subtree; class
1667
+ * *expressions* (`const Foo = class Bar { … }`) propagate null because the
1668
+ * expression-internal name is never visible to the resolver, preserving the
1669
+ * `this.prop` fallback in resolveByMethodOrGlobal. No reset at function
1670
+ * boundaries.
1033
1671
  *
1034
- * Higher-confidence entries take priority when the same variable is seen twice.
1672
+ * - object-rest params (`objectRestClass`, Phase 8.3f): context flows only
1673
+ * class_declaration/class → class_body → method_definition so methods are
1674
+ * keyed "ClassName.method"; every other node type resets to null, and
1675
+ * function/method bodies recurse with null so nested declarations don't
1676
+ * inherit the class context.
1677
+ *
1678
+ * - spread/for-of (`funcStack`/`classStack`, Phase 8.3e): tracks the
1679
+ * enclosing *function* (not just class) via push/pop so for-of bindings
1680
+ * record the qualified enclosing callable (e.g. 'Foo.bar', 'obj.method',
1681
+ * or '<module>' at top level).
1682
+ *
1683
+ * NOTE: returnTypeMap population stays a separate, earlier pass
1684
+ * (extractReturnTypeMapWalk) — handleVarDeclaratorTypeMap reads it for
1685
+ * inter-procedural propagation, so it must be complete for the whole file
1686
+ * before any declarator is processed (a function declared *after* its first
1687
+ * use would otherwise be missed).
1035
1688
  */
1036
- function extractTypeMapWalk(rootNode, typeMap) {
1037
- function walk(node, depth) {
1689
+ function runContextCollectorWalk(rootNode, out) {
1690
+ const funcStack = [];
1691
+ const classStack = [];
1692
+ const walk = (node, depth, typeMapClass, objectRestClass) => {
1038
1693
  if (depth >= MAX_WALK_DEPTH)
1039
1694
  return;
1040
1695
  const t = node.type;
1696
+ const isClassDecl = t === 'class_declaration' || t === 'abstract_class_declaration';
1697
+ const isClassExpr = t === 'class';
1698
+ const isFnDecl = t === 'function_declaration' || t === 'generator_function_declaration';
1699
+ // Class name read once, shared by every concern that needs it below.
1700
+ let className = null;
1701
+ let classNameIsIdentifier = false;
1702
+ if (isClassDecl || isClassExpr) {
1703
+ const nameNode = node.childForFieldName('name');
1704
+ className = nameNode?.text ?? null;
1705
+ classNameIsIdentifier = nameNode?.type === 'identifier';
1706
+ }
1707
+ // ── spread/for-of enclosing-context stacks (push on enter, pop after children) ──
1708
+ let pushedFunc = false;
1709
+ let pushedClass = false;
1710
+ if (isClassDecl || isClassExpr) {
1711
+ // The stack push keeps the original walk's `identifier`-only check (TS
1712
+ // class names parse as type_identifier and were never pushed), while
1713
+ // typeMapClass/objectRestClass below use the bare text like their
1714
+ // original walks did.
1715
+ if (className && classNameIsIdentifier) {
1716
+ classStack.push(className);
1717
+ pushedClass = true;
1718
+ }
1719
+ }
1720
+ else if (isFnDecl) {
1721
+ const nameNode = node.childForFieldName('name');
1722
+ if (nameNode?.type === 'identifier') {
1723
+ funcStack.push(nameNode.text);
1724
+ pushedFunc = true;
1725
+ }
1726
+ }
1727
+ else if (t === 'method_definition') {
1728
+ const nameNode = node.childForFieldName('name');
1729
+ if (nameNode) {
1730
+ // Qualify with the enclosing class name so the PTS key matches
1731
+ // callerName from findCaller (which uses def.name = 'ClassName.method').
1732
+ const enclosingClass = classStack.length > 0 ? classStack[classStack.length - 1] : null;
1733
+ let rawName;
1734
+ if (nameNode.type === 'computed_property_name') {
1735
+ const inner = nameNode.child(1);
1736
+ if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) {
1737
+ // Non-string computed key — skip adding to funcStack (no resolvable name).
1738
+ rawName = '';
1739
+ }
1740
+ else {
1741
+ rawName = inner.text.replace(/^['"]|['"]$/g, '');
1742
+ }
1743
+ }
1744
+ else {
1745
+ rawName = nameNode.text;
1746
+ }
1747
+ if (rawName) {
1748
+ const qualifiedName = enclosingClass ? `${enclosingClass}.${rawName}` : rawName;
1749
+ funcStack.push(qualifiedName);
1750
+ pushedFunc = true;
1751
+ }
1752
+ }
1753
+ }
1754
+ else if (t === 'variable_declarator') {
1755
+ // `const process = (arr) => { ... }` — arrow/expression functions assigned
1756
+ // to a variable have no `name` field on the function node itself.
1757
+ const nameNode = node.childForFieldName('name');
1758
+ const valueNode = node.childForFieldName('value');
1759
+ if (nameNode?.type === 'identifier' &&
1760
+ (valueNode?.type === 'arrow_function' || valueNode?.type === 'function_expression')) {
1761
+ funcStack.push(nameNode.text);
1762
+ pushedFunc = true;
1763
+ }
1764
+ }
1765
+ else if (t === 'assignment_expression') {
1766
+ // `obj.method = function() { ... }` — func-prop assignment.
1767
+ // Mirror handleFuncPropAssignment's logic so for-of loops inside the
1768
+ // body get the correct enclosingFunc (e.g. 'obj.method') instead of
1769
+ // '<module>' or the wrong outer function name.
1770
+ const lhs = node.childForFieldName('left');
1771
+ const rhs = node.childForFieldName('right');
1772
+ if (lhs?.type === 'member_expression' &&
1773
+ (rhs?.type === 'function_expression' || rhs?.type === 'arrow_function')) {
1774
+ const obj = lhs.childForFieldName('object');
1775
+ const prop = lhs.childForFieldName('property');
1776
+ if (obj?.type === 'identifier' &&
1777
+ (prop?.type === 'property_identifier' || prop?.type === 'identifier') &&
1778
+ !BUILTIN_GLOBALS.has(obj.text) &&
1779
+ prop.text !== 'prototype') {
1780
+ funcStack.push(`${obj.text}.${prop.text}`);
1781
+ pushedFunc = true;
1782
+ }
1783
+ }
1784
+ }
1785
+ // ── per-node collectors (class nodes match none of these types) ──
1041
1786
  if (t === 'variable_declarator') {
1042
- handleVarDeclaratorTypeMap(node, typeMap);
1787
+ handleVarDeclaratorTypeMap(node, out.typeMap, out.returnTypeMap, out.callAssignments, out.fnRefBindings);
1788
+ collectCollectionWrapBinding(node, out.fnRefBindings);
1043
1789
  }
1044
1790
  else if (t === 'required_parameter' || t === 'optional_parameter') {
1045
- handleParamTypeMap(node, typeMap);
1791
+ handleParamTypeMap(node, out.typeMap);
1792
+ }
1793
+ else if (t === 'public_field_definition' || t === 'field_definition') {
1794
+ handleFieldDefTypeMap(node, out.typeMap, typeMapClass);
1795
+ }
1796
+ else if (t === 'assignment_expression') {
1797
+ handlePropWriteTypeMap(node, out.typeMap, typeMapClass);
1798
+ }
1799
+ else if (t === 'call_expression') {
1800
+ handleDefinePropertyTypeMap(node, out.typeMap);
1801
+ collectSpreadAndArrayFromBindings(node, out.spreadArgBindings, out.arrayCallbackBindings);
1802
+ }
1803
+ else if (t === 'for_in_statement') {
1804
+ const enclosingFunc = funcStack.length > 0 ? funcStack[funcStack.length - 1] : '<module>';
1805
+ collectForOfBinding(node, enclosingFunc, out.forOfBindings);
1806
+ }
1807
+ collectObjectRestParams(node, t, objectRestClass, out.objectRestParamBindings);
1808
+ // ── child context per concern ──
1809
+ const childTypeMapClass = isClassDecl ? className : isClassExpr ? null : typeMapClass;
1810
+ let childObjectRestClass = null;
1811
+ if (t === 'class_declaration' || t === 'class') {
1812
+ childObjectRestClass = className;
1813
+ }
1814
+ else if (t === 'class_body') {
1815
+ childObjectRestClass = objectRestClass;
1046
1816
  }
1047
1817
  for (let i = 0; i < node.childCount; i++) {
1048
- walk(node.child(i), depth + 1);
1818
+ walk(node.child(i), depth + 1, childTypeMapClass, childObjectRestClass);
1049
1819
  }
1050
- }
1051
- walk(rootNode, 0);
1820
+ if (pushedFunc)
1821
+ funcStack.pop();
1822
+ if (pushedClass)
1823
+ classStack.pop();
1824
+ };
1825
+ walk(rootNode, 0, null, null);
1052
1826
  }
1053
1827
  /** Extract type info from a variable_declarator: type annotation, constructor, or factory. */
1054
- function handleVarDeclaratorTypeMap(node, typeMap) {
1828
+ function handleVarDeclaratorTypeMap(node, typeMap, returnTypeMap, callAssignments, fnRefBindings) {
1055
1829
  const nameN = node.childForFieldName('name');
1056
- if (!nameN || nameN.type !== 'identifier')
1830
+ if (nameN?.type !== 'identifier')
1057
1831
  return;
1058
1832
  const typeAnno = findChild(node, 'type_annotation');
1059
1833
  const valueN = node.childForFieldName('value');
1834
+ // Phase 8.3: record function-reference bindings before any type-analysis early returns.
1835
+ // Captures `const fn = handler` (identifier) and `const fn = obj.method` (member_expression).
1836
+ // Also handles `const f = fn.bind(ctx)` — bind returns a new function aliasing fn.
1837
+ if (fnRefBindings && valueN) {
1838
+ if (valueN.type === 'identifier' && !BUILTIN_GLOBALS.has(valueN.text)) {
1839
+ fnRefBindings.push({ lhs: nameN.text, rhs: valueN.text });
1840
+ }
1841
+ else if (valueN.type === 'member_expression') {
1842
+ const prop = valueN.childForFieldName('property');
1843
+ const obj = valueN.childForFieldName('object');
1844
+ // Guard: only static property access (property_identifier or identifier), not
1845
+ // computed subscript expressions like obj[expr] where prop.text would be the
1846
+ // full expression rather than a simple name — those can never match pts keys.
1847
+ if (prop &&
1848
+ (prop.type === 'property_identifier' || prop.type === 'identifier') &&
1849
+ obj?.type === 'identifier' &&
1850
+ !BUILTIN_GLOBALS.has(obj.text)) {
1851
+ fnRefBindings.push({ lhs: nameN.text, rhs: prop.text, rhsReceiver: obj.text });
1852
+ }
1853
+ }
1854
+ else if (valueN.type === 'call_expression') {
1855
+ // `const f = fn.bind(ctx)` — bind returns a bound copy of fn; track f → fn so
1856
+ // pts(f) ⊇ pts(fn) and subsequent `f(args)` calls resolve to fn.
1857
+ // Note: only flat-identifier binds (fn.bind) are tracked here; method-receiver
1858
+ // binds like `obj.method.bind(ctx)` are not captured (boundFn must be an identifier).
1859
+ const callFn = valueN.childForFieldName('function');
1860
+ if (callFn?.type === 'member_expression') {
1861
+ const bindProp = callFn.childForFieldName('property');
1862
+ if (bindProp?.text === 'bind') {
1863
+ const boundFn = callFn.childForFieldName('object');
1864
+ if (boundFn?.type === 'identifier' && !BUILTIN_GLOBALS.has(boundFn.text)) {
1865
+ fnRefBindings.push({ lhs: nameN.text, rhs: boundFn.text });
1866
+ }
1867
+ }
1868
+ }
1869
+ }
1870
+ }
1060
1871
  // Constructor on the same declaration wins over annotation: the runtime type is
1061
1872
  // what matters for call resolution (e.g. `const x: Base = new Derived()` should
1062
1873
  // resolve `x.render()` to `Derived.render`, not `Base.render`).
@@ -1078,15 +1889,50 @@ function handleVarDeclaratorTypeMap(node, typeMap) {
1078
1889
  }
1079
1890
  if (!valueN)
1080
1891
  return;
1081
- // Constructor already handled above — only factory path remains.
1082
1892
  if (valueN.type === 'new_expression')
1083
1893
  return;
1084
- // Factory method: const x = Foo.create() → confidence 0.7
1085
- else if (valueN.type === 'call_expression') {
1894
+ if (valueN.type === 'call_expression') {
1895
+ // Phase 8.3e: Object.create({ f1, f2 }) — seed composite pts keys obj.f1 → f1, etc.
1896
+ const createFn = valueN.childForFieldName('function');
1897
+ if (createFn?.type === 'member_expression') {
1898
+ const createObj = createFn.childForFieldName('object');
1899
+ const createProp = createFn.childForFieldName('property');
1900
+ if (createObj?.text === 'Object' && createProp?.text === 'create') {
1901
+ const createArgs = valueN.childForFieldName('arguments') || findChild(valueN, 'arguments');
1902
+ if (createArgs) {
1903
+ let proto = null;
1904
+ for (let i = 0; i < createArgs.childCount; i++) {
1905
+ const n = createArgs.child(i);
1906
+ if (n && n.type !== '(' && n.type !== ')' && n.type !== ',') {
1907
+ proto = n;
1908
+ break;
1909
+ }
1910
+ }
1911
+ if (proto?.type === 'object') {
1912
+ seedProtoProperties(nameN.text, proto, typeMap);
1913
+ }
1914
+ }
1915
+ return;
1916
+ }
1917
+ }
1918
+ // Phase 8.2: inter-procedural propagation — try to resolve return type from
1919
+ // the local returnTypeMap before falling back to factory heuristics.
1920
+ if (returnTypeMap) {
1921
+ const result = resolveCallExprReturnType(valueN, typeMap, returnTypeMap, 0);
1922
+ if (result) {
1923
+ setTypeMapEntry(typeMap, nameN.text, result.type, result.confidence);
1924
+ return;
1925
+ }
1926
+ }
1927
+ // Record for cross-file resolution in build-edges.ts (imported functions)
1928
+ if (callAssignments) {
1929
+ recordCallAssignment(valueN, nameN.text, typeMap, callAssignments);
1930
+ }
1931
+ // Factory method heuristic: const x = Foo.create() → type Foo, confidence 0.7
1086
1932
  const fn = valueN.childForFieldName('function');
1087
- if (fn && fn.type === 'member_expression') {
1933
+ if (fn?.type === 'member_expression') {
1088
1934
  const obj = fn.childForFieldName('object');
1089
- if (obj && obj.type === 'identifier') {
1935
+ if (obj?.type === 'identifier') {
1090
1936
  const objName = obj.text;
1091
1937
  if (objName[0] &&
1092
1938
  objName[0] !== objName[0].toLowerCase() &&
@@ -1096,11 +1942,64 @@ function handleVarDeclaratorTypeMap(node, typeMap) {
1096
1942
  }
1097
1943
  }
1098
1944
  }
1945
+ // Phase 8.3f: seed composite pts keys for object literal properties.
1946
+ // `const obj = { baz: () => {} }` → typeMap['obj.baz'] = 'obj.baz'
1947
+ // `const obj = { baz }` (shorthand) → typeMap['obj.baz'] = 'baz' (bare identifier target)
1948
+ // `const obj = { baz: otherFn }` → typeMap['obj.baz'] = 'otherFn' (identifier alias)
1949
+ //
1950
+ // For function/arrow values, the value is the qualified name ('obj.baz') because
1951
+ // extractObjectLiteralFunctions now registers definitions under that qualified name to avoid
1952
+ // polluting the global index with bare property names like 'init', 'run', or 'render'.
1953
+ // Enables accessor this-dispatch: when typeMap['getter:this'] = 'obj',
1954
+ // resolving this.baz() inside getter → typeMap['obj.baz'] → 'obj.baz' → lookup.byName('obj.baz').
1955
+ //
1956
+ // Scope guard: mirrors Rust handle_var_decl's find_parent_of_types check — skip object literals
1957
+ // inside function bodies so function-scoped `const localObj = { fn: ... }` never seeds
1958
+ // the typeMap (which would shadow a module-level `const obj` with the same property names).
1959
+ if (valueN.type === 'object' && !hasFunctionScopeAncestor(node)) {
1960
+ for (let i = 0; i < valueN.childCount; i++) {
1961
+ const child = valueN.child(i);
1962
+ if (!child)
1963
+ continue;
1964
+ if (child.type === 'shorthand_property_identifier') {
1965
+ setTypeMapEntry(typeMap, `${nameN.text}.${child.text}`, child.text, 0.85);
1966
+ }
1967
+ else if (child.type === 'pair') {
1968
+ const keyNode = child.childForFieldName('key');
1969
+ const valNode = child.childForFieldName('value');
1970
+ if (!keyNode || !valNode)
1971
+ continue;
1972
+ const keyName = keyNode.type === 'string' ? keyNode.text.replace(/^['"]|['"]$/g, '') : keyNode.text;
1973
+ if (!keyName)
1974
+ continue;
1975
+ const qualifiedKey = `${nameN.text}.${keyName}`;
1976
+ if (valNode.type === 'arrow_function' ||
1977
+ valNode.type === 'function_expression' ||
1978
+ valNode.type === 'function') {
1979
+ // Store the qualified name so the resolver finds the qualified definition.
1980
+ setTypeMapEntry(typeMap, qualifiedKey, qualifiedKey, 0.85);
1981
+ }
1982
+ else if (valNode.type === 'identifier') {
1983
+ setTypeMapEntry(typeMap, qualifiedKey, valNode.text, 0.85);
1984
+ }
1985
+ }
1986
+ else if (child.type === 'method_definition') {
1987
+ // Method shorthand: `const obj = { baz() {} }` → typeMap['obj.baz'] = 'obj.baz'
1988
+ // extractObjectLiteralFunctions registers a definition under the qualified name;
1989
+ // seed the matching typeMap entry so the two-step accessor dispatch finds it.
1990
+ const nameNode = child.childForFieldName('name');
1991
+ if (!nameNode)
1992
+ continue;
1993
+ const qualifiedKey = `${nameN.text}.${nameNode.text}`;
1994
+ setTypeMapEntry(typeMap, qualifiedKey, qualifiedKey, 0.85);
1995
+ }
1996
+ }
1997
+ }
1099
1998
  }
1100
1999
  /** Extract type info from a required_parameter or optional_parameter. */
1101
2000
  function handleParamTypeMap(node, typeMap) {
1102
2001
  const nameNode = node.childForFieldName('pattern') || node.childForFieldName('left') || node.child(0);
1103
- if (!nameNode || nameNode.type !== 'identifier')
2002
+ if (nameNode?.type !== 'identifier')
1104
2003
  return;
1105
2004
  const typeAnno = findChild(node, 'type_annotation');
1106
2005
  if (typeAnno) {
@@ -1109,12 +2008,572 @@ function handleParamTypeMap(node, typeMap) {
1109
2008
  setTypeMapEntry(typeMap, nameNode.text, typeName, 0.9);
1110
2009
  }
1111
2010
  }
1112
- function extractReceiverName(objNode) {
1113
- if (!objNode)
1114
- return undefined;
1115
- const t = objNode.type;
1116
- if (t === 'identifier' || t === 'this' || t === 'super')
2011
+ /**
2012
+ * Extract type info from a class field declaration: `private repo: Repository<User>`.
2013
+ *
2014
+ * Seeds a class-scoped key `ClassName.field` (confidence 0.9) as the primary entry
2015
+ * so that two classes with identically-named fields don't overwrite each other's
2016
+ * typeMap entry (issue #1458). The resolver's `CallerClass.X` fallback (call-resolver.ts
2017
+ * line 110) looks up exactly this key.
2018
+ *
2019
+ * Bare `field` and `this.field` keys are kept at lower confidence (0.6) as fallbacks
2020
+ * for single-class files where the resolver may not have a callerClass context.
2021
+ *
2022
+ * Mirrors the field_definition branch of match_js_type_map in
2023
+ * crates/codegraph-core/src/extractors/javascript.rs.
2024
+ */
2025
+ function handleFieldDefTypeMap(node, typeMap, currentClass) {
2026
+ const nameNode = node.childForFieldName('name') ||
2027
+ node.childForFieldName('property') ||
2028
+ findChild(node, 'property_identifier');
2029
+ if (!nameNode)
2030
+ return;
2031
+ const kind = nameNode.type;
2032
+ if (kind !== 'property_identifier' &&
2033
+ kind !== 'identifier' &&
2034
+ kind !== 'private_property_identifier')
2035
+ return;
2036
+ const typeAnno = findChild(node, 'type_annotation');
2037
+ if (!typeAnno)
2038
+ return;
2039
+ const typeName = extractSimpleTypeName(typeAnno);
2040
+ if (!typeName)
2041
+ return;
2042
+ if (currentClass) {
2043
+ // Primary: class-scoped key prevents cross-class collision (issue #1458).
2044
+ setTypeMapEntry(typeMap, `${currentClass}.${nameNode.text}`, typeName, 0.9);
2045
+ // Fallback: bare keys at lower confidence for single-class files or when
2046
+ // the resolver does not have a callerClass in scope.
2047
+ setTypeMapEntry(typeMap, nameNode.text, typeName, 0.6);
2048
+ setTypeMapEntry(typeMap, `this.${nameNode.text}`, typeName, 0.6);
2049
+ }
2050
+ else {
2051
+ // No enclosing class declaration (e.g. class expression) — use bare keys only.
2052
+ setTypeMapEntry(typeMap, nameNode.text, typeName, 0.9);
2053
+ setTypeMapEntry(typeMap, `this.${nameNode.text}`, typeName, 0.9);
2054
+ }
2055
+ }
2056
+ /**
2057
+ * Phase 8.3d: seed the pts map from object property writes.
2058
+ *
2059
+ * `handlers.auth = authMiddleware` → typeMap.set('handlers.auth', { type: 'authMiddleware', confidence: 0.85 })
2060
+ * `this.logger = new Logger(...)` → typeMap.set('UserService.logger', { type: 'Logger', confidence: 1.0 })
2061
+ * (keyed as ClassName.prop when currentClass is known, to avoid collisions across classes)
2062
+ *
2063
+ * Only simple `obj.prop = identifier` and `this.prop = new Ctor()` writes are tracked
2064
+ * (not chained `a.b.c = x`). BUILTIN_GLOBALS are skipped (e.g. `console.log = fn`).
2065
+ */
2066
+ function handlePropWriteTypeMap(node, typeMap, currentClass) {
2067
+ const lhsN = node.childForFieldName('left');
2068
+ const rhsN = node.childForFieldName('right');
2069
+ if (!lhsN || !rhsN)
2070
+ return;
2071
+ if (lhsN.type !== 'member_expression')
2072
+ return;
2073
+ const obj = lhsN.childForFieldName('object');
2074
+ const prop = lhsN.childForFieldName('property');
2075
+ if (!obj || !prop)
2076
+ return;
2077
+ // Guard: only static property access (property_identifier or identifier), not
2078
+ // computed subscript expressions — consistent with the adjacent fnRefBindings block.
2079
+ if (prop.type !== 'property_identifier' && prop.type !== 'identifier')
2080
+ return;
2081
+ // this.prop = new ClassName(...) — constructor-assigned property type.
2082
+ // Key as ClassName.prop (class-scoped) so two classes with identically-named
2083
+ // properties don't overwrite each other's typeMap entry.
2084
+ if (obj.type === 'this' && rhsN.type === 'new_expression') {
2085
+ const ctorType = extractNewExprTypeName(rhsN);
2086
+ if (ctorType) {
2087
+ const key = currentClass ? `${currentClass}.${prop.text}` : `this.${prop.text}`;
2088
+ setTypeMapEntry(typeMap, key, ctorType, 1.0);
2089
+ }
2090
+ return;
2091
+ }
2092
+ // obj.prop = identifier — existing behaviour (skip chained a.b.c = x and builtins)
2093
+ if (rhsN.type !== 'identifier')
2094
+ return;
2095
+ if (obj.type !== 'identifier')
2096
+ return;
2097
+ const objName = obj.text;
2098
+ if (BUILTIN_GLOBALS.has(objName))
2099
+ return;
2100
+ setTypeMapEntry(typeMap, `${objName}.${prop.text}`, rhsN.text, 0.85);
2101
+ }
2102
+ /**
2103
+ * Phase 8.3e/8.3f: seed composite pts keys from Object.defineProperty / defineProperties.
2104
+ *
2105
+ * `Object.defineProperty(obj, "key", { value: fn })` → typeMap.set('obj.key', fn, 0.85)
2106
+ * `Object.defineProperties(obj, { "k1": { value: v1 } })` → typeMap.set('obj.k1', v1, 0.85)
2107
+ * `Object.defineProperty(obj, "key", { get: getter })` → typeMap.set('getter:this', obj, 0.85)
2108
+ */
2109
+ function handleDefinePropertyTypeMap(node, typeMap) {
2110
+ const fn = node.childForFieldName('function');
2111
+ if (fn?.type !== 'member_expression')
2112
+ return;
2113
+ const fnObj = fn.childForFieldName('object');
2114
+ const fnProp = fn.childForFieldName('property');
2115
+ if (fnObj?.text !== 'Object')
2116
+ return;
2117
+ const method = fnProp?.text;
2118
+ if (method !== 'defineProperty' && method !== 'defineProperties')
2119
+ return;
2120
+ const argsNode = node.childForFieldName('arguments') || findChild(node, 'arguments');
2121
+ if (!argsNode)
2122
+ return;
2123
+ const args = [];
2124
+ for (let i = 0; i < argsNode.childCount; i++) {
2125
+ const n = argsNode.child(i);
2126
+ if (n && n.type !== '(' && n.type !== ')' && n.type !== ',')
2127
+ args.push(n);
2128
+ }
2129
+ if (method === 'defineProperty') {
2130
+ if (args.length < 3)
2131
+ return;
2132
+ const arg0 = args[0], arg1 = args[1], arg2 = args[2];
2133
+ if (arg0.type !== 'identifier')
2134
+ return;
2135
+ if (arg1.type !== 'string')
2136
+ return;
2137
+ const key = arg1.text.replace(/^['"]|['"]$/g, '');
2138
+ if (!key)
2139
+ return;
2140
+ // Phase 8.3e: { value: fn } → obj.key pts to fn
2141
+ const target = findDescriptorValue(arg2);
2142
+ if (target) {
2143
+ setTypeMapEntry(typeMap, `${arg0.text}.${key}`, target, 0.85);
2144
+ }
2145
+ // Phase 8.3f: { get: getter } and/or { set: setter } → this inside each accessor is arg0 (obj)
2146
+ // Key format: '<accessorName>:this' — colon is a reserved separator used only by this phase.
2147
+ // JS identifiers cannot contain ':', so this key never collides with real variable names.
2148
+ for (const accessor of findDescriptorAccessors(arg2)) {
2149
+ setTypeMapEntry(typeMap, `${accessor}:this`, arg0.text, 0.85);
2150
+ }
2151
+ }
2152
+ else {
2153
+ // defineProperties
2154
+ if (args.length < 2)
2155
+ return;
2156
+ const arg0 = args[0], arg1 = args[1];
2157
+ if (arg0.type !== 'identifier')
2158
+ return;
2159
+ if (arg1.type !== 'object')
2160
+ return;
2161
+ for (let i = 0; i < arg1.childCount; i++) {
2162
+ const pair = arg1.child(i);
2163
+ if (pair?.type !== 'pair')
2164
+ continue;
2165
+ const keyN = pair.childForFieldName('key');
2166
+ const valN = pair.childForFieldName('value');
2167
+ if (!keyN || !valN)
2168
+ continue;
2169
+ const key = keyN.type === 'string' ? keyN.text.replace(/^['"]|['"]$/g, '') : keyN.text;
2170
+ const target = findDescriptorValue(valN);
2171
+ if (!target)
2172
+ continue;
2173
+ setTypeMapEntry(typeMap, `${arg0.text}.${key}`, target, 0.85);
2174
+ }
2175
+ }
2176
+ }
2177
+ /** Return the identifier text of the `value` field in a property descriptor object. */
2178
+ function findDescriptorValue(desc) {
2179
+ if (desc.type !== 'object')
2180
+ return undefined;
2181
+ for (let i = 0; i < desc.childCount; i++) {
2182
+ const pair = desc.child(i);
2183
+ if (pair?.type !== 'pair')
2184
+ continue;
2185
+ const key = pair.childForFieldName('key');
2186
+ const val = pair.childForFieldName('value');
2187
+ if (key?.text === 'value' && val?.type === 'identifier')
2188
+ return val.text;
2189
+ }
2190
+ return undefined;
2191
+ }
2192
+ /**
2193
+ * Phase 8.3f: return the identifier texts of all `get` and `set` accessors in a property
2194
+ * descriptor. `{ get: getter, set: setter }` → ['getter', 'setter'].
2195
+ * Returns all accessors so that each one gets a `callerName:this = obj` typeMap entry.
2196
+ */
2197
+ function findDescriptorAccessors(desc) {
2198
+ if (desc.type !== 'object')
2199
+ return [];
2200
+ const result = [];
2201
+ for (let i = 0; i < desc.childCount; i++) {
2202
+ const pair = desc.child(i);
2203
+ if (pair?.type !== 'pair')
2204
+ continue;
2205
+ const key = pair.childForFieldName('key');
2206
+ const val = pair.childForFieldName('value');
2207
+ if ((key?.text === 'get' || key?.text === 'set') && val?.type === 'identifier') {
2208
+ result.push(val.text);
2209
+ }
2210
+ }
2211
+ return result;
2212
+ }
2213
+ /** Seed composite pts keys for each property in a prototype object literal. */
2214
+ function seedProtoProperties(varName, proto, typeMap) {
2215
+ for (let i = 0; i < proto.childCount; i++) {
2216
+ const child = proto.child(i);
2217
+ if (!child)
2218
+ continue;
2219
+ if (child.type === 'shorthand_property_identifier') {
2220
+ setTypeMapEntry(typeMap, `${varName}.${child.text}`, child.text, 0.85);
2221
+ }
2222
+ else if (child.type === 'pair') {
2223
+ const keyN = child.childForFieldName('key');
2224
+ const valN = child.childForFieldName('value');
2225
+ if (!keyN || !valN || valN.type !== 'identifier')
2226
+ continue;
2227
+ const key = keyN.type === 'string' ? keyN.text.replace(/^['"]|['"]$/g, '') : keyN.text;
2228
+ setTypeMapEntry(typeMap, `${varName}.${key}`, valN.text, 0.85);
2229
+ }
2230
+ }
2231
+ }
2232
+ /**
2233
+ * Phase 8.3c: record argument-to-parameter bindings at call sites.
2234
+ *
2235
+ * For each `f(x, y)` where the callee is a simple identifier and an argument
2236
+ * is a simple identifier, emits a ParamBinding so the pts solver can add
2237
+ * constraint: pts(param_i_of_f) ⊇ pts(arg_i). The solver uses the
2238
+ * definitionParams map to resolve the actual parameter names.
2239
+ *
2240
+ * Scope: intra-module only (the solver only materialises constraints for
2241
+ * locally-defined callees, so cross-module calls produce no spurious flow).
2242
+ */
2243
+ function collectParamBindings(node, paramBindings) {
2244
+ const fn = node.childForFieldName('function');
2245
+ const args = node.childForFieldName('arguments') ?? findChild(node, 'arguments');
2246
+ if (fn?.type === 'identifier' && !BUILTIN_GLOBALS.has(fn.text) && args) {
2247
+ let argIdx = 0;
2248
+ for (let i = 0; i < args.childCount; i++) {
2249
+ const child = args.child(i);
2250
+ if (!child)
2251
+ continue;
2252
+ const ct = child.type;
2253
+ if (ct === ',' || ct === '(' || ct === ')')
2254
+ continue;
2255
+ if (ct === 'identifier' && !BUILTIN_GLOBALS.has(child.text)) {
2256
+ paramBindings.push({ callee: fn.text, argIndex: argIdx, argName: child.text });
2257
+ }
2258
+ else if (ct === 'spread_element') {
2259
+ // f(...[a, b]) — inline array literal: expand each element as a direct param binding.
2260
+ const inner = child.childForFieldName('argument') ?? (child.childCount > 1 ? child.child(1) : null);
2261
+ if (inner?.type === 'array') {
2262
+ let elemCount = 0;
2263
+ for (let j = 0; j < inner.childCount; j++) {
2264
+ const elem = inner.child(j);
2265
+ if (!elem)
2266
+ continue;
2267
+ if (elem.type === ',' || elem.type === '[' || elem.type === ']')
2268
+ continue;
2269
+ if (elem.type === 'identifier' && !BUILTIN_GLOBALS.has(elem.text)) {
2270
+ paramBindings.push({
2271
+ callee: fn.text,
2272
+ argIndex: argIdx + elemCount,
2273
+ argName: elem.text,
2274
+ });
2275
+ }
2276
+ elemCount++;
2277
+ }
2278
+ // Advance by the exact number of slots this spread occupies and skip
2279
+ // the unconditional argIdx++ below so that zero-element spreads (...[])
2280
+ // do not shift subsequent argument indices.
2281
+ argIdx += elemCount;
2282
+ continue;
2283
+ }
2284
+ }
2285
+ argIdx++;
2286
+ }
2287
+ }
2288
+ }
2289
+ /** Collection constructors whose argument is treated as an element source. */
2290
+ const COLLECTION_CTOR_SET = new Set(['Set', 'Map']);
2291
+ /**
2292
+ * Phase 8.3e: Extract array-element bindings from `const arr = [fn1, fn2]` patterns.
2293
+ * Emits an ArrayElemBinding for each identifier element in an array literal assigned
2294
+ * to a variable.
2295
+ */
2296
+ function collectArrayElemBindings(node, arrayElemBindings) {
2297
+ const nameN = node.childForFieldName('name');
2298
+ const valueN = node.childForFieldName('value');
2299
+ if (nameN?.type === 'identifier' && valueN?.type === 'array') {
2300
+ let idx = 0;
2301
+ for (let i = 0; i < valueN.childCount; i++) {
2302
+ const elem = valueN.child(i);
2303
+ if (!elem)
2304
+ continue;
2305
+ if (elem.type === ',' || elem.type === '[' || elem.type === ']')
2306
+ continue;
2307
+ if (elem.type === 'identifier' && !BUILTIN_GLOBALS.has(elem.text)) {
2308
+ arrayElemBindings.push({ arrayName: nameN.text, index: idx, elemName: elem.text });
2309
+ }
2310
+ idx++;
2311
+ }
2312
+ }
2313
+ }
2314
+ /**
2315
+ * Phase 8.3e collectors (spread-argument, Array.from, collection-wrap, for-of
2316
+ * bindings), invoked from runContextCollectorWalk:
2317
+ *
2318
+ * - Spread: `f(...arr)` → SpreadArgBinding
2319
+ * - Array.from: `Array.from(src, cb)` → ArrayCallbackBinding
2320
+ * - Collection wrap: `new Set(arr)` / `new Map(arr)` → FnRefBinding lhs=s[*] rhs=arr[*]
2321
+ * - For-of: `for (const x of arr)` → ForOfBinding
2322
+ */
2323
+ function collectSpreadAndArrayFromBindings(node, spreadArgBindings, arrayCallbackBindings) {
2324
+ const fn = node.childForFieldName('function');
2325
+ const argsNode = node.childForFieldName('arguments') ?? findChild(node, 'arguments');
2326
+ // Spread: f(...arr)
2327
+ if (fn?.type === 'identifier' && !BUILTIN_GLOBALS.has(fn.text) && argsNode) {
2328
+ let argIdx = 0;
2329
+ for (let i = 0; i < argsNode.childCount; i++) {
2330
+ const child = argsNode.child(i);
2331
+ if (!child)
2332
+ continue;
2333
+ if (child.type === ',' || child.type === '(' || child.type === ')')
2334
+ continue;
2335
+ if (child.type === 'spread_element') {
2336
+ const spreadTarget = child.childForFieldName('argument') ?? (child.childCount > 1 ? child.child(1) : null);
2337
+ if (spreadTarget?.type === 'identifier' && !BUILTIN_GLOBALS.has(spreadTarget.text)) {
2338
+ spreadArgBindings.push({
2339
+ callee: fn.text,
2340
+ arrayName: spreadTarget.text,
2341
+ startIndex: argIdx,
2342
+ });
2343
+ }
2344
+ }
2345
+ argIdx++;
2346
+ }
2347
+ }
2348
+ // Array.from(source, cb)
2349
+ if (fn?.type === 'member_expression' && argsNode) {
2350
+ const obj = fn.childForFieldName('object');
2351
+ const prop = fn.childForFieldName('property');
2352
+ if (obj?.text === 'Array' && prop?.text === 'from') {
2353
+ const fnArgs = [];
2354
+ for (let i = 0; i < argsNode.childCount; i++) {
2355
+ const child = argsNode.child(i);
2356
+ if (!child)
2357
+ continue;
2358
+ if (child.type === ',' || child.type === '(' || child.type === ')')
2359
+ continue;
2360
+ fnArgs.push(child);
2361
+ }
2362
+ if (fnArgs.length >= 2) {
2363
+ const srcArg = fnArgs[0];
2364
+ const cbArg = fnArgs[1];
2365
+ if (srcArg.type === 'identifier' &&
2366
+ !BUILTIN_GLOBALS.has(srcArg.text) &&
2367
+ cbArg.type === 'identifier' &&
2368
+ !BUILTIN_GLOBALS.has(cbArg.text)) {
2369
+ arrayCallbackBindings.push({ sourceName: srcArg.text, calleeName: cbArg.text });
2370
+ }
2371
+ }
2372
+ }
2373
+ }
2374
+ }
2375
+ /** Collection wrap: `const s = new Set(arr)` or `new Map(arr)` (variable_declarator). */
2376
+ function collectCollectionWrapBinding(node, fnRefBindings) {
2377
+ const nameN = node.childForFieldName('name');
2378
+ const valueN = node.childForFieldName('value');
2379
+ if (nameN?.type === 'identifier' && valueN?.type === 'new_expression') {
2380
+ const ctor = valueN.childForFieldName('constructor');
2381
+ const args = valueN.childForFieldName('arguments');
2382
+ if (ctor && COLLECTION_CTOR_SET.has(ctor.text) && args) {
2383
+ for (let i = 0; i < args.childCount; i++) {
2384
+ const arg = args.child(i);
2385
+ if (!arg || arg.type === '(' || arg.type === ')')
2386
+ continue;
2387
+ if (arg.type === 'identifier' && !BUILTIN_GLOBALS.has(arg.text)) {
2388
+ fnRefBindings.push({ lhs: `${nameN.text}[*]`, rhs: `${arg.text}[*]` });
2389
+ break;
2390
+ }
2391
+ }
2392
+ }
2393
+ }
2394
+ }
2395
+ /** For-of: `for (const x of arr)` (for_in_statement with an `of` keyword). */
2396
+ function collectForOfBinding(node, enclosingFunc, forOfBindings) {
2397
+ let isForOf = false;
2398
+ for (let i = 0; i < node.childCount; i++) {
2399
+ if (node.child(i)?.text === 'of') {
2400
+ isForOf = true;
2401
+ break;
2402
+ }
2403
+ }
2404
+ if (!isForOf)
2405
+ return;
2406
+ const right = node.childForFieldName('right');
2407
+ if (right?.type !== 'identifier' || BUILTIN_GLOBALS.has(right.text))
2408
+ return;
2409
+ const left = node.childForFieldName('left');
2410
+ let varName = null;
2411
+ if (left?.type === 'identifier') {
2412
+ varName = left.text;
2413
+ }
2414
+ else if (left) {
2415
+ for (let i = 0; i < left.childCount; i++) {
2416
+ const lc = left.child(i);
2417
+ if (lc?.type === 'variable_declarator') {
2418
+ const nc = lc.childForFieldName('name');
2419
+ if (nc?.type === 'identifier') {
2420
+ varName = nc.text;
2421
+ break;
2422
+ }
2423
+ }
2424
+ else if (lc?.type === 'identifier' &&
2425
+ lc.text !== 'const' &&
2426
+ lc.text !== 'let' &&
2427
+ lc.text !== 'var') {
2428
+ varName = lc.text;
2429
+ break;
2430
+ }
2431
+ }
2432
+ }
2433
+ if (varName && !BUILTIN_GLOBALS.has(varName)) {
2434
+ forOfBindings.push({ varName, sourceName: right.text, enclosingFunc });
2435
+ }
2436
+ }
2437
+ /**
2438
+ * Phase 8.3f: record object-destructuring rest-parameter bindings from function definitions.
2439
+ *
2440
+ * For each `function f({ a, ...rest })` (or arrow/function-expression equivalent),
2441
+ * records { callee: 'f', restName: 'rest', argIndex: N }. Also covers class methods
2442
+ * (`callee: 'ClassName.method'`) and object-literal methods (`callee: 'method'`).
2443
+ * The edge builder uses these to seed typeMap[rest] = { type: argName } when f(obj)
2444
+ * is called with an identifier, enabling `rest.method()` calls to resolve.
2445
+ */
2446
+ function collectObjectRestParams(node, t, currentClass, bindings) {
2447
+ let fnName = null;
2448
+ let paramsNode = null;
2449
+ if (t === 'function_declaration' || t === 'generator_function_declaration') {
2450
+ const nameN = node.childForFieldName('name');
2451
+ if (nameN?.type === 'identifier')
2452
+ fnName = nameN.text;
2453
+ paramsNode = node.childForFieldName('parameters') ?? findChild(node, 'formal_parameters');
2454
+ }
2455
+ else if (t === 'variable_declarator') {
2456
+ const nameN = node.childForFieldName('name');
2457
+ const valueN = node.childForFieldName('value');
2458
+ if (nameN?.type === 'identifier' && valueN) {
2459
+ const vt = valueN.type;
2460
+ if (vt === 'arrow_function' || vt === 'function_expression' || vt === 'generator_function') {
2461
+ fnName = nameN.text;
2462
+ paramsNode =
2463
+ valueN.childForFieldName('parameters') ?? findChild(valueN, 'formal_parameters');
2464
+ }
2465
+ }
2466
+ }
2467
+ else if (t === 'method_definition') {
2468
+ // class method: `class Foo { bar({ a, ...rest }) {} }`
2469
+ // object-literal shorthand method: `{ bar({ a, ...rest }) {} }`
2470
+ const nameN = node.childForFieldName('name');
2471
+ if (nameN) {
2472
+ fnName = currentClass ? `${currentClass}.${nameN.text}` : nameN.text;
2473
+ paramsNode = node.childForFieldName('parameters') ?? findChild(node, 'formal_parameters');
2474
+ }
2475
+ }
2476
+ else if (t === 'pair') {
2477
+ // object-literal method: `{ bar: function({ a, ...rest }) {} }`
2478
+ // Skip computed property keys (e.g. `{ [Symbol.iterator]: function({ ...rest }) {} }`)
2479
+ // because `callee: '[Symbol.iterator]'` can never match a paramBinding callee.
2480
+ const keyN = node.childForFieldName('key');
2481
+ const valueN = node.childForFieldName('value');
2482
+ if (keyN && valueN && keyN.type !== 'computed_property_name') {
2483
+ const vt = valueN.type;
2484
+ if (vt === 'arrow_function' || vt === 'function_expression' || vt === 'generator_function') {
2485
+ fnName = keyN.type === 'string' ? keyN.text.slice(1, -1) : keyN.text;
2486
+ paramsNode =
2487
+ valueN.childForFieldName('parameters') ?? findChild(valueN, 'formal_parameters');
2488
+ }
2489
+ }
2490
+ }
2491
+ if (fnName && paramsNode) {
2492
+ let paramIdx = 0;
2493
+ for (let i = 0; i < paramsNode.childCount; i++) {
2494
+ const child = paramsNode.child(i);
2495
+ if (!child)
2496
+ continue;
2497
+ const ct = child.type;
2498
+ if (ct === ',' || ct === '(' || ct === ')')
2499
+ continue;
2500
+ if (ct === 'object_pattern') {
2501
+ for (let j = 0; j < child.childCount; j++) {
2502
+ const inner = child.child(j);
2503
+ if (!inner)
2504
+ continue;
2505
+ if (inner.type === 'rest_pattern' || inner.type === 'rest_element') {
2506
+ // rest_pattern node: `...identifier` — the identifier is at child index 1
2507
+ const restId = inner.child(1) ?? inner.childForFieldName('name');
2508
+ if (restId?.type === 'identifier') {
2509
+ bindings.push({ callee: fnName, restName: restId.text, argIndex: paramIdx });
2510
+ }
2511
+ }
2512
+ }
2513
+ }
2514
+ paramIdx++;
2515
+ }
2516
+ }
2517
+ }
2518
+ /**
2519
+ * Phase 8.3f: collect object-property bindings from object literals.
2520
+ *
2521
+ * `const obj = { e4 }` → `{ objectName: "obj", propName: "e4", valueName: "e4" }`
2522
+ * `const obj = { e1: fn }` → `{ objectName: "obj", propName: "e1", valueName: "fn" }`
2523
+ *
2524
+ * Only tracks shorthand and `key: identifier` pairs; skips function literals.
2525
+ */
2526
+ function collectObjectPropBindings(node, bindings) {
2527
+ const nameN = node.childForFieldName('name');
2528
+ const valueN = node.childForFieldName('value');
2529
+ if (nameN?.type === 'identifier' && valueN?.type === 'object') {
2530
+ const objectName = nameN.text;
2531
+ for (let i = 0; i < valueN.childCount; i++) {
2532
+ const child = valueN.child(i);
2533
+ if (!child)
2534
+ continue;
2535
+ if (child.type === 'shorthand_property_identifier') {
2536
+ bindings.push({ objectName, propName: child.text, valueName: child.text });
2537
+ }
2538
+ else if (child.type === 'pair') {
2539
+ const keyN = child.childForFieldName('key');
2540
+ const valN = child.childForFieldName('value');
2541
+ if (keyN?.type === 'property_identifier' &&
2542
+ valN?.type === 'identifier' &&
2543
+ !BUILTIN_GLOBALS.has(valN.text)) {
2544
+ bindings.push({ objectName, propName: keyN.text, valueName: valN.text });
2545
+ }
2546
+ }
2547
+ }
2548
+ }
2549
+ }
2550
+ function extractReceiverName(objNode) {
2551
+ if (!objNode)
2552
+ return undefined;
2553
+ const t = objNode.type;
2554
+ if (t === 'identifier' || t === 'this' || t === 'super')
1117
2555
  return objNode.text;
2556
+ // `(new Foo(...)).method()` — extract the constructor name so the resolver can
2557
+ // look up `Foo.method` directly without relying on a text-based regex heuristic.
2558
+ if (t === 'new_expression') {
2559
+ const name = extractNewExprTypeName(objNode);
2560
+ if (name)
2561
+ return name;
2562
+ }
2563
+ if (t === 'parenthesized_expression') {
2564
+ // Only one level of parentheses is unwrapped here. Doubly-nested parens
2565
+ // (e.g. `((new Dog())).bark()`) and cast expressions inside parens
2566
+ // (e.g. `(new Dog() as Animal).bark()`) fall through to raw-text handling
2567
+ // below and are caught by the regex fallback in call-resolver.ts.
2568
+ for (let i = 0; i < objNode.childCount; i++) {
2569
+ const child = objNode.child(i);
2570
+ if (child?.type === 'new_expression') {
2571
+ const name = extractNewExprTypeName(child);
2572
+ if (name)
2573
+ return name;
2574
+ }
2575
+ }
2576
+ }
1118
2577
  return objNode.text;
1119
2578
  }
1120
2579
  function extractCallInfo(fn, callNode) {
@@ -1320,12 +2779,23 @@ function firstArgIsStringLiteral(argsNode) {
1320
2779
  * member-expr args are only emitted when the first argument is a string
1321
2780
  * literal route path — matching Express/router shape and skipping
1322
2781
  * `cache.get(user.id)`-style calls.
2782
+ *
2783
+ * `.call()` / `.apply()` / `.bind()` — the first arg is the `this` context (not a callback of
2784
+ * the enclosing function) and subsequent args flow into the delegated function's parameters.
2785
+ * Emitting them here would produce false-positive edges from the *calling* function.
2786
+ * This-rebinding (fn::this → ctx) is handled separately by extractThisCallBindingsWalk.
1323
2787
  */
1324
2788
  function extractCallbackReferenceCalls(callNode) {
1325
2789
  const args = callNode.childForFieldName('arguments') || findChild(callNode, 'arguments');
1326
2790
  if (!args)
1327
2791
  return [];
1328
2792
  const calleeName = extractCalleeName(callNode);
2793
+ // .call() / .apply() / .bind() — the first arg is the `this` context (not a callback of
2794
+ // the enclosing function) and subsequent args flow into the delegated function's parameters.
2795
+ // Emitting them here would produce false-positive edges from the *calling* function.
2796
+ // This-rebinding (fn::this → ctx) is handled separately by extractThisCallBindingsWalk.
2797
+ if (calleeName === 'call' || calleeName === 'apply' || calleeName === 'bind')
2798
+ return [];
1329
2799
  let memberExprArgsAllowed = calleeName !== null && CALLBACK_ACCEPTING_CALLEES.has(calleeName);
1330
2800
  if (memberExprArgsAllowed && calleeName !== null && HTTP_VERB_CALLEES.has(calleeName)) {
1331
2801
  // HTTP verbs require a string-literal route path to be treated as a
@@ -1353,6 +2823,126 @@ function extractCallbackReferenceCalls(callNode) {
1353
2823
  }
1354
2824
  return result;
1355
2825
  }
2826
+ /**
2827
+ * Collect, from a call_expression node:
2828
+ * - `this(args)` call expressions → `{name: 'this', ...}` entries in `calls`
2829
+ * (where `this` is used as a function, not as a receiver)
2830
+ * - `fn.call(namedCtx, ...)` / `fn.apply(namedCtx, ...)` bindings →
2831
+ * `{ callee: 'fn', thisArg: 'namedCtx' }` entries in `thisCallBindings`
2832
+ */
2833
+ function collectThisCallAndBindings(node, calls, thisCallBindings) {
2834
+ const fn = node.childForFieldName('function');
2835
+ if (fn?.type === 'this') {
2836
+ calls.push({ name: 'this', line: nodeStartLine(node) });
2837
+ }
2838
+ else if (fn?.type === 'member_expression') {
2839
+ const obj = fn.childForFieldName('object');
2840
+ const prop = fn.childForFieldName('property');
2841
+ if (obj?.type === 'identifier' &&
2842
+ prop &&
2843
+ (prop.text === 'call' || prop.text === 'apply') &&
2844
+ !BUILTIN_GLOBALS.has(obj.text)) {
2845
+ const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
2846
+ if (args) {
2847
+ for (let i = 0; i < args.childCount; i++) {
2848
+ const child = args.child(i);
2849
+ if (!child)
2850
+ continue;
2851
+ const t = child.type;
2852
+ if (t === '(' || t === ')' || t === ',')
2853
+ continue;
2854
+ // First real argument: only bind if it's a plain identifier
2855
+ if (t === 'identifier' &&
2856
+ !BUILTIN_GLOBALS.has(child.text) &&
2857
+ child.text !== 'undefined' &&
2858
+ child.text !== 'null') {
2859
+ thisCallBindings.push({ callee: obj.text, thisArg: child.text });
2860
+ }
2861
+ break;
2862
+ }
2863
+ }
2864
+ }
2865
+ }
2866
+ }
2867
+ /**
2868
+ * Single-pass collector walk: one DFS that dispatches each node to every
2869
+ * collector interested in its type.
2870
+ *
2871
+ * This replaces what had grown to ten independent full-tree traversals (one
2872
+ * per collector). On WASM trees every node access (`child(i)`, `.type`,
2873
+ * `childForFieldName`) marshals through the JS↔WASM boundary, so traversal
2874
+ * count — not collector work — dominated extraction cost: the accumulated
2875
+ * per-collector walks made extraction ~2.4× slower between v3.11.2 and
2876
+ * v3.12.0 (7.5 → 17.7 ms/file on codegraph's own corpus).
2877
+ *
2878
+ * Collectors with bespoke traversal semantics stay separate:
2879
+ * - extractConstantsWalk / extractDestructuredBindingsWalk prune function
2880
+ * scopes and unwrap export statements on the way down;
2881
+ * - extractReturnTypeMapWalk / extractTypeMapWalk / extractSpreadForOfWalk /
2882
+ * extractObjectRestParamBindingsWalk thread enclosing-class context with
2883
+ * per-walk reset rules that intentionally differ (see each walk's comments).
2884
+ */
2885
+ function runCollectorWalk(rootNode, targets) {
2886
+ const walk = (node, depth, inDynamicImport) => {
2887
+ if (depth >= MAX_WALK_DEPTH)
2888
+ return;
2889
+ let childInDynamicImport = inDynamicImport;
2890
+ switch (node.type) {
2891
+ case 'call_expression': {
2892
+ // Matched import() calls suppress *dynamic-import* collection in their
2893
+ // argument subtree (mirrors the old walk's early return) while leaving
2894
+ // the subtree visible to every other collector. The !inDynamicImport
2895
+ // check runs first so nested import() calls are neither collected nor
2896
+ // re-matched.
2897
+ if (targets.imports && !inDynamicImport && collectDynamicImport(node, targets.imports)) {
2898
+ childInDynamicImport = true;
2899
+ }
2900
+ if (targets.calls && targets.thisCallBindings) {
2901
+ collectThisCallAndBindings(node, targets.calls, targets.thisCallBindings);
2902
+ }
2903
+ collectParamBindings(node, targets.paramBindings);
2904
+ collectDefinePropertyReceiver(node, targets.definePropertyReceivers);
2905
+ break;
2906
+ }
2907
+ case 'variable_declarator':
2908
+ collectArrayElemBindings(node, targets.arrayElemBindings);
2909
+ collectObjectPropBindings(node, targets.objectPropBindings);
2910
+ break;
2911
+ case 'expression_statement': {
2912
+ const expr = node.child(0);
2913
+ if (expr?.type === 'assignment_expression') {
2914
+ const lhs = expr.childForFieldName('left');
2915
+ const rhs = expr.childForFieldName('right');
2916
+ if (lhs && rhs) {
2917
+ handlePrototypeAssignment(lhs, rhs, targets.definitions, targets.typeMap);
2918
+ if (targets.funcPropDefs)
2919
+ handleFuncPropAssignment(lhs, rhs, targets.funcPropDefs);
2920
+ }
2921
+ }
2922
+ break;
2923
+ }
2924
+ case 'new_expression': {
2925
+ const name = extractNewExprTypeName(node);
2926
+ if (name)
2927
+ targets.newExpressions.push(name);
2928
+ break;
2929
+ }
2930
+ case 'field_definition':
2931
+ case 'public_field_definition':
2932
+ if (targets.classMemberDefs)
2933
+ handleFieldDef(node, targets.classMemberDefs);
2934
+ break;
2935
+ case 'class_static_block':
2936
+ if (targets.classMemberDefs)
2937
+ handleStaticBlock(node, targets.classMemberDefs);
2938
+ break;
2939
+ }
2940
+ for (let i = 0; i < node.childCount; i++) {
2941
+ walk(node.child(i), depth + 1, childInDynamicImport);
2942
+ }
2943
+ };
2944
+ walk(rootNode, 0, false);
2945
+ }
1356
2946
  function findAnonymousCallback(argsNode) {
1357
2947
  for (let i = 0; i < argsNode.childCount; i++) {
1358
2948
  const child = argsNode.child(i);
@@ -1409,7 +2999,7 @@ const EVENT_METHODS = new Set(['on', 'once', 'addEventListener', 'addListener'])
1409
2999
  function extractCallbackDefinition(callNode, fn) {
1410
3000
  if (!fn)
1411
3001
  fn = callNode.childForFieldName('function');
1412
- if (!fn || fn.type !== 'member_expression')
3002
+ if (fn?.type !== 'member_expression')
1413
3003
  return null;
1414
3004
  const prop = fn.childForFieldName('property');
1415
3005
  if (!prop)
@@ -1443,7 +3033,7 @@ function extractCallbackDefinition(callNode, fn) {
1443
3033
  // Express: app.get('/path', callback)
1444
3034
  if (EXPRESS_METHODS.has(method)) {
1445
3035
  const strArg = findFirstStringArg(args);
1446
- if (!strArg || !strArg.startsWith('/'))
3036
+ if (!strArg?.startsWith('/'))
1447
3037
  return null;
1448
3038
  const cb = findAnonymousCallback(args);
1449
3039
  if (!cb)
@@ -1485,7 +3075,7 @@ function extractSuperclass(heritage) {
1485
3075
  }
1486
3076
  return null;
1487
3077
  }
1488
- const JS_CLASS_TYPES = ['class_declaration', 'class'];
3078
+ const JS_CLASS_TYPES = ['class_declaration', 'abstract_class_declaration', 'class'];
1489
3079
  function findParentClass(node) {
1490
3080
  return findParentNode(node, JS_CLASS_TYPES);
1491
3081
  }
@@ -1529,7 +3119,7 @@ function extractDynamicImportNames(callNode) {
1529
3119
  if (current && current.type === 'await_expression')
1530
3120
  current = current.parent;
1531
3121
  // We should now be at a variable_declarator (or not, if standalone import())
1532
- if (!current || current.type !== 'variable_declarator')
3122
+ if (current?.type !== 'variable_declarator')
1533
3123
  return [];
1534
3124
  const nameNode = current.childForFieldName('name');
1535
3125
  if (!nameNode)
@@ -1573,4 +3163,155 @@ function extractDynamicImportNames(callNode) {
1573
3163
  }
1574
3164
  return [];
1575
3165
  }
3166
+ // ── Phase 8.X: Prototype-based method extraction ────────────────────────────
3167
+ /**
3168
+ * Walk the AST and extract prototype-based method definitions and aliases.
3169
+ *
3170
+ * Handles three patterns:
3171
+ * 1. `Foo.prototype.bar = function(){...}` — emits Foo.bar as method definition
3172
+ * 2. `Foo.prototype.bar = identifier` — sets typeMap['Foo.bar'] = { type: identifier }
3173
+ * 3. `Foo.prototype = { bar: fn, ... }` — emits defs and typeMap entries per property
3174
+ *
3175
+ * Emitting definitions under the canonical `ClassName.methodName` name lets the
3176
+ * existing typeMap-based call resolver find them when a typed receiver dispatches
3177
+ * `instance.method()` (lookup.byName('C.foo') in resolveByMethodOrGlobal).
3178
+ *
3179
+ * typeMap entries for identifier aliases (`Foo.bar → { type: 'someId' }`) are
3180
+ * consumed by the prototype-alias fallback added to resolveByMethodOrGlobal.
3181
+ */
3182
+ // Prototype-method assignments (`Foo.prototype.bar = fn`) are collected inline
3183
+ // in runCollectorWalk's expression_statement case via handlePrototypeAssignment.
3184
+ /**
3185
+ * Handle an assignment_expression that may be a prototype assignment.
3186
+ *
3187
+ * Matches:
3188
+ * - `Foo.prototype.bar = rhs` (lhs ends in .prototype.bar)
3189
+ * - `Foo.prototype = { ... }` (lhs ends in .prototype, rhs is object literal)
3190
+ */
3191
+ function handlePrototypeAssignment(lhs, rhs, definitions, typeMap) {
3192
+ if (lhs.type !== 'member_expression')
3193
+ return;
3194
+ const lhsObj = lhs.childForFieldName('object');
3195
+ const lhsProp = lhs.childForFieldName('property');
3196
+ if (!lhsObj || !lhsProp)
3197
+ return;
3198
+ // Pattern 1: `Foo.prototype.bar = rhs`
3199
+ // lhs.object is `Foo.prototype` (member_expression), lhs.property is `bar`
3200
+ if (lhsObj.type === 'member_expression' &&
3201
+ (lhsProp.type === 'property_identifier' || lhsProp.type === 'identifier')) {
3202
+ const protoObj = lhsObj.childForFieldName('object');
3203
+ const protoProp = lhsObj.childForFieldName('property');
3204
+ if (protoObj?.type === 'identifier' &&
3205
+ protoProp?.text === 'prototype' &&
3206
+ !BUILTIN_GLOBALS.has(protoObj.text)) {
3207
+ emitPrototypeMethod(protoObj.text, lhsProp.text, rhs, definitions, typeMap);
3208
+ }
3209
+ return;
3210
+ }
3211
+ // Pattern 2: `Foo.prototype = { bar: fn, ... }`
3212
+ // lhs.object is `Foo` (identifier), lhs.property is `prototype`
3213
+ if (lhsObj.type === 'identifier' &&
3214
+ lhsProp.text === 'prototype' &&
3215
+ !BUILTIN_GLOBALS.has(lhsObj.text) &&
3216
+ rhs.type === 'object') {
3217
+ extractPrototypeObjectLiteral(lhsObj.text, rhs, definitions, typeMap);
3218
+ }
3219
+ }
3220
+ /** Emit one prototype method definition or typeMap alias for `ClassName.methodName = rhs`. */
3221
+ function emitPrototypeMethod(className, methodName, rhs, definitions, typeMap) {
3222
+ const fullName = `${className}.${methodName}`;
3223
+ if (rhs.type === 'function_expression' || rhs.type === 'arrow_function') {
3224
+ const params = extractParameters(rhs);
3225
+ definitions.push({
3226
+ name: fullName,
3227
+ kind: 'method',
3228
+ line: nodeStartLine(rhs),
3229
+ endLine: nodeEndLine(rhs),
3230
+ children: params.length > 0 ? params : undefined,
3231
+ });
3232
+ }
3233
+ else if (rhs.type === 'identifier' && !BUILTIN_GLOBALS.has(rhs.text)) {
3234
+ // Prototype alias: `A.prototype.t = f` → typeMap['A.t'] = { type: 'f' }
3235
+ // Consumed by the prototype-alias fallback in resolveByMethodOrGlobal.
3236
+ setTypeMapEntry(typeMap, fullName, rhs.text, 0.9);
3237
+ }
3238
+ }
3239
+ /**
3240
+ * Extract function-as-object property method definitions.
3241
+ *
3242
+ * Handles `fn.method = function() {}` and `fn.method = () => {}` patterns.
3243
+ * Emits a `method` definition named `fn.method` so that:
3244
+ * 1. `findCaller` attributes calls inside the body to `fn.method`
3245
+ * 2. `resolveByMethodOrGlobal` resolves `this.other()` inside `fn.method` to `fn.other`
3246
+ *
3247
+ * Excludes BUILTIN_GLOBALS objects and `.prototype` (handled by extractPrototypeMethodsWalk).
3248
+ */
3249
+ // Function-as-object-property assignments (`fn.method = function(){}`) are
3250
+ // collected inline in runCollectorWalk's expression_statement case (walk path
3251
+ // only — the query path captures them via the `assign_left`/`assign_right`
3252
+ // query pattern in dispatchQueryMatch).
3253
+ function handleFuncPropAssignment(lhs, rhs, definitions) {
3254
+ if (lhs.type !== 'member_expression')
3255
+ return;
3256
+ if (rhs.type !== 'function_expression' && rhs.type !== 'arrow_function')
3257
+ return;
3258
+ const obj = lhs.childForFieldName('object');
3259
+ const prop = lhs.childForFieldName('property');
3260
+ if (!obj || !prop)
3261
+ return;
3262
+ if (obj.type !== 'identifier')
3263
+ return;
3264
+ if (prop.type !== 'property_identifier' && prop.type !== 'identifier')
3265
+ return;
3266
+ if (BUILTIN_GLOBALS.has(obj.text))
3267
+ return;
3268
+ if (prop.text === 'prototype')
3269
+ return;
3270
+ const params = extractParameters(rhs);
3271
+ definitions.push({
3272
+ name: `${obj.text}.${prop.text}`,
3273
+ kind: 'method',
3274
+ line: nodeStartLine(rhs),
3275
+ endLine: nodeEndLine(rhs),
3276
+ children: params.length > 0 ? params : undefined,
3277
+ });
3278
+ }
3279
+ /** Iterate over an object literal assigned to `Foo.prototype` and emit defs/aliases. */
3280
+ function extractPrototypeObjectLiteral(className, objNode, definitions, typeMap) {
3281
+ for (let i = 0; i < objNode.childCount; i++) {
3282
+ const child = objNode.child(i);
3283
+ if (!child)
3284
+ continue;
3285
+ if (child.type === 'method_definition') {
3286
+ // Shorthand method: `Foo.prototype = { bar() {} }`
3287
+ const nameNode = child.childForFieldName('name');
3288
+ if (nameNode) {
3289
+ definitions.push({
3290
+ name: `${className}.${nameNode.text}`,
3291
+ kind: 'method',
3292
+ line: nodeStartLine(child),
3293
+ endLine: nodeEndLine(child),
3294
+ });
3295
+ }
3296
+ continue;
3297
+ }
3298
+ if (child.type === 'shorthand_property_identifier') {
3299
+ // ES6 shorthand: `Foo.prototype = { bar }` → alias typeMap['Foo.bar'] = { type: 'bar' }
3300
+ if (!BUILTIN_GLOBALS.has(child.text)) {
3301
+ setTypeMapEntry(typeMap, `${className}.${child.text}`, child.text, 0.9);
3302
+ }
3303
+ continue;
3304
+ }
3305
+ if (child.type !== 'pair')
3306
+ continue;
3307
+ const keyNode = child.childForFieldName('key');
3308
+ const valueNode = child.childForFieldName('value');
3309
+ if (!keyNode || !valueNode)
3310
+ continue;
3311
+ const methodName = keyNode.type === 'string' ? keyNode.text.replace(/['"]/g, '') : keyNode.text;
3312
+ if (!methodName)
3313
+ continue;
3314
+ emitPrototypeMethod(className, methodName, valueNode, definitions, typeMap);
3315
+ }
3316
+ }
1576
3317
  //# sourceMappingURL=javascript.js.map