gitnexus 1.6.8-rc.9 → 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 (430) 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 +11 -0
  135. package/dist/core/ingestion/languages/c-cpp.js +6 -1
  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 +3 -0
  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 +23 -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/parse-worker.d.ts +13 -0
  238. package/dist/core/ingestion/workers/parse-worker.js +76 -17
  239. package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
  240. package/dist/core/ingestion/workers/worker-pool.js +15 -2
  241. package/dist/core/lbug/csv-generator.d.ts +51 -3
  242. package/dist/core/lbug/csv-generator.js +369 -255
  243. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  244. package/dist/core/lbug/lbug-adapter.js +346 -84
  245. package/dist/core/lbug/lbug-config.d.ts +19 -0
  246. package/dist/core/lbug/lbug-config.js +29 -0
  247. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  248. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  249. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  250. package/dist/core/lbug/pool-adapter.js +90 -1
  251. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  252. package/dist/core/lbug/rel-pair-routing.js +140 -0
  253. package/dist/core/lbug/schema.d.ts +2 -2
  254. package/dist/core/lbug/schema.js +3 -0
  255. package/dist/core/logger.d.ts +0 -5
  256. package/dist/core/run-analyze.d.ts +96 -0
  257. package/dist/core/run-analyze.js +207 -3
  258. package/dist/core/tree-sitter/parser-loader.js +5 -4
  259. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  260. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  261. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  262. package/dist/core/wiki/generator.js +3 -1
  263. package/dist/core/wiki/graph-queries.d.ts +6 -0
  264. package/dist/core/wiki/graph-queries.js +9 -1
  265. package/dist/mcp/http-transport.d.ts +134 -0
  266. package/dist/mcp/http-transport.js +503 -0
  267. package/dist/mcp/local/local-backend.d.ts +110 -24
  268. package/dist/mcp/local/local-backend.js +1225 -19
  269. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  270. package/dist/mcp/local/pdg-impact.js +1972 -0
  271. package/dist/mcp/resources.js +13 -1
  272. package/dist/mcp/tools.d.ts +12 -0
  273. package/dist/mcp/tools.js +214 -7
  274. package/dist/server/analyze-launch.d.ts +29 -0
  275. package/dist/server/analyze-launch.js +137 -0
  276. package/dist/server/analyze-upload.d.ts +33 -0
  277. package/dist/server/analyze-upload.js +123 -0
  278. package/dist/server/analyze-worker.d.ts +17 -1
  279. package/dist/server/api.d.ts +17 -0
  280. package/dist/server/api.js +132 -168
  281. package/dist/server/git-clone.d.ts +43 -2
  282. package/dist/server/git-clone.js +211 -16
  283. package/dist/server/mcp-http.d.ts +5 -6
  284. package/dist/server/mcp-http.js +9 -82
  285. package/dist/server/middleware.d.ts +40 -0
  286. package/dist/server/middleware.js +92 -0
  287. package/dist/server/private-ip.d.ts +1 -0
  288. package/dist/server/private-ip.js +15 -0
  289. package/dist/server/upload-ingest.d.ts +56 -0
  290. package/dist/server/upload-ingest.js +276 -0
  291. package/dist/server/upload-paths.d.ts +37 -0
  292. package/dist/server/upload-paths.js +57 -0
  293. package/dist/server/upload-sweep.d.ts +20 -0
  294. package/dist/server/upload-sweep.js +57 -0
  295. package/dist/storage/git.js +5 -8
  296. package/dist/storage/parse-cache.d.ts +22 -1
  297. package/dist/storage/parse-cache.js +41 -10
  298. package/dist/storage/repo-manager.d.ts +121 -10
  299. package/dist/storage/repo-manager.js +43 -5
  300. package/dist/types/pipeline.d.ts +9 -0
  301. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  302. package/hooks/claude/gitnexus-hook.cjs +110 -13
  303. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  304. package/package.json +8 -2
  305. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  306. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  307. package/scripts/cross-platform-tests.ts +1 -0
  308. package/skills/gitnexus-debugging.md +16 -4
  309. package/skills/gitnexus-exploring.md +3 -3
  310. package/skills/gitnexus-guide.md +33 -0
  311. package/skills/gitnexus-pdg-query.md +89 -0
  312. package/skills/gitnexus-refactoring.md +1 -1
  313. package/skills/gitnexus-taint-analysis.md +178 -0
  314. package/vendor/tree-sitter-c/package.json +1 -1
  315. package/vendor/tree-sitter-dart/package.json +1 -1
  316. package/vendor/tree-sitter-kotlin/package.json +1 -1
  317. package/vendor/tree-sitter-proto/package.json +1 -1
  318. package/vendor/tree-sitter-swift/package.json +1 -1
  319. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  320. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  321. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  322. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  323. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  324. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  325. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  326. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  327. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  328. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  329. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  330. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  331. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  332. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  333. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  334. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  335. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  336. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  337. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  338. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  339. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  340. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  341. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  342. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  343. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  344. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  345. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  346. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  347. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  348. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  349. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  350. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  351. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  352. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  353. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  354. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  355. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  356. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  357. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  358. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  359. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  360. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  361. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  362. package/web/assets/index-BvYev1UJ.js +635 -0
  363. package/web/assets/index-_lgn7hs5.css +2 -0
  364. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  365. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  366. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  367. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  368. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  369. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  370. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  371. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  372. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  373. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  374. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  375. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  376. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  377. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  378. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  379. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  380. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  381. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  382. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  383. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  384. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  385. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  386. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  387. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  388. package/web/index.html +13 -15
  389. package/scripts/materialize-vendor-grammars.cjs +0 -97
  390. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  391. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  392. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  393. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  394. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  395. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  396. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  397. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  398. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  399. package/web/assets/chunk-CilyBKbf.js +0 -1
  400. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  401. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  402. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  403. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  404. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  405. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  406. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  407. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  408. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  409. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  410. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  411. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  412. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  413. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  414. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  415. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  416. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  417. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  418. package/web/assets/index-BKWA-m7o.css +0 -2
  419. package/web/assets/index-CG6q8eTs.js +0 -626
  420. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  421. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  422. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  423. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  424. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  425. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  426. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  427. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  428. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  429. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  430. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,787 @@
1
+ import { CfgBuilder } from '../cfg-builder.js';
2
+ import { ControlFlowContext, wireJumpThroughFinalizers } from '../control-flow-context.js';
3
+ import { drainFinalizerPending } from '../control-flow-context.js';
4
+ import { SwiftHarvester } from './swift-harvest.js';
5
+ /** Swift node types that own a CFG-bearing function body. */
6
+ const SWIFT_FUNCTION_TYPES = new Set([
7
+ 'function_declaration',
8
+ 'init_declaration',
9
+ 'deinit_declaration',
10
+ 'lambda_literal',
11
+ ]);
12
+ /** Statement node types that break a basic block (everything else coalesces). */
13
+ const CONTROL_FLOW_TYPES = new Set([
14
+ 'if_statement',
15
+ 'guard_statement',
16
+ 'for_statement',
17
+ 'while_statement',
18
+ 'repeat_while_statement',
19
+ 'switch_statement',
20
+ 'do_statement',
21
+ 'control_transfer_statement',
22
+ 'statements',
23
+ ]);
24
+ const startLineOf = (n) => n.startPosition.row + 1;
25
+ const endLineOf = (n) => n.endPosition.row + 1;
26
+ /**
27
+ * Per-function Swift walk state. One instance per function so the
28
+ * {@link ControlFlowContext}, the `defer` finalizer chain, and the label tables
29
+ * are scoped to that function and never leak across functions.
30
+ */
31
+ class SwiftCfgWalk {
32
+ builder;
33
+ harvest;
34
+ cfc = new ControlFlowContext();
35
+ /** Stack of `do`/`catch` handler entry blocks a `throw`/`try` jumps to. */
36
+ handlers = [];
37
+ /** Label(s) pending attachment to the NEXT pushed loop/switch frame. */
38
+ pendingLabels = [];
39
+ /**
40
+ * Active `defer` finalizer frames in source (push) order. Innermost-LIFO is the
41
+ * REVERSE of this list. Frames stay active for the whole function tail and are
42
+ * drained once at the top-level walk's end (mirrors the Go visitor).
43
+ */
44
+ deferFrames = [];
45
+ constructor(builder, harvest) {
46
+ this.builder = builder;
47
+ this.harvest = harvest;
48
+ }
49
+ /** Statements of a `statements` node, ignoring comments. */
50
+ statementsOf(block) {
51
+ return block.namedChildren.filter((c) => c.type !== 'comment' && c.type !== 'multiline_comment');
52
+ }
53
+ /** The body `statements` of a loop/branch node (the LAST `statements` child). */
54
+ bodyStatements(node) {
55
+ const all = node.namedChildren.filter((c) => c.type === 'statements');
56
+ return all.length ? all[all.length - 1] : undefined;
57
+ }
58
+ /** Visit a body that is a `statements` node (or a single statement). */
59
+ visitBody(node) {
60
+ return this.builder.withNesting(() => {
61
+ if (!node)
62
+ return null;
63
+ if (node.type === 'statements')
64
+ return this.visitSeq(this.statementsOf(node));
65
+ return this.visitStmt(node);
66
+ });
67
+ }
68
+ /** Wire a sequence of statements, coalescing straight-line runs into blocks. */
69
+ visitSeq(stmts) {
70
+ return this.builder.withNesting(() => {
71
+ let entry;
72
+ let dangling = [];
73
+ let openSimple;
74
+ for (const stmt of stmts) {
75
+ if (this.isControlFlow(stmt)) {
76
+ openSimple = undefined; // close any open straight-line block
77
+ const res = this.visitStmt(stmt);
78
+ if (res === null)
79
+ continue; // transparent (empty nested block / label)
80
+ if (entry === undefined)
81
+ entry = res.entry;
82
+ else
83
+ this.builder.connect(dangling, res.entry, 'seq');
84
+ dangling = [...res.exits];
85
+ }
86
+ else {
87
+ if (openSimple === undefined) {
88
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
89
+ if (entry === undefined)
90
+ entry = idx;
91
+ else
92
+ this.builder.connect(dangling, idx, 'seq');
93
+ openSimple = idx;
94
+ dangling = [idx];
95
+ }
96
+ else {
97
+ this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
98
+ }
99
+ // A straight-line statement may contain a `try` — it can error-return.
100
+ this.wireTryExits(stmt, openSimple);
101
+ }
102
+ }
103
+ if (entry === undefined)
104
+ return null;
105
+ return { entry, exits: dangling };
106
+ });
107
+ }
108
+ /** Whether a statement node breaks the current straight-line block. */
109
+ isControlFlow(stmt) {
110
+ if (stmt.type === 'statement_label')
111
+ return true; // queue label, emit no block
112
+ if (this.isDeferCall(stmt))
113
+ return true;
114
+ // `let x = if … / switch …` (Swift 5.9, #2207): a value-position branch breaks
115
+ // so `visitStmt` models the arms as control flow instead of coalescing.
116
+ if (stmt.type === 'property_declaration') {
117
+ const v = this.directValue(stmt);
118
+ return v !== undefined && this.isModelableValueBranch(v);
119
+ }
120
+ return CONTROL_FLOW_TYPES.has(stmt.type);
121
+ }
122
+ /** Dispatch one statement to its handler. Non-null except for empty / label-only. */
123
+ visitStmt(stmt) {
124
+ if (stmt.type === 'statement_label') {
125
+ // A label preceding its loop/switch — queue it; the construct picks it up.
126
+ const name = this.labelName(stmt);
127
+ if (name !== undefined)
128
+ this.pendingLabels = [...this.pendingLabels, name];
129
+ return null; // emits no block of its own
130
+ }
131
+ if (this.isDeferCall(stmt))
132
+ return this.visitDefer(stmt);
133
+ switch (stmt.type) {
134
+ case 'property_declaration': {
135
+ // `let x = if … / switch …` (Swift 5.9, #2207): the value is a value-
136
+ // position branch — model it as control flow and bind on the rejoin.
137
+ const value = this.directValue(stmt);
138
+ if (value && this.isModelableValueBranch(value))
139
+ return this.visitBindBranch(stmt, value);
140
+ return this.visitSimple(stmt);
141
+ }
142
+ case 'if_statement':
143
+ return this.visitIf(stmt);
144
+ case 'guard_statement':
145
+ return this.visitGuard(stmt);
146
+ case 'for_statement':
147
+ return this.visitFor(stmt);
148
+ case 'while_statement':
149
+ return this.visitWhile(stmt);
150
+ case 'repeat_while_statement':
151
+ return this.visitRepeatWhile(stmt);
152
+ case 'switch_statement':
153
+ return this.visitSwitch(stmt);
154
+ case 'do_statement':
155
+ return this.visitDo(stmt);
156
+ case 'control_transfer_statement':
157
+ return this.visitControlTransfer(stmt);
158
+ case 'statements':
159
+ return this.visitSeq(this.statementsOf(stmt));
160
+ default:
161
+ return this.visitSimple(stmt);
162
+ }
163
+ }
164
+ visitSimple(stmt) {
165
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
166
+ this.wireTryExits(stmt, idx);
167
+ return { entry: idx, exits: [idx] };
168
+ }
169
+ // ── control transfer (break / continue / return / throw) ─────────────────
170
+ /** The leading keyword of a `control_transfer_statement` decides its kind. */
171
+ transferKeyword(stmt) {
172
+ const first = stmt.child(0);
173
+ return first?.text ?? '';
174
+ }
175
+ visitControlTransfer(stmt) {
176
+ const kw = this.transferKeyword(stmt);
177
+ switch (kw) {
178
+ case 'return':
179
+ return this.visitReturn(stmt);
180
+ case 'throw':
181
+ return this.visitThrow(stmt);
182
+ case 'break':
183
+ return this.visitBreak(stmt);
184
+ case 'continue':
185
+ return this.visitContinue(stmt);
186
+ default:
187
+ // `fallthrough` standalone (rare outside a switch) / unknown — straight
188
+ // through (the switch handler treats the in-case `fallthrough` keyword).
189
+ return this.visitSimple(stmt);
190
+ }
191
+ }
192
+ /** `return [expr]` — threads through every active `defer` (LIFO) before EXIT. */
193
+ visitReturn(stmt) {
194
+ // `return if … / switch …` (Swift 5.9, #2207): the returned value is a value-
195
+ // position branch — model it as control flow, with each arm returning (its
196
+ // value IS the function result), threading every active finalizer per arm.
197
+ const branch = stmt.namedChildren.find((c) => c.type === 'if_statement' || c.type === 'switch_statement');
198
+ if (branch && this.isModelableValueBranch(branch)) {
199
+ const res = this.visitBranchExpr(branch);
200
+ const finalizers = this.cfc.finalizersForReturn();
201
+ for (const ex of res.exits) {
202
+ wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
203
+ }
204
+ return { entry: res.entry, exits: [] };
205
+ }
206
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
207
+ this.wireTryExits(stmt, idx);
208
+ wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
209
+ return { entry: idx, exits: [] };
210
+ }
211
+ /** `throw e` — routes to the nearest enclosing `catch` handler, else EXIT. */
212
+ visitThrow(stmt) {
213
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
214
+ this.builder.edge(idx, this.currentHandler(), 'throw');
215
+ return { entry: idx, exits: [] };
216
+ }
217
+ visitBreak(stmt) {
218
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
219
+ const label = this.jumpLabel(stmt);
220
+ const res = this.cfc.resolveBreak(label);
221
+ const { target, finalizers } = res ?? {
222
+ target: this.builder.exitIndex,
223
+ finalizers: this.cfc.finalizersForReturn(),
224
+ };
225
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
226
+ return { entry: idx, exits: [] };
227
+ }
228
+ visitContinue(stmt) {
229
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
230
+ const label = this.jumpLabel(stmt);
231
+ const res = this.cfc.resolveContinue(label);
232
+ const { target, finalizers } = res ?? {
233
+ target: this.builder.exitIndex,
234
+ finalizers: this.cfc.finalizersForReturn(),
235
+ };
236
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
237
+ return { entry: idx, exits: [] };
238
+ }
239
+ /** The `result` label of a `break outer` / `continue outer`, if any. */
240
+ jumpLabel(stmt) {
241
+ const result = stmt.childForFieldName('result');
242
+ return result?.type === 'simple_identifier' ? result.text : undefined;
243
+ }
244
+ /** The bare name of a `statement_label` (`outer:` ⇒ `outer`). */
245
+ labelName(label) {
246
+ const id = label.namedChildren.find((c) => c.type === 'simple_identifier');
247
+ if (id?.text)
248
+ return id.text;
249
+ const stripped = label.text.replace(/:\s*$/, '').trim();
250
+ return stripped || undefined;
251
+ }
252
+ /** Take and clear the labels queued by a preceding `statement_label`. */
253
+ takeLabels() {
254
+ const labels = this.pendingLabels;
255
+ this.pendingLabels = [];
256
+ return labels;
257
+ }
258
+ // ── value-position branches (#2207) ─────────────────────────────────────────
259
+ /**
260
+ * The value-position branch of a `property_declaration` (`let x = if … / switch
261
+ * …`, Swift 5.9): the direct `if_statement` / `switch_statement` child (the value
262
+ * after `=`), or undefined. tree-sitter-swift reuses the statement nodes for the
263
+ * value form — there is no separate `if_expression` / `switch_expression`.
264
+ */
265
+ directValue(stmt) {
266
+ return stmt.namedChildren.find((c) => c.type === 'if_statement' || c.type === 'switch_statement');
267
+ }
268
+ /**
269
+ * Whether `node` is a value-position branch worth modeling as control flow
270
+ * (#2207): an `if` with an `else` (a value-position `if` always has one), or a
271
+ * `switch` with ≥2 entries — a real dispatch. The ternary `?:` and `??` are
272
+ * excluded by design (micro-branches, like the Kotlin elvis).
273
+ */
274
+ isModelableValueBranch(node) {
275
+ if (node.type === 'if_statement')
276
+ return this.elseNodeOf(node) !== undefined;
277
+ if (node.type === 'switch_statement') {
278
+ return node.namedChildren.filter((c) => c.type === 'switch_entry').length >= 2;
279
+ }
280
+ return false;
281
+ }
282
+ /** Model a value-position `if`/`switch` as control flow, bypassing position. */
283
+ visitBranchExpr(node) {
284
+ return node.type === 'switch_statement' ? this.visitSwitch(node) : this.visitIf(node);
285
+ }
286
+ /**
287
+ * `let x = if … / switch …` (#2207): visit the branch as control flow, then
288
+ * rejoin its arms at a facts-only continuation carrying ONLY the bound name's
289
+ * def (the condition + arm-value uses are already on the branch's blocks). The
290
+ * arms are now control-dependent on the branch — mirrors Kotlin / Rust.
291
+ */
292
+ visitBindBranch(stmt, branch) {
293
+ const res = this.visitBranchExpr(branch);
294
+ const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.bindingDefFacts(stmt));
295
+ this.builder.connect(res.exits, cont, 'seq');
296
+ return { entry: res.entry, exits: [cont] };
297
+ }
298
+ // ── branches ──────────────────────────────────────────────────────────────
299
+ /**
300
+ * `if COND { … } [else { … } | else if …]`. COND may be a plain expression or
301
+ * an optional binding (`if let y = opt`). The else is the node AFTER the `else`
302
+ * keyword — a nested `if_statement` (`else if`) or the else-body `statements`.
303
+ */
304
+ visitIf(stmt) {
305
+ const header = this.builder.newBlock(startLineOf(stmt), this.conditionEndLine(stmt), this.conditionText(stmt), 'normal', this.harvest.conditionFacts(stmt, false));
306
+ const exits = [];
307
+ const thenRes = this.visitBody(this.thenStatements(stmt));
308
+ if (thenRes) {
309
+ this.builder.edge(header, thenRes.entry, 'cond-true');
310
+ exits.push(...thenRes.exits);
311
+ }
312
+ else {
313
+ exits.push(header); // empty then — true path falls through
314
+ }
315
+ const elseNode = this.elseNodeOf(stmt);
316
+ if (elseNode) {
317
+ const elseRes = this.visitBody(elseNode);
318
+ if (elseRes) {
319
+ this.builder.edge(header, elseRes.entry, 'cond-false');
320
+ exits.push(...elseRes.exits);
321
+ }
322
+ else {
323
+ exits.push(header);
324
+ }
325
+ }
326
+ else {
327
+ exits.push(header); // no else — false path falls through to the join
328
+ }
329
+ return { entry: header, exits: [...new Set(exits)] };
330
+ }
331
+ /**
332
+ * `guard COND else { … }` — the inverse of `if`. The `else` body runs when COND
333
+ * is false and (per Swift) MUST diverge (return/throw/break/continue); it is
334
+ * branched with a `cond-false` edge. The guard body CONTINUES straight-line on
335
+ * the true path (`cond-true`).
336
+ */
337
+ visitGuard(stmt) {
338
+ const header = this.builder.newBlock(startLineOf(stmt), this.conditionEndLine(stmt), this.conditionText(stmt), 'normal', this.harvest.conditionFacts(stmt, false));
339
+ // The else body is the guard's `statements` (after the `else` keyword).
340
+ const elseNode = this.elseNodeOf(stmt) ?? this.bodyStatements(stmt);
341
+ if (elseNode) {
342
+ const elseRes = this.visitBody(elseNode);
343
+ if (elseRes) {
344
+ this.builder.edge(header, elseRes.entry, 'cond-false');
345
+ // The else MUST diverge; any normal exit it leaves rejoins EXIT-bound
346
+ // flow conservatively (Swift forbids fall-through, but stay robust).
347
+ this.builder.connect(elseRes.exits, this.builder.exitIndex, 'seq');
348
+ }
349
+ else {
350
+ this.builder.edge(header, this.builder.exitIndex, 'cond-false');
351
+ }
352
+ }
353
+ else {
354
+ this.builder.edge(header, this.builder.exitIndex, 'cond-false');
355
+ }
356
+ // The true path continues straight-line after the guard.
357
+ return { entry: header, exits: [header] };
358
+ }
359
+ /** The THEN body `statements` of an `if`/`guard` (the first `statements` child). */
360
+ thenStatements(stmt) {
361
+ return stmt.namedChildren.find((c) => c.type === 'statements');
362
+ }
363
+ /** The node after the `else` keyword: a nested `if_statement` or the else `statements`. */
364
+ elseNodeOf(stmt) {
365
+ let sawElse = false;
366
+ for (let i = 0; i < stmt.childCount; i++) {
367
+ const c = stmt.child(i);
368
+ if (!c)
369
+ continue;
370
+ if (sawElse && c.isNamed)
371
+ return c;
372
+ if (c.type === 'else')
373
+ sawElse = true;
374
+ }
375
+ return undefined;
376
+ }
377
+ /** End line of a construct's condition (the last `condition`-field child). */
378
+ conditionEndLine(stmt) {
379
+ let line = startLineOf(stmt);
380
+ for (let i = 0; i < stmt.childCount; i++) {
381
+ const field = stmt.fieldNameForChild(i);
382
+ const c = stmt.child(i);
383
+ if (c && (field === 'condition' || field === 'bound_identifier'))
384
+ line = endLineOf(c);
385
+ }
386
+ return line;
387
+ }
388
+ /** Display text for a construct's condition (joined `condition`-field children). */
389
+ conditionText(stmt) {
390
+ const parts = [];
391
+ const kw = stmt.child(0);
392
+ if (kw && !kw.isNamed)
393
+ parts.push(kw.text);
394
+ for (let i = 0; i < stmt.childCount; i++) {
395
+ const field = stmt.fieldNameForChild(i);
396
+ const c = stmt.child(i);
397
+ if (c && (field === 'condition' || field === 'bound_identifier'))
398
+ parts.push(c.text);
399
+ }
400
+ return parts.join(' ') || stmt.text;
401
+ }
402
+ // ── loops ───────────────────────────────────────────────────────────────
403
+ /** `for item in COLLECTION [where …] { … }`. */
404
+ visitFor(stmt) {
405
+ const labels = this.takeLabels();
406
+ const collection = stmt.childForFieldName('collection');
407
+ const header = this.builder.newBlock(startLineOf(stmt), collection ? endLineOf(collection) : startLineOf(stmt), this.forHeaderText(stmt), 'normal', this.harvest.forHeadFacts(stmt));
408
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
409
+ this.cfc.pushLoop(header, loopExit, labels);
410
+ const body = this.visitBody(this.bodyStatements(stmt));
411
+ this.cfc.pop();
412
+ if (body) {
413
+ this.builder.edge(header, body.entry, 'cond-true');
414
+ this.builder.connect(body.exits, header, 'loop-back');
415
+ }
416
+ else {
417
+ this.builder.edge(header, header, 'loop-back'); // empty body re-iterates
418
+ }
419
+ this.builder.edge(header, loopExit, 'cond-false');
420
+ return { entry: header, exits: [loopExit] };
421
+ }
422
+ forHeaderText(stmt) {
423
+ const item = stmt.childForFieldName('item')?.text ?? '';
424
+ const collection = stmt.childForFieldName('collection')?.text ?? '';
425
+ return item || collection ? `for ${item} in ${collection}` : 'for';
426
+ }
427
+ /** `while COND { … }` (and `while let PAT = e { … }`). */
428
+ visitWhile(stmt) {
429
+ const labels = this.takeLabels();
430
+ const header = this.builder.newBlock(startLineOf(stmt), this.conditionEndLine(stmt), this.conditionText(stmt), 'normal', this.harvest.conditionFacts(stmt, true));
431
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
432
+ this.cfc.pushLoop(header, loopExit, labels);
433
+ const body = this.visitBody(this.bodyStatements(stmt));
434
+ this.cfc.pop();
435
+ if (body) {
436
+ this.builder.edge(header, body.entry, 'cond-true');
437
+ this.builder.connect(body.exits, header, 'loop-back');
438
+ }
439
+ else {
440
+ this.builder.edge(header, header, 'loop-back'); // empty body re-tests
441
+ }
442
+ // Structural exit edge — even `while true {}` keeps EXIT reverse-reachable.
443
+ this.builder.edge(header, loopExit, 'cond-false');
444
+ return { entry: header, exits: [loopExit] };
445
+ }
446
+ /**
447
+ * `repeat { … } while COND` — BOTTOM-TEST: the body runs at least once, THEN
448
+ * the condition decides whether to loop back. The body entry is the loop entry;
449
+ * the condition block is the loop-back / exit decision.
450
+ */
451
+ visitRepeatWhile(stmt) {
452
+ const labels = this.takeLabels();
453
+ const cond = stmt.childForFieldName('condition');
454
+ 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);
455
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
456
+ // `continue` re-tests the condition; `break` leaves the loop.
457
+ this.cfc.pushLoop(condBlock, loopExit, labels);
458
+ const body = this.visitBody(this.bodyStatements(stmt));
459
+ this.cfc.pop();
460
+ const backTarget = body ? body.entry : condBlock;
461
+ if (body)
462
+ this.builder.connect(body.exits, condBlock, 'seq');
463
+ this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
464
+ // Structural exit edge — even `repeat {} while true` keeps EXIT reachable.
465
+ this.builder.edge(condBlock, loopExit, 'cond-false');
466
+ return { entry: backTarget, exits: [loopExit] };
467
+ }
468
+ // ── switch ───────────────────────────────────────────────────────────────
469
+ /**
470
+ * `switch EXPR { case … : … fallthrough? … default: … }`. Cases do NOT fall
471
+ * through implicitly (the opposite of C); an explicit `fallthrough` keyword at
472
+ * the end of a `switch_entry` spills into the NEXT entry's body. A `where` guard
473
+ * on a case is harvested conditionally onto the dispatch block.
474
+ */
475
+ visitSwitch(stmt) {
476
+ const labels = this.takeLabels();
477
+ const value = stmt.childForFieldName('expr');
478
+ const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `switch ${value.text}` : 'switch', 'normal', value ? this.harvest.facts(value) : undefined);
479
+ const switchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
480
+ this.cfc.pushSwitch(switchExit, labels);
481
+ const entries = stmt.namedChildren.filter((c) => c.type === 'switch_entry');
482
+ // A case `where` guard evaluates conditionally before the body — harvest its
483
+ // uses onto the dispatch block (a later case tests only when earlier ones
484
+ // didn't match; any def there is a may-def).
485
+ for (const entry of entries) {
486
+ // `case let n` value bindings are may-defs on the dispatch block (the case
487
+ // may not match) — propagated into the body where the name is read.
488
+ const pat = this.entryPattern(entry);
489
+ if (pat)
490
+ this.builder.attachFacts(dispatch, this.harvest.switchPatternFacts(pat));
491
+ const guard = this.entryGuard(entry);
492
+ if (guard)
493
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(guard));
494
+ }
495
+ const entryResults = entries.map((e) => this.visitBody(this.entryBody(e)));
496
+ const hasDefault = entries.some((e) => this.entryIsDefault(e));
497
+ const entryOf = new Array(entries.length);
498
+ let after = switchExit;
499
+ for (let i = entries.length - 1; i >= 0; i--) {
500
+ entryOf[i] = entryResults[i]?.entry ?? after;
501
+ after = entryOf[i];
502
+ }
503
+ for (let i = 0; i < entries.length; i++) {
504
+ this.builder.edge(dispatch, entryOf[i], 'switch-case');
505
+ }
506
+ if (!hasDefault)
507
+ this.builder.edge(dispatch, switchExit, 'switch-case'); // no-match path
508
+ // A case body rejoins AFTER the switch (no implicit fallthrough) UNLESS the
509
+ // entry ends in an explicit `fallthrough`, which spills into the next entry.
510
+ for (let i = 0; i < entries.length; i++) {
511
+ const res = entryResults[i];
512
+ if (!res)
513
+ continue;
514
+ const fallsThrough = this.entryFallsThrough(entries[i]);
515
+ const fallTarget = i + 1 < entries.length ? entryOf[i + 1] : switchExit;
516
+ if (fallsThrough)
517
+ this.builder.connect(res.exits, fallTarget, 'fallthrough');
518
+ else
519
+ this.builder.connect(res.exits, switchExit, 'seq');
520
+ }
521
+ this.cfc.pop();
522
+ return { entry: dispatch, exits: [switchExit] };
523
+ }
524
+ /** The `switch_pattern` of a `switch_entry` (absent for `default:`). */
525
+ entryPattern(entry) {
526
+ return entry.namedChildren.find((c) => c.type === 'switch_pattern');
527
+ }
528
+ /** The `where` guard expression of a `switch_entry`, if any. */
529
+ entryGuard(entry) {
530
+ let sawWhere = false;
531
+ for (let i = 0; i < entry.childCount; i++) {
532
+ const c = entry.child(i);
533
+ if (!c)
534
+ continue;
535
+ if (sawWhere && c.isNamed)
536
+ return c;
537
+ if (c.type === 'where_keyword')
538
+ sawWhere = true;
539
+ }
540
+ return undefined;
541
+ }
542
+ /** The body `statements` of a `switch_entry`. */
543
+ entryBody(entry) {
544
+ return entry.namedChildren.find((c) => c.type === 'statements');
545
+ }
546
+ entryIsDefault(entry) {
547
+ return (entry.namedChildren.some((c) => c.type === 'default_keyword') ||
548
+ entry.children.some((c) => c.type === 'default_keyword'));
549
+ }
550
+ /** A `switch_entry` ends in an explicit `fallthrough` keyword child. */
551
+ entryFallsThrough(entry) {
552
+ for (let i = 0; i < entry.childCount; i++) {
553
+ if (entry.child(i)?.type === 'fallthrough')
554
+ return true;
555
+ }
556
+ return false;
557
+ }
558
+ // ── do / catch (error handling) ──────────────────────────────────────────
559
+ /**
560
+ * `do { … } catch [pat] { … }`. Conservative exceptional flow (mirrors the C++
561
+ * `try`/`catch` over-approximation): every block created while walking the
562
+ * protected `do` body edges to the FIRST catch handler — a `try`/`throw` may
563
+ * fire mid-block. Each `catch_block`'s normal completion joins the post-`do`
564
+ * continuation.
565
+ */
566
+ visitDo(stmt) {
567
+ const bodyNode = stmt.namedChildren.find((c) => c.type === 'statements');
568
+ const catchBlocks = stmt.namedChildren.filter((c) => c.type === 'catch_block');
569
+ // Build each catch handler. The handler entry is the error-binding block (a
570
+ // facts-only block) in front of the body, so the binding happens once on entry.
571
+ const handlerEntries = [];
572
+ const handlerExits = [];
573
+ for (const clause of catchBlocks) {
574
+ const clauseBody = clause.namedChildren.find((c) => c.type === 'statements');
575
+ let res = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
576
+ if (res === null) {
577
+ // Empty catch body still CATCHES — synthesize one block so the error
578
+ // lands somewhere and post-`do` code stays reachable.
579
+ const idx = this.builder.newBlock(startLineOf(clause), endLineOf(clause), '');
580
+ res = { entry: idx, exits: [idx] };
581
+ }
582
+ const errFacts = this.harvest.catchErrorFacts(clause);
583
+ if (errFacts) {
584
+ const errBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', errFacts);
585
+ this.builder.edge(errBlock, res.entry, 'seq');
586
+ res = { entry: errBlock, exits: res.exits };
587
+ }
588
+ handlerEntries.push(res.entry);
589
+ handlerExits.push(...res.exits);
590
+ }
591
+ // The protected body's handler is the FIRST catch (if any), else the outer.
592
+ const doHandler = handlerEntries[0] ?? this.currentHandler();
593
+ const protectedStart = this.builder.blockCount;
594
+ this.handlers.push(doHandler);
595
+ const bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
596
+ this.handlers.pop();
597
+ // Conservative exceptional edges: every protected-region block → EACH catch
598
+ // handler. Swift tries the catch clauses in order until one matches; the
599
+ // thrown type is unknown at CFG time, so any protected block may reach ANY
600
+ // clause. Edging only the first handler orphaned the 2nd..Nth catch blocks
601
+ // (unreachable from ENTRY, stranding their error bindings + def/use facts).
602
+ if (handlerEntries.length > 0) {
603
+ for (let b = protectedStart; b < this.builder.blockCount; b++) {
604
+ for (const handler of handlerEntries)
605
+ this.builder.edge(b, handler, 'throw');
606
+ }
607
+ }
608
+ const exits = [];
609
+ if (bodyRes)
610
+ exits.push(...bodyRes.exits);
611
+ exits.push(...handlerExits);
612
+ // No catch at all — a `do {}` without `catch` is just a scope: the body
613
+ // flows straight through to its own normal exits (no extra edge needed).
614
+ const entry = bodyRes?.entry ?? handlerEntries[0];
615
+ if (entry === undefined)
616
+ return null;
617
+ return { entry, exits: [...new Set(exits)] };
618
+ }
619
+ /** Nearest enclosing `catch` handler, or the function EXIT. */
620
+ currentHandler() {
621
+ return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
622
+ }
623
+ /**
624
+ * Emit a `throw` edge to the nearest handler (or EXIT) for every `try`/`try?`/
625
+ * `try!` operator inside a straight-line statement's subtree (excluding nested
626
+ * function bodies). A `try` can propagate an error mid-statement; routing it
627
+ * keeps the error path represented while the success path falls through.
628
+ * Deduped by the builder, so repeated `try` in a statement emit one edge.
629
+ */
630
+ wireTryExits(stmt, fromBlock) {
631
+ if (this.containsTry(stmt))
632
+ this.builder.edge(fromBlock, this.currentHandler(), 'throw');
633
+ }
634
+ containsTry(node) {
635
+ if (node.type === 'try_expression') {
636
+ // `try?` / `try!` handle the error in-place (optional / trap), so only a
637
+ // bare `try` propagates — but conservatively any `try` may error in a
638
+ // throwing context; route all to the handler (deduped, low cost).
639
+ return true;
640
+ }
641
+ if (SWIFT_FUNCTION_TYPES.has(node.type))
642
+ return false; // opaque nested fn
643
+ for (let i = 0; i < node.namedChildCount; i++) {
644
+ const c = node.namedChild(i);
645
+ if (c && this.containsTry(c))
646
+ return true;
647
+ }
648
+ return false;
649
+ }
650
+ // ── defer ──────────────────────────────────────────────────────────────
651
+ /**
652
+ * `defer { … }` is parsed as a `call_expression` whose callee is a bare
653
+ * `defer` identifier carrying a trailing-closure `lambda_literal`. Detect that
654
+ * exact shape so the deferred body threads the function-exit completion chain.
655
+ */
656
+ isDeferCall(stmt) {
657
+ if (stmt.type !== 'call_expression')
658
+ return false;
659
+ const callee = stmt.namedChild(0);
660
+ if (!callee || callee.type !== 'simple_identifier' || callee.text !== 'defer')
661
+ return false;
662
+ return this.deferClosure(stmt) !== undefined;
663
+ }
664
+ /** The trailing-closure `lambda_literal` body of a `defer { … }` call. */
665
+ deferClosure(stmt) {
666
+ for (let i = 0; i < stmt.namedChildCount; i++) {
667
+ const c = stmt.namedChild(i);
668
+ if (c?.type === 'call_suffix') {
669
+ const lambda = c.namedChildren.find((x) => x.type === 'lambda_literal');
670
+ if (lambda)
671
+ return lambda;
672
+ }
673
+ if (c?.type === 'lambda_literal')
674
+ return c;
675
+ }
676
+ return undefined;
677
+ }
678
+ /**
679
+ * `defer { … }` — register the deferred body as a finalizer frame that stays
680
+ * active for the rest of the function tail (mirrors the Go visitor). Every later
681
+ * `return` AND the normal fall-off thread through it; LIFO across multiple
682
+ * defers falls out of `finalizersForReturn()` yielding innermost-first. The
683
+ * deferred block carries the closure body's def/use facts. The `defer` itself is
684
+ * a no-op at its source position — control falls straight through.
685
+ */
686
+ visitDefer(stmt) {
687
+ const lambda = this.deferClosure(stmt);
688
+ const deferBlock = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', lambda ? this.harvest.facts(lambda) : undefined);
689
+ const frame = this.cfc.pushFinalizer(deferBlock);
690
+ this.deferFrames.push(frame);
691
+ // The `defer` statement is a no-op inline — return a SEPARATE marker block as
692
+ // the source position so the deferred block stays out of straight-line flow.
693
+ const marker = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '');
694
+ return { entry: marker, exits: [marker] };
695
+ }
696
+ /**
697
+ * Drain the active `defer` chain at function end (mirrors the Go visitor). The
698
+ * chain runs innermost-first (LIFO). Returns the block set control reaches AFTER
699
+ * the whole defer chain runs (to be wired to EXIT by the caller), or
700
+ * `normalExits` unchanged when there are no defers.
701
+ */
702
+ finishDefers(normalExits) {
703
+ if (this.deferFrames.length === 0)
704
+ return normalExits;
705
+ const lifo = [...this.deferFrames].reverse();
706
+ for (let i = 0; i < lifo.length; i++)
707
+ this.cfc.pop();
708
+ for (const frame of lifo)
709
+ drainFinalizerPending(this.builder, frame, [frame.entry]);
710
+ if (normalExits.length > 0) {
711
+ this.builder.connect(normalExits, lifo[0].entry, 'return');
712
+ for (let i = 0; i + 1 < lifo.length; i++) {
713
+ this.builder.edge(lifo[i].entry, lifo[i + 1].entry, 'finally-return');
714
+ }
715
+ }
716
+ return [lifo[lifo.length - 1].entry];
717
+ }
718
+ }
719
+ /** Build the CFG for one Swift function node, or `undefined` if not modelable. */
720
+ function buildFunctionCfg(fnNode, filePath) {
721
+ try {
722
+ if (!SWIFT_FUNCTION_TYPES.has(fnNode.type))
723
+ return undefined;
724
+ const startLine = startLineOf(fnNode);
725
+ const endLine = endLineOf(fnNode);
726
+ const startColumn = fnNode.startPosition.column;
727
+ // A function/init/deinit must own a `function_body`; a lambda owns its
728
+ // `statements` directly. Absence ⇒ a protocol requirement / signature-only
729
+ // declaration with no body to model (return undefined). A PRESENT-but-empty
730
+ // body is a valid empty function (ENTRY → EXIT), distinct from "no body".
731
+ const hasBodyContainer = fnNode.childForFieldName('body') !== null ||
732
+ fnNode.namedChildren.some((c) => c.type === 'function_body') ||
733
+ (fnNode.type === 'lambda_literal' &&
734
+ fnNode.namedChildren.some((c) => c.type === 'statements')) ||
735
+ fnNode.type === 'lambda_literal';
736
+ if (!hasBodyContainer)
737
+ return undefined;
738
+ const body = bodyStatementsOf(fnNode); // may be undefined for an empty body
739
+ const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
740
+ const harvest = new SwiftHarvester(fnNode);
741
+ const paramFacts = harvest.paramFacts();
742
+ if (paramFacts)
743
+ builder.attachFacts(builder.entryIndex, paramFacts);
744
+ const walk = new SwiftCfgWalk(builder, harvest);
745
+ const res = body
746
+ ? walk.visitSeq(body.namedChildren.filter((c) => c.type !== 'comment' && c.type !== 'multiline_comment'))
747
+ : null;
748
+ builder.edge(builder.entryIndex, res ? res.entry : builder.exitIndex, 'seq');
749
+ // Normal fall-off threads through the active `defer` chain (LIFO) → EXIT.
750
+ const normalExits = res ? res.exits : [builder.entryIndex];
751
+ const afterDefers = walk.finishDefers(normalExits);
752
+ builder.connect(afterDefers, builder.exitIndex, 'seq');
753
+ return builder.finish(harvest.bindingTable());
754
+ }
755
+ catch (err) {
756
+ // Never throw out of buildFunctionCfg — a malformed AST shape must skip only
757
+ // this one function's CFG, never drop the whole file's language group (R4).
758
+ // eslint-disable-next-line no-console
759
+ console.warn(`[cfg] Swift buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
760
+ return undefined;
761
+ }
762
+ }
763
+ /**
764
+ * The function's body `statements` node. A `function_declaration` / `init_` /
765
+ * `deinit_declaration` wraps it in a `function_body`; a `lambda_literal` carries
766
+ * the `statements` directly (after an optional `lambda_function_type` + `in`).
767
+ */
768
+ function bodyStatementsOf(fnNode) {
769
+ const fb = fnNode.childForFieldName('body') ??
770
+ fnNode.namedChildren.find((c) => c.type === 'function_body');
771
+ if (fb && fb.type === 'function_body') {
772
+ return fb.namedChildren.find((c) => c.type === 'statements');
773
+ }
774
+ if (fnNode.type === 'lambda_literal') {
775
+ return fnNode.namedChildren.find((c) => c.type === 'statements');
776
+ }
777
+ return undefined;
778
+ }
779
+ /** Whether a node is a Swift function/closure this visitor builds a CFG for. */
780
+ function isFunction(node) {
781
+ return SWIFT_FUNCTION_TYPES.has(node.type);
782
+ }
783
+ /** The Swift CFG visitor. */
784
+ export function createSwiftCfgVisitor() {
785
+ return { buildFunctionCfg, isFunction };
786
+ }
787
+ export { SWIFT_FUNCTION_TYPES };