@optave/codegraph 3.8.0 → 3.9.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 (310) hide show
  1. package/README.md +13 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +137 -86
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +81 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/branch-compare.d.ts.map +1 -1
  28. package/dist/cli/commands/branch-compare.js +4 -0
  29. package/dist/cli/commands/branch-compare.js.map +1 -1
  30. package/dist/cli/commands/diff-impact.d.ts.map +1 -1
  31. package/dist/cli/commands/diff-impact.js +2 -1
  32. package/dist/cli/commands/diff-impact.js.map +1 -1
  33. package/dist/cli/commands/info.d.ts.map +1 -1
  34. package/dist/cli/commands/info.js +3 -2
  35. package/dist/cli/commands/info.js.map +1 -1
  36. package/dist/cli/commands/watch.d.ts.map +1 -1
  37. package/dist/cli/commands/watch.js +16 -2
  38. package/dist/cli/commands/watch.js.map +1 -1
  39. package/dist/db/connection.d.ts.map +1 -1
  40. package/dist/db/connection.js +29 -26
  41. package/dist/db/connection.js.map +1 -1
  42. package/dist/db/query-builder.d.ts.map +1 -1
  43. package/dist/db/query-builder.js +16 -5
  44. package/dist/db/query-builder.js.map +1 -1
  45. package/dist/db/repository/base.d.ts +16 -0
  46. package/dist/db/repository/base.d.ts.map +1 -1
  47. package/dist/db/repository/base.js +31 -0
  48. package/dist/db/repository/base.js.map +1 -1
  49. package/dist/db/repository/native-repository.d.ts +7 -1
  50. package/dist/db/repository/native-repository.d.ts.map +1 -1
  51. package/dist/db/repository/native-repository.js +100 -1
  52. package/dist/db/repository/native-repository.js.map +1 -1
  53. package/dist/db/repository/nodes.d.ts.map +1 -1
  54. package/dist/db/repository/nodes.js +8 -4
  55. package/dist/db/repository/nodes.js.map +1 -1
  56. package/dist/db/repository/sqlite-repository.d.ts +4 -0
  57. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  58. package/dist/db/repository/sqlite-repository.js +51 -0
  59. package/dist/db/repository/sqlite-repository.js.map +1 -1
  60. package/dist/domain/analysis/brief.d.ts.map +1 -1
  61. package/dist/domain/analysis/brief.js +13 -17
  62. package/dist/domain/analysis/brief.js.map +1 -1
  63. package/dist/domain/analysis/context.d.ts.map +1 -1
  64. package/dist/domain/analysis/context.js +14 -11
  65. package/dist/domain/analysis/context.js.map +1 -1
  66. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  67. package/dist/domain/analysis/dependencies.js +64 -59
  68. package/dist/domain/analysis/dependencies.js.map +1 -1
  69. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  70. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  71. package/dist/domain/analysis/fn-impact.js +33 -31
  72. package/dist/domain/analysis/fn-impact.js.map +1 -1
  73. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  74. package/dist/domain/analysis/implementations.js +11 -19
  75. package/dist/domain/analysis/implementations.js.map +1 -1
  76. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  77. package/dist/domain/analysis/module-map.js +55 -76
  78. package/dist/domain/analysis/module-map.js.map +1 -1
  79. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  80. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  81. package/dist/domain/analysis/query-helpers.js +15 -1
  82. package/dist/domain/analysis/query-helpers.js.map +1 -1
  83. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  84. package/dist/domain/graph/builder/pipeline.js +352 -107
  85. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  86. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  87. package/dist/domain/graph/builder/stages/build-edges.js +49 -18
  88. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  89. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  90. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  91. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  92. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  93. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  94. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  95. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  96. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  97. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  98. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  99. package/dist/domain/graph/cycles.d.ts +6 -0
  100. package/dist/domain/graph/cycles.d.ts.map +1 -1
  101. package/dist/domain/graph/cycles.js +114 -22
  102. package/dist/domain/graph/cycles.js.map +1 -1
  103. package/dist/domain/graph/resolve.js +1 -1
  104. package/dist/domain/graph/resolve.js.map +1 -1
  105. package/dist/domain/graph/watcher.d.ts +2 -0
  106. package/dist/domain/graph/watcher.d.ts.map +1 -1
  107. package/dist/domain/graph/watcher.js +170 -75
  108. package/dist/domain/graph/watcher.js.map +1 -1
  109. package/dist/domain/parser.d.ts +3 -4
  110. package/dist/domain/parser.d.ts.map +1 -1
  111. package/dist/domain/parser.js +141 -89
  112. package/dist/domain/parser.js.map +1 -1
  113. package/dist/domain/search/generator.js +1 -1
  114. package/dist/domain/search/generator.js.map +1 -1
  115. package/dist/domain/search/models.d.ts +4 -3
  116. package/dist/domain/search/models.d.ts.map +1 -1
  117. package/dist/domain/search/models.js +23 -8
  118. package/dist/domain/search/models.js.map +1 -1
  119. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  120. package/dist/domain/search/search/hybrid.js +29 -18
  121. package/dist/domain/search/search/hybrid.js.map +1 -1
  122. package/dist/extractors/go.js +36 -33
  123. package/dist/extractors/go.js.map +1 -1
  124. package/dist/extractors/helpers.d.ts.map +1 -1
  125. package/dist/extractors/helpers.js +40 -29
  126. package/dist/extractors/helpers.js.map +1 -1
  127. package/dist/extractors/java.js +58 -46
  128. package/dist/extractors/java.js.map +1 -1
  129. package/dist/extractors/javascript.js +65 -54
  130. package/dist/extractors/javascript.js.map +1 -1
  131. package/dist/extractors/kotlin.js +84 -78
  132. package/dist/extractors/kotlin.js.map +1 -1
  133. package/dist/extractors/python.js +29 -24
  134. package/dist/extractors/python.js.map +1 -1
  135. package/dist/extractors/rust.js +41 -32
  136. package/dist/extractors/rust.js.map +1 -1
  137. package/dist/extractors/solidity.js +58 -67
  138. package/dist/extractors/solidity.js.map +1 -1
  139. package/dist/extractors/swift.js +83 -81
  140. package/dist/extractors/swift.js.map +1 -1
  141. package/dist/extractors/zig.js +58 -60
  142. package/dist/extractors/zig.js.map +1 -1
  143. package/dist/features/ast.d.ts +16 -14
  144. package/dist/features/ast.d.ts.map +1 -1
  145. package/dist/features/ast.js +83 -81
  146. package/dist/features/ast.js.map +1 -1
  147. package/dist/features/audit.d.ts.map +1 -1
  148. package/dist/features/audit.js +8 -6
  149. package/dist/features/audit.js.map +1 -1
  150. package/dist/features/branch-compare.d.ts.map +1 -1
  151. package/dist/features/branch-compare.js +69 -72
  152. package/dist/features/branch-compare.js.map +1 -1
  153. package/dist/features/communities.d.ts.map +1 -1
  154. package/dist/features/communities.js +19 -7
  155. package/dist/features/communities.js.map +1 -1
  156. package/dist/features/complexity.d.ts.map +1 -1
  157. package/dist/features/complexity.js +120 -125
  158. package/dist/features/complexity.js.map +1 -1
  159. package/dist/features/dataflow.d.ts.map +1 -1
  160. package/dist/features/dataflow.js +136 -137
  161. package/dist/features/dataflow.js.map +1 -1
  162. package/dist/features/flow.d.ts.map +1 -1
  163. package/dist/features/flow.js +84 -79
  164. package/dist/features/flow.js.map +1 -1
  165. package/dist/features/structure-query.d.ts.map +1 -1
  166. package/dist/features/structure-query.js +69 -65
  167. package/dist/features/structure-query.js.map +1 -1
  168. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  169. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  170. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  171. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  172. package/dist/graph/algorithms/leiden/partition.js +288 -266
  173. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  174. package/dist/graph/model.d.ts.map +1 -1
  175. package/dist/graph/model.js +5 -1
  176. package/dist/graph/model.js.map +1 -1
  177. package/dist/infrastructure/config.d.ts.map +1 -1
  178. package/dist/infrastructure/config.js +6 -4
  179. package/dist/infrastructure/config.js.map +1 -1
  180. package/dist/infrastructure/suppress.d.ts +25 -0
  181. package/dist/infrastructure/suppress.d.ts.map +1 -0
  182. package/dist/infrastructure/suppress.js +43 -0
  183. package/dist/infrastructure/suppress.js.map +1 -0
  184. package/dist/mcp/server.d.ts.map +1 -1
  185. package/dist/mcp/server.js +29 -24
  186. package/dist/mcp/server.js.map +1 -1
  187. package/dist/presentation/dataflow.d.ts.map +1 -1
  188. package/dist/presentation/dataflow.js +47 -38
  189. package/dist/presentation/dataflow.js.map +1 -1
  190. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  191. package/dist/presentation/diff-impact-mermaid.js +60 -51
  192. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  193. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/exports.js +20 -14
  195. package/dist/presentation/queries-cli/exports.js.map +1 -1
  196. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  197. package/dist/presentation/queries-cli/impact.js +15 -13
  198. package/dist/presentation/queries-cli/impact.js.map +1 -1
  199. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  200. package/dist/presentation/queries-cli/inspect.js +101 -79
  201. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  202. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  203. package/dist/presentation/queries-cli/overview.js +25 -16
  204. package/dist/presentation/queries-cli/overview.js.map +1 -1
  205. package/dist/presentation/queries-cli/path.js +26 -20
  206. package/dist/presentation/queries-cli/path.js.map +1 -1
  207. package/dist/presentation/result-formatter.d.ts +10 -0
  208. package/dist/presentation/result-formatter.d.ts.map +1 -1
  209. package/dist/presentation/result-formatter.js +16 -1
  210. package/dist/presentation/result-formatter.js.map +1 -1
  211. package/dist/presentation/viewer.d.ts.map +1 -1
  212. package/dist/presentation/viewer.js +18 -12
  213. package/dist/presentation/viewer.js.map +1 -1
  214. package/dist/shared/errors.d.ts +5 -0
  215. package/dist/shared/errors.d.ts.map +1 -1
  216. package/dist/shared/errors.js +5 -0
  217. package/dist/shared/errors.js.map +1 -1
  218. package/dist/shared/hierarchy.d.ts +8 -2
  219. package/dist/shared/hierarchy.d.ts.map +1 -1
  220. package/dist/shared/hierarchy.js +42 -1
  221. package/dist/shared/hierarchy.js.map +1 -1
  222. package/dist/shared/normalize.d.ts +6 -1
  223. package/dist/shared/normalize.d.ts.map +1 -1
  224. package/dist/shared/normalize.js +20 -12
  225. package/dist/shared/normalize.js.map +1 -1
  226. package/dist/shared/paginate.d.ts +0 -9
  227. package/dist/shared/paginate.d.ts.map +1 -1
  228. package/dist/shared/paginate.js +0 -15
  229. package/dist/shared/paginate.js.map +1 -1
  230. package/dist/types.d.ts +12 -5
  231. package/dist/types.d.ts.map +1 -1
  232. package/grammars/tree-sitter-erlang.wasm +0 -0
  233. package/grammars/tree-sitter-gleam.wasm +0 -0
  234. package/package.json +9 -9
  235. package/src/ast-analysis/engine.ts +176 -104
  236. package/src/ast-analysis/metrics.ts +33 -11
  237. package/src/ast-analysis/shared.ts +33 -24
  238. package/src/ast-analysis/visitor-utils.ts +52 -32
  239. package/src/ast-analysis/visitor.ts +132 -71
  240. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  241. package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
  242. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  243. package/src/cli/commands/branch-compare.ts +4 -0
  244. package/src/cli/commands/diff-impact.ts +2 -1
  245. package/src/cli/commands/info.ts +3 -2
  246. package/src/cli/commands/watch.ts +16 -2
  247. package/src/db/connection.ts +29 -28
  248. package/src/db/query-builder.ts +15 -3
  249. package/src/db/repository/base.ts +34 -0
  250. package/src/db/repository/native-repository.ts +104 -1
  251. package/src/db/repository/nodes.ts +13 -8
  252. package/src/db/repository/sqlite-repository.ts +55 -0
  253. package/src/domain/analysis/brief.ts +15 -25
  254. package/src/domain/analysis/context.ts +17 -10
  255. package/src/domain/analysis/dependencies.ts +77 -81
  256. package/src/domain/analysis/fn-impact.ts +36 -43
  257. package/src/domain/analysis/implementations.ts +11 -17
  258. package/src/domain/analysis/module-map.ts +58 -92
  259. package/src/domain/analysis/query-helpers.ts +18 -1
  260. package/src/domain/graph/builder/pipeline.ts +409 -99
  261. package/src/domain/graph/builder/stages/build-edges.ts +45 -19
  262. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  263. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  264. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  265. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  266. package/src/domain/graph/cycles.ts +110 -23
  267. package/src/domain/graph/resolve.ts +1 -1
  268. package/src/domain/graph/watcher.ts +202 -96
  269. package/src/domain/parser.ts +143 -89
  270. package/src/domain/search/generator.ts +1 -1
  271. package/src/domain/search/models.ts +26 -7
  272. package/src/domain/search/search/hybrid.ts +69 -51
  273. package/src/extractors/go.ts +43 -33
  274. package/src/extractors/helpers.ts +37 -23
  275. package/src/extractors/java.ts +66 -47
  276. package/src/extractors/javascript.ts +66 -54
  277. package/src/extractors/kotlin.ts +84 -77
  278. package/src/extractors/python.ts +31 -25
  279. package/src/extractors/rust.ts +37 -29
  280. package/src/extractors/solidity.ts +57 -61
  281. package/src/extractors/swift.ts +81 -80
  282. package/src/extractors/zig.ts +58 -61
  283. package/src/features/ast.ts +130 -110
  284. package/src/features/audit.ts +8 -6
  285. package/src/features/branch-compare.ts +105 -79
  286. package/src/features/communities.ts +25 -10
  287. package/src/features/complexity.ts +171 -134
  288. package/src/features/dataflow.ts +165 -175
  289. package/src/features/flow.ts +129 -92
  290. package/src/features/structure-query.ts +79 -64
  291. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  292. package/src/graph/algorithms/leiden/partition.ts +359 -294
  293. package/src/graph/model.ts +6 -1
  294. package/src/infrastructure/config.ts +6 -4
  295. package/src/infrastructure/suppress.ts +47 -0
  296. package/src/mcp/server.ts +53 -37
  297. package/src/presentation/dataflow.ts +50 -44
  298. package/src/presentation/diff-impact-mermaid.ts +104 -62
  299. package/src/presentation/queries-cli/exports.ts +21 -13
  300. package/src/presentation/queries-cli/impact.ts +15 -13
  301. package/src/presentation/queries-cli/inspect.ts +100 -81
  302. package/src/presentation/queries-cli/overview.ts +26 -16
  303. package/src/presentation/queries-cli/path.ts +33 -25
  304. package/src/presentation/result-formatter.ts +19 -1
  305. package/src/presentation/viewer.ts +42 -14
  306. package/src/shared/errors.ts +6 -0
  307. package/src/shared/hierarchy.ts +50 -2
  308. package/src/shared/normalize.ts +31 -12
  309. package/src/shared/paginate.ts +0 -17
  310. package/src/types.ts +26 -5
@@ -80,21 +80,9 @@ function handleGoFuncDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
80
80
 
81
81
  function handleGoMethodDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
82
82
  const nameNode = node.childForFieldName('name');
83
- const receiver = node.childForFieldName('receiver');
84
83
  if (!nameNode) return;
85
- let receiverType: string | null = null;
86
- if (receiver) {
87
- for (let i = 0; i < receiver.childCount; i++) {
88
- const param = receiver.child(i);
89
- if (!param) continue;
90
- const typeNode = param.childForFieldName('type');
91
- if (typeNode) {
92
- receiverType =
93
- typeNode.type === 'pointer_type' ? typeNode.text.replace(/^\*/, '') : typeNode.text;
94
- break;
95
- }
96
- }
97
- }
84
+ const receiver = node.childForFieldName('receiver');
85
+ const receiverType = receiver ? extractGoReceiverType(receiver) : null;
98
86
  const fullName = receiverType ? `${receiverType}.${nameNode.text}` : nameNode.text;
99
87
  const params = extractGoParameters(node.childForFieldName('parameters'));
100
88
  ctx.definitions.push({
@@ -107,6 +95,19 @@ function handleGoMethodDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
107
95
  });
108
96
  }
109
97
 
98
+ /** Extract the receiver type name from a method receiver parameter list. */
99
+ function extractGoReceiverType(receiver: TreeSitterNode): string | null {
100
+ for (let i = 0; i < receiver.childCount; i++) {
101
+ const param = receiver.child(i);
102
+ if (!param) continue;
103
+ const typeNode = param.childForFieldName('type');
104
+ if (typeNode) {
105
+ return typeNode.type === 'pointer_type' ? typeNode.text.replace(/^\*/, '') : typeNode.text;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+
110
111
  function handleGoTypeDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
111
112
  for (let i = 0; i < node.childCount; i++) {
112
113
  const spec = node.child(i);
@@ -403,13 +404,38 @@ function extractGoParameters(paramListNode: TreeSitterNode | null): SubDeclarati
403
404
  * This performs file-local matching (cross-file matching requires build-edges).
404
405
  */
405
406
  function matchGoStructuralInterfaces(ctx: ExtractorOutput): void {
407
+ const { interfaceMethods, structMethods, structLines } = collectGoMethodSets(ctx);
408
+
409
+ // Match: struct satisfies interface if it has all interface methods (name-only;
410
+ // signatures are not verified — treat as candidate match, not definitive).
411
+ // NOTE: embedded interfaces (type_elem nodes) are not resolved — composite
412
+ // interfaces like `type ReadWriter interface { Reader; Writer }` will have an
413
+ // empty method set and be silently excluded from matching.
414
+ for (const [structName, methods] of structMethods) {
415
+ for (const [ifaceName, ifaceMethods] of interfaceMethods) {
416
+ if (ifaceMethods.size > 0 && [...ifaceMethods].every((m) => methods.has(m))) {
417
+ ctx.classes.push({
418
+ name: structName,
419
+ implements: ifaceName,
420
+ line: structLines.get(structName) || 1,
421
+ });
422
+ }
423
+ }
424
+ }
425
+ }
426
+
427
+ /** Collect interface and struct method sets from definitions for structural matching. */
428
+ function collectGoMethodSets(ctx: ExtractorOutput): {
429
+ interfaceMethods: Map<string, Set<string>>;
430
+ structMethods: Map<string, Set<string>>;
431
+ structLines: Map<string, number>;
432
+ } {
406
433
  const interfaceMethods = new Map<string, Set<string>>();
407
434
  const structMethods = new Map<string, Set<string>>();
408
435
  const structLines = new Map<string, number>();
409
-
410
- // Collect interface and struct definitions
411
436
  const interfaceNames = new Set<string>();
412
437
  const structNames = new Set<string>();
438
+
413
439
  for (const def of ctx.definitions) {
414
440
  if (def.kind === 'interface') interfaceNames.add(def.name);
415
441
  if (def.kind === 'struct') {
@@ -418,7 +444,6 @@ function matchGoStructuralInterfaces(ctx: ExtractorOutput): void {
418
444
  }
419
445
  }
420
446
 
421
- // Collect methods grouped by receiver type
422
447
  for (const def of ctx.definitions) {
423
448
  if (def.kind !== 'method' || !def.name.includes('.')) continue;
424
449
  const dotIdx = def.name.indexOf('.');
@@ -435,22 +460,7 @@ function matchGoStructuralInterfaces(ctx: ExtractorOutput): void {
435
460
  }
436
461
  }
437
462
 
438
- // Match: struct satisfies interface if it has all interface methods (name-only;
439
- // signatures are not verified — treat as candidate match, not definitive).
440
- // NOTE: embedded interfaces (type_elem nodes) are not resolved — composite
441
- // interfaces like `type ReadWriter interface { Reader; Writer }` will have an
442
- // empty method set and be silently excluded from matching.
443
- for (const [structName, methods] of structMethods) {
444
- for (const [ifaceName, ifaceMethods] of interfaceMethods) {
445
- if (ifaceMethods.size > 0 && [...ifaceMethods].every((m) => methods.has(m))) {
446
- ctx.classes.push({
447
- name: structName,
448
- implements: ifaceName,
449
- line: structLines.get(structName) || 1,
450
- });
451
- }
452
- }
453
- }
463
+ return { interfaceMethods, structMethods, structLines };
454
464
  }
455
465
 
456
466
  function extractStructFields(structTypeNode: TreeSitterNode): SubDeclaration[] {
@@ -110,6 +110,20 @@ export function findParentNode(
110
110
  return null;
111
111
  }
112
112
 
113
+ /**
114
+ * Resolve a container's body node by trying each field name in order.
115
+ */
116
+ function resolveBodyNode(
117
+ containerNode: TreeSitterNode,
118
+ bodyFields: readonly string[],
119
+ ): TreeSitterNode | null {
120
+ for (const field of bodyFields) {
121
+ const body = containerNode.childForFieldName(field) || findChild(containerNode, field);
122
+ if (body) return body;
123
+ }
124
+ return null;
125
+ }
126
+
113
127
  /**
114
128
  * Extract child declarations from a container node's body.
115
129
  * Finds the body via `bodyFields` (tries childForFieldName then findChild for each),
@@ -126,22 +140,17 @@ export function extractBodyMembers(
126
140
  nameField: string = 'name',
127
141
  visibility?: (member: TreeSitterNode) => SubDeclaration['visibility'],
128
142
  ): SubDeclaration[] {
143
+ const body = resolveBodyNode(containerNode, bodyFields);
144
+ if (!body) return [];
129
145
  const members: SubDeclaration[] = [];
130
- let body: TreeSitterNode | null = null;
131
- for (const field of bodyFields) {
132
- body = containerNode.childForFieldName(field) || findChild(containerNode, field);
133
- if (body) break;
134
- }
135
- if (!body) return members;
136
146
  for (let i = 0; i < body.childCount; i++) {
137
147
  const member = body.child(i);
138
148
  if (!member || member.type !== memberType) continue;
139
149
  const nn = member.childForFieldName(nameField);
140
- if (nn) {
141
- const entry: SubDeclaration = { name: nn.text, kind, line: member.startPosition.row + 1 };
142
- if (visibility) entry.visibility = visibility(member);
143
- members.push(entry);
144
- }
150
+ if (!nn) continue;
151
+ const entry: SubDeclaration = { name: nn.text, kind, line: member.startPosition.row + 1 };
152
+ if (visibility) entry.visibility = visibility(member);
153
+ members.push(entry);
145
154
  }
146
155
  return members;
147
156
  }
@@ -162,24 +171,29 @@ export function lastPathSegment(path: string, separator: string = '/'): string {
162
171
  return path.split(separator).pop() ?? path;
163
172
  }
164
173
 
174
+ /**
175
+ * Parse visibility from a modifier node's text content.
176
+ */
177
+ function parseModifierText(text: string): 'public' | 'private' | 'protected' | undefined {
178
+ if (VISIBILITY_KEYWORDS.has(text)) return text as 'public' | 'private' | 'protected';
179
+ // C# 'private protected' — accessible to derived types in same assembly → protected
180
+ if (text === 'private protected') return 'protected';
181
+ // Compound modifiers node (Java: "public static") — scan its text for a keyword
182
+ for (const kw of VISIBILITY_KEYWORDS) {
183
+ if (text.includes(kw)) return kw as 'public' | 'private' | 'protected';
184
+ }
185
+ return undefined;
186
+ }
187
+
165
188
  export function extractModifierVisibility(
166
189
  node: TreeSitterNode,
167
190
  modifierTypes: Set<string> = DEFAULT_MODIFIER_TYPES,
168
191
  ): 'public' | 'private' | 'protected' | undefined {
169
192
  for (let i = 0; i < node.childCount; i++) {
170
193
  const child = node.child(i);
171
- if (!child) continue;
172
- // Direct keyword match (e.g., PHP visibility_modifier = "public")
173
- if (modifierTypes.has(child.type)) {
174
- const text = child.text;
175
- if (VISIBILITY_KEYWORDS.has(text)) return text as 'public' | 'private' | 'protected';
176
- // C# 'private protected' — accessible to derived types in same assembly → protected
177
- if (text === 'private protected') return 'protected';
178
- // Compound modifiers node (Java: "public static") — scan its text for a keyword
179
- for (const kw of VISIBILITY_KEYWORDS) {
180
- if (text.includes(kw)) return kw as 'public' | 'private' | 'protected';
181
- }
182
- }
194
+ if (!child || !modifierTypes.has(child.type)) continue;
195
+ const result = parseModifierText(child.text);
196
+ if (result) return result;
183
197
  }
184
198
  return undefined;
185
199
  }
@@ -83,27 +83,7 @@ function handleJavaClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
83
83
  children: classChildren.length > 0 ? classChildren : undefined,
84
84
  });
85
85
 
86
- const superclass = node.childForFieldName('superclass');
87
- if (superclass) {
88
- for (let i = 0; i < superclass.childCount; i++) {
89
- const child = superclass.child(i);
90
- if (
91
- child &&
92
- (child.type === 'type_identifier' ||
93
- child.type === 'identifier' ||
94
- child.type === 'generic_type')
95
- ) {
96
- const superName = child.type === 'generic_type' ? child.child(0)?.text : child.text;
97
- if (superName)
98
- ctx.classes.push({
99
- name: nameNode.text,
100
- extends: superName,
101
- line: node.startPosition.row + 1,
102
- });
103
- break;
104
- }
105
- }
106
- }
86
+ extractJavaSuperclass(node, nameNode.text, ctx);
107
87
 
108
88
  const interfaces = node.childForFieldName('interfaces');
109
89
  if (interfaces) {
@@ -111,6 +91,32 @@ function handleJavaClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
111
91
  }
112
92
  }
113
93
 
94
+ /** Extract the superclass (extends) relationship from a Java class declaration. */
95
+ function extractJavaSuperclass(
96
+ node: TreeSitterNode,
97
+ className: string,
98
+ ctx: ExtractorOutput,
99
+ ): void {
100
+ const superclass = node.childForFieldName('superclass');
101
+ if (!superclass) return;
102
+ const superName = findJavaSuperTypeName(superclass);
103
+ if (superName) {
104
+ ctx.classes.push({ name: className, extends: superName, line: node.startPosition.row + 1 });
105
+ }
106
+ }
107
+
108
+ /** Find the type name from a superclass node (handles generic_type unwrapping). */
109
+ function findJavaSuperTypeName(superclass: TreeSitterNode): string | undefined {
110
+ for (let i = 0; i < superclass.childCount; i++) {
111
+ const child = superclass.child(i);
112
+ if (!child) continue;
113
+ if (JAVA_TYPE_NODE_TYPES.has(child.type)) {
114
+ return resolveJavaIfaceName(child);
115
+ }
116
+ }
117
+ return undefined;
118
+ }
119
+
114
120
  const JAVA_TYPE_NODE_TYPES = new Set(['type_identifier', 'identifier', 'generic_type']);
115
121
 
116
122
  /** Resolve interface name from a type node (handles generic_type unwrapping). */
@@ -161,19 +167,26 @@ function handleJavaInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): vo
161
167
  endLine: nodeEndLine(node),
162
168
  });
163
169
  const body = node.childForFieldName('body');
164
- if (body) {
165
- for (let i = 0; i < body.childCount; i++) {
166
- const child = body.child(i);
167
- if (child && child.type === 'method_declaration') {
168
- const methName = child.childForFieldName('name');
169
- if (methName) {
170
- ctx.definitions.push({
171
- name: `${nameNode.text}.${methName.text}`,
172
- kind: 'method',
173
- line: child.startPosition.row + 1,
174
- endLine: child.endPosition.row + 1,
175
- });
176
- }
170
+ if (body) extractJavaInterfaceMethods(body, nameNode.text, ctx);
171
+ }
172
+
173
+ /** Extract method declarations from a Java interface body. */
174
+ function extractJavaInterfaceMethods(
175
+ body: TreeSitterNode,
176
+ ifaceName: string,
177
+ ctx: ExtractorOutput,
178
+ ): void {
179
+ for (let i = 0; i < body.childCount; i++) {
180
+ const child = body.child(i);
181
+ if (child && child.type === 'method_declaration') {
182
+ const methName = child.childForFieldName('name');
183
+ if (methName) {
184
+ ctx.definitions.push({
185
+ name: `${ifaceName}.${methName.text}`,
186
+ kind: 'method',
187
+ line: child.startPosition.row + 1,
188
+ endLine: child.endPosition.row + 1,
189
+ });
177
190
  }
178
191
  }
179
192
  }
@@ -321,23 +334,29 @@ function extractClassFields(classNode: TreeSitterNode): SubDeclaration[] {
321
334
  for (let i = 0; i < body.childCount; i++) {
322
335
  const member = body.child(i);
323
336
  if (!member || member.type !== 'field_declaration') continue;
324
- for (let j = 0; j < member.childCount; j++) {
325
- const child = member.child(j);
326
- if (!child || child.type !== 'variable_declarator') continue;
327
- const nameNode = child.childForFieldName('name');
328
- if (nameNode) {
329
- fields.push({
330
- name: nameNode.text,
331
- kind: 'property',
332
- line: member.startPosition.row + 1,
333
- visibility: extractModifierVisibility(member),
334
- });
335
- }
336
- }
337
+ extractFieldDeclarators(member, fields);
337
338
  }
338
339
  return fields;
339
340
  }
340
341
 
342
+ /** Extract variable_declarator names from a field_declaration node. */
343
+ function extractFieldDeclarators(member: TreeSitterNode, fields: SubDeclaration[]): void {
344
+ const vis = extractModifierVisibility(member);
345
+ for (let j = 0; j < member.childCount; j++) {
346
+ const child = member.child(j);
347
+ if (!child || child.type !== 'variable_declarator') continue;
348
+ const nameNode = child.childForFieldName('name');
349
+ if (nameNode) {
350
+ fields.push({
351
+ name: nameNode.text,
352
+ kind: 'property',
353
+ line: member.startPosition.row + 1,
354
+ visibility: vis,
355
+ });
356
+ }
357
+ }
358
+ }
359
+
341
360
  function extractEnumConstants(enumNode: TreeSitterNode): SubDeclaration[] {
342
361
  return extractBodyMembers(enumNode, ['body', 'enum_body'], 'enum_constant', 'constant');
343
362
  }
@@ -328,34 +328,7 @@ function extractConstantsWalk(node: TreeSitterNode, definitions: Definition[]):
328
328
  if (inner) declNode = inner;
329
329
  }
330
330
 
331
- const t = declNode.type;
332
- if (t === 'lexical_declaration' || t === 'variable_declaration') {
333
- if (declNode.text.startsWith('const ')) {
334
- for (let j = 0; j < declNode.childCount; j++) {
335
- const declarator = declNode.child(j);
336
- if (!declarator || declarator.type !== 'variable_declarator') continue;
337
- const nameN = declarator.childForFieldName('name');
338
- const valueN = declarator.childForFieldName('value');
339
- if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
340
- // Skip functions — already captured by query patterns
341
- const valType = valueN.type;
342
- if (
343
- valType === 'arrow_function' ||
344
- valType === 'function_expression' ||
345
- valType === 'function'
346
- )
347
- continue;
348
- if (isConstantValue(valueN)) {
349
- definitions.push({
350
- name: nameN.text,
351
- kind: 'constant',
352
- line: declNode.startPosition.row + 1,
353
- endLine: nodeEndLine(declNode),
354
- });
355
- }
356
- }
357
- }
358
- }
331
+ extractConstDeclarators(declNode, definitions);
359
332
 
360
333
  // Recurse into non-function, non-export-statement children (blocks, if-statements, etc.)
361
334
  if (child.type !== 'export_statement') {
@@ -364,6 +337,33 @@ function extractConstantsWalk(node: TreeSitterNode, definitions: Definition[]):
364
337
  }
365
338
  }
366
339
 
340
+ /** Extract constant definitions from a `const` declaration node. */
341
+ function extractConstDeclarators(declNode: TreeSitterNode, definitions: Definition[]): void {
342
+ const t = declNode.type;
343
+ if (t !== 'lexical_declaration' && t !== 'variable_declaration') return;
344
+ if (!declNode.text.startsWith('const ')) return;
345
+
346
+ for (let j = 0; j < declNode.childCount; j++) {
347
+ const declarator = declNode.child(j);
348
+ if (!declarator || declarator.type !== 'variable_declarator') continue;
349
+ const nameN = declarator.childForFieldName('name');
350
+ const valueN = declarator.childForFieldName('value');
351
+ if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
352
+ // Skip functions — already captured by query patterns
353
+ const valType = valueN.type;
354
+ if (valType === 'arrow_function' || valType === 'function_expression' || valType === 'function')
355
+ continue;
356
+ if (isConstantValue(valueN)) {
357
+ definitions.push({
358
+ name: nameN.text,
359
+ kind: 'constant',
360
+ line: declNode.startPosition.row + 1,
361
+ endLine: nodeEndLine(declNode),
362
+ });
363
+ }
364
+ }
365
+ }
366
+
367
367
  /**
368
368
  * Recursive walk to find dynamic import() calls.
369
369
  * Query patterns match call_expression with identifier/member_expression/subscript_expression
@@ -678,24 +678,7 @@ function handleCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
678
678
  const fn = node.childForFieldName('function');
679
679
  if (!fn) return;
680
680
  if (fn.type === 'import') {
681
- const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
682
- if (args) {
683
- const strArg = findChild(args, 'string');
684
- if (strArg) {
685
- const modPath = strArg.text.replace(/['"]/g, '');
686
- const names = extractDynamicImportNames(node);
687
- ctx.imports.push({
688
- source: modPath,
689
- names,
690
- line: node.startPosition.row + 1,
691
- dynamicImport: true,
692
- });
693
- } else {
694
- debug(
695
- `Skipping non-static dynamic import() at line ${node.startPosition.row + 1} (template literal or variable)`,
696
- );
697
- }
698
- }
681
+ handleDynamicImportCall(node, ctx.imports);
699
682
  } else {
700
683
  const callInfo = extractCallInfo(fn, node);
701
684
  if (callInfo) ctx.calls.push(callInfo);
@@ -706,6 +689,22 @@ function handleCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
706
689
  }
707
690
  }
708
691
 
692
+ /** Handle a dynamic import() call expression and add to imports if static. */
693
+ function handleDynamicImportCall(node: TreeSitterNode, imports: Import[]): void {
694
+ const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
695
+ if (!args) return;
696
+ const strArg = findChild(args, 'string');
697
+ if (strArg) {
698
+ const modPath = strArg.text.replace(/['"]/g, '');
699
+ const names = extractDynamicImportNames(node);
700
+ imports.push({ source: modPath, names, line: node.startPosition.row + 1, dynamicImport: true });
701
+ } else {
702
+ debug(
703
+ `Skipping non-static dynamic import() at line ${node.startPosition.row + 1} (template literal or variable)`,
704
+ );
705
+ }
706
+ }
707
+
709
708
  function handleImportStmt(node: TreeSitterNode, ctx: ExtractorOutput): void {
710
709
  const isTypeOnly = node.text.startsWith('import type');
711
710
  const source = node.childForFieldName('source') || findChild(node, 'string');
@@ -998,21 +997,34 @@ function handleVarDeclaratorTypeMap(
998
997
  const nameN = node.childForFieldName('name');
999
998
  if (!nameN || nameN.type !== 'identifier') return;
1000
999
 
1001
- // Type annotation: const x: Foo = …
1002
1000
  const typeAnno = findChild(node, 'type_annotation');
1001
+ const valueN = node.childForFieldName('value');
1002
+
1003
+ // Constructor on the same declaration wins over annotation: the runtime type is
1004
+ // what matters for call resolution (e.g. `const x: Base = new Derived()` should
1005
+ // resolve `x.render()` to `Derived.render`, not `Base.render`).
1006
+ // When no constructor is present, annotation still takes precedence over factory.
1007
+ if (valueN?.type === 'new_expression') {
1008
+ const ctorType = extractNewExprTypeName(valueN);
1009
+ if (ctorType) {
1010
+ setTypeMapEntry(typeMap, nameN.text, ctorType, 1.0);
1011
+ return;
1012
+ }
1013
+ }
1014
+
1015
+ // Type annotation: const x: Foo = … → confidence 0.9
1003
1016
  if (typeAnno) {
1004
1017
  const typeName = extractSimpleTypeName(typeAnno);
1005
- if (typeName) setTypeMapEntry(typeMap, nameN.text, typeName, 0.9);
1018
+ if (typeName) {
1019
+ setTypeMapEntry(typeMap, nameN.text, typeName, 0.9);
1020
+ return;
1021
+ }
1006
1022
  }
1007
1023
 
1008
- const valueN = node.childForFieldName('value');
1009
1024
  if (!valueN) return;
1010
1025
 
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
- }
1026
+ // Constructor already handled above only factory path remains.
1027
+ if (valueN.type === 'new_expression') return;
1016
1028
  // Factory method: const x = Foo.create() → confidence 0.7
1017
1029
  else if (valueN.type === 'call_expression') {
1018
1030
  const fn = valueN.childForFieldName('function');