@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
@@ -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 Swift files.
12
+ */
13
+ export function extractSwiftSymbols(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
+ walkSwiftNode(tree.rootNode, ctx);
24
+ return ctx;
25
+ }
26
+
27
+ function walkSwiftNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
28
+ switch (node.type) {
29
+ case 'class_declaration':
30
+ handleSwiftClassDecl(node, ctx);
31
+ break;
32
+ case 'protocol_declaration':
33
+ handleSwiftProtocolDecl(node, ctx);
34
+ break;
35
+ case 'function_declaration':
36
+ handleSwiftFunctionDecl(node, ctx);
37
+ break;
38
+ case 'import_declaration':
39
+ handleSwiftImportDecl(node, ctx);
40
+ break;
41
+ case 'call_expression':
42
+ handleSwiftCallExpression(node, ctx);
43
+ break;
44
+ case 'property_declaration':
45
+ handleSwiftPropertyDecl(node, ctx);
46
+ break;
47
+ }
48
+
49
+ for (let i = 0; i < node.childCount; i++) {
50
+ const child = node.child(i);
51
+ if (child) walkSwiftNode(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 handleSwiftClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
66
+ const isStruct = hasKeywordChild(node, 'struct');
67
+ const isEnum = hasKeywordChild(node, 'enum');
68
+
69
+ // Name is a type_identifier direct child
70
+ const nameNode = findChild(node, 'type_identifier');
71
+ if (!nameNode) return;
72
+ const name = nameNode.text;
73
+
74
+ const kind = isEnum ? 'enum' : isStruct ? 'struct' : 'class';
75
+
76
+ const children: SubDeclaration[] = [];
77
+
78
+ if (isEnum) {
79
+ // Enum cases: enum_entry > simple_identifier, inside enum_class_body
80
+ const body = findChild(node, 'enum_class_body');
81
+ if (body) {
82
+ for (let i = 0; i < body.childCount; i++) {
83
+ const child = body.child(i);
84
+ if (child && child.type === 'enum_entry') {
85
+ const entryName = findChild(child, 'simple_identifier');
86
+ if (entryName) {
87
+ children.push({
88
+ name: entryName.text,
89
+ kind: 'constant',
90
+ line: child.startPosition.row + 1,
91
+ });
92
+ }
93
+ }
94
+ }
95
+ }
96
+ } else {
97
+ // Extract properties from class_body
98
+ const body = findChild(node, 'class_body');
99
+ if (body) {
100
+ for (let i = 0; i < body.childCount; i++) {
101
+ const child = body.child(i);
102
+ if (child && child.type === 'property_declaration') {
103
+ const pattern = findChild(child, 'pattern');
104
+ if (pattern) {
105
+ const propName = findChild(pattern, 'simple_identifier');
106
+ if (propName) {
107
+ children.push({
108
+ name: propName.text,
109
+ kind: 'property',
110
+ line: child.startPosition.row + 1,
111
+ visibility: extractModifierVisibility(child),
112
+ });
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ ctx.definitions.push({
121
+ name,
122
+ kind,
123
+ line: node.startPosition.row + 1,
124
+ endLine: nodeEndLine(node),
125
+ children: children.length > 0 ? children : undefined,
126
+ });
127
+
128
+ // Methods inside class_body or enum_class_body
129
+ const body = findChild(node, 'class_body') || findChild(node, 'enum_class_body');
130
+ if (body) {
131
+ for (let i = 0; i < body.childCount; i++) {
132
+ const child = body.child(i);
133
+ if (child && child.type === 'function_declaration') {
134
+ const methName = findChild(child, 'simple_identifier');
135
+ if (methName) {
136
+ ctx.definitions.push({
137
+ name: `${name}.${methName.text}`,
138
+ kind: 'method',
139
+ line: child.startPosition.row + 1,
140
+ endLine: child.endPosition.row + 1,
141
+ visibility: extractModifierVisibility(child),
142
+ });
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ // Inheritance: inheritance_specifier nodes are DIRECT children of class_declaration
149
+ // First specifier is the superclass (extends), rest are protocol conformances (implements)
150
+ let first = true;
151
+ for (let i = 0; i < node.childCount; i++) {
152
+ const child = node.child(i);
153
+ if (!child || child.type !== 'inheritance_specifier') continue;
154
+ // inheritance_specifier > user_type > type_identifier
155
+ const userType = findChild(child, 'user_type');
156
+ if (userType) {
157
+ const typeId = findChild(userType, 'type_identifier');
158
+ if (typeId) {
159
+ if (first) {
160
+ ctx.classes.push({
161
+ name,
162
+ extends: typeId.text,
163
+ line: node.startPosition.row + 1,
164
+ });
165
+ first = false;
166
+ } else {
167
+ ctx.classes.push({
168
+ name,
169
+ implements: typeId.text,
170
+ line: node.startPosition.row + 1,
171
+ });
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ function handleSwiftProtocolDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
179
+ const nameNode = findChild(node, 'type_identifier');
180
+ if (!nameNode) return;
181
+ const name = nameNode.text;
182
+
183
+ ctx.definitions.push({
184
+ name,
185
+ kind: 'interface',
186
+ line: node.startPosition.row + 1,
187
+ endLine: nodeEndLine(node),
188
+ });
189
+
190
+ // Methods inside protocol_body or class_body
191
+ const body = findChild(node, 'protocol_body') || findChild(node, 'class_body');
192
+ if (body) {
193
+ for (let i = 0; i < body.childCount; i++) {
194
+ const child = body.child(i);
195
+ if (child && child.type === 'function_declaration') {
196
+ const methName = findChild(child, 'simple_identifier');
197
+ if (methName) {
198
+ ctx.definitions.push({
199
+ name: `${name}.${methName.text}`,
200
+ kind: 'method',
201
+ line: child.startPosition.row + 1,
202
+ endLine: child.endPosition.row + 1,
203
+ });
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ function handleSwiftFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
211
+ // Skip methods already emitted by class/protocol handlers
212
+ if (
213
+ node.parent?.type === 'class_body' ||
214
+ node.parent?.type === 'protocol_body' ||
215
+ node.parent?.type === 'enum_class_body'
216
+ ) {
217
+ if (
218
+ node.parent.parent?.type === 'class_declaration' ||
219
+ node.parent.parent?.type === 'protocol_declaration'
220
+ ) {
221
+ return;
222
+ }
223
+ }
224
+ const nameNode = findChild(node, 'simple_identifier');
225
+ if (!nameNode) return;
226
+ ctx.definitions.push({
227
+ name: nameNode.text,
228
+ kind: 'function',
229
+ line: node.startPosition.row + 1,
230
+ endLine: nodeEndLine(node),
231
+ visibility: extractModifierVisibility(node),
232
+ });
233
+ }
234
+
235
+ function handleSwiftImportDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
236
+ const identNode = findChild(node, 'identifier');
237
+ if (!identNode) return;
238
+ const source = identNode.text;
239
+ ctx.imports.push({
240
+ source,
241
+ names: [source],
242
+ line: node.startPosition.row + 1,
243
+ swiftImport: true,
244
+ });
245
+ }
246
+
247
+ function handleSwiftCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
248
+ const funcNode = node.child(0);
249
+ if (!funcNode) return;
250
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
251
+ if (funcNode.type === 'navigation_expression') {
252
+ // obj.method(...)
253
+ const lastChild = funcNode.child(funcNode.childCount - 1);
254
+ const firstChild = funcNode.child(0);
255
+ if (lastChild && lastChild.type === 'simple_identifier' && firstChild) {
256
+ call.name = lastChild.text;
257
+ call.receiver = firstChild.text;
258
+ }
259
+ } else if (funcNode.type === 'simple_identifier') {
260
+ call.name = funcNode.text;
261
+ } else {
262
+ call.name = funcNode.text;
263
+ }
264
+ if (call.name) ctx.calls.push(call);
265
+ }
266
+
267
+ function handleSwiftPropertyDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
268
+ // Only handle top-level properties (class properties are handled inline)
269
+ if (
270
+ node.parent?.type === 'class_body' ||
271
+ node.parent?.type === 'protocol_body' ||
272
+ node.parent?.type === 'enum_class_body'
273
+ ) {
274
+ return;
275
+ }
276
+ // Skip function-local let/var bindings
277
+ if (node.parent?.type === 'statements' || node.parent?.type === 'function_body') {
278
+ return;
279
+ }
280
+ const pattern = findChild(node, 'pattern');
281
+ if (!pattern) return;
282
+ const nameNode = findChild(pattern, 'simple_identifier');
283
+ if (!nameNode) return;
284
+ // let → constant, var → variable
285
+ const isLet = hasKeywordChild(node, 'let');
286
+ const kind = isLet ? 'constant' : 'variable';
287
+ ctx.definitions.push({
288
+ name: nameNode.text,
289
+ kind,
290
+ line: node.startPosition.row + 1,
291
+ endLine: nodeEndLine(node),
292
+ });
293
+ }
@@ -0,0 +1,294 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import { findChild, nodeEndLine } from './helpers.js';
9
+
10
+ /**
11
+ * Extract symbols from Zig files.
12
+ *
13
+ * Zig's structs/enums/unions are anonymous — their names come from the
14
+ * enclosing `variable_declaration` (e.g. `const Foo = struct { ... };`).
15
+ */
16
+ export function extractZigSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
17
+ const ctx: ExtractorOutput = {
18
+ definitions: [],
19
+ calls: [],
20
+ imports: [],
21
+ classes: [],
22
+ exports: [],
23
+ typeMap: new Map(),
24
+ };
25
+
26
+ walkZigNode(tree.rootNode, ctx);
27
+ return ctx;
28
+ }
29
+
30
+ function walkZigNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
31
+ switch (node.type) {
32
+ case 'function_declaration':
33
+ handleZigFunction(node, ctx);
34
+ break;
35
+ case 'variable_declaration':
36
+ handleZigVariable(node, ctx);
37
+ break;
38
+ case 'call_expression':
39
+ handleZigCallExpression(node, ctx);
40
+ break;
41
+ case 'builtin_function':
42
+ handleZigBuiltin(node, ctx);
43
+ break;
44
+ case 'test_declaration':
45
+ handleZigTest(node, ctx);
46
+ break;
47
+ }
48
+
49
+ for (let i = 0; i < node.childCount; i++) {
50
+ const child = node.child(i);
51
+ if (child) walkZigNode(child, ctx);
52
+ }
53
+ }
54
+
55
+ function isInsideZigContainer(node: TreeSitterNode): boolean {
56
+ let current = node.parent;
57
+ while (current) {
58
+ if (current.type === 'struct_declaration' || current.type === 'union_declaration') return true;
59
+ current = current.parent;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ function handleZigFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
65
+ if (isInsideZigContainer(node)) return; // already emitted by extractZigContainerMethods
66
+
67
+ const nameNode = node.childForFieldName('name');
68
+ if (!nameNode) return;
69
+
70
+ const params = extractZigParams(node);
71
+
72
+ ctx.definitions.push({
73
+ name: nameNode.text,
74
+ kind: 'function',
75
+ line: node.startPosition.row + 1,
76
+ endLine: nodeEndLine(node),
77
+ children: params.length > 0 ? params : undefined,
78
+ visibility: isZigPub(node) ? 'public' : 'private',
79
+ });
80
+ }
81
+
82
+ function extractZigParams(funcNode: TreeSitterNode): SubDeclaration[] {
83
+ const params: SubDeclaration[] = [];
84
+ const paramList = funcNode.childForFieldName('parameters');
85
+ if (!paramList) return params;
86
+
87
+ for (let i = 0; i < paramList.childCount; i++) {
88
+ const param = paramList.child(i);
89
+ if (!param || param.type !== 'parameter') continue;
90
+ const nameNode = findChild(param, 'identifier');
91
+ if (nameNode) {
92
+ params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
93
+ }
94
+ }
95
+ return params;
96
+ }
97
+
98
+ function handleZigVariable(node: TreeSitterNode, ctx: ExtractorOutput): void {
99
+ const nameNode = findChild(node, 'identifier');
100
+ if (!nameNode) return;
101
+ const name = nameNode.text;
102
+
103
+ // Check if this is a struct/enum/union definition
104
+ for (let i = 0; i < node.childCount; i++) {
105
+ const child = node.child(i);
106
+ if (!child) continue;
107
+
108
+ if (child.type === 'struct_declaration') {
109
+ const members = extractZigContainerFields(child);
110
+ ctx.definitions.push({
111
+ name,
112
+ kind: 'struct',
113
+ line: node.startPosition.row + 1,
114
+ endLine: nodeEndLine(node),
115
+ children: members.length > 0 ? members : undefined,
116
+ visibility: isZigPub(node) ? 'public' : undefined,
117
+ });
118
+ extractZigContainerMethods(child, name, ctx);
119
+ return;
120
+ }
121
+ if (child.type === 'enum_declaration') {
122
+ ctx.definitions.push({
123
+ name,
124
+ kind: 'enum',
125
+ line: node.startPosition.row + 1,
126
+ endLine: nodeEndLine(node),
127
+ visibility: isZigPub(node) ? 'public' : undefined,
128
+ });
129
+ return;
130
+ }
131
+ if (child.type === 'union_declaration') {
132
+ ctx.definitions.push({
133
+ name,
134
+ kind: 'struct',
135
+ line: node.startPosition.row + 1,
136
+ endLine: nodeEndLine(node),
137
+ visibility: isZigPub(node) ? 'public' : undefined,
138
+ });
139
+ return;
140
+ }
141
+ }
142
+
143
+ // Check for @import
144
+ for (let i = 0; i < node.childCount; i++) {
145
+ const child = node.child(i);
146
+ if (!child) continue;
147
+ if (child.type === 'builtin_function') {
148
+ const builtinId = findChild(child, 'builtin_identifier');
149
+ if (builtinId?.text === '@import') {
150
+ const args = findChild(child, 'arguments');
151
+ if (args) {
152
+ const strArg = findChild(args, 'string_literal') || findChild(args, 'string');
153
+ if (strArg) {
154
+ const source = strArg.text.replace(/^"|"$/g, '');
155
+ ctx.imports.push({
156
+ source,
157
+ names: [name],
158
+ line: node.startPosition.row + 1,
159
+ });
160
+ return;
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ // Regular constant/variable
168
+ const isConst = hasChildText(node, 'const');
169
+ ctx.definitions.push({
170
+ name,
171
+ kind: isConst ? 'constant' : 'variable',
172
+ line: node.startPosition.row + 1,
173
+ endLine: nodeEndLine(node),
174
+ });
175
+ }
176
+
177
+ function extractZigContainerFields(container: TreeSitterNode): SubDeclaration[] {
178
+ const fields: SubDeclaration[] = [];
179
+ for (let i = 0; i < container.childCount; i++) {
180
+ const child = container.child(i);
181
+ if (!child || child.type !== 'container_field') continue;
182
+ const nameNode = child.childForFieldName('name') || findChild(child, 'identifier');
183
+ if (nameNode) {
184
+ fields.push({ name: nameNode.text, kind: 'property', line: child.startPosition.row + 1 });
185
+ }
186
+ }
187
+ return fields;
188
+ }
189
+
190
+ function extractZigContainerMethods(
191
+ container: TreeSitterNode,
192
+ parentName: string,
193
+ ctx: ExtractorOutput,
194
+ ): void {
195
+ for (let i = 0; i < container.childCount; i++) {
196
+ const child = container.child(i);
197
+ if (!child || child.type !== 'function_declaration') continue;
198
+ const nameNode = child.childForFieldName('name');
199
+ if (nameNode) {
200
+ ctx.definitions.push({
201
+ name: `${parentName}.${nameNode.text}`,
202
+ kind: 'method',
203
+ line: child.startPosition.row + 1,
204
+ endLine: nodeEndLine(child),
205
+ visibility: isZigPub(child) ? 'public' : 'private',
206
+ });
207
+ }
208
+ }
209
+ }
210
+
211
+ function handleZigCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
212
+ const funcNode = node.childForFieldName('function');
213
+ if (!funcNode) return;
214
+
215
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
216
+
217
+ if (funcNode.type === 'field_expression' || funcNode.type === 'field_access') {
218
+ const field = funcNode.childForFieldName('field') || funcNode.childForFieldName('member');
219
+ const value = funcNode.childForFieldName('value') || funcNode.child(0);
220
+ if (field) call.name = field.text;
221
+ if (value) call.receiver = value.text;
222
+ } else {
223
+ call.name = funcNode.text;
224
+ }
225
+
226
+ if (call.name) ctx.calls.push(call);
227
+ }
228
+
229
+ function handleZigBuiltin(node: TreeSitterNode, ctx: ExtractorOutput): void {
230
+ const builtinId = findChild(node, 'builtin_identifier');
231
+ if (!builtinId) return;
232
+
233
+ // Treat @import as import (when standalone, not in variable_declaration)
234
+ if (builtinId.text === '@import' && node.parent?.type !== 'variable_declaration') {
235
+ const args = findChild(node, 'arguments');
236
+ if (args) {
237
+ const strArg = findChild(args, 'string_literal') || findChild(args, 'string');
238
+ if (strArg) {
239
+ const source = strArg.text.replace(/^"|"$/g, '');
240
+ ctx.imports.push({
241
+ source,
242
+ names: ['@import'],
243
+ line: node.startPosition.row + 1,
244
+ });
245
+ }
246
+ }
247
+ return;
248
+ }
249
+
250
+ // Other builtins are calls
251
+ ctx.calls.push({ name: builtinId.text, line: node.startPosition.row + 1 });
252
+ }
253
+
254
+ function handleZigTest(node: TreeSitterNode, ctx: ExtractorOutput): void {
255
+ let name = 'test';
256
+ for (let i = 0; i < node.childCount; i++) {
257
+ const child = node.child(i);
258
+ if (!child) continue;
259
+ if (child.type === 'string_literal' || child.type === 'string') {
260
+ // Extract the string content child if available, otherwise strip quotes
261
+ const content = findChild(child, 'string_content');
262
+ name = content ? content.text : child.text.replace(/^"|"$/g, '');
263
+ break;
264
+ }
265
+ if (child.type === 'identifier') {
266
+ name = child.text;
267
+ break;
268
+ }
269
+ }
270
+
271
+ ctx.definitions.push({
272
+ name,
273
+ kind: 'function',
274
+ line: node.startPosition.row + 1,
275
+ endLine: nodeEndLine(node),
276
+ });
277
+ }
278
+
279
+ function isZigPub(node: TreeSitterNode): boolean {
280
+ for (let i = 0; i < node.childCount; i++) {
281
+ const child = node.child(i);
282
+ if (child && child.type === 'pub') return true;
283
+ if (child && child.text === 'pub') return true;
284
+ }
285
+ return false;
286
+ }
287
+
288
+ function hasChildText(node: TreeSitterNode, text: string): boolean {
289
+ for (let i = 0; i < node.childCount; i++) {
290
+ const child = node.child(i);
291
+ if (child && child.text === text) return true;
292
+ }
293
+ return false;
294
+ }
@@ -80,6 +80,8 @@ export async function buildAstNodes(
80
80
  }>,
81
81
  ): number;
82
82
  };
83
+ suspendJsDb?: () => void;
84
+ resumeJsDb?: () => void;
83
85
  },
84
86
  ): Promise<void> {
85
87
  // ── Native bulk-insert fast path ──────────────────────────────────────
@@ -108,7 +110,7 @@ export async function buildAstNodes(
108
110
  kind: n.kind,
109
111
  name: n.name,
110
112
  text: n.text,
111
- receiver: n.receiver,
113
+ receiver: n.receiver ?? '',
112
114
  })),
113
115
  });
114
116
  } else if (symbols.calls || symbols._tree) {
@@ -119,7 +121,13 @@ export async function buildAstNodes(
119
121
 
120
122
  if (!needsJsFallback) {
121
123
  const expectedNodes = batches.reduce((s, b) => s + b.nodes.length, 0);
122
- const inserted = nativeDb.bulkInsertAstNodes(batches);
124
+ let inserted: number;
125
+ try {
126
+ engineOpts?.suspendJsDb?.();
127
+ inserted = nativeDb.bulkInsertAstNodes(batches);
128
+ } finally {
129
+ engineOpts?.resumeJsDb?.();
130
+ }
123
131
  if (inserted === expectedNodes) {
124
132
  debug(`AST extraction (native bulk): ${inserted} nodes stored`);
125
133
  return;
@@ -158,29 +166,6 @@ export async function buildAstNodes(
158
166
  nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
159
167
  }
160
168
 
161
- // When native astNodes includes call entries, skip separate symbols.calls processing
162
- // to avoid duplication. Fall back to symbols.calls for WASM or older native binaries.
163
- const nativeProvidedAstNodes = Array.isArray(symbols.astNodes);
164
- if (symbols.calls && !nativeProvidedAstNodes) {
165
- for (const call of symbols.calls) {
166
- const parentDef = findParentDef(defs, call.line);
167
- let parentNodeId: number | null = null;
168
- if (parentDef) {
169
- parentNodeId =
170
- nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
171
- }
172
- allRows.push({
173
- file: relPath,
174
- line: call.line,
175
- kind: 'call',
176
- name: call.name,
177
- text: call.dynamic ? `[dynamic] ${call.name}` : null,
178
- receiver: call.receiver || null,
179
- parentNodeId,
180
- });
181
- }
182
- }
183
-
184
169
  if (Array.isArray(symbols.astNodes)) {
185
170
  // Native engine provided AST nodes (may be empty for files with no AST content)
186
171
  for (const n of symbols.astNodes) {
@@ -183,27 +183,8 @@ export function auditData(
183
183
  let functions: unknown[];
184
184
  try {
185
185
  if (explained.kind === 'file') {
186
- // File target: explainData returns file-level info with publicApi + internal
187
- // We need to enrich each symbol
188
- functions = [];
189
- for (const fileResult of results) {
190
- const allSymbols = [
191
- ...(fileResult.publicApi || []),
192
- ...(fileResult.internal || []),
193
- ] as FileSymbol[];
194
- if (kind) {
195
- const filtered = allSymbols.filter((s) => s.kind === kind);
196
- for (const sym of filtered) {
197
- functions.push(enrichSymbol(db, sym, fileResult.file, noTests, maxDepth, thresholds));
198
- }
199
- } else {
200
- for (const sym of allSymbols) {
201
- functions.push(enrichSymbol(db, sym, fileResult.file, noTests, maxDepth, thresholds));
202
- }
203
- }
204
- }
186
+ functions = enrichFileResults(db, results, kind, noTests, maxDepth, thresholds);
205
187
  } else {
206
- // Function target: explainData returns per-function results
207
188
  functions = results.map((r: ExplainResult) =>
208
189
  enrichFunction(db, r, noTests, maxDepth, thresholds),
209
190
  );
@@ -232,6 +213,29 @@ interface ExplainResult {
232
213
  relatedTests?: { file: string }[];
233
214
  }
234
215
 
216
+ /** Enrich all symbols from file-target results. */
217
+ function enrichFileResults(
218
+ db: BetterSqlite3Database,
219
+ results: any[],
220
+ kind: string | undefined,
221
+ noTests: boolean,
222
+ maxDepth: number,
223
+ thresholds: Record<string, ThresholdEntry>,
224
+ ): unknown[] {
225
+ const functions: unknown[] = [];
226
+ for (const fileResult of results) {
227
+ let allSymbols = [
228
+ ...(fileResult.publicApi || []),
229
+ ...(fileResult.internal || []),
230
+ ] as FileSymbol[];
231
+ if (kind) allSymbols = allSymbols.filter((s) => s.kind === kind);
232
+ for (const sym of allSymbols) {
233
+ functions.push(enrichSymbol(db, sym, fileResult.file, noTests, maxDepth, thresholds));
234
+ }
235
+ }
236
+ return functions;
237
+ }
238
+
235
239
  function enrichFunction(
236
240
  db: BetterSqlite3Database,
237
241
  r: ExplainResult,