gitnexus 1.6.8-rc.8 → 1.6.8

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 (439) hide show
  1. package/README.md +3 -3
  2. package/dist/_shared/graph/types.d.ts +31 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/language-detection.d.ts.map +1 -1
  5. package/dist/_shared/language-detection.js +11 -1
  6. package/dist/_shared/language-detection.js.map +1 -1
  7. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  8. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  9. package/dist/_shared/lbug/schema-constants.js +6 -0
  10. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  11. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  12. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  13. package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
  14. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  15. package/dist/cli/ai-context.d.ts +31 -1
  16. package/dist/cli/ai-context.js +18 -16
  17. package/dist/cli/analyze-config.js +11 -0
  18. package/dist/cli/analyze.d.ts +14 -0
  19. package/dist/cli/analyze.js +98 -0
  20. package/dist/cli/embedding-dims.d.ts +33 -0
  21. package/dist/cli/embedding-dims.js +39 -0
  22. package/dist/cli/eval-server.d.ts +8 -0
  23. package/dist/cli/eval-server.js +242 -2
  24. package/dist/cli/help-i18n.js +19 -0
  25. package/dist/cli/i18n/en.d.ts +15 -1
  26. package/dist/cli/i18n/en.js +15 -1
  27. package/dist/cli/i18n/resources.d.ts +29 -1
  28. package/dist/cli/i18n/zh-CN.d.ts +14 -0
  29. package/dist/cli/i18n/zh-CN.js +15 -1
  30. package/dist/cli/index.js +89 -1
  31. package/dist/cli/mcp.d.ts +6 -1
  32. package/dist/cli/mcp.js +26 -1
  33. package/dist/cli/optional-grammars.d.ts +6 -8
  34. package/dist/cli/optional-grammars.js +8 -11
  35. package/dist/cli/setup.d.ts +3 -1
  36. package/dist/cli/setup.js +67 -17
  37. package/dist/cli/skill-gen.js +2 -1
  38. package/dist/cli/tool.d.ts +19 -1
  39. package/dist/cli/tool.js +96 -3
  40. package/dist/core/embeddings/hf-env.d.ts +0 -66
  41. package/dist/core/embeddings/http-client.d.ts +7 -0
  42. package/dist/core/embeddings/http-client.js +5 -3
  43. package/dist/core/embeddings/server-mapping.d.ts +1 -1
  44. package/dist/core/embeddings/server-mapping.js +7 -3
  45. package/dist/core/graph/import-cycles.d.ts +10 -0
  46. package/dist/core/graph/import-cycles.js +103 -0
  47. package/dist/core/group/cross-impact.d.ts +2 -1
  48. package/dist/core/group/cross-impact.js +5 -1
  49. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  50. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  51. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  52. package/dist/core/group/extractors/http-route-extractor.js +22 -4
  53. package/dist/core/group/extractors/include-extractor.js +22 -13
  54. package/dist/core/group/sync.d.ts +27 -1
  55. package/dist/core/group/sync.js +0 -0
  56. package/dist/core/incremental/subgraph-extract.js +18 -1
  57. package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
  58. package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
  59. package/dist/core/ingestion/cfg/collect.d.ts +51 -0
  60. package/dist/core/ingestion/cfg/collect.js +77 -0
  61. package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
  62. package/dist/core/ingestion/cfg/control-dependence.js +182 -0
  63. package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
  64. package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
  65. package/dist/core/ingestion/cfg/emit.d.ts +293 -0
  66. package/dist/core/ingestion/cfg/emit.js +646 -0
  67. package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
  68. package/dist/core/ingestion/cfg/post-dominators.js +176 -0
  69. package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
  70. package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
  71. package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
  72. package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
  73. package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
  74. package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
  75. package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
  76. package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
  77. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  78. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  79. package/dist/core/ingestion/cfg/types.d.ts +274 -0
  80. package/dist/core/ingestion/cfg/types.js +13 -0
  81. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
  82. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
  83. package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
  84. package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
  85. package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
  86. package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
  87. package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
  88. package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
  89. package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
  90. package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
  91. package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
  92. package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
  93. package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
  94. package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
  95. package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
  96. package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
  97. package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
  98. package/dist/core/ingestion/cfg/visitors/go.js +638 -0
  99. package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
  100. package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
  101. package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
  102. package/dist/core/ingestion/cfg/visitors/java.js +812 -0
  103. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
  104. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
  105. package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
  106. package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
  107. package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
  108. package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
  109. package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
  110. package/dist/core/ingestion/cfg/visitors/php.js +721 -0
  111. package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
  112. package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
  113. package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
  114. package/dist/core/ingestion/cfg/visitors/python.js +558 -0
  115. package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
  116. package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
  117. package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
  118. package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
  119. package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
  120. package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
  121. package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
  122. package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
  123. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
  124. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
  125. package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
  126. package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
  127. package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
  128. package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
  129. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
  130. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
  131. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  132. package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
  133. package/dist/core/ingestion/import-resolvers/utils.js +2 -0
  134. package/dist/core/ingestion/language-provider.d.ts +23 -2
  135. package/dist/core/ingestion/languages/c-cpp.js +17 -5
  136. package/dist/core/ingestion/languages/cobol.js +4 -0
  137. package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
  138. package/dist/core/ingestion/languages/cpp/captures.js +50 -1
  139. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
  140. package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
  141. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
  142. package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
  143. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
  144. package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
  145. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  146. package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
  147. package/dist/core/ingestion/languages/csharp.js +2 -0
  148. package/dist/core/ingestion/languages/dart.js +2 -0
  149. package/dist/core/ingestion/languages/go.js +2 -0
  150. package/dist/core/ingestion/languages/java.js +3 -0
  151. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  152. package/dist/core/ingestion/languages/kotlin.js +8 -1
  153. package/dist/core/ingestion/languages/php.js +2 -0
  154. package/dist/core/ingestion/languages/python.js +2 -0
  155. package/dist/core/ingestion/languages/ruby.js +2 -0
  156. package/dist/core/ingestion/languages/rust.js +2 -0
  157. package/dist/core/ingestion/languages/swift.js +2 -0
  158. package/dist/core/ingestion/languages/typescript.js +5 -0
  159. package/dist/core/ingestion/languages/vue.js +6 -0
  160. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  161. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  162. package/dist/core/ingestion/method-types.d.ts +2 -0
  163. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  164. package/dist/core/ingestion/model/symbol-table.js +1 -0
  165. package/dist/core/ingestion/mro-processor.js +109 -0
  166. package/dist/core/ingestion/parsing-processor.js +44 -0
  167. package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
  168. package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
  169. package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
  170. package/dist/core/ingestion/pipeline-phases/index.js +2 -0
  171. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
  172. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  173. package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
  174. package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
  175. package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
  176. package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
  177. package/dist/core/ingestion/pipeline.d.ts +92 -10
  178. package/dist/core/ingestion/pipeline.js +11 -2
  179. package/dist/core/ingestion/scope-extractor.js +12 -2
  180. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
  181. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
  182. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
  183. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
  184. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
  185. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
  186. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
  187. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
  188. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
  189. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
  190. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
  191. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
  192. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
  193. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
  194. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
  195. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  196. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
  197. package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
  198. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  199. package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
  200. package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
  201. package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
  202. package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
  203. package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
  204. package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
  205. package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
  206. package/dist/core/ingestion/taint/call-summary-model.js +25 -0
  207. package/dist/core/ingestion/taint/emit.d.ts +125 -0
  208. package/dist/core/ingestion/taint/emit.js +208 -0
  209. package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
  210. package/dist/core/ingestion/taint/interproc-emit.js +88 -0
  211. package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
  212. package/dist/core/ingestion/taint/interproc-solver.js +312 -0
  213. package/dist/core/ingestion/taint/match.d.ts +153 -0
  214. package/dist/core/ingestion/taint/match.js +278 -0
  215. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  216. package/dist/core/ingestion/taint/path-codec.js +190 -0
  217. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  218. package/dist/core/ingestion/taint/propagate.js +659 -0
  219. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  220. package/dist/core/ingestion/taint/site-safety.js +98 -0
  221. package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
  222. package/dist/core/ingestion/taint/source-sink-config.js +35 -12
  223. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  224. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  225. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
  226. package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
  227. package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
  228. package/dist/core/ingestion/taint/summary-harvest.js +537 -0
  229. package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
  230. package/dist/core/ingestion/taint/summary-model.js +86 -0
  231. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  232. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  233. package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
  234. package/dist/core/ingestion/utils/env.d.ts +7 -0
  235. package/dist/core/ingestion/utils/env.js +12 -0
  236. package/dist/core/ingestion/utils/method-props.js +1 -0
  237. package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
  238. package/dist/core/ingestion/workers/clone-safety.js +465 -0
  239. package/dist/core/ingestion/workers/parse-worker.d.ts +23 -0
  240. package/dist/core/ingestion/workers/parse-worker.js +81 -53
  241. package/dist/core/ingestion/workers/post-result.d.ts +22 -0
  242. package/dist/core/ingestion/workers/post-result.js +87 -0
  243. package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
  244. package/dist/core/ingestion/workers/result-merge.js +43 -0
  245. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -40
  246. package/dist/core/ingestion/workers/worker-pool.js +43 -16
  247. package/dist/core/lbug/csv-generator.d.ts +51 -3
  248. package/dist/core/lbug/csv-generator.js +369 -255
  249. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  250. package/dist/core/lbug/lbug-adapter.js +346 -84
  251. package/dist/core/lbug/lbug-config.d.ts +19 -0
  252. package/dist/core/lbug/lbug-config.js +29 -0
  253. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  254. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  255. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  256. package/dist/core/lbug/pool-adapter.js +90 -1
  257. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  258. package/dist/core/lbug/rel-pair-routing.js +140 -0
  259. package/dist/core/lbug/schema.d.ts +2 -2
  260. package/dist/core/lbug/schema.js +3 -0
  261. package/dist/core/logger.d.ts +0 -5
  262. package/dist/core/run-analyze.d.ts +96 -0
  263. package/dist/core/run-analyze.js +207 -3
  264. package/dist/core/tree-sitter/parser-loader.js +5 -4
  265. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  266. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  267. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  268. package/dist/core/wiki/generator.js +3 -1
  269. package/dist/core/wiki/graph-queries.d.ts +6 -0
  270. package/dist/core/wiki/graph-queries.js +9 -1
  271. package/dist/mcp/http-transport.d.ts +134 -0
  272. package/dist/mcp/http-transport.js +503 -0
  273. package/dist/mcp/local/local-backend.d.ts +110 -24
  274. package/dist/mcp/local/local-backend.js +1225 -19
  275. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  276. package/dist/mcp/local/pdg-impact.js +1972 -0
  277. package/dist/mcp/resources.js +13 -1
  278. package/dist/mcp/tools.d.ts +12 -0
  279. package/dist/mcp/tools.js +214 -7
  280. package/dist/server/analyze-launch.d.ts +29 -0
  281. package/dist/server/analyze-launch.js +137 -0
  282. package/dist/server/analyze-upload.d.ts +33 -0
  283. package/dist/server/analyze-upload.js +123 -0
  284. package/dist/server/analyze-worker-ipc.d.ts +58 -0
  285. package/dist/server/analyze-worker-ipc.js +16 -0
  286. package/dist/server/analyze-worker.d.ts +17 -1
  287. package/dist/server/analyze-worker.js +7 -1
  288. package/dist/server/api.d.ts +17 -0
  289. package/dist/server/api.js +132 -168
  290. package/dist/server/git-clone.d.ts +43 -2
  291. package/dist/server/git-clone.js +211 -16
  292. package/dist/server/mcp-http.d.ts +5 -6
  293. package/dist/server/mcp-http.js +9 -82
  294. package/dist/server/middleware.d.ts +40 -0
  295. package/dist/server/middleware.js +92 -0
  296. package/dist/server/private-ip.d.ts +1 -0
  297. package/dist/server/private-ip.js +15 -0
  298. package/dist/server/upload-ingest.d.ts +56 -0
  299. package/dist/server/upload-ingest.js +276 -0
  300. package/dist/server/upload-paths.d.ts +37 -0
  301. package/dist/server/upload-paths.js +57 -0
  302. package/dist/server/upload-sweep.d.ts +20 -0
  303. package/dist/server/upload-sweep.js +57 -0
  304. package/dist/storage/git.js +5 -8
  305. package/dist/storage/parse-cache.d.ts +22 -1
  306. package/dist/storage/parse-cache.js +44 -10
  307. package/dist/storage/repo-manager.d.ts +121 -10
  308. package/dist/storage/repo-manager.js +43 -5
  309. package/dist/types/pipeline.d.ts +9 -0
  310. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  311. package/hooks/claude/gitnexus-hook.cjs +110 -13
  312. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  313. package/package.json +8 -2
  314. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  315. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  316. package/scripts/cross-platform-tests.ts +1 -0
  317. package/skills/gitnexus-debugging.md +16 -4
  318. package/skills/gitnexus-exploring.md +3 -3
  319. package/skills/gitnexus-guide.md +33 -0
  320. package/skills/gitnexus-pdg-query.md +89 -0
  321. package/skills/gitnexus-refactoring.md +1 -1
  322. package/skills/gitnexus-taint-analysis.md +178 -0
  323. package/vendor/tree-sitter-c/package.json +1 -1
  324. package/vendor/tree-sitter-dart/package.json +1 -1
  325. package/vendor/tree-sitter-kotlin/package.json +1 -1
  326. package/vendor/tree-sitter-proto/package.json +1 -1
  327. package/vendor/tree-sitter-swift/package.json +1 -1
  328. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  329. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  330. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  331. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  332. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  333. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  334. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  335. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  336. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  337. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  338. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  339. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  340. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  341. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  342. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  343. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  344. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  345. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  346. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  347. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  348. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  349. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  350. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  351. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  352. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  353. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  354. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  355. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  356. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  357. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  358. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  359. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  360. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  361. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  362. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  363. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  364. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  365. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  366. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  367. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  368. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  369. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  370. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  371. package/web/assets/index-BvYev1UJ.js +635 -0
  372. package/web/assets/index-_lgn7hs5.css +2 -0
  373. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  374. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  375. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  376. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  377. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  378. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  379. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  380. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  381. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  382. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  383. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  384. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  385. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  386. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  387. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  388. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  389. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  390. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  391. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  392. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  393. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  394. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  395. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  396. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  397. package/web/index.html +13 -15
  398. package/scripts/materialize-vendor-grammars.cjs +0 -97
  399. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  400. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  401. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  402. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  403. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  404. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  405. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  406. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  407. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  408. package/web/assets/chunk-CilyBKbf.js +0 -1
  409. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  410. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  411. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  412. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  413. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  414. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  415. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  416. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  417. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  418. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  419. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  420. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  421. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  422. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  423. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  424. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  425. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  426. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  427. package/web/assets/index-BKWA-m7o.css +0 -2
  428. package/web/assets/index-CG6q8eTs.js +0 -626
  429. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  430. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  431. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  432. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  433. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  434. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  435. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  436. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  437. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  438. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  439. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,809 @@
1
+ import { CfgBuilder } from '../cfg-builder.js';
2
+ import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
3
+ import { KotlinHarvester } from './kotlin-harvest.js';
4
+ /** Kotlin node types that own a CFG-bearing function body. */
5
+ const KOTLIN_FUNCTION_TYPES = new Set([
6
+ 'function_declaration',
7
+ 'anonymous_function',
8
+ 'lambda_literal',
9
+ ]);
10
+ /** Statement node types that break a basic block (everything else coalesces). */
11
+ const CONTROL_FLOW_TYPES = new Set([
12
+ 'if_expression',
13
+ 'when_expression',
14
+ 'for_statement',
15
+ 'while_statement',
16
+ 'do_while_statement',
17
+ 'try_expression',
18
+ 'jump_expression',
19
+ 'label',
20
+ ]);
21
+ /** Comment / non-code node types tree-sitter-kotlin surfaces (NOT `comment`). */
22
+ const COMMENT_TYPES = new Set(['line_comment', 'multiline_comment', 'shebang_line']);
23
+ const startLineOf = (n) => n.startPosition.row + 1;
24
+ const endLineOf = (n) => n.endPosition.row + 1;
25
+ const isComment = (n) => COMMENT_TYPES.has(n.type);
26
+ /**
27
+ * Per-function Kotlin walk state. One instance per function so the
28
+ * {@link ControlFlowContext}, exception-handler stack, and labeled-frame
29
+ * bookkeeping are scoped to that function and never leak across functions.
30
+ */
31
+ class KotlinCfgWalk {
32
+ builder;
33
+ harvest;
34
+ cfc = new ControlFlowContext();
35
+ /** Stack of exception-handler entry blocks (catch/finally) a `throw` jumps to. */
36
+ handlers = [];
37
+ /** Label(s) pending attachment to the NEXT pushed loop frame. */
38
+ pendingLabels = [];
39
+ constructor(builder, harvest) {
40
+ this.builder = builder;
41
+ this.harvest = harvest;
42
+ }
43
+ /** Named statements of a `statements` node, ignoring comments. */
44
+ statementsOf(block) {
45
+ return block.namedChildren.filter((c) => !isComment(c));
46
+ }
47
+ /**
48
+ * Unwrap a `control_structure_body`: a `{ statements }` block yields its
49
+ * `statements` node; a bare single statement (`if (c) a()`) yields itself.
50
+ */
51
+ bodyOf(csb) {
52
+ if (!csb)
53
+ return undefined;
54
+ if (csb.type === 'control_structure_body') {
55
+ const stmts = csb.namedChildren.find((c) => c.type === 'statements');
56
+ if (stmts)
57
+ return stmts;
58
+ const single = csb.namedChildren.find((c) => !isComment(c));
59
+ return single;
60
+ }
61
+ return csb;
62
+ }
63
+ /** Visit a `control_structure_body` (block or single statement). */
64
+ visitBody(csb) {
65
+ return this.builder.withNesting(() => {
66
+ const inner = this.bodyOf(csb);
67
+ if (!inner)
68
+ return null;
69
+ if (inner.type === 'statements')
70
+ return this.visitSeq(this.statementsOf(inner));
71
+ return this.visitStmt(inner);
72
+ });
73
+ }
74
+ /** Wire a sequence of statements, coalescing straight-line runs into blocks. */
75
+ visitSeq(stmts) {
76
+ return this.builder.withNesting(() => {
77
+ let entry;
78
+ let dangling = [];
79
+ let openSimple;
80
+ for (const stmt of stmts) {
81
+ if (this.isControlFlow(stmt)) {
82
+ openSimple = undefined; // close any open straight-line block
83
+ const res = this.visitStmt(stmt);
84
+ if (res === null)
85
+ continue; // transparent (empty nested block / label-only)
86
+ if (entry === undefined)
87
+ entry = res.entry;
88
+ else
89
+ this.builder.connect(dangling, res.entry, 'seq');
90
+ dangling = [...res.exits];
91
+ }
92
+ else {
93
+ if (openSimple === undefined) {
94
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
95
+ if (entry === undefined)
96
+ entry = idx;
97
+ else
98
+ this.builder.connect(dangling, idx, 'seq');
99
+ openSimple = idx;
100
+ dangling = [idx];
101
+ }
102
+ else {
103
+ this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
104
+ }
105
+ }
106
+ }
107
+ if (entry === undefined)
108
+ return null;
109
+ return { entry, exits: dangling };
110
+ });
111
+ }
112
+ /**
113
+ * Whether a statement breaks the current straight-line block. `if` / `when` /
114
+ * `try` are EXPRESSIONS in Kotlin — they break a block when used as a STATEMENT
115
+ * (a direct child of a `statements` list), OR when they are the value of a
116
+ * `val/var x = <branch>` binding or an `x = <branch>` assignment (#2205) —
117
+ * `visitStmt`'s `property_declaration` / `assignment` case then models the arms
118
+ * as control flow. A call argument value position still coalesces (a remaining
119
+ * gap — the branch is nested in a call, harder to bind).
120
+ */
121
+ isControlFlow(stmt) {
122
+ if (stmt.type === 'label')
123
+ return true; // queue label, emit no block
124
+ if (stmt.type === 'property_declaration') {
125
+ const v = this.directValue(stmt);
126
+ return v !== undefined && this.isModelableValueBranch(v);
127
+ }
128
+ if (stmt.type === 'assignment')
129
+ return this.assignmentBranch(stmt) !== undefined;
130
+ if (!CONTROL_FLOW_TYPES.has(stmt.type))
131
+ return false;
132
+ if (this.isExpressionConstruct(stmt.type))
133
+ return this.isStatementPosition(stmt);
134
+ return true;
135
+ }
136
+ isExpressionConstruct(type) {
137
+ return type === 'if_expression' || type === 'when_expression' || type === 'try_expression';
138
+ }
139
+ /**
140
+ * Whether an expression-construct (`if`/`when`/`try`) is in STATEMENT position
141
+ * (a direct `statements` child, or a `control_structure_body` that is itself a
142
+ * bare statement) vs an expression VALUE (nested under a declaration / jump /
143
+ * assignment / argument). Statement-position constructs are modeled as
144
+ * dispatch/branch; value-position ones stay inline.
145
+ */
146
+ isStatementPosition(node) {
147
+ const p = node.parent;
148
+ if (!p)
149
+ return false;
150
+ return p.type === 'statements' || p.type === 'control_structure_body';
151
+ }
152
+ /** Dispatch one statement to its handler. Non-null except for empty / label-only. */
153
+ visitStmt(stmt) {
154
+ if (stmt.type === 'label') {
155
+ // A label preceding its loop — queue it; the loop construct picks it up.
156
+ const name = this.labelName(stmt);
157
+ if (name !== undefined)
158
+ this.pendingLabels = [...this.pendingLabels, name];
159
+ return null; // emits no block of its own
160
+ }
161
+ switch (stmt.type) {
162
+ case 'if_expression':
163
+ return this.isStatementPosition(stmt) ? this.visitIf(stmt) : this.visitSimple(stmt);
164
+ case 'when_expression':
165
+ return this.isStatementPosition(stmt) ? this.visitWhen(stmt) : this.visitSimple(stmt);
166
+ case 'try_expression':
167
+ return this.isStatementPosition(stmt) ? this.visitTry(stmt) : this.visitSimple(stmt);
168
+ case 'for_statement':
169
+ return this.visitFor(stmt);
170
+ case 'while_statement':
171
+ return this.visitWhile(stmt);
172
+ case 'do_while_statement':
173
+ return this.visitDoWhile(stmt);
174
+ case 'jump_expression':
175
+ return this.visitJump(stmt);
176
+ case 'property_declaration': {
177
+ // `val x = when (k) { … }` / `val x = if (c) a else b` (#2205): the value
178
+ // is a value-position branch — model it as control flow and bind the
179
+ // result on the rejoin, instead of collapsing the whole decl to one block.
180
+ const value = this.directValue(stmt);
181
+ if (value && this.isModelableValueBranch(value))
182
+ return this.visitBindBranch(stmt, value);
183
+ return this.visitSimple(stmt);
184
+ }
185
+ case 'assignment': {
186
+ // `x = when (k) { … }` / `x = if (c) a else b` / `x = try { … }` (#2205):
187
+ // model the RHS branch as control flow and bind the target on the rejoin.
188
+ const branch = this.assignmentBranch(stmt);
189
+ if (branch)
190
+ return this.visitBindAssign(stmt, branch);
191
+ return this.visitSimple(stmt);
192
+ }
193
+ default:
194
+ return this.visitSimple(stmt);
195
+ }
196
+ }
197
+ visitSimple(stmt) {
198
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
199
+ return { entry: idx, exits: [idx] };
200
+ }
201
+ // ── jump expressions (return / throw / break / continue) ──────────────────
202
+ /** The leading anonymous keyword of a `jump_expression` decides its kind. */
203
+ jumpKeyword(stmt) {
204
+ const first = stmt.child(0);
205
+ return first?.text ?? '';
206
+ }
207
+ visitJump(stmt) {
208
+ const kw = this.jumpKeyword(stmt);
209
+ if (kw === 'return' || kw === 'return@')
210
+ return this.visitReturn(stmt);
211
+ if (kw === 'throw')
212
+ return this.visitThrow(stmt);
213
+ if (kw === 'break' || kw === 'break@')
214
+ return this.visitBreak(stmt);
215
+ if (kw === 'continue' || kw === 'continue@')
216
+ return this.visitContinue(stmt);
217
+ // Unknown jump — straight through (defensive; the grammar emits only the above).
218
+ return this.visitSimple(stmt);
219
+ }
220
+ /** `return [expr]` / `return@label` — threads through every active finalizer. */
221
+ visitReturn(stmt) {
222
+ // `return when (k) { … }` / `return if (c) a else b` / `return try { … }`
223
+ // (#2205): the returned value is a value-position branch — model it as control
224
+ // flow, with each arm returning (its value IS the function result), threading
225
+ // finalizers per arm.
226
+ const branch = stmt.namedChildren.find((c) => c.type === 'when_expression' || c.type === 'if_expression' || c.type === 'try_expression');
227
+ if (branch && this.isModelableValueBranch(branch)) {
228
+ const res = this.visitBranchExpr(branch);
229
+ const finalizers = this.cfc.finalizersForReturn();
230
+ for (const ex of res.exits) {
231
+ wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
232
+ }
233
+ return { entry: res.entry, exits: [] };
234
+ }
235
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
236
+ wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
237
+ return { entry: idx, exits: [] };
238
+ }
239
+ /** `throw e` — routes to the nearest enclosing handler (catch/finally), else EXIT. */
240
+ visitThrow(stmt) {
241
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
242
+ this.builder.edge(idx, this.currentHandler(), 'throw');
243
+ return { entry: idx, exits: [] };
244
+ }
245
+ visitBreak(stmt) {
246
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
247
+ const label = this.jumpLabel(stmt);
248
+ const res = this.cfc.resolveBreak(label);
249
+ const { target, finalizers } = res ?? {
250
+ target: this.builder.exitIndex,
251
+ finalizers: this.cfc.finalizersForReturn(),
252
+ };
253
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
254
+ return { entry: idx, exits: [] };
255
+ }
256
+ visitContinue(stmt) {
257
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
258
+ const label = this.jumpLabel(stmt);
259
+ const res = this.cfc.resolveContinue(label);
260
+ const { target, finalizers } = res ?? {
261
+ target: this.builder.exitIndex,
262
+ finalizers: this.cfc.finalizersForReturn(),
263
+ };
264
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
265
+ return { entry: idx, exits: [] };
266
+ }
267
+ /** The target `label` of a `break@outer` / `continue@outer` / `return@x`, if any. */
268
+ jumpLabel(stmt) {
269
+ const label = stmt.namedChildren.find((c) => c.type === 'label');
270
+ return label ? this.stripLabel(label.text) : undefined;
271
+ }
272
+ /** The name of a `label` sibling (`outer@` ⇒ `outer`; jump target `outer` ⇒ `outer`). */
273
+ labelName(label) {
274
+ const id = label.namedChildren.find((c) => c.type === 'simple_identifier');
275
+ if (id?.text)
276
+ return this.stripLabel(id.text);
277
+ return this.stripLabel(label.text) || undefined;
278
+ }
279
+ stripLabel(text) {
280
+ return text.replace(/@$/, '').replace(/^@/, '').trim();
281
+ }
282
+ /** Take and clear the labels queued by a preceding `label` sibling. */
283
+ takeLabels() {
284
+ const labels = this.pendingLabels;
285
+ this.pendingLabels = [];
286
+ return labels;
287
+ }
288
+ // ── branches ──────────────────────────────────────────────────────────────
289
+ /**
290
+ * The direct value expression of a `= VALUE` carrier (`property_declaration`,
291
+ * a `function_body` expression body): the first named child after the `=`
292
+ * token. Returns the DIRECT value only — `val x = f(when …)` yields the call,
293
+ * not the nested `when`, so an argument-position branch is left inline (#2205).
294
+ */
295
+ directValue(stmt) {
296
+ let sawEq = false;
297
+ for (let i = 0; i < stmt.childCount; i++) {
298
+ const c = stmt.child(i);
299
+ if (!c)
300
+ continue;
301
+ if (c.type === '=') {
302
+ sawEq = true;
303
+ continue;
304
+ }
305
+ if (sawEq && c.isNamed && !isComment(c))
306
+ return c;
307
+ }
308
+ return undefined;
309
+ }
310
+ /**
311
+ * Whether `node` is a value-position branch worth modeling as control flow
312
+ * (#2205): a `when` with ≥2 arms, or an `if` that has an `else` (a value-position
313
+ * `if` always does). A single-arm `when` / else-less `if` carries no real
314
+ * control dependence, so it stays an inline {@link visitSimple} block.
315
+ */
316
+ isModelableValueBranch(node) {
317
+ if (node.type === 'when_expression') {
318
+ return node.namedChildren.filter((c) => c.type === 'when_entry').length >= 2;
319
+ }
320
+ if (node.type === 'if_expression')
321
+ return this.elseNodeOf(node) !== undefined;
322
+ // `val x = try { … } catch { … }` / `try { … } finally { … }` (#2205): a
323
+ // value-position `try` with a `catch` OR a `finally` is a real branch — its
324
+ // value is the body's value, a catch's value, or the body's value threaded
325
+ // through a finalizer — so model it as control flow.
326
+ if (node.type === 'try_expression') {
327
+ return node.namedChildren.some((c) => c.type === 'catch_block' || c.type === 'finally_block');
328
+ }
329
+ return false;
330
+ }
331
+ /**
332
+ * Model a value-position `when`/`if`/`try` as control flow regardless of its
333
+ * statement/value position — {@link visitStmt}'s `isStatementPosition` gate keeps
334
+ * value-position branches inline, so call the branch handlers directly here.
335
+ */
336
+ visitBranchExpr(node) {
337
+ if (node.type === 'when_expression')
338
+ return this.visitWhen(node);
339
+ if (node.type === 'try_expression')
340
+ return this.visitTry(node) ?? this.visitSimple(node);
341
+ return this.visitIf(node);
342
+ }
343
+ /**
344
+ * `val x = <branch>` (#2205): visit the branch as control flow, then rejoin its
345
+ * arms at a facts-only continuation carrying ONLY the bound name's def (the
346
+ * subject + arm-value uses are already harvested onto the branch's blocks). The
347
+ * arms are now control-dependent on the branch condition, and `x` is defined at
348
+ * the join — mirrors the Rust visitor's value-position `let` handling.
349
+ */
350
+ visitBindBranch(stmt, branch) {
351
+ const res = this.visitBranchExpr(branch);
352
+ const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.bindingDefFacts(stmt));
353
+ this.builder.connect(res.exits, cont, 'seq');
354
+ return { entry: res.entry, exits: [cont] };
355
+ }
356
+ /**
357
+ * The value-position branch on a plain `=` assignment RHS (`x = when (k) {…}` /
358
+ * `x = if (c) a else b` / `x = try {…}`, #2205), or undefined. Only a plain `=`
359
+ * (not a compound `+=`) with a modelable-branch RHS qualifies.
360
+ */
361
+ assignmentBranch(stmt) {
362
+ if (stmt.type !== 'assignment')
363
+ return undefined;
364
+ const eq = stmt.children.find((c) => !c.isNamed && c.text === '=');
365
+ if (!eq)
366
+ return undefined; // compound assignment (`+=` etc.) is not a carrier
367
+ const rhs = stmt.namedChildren.find((c) => c.type !== 'directly_assignable_expression' && !isComment(c));
368
+ return rhs && this.isModelableValueBranch(rhs) ? rhs : undefined;
369
+ }
370
+ /**
371
+ * `x = <branch>` (#2205): visit the RHS branch as control flow, then rejoin its
372
+ * arms at a facts-only continuation carrying ONLY the LHS target def (the branch
373
+ * subject + arm-value uses are already on the branch's blocks). The arms are now
374
+ * control-dependent on the branch — mirrors the Ruby value-branch assignment.
375
+ */
376
+ visitBindAssign(stmt, branch) {
377
+ const res = this.visitBranchExpr(branch);
378
+ const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.assignmentDefFacts(stmt));
379
+ this.builder.connect(res.exits, cont, 'seq');
380
+ return { entry: res.entry, exits: [cont] };
381
+ }
382
+ /**
383
+ * A `fun f() = EXPR` expression body (#2205). A value-position branch is modeled
384
+ * as control flow (each arm yields the returned function result); any other
385
+ * expression stays one block. The caller wires entry ← ENTRY and exits → EXIT
386
+ * with a `return` edge (the body's value is the function's result).
387
+ */
388
+ visitExprBody(expr) {
389
+ if (this.isModelableValueBranch(expr))
390
+ return this.visitBranchExpr(expr);
391
+ const blk = this.builder.newBlock(startLineOf(expr), endLineOf(expr), expr.text, 'normal', this.harvest.facts(expr));
392
+ return { entry: blk, exits: [blk] };
393
+ }
394
+ /**
395
+ * `if ( COND ) control_structure_body [ else (control_structure_body |
396
+ * if_expression) ]`. The else child after the `else` keyword is either the
397
+ * else body (`control_structure_body`) or a nested `if_expression` (`else if`).
398
+ */
399
+ visitIf(stmt) {
400
+ const cond = this.parenCondition(stmt);
401
+ const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? `if (${cond.text})` : 'if', 'normal', cond ? this.harvest.facts(cond) : undefined);
402
+ const bodies = stmt.namedChildren.filter((c) => c.type === 'control_structure_body');
403
+ const elseNode = this.elseNodeOf(stmt);
404
+ const exits = [];
405
+ const thenRes = this.visitBody(bodies[0]);
406
+ if (thenRes) {
407
+ this.builder.edge(header, thenRes.entry, 'cond-true');
408
+ exits.push(...thenRes.exits);
409
+ }
410
+ else {
411
+ exits.push(header); // empty then — true path falls through
412
+ }
413
+ if (elseNode) {
414
+ const elseRes = this.visitBody(elseNode);
415
+ if (elseRes) {
416
+ this.builder.edge(header, elseRes.entry, 'cond-false');
417
+ exits.push(...elseRes.exits);
418
+ }
419
+ else {
420
+ exits.push(header);
421
+ }
422
+ }
423
+ else {
424
+ exits.push(header); // no else — false path falls through to the join
425
+ }
426
+ return { entry: header, exits: [...new Set(exits)] };
427
+ }
428
+ /**
429
+ * The node after the `else` keyword: a nested `if_expression` (`else if`) or the
430
+ * else-body `control_structure_body`.
431
+ */
432
+ elseNodeOf(stmt) {
433
+ let sawElse = false;
434
+ for (let i = 0; i < stmt.childCount; i++) {
435
+ const c = stmt.child(i);
436
+ if (!c)
437
+ continue;
438
+ if (sawElse && c.isNamed)
439
+ return c;
440
+ if (c.type === 'else')
441
+ sawElse = true;
442
+ }
443
+ return undefined;
444
+ }
445
+ /** The condition expression of an `if`/`while` (the named child between `(` and `)`). */
446
+ parenCondition(stmt) {
447
+ let sawOpen = false;
448
+ for (let i = 0; i < stmt.childCount; i++) {
449
+ const c = stmt.child(i);
450
+ if (!c)
451
+ continue;
452
+ if (c.type === '(') {
453
+ sawOpen = true;
454
+ continue;
455
+ }
456
+ if (c.type === ')')
457
+ return undefined;
458
+ if (sawOpen && c.isNamed && !isComment(c))
459
+ return c;
460
+ }
461
+ return undefined;
462
+ }
463
+ // ── when (no fallthrough) ──────────────────────────────────────────────────
464
+ /**
465
+ * `when when_subject? { when_entry* }`. Arms do NOT fall through — each
466
+ * `when_entry` body rejoins after the `when`. The subject (and each entry's
467
+ * `when_condition` tests) evaluate before the body; their uses are harvested
468
+ * onto the dispatch block (a later entry's test runs only when earlier ones
469
+ * didn't match, so any binding there is a may-def).
470
+ */
471
+ visitWhen(stmt) {
472
+ const labels = this.takeLabels();
473
+ const subject = stmt.namedChildren.find((c) => c.type === 'when_subject');
474
+ const dispatch = this.builder.newBlock(startLineOf(stmt), subject ? endLineOf(subject) : startLineOf(stmt), subject ? `when ${subject.text}` : 'when', 'normal', subject ? this.harvest.whenSubjectFacts(subject) : undefined);
475
+ const whenExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
476
+ this.cfc.pushSwitch(whenExit, labels);
477
+ const entries = stmt.namedChildren.filter((c) => c.type === 'when_entry');
478
+ // Each entry's case tests evaluate conditionally before its body.
479
+ for (const entry of entries) {
480
+ for (const test of this.entryConditions(entry)) {
481
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(test));
482
+ }
483
+ }
484
+ const entryResults = entries.map((e) => this.visitBody(this.entryBody(e)));
485
+ const hasElse = entries.some((e) => this.entryIsElse(e));
486
+ for (const res of entryResults) {
487
+ // An EMPTY-body arm still dispatches — it falls straight to the join. Wiring
488
+ // it (rather than skipping) keeps the dispatch from ending up with ZERO
489
+ // successors, which would orphan whenExit and break EXIT reverse-reachability
490
+ // (so the whole function's CDG gets dropped). The canonical trigger is an
491
+ // all-empty `when` with an `else` arm — `when(k){0->{};else->{}}` — where the
492
+ // no-match edge below is suppressed. The builder dedups, so this coexists
493
+ // with the no-match edge.
494
+ this.builder.edge(dispatch, res ? res.entry : whenExit, 'switch-case');
495
+ }
496
+ // A `when` with no `else` (statement position) may match no arm — the no-match
497
+ // path falls straight to the join.
498
+ if (!hasElse)
499
+ this.builder.edge(dispatch, whenExit, 'switch-case');
500
+ const exits = [whenExit];
501
+ // Each non-empty arm rejoins after the when (no fallthrough); an empty arm's
502
+ // dispatch edge already targets whenExit above.
503
+ for (const res of entryResults) {
504
+ if (!res)
505
+ continue;
506
+ this.builder.connect(res.exits, whenExit, 'seq');
507
+ }
508
+ this.cfc.pop();
509
+ return { entry: dispatch, exits };
510
+ }
511
+ /** The `when_condition` test(s) of a `when_entry` (empty for an `else` arm). */
512
+ entryConditions(entry) {
513
+ return entry.namedChildren.filter((c) => c.type === 'when_condition');
514
+ }
515
+ /** The body `control_structure_body` of a `when_entry`. */
516
+ entryBody(entry) {
517
+ return entry.namedChildren.find((c) => c.type === 'control_structure_body');
518
+ }
519
+ /** An `else ->` arm has an `else` keyword child and no `when_condition`. */
520
+ entryIsElse(entry) {
521
+ return entry.children.some((c) => c.type === 'else');
522
+ }
523
+ // ── loops ───────────────────────────────────────────────────────────────
524
+ /** `for ( PAT in COLLECTION ) control_structure_body`. */
525
+ visitFor(stmt) {
526
+ const labels = this.takeLabels();
527
+ const collection = this.forCollection(stmt);
528
+ const header = this.builder.newBlock(startLineOf(stmt), collection ? endLineOf(collection) : startLineOf(stmt), this.forHeaderText(stmt), 'normal', this.harvest.forHeadFacts(stmt));
529
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
530
+ this.cfc.pushLoop(header, loopExit, labels);
531
+ const body = this.visitBody(this.loopBody(stmt));
532
+ this.cfc.pop();
533
+ if (body) {
534
+ this.builder.edge(header, body.entry, 'cond-true');
535
+ this.builder.connect(body.exits, header, 'loop-back');
536
+ }
537
+ else {
538
+ this.builder.edge(header, header, 'loop-back'); // empty body re-iterates
539
+ }
540
+ this.builder.edge(header, loopExit, 'cond-false');
541
+ return { entry: header, exits: [loopExit] };
542
+ }
543
+ /** The iterated collection of a `for` — the named child after `in` before `)`. */
544
+ forCollection(stmt) {
545
+ let sawIn = false;
546
+ for (let i = 0; i < stmt.childCount; i++) {
547
+ const c = stmt.child(i);
548
+ if (!c)
549
+ continue;
550
+ if (c.type === 'in') {
551
+ sawIn = true;
552
+ continue;
553
+ }
554
+ if (c.type === ')')
555
+ return undefined;
556
+ if (sawIn && c.isNamed && !isComment(c))
557
+ return c;
558
+ }
559
+ return undefined;
560
+ }
561
+ forHeaderText(stmt) {
562
+ const pat = stmt.namedChildren.find((c) => c.type === 'variable_declaration' || c.type === 'multi_variable_declaration');
563
+ const collection = this.forCollection(stmt);
564
+ const p = pat?.text ?? '';
565
+ const col = collection?.text ?? '';
566
+ return p || col ? `for (${p} in ${col})` : 'for';
567
+ }
568
+ /** The loop body `control_structure_body` (the LAST one — for/while/do). */
569
+ loopBody(stmt) {
570
+ const all = stmt.namedChildren.filter((c) => c.type === 'control_structure_body');
571
+ return all.length ? all[all.length - 1] : undefined;
572
+ }
573
+ /** `while ( COND ) control_structure_body`. */
574
+ visitWhile(stmt) {
575
+ const labels = this.takeLabels();
576
+ const cond = this.parenCondition(stmt);
577
+ const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? `while (${cond.text})` : 'while', 'normal', cond ? this.harvest.facts(cond) : undefined);
578
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
579
+ this.cfc.pushLoop(header, loopExit, labels);
580
+ const body = this.visitBody(this.loopBody(stmt));
581
+ this.cfc.pop();
582
+ if (body) {
583
+ this.builder.edge(header, body.entry, 'cond-true');
584
+ this.builder.connect(body.exits, header, 'loop-back');
585
+ }
586
+ else {
587
+ this.builder.edge(header, header, 'loop-back'); // empty body re-tests
588
+ }
589
+ // Structural exit edge — even `while (true) {}` keeps EXIT reverse-reachable.
590
+ this.builder.edge(header, loopExit, 'cond-false');
591
+ return { entry: header, exits: [loopExit] };
592
+ }
593
+ /**
594
+ * `do control_structure_body while ( COND )` — BOTTOM-TEST: the body runs at
595
+ * least once, THEN the condition decides whether to loop back.
596
+ */
597
+ visitDoWhile(stmt) {
598
+ const labels = this.takeLabels();
599
+ const cond = this.doWhileCondition(stmt);
600
+ const condBlock = this.builder.newBlock(cond ? startLineOf(cond) : endLineOf(stmt), cond ? endLineOf(cond) : endLineOf(stmt), cond ? `while (${cond.text})` : 'while', 'normal', cond ? this.harvest.facts(cond) : undefined);
601
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
602
+ // `continue` re-tests the condition; `break` leaves the loop.
603
+ this.cfc.pushLoop(condBlock, loopExit, labels);
604
+ const body = this.visitBody(this.loopBody(stmt));
605
+ this.cfc.pop();
606
+ const backTarget = body ? body.entry : condBlock;
607
+ if (body)
608
+ this.builder.connect(body.exits, condBlock, 'seq');
609
+ this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
610
+ // Structural exit edge — even `do {} while (true)` keeps EXIT reachable.
611
+ this.builder.edge(condBlock, loopExit, 'cond-false');
612
+ return { entry: backTarget, exits: [loopExit] };
613
+ }
614
+ /** The condition of a `do … while ( COND )` — the named child after the trailing `while`. */
615
+ doWhileCondition(stmt) {
616
+ let sawWhile = false;
617
+ for (let i = 0; i < stmt.childCount; i++) {
618
+ const c = stmt.child(i);
619
+ if (!c)
620
+ continue;
621
+ if (c.type === 'while') {
622
+ sawWhile = true;
623
+ continue;
624
+ }
625
+ if (sawWhile && c.type === ')')
626
+ return undefined;
627
+ if (sawWhile && c.isNamed && !isComment(c))
628
+ return c;
629
+ }
630
+ return undefined;
631
+ }
632
+ // ── try / catch / finally ──────────────────────────────────────────────────
633
+ /**
634
+ * `try { statements } catch_block* finally_block?`. The `finally` runs on BOTH
635
+ * normal and exception exit (finally semantics) — a `return`/`break`/`continue`
636
+ * crossing it threads through and gets a `finally-*` completion edge. Mirrors
637
+ * the Java `visitTry`.
638
+ */
639
+ visitTry(stmt) {
640
+ const bodyNode = stmt.namedChildren.find((c) => c.type === 'statements');
641
+ const catchBlocks = stmt.namedChildren.filter((c) => c.type === 'catch_block');
642
+ const finallyBlock = stmt.namedChildren.find((c) => c.type === 'finally_block');
643
+ const finallyBody = finallyBlock?.namedChildren.find((c) => c.type === 'statements');
644
+ // The explicit finally is a finalizer the whole protected region threads through.
645
+ const finallyRes = finallyBody ? this.visitSeq(this.statementsOf(finallyBody)) : null;
646
+ const finallyFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
647
+ const finalizerEntry = finallyRes?.entry;
648
+ const finalizerExits = finallyRes?.exits ?? null;
649
+ // Build each catch handler.
650
+ const catchEntries = [];
651
+ const catchExits = [];
652
+ let firstCatchEntry;
653
+ for (const clause of catchBlocks) {
654
+ const clauseBody = clause.namedChildren.find((c) => c.type === 'statements');
655
+ if (finalizerEntry !== undefined)
656
+ this.handlers.push(finalizerEntry);
657
+ let res = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
658
+ if (finalizerEntry !== undefined)
659
+ this.handlers.pop();
660
+ if (res === null) {
661
+ // Empty `catch {}` still catches — synthesize one block so exception flow
662
+ // lands somewhere and post-try code stays reachable.
663
+ const idx = this.builder.newBlock(startLineOf(clause), endLineOf(clause), '');
664
+ res = { entry: idx, exits: [idx] };
665
+ }
666
+ const paramFacts = this.harvest.catchParamFacts(clause);
667
+ if (paramFacts) {
668
+ const paramBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', paramFacts);
669
+ this.builder.edge(paramBlock, res.entry, 'seq');
670
+ res = { entry: paramBlock, exits: res.exits };
671
+ }
672
+ catchEntries.push(res.entry);
673
+ catchExits.push(...res.exits);
674
+ if (firstCatchEntry === undefined)
675
+ firstCatchEntry = res.entry;
676
+ }
677
+ // Handler for the try body: first catch if present, else the finally, else outer.
678
+ const tryHandler = firstCatchEntry ?? finalizerEntry ?? this.currentHandler();
679
+ const protectedStart = this.builder.blockCount;
680
+ this.handlers.push(tryHandler);
681
+ let bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
682
+ this.handlers.pop();
683
+ if (bodyRes === null && (catchBlocks.length > 0 || finalizerEntry !== undefined)) {
684
+ // An empty `try {}` body still establishes a protected region. Synthesize
685
+ // one block (like the empty-`catch` case above) so the throw-edge loop
686
+ // wires the catch handler(s) and the try's entry is the body — otherwise
687
+ // the catch handler block + its error binding are orphaned (unreachable
688
+ // from ENTRY) and control routes straight to the finally, bypassing catch.
689
+ const idx = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '');
690
+ bodyRes = { entry: idx, exits: [idx] };
691
+ }
692
+ if (catchBlocks.length > 0 || finalizerEntry !== undefined) {
693
+ for (let b = protectedStart; b < this.builder.blockCount; b++) {
694
+ this.builder.edge(b, tryHandler, 'throw');
695
+ }
696
+ }
697
+ // Pop the finalizer frame and drain its pending completion legs.
698
+ if (finallyFrame && finallyRes) {
699
+ this.cfc.pop();
700
+ drainFinalizerPending(this.builder, finallyFrame, finallyRes.exits);
701
+ }
702
+ const exits = [];
703
+ if (finalizerEntry !== undefined) {
704
+ if (bodyRes)
705
+ this.builder.connect(bodyRes.exits, finalizerEntry, 'seq');
706
+ for (const e of catchExits)
707
+ this.builder.edge(e, finalizerEntry, 'seq');
708
+ if (finalizerExits)
709
+ exits.push(...finalizerExits);
710
+ // No catch → an exception re-propagates out after the finally runs.
711
+ if (catchBlocks.length === 0 && finalizerExits) {
712
+ this.builder.connect(finalizerExits, this.currentHandler(), 'throw');
713
+ }
714
+ }
715
+ else {
716
+ if (bodyRes)
717
+ exits.push(...bodyRes.exits);
718
+ exits.push(...catchExits);
719
+ }
720
+ const entry = bodyRes?.entry ?? finalizerEntry ?? catchEntries[0];
721
+ if (entry === undefined)
722
+ return null;
723
+ return { entry, exits: [...new Set(exits)] };
724
+ }
725
+ /** Nearest enclosing exception handler, or the function EXIT. */
726
+ currentHandler() {
727
+ return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
728
+ }
729
+ }
730
+ /** The function/lambda body `statements`, or an expression body, or undefined. */
731
+ function bodyStatementsOf(fnNode) {
732
+ if (fnNode.type === 'lambda_literal') {
733
+ return fnNode.namedChildren.find((c) => c.type === 'statements');
734
+ }
735
+ const fb = fnNode.namedChildren.find((c) => c.type === 'function_body');
736
+ if (!fb)
737
+ return undefined;
738
+ // `function_body` is `{ statements }` OR an expression body `= expr`.
739
+ const stmts = fb.namedChildren.find((c) => c.type === 'statements');
740
+ if (stmts)
741
+ return stmts;
742
+ // Expression body — return the body itself so the caller treats it as one block.
743
+ return fb;
744
+ }
745
+ /** Build the CFG for one Kotlin function node, or `undefined` if not modelable. */
746
+ function buildFunctionCfg(fnNode, filePath) {
747
+ try {
748
+ if (!KOTLIN_FUNCTION_TYPES.has(fnNode.type))
749
+ return undefined;
750
+ const startLine = startLineOf(fnNode);
751
+ const endLine = endLineOf(fnNode);
752
+ const startColumn = fnNode.startPosition.column;
753
+ // A `function_declaration` / `anonymous_function` needs a `function_body`; a
754
+ // `lambda_literal` carries its `statements` directly. Absence of a body
755
+ // container ⇒ an abstract / interface-member / signature-only declaration with
756
+ // nothing to model (return undefined).
757
+ const hasBody = fnNode.type === 'lambda_literal' ||
758
+ fnNode.namedChildren.some((c) => c.type === 'function_body');
759
+ if (!hasBody)
760
+ return undefined;
761
+ const body = bodyStatementsOf(fnNode);
762
+ const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
763
+ const harvest = new KotlinHarvester(fnNode);
764
+ const paramFacts = harvest.paramFacts();
765
+ if (paramFacts)
766
+ builder.attachFacts(builder.entryIndex, paramFacts);
767
+ // Expression body (`fun f() = expr` / a `function_body` that is `= expr`):
768
+ // the body's value is returned. A value-position branch (`= when (k) { … }`)
769
+ // is modeled as control flow so each arm is control-dependent on the
770
+ // condition (#2205); any other expression is one block.
771
+ if (body && body.type === 'function_body') {
772
+ const expr = body.namedChildren.find((c) => !isComment(c) && c.type !== 'statements');
773
+ if (expr) {
774
+ const res = new KotlinCfgWalk(builder, harvest).visitExprBody(expr);
775
+ builder.edge(builder.entryIndex, res.entry, 'seq');
776
+ builder.connect(res.exits, builder.exitIndex, 'return');
777
+ return builder.finish(harvest.bindingTable());
778
+ }
779
+ // `function_body` with neither statements nor an expression — empty.
780
+ builder.edge(builder.entryIndex, builder.exitIndex, 'seq');
781
+ return builder.finish(harvest.bindingTable());
782
+ }
783
+ const walk = new KotlinCfgWalk(builder, harvest);
784
+ const res = body ? walk.visitSeq(body.namedChildren.filter((c) => !isComment(c))) : null;
785
+ if (!res) {
786
+ builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
787
+ return builder.finish(harvest.bindingTable());
788
+ }
789
+ builder.edge(builder.entryIndex, res.entry, 'seq');
790
+ builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
791
+ return builder.finish(harvest.bindingTable());
792
+ }
793
+ catch (err) {
794
+ // Never throw out of buildFunctionCfg — a malformed AST shape must skip only
795
+ // this one function's CFG, never drop the whole file's language group (R4).
796
+ // eslint-disable-next-line no-console
797
+ console.warn(`[cfg] Kotlin buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
798
+ return undefined;
799
+ }
800
+ }
801
+ /** Whether a node is a Kotlin function/lambda this visitor builds a CFG for. */
802
+ function isFunction(node) {
803
+ return KOTLIN_FUNCTION_TYPES.has(node.type);
804
+ }
805
+ /** The Kotlin CFG visitor. */
806
+ export function createKotlinCfgVisitor() {
807
+ return { buildFunctionCfg, isFunction };
808
+ }
809
+ export { KOTLIN_FUNCTION_TYPES };