homegraph 1.1.3 → 1.4.1
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 +43 -18
- package/dist/arkts/ohos-api-index.d.ts +15 -0
- package/dist/arkts/ohos-api-index.d.ts.map +1 -0
- package/dist/arkts/ohos-api-index.js +190 -0
- package/dist/arkts/ohos-api-index.js.map +1 -0
- package/dist/arkts/ohos-sdk-input.d.ts +36 -0
- package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
- package/dist/arkts/ohos-sdk-input.js +214 -0
- package/dist/arkts/ohos-sdk-input.js.map +1 -0
- package/dist/bin/command-supervision.d.ts +12 -0
- package/dist/bin/command-supervision.d.ts.map +1 -0
- package/dist/bin/command-supervision.js +86 -0
- package/dist/bin/command-supervision.js.map +1 -0
- package/dist/bin/homegraph.d.ts +1 -1
- package/dist/bin/homegraph.js +709 -168
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +85 -0
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +84 -5
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +217 -14
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +58 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +131 -2
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +473 -9
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +52 -1
- package/dist/db/wal-valve.d.ts +106 -0
- package/dist/db/wal-valve.d.ts.map +1 -0
- package/dist/db/wal-valve.js +208 -0
- package/dist/db/wal-valve.js.map +1 -0
- package/dist/directory.d.ts +36 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +274 -0
- package/dist/directory.js.map +1 -1
- package/dist/extraction/arkts-batch-worker.d.ts +2 -0
- package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
- package/dist/extraction/arkts-batch-worker.js +28 -0
- package/dist/extraction/arkts-batch-worker.js.map +1 -0
- package/dist/extraction/cfml-extractor.d.ts +107 -0
- package/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/dist/extraction/cfml-extractor.js +494 -0
- package/dist/extraction/cfml-extractor.js.map +1 -0
- package/dist/extraction/context.d.ts +11 -0
- package/dist/extraction/context.d.ts.map +1 -1
- package/dist/extraction/context.js +20 -0
- package/dist/extraction/context.js.map +1 -1
- package/dist/extraction/grammars.d.ts +25 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +177 -24
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +55 -2
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +923 -381
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
- package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
- package/dist/extraction/languages/arkts-state-decorators.js +26 -0
- package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
- package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.js +21 -6
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +98 -2
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +1290 -61
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +98 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +563 -1
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/cfquery.d.ts +12 -0
- package/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/dist/extraction/languages/cfquery.js +28 -0
- package/dist/extraction/languages/cfquery.js.map +1 -0
- package/dist/extraction/languages/cfscript.d.ts +3 -0
- package/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/dist/extraction/languages/cfscript.js +73 -0
- package/dist/extraction/languages/cfscript.js.map +1 -0
- package/dist/extraction/languages/cobol.d.ts +33 -0
- package/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/dist/extraction/languages/cobol.js +499 -0
- package/dist/extraction/languages/cobol.js.map +1 -0
- package/dist/extraction/languages/erlang.d.ts +3 -0
- package/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/dist/extraction/languages/erlang.js +350 -0
- package/dist/extraction/languages/erlang.js.map +1 -0
- package/dist/extraction/languages/index.d.ts.map +1 -1
- package/dist/extraction/languages/index.js +16 -0
- package/dist/extraction/languages/index.js.map +1 -1
- package/dist/extraction/languages/nix.d.ts +3 -0
- package/dist/extraction/languages/nix.d.ts.map +1 -0
- package/dist/extraction/languages/nix.js +294 -0
- package/dist/extraction/languages/nix.js.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.js +283 -0
- package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
- package/dist/extraction/languages/solidity.d.ts +3 -0
- package/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/dist/extraction/languages/solidity.js +293 -0
- package/dist/extraction/languages/solidity.js.map +1 -0
- package/dist/extraction/languages/terraform.d.ts +3 -0
- package/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/dist/extraction/languages/terraform.js +641 -0
- package/dist/extraction/languages/terraform.js.map +1 -0
- package/dist/extraction/languages/vbnet.d.ts +11 -0
- package/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/dist/extraction/languages/vbnet.js +141 -0
- package/dist/extraction/languages/vbnet.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/dist/extraction/mybatis-extractor.js +140 -38
- package/dist/extraction/mybatis-extractor.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +152 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -0
- package/dist/extraction/parse-pool.js +387 -0
- package/dist/extraction/parse-pool.js.map +1 -0
- package/dist/extraction/parse-worker.js +9 -2
- package/dist/extraction/parse-worker.js.map +1 -1
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/dist/extraction/tree-sitter-helpers.js +1 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/dist/extraction/tree-sitter-types.d.ts +20 -1
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +59 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +952 -37
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +77 -18
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +85 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +426 -9
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +42 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +97 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/daemon-paths.d.ts +30 -3
- package/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/dist/mcp/daemon-paths.js +50 -10
- package/dist/mcp/daemon-paths.js.map +1 -1
- package/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/dist/mcp/daemon-registry.js +7 -3
- package/dist/mcp/daemon-registry.js.map +1 -1
- package/dist/mcp/daemon.d.ts +73 -3
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +258 -40
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/dist/mcp/dynamic-boundaries.js +2 -1
- package/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/dist/mcp/early-ppid.d.ts +26 -0
- package/dist/mcp/early-ppid.d.ts.map +1 -0
- package/dist/mcp/early-ppid.js +29 -0
- package/dist/mcp/early-ppid.js.map +1 -0
- package/dist/mcp/engine.d.ts +16 -0
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +100 -2
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +66 -49
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +73 -13
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/memory-budget.d.ts +30 -0
- package/dist/mcp/memory-budget.d.ts.map +1 -0
- package/dist/mcp/memory-budget.js +123 -0
- package/dist/mcp/memory-budget.js.map +1 -0
- package/dist/mcp/ppid-watchdog.d.ts +18 -0
- package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
- package/dist/mcp/ppid-watchdog.js +37 -0
- package/dist/mcp/ppid-watchdog.js.map +1 -1
- package/dist/mcp/proxy.d.ts.map +1 -1
- package/dist/mcp/proxy.js +88 -4
- package/dist/mcp/proxy.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +56 -0
- package/dist/mcp/query-cache.d.ts.map +1 -0
- package/dist/mcp/query-cache.js +261 -0
- package/dist/mcp/query-cache.js.map +1 -0
- package/dist/mcp/query-pool.d.ts +128 -0
- package/dist/mcp/query-pool.d.ts.map +1 -0
- package/dist/mcp/query-pool.js +374 -0
- package/dist/mcp/query-pool.js.map +1 -0
- package/dist/mcp/query-worker.d.ts +24 -0
- package/dist/mcp/query-worker.d.ts.map +1 -0
- package/dist/mcp/query-worker.js +87 -0
- package/dist/mcp/query-worker.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +4 -29
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +39 -86
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/session.d.ts +14 -0
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +28 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/startup-handshake.d.ts +44 -0
- package/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/dist/mcp/startup-handshake.js +73 -0
- package/dist/mcp/startup-handshake.js.map +1 -0
- package/dist/mcp/tools.d.ts +179 -2
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +3027 -192
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/project-config.d.ts +58 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +141 -2
- package/dist/project-config.js.map +1 -1
- package/dist/reasoning/login.js +1 -1
- package/dist/reasoning/login.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -29
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +866 -148
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +9 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +1232 -183
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/cooperative-yield.d.ts +32 -0
- package/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/dist/resolution/cooperative-yield.js +42 -0
- package/dist/resolution/cooperative-yield.js.map +1 -0
- package/dist/resolution/frameworks/cics.d.ts +20 -0
- package/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/dist/resolution/frameworks/cics.js +90 -0
- package/dist/resolution/frameworks/cics.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +3 -0
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/dist/resolution/frameworks/java.js +14 -6
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/dist/resolution/frameworks/terraform.js +277 -0
- package/dist/resolution/frameworks/terraform.js.map +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/dist/resolution/goframe-synthesizer.js +8 -3
- package/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +7 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +129 -4
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +65 -3
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +477 -59
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +22 -3
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +657 -39
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/strip-comments.d.ts +1 -1
- package/dist/resolution/strip-comments.d.ts.map +1 -1
- package/dist/resolution/strip-comments.js +49 -0
- package/dist/resolution/strip-comments.js.map +1 -1
- package/dist/resolution/types.d.ts +29 -0
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.d.ts +10 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.js +142 -4
- package/dist/resolution/workspace-packages.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +60 -0
- package/dist/search/identifier-segments.d.ts.map +1 -0
- package/dist/search/identifier-segments.js +176 -0
- package/dist/search/identifier-segments.js.map +1 -0
- package/dist/search/query-utils.d.ts +201 -0
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +919 -2
- package/dist/search/query-utils.js.map +1 -1
- package/dist/spec/config.d.ts +7 -11
- package/dist/spec/config.d.ts.map +1 -1
- package/dist/spec/config.js +77 -48
- package/dist/spec/config.js.map +1 -1
- package/dist/spec/db/fragment-node.d.ts +1 -0
- package/dist/spec/db/fragment-node.d.ts.map +1 -1
- package/dist/spec/db/fragment-node.js +8 -0
- package/dist/spec/db/fragment-node.js.map +1 -1
- package/dist/spec/db/fts.d.ts +14 -0
- package/dist/spec/db/fts.d.ts.map +1 -1
- package/dist/spec/db/fts.js +43 -4
- package/dist/spec/db/fts.js.map +1 -1
- package/dist/spec/db/schema.d.ts +6 -3
- package/dist/spec/db/schema.d.ts.map +1 -1
- package/dist/spec/db/schema.js +36 -4
- package/dist/spec/db/schema.js.map +1 -1
- package/dist/spec/db/schema.sql +11 -1
- package/dist/spec/evolve/logic-checker.d.ts +2 -2
- package/dist/spec/evolve/logic-checker.d.ts.map +1 -1
- package/dist/spec/evolve/logic-checker.js +3 -27
- package/dist/spec/evolve/logic-checker.js.map +1 -1
- package/dist/spec/evolve/pipeline.d.ts +21 -19
- package/dist/spec/evolve/pipeline.d.ts.map +1 -1
- package/dist/spec/evolve/pipeline.js +284 -31
- package/dist/spec/evolve/pipeline.js.map +1 -1
- package/dist/spec/evolve/spec-rewriter.d.ts +2 -2
- package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
- package/dist/spec/evolve/spec-rewriter.js +7 -31
- package/dist/spec/evolve/spec-rewriter.js.map +1 -1
- package/dist/spec/graph/queries.d.ts +97 -0
- package/dist/spec/graph/queries.d.ts.map +1 -1
- package/dist/spec/graph/queries.js +225 -0
- package/dist/spec/graph/queries.js.map +1 -1
- package/dist/spec/llm/client.d.ts +29 -0
- package/dist/spec/llm/client.d.ts.map +1 -0
- package/dist/spec/llm/client.js +123 -0
- package/dist/spec/llm/client.js.map +1 -0
- package/dist/spec/llm/index.d.ts +3 -0
- package/dist/spec/llm/index.d.ts.map +1 -0
- package/dist/spec/llm/index.js +11 -0
- package/dist/spec/llm/index.js.map +1 -0
- package/dist/spec/llm/prompts.d.ts +13 -0
- package/dist/spec/llm/prompts.d.ts.map +1 -0
- package/dist/spec/llm/prompts.js +75 -0
- package/dist/spec/llm/prompts.js.map +1 -0
- package/dist/spec/mining/git-scanner.d.ts +12 -0
- package/dist/spec/mining/git-scanner.d.ts.map +1 -1
- package/dist/spec/mining/git-scanner.js +41 -0
- package/dist/spec/mining/git-scanner.js.map +1 -1
- package/dist/spec/mining/pipeline.d.ts.map +1 -1
- package/dist/spec/mining/pipeline.js +14 -1
- package/dist/spec/mining/pipeline.js.map +1 -1
- package/dist/spec/utils.d.ts +2 -1
- package/dist/spec/utils.d.ts.map +1 -1
- package/dist/spec/utils.js +3 -1
- package/dist/spec/utils.js.map +1 -1
- package/dist/sync/git-hooks.d.ts.map +1 -1
- package/dist/sync/git-hooks.js +2 -0
- package/dist/sync/git-hooks.js.map +1 -1
- package/dist/sync/watcher.d.ts +10 -5
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +51 -14
- package/dist/sync/watcher.js.map +1 -1
- package/dist/sync/worktree.d.ts +9 -0
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +45 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/types.d.ts +25 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/ui/shimmer-progress.d.ts +2 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -1
- package/dist/ui/shimmer-progress.js +19 -2
- package/dist/ui/shimmer-progress.js.map +1 -1
- package/dist/upgrade/index.d.ts +32 -0
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +163 -15
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/remove-binary.d.ts +87 -0
- package/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/dist/upgrade/remove-binary.js +289 -0
- package/dist/upgrade/remove-binary.js.map +1 -0
- package/dist/upgrade/update-check.d.ts +92 -0
- package/dist/upgrade/update-check.d.ts.map +1 -0
- package/dist/upgrade/update-check.js +258 -0
- package/dist/upgrade/update-check.js.map +1 -0
- package/package.json +4 -4
- package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
- package/scripts/agent-eval/run-all.sh +6 -0
- package/scripts/build-bundle.sh +5 -0
- package/scripts/exp_boundary_eval/README.md +247 -0
- package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
- package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
- package/scripts/exp_boundary_eval/_utils.py +1116 -0
- package/scripts/exp_boundary_eval/analyze.py +1313 -0
- package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
- package/scripts/exp_boundary_eval/run_all.py +378 -0
- package/scripts/exp_boundary_eval/run_one.py +165 -0
- package/scripts/exp_boundary_eval/run_session.py +158 -0
- package/scripts/exp_boundary_eval/setup.py +120 -0
- package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
- package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
- package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
- package/scripts/npm-shim.js +32 -3
- package/scripts/ohos-sdk-publish.mjs +133 -0
- package/scripts/qa_eval/README.md +0 -407
- package/scripts/qa_eval/_test_deveco_probe.py +0 -41
- package/scripts/qa_eval/agent_runner.py +0 -526
- package/scripts/qa_eval/data/.gitignore +0 -4
- package/scripts/qa_eval/data/test-set.jsonl +0 -2
- package/scripts/qa_eval/eval_metrics.py +0 -274
- package/scripts/qa_eval/external_agent.py +0 -976
- package/scripts/qa_eval/llm_config.py +0 -92
- package/scripts/qa_eval/memory_monitor.py +0 -132
- package/scripts/qa_eval/my_answer_accuracy.py +0 -187
- package/scripts/qa_eval/requirements.txt +0 -2
- package/scripts/qa_eval/run_pipeline.py +0 -804
- package/scripts/qa_eval/stats_efficiency.py +0 -279
- package/scripts/qa_eval/stats_scores.py +0 -207
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SYNTH_PROGRESS_STEPS = void 0;
|
|
3
4
|
exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
|
|
4
5
|
const generated_detection_1 = require("../extraction/generated-detection");
|
|
5
6
|
const strip_comments_1 = require("./strip-comments");
|
|
6
7
|
const c_fnptr_synthesizer_1 = require("./c-fnptr-synthesizer");
|
|
7
8
|
const goframe_synthesizer_1 = require("./goframe-synthesizer");
|
|
9
|
+
const cooperative_yield_1 = require("./cooperative-yield");
|
|
8
10
|
const arkts_entry_1 = require("./frameworks/arkts-entry");
|
|
9
11
|
const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
|
|
10
12
|
const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
|
|
@@ -40,6 +42,15 @@ const CC_DISPATCH_RE = /(\w+)\.forEach\s*\{\s*(?:\$0|it)\s*\(/g;
|
|
|
40
42
|
const CC_APPEND_WRITE_RE = /(\w+)\.write\s*\{\s*\$0(?:\.(\w+))?\.(?:append|add|push|insert)\s*\(/g;
|
|
41
43
|
const CC_APPEND_DIRECT_RE = /(\w+)\.(?:append|add|push|insert)\s*\(/g;
|
|
42
44
|
const CC_FANOUT_CAP = 8; // skip a field name with more dispatchers/registrars than this (too generic to pair confidently)
|
|
45
|
+
// The dispatcher gate — `{ $0( ` / `{ it( ` element-invocation — is Swift/Kotlin
|
|
46
|
+
// trailing-closure syntax, so ONLY those languages can ever contribute a
|
|
47
|
+
// dispatcher, and a cross-language registrar pairing (a JS `.push(` against a
|
|
48
|
+
// Swift dispatcher's field name) would be a wrong edge, not a missed one.
|
|
49
|
+
// Gating both sides here isn't just precision: `.push(`/`.add(` is everywhere
|
|
50
|
+
// in JS/PHP, so an ungated scan slices + regexes nearly every function on repos
|
|
51
|
+
// where the pass cannot emit a single edge — on a 12k-file PHP/JS app that was
|
|
52
|
+
// 20+ minutes of the "Resolving refs" tail and a #850 watchdog kill (#1235).
|
|
53
|
+
const CC_LANGUAGES = new Set(['swift', 'kotlin']);
|
|
43
54
|
function kebabToPascal(s) {
|
|
44
55
|
return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
|
|
45
56
|
}
|
|
@@ -75,6 +86,35 @@ function sliceLines(content, startLine, endLine) {
|
|
|
75
86
|
return null;
|
|
76
87
|
return content.split('\n').slice(startLine - 1, endLine).join('\n');
|
|
77
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Per-match line resolver over `src`, 1-based at `baseLine`. The inline
|
|
91
|
+
* `src.slice(0, idx).split('\n').length` idiom is O(source-length) PER MATCH,
|
|
92
|
+
* which goes quadratic on a match-dense source (a generated function full of
|
|
93
|
+
* `.push(` calls re-scanned tens of thousands of times was most of the #1235
|
|
94
|
+
* indexing wedge). Builds the newline index once — lazily, since most sources
|
|
95
|
+
* never produce a match — then answers each call with a binary search.
|
|
96
|
+
*/
|
|
97
|
+
function makeLineAt(src, baseLine) {
|
|
98
|
+
let nl = null;
|
|
99
|
+
return (idx) => {
|
|
100
|
+
if (!nl) {
|
|
101
|
+
nl = [];
|
|
102
|
+
for (let i = src.indexOf('\n'); i !== -1; i = src.indexOf('\n', i + 1))
|
|
103
|
+
nl.push(i);
|
|
104
|
+
}
|
|
105
|
+
// Count newlines strictly before idx.
|
|
106
|
+
let lo = 0;
|
|
107
|
+
let hi = nl.length;
|
|
108
|
+
while (lo < hi) {
|
|
109
|
+
const mid = (lo + hi) >> 1;
|
|
110
|
+
if (nl[mid] < idx)
|
|
111
|
+
lo = mid + 1;
|
|
112
|
+
else
|
|
113
|
+
hi = mid;
|
|
114
|
+
}
|
|
115
|
+
return baseLine + lo;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
78
118
|
function registrarField(src) {
|
|
79
119
|
const m = src.match(/this\.(\w+)\.(?:add|push|set)\(/);
|
|
80
120
|
return m ? m[1] : null;
|
|
@@ -114,10 +154,13 @@ function* methodAndFunctionNodes(queries) {
|
|
|
114
154
|
yield* queries.iterateNodesByKind('function');
|
|
115
155
|
}
|
|
116
156
|
/** Phase 1: field-backed observer channels (registrar/dispatcher share a store). */
|
|
117
|
-
function fieldChannelEdges(queries, ctx) {
|
|
157
|
+
async function fieldChannelEdges(queries, ctx, onYield) {
|
|
118
158
|
const registrars = [];
|
|
119
159
|
const dispatchers = [];
|
|
160
|
+
let scanned = 0;
|
|
120
161
|
for (const m of methodAndFunctionNodes(queries)) {
|
|
162
|
+
if ((++scanned & 255) === 0)
|
|
163
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
121
164
|
const isReg = REGISTRAR_NAME.test(m.name);
|
|
122
165
|
const isDisp = DISPATCHER_NAME.test(m.name);
|
|
123
166
|
if (!isReg && !isDisp)
|
|
@@ -198,7 +241,7 @@ function fieldChannelEdges(queries, ctx) {
|
|
|
198
241
|
* subclass `DataRequest.validate`), bounded by a fan-out cap so a generic field
|
|
199
242
|
* name shared across unrelated classes can't fan out into noise.
|
|
200
243
|
*/
|
|
201
|
-
function closureCollectionEdges(queries, ctx) {
|
|
244
|
+
async function closureCollectionEdges(queries, ctx, onYield) {
|
|
202
245
|
const dispatchers = new Map(); // field → dispatcher methods + forEach line
|
|
203
246
|
const registrars = new Map(); // field → registrar methods + append line
|
|
204
247
|
const addReg = (field, node, absLine) => {
|
|
@@ -209,7 +252,18 @@ function closureCollectionEdges(queries, ctx) {
|
|
|
209
252
|
arr.push({ node, line: absLine });
|
|
210
253
|
registrars.set(field, arr);
|
|
211
254
|
};
|
|
255
|
+
// Slices EVERY Swift/Kotlin method/function's source (no cheap name-gate), so
|
|
256
|
+
// on a repo with a huge file this is the heaviest synthesis pass — yield
|
|
257
|
+
// mid-scan (and mid-match-loop below: a single generated function dense with
|
|
258
|
+
// matches must not starve the watchdog either) so it can't wedge the #850
|
|
259
|
+
// watchdog on its own (#1091, #1235).
|
|
260
|
+
let scanned = 0;
|
|
261
|
+
let matchTick = 0;
|
|
212
262
|
for (const m of methodAndFunctionNodes(queries)) {
|
|
263
|
+
if ((++scanned & 127) === 0)
|
|
264
|
+
await onYield();
|
|
265
|
+
if (!CC_LANGUAGES.has(m.language))
|
|
266
|
+
continue;
|
|
213
267
|
const content = ctx.readFile(m.filePath);
|
|
214
268
|
const src = content && sliceLines(content, m.startLine, m.endLine);
|
|
215
269
|
if (!src)
|
|
@@ -218,11 +272,13 @@ function closureCollectionEdges(queries, ctx) {
|
|
|
218
272
|
const hasAppend = src.includes('.append(') || src.includes('.add(') || src.includes('.push(') || src.includes('.insert(');
|
|
219
273
|
if (!hasForEach && !hasAppend)
|
|
220
274
|
continue;
|
|
221
|
-
const lineAt = (
|
|
275
|
+
const lineAt = makeLineAt(src, m.startLine ?? 1);
|
|
222
276
|
if (hasForEach) {
|
|
223
277
|
CC_DISPATCH_RE.lastIndex = 0;
|
|
224
278
|
let d;
|
|
225
279
|
while ((d = CC_DISPATCH_RE.exec(src))) {
|
|
280
|
+
if ((++matchTick & 255) === 0)
|
|
281
|
+
await onYield();
|
|
226
282
|
const arr = dispatchers.get(d[1]) ?? [];
|
|
227
283
|
if (!arr.some((n) => n.node.id === m.id))
|
|
228
284
|
arr.push({ node: m, line: lineAt(d.index) });
|
|
@@ -232,12 +288,18 @@ function closureCollectionEdges(queries, ctx) {
|
|
|
232
288
|
if (hasAppend) {
|
|
233
289
|
CC_APPEND_WRITE_RE.lastIndex = 0;
|
|
234
290
|
let w;
|
|
235
|
-
while ((w = CC_APPEND_WRITE_RE.exec(src)))
|
|
291
|
+
while ((w = CC_APPEND_WRITE_RE.exec(src))) {
|
|
292
|
+
if ((++matchTick & 255) === 0)
|
|
293
|
+
await onYield();
|
|
236
294
|
addReg(w[2] || w[1], m, lineAt(w.index)); // nested `$0.streams` else the `.write` receiver
|
|
295
|
+
}
|
|
237
296
|
CC_APPEND_DIRECT_RE.lastIndex = 0;
|
|
238
297
|
let a;
|
|
239
|
-
while ((a = CC_APPEND_DIRECT_RE.exec(src)))
|
|
298
|
+
while ((a = CC_APPEND_DIRECT_RE.exec(src))) {
|
|
299
|
+
if ((++matchTick & 255) === 0)
|
|
300
|
+
await onYield();
|
|
240
301
|
addReg(a[1], m, lineAt(a.index));
|
|
302
|
+
}
|
|
241
303
|
}
|
|
242
304
|
}
|
|
243
305
|
const edges = [];
|
|
@@ -266,10 +328,16 @@ function closureCollectionEdges(queries, ctx) {
|
|
|
266
328
|
return edges;
|
|
267
329
|
}
|
|
268
330
|
/** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
|
|
269
|
-
function eventEmitterEdges(ctx) {
|
|
331
|
+
async function eventEmitterEdges(ctx, onYield) {
|
|
332
|
+
let scannedFiles = 0;
|
|
270
333
|
const emitsByEvent = new Map(); // event → dispatcher node ids
|
|
271
334
|
const handlersByEvent = new Map(); // event → handler id → registration site (file:line)
|
|
335
|
+
let scanned = 0;
|
|
272
336
|
for (const file of ctx.getAllFiles()) {
|
|
337
|
+
if ((++scannedFiles & 15) === 0)
|
|
338
|
+
await onYield();
|
|
339
|
+
if ((++scanned & 255) === 0)
|
|
340
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
273
341
|
const content = ctx.readFile(file);
|
|
274
342
|
if (!content)
|
|
275
343
|
continue;
|
|
@@ -278,7 +346,7 @@ function eventEmitterEdges(ctx) {
|
|
|
278
346
|
if (!hasEmit && !hasOn)
|
|
279
347
|
continue;
|
|
280
348
|
const nodesInFile = ctx.getNodesInFile(file);
|
|
281
|
-
const lineOf = (
|
|
349
|
+
const lineOf = makeLineAt(content, 1);
|
|
282
350
|
if (hasEmit) {
|
|
283
351
|
EMIT_RE.lastIndex = 0;
|
|
284
352
|
let m;
|
|
@@ -341,10 +409,13 @@ function eventEmitterEdges(ctx) {
|
|
|
341
409
|
* `this.setState`). Over-approximation (all setState methods reach render) is
|
|
342
410
|
* accepted — it's reachability-correct, like the callback channels.
|
|
343
411
|
*/
|
|
344
|
-
function reactRenderEdges(queries, ctx) {
|
|
412
|
+
async function reactRenderEdges(queries, ctx, onYield) {
|
|
413
|
+
let scanned255 = 0;
|
|
345
414
|
const edges = [];
|
|
346
415
|
const seen = new Set();
|
|
347
|
-
for (const cls of queries.
|
|
416
|
+
for (const cls of queries.iterateNodesByKind('class')) {
|
|
417
|
+
if ((++scanned255 & 63) === 0)
|
|
418
|
+
await onYield();
|
|
348
419
|
const children = queries.getOutgoingEdges(cls.id, ['contains'])
|
|
349
420
|
.map((e) => queries.getNodeById(e.target))
|
|
350
421
|
.filter((n) => !!n && n.kind === 'method');
|
|
@@ -384,10 +455,13 @@ function reactRenderEdges(queries, ctx) {
|
|
|
384
455
|
* body calls `setState(` → `build`. The setState gate + `.dart` file keep this to
|
|
385
456
|
* Flutter State classes. Over-approximation accepted (reachability-correct).
|
|
386
457
|
*/
|
|
387
|
-
function flutterBuildEdges(queries, ctx) {
|
|
458
|
+
async function flutterBuildEdges(queries, ctx, onYield) {
|
|
459
|
+
let scanned255 = 0;
|
|
388
460
|
const edges = [];
|
|
389
461
|
const seen = new Set();
|
|
390
|
-
for (const cls of queries.
|
|
462
|
+
for (const cls of queries.iterateNodesByKind('class')) {
|
|
463
|
+
if ((++scanned255 & 63) === 0)
|
|
464
|
+
await onYield();
|
|
391
465
|
const children = queries.getOutgoingEdges(cls.id, ['contains'])
|
|
392
466
|
.map((e) => queries.getNodeById(e.target))
|
|
393
467
|
.filter((n) => !!n && n.kind === 'method');
|
|
@@ -418,6 +492,279 @@ function flutterBuildEdges(queries, ctx) {
|
|
|
418
492
|
}
|
|
419
493
|
return edges;
|
|
420
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Reactive ArkUI property decorators: assigning a property carrying one of
|
|
497
|
+
* these re-runs the owning struct's `build()`. Covers both state models —
|
|
498
|
+
* V1 (`@Component`: State/Prop/Link/Provide/Consume/Storage*) and V2
|
|
499
|
+
* (`@ComponentV2`: Local/Provider/Consumer; `@Param` is read-only in V2 so
|
|
500
|
+
* the assignment gate never fires on it, and `@Trace` lives on `@ObservedV2`
|
|
501
|
+
* data classes, not struct properties).
|
|
502
|
+
*/
|
|
503
|
+
const ARKUI_REACTIVE_DECORATORS = new Set([
|
|
504
|
+
'State', 'Prop', 'Link', 'Provide', 'Consume', 'StorageLink', 'StorageProp',
|
|
505
|
+
'LocalStorageLink', 'LocalStorageProp', 'ObjectLink',
|
|
506
|
+
'Local', 'Provider', 'Consumer',
|
|
507
|
+
]);
|
|
508
|
+
/** ArkUI-observed array mutators — `this.todos.push(x)` re-renders like an assignment. */
|
|
509
|
+
const ARKUI_ARRAY_MUTATORS = 'push|pop|shift|unshift|splice|sort|reverse|fill';
|
|
510
|
+
/**
|
|
511
|
+
* Phase 4b-ets: ArkUI state → build (the ArkTS analog of react-render /
|
|
512
|
+
* flutter-build). Assigning a reactive-decorated property (`@State count`,
|
|
513
|
+
* `@Link selected`, …) re-runs the `@Component struct`'s `build()`, but that
|
|
514
|
+
* hop is framework-internal — no static edge — so "onClick → markAllDone →
|
|
515
|
+
* this.todos = […] → rebuilt list" dead-ends at the assignment. Bridge it:
|
|
516
|
+
* for each arkts struct with a `build()` method and at least one reactive
|
|
517
|
+
* property, link every sibling method whose body ASSIGNS (or array-mutates)
|
|
518
|
+
* one of those properties → `build`. Assignment-gated on the struct's OWN
|
|
519
|
+
* reactive property names — a method that merely reads state, or a struct
|
|
520
|
+
* with no reactive properties, gets nothing (this is the precision line the
|
|
521
|
+
* all-sibling-methods design would erase).
|
|
522
|
+
*/
|
|
523
|
+
async function arkuiStateBuildEdges(queries, ctx, onYield) {
|
|
524
|
+
let scanned255 = 0;
|
|
525
|
+
const edges = [];
|
|
526
|
+
const seen = new Set();
|
|
527
|
+
for (const struct of queries.iterateNodesByKind('struct')) {
|
|
528
|
+
if ((++scanned255 & 63) === 0)
|
|
529
|
+
await onYield();
|
|
530
|
+
if (struct.language !== 'arkts')
|
|
531
|
+
continue;
|
|
532
|
+
const children = queries.getOutgoingEdges(struct.id, ['contains'])
|
|
533
|
+
.map((e) => queries.getNodeById(e.target))
|
|
534
|
+
.filter((n) => !!n);
|
|
535
|
+
const build = children.find((n) => n.kind === 'method' && n.name === 'build');
|
|
536
|
+
if (!build)
|
|
537
|
+
continue;
|
|
538
|
+
const reactiveProps = children.filter((n) => n.kind === 'property' && (n.decorators ?? []).some((d) => ARKUI_REACTIVE_DECORATORS.has(d)));
|
|
539
|
+
if (reactiveProps.length === 0)
|
|
540
|
+
continue;
|
|
541
|
+
const propAlternation = reactiveProps
|
|
542
|
+
.map((p) => p.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
543
|
+
.join('|');
|
|
544
|
+
// `this.count = …` / `+=` / `++` / `--` / `this.todos.push(…)`. The
|
|
545
|
+
// `=(?!=)` keeps `this.done == x` comparisons out.
|
|
546
|
+
const mutationRe = new RegExp(`this\\.(?:${propAlternation})\\s*(?:=(?!=)|\\+\\+|--|[+\\-*/%&|^]=|\\.(?:${ARKUI_ARRAY_MUTATORS})\\s*\\()`);
|
|
547
|
+
let added = 0;
|
|
548
|
+
for (const m of children) {
|
|
549
|
+
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
550
|
+
break;
|
|
551
|
+
if (m.kind !== 'method' || m.id === build.id)
|
|
552
|
+
continue;
|
|
553
|
+
const content = ctx.readFile(m.filePath);
|
|
554
|
+
const src = content && sliceLines(content, m.startLine, m.endLine);
|
|
555
|
+
if (!src || !mutationRe.test((0, strip_comments_1.stripCommentsForRegex)(src, 'typescript')))
|
|
556
|
+
continue;
|
|
557
|
+
const key = `${m.id}>${build.id}`;
|
|
558
|
+
if (seen.has(key))
|
|
559
|
+
continue;
|
|
560
|
+
seen.add(key);
|
|
561
|
+
edges.push({
|
|
562
|
+
source: m.id, target: build.id, kind: 'calls', line: m.startLine,
|
|
563
|
+
provenance: 'heuristic',
|
|
564
|
+
metadata: { synthesizedBy: 'arkui-state', via: 'state assignment', registeredAt: `${build.filePath}:${build.startLine}` },
|
|
565
|
+
});
|
|
566
|
+
added++;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return edges;
|
|
570
|
+
}
|
|
571
|
+
/** Emit/subscribe call sites of HarmonyOS's `@ohos.events.emitter` bus. */
|
|
572
|
+
const ARKUI_EMITTER_CALL_RE = /\bemitter\s*\.\s*(emit|on|once)\s*\(\s*([A-Za-z_$][\w$.]*|\{[^)]{0,120}?\beventId\s*:\s*[^,}]+[^)]*?\})/g;
|
|
573
|
+
/** Cap per event bucket — a generic key with many parties is dynamic routing, not a static pair. */
|
|
574
|
+
const ARKUI_EMITTER_FANOUT_CAP = 8;
|
|
575
|
+
/**
|
|
576
|
+
* Phase 4b-ets2: HarmonyOS `@ohos.events.emitter` bridge. The cross-component
|
|
577
|
+
* bus — `emitter.emit(eventId)` fires `emitter.on(eventId, cb)` — is
|
|
578
|
+
* framework-internal, so an order flow riding it (OrangeShopping's
|
|
579
|
+
* add-to-cart) dead-ends at the emit. Link emit-site enclosing
|
|
580
|
+
* function/method → on/once-site enclosing function/method when both
|
|
581
|
+
* reference the SAME statically-recoverable event key.
|
|
582
|
+
*
|
|
583
|
+
* Key recovery, per call site (comment-stripped enclosing-file source): the
|
|
584
|
+
* first argument is an `{ eventId: K }` literal, a `Names.Dotted` constant, or
|
|
585
|
+
* a local whose same-file declaration is `new EventsId(K)` / `= K` — chase one
|
|
586
|
+
* level. Precision scoping learned from the samples monorepo (thousands of
|
|
587
|
+
* unrelated samples, most using eventId 1): NUMERIC keys pair within the same
|
|
588
|
+
* FILE only; NAMED keys pair within the same workspace module directory (or
|
|
589
|
+
* the whole project when it declares no modules — the single-app case), both
|
|
590
|
+
* behind a fan-out cap. Inline `on(id, (e) => {…})` arrows need no special
|
|
591
|
+
* handling — their bodies' calls already attribute to the registering method,
|
|
592
|
+
* so targeting that method keeps the chain connected.
|
|
593
|
+
*/
|
|
594
|
+
async function arkuiEmitterEdges(ctx, onYield) {
|
|
595
|
+
let scannedFiles = 0;
|
|
596
|
+
// bucket key -> emit sites / handler sites
|
|
597
|
+
const emits = new Map();
|
|
598
|
+
const handlers = new Map();
|
|
599
|
+
const moduleDirs = (() => {
|
|
600
|
+
const ws = ctx.getWorkspacePackages?.();
|
|
601
|
+
return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
|
|
602
|
+
})();
|
|
603
|
+
const moduleScopeOf = (file) => {
|
|
604
|
+
for (const dir of moduleDirs) {
|
|
605
|
+
if (file === dir || file.startsWith(dir + '/'))
|
|
606
|
+
return dir;
|
|
607
|
+
}
|
|
608
|
+
return '';
|
|
609
|
+
};
|
|
610
|
+
for (const file of ctx.getAllFiles()) {
|
|
611
|
+
if ((++scannedFiles & 15) === 0)
|
|
612
|
+
await onYield();
|
|
613
|
+
if (!file.endsWith('.ets'))
|
|
614
|
+
continue;
|
|
615
|
+
const content = ctx.readFile(file);
|
|
616
|
+
if (!content || !content.includes('emitter.'))
|
|
617
|
+
continue;
|
|
618
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
|
|
619
|
+
const nodes = ctx.getNodesInFile(file)
|
|
620
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
621
|
+
ARKUI_EMITTER_CALL_RE.lastIndex = 0;
|
|
622
|
+
let m;
|
|
623
|
+
while ((m = ARKUI_EMITTER_CALL_RE.exec(safe))) {
|
|
624
|
+
const verb = m[1];
|
|
625
|
+
const arg = m[2].trim();
|
|
626
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
627
|
+
const encl = nodes
|
|
628
|
+
.filter((n) => n.startLine <= line && n.endLine >= line)
|
|
629
|
+
.sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
|
|
630
|
+
if (!encl)
|
|
631
|
+
continue;
|
|
632
|
+
// Recover the event key from the first argument.
|
|
633
|
+
let key = null;
|
|
634
|
+
const idLit = arg.startsWith('{') ? arg.match(/\beventId\s*:\s*([\w$.]+)/)?.[1] : undefined;
|
|
635
|
+
const token = idLit ?? arg;
|
|
636
|
+
if (token !== undefined) {
|
|
637
|
+
if (/^\d+$/.test(token)) {
|
|
638
|
+
key = `num:${file}:${token}`; // numeric: same-file only
|
|
639
|
+
}
|
|
640
|
+
else if (token.includes('.')) {
|
|
641
|
+
key = `name:${moduleScopeOf(file)}:${token}`;
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
// Local variable — chase its same-file declaration one level:
|
|
645
|
+
// `let x = new EventsId(K)` / `const x = K`.
|
|
646
|
+
const declRe = new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b\\s*(?::[^=\\n]+)?=\\s*(?:new\\s+[\\w$.]+\\(\\s*([^)\\n]+?)\\s*\\)|([\\w$.]+))`);
|
|
647
|
+
const decl = safe.match(declRe);
|
|
648
|
+
const inner = (decl?.[1] ?? decl?.[2])?.trim();
|
|
649
|
+
if (inner && /^\d+$/.test(inner))
|
|
650
|
+
key = `num:${file}:${inner}`;
|
|
651
|
+
else if (inner && /^[\w$.]+$/.test(inner))
|
|
652
|
+
key = `name:${moduleScopeOf(file)}:${inner}`;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (!key)
|
|
656
|
+
continue;
|
|
657
|
+
const site = { nodeId: encl.id, file, line };
|
|
658
|
+
if (verb === 'emit') {
|
|
659
|
+
(emits.get(key) ?? emits.set(key, []).get(key)).push(site);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
(handlers.get(key) ?? handlers.set(key, []).get(key)).push(site);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const edges = [];
|
|
667
|
+
const seen = new Set();
|
|
668
|
+
for (const [key, emitSites] of emits) {
|
|
669
|
+
const handlerSites = handlers.get(key);
|
|
670
|
+
if (!handlerSites)
|
|
671
|
+
continue;
|
|
672
|
+
if (emitSites.length > ARKUI_EMITTER_FANOUT_CAP || handlerSites.length > ARKUI_EMITTER_FANOUT_CAP)
|
|
673
|
+
continue;
|
|
674
|
+
const eventLabel = key.slice(key.lastIndexOf(':') + 1);
|
|
675
|
+
for (const e of emitSites)
|
|
676
|
+
for (const h of handlerSites) {
|
|
677
|
+
if (e.nodeId === h.nodeId)
|
|
678
|
+
continue;
|
|
679
|
+
const dedupe = `${e.nodeId}>${h.nodeId}`;
|
|
680
|
+
if (seen.has(dedupe))
|
|
681
|
+
continue;
|
|
682
|
+
seen.add(dedupe);
|
|
683
|
+
edges.push({
|
|
684
|
+
source: e.nodeId, target: h.nodeId, kind: 'calls', line: e.line,
|
|
685
|
+
provenance: 'heuristic',
|
|
686
|
+
metadata: { synthesizedBy: 'arkui-emitter', event: eventLabel, registeredAt: `${h.file}:${h.line}` },
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return edges;
|
|
691
|
+
}
|
|
692
|
+
/** `router.pushUrl({ url: 'pages/Detail' })` / replaceUrl — literal urls only. */
|
|
693
|
+
const ARKUI_ROUTER_RE = /\brouter\s*\.\s*(?:pushUrl|replaceUrl)\s*\(\s*\{[^)]{0,200}?\burl\s*:\s*['"]([\w\-./]+)['"]/g;
|
|
694
|
+
/**
|
|
695
|
+
* Phase 4b-ets3: HarmonyOS page navigation. `router.pushUrl({ url:
|
|
696
|
+
* 'pages/Detail' })` reaches the `@Entry struct` of
|
|
697
|
+
* `<module>/src/main/ets/pages/Detail.ets`, but the hop is a string — no
|
|
698
|
+
* static edge — so "tap → openDetail → ???" ends at the router call. Bridge
|
|
699
|
+
* literal urls to the page struct: the url resolves against the standard
|
|
700
|
+
* `src/main/ets/` layout (what main_pages.json entries name); candidates
|
|
701
|
+
* prefer the caller's own workspace module (routes are module-scoped), and
|
|
702
|
+
* anything still ambiguous is dropped rather than guessed. Only `@Entry`
|
|
703
|
+
* structs qualify as targets — the decorator is what makes a file a page.
|
|
704
|
+
*/
|
|
705
|
+
async function arkuiRouterEdges(ctx, onYield) {
|
|
706
|
+
const edges = [];
|
|
707
|
+
const seen = new Set();
|
|
708
|
+
const allFiles = ctx.getAllFiles();
|
|
709
|
+
const moduleDirs = (() => {
|
|
710
|
+
const ws = ctx.getWorkspacePackages?.();
|
|
711
|
+
return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
|
|
712
|
+
})();
|
|
713
|
+
const moduleScopeOf = (file) => {
|
|
714
|
+
for (const dir of moduleDirs) {
|
|
715
|
+
if (file === dir || file.startsWith(dir + '/'))
|
|
716
|
+
return dir;
|
|
717
|
+
}
|
|
718
|
+
return '';
|
|
719
|
+
};
|
|
720
|
+
let scannedFiles = 0;
|
|
721
|
+
for (const file of allFiles) {
|
|
722
|
+
if ((++scannedFiles & 15) === 0)
|
|
723
|
+
await onYield();
|
|
724
|
+
if (!file.endsWith('.ets'))
|
|
725
|
+
continue;
|
|
726
|
+
const content = ctx.readFile(file);
|
|
727
|
+
if (!content || !content.includes('router.'))
|
|
728
|
+
continue;
|
|
729
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
|
|
730
|
+
const nodes = ctx.getNodesInFile(file)
|
|
731
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
732
|
+
ARKUI_ROUTER_RE.lastIndex = 0;
|
|
733
|
+
let m;
|
|
734
|
+
while ((m = ARKUI_ROUTER_RE.exec(safe))) {
|
|
735
|
+
const url = m[1];
|
|
736
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
737
|
+
const encl = nodes
|
|
738
|
+
.filter((n) => n.startLine <= line && n.endLine >= line)
|
|
739
|
+
.sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
|
|
740
|
+
if (!encl)
|
|
741
|
+
continue;
|
|
742
|
+
const suffix = `/src/main/ets/${url}.ets`;
|
|
743
|
+
let candidates = allFiles.filter((f) => f.endsWith(suffix));
|
|
744
|
+
if (candidates.length > 1) {
|
|
745
|
+
const scope = moduleScopeOf(file);
|
|
746
|
+
const sameModule = candidates.filter((f) => moduleScopeOf(f) === scope);
|
|
747
|
+
if (sameModule.length > 0)
|
|
748
|
+
candidates = sameModule;
|
|
749
|
+
}
|
|
750
|
+
if (candidates.length !== 1)
|
|
751
|
+
continue; // ambiguous or unresolved — never guess
|
|
752
|
+
const page = ctx.getNodesInFile(candidates[0]).find((n) => n.kind === 'struct' && (n.decorators ?? []).includes('Entry'));
|
|
753
|
+
if (!page)
|
|
754
|
+
continue;
|
|
755
|
+
const key = `${encl.id}>${page.id}`;
|
|
756
|
+
if (seen.has(key))
|
|
757
|
+
continue;
|
|
758
|
+
seen.add(key);
|
|
759
|
+
edges.push({
|
|
760
|
+
source: encl.id, target: page.id, kind: 'calls', line,
|
|
761
|
+
provenance: 'heuristic',
|
|
762
|
+
metadata: { synthesizedBy: 'arkui-route', event: url, registeredAt: `${candidates[0]}:${page.startLine}` },
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return edges;
|
|
767
|
+
}
|
|
421
768
|
/**
|
|
422
769
|
* Phase 4c: C++ virtual override. A call through a base/interface pointer
|
|
423
770
|
* (`db->Get(...)`, `iter->Next()`) dispatches at runtime to a subclass override,
|
|
@@ -428,14 +775,17 @@ function flutterBuildEdges(queries, ctx) {
|
|
|
428
775
|
* implementation(s). Over-approximation accepted (reachability-correct); capped
|
|
429
776
|
* per class and gated to C++ to avoid touching other languages' dispatch.
|
|
430
777
|
*/
|
|
431
|
-
function cppOverrideEdges(queries) {
|
|
778
|
+
async function cppOverrideEdges(queries, onYield) {
|
|
779
|
+
let scanned255 = 0;
|
|
432
780
|
const edges = [];
|
|
433
781
|
const seen = new Set();
|
|
434
782
|
const methodsOf = (classId) => queries
|
|
435
783
|
.getOutgoingEdges(classId, ['contains'])
|
|
436
784
|
.map((e) => queries.getNodeById(e.target))
|
|
437
785
|
.filter((n) => !!n && n.kind === 'method');
|
|
438
|
-
for (const cls of queries.
|
|
786
|
+
for (const cls of queries.iterateNodesByKind('class')) {
|
|
787
|
+
if ((++scanned255 & 63) === 0)
|
|
788
|
+
await onYield();
|
|
439
789
|
const subMethods = methodsOf(cls.id).filter((n) => n.language === 'cpp');
|
|
440
790
|
if (subMethods.length === 0)
|
|
441
791
|
continue;
|
|
@@ -489,6 +839,7 @@ function cppOverrideEdges(queries) {
|
|
|
489
839
|
// or an `object` (Scala) so the loop also iterates those kinds.
|
|
490
840
|
const IFACE_OVERRIDE_LANGS = new Set([
|
|
491
841
|
'java', 'kotlin', 'csharp', 'typescript', 'javascript', 'swift', 'scala', 'go', 'rust',
|
|
842
|
+
'arkts',
|
|
492
843
|
]);
|
|
493
844
|
/**
|
|
494
845
|
* Go implicit interface satisfaction (#584). Go has no `implements` keyword — a
|
|
@@ -502,7 +853,8 @@ const IFACE_OVERRIDE_LANGS = new Set([
|
|
|
502
853
|
* with the other dispatch synthesizers; capped per interface. Empty interfaces
|
|
503
854
|
* (`any`) are skipped so they don't match every struct.
|
|
504
855
|
*/
|
|
505
|
-
function goImplementsEdges(queries) {
|
|
856
|
+
async function goImplementsEdges(queries, onYield) {
|
|
857
|
+
let scanned255 = 0;
|
|
506
858
|
const edges = [];
|
|
507
859
|
const seen = new Set();
|
|
508
860
|
const methodNameSet = (id) => new Set(queries
|
|
@@ -510,11 +862,24 @@ function goImplementsEdges(queries) {
|
|
|
510
862
|
.map((e) => queries.getNodeById(e.target))
|
|
511
863
|
.filter((n) => !!n && n.kind === 'method')
|
|
512
864
|
.map((n) => n.name));
|
|
513
|
-
|
|
865
|
+
// Materializes GO structs only (the pass is language-gated by the caller),
|
|
866
|
+
// never the whole struct kind — that array is O(nodes) on struct-heavy
|
|
867
|
+
// repos like the Linux kernel (#1212).
|
|
868
|
+
const goStructs = [];
|
|
869
|
+
for (const s of queries.iterateNodesByKind('struct')) {
|
|
870
|
+
if ((++scanned255 & 63) === 0)
|
|
871
|
+
await onYield();
|
|
872
|
+
if (s.language === 'go')
|
|
873
|
+
goStructs.push(s);
|
|
874
|
+
}
|
|
514
875
|
const structMethods = new Map();
|
|
515
876
|
for (const s of goStructs)
|
|
516
877
|
structMethods.set(s.id, methodNameSet(s.id));
|
|
517
|
-
for (const iface of queries.
|
|
878
|
+
for (const iface of queries.iterateNodesByKind('interface')) {
|
|
879
|
+
if ((++scanned255 & 63) === 0)
|
|
880
|
+
await onYield();
|
|
881
|
+
if ((++scanned255 & 63) === 0)
|
|
882
|
+
await onYield();
|
|
518
883
|
if (iface.language !== 'go')
|
|
519
884
|
continue;
|
|
520
885
|
const want = methodNameSet(iface.id);
|
|
@@ -574,7 +939,8 @@ function goImplementsEdges(queries) {
|
|
|
574
939
|
* matching the same-file edges extraction already emits). Skips methods that
|
|
575
940
|
* already have a type parent (the same-file case). (#583, cross-file half)
|
|
576
941
|
*/
|
|
577
|
-
function goCrossFileMethodContainsEdges(queries) {
|
|
942
|
+
async function goCrossFileMethodContainsEdges(queries, onYield) {
|
|
943
|
+
let scanned255 = 0;
|
|
578
944
|
const edges = [];
|
|
579
945
|
const seen = new Set();
|
|
580
946
|
const TYPE_KINDS = new Set(['struct', 'class', 'interface', 'enum', 'type_alias']);
|
|
@@ -582,7 +948,11 @@ function goCrossFileMethodContainsEdges(queries) {
|
|
|
582
948
|
const i = p.replace(/\\/g, '/').lastIndexOf('/');
|
|
583
949
|
return i >= 0 ? p.slice(0, i) : '';
|
|
584
950
|
};
|
|
585
|
-
for (const method of queries.
|
|
951
|
+
for (const method of queries.iterateNodesByKind('method')) {
|
|
952
|
+
if ((++scanned255 & 63) === 0)
|
|
953
|
+
await onYield();
|
|
954
|
+
if ((++scanned255 & 63) === 0)
|
|
955
|
+
await onYield();
|
|
586
956
|
if (method.language !== 'go')
|
|
587
957
|
continue;
|
|
588
958
|
// The receiver type is encoded in the method's qualifiedName as `Recv::name`
|
|
@@ -652,13 +1022,20 @@ const KMP_TYPE_KINDS = new Set(['class', 'interface', 'struct', 'enum', 'type_al
|
|
|
652
1022
|
function kmpKindsCompatible(a, b) {
|
|
653
1023
|
return a === b || (KMP_TYPE_KINDS.has(a) && KMP_TYPE_KINDS.has(b));
|
|
654
1024
|
}
|
|
655
|
-
function kotlinExpectActualEdges(queries) {
|
|
1025
|
+
async function kotlinExpectActualEdges(queries, onYield) {
|
|
1026
|
+
let scanned255 = 0;
|
|
656
1027
|
const edges = [];
|
|
657
1028
|
const seen = new Set();
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
1029
|
+
// SQL-side language+decorator pre-filter, streamed. The old
|
|
1030
|
+
// `getAllNodes().filter(...)` hydrated the ENTIRE node table into one array
|
|
1031
|
+
// just to find kotlin `actual` declarations — on a 2M-node graph that alone
|
|
1032
|
+
// exceeded Node's default heap and killed the index (#1212). The LIKE
|
|
1033
|
+
// pre-filter can over-match (substring), so the exact decorator check stays.
|
|
1034
|
+
for (const act of queries.iterateNodesByLanguageWithDecorator('kotlin', 'actual')) {
|
|
1035
|
+
if ((++scanned255 & 63) === 0)
|
|
1036
|
+
await onYield();
|
|
1037
|
+
if (!act.decorators?.includes('actual'))
|
|
1038
|
+
continue;
|
|
662
1039
|
let added = 0;
|
|
663
1040
|
for (const cand of queries.getNodesByQualifiedNameExact(act.qualifiedName)) {
|
|
664
1041
|
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
@@ -692,7 +1069,8 @@ function kotlinExpectActualEdges(queries) {
|
|
|
692
1069
|
}
|
|
693
1070
|
return edges;
|
|
694
1071
|
}
|
|
695
|
-
function interfaceOverrideEdges(queries) {
|
|
1072
|
+
async function interfaceOverrideEdges(queries, onYield) {
|
|
1073
|
+
let scanned255 = 0;
|
|
696
1074
|
const edges = [];
|
|
697
1075
|
const seen = new Set();
|
|
698
1076
|
const methodsOf = (classId) => queries
|
|
@@ -704,7 +1082,9 @@ function interfaceOverrideEdges(queries) {
|
|
|
704
1082
|
// types that conform to protocols. Iterate both.
|
|
705
1083
|
const concreteKinds = ['class', 'struct'];
|
|
706
1084
|
for (const kind of concreteKinds) {
|
|
707
|
-
for (const cls of queries.
|
|
1085
|
+
for (const cls of queries.iterateNodesByKind(kind)) {
|
|
1086
|
+
if ((++scanned255 & 63) === 0)
|
|
1087
|
+
await onYield();
|
|
708
1088
|
const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
|
|
709
1089
|
if (implMethods.length === 0)
|
|
710
1090
|
continue;
|
|
@@ -778,7 +1158,8 @@ function interfaceOverrideEdges(queries) {
|
|
|
778
1158
|
* Provenance: `heuristic`, `synthesizedBy: 'go-grpc-stub-impl'`. The
|
|
779
1159
|
* stub's source line is the wiring site shown in the trace trail.
|
|
780
1160
|
*/
|
|
781
|
-
function goGrpcStubImplEdges(queries) {
|
|
1161
|
+
async function goGrpcStubImplEdges(queries, onYield) {
|
|
1162
|
+
let scanned255 = 0;
|
|
782
1163
|
const edges = [];
|
|
783
1164
|
const seen = new Set();
|
|
784
1165
|
const STUB_RE = /^Unimplemented.*Server$/;
|
|
@@ -790,7 +1171,9 @@ function goGrpcStubImplEdges(queries) {
|
|
|
790
1171
|
const methodNamesByStruct = new Map();
|
|
791
1172
|
const methodNodesByStruct = new Map();
|
|
792
1173
|
const goStructs = [];
|
|
793
|
-
for (const s of queries.
|
|
1174
|
+
for (const s of queries.iterateNodesByKind('struct')) {
|
|
1175
|
+
if ((++scanned255 & 63) === 0)
|
|
1176
|
+
await onYield();
|
|
794
1177
|
if (s.language !== 'go')
|
|
795
1178
|
continue;
|
|
796
1179
|
goStructs.push(s);
|
|
@@ -873,11 +1256,17 @@ function goGrpcStubImplEdges(queries) {
|
|
|
873
1256
|
* component/function/class node — TS generics like `Array<Foo>` resolve to a type
|
|
874
1257
|
* (or nothing) and are dropped.
|
|
875
1258
|
*/
|
|
876
|
-
function reactJsxChildEdges(ctx) {
|
|
1259
|
+
async function reactJsxChildEdges(ctx, onYield) {
|
|
1260
|
+
let scannedFiles = 0;
|
|
877
1261
|
const edges = [];
|
|
878
1262
|
const seen = new Set();
|
|
879
1263
|
const PARENT_KINDS = new Set(['method', 'function', 'component']);
|
|
1264
|
+
let scanned = 0;
|
|
880
1265
|
for (const file of ctx.getAllFiles()) {
|
|
1266
|
+
if ((++scannedFiles & 15) === 0)
|
|
1267
|
+
await onYield();
|
|
1268
|
+
if ((++scanned & 255) === 0)
|
|
1269
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
881
1270
|
const content = ctx.readFile(file);
|
|
882
1271
|
if (!content || (!content.includes('</') && !content.includes('/>')))
|
|
883
1272
|
continue; // JSX-file gate
|
|
@@ -925,7 +1314,9 @@ function reactJsxChildEdges(ctx) {
|
|
|
925
1314
|
* component, handler→function/method) keeps precision; inline arrows / `$emit`
|
|
926
1315
|
* skipped.
|
|
927
1316
|
*/
|
|
928
|
-
function vueTemplateEdges(ctx) {
|
|
1317
|
+
async function vueTemplateEdges(ctx, onYield) {
|
|
1318
|
+
let scannedFiles = 0;
|
|
1319
|
+
let scanned255 = 0;
|
|
929
1320
|
const edges = [];
|
|
930
1321
|
const seen = new Set();
|
|
931
1322
|
const COMPONENT_KINDS = new Set(['component', 'function', 'class']);
|
|
@@ -939,12 +1330,16 @@ function vueTemplateEdges(ctx) {
|
|
|
939
1330
|
// misses it (flat components match by basename and don't need this). Map each
|
|
940
1331
|
// nested component's Nuxt name → node so those template usages resolve.
|
|
941
1332
|
const nuxtComponents = new Map();
|
|
942
|
-
for (const c of ctx.getNodesByKind('component')) {
|
|
1333
|
+
for (const c of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
|
|
1334
|
+
if ((++scanned255 & 63) === 0)
|
|
1335
|
+
await onYield();
|
|
943
1336
|
const nn = nuxtComponentName(c.filePath);
|
|
944
1337
|
if (nn && !nuxtComponents.has(nn))
|
|
945
1338
|
nuxtComponents.set(nn, c);
|
|
946
1339
|
}
|
|
947
1340
|
for (const file of ctx.getAllFiles()) {
|
|
1341
|
+
if ((++scannedFiles & 15) === 0)
|
|
1342
|
+
await onYield();
|
|
948
1343
|
if (!file.endsWith('.vue'))
|
|
949
1344
|
continue;
|
|
950
1345
|
const content = ctx.readFile(file);
|
|
@@ -1078,18 +1473,21 @@ const RN_JVM_EMIT_RE = /\.emit\s*\(\s*"([^"]+)"\s*,/g;
|
|
|
1078
1473
|
// statement and stops at a block boundary, so the wrapper DEFINITION (whose `(`
|
|
1079
1474
|
// is followed by `… ) {`) never matches. Multi-line tolerant. (java/kotlin/swift)
|
|
1080
1475
|
const RN_NATIVE_SENDEVENT_RE = /\bsendEvent\s*\([^;{}]*?"([^"]+)"/g;
|
|
1081
|
-
function rnEventEdges(ctx) {
|
|
1476
|
+
async function rnEventEdges(ctx, onYield) {
|
|
1477
|
+
let scannedFiles = 0;
|
|
1082
1478
|
// Native dispatchers (source = the native method whose body sends the
|
|
1083
1479
|
// event) and JS handlers (target = the function/method registered as
|
|
1084
1480
|
// the listener) keyed by event name.
|
|
1085
1481
|
const nativeDispatchersByEvent = new Map();
|
|
1086
1482
|
const jsHandlersByEvent = new Map();
|
|
1087
1483
|
for (const file of ctx.getAllFiles()) {
|
|
1484
|
+
if ((++scannedFiles & 15) === 0)
|
|
1485
|
+
await onYield();
|
|
1088
1486
|
const content = ctx.readFile(file);
|
|
1089
1487
|
if (!content)
|
|
1090
1488
|
continue;
|
|
1091
1489
|
const nodesInFile = ctx.getNodesInFile(file);
|
|
1092
|
-
const lineOf = (
|
|
1490
|
+
const lineOf = makeLineAt(content, 1);
|
|
1093
1491
|
const addDispatcher = (event, line) => {
|
|
1094
1492
|
const disp = enclosingFn(nodesInFile, line);
|
|
1095
1493
|
if (!disp)
|
|
@@ -1272,11 +1670,14 @@ const FABRIC_NATIVE_SUFFIXES = ['', 'View', 'ViewManager', 'ComponentView', 'Man
|
|
|
1272
1670
|
* caller. The Expo method nodes are id-prefixed `expo-module:` and qualified
|
|
1273
1671
|
* `<file>::<module>.<method>` by the framework extractor.
|
|
1274
1672
|
*/
|
|
1275
|
-
function expoCrossPlatformEdges(queries) {
|
|
1673
|
+
async function expoCrossPlatformEdges(queries, onYield) {
|
|
1674
|
+
let scanned255 = 0;
|
|
1276
1675
|
const edges = [];
|
|
1277
1676
|
const seen = new Set();
|
|
1278
1677
|
const byKey = new Map();
|
|
1279
|
-
for (const m of queries.
|
|
1678
|
+
for (const m of queries.iterateNodesByKind('method')) {
|
|
1679
|
+
if ((++scanned255 & 63) === 0)
|
|
1680
|
+
await onYield();
|
|
1280
1681
|
if (!m.id.startsWith('expo-module:'))
|
|
1281
1682
|
continue;
|
|
1282
1683
|
const key = m.qualifiedName.split('::').pop(); // `<module>.<method>`
|
|
@@ -1325,7 +1726,8 @@ function expoCrossPlatformEdges(queries) {
|
|
|
1325
1726
|
* `getFreeDiskStorage`) — that's the JS-visible name, and how the iOS selector
|
|
1326
1727
|
* lines up with the bare Android method name.
|
|
1327
1728
|
*/
|
|
1328
|
-
function rnCrossPlatformEdges(queries) {
|
|
1729
|
+
async function rnCrossPlatformEdges(queries, onYield) {
|
|
1730
|
+
let scanned255 = 0;
|
|
1329
1731
|
const edges = [];
|
|
1330
1732
|
const seen = new Set();
|
|
1331
1733
|
const NATIVE = new Set(['java', 'kotlin', 'objc', 'cpp']);
|
|
@@ -1347,6 +1749,8 @@ function rnCrossPlatformEdges(queries) {
|
|
|
1347
1749
|
// below only runs for genuine cross-platform candidates.
|
|
1348
1750
|
const byName = new Map();
|
|
1349
1751
|
for (const m of queries.iterateNodesByKind('method')) {
|
|
1752
|
+
if ((++scanned255 & 63) === 0)
|
|
1753
|
+
await onYield();
|
|
1350
1754
|
if (!NATIVE.has(m.language))
|
|
1351
1755
|
continue;
|
|
1352
1756
|
const key = norm(m.name);
|
|
@@ -1397,17 +1801,27 @@ function rnCrossPlatformEdges(queries) {
|
|
|
1397
1801
|
}
|
|
1398
1802
|
return edges;
|
|
1399
1803
|
}
|
|
1400
|
-
function fabricNativeImplEdges(ctx) {
|
|
1804
|
+
async function fabricNativeImplEdges(ctx, onYield) {
|
|
1805
|
+
let scanned255 = 0;
|
|
1401
1806
|
const edges = [];
|
|
1402
1807
|
const seen = new Set();
|
|
1403
1808
|
// The Fabric extractor IDs are prefixed `fabric-component:` so we can
|
|
1404
|
-
// filter to just those
|
|
1405
|
-
|
|
1809
|
+
// filter to just those while streaming — never materializing the whole
|
|
1810
|
+
// `component` kind (#1212).
|
|
1811
|
+
const components = [];
|
|
1812
|
+
for (const n of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
|
|
1813
|
+
if ((++scanned255 & 63) === 0)
|
|
1814
|
+
await onYield();
|
|
1815
|
+
if (n.id.startsWith('fabric-component:'))
|
|
1816
|
+
components.push(n);
|
|
1817
|
+
}
|
|
1406
1818
|
if (components.length === 0)
|
|
1407
1819
|
return edges;
|
|
1408
1820
|
// Pre-index native classes by name for O(1) lookup.
|
|
1409
1821
|
const nativeClassesByName = new Map();
|
|
1410
|
-
for (const n of ctx.getNodesByKind('class')) {
|
|
1822
|
+
for (const n of (ctx.iterateNodesByKind?.('class') ?? ctx.getNodesByKind('class'))) {
|
|
1823
|
+
if ((++scanned255 & 63) === 0)
|
|
1824
|
+
await onYield();
|
|
1411
1825
|
if (n.language !== 'objc' && n.language !== 'kotlin' && n.language !== 'java' && n.language !== 'cpp')
|
|
1412
1826
|
continue;
|
|
1413
1827
|
const arr = nativeClassesByName.get(n.name);
|
|
@@ -1460,12 +1874,15 @@ function fabricNativeImplEdges(ctx) {
|
|
|
1460
1874
|
* same simple name) are dropped. We need-not bridge by package because Java
|
|
1461
1875
|
* mapper interfaces are typically uniquely named within a project.
|
|
1462
1876
|
*/
|
|
1463
|
-
function mybatisJavaXmlEdges(queries) {
|
|
1877
|
+
async function mybatisJavaXmlEdges(queries, onYield) {
|
|
1878
|
+
let scanned255 = 0;
|
|
1464
1879
|
const edges = [];
|
|
1465
1880
|
const seen = new Set();
|
|
1466
1881
|
// Index Java methods by `<ClassName>::<methodName>` for O(1) lookup.
|
|
1467
1882
|
const javaIndex = new Map();
|
|
1468
1883
|
for (const m of queries.iterateNodesByKind('method')) {
|
|
1884
|
+
if ((++scanned255 & 63) === 0)
|
|
1885
|
+
await onYield();
|
|
1469
1886
|
if (m.language !== 'java' && m.language !== 'kotlin')
|
|
1470
1887
|
continue;
|
|
1471
1888
|
const parts = m.qualifiedName.split('::');
|
|
@@ -1481,6 +1898,8 @@ function mybatisJavaXmlEdges(queries) {
|
|
|
1481
1898
|
javaIndex.set(key, [m]);
|
|
1482
1899
|
}
|
|
1483
1900
|
for (const xml of queries.iterateNodesByKind('method')) {
|
|
1901
|
+
if ((++scanned255 & 63) === 0)
|
|
1902
|
+
await onYield();
|
|
1484
1903
|
if (xml.language !== 'xml')
|
|
1485
1904
|
continue;
|
|
1486
1905
|
// Qualified name: `<namespace>::<id>`. Extract the simple class name.
|
|
@@ -1594,10 +2013,14 @@ function goHandlerIdent(expr) {
|
|
|
1594
2013
|
const m = cleaned.match(/(?:\.|^)([A-Za-z_]\w*)$/);
|
|
1595
2014
|
return m ? m[1] : null;
|
|
1596
2015
|
}
|
|
1597
|
-
function ginMiddlewareChainEdges(queries, ctx) {
|
|
2016
|
+
async function ginMiddlewareChainEdges(queries, ctx, onYield) {
|
|
2017
|
+
let scanned255 = 0;
|
|
2018
|
+
let scannedFiles = 0;
|
|
1598
2019
|
// 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index.
|
|
1599
2020
|
const dispatchers = [];
|
|
1600
2021
|
for (const n of queries.iterateNodesByKind('method')) {
|
|
2022
|
+
if ((++scanned255 & 63) === 0)
|
|
2023
|
+
await onYield();
|
|
1601
2024
|
if (n.language !== 'go')
|
|
1602
2025
|
continue;
|
|
1603
2026
|
const content = ctx.readFile(n.filePath);
|
|
@@ -1612,6 +2035,8 @@ function ginMiddlewareChainEdges(queries, ctx) {
|
|
|
1612
2035
|
// closures are dropped by goHandlerIdent; the rest are HandlerFuncs.
|
|
1613
2036
|
const registered = new Map(); // name → registeredAt (file:line)
|
|
1614
2037
|
for (const file of ctx.getAllFiles()) {
|
|
2038
|
+
if ((++scannedFiles & 15) === 0)
|
|
2039
|
+
await onYield();
|
|
1615
2040
|
if (!file.endsWith('.go'))
|
|
1616
2041
|
continue;
|
|
1617
2042
|
const content = ctx.readFile(file);
|
|
@@ -1667,10 +2092,13 @@ function ginMiddlewareChainEdges(queries, ctx) {
|
|
|
1667
2092
|
* clause. Link the unit → its form so a `.dfm`/`.fmx` used only as a form
|
|
1668
2093
|
* definition isn't orphaned, and editing the form surfaces its code-behind unit.
|
|
1669
2094
|
*/
|
|
1670
|
-
function pascalFormEdges(ctx) {
|
|
2095
|
+
async function pascalFormEdges(ctx, onYield) {
|
|
2096
|
+
let scannedFiles = 0;
|
|
1671
2097
|
const edges = [];
|
|
1672
2098
|
const allFiles = new Set(ctx.getAllFiles());
|
|
1673
2099
|
for (const file of allFiles) {
|
|
2100
|
+
if ((++scannedFiles & 255) === 0)
|
|
2101
|
+
await onYield();
|
|
1674
2102
|
if (!/\.(dfm|fmx)$/i.test(file))
|
|
1675
2103
|
continue;
|
|
1676
2104
|
const pasFile = file.replace(/\.(dfm|fmx)$/i, '.pas');
|
|
@@ -1706,12 +2134,15 @@ function pascalFormEdges(ctx) {
|
|
|
1706
2134
|
* a loader's data shows the page it feeds) and the page's dependencies include
|
|
1707
2135
|
* its loader.
|
|
1708
2136
|
*/
|
|
1709
|
-
function svelteKitLoadEdges(ctx) {
|
|
2137
|
+
async function svelteKitLoadEdges(ctx, onYield) {
|
|
2138
|
+
let scannedFiles = 0;
|
|
1710
2139
|
const edges = [];
|
|
1711
2140
|
const allFiles = new Set(ctx.getAllFiles());
|
|
1712
2141
|
const HOOKS = new Set(['load', 'actions']);
|
|
1713
2142
|
const HOOK_KINDS = new Set(['function', 'method', 'constant', 'variable']);
|
|
1714
2143
|
for (const file of allFiles) {
|
|
2144
|
+
if ((++scannedFiles & 255) === 0)
|
|
2145
|
+
await onYield();
|
|
1715
2146
|
const m = file.match(/(.*\/)(\+(?:page|layout))\.svelte$/);
|
|
1716
2147
|
if (!m)
|
|
1717
2148
|
continue;
|
|
@@ -1744,91 +2175,6 @@ function svelteKitLoadEdges(ctx) {
|
|
|
1744
2175
|
}
|
|
1745
2176
|
return edges;
|
|
1746
2177
|
}
|
|
1747
|
-
/** ArkTS startup + UI lifecycle: loadContent → first screen, Ability chain, aboutToAppear → build. */
|
|
1748
|
-
function arktsEntryEdges(queries, ctx) {
|
|
1749
|
-
const edges = [];
|
|
1750
|
-
const seen = new Set();
|
|
1751
|
-
const add = (source, target, synthesizedBy, extra = {}) => {
|
|
1752
|
-
const key = `${source}>${target}`;
|
|
1753
|
-
if (seen.has(key))
|
|
1754
|
-
return;
|
|
1755
|
-
seen.add(key);
|
|
1756
|
-
const targetNode = queries.getNodeById(target);
|
|
1757
|
-
edges.push({
|
|
1758
|
-
source,
|
|
1759
|
-
target,
|
|
1760
|
-
kind: 'calls',
|
|
1761
|
-
provenance: 'heuristic',
|
|
1762
|
-
metadata: {
|
|
1763
|
-
synthesizedBy,
|
|
1764
|
-
registeredAt: targetNode ? `${targetNode.filePath}:${targetNode.startLine}` : '',
|
|
1765
|
-
...extra,
|
|
1766
|
-
},
|
|
1767
|
-
});
|
|
1768
|
-
};
|
|
1769
|
-
for (const m of methodAndFunctionNodes(queries)) {
|
|
1770
|
-
if (m.language !== 'arkts')
|
|
1771
|
-
continue;
|
|
1772
|
-
const content = ctx.readFile(m.filePath);
|
|
1773
|
-
if (!content)
|
|
1774
|
-
continue;
|
|
1775
|
-
const lines = content.split('\n');
|
|
1776
|
-
const start = Math.max((m.startLine ?? 1) - 1, 0);
|
|
1777
|
-
const end = Math.min(m.endLine ?? m.startLine ?? lines.length, lines.length);
|
|
1778
|
-
const body = lines.slice(start, end).join('\n');
|
|
1779
|
-
const loadMatch = body.match(/loadContent\s*\(\s*['"]([^'"]+)['"]/);
|
|
1780
|
-
if (!loadMatch)
|
|
1781
|
-
continue;
|
|
1782
|
-
const page = loadMatch[1];
|
|
1783
|
-
const pageComponent = (0, arkts_entry_1.findArktsEntryPageComponent)(ctx, page);
|
|
1784
|
-
if (pageComponent) {
|
|
1785
|
-
const aboutToAppear = (0, arkts_entry_1.findArktsLifecycleMethod)(ctx, pageComponent, 'aboutToAppear');
|
|
1786
|
-
if (aboutToAppear) {
|
|
1787
|
-
add(m.id, aboutToAppear.id, 'arkts-load-content', { page });
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
for (const cls of [...queries.getNodesByKind('class'), ...queries.getNodesByKind('struct')]) {
|
|
1792
|
-
if (cls.language !== 'arkts')
|
|
1793
|
-
continue;
|
|
1794
|
-
const methods = queries
|
|
1795
|
-
.getOutgoingEdges(cls.id, ['contains'])
|
|
1796
|
-
.map((e) => queries.getNodeById(e.target))
|
|
1797
|
-
.filter((n) => !!n && n.kind === 'method');
|
|
1798
|
-
const onCreate = methods.find((n) => n.name === 'onCreate');
|
|
1799
|
-
const onWindowStageCreate = methods.find((n) => n.name === 'onWindowStageCreate');
|
|
1800
|
-
if (onCreate && onWindowStageCreate) {
|
|
1801
|
-
add(onCreate.id, onWindowStageCreate.id, 'arkts-lifecycle', { via: 'ability' });
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
for (const comp of [
|
|
1805
|
-
...queries.getNodesByKind('component'),
|
|
1806
|
-
...queries.getNodesByKind('class'),
|
|
1807
|
-
...queries.getNodesByKind('struct'),
|
|
1808
|
-
]) {
|
|
1809
|
-
if (comp.language !== 'arkts')
|
|
1810
|
-
continue;
|
|
1811
|
-
const owner = comp.kind === 'component'
|
|
1812
|
-
? queries.getNodesByKind('struct').find((c) => c.filePath === comp.filePath && c.name === comp.name) ??
|
|
1813
|
-
queries.getNodesByKind('class').find((c) => c.filePath === comp.filePath && c.name === comp.name) ??
|
|
1814
|
-
comp
|
|
1815
|
-
: comp;
|
|
1816
|
-
const methods = queries
|
|
1817
|
-
.getOutgoingEdges(owner.id, ['contains'])
|
|
1818
|
-
.map((e) => queries.getNodeById(e.target))
|
|
1819
|
-
.filter((n) => !!n && n.kind === 'method');
|
|
1820
|
-
const build = methods.find((n) => n.name === 'build');
|
|
1821
|
-
if (!build)
|
|
1822
|
-
continue;
|
|
1823
|
-
for (const life of ['aboutToAppear', 'onPageShow']) {
|
|
1824
|
-
const lifeMethod = methods.find((n) => n.name === life);
|
|
1825
|
-
if (lifeMethod) {
|
|
1826
|
-
add(lifeMethod.id, build.id, 'arkts-lifecycle', { via: life });
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
return edges;
|
|
1831
|
-
}
|
|
1832
2178
|
/**
|
|
1833
2179
|
* Redux-thunk dispatch chain. `export const X = createAsyncThunk(prefix, async (a, api) => {...})`
|
|
1834
2180
|
* (or a wrapper like trezor's `createThunk(...)`) passes the async body as an ARGUMENT, so
|
|
@@ -1845,10 +2191,13 @@ function arktsEntryEdges(queries, ctx) {
|
|
|
1845
2191
|
const THUNK_DECL_RE = /create(?:Async)?Thunk/;
|
|
1846
2192
|
const THUNK_DISPATCH_RE = /\bdispatch\s*\(\s*([A-Za-z_]\w*)\s*[(),]/g;
|
|
1847
2193
|
const THUNK_FANOUT_CAP = 24;
|
|
1848
|
-
function reduxThunkEdges(queries, ctx) {
|
|
2194
|
+
async function reduxThunkEdges(queries, ctx, onYield) {
|
|
2195
|
+
let scanned255 = 0;
|
|
1849
2196
|
const edges = [];
|
|
1850
2197
|
const seen = new Set();
|
|
1851
2198
|
for (const node of queries.iterateNodesByKind('constant')) {
|
|
2199
|
+
if ((++scanned255 & 63) === 0)
|
|
2200
|
+
await onYield();
|
|
1852
2201
|
// Cheap gate: the initializer (captured in `signature`) must be a create(Async)Thunk call —
|
|
1853
2202
|
// avoids reading every constant's body on a large repo.
|
|
1854
2203
|
if (!node.signature || !THUNK_DECL_RE.test(node.signature))
|
|
@@ -1979,10 +2328,16 @@ function resolveRegistryHandler(ctx, name, chained) {
|
|
|
1979
2328
|
// entry resolving to the global URL constant).
|
|
1980
2329
|
return cands.find((n) => n.kind === 'method') ?? null;
|
|
1981
2330
|
}
|
|
1982
|
-
function objectRegistryEdges(ctx) {
|
|
2331
|
+
async function objectRegistryEdges(ctx, onYield) {
|
|
2332
|
+
let scannedFiles = 0;
|
|
1983
2333
|
const edges = [];
|
|
1984
2334
|
const seen = new Set();
|
|
2335
|
+
let scanned = 0;
|
|
1985
2336
|
for (const file of ctx.getAllFiles()) {
|
|
2337
|
+
if ((++scannedFiles & 15) === 0)
|
|
2338
|
+
await onYield();
|
|
2339
|
+
if ((++scanned & 255) === 0)
|
|
2340
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
1986
2341
|
if (!REGISTRY_JS_EXT.test(file))
|
|
1987
2342
|
continue;
|
|
1988
2343
|
const content = ctx.readFile(file);
|
|
@@ -2092,10 +2447,13 @@ function rtkEndpointNameFromHook(hook) {
|
|
|
2092
2447
|
return null;
|
|
2093
2448
|
return mid.charAt(0).toLowerCase() + mid.slice(1);
|
|
2094
2449
|
}
|
|
2095
|
-
function rtkQueryEdges(queries, ctx) {
|
|
2450
|
+
async function rtkQueryEdges(queries, ctx, onYield) {
|
|
2451
|
+
let scanned255 = 0;
|
|
2096
2452
|
const edges = [];
|
|
2097
2453
|
const seen = new Set();
|
|
2098
2454
|
for (const hook of queries.iterateNodesByKind('function')) {
|
|
2455
|
+
if ((++scanned255 & 63) === 0)
|
|
2456
|
+
await onYield();
|
|
2099
2457
|
// Only our extracted generated-hook bindings (sentinel) — not a real hook fn.
|
|
2100
2458
|
if (hook.signature !== RTK_GENERATED_HOOK_SIGNATURE)
|
|
2101
2459
|
continue;
|
|
@@ -2139,10 +2497,13 @@ const PINIA_FACTORY_RE = /\b(?:export\s+)?const\s+(\w+)\s*=\s*defineStore\s*\(/g
|
|
|
2139
2497
|
const PINIA_BIND_RE = /\bconst\s+(\w+)\s*=\s*(?:await\s+)?(\w+)\s*\(/g;
|
|
2140
2498
|
const PINIA_CALL_RE = /(\w+)\s*\.\s*(\w+)\s*\(/g;
|
|
2141
2499
|
const PINIA_FANOUT_CAP = 80;
|
|
2142
|
-
function piniaStoreEdges(ctx) {
|
|
2500
|
+
async function piniaStoreEdges(ctx, onYield) {
|
|
2501
|
+
let scannedFiles = 0;
|
|
2143
2502
|
// 1. Map each `const useXStore = defineStore(...)` factory → its store file.
|
|
2144
2503
|
const factoryFile = new Map();
|
|
2145
2504
|
for (const file of ctx.getAllFiles()) {
|
|
2505
|
+
if ((++scannedFiles & 15) === 0)
|
|
2506
|
+
await onYield();
|
|
2146
2507
|
if (!PINIA_CONSUMER_EXT.test(file))
|
|
2147
2508
|
continue;
|
|
2148
2509
|
const content = ctx.readFile(file);
|
|
@@ -2158,6 +2519,8 @@ function piniaStoreEdges(ctx) {
|
|
|
2158
2519
|
const edges = [];
|
|
2159
2520
|
const seen = new Set();
|
|
2160
2521
|
for (const file of ctx.getAllFiles()) {
|
|
2522
|
+
if ((++scannedFiles & 15) === 0)
|
|
2523
|
+
await onYield();
|
|
2161
2524
|
if (!PINIA_CONSUMER_EXT.test(file))
|
|
2162
2525
|
continue;
|
|
2163
2526
|
const content = ctx.readFile(file);
|
|
@@ -2230,7 +2593,8 @@ const VUEX_FANOUT_CAP = 120;
|
|
|
2230
2593
|
function pathHasSegment(filePath, seg) {
|
|
2231
2594
|
return new RegExp('[\\\\/]' + seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[\\\\/.]').test(filePath);
|
|
2232
2595
|
}
|
|
2233
|
-
function vuexDispatchEdges(ctx) {
|
|
2596
|
+
async function vuexDispatchEdges(ctx, onYield) {
|
|
2597
|
+
let scannedFiles = 0;
|
|
2234
2598
|
const storeFileCache = new Map();
|
|
2235
2599
|
const isStoreFile = (file) => {
|
|
2236
2600
|
let v = storeFileCache.get(file);
|
|
@@ -2268,6 +2632,8 @@ function vuexDispatchEdges(ctx) {
|
|
|
2268
2632
|
const edges = [];
|
|
2269
2633
|
const seen = new Set();
|
|
2270
2634
|
for (const file of ctx.getAllFiles()) {
|
|
2635
|
+
if ((++scannedFiles & 15) === 0)
|
|
2636
|
+
await onYield();
|
|
2271
2637
|
if (!PINIA_CONSUMER_EXT.test(file))
|
|
2272
2638
|
continue;
|
|
2273
2639
|
const content = ctx.readFile(file);
|
|
@@ -2327,7 +2693,8 @@ const CELERY_TASK_DECORATOR_RE = /@\s*(?:[A-Za-z_][\w.]*\.)?(?:shared_task|task)
|
|
|
2327
2693
|
const CELERY_PY_EXT = /\.py$/;
|
|
2328
2694
|
const CELERY_FANOUT_CAP = 80;
|
|
2329
2695
|
const CELERY_DECORATOR_LOOKBACK = 12; // max lines above a `def` to scan for its decorators
|
|
2330
|
-
function celeryDispatchEdges(ctx) {
|
|
2696
|
+
async function celeryDispatchEdges(ctx, onYield) {
|
|
2697
|
+
let scannedFiles = 0;
|
|
2331
2698
|
// Memoize the decorator check per task-candidate node: it reads the file and scans a few
|
|
2332
2699
|
// lines above the def. Only called on names that are actually `.delay`/`.apply_async`
|
|
2333
2700
|
// receivers, so the candidate set stays small.
|
|
@@ -2371,6 +2738,8 @@ function celeryDispatchEdges(ctx) {
|
|
|
2371
2738
|
const edges = [];
|
|
2372
2739
|
const seen = new Set();
|
|
2373
2740
|
for (const file of ctx.getAllFiles()) {
|
|
2741
|
+
if ((++scannedFiles & 15) === 0)
|
|
2742
|
+
await onYield();
|
|
2374
2743
|
if (!CELERY_PY_EXT.test(file))
|
|
2375
2744
|
continue;
|
|
2376
2745
|
const content = ctx.readFile(file);
|
|
@@ -2449,15 +2818,24 @@ function springFirstParamType(sig) {
|
|
|
2449
2818
|
const type = toks[toks.length - 2].replace(/<.*$/, ''); // drop generic args
|
|
2450
2819
|
return /^[A-Z][A-Za-z0-9_]*$/.test(type) ? type : null;
|
|
2451
2820
|
}
|
|
2452
|
-
function springEventEdges(ctx) {
|
|
2821
|
+
async function springEventEdges(ctx, onYield) {
|
|
2822
|
+
let scannedFiles = 0;
|
|
2453
2823
|
// Pass 1 — event-type → listener methods, scanning only event-relevant files.
|
|
2824
|
+
// This is the ONLY full read sweep: publisher files are recorded here so
|
|
2825
|
+
// pass 2 re-reads just those instead of every .java file again (#1212 —
|
|
2826
|
+
// the double full-repo read was one of the tail's longest unyielded spans).
|
|
2454
2827
|
const listeners = new Map();
|
|
2828
|
+
const publisherFiles = [];
|
|
2455
2829
|
for (const file of ctx.getAllFiles()) {
|
|
2830
|
+
if ((++scannedFiles & 15) === 0)
|
|
2831
|
+
await onYield();
|
|
2456
2832
|
if (!SPRING_JAVA_EXT.test(file))
|
|
2457
2833
|
continue;
|
|
2458
2834
|
const content = ctx.readFile(file);
|
|
2459
2835
|
if (!content)
|
|
2460
2836
|
continue;
|
|
2837
|
+
if (content.includes('.publishEvent('))
|
|
2838
|
+
publisherFiles.push(file);
|
|
2461
2839
|
const hasAnno = content.includes('@EventListener') || content.includes('@TransactionalEventListener');
|
|
2462
2840
|
const hasAppListener = SPRING_APP_LISTENER_RE.test(content);
|
|
2463
2841
|
if (!hasAnno && !hasAppListener)
|
|
@@ -2497,12 +2875,13 @@ function springEventEdges(ctx) {
|
|
|
2497
2875
|
}
|
|
2498
2876
|
if (!listeners.size)
|
|
2499
2877
|
return [];
|
|
2500
|
-
// Pass 2 — link each publishEvent(new XEvent(...)) site → every listener of
|
|
2878
|
+
// Pass 2 — link each publishEvent(new XEvent(...)) site → every listener of
|
|
2879
|
+
// XEvent. Only the publisher files recorded in pass 1 are (re-)read.
|
|
2501
2880
|
const edges = [];
|
|
2502
2881
|
const seen = new Set();
|
|
2503
|
-
for (const file of
|
|
2504
|
-
if (
|
|
2505
|
-
|
|
2882
|
+
for (const file of publisherFiles) {
|
|
2883
|
+
if ((++scannedFiles & 15) === 0)
|
|
2884
|
+
await onYield();
|
|
2506
2885
|
const content = ctx.readFile(file);
|
|
2507
2886
|
if (!content || !content.includes('.publishEvent('))
|
|
2508
2887
|
continue;
|
|
@@ -2587,10 +2966,13 @@ function resolveMediatrArgType(arg, lines, methodStart, dispatchLine) {
|
|
|
2587
2966
|
}
|
|
2588
2967
|
return declType;
|
|
2589
2968
|
}
|
|
2590
|
-
function mediatrDispatchEdges(ctx) {
|
|
2969
|
+
async function mediatrDispatchEdges(ctx, onYield) {
|
|
2970
|
+
let scannedFiles = 0;
|
|
2591
2971
|
// Pass 1 — request/notification type → the Handle method of each handler class.
|
|
2592
2972
|
const handlers = new Map();
|
|
2593
2973
|
for (const file of ctx.getAllFiles()) {
|
|
2974
|
+
if ((++scannedFiles & 15) === 0)
|
|
2975
|
+
await onYield();
|
|
2594
2976
|
if (!MEDIATR_CS_EXT.test(file))
|
|
2595
2977
|
continue;
|
|
2596
2978
|
const content = ctx.readFile(file);
|
|
@@ -2624,6 +3006,8 @@ function mediatrDispatchEdges(ctx) {
|
|
|
2624
3006
|
const edges = [];
|
|
2625
3007
|
const seen = new Set();
|
|
2626
3008
|
for (const file of ctx.getAllFiles()) {
|
|
3009
|
+
if ((++scannedFiles & 15) === 0)
|
|
3010
|
+
await onYield();
|
|
2627
3011
|
if (!MEDIATR_CS_EXT.test(file))
|
|
2628
3012
|
continue;
|
|
2629
3013
|
const content = ctx.readFile(file);
|
|
@@ -2687,7 +3071,8 @@ const SIDEKIQ_DISPATCH_RE = /([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)\s*\.\
|
|
|
2687
3071
|
const SIDEKIQ_WORKER_RE = /\binclude\s+Sidekiq::(?:Job|Worker)\b/;
|
|
2688
3072
|
const SIDEKIQ_RB_EXT = /\.rb$/;
|
|
2689
3073
|
const SIDEKIQ_FANOUT_CAP = 80;
|
|
2690
|
-
function sidekiqDispatchEdges(ctx) {
|
|
3074
|
+
async function sidekiqDispatchEdges(ctx, onYield) {
|
|
3075
|
+
let scannedFiles = 0;
|
|
2691
3076
|
// class node id → its instance `perform` method (null if the class isn't a Sidekiq worker),
|
|
2692
3077
|
// memoized. Reads the class body for the mixin; only consulted for actual dispatch receivers.
|
|
2693
3078
|
const performCache = new Map();
|
|
@@ -2724,6 +3109,8 @@ function sidekiqDispatchEdges(ctx) {
|
|
|
2724
3109
|
const edges = [];
|
|
2725
3110
|
const seen = new Set();
|
|
2726
3111
|
for (const file of ctx.getAllFiles()) {
|
|
3112
|
+
if ((++scannedFiles & 15) === 0)
|
|
3113
|
+
await onYield();
|
|
2727
3114
|
if (!SIDEKIQ_RB_EXT.test(file))
|
|
2728
3115
|
continue;
|
|
2729
3116
|
const content = ctx.readFile(file);
|
|
@@ -2759,6 +3146,414 @@ function sidekiqDispatchEdges(ctx) {
|
|
|
2759
3146
|
}
|
|
2760
3147
|
return edges;
|
|
2761
3148
|
}
|
|
3149
|
+
// ── Erlang behaviour-callback dispatch ────────────────────────────────────────
|
|
3150
|
+
// An Erlang behaviour is a compile-checked callback contract: the behaviour
|
|
3151
|
+
// module declares `-callback init(...) -> ...`, implementers declare
|
|
3152
|
+
// `-behaviour(B)` and export the callbacks, and the framework side dispatches
|
|
3153
|
+
// through a VARIABLE module — cowboy's `Handler:init(Req, Opts)` and
|
|
3154
|
+
// `Middleware:execute(Req, Env)` folds, ejabberd's `Mod:start/2`. Extraction
|
|
3155
|
+
// deliberately leaves var-module calls silent (no static target), so the flow
|
|
3156
|
+
// breaks at exactly the hop agents ask about (request → handler init). Bridge:
|
|
3157
|
+
//
|
|
3158
|
+
// dispatch site `Var:fn(args…)` → every in-repo implementer of the behaviour
|
|
3159
|
+
// declaring `fn` with the SITE's arity — provided exactly ONE in-repo
|
|
3160
|
+
// behaviour declares (fn, arity); a name+arity collision across behaviours
|
|
3161
|
+
// bails (silent beats wrong) — and the implementer defines and exports `fn`.
|
|
3162
|
+
//
|
|
3163
|
+
// Behaviours are discovered by scanning every Erlang file for `-callback`
|
|
3164
|
+
// declarations (not just `implements` targets), so a behaviour with zero
|
|
3165
|
+
// implementers still participates in the ambiguity gate. Fan-out control: a
|
|
3166
|
+
// mega-behaviour (ejabberd's gen_mod, ~200 mod_* implementers) would mint
|
|
3167
|
+
// hundreds of edges per site that READ as complete coverage while being
|
|
3168
|
+
// arbitrary — above the cap the site is skipped entirely and the boundary
|
|
3169
|
+
// stays visibly dynamic (explore's boundary announcer covers it) instead of
|
|
3170
|
+
// silently truncated.
|
|
3171
|
+
const ERLANG_EXT = /\.(?:erl|hrl)$/;
|
|
3172
|
+
// `Var:fn(` — variable (capitalized) module, lowercase function, immediate
|
|
3173
|
+
// open-paren. The leading char class rejects `?MODULE:fn(` (macro), `a:b(`
|
|
3174
|
+
// (static remote call, already linked), and mid-word matches.
|
|
3175
|
+
const ERLANG_DISPATCH_RE = /(^|[^?\w@'])([A-Z][A-Za-z0-9_@]*):([a-z][A-Za-z0-9_@]*)\(/g;
|
|
3176
|
+
const ERLANG_CALLBACK_DECL_RE = /(^|\n)\s*-callback\s+('[^'\n]+'|[a-z][A-Za-z0-9_@]*)\s*\(/g;
|
|
3177
|
+
const ERLANG_BEHAVIOUR_FANOUT_CAP = 24;
|
|
3178
|
+
/**
|
|
3179
|
+
* Argument count of the call/declaration whose `(` sits at `openIdx` —
|
|
3180
|
+
* top-level commas + 1, `()` → 0, unbalanced/oversized → -1. Skips nested
|
|
3181
|
+
* (), [], {}, <<>> content, `"strings"`, `'atoms'`, and `$c` char literals,
|
|
3182
|
+
* so `-callback init(fun((a, b) -> ok), #{k => v}) -> ok.` counts 2.
|
|
3183
|
+
*/
|
|
3184
|
+
function erlangArityAt(src, openIdx) {
|
|
3185
|
+
let depth = 1;
|
|
3186
|
+
let commas = 0;
|
|
3187
|
+
let sawArg = false;
|
|
3188
|
+
const limit = Math.min(src.length, openIdx + 4000);
|
|
3189
|
+
for (let i = openIdx + 1; i < limit; i++) {
|
|
3190
|
+
const ch = src[i];
|
|
3191
|
+
if (ch === '"' || ch === "'") {
|
|
3192
|
+
i++;
|
|
3193
|
+
while (i < limit && src[i] !== ch) {
|
|
3194
|
+
if (src[i] === '\\')
|
|
3195
|
+
i++;
|
|
3196
|
+
i++;
|
|
3197
|
+
}
|
|
3198
|
+
sawArg = true;
|
|
3199
|
+
continue;
|
|
3200
|
+
}
|
|
3201
|
+
if (ch === '$') {
|
|
3202
|
+
i++;
|
|
3203
|
+
if (src[i] === '\\')
|
|
3204
|
+
i++;
|
|
3205
|
+
sawArg = true;
|
|
3206
|
+
continue;
|
|
3207
|
+
}
|
|
3208
|
+
if (ch === '(' || ch === '[' || ch === '{') {
|
|
3209
|
+
depth++;
|
|
3210
|
+
sawArg = true;
|
|
3211
|
+
continue;
|
|
3212
|
+
}
|
|
3213
|
+
if (ch === ')' || ch === ']' || ch === '}') {
|
|
3214
|
+
depth--;
|
|
3215
|
+
if (depth === 0)
|
|
3216
|
+
return sawArg ? commas + 1 : 0;
|
|
3217
|
+
continue;
|
|
3218
|
+
}
|
|
3219
|
+
if (ch === ',' && depth === 1) {
|
|
3220
|
+
commas++;
|
|
3221
|
+
continue;
|
|
3222
|
+
}
|
|
3223
|
+
if (!/\s/.test(ch))
|
|
3224
|
+
sawArg = true;
|
|
3225
|
+
}
|
|
3226
|
+
return -1;
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* Nix module-system option wiring. A NixOS/home-manager/nix-darwin option is
|
|
3230
|
+
* DECLARED in one module (`options.launchd.user.agents = mkOption { ... }`)
|
|
3231
|
+
* and SET in others (`launchd.user.agents.yabai = { ... }` inside a module's
|
|
3232
|
+
* config) — the connection happens by option-path unification inside the
|
|
3233
|
+
* module-system evaluator, so there is no static call/import edge to follow
|
|
3234
|
+
* and flow questions ("how does services.yabai.enable become a launchd
|
|
3235
|
+
* service?") go dark at the module boundary.
|
|
3236
|
+
*
|
|
3237
|
+
* This pass links each config-write binding to the option declaration whose
|
|
3238
|
+
* path is the longest static-segment prefix of the write path. Precision gates:
|
|
3239
|
+
* - only STATIC segments participate: plain identifiers, plus quoted segments
|
|
3240
|
+
* (`"git/config"`, `"com.apple.dock"`) as opaque verbatim tokens that match
|
|
3241
|
+
* only quote-exactly; an interpolated (`${name}`) segment ends the prefix,
|
|
3242
|
+
* so dynamic paths never match beyond their static head;
|
|
3243
|
+
* - matched prefixes must be ≥2 segments: 1-segment paths would wrongly link
|
|
3244
|
+
* every package's `meta = { ... }` attrset to nixos's `options.meta`;
|
|
3245
|
+
* - a prefix declared in more than one file is ambiguous → no edge (a wrong
|
|
3246
|
+
* edge is worse than none);
|
|
3247
|
+
* - writes physically inside an options block are declaration internals
|
|
3248
|
+
* (types, defaults, examples), never config writes → excluded.
|
|
3249
|
+
* Both declaration spellings register: flat (`options.a.b = ...`) by name, and
|
|
3250
|
+
* nested (`options = { a.b = ...; }`) by line-span containment.
|
|
3251
|
+
*/
|
|
3252
|
+
function nixLeadingPlainSegments(name) {
|
|
3253
|
+
const segs = [];
|
|
3254
|
+
let i = 0;
|
|
3255
|
+
const n = name.length;
|
|
3256
|
+
while (i < n) {
|
|
3257
|
+
if (name[i] === '"') {
|
|
3258
|
+
// Quoted segment — an opaque verbatim token (quotes kept, so it can
|
|
3259
|
+
// never collide with a plain identifier). `NSGlobalDomain."com.apple.
|
|
3260
|
+
// mouse.tapBehavior"` must match ITS OWN quoted declaration, not
|
|
3261
|
+
// whichever sibling registered the shared plain prefix first.
|
|
3262
|
+
let j = i + 1;
|
|
3263
|
+
while (j < n && name[j] !== '"') {
|
|
3264
|
+
if (name[j] === '\\')
|
|
3265
|
+
j++;
|
|
3266
|
+
j++;
|
|
3267
|
+
}
|
|
3268
|
+
if (j >= n)
|
|
3269
|
+
return segs; // unterminated — stop at the static head
|
|
3270
|
+
const tok = name.slice(i, j + 1);
|
|
3271
|
+
if (tok.includes('${'))
|
|
3272
|
+
return segs; // interpolated → dynamic → stop
|
|
3273
|
+
segs.push(tok);
|
|
3274
|
+
i = j + 1;
|
|
3275
|
+
if (i >= n)
|
|
3276
|
+
break;
|
|
3277
|
+
if (name[i] !== '.')
|
|
3278
|
+
return segs;
|
|
3279
|
+
i++;
|
|
3280
|
+
continue;
|
|
3281
|
+
}
|
|
3282
|
+
let j = i;
|
|
3283
|
+
while (j < n && name[j] !== '.') {
|
|
3284
|
+
if (name[j] === '"' || (name[j] === '$' && name[j + 1] === '{'))
|
|
3285
|
+
return segs;
|
|
3286
|
+
j++;
|
|
3287
|
+
}
|
|
3288
|
+
const seg = name.slice(i, j);
|
|
3289
|
+
if (!/^[A-Za-z_][A-Za-z0-9_'-]*$/.test(seg))
|
|
3290
|
+
return segs;
|
|
3291
|
+
segs.push(seg);
|
|
3292
|
+
i = j + 1;
|
|
3293
|
+
}
|
|
3294
|
+
return segs;
|
|
3295
|
+
}
|
|
3296
|
+
async function nixOptionPathEdges(queries, onYield) {
|
|
3297
|
+
let scanned255 = 0;
|
|
3298
|
+
// One streaming pass over nix bindings (variables + the odd function-valued
|
|
3299
|
+
// option); memory stays O(bindings-kept), not O(all nodes) (#610).
|
|
3300
|
+
const byFile = new Map();
|
|
3301
|
+
let scanned = 0;
|
|
3302
|
+
for (const kind of ['variable', 'function']) {
|
|
3303
|
+
for (const node of queries.iterateNodesByKind(kind)) {
|
|
3304
|
+
if ((++scanned255 & 63) === 0)
|
|
3305
|
+
await onYield();
|
|
3306
|
+
if ((++scanned & 0x3fff) === 0 && onYield)
|
|
3307
|
+
await onYield();
|
|
3308
|
+
if (node.language !== 'nix')
|
|
3309
|
+
continue;
|
|
3310
|
+
const segs = nixLeadingPlainSegments(node.name);
|
|
3311
|
+
if (segs.length === 0)
|
|
3312
|
+
continue;
|
|
3313
|
+
const rec = {
|
|
3314
|
+
id: node.id,
|
|
3315
|
+
filePath: node.filePath,
|
|
3316
|
+
startLine: node.startLine,
|
|
3317
|
+
endLine: node.endLine,
|
|
3318
|
+
segs,
|
|
3319
|
+
};
|
|
3320
|
+
const arr = byFile.get(node.filePath);
|
|
3321
|
+
if (arr)
|
|
3322
|
+
arr.push(rec);
|
|
3323
|
+
else
|
|
3324
|
+
byFile.set(node.filePath, [rec]);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
// Per file: walk bindings outermost-first with a stack of active option
|
|
3328
|
+
// spans, composing nested declaration paths (`options = { services.foo = {
|
|
3329
|
+
// enable = mkOption ...; }; }` registers services.foo AND services.foo.enable).
|
|
3330
|
+
// An `options` binding nested inside another option span is a SUBMODULE's
|
|
3331
|
+
// own namespace (`attrsOf (submodule { options = ...; })`) — its internals
|
|
3332
|
+
// are not globally addressable, so the sentinel blocks registration below it
|
|
3333
|
+
// while still excluding the region from write candidates.
|
|
3334
|
+
const SUBMODULE = 'submodule';
|
|
3335
|
+
const decls = new Map();
|
|
3336
|
+
const writes = [];
|
|
3337
|
+
const register = (path, rec) => {
|
|
3338
|
+
if (path.length < 2 || path.includes(SUBMODULE))
|
|
3339
|
+
return;
|
|
3340
|
+
const key = path.join('.');
|
|
3341
|
+
const arr = decls.get(key);
|
|
3342
|
+
if (arr)
|
|
3343
|
+
arr.push(rec);
|
|
3344
|
+
else
|
|
3345
|
+
decls.set(key, [rec]);
|
|
3346
|
+
};
|
|
3347
|
+
for (const recs of byFile.values()) {
|
|
3348
|
+
recs.sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
|
|
3349
|
+
const stack = [];
|
|
3350
|
+
for (const rec of recs) {
|
|
3351
|
+
while (stack.length > 0 && stack[stack.length - 1].end < rec.startLine)
|
|
3352
|
+
stack.pop();
|
|
3353
|
+
// Strict containment at line granularity: a one-line nested binding is
|
|
3354
|
+
// indistinguishable from its container, so it stays unclassified (rare
|
|
3355
|
+
// in module code, where option blocks are multi-line).
|
|
3356
|
+
const enclosing = stack.length > 0 &&
|
|
3357
|
+
rec.startLine >= stack[stack.length - 1].start &&
|
|
3358
|
+
rec.endLine <= stack[stack.length - 1].end &&
|
|
3359
|
+
!(rec.startLine === stack[stack.length - 1].start && rec.endLine === stack[stack.length - 1].end)
|
|
3360
|
+
? stack[stack.length - 1]
|
|
3361
|
+
: null;
|
|
3362
|
+
if (rec.segs[0] === 'options') {
|
|
3363
|
+
const ownPath = rec.segs.slice(1); // [] for the bare `options = { ... }` spelling
|
|
3364
|
+
const prefix = enclosing ? [SUBMODULE] : ownPath;
|
|
3365
|
+
register(prefix, rec);
|
|
3366
|
+
stack.push({ start: rec.startLine, end: rec.endLine, prefix });
|
|
3367
|
+
continue;
|
|
3368
|
+
}
|
|
3369
|
+
if (enclosing) {
|
|
3370
|
+
const composed = [...enclosing.prefix, ...rec.segs];
|
|
3371
|
+
register(composed, rec);
|
|
3372
|
+
stack.push({ start: rec.startLine, end: rec.endLine, prefix: composed });
|
|
3373
|
+
continue;
|
|
3374
|
+
}
|
|
3375
|
+
if (rec.segs.length >= 2) {
|
|
3376
|
+
writes.push(rec);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
if (decls.size === 0 || writes.length === 0)
|
|
3381
|
+
return [];
|
|
3382
|
+
const edges = [];
|
|
3383
|
+
for (const w of writes) {
|
|
3384
|
+
// `config.services.x = ...` spells the same write with an explicit prefix.
|
|
3385
|
+
const segs = w.segs[0] === 'config' ? w.segs.slice(1) : w.segs;
|
|
3386
|
+
if (segs.length < 2)
|
|
3387
|
+
continue;
|
|
3388
|
+
// Longest prefix wins; an ambiguous longest match does NOT fall back to a
|
|
3389
|
+
// shorter one (that would link `services.nginx.virtualHosts.x` to
|
|
3390
|
+
// `options.services.nginx` when virtualHosts is the contested path).
|
|
3391
|
+
for (let len = Math.min(segs.length, 6); len >= 2; len--) {
|
|
3392
|
+
const candidates = decls.get(segs.slice(0, len).join('.'));
|
|
3393
|
+
if (!candidates || candidates.length === 0)
|
|
3394
|
+
continue;
|
|
3395
|
+
const files = new Set(candidates.map((c) => c.filePath));
|
|
3396
|
+
if (files.size === 1) {
|
|
3397
|
+
const target = candidates[0];
|
|
3398
|
+
if (target.id !== w.id) {
|
|
3399
|
+
edges.push({
|
|
3400
|
+
source: w.id,
|
|
3401
|
+
target: target.id,
|
|
3402
|
+
kind: 'references',
|
|
3403
|
+
line: w.startLine,
|
|
3404
|
+
provenance: 'heuristic',
|
|
3405
|
+
metadata: {
|
|
3406
|
+
synthesizedBy: 'nix-option-path',
|
|
3407
|
+
optionPath: segs.slice(0, len).join('.'),
|
|
3408
|
+
registeredAt: `${target.filePath}:${target.startLine}`,
|
|
3409
|
+
},
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
break; // longest hit decides, matched or ambiguous
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
return edges;
|
|
3417
|
+
}
|
|
3418
|
+
async function erlangBehaviourDispatchEdges(queries, ctx, onYield) {
|
|
3419
|
+
let scannedFiles = 0;
|
|
3420
|
+
let scanned255 = 0;
|
|
3421
|
+
// Cheap language gate: no Erlang modules → no cost beyond one streamed
|
|
3422
|
+
// kind scan (never a materialized array of every namespace — #1212).
|
|
3423
|
+
const erlangModules = [];
|
|
3424
|
+
for (const n of queries.iterateNodesByKind('namespace')) {
|
|
3425
|
+
if ((++scanned255 & 63) === 0)
|
|
3426
|
+
await onYield();
|
|
3427
|
+
if (n.language === 'erlang')
|
|
3428
|
+
erlangModules.push(n);
|
|
3429
|
+
}
|
|
3430
|
+
if (erlangModules.length === 0)
|
|
3431
|
+
return [];
|
|
3432
|
+
// Pass 1 — scan every Erlang file with `-callback` decls: behaviour module →
|
|
3433
|
+
// its (name, arity) callback set, and the global `name/arity` → declaring
|
|
3434
|
+
// behaviours map that drives the ambiguity gate.
|
|
3435
|
+
const moduleByFile = new Map();
|
|
3436
|
+
for (const ns of erlangModules) {
|
|
3437
|
+
if (!moduleByFile.has(ns.filePath))
|
|
3438
|
+
moduleByFile.set(ns.filePath, ns);
|
|
3439
|
+
}
|
|
3440
|
+
const declaringBehaviours = new Map(); // `fn/arity` → behaviour namespaces
|
|
3441
|
+
const callbackNames = new Set();
|
|
3442
|
+
for (const file of ctx.getAllFiles()) {
|
|
3443
|
+
if ((++scannedFiles & 15) === 0)
|
|
3444
|
+
await onYield();
|
|
3445
|
+
if (!ERLANG_EXT.test(file))
|
|
3446
|
+
continue;
|
|
3447
|
+
const behaviour = moduleByFile.get(file);
|
|
3448
|
+
if (!behaviour)
|
|
3449
|
+
continue; // a .hrl or module-less file can't be a behaviour
|
|
3450
|
+
const content = ctx.readFile(file);
|
|
3451
|
+
if (!content || !content.includes('-callback'))
|
|
3452
|
+
continue;
|
|
3453
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
|
|
3454
|
+
ERLANG_CALLBACK_DECL_RE.lastIndex = 0;
|
|
3455
|
+
let m;
|
|
3456
|
+
while ((m = ERLANG_CALLBACK_DECL_RE.exec(safe))) {
|
|
3457
|
+
const name = m[2].replace(/^'|'$/g, '');
|
|
3458
|
+
const arity = erlangArityAt(safe, m.index + m[0].length - 1);
|
|
3459
|
+
if (arity < 0)
|
|
3460
|
+
continue;
|
|
3461
|
+
const key = `${name}/${arity}`;
|
|
3462
|
+
const arr = declaringBehaviours.get(key);
|
|
3463
|
+
if (arr) {
|
|
3464
|
+
if (!arr.some((b) => b.id === behaviour.id))
|
|
3465
|
+
arr.push(behaviour);
|
|
3466
|
+
}
|
|
3467
|
+
else {
|
|
3468
|
+
declaringBehaviours.set(key, [behaviour]);
|
|
3469
|
+
}
|
|
3470
|
+
callbackNames.add(name);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
if (declaringBehaviours.size === 0)
|
|
3474
|
+
return [];
|
|
3475
|
+
// Implementer target lookup, lazy per (behaviour, fn): implementers come
|
|
3476
|
+
// from the `implements` edges extraction resolved, and the target is the
|
|
3477
|
+
// implementer module's own exported `fn` function node.
|
|
3478
|
+
const targetCache = new Map();
|
|
3479
|
+
const targetsOf = (behaviour, fn) => {
|
|
3480
|
+
const cacheKey = `${behaviour.id}#${fn}`;
|
|
3481
|
+
let targets = targetCache.get(cacheKey);
|
|
3482
|
+
if (targets)
|
|
3483
|
+
return targets;
|
|
3484
|
+
targets = [];
|
|
3485
|
+
for (const e of queries.getIncomingEdges(behaviour.id, ['implements'])) {
|
|
3486
|
+
const impl = queries.getNodeById(e.source);
|
|
3487
|
+
if (!impl || impl.language !== 'erlang' || impl.kind !== 'namespace')
|
|
3488
|
+
continue;
|
|
3489
|
+
const fnNode = ctx
|
|
3490
|
+
.getNodesInFile(impl.filePath)
|
|
3491
|
+
.find((n) => n.kind === 'function' && n.name === fn && n.isExported !== false);
|
|
3492
|
+
if (fnNode)
|
|
3493
|
+
targets.push(fnNode);
|
|
3494
|
+
}
|
|
3495
|
+
targetCache.set(cacheKey, targets);
|
|
3496
|
+
return targets;
|
|
3497
|
+
};
|
|
3498
|
+
// Pass 2 — dispatch sites. Only files containing a var-module call shape are
|
|
3499
|
+
// scanned in full.
|
|
3500
|
+
const edges = [];
|
|
3501
|
+
const seen = new Set();
|
|
3502
|
+
for (const file of ctx.getAllFiles()) {
|
|
3503
|
+
if ((++scannedFiles & 15) === 0)
|
|
3504
|
+
await onYield();
|
|
3505
|
+
if (!ERLANG_EXT.test(file))
|
|
3506
|
+
continue;
|
|
3507
|
+
const content = ctx.readFile(file);
|
|
3508
|
+
if (!content || !/[A-Z][A-Za-z0-9_@]*:[a-z]/.test(content))
|
|
3509
|
+
continue;
|
|
3510
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
|
|
3511
|
+
const nodesInFile = ctx.getNodesInFile(file);
|
|
3512
|
+
ERLANG_DISPATCH_RE.lastIndex = 0;
|
|
3513
|
+
let m;
|
|
3514
|
+
while ((m = ERLANG_DISPATCH_RE.exec(safe))) {
|
|
3515
|
+
const fn = m[3];
|
|
3516
|
+
if (!callbackNames.has(fn))
|
|
3517
|
+
continue;
|
|
3518
|
+
const openIdx = m.index + m[0].length - 1;
|
|
3519
|
+
const arity = erlangArityAt(safe, openIdx);
|
|
3520
|
+
if (arity < 0)
|
|
3521
|
+
continue;
|
|
3522
|
+
const behaviours = declaringBehaviours.get(`${fn}/${arity}`);
|
|
3523
|
+
if (!behaviours || behaviours.length !== 1)
|
|
3524
|
+
continue; // unknown or ambiguous
|
|
3525
|
+
const behaviour = behaviours[0];
|
|
3526
|
+
const targets = targetsOf(behaviour, fn);
|
|
3527
|
+
if (targets.length === 0 || targets.length > ERLANG_BEHAVIOUR_FANOUT_CAP)
|
|
3528
|
+
continue;
|
|
3529
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
3530
|
+
const disp = enclosingFn(nodesInFile, line);
|
|
3531
|
+
if (!disp)
|
|
3532
|
+
continue;
|
|
3533
|
+
for (const target of targets) {
|
|
3534
|
+
if (target.id === disp.id)
|
|
3535
|
+
continue;
|
|
3536
|
+
const key = `${disp.id}>${target.id}`;
|
|
3537
|
+
if (seen.has(key))
|
|
3538
|
+
continue;
|
|
3539
|
+
seen.add(key);
|
|
3540
|
+
edges.push({
|
|
3541
|
+
source: disp.id,
|
|
3542
|
+
target: target.id,
|
|
3543
|
+
kind: 'calls',
|
|
3544
|
+
line,
|
|
3545
|
+
provenance: 'heuristic',
|
|
3546
|
+
metadata: {
|
|
3547
|
+
synthesizedBy: 'erlang-behaviour',
|
|
3548
|
+
via: `${behaviour.name}:${fn}/${arity}`,
|
|
3549
|
+
registeredAt: `${file}:${line}`,
|
|
3550
|
+
},
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
return edges;
|
|
3556
|
+
}
|
|
2762
3557
|
// ── Laravel events (PHP) ──────────────────────────────────────────────────────
|
|
2763
3558
|
// Laravel decouples an event dispatch from its listener(s), linked by the EVENT CLASS:
|
|
2764
3559
|
// // app/Events/PlaybackStarted.php + app/Listeners/UpdateLastfmNowPlaying.php
|
|
@@ -2805,7 +3600,8 @@ function phpArrayBody(src, openIdx) {
|
|
|
2805
3600
|
}
|
|
2806
3601
|
return null;
|
|
2807
3602
|
}
|
|
2808
|
-
function laravelEventEdges(ctx) {
|
|
3603
|
+
async function laravelEventEdges(ctx, onYield) {
|
|
3604
|
+
let scannedFiles = 0;
|
|
2809
3605
|
// event short name → its listener `handle` methods (deduped by node id).
|
|
2810
3606
|
const listeners = new Map();
|
|
2811
3607
|
const add = (event, handle) => {
|
|
@@ -2822,6 +3618,8 @@ function laravelEventEdges(ctx) {
|
|
|
2822
3618
|
&& n.startLine >= cls.startLine && n.startLine <= (cls.endLine ?? cls.startLine)) ?? null;
|
|
2823
3619
|
// Pass 1 — build the event→handle map from both registration mechanisms.
|
|
2824
3620
|
for (const file of ctx.getAllFiles()) {
|
|
3621
|
+
if ((++scannedFiles & 15) === 0)
|
|
3622
|
+
await onYield();
|
|
2825
3623
|
if (!LARAVEL_PHP_EXT.test(file))
|
|
2826
3624
|
continue;
|
|
2827
3625
|
const content = ctx.readFile(file);
|
|
@@ -2867,6 +3665,8 @@ function laravelEventEdges(ctx) {
|
|
|
2867
3665
|
const edges = [];
|
|
2868
3666
|
const seen = new Set();
|
|
2869
3667
|
for (const file of ctx.getAllFiles()) {
|
|
3668
|
+
if ((++scannedFiles & 15) === 0)
|
|
3669
|
+
await onYield();
|
|
2870
3670
|
if (!LARAVEL_PHP_EXT.test(file))
|
|
2871
3671
|
continue;
|
|
2872
3672
|
const content = ctx.readFile(file);
|
|
@@ -2906,6 +3706,91 @@ function laravelEventEdges(ctx) {
|
|
|
2906
3706
|
}
|
|
2907
3707
|
return edges;
|
|
2908
3708
|
}
|
|
3709
|
+
/** ArkTS startup + UI lifecycle: loadContent → first screen, Ability chain, aboutToAppear → build. */
|
|
3710
|
+
function arktsEntryEdges(queries, ctx) {
|
|
3711
|
+
const edges = [];
|
|
3712
|
+
const seen = new Set();
|
|
3713
|
+
const add = (source, target, synthesizedBy, extra = {}) => {
|
|
3714
|
+
const key = `${source}>${target}`;
|
|
3715
|
+
if (seen.has(key))
|
|
3716
|
+
return;
|
|
3717
|
+
seen.add(key);
|
|
3718
|
+
const targetNode = queries.getNodeById(target);
|
|
3719
|
+
edges.push({
|
|
3720
|
+
source,
|
|
3721
|
+
target,
|
|
3722
|
+
kind: 'calls',
|
|
3723
|
+
provenance: 'heuristic',
|
|
3724
|
+
metadata: {
|
|
3725
|
+
synthesizedBy,
|
|
3726
|
+
registeredAt: targetNode ? `${targetNode.filePath}:${targetNode.startLine}` : '',
|
|
3727
|
+
...extra,
|
|
3728
|
+
},
|
|
3729
|
+
});
|
|
3730
|
+
};
|
|
3731
|
+
for (const m of methodAndFunctionNodes(queries)) {
|
|
3732
|
+
if (m.language !== 'arkts')
|
|
3733
|
+
continue;
|
|
3734
|
+
const content = ctx.readFile(m.filePath);
|
|
3735
|
+
if (!content)
|
|
3736
|
+
continue;
|
|
3737
|
+
const lines = content.split('\n');
|
|
3738
|
+
const start = Math.max((m.startLine ?? 1) - 1, 0);
|
|
3739
|
+
const end = Math.min(m.endLine ?? m.startLine ?? lines.length, lines.length);
|
|
3740
|
+
const body = lines.slice(start, end).join('\n');
|
|
3741
|
+
const loadMatch = body.match(/loadContent\s*\(\s*['"]([^'"]+)['"]/);
|
|
3742
|
+
if (!loadMatch)
|
|
3743
|
+
continue;
|
|
3744
|
+
const page = loadMatch[1];
|
|
3745
|
+
const pageComponent = (0, arkts_entry_1.findArktsEntryPageComponent)(ctx, page);
|
|
3746
|
+
if (pageComponent) {
|
|
3747
|
+
const aboutToAppear = (0, arkts_entry_1.findArktsLifecycleMethod)(ctx, pageComponent, 'aboutToAppear');
|
|
3748
|
+
if (aboutToAppear) {
|
|
3749
|
+
add(m.id, aboutToAppear.id, 'arkts-load-content', { page });
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
for (const cls of [...queries.getNodesByKind('class'), ...queries.getNodesByKind('struct')]) {
|
|
3754
|
+
if (cls.language !== 'arkts')
|
|
3755
|
+
continue;
|
|
3756
|
+
const methods = queries
|
|
3757
|
+
.getOutgoingEdges(cls.id, ['contains'])
|
|
3758
|
+
.map((e) => queries.getNodeById(e.target))
|
|
3759
|
+
.filter((n) => !!n && n.kind === 'method');
|
|
3760
|
+
const onCreate = methods.find((n) => n.name === 'onCreate');
|
|
3761
|
+
const onWindowStageCreate = methods.find((n) => n.name === 'onWindowStageCreate');
|
|
3762
|
+
if (onCreate && onWindowStageCreate) {
|
|
3763
|
+
add(onCreate.id, onWindowStageCreate.id, 'arkts-lifecycle', { via: 'ability' });
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
for (const comp of [
|
|
3767
|
+
...queries.getNodesByKind('component'),
|
|
3768
|
+
...queries.getNodesByKind('class'),
|
|
3769
|
+
...queries.getNodesByKind('struct'),
|
|
3770
|
+
]) {
|
|
3771
|
+
if (comp.language !== 'arkts')
|
|
3772
|
+
continue;
|
|
3773
|
+
const owner = comp.kind === 'component'
|
|
3774
|
+
? queries.getNodesByKind('struct').find((c) => c.filePath === comp.filePath && c.name === comp.name) ??
|
|
3775
|
+
queries.getNodesByKind('class').find((c) => c.filePath === comp.filePath && c.name === comp.name) ??
|
|
3776
|
+
comp
|
|
3777
|
+
: comp;
|
|
3778
|
+
const methods = queries
|
|
3779
|
+
.getOutgoingEdges(owner.id, ['contains'])
|
|
3780
|
+
.map((e) => queries.getNodeById(e.target))
|
|
3781
|
+
.filter((n) => !!n && n.kind === 'method');
|
|
3782
|
+
const build = methods.find((n) => n.name === 'build');
|
|
3783
|
+
if (!build)
|
|
3784
|
+
continue;
|
|
3785
|
+
for (const life of ['aboutToAppear', 'onPageShow']) {
|
|
3786
|
+
const lifeMethod = methods.find((n) => n.name === life);
|
|
3787
|
+
if (lifeMethod) {
|
|
3788
|
+
add(lifeMethod.id, build.id, 'arkts-lifecycle', { via: life });
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
return edges;
|
|
3793
|
+
}
|
|
2909
3794
|
/**
|
|
2910
3795
|
* Synthesize dispatcher→callback edges (field observers + EventEmitters +
|
|
2911
3796
|
* React re-render + JSX children + Vue templates + SvelteKit load + RN event
|
|
@@ -2917,54 +3802,206 @@ function laravelEventEdges(ctx) {
|
|
|
2917
3802
|
* Sidekiq Worker.perform_async → #perform + Laravel event(new X) → listener handle).
|
|
2918
3803
|
* Returns the count added. Never throws into indexing — callers wrap in try/catch.
|
|
2919
3804
|
*/
|
|
2920
|
-
|
|
3805
|
+
/**
|
|
3806
|
+
* Number of progress steps synthesizeCallbackEdges reports: one per `__mark()`
|
|
3807
|
+
* call (every synthesis pass, plus the dedupe-merge and edge-insert steps).
|
|
3808
|
+
* Cosmetic only — drift just makes the progress bar end early or jump — and a
|
|
3809
|
+
* test pins it to the actual `__mark(` call count so adding a pass without
|
|
3810
|
+
* bumping this fails loudly instead of silently skewing the bar.
|
|
3811
|
+
*/
|
|
3812
|
+
exports.SYNTH_PROGRESS_STEPS = 40;
|
|
3813
|
+
async function synthesizeCallbackEdges(queries, ctx, onProgress) {
|
|
3814
|
+
// Each sub-pass below is a whole-graph scan, and there are ~30 of them, all
|
|
3815
|
+
// running synchronously on the indexer's main thread. Their AGGREGATE can run
|
|
3816
|
+
// for well over a minute on a large repo — long enough for the #850 liveness
|
|
3817
|
+
// watchdog to SIGKILL the process mid-index (#1091), since its heartbeat lives
|
|
3818
|
+
// on this same thread. Yield between passes so the heartbeat can fire; a pass
|
|
3819
|
+
// that itself hangs (a real wedge) never reaches the next yield, so the
|
|
3820
|
+
// watchdog still catches that. See ./cooperative-yield.
|
|
3821
|
+
const yieldToLoop = (0, cooperative_yield_1.createYielder)();
|
|
3822
|
+
// Synthesis runs AFTER the resolution progress bar reaches 100%, so without
|
|
3823
|
+
// its own progress the UI freezes at "Resolving refs 100%" for the whole
|
|
3824
|
+
// tail — long enough on big repos that users conclude the index hung and
|
|
3825
|
+
// kill it. Report each completed pass; the caller surfaces it as its own
|
|
3826
|
+
// progress phase. Emit 0/total up front so the phase flips immediately.
|
|
3827
|
+
// Emissions are throttled to whole-percent movement (each consumes a UI
|
|
3828
|
+
// message); values may be fractional steps from within-pass reporting.
|
|
3829
|
+
let passesDone = 0;
|
|
3830
|
+
let lastPct = -1;
|
|
3831
|
+
const emit = (value) => {
|
|
3832
|
+
if (!onProgress)
|
|
3833
|
+
return;
|
|
3834
|
+
const v = Math.min(value, exports.SYNTH_PROGRESS_STEPS);
|
|
3835
|
+
const pct = Math.floor((v / exports.SYNTH_PROGRESS_STEPS) * 100);
|
|
3836
|
+
if (pct === lastPct)
|
|
3837
|
+
return;
|
|
3838
|
+
lastPct = pct;
|
|
3839
|
+
onProgress(v, exports.SYNTH_PROGRESS_STEPS);
|
|
3840
|
+
};
|
|
3841
|
+
// A single long pass otherwise parks the bar between steps; a pass that
|
|
3842
|
+
// takes this callback reports a 0..1 fraction of its own work, surfaced
|
|
3843
|
+
// here as fractional progress within its step.
|
|
3844
|
+
const subProgress = (fraction) => emit(passesDone + Math.max(0, Math.min(fraction, 1)));
|
|
3845
|
+
emit(0);
|
|
3846
|
+
// Per-pass wall-clock timing to stderr, opt-in via HOMEGRAPH_SYNTH_TIMINGS
|
|
3847
|
+
// (=1: passes over 250ms; =all: every pass). This is the diagnostic that
|
|
3848
|
+
// located both the #1091/#1122 watchdog stalls and the #1212 OOM — keep it.
|
|
3849
|
+
const markT = { t: Date.now() };
|
|
3850
|
+
const __mark = (label) => {
|
|
3851
|
+
const now = Date.now();
|
|
3852
|
+
const dt = now - markT.t;
|
|
3853
|
+
markT.t = now;
|
|
3854
|
+
if (process.env.HOMEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.HOMEGRAPH_SYNTH_TIMINGS === 'all')) {
|
|
3855
|
+
console.error(`[synth-timing] ${label}: ${dt}ms`);
|
|
3856
|
+
}
|
|
3857
|
+
passesDone++;
|
|
3858
|
+
emit(passesDone);
|
|
3859
|
+
};
|
|
3860
|
+
// Language gating: one indexed DISTINCT over the files table lets a pass
|
|
3861
|
+
// whose own filters reference a specific language/extension be skipped
|
|
3862
|
+
// outright when the project has no such files — its result is provably
|
|
3863
|
+
// empty, so skipping is behavior-identical and the cost drops to zero
|
|
3864
|
+
// (the Kotlin pass was the OOM culprit on the pure-C Linux kernel, #1212).
|
|
3865
|
+
// Passes without an explicit language filter always run.
|
|
3866
|
+
const langs = queries.getDistinctFileLanguages();
|
|
3867
|
+
const has = (...ls) => ls.some((l) => langs.has(l));
|
|
3868
|
+
const JS_FAMILY = ['typescript', 'javascript', 'tsx', 'jsx'];
|
|
3869
|
+
const NONE = [];
|
|
2921
3870
|
// Cross-file Go method→type `contains` edges must be synthesized AND persisted
|
|
2922
3871
|
// FIRST: a method declared in a different file from its receiver type is
|
|
2923
3872
|
// otherwise orphaned from the struct, and goImplementsEdges (next) derives a
|
|
2924
3873
|
// struct's method set from its `contains` edges — so without this it would
|
|
2925
3874
|
// under-count the interfaces a cross-file struct satisfies. (#583)
|
|
2926
|
-
const goMethodContains = goCrossFileMethodContainsEdges(queries);
|
|
2927
|
-
|
|
2928
|
-
queries.insertEdges(goMethodContains);
|
|
3875
|
+
const goMethodContains = has('go') ? await goCrossFileMethodContainsEdges(queries, yieldToLoop) : NONE;
|
|
3876
|
+
for (let i = 0; i < goMethodContains.length; i += 2000) {
|
|
3877
|
+
queries.insertEdges(goMethodContains.slice(i, i + 2000));
|
|
3878
|
+
await yieldToLoop();
|
|
3879
|
+
}
|
|
3880
|
+
await yieldToLoop();
|
|
3881
|
+
__mark('goMethodContains');
|
|
2929
3882
|
// Go implicit `implements` edges must be synthesized AND persisted next: the
|
|
2930
3883
|
// interface-dispatch bridge below reads `implements` edges from the DB, and
|
|
2931
3884
|
// Go has none statically. (Other languages already have static implements
|
|
2932
3885
|
// edges from extraction, so they don't need this pre-pass.)
|
|
2933
|
-
const goImpl = goImplementsEdges(queries);
|
|
2934
|
-
|
|
2935
|
-
queries.insertEdges(goImpl);
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
const
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
const
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
const
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
const
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
const
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
const
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
const
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
const
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
const
|
|
3886
|
+
const goImpl = has('go') ? await goImplementsEdges(queries, yieldToLoop) : NONE;
|
|
3887
|
+
for (let i = 0; i < goImpl.length; i += 2000) {
|
|
3888
|
+
queries.insertEdges(goImpl.slice(i, i + 2000));
|
|
3889
|
+
await yieldToLoop();
|
|
3890
|
+
}
|
|
3891
|
+
await yieldToLoop();
|
|
3892
|
+
__mark('goImplements');
|
|
3893
|
+
const fieldEdges = await fieldChannelEdges(queries, ctx, yieldToLoop);
|
|
3894
|
+
await yieldToLoop();
|
|
3895
|
+
__mark('fieldEdges');
|
|
3896
|
+
const closureCollEdges = await closureCollectionEdges(queries, ctx, yieldToLoop);
|
|
3897
|
+
await yieldToLoop();
|
|
3898
|
+
__mark('closureCollEdges');
|
|
3899
|
+
const emitterEdges = await eventEmitterEdges(ctx, yieldToLoop);
|
|
3900
|
+
await yieldToLoop();
|
|
3901
|
+
__mark('emitterEdges');
|
|
3902
|
+
const renderEdges = await reactRenderEdges(queries, ctx, yieldToLoop);
|
|
3903
|
+
await yieldToLoop();
|
|
3904
|
+
__mark('renderEdges');
|
|
3905
|
+
const jsxEdges = await reactJsxChildEdges(ctx, yieldToLoop);
|
|
3906
|
+
await yieldToLoop();
|
|
3907
|
+
__mark('jsxEdges');
|
|
3908
|
+
const vueEdges = has('vue') ? await vueTemplateEdges(ctx, yieldToLoop) : NONE;
|
|
3909
|
+
await yieldToLoop();
|
|
3910
|
+
__mark('vueEdges');
|
|
3911
|
+
const svelteKitEdges = has('svelte') ? await svelteKitLoadEdges(ctx, yieldToLoop) : NONE;
|
|
3912
|
+
await yieldToLoop();
|
|
3913
|
+
__mark('svelteKitEdges');
|
|
3914
|
+
const pascalEdges = await pascalFormEdges(ctx, yieldToLoop);
|
|
3915
|
+
await yieldToLoop();
|
|
3916
|
+
__mark('pascalEdges');
|
|
3917
|
+
const flutterEdges = has('dart') ? await flutterBuildEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3918
|
+
await yieldToLoop();
|
|
3919
|
+
__mark('flutterEdges');
|
|
3920
|
+
const arkuiStateEdges = has('arkts') ? await arkuiStateBuildEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3921
|
+
await yieldToLoop();
|
|
3922
|
+
__mark('arkuiStateEdges');
|
|
3923
|
+
const arkuiEmitter = has('arkts') ? await arkuiEmitterEdges(ctx, yieldToLoop) : NONE;
|
|
3924
|
+
await yieldToLoop();
|
|
3925
|
+
__mark('arkuiEmitter');
|
|
3926
|
+
const arkuiRoutes = has('arkts') ? await arkuiRouterEdges(ctx, yieldToLoop) : NONE;
|
|
3927
|
+
await yieldToLoop();
|
|
3928
|
+
__mark('arkuiRoutes');
|
|
3929
|
+
const cppEdges = has('cpp') ? await cppOverrideEdges(queries, yieldToLoop) : NONE;
|
|
3930
|
+
await yieldToLoop();
|
|
3931
|
+
__mark('cppEdges');
|
|
3932
|
+
const ifaceEdges = has('java', 'kotlin', 'csharp', 'swift', 'scala', 'go', 'rust', 'arkts', ...JS_FAMILY)
|
|
3933
|
+
? await interfaceOverrideEdges(queries, yieldToLoop) : NONE;
|
|
3934
|
+
await yieldToLoop();
|
|
3935
|
+
__mark('ifaceEdges');
|
|
3936
|
+
const kotlinExpectActual = has('kotlin') ? await kotlinExpectActualEdges(queries, yieldToLoop) : NONE;
|
|
3937
|
+
await yieldToLoop();
|
|
3938
|
+
__mark('kotlinExpectActual');
|
|
3939
|
+
const goGrpcEdges = has('go') ? await goGrpcStubImplEdges(queries, yieldToLoop) : NONE;
|
|
3940
|
+
await yieldToLoop();
|
|
3941
|
+
__mark('goGrpcEdges');
|
|
3942
|
+
const rnEventEdgesList = has(...JS_FAMILY) ? await rnEventEdges(ctx, yieldToLoop) : NONE;
|
|
3943
|
+
await yieldToLoop();
|
|
3944
|
+
__mark('rnEventEdgesList');
|
|
3945
|
+
const fabricNativeEdges = await fabricNativeImplEdges(ctx, yieldToLoop);
|
|
3946
|
+
await yieldToLoop();
|
|
3947
|
+
__mark('fabricNativeEdges');
|
|
3948
|
+
const expoXPlatEdges = await expoCrossPlatformEdges(queries, yieldToLoop);
|
|
3949
|
+
await yieldToLoop();
|
|
3950
|
+
__mark('expoXPlatEdges');
|
|
3951
|
+
const rnXPlatEdges = await rnCrossPlatformEdges(queries, yieldToLoop);
|
|
3952
|
+
await yieldToLoop();
|
|
3953
|
+
__mark('rnXPlatEdges');
|
|
3954
|
+
const mybatisEdges = has('java', 'kotlin') && has('xml') ? await mybatisJavaXmlEdges(queries, yieldToLoop) : NONE;
|
|
3955
|
+
await yieldToLoop();
|
|
3956
|
+
__mark('mybatisEdges');
|
|
3957
|
+
const ginEdges = has('go') ? await ginMiddlewareChainEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3958
|
+
await yieldToLoop();
|
|
3959
|
+
__mark('ginEdges');
|
|
3960
|
+
const thunkEdges = has(...JS_FAMILY) ? await reduxThunkEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3961
|
+
await yieldToLoop();
|
|
3962
|
+
__mark('thunkEdges');
|
|
3963
|
+
const registryEdges = await objectRegistryEdges(ctx, yieldToLoop);
|
|
3964
|
+
await yieldToLoop();
|
|
3965
|
+
__mark('registryEdges');
|
|
3966
|
+
const rtkEdges = has(...JS_FAMILY) ? await rtkQueryEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3967
|
+
await yieldToLoop();
|
|
3968
|
+
__mark('rtkEdges');
|
|
3969
|
+
const piniaEdges = has('vue', ...JS_FAMILY) ? await piniaStoreEdges(ctx, yieldToLoop) : NONE;
|
|
3970
|
+
await yieldToLoop();
|
|
3971
|
+
__mark('piniaEdges');
|
|
3972
|
+
const vuexEdges = has('vue', ...JS_FAMILY) ? await vuexDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3973
|
+
await yieldToLoop();
|
|
3974
|
+
__mark('vuexEdges');
|
|
3975
|
+
const celeryEdges = has('python') ? await celeryDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3976
|
+
await yieldToLoop();
|
|
3977
|
+
__mark('celeryEdges');
|
|
3978
|
+
const springEdges = has('java') ? await springEventEdges(ctx, yieldToLoop) : NONE;
|
|
3979
|
+
await yieldToLoop();
|
|
3980
|
+
__mark('springEdges');
|
|
3981
|
+
const mediatrEdges = has('csharp') ? await mediatrDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3982
|
+
await yieldToLoop();
|
|
3983
|
+
__mark('mediatrEdges');
|
|
3984
|
+
const sidekiqEdges = has('ruby') ? await sidekiqDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3985
|
+
await yieldToLoop();
|
|
3986
|
+
__mark('sidekiqEdges');
|
|
3987
|
+
const erlangBehaviourEdges = has('erlang') ? await erlangBehaviourDispatchEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3988
|
+
await yieldToLoop();
|
|
3989
|
+
__mark('erlangBehaviourEdges');
|
|
3990
|
+
const laravelEdges = has('php') ? await laravelEventEdges(ctx, yieldToLoop) : NONE;
|
|
3991
|
+
await yieldToLoop();
|
|
3992
|
+
__mark('laravelEdges');
|
|
3993
|
+
const cFnPtrEdges = has('c', 'cpp') ? await (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(queries, ctx, yieldToLoop, subProgress) : NONE;
|
|
3994
|
+
await yieldToLoop();
|
|
3995
|
+
__mark('cFnPtrEdges');
|
|
3996
|
+
const goframeEdges = has('go') ? await (0, goframe_synthesizer_1.goframeRouteEdges)(ctx, yieldToLoop) : NONE;
|
|
3997
|
+
await yieldToLoop();
|
|
3998
|
+
__mark('goframeEdges');
|
|
3999
|
+
const nixOptionEdges = has('nix') ? await nixOptionPathEdges(queries, yieldToLoop) : NONE;
|
|
4000
|
+
await yieldToLoop();
|
|
4001
|
+
__mark('nixOptionEdges');
|
|
4002
|
+
const arktsStartupEdges = has('arkts') ? arktsEntryEdges(queries, ctx) : NONE;
|
|
4003
|
+
await yieldToLoop();
|
|
4004
|
+
__mark('arktsStartupEdges');
|
|
2968
4005
|
const merged = [];
|
|
2969
4006
|
const seen = new Set();
|
|
2970
4007
|
for (const e of [
|
|
@@ -2977,6 +4014,9 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
2977
4014
|
...svelteKitEdges,
|
|
2978
4015
|
...pascalEdges,
|
|
2979
4016
|
...flutterEdges,
|
|
4017
|
+
...arkuiStateEdges,
|
|
4018
|
+
...arkuiEmitter,
|
|
4019
|
+
...arkuiRoutes,
|
|
2980
4020
|
...cppEdges,
|
|
2981
4021
|
...ifaceEdges,
|
|
2982
4022
|
...kotlinExpectActual,
|
|
@@ -2996,9 +4036,11 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
2996
4036
|
...springEdges,
|
|
2997
4037
|
...mediatrEdges,
|
|
2998
4038
|
...sidekiqEdges,
|
|
4039
|
+
...erlangBehaviourEdges,
|
|
2999
4040
|
...laravelEdges,
|
|
3000
4041
|
...cFnPtrEdges,
|
|
3001
4042
|
...goframeEdges,
|
|
4043
|
+
...nixOptionEdges,
|
|
3002
4044
|
...arktsStartupEdges,
|
|
3003
4045
|
]) {
|
|
3004
4046
|
const key = `${e.source}>${e.target}`;
|
|
@@ -3007,8 +4049,15 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
3007
4049
|
seen.add(key);
|
|
3008
4050
|
merged.push(e);
|
|
3009
4051
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
4052
|
+
__mark('dedupe-merge');
|
|
4053
|
+
// Chunked insert with yields: on the Linux kernel the merged synthesized
|
|
4054
|
+
// edge set is ~275k rows, and one transaction for all of them was a 20s
|
|
4055
|
+
// unyielded main-thread span (#1212 follow-up) — the last one in the tail.
|
|
4056
|
+
for (let i = 0; i < merged.length; i += 2000) {
|
|
4057
|
+
queries.insertEdges(merged.slice(i, i + 2000));
|
|
4058
|
+
await yieldToLoop();
|
|
4059
|
+
}
|
|
4060
|
+
__mark('insertMergedEdges');
|
|
3012
4061
|
return merged.length + goImpl.length + goMethodContains.length;
|
|
3013
4062
|
}
|
|
3014
4063
|
//# sourceMappingURL=callback-synthesizer.js.map
|