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.
- package/README.md +3 -3
- package/dist/_shared/graph/types.d.ts +31 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +11 -1
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +6 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +31 -1
- package/dist/cli/ai-context.js +18 -16
- package/dist/cli/analyze-config.js +11 -0
- package/dist/cli/analyze.d.ts +14 -0
- package/dist/cli/analyze.js +98 -0
- package/dist/cli/embedding-dims.d.ts +33 -0
- package/dist/cli/embedding-dims.js +39 -0
- package/dist/cli/eval-server.d.ts +8 -0
- package/dist/cli/eval-server.js +242 -2
- package/dist/cli/help-i18n.js +19 -0
- package/dist/cli/i18n/en.d.ts +15 -1
- package/dist/cli/i18n/en.js +15 -1
- package/dist/cli/i18n/resources.d.ts +29 -1
- package/dist/cli/i18n/zh-CN.d.ts +14 -0
- package/dist/cli/i18n/zh-CN.js +15 -1
- package/dist/cli/index.js +89 -1
- package/dist/cli/mcp.d.ts +6 -1
- package/dist/cli/mcp.js +26 -1
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/setup.d.ts +3 -1
- package/dist/cli/setup.js +67 -17
- package/dist/cli/skill-gen.js +2 -1
- package/dist/cli/tool.d.ts +19 -1
- package/dist/cli/tool.js +96 -3
- package/dist/core/embeddings/hf-env.d.ts +0 -66
- package/dist/core/embeddings/http-client.d.ts +7 -0
- package/dist/core/embeddings/http-client.js +5 -3
- package/dist/core/embeddings/server-mapping.d.ts +1 -1
- package/dist/core/embeddings/server-mapping.js +7 -3
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +5 -1
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +22 -4
- package/dist/core/group/extractors/include-extractor.js +22 -13
- package/dist/core/group/sync.d.ts +27 -1
- package/dist/core/group/sync.js +0 -0
- package/dist/core/incremental/subgraph-extract.js +18 -1
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
- package/dist/core/ingestion/cfg/collect.d.ts +51 -0
- package/dist/core/ingestion/cfg/collect.js +77 -0
- package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
- package/dist/core/ingestion/cfg/control-dependence.js +182 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
- package/dist/core/ingestion/cfg/emit.d.ts +293 -0
- package/dist/core/ingestion/cfg/emit.js +646 -0
- package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
- package/dist/core/ingestion/cfg/post-dominators.js +176 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
- package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
- package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +274 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
- package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
- package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
- package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
- package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/go.js +638 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
- package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
- package/dist/core/ingestion/cfg/visitors/java.js +812 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
- package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
- package/dist/core/ingestion/cfg/visitors/php.js +721 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
- package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
- package/dist/core/ingestion/cfg/visitors/python.js +558 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
- package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
- package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
- package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
- package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
- package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
- package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
- package/dist/core/ingestion/import-resolvers/utils.js +2 -0
- package/dist/core/ingestion/language-provider.d.ts +11 -0
- package/dist/core/ingestion/languages/c-cpp.js +6 -1
- package/dist/core/ingestion/languages/cobol.js +4 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
- package/dist/core/ingestion/languages/cpp/captures.js +50 -1
- package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
- package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
- package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
- package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
- package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
- package/dist/core/ingestion/languages/csharp.js +2 -0
- package/dist/core/ingestion/languages/dart.js +2 -0
- package/dist/core/ingestion/languages/go.js +2 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +3 -0
- package/dist/core/ingestion/languages/php.js +2 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/ruby.js +2 -0
- package/dist/core/ingestion/languages/rust.js +2 -0
- package/dist/core/ingestion/languages/swift.js +2 -0
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/languages/vue.js +6 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/mro-processor.js +109 -0
- package/dist/core/ingestion/parsing-processor.js +23 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/index.js +2 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
- package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
- package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
- package/dist/core/ingestion/pipeline.d.ts +92 -10
- package/dist/core/ingestion/pipeline.js +11 -2
- package/dist/core/ingestion/scope-extractor.js +12 -2
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
- package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
- package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
- package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
- package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
- package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
- package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
- package/dist/core/ingestion/taint/call-summary-model.js +25 -0
- package/dist/core/ingestion/taint/emit.d.ts +125 -0
- package/dist/core/ingestion/taint/emit.js +208 -0
- package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
- package/dist/core/ingestion/taint/interproc-emit.js +88 -0
- package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
- package/dist/core/ingestion/taint/interproc-solver.js +312 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +659 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
- package/dist/core/ingestion/taint/source-sink-config.js +35 -12
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
- package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
- package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
- package/dist/core/ingestion/taint/summary-harvest.js +537 -0
- package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
- package/dist/core/ingestion/taint/summary-model.js +86 -0
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
- package/dist/core/ingestion/utils/env.d.ts +7 -0
- package/dist/core/ingestion/utils/env.js +12 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
- package/dist/core/ingestion/workers/parse-worker.js +76 -17
- package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
- package/dist/core/ingestion/workers/worker-pool.js +15 -2
- package/dist/core/lbug/csv-generator.d.ts +51 -3
- package/dist/core/lbug/csv-generator.js +369 -255
- package/dist/core/lbug/lbug-adapter.d.ts +58 -18
- package/dist/core/lbug/lbug-adapter.js +346 -84
- package/dist/core/lbug/lbug-config.d.ts +19 -0
- package/dist/core/lbug/lbug-config.js +29 -0
- package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
- package/dist/core/lbug/pdg-emit-sink.js +358 -0
- package/dist/core/lbug/pool-adapter.d.ts +35 -0
- package/dist/core/lbug/pool-adapter.js +90 -1
- package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
- package/dist/core/lbug/rel-pair-routing.js +140 -0
- package/dist/core/lbug/schema.d.ts +2 -2
- package/dist/core/lbug/schema.js +3 -0
- package/dist/core/logger.d.ts +0 -5
- package/dist/core/run-analyze.d.ts +96 -0
- package/dist/core/run-analyze.js +207 -3
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/core/wiki/generator.js +3 -1
- package/dist/core/wiki/graph-queries.d.ts +6 -0
- package/dist/core/wiki/graph-queries.js +9 -1
- package/dist/mcp/http-transport.d.ts +134 -0
- package/dist/mcp/http-transport.js +503 -0
- package/dist/mcp/local/local-backend.d.ts +110 -24
- package/dist/mcp/local/local-backend.js +1225 -19
- package/dist/mcp/local/pdg-impact.d.ts +384 -0
- package/dist/mcp/local/pdg-impact.js +1972 -0
- package/dist/mcp/resources.js +13 -1
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.js +214 -7
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/api.d.ts +17 -0
- package/dist/server/api.js +132 -168
- package/dist/server/git-clone.d.ts +43 -2
- package/dist/server/git-clone.js +211 -16
- package/dist/server/mcp-http.d.ts +5 -6
- package/dist/server/mcp-http.js +9 -82
- package/dist/server/middleware.d.ts +40 -0
- package/dist/server/middleware.js +92 -0
- package/dist/server/private-ip.d.ts +1 -0
- package/dist/server/private-ip.js +15 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +37 -0
- package/dist/server/upload-paths.js +57 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/git.js +5 -8
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +41 -10
- package/dist/storage/repo-manager.d.ts +121 -10
- package/dist/storage/repo-manager.js +43 -5
- package/dist/types/pipeline.d.ts +9 -0
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
- package/hooks/claude/gitnexus-hook.cjs +110 -13
- package/hooks/claude/hook-db-lock-probe.cjs +513 -26
- package/package.json +8 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/scripts/cross-platform-tests.ts +1 -0
- package/skills/gitnexus-debugging.md +16 -4
- package/skills/gitnexus-exploring.md +3 -3
- package/skills/gitnexus-guide.md +33 -0
- package/skills/gitnexus-pdg-query.md +89 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/skills/gitnexus-taint-analysis.md +178 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
- package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
- package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
- package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
- package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
- package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
- package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
- package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
- package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
- package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
- package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
- package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
- package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
- package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
- package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
- package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
- package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
- package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
- package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
- package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
- package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
- package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
- package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
- package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
- package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
- package/web/assets/index-BvYev1UJ.js +635 -0
- package/web/assets/index-_lgn7hs5.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
- package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
- package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
- package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
- package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
- package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
- package/web/index.html +13 -15
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
- package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
- package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
- package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
- package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
- package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
- package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
- package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
- package/web/assets/chunk-CilyBKbf.js +0 -1
- package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
- package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
- package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
- package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
- package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
- package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
- package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
- package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
- package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
- package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
- package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
- package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
- package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
- package/web/assets/index-BKWA-m7o.css +0 -2
- package/web/assets/index-CG6q8eTs.js +0 -626
- package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
- /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
|
|
3
|
+
import { DartHarvester } from './dart-harvest.js';
|
|
4
|
+
/** Signature node types whose SIBLING `function_body` owns a CFG-bearing body. */
|
|
5
|
+
const DART_SIGNATURE_TYPES = new Set([
|
|
6
|
+
'function_signature',
|
|
7
|
+
'method_signature',
|
|
8
|
+
'getter_signature',
|
|
9
|
+
'setter_signature',
|
|
10
|
+
'constructor_signature',
|
|
11
|
+
'factory_constructor_signature',
|
|
12
|
+
]);
|
|
13
|
+
/** Statement node types that break a basic block (everything else coalesces). */
|
|
14
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
15
|
+
'if_statement',
|
|
16
|
+
'for_statement',
|
|
17
|
+
'while_statement',
|
|
18
|
+
'do_statement',
|
|
19
|
+
'switch_statement',
|
|
20
|
+
'try_statement',
|
|
21
|
+
'return_statement',
|
|
22
|
+
'break_statement',
|
|
23
|
+
'continue_statement',
|
|
24
|
+
'assert_statement',
|
|
25
|
+
]);
|
|
26
|
+
/** Comment node types tree-sitter-dart surfaces. */
|
|
27
|
+
const COMMENT_TYPES = new Set(['comment', 'documentation_comment']);
|
|
28
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
29
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
30
|
+
const isComment = (n) => COMMENT_TYPES.has(n.type);
|
|
31
|
+
/** Whether an `expression_statement` is a bare `throw …;`. */
|
|
32
|
+
const isThrowStatement = (n) => n.type === 'expression_statement' && n.namedChildren.some((c) => c.type === 'throw_expression');
|
|
33
|
+
/** Whether an `expression_statement` is a bare `rethrow;`. */
|
|
34
|
+
const isRethrowStatement = (n) => n.type === 'expression_statement' && n.namedChildren.some((c) => c.type === 'rethrow_expression');
|
|
35
|
+
/**
|
|
36
|
+
* Whether a node is a LABELED `continue LABEL;` (a switch fallthrough-spill),
|
|
37
|
+
* vs a bare `continue;` (which targets the enclosing loop). Only the labeled form
|
|
38
|
+
* is stripped from a case body and handled via {@link caseContinueLabel}.
|
|
39
|
+
*/
|
|
40
|
+
const isLabeledContinue = (n) => n.type === 'continue_statement' && n.namedChildren.some((c) => c.type === 'identifier');
|
|
41
|
+
/**
|
|
42
|
+
* Per-function Dart walk state. One instance per function so the
|
|
43
|
+
* {@link ControlFlowContext}, exception-handler stack, and labeled-frame
|
|
44
|
+
* bookkeeping are scoped to that function and never leak across functions.
|
|
45
|
+
*/
|
|
46
|
+
class DartCfgWalk {
|
|
47
|
+
builder;
|
|
48
|
+
harvest;
|
|
49
|
+
cfc = new ControlFlowContext();
|
|
50
|
+
/** Stack of exception-handler entry blocks (catch/finally) a `throw` jumps to. */
|
|
51
|
+
handlers = [];
|
|
52
|
+
/** Label(s) pending attachment to the NEXT pushed loop/switch frame. */
|
|
53
|
+
pendingLabels = [];
|
|
54
|
+
constructor(builder, harvest) {
|
|
55
|
+
this.builder = builder;
|
|
56
|
+
this.harvest = harvest;
|
|
57
|
+
}
|
|
58
|
+
/** Named statements of a `block`, ignoring comments. */
|
|
59
|
+
statementsOf(block) {
|
|
60
|
+
return block.namedChildren.filter((c) => !isComment(c));
|
|
61
|
+
}
|
|
62
|
+
/** Unwrap a body STMT: a `block` yields its statements; a bare statement is itself. */
|
|
63
|
+
visitBody(node) {
|
|
64
|
+
return this.builder.withNesting(() => {
|
|
65
|
+
if (!node)
|
|
66
|
+
return null;
|
|
67
|
+
if (node.type === 'block')
|
|
68
|
+
return this.visitSeq(this.statementsOf(node));
|
|
69
|
+
if (isComment(node))
|
|
70
|
+
return null;
|
|
71
|
+
return this.visitStmt(node);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
75
|
+
visitSeq(stmts) {
|
|
76
|
+
return this.builder.withNesting(() => {
|
|
77
|
+
let entry;
|
|
78
|
+
let dangling = [];
|
|
79
|
+
let openSimple;
|
|
80
|
+
for (const stmt of stmts) {
|
|
81
|
+
if (this.isControlFlow(stmt)) {
|
|
82
|
+
openSimple = undefined; // close any open straight-line block
|
|
83
|
+
const res = this.visitStmt(stmt);
|
|
84
|
+
if (res === null)
|
|
85
|
+
continue; // transparent (empty nested block / label-only)
|
|
86
|
+
if (entry === undefined)
|
|
87
|
+
entry = res.entry;
|
|
88
|
+
else
|
|
89
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
90
|
+
dangling = [...res.exits];
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (openSimple === undefined) {
|
|
94
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
95
|
+
if (entry === undefined)
|
|
96
|
+
entry = idx;
|
|
97
|
+
else
|
|
98
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
99
|
+
openSimple = idx;
|
|
100
|
+
dangling = [idx];
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (entry === undefined)
|
|
108
|
+
return null;
|
|
109
|
+
return { entry, exits: dangling };
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Whether a statement node breaks the current straight-line block. */
|
|
113
|
+
isControlFlow(stmt) {
|
|
114
|
+
if (this.isLabelError(stmt))
|
|
115
|
+
return true; // a stray label sibling — queue it
|
|
116
|
+
if (isThrowStatement(stmt) || isRethrowStatement(stmt))
|
|
117
|
+
return true;
|
|
118
|
+
// `var x = switch (v) { … }` (#2207): a value-position switch breaks so
|
|
119
|
+
// `visitStmt` models the arms as control flow instead of coalescing.
|
|
120
|
+
if (stmt.type === 'local_variable_declaration') {
|
|
121
|
+
const v = this.directValue(stmt);
|
|
122
|
+
return v !== undefined && this.isModelableValueBranch(v);
|
|
123
|
+
}
|
|
124
|
+
return CONTROL_FLOW_TYPES.has(stmt.type);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A labeled LOOP outside a switch (`outer: for …`) is mis-parsed by
|
|
128
|
+
* tree-sitter-dart as a stray `ERROR [identifier :]` SIBLING preceding the
|
|
129
|
+
* loop. Recognize that exact shape so the label still resolves a `break outer`.
|
|
130
|
+
*/
|
|
131
|
+
isLabelError(stmt) {
|
|
132
|
+
if (stmt.type !== 'ERROR')
|
|
133
|
+
return false;
|
|
134
|
+
const id = stmt.namedChildren.find((c) => c.type === 'identifier');
|
|
135
|
+
return id !== undefined && stmt.children.some((c) => c.type === ':');
|
|
136
|
+
}
|
|
137
|
+
/** Dispatch one statement to its handler. Non-null except for empty / label-only. */
|
|
138
|
+
visitStmt(stmt) {
|
|
139
|
+
if (this.isLabelError(stmt)) {
|
|
140
|
+
const id = stmt.namedChildren.find((c) => c.type === 'identifier');
|
|
141
|
+
if (id?.text)
|
|
142
|
+
this.pendingLabels = [...this.pendingLabels, id.text];
|
|
143
|
+
return null; // emits no block of its own
|
|
144
|
+
}
|
|
145
|
+
if (isThrowStatement(stmt))
|
|
146
|
+
return this.visitThrow(stmt);
|
|
147
|
+
if (isRethrowStatement(stmt))
|
|
148
|
+
return this.visitRethrow(stmt);
|
|
149
|
+
switch (stmt.type) {
|
|
150
|
+
case 'local_variable_declaration': {
|
|
151
|
+
// `var x = switch (v) { … }` (#2207): the value is a value-position
|
|
152
|
+
// branch — model it as control flow and bind the result on the rejoin.
|
|
153
|
+
const value = this.directValue(stmt);
|
|
154
|
+
if (value && this.isModelableValueBranch(value))
|
|
155
|
+
return this.visitBindBranch(stmt, value);
|
|
156
|
+
return this.visitSimple(stmt);
|
|
157
|
+
}
|
|
158
|
+
case 'if_statement':
|
|
159
|
+
return this.visitIf(stmt);
|
|
160
|
+
case 'for_statement':
|
|
161
|
+
return this.visitFor(stmt);
|
|
162
|
+
case 'while_statement':
|
|
163
|
+
return this.visitWhile(stmt);
|
|
164
|
+
case 'do_statement':
|
|
165
|
+
return this.visitDoWhile(stmt);
|
|
166
|
+
case 'switch_statement':
|
|
167
|
+
return this.visitSwitch(stmt);
|
|
168
|
+
case 'try_statement':
|
|
169
|
+
return this.visitTry(stmt);
|
|
170
|
+
case 'return_statement':
|
|
171
|
+
return this.visitReturn(stmt);
|
|
172
|
+
case 'break_statement':
|
|
173
|
+
return this.visitBreak(stmt);
|
|
174
|
+
case 'continue_statement':
|
|
175
|
+
return this.visitContinue(stmt);
|
|
176
|
+
case 'assert_statement':
|
|
177
|
+
return this.visitAssert(stmt);
|
|
178
|
+
case 'block':
|
|
179
|
+
return this.visitSeq(this.statementsOf(stmt));
|
|
180
|
+
default:
|
|
181
|
+
return this.visitSimple(stmt);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
visitSimple(stmt) {
|
|
185
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
186
|
+
return { entry: idx, exits: [idx] };
|
|
187
|
+
}
|
|
188
|
+
/** Take and clear the labels queued by a preceding label sibling. */
|
|
189
|
+
takeLabels() {
|
|
190
|
+
const labels = this.pendingLabels;
|
|
191
|
+
this.pendingLabels = [];
|
|
192
|
+
return labels;
|
|
193
|
+
}
|
|
194
|
+
// ── jumps (return / throw / rethrow / break / continue / assert) ──────────
|
|
195
|
+
/** `return [expr];` — threads through every active finalizer before EXIT. */
|
|
196
|
+
visitReturn(stmt) {
|
|
197
|
+
// `return switch (v) { … };` (#2207): the returned value is a value-position
|
|
198
|
+
// branch — model it as control flow, with each arm returning (its value IS
|
|
199
|
+
// the function result), threading every active finalizer per arm.
|
|
200
|
+
const branch = stmt.namedChildren.find((c) => !isComment(c));
|
|
201
|
+
if (branch && this.isModelableValueBranch(branch)) {
|
|
202
|
+
const res = this.visitBranchExpr(branch);
|
|
203
|
+
const finalizers = this.cfc.finalizersForReturn();
|
|
204
|
+
for (const ex of res.exits) {
|
|
205
|
+
wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
|
|
206
|
+
}
|
|
207
|
+
return { entry: res.entry, exits: [] };
|
|
208
|
+
}
|
|
209
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
210
|
+
wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
|
|
211
|
+
return { entry: idx, exits: [] };
|
|
212
|
+
}
|
|
213
|
+
/** `throw e;` — routes to the nearest enclosing handler (catch/finally), else EXIT. */
|
|
214
|
+
visitThrow(stmt) {
|
|
215
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
216
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
217
|
+
return { entry: idx, exits: [] };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* `rethrow;` — re-raises the current exception. Inside a handler it propagates
|
|
221
|
+
* past the CURRENT catch to the next-outer handler / EXIT, which is exactly
|
|
222
|
+
* what `currentHandler()` resolves to (the in-flight catch is not on the
|
|
223
|
+
* handler stack while its own body is walked). Terminates its block.
|
|
224
|
+
*/
|
|
225
|
+
visitRethrow(stmt) {
|
|
226
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
227
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
228
|
+
return { entry: idx, exits: [] };
|
|
229
|
+
}
|
|
230
|
+
visitBreak(stmt) {
|
|
231
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
232
|
+
const label = this.jumpLabel(stmt);
|
|
233
|
+
const res = this.cfc.resolveBreak(label);
|
|
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 label = this.jumpLabel(stmt);
|
|
244
|
+
const res = this.cfc.resolveContinue(label);
|
|
245
|
+
const { target, finalizers } = res ?? {
|
|
246
|
+
target: this.builder.exitIndex,
|
|
247
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
248
|
+
};
|
|
249
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
250
|
+
return { entry: idx, exits: [] };
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* `assert ( cond [, msg] );` — may throw an `AssertionError`. Modeled as a
|
|
254
|
+
* straight-line block that ALSO edges to the current handler (the assertion
|
|
255
|
+
* may fire), so the error path stays represented while the success path falls
|
|
256
|
+
* through. Conservative; deduped by the builder.
|
|
257
|
+
*/
|
|
258
|
+
visitAssert(stmt) {
|
|
259
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
260
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
261
|
+
return { entry: idx, exits: [idx] };
|
|
262
|
+
}
|
|
263
|
+
/** The `identifier` label of a `break outer;` / `continue outer;`, if any. */
|
|
264
|
+
jumpLabel(stmt) {
|
|
265
|
+
const id = stmt.namedChildren.find((c) => c.type === 'identifier');
|
|
266
|
+
return id?.text || undefined;
|
|
267
|
+
}
|
|
268
|
+
// ── branches (if/else) ─────────────────────────────────────────────────────
|
|
269
|
+
/**
|
|
270
|
+
* `if ( COND ) consequence:STMT [ else alternative:STMT ]`. The consequence /
|
|
271
|
+
* alternative are a `block` or a bare statement; an `else if` is the nested
|
|
272
|
+
* `if_statement` in the `alternative` field.
|
|
273
|
+
*/
|
|
274
|
+
visitIf(stmt) {
|
|
275
|
+
const cond = this.parenCondition(stmt);
|
|
276
|
+
const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? `if (${cond.text})` : 'if', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
277
|
+
const consequence = stmt.childForFieldName('consequence');
|
|
278
|
+
const alternative = stmt.childForFieldName('alternative');
|
|
279
|
+
const exits = [];
|
|
280
|
+
const thenRes = this.visitBody(consequence);
|
|
281
|
+
if (thenRes) {
|
|
282
|
+
this.builder.edge(header, thenRes.entry, 'cond-true');
|
|
283
|
+
exits.push(...thenRes.exits);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
exits.push(header); // empty then — true path falls through
|
|
287
|
+
}
|
|
288
|
+
if (alternative) {
|
|
289
|
+
const elseRes = this.visitBody(alternative);
|
|
290
|
+
if (elseRes) {
|
|
291
|
+
this.builder.edge(header, elseRes.entry, 'cond-false');
|
|
292
|
+
exits.push(...elseRes.exits);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
exits.push(header);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
exits.push(header); // no else — false path falls through to the join
|
|
300
|
+
}
|
|
301
|
+
return { entry: header, exits: [...new Set(exits)] };
|
|
302
|
+
}
|
|
303
|
+
/** The condition expression of an `if` — the named child between `(` and `)`. */
|
|
304
|
+
parenCondition(stmt) {
|
|
305
|
+
let sawOpen = false;
|
|
306
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
307
|
+
const c = stmt.child(i);
|
|
308
|
+
if (!c)
|
|
309
|
+
continue;
|
|
310
|
+
if (c.type === '(') {
|
|
311
|
+
sawOpen = true;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (c.type === ')')
|
|
315
|
+
return undefined;
|
|
316
|
+
if (sawOpen && c.isNamed && !isComment(c))
|
|
317
|
+
return c;
|
|
318
|
+
}
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
// ── loops ───────────────────────────────────────────────────────────────
|
|
322
|
+
/** `for ( for_loop_parts ) body:STMT` (C-style and for-in). */
|
|
323
|
+
visitFor(stmt) {
|
|
324
|
+
const labels = this.takeLabels();
|
|
325
|
+
const parts = stmt.namedChildren.find((c) => c.type === 'for_loop_parts');
|
|
326
|
+
const header = this.builder.newBlock(startLineOf(stmt), parts ? endLineOf(parts) : startLineOf(stmt), this.forHeaderText(stmt, parts), 'normal', this.harvest.forHeadFacts(parts));
|
|
327
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
328
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
329
|
+
const body = this.visitBody(stmt.childForFieldName('body'));
|
|
330
|
+
this.cfc.pop();
|
|
331
|
+
if (body) {
|
|
332
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
333
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-iterates
|
|
337
|
+
}
|
|
338
|
+
// Structural exit edge — even `for (;;) {}` keeps EXIT reverse-reachable.
|
|
339
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
340
|
+
return { entry: header, exits: [loopExit] };
|
|
341
|
+
}
|
|
342
|
+
forHeaderText(stmt, parts) {
|
|
343
|
+
return parts ? `for ${parts.text}` : 'for';
|
|
344
|
+
}
|
|
345
|
+
/** `while condition:parenthesized_expression body:STMT`. */
|
|
346
|
+
visitWhile(stmt) {
|
|
347
|
+
const labels = this.takeLabels();
|
|
348
|
+
const cond = stmt.childForFieldName('condition');
|
|
349
|
+
const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? `while ${cond.text}` : 'while', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
350
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
351
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
352
|
+
const body = this.visitBody(stmt.childForFieldName('body'));
|
|
353
|
+
this.cfc.pop();
|
|
354
|
+
if (body) {
|
|
355
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
356
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
360
|
+
}
|
|
361
|
+
// Structural exit edge — even `while (true) {}` keeps EXIT reverse-reachable.
|
|
362
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
363
|
+
return { entry: header, exits: [loopExit] };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* `do body:STMT while condition:… ;` — BOTTOM-TEST: the body runs at least
|
|
367
|
+
* once, THEN the condition decides whether to loop back.
|
|
368
|
+
*/
|
|
369
|
+
visitDoWhile(stmt) {
|
|
370
|
+
const labels = this.takeLabels();
|
|
371
|
+
const cond = stmt.childForFieldName('condition');
|
|
372
|
+
const condBlock = this.builder.newBlock(cond ? startLineOf(cond) : endLineOf(stmt), cond ? endLineOf(cond) : endLineOf(stmt), cond ? `while ${cond.text}` : 'while', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
373
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
374
|
+
// `continue` re-tests the condition; `break` leaves the loop.
|
|
375
|
+
this.cfc.pushLoop(condBlock, loopExit, labels);
|
|
376
|
+
const body = this.visitBody(stmt.childForFieldName('body'));
|
|
377
|
+
this.cfc.pop();
|
|
378
|
+
const backTarget = body ? body.entry : condBlock;
|
|
379
|
+
if (body)
|
|
380
|
+
this.builder.connect(body.exits, condBlock, 'seq');
|
|
381
|
+
this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
|
|
382
|
+
// Structural exit edge — even `do {} while (true)` keeps EXIT reachable.
|
|
383
|
+
this.builder.edge(condBlock, loopExit, 'cond-false');
|
|
384
|
+
return { entry: backTarget, exits: [loopExit] };
|
|
385
|
+
}
|
|
386
|
+
// ── switch (no implicit fallthrough; empty-case + continue-label spill) ─────
|
|
387
|
+
/**
|
|
388
|
+
* `switch condition:parenthesized_expression body:switch_block`. A non-empty
|
|
389
|
+
* case rejoins after the switch (no implicit fallthrough). An EMPTY case (no
|
|
390
|
+
* statements) spills into the next case (Dart empty-case fallthrough). An
|
|
391
|
+
* explicit `continue LABEL;` jumps to the labeled case.
|
|
392
|
+
*/
|
|
393
|
+
visitSwitch(stmt) {
|
|
394
|
+
const labels = this.takeLabels();
|
|
395
|
+
// The `condition` field is a `parenthesized_expression` (verified) — unwrap it
|
|
396
|
+
// so the dispatch text/discriminant matches the value-position `visitSwitchExpr`
|
|
397
|
+
// form (`switch x`, not `switch (x)`). The harvest walks into the paren either
|
|
398
|
+
// way, so the def/use facts are unchanged — only the block text normalizes.
|
|
399
|
+
const condRaw = stmt.childForFieldName('condition');
|
|
400
|
+
const value = condRaw ? this.unwrapParen(condRaw) : undefined;
|
|
401
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `switch ${value.text}` : 'switch', 'normal', value ? this.harvest.facts(value) : undefined);
|
|
402
|
+
const switchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
403
|
+
const block = stmt.childForFieldName('body');
|
|
404
|
+
const cases = block
|
|
405
|
+
? block.namedChildren.filter((c) => c.type === 'switch_statement_case' || c.type === 'switch_statement_default')
|
|
406
|
+
: [];
|
|
407
|
+
this.cfc.pushSwitch(switchExit, labels);
|
|
408
|
+
// Phase 1: build each case body. Register case labels so `continue LABEL`
|
|
409
|
+
// can target a labeled case's entry.
|
|
410
|
+
const caseBodies = [];
|
|
411
|
+
const caseLabels = [];
|
|
412
|
+
for (const c of cases) {
|
|
413
|
+
caseLabels.push(this.caseLabel(c));
|
|
414
|
+
// A case-pattern test runs conditionally before the body — harvest its
|
|
415
|
+
// uses onto the dispatch block (a later case tests only if earlier ones
|
|
416
|
+
// didn't match; any def there is a may-def).
|
|
417
|
+
for (const pat of this.casePatterns(c)) {
|
|
418
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(pat));
|
|
419
|
+
}
|
|
420
|
+
caseBodies.push(this.visitSeq(this.caseStatements(c)));
|
|
421
|
+
}
|
|
422
|
+
// The entry block of each case (its body, or the NEXT non-empty case's entry
|
|
423
|
+
// for an empty fallthrough case, or switchExit when nothing follows).
|
|
424
|
+
const entryOf = new Array(cases.length);
|
|
425
|
+
let after = switchExit;
|
|
426
|
+
for (let i = cases.length - 1; i >= 0; i--) {
|
|
427
|
+
entryOf[i] = caseBodies[i]?.entry ?? after;
|
|
428
|
+
after = entryOf[i];
|
|
429
|
+
}
|
|
430
|
+
// Resolve a `continue LABEL` target: the entry of the case carrying LABEL.
|
|
431
|
+
const labelTarget = (name) => {
|
|
432
|
+
const idx = caseLabels.findIndex((l) => l === name);
|
|
433
|
+
return idx >= 0 ? entryOf[idx] : undefined;
|
|
434
|
+
};
|
|
435
|
+
const hasDefault = cases.some((c) => c.type === 'switch_statement_default');
|
|
436
|
+
// Dispatch edges: one per case.
|
|
437
|
+
for (let i = 0; i < cases.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
|
+
// Case-body completion: a non-empty case rejoins after the switch UNLESS it
|
|
443
|
+
// ends in an explicit `continue LABEL` (handled as the body's own terminator)
|
|
444
|
+
// — and an EMPTY case spills into the next case (fallthrough edge). A bare
|
|
445
|
+
// `break;` inside a case is a normal break to switchExit (the jump handler).
|
|
446
|
+
for (let i = 0; i < cases.length; i++) {
|
|
447
|
+
const res = caseBodies[i];
|
|
448
|
+
const contLabel = this.caseContinueLabel(cases[i]);
|
|
449
|
+
if (!res) {
|
|
450
|
+
// Empty case — spill to the next case (or switchExit).
|
|
451
|
+
this.builder.edge(dispatch, i + 1 < cases.length ? entryOf[i + 1] : switchExit, 'fallthrough');
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (contLabel) {
|
|
455
|
+
const tgt = labelTarget(contLabel);
|
|
456
|
+
if (tgt !== undefined)
|
|
457
|
+
this.builder.connect(res.exits, tgt, 'fallthrough');
|
|
458
|
+
else
|
|
459
|
+
this.builder.connect(res.exits, switchExit, 'seq');
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
this.builder.connect(res.exits, switchExit, 'seq');
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
this.cfc.pop();
|
|
466
|
+
return { entry: dispatch, exits: [switchExit] };
|
|
467
|
+
}
|
|
468
|
+
/** The `label` name of a `switch_statement_case` (`myLabel: case …`), if any. */
|
|
469
|
+
caseLabel(c) {
|
|
470
|
+
const label = c.namedChildren.find((ch) => ch.type === 'label');
|
|
471
|
+
const id = label?.namedChildren.find((ch) => ch.type === 'identifier');
|
|
472
|
+
return id?.text || undefined;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Case-pattern nodes of a `switch_statement_case` whose test evaluates before
|
|
476
|
+
* the body — the only DISTINCT pattern node tree-sitter-dart emits is
|
|
477
|
+
* `constant_pattern` (`case 1:` / `case 1 || 2:`); a relational guard
|
|
478
|
+
* (`case > 5:`) is a bare `relational_operator` + operand, not a wrapper node.
|
|
479
|
+
* Harvesting the `constant_pattern` uses onto the dispatch block covers the
|
|
480
|
+
* common case; the rest fold into the body walk.
|
|
481
|
+
*/
|
|
482
|
+
casePatterns(c) {
|
|
483
|
+
return c.namedChildren.filter((ch) => ch.type === 'constant_pattern');
|
|
484
|
+
}
|
|
485
|
+
/** Body statements of a switch case/default (skip the case keyword/pattern/label). */
|
|
486
|
+
caseStatements(c) {
|
|
487
|
+
const NON_BODY = new Set(['case_builtin', 'label', 'constant_pattern']);
|
|
488
|
+
// A LABELED `continue LABEL;` at a case tail is a fallthrough-spill, handled
|
|
489
|
+
// via caseContinueLabel — drop it from the body. But a BARE `continue;` targets
|
|
490
|
+
// the ENCLOSING LOOP (valid Dart) and MUST stay in the body: dropping it
|
|
491
|
+
// silently removes the jump and fabricates a false case→next-statement
|
|
492
|
+
// fall-through path. Only drop the labeled form.
|
|
493
|
+
return c.namedChildren.filter((ch) => !isComment(ch) && !NON_BODY.has(ch.type) && !isLabeledContinue(ch));
|
|
494
|
+
}
|
|
495
|
+
/** A trailing `continue LABEL;` in a case spills to the labeled case. */
|
|
496
|
+
caseContinueLabel(c) {
|
|
497
|
+
const cont = c.namedChildren.find((ch) => isLabeledContinue(ch));
|
|
498
|
+
if (!cont)
|
|
499
|
+
return undefined;
|
|
500
|
+
const id = cont.namedChildren.find((ch) => ch.type === 'identifier');
|
|
501
|
+
return id?.text || undefined;
|
|
502
|
+
}
|
|
503
|
+
// ── value-position switch expression (#2207) ────────────────────────────────
|
|
504
|
+
/**
|
|
505
|
+
* The direct value of a `local_variable_declaration` with a SINGLE
|
|
506
|
+
* `initialized_variable_definition` (`var x = <value>`): its `value` field.
|
|
507
|
+
* Returns undefined for a multi-binding decl (`var a = …, b = …`) — modeling
|
|
508
|
+
* those arm-by-arm is out of scope, so they coalesce inline.
|
|
509
|
+
*/
|
|
510
|
+
directValue(stmt) {
|
|
511
|
+
const defs = stmt.namedChildren.filter((c) => c.type === 'initialized_variable_definition');
|
|
512
|
+
if (defs.length !== 1)
|
|
513
|
+
return undefined;
|
|
514
|
+
return defs[0].childForFieldName('value') ?? undefined;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Whether `node` is a value-position branch worth modeling as control flow
|
|
518
|
+
* (#2207): a `switch_expression` (Dart 3) with ≥2 arms — a real dispatch. Dart's
|
|
519
|
+
* value-position `if` does not exist; the ternary `?:` is excluded by design.
|
|
520
|
+
*/
|
|
521
|
+
isModelableValueBranch(node) {
|
|
522
|
+
if (node.type !== 'switch_expression')
|
|
523
|
+
return false;
|
|
524
|
+
return node.namedChildren.filter((c) => c.type === 'switch_expression_case').length >= 2;
|
|
525
|
+
}
|
|
526
|
+
/** Model a value-position branch as control flow (only `switch_expression`). */
|
|
527
|
+
visitBranchExpr(node) {
|
|
528
|
+
return this.visitSwitchExpr(node);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Model a value-position `switch (v) { p [when g] => e, _ => e }` (Dart 3) as a
|
|
532
|
+
* CFG dispatch: a discriminant block, each arm's value a block reached by a
|
|
533
|
+
* `switch-case` edge, all arms rejoining at one exit (no fallthrough). The arm
|
|
534
|
+
* PATTERN and any `when` GUARD are harvested as conditional uses on the dispatch
|
|
535
|
+
* (they evaluate before the body, only when earlier arms missed); a Dart call
|
|
536
|
+
* value parses as `identifier` + `selector` (multiple children), so the arm-value
|
|
537
|
+
* facts come from each post-`=>` child. Only an UNGUARDED `_` arm is the
|
|
538
|
+
* exhaustive catch-all — a guarded `_ when …` is NOT (the no-match path still
|
|
539
|
+
* needs the conservative edge), mirroring the C# `visitSwitchExpr`.
|
|
540
|
+
*/
|
|
541
|
+
visitSwitchExpr(node) {
|
|
542
|
+
const condRaw = node.childForFieldName('condition');
|
|
543
|
+
const cond = condRaw ? this.unwrapParen(condRaw) : node;
|
|
544
|
+
const dispatch = this.builder.newBlock(startLineOf(node), endLineOf(cond), `switch ${cond.text}`, 'normal', this.harvest.facts(cond));
|
|
545
|
+
const switchExit = this.builder.newBlock(endLineOf(node), endLineOf(node), '');
|
|
546
|
+
const arms = node.namedChildren.filter((c) => c.type === 'switch_expression_case');
|
|
547
|
+
let hasCatchAll = false;
|
|
548
|
+
for (const arm of arms) {
|
|
549
|
+
const { pattern, guards, values } = this.armParts(arm);
|
|
550
|
+
// The pattern + `when` guard are conditional dispatch tests, NOT arm-value
|
|
551
|
+
// uses — harvest them onto the dispatch (mirrors casePatterns for switch_statement).
|
|
552
|
+
if (pattern)
|
|
553
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(pattern));
|
|
554
|
+
for (const g of guards)
|
|
555
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(g));
|
|
556
|
+
if (pattern && pattern.text === '_' && guards.length === 0)
|
|
557
|
+
hasCatchAll = true;
|
|
558
|
+
const first = values[0] ?? arm;
|
|
559
|
+
const last = values[values.length - 1] ?? arm;
|
|
560
|
+
const armBlock = this.builder.newBlock(startLineOf(first), endLineOf(last), values.map((c) => c.text).join('') || arm.text, 'normal', undefined);
|
|
561
|
+
for (const v of values)
|
|
562
|
+
this.builder.attachFacts(armBlock, this.harvest.facts(v));
|
|
563
|
+
this.builder.edge(dispatch, armBlock, 'switch-case');
|
|
564
|
+
this.builder.edge(armBlock, switchExit, 'seq');
|
|
565
|
+
}
|
|
566
|
+
// A non-exhaustive Dart switch expression throws at runtime; conservatively
|
|
567
|
+
// keep EXIT reachable via a no-match edge when no `_` catch-all arm exists.
|
|
568
|
+
if (!hasCatchAll)
|
|
569
|
+
this.builder.edge(dispatch, switchExit, 'switch-case');
|
|
570
|
+
return { entry: dispatch, exits: [switchExit] };
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Split a `switch_expression_case` at the `=>` token: the PATTERN (first named
|
|
574
|
+
* child before `=>`), any `when` GUARD (named children between the pattern and
|
|
575
|
+
* `=>` — tree-sitter-dart parses the guard as a bare sibling, not a wrapper),
|
|
576
|
+
* and the VALUE expression (named children after `=>` — a Dart call is split
|
|
577
|
+
* across `identifier` + `selector`, hence an array).
|
|
578
|
+
*/
|
|
579
|
+
armParts(arm) {
|
|
580
|
+
const before = [];
|
|
581
|
+
const values = [];
|
|
582
|
+
let seenArrow = false;
|
|
583
|
+
for (let i = 0; i < arm.childCount; i++) {
|
|
584
|
+
const c = arm.child(i);
|
|
585
|
+
if (!c)
|
|
586
|
+
continue;
|
|
587
|
+
if (!c.isNamed) {
|
|
588
|
+
if (c.text === '=>')
|
|
589
|
+
seenArrow = true;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (isComment(c))
|
|
593
|
+
continue;
|
|
594
|
+
(seenArrow ? values : before).push(c);
|
|
595
|
+
}
|
|
596
|
+
return { pattern: before[0], guards: before.slice(1), values };
|
|
597
|
+
}
|
|
598
|
+
/** Strip a `parenthesized_expression` wrapper (a switch/if condition). */
|
|
599
|
+
unwrapParen(node) {
|
|
600
|
+
if (node.type === 'parenthesized_expression') {
|
|
601
|
+
const inner = node.namedChildren.find((c) => !isComment(c));
|
|
602
|
+
if (inner)
|
|
603
|
+
return inner;
|
|
604
|
+
}
|
|
605
|
+
return node;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* `var x = switch (v) { … }` (#2207): visit the switch as control flow, then
|
|
609
|
+
* rejoin its arms at a facts-only continuation carrying ONLY the declared name's
|
|
610
|
+
* def (the subject + arm-value uses are already on the switch's blocks). The
|
|
611
|
+
* arms are now control-dependent on the dispatch — mirrors Java / Kotlin / Rust.
|
|
612
|
+
*/
|
|
613
|
+
visitBindBranch(stmt, branch) {
|
|
614
|
+
const res = this.visitBranchExpr(branch);
|
|
615
|
+
const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.bindingDefFacts(stmt));
|
|
616
|
+
this.builder.connect(res.exits, cont, 'seq');
|
|
617
|
+
return { entry: res.entry, exits: [cont] };
|
|
618
|
+
}
|
|
619
|
+
// ── try / on / catch / finally ─────────────────────────────────────────────
|
|
620
|
+
/**
|
|
621
|
+
* `try body:block (on TYPE? catch_clause? block)* finally_clause?`. The handler
|
|
622
|
+
* groups are bare siblings: an `on type_identifier`, an optional `catch_clause`
|
|
623
|
+
* (→ `catch_parameters`), and the handler `block`. The `finally` runs on BOTH
|
|
624
|
+
* normal and exception exit — a `return`/`break`/`continue` crossing it threads
|
|
625
|
+
* through (`finally-*` completion edges). Mirrors the Java/Kotlin `visitTry`.
|
|
626
|
+
*/
|
|
627
|
+
visitTry(stmt) {
|
|
628
|
+
const bodyNode = stmt.childForFieldName('body');
|
|
629
|
+
const finallyClause = stmt.namedChildren.find((c) => c.type === 'finally_clause');
|
|
630
|
+
const finallyBody = finallyClause?.namedChildren.find((c) => c.type === 'block');
|
|
631
|
+
const handlerGroups = this.handlerGroups(stmt);
|
|
632
|
+
// The explicit finally is a finalizer the whole protected region threads through.
|
|
633
|
+
const finallyRes = finallyBody ? this.visitSeq(this.statementsOf(finallyBody)) : null;
|
|
634
|
+
const finallyFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
|
|
635
|
+
const finalizerEntry = finallyRes?.entry;
|
|
636
|
+
const finalizerExits = finallyRes?.exits ?? null;
|
|
637
|
+
// Build each catch/on handler.
|
|
638
|
+
const catchExits = [];
|
|
639
|
+
let firstCatchEntry;
|
|
640
|
+
for (const group of handlerGroups) {
|
|
641
|
+
const clauseBody = group.block;
|
|
642
|
+
if (finalizerEntry !== undefined)
|
|
643
|
+
this.handlers.push(finalizerEntry);
|
|
644
|
+
let res = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
|
|
645
|
+
if (finalizerEntry !== undefined)
|
|
646
|
+
this.handlers.pop();
|
|
647
|
+
if (res === null) {
|
|
648
|
+
// Empty `on T { }` / `catch (e) {}` still catches — synthesize one block
|
|
649
|
+
// so exception flow lands somewhere and post-try code stays reachable.
|
|
650
|
+
const anchor = clauseBody ?? group.catchClause ?? stmt;
|
|
651
|
+
const idx = this.builder.newBlock(startLineOf(anchor), endLineOf(anchor), '');
|
|
652
|
+
res = { entry: idx, exits: [idx] };
|
|
653
|
+
}
|
|
654
|
+
const paramFacts = this.harvest.catchParamFacts(group.catchParameters);
|
|
655
|
+
if (paramFacts) {
|
|
656
|
+
const anchor = group.catchParameters ?? group.catchClause ?? stmt;
|
|
657
|
+
const paramBlock = this.builder.newBlock(startLineOf(anchor), startLineOf(anchor), '', 'normal', paramFacts);
|
|
658
|
+
this.builder.edge(paramBlock, res.entry, 'seq');
|
|
659
|
+
res = { entry: paramBlock, exits: res.exits };
|
|
660
|
+
}
|
|
661
|
+
catchExits.push(...res.exits);
|
|
662
|
+
if (firstCatchEntry === undefined)
|
|
663
|
+
firstCatchEntry = res.entry;
|
|
664
|
+
}
|
|
665
|
+
// Handler for the try body: first catch if present, else the finally, else outer.
|
|
666
|
+
const tryHandler = firstCatchEntry ?? finalizerEntry ?? this.currentHandler();
|
|
667
|
+
const protectedStart = this.builder.blockCount;
|
|
668
|
+
this.handlers.push(tryHandler);
|
|
669
|
+
const bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
|
|
670
|
+
this.handlers.pop();
|
|
671
|
+
if (handlerGroups.length > 0 || finalizerEntry !== undefined) {
|
|
672
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
673
|
+
this.builder.edge(b, tryHandler, 'throw');
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Pop the finalizer frame and drain its pending completion legs.
|
|
677
|
+
if (finallyFrame && finallyRes) {
|
|
678
|
+
this.cfc.pop();
|
|
679
|
+
drainFinalizerPending(this.builder, finallyFrame, finallyRes.exits);
|
|
680
|
+
}
|
|
681
|
+
const exits = [];
|
|
682
|
+
if (finalizerEntry !== undefined) {
|
|
683
|
+
if (bodyRes)
|
|
684
|
+
this.builder.connect(bodyRes.exits, finalizerEntry, 'seq');
|
|
685
|
+
for (const e of catchExits)
|
|
686
|
+
this.builder.edge(e, finalizerEntry, 'seq');
|
|
687
|
+
if (finalizerExits)
|
|
688
|
+
exits.push(...finalizerExits);
|
|
689
|
+
// No catch → an exception re-propagates out after the finally runs.
|
|
690
|
+
if (handlerGroups.length === 0 && finalizerExits) {
|
|
691
|
+
this.builder.connect(finalizerExits, this.currentHandler(), 'throw');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
if (bodyRes)
|
|
696
|
+
exits.push(...bodyRes.exits);
|
|
697
|
+
exits.push(...catchExits);
|
|
698
|
+
}
|
|
699
|
+
const entry = bodyRes?.entry ?? finalizerEntry ?? firstCatchEntry;
|
|
700
|
+
if (entry === undefined)
|
|
701
|
+
return null;
|
|
702
|
+
return { entry, exits: [...new Set(exits)] };
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* The handler groups of a `try_statement`: each is an `on TYPE` and/or
|
|
706
|
+
* `catch (params)` plus the handler `block` that follows it. The `on`/`type`/
|
|
707
|
+
* `catch_clause`/`block` are bare children in source order, so a group is a
|
|
708
|
+
* `catch_clause` (or an `on` keyword + `type_identifier`) followed by a `block`.
|
|
709
|
+
*/
|
|
710
|
+
handlerGroups(stmt) {
|
|
711
|
+
const groups = [];
|
|
712
|
+
const bodyNode = stmt.childForFieldName('body');
|
|
713
|
+
let pendingCatch;
|
|
714
|
+
let sawOn = false;
|
|
715
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
716
|
+
const c = stmt.child(i);
|
|
717
|
+
if (!c)
|
|
718
|
+
continue;
|
|
719
|
+
if (c.type === 'finally_clause')
|
|
720
|
+
break; // finally is separate
|
|
721
|
+
if (c.type === 'on') {
|
|
722
|
+
sawOn = true;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (c.type === 'catch_clause') {
|
|
726
|
+
pendingCatch = c;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (c.type === 'block' && c !== bodyNode) {
|
|
730
|
+
const catchParameters = pendingCatch?.namedChildren.find((ch) => ch.type === 'catch_parameters');
|
|
731
|
+
groups.push({ catchClause: pendingCatch, catchParameters, block: c });
|
|
732
|
+
pendingCatch = undefined;
|
|
733
|
+
sawOn = false;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// A trailing `on T catch (e)` with no `{}` body is malformed; ignore here.
|
|
737
|
+
void sawOn;
|
|
738
|
+
return groups;
|
|
739
|
+
}
|
|
740
|
+
/** Nearest enclosing exception handler, or the function EXIT. */
|
|
741
|
+
currentHandler() {
|
|
742
|
+
return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/** The signature node that precedes a `function_body` (params live there). */
|
|
746
|
+
function signatureFor(fnNode) {
|
|
747
|
+
if (fnNode.type !== 'function_body')
|
|
748
|
+
return undefined;
|
|
749
|
+
const prev = fnNode.previousSibling;
|
|
750
|
+
if (!prev)
|
|
751
|
+
return undefined;
|
|
752
|
+
if (DART_SIGNATURE_TYPES.has(prev.type))
|
|
753
|
+
return prev;
|
|
754
|
+
// A `method_signature` wraps a `function_signature` / getter / setter — the
|
|
755
|
+
// params live on the wrapped signature for a plain method but the wrapper still
|
|
756
|
+
// carries them transitively; return the wrapper (paramList searches its named
|
|
757
|
+
// children, which include the `formal_parameter_list`).
|
|
758
|
+
return undefined;
|
|
759
|
+
}
|
|
760
|
+
/** The body STMT of a `function_body` / `function_expression`: a `block` or an arrow expr. */
|
|
761
|
+
function bodyAndArrow(fnNode) {
|
|
762
|
+
let container = fnNode;
|
|
763
|
+
if (fnNode.type === 'function_expression') {
|
|
764
|
+
container = fnNode.childForFieldName('body') ?? undefined; // function_expression_body
|
|
765
|
+
}
|
|
766
|
+
if (!container)
|
|
767
|
+
return {};
|
|
768
|
+
const block = container.namedChildren.find((c) => c.type === 'block');
|
|
769
|
+
if (block)
|
|
770
|
+
return { block };
|
|
771
|
+
// Arrow body `=> expr`: the expression is the first non-comment named child.
|
|
772
|
+
const arrowExpr = container.namedChildren.find((c) => !COMMENT_TYPES.has(c.type));
|
|
773
|
+
return { arrowExpr };
|
|
774
|
+
}
|
|
775
|
+
/** Build the CFG for one Dart function node, or `undefined` if not modelable. */
|
|
776
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
777
|
+
try {
|
|
778
|
+
if (!isFunction(fnNode))
|
|
779
|
+
return undefined;
|
|
780
|
+
const signature = signatureFor(fnNode);
|
|
781
|
+
// Anchor the span/column to the signature when present (so same-line
|
|
782
|
+
// functions `void a(){} void b(){}` get distinct start columns from the
|
|
783
|
+
// signature, and the span covers the declaration head), else the body node.
|
|
784
|
+
const anchor = signature ?? fnNode;
|
|
785
|
+
const startLine = startLineOf(anchor);
|
|
786
|
+
const endLine = endLineOf(fnNode);
|
|
787
|
+
const startColumn = anchor.startPosition.column;
|
|
788
|
+
const { block, arrowExpr } = bodyAndArrow(fnNode);
|
|
789
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
790
|
+
const harvest = new DartHarvester(fnNode, signature);
|
|
791
|
+
const paramFacts = harvest.paramFacts();
|
|
792
|
+
if (paramFacts)
|
|
793
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
794
|
+
// Arrow body (`=> expr`): one block whose value is returned.
|
|
795
|
+
if (!block && arrowExpr) {
|
|
796
|
+
const blk = builder.newBlock(startLineOf(arrowExpr), endLineOf(arrowExpr), arrowExpr.text, 'normal', harvest.facts(arrowExpr));
|
|
797
|
+
builder.edge(builder.entryIndex, blk, 'seq');
|
|
798
|
+
builder.edge(blk, builder.exitIndex, 'return');
|
|
799
|
+
return builder.finish(harvest.bindingTable());
|
|
800
|
+
}
|
|
801
|
+
const walk = new DartCfgWalk(builder, harvest);
|
|
802
|
+
const res = block ? walk.visitSeq(block.namedChildren.filter((c) => !isComment(c))) : null;
|
|
803
|
+
if (!res) {
|
|
804
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
|
805
|
+
return builder.finish(harvest.bindingTable());
|
|
806
|
+
}
|
|
807
|
+
builder.edge(builder.entryIndex, res.entry, 'seq');
|
|
808
|
+
builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
|
|
809
|
+
return builder.finish(harvest.bindingTable());
|
|
810
|
+
}
|
|
811
|
+
catch (err) {
|
|
812
|
+
// Never throw out of buildFunctionCfg — a malformed AST shape must skip only
|
|
813
|
+
// this one function's CFG, never drop the whole file's language group (R4).
|
|
814
|
+
// eslint-disable-next-line no-console
|
|
815
|
+
console.warn(`[cfg] Dart buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Whether a node is a Dart function this visitor builds a CFG for: a
|
|
821
|
+
* `function_body` whose previous sibling is a signature (top-level fn / method /
|
|
822
|
+
* getter / setter / constructor), or a `function_expression` (a closure).
|
|
823
|
+
*/
|
|
824
|
+
function isFunction(node) {
|
|
825
|
+
if (node.type === 'function_expression')
|
|
826
|
+
return true;
|
|
827
|
+
if (node.type !== 'function_body')
|
|
828
|
+
return false;
|
|
829
|
+
const prev = node.previousSibling;
|
|
830
|
+
return prev !== null && DART_SIGNATURE_TYPES.has(prev.type);
|
|
831
|
+
}
|
|
832
|
+
/** The Dart CFG visitor. */
|
|
833
|
+
export function createDartCfgVisitor() {
|
|
834
|
+
return { buildFunctionCfg, isFunction };
|
|
835
|
+
}
|
|
836
|
+
export { DART_SIGNATURE_TYPES };
|