@optave/codegraph 3.5.0 → 3.7.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 (346) hide show
  1. package/README.md +47 -21
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +119 -127
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  10. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  11. package/dist/db/connection.d.ts +12 -2
  12. package/dist/db/connection.d.ts.map +1 -1
  13. package/dist/db/connection.js +81 -53
  14. package/dist/db/connection.js.map +1 -1
  15. package/dist/db/index.d.ts +1 -1
  16. package/dist/db/index.d.ts.map +1 -1
  17. package/dist/db/index.js +1 -1
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/db/migrations.d.ts.map +1 -1
  20. package/dist/db/migrations.js +38 -32
  21. package/dist/db/migrations.js.map +1 -1
  22. package/dist/domain/analysis/context.d.ts.map +1 -1
  23. package/dist/domain/analysis/context.js +51 -66
  24. package/dist/domain/analysis/context.js.map +1 -1
  25. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  26. package/dist/domain/analysis/dependencies.js +62 -70
  27. package/dist/domain/analysis/dependencies.js.map +1 -1
  28. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  29. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  30. package/dist/domain/analysis/exports.d.ts.map +1 -1
  31. package/dist/domain/analysis/exports.js +29 -33
  32. package/dist/domain/analysis/exports.js.map +1 -1
  33. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  34. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  35. package/dist/domain/analysis/fn-impact.js +35 -65
  36. package/dist/domain/analysis/fn-impact.js.map +1 -1
  37. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  38. package/dist/domain/analysis/module-map.js +91 -6
  39. package/dist/domain/analysis/module-map.js.map +1 -1
  40. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  41. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  42. package/dist/domain/analysis/query-helpers.js +27 -0
  43. package/dist/domain/analysis/query-helpers.js.map +1 -0
  44. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  45. package/dist/domain/graph/builder/helpers.js +15 -9
  46. package/dist/domain/graph/builder/helpers.js.map +1 -1
  47. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  48. package/dist/domain/graph/builder/incremental.js +3 -2
  49. package/dist/domain/graph/builder/incremental.js.map +1 -1
  50. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  51. package/dist/domain/graph/builder/pipeline.js +69 -3
  52. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  53. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  54. package/dist/domain/graph/builder/stages/build-edges.js +7 -51
  55. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  56. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/build-structure.js +7 -5
  58. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/collect-files.js +2 -2
  60. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  63. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  64. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  65. package/dist/domain/graph/builder/stages/finalize.js +124 -105
  66. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  67. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  68. package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
  69. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  70. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  71. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  72. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  73. package/dist/domain/graph/resolve.d.ts +0 -4
  74. package/dist/domain/graph/resolve.d.ts.map +1 -1
  75. package/dist/domain/graph/resolve.js +32 -48
  76. package/dist/domain/graph/resolve.js.map +1 -1
  77. package/dist/domain/graph/watcher.d.ts.map +1 -1
  78. package/dist/domain/graph/watcher.js +12 -12
  79. package/dist/domain/graph/watcher.js.map +1 -1
  80. package/dist/domain/parser.d.ts +1 -1
  81. package/dist/domain/parser.d.ts.map +1 -1
  82. package/dist/domain/parser.js +206 -101
  83. package/dist/domain/parser.js.map +1 -1
  84. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  85. package/dist/domain/search/search/cli-formatter.js +88 -83
  86. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  87. package/dist/extractors/bash.d.ts +6 -0
  88. package/dist/extractors/bash.d.ts.map +1 -0
  89. package/dist/extractors/bash.js +91 -0
  90. package/dist/extractors/bash.js.map +1 -0
  91. package/dist/extractors/c.d.ts +6 -0
  92. package/dist/extractors/c.d.ts.map +1 -0
  93. package/dist/extractors/c.js +204 -0
  94. package/dist/extractors/c.js.map +1 -0
  95. package/dist/extractors/cpp.d.ts +6 -0
  96. package/dist/extractors/cpp.d.ts.map +1 -0
  97. package/dist/extractors/cpp.js +283 -0
  98. package/dist/extractors/cpp.js.map +1 -0
  99. package/dist/extractors/csharp.d.ts.map +1 -1
  100. package/dist/extractors/csharp.js +42 -54
  101. package/dist/extractors/csharp.js.map +1 -1
  102. package/dist/extractors/dart.d.ts +6 -0
  103. package/dist/extractors/dart.d.ts.map +1 -0
  104. package/dist/extractors/dart.js +277 -0
  105. package/dist/extractors/dart.js.map +1 -0
  106. package/dist/extractors/elixir.d.ts +9 -0
  107. package/dist/extractors/elixir.d.ts.map +1 -0
  108. package/dist/extractors/elixir.js +223 -0
  109. package/dist/extractors/elixir.js.map +1 -0
  110. package/dist/extractors/go.d.ts.map +1 -1
  111. package/dist/extractors/go.js +126 -130
  112. package/dist/extractors/go.js.map +1 -1
  113. package/dist/extractors/haskell.d.ts +8 -0
  114. package/dist/extractors/haskell.d.ts.map +1 -0
  115. package/dist/extractors/haskell.js +217 -0
  116. package/dist/extractors/haskell.js.map +1 -0
  117. package/dist/extractors/hcl.js +6 -6
  118. package/dist/extractors/hcl.js.map +1 -1
  119. package/dist/extractors/helpers.d.ts +32 -1
  120. package/dist/extractors/helpers.d.ts.map +1 -1
  121. package/dist/extractors/helpers.js +74 -0
  122. package/dist/extractors/helpers.js.map +1 -1
  123. package/dist/extractors/index.d.ts +12 -0
  124. package/dist/extractors/index.d.ts.map +1 -1
  125. package/dist/extractors/index.js +12 -0
  126. package/dist/extractors/index.js.map +1 -1
  127. package/dist/extractors/java.d.ts.map +1 -1
  128. package/dist/extractors/java.js +32 -47
  129. package/dist/extractors/java.js.map +1 -1
  130. package/dist/extractors/javascript.d.ts.map +1 -1
  131. package/dist/extractors/javascript.js +306 -292
  132. package/dist/extractors/javascript.js.map +1 -1
  133. package/dist/extractors/kotlin.d.ts +6 -0
  134. package/dist/extractors/kotlin.d.ts.map +1 -0
  135. package/dist/extractors/kotlin.js +275 -0
  136. package/dist/extractors/kotlin.js.map +1 -0
  137. package/dist/extractors/lua.d.ts +6 -0
  138. package/dist/extractors/lua.d.ts.map +1 -0
  139. package/dist/extractors/lua.js +162 -0
  140. package/dist/extractors/lua.js.map +1 -0
  141. package/dist/extractors/ocaml.d.ts +6 -0
  142. package/dist/extractors/ocaml.d.ts.map +1 -0
  143. package/dist/extractors/ocaml.js +236 -0
  144. package/dist/extractors/ocaml.js.map +1 -0
  145. package/dist/extractors/php.d.ts.map +1 -1
  146. package/dist/extractors/php.js +39 -44
  147. package/dist/extractors/php.js.map +1 -1
  148. package/dist/extractors/python.d.ts.map +1 -1
  149. package/dist/extractors/python.js +75 -93
  150. package/dist/extractors/python.js.map +1 -1
  151. package/dist/extractors/ruby.js +6 -13
  152. package/dist/extractors/ruby.js.map +1 -1
  153. package/dist/extractors/rust.d.ts.map +1 -1
  154. package/dist/extractors/rust.js +58 -83
  155. package/dist/extractors/rust.js.map +1 -1
  156. package/dist/extractors/scala.d.ts +6 -0
  157. package/dist/extractors/scala.d.ts.map +1 -0
  158. package/dist/extractors/scala.js +269 -0
  159. package/dist/extractors/scala.js.map +1 -0
  160. package/dist/extractors/swift.d.ts +6 -0
  161. package/dist/extractors/swift.d.ts.map +1 -0
  162. package/dist/extractors/swift.js +275 -0
  163. package/dist/extractors/swift.js.map +1 -0
  164. package/dist/extractors/zig.d.ts +9 -0
  165. package/dist/extractors/zig.d.ts.map +1 -0
  166. package/dist/extractors/zig.js +276 -0
  167. package/dist/extractors/zig.js.map +1 -0
  168. package/dist/features/ast.d.ts +2 -0
  169. package/dist/features/ast.d.ts.map +1 -1
  170. package/dist/features/ast.js +9 -24
  171. package/dist/features/ast.js.map +1 -1
  172. package/dist/features/audit.d.ts.map +1 -1
  173. package/dist/features/audit.js +17 -21
  174. package/dist/features/audit.js.map +1 -1
  175. package/dist/features/branch-compare.d.ts.map +1 -1
  176. package/dist/features/branch-compare.js +47 -3
  177. package/dist/features/branch-compare.js.map +1 -1
  178. package/dist/features/cfg.d.ts +7 -1
  179. package/dist/features/cfg.d.ts.map +1 -1
  180. package/dist/features/cfg.js +72 -61
  181. package/dist/features/cfg.js.map +1 -1
  182. package/dist/features/check.d.ts.map +1 -1
  183. package/dist/features/check.js +79 -62
  184. package/dist/features/check.js.map +1 -1
  185. package/dist/features/complexity-query.d.ts.map +1 -1
  186. package/dist/features/complexity-query.js +142 -137
  187. package/dist/features/complexity-query.js.map +1 -1
  188. package/dist/features/complexity.d.ts +7 -1
  189. package/dist/features/complexity.d.ts.map +1 -1
  190. package/dist/features/complexity.js +62 -1
  191. package/dist/features/complexity.js.map +1 -1
  192. package/dist/features/dataflow.d.ts +7 -1
  193. package/dist/features/dataflow.d.ts.map +1 -1
  194. package/dist/features/dataflow.js +356 -188
  195. package/dist/features/dataflow.js.map +1 -1
  196. package/dist/features/graph-enrichment.d.ts.map +1 -1
  197. package/dist/features/graph-enrichment.js +117 -104
  198. package/dist/features/graph-enrichment.js.map +1 -1
  199. package/dist/features/sequence.d.ts.map +1 -1
  200. package/dist/features/sequence.js +25 -4
  201. package/dist/features/sequence.js.map +1 -1
  202. package/dist/features/structure-query.d.ts.map +1 -1
  203. package/dist/features/structure-query.js +29 -4
  204. package/dist/features/structure-query.js.map +1 -1
  205. package/dist/features/structure.d.ts.map +1 -1
  206. package/dist/features/structure.js +35 -15
  207. package/dist/features/structure.js.map +1 -1
  208. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  209. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  210. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  211. package/dist/graph/algorithms/leiden/index.js +43 -28
  212. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  213. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  214. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  215. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  216. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  217. package/dist/graph/algorithms/leiden/partition.js +89 -106
  218. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  219. package/dist/graph/model.d.ts +2 -0
  220. package/dist/graph/model.d.ts.map +1 -1
  221. package/dist/graph/model.js +20 -8
  222. package/dist/graph/model.js.map +1 -1
  223. package/dist/infrastructure/config.d.ts +0 -8
  224. package/dist/infrastructure/config.d.ts.map +1 -1
  225. package/dist/infrastructure/config.js +73 -62
  226. package/dist/infrastructure/config.js.map +1 -1
  227. package/dist/infrastructure/registry.d.ts +0 -8
  228. package/dist/infrastructure/registry.d.ts.map +1 -1
  229. package/dist/infrastructure/registry.js +12 -14
  230. package/dist/infrastructure/registry.js.map +1 -1
  231. package/dist/mcp/server.d.ts.map +1 -1
  232. package/dist/mcp/server.js +45 -36
  233. package/dist/mcp/server.js.map +1 -1
  234. package/dist/presentation/audit.d.ts.map +1 -1
  235. package/dist/presentation/audit.js +61 -57
  236. package/dist/presentation/audit.js.map +1 -1
  237. package/dist/presentation/branch-compare.d.ts.map +1 -1
  238. package/dist/presentation/branch-compare.js +56 -38
  239. package/dist/presentation/branch-compare.js.map +1 -1
  240. package/dist/presentation/check.d.ts.map +1 -1
  241. package/dist/presentation/check.js +30 -32
  242. package/dist/presentation/check.js.map +1 -1
  243. package/dist/presentation/colors.d.ts.map +1 -1
  244. package/dist/presentation/colors.js +2 -0
  245. package/dist/presentation/colors.js.map +1 -1
  246. package/dist/presentation/complexity.d.ts.map +1 -1
  247. package/dist/presentation/complexity.js +25 -19
  248. package/dist/presentation/complexity.js.map +1 -1
  249. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  250. package/dist/presentation/queries-cli/exports.js +15 -15
  251. package/dist/presentation/queries-cli/exports.js.map +1 -1
  252. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  253. package/dist/presentation/queries-cli/impact.js +29 -19
  254. package/dist/presentation/queries-cli/impact.js.map +1 -1
  255. package/dist/types.d.ts +182 -7
  256. package/dist/types.d.ts.map +1 -1
  257. package/grammars/tree-sitter-bash.wasm +0 -0
  258. package/grammars/tree-sitter-c.wasm +0 -0
  259. package/grammars/tree-sitter-cpp.wasm +0 -0
  260. package/grammars/tree-sitter-dart.wasm +0 -0
  261. package/grammars/tree-sitter-elixir.wasm +0 -0
  262. package/grammars/tree-sitter-haskell.wasm +0 -0
  263. package/grammars/tree-sitter-kotlin.wasm +0 -0
  264. package/grammars/tree-sitter-lua.wasm +0 -0
  265. package/grammars/tree-sitter-ocaml.wasm +0 -0
  266. package/grammars/tree-sitter-scala.wasm +0 -0
  267. package/grammars/tree-sitter-swift.wasm +0 -0
  268. package/grammars/tree-sitter-zig.wasm +0 -0
  269. package/package.json +19 -7
  270. package/src/ast-analysis/engine.ts +147 -138
  271. package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
  272. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  273. package/src/db/connection.ts +90 -59
  274. package/src/db/index.ts +1 -0
  275. package/src/db/migrations.ts +36 -32
  276. package/src/domain/analysis/context.ts +73 -75
  277. package/src/domain/analysis/dependencies.ts +78 -68
  278. package/src/domain/analysis/exports.ts +45 -34
  279. package/src/domain/analysis/fn-impact.ts +67 -64
  280. package/src/domain/analysis/module-map.ts +103 -8
  281. package/src/domain/analysis/query-helpers.ts +35 -0
  282. package/src/domain/graph/builder/helpers.ts +12 -6
  283. package/src/domain/graph/builder/incremental.ts +3 -2
  284. package/src/domain/graph/builder/pipeline.ts +71 -3
  285. package/src/domain/graph/builder/stages/build-edges.ts +10 -75
  286. package/src/domain/graph/builder/stages/build-structure.ts +9 -7
  287. package/src/domain/graph/builder/stages/collect-files.ts +2 -2
  288. package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
  289. package/src/domain/graph/builder/stages/finalize.ts +159 -125
  290. package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
  291. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  292. package/src/domain/graph/resolve.ts +34 -46
  293. package/src/domain/graph/watcher.ts +12 -14
  294. package/src/domain/parser.ts +222 -97
  295. package/src/domain/search/search/cli-formatter.ts +121 -94
  296. package/src/extractors/bash.ts +97 -0
  297. package/src/extractors/c.ts +212 -0
  298. package/src/extractors/cpp.ts +298 -0
  299. package/src/extractors/csharp.ts +53 -56
  300. package/src/extractors/dart.ts +304 -0
  301. package/src/extractors/elixir.ts +251 -0
  302. package/src/extractors/go.ts +152 -134
  303. package/src/extractors/haskell.ts +235 -0
  304. package/src/extractors/hcl.ts +6 -6
  305. package/src/extractors/helpers.ts +93 -1
  306. package/src/extractors/index.ts +12 -0
  307. package/src/extractors/java.ts +43 -48
  308. package/src/extractors/javascript.ts +328 -281
  309. package/src/extractors/kotlin.ts +293 -0
  310. package/src/extractors/lua.ts +169 -0
  311. package/src/extractors/ocaml.ts +259 -0
  312. package/src/extractors/php.ts +46 -40
  313. package/src/extractors/python.ts +81 -104
  314. package/src/extractors/ruby.ts +6 -13
  315. package/src/extractors/rust.ts +65 -85
  316. package/src/extractors/scala.ts +285 -0
  317. package/src/extractors/swift.ts +293 -0
  318. package/src/extractors/zig.ts +294 -0
  319. package/src/features/ast.ts +10 -25
  320. package/src/features/audit.ts +24 -20
  321. package/src/features/branch-compare.ts +51 -4
  322. package/src/features/cfg.ts +113 -65
  323. package/src/features/check.ts +90 -74
  324. package/src/features/complexity-query.ts +181 -163
  325. package/src/features/complexity.ts +64 -1
  326. package/src/features/dataflow.ts +462 -217
  327. package/src/features/graph-enrichment.ts +161 -117
  328. package/src/features/sequence.ts +27 -4
  329. package/src/features/structure-query.ts +43 -4
  330. package/src/features/structure.ts +50 -22
  331. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  332. package/src/graph/algorithms/leiden/index.ts +67 -28
  333. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  334. package/src/graph/algorithms/leiden/partition.ts +131 -98
  335. package/src/graph/model.ts +19 -7
  336. package/src/infrastructure/config.ts +60 -58
  337. package/src/infrastructure/registry.ts +17 -14
  338. package/src/mcp/server.ts +46 -37
  339. package/src/presentation/audit.ts +72 -67
  340. package/src/presentation/branch-compare.ts +54 -50
  341. package/src/presentation/check.ts +34 -34
  342. package/src/presentation/colors.ts +2 -0
  343. package/src/presentation/complexity.ts +39 -33
  344. package/src/presentation/queries-cli/exports.ts +17 -17
  345. package/src/presentation/queries-cli/impact.ts +30 -22
  346. package/src/types.ts +195 -7
@@ -12,7 +12,13 @@ import type {
12
12
  TreeSitterTree,
13
13
  TypeMapEntry,
14
14
  } from '../types.js';
15
- import { findChild, MAX_WALK_DEPTH, nodeEndLine } from './helpers.js';
15
+ import {
16
+ findChild,
17
+ findParentNode,
18
+ MAX_WALK_DEPTH,
19
+ nodeEndLine,
20
+ setTypeMapEntry,
21
+ } from './helpers.js';
16
22
 
17
23
  /** Built-in globals that start with uppercase but are not user-defined types. */
18
24
  const BUILTIN_GLOBALS: Set<string> = new Set([
@@ -87,6 +93,182 @@ export function extractSymbols(
87
93
 
88
94
  // ── Query-based extraction (fast path) ──────────────────────────────────────
89
95
 
96
+ /** Handle function_declaration capture. */
97
+ function handleFnCapture(c: Record<string, TreeSitterNode>, definitions: Definition[]): void {
98
+ const fnChildren = extractParameters(c.fn_node!);
99
+ definitions.push({
100
+ name: c.fn_name!.text,
101
+ kind: 'function',
102
+ line: c.fn_node!.startPosition.row + 1,
103
+ endLine: nodeEndLine(c.fn_node!),
104
+ children: fnChildren.length > 0 ? fnChildren : undefined,
105
+ });
106
+ }
107
+
108
+ /** Handle variable_declarator with arrow_function / function_expression capture. */
109
+ function handleVarFnCapture(c: Record<string, TreeSitterNode>, definitions: Definition[]): void {
110
+ const declNode = c.varfn_name!.parent?.parent;
111
+ const line = declNode ? declNode.startPosition.row + 1 : c.varfn_name!.startPosition.row + 1;
112
+ const varFnChildren = extractParameters(c.varfn_value!);
113
+ definitions.push({
114
+ name: c.varfn_name!.text,
115
+ kind: 'function',
116
+ line,
117
+ endLine: nodeEndLine(c.varfn_value!),
118
+ children: varFnChildren.length > 0 ? varFnChildren : undefined,
119
+ });
120
+ }
121
+
122
+ /** Handle class_declaration capture. */
123
+ function handleClassCapture(
124
+ c: Record<string, TreeSitterNode>,
125
+ definitions: Definition[],
126
+ classes: ClassRelation[],
127
+ ): void {
128
+ const className = c.cls_name!.text;
129
+ const startLine = c.cls_node!.startPosition.row + 1;
130
+ const clsChildren = extractClassProperties(c.cls_node!);
131
+ definitions.push({
132
+ name: className,
133
+ kind: 'class',
134
+ line: startLine,
135
+ endLine: nodeEndLine(c.cls_node!),
136
+ children: clsChildren.length > 0 ? clsChildren : undefined,
137
+ });
138
+ const heritage =
139
+ c.cls_node!.childForFieldName('heritage') || findChild(c.cls_node!, 'class_heritage');
140
+ if (heritage) {
141
+ const superName = extractSuperclass(heritage);
142
+ if (superName) classes.push({ name: className, extends: superName, line: startLine });
143
+ const implementsList = extractImplements(heritage);
144
+ for (const iface of implementsList) {
145
+ classes.push({ name: className, implements: iface, line: startLine });
146
+ }
147
+ }
148
+ }
149
+
150
+ /** Handle method_definition capture. */
151
+ function handleMethodCapture(c: Record<string, TreeSitterNode>, definitions: Definition[]): void {
152
+ const methName = c.meth_name!.text;
153
+ const parentClass = findParentClass(c.meth_node!);
154
+ const fullName = parentClass ? `${parentClass}.${methName}` : methName;
155
+ const methChildren = extractParameters(c.meth_node!);
156
+ const methVis = extractVisibility(c.meth_node!);
157
+ definitions.push({
158
+ name: fullName,
159
+ kind: 'method',
160
+ line: c.meth_node!.startPosition.row + 1,
161
+ endLine: nodeEndLine(c.meth_node!),
162
+ children: methChildren.length > 0 ? methChildren : undefined,
163
+ visibility: methVis,
164
+ });
165
+ }
166
+
167
+ /** Handle export_statement capture. */
168
+ function handleExportCapture(
169
+ c: Record<string, TreeSitterNode>,
170
+ exps: Export[],
171
+ imports: Import[],
172
+ ): void {
173
+ const exportLine = c.exp_node!.startPosition.row + 1;
174
+ const decl = c.exp_node!.childForFieldName('declaration');
175
+ if (decl) {
176
+ const declType = decl.type;
177
+ const kindMap: Record<string, string> = {
178
+ function_declaration: 'function',
179
+ class_declaration: 'class',
180
+ interface_declaration: 'interface',
181
+ type_alias_declaration: 'type',
182
+ };
183
+ const kind = kindMap[declType];
184
+ if (kind) {
185
+ const n = decl.childForFieldName('name');
186
+ if (n) exps.push({ name: n.text, kind: kind as Export['kind'], line: exportLine });
187
+ }
188
+ }
189
+ const source = c.exp_node!.childForFieldName('source') || findChild(c.exp_node!, 'string');
190
+ if (source && !decl) {
191
+ const modPath = source.text.replace(/['"]/g, '');
192
+ const reexportNames = extractImportNames(c.exp_node!);
193
+ const nodeText = c.exp_node!.text;
194
+ const isWildcard = nodeText.includes('export *') || nodeText.includes('export*');
195
+ imports.push({
196
+ source: modPath,
197
+ names: reexportNames,
198
+ line: exportLine,
199
+ reexport: true,
200
+ wildcardReexport: isWildcard && reexportNames.length === 0,
201
+ });
202
+ }
203
+ }
204
+
205
+ /** Dispatch a single query match to the appropriate handler. */
206
+ function dispatchQueryMatch(
207
+ c: Record<string, TreeSitterNode>,
208
+ definitions: Definition[],
209
+ calls: Call[],
210
+ imports: Import[],
211
+ classes: ClassRelation[],
212
+ exps: Export[],
213
+ ): void {
214
+ if (c.fn_node) {
215
+ handleFnCapture(c, definitions);
216
+ } else if (c.varfn_name) {
217
+ handleVarFnCapture(c, definitions);
218
+ } else if (c.cls_node) {
219
+ handleClassCapture(c, definitions, classes);
220
+ } else if (c.meth_node) {
221
+ handleMethodCapture(c, definitions);
222
+ } else if (c.iface_node) {
223
+ const ifaceName = c.iface_name!.text;
224
+ definitions.push({
225
+ name: ifaceName,
226
+ kind: 'interface',
227
+ line: c.iface_node.startPosition.row + 1,
228
+ endLine: nodeEndLine(c.iface_node),
229
+ });
230
+ const body =
231
+ c.iface_node.childForFieldName('body') ||
232
+ findChild(c.iface_node, 'interface_body') ||
233
+ findChild(c.iface_node, 'object_type');
234
+ if (body) extractInterfaceMethods(body, ifaceName, definitions);
235
+ } else if (c.type_node) {
236
+ definitions.push({
237
+ name: c.type_name!.text,
238
+ kind: 'type',
239
+ line: c.type_node.startPosition.row + 1,
240
+ endLine: nodeEndLine(c.type_node),
241
+ });
242
+ } else if (c.imp_node) {
243
+ const isTypeOnly = c.imp_node.text.startsWith('import type');
244
+ const modPath = c.imp_source!.text.replace(/['"]/g, '');
245
+ const names = extractImportNames(c.imp_node);
246
+ imports.push({
247
+ source: modPath,
248
+ names,
249
+ line: c.imp_node.startPosition.row + 1,
250
+ typeOnly: isTypeOnly,
251
+ });
252
+ } else if (c.exp_node) {
253
+ handleExportCapture(c, exps, imports);
254
+ } else if (c.callfn_node) {
255
+ calls.push({
256
+ name: c.callfn_name!.text,
257
+ line: c.callfn_node.startPosition.row + 1,
258
+ });
259
+ } else if (c.callmem_node) {
260
+ const callInfo = extractCallInfo(c.callmem_fn!, c.callmem_node);
261
+ if (callInfo) calls.push(callInfo);
262
+ const cbDef = extractCallbackDefinition(c.callmem_node, c.callmem_fn);
263
+ if (cbDef) definitions.push(cbDef);
264
+ } else if (c.callsub_node) {
265
+ const callInfo = extractCallInfo(c.callsub_fn!, c.callsub_node);
266
+ if (callInfo) calls.push(callInfo);
267
+ } else if (c.assign_node) {
268
+ handleCommonJSAssignment(c.assign_left!, c.assign_right!, c.assign_node, imports);
269
+ }
270
+ }
271
+
90
272
  function extractSymbolsQuery(tree: TreeSitterTree, query: TreeSitterQuery): ExtractorOutput {
91
273
  const definitions: Definition[] = [];
92
274
  const calls: Call[] = [];
@@ -101,151 +283,7 @@ function extractSymbolsQuery(tree: TreeSitterTree, query: TreeSitterQuery): Extr
101
283
  // Build capture lookup for this match (1-3 captures each, very fast)
102
284
  const c: Record<string, TreeSitterNode> = Object.create(null);
103
285
  for (const cap of match.captures) c[cap.name] = cap.node;
104
-
105
- if (c.fn_node) {
106
- // function_declaration
107
- const fnChildren = extractParameters(c.fn_node);
108
- definitions.push({
109
- name: c.fn_name!.text,
110
- kind: 'function',
111
- line: c.fn_node.startPosition.row + 1,
112
- endLine: nodeEndLine(c.fn_node),
113
- children: fnChildren.length > 0 ? fnChildren : undefined,
114
- });
115
- } else if (c.varfn_name) {
116
- // variable_declarator with arrow_function / function_expression
117
- const declNode = c.varfn_name.parent?.parent;
118
- const line = declNode ? declNode.startPosition.row + 1 : c.varfn_name.startPosition.row + 1;
119
- const varFnChildren = extractParameters(c.varfn_value!);
120
- definitions.push({
121
- name: c.varfn_name.text,
122
- kind: 'function',
123
- line,
124
- endLine: nodeEndLine(c.varfn_value!),
125
- children: varFnChildren.length > 0 ? varFnChildren : undefined,
126
- });
127
- } else if (c.cls_node) {
128
- // class_declaration
129
- const className = c.cls_name!.text;
130
- const startLine = c.cls_node.startPosition.row + 1;
131
- const clsChildren = extractClassProperties(c.cls_node);
132
- definitions.push({
133
- name: className,
134
- kind: 'class',
135
- line: startLine,
136
- endLine: nodeEndLine(c.cls_node),
137
- children: clsChildren.length > 0 ? clsChildren : undefined,
138
- });
139
- const heritage =
140
- c.cls_node.childForFieldName('heritage') || findChild(c.cls_node, 'class_heritage');
141
- if (heritage) {
142
- const superName = extractSuperclass(heritage);
143
- if (superName) classes.push({ name: className, extends: superName, line: startLine });
144
- const implementsList = extractImplements(heritage);
145
- for (const iface of implementsList) {
146
- classes.push({ name: className, implements: iface, line: startLine });
147
- }
148
- }
149
- } else if (c.meth_node) {
150
- // method_definition
151
- const methName = c.meth_name!.text;
152
- const parentClass = findParentClass(c.meth_node);
153
- const fullName = parentClass ? `${parentClass}.${methName}` : methName;
154
- const methChildren = extractParameters(c.meth_node);
155
- const methVis = extractVisibility(c.meth_node);
156
- definitions.push({
157
- name: fullName,
158
- kind: 'method',
159
- line: c.meth_node.startPosition.row + 1,
160
- endLine: nodeEndLine(c.meth_node),
161
- children: methChildren.length > 0 ? methChildren : undefined,
162
- visibility: methVis,
163
- });
164
- } else if (c.iface_node) {
165
- // interface_declaration (TS/TSX only)
166
- const ifaceName = c.iface_name!.text;
167
- definitions.push({
168
- name: ifaceName,
169
- kind: 'interface',
170
- line: c.iface_node.startPosition.row + 1,
171
- endLine: nodeEndLine(c.iface_node),
172
- });
173
- const body =
174
- c.iface_node.childForFieldName('body') ||
175
- findChild(c.iface_node, 'interface_body') ||
176
- findChild(c.iface_node, 'object_type');
177
- if (body) extractInterfaceMethods(body, ifaceName, definitions);
178
- } else if (c.type_node) {
179
- // type_alias_declaration (TS/TSX only)
180
- definitions.push({
181
- name: c.type_name!.text,
182
- kind: 'type',
183
- line: c.type_node.startPosition.row + 1,
184
- endLine: nodeEndLine(c.type_node),
185
- });
186
- } else if (c.imp_node) {
187
- // import_statement
188
- const isTypeOnly = c.imp_node.text.startsWith('import type');
189
- const modPath = c.imp_source!.text.replace(/['"]/g, '');
190
- const names = extractImportNames(c.imp_node);
191
- imports.push({
192
- source: modPath,
193
- names,
194
- line: c.imp_node.startPosition.row + 1,
195
- typeOnly: isTypeOnly,
196
- });
197
- } else if (c.exp_node) {
198
- // export_statement
199
- const exportLine = c.exp_node.startPosition.row + 1;
200
- const decl = c.exp_node.childForFieldName('declaration');
201
- if (decl) {
202
- const declType = decl.type;
203
- const kindMap: Record<string, string> = {
204
- function_declaration: 'function',
205
- class_declaration: 'class',
206
- interface_declaration: 'interface',
207
- type_alias_declaration: 'type',
208
- };
209
- const kind = kindMap[declType];
210
- if (kind) {
211
- const n = decl.childForFieldName('name');
212
- if (n) exps.push({ name: n.text, kind: kind as Export['kind'], line: exportLine });
213
- }
214
- }
215
- const source = c.exp_node.childForFieldName('source') || findChild(c.exp_node, 'string');
216
- if (source && !decl) {
217
- const modPath = source.text.replace(/['"]/g, '');
218
- const reexportNames = extractImportNames(c.exp_node);
219
- const nodeText = c.exp_node.text;
220
- const isWildcard = nodeText.includes('export *') || nodeText.includes('export*');
221
- imports.push({
222
- source: modPath,
223
- names: reexportNames,
224
- line: exportLine,
225
- reexport: true,
226
- wildcardReexport: isWildcard && reexportNames.length === 0,
227
- });
228
- }
229
- } else if (c.callfn_node) {
230
- // call_expression with identifier function
231
- calls.push({
232
- name: c.callfn_name!.text,
233
- line: c.callfn_node.startPosition.row + 1,
234
- });
235
- } else if (c.callmem_node) {
236
- // call_expression with member_expression function
237
- const callInfo = extractCallInfo(c.callmem_fn!, c.callmem_node);
238
- if (callInfo) calls.push(callInfo);
239
- const cbDef = extractCallbackDefinition(c.callmem_node, c.callmem_fn);
240
- if (cbDef) definitions.push(cbDef);
241
- } else if (c.callsub_node) {
242
- // call_expression with subscript_expression function
243
- const callInfo = extractCallInfo(c.callsub_fn!, c.callsub_node);
244
- if (callInfo) calls.push(callInfo);
245
- } else if (c.assign_node) {
246
- // CommonJS: module.exports = require(...) / module.exports = { ...require(...) }
247
- handleCommonJSAssignment(c.assign_left!, c.assign_right!, c.assign_node, imports);
248
- }
286
+ dispatchQueryMatch(c, definitions, calls, imports, classes, exps);
249
287
  }
250
288
 
251
289
  // Extract top-level constants via targeted walk (query patterns don't cover these)
@@ -371,48 +409,49 @@ function handleCommonJSAssignment(
371
409
  const leftText = left.text;
372
410
  if (!leftText.startsWith('module.exports') && leftText !== 'exports') return;
373
411
 
374
- const rightType = right.type;
375
412
  const assignLine = node.startPosition.row + 1;
376
413
 
377
- if (rightType === 'call_expression') {
378
- const fn = right.childForFieldName('function');
379
- const args = right.childForFieldName('arguments') || findChild(right, 'arguments');
380
- if (fn && fn.text === 'require' && args) {
381
- const strArg = findChild(args, 'string');
382
- if (strArg) {
383
- imports.push({
384
- source: strArg.text.replace(/['"]/g, ''),
385
- names: [],
386
- line: assignLine,
387
- reexport: true,
388
- wildcardReexport: true,
389
- });
390
- }
414
+ // module.exports = require("…") — direct re-export
415
+ if (right.type === 'call_expression') {
416
+ extractRequireReexport(right, assignLine, imports);
417
+ }
418
+
419
+ // module.exports = { ...require("…") } — spread re-export
420
+ if (right.type === 'object') {
421
+ extractSpreadRequireReexports(right, assignLine, imports);
422
+ }
423
+ }
424
+
425
+ /** Extract a direct `require()` re-export from a call_expression. */
426
+ function extractRequireReexport(callExpr: TreeSitterNode, line: number, imports: Import[]): void {
427
+ const fn = callExpr.childForFieldName('function');
428
+ const args = callExpr.childForFieldName('arguments') || findChild(callExpr, 'arguments');
429
+ if (fn && fn.text === 'require' && args) {
430
+ const strArg = findChild(args, 'string');
431
+ if (strArg) {
432
+ imports.push({
433
+ source: strArg.text.replace(/['"]/g, ''),
434
+ names: [],
435
+ line,
436
+ reexport: true,
437
+ wildcardReexport: true,
438
+ });
391
439
  }
392
440
  }
441
+ }
393
442
 
394
- if (rightType === 'object') {
395
- for (let ci = 0; ci < right.childCount; ci++) {
396
- const child = right.child(ci);
397
- if (child && child.type === 'spread_element') {
398
- const spreadExpr = child.child(1) || child.childForFieldName('value');
399
- if (spreadExpr && spreadExpr.type === 'call_expression') {
400
- const fn2 = spreadExpr.childForFieldName('function');
401
- const args2 =
402
- spreadExpr.childForFieldName('arguments') || findChild(spreadExpr, 'arguments');
403
- if (fn2 && fn2.text === 'require' && args2) {
404
- const strArg2 = findChild(args2, 'string');
405
- if (strArg2) {
406
- imports.push({
407
- source: strArg2.text.replace(/['"]/g, ''),
408
- names: [],
409
- line: assignLine,
410
- reexport: true,
411
- wildcardReexport: true,
412
- });
413
- }
414
- }
415
- }
443
+ /** Extract `...require()` re-exports from spread elements inside an object literal. */
444
+ function extractSpreadRequireReexports(
445
+ objectNode: TreeSitterNode,
446
+ line: number,
447
+ imports: Import[],
448
+ ): void {
449
+ for (let ci = 0; ci < objectNode.childCount; ci++) {
450
+ const child = objectNode.child(ci);
451
+ if (child && child.type === 'spread_element') {
452
+ const spreadExpr = child.child(1) || child.childForFieldName('value');
453
+ if (spreadExpr && spreadExpr.type === 'call_expression') {
454
+ extractRequireReexport(spreadExpr, line, imports);
416
455
  }
417
456
  }
418
457
  }
@@ -936,56 +975,13 @@ function extractNewExprTypeName(newExprNode: TreeSitterNode): string | null {
936
975
  * Higher-confidence entries take priority when the same variable is seen twice.
937
976
  */
938
977
  function extractTypeMapWalk(rootNode: TreeSitterNode, typeMap: Map<string, TypeMapEntry>): void {
939
- function setIfHigher(name: string, type: string, confidence: number): void {
940
- const existing = typeMap.get(name);
941
- if (!existing || confidence > existing.confidence) {
942
- typeMap.set(name, { type, confidence });
943
- }
944
- }
945
-
946
978
  function walk(node: TreeSitterNode, depth: number): void {
947
979
  if (depth >= MAX_WALK_DEPTH) return;
948
980
  const t = node.type;
949
981
  if (t === 'variable_declarator') {
950
- const nameN = node.childForFieldName('name');
951
- if (nameN && nameN.type === 'identifier') {
952
- const typeAnno = findChild(node, 'type_annotation');
953
- if (typeAnno) {
954
- const typeName = extractSimpleTypeName(typeAnno);
955
- if (typeName) setIfHigher(nameN.text, typeName, 0.9);
956
- }
957
- const valueN = node.childForFieldName('value');
958
- if (valueN) {
959
- // Constructor: const x = new Foo() → confidence 1.0
960
- if (valueN.type === 'new_expression') {
961
- const ctorType = extractNewExprTypeName(valueN);
962
- if (ctorType) setIfHigher(nameN.text, ctorType, 1.0);
963
- }
964
- // Factory method: const x = Foo.create() → confidence 0.7
965
- else if (valueN.type === 'call_expression') {
966
- const fn = valueN.childForFieldName('function');
967
- if (fn && fn.type === 'member_expression') {
968
- const obj = fn.childForFieldName('object');
969
- if (obj && obj.type === 'identifier') {
970
- const objName = obj.text;
971
- if (objName[0]! !== objName[0]!.toLowerCase() && !BUILTIN_GLOBALS.has(objName)) {
972
- setIfHigher(nameN.text, objName, 0.7);
973
- }
974
- }
975
- }
976
- }
977
- }
978
- }
982
+ handleVarDeclaratorTypeMap(node, typeMap);
979
983
  } else if (t === 'required_parameter' || t === 'optional_parameter') {
980
- const nameNode =
981
- node.childForFieldName('pattern') || node.childForFieldName('left') || node.child(0);
982
- if (nameNode && nameNode.type === 'identifier') {
983
- const typeAnno = findChild(node, 'type_annotation');
984
- if (typeAnno) {
985
- const typeName = extractSimpleTypeName(typeAnno);
986
- if (typeName) setIfHigher(nameNode.text, typeName, 0.9);
987
- }
988
- }
984
+ handleParamTypeMap(node, typeMap);
989
985
  }
990
986
  for (let i = 0; i < node.childCount; i++) {
991
987
  walk(node.child(i)!, depth + 1);
@@ -994,6 +990,56 @@ function extractTypeMapWalk(rootNode: TreeSitterNode, typeMap: Map<string, TypeM
994
990
  walk(rootNode, 0);
995
991
  }
996
992
 
993
+ /** Extract type info from a variable_declarator: type annotation, constructor, or factory. */
994
+ function handleVarDeclaratorTypeMap(
995
+ node: TreeSitterNode,
996
+ typeMap: Map<string, TypeMapEntry>,
997
+ ): void {
998
+ const nameN = node.childForFieldName('name');
999
+ if (!nameN || nameN.type !== 'identifier') return;
1000
+
1001
+ // Type annotation: const x: Foo = …
1002
+ const typeAnno = findChild(node, 'type_annotation');
1003
+ if (typeAnno) {
1004
+ const typeName = extractSimpleTypeName(typeAnno);
1005
+ if (typeName) setTypeMapEntry(typeMap, nameN.text, typeName, 0.9);
1006
+ }
1007
+
1008
+ const valueN = node.childForFieldName('value');
1009
+ if (!valueN) return;
1010
+
1011
+ // Constructor: const x = new Foo() → confidence 1.0
1012
+ if (valueN.type === 'new_expression') {
1013
+ const ctorType = extractNewExprTypeName(valueN);
1014
+ if (ctorType) setTypeMapEntry(typeMap, nameN.text, ctorType, 1.0);
1015
+ }
1016
+ // Factory method: const x = Foo.create() → confidence 0.7
1017
+ else if (valueN.type === 'call_expression') {
1018
+ const fn = valueN.childForFieldName('function');
1019
+ if (fn && fn.type === 'member_expression') {
1020
+ const obj = fn.childForFieldName('object');
1021
+ if (obj && obj.type === 'identifier') {
1022
+ const objName = obj.text;
1023
+ if (objName[0]! !== objName[0]!.toLowerCase() && !BUILTIN_GLOBALS.has(objName)) {
1024
+ setTypeMapEntry(typeMap, nameN.text, objName, 0.7);
1025
+ }
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+
1031
+ /** Extract type info from a required_parameter or optional_parameter. */
1032
+ function handleParamTypeMap(node: TreeSitterNode, typeMap: Map<string, TypeMapEntry>): void {
1033
+ const nameNode =
1034
+ node.childForFieldName('pattern') || node.childForFieldName('left') || node.child(0);
1035
+ if (!nameNode || nameNode.type !== 'identifier') return;
1036
+ const typeAnno = findChild(node, 'type_annotation');
1037
+ if (typeAnno) {
1038
+ const typeName = extractSimpleTypeName(typeAnno);
1039
+ if (typeName) setTypeMapEntry(typeMap, nameNode.text, typeName, 0.9);
1040
+ }
1041
+ }
1042
+
997
1043
  function extractReceiverName(objNode: TreeSitterNode | null): string | undefined {
998
1044
  if (!objNode) return undefined;
999
1045
  const t = objNode.type;
@@ -1006,57 +1052,66 @@ function extractCallInfo(fn: TreeSitterNode, callNode: TreeSitterNode): Call | n
1006
1052
  if (fnType === 'identifier') {
1007
1053
  return { name: fn.text, line: callNode.startPosition.row + 1 };
1008
1054
  }
1009
-
1010
1055
  if (fnType === 'member_expression') {
1011
- const obj = fn.childForFieldName('object');
1012
- const prop = fn.childForFieldName('property');
1013
- if (!prop) return null;
1014
-
1015
- const callLine = callNode.startPosition.row + 1;
1016
- const propText = prop.text;
1017
-
1018
- if (propText === 'call' || propText === 'apply' || propText === 'bind') {
1019
- if (obj && obj.type === 'identifier')
1020
- return { name: obj.text, line: callLine, dynamic: true };
1021
- if (obj && obj.type === 'member_expression') {
1022
- const innerProp = obj.childForFieldName('property');
1023
- if (innerProp) return { name: innerProp.text, line: callLine, dynamic: true };
1024
- }
1025
- }
1056
+ return extractMemberExprCallInfo(fn, callNode);
1057
+ }
1058
+ if (fnType === 'subscript_expression') {
1059
+ return extractSubscriptCallInfo(fn, callNode);
1060
+ }
1061
+ return null;
1062
+ }
1026
1063
 
1027
- const propType = prop.type;
1028
- if (propType === 'string' || propType === 'string_fragment') {
1029
- const methodName = propText.replace(/['"]/g, '');
1030
- if (methodName) {
1031
- const receiver = extractReceiverName(obj);
1032
- return { name: methodName, line: callLine, dynamic: true, receiver };
1033
- }
1034
- }
1064
+ /** Extract call info from a member_expression function node (obj.method()). */
1065
+ function extractMemberExprCallInfo(fn: TreeSitterNode, callNode: TreeSitterNode): Call | null {
1066
+ const obj = fn.childForFieldName('object');
1067
+ const prop = fn.childForFieldName('property');
1068
+ if (!prop) return null;
1069
+
1070
+ const callLine = callNode.startPosition.row + 1;
1071
+ const propText = prop.text;
1035
1072
 
1036
- const receiver = extractReceiverName(obj);
1037
- return { name: propText, line: callLine, receiver };
1073
+ // .call()/.apply()/.bind() dynamic invocation
1074
+ if (propText === 'call' || propText === 'apply' || propText === 'bind') {
1075
+ if (obj && obj.type === 'identifier') return { name: obj.text, line: callLine, dynamic: true };
1076
+ if (obj && obj.type === 'member_expression') {
1077
+ const innerProp = obj.childForFieldName('property');
1078
+ if (innerProp) return { name: innerProp.text, line: callLine, dynamic: true };
1079
+ }
1038
1080
  }
1039
1081
 
1040
- if (fnType === 'subscript_expression') {
1041
- const obj = fn.childForFieldName('object');
1042
- const index = fn.childForFieldName('index');
1043
- if (index) {
1044
- const indexType = index.type;
1045
- if (indexType === 'string' || indexType === 'template_string') {
1046
- const methodName = index.text.replace(/['"`]/g, '');
1047
- if (methodName && !methodName.includes('$')) {
1048
- const receiver = extractReceiverName(obj);
1049
- return {
1050
- name: methodName,
1051
- line: callNode.startPosition.row + 1,
1052
- dynamic: true,
1053
- receiver,
1054
- };
1055
- }
1056
- }
1082
+ // Computed property: obj["method"]()
1083
+ const propType = prop.type;
1084
+ if (propType === 'string' || propType === 'string_fragment') {
1085
+ const methodName = propText.replace(/['"]/g, '');
1086
+ if (methodName) {
1087
+ const receiver = extractReceiverName(obj);
1088
+ return { name: methodName, line: callLine, dynamic: true, receiver };
1057
1089
  }
1058
1090
  }
1059
1091
 
1092
+ const receiver = extractReceiverName(obj);
1093
+ return { name: propText, line: callLine, receiver };
1094
+ }
1095
+
1096
+ /** Extract call info from a subscript_expression function node (obj["method"]()). */
1097
+ function extractSubscriptCallInfo(fn: TreeSitterNode, callNode: TreeSitterNode): Call | null {
1098
+ const obj = fn.childForFieldName('object');
1099
+ const index = fn.childForFieldName('index');
1100
+ if (!index) return null;
1101
+
1102
+ const indexType = index.type;
1103
+ if (indexType === 'string' || indexType === 'template_string') {
1104
+ const methodName = index.text.replace(/['"`]/g, '');
1105
+ if (methodName && !methodName.includes('$')) {
1106
+ const receiver = extractReceiverName(obj);
1107
+ return {
1108
+ name: methodName,
1109
+ line: callNode.startPosition.row + 1,
1110
+ dynamic: true,
1111
+ receiver,
1112
+ };
1113
+ }
1114
+ }
1060
1115
  return null;
1061
1116
  }
1062
1117
 
@@ -1191,17 +1246,9 @@ function extractSuperclass(heritage: TreeSitterNode): string | null {
1191
1246
  return null;
1192
1247
  }
1193
1248
 
1249
+ const JS_CLASS_TYPES = ['class_declaration', 'class'] as const;
1194
1250
  function findParentClass(node: TreeSitterNode): string | null {
1195
- let current = node.parent;
1196
- while (current) {
1197
- const t = current.type;
1198
- if (t === 'class_declaration' || t === 'class') {
1199
- const nameNode = current.childForFieldName('name');
1200
- return nameNode ? nameNode.text : null;
1201
- }
1202
- current = current.parent;
1203
- }
1204
- return null;
1251
+ return findParentNode(node, JS_CLASS_TYPES);
1205
1252
  }
1206
1253
 
1207
1254
  function extractImportNames(node: TreeSitterNode): string[] {