@optave/codegraph 3.10.0 → 3.11.1

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 (312) hide show
  1. package/README.md +40 -33
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +91 -60
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/rules/index.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/index.js +77 -0
  7. package/dist/ast-analysis/rules/index.js.map +1 -1
  8. package/dist/ast-analysis/visitor-utils.d.ts +3 -0
  9. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  10. package/dist/ast-analysis/visitor-utils.js +83 -49
  11. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  12. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
  14. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  15. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
  17. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  18. package/dist/cli/commands/audit.js +1 -1
  19. package/dist/cli/commands/audit.js.map +1 -1
  20. package/dist/cli/commands/build.d.ts.map +1 -1
  21. package/dist/cli/commands/build.js +2 -0
  22. package/dist/cli/commands/build.js.map +1 -1
  23. package/dist/cli/commands/check.js +1 -1
  24. package/dist/cli/commands/check.js.map +1 -1
  25. package/dist/cli/commands/children.js +1 -1
  26. package/dist/cli/commands/children.js.map +1 -1
  27. package/dist/cli/commands/diff-impact.js +1 -1
  28. package/dist/cli/commands/diff-impact.js.map +1 -1
  29. package/dist/cli/commands/embed.d.ts.map +1 -1
  30. package/dist/cli/commands/embed.js +49 -4
  31. package/dist/cli/commands/embed.js.map +1 -1
  32. package/dist/cli/commands/roles.js +1 -1
  33. package/dist/cli/commands/roles.js.map +1 -1
  34. package/dist/cli/commands/structure.js +1 -1
  35. package/dist/cli/commands/structure.js.map +1 -1
  36. package/dist/cli/shared/options.js +1 -1
  37. package/dist/cli/shared/options.js.map +1 -1
  38. package/dist/db/connection.d.ts.map +1 -1
  39. package/dist/db/connection.js +8 -0
  40. package/dist/db/connection.js.map +1 -1
  41. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  42. package/dist/domain/analysis/dependencies.js +106 -80
  43. package/dist/domain/analysis/dependencies.js.map +1 -1
  44. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  45. package/dist/domain/analysis/fn-impact.js +77 -52
  46. package/dist/domain/analysis/fn-impact.js.map +1 -1
  47. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  48. package/dist/domain/analysis/module-map.js +132 -121
  49. package/dist/domain/analysis/module-map.js.map +1 -1
  50. package/dist/domain/graph/builder/helpers.d.ts +4 -4
  51. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  52. package/dist/domain/graph/builder/helpers.js +47 -33
  53. package/dist/domain/graph/builder/helpers.js.map +1 -1
  54. package/dist/domain/graph/builder/incremental.d.ts +6 -6
  55. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  56. package/dist/domain/graph/builder/incremental.js +148 -99
  57. package/dist/domain/graph/builder/incremental.js.map +1 -1
  58. package/dist/domain/graph/builder/pipeline.d.ts +1 -0
  59. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  60. package/dist/domain/graph/builder/pipeline.js +23 -637
  61. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  62. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  63. package/dist/domain/graph/builder/stages/build-edges.js +141 -98
  64. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  65. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  66. package/dist/domain/graph/builder/stages/build-structure.js +82 -65
  67. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  68. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  69. package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
  70. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  71. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  72. package/dist/domain/graph/builder/stages/finalize.js +60 -51
  73. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  74. package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
  75. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  76. package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
  77. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  78. package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
  79. package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
  80. package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
  81. package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
  82. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
  83. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
  84. package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
  85. package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
  86. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  87. package/dist/domain/graph/builder/stages/resolve-imports.js +73 -22
  88. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  89. package/dist/domain/graph/cycles.d.ts +6 -4
  90. package/dist/domain/graph/cycles.d.ts.map +1 -1
  91. package/dist/domain/graph/cycles.js +50 -55
  92. package/dist/domain/graph/cycles.js.map +1 -1
  93. package/dist/domain/graph/journal.d.ts.map +1 -1
  94. package/dist/domain/graph/journal.js +89 -70
  95. package/dist/domain/graph/journal.js.map +1 -1
  96. package/dist/domain/graph/watcher.d.ts.map +1 -1
  97. package/dist/domain/graph/watcher.js +28 -20
  98. package/dist/domain/graph/watcher.js.map +1 -1
  99. package/dist/domain/parser.d.ts +12 -23
  100. package/dist/domain/parser.d.ts.map +1 -1
  101. package/dist/domain/parser.js +153 -80
  102. package/dist/domain/parser.js.map +1 -1
  103. package/dist/domain/search/generator.d.ts +3 -1
  104. package/dist/domain/search/generator.d.ts.map +1 -1
  105. package/dist/domain/search/generator.js +68 -45
  106. package/dist/domain/search/generator.js.map +1 -1
  107. package/dist/domain/search/models.d.ts +18 -0
  108. package/dist/domain/search/models.d.ts.map +1 -1
  109. package/dist/domain/search/models.js +72 -4
  110. package/dist/domain/search/models.js.map +1 -1
  111. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  112. package/dist/domain/search/search/hybrid.js +49 -40
  113. package/dist/domain/search/search/hybrid.js.map +1 -1
  114. package/dist/domain/search/search/semantic.d.ts.map +1 -1
  115. package/dist/domain/search/search/semantic.js +69 -49
  116. package/dist/domain/search/search/semantic.js.map +1 -1
  117. package/dist/domain/wasm-worker-entry.js +209 -137
  118. package/dist/domain/wasm-worker-entry.js.map +1 -1
  119. package/dist/extractors/c.js +25 -6
  120. package/dist/extractors/c.js.map +1 -1
  121. package/dist/extractors/cpp.js +47 -6
  122. package/dist/extractors/cpp.js.map +1 -1
  123. package/dist/extractors/cuda.js +90 -14
  124. package/dist/extractors/cuda.js.map +1 -1
  125. package/dist/extractors/elixir.js +108 -4
  126. package/dist/extractors/elixir.js.map +1 -1
  127. package/dist/extractors/erlang.js +56 -20
  128. package/dist/extractors/erlang.js.map +1 -1
  129. package/dist/extractors/fsharp.d.ts +7 -0
  130. package/dist/extractors/fsharp.d.ts.map +1 -1
  131. package/dist/extractors/fsharp.js +94 -0
  132. package/dist/extractors/fsharp.js.map +1 -1
  133. package/dist/extractors/gleam.d.ts.map +1 -1
  134. package/dist/extractors/gleam.js +29 -33
  135. package/dist/extractors/gleam.js.map +1 -1
  136. package/dist/extractors/groovy.js +41 -1
  137. package/dist/extractors/groovy.js.map +1 -1
  138. package/dist/extractors/haskell.js +48 -4
  139. package/dist/extractors/haskell.js.map +1 -1
  140. package/dist/extractors/helpers.d.ts +79 -1
  141. package/dist/extractors/helpers.d.ts.map +1 -1
  142. package/dist/extractors/helpers.js +137 -0
  143. package/dist/extractors/helpers.js.map +1 -1
  144. package/dist/extractors/java.d.ts.map +1 -1
  145. package/dist/extractors/java.js +37 -49
  146. package/dist/extractors/java.js.map +1 -1
  147. package/dist/extractors/javascript.d.ts.map +1 -1
  148. package/dist/extractors/javascript.js +44 -44
  149. package/dist/extractors/javascript.js.map +1 -1
  150. package/dist/extractors/julia.js +198 -74
  151. package/dist/extractors/julia.js.map +1 -1
  152. package/dist/extractors/kotlin.js +4 -0
  153. package/dist/extractors/kotlin.js.map +1 -1
  154. package/dist/extractors/objc.js +184 -47
  155. package/dist/extractors/objc.js.map +1 -1
  156. package/dist/extractors/python.js +7 -4
  157. package/dist/extractors/python.js.map +1 -1
  158. package/dist/extractors/r.d.ts.map +1 -1
  159. package/dist/extractors/r.js +103 -87
  160. package/dist/extractors/r.js.map +1 -1
  161. package/dist/extractors/scala.d.ts.map +1 -1
  162. package/dist/extractors/scala.js +18 -32
  163. package/dist/extractors/scala.js.map +1 -1
  164. package/dist/extractors/solidity.d.ts.map +1 -1
  165. package/dist/extractors/solidity.js +55 -69
  166. package/dist/extractors/solidity.js.map +1 -1
  167. package/dist/extractors/verilog.js +80 -15
  168. package/dist/extractors/verilog.js.map +1 -1
  169. package/dist/features/boundaries.d.ts.map +1 -1
  170. package/dist/features/boundaries.js +49 -39
  171. package/dist/features/boundaries.js.map +1 -1
  172. package/dist/features/cfg.d.ts.map +1 -1
  173. package/dist/features/cfg.js +90 -63
  174. package/dist/features/cfg.js.map +1 -1
  175. package/dist/features/check.d.ts.map +1 -1
  176. package/dist/features/check.js +43 -34
  177. package/dist/features/check.js.map +1 -1
  178. package/dist/features/cochange.d.ts.map +1 -1
  179. package/dist/features/cochange.js +68 -56
  180. package/dist/features/cochange.js.map +1 -1
  181. package/dist/features/complexity.d.ts.map +1 -1
  182. package/dist/features/complexity.js +105 -75
  183. package/dist/features/complexity.js.map +1 -1
  184. package/dist/features/dataflow.d.ts.map +1 -1
  185. package/dist/features/dataflow.js +37 -29
  186. package/dist/features/dataflow.js.map +1 -1
  187. package/dist/features/flow.d.ts.map +1 -1
  188. package/dist/features/flow.js +31 -22
  189. package/dist/features/flow.js.map +1 -1
  190. package/dist/features/graph-enrichment.d.ts.map +1 -1
  191. package/dist/features/graph-enrichment.js +77 -70
  192. package/dist/features/graph-enrichment.js.map +1 -1
  193. package/dist/features/owners.d.ts +17 -26
  194. package/dist/features/owners.d.ts.map +1 -1
  195. package/dist/features/owners.js +120 -109
  196. package/dist/features/owners.js.map +1 -1
  197. package/dist/features/sequence.d.ts.map +1 -1
  198. package/dist/features/sequence.js +59 -54
  199. package/dist/features/sequence.js.map +1 -1
  200. package/dist/features/structure-query.d.ts.map +1 -1
  201. package/dist/features/structure-query.js +60 -60
  202. package/dist/features/structure-query.js.map +1 -1
  203. package/dist/features/structure.js +28 -36
  204. package/dist/features/structure.js.map +1 -1
  205. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  206. package/dist/graph/algorithms/leiden/optimiser.js +100 -69
  207. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  208. package/dist/graph/classifiers/roles.d.ts.map +1 -1
  209. package/dist/graph/classifiers/roles.js +63 -59
  210. package/dist/graph/classifiers/roles.js.map +1 -1
  211. package/dist/infrastructure/config.d.ts +1 -1
  212. package/dist/infrastructure/config.d.ts.map +1 -1
  213. package/dist/infrastructure/config.js +1 -1
  214. package/dist/infrastructure/config.js.map +1 -1
  215. package/dist/mcp/tool-registry.d.ts.map +1 -1
  216. package/dist/mcp/tool-registry.js +4 -0
  217. package/dist/mcp/tool-registry.js.map +1 -1
  218. package/dist/mcp/tools/semantic-search.d.ts +1 -0
  219. package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
  220. package/dist/mcp/tools/semantic-search.js +1 -0
  221. package/dist/mcp/tools/semantic-search.js.map +1 -1
  222. package/dist/presentation/cfg.d.ts.map +1 -1
  223. package/dist/presentation/cfg.js +44 -29
  224. package/dist/presentation/cfg.js.map +1 -1
  225. package/dist/presentation/flow.d.ts.map +1 -1
  226. package/dist/presentation/flow.js +58 -38
  227. package/dist/presentation/flow.js.map +1 -1
  228. package/dist/types.d.ts +16 -2
  229. package/dist/types.d.ts.map +1 -1
  230. package/grammars/tree-sitter-erlang.wasm +0 -0
  231. package/grammars/tree-sitter-fsharp.wasm +0 -0
  232. package/grammars/tree-sitter-fsharp_signature.wasm +0 -0
  233. package/grammars/tree-sitter-gleam.wasm +0 -0
  234. package/package.json +10 -10
  235. package/src/ast-analysis/engine.ts +145 -61
  236. package/src/ast-analysis/rules/index.ts +87 -0
  237. package/src/ast-analysis/visitor-utils.ts +86 -46
  238. package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
  239. package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
  240. package/src/cli/commands/audit.ts +1 -1
  241. package/src/cli/commands/build.ts +2 -0
  242. package/src/cli/commands/check.ts +1 -1
  243. package/src/cli/commands/children.ts +1 -1
  244. package/src/cli/commands/diff-impact.ts +1 -1
  245. package/src/cli/commands/embed.ts +54 -4
  246. package/src/cli/commands/roles.ts +1 -1
  247. package/src/cli/commands/structure.ts +1 -1
  248. package/src/cli/shared/options.ts +1 -1
  249. package/src/db/connection.ts +8 -0
  250. package/src/domain/analysis/dependencies.ts +166 -85
  251. package/src/domain/analysis/fn-impact.ts +120 -50
  252. package/src/domain/analysis/module-map.ts +175 -140
  253. package/src/domain/graph/builder/helpers.ts +85 -76
  254. package/src/domain/graph/builder/incremental.ts +223 -131
  255. package/src/domain/graph/builder/pipeline.ts +32 -785
  256. package/src/domain/graph/builder/stages/build-edges.ts +207 -142
  257. package/src/domain/graph/builder/stages/build-structure.ts +115 -82
  258. package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
  259. package/src/domain/graph/builder/stages/finalize.ts +72 -70
  260. package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
  261. package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
  262. package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
  263. package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
  264. package/src/domain/graph/cycles.ts +51 -49
  265. package/src/domain/graph/journal.ts +84 -69
  266. package/src/domain/graph/watcher.ts +29 -25
  267. package/src/domain/parser.ts +170 -67
  268. package/src/domain/search/generator.ts +132 -74
  269. package/src/domain/search/models.ts +75 -4
  270. package/src/domain/search/search/hybrid.ts +53 -42
  271. package/src/domain/search/search/semantic.ts +105 -65
  272. package/src/domain/wasm-worker-entry.ts +243 -153
  273. package/src/extractors/c.ts +27 -8
  274. package/src/extractors/cpp.ts +50 -8
  275. package/src/extractors/cuda.ts +90 -16
  276. package/src/extractors/elixir.ts +103 -4
  277. package/src/extractors/erlang.ts +63 -20
  278. package/src/extractors/fsharp.ts +104 -0
  279. package/src/extractors/gleam.ts +40 -39
  280. package/src/extractors/groovy.ts +45 -1
  281. package/src/extractors/haskell.ts +45 -4
  282. package/src/extractors/helpers.ts +205 -1
  283. package/src/extractors/java.ts +42 -45
  284. package/src/extractors/javascript.ts +44 -43
  285. package/src/extractors/julia.ts +191 -77
  286. package/src/extractors/kotlin.ts +4 -0
  287. package/src/extractors/objc.ts +171 -47
  288. package/src/extractors/python.ts +5 -3
  289. package/src/extractors/r.ts +104 -82
  290. package/src/extractors/scala.ts +24 -36
  291. package/src/extractors/solidity.ts +59 -78
  292. package/src/extractors/verilog.ts +83 -15
  293. package/src/features/boundaries.ts +64 -46
  294. package/src/features/cfg.ts +145 -74
  295. package/src/features/check.ts +60 -43
  296. package/src/features/cochange.ts +95 -72
  297. package/src/features/complexity.ts +134 -79
  298. package/src/features/dataflow.ts +57 -34
  299. package/src/features/flow.ts +48 -24
  300. package/src/features/graph-enrichment.ts +105 -70
  301. package/src/features/owners.ts +186 -146
  302. package/src/features/sequence.ts +99 -69
  303. package/src/features/structure-query.ts +94 -79
  304. package/src/features/structure.ts +56 -56
  305. package/src/graph/algorithms/leiden/optimiser.ts +142 -87
  306. package/src/graph/classifiers/roles.ts +64 -54
  307. package/src/infrastructure/config.ts +1 -1
  308. package/src/mcp/tool-registry.ts +5 -0
  309. package/src/mcp/tools/semantic-search.ts +2 -0
  310. package/src/presentation/cfg.ts +48 -32
  311. package/src/presentation/flow.ts +100 -52
  312. package/src/types.ts +16 -1
@@ -265,10 +265,10 @@ function extractCudaParameters(paramListNode: TreeSitterNode | null): SubDeclara
265
265
  if (!param || param.type !== 'parameter_declaration') continue;
266
266
  const nameNode = param.childForFieldName('declarator');
267
267
  if (nameNode) {
268
- const name =
269
- nameNode.type === 'identifier'
270
- ? nameNode.text
271
- : (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
268
+ // Reuse the field-name drill helper so function-type parameters like
269
+ // `void process(int callback(int))` yield the bare name `callback`
270
+ // instead of the raw declarator text, matching the native unwrap path.
271
+ const name = extractCudaFieldName(nameNode);
272
272
  params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
273
273
  }
274
274
  }
@@ -284,22 +284,96 @@ function extractCudaClassFields(classNode: TreeSitterNode): SubDeclaration[] {
284
284
  const member = body.child(i);
285
285
  if (!member || member.type !== 'field_declaration') continue;
286
286
  const nameNode = member.childForFieldName('declarator');
287
- if (nameNode) {
288
- const name =
289
- nameNode.type === 'identifier'
290
- ? nameNode.text
291
- : (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
292
- fields.push({
293
- name,
294
- kind: 'property',
295
- line: member.startPosition.row + 1,
296
- visibility: extractModifierVisibility(member),
297
- });
298
- }
287
+ if (!nameNode) continue;
288
+ // Skip method declarations — a `field_declaration` whose declarator
289
+ // (after unwrapping pointer/reference/array) is a `function_declarator`
290
+ // is a method signature in a header, not a data field. Native and WASM
291
+ // previously diverged on how to format these (native stripped the `*`
292
+ // from pointer-return types, WASM kept it), and both produced
293
+ // method-signature-shaped "property" entries that are not real fields.
294
+ if (isCudaMethodDeclarator(nameNode)) continue;
295
+ const name = extractCudaFieldName(nameNode);
296
+ fields.push({
297
+ name,
298
+ kind: 'property',
299
+ line: member.startPosition.row + 1,
300
+ visibility: extractModifierVisibility(member),
301
+ });
299
302
  }
300
303
  return fields;
301
304
  }
302
305
 
306
+ const CUDA_DECLARATOR_WRAPPERS = new Set([
307
+ 'pointer_declarator',
308
+ 'reference_declarator',
309
+ 'array_declarator',
310
+ 'parenthesized_declarator',
311
+ ]);
312
+
313
+ function isCudaMethodDeclarator(node: TreeSitterNode): boolean {
314
+ let current: TreeSitterNode | null = node;
315
+ while (current && CUDA_DECLARATOR_WRAPPERS.has(current.type)) {
316
+ current = current.childForFieldName('declarator');
317
+ }
318
+ if (current?.type !== 'function_declarator') return false;
319
+ // A `function_declarator` whose inner declarator is a `parenthesized_declarator`
320
+ // is a function-pointer (or function-reference) field — e.g. `void (*cb)(int)`
321
+ // parses as function_declarator > parenthesized_declarator > pointer_declarator >
322
+ // field_identifier. Those are real data fields, not method declarations.
323
+ const inner = current.childForFieldName('declarator');
324
+ return inner?.type !== 'parenthesized_declarator';
325
+ }
326
+
327
+ /**
328
+ * Resolve the identifier of a declarator by walking through any combination of
329
+ * pointer/reference/array/parenthesized wrappers and `function_declarator`
330
+ * nodes. Used by both class-field extraction (where `function_declarator`
331
+ * indicates a function-pointer field after method declarations have been
332
+ * filtered out) and parameter extraction (where `function_declarator` wraps a
333
+ * bare function-type parameter name like `callback` in
334
+ * `void process(int callback(int))`).
335
+ */
336
+ function extractCudaFieldName(decl: TreeSitterNode): string {
337
+ let current: TreeSitterNode | null = decl;
338
+ while (current) {
339
+ if (current.type === 'identifier' || current.type === 'field_identifier') {
340
+ return current.text;
341
+ }
342
+ if (CUDA_DECLARATOR_WRAPPERS.has(current.type) || current.type === 'function_declarator') {
343
+ const next = innerCudaDeclarator(current);
344
+ if (!next) break;
345
+ current = next;
346
+ continue;
347
+ }
348
+ break;
349
+ }
350
+ return decl.text;
351
+ }
352
+
353
+ /**
354
+ * Find the inner declarator of a wrapper node. Most C++ declarator wrappers
355
+ * expose it via the `declarator` field, but some (e.g. `parenthesized_declarator`
356
+ * and `reference_declarator` in tree-sitter-cuda) have unnamed children — so
357
+ * fall back to scanning children for a declarator-shaped node.
358
+ */
359
+ function innerCudaDeclarator(node: TreeSitterNode): TreeSitterNode | null {
360
+ const named = node.childForFieldName('declarator');
361
+ if (named) return named;
362
+ for (let i = 0; i < node.childCount; i++) {
363
+ const child = node.child(i);
364
+ if (!child) continue;
365
+ if (
366
+ child.type === 'identifier' ||
367
+ child.type === 'field_identifier' ||
368
+ child.type === 'function_declarator' ||
369
+ CUDA_DECLARATOR_WRAPPERS.has(child.type)
370
+ ) {
371
+ return child;
372
+ }
373
+ }
374
+ return null;
375
+ }
376
+
303
377
  function extractCudaEnumEntries(enumNode: TreeSitterNode): SubDeclaration[] {
304
378
  const entries: SubDeclaration[] = [];
305
379
  const body = findChild(enumNode, 'enumerator_list');
@@ -5,7 +5,7 @@ import type {
5
5
  TreeSitterNode,
6
6
  TreeSitterTree,
7
7
  } from '../types.js';
8
- import { findChild, nodeEndLine } from './helpers.js';
8
+ import { findChild, iterChildren, nodeEndLine, PUNCTUATION_TOKENS } from './helpers.js';
9
9
 
10
10
  /**
11
11
  * Extract symbols from Elixir files.
@@ -190,14 +190,113 @@ function extractElixirParams(defCallNode: TreeSitterNode): SubDeclaration[] {
190
190
  for (let j = 0; j < innerArgs.childCount; j++) {
191
191
  const param = innerArgs.child(j);
192
192
  if (!param) continue;
193
- if (param.type === 'identifier') {
194
- params.push({ name: param.text, kind: 'parameter', line: param.startPosition.row + 1 });
195
- }
193
+ collectElixirParamIdentifiers(param, params);
196
194
  }
197
195
  }
198
196
  return params;
199
197
  }
200
198
 
199
+ /**
200
+ * Walk a parameter pattern and emit each bound identifier as a `parameter`
201
+ * child. Handles bare identifiers, default-value `a \\ default`, list-cons
202
+ * `[head | tail]`, list `[a, b, c]`, tuple `{x, y}`, and map / struct
203
+ * destructuring (`%{k: v}`, `%Foo{k: v}`).
204
+ *
205
+ * Implemented as an iterative worklist (rather than recursion + helpers) so
206
+ * the call graph has no function-level cycle: only one function performs the
207
+ * traversal and it invokes only leaf helpers (`pushSubNodes`, `pushMapValues`).
208
+ */
209
+ function collectElixirParamIdentifiers(root: TreeSitterNode, out: SubDeclaration[]): void {
210
+ const stack: TreeSitterNode[] = [root];
211
+ while (stack.length > 0) {
212
+ const node = stack.pop();
213
+ if (!node) continue;
214
+ switch (node.type) {
215
+ case 'identifier':
216
+ out.push({ name: node.text, kind: 'parameter', line: node.startPosition.row + 1 });
217
+ break;
218
+ case 'binary_operator':
219
+ pushElixirBinaryOperatorOperands(node, stack);
220
+ break;
221
+ case 'list':
222
+ case 'tuple':
223
+ pushElixirSequenceItems(node, stack);
224
+ break;
225
+ case 'map':
226
+ pushElixirMapValues(node, stack);
227
+ break;
228
+ }
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Push the binding-relevant operands of a `binary_operator` parameter onto the
234
+ * worklist:
235
+ * - `name \\ default` (default-value) binds the left operand only.
236
+ * - `head | tail` (list-cons, appears inside a `list` pattern) binds both.
237
+ */
238
+ function pushElixirBinaryOperatorOperands(node: TreeSitterNode, stack: TreeSitterNode[]): void {
239
+ const op = node.child(1);
240
+ if (!op) return;
241
+ if (op.type === '\\\\') {
242
+ const left = node.child(0);
243
+ if (left) stack.push(left);
244
+ return;
245
+ }
246
+ if (op.type === '|') {
247
+ const right = node.child(2);
248
+ const left = node.child(0);
249
+ if (right) stack.push(right);
250
+ if (left) stack.push(left);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Push the binding-relevant elements of a `list` or `tuple` parameter onto
256
+ * the worklist, skipping punctuation tokens.
257
+ *
258
+ * Items are pushed in reverse document order so that, with a LIFO stack, they
259
+ * are processed left-to-right — preserving the source ordering of bound names.
260
+ */
261
+ function pushElixirSequenceItems(node: TreeSitterNode, stack: TreeSitterNode[]): void {
262
+ const items: TreeSitterNode[] = [...iterChildren(node, PUNCTUATION_TOKENS)];
263
+ for (let i = items.length - 1; i >= 0; i--) {
264
+ stack.push(items[i] as TreeSitterNode);
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Push the value side of every pair in a `map` or `%Foo{...}` parameter onto
270
+ * the worklist. The struct alias (`Foo`) is a type, not a bound identifier, so
271
+ * the leading `struct` child is intentionally skipped.
272
+ *
273
+ * Items are collected in document order and pushed in reverse so that, with a
274
+ * LIFO stack, they are processed left-to-right — preserving source ordering.
275
+ */
276
+ function pushElixirMapValues(node: TreeSitterNode, stack: TreeSitterNode[]): void {
277
+ const parts: TreeSitterNode[] = [];
278
+ for (let i = 0; i < node.childCount; i++) {
279
+ const content = node.child(i);
280
+ if (!content || content.type !== 'map_content') continue;
281
+ for (let j = 0; j < content.childCount; j++) {
282
+ const kws = content.child(j);
283
+ if (!kws || kws.type !== 'keywords') continue;
284
+ for (let k = 0; k < kws.childCount; k++) {
285
+ const pair = kws.child(k);
286
+ if (!pair || pair.type !== 'pair') continue;
287
+ for (let p = 0; p < pair.childCount; p++) {
288
+ const part = pair.child(p);
289
+ if (!part || part.type === 'keyword') continue;
290
+ parts.push(part);
291
+ }
292
+ }
293
+ }
294
+ }
295
+ for (let i = parts.length - 1; i >= 0; i--) {
296
+ stack.push(parts[i] as TreeSitterNode);
297
+ }
298
+ }
299
+
201
300
  function handleDefprotocol(node: TreeSitterNode, ctx: ExtractorOutput): void {
202
301
  const args = findChild(node, 'arguments');
203
302
  if (!args) return;
@@ -70,7 +70,10 @@ function walkErlangNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
70
70
 
71
71
  function handleModuleAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
72
72
  // module_attribute: - module ( atom ) .
73
- const nameNode = findChild(node, 'atom');
73
+ // Prefer the named `name` field exposed by tree-sitter-erlang so we don't
74
+ // accidentally pick up the `module` keyword if a future grammar exposes it
75
+ // as a named `atom` child.
76
+ const nameNode = node.childForFieldName('name') ?? findChild(node, 'atom');
74
77
  if (!nameNode) return;
75
78
 
76
79
  ctx.definitions.push({
@@ -83,7 +86,10 @@ function handleModuleAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
83
86
 
84
87
  function handleRecordDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
85
88
  // record_decl: - record ( atom , { record_field, ... } ) .
86
- const nameNode = findChild(node, 'atom');
89
+ // Prefer the named `name` field exposed by tree-sitter-erlang; fall back to
90
+ // the first atom child for grammar versions that don't expose it. Mirrors
91
+ // the Rust `handle_record_decl` defensive pattern.
92
+ const nameNode = node.childForFieldName('name') ?? findChild(node, 'atom');
87
93
  if (!nameNode) return;
88
94
 
89
95
  const children: SubDeclaration[] = [];
@@ -112,7 +118,14 @@ function handleRecordDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
112
118
  }
113
119
 
114
120
  function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
115
- const nameNode = findChild(node, 'atom');
121
+ // type_alias: -type name(...) :: ty.
122
+ // Name is typically wrapped in a `type_name` node containing an `atom`.
123
+ // Mirrors the Rust `handle_type_alias` fallback so the two engines agree
124
+ // even when the grammar nests the name inside `type_name`.
125
+ const directAtom = findChild(node, 'atom');
126
+ const typeNameNode = !directAtom ? findChild(node, 'type_name') : null;
127
+ const wrappedAtom = typeNameNode ? findChild(typeNameNode, 'atom') : null;
128
+ const nameNode = directAtom ?? wrappedAtom;
116
129
  if (!nameNode) return;
117
130
 
118
131
  ctx.definitions.push({
@@ -134,13 +147,22 @@ function handleFunDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
134
147
 
135
148
  function handleFunctionClause(node: TreeSitterNode, ctx: ExtractorOutput): void {
136
149
  // function_clause: atom expr_args clause_body
137
- const nameNode = findChild(node, 'atom');
150
+ const nameNode = node.childForFieldName('name') ?? findChild(node, 'atom');
138
151
  if (!nameNode) return;
139
152
 
140
- // Don't duplicate if we already have this function
141
- if (ctx.definitions.some((d) => d.name === nameNode.text && d.kind === 'function')) return;
142
-
143
153
  const params = extractErlangParams(node);
154
+ const arity = params.length;
155
+
156
+ // Don't duplicate if we already have this function at the same arity.
157
+ // Erlang overloads by arity, so `foo/1` and `foo/2` are distinct definitions.
158
+ if (
159
+ ctx.definitions.some(
160
+ (d) =>
161
+ d.name === nameNode.text && d.kind === 'function' && (d.children?.length ?? 0) === arity,
162
+ )
163
+ ) {
164
+ return;
165
+ }
144
166
 
145
167
  ctx.definitions.push({
146
168
  name: nameNode.text,
@@ -154,31 +176,42 @@ function handleFunctionClause(node: TreeSitterNode, ctx: ExtractorOutput): void
154
176
 
155
177
  function extractErlangParams(clauseNode: TreeSitterNode): SubDeclaration[] {
156
178
  const params: SubDeclaration[] = [];
157
- const argsNode = findChild(clauseNode, 'expr_args');
179
+ const argsNode = clauseNode.childForFieldName('args') ?? findChild(clauseNode, 'expr_args');
158
180
  if (!argsNode) return params;
159
181
 
160
- for (let i = 0; i < argsNode.childCount; i++) {
161
- const child = argsNode.child(i);
182
+ // Iterate named children so every argument pattern counts as one parameter,
183
+ // independent of whether it is a bare `var`/`atom` or a complex destructuring
184
+ // pattern (tuple, list, binary, etc.). Punctuation tokens are anonymous and
185
+ // therefore excluded automatically.
186
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
187
+ const child = argsNode.namedChild(i);
162
188
  if (!child) continue;
163
- if (child.type === 'var') {
164
- params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
165
- }
166
- if (child.type === 'atom') {
167
- params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
168
- }
189
+ const label =
190
+ child.type === 'var' || child.type === 'atom'
191
+ ? child.text
192
+ : // Placeholder for complex patterns so arity is preserved.
193
+ `_${i}`;
194
+ params.push({ name: label, kind: 'parameter', line: child.startPosition.row + 1 });
169
195
  }
170
196
  return params;
171
197
  }
172
198
 
173
199
  function handleDefine(node: TreeSitterNode, ctx: ExtractorOutput): void {
174
200
  // pp_define: -define(NAME, value).
201
+ // For parametric macros, the grammar wraps the name in a `macro_lhs(name, args)`
202
+ // node. Inside `macro_lhs` the name comes first, followed by `(`, the argument
203
+ // `var` children, and `)`. We must therefore try `atom` (lowercase macros,
204
+ // e.g. `-define(foo(X), X+1)`) before `var` (uppercase macros, e.g.
205
+ // `-define(FOO(X), X+1)`) — otherwise `findChild(.., 'var')` skips the leading
206
+ // atom and lands on the first argument variable, mislabeling the definition.
207
+ // Mirrors the Rust `handle_define` so both engines agree.
175
208
  const nameNode =
176
209
  findChild(node, 'var') || findChild(node, 'atom') || findChild(node, 'macro_lhs');
177
210
  if (!nameNode) return;
178
211
 
179
212
  const name =
180
213
  nameNode.type === 'macro_lhs'
181
- ? (findChild(nameNode, 'var')?.text ?? nameNode.text)
214
+ ? (findChild(nameNode, 'atom')?.text ?? findChild(nameNode, 'var')?.text ?? nameNode.text)
182
215
  : nameNode.text;
183
216
 
184
217
  ctx.definitions.push({
@@ -194,9 +227,15 @@ function handleInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
194
227
  if (!strNode) return;
195
228
 
196
229
  const source = strNode.text.replace(/^"|"$/g, '');
230
+ // Preserve the distinction between local includes (`-include("foo.hrl")`)
231
+ // and OTP library includes (`-include_lib("kernel/include/file.hrl")`) so
232
+ // downstream consumers can apply the correct path-resolution strategy
233
+ // (local: relative to the source file; lib: relative to an OTP app root).
234
+ // Mirrors the Rust `handle_include` so both engines agree.
235
+ const kind = node.type === 'pp_include_lib' ? 'include_lib' : 'include';
197
236
  ctx.imports.push({
198
237
  source,
199
- names: ['include'],
238
+ names: [kind],
200
239
  line: node.startPosition.row + 1,
201
240
  });
202
241
  }
@@ -224,8 +263,12 @@ function handleImportAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
224
263
  }
225
264
 
226
265
  function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
227
- // call: first child is function ref (atom or remote), then expr_args
228
- const funcNode = node.child(0);
266
+ // call: first named child is function ref (atom or remote), then expr_args.
267
+ // Using `namedChild(0)` rather than `child(0)` skips anonymous tokens
268
+ // (punctuation, keywords) so a future grammar revision that inserts a
269
+ // leading anonymous node won't silently drop the call. Mirrors the Rust
270
+ // `handle_call` so both engines emit the same set of calls.
271
+ const funcNode = node.namedChild(0);
229
272
  if (!funcNode) return;
230
273
 
231
274
  if (funcNode.type === 'atom' || funcNode.type === 'identifier') {
@@ -10,6 +10,13 @@ import { findChild, nodeEndLine } from './helpers.js';
10
10
  /**
11
11
  * Extract symbols from F# files.
12
12
  *
13
+ * Grammar source: `tree-sitter-fsharp` v0.3.0 installed via a pinned GitHub
14
+ * tarball in `package.json` because the ionide/tree-sitter-fsharp project has
15
+ * no v0.3.0 release published to the npm registry. The cargo crate the native
16
+ * engine uses is also v0.3.0; both engines must stay aligned. Upgrading
17
+ * requires a manual edit of the tarball URL in `package.json` and
18
+ * `package-lock.json` — `npm update` will not bump this entry.
19
+ *
13
20
  * tree-sitter-fsharp grammar notes:
14
21
  * - named_module: top-level module declaration
15
22
  * - function_declaration_left: LHS of `let name params = ...`
@@ -42,6 +49,14 @@ function walkFSharpNode(
42
49
  case 'named_module':
43
50
  nextModule = handleNamedModule(node, ctx);
44
51
  break;
52
+ case 'module_defn':
53
+ // Nested signature module (`module Foo = ...`) in `.fsi` files,
54
+ // emitted by both the WASM (npm ionide tarball v0.3.0) and cargo
55
+ // v0.3.0 tree-sitter-fsharp signature grammars. Accumulate the
56
+ // dotted module path so nested `val` declarations are qualified
57
+ // as `Outer.Inner.foo` in parity with the native engine.
58
+ nextModule = handleModuleDefn(node, ctx, currentModule);
59
+ break;
45
60
  case 'function_declaration_left':
46
61
  handleFunctionDecl(node, ctx, currentModule);
47
62
  break;
@@ -57,6 +72,9 @@ function walkFSharpNode(
57
72
  case 'dot_expression':
58
73
  handleDotExpression(node, ctx);
59
74
  break;
75
+ case 'value_definition':
76
+ handleValueDefinition(node, ctx, currentModule);
77
+ break;
60
78
  }
61
79
 
62
80
  for (let i = 0; i < node.childCount; i++) {
@@ -79,6 +97,27 @@ function handleNamedModule(node: TreeSitterNode, ctx: ExtractorOutput): string |
79
97
  return nameNode.text;
80
98
  }
81
99
 
100
+ function handleModuleDefn(
101
+ node: TreeSitterNode,
102
+ ctx: ExtractorOutput,
103
+ currentModule: string | null,
104
+ ): string | null {
105
+ // `module_defn` (cargo 0.3.0 signature grammar) wraps `module Foo = ...`
106
+ // sections inside an outer `namespace` or another module. The name is a
107
+ // direct `identifier` child.
108
+ const nameNode = findChild(node, 'identifier');
109
+ if (!nameNode) return currentModule;
110
+
111
+ const qualified = currentModule ? `${currentModule}.${nameNode.text}` : nameNode.text;
112
+ ctx.definitions.push({
113
+ name: qualified,
114
+ kind: 'module',
115
+ line: node.startPosition.row + 1,
116
+ endLine: nodeEndLine(node),
117
+ });
118
+ return qualified;
119
+ }
120
+
82
121
  function handleFunctionDecl(
83
122
  node: TreeSitterNode,
84
123
  ctx: ExtractorOutput,
@@ -251,3 +290,68 @@ function handleDotExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
251
290
  ctx.calls.push(call);
252
291
  }
253
292
  }
293
+
294
+ // Handle `val name : type` declarations in `.fsi` signature files.
295
+ // The signature grammar reuses `value_definition` for `val` bindings,
296
+ // distinguished from the source grammar's `let` bindings by the first
297
+ // child being the literal `val` keyword. Source-file `value_definition`
298
+ // nodes (which start with `let`) are intentionally ignored to preserve
299
+ // `.fs` extractor parity.
300
+ function handleValueDefinition(
301
+ node: TreeSitterNode,
302
+ ctx: ExtractorOutput,
303
+ currentModule: string | null,
304
+ ): void {
305
+ const first = node.child(0);
306
+ if (!first || first.type !== 'val') return;
307
+
308
+ const declLeft = findChild(node, 'value_declaration_left');
309
+ if (!declLeft) return;
310
+
311
+ const pattern = findChild(declLeft, 'identifier_pattern');
312
+ if (!pattern) return;
313
+
314
+ const ident =
315
+ findChild(findChild(pattern, 'long_identifier_or_op') ?? pattern, 'identifier') ??
316
+ findChild(pattern, 'identifier');
317
+ if (!ident) return;
318
+
319
+ // The npm and cargo tree-sitter-fsharp 0.3.0 grammars — though sharing a
320
+ // version tag — emit type signatures with different node shapes:
321
+ // • WASM (npm 0.3.0 ionide tarball): `function_type` is the explicit
322
+ // function-type kind, present as a direct child of `value_definition`
323
+ // for `a -> b` types; plain values (e.g. `val pi : float`) appear as
324
+ // `simple_type`.
325
+ // • Native (cargo 0.3.0): every type signature is wrapped in
326
+ // `curried_spec`. A function type contains one or more `arguments_spec`
327
+ // children; a plain value wraps a single `simple_type`.
328
+ // Classify as a function whenever `function_type` appears OR a
329
+ // `curried_spec` contains an `arguments_spec` child, so both engines stay
330
+ // in parity until the grammars converge.
331
+ let hasFunctionType = false;
332
+ for (let i = 0; i < node.childCount; i++) {
333
+ const c = node.child(i);
334
+ if (!c) continue;
335
+ if (c.type === 'function_type') {
336
+ hasFunctionType = true;
337
+ break;
338
+ }
339
+ if (c.type === 'curried_spec') {
340
+ for (let j = 0; j < c.childCount; j++) {
341
+ if (c.child(j)?.type === 'arguments_spec') {
342
+ hasFunctionType = true;
343
+ break;
344
+ }
345
+ }
346
+ if (hasFunctionType) break;
347
+ }
348
+ }
349
+
350
+ const name = currentModule ? `${currentModule}.${ident.text}` : ident.text;
351
+ ctx.definitions.push({
352
+ name,
353
+ kind: hasFunctionType ? 'function' : 'variable',
354
+ line: node.startPosition.row + 1,
355
+ endLine: nodeEndLine(node),
356
+ });
357
+ }