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,867 @@
1
+ import { CfgBuilder } from '../cfg-builder.js';
2
+ import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
3
+ import { CsharpHarvester } from './csharp-harvest.js';
4
+ /** C# node types that own a CFG-bearing function body. */
5
+ const CSHARP_FUNCTION_TYPES = new Set([
6
+ 'method_declaration',
7
+ 'local_function_statement',
8
+ 'constructor_declaration',
9
+ 'lambda_expression',
10
+ 'anonymous_method_expression',
11
+ ]);
12
+ /** Statement node types that break a basic block (everything else coalesces). */
13
+ const CONTROL_FLOW_TYPES = new Set([
14
+ 'if_statement',
15
+ 'while_statement',
16
+ 'do_statement',
17
+ 'for_statement',
18
+ 'foreach_statement',
19
+ 'switch_statement',
20
+ 'try_statement',
21
+ 'using_statement',
22
+ 'lock_statement',
23
+ 'return_statement',
24
+ 'break_statement',
25
+ 'continue_statement',
26
+ 'throw_statement',
27
+ 'goto_statement',
28
+ 'labeled_statement',
29
+ 'yield_statement',
30
+ 'block',
31
+ ]);
32
+ const startLineOf = (n) => n.startPosition.row + 1;
33
+ const endLineOf = (n) => n.endPosition.row + 1;
34
+ /**
35
+ * Per-function C# walk state. One instance per function so the
36
+ * {@link ControlFlowContext}, exception-handler stack, and label tables are
37
+ * scoped to that function and never leak across functions.
38
+ */
39
+ class CsharpCfgWalk {
40
+ builder;
41
+ harvest;
42
+ cfc = new ControlFlowContext();
43
+ /** Stack of exception-handler entry blocks (catch/finally) a `throw` jumps to. */
44
+ handlers = [];
45
+ /** label name → its `labeled_statement` body's entry block (resolved on demand). */
46
+ labelBlocks = new Map();
47
+ /** Pending gotos to a label not yet seen: label → list of source blocks. */
48
+ pendingGotos = new Map();
49
+ constructor(builder, harvest) {
50
+ this.builder = builder;
51
+ this.harvest = harvest;
52
+ }
53
+ /** Statements of a block node, ignoring comments. */
54
+ statementsOf(block) {
55
+ return block.namedChildren.filter((c) => c.type !== 'comment');
56
+ }
57
+ /** The `body` block of a node (field, or the first `block` child). */
58
+ bodyBlockOf(node) {
59
+ return node.childForFieldName('body') ?? node.namedChildren.find((c) => c.type === 'block');
60
+ }
61
+ /** Visit a body that may be a `block` or a single statement. */
62
+ visitBody(node) {
63
+ return this.builder.withNesting(() => {
64
+ if (!node)
65
+ return null;
66
+ if (node.type === 'block')
67
+ return this.visitSeq(this.statementsOf(node));
68
+ return this.visitStmt(node);
69
+ });
70
+ }
71
+ /** Wire a sequence of statements, coalescing straight-line runs into blocks. */
72
+ visitSeq(stmts) {
73
+ return this.builder.withNesting(() => {
74
+ let entry;
75
+ let dangling = [];
76
+ let openSimple;
77
+ for (let i = 0; i < stmts.length; i++) {
78
+ const stmt = stmts[i];
79
+ if (this.isUsingLocalDecl(stmt)) {
80
+ // `using var f = e;` (C# 8 declaration form): the dispose runs at the END
81
+ // of the enclosing scope, so the REST of this sequence is the protected
82
+ // body. The acquisition (`var f = e`) is a normal block OUTSIDE the dispose
83
+ // region (if it throws, the resource was never acquired) (#2206).
84
+ openSimple = undefined;
85
+ const acq = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
86
+ if (entry === undefined)
87
+ entry = acq;
88
+ else
89
+ this.builder.connect(dangling, acq, 'seq');
90
+ const scope = this.buildUsingDeclScope(stmt, stmts.slice(i + 1));
91
+ this.builder.connect([acq], scope.entry, 'seq');
92
+ dangling = [...scope.exits];
93
+ break; // the rest of the sequence is consumed by the dispose scope
94
+ }
95
+ if (this.breaksBlock(stmt)) {
96
+ openSimple = undefined; // close any open straight-line block
97
+ const res = this.visitStmt(stmt);
98
+ if (res === null)
99
+ continue; // transparent (empty nested block)
100
+ if (entry === undefined)
101
+ entry = res.entry;
102
+ else
103
+ this.builder.connect(dangling, res.entry, 'seq');
104
+ dangling = [...res.exits];
105
+ }
106
+ else {
107
+ if (openSimple === undefined) {
108
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
109
+ if (entry === undefined)
110
+ entry = idx;
111
+ else
112
+ this.builder.connect(dangling, idx, 'seq');
113
+ openSimple = idx;
114
+ dangling = [idx];
115
+ }
116
+ else {
117
+ this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
118
+ }
119
+ }
120
+ }
121
+ if (entry === undefined)
122
+ return null;
123
+ return { entry, exits: dangling };
124
+ });
125
+ }
126
+ /**
127
+ * Whether a statement breaks the current straight-line block. Adds the
128
+ * value-position switch carrier to the base {@link CONTROL_FLOW_TYPES} set: a
129
+ * `local_declaration_statement` whose single initializer is a modelable
130
+ * `switch_expression` (`var x = k switch {…}`, #2207) breaks so `visitStmt`
131
+ * models the arms as control flow instead of collapsing the decl to one block.
132
+ */
133
+ breaksBlock(stmt) {
134
+ if (this.isValueSwitchDecl(stmt))
135
+ return true;
136
+ return CONTROL_FLOW_TYPES.has(stmt.type);
137
+ }
138
+ /** Dispatch one statement to its handler. Non-null except for empty blocks. */
139
+ visitStmt(stmt) {
140
+ switch (stmt.type) {
141
+ case 'local_declaration_statement': {
142
+ // `var x = k switch { … }` (#2207): the initializer is a value-position
143
+ // branch — model it as control flow and bind the result on the rejoin.
144
+ const branch = this.declValueSwitch(stmt);
145
+ if (branch)
146
+ return this.visitBindBranch(stmt, branch);
147
+ return this.visitSimple(stmt);
148
+ }
149
+ case 'if_statement':
150
+ return this.visitIf(stmt);
151
+ case 'while_statement':
152
+ return this.visitWhile(stmt);
153
+ case 'do_statement':
154
+ return this.visitDoWhile(stmt);
155
+ case 'for_statement':
156
+ return this.visitFor(stmt);
157
+ case 'foreach_statement':
158
+ return this.visitForEach(stmt);
159
+ case 'switch_statement':
160
+ return this.visitSwitch(stmt);
161
+ case 'try_statement':
162
+ return this.visitTry(stmt);
163
+ case 'using_statement':
164
+ return this.visitUsing(stmt);
165
+ case 'lock_statement':
166
+ return this.visitLock(stmt);
167
+ case 'return_statement':
168
+ return this.visitReturn(stmt);
169
+ case 'throw_statement':
170
+ return this.visitThrow(stmt);
171
+ case 'break_statement':
172
+ return this.visitBreak(stmt);
173
+ case 'continue_statement':
174
+ return this.visitContinue(stmt);
175
+ case 'goto_statement':
176
+ return this.visitGoto(stmt);
177
+ case 'labeled_statement':
178
+ return this.visitLabeled(stmt);
179
+ case 'yield_statement':
180
+ return this.visitYield(stmt);
181
+ case 'block':
182
+ return this.visitSeq(this.statementsOf(stmt));
183
+ default:
184
+ return this.visitSimple(stmt);
185
+ }
186
+ }
187
+ visitSimple(stmt) {
188
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
189
+ return { entry: idx, exits: [idx] };
190
+ }
191
+ visitReturn(stmt) {
192
+ // `return k switch { … };` (#2207): the returned value is a value-position
193
+ // branch — model it as control flow, with each arm returning (its value IS
194
+ // the function result), threading every active finalizer per arm.
195
+ const branch = stmt.namedChildren.find((c) => c.type !== 'comment');
196
+ if (branch && this.isModelableValueBranch(branch)) {
197
+ const res = this.visitBranchExpr(branch);
198
+ const finalizers = this.cfc.finalizersForReturn();
199
+ for (const ex of res.exits) {
200
+ wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
201
+ }
202
+ return { entry: res.entry, exits: [] };
203
+ }
204
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
205
+ // A return crosses EVERY active finally (try/using/lock) before EXIT.
206
+ wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
207
+ return { entry: idx, exits: [] };
208
+ }
209
+ visitThrow(stmt) {
210
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
211
+ this.builder.edge(idx, this.currentHandler(), 'throw');
212
+ return { entry: idx, exits: [] };
213
+ }
214
+ /** `yield return e;` continues; `yield break;` terminates the iterator. */
215
+ visitYield(stmt) {
216
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
217
+ // `yield break;` (no value child) ends iteration → EXIT (modeled like return);
218
+ // `yield return e;` (has a value child) yields one element and CONTINUES, so
219
+ // it falls through to the next statement. The hidden iterator state machine
220
+ // is not modeled (documented limitation).
221
+ if (this.isYieldBreak(stmt)) {
222
+ wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
223
+ return { entry: idx, exits: [] };
224
+ }
225
+ return { entry: idx, exits: [idx] };
226
+ }
227
+ /** A `yield break;` has no value child; `yield return e;` has one. */
228
+ isYieldBreak(stmt) {
229
+ return stmt.namedChildCount === 0;
230
+ }
231
+ visitBreak(stmt) {
232
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
233
+ const res = this.cfc.resolveBreak(undefined);
234
+ const { target, finalizers } = res ?? {
235
+ target: this.builder.exitIndex,
236
+ finalizers: this.cfc.finalizersForReturn(),
237
+ };
238
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
239
+ return { entry: idx, exits: [] };
240
+ }
241
+ visitContinue(stmt) {
242
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
243
+ const res = this.cfc.resolveContinue(undefined);
244
+ const { target, finalizers } = res ?? {
245
+ target: this.builder.exitIndex,
246
+ finalizers: this.cfc.finalizersForReturn(),
247
+ };
248
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
249
+ return { entry: idx, exits: [] };
250
+ }
251
+ /** `goto label;` — route to the label block if known, else defer / EXIT. */
252
+ visitGoto(stmt) {
253
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
254
+ const label = this.gotoLabel(stmt);
255
+ if (label === undefined) {
256
+ // `goto case L;` / `goto default;` — no statically-labeled CFG target.
257
+ this.builder.edge(idx, this.builder.exitIndex, 'seq');
258
+ return { entry: idx, exits: [] };
259
+ }
260
+ const target = this.labelBlocks.get(label);
261
+ if (target !== undefined) {
262
+ this.builder.edge(idx, target, 'seq'); // backward goto: label already built
263
+ }
264
+ else {
265
+ const list = this.pendingGotos.get(label);
266
+ if (list)
267
+ list.push(idx);
268
+ else
269
+ this.pendingGotos.set(label, [idx]);
270
+ }
271
+ return { entry: idx, exits: [] };
272
+ }
273
+ visitLabeled(stmt) {
274
+ // `labeled_statement` = label `identifier` + the labeled statement.
275
+ const labelNode = stmt.namedChildren.find((c) => c.type === 'identifier');
276
+ const label = labelNode?.text;
277
+ const body = stmt.namedChildren.find((c) => c.id !== labelNode?.id && c.type !== 'comment');
278
+ const res = this.visitBody(body ?? null);
279
+ if (label !== undefined) {
280
+ const entry = res?.entry ?? this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
281
+ this.labelBlocks.set(label, entry);
282
+ const pending = this.pendingGotos.get(label);
283
+ if (pending) {
284
+ for (const from of pending)
285
+ this.builder.edge(from, entry, 'seq');
286
+ this.pendingGotos.delete(label);
287
+ }
288
+ if (!res)
289
+ return { entry, exits: [entry] };
290
+ }
291
+ return res;
292
+ }
293
+ visitIf(stmt) {
294
+ const cond = stmt.childForFieldName('condition') ?? stmt;
295
+ const condBlock = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
296
+ const exits = [];
297
+ const thenRes = this.visitBody(stmt.childForFieldName('consequence'));
298
+ if (thenRes) {
299
+ this.builder.edge(condBlock, thenRes.entry, 'cond-true');
300
+ exits.push(...thenRes.exits);
301
+ }
302
+ else {
303
+ exits.push(condBlock); // empty then — true path falls through
304
+ }
305
+ // No `else_clause` wrapper in C#: `alternative` is the else body or the
306
+ // nested `if_statement` for an `else if` chain directly.
307
+ const elseNode = stmt.childForFieldName('alternative');
308
+ if (elseNode) {
309
+ const elseRes = this.visitBody(elseNode);
310
+ if (elseRes) {
311
+ this.builder.edge(condBlock, elseRes.entry, 'cond-false');
312
+ exits.push(...elseRes.exits);
313
+ }
314
+ else {
315
+ exits.push(condBlock);
316
+ }
317
+ }
318
+ else {
319
+ exits.push(condBlock); // no else — false path falls through to the join
320
+ }
321
+ return { entry: condBlock, exits: [...new Set(exits)] };
322
+ }
323
+ visitWhile(stmt) {
324
+ const cond = stmt.childForFieldName('condition') ?? stmt;
325
+ const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
326
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
327
+ this.cfc.pushLoop(header, loopExit);
328
+ const body = this.visitBody(this.bodyBlockOf(stmt));
329
+ this.cfc.pop();
330
+ if (body) {
331
+ this.builder.edge(header, body.entry, 'cond-true');
332
+ this.builder.connect(body.exits, header, 'loop-back');
333
+ }
334
+ else {
335
+ this.builder.edge(header, header, 'loop-back'); // empty body re-tests
336
+ }
337
+ // Always emit the structural exit edge — even `while (true)` keeps EXIT
338
+ // reverse-reachable for the post-dominator / CDG pass.
339
+ this.builder.edge(header, loopExit, 'cond-false');
340
+ return { entry: header, exits: [loopExit] };
341
+ }
342
+ visitDoWhile(stmt) {
343
+ const cond = stmt.childForFieldName('condition') ?? stmt;
344
+ const condBlock = this.builder.newBlock(startLineOf(cond), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
345
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
346
+ this.cfc.pushLoop(condBlock, loopExit);
347
+ const body = this.visitBody(this.bodyBlockOf(stmt));
348
+ this.cfc.pop();
349
+ const backTarget = body ? body.entry : condBlock;
350
+ if (body)
351
+ this.builder.connect(body.exits, condBlock, 'seq');
352
+ this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
353
+ this.builder.edge(condBlock, loopExit, 'cond-false');
354
+ return { entry: backTarget, exits: [loopExit] };
355
+ }
356
+ visitFor(stmt) {
357
+ const init = stmt.childForFieldName('initializer');
358
+ const cond = stmt.childForFieldName('condition');
359
+ const incr = stmt.childForFieldName('update');
360
+ const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? cond.text : 'for(;;)', 'normal', cond ? this.harvest.facts(cond) : undefined);
361
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
362
+ let incrBlock = header;
363
+ if (incr) {
364
+ incrBlock = this.builder.newBlock(startLineOf(incr), endLineOf(incr), incr.text, 'normal', this.harvest.facts(incr));
365
+ this.builder.edge(incrBlock, header, 'loop-back');
366
+ }
367
+ this.cfc.pushLoop(incrBlock, loopExit);
368
+ const body = this.visitBody(this.bodyBlockOf(stmt));
369
+ this.cfc.pop();
370
+ if (body) {
371
+ this.builder.edge(header, body.entry, 'cond-true');
372
+ this.builder.connect(body.exits, incrBlock, incr ? 'seq' : 'loop-back');
373
+ }
374
+ else {
375
+ this.builder.edge(header, incrBlock, 'cond-true');
376
+ if (!incr)
377
+ this.builder.edge(header, header, 'loop-back');
378
+ }
379
+ // Structural exit edge — `for (;;) {}` (no condition) still keeps EXIT
380
+ // reverse-reachable so CDG is not silently skipped for the function.
381
+ this.builder.edge(header, loopExit, 'cond-false');
382
+ let entry = header;
383
+ if (init) {
384
+ const initBlock = this.builder.newBlock(startLineOf(init), endLineOf(init), init.text, 'normal', this.harvest.facts(init));
385
+ this.builder.edge(initBlock, header, 'seq');
386
+ entry = initBlock;
387
+ }
388
+ return { entry, exits: [loopExit] };
389
+ }
390
+ visitForEach(stmt) {
391
+ // Header text is SYNTHESIZED, so facts come from the left/right nodes
392
+ // directly (the loop variable is a def, the iterated expression a use).
393
+ const header = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), this.forEachHeaderText(stmt), 'normal', this.harvest.forEachHeadFacts(stmt));
394
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
395
+ this.cfc.pushLoop(header, loopExit);
396
+ const body = this.visitBody(this.bodyBlockOf(stmt));
397
+ this.cfc.pop();
398
+ if (body) {
399
+ this.builder.edge(header, body.entry, 'cond-true');
400
+ this.builder.connect(body.exits, header, 'loop-back');
401
+ }
402
+ else {
403
+ this.builder.edge(header, header, 'loop-back');
404
+ }
405
+ this.builder.edge(header, loopExit, 'cond-false');
406
+ return { entry: header, exits: [loopExit] };
407
+ }
408
+ forEachHeaderText(stmt) {
409
+ const left = stmt.childForFieldName('left')?.text ?? '';
410
+ const right = stmt.childForFieldName('right')?.text ?? '';
411
+ return left || right ? `foreach(${left} in ${right})` : 'foreach(… in …)';
412
+ }
413
+ visitSwitch(stmt) {
414
+ const value = stmt.childForFieldName('value') ?? stmt;
415
+ const dispatch = this.builder.newBlock(startLineOf(stmt), endLineOf(value), value.text, 'normal', this.harvest.facts(value));
416
+ const switchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
417
+ this.cfc.pushSwitch(switchExit);
418
+ const body = stmt.childForFieldName('body');
419
+ const sections = body ? body.namedChildren.filter((c) => c.type === 'switch_section') : [];
420
+ // Each `switch_section`'s case-test patterns/`when` clauses evaluate before
421
+ // the body runs — harvest their uses (and any pattern binding as a may-def)
422
+ // onto the dispatch block, one record per section, conditionally (a later
423
+ // section test only runs when earlier sections didn't match).
424
+ for (const sec of sections) {
425
+ for (const test of this.sectionTests(sec)) {
426
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(test));
427
+ }
428
+ }
429
+ const sectionResults = sections.map((s) => this.visitSeq(this.sectionStatements(s)));
430
+ const hasDefault = sections.some((s) => this.sectionIsDefault(s));
431
+ const entryOf = new Array(sections.length);
432
+ let after = switchExit;
433
+ for (let i = sections.length - 1; i >= 0; i--) {
434
+ entryOf[i] = sectionResults[i]?.entry ?? after;
435
+ after = entryOf[i];
436
+ }
437
+ for (let i = 0; i < sections.length; i++) {
438
+ this.builder.edge(dispatch, entryOf[i], 'switch-case');
439
+ }
440
+ if (!hasDefault)
441
+ this.builder.edge(dispatch, switchExit, 'switch-case'); // no-match path
442
+ // A non-`break` section's dangling exits fall through to the next section
443
+ // (an empty `case 2:` falling into `case 3:`, or `goto case`-less spill).
444
+ for (let i = 0; i < sections.length; i++) {
445
+ const res = sectionResults[i];
446
+ if (!res)
447
+ continue;
448
+ const fallTarget = i + 1 < sections.length ? entryOf[i + 1] : switchExit;
449
+ this.builder.connect(res.exits, fallTarget, 'fallthrough');
450
+ }
451
+ this.cfc.pop();
452
+ return { entry: dispatch, exits: [switchExit] };
453
+ }
454
+ /** A switch_section's body statements (everything but its case labels/patterns). */
455
+ sectionStatements(section) {
456
+ return section.namedChildren.filter((c) => this.isStatementLike(c) && c.type !== 'comment');
457
+ }
458
+ /** The case-test expressions of a section (constant patterns + when clauses). */
459
+ sectionTests(section) {
460
+ const out = [];
461
+ for (const c of section.namedChildren) {
462
+ if (c.type === 'when_clause') {
463
+ const inner = c.namedChild(0);
464
+ if (inner)
465
+ out.push(inner);
466
+ }
467
+ else if (!this.isStatementLike(c) && c.type !== 'comment') {
468
+ out.push(c); // a pattern (constant_pattern / declaration_pattern / …)
469
+ }
470
+ }
471
+ return out;
472
+ }
473
+ /** A `default:` section has only a `default` label (no pattern). */
474
+ sectionIsDefault(section) {
475
+ return !section.namedChildren.some((c) => !this.isStatementLike(c) && c.type !== 'comment' && c.type !== 'when_clause');
476
+ }
477
+ /** Heuristic: a section child is statement-like if it ends a basic-block run. */
478
+ isStatementLike(node) {
479
+ return node.type.endsWith('_statement') || node.type === 'block';
480
+ }
481
+ // ── value-position switch expression (#2207) ────────────────────────────────
482
+ /**
483
+ * The `switch_expression` initializer of a single-declarator
484
+ * `local_declaration_statement` (`var x = k switch {…}`) when it is a modelable
485
+ * value branch, else undefined. A `using` decl and a multi-declarator decl are
486
+ * excluded (the `using` dispose path / multi-declarator stay inline).
487
+ */
488
+ declValueSwitch(stmt) {
489
+ if (stmt.type !== 'local_declaration_statement')
490
+ return undefined;
491
+ if (this.isUsingLocalDecl(stmt))
492
+ return undefined;
493
+ const decl = stmt.namedChildren.find((c) => c.type === 'variable_declaration');
494
+ if (!decl)
495
+ return undefined;
496
+ const declarators = decl.namedChildren.filter((c) => c.type === 'variable_declarator');
497
+ if (declarators.length !== 1)
498
+ return undefined;
499
+ const init = this.declaratorInit(declarators[0]);
500
+ return init && this.isModelableValueBranch(init) ? init : undefined;
501
+ }
502
+ isValueSwitchDecl(stmt) {
503
+ return this.declValueSwitch(stmt) !== undefined;
504
+ }
505
+ /**
506
+ * The initializer of a `variable_declarator` — its named child after `name`.
507
+ * NOTE: deliberately duplicated in `csharp-harvest.ts` (the harvester is a
508
+ * standalone class with no shared base — repo convention). The two copies must
509
+ * stay in sync; there is no C#-specific shared module to host it, and the only
510
+ * module both files share is the generic `utils/ast-helpers` (types only).
511
+ */
512
+ declaratorInit(declarator) {
513
+ const name = declarator.childForFieldName('name');
514
+ for (let i = 0; i < declarator.namedChildCount; i++) {
515
+ const c = declarator.namedChild(i);
516
+ if (c && c.id !== name?.id)
517
+ return c;
518
+ }
519
+ return undefined;
520
+ }
521
+ /**
522
+ * Whether `node` is a value-position branch worth modeling as control flow
523
+ * (#2207): a `switch_expression` (`k switch {…}`) with ≥2 arms — a real
524
+ * dispatch. C# value-position `if` does not exist (the ternary `?:` is excluded,
525
+ * like elvis in Kotlin).
526
+ */
527
+ isModelableValueBranch(node) {
528
+ if (node.type !== 'switch_expression')
529
+ return false;
530
+ return node.namedChildren.filter((c) => c.type === 'switch_expression_arm').length >= 2;
531
+ }
532
+ /**
533
+ * Model a value-position `switch_expression` (`k switch { p => v, … }`) as a CFG
534
+ * dispatch: a discriminant block, each arm's value expression a block reached by
535
+ * a `switch-case` edge, all arms rejoining at a single exit. The arm patterns /
536
+ * `when` guards are harvested as conditional uses on the dispatch (a later arm
537
+ * test runs only when earlier arms didn't match), mirroring {@link visitSwitch}.
538
+ */
539
+ visitSwitchExpr(node) {
540
+ const arms = node.namedChildren.filter((c) => c.type === 'switch_expression_arm');
541
+ const discriminant = node.namedChildren.find((c) => c.type !== 'switch_expression_arm') ?? node;
542
+ const dispatch = this.builder.newBlock(startLineOf(node), endLineOf(discriminant), discriminant.text, 'normal', this.harvest.facts(discriminant));
543
+ const switchExit = this.builder.newBlock(endLineOf(node), endLineOf(node), '');
544
+ let hasCatchAll = false;
545
+ for (const arm of arms) {
546
+ const pattern = arm.namedChild(0);
547
+ const guard = arm.namedChildren.find((c) => c.type === 'when_clause');
548
+ if (pattern)
549
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(pattern));
550
+ if (guard) {
551
+ const inner = guard.namedChild(0);
552
+ if (inner)
553
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(inner));
554
+ }
555
+ // An unguarded `_`/`var` arm matches everything — the exhaustive default.
556
+ if (!guard && pattern && (pattern.type === 'discard' || pattern.type === 'var_pattern')) {
557
+ hasCatchAll = true;
558
+ }
559
+ const value = this.armValue(arm);
560
+ const armBlock = this.builder.newBlock(startLineOf(value ?? arm), endLineOf(value ?? arm), (value ?? arm).text, 'normal', value ? this.harvest.facts(value) : undefined);
561
+ this.builder.edge(dispatch, armBlock, 'switch-case');
562
+ this.builder.edge(armBlock, switchExit, 'seq');
563
+ }
564
+ // A non-exhaustive switch throws at runtime; conservatively keep EXIT directly
565
+ // reachable from the dispatch when no catch-all arm covers the no-match path.
566
+ if (!hasCatchAll)
567
+ this.builder.edge(dispatch, switchExit, 'switch-case');
568
+ return { entry: dispatch, exits: [switchExit] };
569
+ }
570
+ /** The value expression of a `switch_expression_arm` (the child after `=>`). */
571
+ armValue(arm) {
572
+ // pattern [when_clause] => value — the value is the LAST named child.
573
+ return arm.namedChild(arm.namedChildCount - 1) ?? undefined;
574
+ }
575
+ /** Model a value-position branch as control flow (only `switch_expression`). */
576
+ visitBranchExpr(node) {
577
+ return this.visitSwitchExpr(node);
578
+ }
579
+ /**
580
+ * An expression-bodied member's value (`=> k switch {…}`, #2207): if it is a
581
+ * modelable value branch, model its arms as control flow (each arm returns the
582
+ * function result); otherwise return null so the caller falls back to a single
583
+ * inline block.
584
+ */
585
+ tryVisitValueBranchBody(expr) {
586
+ return this.isModelableValueBranch(expr) ? this.visitBranchExpr(expr) : null;
587
+ }
588
+ /**
589
+ * `var x = k switch { … }` (#2207): visit the switch as control flow, then
590
+ * rejoin its arms at a facts-only continuation carrying ONLY the bound name's
591
+ * def (the discriminant + arm-value uses are already on the switch's blocks).
592
+ * The arms are now control-dependent on the dispatch, and `x` is defined at the
593
+ * join — mirrors the Java / Kotlin / Rust value-position binding.
594
+ */
595
+ visitBindBranch(stmt, branch) {
596
+ const res = this.visitBranchExpr(branch);
597
+ const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.bindingDefFacts(stmt));
598
+ this.builder.connect(res.exits, cont, 'seq');
599
+ return { entry: res.entry, exits: [cont] };
600
+ }
601
+ visitTry(stmt) {
602
+ const bodyNode = stmt.childForFieldName('body');
603
+ const catchClauses = [];
604
+ let finallyClause;
605
+ for (let i = 0; i < stmt.namedChildCount; i++) {
606
+ const c = stmt.namedChild(i);
607
+ if (c?.type === 'catch_clause')
608
+ catchClauses.push(c);
609
+ else if (c?.type === 'finally_clause')
610
+ finallyClause = c;
611
+ }
612
+ const finallyBody = finallyClause?.namedChildren.find((c) => c.type === 'block');
613
+ return this.buildProtected(bodyNode ?? null, catchClauses, finallyBody ?? null, stmt);
614
+ }
615
+ /**
616
+ * `using (resource) body` — the dispose runs deterministically on BOTH normal
617
+ * AND exception exit, which is exactly `try { body } finally { dispose }`. We
618
+ * model the dispose as a synthesized finalizer block (the AST has no Dispose()
619
+ * call node), so a `return`/`break`/`continue` crossing it threads through and
620
+ * gets a `finally-*` completion edge.
621
+ */
622
+ visitUsing(stmt) {
623
+ const bodyNode = this.bodyBlockOf(stmt) ?? null;
624
+ // The resource (declaration or expression) is the named child before `body`.
625
+ const resource = this.usingResource(stmt);
626
+ const disposeFacts = resource ? this.harvest.facts(resource) : undefined;
627
+ return this.buildProtectedSynthetic(bodyNode, stmt, 'dispose', disposeFacts, resource ?? stmt);
628
+ }
629
+ /**
630
+ * `lock (obj) body` — the monitor release runs on both normal and exception
631
+ * exit (deterministic finalizer), same shape as `using`'s dispose.
632
+ */
633
+ visitLock(stmt) {
634
+ const bodyNode = this.bodyBlockOf(stmt) ?? null;
635
+ const lockObj = stmt.namedChildren.find((c) => c.type !== 'block');
636
+ const releaseFacts = lockObj ? this.harvest.facts(lockObj) : undefined;
637
+ return this.buildProtectedSynthetic(bodyNode, stmt, 'release', releaseFacts, lockObj ?? stmt);
638
+ }
639
+ /** The `using` resource node (variable_declaration or an expression). */
640
+ usingResource(stmt) {
641
+ const body = stmt.childForFieldName('body');
642
+ return stmt.namedChildren.find((c) => c.id !== body?.id && c.type !== 'comment');
643
+ }
644
+ /**
645
+ * Whether `stmt` is a C# 8 `using var f = …;` declaration (a
646
+ * `local_declaration_statement` with a leading `using` keyword) — the dispose
647
+ * runs at enclosing-scope exit, NOT a delimited block (#2206).
648
+ */
649
+ isUsingLocalDecl(stmt) {
650
+ if (stmt.type !== 'local_declaration_statement')
651
+ return false;
652
+ for (let i = 0; i < stmt.childCount; i++) {
653
+ if (stmt.child(i)?.type === 'using')
654
+ return true;
655
+ }
656
+ return false;
657
+ }
658
+ /**
659
+ * Build the dispose scope for a `using var f = …;` declaration: `restStmts` (the
660
+ * rest of the enclosing block) is the protected body, and a synthetic `dispose`
661
+ * finalizer runs on normal exit AND on exception, with a `return`/`break`/
662
+ * `continue` crossing it threading through (finally-* completion edges). Mirrors
663
+ * {@link buildProtectedSynthetic}, but the body is a statement LIST (#2206).
664
+ */
665
+ buildUsingDeclScope(declStmt, restStmts) {
666
+ const disposeBlock = this.builder.newBlock(endLineOf(declStmt), endLineOf(declStmt), 'dispose');
667
+ const finFrame = this.cfc.pushFinalizer(disposeBlock);
668
+ const protectedStart = this.builder.blockCount;
669
+ this.handlers.push(disposeBlock);
670
+ const bodyRes = this.visitSeq(restStmts);
671
+ this.handlers.pop();
672
+ for (let b = protectedStart; b < this.builder.blockCount; b++) {
673
+ this.builder.edge(b, disposeBlock, 'throw');
674
+ }
675
+ this.cfc.pop();
676
+ drainFinalizerPending(this.builder, finFrame, [disposeBlock]);
677
+ if (bodyRes)
678
+ this.builder.connect(bodyRes.exits, disposeBlock, 'seq');
679
+ this.builder.connect([disposeBlock], this.currentHandler(), 'throw');
680
+ return { entry: bodyRes?.entry ?? disposeBlock, exits: [disposeBlock] };
681
+ }
682
+ /**
683
+ * Shared try/catch/finally builder. `catchClauses` may be empty; `finallyBody`
684
+ * is the finally's `block` (or null). Models the TS `visitTry` semantics:
685
+ * normal completion of try AND catch flow through finally; a throw in the
686
+ * protected region routes to the handler; early exits crossing the finally
687
+ * thread through it (finally-* completion edges).
688
+ */
689
+ buildProtected(bodyNode, catchClauses, finallyBody, span) {
690
+ const finallyRes = finallyBody ? this.visitSeq(this.statementsOf(finallyBody)) : null;
691
+ const finFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
692
+ // Build each catch handler.
693
+ const catchEntries = [];
694
+ const catchExits = [];
695
+ let firstCatchEntry;
696
+ for (const clause of catchClauses) {
697
+ const clauseBody = clause.childForFieldName('body');
698
+ if (finallyRes)
699
+ this.handlers.push(finallyRes.entry);
700
+ let res = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
701
+ if (finallyRes)
702
+ this.handlers.pop();
703
+ if (res === null) {
704
+ // Empty `catch {}` still catches — synthesize one block so exception
705
+ // flow lands somewhere and the post-try code stays reachable.
706
+ const idx = this.builder.newBlock(startLineOf(clause), endLineOf(clause), '');
707
+ res = { entry: idx, exits: [idx] };
708
+ }
709
+ const paramFacts = this.harvest.catchParamFacts(clause);
710
+ if (paramFacts) {
711
+ const paramBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', paramFacts);
712
+ this.builder.edge(paramBlock, res.entry, 'seq');
713
+ res = { entry: paramBlock, exits: res.exits };
714
+ }
715
+ catchEntries.push(res.entry);
716
+ catchExits.push(...res.exits);
717
+ if (firstCatchEntry === undefined)
718
+ firstCatchEntry = res.entry;
719
+ }
720
+ // Handler for the try body: first catch if present, else finally, else outer.
721
+ const tryHandler = firstCatchEntry ?? finallyRes?.entry ?? this.currentHandler();
722
+ const protectedStart = this.builder.blockCount;
723
+ this.handlers.push(tryHandler);
724
+ const bodyRes = bodyNode
725
+ ? bodyNode.type === 'block'
726
+ ? this.visitSeq(this.statementsOf(bodyNode))
727
+ : this.visitStmt(bodyNode)
728
+ : null;
729
+ this.handlers.pop();
730
+ if (catchClauses.length > 0 || finallyBody) {
731
+ for (let b = protectedStart; b < this.builder.blockCount; b++) {
732
+ this.builder.edge(b, tryHandler, 'throw');
733
+ }
734
+ }
735
+ if (finFrame && finallyRes) {
736
+ this.cfc.pop();
737
+ drainFinalizerPending(this.builder, finFrame, finallyRes.exits);
738
+ }
739
+ const exits = [];
740
+ if (finallyRes) {
741
+ if (bodyRes)
742
+ this.builder.connect(bodyRes.exits, finallyRes.entry, 'seq');
743
+ for (const e of catchExits)
744
+ this.builder.edge(e, finallyRes.entry, 'seq');
745
+ exits.push(...finallyRes.exits);
746
+ // No catch → an exception re-propagates out after finally runs.
747
+ if (catchClauses.length === 0) {
748
+ this.builder.connect(finallyRes.exits, this.currentHandler(), 'throw');
749
+ }
750
+ }
751
+ else {
752
+ if (bodyRes)
753
+ exits.push(...bodyRes.exits);
754
+ exits.push(...catchExits);
755
+ }
756
+ const entry = bodyRes?.entry ?? finallyRes?.entry ?? catchEntries[0];
757
+ if (entry === undefined) {
758
+ void span;
759
+ return null;
760
+ }
761
+ return { entry, exits: [...new Set(exits)] };
762
+ }
763
+ /**
764
+ * `using`/`lock`: a protected body whose finalizer is a SYNTHESIZED single
765
+ * block (Dispose() / Monitor.Exit() have no AST node). The finalizer runs on
766
+ * both normal and exception exit, and crossing jumps thread through it.
767
+ */
768
+ buildProtectedSynthetic(bodyNode, span, text, finalizerFacts, factsNode) {
769
+ void factsNode;
770
+ const finalizerBlock = this.builder.newBlock(endLineOf(span), endLineOf(span), text, 'normal', finalizerFacts ?? undefined);
771
+ const finRes = { entry: finalizerBlock, exits: [finalizerBlock] };
772
+ const finFrame = this.cfc.pushFinalizer(finRes.entry);
773
+ const protectedStart = this.builder.blockCount;
774
+ // The finalizer IS the handler — an exception in the body still runs dispose,
775
+ // which then re-propagates to the outer handler.
776
+ this.handlers.push(finRes.entry);
777
+ const bodyRes = bodyNode
778
+ ? bodyNode.type === 'block'
779
+ ? this.visitSeq(this.statementsOf(bodyNode))
780
+ : this.visitStmt(bodyNode)
781
+ : null;
782
+ this.handlers.pop();
783
+ for (let b = protectedStart; b < this.builder.blockCount; b++) {
784
+ this.builder.edge(b, finRes.entry, 'throw');
785
+ }
786
+ this.cfc.pop();
787
+ drainFinalizerPending(this.builder, finFrame, finRes.exits);
788
+ // Normal completion of the body flows through the finalizer; the finalizer's
789
+ // exit re-propagates an exception to the outer handler (it had no catch).
790
+ if (bodyRes)
791
+ this.builder.connect(bodyRes.exits, finRes.entry, 'seq');
792
+ this.builder.connect(finRes.exits, this.currentHandler(), 'throw');
793
+ const entry = bodyRes?.entry ?? finRes.entry;
794
+ return { entry, exits: [...finRes.exits] };
795
+ }
796
+ /** Nearest enclosing exception handler, or the function EXIT. */
797
+ currentHandler() {
798
+ return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
799
+ }
800
+ /** The target label of a `goto label;` (undefined for `goto case`/`goto default`). */
801
+ gotoLabel(stmt) {
802
+ const id = stmt.namedChildren.find((c) => c.type === 'identifier');
803
+ return id?.text;
804
+ }
805
+ }
806
+ /** Build the CFG for one C# function node, or `undefined` if not modelable. */
807
+ function buildFunctionCfg(fnNode, filePath) {
808
+ try {
809
+ if (!CSHARP_FUNCTION_TYPES.has(fnNode.type))
810
+ return undefined;
811
+ const startLine = startLineOf(fnNode);
812
+ const endLine = endLineOf(fnNode);
813
+ const startColumn = fnNode.startPosition.column;
814
+ // The body is a `block` (field `body`) OR an `arrow_expression_clause`
815
+ // (expression-bodied member) OR an expression (single-expression lambda).
816
+ const body = fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'block');
817
+ if (!body)
818
+ return undefined; // abstract / partial / interface member — no body
819
+ const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
820
+ const harvest = new CsharpHarvester(fnNode);
821
+ const paramFacts = harvest.paramFacts();
822
+ if (paramFacts)
823
+ builder.attachFacts(builder.entryIndex, paramFacts);
824
+ if (body.type === 'arrow_expression_clause' || body.type !== 'block') {
825
+ // Expression-bodied member / single-expression lambda: one block whose
826
+ // value is returned. For an arrow clause the value is its inner expression.
827
+ const expr = body.type === 'arrow_expression_clause' ? (body.namedChild(0) ?? body) : body;
828
+ // `=> k switch { … }` (#2207): model the arms as control flow, each arm
829
+ // returning the function result, instead of one inline block.
830
+ const branchRes = new CsharpCfgWalk(builder, harvest).tryVisitValueBranchBody(expr);
831
+ if (branchRes) {
832
+ builder.edge(builder.entryIndex, branchRes.entry, 'seq');
833
+ builder.connect(branchRes.exits, builder.exitIndex, 'return');
834
+ return builder.finish(harvest.bindingTable());
835
+ }
836
+ const blk = builder.newBlock(startLineOf(expr), endLineOf(expr), expr.text, 'normal', harvest.facts(expr));
837
+ builder.edge(builder.entryIndex, blk, 'seq');
838
+ builder.edge(blk, builder.exitIndex, 'return');
839
+ return builder.finish(harvest.bindingTable());
840
+ }
841
+ const walk = new CsharpCfgWalk(builder, harvest);
842
+ const res = walk.visitSeq(body.namedChildren.filter((c) => c.type !== 'comment'));
843
+ if (!res) {
844
+ builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
845
+ return builder.finish(harvest.bindingTable());
846
+ }
847
+ builder.edge(builder.entryIndex, res.entry, 'seq');
848
+ builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
849
+ return builder.finish(harvest.bindingTable());
850
+ }
851
+ catch (err) {
852
+ // Never throw out of buildFunctionCfg — a malformed AST shape must skip only
853
+ // this one function's CFG, never drop the whole file's language group (R4).
854
+ // eslint-disable-next-line no-console
855
+ console.warn(`[cfg] C# buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
856
+ return undefined;
857
+ }
858
+ }
859
+ /** Whether a node is a C# function this visitor builds a CFG for. */
860
+ function isFunction(node) {
861
+ return CSHARP_FUNCTION_TYPES.has(node.type);
862
+ }
863
+ /** The C# CFG visitor. */
864
+ export function createCsharpCfgVisitor() {
865
+ return { buildFunctionCfg, isFunction };
866
+ }
867
+ export { CSHARP_FUNCTION_TYPES };