@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
@@ -1,11 +1,13 @@
1
- import type {
2
- Call,
3
- ExtractorOutput,
4
- SubDeclaration,
5
- TreeSitterNode,
6
- TreeSitterTree,
7
- } from '../types.js';
8
- import { findChild, nodeEndLine, stripQuotes } from './helpers.js';
1
+ import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
2
+ import {
3
+ findChild,
4
+ findFirstChildOfTypes,
5
+ nodeEndLine,
6
+ nodeStartLine,
7
+ pushCall,
8
+ pushImport,
9
+ stripQuotes,
10
+ } from './helpers.js';
9
11
 
10
12
  /**
11
13
  * Extract symbols from Gleam files.
@@ -74,7 +76,7 @@ function handleFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
74
76
  ctx.definitions.push({
75
77
  name: nameNode.text,
76
78
  kind: 'function',
77
- line: node.startPosition.row + 1,
79
+ line: nodeStartLine(node),
78
80
  endLine: nodeEndLine(node),
79
81
  visibility,
80
82
  children: params.length > 0 ? params : undefined,
@@ -85,12 +87,15 @@ function handleExternalFunction(node: TreeSitterNode, ctx: ExtractorOutput): voi
85
87
  const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
86
88
  if (!nameNode) return;
87
89
 
90
+ const params = extractParams(node);
91
+
88
92
  ctx.definitions.push({
89
93
  name: nameNode.text,
90
94
  kind: 'function',
91
- line: node.startPosition.row + 1,
95
+ line: nodeStartLine(node),
92
96
  endLine: nodeEndLine(node),
93
97
  visibility: isPublic(node) ? 'public' : 'private',
98
+ children: params.length > 0 ? params : undefined,
94
99
  });
95
100
  }
96
101
 
@@ -104,10 +109,7 @@ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
104
109
  const child = node.child(i);
105
110
  if (!child) continue;
106
111
  if (child.type === 'data_constructor' || child.type === 'type_constructor') {
107
- const ctorName = child.childForFieldName('name') || findChild(child, 'constructor_name');
108
- if (ctorName) {
109
- children.push({ name: ctorName.text, kind: 'property', line: child.startPosition.row + 1 });
110
- }
112
+ pushConstructor(child, children);
111
113
  }
112
114
  // Recurse into constructors block
113
115
  if (child.type === 'data_constructors' || child.type === 'type_constructors') {
@@ -115,14 +117,7 @@ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
115
117
  const ctor = child.child(j);
116
118
  if (!ctor) continue;
117
119
  if (ctor.type === 'data_constructor' || ctor.type === 'type_constructor') {
118
- const ctorName = ctor.childForFieldName('name') || findChild(ctor, 'constructor_name');
119
- if (ctorName) {
120
- children.push({
121
- name: ctorName.text,
122
- kind: 'property',
123
- line: ctor.startPosition.row + 1,
124
- });
125
- }
120
+ pushConstructor(ctor, children);
126
121
  }
127
122
  }
128
123
  }
@@ -131,13 +126,20 @@ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
131
126
  ctx.definitions.push({
132
127
  name: nameNode.text,
133
128
  kind: 'type',
134
- line: node.startPosition.row + 1,
129
+ line: nodeStartLine(node),
135
130
  endLine: nodeEndLine(node),
136
131
  visibility: isPublic(node) ? 'public' : 'private',
137
132
  children: children.length > 0 ? children : undefined,
138
133
  });
139
134
  }
140
135
 
136
+ function pushConstructor(ctorNode: TreeSitterNode, out: SubDeclaration[]): void {
137
+ const ctorName = ctorNode.childForFieldName('name') || findChild(ctorNode, 'constructor_name');
138
+ if (ctorName) {
139
+ out.push({ name: ctorName.text, kind: 'property', line: nodeStartLine(ctorNode) });
140
+ }
141
+ }
142
+
141
143
  function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
142
144
  const nameNode = node.childForFieldName('name') || findChild(node, 'type_name');
143
145
  if (!nameNode) return;
@@ -145,7 +147,7 @@ function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
145
147
  ctx.definitions.push({
146
148
  name: nameNode.text,
147
149
  kind: 'type',
148
- line: node.startPosition.row + 1,
150
+ line: nodeStartLine(node),
149
151
  endLine: nodeEndLine(node),
150
152
  visibility: isPublic(node) ? 'public' : 'private',
151
153
  });
@@ -158,7 +160,7 @@ function handleConstant(node: TreeSitterNode, ctx: ExtractorOutput): void {
158
160
  ctx.definitions.push({
159
161
  name: nameNode.text,
160
162
  kind: 'variable',
161
- line: node.startPosition.row + 1,
163
+ line: nodeStartLine(node),
162
164
  endLine: nodeEndLine(node),
163
165
  visibility: isPublic(node) ? 'public' : 'private',
164
166
  });
@@ -166,7 +168,7 @@ function handleConstant(node: TreeSitterNode, ctx: ExtractorOutput): void {
166
168
 
167
169
  function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
168
170
  const moduleNode =
169
- node.childForFieldName('module') || findChild(node, 'module') || findChild(node, 'string');
171
+ node.childForFieldName('module') || findFirstChildOfTypes(node, ['module', 'string']);
170
172
  if (!moduleNode) return;
171
173
 
172
174
  const source = stripQuotes(moduleNode.text);
@@ -190,26 +192,25 @@ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
190
192
  names.push(alias.text);
191
193
  }
192
194
 
193
- ctx.imports.push({
194
- source,
195
- names: names.length > 0 ? names : [source.split('/').pop() || source],
196
- line: node.startPosition.row + 1,
197
- });
195
+ // `pushImport` falls back to the source basename when `names` is empty,
196
+ // preserving the previous `source.split('/').pop() || source` default.
197
+ pushImport(ctx, node, source, names);
198
198
  }
199
199
 
200
200
  function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
201
- const funcNode = node.childForFieldName('function') || node.child(0);
201
+ const funcNode = node.childForFieldName('function') || node.namedChild(0);
202
202
  if (!funcNode) return;
203
203
 
204
204
  if (funcNode.type === 'identifier' || funcNode.type === 'variable') {
205
- ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
205
+ pushCall(ctx, node, funcNode.text);
206
206
  } else if (funcNode.type === 'field_access' || funcNode.type === 'module_select') {
207
207
  const field = funcNode.childForFieldName('field') || funcNode.childForFieldName('label');
208
- const record = funcNode.child(0);
208
+ // Prefer the `record` field; fall back to first named child to skip
209
+ // anonymous punctuation tokens (the `.` between record and field).
210
+ const record = funcNode.childForFieldName('record') || funcNode.namedChild(0);
209
211
  if (field) {
210
- const call: Call = { name: field.text, line: node.startPosition.row + 1 };
211
- if (record && record !== field) call.receiver = record.text;
212
- ctx.calls.push(call);
212
+ const receiver = record && record !== field ? record.text : undefined;
213
+ pushCall(ctx, node, field.text, receiver !== undefined ? { receiver } : {});
213
214
  }
214
215
  }
215
216
  }
@@ -226,11 +227,11 @@ function extractParams(funcNode: TreeSitterNode): SubDeclaration[] {
226
227
  if (param.type === 'function_parameter' || param.type === 'parameter') {
227
228
  const nameNode = param.childForFieldName('name') || findChild(param, 'identifier');
228
229
  if (nameNode) {
229
- params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
230
+ params.push({ name: nameNode.text, kind: 'parameter', line: nodeStartLine(param) });
230
231
  }
231
232
  }
232
233
  if (param.type === 'identifier') {
233
- params.push({ name: param.text, kind: 'parameter', line: param.startPosition.row + 1 });
234
+ params.push({ name: param.text, kind: 'parameter', line: nodeStartLine(param) });
234
235
  }
235
236
  }
236
237
  return params;
@@ -68,6 +68,7 @@ function walkGroovyNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
68
68
  case 'method_invocation':
69
69
  case 'call_expression':
70
70
  case 'function_call':
71
+ case 'juxt_function_call':
71
72
  handleGroovyCallExpr(node, ctx);
72
73
  break;
73
74
  case 'object_creation_expression':
@@ -140,14 +141,57 @@ function handleGroovyClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void
140
141
  function handleGroovyInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
141
142
  const nameNode = node.childForFieldName('name');
142
143
  if (!nameNode) return;
144
+ const ifaceName = nameNode.text;
143
145
 
144
146
  ctx.definitions.push({
145
- name: nameNode.text,
147
+ name: ifaceName,
146
148
  kind: 'interface',
147
149
  line: node.startPosition.row + 1,
148
150
  endLine: nodeEndLine(node),
149
151
  visibility: extractModifierVisibility(node),
150
152
  });
153
+
154
+ // `interface X extends Y, Z` — tree-sitter-groovy 0.1.x exposes parent
155
+ // interfaces via an unnamed `extends_interfaces` child (not a field), which
156
+ // wraps a `type_list` of `_type` nodes. Mirrors the Rust extractor.
157
+ for (let i = 0; i < node.childCount; i++) {
158
+ const child = node.child(i);
159
+ if (child && child.type === 'extends_interfaces') {
160
+ collectGroovyParentInterfaces(child, ifaceName, ctx);
161
+ break;
162
+ }
163
+ }
164
+ }
165
+
166
+ function collectGroovyParentInterfaces(
167
+ parent: TreeSitterNode,
168
+ name: string,
169
+ ctx: ExtractorOutput,
170
+ ): void {
171
+ // Use the current node's start line at each recursion level — matches the
172
+ // Rust `collect_interfaces` helper, which re-evaluates `start_line(interfaces)`
173
+ // for whatever node (`extends_interfaces` → `type_list`) is being processed.
174
+ const line = parent.startPosition.row + 1;
175
+ for (let i = 0; i < parent.childCount; i++) {
176
+ const child = parent.child(i);
177
+ if (!child) continue;
178
+ switch (child.type) {
179
+ case 'type_identifier':
180
+ case 'identifier':
181
+ case 'scoped_type_identifier': {
182
+ ctx.classes.push({ name, implements: child.text, line });
183
+ break;
184
+ }
185
+ case 'generic_type': {
186
+ const inner = child.child(0)?.text;
187
+ if (inner) ctx.classes.push({ name, implements: inner, line });
188
+ break;
189
+ }
190
+ case 'type_list':
191
+ collectGroovyParentInterfaces(child, name, ctx);
192
+ break;
193
+ }
194
+ }
151
195
  }
152
196
 
153
197
  function handleGroovyEnumDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
@@ -81,19 +81,60 @@ function extractHaskellParams(funcNode: TreeSitterNode): SubDeclaration[] {
81
81
  if (child.type === 'patterns' || child.type === 'parameter') {
82
82
  for (let j = 0; j < child.childCount; j++) {
83
83
  const pat = child.child(j);
84
- if (pat && (pat.type === 'variable' || pat.type === 'identifier')) {
85
- params.push({ name: pat.text, kind: 'parameter', line: pat.startPosition.row + 1 });
86
- }
84
+ if (pat) collectHaskellPatternBindings(pat, params);
87
85
  }
88
86
  }
89
87
  if (child.type === 'variable' && i > 0) {
90
- // Pattern parameters after the function name
88
+ // Pattern parameters after the function name (no enclosing `patterns` node)
91
89
  params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
92
90
  }
93
91
  }
94
92
  return params;
95
93
  }
96
94
 
95
+ // Walk a pattern node and emit each bound variable (and `_` for wildcards) as a parameter.
96
+ // Container patterns — parens, constructor application, infix (cons), tuple, list, as, strict,
97
+ // irrefutable, qualified — are transparent: descend into their children. `record` is special:
98
+ // only the right-hand-side of each `field_pattern` is bound (the field name is not).
99
+ // Literals, bare constructors, and operators do not bind.
100
+ function collectHaskellPatternBindings(node: TreeSitterNode, out: SubDeclaration[]): void {
101
+ switch (node.type) {
102
+ case 'variable':
103
+ case 'identifier':
104
+ out.push({ name: node.text, kind: 'parameter', line: node.startPosition.row + 1 });
105
+ return;
106
+ case 'wildcard':
107
+ out.push({ name: '_', kind: 'parameter', line: node.startPosition.row + 1 });
108
+ return;
109
+ case 'parens':
110
+ case 'apply':
111
+ case 'infix':
112
+ case 'tuple':
113
+ case 'list':
114
+ case 'strict':
115
+ case 'irrefutable':
116
+ case 'as':
117
+ case 'qualified':
118
+ for (let i = 0; i < node.childCount; i++) {
119
+ const c = node.child(i);
120
+ if (c) collectHaskellPatternBindings(c, out);
121
+ }
122
+ return;
123
+ case 'record':
124
+ for (let i = 0; i < node.childCount; i++) {
125
+ const fp = node.child(i);
126
+ if (!fp || fp.type !== 'field_pattern') continue;
127
+ for (let j = 0; j < fp.childCount; j++) {
128
+ const g = fp.child(j);
129
+ if (g && g.type !== 'field_name') collectHaskellPatternBindings(g, out);
130
+ }
131
+ }
132
+ return;
133
+ default:
134
+ return;
135
+ }
136
+ }
137
+
97
138
  function handleHaskellBind(node: TreeSitterNode, ctx: ExtractorOutput): void {
98
139
  const nameNode = node.childForFieldName('name');
99
140
  if (!nameNode) return;
@@ -1,4 +1,11 @@
1
- import type { SubDeclaration, TreeSitterNode, TypeMapEntry } from '../types.js';
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ Import,
5
+ SubDeclaration,
6
+ TreeSitterNode,
7
+ TypeMapEntry,
8
+ } from '../types.js';
2
9
 
3
10
  /**
4
11
  * Maximum recursion depth for tree-sitter AST walkers.
@@ -6,6 +13,11 @@ import type { SubDeclaration, TreeSitterNode, TypeMapEntry } from '../types.js';
6
13
  */
7
14
  export const MAX_WALK_DEPTH = 200;
8
15
 
16
+ /** Convert a tree-sitter node's start row to a 1-based source line. */
17
+ export function nodeStartLine(node: TreeSitterNode): number {
18
+ return node.startPosition.row + 1;
19
+ }
20
+
9
21
  export function nodeEndLine(node: TreeSitterNode): number {
10
22
  return node.endPosition.row + 1;
11
23
  }
@@ -18,6 +30,56 @@ export function findChild(node: TreeSitterNode, type: string): TreeSitterNode |
18
30
  return null;
19
31
  }
20
32
 
33
+ /**
34
+ * Find the first child whose type is in `types`. Useful when several grammar
35
+ * variants name the same conceptual node differently (e.g. `string` vs
36
+ * `string_literal`). Returns the first match in document order, or null.
37
+ */
38
+ export function findFirstChildOfTypes(
39
+ node: TreeSitterNode,
40
+ types: readonly string[],
41
+ ): TreeSitterNode | null {
42
+ for (let i = 0; i < node.childCount; i++) {
43
+ const child = node.child(i);
44
+ if (child && types.includes(child.type)) return child;
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Iterate the direct children of `node` in document order, skipping nulls and
51
+ * tokens whose type appears in `skipTypes`. Mirrors the common
52
+ * `for (let i = 0; i < node.childCount; i++) { const c = node.child(i); if (...) continue; ... }`
53
+ * idiom while letting callers filter out grammar punctuation (`,`, `(`, `{`, etc.).
54
+ */
55
+ export function* iterChildren(
56
+ node: TreeSitterNode,
57
+ skipTypes: ReadonlySet<string> = EMPTY_SKIP_SET,
58
+ ): Generator<TreeSitterNode> {
59
+ for (let i = 0; i < node.childCount; i++) {
60
+ const child = node.child(i);
61
+ if (!child) continue;
62
+ if (skipTypes.has(child.type)) continue;
63
+ yield child;
64
+ }
65
+ }
66
+
67
+ const EMPTY_SKIP_SET: ReadonlySet<string> = new Set();
68
+
69
+ /** Common punctuation tokens — handy as a `skipTypes` set for `iterChildren`. */
70
+ export const PUNCTUATION_TOKENS: ReadonlySet<string> = new Set([
71
+ ',',
72
+ ';',
73
+ '(',
74
+ ')',
75
+ '[',
76
+ ']',
77
+ '{',
78
+ '}',
79
+ ':',
80
+ '.',
81
+ ]);
82
+
21
83
  /**
22
84
  * Merge a type-map entry, keeping the higher-confidence one.
23
85
  * Shared across all language extractors that build type maps for call resolution.
@@ -197,3 +259,145 @@ export function extractModifierVisibility(
197
259
  }
198
260
  return undefined;
199
261
  }
262
+
263
+ // ── Output-push helpers ────────────────────────────────────────────────────
264
+ //
265
+ // Most extractors finish with `ctx.calls.push({ name, line: node.startPosition.row + 1 })`
266
+ // or `ctx.imports.push({ source, names, line: node.startPosition.row + 1 })`.
267
+ // Centralising the construction keeps `line` derivation consistent and removes
268
+ // the ~108 hand-rolled `startPosition.row + 1` literals scattered across
269
+ // language extractors.
270
+
271
+ /**
272
+ * Append a `Call` to the extractor output. `line` defaults to the start line of
273
+ * `node`; pass `extra` for `receiver` / `dynamic` flags.
274
+ */
275
+ export function pushCall(
276
+ ctx: ExtractorOutput,
277
+ node: TreeSitterNode,
278
+ name: string,
279
+ extra: { receiver?: string; dynamic?: boolean } = {},
280
+ ): void {
281
+ if (!name) return;
282
+ const call: Call = { name, line: nodeStartLine(node) };
283
+ if (extra.receiver !== undefined) call.receiver = extra.receiver;
284
+ if (extra.dynamic !== undefined) call.dynamic = extra.dynamic;
285
+ ctx.calls.push(call);
286
+ }
287
+
288
+ /**
289
+ * Append an `Import` to the extractor output. `line` defaults to the start
290
+ * line of `node`. If `names` is empty, the source basename (split on `/`) is
291
+ * used as a single-name fallback — matching the convention in gleam, julia,
292
+ * and similar module-path imports.
293
+ */
294
+ export function pushImport(
295
+ ctx: ExtractorOutput,
296
+ node: TreeSitterNode,
297
+ source: string,
298
+ names: string[],
299
+ flags: Partial<Omit<Import, 'source' | 'names' | 'line'>> = {},
300
+ ): void {
301
+ if (!source) return;
302
+ const resolved = names.length > 0 ? names : [lastPathSegment(source, '/') || source];
303
+ const entry: Import = { source, names: resolved, line: nodeStartLine(node) };
304
+ Object.assign(entry, flags);
305
+ ctx.imports.push(entry);
306
+ }
307
+
308
+ // ── Parameter extraction ───────────────────────────────────────────────────
309
+
310
+ /**
311
+ * Options for {@link extractSimpleParameters}.
312
+ */
313
+ export interface ExtractParametersOptions {
314
+ /** Tree-sitter types that mark a single parameter node (e.g. `formal_parameter`). */
315
+ paramTypes: readonly string[];
316
+ /**
317
+ * Field name on each parameter that holds the bound identifier. Defaults to
318
+ * `'name'`. Pass `null` to use the parameter node itself when its type is in
319
+ * `paramTypes` and it has no `name` field (e.g. R's bare `identifier`).
320
+ */
321
+ nameField?: string | null;
322
+ /**
323
+ * If true, when `nameField` lookup fails fall back to the first `identifier`
324
+ * child of the parameter. Useful for Gleam / Solidity-style grammars.
325
+ */
326
+ fallbackToIdentifier?: boolean;
327
+ /**
328
+ * Optional type-map sink. When provided, the parameter's `type` field text
329
+ * (if present) is recorded with the given confidence.
330
+ */
331
+ typeMap?: Map<string, TypeMapEntry>;
332
+ /** Confidence used when writing into `typeMap`. Defaults to `0.9`. */
333
+ typeMapConfidence?: number;
334
+ /**
335
+ * Optional callback to derive the type text from the parameter's `type`
336
+ * field node. Defaults to `node.text`. Use this for languages where the
337
+ * `type` field is wrapped (e.g. Java `generic_type` → first child).
338
+ */
339
+ resolveType?: (typeNode: TreeSitterNode) => string | undefined;
340
+ }
341
+
342
+ /**
343
+ * Extract parameters from a parameter-list node using a uniform pattern.
344
+ *
345
+ * This collapses the boilerplate in `extract*Params` helpers across
346
+ * Java/Julia/Gleam/Solidity/R/etc. — each one walks the parameter list,
347
+ * matches a parameter node type, reads the `name` field, and pushes a
348
+ * `SubDeclaration` with `kind: 'parameter'`.
349
+ */
350
+ export function extractSimpleParameters(
351
+ paramListNode: TreeSitterNode | null,
352
+ options: ExtractParametersOptions,
353
+ ): SubDeclaration[] {
354
+ const params: SubDeclaration[] = [];
355
+ if (!paramListNode) return params;
356
+ const { paramTypes, nameField = 'name', fallbackToIdentifier = false } = options;
357
+
358
+ for (let i = 0; i < paramListNode.childCount; i++) {
359
+ const param = paramListNode.child(i);
360
+ if (!param || !paramTypes.includes(param.type)) continue;
361
+ const nameNode = resolveParamName(param, nameField, fallbackToIdentifier);
362
+ if (!nameNode) continue;
363
+ params.push({ name: nameNode.text, kind: 'parameter', line: nodeStartLine(param) });
364
+ recordParamType(param, nameNode.text, options);
365
+ }
366
+ return params;
367
+ }
368
+
369
+ /** Record a parameter's declared type into the type-map sink, if configured. */
370
+ function recordParamType(
371
+ param: TreeSitterNode,
372
+ paramName: string,
373
+ options: ExtractParametersOptions,
374
+ ): void {
375
+ const { typeMap, resolveType, typeMapConfidence = 0.9 } = options;
376
+ if (!typeMap) return;
377
+ const typeNode = param.childForFieldName('type');
378
+ if (!typeNode) return;
379
+ const typeText = resolveType ? resolveType(typeNode) : typeNode.text;
380
+ if (!typeText) return;
381
+ setTypeMapEntry(typeMap, paramName, typeText, typeMapConfidence);
382
+ }
383
+
384
+ /**
385
+ * Resolve the identifier node that names a parameter. Used by
386
+ * {@link extractSimpleParameters}; exposed so language-specific extractors
387
+ * can reuse the same lookup logic in custom loops.
388
+ */
389
+ export function resolveParamName(
390
+ paramNode: TreeSitterNode,
391
+ nameField: string | null,
392
+ fallbackToIdentifier: boolean,
393
+ ): TreeSitterNode | null {
394
+ if (nameField === null) {
395
+ return paramNode;
396
+ }
397
+ const named = paramNode.childForFieldName(nameField);
398
+ if (named) return named;
399
+ if (fallbackToIdentifier) {
400
+ return findChild(paramNode, 'identifier');
401
+ }
402
+ return null;
403
+ }