@optave/codegraph 3.5.0 → 3.6.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 +35 -14
  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 +164 -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/go.d.ts.map +1 -1
  103. package/dist/extractors/go.js +126 -130
  104. package/dist/extractors/go.js.map +1 -1
  105. package/dist/extractors/hcl.js +6 -6
  106. package/dist/extractors/hcl.js.map +1 -1
  107. package/dist/extractors/helpers.d.ts +32 -1
  108. package/dist/extractors/helpers.d.ts.map +1 -1
  109. package/dist/extractors/helpers.js +74 -0
  110. package/dist/extractors/helpers.js.map +1 -1
  111. package/dist/extractors/index.d.ts +6 -0
  112. package/dist/extractors/index.d.ts.map +1 -1
  113. package/dist/extractors/index.js +6 -0
  114. package/dist/extractors/index.js.map +1 -1
  115. package/dist/extractors/java.d.ts.map +1 -1
  116. package/dist/extractors/java.js +32 -47
  117. package/dist/extractors/java.js.map +1 -1
  118. package/dist/extractors/javascript.d.ts.map +1 -1
  119. package/dist/extractors/javascript.js +306 -292
  120. package/dist/extractors/javascript.js.map +1 -1
  121. package/dist/extractors/kotlin.d.ts +6 -0
  122. package/dist/extractors/kotlin.d.ts.map +1 -0
  123. package/dist/extractors/kotlin.js +275 -0
  124. package/dist/extractors/kotlin.js.map +1 -0
  125. package/dist/extractors/php.d.ts.map +1 -1
  126. package/dist/extractors/php.js +39 -44
  127. package/dist/extractors/php.js.map +1 -1
  128. package/dist/extractors/python.d.ts.map +1 -1
  129. package/dist/extractors/python.js +75 -93
  130. package/dist/extractors/python.js.map +1 -1
  131. package/dist/extractors/ruby.js +6 -13
  132. package/dist/extractors/ruby.js.map +1 -1
  133. package/dist/extractors/rust.d.ts.map +1 -1
  134. package/dist/extractors/rust.js +58 -83
  135. package/dist/extractors/rust.js.map +1 -1
  136. package/dist/extractors/scala.d.ts +6 -0
  137. package/dist/extractors/scala.d.ts.map +1 -0
  138. package/dist/extractors/scala.js +269 -0
  139. package/dist/extractors/scala.js.map +1 -0
  140. package/dist/extractors/swift.d.ts +6 -0
  141. package/dist/extractors/swift.d.ts.map +1 -0
  142. package/dist/extractors/swift.js +275 -0
  143. package/dist/extractors/swift.js.map +1 -0
  144. package/dist/features/ast.d.ts +2 -0
  145. package/dist/features/ast.d.ts.map +1 -1
  146. package/dist/features/ast.js +9 -24
  147. package/dist/features/ast.js.map +1 -1
  148. package/dist/features/audit.d.ts.map +1 -1
  149. package/dist/features/audit.js +17 -21
  150. package/dist/features/audit.js.map +1 -1
  151. package/dist/features/branch-compare.d.ts.map +1 -1
  152. package/dist/features/branch-compare.js +47 -3
  153. package/dist/features/branch-compare.js.map +1 -1
  154. package/dist/features/cfg.d.ts +7 -1
  155. package/dist/features/cfg.d.ts.map +1 -1
  156. package/dist/features/cfg.js +118 -62
  157. package/dist/features/cfg.js.map +1 -1
  158. package/dist/features/check.d.ts.map +1 -1
  159. package/dist/features/check.js +79 -62
  160. package/dist/features/check.js.map +1 -1
  161. package/dist/features/complexity-query.d.ts.map +1 -1
  162. package/dist/features/complexity-query.js +142 -137
  163. package/dist/features/complexity-query.js.map +1 -1
  164. package/dist/features/complexity.d.ts +7 -1
  165. package/dist/features/complexity.d.ts.map +1 -1
  166. package/dist/features/complexity.js +62 -1
  167. package/dist/features/complexity.js.map +1 -1
  168. package/dist/features/dataflow.d.ts +7 -1
  169. package/dist/features/dataflow.d.ts.map +1 -1
  170. package/dist/features/dataflow.js +356 -188
  171. package/dist/features/dataflow.js.map +1 -1
  172. package/dist/features/graph-enrichment.d.ts.map +1 -1
  173. package/dist/features/graph-enrichment.js +117 -104
  174. package/dist/features/graph-enrichment.js.map +1 -1
  175. package/dist/features/sequence.d.ts.map +1 -1
  176. package/dist/features/sequence.js +25 -4
  177. package/dist/features/sequence.js.map +1 -1
  178. package/dist/features/structure-query.d.ts.map +1 -1
  179. package/dist/features/structure-query.js +29 -4
  180. package/dist/features/structure-query.js.map +1 -1
  181. package/dist/features/structure.d.ts.map +1 -1
  182. package/dist/features/structure.js +35 -15
  183. package/dist/features/structure.js.map +1 -1
  184. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  185. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  186. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  187. package/dist/graph/algorithms/leiden/index.js +43 -28
  188. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  189. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  190. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  191. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  192. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  193. package/dist/graph/algorithms/leiden/partition.js +89 -106
  194. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  195. package/dist/graph/model.d.ts +2 -0
  196. package/dist/graph/model.d.ts.map +1 -1
  197. package/dist/graph/model.js +20 -8
  198. package/dist/graph/model.js.map +1 -1
  199. package/dist/infrastructure/config.d.ts +0 -8
  200. package/dist/infrastructure/config.d.ts.map +1 -1
  201. package/dist/infrastructure/config.js +73 -62
  202. package/dist/infrastructure/config.js.map +1 -1
  203. package/dist/infrastructure/registry.d.ts +0 -8
  204. package/dist/infrastructure/registry.d.ts.map +1 -1
  205. package/dist/infrastructure/registry.js +12 -14
  206. package/dist/infrastructure/registry.js.map +1 -1
  207. package/dist/mcp/server.d.ts.map +1 -1
  208. package/dist/mcp/server.js +45 -36
  209. package/dist/mcp/server.js.map +1 -1
  210. package/dist/presentation/audit.d.ts.map +1 -1
  211. package/dist/presentation/audit.js +61 -57
  212. package/dist/presentation/audit.js.map +1 -1
  213. package/dist/presentation/branch-compare.d.ts.map +1 -1
  214. package/dist/presentation/branch-compare.js +56 -38
  215. package/dist/presentation/branch-compare.js.map +1 -1
  216. package/dist/presentation/check.d.ts.map +1 -1
  217. package/dist/presentation/check.js +30 -32
  218. package/dist/presentation/check.js.map +1 -1
  219. package/dist/presentation/colors.d.ts.map +1 -1
  220. package/dist/presentation/colors.js +2 -0
  221. package/dist/presentation/colors.js.map +1 -1
  222. package/dist/presentation/complexity.d.ts.map +1 -1
  223. package/dist/presentation/complexity.js +25 -19
  224. package/dist/presentation/complexity.js.map +1 -1
  225. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  226. package/dist/presentation/queries-cli/exports.js +15 -15
  227. package/dist/presentation/queries-cli/exports.js.map +1 -1
  228. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  229. package/dist/presentation/queries-cli/impact.js +29 -19
  230. package/dist/presentation/queries-cli/impact.js.map +1 -1
  231. package/dist/types.d.ts +182 -7
  232. package/dist/types.d.ts.map +1 -1
  233. package/grammars/tree-sitter-bash.wasm +0 -0
  234. package/grammars/tree-sitter-c.wasm +0 -0
  235. package/grammars/tree-sitter-cpp.wasm +0 -0
  236. package/grammars/tree-sitter-kotlin.wasm +0 -0
  237. package/grammars/tree-sitter-scala.wasm +0 -0
  238. package/grammars/tree-sitter-swift.wasm +0 -0
  239. package/package.json +13 -7
  240. package/src/ast-analysis/engine.ts +147 -138
  241. package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
  242. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  243. package/src/db/connection.ts +90 -59
  244. package/src/db/index.ts +1 -0
  245. package/src/db/migrations.ts +36 -32
  246. package/src/domain/analysis/context.ts +73 -75
  247. package/src/domain/analysis/dependencies.ts +78 -68
  248. package/src/domain/analysis/exports.ts +45 -34
  249. package/src/domain/analysis/fn-impact.ts +67 -64
  250. package/src/domain/analysis/module-map.ts +103 -8
  251. package/src/domain/analysis/query-helpers.ts +35 -0
  252. package/src/domain/graph/builder/helpers.ts +12 -6
  253. package/src/domain/graph/builder/incremental.ts +3 -2
  254. package/src/domain/graph/builder/pipeline.ts +71 -3
  255. package/src/domain/graph/builder/stages/build-edges.ts +10 -75
  256. package/src/domain/graph/builder/stages/build-structure.ts +9 -7
  257. package/src/domain/graph/builder/stages/collect-files.ts +2 -2
  258. package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
  259. package/src/domain/graph/builder/stages/finalize.ts +159 -125
  260. package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
  261. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  262. package/src/domain/graph/resolve.ts +34 -46
  263. package/src/domain/graph/watcher.ts +12 -14
  264. package/src/domain/parser.ts +168 -97
  265. package/src/domain/search/search/cli-formatter.ts +121 -94
  266. package/src/extractors/bash.ts +97 -0
  267. package/src/extractors/c.ts +212 -0
  268. package/src/extractors/cpp.ts +298 -0
  269. package/src/extractors/csharp.ts +53 -56
  270. package/src/extractors/go.ts +152 -134
  271. package/src/extractors/hcl.ts +6 -6
  272. package/src/extractors/helpers.ts +93 -1
  273. package/src/extractors/index.ts +6 -0
  274. package/src/extractors/java.ts +43 -48
  275. package/src/extractors/javascript.ts +328 -281
  276. package/src/extractors/kotlin.ts +293 -0
  277. package/src/extractors/php.ts +46 -40
  278. package/src/extractors/python.ts +81 -104
  279. package/src/extractors/ruby.ts +6 -13
  280. package/src/extractors/rust.ts +65 -85
  281. package/src/extractors/scala.ts +285 -0
  282. package/src/extractors/swift.ts +293 -0
  283. package/src/features/ast.ts +10 -25
  284. package/src/features/audit.ts +24 -20
  285. package/src/features/branch-compare.ts +51 -4
  286. package/src/features/cfg.ts +158 -65
  287. package/src/features/check.ts +90 -74
  288. package/src/features/complexity-query.ts +181 -163
  289. package/src/features/complexity.ts +64 -1
  290. package/src/features/dataflow.ts +462 -217
  291. package/src/features/graph-enrichment.ts +161 -117
  292. package/src/features/sequence.ts +27 -4
  293. package/src/features/structure-query.ts +43 -4
  294. package/src/features/structure.ts +50 -22
  295. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  296. package/src/graph/algorithms/leiden/index.ts +67 -28
  297. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  298. package/src/graph/algorithms/leiden/partition.ts +131 -98
  299. package/src/graph/model.ts +19 -7
  300. package/src/infrastructure/config.ts +60 -58
  301. package/src/infrastructure/registry.ts +17 -14
  302. package/src/mcp/server.ts +46 -37
  303. package/src/presentation/audit.ts +72 -67
  304. package/src/presentation/branch-compare.ts +54 -50
  305. package/src/presentation/check.ts +34 -34
  306. package/src/presentation/colors.ts +2 -0
  307. package/src/presentation/complexity.ts +39 -33
  308. package/src/presentation/queries-cli/exports.ts +17 -17
  309. package/src/presentation/queries-cli/impact.ts +30 -22
  310. package/src/types.ts +189 -7
@@ -0,0 +1,293 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
9
+
10
+ /**
11
+ * Extract symbols from Kotlin files.
12
+ */
13
+ export function extractKotlinSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
14
+ const ctx: ExtractorOutput = {
15
+ definitions: [],
16
+ calls: [],
17
+ imports: [],
18
+ classes: [],
19
+ exports: [],
20
+ typeMap: new Map(),
21
+ };
22
+
23
+ walkKotlinNode(tree.rootNode, ctx);
24
+ return ctx;
25
+ }
26
+
27
+ function walkKotlinNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
28
+ switch (node.type) {
29
+ case 'class_declaration':
30
+ handleKotlinClassDecl(node, ctx);
31
+ break;
32
+ case 'object_declaration':
33
+ handleKotlinObjectDecl(node, ctx);
34
+ break;
35
+ case 'function_declaration':
36
+ handleKotlinFunctionDecl(node, ctx);
37
+ break;
38
+ case 'import_header':
39
+ handleKotlinImport(node, ctx);
40
+ break;
41
+ case 'call_expression':
42
+ handleKotlinCallExpression(node, ctx);
43
+ break;
44
+ case 'navigation_expression':
45
+ handleKotlinNavExpression(node, ctx);
46
+ break;
47
+ }
48
+
49
+ for (let i = 0; i < node.childCount; i++) {
50
+ const child = node.child(i);
51
+ if (child) walkKotlinNode(child, ctx);
52
+ }
53
+ }
54
+
55
+ // ── Walk-path per-node-type handlers ────────────────────────────────────────
56
+
57
+ function hasKeywordChild(node: TreeSitterNode, keyword: string): boolean {
58
+ for (let i = 0; i < node.childCount; i++) {
59
+ const child = node.child(i);
60
+ if (child && child.text === keyword) return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ function hasModifier(node: TreeSitterNode, keyword: string): boolean {
66
+ for (let i = 0; i < node.childCount; i++) {
67
+ const child = node.child(i);
68
+ if (!child) continue;
69
+ if (child.type === 'modifiers' && child.text.includes(keyword)) return true;
70
+ }
71
+ return false;
72
+ }
73
+
74
+ function handleKotlinClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
75
+ const isInterface = hasKeywordChild(node, 'interface');
76
+ const isEnum = hasModifier(node, 'enum');
77
+
78
+ const nameNode = findChild(node, 'type_identifier');
79
+ if (!nameNode) return;
80
+ const name = nameNode.text;
81
+
82
+ const kind = isInterface ? 'interface' : isEnum ? 'enum' : 'class';
83
+
84
+ const children: SubDeclaration[] = [];
85
+ if (isEnum) {
86
+ // Enum entries are inside class_body
87
+ const body = findChild(node, 'class_body');
88
+ if (body) {
89
+ for (let i = 0; i < body.childCount; i++) {
90
+ const child = body.child(i);
91
+ if (child && child.type === 'enum_entry') {
92
+ const entryName = findChild(child, 'simple_identifier');
93
+ if (entryName) {
94
+ children.push({
95
+ name: entryName.text,
96
+ kind: 'constant',
97
+ line: child.startPosition.row + 1,
98
+ });
99
+ }
100
+ }
101
+ }
102
+ }
103
+ } else {
104
+ // Extract properties from class_body
105
+ const body = findChild(node, 'class_body');
106
+ if (body) {
107
+ for (let i = 0; i < body.childCount; i++) {
108
+ const child = body.child(i);
109
+ if (child && child.type === 'property_declaration') {
110
+ const propName = findChild(child, 'variable_declaration');
111
+ if (propName) {
112
+ const id = findChild(propName, 'simple_identifier');
113
+ if (id) {
114
+ children.push({
115
+ name: id.text,
116
+ kind: 'property',
117
+ line: child.startPosition.row + 1,
118
+ visibility: extractModifierVisibility(child),
119
+ });
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ ctx.definitions.push({
128
+ name,
129
+ kind,
130
+ line: node.startPosition.row + 1,
131
+ endLine: nodeEndLine(node),
132
+ children: children.length > 0 ? children : undefined,
133
+ });
134
+
135
+ // Methods inside class_body
136
+ const body = findChild(node, 'class_body');
137
+ if (body) {
138
+ for (let i = 0; i < body.childCount; i++) {
139
+ const child = body.child(i);
140
+ if (child && child.type === 'function_declaration') {
141
+ const methName = findChild(child, 'simple_identifier');
142
+ if (methName) {
143
+ ctx.definitions.push({
144
+ name: `${name}.${methName.text}`,
145
+ kind: 'method',
146
+ line: child.startPosition.row + 1,
147
+ endLine: child.endPosition.row + 1,
148
+ visibility: extractModifierVisibility(child),
149
+ });
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ // Inheritance: delegation_specifier nodes are DIRECT children
156
+ for (let i = 0; i < node.childCount; i++) {
157
+ const child = node.child(i);
158
+ if (!child || child.type !== 'delegation_specifier') continue;
159
+
160
+ // constructor_invocation > user_type > type_identifier (extends)
161
+ const ctorInvocation = findChild(child, 'constructor_invocation');
162
+ if (ctorInvocation) {
163
+ const userType = findChild(ctorInvocation, 'user_type');
164
+ if (userType) {
165
+ const typeId = findChild(userType, 'type_identifier');
166
+ if (typeId) {
167
+ ctx.classes.push({
168
+ name,
169
+ extends: typeId.text,
170
+ line: node.startPosition.row + 1,
171
+ });
172
+ }
173
+ }
174
+ continue;
175
+ }
176
+
177
+ // user_type > type_identifier (implements)
178
+ const userType = findChild(child, 'user_type');
179
+ if (userType) {
180
+ const typeId = findChild(userType, 'type_identifier');
181
+ if (typeId) {
182
+ ctx.classes.push({
183
+ name,
184
+ implements: typeId.text,
185
+ line: node.startPosition.row + 1,
186
+ });
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ function handleKotlinObjectDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
193
+ const nameNode = findChild(node, 'type_identifier');
194
+ if (!nameNode) return;
195
+ ctx.definitions.push({
196
+ name: nameNode.text,
197
+ kind: 'class',
198
+ line: node.startPosition.row + 1,
199
+ endLine: nodeEndLine(node),
200
+ });
201
+
202
+ // Methods inside object body
203
+ const body = findChild(node, 'class_body');
204
+ if (body) {
205
+ for (let i = 0; i < body.childCount; i++) {
206
+ const child = body.child(i);
207
+ if (child && child.type === 'function_declaration') {
208
+ const methName = findChild(child, 'simple_identifier');
209
+ if (methName) {
210
+ ctx.definitions.push({
211
+ name: `${nameNode.text}.${methName.text}`,
212
+ kind: 'method',
213
+ line: child.startPosition.row + 1,
214
+ endLine: child.endPosition.row + 1,
215
+ visibility: extractModifierVisibility(child),
216
+ });
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ function handleKotlinFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
224
+ // Skip methods already emitted by class/object handlers
225
+ if (
226
+ node.parent?.type === 'class_body' &&
227
+ (node.parent.parent?.type === 'class_declaration' ||
228
+ node.parent.parent?.type === 'object_declaration')
229
+ ) {
230
+ return;
231
+ }
232
+ const nameNode = findChild(node, 'simple_identifier');
233
+ if (!nameNode) return;
234
+ const params = extractKotlinParameters(node);
235
+ ctx.definitions.push({
236
+ name: nameNode.text,
237
+ kind: 'function',
238
+ line: node.startPosition.row + 1,
239
+ endLine: nodeEndLine(node),
240
+ children: params.length > 0 ? params : undefined,
241
+ visibility: extractModifierVisibility(node),
242
+ });
243
+ }
244
+
245
+ function handleKotlinImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
246
+ const identNode = findChild(node, 'identifier');
247
+ if (!identNode) return;
248
+ const fullPath = identNode.text;
249
+ const lastName = fullPath.split('.').pop() ?? fullPath;
250
+ ctx.imports.push({
251
+ source: fullPath,
252
+ names: [lastName],
253
+ line: node.startPosition.row + 1,
254
+ kotlinImport: true,
255
+ });
256
+ }
257
+
258
+ function handleKotlinCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
259
+ const funcNode = node.child(0);
260
+ if (!funcNode) return;
261
+ if (funcNode.type === 'simple_identifier') {
262
+ ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
263
+ }
264
+ }
265
+
266
+ function handleKotlinNavExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
267
+ // navigation_expression: expr . identifier — only emit if parent is call_expression
268
+ if (node.parent?.type !== 'call_expression') return;
269
+ const lastChild = node.child(node.childCount - 1);
270
+ const firstChild = node.child(0);
271
+ if (lastChild && lastChild.type === 'simple_identifier' && firstChild) {
272
+ const call: Call = { name: lastChild.text, line: node.startPosition.row + 1 };
273
+ call.receiver = firstChild.text;
274
+ ctx.calls.push(call);
275
+ }
276
+ }
277
+
278
+ // ── Child extraction helpers ────────────────────────────────────────────────
279
+
280
+ function extractKotlinParameters(funcNode: TreeSitterNode): SubDeclaration[] {
281
+ const params: SubDeclaration[] = [];
282
+ const paramList = findChild(funcNode, 'function_value_parameters');
283
+ if (!paramList) return params;
284
+ for (let i = 0; i < paramList.childCount; i++) {
285
+ const param = paramList.child(i);
286
+ if (!param || param.type !== 'parameter') continue;
287
+ const nameNode = findChild(param, 'simple_identifier');
288
+ if (nameNode) {
289
+ params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
290
+ }
291
+ }
292
+ return params;
293
+ }
@@ -5,7 +5,14 @@ import type {
5
5
  TreeSitterNode,
6
6
  TreeSitterTree,
7
7
  } from '../types.js';
8
- import { extractModifierVisibility, findChild, MAX_WALK_DEPTH, nodeEndLine } from './helpers.js';
8
+ import {
9
+ extractBodyMembers,
10
+ extractModifierVisibility,
11
+ findChild,
12
+ lastPathSegment,
13
+ MAX_WALK_DEPTH,
14
+ nodeEndLine,
15
+ } from './helpers.js';
9
16
 
10
17
  function extractPhpParameters(fnNode: TreeSitterNode): SubDeclaration[] {
11
18
  const params: SubDeclaration[] = [];
@@ -25,6 +32,39 @@ function extractPhpParameters(fnNode: TreeSitterNode): SubDeclaration[] {
25
32
  return params;
26
33
  }
27
34
 
35
+ /** Extract property declarations from a PHP class member. */
36
+ function extractPhpProperties(member: TreeSitterNode, children: SubDeclaration[]): void {
37
+ for (let j = 0; j < member.childCount; j++) {
38
+ const el = member.child(j);
39
+ if (!el || el.type !== 'property_element') continue;
40
+ const varNode = findChild(el, 'variable_name');
41
+ if (varNode) {
42
+ children.push({
43
+ name: varNode.text,
44
+ kind: 'property',
45
+ line: member.startPosition.row + 1,
46
+ visibility: extractModifierVisibility(member),
47
+ });
48
+ }
49
+ }
50
+ }
51
+
52
+ /** Extract constant declarations from a PHP class member. */
53
+ function extractPhpConstants(member: TreeSitterNode, children: SubDeclaration[]): void {
54
+ for (let j = 0; j < member.childCount; j++) {
55
+ const el = member.child(j);
56
+ if (!el || el.type !== 'const_element') continue;
57
+ const nameNode = el.childForFieldName('name') || findChild(el, 'name');
58
+ if (nameNode) {
59
+ children.push({
60
+ name: nameNode.text,
61
+ kind: 'constant',
62
+ line: member.startPosition.row + 1,
63
+ });
64
+ }
65
+ }
66
+ }
67
+
28
68
  function extractPhpClassChildren(classNode: TreeSitterNode): SubDeclaration[] {
29
69
  const children: SubDeclaration[] = [];
30
70
  const body = classNode.childForFieldName('body') || findChild(classNode, 'declaration_list');
@@ -33,50 +73,16 @@ function extractPhpClassChildren(classNode: TreeSitterNode): SubDeclaration[] {
33
73
  const member = body.child(i);
34
74
  if (!member) continue;
35
75
  if (member.type === 'property_declaration') {
36
- for (let j = 0; j < member.childCount; j++) {
37
- const el = member.child(j);
38
- if (!el || el.type !== 'property_element') continue;
39
- const varNode = findChild(el, 'variable_name');
40
- if (varNode) {
41
- children.push({
42
- name: varNode.text,
43
- kind: 'property',
44
- line: member.startPosition.row + 1,
45
- visibility: extractModifierVisibility(member),
46
- });
47
- }
48
- }
76
+ extractPhpProperties(member, children);
49
77
  } else if (member.type === 'const_declaration') {
50
- for (let j = 0; j < member.childCount; j++) {
51
- const el = member.child(j);
52
- if (!el || el.type !== 'const_element') continue;
53
- const nameNode = el.childForFieldName('name') || findChild(el, 'name');
54
- if (nameNode) {
55
- children.push({
56
- name: nameNode.text,
57
- kind: 'constant',
58
- line: member.startPosition.row + 1,
59
- });
60
- }
61
- }
78
+ extractPhpConstants(member, children);
62
79
  }
63
80
  }
64
81
  return children;
65
82
  }
66
83
 
67
84
  function extractPhpEnumCases(enumNode: TreeSitterNode): SubDeclaration[] {
68
- const children: SubDeclaration[] = [];
69
- const body = enumNode.childForFieldName('body') || findChild(enumNode, 'enum_declaration_list');
70
- if (!body) return children;
71
- for (let i = 0; i < body.childCount; i++) {
72
- const member = body.child(i);
73
- if (!member || member.type !== 'enum_case') continue;
74
- const nameNode = member.childForFieldName('name');
75
- if (nameNode) {
76
- children.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });
77
- }
78
- }
79
- return children;
85
+ return extractBodyMembers(enumNode, ['body', 'enum_declaration_list'], 'enum_case', 'constant');
80
86
  }
81
87
 
82
88
  /**
@@ -272,7 +278,7 @@ function handlePhpNamespaceUse(node: TreeSitterNode, ctx: ExtractorOutput): void
272
278
  const nameNode = findChild(child, 'qualified_name') || findChild(child, 'name');
273
279
  if (nameNode) {
274
280
  const fullPath = nameNode.text;
275
- const lastName = fullPath.split('\\').pop() ?? fullPath;
281
+ const lastName = lastPathSegment(fullPath, '\\');
276
282
  const alias = child.childForFieldName('alias');
277
283
  ctx.imports.push({
278
284
  source: fullPath,
@@ -284,7 +290,7 @@ function handlePhpNamespaceUse(node: TreeSitterNode, ctx: ExtractorOutput): void
284
290
  }
285
291
  if (child && (child.type === 'qualified_name' || child.type === 'name')) {
286
292
  const fullPath = child.text;
287
- const lastName = fullPath.split('\\').pop() ?? fullPath;
293
+ const lastName = lastPathSegment(fullPath, '\\');
288
294
  ctx.imports.push({
289
295
  source: fullPath,
290
296
  names: [lastName],
@@ -4,9 +4,15 @@ import type {
4
4
  SubDeclaration,
5
5
  TreeSitterNode,
6
6
  TreeSitterTree,
7
- TypeMapEntry,
8
7
  } from '../types.js';
9
- import { findChild, MAX_WALK_DEPTH, nodeEndLine, pythonVisibility } from './helpers.js';
8
+ import {
9
+ findChild,
10
+ findParentNode,
11
+ MAX_WALK_DEPTH,
12
+ nodeEndLine,
13
+ pythonVisibility,
14
+ setTypeMapEntry,
15
+ } from './helpers.js';
10
16
 
11
17
  /** Built-in globals that start with uppercase but are not user-defined types. */
12
18
  const BUILTIN_GLOBALS_PY: Set<string> = new Set([
@@ -268,6 +274,37 @@ function extractPythonParameters(fnNode: TreeSitterNode): SubDeclaration[] {
268
274
  return params;
269
275
  }
270
276
 
277
+ /** Extract class-level assignment properties from expression statements. */
278
+ function extractClassAssignment(
279
+ child: TreeSitterNode,
280
+ seen: Set<string>,
281
+ props: SubDeclaration[],
282
+ ): void {
283
+ const assignment = findChild(child, 'assignment');
284
+ if (!assignment) return;
285
+ const left = assignment.childForFieldName('left');
286
+ if (!left || left.type !== 'identifier' || seen.has(left.text)) return;
287
+ seen.add(left.text);
288
+ props.push({
289
+ name: left.text,
290
+ kind: 'property',
291
+ line: child.startPosition.row + 1,
292
+ visibility: pythonVisibility(left.text),
293
+ });
294
+ }
295
+
296
+ /** If node is an __init__ method, walk its body for self.x assignments. */
297
+ function extractInitProperties(
298
+ node: TreeSitterNode,
299
+ seen: Set<string>,
300
+ props: SubDeclaration[],
301
+ ): void {
302
+ const fnName = node.childForFieldName('name');
303
+ if (!fnName || fnName.text !== '__init__') return;
304
+ const initBody = node.childForFieldName('body') || findChild(node, 'block');
305
+ if (initBody) walkInitBody(initBody, seen, props);
306
+ }
307
+
271
308
  function extractPythonClassProperties(classNode: TreeSitterNode): SubDeclaration[] {
272
309
  const props: SubDeclaration[] = [];
273
310
  const seen = new Set<string>();
@@ -279,42 +316,14 @@ function extractPythonClassProperties(classNode: TreeSitterNode): SubDeclaration
279
316
  if (!child) continue;
280
317
 
281
318
  if (child.type === 'expression_statement') {
282
- const assignment = findChild(child, 'assignment');
283
- if (assignment) {
284
- const left = assignment.childForFieldName('left');
285
- if (left && left.type === 'identifier' && !seen.has(left.text)) {
286
- seen.add(left.text);
287
- props.push({
288
- name: left.text,
289
- kind: 'property',
290
- line: child.startPosition.row + 1,
291
- visibility: pythonVisibility(left.text),
292
- });
293
- }
294
- }
295
- }
296
-
297
- if (child.type === 'function_definition') {
298
- const fnName = child.childForFieldName('name');
299
- if (fnName && fnName.text === '__init__') {
300
- const initBody = child.childForFieldName('body') || findChild(child, 'block');
301
- if (initBody) {
302
- walkInitBody(initBody, seen, props);
303
- }
304
- }
305
- }
306
-
307
- if (child.type === 'decorated_definition') {
319
+ extractClassAssignment(child, seen, props);
320
+ } else if (child.type === 'function_definition') {
321
+ extractInitProperties(child, seen, props);
322
+ } else if (child.type === 'decorated_definition') {
308
323
  for (let j = 0; j < child.childCount; j++) {
309
324
  const inner = child.child(j);
310
325
  if (inner && inner.type === 'function_definition') {
311
- const fnName = inner.childForFieldName('name');
312
- if (fnName && fnName.text === '__init__') {
313
- const initBody = inner.childForFieldName('body') || findChild(inner, 'block');
314
- if (initBody) {
315
- walkInitBody(initBody, seen, props);
316
- }
317
- }
326
+ extractInitProperties(inner, seen, props);
318
327
  }
319
328
  }
320
329
  }
@@ -348,15 +357,37 @@ function extractPythonTypeMap(node: TreeSitterNode, ctx: ExtractorOutput): void
348
357
  extractPythonTypeMapDepth(node, ctx, 0);
349
358
  }
350
359
 
351
- function setIfHigherPy(
352
- typeMap: Map<string, TypeMapEntry>,
353
- name: string,
354
- type: string,
355
- confidence: number,
356
- ): void {
357
- const existing = typeMap.get(name);
358
- if (!existing || confidence > existing.confidence) {
359
- typeMap.set(name, { type, confidence });
360
+ /** Handle typed_parameter or typed_default_parameter for type map. */
361
+ function handlePyTypedParam(node: TreeSitterNode, ctx: ExtractorOutput): void {
362
+ const isDefault = node.type === 'typed_default_parameter';
363
+ const nameNode = isDefault ? node.childForFieldName('name') : node.child(0);
364
+ const typeNode = node.childForFieldName('type');
365
+ if (!nameNode || nameNode.type !== 'identifier' || !typeNode) return;
366
+ if (nameNode.text === 'self' || nameNode.text === 'cls') return;
367
+ const typeName = extractPythonTypeName(typeNode);
368
+ if (typeName && ctx.typeMap) setTypeMapEntry(ctx.typeMap, nameNode.text, typeName, 0.9);
369
+ }
370
+
371
+ /** Handle assignment for constructor/factory type inference. */
372
+ function handlePyAssignmentType(node: TreeSitterNode, ctx: ExtractorOutput): void {
373
+ const left = node.childForFieldName('left');
374
+ const right = node.childForFieldName('right');
375
+ if (!left || left.type !== 'identifier' || !right || right.type !== 'call') return;
376
+
377
+ const fn = right.childForFieldName('function');
378
+ if (!fn) return;
379
+ if (fn.type === 'identifier') {
380
+ const name = fn.text;
381
+ if (name[0] && name[0] !== name[0].toLowerCase()) {
382
+ if (ctx.typeMap) setTypeMapEntry(ctx.typeMap, left.text, name, 1.0);
383
+ }
384
+ } else if (fn.type === 'attribute') {
385
+ const obj = fn.childForFieldName('object');
386
+ if (!obj || obj.type !== 'identifier') return;
387
+ const objName = obj.text;
388
+ if (objName[0] && objName[0] !== objName[0].toLowerCase() && !BUILTIN_GLOBALS_PY.has(objName)) {
389
+ if (ctx.typeMap) setTypeMapEntry(ctx.typeMap, left.text, objName, 0.7);
390
+ }
360
391
  }
361
392
  }
362
393
 
@@ -367,57 +398,10 @@ function extractPythonTypeMapDepth(
367
398
  ): void {
368
399
  if (depth >= MAX_WALK_DEPTH) return;
369
400
 
370
- // typed_parameter: identifier : type (confidence 0.9)
371
- if (node.type === 'typed_parameter') {
372
- const nameNode = node.child(0);
373
- const typeNode = node.childForFieldName('type');
374
- if (nameNode && nameNode.type === 'identifier' && typeNode) {
375
- const typeName = extractPythonTypeName(typeNode);
376
- if (typeName && nameNode.text !== 'self' && nameNode.text !== 'cls') {
377
- if (ctx.typeMap) setIfHigherPy(ctx.typeMap, nameNode.text, typeName, 0.9);
378
- }
379
- }
380
- }
381
-
382
- // typed_default_parameter: name : type = default (confidence 0.9)
383
- if (node.type === 'typed_default_parameter') {
384
- const nameNode = node.childForFieldName('name');
385
- const typeNode = node.childForFieldName('type');
386
- if (nameNode && nameNode.type === 'identifier' && typeNode) {
387
- const typeName = extractPythonTypeName(typeNode);
388
- if (typeName && nameNode.text !== 'self' && nameNode.text !== 'cls') {
389
- if (ctx.typeMap) setIfHigherPy(ctx.typeMap, nameNode.text, typeName, 0.9);
390
- }
391
- }
392
- }
393
-
394
- // assignment: x = SomeClass(...) → constructor (confidence 1.0)
395
- // x = SomeClass.create(...) → factory (confidence 0.7)
396
- if (node.type === 'assignment') {
397
- const left = node.childForFieldName('left');
398
- const right = node.childForFieldName('right');
399
- if (left && left.type === 'identifier' && right && right.type === 'call') {
400
- const fn = right.childForFieldName('function');
401
- if (fn && fn.type === 'identifier') {
402
- const name = fn.text;
403
- if (name[0] && name[0] !== name[0].toLowerCase()) {
404
- if (ctx.typeMap) setIfHigherPy(ctx.typeMap, left.text, name, 1.0);
405
- }
406
- }
407
- if (fn && fn.type === 'attribute') {
408
- const obj = fn.childForFieldName('object');
409
- if (obj && obj.type === 'identifier') {
410
- const objName = obj.text;
411
- if (
412
- objName[0] &&
413
- objName[0] !== objName[0].toLowerCase() &&
414
- !BUILTIN_GLOBALS_PY.has(objName)
415
- ) {
416
- if (ctx.typeMap) setIfHigherPy(ctx.typeMap, left.text, objName, 0.7);
417
- }
418
- }
419
- }
420
- }
401
+ if (node.type === 'typed_parameter' || node.type === 'typed_default_parameter') {
402
+ handlePyTypedParam(node, ctx);
403
+ } else if (node.type === 'assignment') {
404
+ handlePyAssignmentType(node, ctx);
421
405
  }
422
406
 
423
407
  for (let i = 0; i < node.childCount; i++) {
@@ -441,14 +425,7 @@ function extractPythonTypeName(typeNode: TreeSitterNode): string | null {
441
425
  return null;
442
426
  }
443
427
 
428
+ const PY_CLASS_TYPES = ['class_definition'] as const;
444
429
  function findPythonParentClass(node: TreeSitterNode): string | null {
445
- let current = node.parent;
446
- while (current) {
447
- if (current.type === 'class_definition') {
448
- const nameNode = current.childForFieldName('name');
449
- return nameNode ? nameNode.text : null;
450
- }
451
- current = current.parent;
452
- }
453
- return null;
430
+ return findParentNode(node, PY_CLASS_TYPES);
454
431
  }
@@ -5,7 +5,7 @@ import type {
5
5
  TreeSitterNode,
6
6
  TreeSitterTree,
7
7
  } from '../types.js';
8
- import { findChild, nodeEndLine } from './helpers.js';
8
+ import { findChild, findParentNode, lastPathSegment, nodeEndLine, stripQuotes } from './helpers.js';
9
9
 
10
10
  /**
11
11
  * Extract symbols from Ruby files.
@@ -176,10 +176,10 @@ function handleRubyRequire(node: TreeSitterNode, ctx: ExtractorOutput): void {
176
176
  for (let i = 0; i < args.childCount; i++) {
177
177
  const arg = args.child(i);
178
178
  if (arg && (arg.type === 'string' || arg.type === 'string_content')) {
179
- const strContent = arg.text.replace(/^['"]|['"]$/g, '');
179
+ const strContent = stripQuotes(arg.text);
180
180
  ctx.imports.push({
181
181
  source: strContent,
182
- names: [strContent.split('/').pop() ?? strContent],
182
+ names: [lastPathSegment(strContent)],
183
183
  line: node.startPosition.row + 1,
184
184
  rubyRequire: true,
185
185
  });
@@ -190,7 +190,7 @@ function handleRubyRequire(node: TreeSitterNode, ctx: ExtractorOutput): void {
190
190
  if (content) {
191
191
  ctx.imports.push({
192
192
  source: content.text,
193
- names: [content.text.split('/').pop() ?? content.text],
193
+ names: [lastPathSegment(content.text)],
194
194
  line: node.startPosition.row + 1,
195
195
  rubyRequire: true,
196
196
  });
@@ -221,16 +221,9 @@ function handleRubyModuleInclusion(
221
221
  }
222
222
  }
223
223
 
224
+ const RUBY_PARENT_TYPES = ['class', 'module'] as const;
224
225
  function findRubyParentClass(node: TreeSitterNode): string | null {
225
- let current = node.parent;
226
- while (current) {
227
- if (current.type === 'class' || current.type === 'module') {
228
- const nameNode = current.childForFieldName('name');
229
- return nameNode ? nameNode.text : null;
230
- }
231
- current = current.parent;
232
- }
233
- return null;
226
+ return findParentNode(node, RUBY_PARENT_TYPES);
234
227
  }
235
228
 
236
229
  // ── Child extraction helpers ────────────────────────────────────────────────