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,721 @@
1
+ import { CfgBuilder } from '../cfg-builder.js';
2
+ import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
3
+ import { PhpHarvester } from './php-harvest.js';
4
+ /** PHP node types that own a CFG-bearing function body. */
5
+ const PHP_FUNCTION_TYPES = new Set([
6
+ 'function_definition',
7
+ 'method_declaration',
8
+ 'anonymous_function',
9
+ 'arrow_function',
10
+ ]);
11
+ /** Statement node types that break a basic block (everything else coalesces). */
12
+ const CONTROL_FLOW_TYPES = new Set([
13
+ 'if_statement',
14
+ 'for_statement',
15
+ 'foreach_statement',
16
+ 'while_statement',
17
+ 'do_statement',
18
+ 'switch_statement',
19
+ 'try_statement',
20
+ 'return_statement',
21
+ 'break_statement',
22
+ 'continue_statement',
23
+ 'goto_statement',
24
+ 'named_label_statement',
25
+ 'compound_statement',
26
+ ]);
27
+ const startLineOf = (n) => n.startPosition.row + 1;
28
+ const endLineOf = (n) => n.endPosition.row + 1;
29
+ const isComment = (n) => n.type === 'comment';
30
+ /**
31
+ * Per-function PHP walk state. One instance per function so the
32
+ * {@link ControlFlowContext}, exception-handler stack, and the `break N` /
33
+ * `continue N` synthetic-label bookkeeping are scoped to that function and never
34
+ * leak across functions.
35
+ */
36
+ class PhpCfgWalk {
37
+ builder;
38
+ harvest;
39
+ cfc = new ControlFlowContext();
40
+ /** Stack of exception-handler entry blocks (catch / finally) a `throw` jumps to. */
41
+ handlers = [];
42
+ /**
43
+ * Synthetic labels of the active loop/switch frames, innermost LAST — so the
44
+ * N-th enclosing frame's label is `loopLabels[length - N]`. PHP's `break N` /
45
+ * `continue N` resolve against these (no source labels exist).
46
+ */
47
+ loopLabels = [];
48
+ labelSeq = 0;
49
+ constructor(builder, harvest) {
50
+ this.builder = builder;
51
+ this.harvest = harvest;
52
+ }
53
+ /** Statements of a body node, ignoring comments. */
54
+ statementsOf(block) {
55
+ return block.namedChildren.filter((c) => !isComment(c));
56
+ }
57
+ /** The `body` block of a node (a `compound_statement` / `colon_block` / stmt). */
58
+ bodyBlockOf(node) {
59
+ return node.childForFieldName('body') ?? undefined;
60
+ }
61
+ /** Strip a `parenthesized_expression` wrapper (PHP `if`/`while` conditions). */
62
+ unwrapParen(node) {
63
+ if (node.type === 'parenthesized_expression') {
64
+ const inner = node.namedChildren.find((c) => !isComment(c));
65
+ if (inner)
66
+ return inner;
67
+ }
68
+ return node;
69
+ }
70
+ /** Visit a body that may be a block-ish container or a single statement. */
71
+ visitBody(node) {
72
+ return this.builder.withNesting(() => {
73
+ if (!node)
74
+ return null;
75
+ if (node.type === 'compound_statement' || node.type === 'colon_block') {
76
+ return this.visitSeq(this.statementsOf(node));
77
+ }
78
+ return this.visitStmt(node);
79
+ });
80
+ }
81
+ /** Wire a sequence of statements, coalescing straight-line runs into blocks. */
82
+ visitSeq(stmts) {
83
+ return this.builder.withNesting(() => {
84
+ let entry;
85
+ let dangling = [];
86
+ let openSimple;
87
+ for (const stmt of stmts) {
88
+ // An `expression_statement` wrapping a bare `throw_expression` is a
89
+ // terminator (PHP has no `throw_statement` node), so it breaks the block.
90
+ // An `$x = match($v) {…}` value-position assignment breaks too (#2207).
91
+ const breaks = CONTROL_FLOW_TYPES.has(stmt.type) ||
92
+ this.isThrowStatement(stmt) ||
93
+ this.isValueBranchAssignment(stmt);
94
+ if (breaks) {
95
+ openSimple = undefined; // close any open straight-line block
96
+ const res = this.visitStmt(stmt);
97
+ if (res === null)
98
+ continue; // transparent (empty nested block)
99
+ if (entry === undefined)
100
+ entry = res.entry;
101
+ else
102
+ this.builder.connect(dangling, res.entry, 'seq');
103
+ dangling = [...res.exits];
104
+ }
105
+ else {
106
+ if (openSimple === undefined) {
107
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
108
+ if (entry === undefined)
109
+ entry = idx;
110
+ else
111
+ this.builder.connect(dangling, idx, 'seq');
112
+ openSimple = idx;
113
+ dangling = [idx];
114
+ }
115
+ else {
116
+ this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
117
+ }
118
+ }
119
+ }
120
+ if (entry === undefined)
121
+ return null;
122
+ return { entry, exits: dangling };
123
+ });
124
+ }
125
+ /** Dispatch one statement to its handler. Non-null except for empty blocks. */
126
+ visitStmt(stmt) {
127
+ if (this.isThrowStatement(stmt))
128
+ return this.visitThrow(stmt);
129
+ // `$x = match($v) { … };` (#2207): model the match arms as control flow and
130
+ // bind the assignment target on the rejoin.
131
+ const assign = this.assignmentBranch(stmt);
132
+ if (assign)
133
+ return this.visitBindAssign(stmt, assign.expr, assign.match);
134
+ switch (stmt.type) {
135
+ case 'if_statement':
136
+ return this.visitIf(stmt);
137
+ case 'for_statement':
138
+ return this.visitFor(stmt);
139
+ case 'foreach_statement':
140
+ return this.visitForEach(stmt);
141
+ case 'while_statement':
142
+ return this.visitWhile(stmt);
143
+ case 'do_statement':
144
+ return this.visitDoWhile(stmt);
145
+ case 'switch_statement':
146
+ return this.visitSwitch(stmt);
147
+ case 'try_statement':
148
+ return this.visitTry(stmt);
149
+ case 'return_statement':
150
+ return this.visitReturn(stmt);
151
+ case 'break_statement':
152
+ return this.visitBreak(stmt);
153
+ case 'continue_statement':
154
+ return this.visitContinue(stmt);
155
+ case 'compound_statement':
156
+ case 'colon_block':
157
+ return this.visitSeq(this.statementsOf(stmt));
158
+ case 'goto_statement':
159
+ case 'named_label_statement':
160
+ // `goto` / labels are modeled as straight-line blocks (no jump edge — see
161
+ // the visitor limitations); they still carry their text + facts.
162
+ return this.visitSimple(stmt);
163
+ default:
164
+ return this.visitSimple(stmt);
165
+ }
166
+ }
167
+ /** True for an `expression_statement` whose value is a bare `throw_expression`. */
168
+ isThrowStatement(stmt) {
169
+ if (stmt.type !== 'expression_statement')
170
+ return false;
171
+ const inner = stmt.namedChildren.find((c) => !isComment(c));
172
+ return inner?.type === 'throw_expression';
173
+ }
174
+ visitSimple(stmt) {
175
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
176
+ return { entry: idx, exits: [idx] };
177
+ }
178
+ visitReturn(stmt) {
179
+ // `return match($v) { … };` (#2207): the returned value is a value-position
180
+ // branch — model it as control flow, with each arm returning (its value IS
181
+ // the function result), threading every active finally per arm.
182
+ const branch = stmt.namedChildren.find((c) => !isComment(c));
183
+ if (branch && this.isModelableValueBranch(branch)) {
184
+ const res = this.visitBranchExpr(branch);
185
+ const finalizers = this.cfc.finalizersForReturn();
186
+ for (const ex of res.exits) {
187
+ wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
188
+ }
189
+ return { entry: res.entry, exits: [] };
190
+ }
191
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
192
+ // A return crosses EVERY active finally before EXIT.
193
+ wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
194
+ return { entry: idx, exits: [] };
195
+ }
196
+ visitThrow(stmt) {
197
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
198
+ this.builder.edge(idx, this.currentHandler(), 'throw');
199
+ return { entry: idx, exits: [] };
200
+ }
201
+ visitBreak(stmt) {
202
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
203
+ const label = this.jumpLabel(stmt);
204
+ const res = this.cfc.resolveBreak(label);
205
+ const { target, finalizers } = res ?? {
206
+ target: this.builder.exitIndex,
207
+ finalizers: this.cfc.finalizersForReturn(),
208
+ };
209
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
210
+ return { entry: idx, exits: [] };
211
+ }
212
+ visitContinue(stmt) {
213
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
214
+ const label = this.jumpLabel(stmt);
215
+ const res = this.cfc.resolveContinue(label);
216
+ const { target, finalizers } = res ?? {
217
+ target: this.builder.exitIndex,
218
+ finalizers: this.cfc.finalizersForReturn(),
219
+ };
220
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
221
+ return { entry: idx, exits: [] };
222
+ }
223
+ /**
224
+ * Resolve a `break N` / `continue N` to the SYNTHETIC label of the N-th
225
+ * enclosing loop/switch frame (innermost = 1). Returns undefined for a bare
226
+ * `break`/`continue` (no level), so the context resolves the nearest frame as
227
+ * usual. PHP counts BOTH loop AND switch frames for `break N` and `continue N`
228
+ * (a `switch` acts like a loop level for `continue`), which is exactly the set
229
+ * pushed onto {@link loopLabels} here — so one count serves both.
230
+ */
231
+ jumpLabel(stmt) {
232
+ const level = this.jumpLevel(stmt);
233
+ if (level <= 1)
234
+ return undefined; // bare break/continue → nearest frame
235
+ const n = this.loopLabels.length;
236
+ if (level > n)
237
+ return undefined; // over-deep level → conservative fallback
238
+ return this.loopLabels[n - level];
239
+ }
240
+ /** The integer level of a `break N;` / `continue N;` (default 1). */
241
+ jumpLevel(stmt) {
242
+ const intNode = stmt.namedChildren.find((c) => c.type === 'integer');
243
+ if (!intNode)
244
+ return 1;
245
+ const v = parseInt(intNode.text, 10);
246
+ return Number.isFinite(v) && v >= 1 ? v : 1;
247
+ }
248
+ /** Push a fresh synthetic loop/switch label and return it. */
249
+ nextLabel() {
250
+ const label = `__php_lvl_${this.labelSeq++}`;
251
+ return label;
252
+ }
253
+ /**
254
+ * `if cond: … elseif cond: … else: …`. PHP has NO nested-if else chain: the
255
+ * `if_statement` carries the condition + body plus zero-or-more `alternative`
256
+ * fields, each an `else_if_clause` (its own condition + body) or a trailing
257
+ * `else_clause`. The elif chain is threaded on the `cond-false` edge. Handles
258
+ * both brace bodies and the colon (`endif`) syntax uniformly (body field).
259
+ */
260
+ visitIf(stmt) {
261
+ const cond = this.condOf(stmt) ?? stmt;
262
+ const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
263
+ const exits = [];
264
+ const thenRes = this.visitBody(stmt.childForFieldName('body'));
265
+ if (thenRes) {
266
+ this.builder.edge(header, thenRes.entry, 'cond-true');
267
+ exits.push(...thenRes.exits);
268
+ }
269
+ else {
270
+ exits.push(header); // empty then — true path falls through
271
+ }
272
+ const alternatives = this.alternativesOf(stmt);
273
+ let falseFrom = header;
274
+ for (const alt of alternatives) {
275
+ if (alt.type === 'else_if_clause') {
276
+ const elifCondRaw = alt.childForFieldName('condition');
277
+ const elifCond = elifCondRaw ? this.unwrapParen(elifCondRaw) : alt;
278
+ const elifHeader = this.builder.newBlock(startLineOf(alt), endLineOf(elifCond), elifCond.text, 'normal', this.harvest.facts(elifCond));
279
+ this.builder.edge(falseFrom, elifHeader, 'cond-false');
280
+ const elifRes = this.visitBody(alt.childForFieldName('body'));
281
+ if (elifRes) {
282
+ this.builder.edge(elifHeader, elifRes.entry, 'cond-true');
283
+ exits.push(...elifRes.exits);
284
+ }
285
+ else {
286
+ exits.push(elifHeader);
287
+ }
288
+ falseFrom = elifHeader;
289
+ }
290
+ else if (alt.type === 'else_clause') {
291
+ const elseRes = this.visitBody(alt.childForFieldName('body'));
292
+ if (elseRes) {
293
+ this.builder.edge(falseFrom, elseRes.entry, 'cond-false');
294
+ exits.push(...elseRes.exits);
295
+ }
296
+ else {
297
+ exits.push(falseFrom);
298
+ }
299
+ falseFrom = -1; // an else consumes the false path entirely
300
+ }
301
+ }
302
+ if (falseFrom >= 0)
303
+ exits.push(falseFrom); // no trailing else → fall through
304
+ return { entry: header, exits: [...new Set(exits)] };
305
+ }
306
+ /** The `alternative`-field children of an `if_statement`, in source order. */
307
+ alternativesOf(stmt) {
308
+ const out = [];
309
+ for (let i = 0; i < stmt.childCount; i++) {
310
+ if (stmt.fieldNameForChild(i) === 'alternative') {
311
+ const c = stmt.child(i);
312
+ if (c)
313
+ out.push(c);
314
+ }
315
+ }
316
+ return out;
317
+ }
318
+ /** The (paren-unwrapped) condition expression of an if/while/do/switch. */
319
+ condOf(stmt) {
320
+ const cond = stmt.childForFieldName('condition');
321
+ return cond ? this.unwrapParen(cond) : undefined;
322
+ }
323
+ visitWhile(stmt) {
324
+ const label = this.nextLabel();
325
+ const cond = this.condOf(stmt) ?? stmt;
326
+ const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
327
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
328
+ this.loopLabels.push(label);
329
+ this.cfc.pushLoop(header, loopExit, [label]);
330
+ const body = this.visitBody(this.bodyBlockOf(stmt));
331
+ this.cfc.pop();
332
+ this.loopLabels.pop();
333
+ if (body) {
334
+ this.builder.edge(header, body.entry, 'cond-true');
335
+ this.builder.connect(body.exits, header, 'loop-back');
336
+ }
337
+ else {
338
+ this.builder.edge(header, header, 'loop-back'); // empty body re-tests
339
+ }
340
+ // Always emit the structural exit edge — even `while (true)` keeps EXIT
341
+ // reverse-reachable for the post-dominator / CDG pass.
342
+ this.builder.edge(header, loopExit, 'cond-false');
343
+ return { entry: header, exits: [loopExit] };
344
+ }
345
+ visitDoWhile(stmt) {
346
+ const label = this.nextLabel();
347
+ const cond = this.condOf(stmt) ?? stmt;
348
+ const condBlock = this.builder.newBlock(startLineOf(cond), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
349
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
350
+ this.loopLabels.push(label);
351
+ this.cfc.pushLoop(condBlock, loopExit, [label]);
352
+ const body = this.visitBody(this.bodyBlockOf(stmt));
353
+ this.cfc.pop();
354
+ this.loopLabels.pop();
355
+ const backTarget = body ? body.entry : condBlock;
356
+ if (body)
357
+ this.builder.connect(body.exits, condBlock, 'seq');
358
+ this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
359
+ this.builder.edge(condBlock, loopExit, 'cond-false');
360
+ return { entry: backTarget, exits: [loopExit] };
361
+ }
362
+ visitFor(stmt) {
363
+ const label = this.nextLabel();
364
+ const init = stmt.childForFieldName('initialize');
365
+ const cond = stmt.childForFieldName('condition');
366
+ const incr = stmt.childForFieldName('update');
367
+ const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? cond.text : 'for(;;)', 'normal', cond ? this.harvest.facts(cond) : undefined);
368
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
369
+ let incrBlock = header;
370
+ if (incr) {
371
+ incrBlock = this.builder.newBlock(startLineOf(incr), endLineOf(incr), incr.text, 'normal', this.harvest.facts(incr));
372
+ this.builder.edge(incrBlock, header, 'loop-back');
373
+ }
374
+ this.loopLabels.push(label);
375
+ this.cfc.pushLoop(incrBlock, loopExit, [label]);
376
+ const body = this.visitBody(this.bodyBlockOf(stmt));
377
+ this.cfc.pop();
378
+ this.loopLabels.pop();
379
+ if (body) {
380
+ this.builder.edge(header, body.entry, 'cond-true');
381
+ this.builder.connect(body.exits, incrBlock, incr ? 'seq' : 'loop-back');
382
+ }
383
+ else {
384
+ this.builder.edge(header, incrBlock, 'cond-true');
385
+ if (!incr)
386
+ this.builder.edge(header, header, 'loop-back');
387
+ }
388
+ // Structural exit edge — `for (;;) {}` (no condition) still keeps EXIT
389
+ // reverse-reachable so CDG is not silently skipped for the function.
390
+ this.builder.edge(header, loopExit, 'cond-false');
391
+ let entry = header;
392
+ if (init) {
393
+ const initBlock = this.builder.newBlock(startLineOf(init), endLineOf(init), init.text, 'normal', this.harvest.facts(init));
394
+ this.builder.edge(initBlock, header, 'seq');
395
+ entry = initBlock;
396
+ }
397
+ return { entry, exits: [loopExit] };
398
+ }
399
+ visitForEach(stmt) {
400
+ const label = this.nextLabel();
401
+ // Header text is SYNTHESIZED, so facts come from the iterable (use) + the
402
+ // loop target variable(s) (def) directly.
403
+ const header = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), this.forEachHeaderText(stmt), 'normal', this.harvest.foreachHeadFacts(stmt));
404
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
405
+ this.loopLabels.push(label);
406
+ this.cfc.pushLoop(header, loopExit, [label]);
407
+ const body = this.visitBody(this.bodyBlockOf(stmt));
408
+ this.cfc.pop();
409
+ this.loopLabels.pop();
410
+ if (body) {
411
+ this.builder.edge(header, body.entry, 'cond-true');
412
+ this.builder.connect(body.exits, header, 'loop-back');
413
+ }
414
+ else {
415
+ this.builder.edge(header, header, 'loop-back');
416
+ }
417
+ this.builder.edge(header, loopExit, 'cond-false');
418
+ return { entry: header, exits: [loopExit] };
419
+ }
420
+ forEachHeaderText(stmt) {
421
+ const first = stmt.namedChild(0);
422
+ return first ? `foreach(${first.text} as …)` : 'foreach(… as …)';
423
+ }
424
+ visitSwitch(stmt) {
425
+ const label = this.nextLabel();
426
+ const value = this.condOf(stmt) ?? stmt;
427
+ const dispatch = this.builder.newBlock(startLineOf(stmt), endLineOf(value), value.text, 'normal', this.harvest.facts(value));
428
+ const switchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
429
+ this.loopLabels.push(label);
430
+ this.cfc.pushSwitch(switchExit, [label]);
431
+ const body = stmt.childForFieldName('body');
432
+ // A `switch_block` holds `case_statement`s (field `value`, fall through) and a
433
+ // `default_statement`.
434
+ const groups = body
435
+ ? body.namedChildren.filter((c) => c.type === 'case_statement' || c.type === 'default_statement')
436
+ : [];
437
+ // Each case-test expression evaluates before its body runs — harvest its uses
438
+ // onto the dispatch block, CONDITIONALLY (a later case test only runs when
439
+ // earlier cases didn't match).
440
+ for (const g of groups) {
441
+ const test = this.caseTest(g);
442
+ if (test)
443
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(test));
444
+ }
445
+ const groupResults = groups.map((g) => this.visitSeq(this.caseStatements(g)));
446
+ const hasDefault = groups.some((g) => g.type === 'default_statement');
447
+ const entryOf = new Array(groups.length);
448
+ let after = switchExit;
449
+ for (let i = groups.length - 1; i >= 0; i--) {
450
+ entryOf[i] = groupResults[i]?.entry ?? after;
451
+ after = entryOf[i];
452
+ }
453
+ for (let i = 0; i < groups.length; i++) {
454
+ this.builder.edge(dispatch, entryOf[i], 'switch-case');
455
+ }
456
+ if (!hasDefault)
457
+ this.builder.edge(dispatch, switchExit, 'switch-case'); // no-match path
458
+ // C-style FALLTHROUGH: a case with no break/return falls through to the next.
459
+ for (let i = 0; i < groups.length; i++) {
460
+ const res = groupResults[i];
461
+ if (!res)
462
+ continue;
463
+ const fallTarget = i + 1 < groups.length ? entryOf[i + 1] : switchExit;
464
+ this.builder.connect(res.exits, fallTarget, 'fallthrough');
465
+ }
466
+ this.cfc.pop();
467
+ this.loopLabels.pop();
468
+ return { entry: dispatch, exits: [switchExit] };
469
+ }
470
+ /** A switch group's body statements (everything but its case-test value). */
471
+ caseStatements(group) {
472
+ const value = group.childForFieldName('value');
473
+ return group.namedChildren.filter((c) => c.id !== value?.id && !isComment(c));
474
+ }
475
+ /** The case-test value expression of a `case_statement` (default has none). */
476
+ caseTest(group) {
477
+ return group.childForFieldName('value') ?? undefined;
478
+ }
479
+ // ── value-position match expression (#2207) ─────────────────────────────────
480
+ /**
481
+ * The `{expr, match}` of an `$x = match($v) {…}` value-position assignment
482
+ * carrier, or undefined. `expr` is the `assignment_expression` (for the target
483
+ * def); `match` is the modelable `match_expression` RHS. Only a plain `=`
484
+ * assignment qualifies (an augmented `??=` etc. is not a value-branch bind).
485
+ */
486
+ assignmentBranch(stmt) {
487
+ if (stmt.type !== 'expression_statement')
488
+ return undefined;
489
+ const expr = stmt.namedChildren.find((c) => !isComment(c));
490
+ if (!expr || expr.type !== 'assignment_expression')
491
+ return undefined;
492
+ const right = expr.childForFieldName('right');
493
+ return right && this.isModelableValueBranch(right) ? { expr, match: right } : undefined;
494
+ }
495
+ /** Whether a statement is an `$x = match(…) {…}` value-branch assignment. */
496
+ isValueBranchAssignment(stmt) {
497
+ return this.assignmentBranch(stmt) !== undefined;
498
+ }
499
+ /**
500
+ * Whether `node` is a value-position branch worth modeling as control flow
501
+ * (#2207): a `match_expression` with ≥2 arms — a real dispatch. PHP `match` is
502
+ * the only value-position branch (there is no `if`-expression); the ternary
503
+ * `?:` is deliberately excluded, like elvis in Kotlin.
504
+ */
505
+ isModelableValueBranch(node) {
506
+ if (node.type !== 'match_expression')
507
+ return false;
508
+ const block = node.childForFieldName('body');
509
+ if (!block)
510
+ return false;
511
+ return (block.namedChildren.filter((c) => c.type === 'match_conditional_expression' || c.type === 'match_default_expression').length >= 2);
512
+ }
513
+ /** Model a value-position branch as control flow (only `match_expression`). */
514
+ visitBranchExpr(node) {
515
+ return this.visitMatch(node);
516
+ }
517
+ /**
518
+ * Model a value-position `match($v) { c => v, default => v }` as a CFG dispatch:
519
+ * a discriminant block, each arm's value expression a block reached by a
520
+ * `switch-case` edge, all arms rejoining at one exit (no fallthrough — `match`
521
+ * never falls through). The arm condition lists are harvested as conditional
522
+ * uses on the dispatch (a later arm test runs only when earlier arms missed).
523
+ */
524
+ visitMatch(node) {
525
+ const condRaw = node.childForFieldName('condition');
526
+ const cond = condRaw ? this.unwrapParen(condRaw) : node;
527
+ const dispatch = this.builder.newBlock(startLineOf(node), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
528
+ const matchExit = this.builder.newBlock(endLineOf(node), endLineOf(node), '');
529
+ const block = node.childForFieldName('body');
530
+ const arms = block
531
+ ? block.namedChildren.filter((c) => c.type === 'match_conditional_expression' || c.type === 'match_default_expression')
532
+ : [];
533
+ let hasDefault = false;
534
+ for (const arm of arms) {
535
+ const condList = arm.namedChildren.find((c) => c.type === 'match_condition_list');
536
+ if (condList)
537
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(condList));
538
+ if (arm.type === 'match_default_expression')
539
+ hasDefault = true;
540
+ const value = this.matchArmValue(arm);
541
+ const armBlock = this.builder.newBlock(startLineOf(value ?? arm), endLineOf(value ?? arm), (value ?? arm).text, 'normal', value ? this.harvest.facts(value) : undefined);
542
+ this.builder.edge(dispatch, armBlock, 'switch-case');
543
+ this.builder.edge(armBlock, matchExit, 'seq');
544
+ }
545
+ // `match` with no `default` throws `\UnhandledMatchError` on no match; keep
546
+ // EXIT reachable via a conservative no-match edge when no default arm exists.
547
+ if (!hasDefault)
548
+ this.builder.edge(dispatch, matchExit, 'switch-case');
549
+ return { entry: dispatch, exits: [matchExit] };
550
+ }
551
+ /** The value (result) expression of a match arm — its LAST named child. */
552
+ matchArmValue(arm) {
553
+ const named = arm.namedChildren.filter((c) => !isComment(c));
554
+ return named[named.length - 1];
555
+ }
556
+ /**
557
+ * `$x = match($v) { … }` (#2207): visit the match as control flow, then rejoin
558
+ * its arms at a facts-only continuation carrying ONLY the LHS target def (the
559
+ * condition + arm-value uses are already on the match's blocks). The arms are
560
+ * now control-dependent on the dispatch — mirrors the Ruby value-branch assign.
561
+ */
562
+ visitBindAssign(stmt, assignExpr, branch) {
563
+ const res = this.visitBranchExpr(branch);
564
+ const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.assignmentDefFacts(assignExpr));
565
+ this.builder.connect(res.exits, cont, 'seq');
566
+ return { entry: res.entry, exits: [cont] };
567
+ }
568
+ /**
569
+ * try / catch / finally. A `finally` runs on BOTH normal and exception exit —
570
+ * a `return`/`break`/`continue` crossing it threads through it (`finally-*`
571
+ * completion edges).
572
+ */
573
+ visitTry(stmt) {
574
+ const bodyNode = stmt.childForFieldName('body');
575
+ const catchClauses = [];
576
+ let finallyClause;
577
+ for (let i = 0; i < stmt.namedChildCount; i++) {
578
+ const c = stmt.namedChild(i);
579
+ if (c?.type === 'catch_clause')
580
+ catchClauses.push(c);
581
+ else if (c?.type === 'finally_clause')
582
+ finallyClause = c;
583
+ }
584
+ const finallyBody = finallyClause?.childForFieldName('body');
585
+ return this.buildProtected(bodyNode ?? null, catchClauses, finallyBody ?? null);
586
+ }
587
+ /**
588
+ * Shared try/catch/finally builder (mirrors the Java visitor). `catchClauses`
589
+ * may be empty; `finallyBody` is the explicit finally's body (or null).
590
+ *
591
+ * Normal completion of try AND catch flows through the finally; a throw in the
592
+ * protected region routes to the handler; early exits crossing the finally
593
+ * thread through it (`finally-*` completion edges).
594
+ */
595
+ buildProtected(bodyNode, catchClauses, finallyBody) {
596
+ const finallyRes = finallyBody ? this.visitBody(finallyBody) : null;
597
+ const finFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
598
+ const finalizerEntry = finallyRes?.entry;
599
+ // Build each catch handler.
600
+ const catchEntries = [];
601
+ const catchExits = [];
602
+ let firstCatchEntry;
603
+ for (const clause of catchClauses) {
604
+ const clauseBody = clause.childForFieldName('body');
605
+ if (finalizerEntry !== undefined)
606
+ this.handlers.push(finalizerEntry);
607
+ let res = clauseBody ? this.visitBody(clauseBody) : null;
608
+ if (finalizerEntry !== undefined)
609
+ this.handlers.pop();
610
+ if (res === null) {
611
+ // Empty `catch {}` still catches — synthesize one block so exception flow
612
+ // lands somewhere and the post-try code stays reachable.
613
+ const idx = this.builder.newBlock(startLineOf(clause), endLineOf(clause), '');
614
+ res = { entry: idx, exits: [idx] };
615
+ }
616
+ const paramFacts = this.harvest.catchParamFacts(clause);
617
+ if (paramFacts) {
618
+ const paramBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', paramFacts);
619
+ this.builder.edge(paramBlock, res.entry, 'seq');
620
+ res = { entry: paramBlock, exits: res.exits };
621
+ }
622
+ catchEntries.push(res.entry);
623
+ catchExits.push(...res.exits);
624
+ if (firstCatchEntry === undefined)
625
+ firstCatchEntry = res.entry;
626
+ }
627
+ // Handler for the try body: first catch if present, else the finally, else
628
+ // the outer handler.
629
+ const tryHandler = firstCatchEntry ?? finalizerEntry ?? this.currentHandler();
630
+ const protectedStart = this.builder.blockCount;
631
+ this.handlers.push(tryHandler);
632
+ const bodyRes = bodyNode ? this.visitBody(bodyNode) : null;
633
+ this.handlers.pop();
634
+ if (catchClauses.length > 0 || finalizerEntry !== undefined) {
635
+ for (let b = protectedStart; b < this.builder.blockCount; b++) {
636
+ this.builder.edge(b, tryHandler, 'throw');
637
+ }
638
+ }
639
+ // Pop the finalizer frame and drain its pending crossing-jump legs.
640
+ if (finFrame && finallyRes) {
641
+ this.cfc.pop();
642
+ drainFinalizerPending(this.builder, finFrame, finallyRes.exits);
643
+ }
644
+ const exits = [];
645
+ if (finalizerEntry !== undefined && finallyRes) {
646
+ if (bodyRes)
647
+ this.builder.connect(bodyRes.exits, finalizerEntry, 'seq');
648
+ for (const e of catchExits)
649
+ this.builder.edge(e, finalizerEntry, 'seq');
650
+ exits.push(...finallyRes.exits);
651
+ // No catch → an exception re-propagates out after the finally runs.
652
+ if (catchClauses.length === 0) {
653
+ this.builder.connect(finallyRes.exits, this.currentHandler(), 'throw');
654
+ }
655
+ }
656
+ else {
657
+ if (bodyRes)
658
+ exits.push(...bodyRes.exits);
659
+ exits.push(...catchExits);
660
+ }
661
+ const entry = bodyRes?.entry ?? finalizerEntry ?? catchEntries[0];
662
+ if (entry === undefined)
663
+ return null;
664
+ return { entry, exits: [...new Set(exits)] };
665
+ }
666
+ /** Nearest enclosing exception handler, or the function EXIT. */
667
+ currentHandler() {
668
+ return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
669
+ }
670
+ }
671
+ /** Build the CFG for one PHP function node, or `undefined` if not modelable. */
672
+ function buildFunctionCfg(fnNode, filePath) {
673
+ try {
674
+ if (!PHP_FUNCTION_TYPES.has(fnNode.type))
675
+ return undefined;
676
+ const startLine = startLineOf(fnNode);
677
+ const endLine = endLineOf(fnNode);
678
+ const startColumn = fnNode.startPosition.column;
679
+ const body = fnNode.childForFieldName('body');
680
+ if (!body)
681
+ return undefined; // abstract / interface method — no body
682
+ const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
683
+ const harvest = new PhpHarvester(fnNode);
684
+ const paramFacts = harvest.paramFacts();
685
+ if (paramFacts)
686
+ builder.attachFacts(builder.entryIndex, paramFacts);
687
+ if (fnNode.type === 'arrow_function' || body.type !== 'compound_statement') {
688
+ // `fn($x) => expr` — the body is an EXPRESSION (no block): one block whose
689
+ // value is returned.
690
+ const blk = builder.newBlock(startLineOf(body), endLineOf(body), body.text, 'normal', harvest.facts(body));
691
+ builder.edge(builder.entryIndex, blk, 'seq');
692
+ builder.edge(blk, builder.exitIndex, 'return');
693
+ return builder.finish(harvest.bindingTable());
694
+ }
695
+ const walk = new PhpCfgWalk(builder, harvest);
696
+ const res = walk.visitSeq(body.namedChildren.filter((c) => c.type !== 'comment'));
697
+ if (!res) {
698
+ builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
699
+ return builder.finish(harvest.bindingTable());
700
+ }
701
+ builder.edge(builder.entryIndex, res.entry, 'seq');
702
+ builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
703
+ return builder.finish(harvest.bindingTable());
704
+ }
705
+ catch (err) {
706
+ // Never throw out of buildFunctionCfg — a malformed AST shape must skip only
707
+ // this one function's CFG, never drop the whole file's language group (R4).
708
+ // eslint-disable-next-line no-console
709
+ console.warn(`[cfg] PHP buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
710
+ return undefined;
711
+ }
712
+ }
713
+ /** Whether a node is a PHP function this visitor builds a CFG for. */
714
+ function isFunction(node) {
715
+ return PHP_FUNCTION_TYPES.has(node.type);
716
+ }
717
+ /** The PHP CFG visitor. */
718
+ export function createPhpCfgVisitor() {
719
+ return { buildFunctionCfg, isFunction };
720
+ }
721
+ export { PHP_FUNCTION_TYPES };