@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
@@ -152,10 +152,13 @@ interface WasmExtractResult {
152
152
  // Shared patterns for all JS/TS/TSX (class_declaration excluded — name type differs)
153
153
  const COMMON_QUERY_PATTERNS: string[] = [
154
154
  '(function_declaration name: (identifier) @fn_name) @fn_node',
155
+ '(generator_function_declaration name: (identifier) @fn_name) @fn_node',
155
156
  '(variable_declarator name: (identifier) @varfn_name value: (arrow_function) @varfn_value)',
156
157
  '(variable_declarator name: (identifier) @varfn_name value: (function_expression) @varfn_value)',
158
+ '(variable_declarator name: (identifier) @varfn_name value: (generator_function) @varfn_value)',
157
159
  '(method_definition name: (property_identifier) @meth_name) @meth_node',
158
160
  '(method_definition name: (private_property_identifier) @meth_name) @meth_node',
161
+ '(method_definition name: (computed_property_name) @meth_name) @meth_node',
159
162
  '(import_statement source: (string) @imp_source) @imp_node',
160
163
  '(export_statement) @exp_node',
161
164
  '(call_expression function: (identifier) @callfn_name) @callfn_node',
@@ -166,12 +169,20 @@ const COMMON_QUERY_PATTERNS: string[] = [
166
169
  '(expression_statement (assignment_expression left: (member_expression) @assign_left right: (_) @assign_right)) @assign_node',
167
170
  ];
168
171
 
169
- // JS: class name is (identifier)
170
- const JS_CLASS_PATTERN: string = '(class_declaration name: (identifier) @cls_name) @cls_node';
172
+ // JS: class name is (identifier) — declarations and expressions
173
+ const JS_CLASS_PATTERNS: string[] = [
174
+ '(class_declaration name: (identifier) @cls_name) @cls_node',
175
+ // class expressions: `return class Foo extends Bar { ... }` or `const X = class Foo { ... }`
176
+ '(class name: (identifier) @cls_name) @cls_node',
177
+ ];
171
178
 
172
179
  // TS/TSX: class name is (type_identifier), plus interface and type alias
180
+ // abstract_class_declaration is a separate node type in tree-sitter-typescript
173
181
  const TS_EXTRA_PATTERNS: string[] = [
174
182
  '(class_declaration name: (type_identifier) @cls_name) @cls_node',
183
+ '(abstract_class_declaration name: (type_identifier) @cls_name) @cls_node',
184
+ // class expressions: `return class Foo extends Bar { ... }`
185
+ '(class name: (type_identifier) @cls_name) @cls_node',
175
186
  '(interface_declaration name: (type_identifier) @iface_name) @iface_node',
176
187
  '(type_alias_declaration name: (type_identifier) @type_name) @type_node',
177
188
  ];
@@ -202,7 +213,7 @@ async function doLoadLanguage(entry: LanguageRegistryEntry): Promise<void> {
202
213
  const isTS = entry.id === 'typescript' || entry.id === 'tsx';
203
214
  const patterns = isTS
204
215
  ? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
205
- : [...COMMON_QUERY_PATTERNS, JS_CLASS_PATTERN];
216
+ : [...COMMON_QUERY_PATTERNS, ...JS_CLASS_PATTERNS];
206
217
  _queryCache.set(entry.id, new Query(lang, patterns.join('\n')));
207
218
  }
208
219
  } catch (e: unknown) {
@@ -211,7 +222,9 @@ async function doLoadLanguage(entry: LanguageRegistryEntry): Promise<void> {
211
222
  file: entry.grammarFile,
212
223
  cause: e as Error,
213
224
  });
214
- warn(
225
+ const isEnoent = (e as NodeJS.ErrnoException).code === 'ENOENT';
226
+ const log = isEnoent ? debug : warn;
227
+ log(
215
228
  `${entry.id} parser failed to initialize: ${(e as Error).message}. ${entry.id} files will be skipped.`,
216
229
  );
217
230
  _cachedParsers!.set(entry.id, null);
@@ -455,7 +468,7 @@ export function getInstalledWasmExtensions(): Set<string> {
455
468
  * Lowercase file extensions covered by the native Rust addon.
456
469
  *
457
470
  * Mirrors `LanguageKind::from_extension` in
458
- * `crates/codegraph-core/src/parser_registry.rs`. Used to classify why the
471
+ * `crates/codegraph-core/src/domain/parser.rs`. Used to classify why the
459
472
  * native orchestrator dropped a file: extensions outside this set are a
460
473
  * legitimate parser limit (no Rust extractor exists), while extensions inside
461
474
  * it indicate a real native bug (parse/read/extract failure).
@@ -684,6 +697,24 @@ function patchTypeMap(r: any): void {
684
697
  }
685
698
  }
686
699
 
700
+ /** Normalize native returnTypeMap array to a Map instance, keeping highest-confidence entry per key. */
701
+ function patchReturnTypeMap(r: any): void {
702
+ if (!r.returnTypeMap || r.returnTypeMap instanceof Map) return;
703
+ const map = new Map<string, TypeMapEntry>();
704
+ for (const e of r.returnTypeMap as Array<{
705
+ name: string;
706
+ typeName: string;
707
+ confidence?: number;
708
+ }>) {
709
+ const conf = e.confidence ?? 1.0;
710
+ const existing = map.get(e.name);
711
+ if (!existing || conf > existing.confidence) {
712
+ map.set(e.name, { type: e.typeName, confidence: conf });
713
+ }
714
+ }
715
+ r.returnTypeMap = map.size > 0 ? map : new Map();
716
+ }
717
+
687
718
  /** Wrap bindingType into binding object for dataflow argFlows and mutations. */
688
719
  function patchDataflow(dataflow: any): void {
689
720
  if (dataflow.argFlows) {
@@ -706,6 +737,7 @@ function patchNativeResult(r: any): ExtractorOutput {
706
737
  if (r.definitions) patchDefinitions(r.definitions);
707
738
  if (r.imports) patchImports(r.imports);
708
739
  patchTypeMap(r);
740
+ patchReturnTypeMap(r);
709
741
  if (r.dataflow) patchDataflow(r.dataflow);
710
742
 
711
743
  return r;
@@ -1127,6 +1159,7 @@ async function backfillTypeMapBatch(
1127
1159
  async function parseFilesWasm(
1128
1160
  filePaths: string[],
1129
1161
  rootDir: string,
1162
+ analysis: WorkerAnalysisOpts = FULL_ANALYSIS,
1130
1163
  ): Promise<Map<string, ExtractorOutput>> {
1131
1164
  const result = new Map<string, ExtractorOutput>();
1132
1165
  const pool = getWasmWorkerPool();
@@ -1139,7 +1172,7 @@ async function parseFilesWasm(
1139
1172
  warn(`Skipping ${path.relative(rootDir, filePath)}: ${(err as Error).message}`);
1140
1173
  continue;
1141
1174
  }
1142
- const output = await pool.parse(filePath, code, FULL_ANALYSIS);
1175
+ const output = await pool.parse(filePath, code, analysis);
1143
1176
  if (output) {
1144
1177
  const relPath = path.relative(rootDir, filePath).split(path.sep).join('/');
1145
1178
  result.set(relPath, output);
@@ -1151,12 +1184,25 @@ async function parseFilesWasm(
1151
1184
  /**
1152
1185
  * Files at or below this count use the inline parse path (no worker spawn).
1153
1186
  *
1154
- * Sized for typical engine-parity drops: a handful of fixture files in one
1155
- * or two languages (the recurring HCL case is 4 files). Above this, the
1156
- * worker-pool's IPC + crash-isolation cost (#965) is amortized over enough
1157
- * parse work to be worth paying; below it, the ~1–2s cold-start dominates.
1187
+ * The worker pool exists for crash safety (#965): exotic (non-required) WASM
1188
+ * grammars can trigger uncatchable V8 fatal errors that would kill the main
1189
+ * process. Running them in a worker means only the worker dies; the pool
1190
+ * detects the exit, skips the file, respawns, and continues.
1191
+ *
1192
+ * JS/TS/TSX are required-tier grammars — they have never triggered the V8
1193
+ * fatal crash class and are safe to run inline. The primary hot caller
1194
+ * (this/super dispatch post-pass) exclusively handles JS/TS/TSX files and
1195
+ * measured ~55–64ms/file through the pool vs ~8–10ms/file inline (#1435);
1196
+ * IPC overhead scales linearly with file count, not amortised.
1197
+ *
1198
+ * The threshold is set high enough to keep typical this-dispatch batches
1199
+ * (≤ 18 files on the codegraph corpus) on the inline path, while still
1200
+ * routing truly large exotic-language drops (rare; typical HCL case is 4
1201
+ * files) through the pool for crash isolation. Exotic-language drops are
1202
+ * almost always well under this limit anyway, so they benefit from the
1203
+ * inline fast path too without meaningful crash risk increase.
1158
1204
  */
1159
- const INLINE_BACKFILL_THRESHOLD = 16;
1205
+ const INLINE_BACKFILL_THRESHOLD = 32;
1160
1206
 
1161
1207
  /**
1162
1208
  * Inline WASM parse (no worker) for small file batches.
@@ -1168,11 +1214,16 @@ const INLINE_BACKFILL_THRESHOLD = 16;
1168
1214
  *
1169
1215
  * Returns symbols with `_tree` set so `runAnalyses` can run AST/CFG/dataflow
1170
1216
  * visitors via the unified walker (mirrors how WASM-engine results behaved
1171
- * before the worker pool was introduced).
1217
+ * before the worker pool was introduced), unless `symbolsOnly` is true — in
1218
+ * that case `_tree` is not set, skipping all analysis visitor walks. Use
1219
+ * `symbolsOnly` when only definitions/calls/typeMap are needed (e.g. the
1220
+ * this/super dispatch post-pass) to avoid the analysis overhead on the inline
1221
+ * path, matching the optimization already applied to the worker-pool path.
1172
1222
  */
1173
1223
  async function parseFilesWasmInline(
1174
1224
  filePaths: string[],
1175
1225
  rootDir: string,
1226
+ symbolsOnly = false,
1176
1227
  ): Promise<Map<string, ExtractorOutput>> {
1177
1228
  const result = new Map<string, ExtractorOutput>();
1178
1229
  if (filePaths.length === 0) return result;
@@ -1190,7 +1241,18 @@ async function parseFilesWasmInline(
1190
1241
  if (!extracted) continue;
1191
1242
  const relPath = path.relative(rootDir, filePath).split(path.sep).join('/');
1192
1243
  const symbols = extracted.symbols as ExtractorOutput & { _tree?: unknown; _langId?: string };
1193
- symbols._tree = extracted.tree;
1244
+ // When symbolsOnly=true, skip setting _tree so runAnalyses does not run
1245
+ // AST/complexity/CFG/dataflow visitor walks — only definitions/calls/typeMap
1246
+ // are needed by callers like the this/super dispatch post-pass.
1247
+ if (!symbolsOnly) {
1248
+ symbols._tree = extracted.tree;
1249
+ } else if (typeof (extracted.tree as any)?.delete === 'function') {
1250
+ // Free the WASM-backed tree immediately — web-tree-sitter trees are backed
1251
+ // by WASM linear memory and require explicit disposal. When symbolsOnly is
1252
+ // true the tree is never stored anywhere, so we must delete it here to
1253
+ // avoid leaking WASM heap on every incremental rebuild.
1254
+ (extracted.tree as any).delete();
1255
+ }
1194
1256
  symbols._langId = extracted.langId;
1195
1257
  result.set(relPath, symbols);
1196
1258
  }
@@ -1200,17 +1262,24 @@ async function parseFilesWasmInline(
1200
1262
  /**
1201
1263
  * Backfill helper: small batches use the inline (main-thread) path; larger
1202
1264
  * batches keep the worker-pool isolation against tree-sitter WASM crashes
1203
- * (#965). Threshold matches typical engine-parity drop sizes (a few fixture
1204
- * files in one or two languages).
1265
+ * (#965). See INLINE_BACKFILL_THRESHOLD for threshold rationale.
1266
+ *
1267
+ * `opts.symbolsOnly` skips the AST/complexity/CFG/dataflow visitors in the
1268
+ * worker (and their result serialization across the thread boundary) for
1269
+ * callers that only consume definitions/calls/typeMap — the native
1270
+ * orchestrator's this-dispatch post-pass. Callers that ingest the files into
1271
+ * the DB (dropped-language backfill) must keep
1272
+ * the default full analysis.
1205
1273
  */
1206
1274
  export async function parseFilesWasmForBackfill(
1207
1275
  filePaths: string[],
1208
1276
  rootDir: string,
1277
+ opts: { symbolsOnly?: boolean } = {},
1209
1278
  ): Promise<Map<string, ExtractorOutput>> {
1210
1279
  if (filePaths.length <= INLINE_BACKFILL_THRESHOLD) {
1211
- return parseFilesWasmInline(filePaths, rootDir);
1280
+ return parseFilesWasmInline(filePaths, rootDir, opts.symbolsOnly);
1212
1281
  }
1213
- return parseFilesWasm(filePaths, rootDir);
1282
+ return parseFilesWasm(filePaths, rootDir, opts.symbolsOnly ? EXTRACT_ONLY : FULL_ANALYSIS);
1214
1283
  }
1215
1284
 
1216
1285
  /**
@@ -109,10 +109,13 @@ function grammarPath(name: string): string {
109
109
 
110
110
  const COMMON_QUERY_PATTERNS: string[] = [
111
111
  '(function_declaration name: (identifier) @fn_name) @fn_node',
112
+ '(generator_function_declaration name: (identifier) @fn_name) @fn_node',
112
113
  '(variable_declarator name: (identifier) @varfn_name value: (arrow_function) @varfn_value)',
113
114
  '(variable_declarator name: (identifier) @varfn_name value: (function_expression) @varfn_value)',
115
+ '(variable_declarator name: (identifier) @varfn_name value: (generator_function) @varfn_value)',
114
116
  '(method_definition name: (property_identifier) @meth_name) @meth_node',
115
117
  '(method_definition name: (private_property_identifier) @meth_name) @meth_node',
118
+ '(method_definition name: (computed_property_name) @meth_name) @meth_node',
116
119
  '(import_statement source: (string) @imp_source) @imp_node',
117
120
  '(export_statement) @exp_node',
118
121
  '(call_expression function: (identifier) @callfn_name) @callfn_node',
@@ -123,10 +126,17 @@ const COMMON_QUERY_PATTERNS: string[] = [
123
126
  '(expression_statement (assignment_expression left: (member_expression) @assign_left right: (_) @assign_right)) @assign_node',
124
127
  ];
125
128
 
126
- const JS_CLASS_PATTERN: string = '(class_declaration name: (identifier) @cls_name) @cls_node';
129
+ const JS_CLASS_PATTERNS: string[] = [
130
+ '(class_declaration name: (identifier) @cls_name) @cls_node',
131
+ // class expressions: `return class Foo extends Bar { ... }` or `const X = class Foo { ... }`
132
+ '(class name: (identifier) @cls_name) @cls_node',
133
+ ];
127
134
 
128
135
  const TS_EXTRA_PATTERNS: string[] = [
129
136
  '(class_declaration name: (type_identifier) @cls_name) @cls_node',
137
+ '(abstract_class_declaration name: (type_identifier) @cls_name) @cls_node',
138
+ // class expressions: `return class Foo extends Bar { ... }`
139
+ '(class name: (type_identifier) @cls_name) @cls_node',
130
140
  '(interface_declaration name: (type_identifier) @iface_name) @iface_node',
131
141
  '(type_alias_declaration name: (type_identifier) @type_name) @type_node',
132
142
  ];
@@ -430,7 +440,7 @@ async function loadLanguageLazy(entry: LanguageRegistryEntry): Promise<Parser |
430
440
  const isTS = entry.id === 'typescript' || entry.id === 'tsx';
431
441
  const patterns = isTS
432
442
  ? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
433
- : [...COMMON_QUERY_PATTERNS, JS_CLASS_PATTERN];
443
+ : [...COMMON_QUERY_PATTERNS, ...JS_CLASS_PATTERNS];
434
444
  _queries.set(entry.id, new Query(lang, patterns.join('\n')));
435
445
  }
436
446
  return parser;
@@ -801,6 +811,29 @@ function serializeExtractorOutput(
801
811
  _lineCount: code.split('\n').length,
802
812
  dataflow: symbols.dataflow,
803
813
  astNodes,
814
+ ...(symbols.fnRefBindings?.length ? { fnRefBindings: symbols.fnRefBindings } : {}),
815
+ ...(symbols.paramBindings?.length ? { paramBindings: symbols.paramBindings } : {}),
816
+ ...(symbols.arrayElemBindings?.length ? { arrayElemBindings: symbols.arrayElemBindings } : {}),
817
+ ...(symbols.spreadArgBindings?.length ? { spreadArgBindings: symbols.spreadArgBindings } : {}),
818
+ ...(symbols.forOfBindings?.length ? { forOfBindings: symbols.forOfBindings } : {}),
819
+ ...(symbols.arrayCallbackBindings?.length
820
+ ? { arrayCallbackBindings: symbols.arrayCallbackBindings }
821
+ : {}),
822
+ ...(symbols.objectRestParamBindings?.length
823
+ ? { objectRestParamBindings: symbols.objectRestParamBindings }
824
+ : {}),
825
+ ...(symbols.objectPropBindings?.length
826
+ ? { objectPropBindings: symbols.objectPropBindings }
827
+ : {}),
828
+ ...(symbols.thisCallBindings?.length ? { thisCallBindings: symbols.thisCallBindings } : {}),
829
+ ...(symbols.newExpressions?.length ? { newExpressions: symbols.newExpressions } : {}),
830
+ ...(symbols.definePropertyReceivers?.size
831
+ ? { definePropertyReceivers: Array.from(symbols.definePropertyReceivers.entries()) }
832
+ : {}),
833
+ ...(symbols.returnTypeMap?.size
834
+ ? { returnTypeMap: Array.from(symbols.returnTypeMap.entries()) }
835
+ : {}),
836
+ ...(symbols.callAssignments?.length ? { callAssignments: symbols.callAssignments } : {}),
804
837
  };
805
838
  }
806
839
 
@@ -106,6 +106,28 @@ function deserializeResult(ser: SerializedExtractorOutput | null): ExtractorOutp
106
106
  // {line, kind, name, text?, receiver?} shape — see engine.ts:822 where the
107
107
  // visitor output is cast the same way.
108
108
  if (ser.astNodes !== undefined) out.astNodes = ser.astNodes as unknown as ASTNodeRow[];
109
+ if (ser.fnRefBindings?.length) out.fnRefBindings = ser.fnRefBindings;
110
+ if (ser.paramBindings?.length) out.paramBindings = ser.paramBindings;
111
+ if (ser.arrayElemBindings?.length) out.arrayElemBindings = ser.arrayElemBindings;
112
+ if (ser.spreadArgBindings?.length) out.spreadArgBindings = ser.spreadArgBindings;
113
+ if (ser.forOfBindings?.length) out.forOfBindings = ser.forOfBindings;
114
+ if (ser.arrayCallbackBindings?.length) out.arrayCallbackBindings = ser.arrayCallbackBindings;
115
+ if (ser.objectRestParamBindings?.length)
116
+ out.objectRestParamBindings = ser.objectRestParamBindings;
117
+ if (ser.objectPropBindings?.length) out.objectPropBindings = ser.objectPropBindings;
118
+ if (ser.thisCallBindings?.length) out.thisCallBindings = ser.thisCallBindings;
119
+ if (ser.newExpressions?.length) out.newExpressions = ser.newExpressions;
120
+ if (ser.definePropertyReceivers?.length) {
121
+ const m = new Map<string, string>();
122
+ for (const [k, v] of ser.definePropertyReceivers) m.set(k, v);
123
+ out.definePropertyReceivers = m;
124
+ }
125
+ if (ser.returnTypeMap?.length) {
126
+ const returnTypeMap = new Map<string, TypeMapEntry>();
127
+ for (const [k, v] of ser.returnTypeMap) returnTypeMap.set(k, v);
128
+ out.returnTypeMap = returnTypeMap;
129
+ }
130
+ if (ser.callAssignments?.length) out.callAssignments = ser.callAssignments;
109
131
  return out;
110
132
  }
111
133
 
@@ -12,6 +12,7 @@
12
12
 
13
13
  import type {
14
14
  Call,
15
+ CallAssignment,
15
16
  ClassRelation,
16
17
  DataflowResult,
17
18
  Definition,
@@ -62,6 +63,20 @@ export interface SerializedExtractorOutput {
62
63
  text?: string;
63
64
  receiver?: string;
64
65
  }>;
66
+ fnRefBindings?: import('../types.js').FnRefBinding[];
67
+ arrayElemBindings?: import('../types.js').ArrayElemBinding[];
68
+ spreadArgBindings?: import('../types.js').SpreadArgBinding[];
69
+ forOfBindings?: import('../types.js').ForOfBinding[];
70
+ arrayCallbackBindings?: import('../types.js').ArrayCallbackBinding[];
71
+ objectRestParamBindings?: import('../types.js').ObjectRestParamBinding[];
72
+ objectPropBindings?: import('../types.js').ObjectPropBinding[];
73
+ paramBindings?: import('../types.js').ParamBinding[];
74
+ thisCallBindings?: import('../types.js').ThisCallBinding[];
75
+ newExpressions?: readonly string[];
76
+ /** Serialized definePropertyReceivers map (funcName → receiverVarName) as tuple array. */
77
+ definePropertyReceivers?: Array<[string, string]>;
78
+ returnTypeMap?: Array<[string, TypeMapEntry]>;
79
+ callAssignments?: CallAssignment[];
65
80
  }
66
81
 
67
82
  export interface WorkerParseResponseOk {
@@ -189,7 +189,7 @@ function extractCParameters(paramListNode: TreeSitterNode | null): SubDeclaratio
189
189
  if (!paramListNode) return params;
190
190
  for (let i = 0; i < paramListNode.childCount; i++) {
191
191
  const param = paramListNode.child(i);
192
- if (!param || param.type !== 'parameter_declaration') continue;
192
+ if (param?.type !== 'parameter_declaration') continue;
193
193
  const nameNode = param.childForFieldName('declarator');
194
194
  if (nameNode) {
195
195
  const name = unwrapCDeclaratorName(nameNode);
@@ -205,7 +205,7 @@ function extractStructFields(structNode: TreeSitterNode): SubDeclaration[] {
205
205
  if (!body) return fields;
206
206
  for (let i = 0; i < body.childCount; i++) {
207
207
  const member = body.child(i);
208
- if (!member || member.type !== 'field_declaration') continue;
208
+ if (member?.type !== 'field_declaration') continue;
209
209
  const nameNode = member.childForFieldName('declarator');
210
210
  if (nameNode) {
211
211
  const name = unwrapCDeclaratorName(nameNode);
@@ -221,7 +221,7 @@ function extractEnumEntries(enumNode: TreeSitterNode): SubDeclaration[] {
221
221
  if (!body) return entries;
222
222
  for (let i = 0; i < body.childCount; i++) {
223
223
  const member = body.child(i);
224
- if (!member || member.type !== 'enumerator') continue;
224
+ if (member?.type !== 'enumerator') continue;
225
225
  const nameNode = member.childForFieldName('name');
226
226
  if (nameNode) {
227
227
  entries.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });
@@ -224,7 +224,7 @@ function extractClojureParams(defnNode: TreeSitterNode): SubDeclaration[] {
224
224
  // Find the parameter vector [x y z]
225
225
  for (let i = 0; i < defnNode.childCount; i++) {
226
226
  const child = defnNode.child(i);
227
- if (!child || child.type !== 'vec_lit') continue;
227
+ if (child?.type !== 'vec_lit') continue;
228
228
  for (let j = 0; j < child.childCount; j++) {
229
229
  const param = child.child(j);
230
230
  if (!param) continue;
@@ -5,7 +5,13 @@ import type {
5
5
  TreeSitterNode,
6
6
  TreeSitterTree,
7
7
  } from '../types.js';
8
- import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
8
+ import {
9
+ extractModifierVisibility,
10
+ findChild,
11
+ isCPrimitiveType,
12
+ nodeEndLine,
13
+ setTypeMapEntry,
14
+ } from './helpers.js';
9
15
 
10
16
  /**
11
17
  * Extract symbols from C++ files.
@@ -50,6 +56,9 @@ function walkCppNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
50
56
  case 'call_expression':
51
57
  handleCppCallExpression(node, ctx);
52
58
  break;
59
+ case 'declaration':
60
+ handleCppDeclaration(node, ctx);
61
+ break;
53
62
  }
54
63
 
55
64
  for (let i = 0; i < node.childCount; i++) {
@@ -204,6 +213,40 @@ function handleCppInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
204
213
  });
205
214
  }
206
215
 
216
+ /**
217
+ * Seed typeMap for declaration-typed locals: `UserService svc;` and
218
+ * `UserService svc = makeService();` both yield typeMap["svc"] = "UserService"
219
+ * at confidence 0.9. Mirrors `match_c_family_type_map` ("declaration" branch)
220
+ * in the native Rust C++ extractor.
221
+ */
222
+ function handleCppDeclaration(node: TreeSitterNode, ctx: ExtractorOutput): void {
223
+ const typeNode = node.childForFieldName('type');
224
+ if (!typeNode) return;
225
+ const typeName = typeNode.text;
226
+ // Skip primitive types — they are never class/struct receivers
227
+ if (isCPrimitiveType(typeName)) return;
228
+ for (let i = 0; i < node.childCount; i++) {
229
+ const child = node.child(i);
230
+ if (!child) continue;
231
+ const kind = child.type;
232
+ let nameNode: TreeSitterNode | null = null;
233
+ if (kind === 'init_declarator') {
234
+ nameNode = child.childForFieldName('declarator') ?? null;
235
+ } else if (kind === 'identifier') {
236
+ nameNode = child;
237
+ }
238
+ // Note: pointer_declarator / reference_declarator children (e.g. `UserService *svc;`)
239
+ // are intentionally skipped here — they are also skipped by the native Rust
240
+ // match_c_family_type_map helper, which only handles 'init_declarator' and
241
+ // 'identifier' children. Both engines have the same scope for this case.
242
+ if (!nameNode) continue;
243
+ const varName = unwrapCppDeclaratorName(nameNode);
244
+ if (varName) {
245
+ setTypeMapEntry(ctx.typeMap, varName, typeName, 0.9);
246
+ }
247
+ }
248
+ }
249
+
207
250
  function handleCppCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
208
251
  const funcNode = node.childForFieldName('function');
209
252
  if (!funcNode) return;
@@ -292,7 +335,7 @@ function extractCppParameters(paramListNode: TreeSitterNode | null): SubDeclarat
292
335
  if (!paramListNode) return params;
293
336
  for (let i = 0; i < paramListNode.childCount; i++) {
294
337
  const param = paramListNode.child(i);
295
- if (!param || param.type !== 'parameter_declaration') continue;
338
+ if (param?.type !== 'parameter_declaration') continue;
296
339
  const nameNode = param.childForFieldName('declarator');
297
340
  if (nameNode) {
298
341
  const name = unwrapCppDeclaratorName(nameNode);
@@ -309,7 +352,7 @@ function extractCppClassFields(classNode: TreeSitterNode): SubDeclaration[] {
309
352
  if (!body) return fields;
310
353
  for (let i = 0; i < body.childCount; i++) {
311
354
  const member = body.child(i);
312
- if (!member || member.type !== 'field_declaration') continue;
355
+ if (member?.type !== 'field_declaration') continue;
313
356
  const nameNode = member.childForFieldName('declarator');
314
357
  if (nameNode) {
315
358
  const name = unwrapCppDeclaratorName(nameNode);
@@ -330,7 +373,7 @@ function extractCppEnumEntries(enumNode: TreeSitterNode): SubDeclaration[] {
330
373
  if (!body) return entries;
331
374
  for (let i = 0; i < body.childCount; i++) {
332
375
  const member = body.child(i);
333
- if (!member || member.type !== 'enumerator') continue;
376
+ if (member?.type !== 'enumerator') continue;
334
377
  const nameNode = member.childForFieldName('name');
335
378
  if (nameNode) {
336
379
  entries.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });
@@ -28,6 +28,7 @@ export function extractCSharpSymbols(tree: TreeSitterTree, _filePath: string): E
28
28
  classes: [],
29
29
  exports: [],
30
30
  typeMap: new Map(),
31
+ newExpressions: [],
31
32
  };
32
33
 
33
34
  walkCSharpNode(tree.rootNode, ctx);
@@ -252,7 +253,12 @@ function handleCsObjectCreation(node: TreeSitterNode, ctx: ExtractorOutput): voi
252
253
  typeNode.type === 'generic_name'
253
254
  ? typeNode.childForFieldName('name')?.text || typeNode.child(0)?.text
254
255
  : typeNode.text;
255
- if (typeName) ctx.calls.push({ name: typeName, line: node.startPosition.row + 1 });
256
+ if (typeName) {
257
+ ctx.calls.push({ name: typeName, line: node.startPosition.row + 1 });
258
+ // Phase 8.5 (RTA): record instantiated type for CHA filtering, matching
259
+ // the JS extractor's newExpressions extraction for cross-engine parity.
260
+ (ctx.newExpressions as string[]).push(typeName);
261
+ }
256
262
  }
257
263
 
258
264
  const CS_PARENT_TYPES = [
@@ -273,7 +279,7 @@ function extractCSharpParameters(paramListNode: TreeSitterNode | null): SubDecla
273
279
  if (!paramListNode) return params;
274
280
  for (let i = 0; i < paramListNode.childCount; i++) {
275
281
  const param = paramListNode.child(i);
276
- if (!param || param.type !== 'parameter') continue;
282
+ if (param?.type !== 'parameter') continue;
277
283
  const nameNode = param.childForFieldName('name');
278
284
  if (nameNode) {
279
285
  params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
@@ -288,12 +294,12 @@ function extractCSharpClassFields(classNode: TreeSitterNode): SubDeclaration[] {
288
294
  if (!body) return fields;
289
295
  for (let i = 0; i < body.childCount; i++) {
290
296
  const member = body.child(i);
291
- if (!member || member.type !== 'field_declaration') continue;
297
+ if (member?.type !== 'field_declaration') continue;
292
298
  const varDecl = findChild(member, 'variable_declaration');
293
299
  if (!varDecl) continue;
294
300
  for (let j = 0; j < varDecl.childCount; j++) {
295
301
  const child = varDecl.child(j);
296
- if (!child || child.type !== 'variable_declarator') continue;
302
+ if (child?.type !== 'variable_declarator') continue;
297
303
  const nameNode = child.childForFieldName('name');
298
304
  if (nameNode) {
299
305
  fields.push({
@@ -326,16 +332,34 @@ function extractCSharpTypeMap(node: TreeSitterNode, ctx: ExtractorOutput): void
326
332
  /** Extract type info from a variable_declaration node (local vars with explicit types). */
327
333
  function handleCSharpVarDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
328
334
  const typeNode = node.childForFieldName('type') || node.child(0);
329
- if (!typeNode || typeNode.type === 'var_keyword') return;
335
+ if (!typeNode) return;
336
+
337
+ if (typeNode.type === 'implicit_type') {
338
+ // var x = new Foo() — infer type from object_creation_expression initializer
339
+ if (!ctx.typeMap) return;
340
+ for (let i = 0; i < node.childCount; i++) {
341
+ const child = node.child(i);
342
+ if (child?.type !== 'variable_declarator') continue;
343
+ const nameNode = child.childForFieldName('name') || child.child(0);
344
+ if (nameNode?.type !== 'identifier') continue;
345
+ const objCreation = findChild(child, 'object_creation_expression');
346
+ if (!objCreation) continue;
347
+ const ctorTypeNode = objCreation.childForFieldName('type');
348
+ if (!ctorTypeNode) continue;
349
+ const ctorType = extractCSharpTypeName(ctorTypeNode);
350
+ if (ctorType) setTypeMapEntry(ctx.typeMap, nameNode.text, ctorType, 1.0);
351
+ }
352
+ return;
353
+ }
354
+
330
355
  const typeName = extractCSharpTypeName(typeNode);
331
356
  if (!typeName) return;
332
357
  for (let i = 0; i < node.childCount; i++) {
333
358
  const child = node.child(i);
334
- if (!child || child.type !== 'variable_declarator') continue;
359
+ if (child?.type !== 'variable_declarator') continue;
335
360
  const nameNode = child.childForFieldName('name') || child.child(0);
336
- if (nameNode && nameNode.type === 'identifier' && ctx.typeMap) {
337
- setTypeMapEntry(ctx.typeMap, nameNode.text, typeName, 0.9);
338
- }
361
+ if (nameNode?.type !== 'identifier' || !ctx.typeMap) continue;
362
+ if (typeName) setTypeMapEntry(ctx.typeMap, nameNode.text, typeName, 0.9);
339
363
  }
340
364
  }
341
365
 
@@ -5,7 +5,13 @@ import type {
5
5
  TreeSitterNode,
6
6
  TreeSitterTree,
7
7
  } from '../types.js';
8
- import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
8
+ import {
9
+ extractModifierVisibility,
10
+ findChild,
11
+ isCPrimitiveType,
12
+ nodeEndLine,
13
+ setTypeMapEntry,
14
+ } from './helpers.js';
9
15
 
10
16
  /**
11
17
  * Extract symbols from CUDA files.
@@ -63,6 +69,9 @@ function walkCudaNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
63
69
  case 'call_expression':
64
70
  handleCudaCallExpression(node, ctx);
65
71
  break;
72
+ case 'declaration':
73
+ handleCudaDeclaration(node, ctx);
74
+ break;
66
75
  }
67
76
 
68
77
  for (let i = 0; i < node.childCount; i++) {
@@ -204,6 +213,40 @@ function handleCudaInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
204
213
  });
205
214
  }
206
215
 
216
+ /**
217
+ * Seed typeMap for declaration-typed locals: `UserService svc;` and
218
+ * `UserService svc = make();` both yield typeMap["svc"] = "UserService"
219
+ * at confidence 0.9. Mirrors `match_c_family_type_map` ("declaration" branch)
220
+ * in the native Rust CUDA extractor.
221
+ */
222
+ function handleCudaDeclaration(node: TreeSitterNode, ctx: ExtractorOutput): void {
223
+ const typeNode = node.childForFieldName('type');
224
+ if (!typeNode) return;
225
+ const typeName = typeNode.text;
226
+ // Skip primitive types — they are never class/struct receivers
227
+ if (isCPrimitiveType(typeName)) return;
228
+ for (let i = 0; i < node.childCount; i++) {
229
+ const child = node.child(i);
230
+ if (!child) continue;
231
+ const kind = child.type;
232
+ let nameNode: TreeSitterNode | null = null;
233
+ if (kind === 'init_declarator') {
234
+ nameNode = child.childForFieldName('declarator') ?? null;
235
+ } else if (kind === 'identifier') {
236
+ nameNode = child;
237
+ }
238
+ // Note: pointer_declarator / reference_declarator children (e.g. `UserService *svc;`)
239
+ // are intentionally skipped here — they are also skipped by the native Rust
240
+ // match_c_family_type_map helper, which only handles 'init_declarator' and
241
+ // 'identifier' children. Both engines have the same scope for this case.
242
+ if (!nameNode) continue;
243
+ const varName = extractCudaFieldName(nameNode);
244
+ if (varName) {
245
+ setTypeMapEntry(ctx.typeMap, varName, typeName, 0.9);
246
+ }
247
+ }
248
+ }
249
+
207
250
  function handleCudaCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
208
251
  const funcNode = node.childForFieldName('function');
209
252
  if (!funcNode) return;
@@ -262,7 +305,7 @@ function extractCudaParameters(paramListNode: TreeSitterNode | null): SubDeclara
262
305
  if (!paramListNode) return params;
263
306
  for (let i = 0; i < paramListNode.childCount; i++) {
264
307
  const param = paramListNode.child(i);
265
- if (!param || param.type !== 'parameter_declaration') continue;
308
+ if (param?.type !== 'parameter_declaration') continue;
266
309
  const nameNode = param.childForFieldName('declarator');
267
310
  if (nameNode) {
268
311
  // Reuse the field-name drill helper so function-type parameters like
@@ -282,7 +325,7 @@ function extractCudaClassFields(classNode: TreeSitterNode): SubDeclaration[] {
282
325
  if (!body) return fields;
283
326
  for (let i = 0; i < body.childCount; i++) {
284
327
  const member = body.child(i);
285
- if (!member || member.type !== 'field_declaration') continue;
328
+ if (member?.type !== 'field_declaration') continue;
286
329
  const nameNode = member.childForFieldName('declarator');
287
330
  if (!nameNode) continue;
288
331
  // Skip method declarations — a `field_declaration` whose declarator
@@ -380,7 +423,7 @@ function extractCudaEnumEntries(enumNode: TreeSitterNode): SubDeclaration[] {
380
423
  if (!body) return entries;
381
424
  for (let i = 0; i < body.childCount; i++) {
382
425
  const member = body.child(i);
383
- if (!member || member.type !== 'enumerator') continue;
426
+ if (member?.type !== 'enumerator') continue;
384
427
  const nameNode = member.childForFieldName('name');
385
428
  if (nameNode) {
386
429
  entries.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });