homegraph 1.4.1 → 1.5.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/LICENSE +21 -21
- package/README.md +305 -268
- package/dist/bin/command-supervision.d.ts.map +1 -1
- package/dist/bin/command-supervision.js +7 -4
- package/dist/bin/command-supervision.js.map +1 -1
- package/dist/bin/homegraph.js +304 -81
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +32 -11
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +143 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +305 -28
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.js +37 -37
- package/dist/db/queries.d.ts +43 -4
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +294 -170
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +203 -203
- package/dist/db/sqlite-adapter.d.ts +24 -17
- package/dist/db/sqlite-adapter.d.ts.map +1 -1
- package/dist/db/sqlite-adapter.js +292 -36
- package/dist/db/sqlite-adapter.js.map +1 -1
- package/dist/db/wal-valve.d.ts +15 -2
- package/dist/db/wal-valve.d.ts.map +1 -1
- package/dist/db/wal-valve.js +88 -6
- package/dist/db/wal-valve.js.map +1 -1
- package/dist/directory.js +5 -5
- package/dist/extraction/arkts-batch-worker.js +3 -2
- package/dist/extraction/arkts-batch-worker.js.map +1 -1
- package/dist/extraction/default-ignore.d.ts +39 -0
- package/dist/extraction/default-ignore.d.ts.map +1 -0
- package/dist/extraction/default-ignore.js +221 -0
- package/dist/extraction/default-ignore.js.map +1 -0
- package/dist/extraction/index.d.ts +9 -10
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +227 -323
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.d.ts +2 -4
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.js +6 -21
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +100 -6
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +1777 -173
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/store-worker.d.ts +20 -0
- package/dist/extraction/store-worker.d.ts.map +1 -0
- package/dist/extraction/store-worker.js +102 -0
- package/dist/extraction/store-worker.js.map +1 -0
- package/dist/extraction/store-writer.d.ts +48 -0
- package/dist/extraction/store-writer.d.ts.map +1 -0
- package/dist/extraction/store-writer.js +167 -0
- package/dist/extraction/store-writer.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +47 -0
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-c_sharp.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-pascal.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +14 -4
- package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
- package/dist/extraction/wasm-runtime-flags.js +55 -7
- package/dist/extraction/wasm-runtime-flags.js.map +1 -1
- package/dist/index.d.ts +16 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +135 -97
- package/dist/index.js.map +1 -1
- package/dist/installer/instructions-template.js +9 -9
- package/dist/mcp/liveness-watchdog.d.ts +18 -0
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +185 -73
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/query-worker.d.ts +2 -2
- package/dist/mcp/query-worker.js +2 -2
- package/dist/mcp/server-instructions.js +47 -47
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +42 -26
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reasoning/reasoner.js +32 -32
- package/dist/resolution/callback-synthesizer.d.ts +52 -16
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +234 -180
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/cooperative-yield.d.ts +10 -2
- package/dist/resolution/cooperative-yield.d.ts.map +1 -1
- package/dist/resolution/cooperative-yield.js +6 -4
- package/dist/resolution/cooperative-yield.js.map +1 -1
- package/dist/resolution/frameworks/go.d.ts.map +1 -1
- package/dist/resolution/frameworks/go.js +29 -6
- package/dist/resolution/frameworks/go.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +2 -3
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +166 -7
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +87 -2
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +739 -131
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/memory-budget.d.ts +48 -0
- package/dist/resolution/memory-budget.d.ts.map +1 -0
- package/dist/resolution/memory-budget.js +162 -0
- package/dist/resolution/memory-budget.js.map +1 -0
- package/dist/resolution/name-matcher.d.ts +12 -1
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +439 -97
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/resolver-pool.d.ts +106 -0
- package/dist/resolution/resolver-pool.d.ts.map +1 -0
- package/dist/resolution/resolver-pool.js +344 -0
- package/dist/resolution/resolver-pool.js.map +1 -0
- package/dist/resolution/resolver-worker.d.ts +17 -0
- package/dist/resolution/resolver-worker.d.ts.map +1 -0
- package/dist/resolution/resolver-worker.js +141 -0
- package/dist/resolution/resolver-worker.js.map +1 -0
- package/dist/resolution/types.d.ts +3 -0
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/spec/build/diff-parser.d.ts +55 -0
- package/dist/spec/build/diff-parser.d.ts.map +1 -0
- package/dist/spec/build/diff-parser.js +205 -0
- package/dist/spec/build/diff-parser.js.map +1 -0
- package/dist/spec/build/pipeline.d.ts +54 -0
- package/dist/spec/build/pipeline.d.ts.map +1 -0
- package/dist/spec/build/pipeline.js +170 -0
- package/dist/spec/build/pipeline.js.map +1 -0
- package/dist/spec/build/scan.d.ts +48 -0
- package/dist/spec/build/scan.d.ts.map +1 -0
- package/dist/spec/build/scan.js +89 -0
- package/dist/spec/build/scan.js.map +1 -0
- package/dist/spec/build/scope-resolver.d.ts +50 -0
- package/dist/spec/build/scope-resolver.d.ts.map +1 -0
- package/dist/spec/build/scope-resolver.js +105 -0
- package/dist/spec/build/scope-resolver.js.map +1 -0
- package/dist/spec/build/spec-extractor.d.ts +74 -0
- package/dist/spec/build/spec-extractor.d.ts.map +1 -0
- package/dist/spec/build/spec-extractor.js +344 -0
- package/dist/spec/build/spec-extractor.js.map +1 -0
- package/dist/spec/config.d.ts +35 -1
- package/dist/spec/config.d.ts.map +1 -1
- package/dist/spec/config.js +30 -5
- package/dist/spec/config.js.map +1 -1
- package/dist/spec/db/commit-node.js +4 -4
- package/dist/spec/db/fragment-node.js +10 -10
- package/dist/spec/db/fts.d.ts.map +1 -1
- package/dist/spec/db/fts.js +27 -58
- package/dist/spec/db/fts.js.map +1 -1
- package/dist/spec/db/index.d.ts +5 -3
- package/dist/spec/db/index.d.ts.map +1 -1
- package/dist/spec/db/index.js +16 -1
- package/dist/spec/db/index.js.map +1 -1
- package/dist/spec/db/persist.d.ts +45 -0
- package/dist/spec/db/persist.d.ts.map +1 -0
- package/dist/spec/db/persist.js +66 -0
- package/dist/spec/db/persist.js.map +1 -0
- package/dist/spec/db/relations.d.ts +80 -1
- package/dist/spec/db/relations.d.ts.map +1 -1
- package/dist/spec/db/relations.js +218 -47
- package/dist/spec/db/relations.js.map +1 -1
- package/dist/spec/db/schema.d.ts +4 -0
- package/dist/spec/db/schema.d.ts.map +1 -1
- package/dist/spec/db/schema.js +10 -6
- package/dist/spec/db/schema.js.map +1 -1
- package/dist/spec/db/schema.sql +121 -117
- package/dist/spec/db/spec-node.d.ts +5 -0
- package/dist/spec/db/spec-node.d.ts.map +1 -1
- package/dist/spec/db/spec-node.js +15 -9
- package/dist/spec/db/spec-node.js.map +1 -1
- package/dist/spec/db/sql-utils.d.ts +11 -0
- package/dist/spec/db/sql-utils.d.ts.map +1 -0
- package/dist/spec/db/sql-utils.js +19 -0
- package/dist/spec/db/sql-utils.js.map +1 -0
- package/dist/spec/evolve/cluster-context.d.ts +30 -0
- package/dist/spec/evolve/cluster-context.d.ts.map +1 -0
- package/dist/spec/evolve/cluster-context.js +78 -0
- package/dist/spec/evolve/cluster-context.js.map +1 -0
- package/dist/spec/evolve/commit-spec-analyzer.d.ts +51 -0
- package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +1 -0
- package/dist/spec/evolve/commit-spec-analyzer.js +88 -0
- package/dist/spec/evolve/commit-spec-analyzer.js.map +1 -0
- package/dist/spec/evolve/commit-spec-persister.d.ts +53 -0
- package/dist/spec/evolve/commit-spec-persister.d.ts.map +1 -0
- package/dist/spec/evolve/commit-spec-persister.js +93 -0
- package/dist/spec/evolve/commit-spec-persister.js.map +1 -0
- package/dist/spec/evolve/impact-locator.d.ts +23 -7
- package/dist/spec/evolve/impact-locator.d.ts.map +1 -1
- package/dist/spec/evolve/impact-locator.js +58 -14
- package/dist/spec/evolve/impact-locator.js.map +1 -1
- package/dist/spec/evolve/pipeline.d.ts +62 -25
- package/dist/spec/evolve/pipeline.d.ts.map +1 -1
- package/dist/spec/evolve/pipeline.js +436 -457
- package/dist/spec/evolve/pipeline.js.map +1 -1
- package/dist/spec/evolve/spec-rewriter.d.ts +13 -13
- package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
- package/dist/spec/evolve/spec-rewriter.js +45 -45
- package/dist/spec/evolve/spec-rewriter.js.map +1 -1
- package/dist/spec/git/commits.d.ts +85 -0
- package/dist/spec/git/commits.d.ts.map +1 -0
- package/dist/spec/git/commits.js +218 -0
- package/dist/spec/git/commits.js.map +1 -0
- package/dist/spec/git/exec.d.ts +13 -0
- package/dist/spec/git/exec.d.ts.map +1 -0
- package/dist/spec/git/exec.js +19 -0
- package/dist/spec/git/exec.js.map +1 -0
- package/dist/spec/git/index.d.ts +9 -0
- package/dist/spec/git/index.d.ts.map +1 -0
- package/dist/spec/git/index.js +22 -0
- package/dist/spec/git/index.js.map +1 -0
- package/dist/spec/graph/index.d.ts +8 -0
- package/dist/spec/graph/index.d.ts.map +1 -0
- package/dist/spec/graph/index.js +17 -0
- package/dist/spec/graph/index.js.map +1 -0
- package/dist/spec/graph/queries.d.ts +6 -35
- package/dist/spec/graph/queries.d.ts.map +1 -1
- package/dist/spec/graph/queries.js +71 -248
- package/dist/spec/graph/queries.js.map +1 -1
- package/dist/spec/llm/agent-client.d.ts +59 -0
- package/dist/spec/llm/agent-client.d.ts.map +1 -0
- package/dist/spec/llm/agent-client.js +213 -0
- package/dist/spec/llm/agent-client.js.map +1 -0
- package/dist/spec/llm/agents/claude-code.d.ts +32 -0
- package/dist/spec/llm/agents/claude-code.d.ts.map +1 -0
- package/dist/spec/llm/agents/claude-code.js +125 -0
- package/dist/spec/llm/agents/claude-code.js.map +1 -0
- package/dist/spec/llm/agents/codex.d.ts +32 -0
- package/dist/spec/llm/agents/codex.d.ts.map +1 -0
- package/dist/spec/llm/agents/codex.js +145 -0
- package/dist/spec/llm/agents/codex.js.map +1 -0
- package/dist/spec/llm/agents/detect-utils.d.ts +28 -0
- package/dist/spec/llm/agents/detect-utils.d.ts.map +1 -0
- package/dist/spec/llm/agents/detect-utils.js +91 -0
- package/dist/spec/llm/agents/detect-utils.js.map +1 -0
- package/dist/spec/llm/agents/deveco-code.d.ts +33 -0
- package/dist/spec/llm/agents/deveco-code.d.ts.map +1 -0
- package/dist/spec/llm/agents/deveco-code.js +165 -0
- package/dist/spec/llm/agents/deveco-code.js.map +1 -0
- package/dist/spec/llm/agents/index.d.ts +31 -0
- package/dist/spec/llm/agents/index.d.ts.map +1 -0
- package/dist/spec/llm/agents/index.js +65 -0
- package/dist/spec/llm/agents/index.js.map +1 -0
- package/dist/spec/llm/agents/types.d.ts +76 -0
- package/dist/spec/llm/agents/types.d.ts.map +1 -0
- package/dist/spec/llm/agents/types.js +15 -0
- package/dist/spec/llm/agents/types.js.map +1 -0
- package/dist/spec/llm/client.d.ts +31 -11
- package/dist/spec/llm/client.d.ts.map +1 -1
- package/dist/spec/llm/client.js +143 -81
- package/dist/spec/llm/client.js.map +1 -1
- package/dist/spec/llm/factory.d.ts +41 -0
- package/dist/spec/llm/factory.d.ts.map +1 -0
- package/dist/spec/llm/factory.js +80 -0
- package/dist/spec/llm/factory.js.map +1 -0
- package/dist/spec/llm/prompts.d.ts +24 -8
- package/dist/spec/llm/prompts.d.ts.map +1 -1
- package/dist/spec/llm/prompts.js +112 -62
- package/dist/spec/llm/prompts.js.map +1 -1
- package/dist/spec/llm/retry.d.ts +53 -0
- package/dist/spec/llm/retry.d.ts.map +1 -0
- package/dist/spec/llm/retry.js +153 -0
- package/dist/spec/llm/retry.js.map +1 -0
- package/dist/spec/mine/clustering/features.d.ts +38 -0
- package/dist/spec/mine/clustering/features.d.ts.map +1 -0
- package/dist/spec/mine/clustering/features.js +108 -0
- package/dist/spec/mine/clustering/features.js.map +1 -0
- package/dist/spec/mine/clustering/index.d.ts +62 -0
- package/dist/spec/mine/clustering/index.d.ts.map +1 -0
- package/dist/spec/mine/clustering/index.js +259 -0
- package/dist/spec/mine/clustering/index.js.map +1 -0
- package/dist/spec/mine/clustering/leiden.d.ts +61 -0
- package/dist/spec/mine/clustering/leiden.d.ts.map +1 -0
- package/dist/spec/mine/clustering/leiden.js +489 -0
- package/dist/spec/mine/clustering/leiden.js.map +1 -0
- package/dist/spec/mine/clustering/text-similarity.d.ts +22 -0
- package/dist/spec/mine/clustering/text-similarity.d.ts.map +1 -0
- package/dist/spec/mine/clustering/text-similarity.js +98 -0
- package/dist/spec/mine/clustering/text-similarity.js.map +1 -0
- package/dist/spec/mine/generator.d.ts +54 -0
- package/dist/spec/mine/generator.d.ts.map +1 -0
- package/dist/spec/mine/generator.js +267 -0
- package/dist/spec/mine/generator.js.map +1 -0
- package/dist/spec/mine/persist.d.ts +37 -0
- package/dist/spec/mine/persist.d.ts.map +1 -0
- package/dist/spec/mine/persist.js +170 -0
- package/dist/spec/mine/persist.js.map +1 -0
- package/dist/spec/mine/pipeline.d.ts +41 -0
- package/dist/spec/mine/pipeline.d.ts.map +1 -0
- package/dist/spec/mine/pipeline.js +236 -0
- package/dist/spec/mine/pipeline.js.map +1 -0
- package/dist/spec/mine/scanner.d.ts +59 -0
- package/dist/spec/mine/scanner.d.ts.map +1 -0
- package/dist/spec/mine/scanner.js +383 -0
- package/dist/spec/mine/scanner.js.map +1 -0
- package/dist/spec/types.d.ts +32 -0
- package/dist/spec/types.d.ts.map +1 -1
- package/dist/spec/ui/index.d.ts +9 -0
- package/dist/spec/ui/index.d.ts.map +1 -0
- package/dist/spec/ui/index.js +16 -0
- package/dist/spec/ui/index.js.map +1 -0
- package/dist/spec/ui/progress-handler.d.ts +33 -0
- package/dist/spec/ui/progress-handler.d.ts.map +1 -0
- package/dist/spec/ui/progress-handler.js +132 -0
- package/dist/spec/ui/progress-handler.js.map +1 -0
- package/dist/spec/ui/progress.d.ts +24 -0
- package/dist/spec/ui/progress.d.ts.map +1 -0
- package/dist/spec/ui/progress.js +13 -0
- package/dist/spec/ui/progress.js.map +1 -0
- package/dist/spec/utils/fs.d.ts +45 -0
- package/dist/spec/utils/fs.d.ts.map +1 -0
- package/dist/spec/utils/fs.js +163 -0
- package/dist/spec/utils/fs.js.map +1 -0
- package/dist/spec/utils/index.d.ts +12 -0
- package/dist/spec/utils/index.d.ts.map +1 -0
- package/dist/spec/utils/index.js +26 -0
- package/dist/spec/utils/index.js.map +1 -0
- package/dist/spec/utils/meta.d.ts +32 -0
- package/dist/spec/utils/meta.d.ts.map +1 -0
- package/dist/spec/utils/meta.js +122 -0
- package/dist/spec/utils/meta.js.map +1 -0
- package/dist/spec/utils/truncate.d.ts +86 -0
- package/dist/spec/utils/truncate.d.ts.map +1 -0
- package/dist/spec/utils/truncate.js +172 -0
- package/dist/spec/utils/truncate.js.map +1 -0
- package/dist/sync/watcher.d.ts +25 -1
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +76 -3
- package/dist/sync/watcher.js.map +1 -1
- package/dist/telemetry/index.js +3 -3
- package/dist/telemetry/index.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +62 -58
- package/scripts/_tmp-cfwk-resolve.log +0 -0
- package/scripts/_tmp-cfwk-sig.log +0 -0
- package/scripts/_tmp-cfwk-vt.log +0 -0
- package/scripts/add-lang/bench.sh +60 -60
- package/scripts/add-lang/check-grammar.mjs +75 -75
- package/scripts/add-lang/dump-ast.mjs +103 -103
- package/scripts/add-lang/verify-extraction.mjs +70 -70
- package/scripts/agent-eval/ab-adoption.sh +91 -91
- package/scripts/agent-eval/ab-hook.sh +86 -86
- package/scripts/agent-eval/ab-impl.sh +78 -78
- package/scripts/agent-eval/ab-new-vs-baseline.sh +102 -102
- package/scripts/agent-eval/ab-sufficiency.sh +78 -78
- package/scripts/agent-eval/arms-F.sh +21 -21
- package/scripts/agent-eval/arms-matrix.sh +37 -37
- package/scripts/agent-eval/audit.sh +68 -68
- package/scripts/agent-eval/bench-readme.sh +28 -28
- package/scripts/agent-eval/bench-why-repo.sh +22 -22
- package/scripts/agent-eval/block-read-hook.sh +19 -19
- package/scripts/agent-eval/hook-settings.json +15 -15
- package/scripts/agent-eval/itrun.sh +120 -120
- package/scripts/agent-eval/offload-eval-3arm.sh +72 -72
- package/scripts/agent-eval/offload-eval-cost.mjs +133 -133
- package/scripts/agent-eval/offload-eval-effort.mjs +108 -108
- package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -25
- package/scripts/agent-eval/offload-eval-frontload.sh +47 -47
- package/scripts/agent-eval/offload-eval-ground-truth.json +18 -18
- package/scripts/agent-eval/offload-eval-hook.mjs +84 -84
- package/scripts/agent-eval/offload-eval-judge.mjs +103 -103
- package/scripts/agent-eval/offload-eval-matrix.sh +20 -20
- package/scripts/agent-eval/offload-eval-metrics.mjs +94 -94
- package/scripts/agent-eval/offload-eval-refs1.sh +50 -50
- package/scripts/agent-eval/offload-eval-setup.sh +24 -24
- package/scripts/agent-eval/offload-eval-styles.sh +71 -71
- package/scripts/agent-eval/offload-eval-summarize.mjs +68 -68
- package/scripts/agent-eval/offload-eval.md +76 -76
- package/scripts/agent-eval/parse-arms.mjs +116 -116
- package/scripts/agent-eval/parse-bench-readme.mjs +84 -84
- package/scripts/agent-eval/parse-run.mjs +45 -45
- package/scripts/agent-eval/parse-session.mjs +93 -93
- package/scripts/agent-eval/probe-context.mjs +21 -21
- package/scripts/agent-eval/probe-explore.mjs +40 -40
- package/scripts/agent-eval/probe-node.mjs +20 -20
- package/scripts/agent-eval/probe-sweep.mjs +119 -119
- package/scripts/agent-eval/probe-trace.mjs +20 -20
- package/scripts/agent-eval/redirect-read-hook.sh +38 -38
- package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -119
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -125
- package/scripts/agent-eval/run-agent.sh +34 -34
- package/scripts/agent-eval/run-all.sh +75 -75
- package/scripts/agent-eval/run-arms.sh +56 -56
- package/scripts/agent-eval/seq-matrix.mjs +137 -137
- package/scripts/bench-arkts-init-rss.log +0 -0
- package/scripts/build-bundle.sh +123 -123
- package/scripts/exp_boundary_eval/README.md +247 -247
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/win_mcp_launcher.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -78
- package/scripts/exp_boundary_eval/_test_stdin.py +8 -8
- package/scripts/exp_boundary_eval/_utils.py +1116 -1116
- package/scripts/exp_boundary_eval/analyze.py +1313 -1313
- package/scripts/exp_boundary_eval/deveco_arm.py +519 -519
- package/scripts/exp_boundary_eval/run_all.py +378 -378
- package/scripts/exp_boundary_eval/run_one.py +165 -165
- package/scripts/exp_boundary_eval/run_session.py +158 -158
- package/scripts/exp_boundary_eval/setup.py +120 -120
- package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -73
- package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -36
- package/scripts/exp_boundary_eval/win_node_launcher.py +24 -24
- package/scripts/extract-release-notes.mjs +130 -130
- package/scripts/local-install.sh +41 -41
- package/scripts/npm-sdk.js +75 -75
- package/scripts/npm-shim.js +275 -275
- package/scripts/ohos-sdk-publish.mjs +133 -133
- package/scripts/pack-npm.sh +119 -119
- package/scripts/prepare-release.mjs +270 -270
- package/scripts/probe-arkts-mem-why-run.log +0 -0
- package/scripts/probe-banner-livecard-ir.log +0 -0
- package/scripts/probe-cfwk-ast.stderr.log +0 -0
- package/scripts/probe-cfwk-ast.stdout.log +0 -0
- package/scripts/probe-cfwk-attr-shape.log +0 -0
- package/scripts/probe-cfwk-cfgdump.stderr.log +0 -0
- package/scripts/probe-cfwk-cfgdump.stdout.log +0 -0
- package/scripts/probe-cfwk-cvc.stderr.log +0 -0
- package/scripts/probe-cfwk-cvc.stdout.log +0 -0
- package/scripts/probe-cfwk-diag2.log +0 -0
- package/scripts/probe-cfwk-diag3.log +0 -0
- package/scripts/probe-cfwk-fedbg.stderr.log +0 -0
- package/scripts/probe-cfwk-fedbg.stdout.log +0 -0
- package/scripts/probe-cfwk-fileresult.log +0 -0
- package/scripts/probe-cfwk-fix.stderr.log +0 -0
- package/scripts/probe-cfwk-fix.stdout.log +0 -0
- package/scripts/probe-cfwk-fix2.stderr.log +0 -0
- package/scripts/probe-cfwk-fix2.stdout.log +0 -0
- package/scripts/probe-cfwk-fix3.stderr.log +0 -0
- package/scripts/probe-cfwk-fix3.stdout.log +0 -0
- package/scripts/probe-cfwk-foreach.log +0 -0
- package/scripts/probe-cfwk-getmethod-throw.log +0 -0
- package/scripts/probe-cfwk-hg-extract.log +0 -0
- package/scripts/probe-cfwk-pr1003-noprior.stderr.log +0 -0
- package/scripts/probe-cfwk-pr1003-noprior.stdout.log +0 -0
- package/scripts/probe-cfwk-pr1003.stderr.log +0 -0
- package/scripts/probe-cfwk-pr1003.stdout.log +0 -0
- package/scripts/probe-cfwk-preroot-noprior.stderr.log +0 -0
- package/scripts/probe-cfwk-preroot-noprior.stdout.log +0 -0
- package/scripts/probe-cfwk-preroot-prior.stderr.log +0 -0
- package/scripts/probe-cfwk-preroot-prior.stdout.log +0 -0
- package/scripts/probe-cfwk-preroot-skipstate.stderr.log +0 -0
- package/scripts/probe-cfwk-preroot-skipstate.stdout.log +0 -0
- package/scripts/probe-cfwk-resolve-sim.log +0 -0
- package/scripts/probe-cfwk-tree-shape.log +0 -0
- package/scripts/probe-cfwk-walk-abort.log +0 -0
- package/scripts/probe-cfwk.log +0 -0
- package/scripts/probe-force-index.log +0 -0
- package/scripts/probe-no-force-index.log +0 -0
- package/scripts/probe-samefile-ir.stderr.log +0 -0
- package/scripts/probe-samefile-ir.stdout.log +224 -0
- package/scripts/probe-sdk-vs-project.log +0 -0
- package/scripts/probe-viewtree-downgrade.log +0 -0
- package/dist/arkts/ohos-api-index.d.ts +0 -15
- package/dist/arkts/ohos-api-index.d.ts.map +0 -1
- package/dist/arkts/ohos-api-index.js +0 -190
- package/dist/arkts/ohos-api-index.js.map +0 -1
- package/dist/arkts/ohos-sdk-input.d.ts +0 -36
- package/dist/arkts/ohos-sdk-input.d.ts.map +0 -1
- package/dist/arkts/ohos-sdk-input.js +0 -214
- package/dist/arkts/ohos-sdk-input.js.map +0 -1
- package/dist/extraction/languages/arkts-state-decorators.d.ts +0 -13
- package/dist/extraction/languages/arkts-state-decorators.d.ts.map +0 -1
- package/dist/extraction/languages/arkts-state-decorators.js +0 -26
- package/dist/extraction/languages/arkts-state-decorators.js.map +0 -1
- package/dist/extraction/languages/ohos-api-consumer.d.ts +0 -34
- package/dist/extraction/languages/ohos-api-consumer.d.ts.map +0 -1
- package/dist/extraction/languages/ohos-api-consumer.js +0 -283
- package/dist/extraction/languages/ohos-api-consumer.js.map +0 -1
|
@@ -3,10 +3,27 @@
|
|
|
3
3
|
* ArkTS (.ets) extraction via ArkAnalyzer.
|
|
4
4
|
*
|
|
5
5
|
* Unlike tree-sitter languages, ArkTS is batch-complete: the first `.ets`
|
|
6
|
-
* parse builds
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* parse builds a Scene, runs RTA, persists every `.ets` file (nodes, edges,
|
|
7
|
+
* cross-file RTA calls) directly to the DB, then returns the requested file's
|
|
8
|
+
* result. Later `.ets` hits in the same batch short-circuit (orchestrator store
|
|
9
|
+
* skips via matching content hash).
|
|
10
|
+
*
|
|
11
|
+
* HarmonyOS multi-module projects (`build-profile.json5`) use
|
|
12
|
+
* `Scene.analyseByModule` so each HAP/HSP/HAR is the atomic unit (like a file
|
|
13
|
+
* for tree-sitter languages): symbols + ViewTree + **intra-module RTA** while
|
|
14
|
+
* the module has BODIES. Per-module RTA entries = DummyMain (Ability/Component
|
|
15
|
+
* lifecycle) **plus the module export surface** (exported functions and methods
|
|
16
|
+
* of exported classes), so non-UI libraries still grow reachable call edges.
|
|
17
|
+
* CFG does **not** stitch same-module calls (that densified past unlimited RTA).
|
|
18
|
+
* Cross-module edges come from RTA with an exact MethodSignature map, plus a
|
|
19
|
+
* CFG `%unk` bridge that recovers Class.method / free functions from stmt text,
|
|
20
|
+
* UnclearReferenceType names, or PascalCase base locals (import + unique target
|
|
21
|
+
* + fan-out blocklist). Deps load to SIGNATURES.
|
|
22
|
+
*
|
|
23
|
+
* Sync can re-index a **dirty module subset** (`primeArkTSBatch` + `changedFiles`)
|
|
24
|
+
* via `ModuleAnalysisConfig.setTargetModuleIds`, instead of rebuilding every
|
|
25
|
+
* PROJECT module. Unchanged modules stay in the DB; incoming cross-file edges
|
|
26
|
+
* into dirty files are snapshotted and re-bound like file-level sync (#899).
|
|
10
27
|
*/
|
|
11
28
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
29
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,18 +58,30 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
58
|
return result;
|
|
42
59
|
};
|
|
43
60
|
})();
|
|
61
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
62
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
63
|
+
};
|
|
44
64
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
65
|
exports.OHOS_API_DB_PATH_META = exports.OHOS_API_VERSION_META = exports.OHOS_API_FILE_PREFIX = exports.OhosSdkInputError = exports.ArkTSExtractor = void 0;
|
|
46
66
|
exports.cleanArkTypeString = cleanArkTypeString;
|
|
47
67
|
exports.normalizeArktsReturnType = normalizeArktsReturnType;
|
|
48
68
|
exports.buildArkMethodSignatureFields = buildArkMethodSignatureFields;
|
|
69
|
+
exports.shouldUseIsolatedArkTSBuild = shouldUseIsolatedArkTSBuild;
|
|
70
|
+
exports.normalizeHarmonyModuleSrcPath = normalizeHarmonyModuleSrcPath;
|
|
71
|
+
exports.listHarmonyProjectModules = listHarmonyProjectModules;
|
|
72
|
+
exports.resolveDirtyHarmonyModules = resolveDirtyHarmonyModules;
|
|
49
73
|
exports.isArkAnalyzerSourcePath = isArkAnalyzerSourcePath;
|
|
74
|
+
exports.scanEtsFiles = scanEtsFiles;
|
|
50
75
|
exports.runIsolatedArkTSBatchEntry = runIsolatedArkTSBatchEntry;
|
|
51
76
|
exports.primeArkTSBatch = primeArkTSBatch;
|
|
52
77
|
exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
|
|
53
78
|
exports.resetArkTSBatch = resetArkTSBatch;
|
|
79
|
+
exports.collectExportedRtaEntryPointsFromFiles = collectExportedRtaEntryPointsFromFiles;
|
|
54
80
|
exports.stateDecoratorKinds = stateDecoratorKinds;
|
|
55
81
|
exports.stateTransferViaForField = stateTransferViaForField;
|
|
82
|
+
exports.isUnclearArkClassSignature = isUnclearArkClassSignature;
|
|
83
|
+
exports.isUnclearArkMethodSignature = isUnclearArkMethodSignature;
|
|
84
|
+
exports.pickPreferredClassNodeId = pickPreferredClassNodeId;
|
|
56
85
|
exports.drainArkTSIndexNotices = drainArkTSIndexNotices;
|
|
57
86
|
exports.parseOhosToolsVersionFromName = parseOhosToolsVersionFromName;
|
|
58
87
|
exports.isArchivePath = isArchivePath;
|
|
@@ -82,11 +111,16 @@ const child_process_1 = require("child_process");
|
|
|
82
111
|
const arkanalyzer_1 = require("arkanalyzer");
|
|
83
112
|
const db_1 = require("../../db");
|
|
84
113
|
const queries_1 = require("../../db/queries");
|
|
114
|
+
const directory_1 = require("../../directory");
|
|
115
|
+
const project_config_1 = require("../../project-config");
|
|
85
116
|
const context_1 = require("../context");
|
|
117
|
+
const default_ignore_1 = require("../default-ignore");
|
|
86
118
|
const extraction_version_1 = require("../extraction-version");
|
|
87
119
|
const version_1 = require("../../mcp/version");
|
|
120
|
+
const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
|
|
88
121
|
const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
|
|
89
122
|
const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
|
|
123
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
90
124
|
/** Primitive / void return types — not stored in nodes.return_type (inference-only column). */
|
|
91
125
|
const ARKTS_NON_CLASS_RETURN = new Set([
|
|
92
126
|
'void',
|
|
@@ -222,16 +256,148 @@ function isStackOverflowExitCode(code) {
|
|
|
222
256
|
return false;
|
|
223
257
|
return code === 3221225725 || code === -1073741571;
|
|
224
258
|
}
|
|
225
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Opt-in isolated ArkTS Scene build (child process + enlarged `--stack-size`).
|
|
261
|
+
* Default is in-process: faster and avoids a Windows-only auto-isolated path that
|
|
262
|
+
* could burn minutes on failed stack-size retries before giving up. Set
|
|
263
|
+
* `HOMEGRAPH_ARKTS_ISOLATED=1` only when an in-process Scene build stack-overflows
|
|
264
|
+
* the indexer (rare Photos-scale Windows cases).
|
|
265
|
+
*/
|
|
266
|
+
function shouldUseIsolatedArkTSBuild() {
|
|
226
267
|
const mode = process.env.HOMEGRAPH_ARKTS_ISOLATED?.trim();
|
|
227
|
-
|
|
228
|
-
|
|
268
|
+
return mode === '1' || mode === 'true';
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Scene eviction budget sits below the V8 heap cap (heap default 3584MB so process
|
|
272
|
+
* RSS stays near 4GB — native memory sits outside the heap). Small reserve only.
|
|
273
|
+
*/
|
|
274
|
+
const ARKTS_HOMEGRAPH_RESERVE_MB = 256;
|
|
275
|
+
/** Default Scene `memoryLimitMB`. Override with HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB. */
|
|
276
|
+
function defaultArkTSSceneMemoryLimitMB() {
|
|
277
|
+
return Math.max(512, (0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)() - ARKTS_HOMEGRAPH_RESERVE_MB);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* RSS limit passed to ArkAnalyzer `SceneOptions.memoryLimitMB`.
|
|
281
|
+
* When exceeded, `analyseByModule` evicts cached modules before loading the next.
|
|
282
|
+
* Set `HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB=0` to disable Scene eviction.
|
|
283
|
+
*/
|
|
284
|
+
function resolveArkTSSceneMemoryLimitMB() {
|
|
285
|
+
const raw = process.env.HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB?.trim();
|
|
286
|
+
if (raw !== undefined && raw !== '') {
|
|
287
|
+
const n = Number(raw);
|
|
288
|
+
if (Number.isFinite(n) && n >= 0)
|
|
289
|
+
return Math.floor(n);
|
|
290
|
+
}
|
|
291
|
+
return defaultArkTSSceneMemoryLimitMB();
|
|
292
|
+
}
|
|
293
|
+
/** Prefer modular Scene build when the project has Harmony module metadata. */
|
|
294
|
+
function shouldUseModularArkTSBuild(rootDir) {
|
|
295
|
+
const mode = process.env.HOMEGRAPH_ARKTS_MODULAR?.trim();
|
|
229
296
|
if (mode === '0' || mode === 'false')
|
|
230
297
|
return false;
|
|
231
|
-
|
|
232
|
-
if (process.platform === 'win32' && etsCount >= 500)
|
|
298
|
+
if (mode === '1' || mode === 'true')
|
|
233
299
|
return true;
|
|
234
|
-
return
|
|
300
|
+
return fs.existsSync(path.join(rootDir, 'build-profile.json5'));
|
|
301
|
+
}
|
|
302
|
+
/** Normalize build-profile `srcPath` / relative file paths for prefix matching. */
|
|
303
|
+
function normalizeHarmonyModuleSrcPath(srcPath) {
|
|
304
|
+
return srcPath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Read PROJECT modules from root `build-profile.json5` (name + srcPath).
|
|
308
|
+
* Returns [] when missing or unparsable — callers should fall back to full Scene.
|
|
309
|
+
*/
|
|
310
|
+
function listHarmonyProjectModules(rootDir) {
|
|
311
|
+
const profilePath = path.join(rootDir, 'build-profile.json5');
|
|
312
|
+
if (!fs.existsSync(profilePath))
|
|
313
|
+
return [];
|
|
314
|
+
let raw;
|
|
315
|
+
try {
|
|
316
|
+
raw = parseJson5Minimal(fs.readFileSync(profilePath, 'utf-8'));
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
if (!raw || typeof raw !== 'object')
|
|
322
|
+
return [];
|
|
323
|
+
const modules = raw.modules;
|
|
324
|
+
if (!Array.isArray(modules))
|
|
325
|
+
return [];
|
|
326
|
+
const out = [];
|
|
327
|
+
for (const entry of modules) {
|
|
328
|
+
if (!entry || typeof entry !== 'object')
|
|
329
|
+
continue;
|
|
330
|
+
const rec = entry;
|
|
331
|
+
if (typeof rec.name !== 'string' || typeof rec.srcPath !== 'string')
|
|
332
|
+
continue;
|
|
333
|
+
const srcPath = normalizeHarmonyModuleSrcPath(rec.srcPath);
|
|
334
|
+
if (!srcPath)
|
|
335
|
+
continue;
|
|
336
|
+
out.push({ name: rec.name, srcPath });
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
function findLongestHarmonyModule(fileRel, modules) {
|
|
341
|
+
const file = normIndexPath(fileRel);
|
|
342
|
+
let best = null;
|
|
343
|
+
for (const mod of modules) {
|
|
344
|
+
const src = mod.srcPath;
|
|
345
|
+
if (file === src || file.startsWith(`${src}/`)) {
|
|
346
|
+
if (!best || mod.srcPath.length > best.srcPath.length)
|
|
347
|
+
best = mod;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return best;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Map changed/removed paths to Harmony modules for incremental ArkTS sync.
|
|
354
|
+
* Structural project files (`build-profile.json5`, root `oh-package.json5`) or
|
|
355
|
+
* ArkAnalyzer sources outside every module force a full rebuild.
|
|
356
|
+
*/
|
|
357
|
+
function resolveDirtyHarmonyModules(rootDir, changedFiles) {
|
|
358
|
+
if (!shouldUseModularArkTSBuild(rootDir)) {
|
|
359
|
+
return { mode: 'full', reason: 'non-modular ArkTS project' };
|
|
360
|
+
}
|
|
361
|
+
const modules = listHarmonyProjectModules(rootDir);
|
|
362
|
+
if (modules.length === 0) {
|
|
363
|
+
return { mode: 'full', reason: 'no PROJECT modules in build-profile.json5' };
|
|
364
|
+
}
|
|
365
|
+
const changed = [...changedFiles].map(normIndexPath).filter(Boolean);
|
|
366
|
+
if (changed.length === 0)
|
|
367
|
+
return { mode: 'none' };
|
|
368
|
+
for (const f of changed) {
|
|
369
|
+
if (f === 'build-profile.json5' || f.endsWith('/build-profile.json5')) {
|
|
370
|
+
return { mode: 'full', reason: 'build-profile.json5 changed' };
|
|
371
|
+
}
|
|
372
|
+
if (f === 'oh-package.json5') {
|
|
373
|
+
return { mode: 'full', reason: 'root oh-package.json5 changed' };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const dirty = new Set();
|
|
377
|
+
const unmatched = [];
|
|
378
|
+
for (const f of changed) {
|
|
379
|
+
// Virtual ArkAnalyzer paths are batch artifacts, not Harmony module sources.
|
|
380
|
+
if (f.startsWith('@'))
|
|
381
|
+
continue;
|
|
382
|
+
const isModuleMeta = f.endsWith('/module.json5') || f.endsWith('/oh-package.json5');
|
|
383
|
+
const isArkSrc = isArkAnalyzerSourcePath(f);
|
|
384
|
+
if (!isArkSrc && !isModuleMeta)
|
|
385
|
+
continue;
|
|
386
|
+
const match = findLongestHarmonyModule(f, modules);
|
|
387
|
+
if (match)
|
|
388
|
+
dirty.add(match.srcPath);
|
|
389
|
+
else if (isArkSrc)
|
|
390
|
+
unmatched.push(f);
|
|
391
|
+
}
|
|
392
|
+
if (unmatched.length > 0) {
|
|
393
|
+
return {
|
|
394
|
+
mode: 'full',
|
|
395
|
+
reason: `ArkTS sources outside modules: ${unmatched.slice(0, 5).join(', ')}`,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (dirty.size === 0)
|
|
399
|
+
return { mode: 'none' };
|
|
400
|
+
return { mode: 'modules', moduleSrcPaths: [...dirty].sort() };
|
|
235
401
|
}
|
|
236
402
|
function homegraphDbPath(rootDir) {
|
|
237
403
|
return path.join(rootDir, '.homegraph', 'homegraph.db');
|
|
@@ -239,6 +405,9 @@ function homegraphDbPath(rootDir) {
|
|
|
239
405
|
const ARK_PROVENANCE = 'heuristic';
|
|
240
406
|
/** Virtual file path for ArkAnalyzer's in-scene dummy entry (not on disk). */
|
|
241
407
|
const ARKANALYZER_DUMMY_FILE = '@dummyFile.ets';
|
|
408
|
+
function fieldSigKey(classSig, fieldName) {
|
|
409
|
+
return `${classSig}::${fieldName}`;
|
|
410
|
+
}
|
|
242
411
|
/** HarmonyOS sources indexed via ArkAnalyzer Scene batch (`.ets`, `.ts`, `.d.ts`). */
|
|
243
412
|
function isArkAnalyzerSourcePath(filePath) {
|
|
244
413
|
const base = path.basename(filePath);
|
|
@@ -251,8 +420,24 @@ function isEtsFileName(name) {
|
|
|
251
420
|
function isCoLocatedArkTsFileName(name) {
|
|
252
421
|
return isArkAnalyzerSourcePath(name) && !isEtsFileName(name);
|
|
253
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Ignore matchers aligned with `scanDirectoryWalk`'s base set (built-in defaults
|
|
425
|
+
* + root `.gitignore` + `homegraph.json` `exclude`).
|
|
426
|
+
*/
|
|
427
|
+
function arktsScanIgnoreMatchers(rootDir) {
|
|
428
|
+
const matchers = [(0, default_ignore_1.buildDefaultIgnore)(rootDir)];
|
|
429
|
+
const exclude = (0, project_config_1.loadExcludePatterns)(rootDir);
|
|
430
|
+
if (exclude.length > 0)
|
|
431
|
+
matchers.push((0, ignore_1.default)().add(exclude));
|
|
432
|
+
return matchers;
|
|
433
|
+
}
|
|
434
|
+
function isArktsScanIgnored(relPath, isDir, matchers) {
|
|
435
|
+
const probe = isDir ? `${relPath.replace(/\/$/, '')}/` : relPath;
|
|
436
|
+
return matchers.some((ig) => ig.ignores(probe));
|
|
437
|
+
}
|
|
254
438
|
/** True when the tree looks like a HarmonyOS / ArkTS project (not a generic TS repo). */
|
|
255
439
|
function hasHarmonyProjectMarkers(rootDir) {
|
|
440
|
+
const matchers = arktsScanIgnoreMatchers(rootDir);
|
|
256
441
|
const found = [];
|
|
257
442
|
function walk(dir) {
|
|
258
443
|
if (found.length > 0)
|
|
@@ -266,13 +451,17 @@ function hasHarmonyProjectMarkers(rootDir) {
|
|
|
266
451
|
}
|
|
267
452
|
for (const entry of entries) {
|
|
268
453
|
const full = path.join(dir, entry.name);
|
|
454
|
+
const rel = path.relative(rootDir, full).replace(/\\/g, '/');
|
|
269
455
|
if (entry.isDirectory()) {
|
|
270
|
-
if (entry.name === '
|
|
456
|
+
if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
|
|
457
|
+
continue;
|
|
458
|
+
if (isArktsScanIgnored(rel, true, matchers))
|
|
271
459
|
continue;
|
|
272
|
-
}
|
|
273
460
|
walk(full);
|
|
274
461
|
}
|
|
275
462
|
else if (entry.isFile() && (isEtsFileName(entry.name) || entry.name === 'module.json5')) {
|
|
463
|
+
if (isArktsScanIgnored(rel, false, matchers))
|
|
464
|
+
continue;
|
|
276
465
|
found.push(full);
|
|
277
466
|
return;
|
|
278
467
|
}
|
|
@@ -306,9 +495,17 @@ function emptyResult(filePath, message, severity) {
|
|
|
306
495
|
durationMs: 0,
|
|
307
496
|
};
|
|
308
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Enumerate ArkAnalyzer sources under `rootDir`, using the same ignore rules as
|
|
500
|
+
* `scanDirectory` (defaults like `build/`/`dist/`, root `.gitignore`, exclude).
|
|
501
|
+
* Previously this walk only skipped `node_modules`/`.git`/`.homegraph`, so
|
|
502
|
+
* HarmonyOS build-output `.ets` files got written into the DB and non-git
|
|
503
|
+
* `homegraph status` reported them as Pending Removed.
|
|
504
|
+
*/
|
|
309
505
|
function scanEtsFiles(rootDir) {
|
|
310
506
|
const etsFiles = [];
|
|
311
507
|
const coLocated = [];
|
|
508
|
+
const matchers = arktsScanIgnoreMatchers(rootDir);
|
|
312
509
|
function walk(dir) {
|
|
313
510
|
let entries;
|
|
314
511
|
try {
|
|
@@ -319,13 +516,17 @@ function scanEtsFiles(rootDir) {
|
|
|
319
516
|
}
|
|
320
517
|
for (const entry of entries) {
|
|
321
518
|
const full = path.join(dir, entry.name);
|
|
519
|
+
const rel = path.relative(rootDir, full).replace(/\\/g, '/');
|
|
322
520
|
if (entry.isDirectory()) {
|
|
323
|
-
if (entry.name === '
|
|
521
|
+
if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
|
|
522
|
+
continue;
|
|
523
|
+
if (isArktsScanIgnored(rel, true, matchers))
|
|
324
524
|
continue;
|
|
325
525
|
walk(full);
|
|
326
526
|
}
|
|
327
527
|
else if (entry.isFile()) {
|
|
328
|
-
|
|
528
|
+
if (isArktsScanIgnored(rel, false, matchers))
|
|
529
|
+
continue;
|
|
329
530
|
if (isEtsFileName(entry.name)) {
|
|
330
531
|
etsFiles.push(rel);
|
|
331
532
|
}
|
|
@@ -358,12 +559,15 @@ let batchBuildCommitted = false;
|
|
|
358
559
|
function reportBatchProgress(subphase, current, total, currentFile) {
|
|
359
560
|
(0, context_1.reportArkTSBatchProgress)({ subphase, current, total, currentFile });
|
|
360
561
|
}
|
|
361
|
-
function persistFileResult(queries, filePath, content, stats, result) {
|
|
562
|
+
function persistFileResult(queries, filePath, content, stats, result, options) {
|
|
362
563
|
const contentHash = hashContent(content);
|
|
363
564
|
const existingFile = queries.getFileByPath(filePath);
|
|
364
|
-
if (existingFile?.contentHash === contentHash) {
|
|
565
|
+
if (!options?.force && existingFile?.contentHash === contentHash) {
|
|
365
566
|
return;
|
|
366
567
|
}
|
|
568
|
+
const crossFileIncomingEdges = existingFile && typeof queries.getCrossFileIncomingEdgesWithTarget === 'function'
|
|
569
|
+
? queries.getCrossFileIncomingEdgesWithTarget(filePath)
|
|
570
|
+
: [];
|
|
367
571
|
if (existingFile) {
|
|
368
572
|
queries.deleteFile(filePath);
|
|
369
573
|
}
|
|
@@ -378,6 +582,30 @@ function persistFileResult(queries, filePath, content, stats, result) {
|
|
|
378
582
|
queries.insertEdges(validEdges);
|
|
379
583
|
}
|
|
380
584
|
}
|
|
585
|
+
if (crossFileIncomingEdges.length > 0) {
|
|
586
|
+
const newNodesByKindName = new Map();
|
|
587
|
+
for (const n of validNodes) {
|
|
588
|
+
newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
|
|
589
|
+
}
|
|
590
|
+
const reinserted = [];
|
|
591
|
+
for (const e of crossFileIncomingEdges) {
|
|
592
|
+
const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
|
|
593
|
+
if (newTargetId) {
|
|
594
|
+
reinserted.push({
|
|
595
|
+
source: e.source,
|
|
596
|
+
target: newTargetId,
|
|
597
|
+
kind: e.kind,
|
|
598
|
+
metadata: e.metadata,
|
|
599
|
+
line: e.line,
|
|
600
|
+
column: e.column,
|
|
601
|
+
provenance: e.provenance,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (reinserted.length > 0) {
|
|
606
|
+
queries.insertEdges(reinserted);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
381
609
|
if (result.unresolvedReferences.length > 0) {
|
|
382
610
|
const insertedIds = new Set(validNodes.map((n) => n.id));
|
|
383
611
|
const refsWithContext = result.unresolvedReferences
|
|
@@ -403,6 +631,22 @@ function persistFileResult(queries, filePath, content, stats, result) {
|
|
|
403
631
|
};
|
|
404
632
|
queries.upsertFile(fileRecord);
|
|
405
633
|
}
|
|
634
|
+
/** Force-rewrite every file in an incremental module index (edges may change without content). */
|
|
635
|
+
function persistIncrementalModuleResults(rootDir, queries, index) {
|
|
636
|
+
const persistTotal = index.fileResults.size;
|
|
637
|
+
let persistIndex = 0;
|
|
638
|
+
for (const [filePath, fileResult] of index.fileResults) {
|
|
639
|
+
persistIndex++;
|
|
640
|
+
reportBatchProgress('persist', persistIndex, persistTotal, filePath);
|
|
641
|
+
const isVirtual = filePath.startsWith('@');
|
|
642
|
+
const full = path.join(rootDir, filePath);
|
|
643
|
+
const content = isVirtual ? '' : fs.readFileSync(full, 'utf-8');
|
|
644
|
+
const stats = isVirtual
|
|
645
|
+
? { size: 0, mtimeMs: Date.now() }
|
|
646
|
+
: fs.statSync(full);
|
|
647
|
+
persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
406
650
|
async function persistBatchResultsAsync(rootDir, queries, index) {
|
|
407
651
|
const entries = [...index.fileResults.entries()];
|
|
408
652
|
const persistTotal = entries.length;
|
|
@@ -501,8 +745,12 @@ function spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile) {
|
|
|
501
745
|
const workerPath = path.join(__dirname, '..', 'arkts-batch-worker.js');
|
|
502
746
|
let lastCode = null;
|
|
503
747
|
for (const stackKb of resolveArkTSWorkerStackSizesKb()) {
|
|
504
|
-
process.stderr.write(`\n\x1b[33m ArkTS:
|
|
505
|
-
const argv = (0, wasm_runtime_flags_1.buildRelaunchArgv)(workerPath, [rootDir, dbPath, triggerFile], [
|
|
748
|
+
process.stderr.write(`\n\x1b[33m ArkTS: Scene build in isolated process (--stack-size=${stackKb} KB; modular if build-profile.json5)...\x1b[0m\n`);
|
|
749
|
+
const argv = (0, wasm_runtime_flags_1.buildRelaunchArgv)(workerPath, [rootDir, dbPath, triggerFile], [
|
|
750
|
+
...process.execArgv,
|
|
751
|
+
`--stack-size=${stackKb}`,
|
|
752
|
+
(0, wasm_runtime_flags_1.maxOldSpaceSizeFlag)(),
|
|
753
|
+
]);
|
|
506
754
|
const result = (0, child_process_1.spawnSync)(process.execPath, argv, {
|
|
507
755
|
stdio: 'inherit',
|
|
508
756
|
env: process.env,
|
|
@@ -558,7 +806,7 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
|
|
|
558
806
|
}
|
|
559
807
|
}
|
|
560
808
|
function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
|
|
561
|
-
if (shouldUseIsolatedArkTSBuild(
|
|
809
|
+
if (shouldUseIsolatedArkTSBuild()) {
|
|
562
810
|
const dbPath = homegraphDbPath(rootDir);
|
|
563
811
|
if (!fs.existsSync(dbPath)) {
|
|
564
812
|
throw new Error(`ArkTS isolated build requires ${dbPath}`);
|
|
@@ -569,7 +817,8 @@ function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
|
|
|
569
817
|
const spawned = spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile);
|
|
570
818
|
if (!spawned.ok) {
|
|
571
819
|
throw new Error(`ArkTS isolated Scene build failed (exit ${spawned.lastCode ?? 'unknown'}). ` +
|
|
572
|
-
'
|
|
820
|
+
'Unset HOMEGRAPH_ARKTS_ISOLATED to use the default in-process build, ' +
|
|
821
|
+
'or raise HOMEGRAPH_ARKTS_STACK_SIZES_KB (e.g. 65536,131072,262144)');
|
|
573
822
|
}
|
|
574
823
|
return markBatchCommittedAfterWorker(rootDir, triggerFile, etsFiles, batchKey);
|
|
575
824
|
}
|
|
@@ -605,12 +854,28 @@ function runArkTSBatch(rootDir, queries, triggerFile) {
|
|
|
605
854
|
return runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
|
|
606
855
|
}
|
|
607
856
|
/** Async batch build with parallel file reads during persist (used by indexAll). */
|
|
608
|
-
async function primeArkTSBatch(rootDir, queries, triggerFile) {
|
|
857
|
+
async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
|
|
609
858
|
const normalizedTrigger = normIndexPath(triggerFile);
|
|
610
859
|
const etsFiles = scanEtsFiles(rootDir);
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
860
|
+
const changed = options?.changedFiles
|
|
861
|
+
? [...options.changedFiles].map(normIndexPath).filter(Boolean)
|
|
862
|
+
: null;
|
|
863
|
+
if (changed) {
|
|
864
|
+
const resolution = resolveDirtyHarmonyModules(rootDir, changed);
|
|
865
|
+
if (resolution.mode === 'none') {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (resolution.mode === 'modules') {
|
|
869
|
+
await runArkTSBatchIncrementalModules(rootDir, queries, normalizedTrigger, etsFiles, resolution.moduleSrcPaths);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
// mode === 'full': fall through to full rebuild (ignore in-memory cache).
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
const cached = tryReturnCachedBatch(rootDir, normalizedTrigger, etsFiles);
|
|
876
|
+
if (cached) {
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
614
879
|
}
|
|
615
880
|
const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
|
|
616
881
|
batchTriggerFile = null;
|
|
@@ -620,7 +885,7 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
|
|
|
620
885
|
batchBuildCommitted = false;
|
|
621
886
|
return;
|
|
622
887
|
}
|
|
623
|
-
if (shouldUseIsolatedArkTSBuild(
|
|
888
|
+
if (shouldUseIsolatedArkTSBuild()) {
|
|
624
889
|
runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
|
|
625
890
|
return;
|
|
626
891
|
}
|
|
@@ -646,6 +911,56 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
|
|
|
646
911
|
(0, context_1.setArktsBatchRunning)(false);
|
|
647
912
|
}
|
|
648
913
|
}
|
|
914
|
+
async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, etsFiles, moduleSrcPaths) {
|
|
915
|
+
// Isolated worker has no module-subset protocol yet — fall back to full.
|
|
916
|
+
if (shouldUseIsolatedArkTSBuild()) {
|
|
917
|
+
const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
|
|
918
|
+
runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
batchTriggerFile = null;
|
|
922
|
+
batchPersistedPaths = new Set();
|
|
923
|
+
(0, context_1.setArktsBatchRunning)(true);
|
|
924
|
+
try {
|
|
925
|
+
reportBatchProgress('scene', 0, etsFiles.length);
|
|
926
|
+
const index = buildArkTSIndexByModule(rootDir, etsFiles, { targetModuleSrcPaths: moduleSrcPaths });
|
|
927
|
+
if (index.fileResults.size === 0) {
|
|
928
|
+
const fatal = index.errors.find((e) => e.severity === 'error');
|
|
929
|
+
// Fall back to full project batch when the subset produced nothing.
|
|
930
|
+
const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
|
|
931
|
+
if (fatal) {
|
|
932
|
+
process.stderr.write(`\n\x1b[33m ArkTS: incremental modules failed (${fatal.message}); full rebuild\x1b[0m\n`);
|
|
933
|
+
}
|
|
934
|
+
const full = buildArkTSIndex(rootDir, etsFiles);
|
|
935
|
+
if (full.fileResults.size === 0) {
|
|
936
|
+
throw new Error(fatal?.message ?? 'ArkTS incremental batch produced no indexed files');
|
|
937
|
+
}
|
|
938
|
+
queries.deleteArkTSCrossFileCallEdges();
|
|
939
|
+
await persistBatchResultsAsync(rootDir, queries, full);
|
|
940
|
+
if (full.crossFileEdges.length > 0) {
|
|
941
|
+
const valid = full.crossFileEdges.filter((e) => full.nodeIds.has(e.source) && full.nodeIds.has(e.target));
|
|
942
|
+
if (valid.length > 0)
|
|
943
|
+
queries.insertEdges(valid);
|
|
944
|
+
}
|
|
945
|
+
commitArkTSBatch(rootDir, batchKey, etsFiles, full, triggerFile);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
persistIncrementalModuleResults(rootDir, queries, index);
|
|
949
|
+
if (index.crossFileEdges.length > 0) {
|
|
950
|
+
const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
|
|
951
|
+
if (valid.length > 0) {
|
|
952
|
+
queries.insertEdges(valid);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
// Unique key so a partial index is never mistaken for a full-project cache hit.
|
|
956
|
+
const batchKey = `incremental:${moduleSrcPaths.join(',')}`;
|
|
957
|
+
commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
|
|
958
|
+
process.stderr.write(`\n\x1b[33m ArkTS: incremental module reindex done (${moduleSrcPaths.join(', ')})\x1b[0m\n`);
|
|
959
|
+
}
|
|
960
|
+
finally {
|
|
961
|
+
(0, context_1.setArktsBatchRunning)(false);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
649
964
|
/** True when this `.ets` file was already written by the in-memory ArkTS batch. */
|
|
650
965
|
function isArkTSBatchPersisted(filePath) {
|
|
651
966
|
return batchPersistedPaths.has(normIndexPath(filePath));
|
|
@@ -965,20 +1280,22 @@ function arkEdge(source, target, kind, extra) {
|
|
|
965
1280
|
...extra,
|
|
966
1281
|
};
|
|
967
1282
|
}
|
|
968
|
-
function
|
|
1283
|
+
function collectComponentScopeFromFiles(files) {
|
|
969
1284
|
const scope = [];
|
|
970
|
-
for (const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1285
|
+
for (const file of files) {
|
|
1286
|
+
for (const cls of file.getClasses()) {
|
|
1287
|
+
if (cls.isDefaultArkClass())
|
|
1288
|
+
continue;
|
|
1289
|
+
if (cls.hasComponentDecorator()) {
|
|
1290
|
+
scope.push(cls);
|
|
1291
|
+
}
|
|
975
1292
|
}
|
|
976
1293
|
}
|
|
977
1294
|
return scope;
|
|
978
1295
|
}
|
|
979
|
-
function
|
|
1296
|
+
function collectNonUiRtaEntryPointsFromFiles(files) {
|
|
980
1297
|
const entries = [];
|
|
981
|
-
for (const file of
|
|
1298
|
+
for (const file of files) {
|
|
982
1299
|
const defaultClass = file.getDefaultClass();
|
|
983
1300
|
for (const method of defaultClass.getMethods()) {
|
|
984
1301
|
if (shouldSkipArkMethod(method))
|
|
@@ -991,14 +1308,152 @@ function collectNonUiRtaEntryPoints(scene) {
|
|
|
991
1308
|
}
|
|
992
1309
|
return entries;
|
|
993
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Methods another module can call into: module `export` surface as RTA entries.
|
|
1313
|
+
* - exported function/method → itself
|
|
1314
|
+
* - exported class/struct → all of its non-skip methods (with body)
|
|
1315
|
+
* Skips `export … from` re-exports (not this module's IR).
|
|
1316
|
+
*/
|
|
1317
|
+
function collectExportedRtaEntryPointsFromFiles(files) {
|
|
1318
|
+
const seen = new Set();
|
|
1319
|
+
const out = [];
|
|
1320
|
+
const addMethod = (method) => {
|
|
1321
|
+
if (shouldSkipArkMethod(method))
|
|
1322
|
+
return;
|
|
1323
|
+
try {
|
|
1324
|
+
if (!method.getCfg())
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
catch {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
try {
|
|
1331
|
+
const sig = method.getSignature();
|
|
1332
|
+
const key = sig.toString();
|
|
1333
|
+
if (seen.has(key))
|
|
1334
|
+
return;
|
|
1335
|
+
seen.add(key);
|
|
1336
|
+
out.push(sig);
|
|
1337
|
+
}
|
|
1338
|
+
catch {
|
|
1339
|
+
// signature unavailable
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
const addClass = (cls) => {
|
|
1343
|
+
try {
|
|
1344
|
+
for (const method of cls.getMethods(true)) {
|
|
1345
|
+
addMethod(method);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
catch {
|
|
1349
|
+
// class IR unavailable
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
const visitExportInfos = (infos) => {
|
|
1353
|
+
for (const info of infos) {
|
|
1354
|
+
try {
|
|
1355
|
+
if (info.getFrom?.())
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
catch {
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
let arkExport;
|
|
1362
|
+
try {
|
|
1363
|
+
arkExport = info.getArkExport?.() ?? null;
|
|
1364
|
+
}
|
|
1365
|
+
catch {
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
if (!arkExport || typeof arkExport !== 'object')
|
|
1369
|
+
continue;
|
|
1370
|
+
const exp = arkExport;
|
|
1371
|
+
// Prefer ExportType when present (METHOD=2, CLASS=1, NAME_SPACE=0).
|
|
1372
|
+
let exportType;
|
|
1373
|
+
try {
|
|
1374
|
+
exportType = exp.getExportType?.();
|
|
1375
|
+
}
|
|
1376
|
+
catch {
|
|
1377
|
+
exportType = undefined;
|
|
1378
|
+
}
|
|
1379
|
+
if (exportType === 2 || (exportType === undefined && typeof exp.getDeclaringArkClass === 'function' && typeof exp.getCfg === 'function')) {
|
|
1380
|
+
addMethod(arkExport);
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
if (exportType === 1 || (exportType === undefined && typeof exp.getMethods === 'function')) {
|
|
1384
|
+
addClass(arkExport);
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
if (exportType === 0 || typeof exp.getClasses === 'function') {
|
|
1388
|
+
try {
|
|
1389
|
+
for (const cls of exp.getClasses?.() ?? []) {
|
|
1390
|
+
addClass(cls);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
catch {
|
|
1394
|
+
// ignore
|
|
1395
|
+
}
|
|
1396
|
+
const nested = exp.getExportInfos?.();
|
|
1397
|
+
if (nested)
|
|
1398
|
+
visitExportInfos(nested);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
for (const file of files) {
|
|
1403
|
+
try {
|
|
1404
|
+
visitExportInfos(file.getExportInfos());
|
|
1405
|
+
}
|
|
1406
|
+
catch {
|
|
1407
|
+
// ignore
|
|
1408
|
+
}
|
|
1409
|
+
try {
|
|
1410
|
+
for (const ns of file.getNamespaces()) {
|
|
1411
|
+
try {
|
|
1412
|
+
visitExportInfos(ns.getExportInfos());
|
|
1413
|
+
}
|
|
1414
|
+
catch {
|
|
1415
|
+
// ignore
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
catch {
|
|
1420
|
+
// ignore
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return out;
|
|
1424
|
+
}
|
|
1425
|
+
/** Merge RTA entry signatures, preserving order and dropping duplicates. */
|
|
1426
|
+
function mergeRtaEntryPoints(...groups) {
|
|
1427
|
+
const seen = new Set();
|
|
1428
|
+
const out = [];
|
|
1429
|
+
for (const group of groups) {
|
|
1430
|
+
for (const sig of group) {
|
|
1431
|
+
try {
|
|
1432
|
+
const key = sig.toString();
|
|
1433
|
+
if (seen.has(key))
|
|
1434
|
+
continue;
|
|
1435
|
+
seen.add(key);
|
|
1436
|
+
out.push(sig);
|
|
1437
|
+
}
|
|
1438
|
+
catch {
|
|
1439
|
+
// ignore
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return out;
|
|
1444
|
+
}
|
|
994
1445
|
function resolveRtaEntryPoints(scene) {
|
|
1446
|
+
return resolveRtaEntryPointsFromFiles(scene, scene.getFiles());
|
|
1447
|
+
}
|
|
1448
|
+
/** RTA entries scoped to the given files (one Harmony module while it is loaded). */
|
|
1449
|
+
function resolveRtaEntryPointsFromFiles(scene, files) {
|
|
995
1450
|
const explicit = scene.getEntryPoints();
|
|
996
1451
|
if (explicit.length > 0) {
|
|
997
1452
|
return { entryPoints: explicit, dummyMain: null };
|
|
998
1453
|
}
|
|
999
|
-
if (
|
|
1454
|
+
if (filesHaveArkUiEntries(files)) {
|
|
1000
1455
|
try {
|
|
1001
|
-
const componentScope =
|
|
1456
|
+
const componentScope = collectComponentScopeFromFiles(files);
|
|
1002
1457
|
const dummy = new arkanalyzer_1.DummyMainCreater(scene, undefined, componentScope.length > 0 ? componentScope : undefined, true);
|
|
1003
1458
|
dummy.createDummyMain();
|
|
1004
1459
|
const dummyMain = dummy.getDummyMain();
|
|
@@ -1008,11 +1463,11 @@ function resolveRtaEntryPoints(scene) {
|
|
|
1008
1463
|
// fall through
|
|
1009
1464
|
}
|
|
1010
1465
|
}
|
|
1011
|
-
const fallback =
|
|
1466
|
+
const fallback = collectNonUiRtaEntryPointsFromFiles(files);
|
|
1012
1467
|
return { entryPoints: fallback, dummyMain: null };
|
|
1013
1468
|
}
|
|
1014
|
-
function
|
|
1015
|
-
for (const file of
|
|
1469
|
+
function filesHaveArkUiEntries(files) {
|
|
1470
|
+
for (const file of files) {
|
|
1016
1471
|
for (const cls of file.getClasses()) {
|
|
1017
1472
|
if (cls.isDefaultArkClass())
|
|
1018
1473
|
continue;
|
|
@@ -1049,92 +1504,437 @@ function viewTreeLineFromStmt(stmt) {
|
|
|
1049
1504
|
return stmt?.getOriginFullPosition()?.getFirstLine() ?? 1;
|
|
1050
1505
|
}
|
|
1051
1506
|
function isViewTreeClassSignature(sig) {
|
|
1052
|
-
|
|
1507
|
+
// Prefer instanceof; duck-type fallback if duplicate arkanalyzer copies break instanceof.
|
|
1508
|
+
if (sig instanceof arkanalyzer_1.ClassSignature)
|
|
1509
|
+
return true;
|
|
1510
|
+
if (sig instanceof arkanalyzer_1.MethodSignature)
|
|
1511
|
+
return false;
|
|
1512
|
+
const anySig = sig;
|
|
1513
|
+
// Require ClassSignature shape — MethodSignature-like and other sigs must not enter getClass().
|
|
1514
|
+
return (typeof anySig.getClassName === 'function' &&
|
|
1515
|
+
typeof anySig.getDeclaringFileSignature === 'function' &&
|
|
1516
|
+
typeof anySig.getMethodSubSignature !== 'function');
|
|
1053
1517
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1518
|
+
/** Skip anonymous / synthetic Ark names for name→id fallback maps. */
|
|
1519
|
+
function isUsableArkTypeName(name) {
|
|
1520
|
+
return !!name && !name.startsWith('%') && !name.startsWith('<');
|
|
1521
|
+
}
|
|
1522
|
+
/** Escape a literal for use inside a RegExp source. */
|
|
1523
|
+
function escapeRegExpLiteral(s) {
|
|
1524
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* ArkUI framework IR receivers — `View.create` / `Column.pop` / `If.branch`
|
|
1528
|
+
* are synthesizer noise, not project call sites. Never seed name-match from them.
|
|
1529
|
+
*/
|
|
1530
|
+
const ARKUI_IR_CALL_RECEIVERS = new Set([
|
|
1531
|
+
'View',
|
|
1532
|
+
'If',
|
|
1533
|
+
'ForEach',
|
|
1534
|
+
'LazyForEach',
|
|
1535
|
+
'Repeat',
|
|
1536
|
+
'Column',
|
|
1537
|
+
'Row',
|
|
1538
|
+
'Stack',
|
|
1539
|
+
'Flex',
|
|
1540
|
+
'Grid',
|
|
1541
|
+
'List',
|
|
1542
|
+
'Swiper',
|
|
1543
|
+
'Tabs',
|
|
1544
|
+
'TabContent',
|
|
1545
|
+
'Button',
|
|
1546
|
+
'Text',
|
|
1547
|
+
'Image',
|
|
1548
|
+
'Scroll',
|
|
1549
|
+
'WaterFlow',
|
|
1550
|
+
'RelativeContainer',
|
|
1551
|
+
'Blank',
|
|
1552
|
+
'Divider',
|
|
1553
|
+
'Span',
|
|
1554
|
+
'Canvas',
|
|
1555
|
+
'XComponent',
|
|
1556
|
+
]);
|
|
1557
|
+
const ARKUI_IR_CALL_METHODS = new Set(['create', 'pop', 'branch', 'iterator']);
|
|
1558
|
+
const ARKTS_STDLIB_CALL_RECEIVERS = new Set([
|
|
1559
|
+
'Array',
|
|
1560
|
+
'Map',
|
|
1561
|
+
'Set',
|
|
1562
|
+
'Object',
|
|
1563
|
+
'Promise',
|
|
1564
|
+
'JSON',
|
|
1565
|
+
'String',
|
|
1566
|
+
'Number',
|
|
1567
|
+
'Boolean',
|
|
1568
|
+
'Error',
|
|
1569
|
+
'Date',
|
|
1570
|
+
'Math',
|
|
1571
|
+
'RegExp',
|
|
1572
|
+
'Symbol',
|
|
1573
|
+
]);
|
|
1574
|
+
function isJunkArkCallSeed(className, methodName) {
|
|
1575
|
+
if (ARKTS_STDLIB_CALL_RECEIVERS.has(className))
|
|
1576
|
+
return true;
|
|
1577
|
+
if (ARKUI_IR_CALL_RECEIVERS.has(className) && ARKUI_IR_CALL_METHODS.has(methodName)) {
|
|
1578
|
+
return true;
|
|
1058
1579
|
}
|
|
1580
|
+
return false;
|
|
1059
1581
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1582
|
+
/**
|
|
1583
|
+
* CFG must not name-link these across modules: each is a unique `Class::method`
|
|
1584
|
+
* key that still fans out to tens of thousands of invoke sites (singletons,
|
|
1585
|
+
* loggers, constructors) — far denser than RTA reachability on unlimited indexes.
|
|
1586
|
+
*/
|
|
1587
|
+
const ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST = new Set([
|
|
1588
|
+
'getInstance',
|
|
1589
|
+
'constructor',
|
|
1590
|
+
'toString',
|
|
1591
|
+
'valueOf',
|
|
1592
|
+
'showInfo',
|
|
1593
|
+
'showError',
|
|
1594
|
+
'showWarn',
|
|
1595
|
+
'showDebug',
|
|
1596
|
+
'show',
|
|
1597
|
+
'create',
|
|
1598
|
+
'pop',
|
|
1599
|
+
'push',
|
|
1600
|
+
'splice',
|
|
1601
|
+
'isEmpty',
|
|
1602
|
+
'getContext',
|
|
1603
|
+
'copyFrom',
|
|
1604
|
+
// High fan-out helpers seen densifying same-module CFG on scene_board
|
|
1605
|
+
'getGridOccupyStatus',
|
|
1606
|
+
'isInStatusForGrid',
|
|
1607
|
+
'isInStatus',
|
|
1608
|
+
'getStatus',
|
|
1609
|
+
'isOccupied',
|
|
1610
|
+
'isFree',
|
|
1611
|
+
'markStatus',
|
|
1612
|
+
'markGridForCellAndSpan',
|
|
1613
|
+
'createRelativePath',
|
|
1614
|
+
'dealWantParams',
|
|
1615
|
+
'getPreviewContentWidth',
|
|
1616
|
+
'getIconArray',
|
|
1617
|
+
'getSizeX',
|
|
1618
|
+
'getSizeY',
|
|
1619
|
+
]);
|
|
1620
|
+
/**
|
|
1621
|
+
* When ArkAnalyzer leaves a cross-module static call as `%unk/.use()`, recover
|
|
1622
|
+
* `Lib.use` from the invoke's original source. Only PascalCase receivers — so
|
|
1623
|
+
* `obj.foo()` / `this.bar()` stay unresolved rather than bare-method magnets.
|
|
1624
|
+
*
|
|
1625
|
+
* Trust short snippets only. Allow `return` / simple `const|let|var x =` prefixes
|
|
1626
|
+
* (`const x = Lib.use(b)` is the common modular IR original-text shape).
|
|
1627
|
+
*/
|
|
1628
|
+
function recoverClassMethodFromStmt(stmt, methodName) {
|
|
1629
|
+
if (!stmt || !methodName)
|
|
1630
|
+
return null;
|
|
1631
|
+
let text;
|
|
1632
|
+
try {
|
|
1633
|
+
text = stmt.getOriginalText?.();
|
|
1095
1634
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1635
|
+
catch {
|
|
1636
|
+
text = undefined;
|
|
1637
|
+
}
|
|
1638
|
+
if (!text)
|
|
1639
|
+
return null;
|
|
1640
|
+
const trimmed = text.trim();
|
|
1641
|
+
if (trimmed.length === 0 || trimmed.length > 160)
|
|
1642
|
+
return null;
|
|
1643
|
+
// Mock / matcher wrappers are not real call seeds.
|
|
1644
|
+
if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
|
|
1645
|
+
return null;
|
|
1646
|
+
const re = new RegExp(`(?:^|(?:return\\s+)|(?:(?:const|let|var)\\s+[\\w$]+\\s*=\\s*))([A-Z][\\w$]*)\\.${escapeRegExpLiteral(methodName)}\\s*\\(`);
|
|
1647
|
+
const m = trimmed.match(re);
|
|
1648
|
+
return m?.[1] ? `${m[1]}.${methodName}` : null;
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Free-function call left as `%unk/.getOpaque()`: short text still contains
|
|
1652
|
+
* `getOpaque(` (not `Class.getOpaque`). Caller must import-gate + unique name.
|
|
1653
|
+
*/
|
|
1654
|
+
function recoverFreeFunctionFromStmt(stmt, methodName) {
|
|
1655
|
+
if (!stmt || !methodName)
|
|
1656
|
+
return null;
|
|
1657
|
+
if (!/^[a-z][\w$]*$/.test(methodName))
|
|
1658
|
+
return null;
|
|
1659
|
+
let text;
|
|
1660
|
+
try {
|
|
1661
|
+
text = stmt.getOriginalText?.();
|
|
1662
|
+
}
|
|
1663
|
+
catch {
|
|
1664
|
+
text = undefined;
|
|
1665
|
+
}
|
|
1666
|
+
if (!text)
|
|
1667
|
+
return null;
|
|
1668
|
+
const trimmed = text.trim();
|
|
1669
|
+
if (trimmed.length === 0 || trimmed.length > 160)
|
|
1670
|
+
return null;
|
|
1671
|
+
if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
|
|
1672
|
+
return null;
|
|
1673
|
+
// Reject Class.method — that path belongs to recoverClassMethodFromStmt.
|
|
1674
|
+
if (new RegExp(`[A-Z][\\w$]*\\.${escapeRegExpLiteral(methodName)}\\s*\\(`).test(trimmed)) {
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
const re = new RegExp(`(?:^|[^\\w.$])${escapeRegExpLiteral(methodName)}\\s*\\(`);
|
|
1678
|
+
return re.test(trimmed) ? methodName : null;
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Instance invoke whose MethodSignature is `%unk` may still carry a typed base:
|
|
1682
|
+
* ClassType (full sig) or UnclearReferenceType (type name string only — common
|
|
1683
|
+
* when deps are SIGNATURES-only).
|
|
1684
|
+
*/
|
|
1685
|
+
function recoverClassMethodFromInstanceType(invoke, methodName) {
|
|
1686
|
+
if (!invoke || !methodName)
|
|
1687
|
+
return null;
|
|
1688
|
+
try {
|
|
1689
|
+
const base = invoke.getBase?.();
|
|
1690
|
+
if (!base)
|
|
1691
|
+
return null;
|
|
1692
|
+
const type = base.getType?.();
|
|
1693
|
+
if (!type)
|
|
1694
|
+
return null;
|
|
1695
|
+
const fromClassSig = type.getClassSignature?.()?.getClassName?.() ??
|
|
1696
|
+
'';
|
|
1697
|
+
if (isUsableArkTypeName(fromClassSig) && !fromClassSig.includes('%')) {
|
|
1698
|
+
return `${fromClassSig}.${methodName}`;
|
|
1699
|
+
}
|
|
1700
|
+
// UnclearReferenceType: getName() === "Base" (no file path).
|
|
1701
|
+
const fromName = type.getName?.() ??
|
|
1702
|
+
'';
|
|
1703
|
+
if (isUsableArkTypeName(fromName) && !fromName.includes('%') && /^[A-Z]/.test(fromName)) {
|
|
1704
|
+
return `${fromName}.${methodName}`;
|
|
1705
|
+
}
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
catch {
|
|
1709
|
+
return null;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Modular IR often models `Lib.use` as an instance invoke with base Local named
|
|
1714
|
+
* `Lib` and type unknown — recover from the PascalCase base name.
|
|
1715
|
+
*/
|
|
1716
|
+
function recoverClassMethodFromBaseName(invoke, methodName) {
|
|
1717
|
+
if (!invoke || !methodName)
|
|
1718
|
+
return null;
|
|
1719
|
+
try {
|
|
1720
|
+
const base = invoke.getBase?.();
|
|
1721
|
+
if (!base)
|
|
1722
|
+
return null;
|
|
1723
|
+
const baseName = base.getName?.() ?? '';
|
|
1724
|
+
if (!isUsableArkTypeName(baseName) || !/^[A-Z]/.test(baseName) || baseName.includes('%')) {
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
return `${baseName}.${methodName}`;
|
|
1728
|
+
}
|
|
1729
|
+
catch {
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Cross-module `%unk` invoke → `Class.method`, free function name, or
|
|
1735
|
+
* `instantiates` class. Never emit a bare instance-method name alone.
|
|
1736
|
+
*/
|
|
1737
|
+
function callReferenceNameFromSig(sig, opts) {
|
|
1738
|
+
try {
|
|
1739
|
+
const methodName = sig.getMethodSubSignature()?.getMethodName?.() ?? '';
|
|
1740
|
+
if (!methodName || methodName.startsWith('%') || methodName.startsWith('<'))
|
|
1741
|
+
return null;
|
|
1742
|
+
const className = sig.getDeclaringClassSignature()?.getClassName?.() ?? '';
|
|
1743
|
+
if (isUsableArkTypeName(className) && !className.includes('%')) {
|
|
1744
|
+
if (methodName === 'constructor') {
|
|
1745
|
+
return { name: className, kind: 'instantiates' };
|
|
1746
|
+
}
|
|
1747
|
+
if (isJunkArkCallSeed(className, methodName))
|
|
1748
|
+
return null;
|
|
1749
|
+
return { name: `${className}.${methodName}`, kind: 'calls' };
|
|
1750
|
+
}
|
|
1751
|
+
if (methodName === 'constructor')
|
|
1752
|
+
return null;
|
|
1753
|
+
const recovered = recoverClassMethodFromStmt(opts?.stmt, methodName) ??
|
|
1754
|
+
recoverClassMethodFromInstanceType(opts?.invoke, methodName) ??
|
|
1755
|
+
recoverClassMethodFromBaseName(opts?.invoke, methodName);
|
|
1756
|
+
if (recovered) {
|
|
1757
|
+
const dot = recovered.indexOf('.');
|
|
1758
|
+
if (dot > 0 && isJunkArkCallSeed(recovered.slice(0, dot), recovered.slice(dot + 1))) {
|
|
1759
|
+
return null;
|
|
1760
|
+
}
|
|
1761
|
+
return { name: recovered, kind: 'calls' };
|
|
1762
|
+
}
|
|
1763
|
+
const free = recoverFreeFunctionFromStmt(opts?.stmt, methodName);
|
|
1764
|
+
if (free)
|
|
1765
|
+
return { name: free, kind: 'calls', freeFunction: true };
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
catch {
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* True when a ClassSignature still has ArkAnalyzer's placeholder file
|
|
1774
|
+
* (`%unk`) — common on ViewTree stubs after a dependency was evicted before
|
|
1775
|
+
* type inference could rewrite `new LiveCardListView` to a real file path.
|
|
1776
|
+
*/
|
|
1777
|
+
function isUnclearArkClassSignature(sig) {
|
|
1778
|
+
try {
|
|
1779
|
+
const fileSig = sig.getDeclaringFileSignature();
|
|
1780
|
+
const fileName = fileSig.getFileName();
|
|
1781
|
+
const projectName = fileSig.getProjectName();
|
|
1782
|
+
if (fileName === '%unk' || projectName === '%unk')
|
|
1783
|
+
return true;
|
|
1784
|
+
if (fileName.includes('%unk') || projectName.includes('%unk'))
|
|
1785
|
+
return true;
|
|
1786
|
+
return sig.toString().includes('%unk');
|
|
1787
|
+
}
|
|
1788
|
+
catch {
|
|
1789
|
+
return true;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function isUnclearArkMethodSignature(sig) {
|
|
1793
|
+
try {
|
|
1794
|
+
return isUnclearArkClassSignature(sig.getDeclaringClassSignature());
|
|
1795
|
+
}
|
|
1796
|
+
catch {
|
|
1797
|
+
return true;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
/** Among candidate node ids, prefer a single `component` over same-name struct/class. */
|
|
1801
|
+
function pickPreferredClassNodeId(candidateIds, isComponent) {
|
|
1802
|
+
const ids = [...candidateIds];
|
|
1803
|
+
if (ids.length === 0)
|
|
1804
|
+
return null;
|
|
1805
|
+
if (ids.length === 1)
|
|
1806
|
+
return ids[0] ?? null;
|
|
1807
|
+
const components = ids.filter((id) => isComponent(id));
|
|
1808
|
+
if (components.length === 1)
|
|
1809
|
+
return components[0] ?? null;
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
/** Walk ViewTree with cycle protection (shared/cloned nodes can form graphs). */
|
|
1813
|
+
function walkViewTree(node, visit, seen = new Set()) {
|
|
1814
|
+
if (seen.has(node))
|
|
1815
|
+
return;
|
|
1816
|
+
seen.add(node);
|
|
1817
|
+
visit(node);
|
|
1818
|
+
for (const child of node.children) {
|
|
1819
|
+
walkViewTree(child, visit, seen);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function indexViewTreeForClass(ctx, cls, result, relativePath) {
|
|
1823
|
+
if (!cls.hasViewTree())
|
|
1824
|
+
return;
|
|
1825
|
+
const viewTree = cls.getViewTree();
|
|
1826
|
+
if (!viewTree)
|
|
1827
|
+
return;
|
|
1828
|
+
const root = viewTree.getRoot();
|
|
1829
|
+
if (!root)
|
|
1830
|
+
return;
|
|
1831
|
+
const classNodeId = ctx.resolveClassNodeId(cls);
|
|
1832
|
+
if (!classNodeId)
|
|
1833
|
+
return;
|
|
1834
|
+
const buildMethod = cls.getMethods(true).find((m) => m.getName() === 'build');
|
|
1835
|
+
if (!buildMethod)
|
|
1836
|
+
return;
|
|
1837
|
+
const buildId = ctx.ensureMethodNode(buildMethod, relativePath, result, classNodeId);
|
|
1838
|
+
if (!buildId)
|
|
1839
|
+
return;
|
|
1840
|
+
const seen = new Set();
|
|
1841
|
+
const link = (source, target, kind, via, line) => {
|
|
1842
|
+
const key = `${source}>${target}>${kind}>${via}`;
|
|
1843
|
+
if (seen.has(key))
|
|
1844
|
+
return;
|
|
1845
|
+
seen.add(key);
|
|
1846
|
+
ctx.addEdge(result, source, target, kind, relativePath, via, line);
|
|
1847
|
+
};
|
|
1848
|
+
for (const [field] of viewTree.getStateValues()) {
|
|
1849
|
+
const fieldId = ctx.resolveFieldNodeId(field);
|
|
1850
|
+
if (fieldId) {
|
|
1851
|
+
ctx.markFieldStateDecorators?.(field, fieldId, result);
|
|
1852
|
+
const line = field.getOriginFullPosition?.()?.getFirstLine?.() ??
|
|
1853
|
+
buildMethod.getImplOriginFullPosition()?.getFirstLine() ??
|
|
1854
|
+
1;
|
|
1855
|
+
link(fieldId, buildId, 'references', 'state-binding', line);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
walkViewTree(root, (node) => {
|
|
1859
|
+
const sig = node.signature;
|
|
1860
|
+
if (sig) {
|
|
1861
|
+
try {
|
|
1862
|
+
if (isViewTreeClassSignature(sig)) {
|
|
1863
|
+
// Stub or live: prefer already-indexed sig→id (works after dep unload);
|
|
1864
|
+
// %unk stubs also fall back to class name / parent-file import.
|
|
1865
|
+
// Then fall back to live IR object maps when the class is still resident.
|
|
1866
|
+
// Guard getClass — a mis-typed signature must not abort the whole ViewTree walk.
|
|
1867
|
+
let childCls = null;
|
|
1868
|
+
try {
|
|
1869
|
+
childCls = ctx.scene.getClass(sig);
|
|
1870
|
+
}
|
|
1871
|
+
catch {
|
|
1872
|
+
childCls = null;
|
|
1873
|
+
}
|
|
1874
|
+
const childId = ctx.resolveClassIdBySig(sig, cls) ??
|
|
1875
|
+
(childCls ? ctx.resolveClassNodeId(childCls) : null);
|
|
1876
|
+
if (childId) {
|
|
1877
|
+
let line = buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
|
|
1878
|
+
try {
|
|
1879
|
+
const firstAttr = node.attributes?.values()?.next()?.value;
|
|
1880
|
+
if (firstAttr)
|
|
1881
|
+
line = viewTreeLineFromStmt(firstAttr[0]);
|
|
1882
|
+
}
|
|
1883
|
+
catch {
|
|
1884
|
+
// keep build() line
|
|
1885
|
+
}
|
|
1886
|
+
link(buildId, childId, 'references', 'child-component', line);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
else {
|
|
1890
|
+
// MethodSignature: builder stub (sig only) or live ArkMethod.
|
|
1891
|
+
let builderMethod = null;
|
|
1892
|
+
try {
|
|
1893
|
+
builderMethod = ctx.scene.getMethod(sig);
|
|
1894
|
+
}
|
|
1895
|
+
catch {
|
|
1896
|
+
builderMethod = null;
|
|
1897
|
+
}
|
|
1898
|
+
const builderId = ctx.resolveMethodIdBySig(sig, cls) ??
|
|
1899
|
+
(builderMethod
|
|
1900
|
+
? ctx.ensureMethodNode(builderMethod, relativePath, result, classNodeId)
|
|
1901
|
+
: null);
|
|
1116
1902
|
if (builderId) {
|
|
1117
|
-
|
|
1903
|
+
const line = builderMethod
|
|
1904
|
+
? builderMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1
|
|
1905
|
+
: buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
|
|
1906
|
+
link(buildId, builderId, 'references', 'builder', line);
|
|
1118
1907
|
}
|
|
1119
1908
|
}
|
|
1120
1909
|
}
|
|
1910
|
+
catch {
|
|
1911
|
+
// Skip this node's signature edge; keep walking siblings/children.
|
|
1912
|
+
}
|
|
1121
1913
|
}
|
|
1122
1914
|
if (node.stateValuesTransfer) {
|
|
1123
1915
|
for (const [childField, parentValue] of node.stateValuesTransfer) {
|
|
1124
|
-
const childFieldId = ctx.
|
|
1916
|
+
const childFieldId = ctx.resolveFieldNodeId(childField);
|
|
1125
1917
|
if (parentValue instanceof arkanalyzer_1.ArkField) {
|
|
1126
1918
|
const via = stateTransferViaForField(childField);
|
|
1127
1919
|
if (!via || !childFieldId)
|
|
1128
1920
|
continue;
|
|
1129
1921
|
ctx.markFieldStateDecorators?.(childField, childFieldId, result);
|
|
1130
|
-
const parentFieldId = ctx.
|
|
1922
|
+
const parentFieldId = ctx.resolveFieldNodeId(parentValue);
|
|
1131
1923
|
if (parentFieldId) {
|
|
1132
1924
|
ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
|
|
1133
1925
|
link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
|
|
1134
1926
|
}
|
|
1135
1927
|
}
|
|
1136
1928
|
else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
|
|
1137
|
-
|
|
1929
|
+
let builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
|
|
1930
|
+
if (!builderId) {
|
|
1931
|
+
try {
|
|
1932
|
+
builderId = ctx.resolveMethodIdBySig(parentValue.getSignature());
|
|
1933
|
+
}
|
|
1934
|
+
catch {
|
|
1935
|
+
builderId = null;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1138
1938
|
if (childFieldId && builderId) {
|
|
1139
1939
|
link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
|
|
1140
1940
|
}
|
|
@@ -1146,12 +1946,15 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
|
|
|
1146
1946
|
continue;
|
|
1147
1947
|
const line = viewTreeLineFromStmt(stmt);
|
|
1148
1948
|
for (const v of values) {
|
|
1149
|
-
|
|
1949
|
+
const isMethodSig = v instanceof arkanalyzer_1.MethodSignature ||
|
|
1950
|
+
(v != null &&
|
|
1951
|
+
typeof v.getMethodSubSignature === 'function');
|
|
1952
|
+
if (!isMethodSig)
|
|
1150
1953
|
continue;
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1954
|
+
const methodSig = v;
|
|
1955
|
+
const handler = ctx.scene.getMethod(methodSig);
|
|
1956
|
+
const handlerId = ctx.resolveMethodIdBySig(methodSig, cls) ??
|
|
1957
|
+
(handler ? ctx.ensureMethodNode(handler, relativePath, result, classNodeId) : null);
|
|
1155
1958
|
if (handlerId)
|
|
1156
1959
|
link(buildId, handlerId, 'references', attr, line);
|
|
1157
1960
|
}
|
|
@@ -1166,100 +1969,536 @@ class ArkTSAdapter {
|
|
|
1166
1969
|
classToId = new Map();
|
|
1167
1970
|
componentToId = new Map();
|
|
1168
1971
|
fieldToId = new Map();
|
|
1972
|
+
/** Stable across module unload — signature string → HomeGraph node id. */
|
|
1973
|
+
methodSigToId = new Map();
|
|
1974
|
+
classSigToId = new Map();
|
|
1975
|
+
fieldSigToId = new Map();
|
|
1976
|
+
/**
|
|
1977
|
+
* Name → candidate node ids for ViewTree stub stitch when the signature is still
|
|
1978
|
+
* `@%unk/%unk: ClassName` after dependency eviction. Prefer `component` over
|
|
1979
|
+
* same-name struct/class; ambiguous multi-component names use the importer.
|
|
1980
|
+
*/
|
|
1981
|
+
classNameToIds = new Map();
|
|
1982
|
+
classIdFilePath = new Map();
|
|
1983
|
+
classIdIsComponent = new Map();
|
|
1984
|
+
/** `ClassName::methodName` → candidate method node ids (unclear MethodSignature stubs). */
|
|
1985
|
+
methodNameKeyToIds = new Map();
|
|
1986
|
+
/** Bare default-class function name → ids (unique import-gated CFG bridge). */
|
|
1987
|
+
functionNameToIds = new Map();
|
|
1988
|
+
methodIdFilePath = new Map();
|
|
1989
|
+
emittedEdgeKeys = new Set();
|
|
1169
1990
|
nodeIds = new Set();
|
|
1170
1991
|
fileResults = new Map();
|
|
1171
1992
|
crossFileEdges = [];
|
|
1993
|
+
/** Per-module `@dummyMain@…` created during analyseByModule (for scene aggregator). */
|
|
1994
|
+
moduleDummyMains = [];
|
|
1172
1995
|
constructor(rootDir, scannedFiles) {
|
|
1173
1996
|
this.rootDir = rootDir;
|
|
1174
1997
|
this.scanned = new Set(scannedFiles);
|
|
1175
1998
|
}
|
|
1176
1999
|
build(scene) {
|
|
1177
2000
|
this.scene = scene;
|
|
1178
|
-
const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
|
|
1179
2001
|
for (const arkFile of scene.getFiles()) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
2002
|
+
this.indexScannedFile(arkFile);
|
|
2003
|
+
}
|
|
2004
|
+
this.indexViewTreesFromFiles(scene.getFiles());
|
|
2005
|
+
this.finalizeCallGraph(scene);
|
|
2006
|
+
return this.toBatchIndex();
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Index one Harmony module while loaded (baseline = this module):
|
|
2010
|
+
* symbols → rebind resident deps → ViewTree/RTA from this module (B→A via
|
|
2011
|
+
* live IR or sig→id maps for already-indexed unloaded deps).
|
|
2012
|
+
*/
|
|
2013
|
+
indexModule(module, scene) {
|
|
2014
|
+
this.scene = scene;
|
|
2015
|
+
const files = [...module.getFilesMap().values()];
|
|
2016
|
+
const moduleFiles = new Set(files.map((f) => normalizeRelPath(this.rootDir, f.getFilePath())).filter(Boolean));
|
|
2017
|
+
for (const arkFile of files) {
|
|
2018
|
+
this.indexScannedFile(arkFile);
|
|
2019
|
+
}
|
|
2020
|
+
// Rebind still-resident deps so live getClass/getMethod works when topo kept them warm.
|
|
2021
|
+
this.bindLiveMapsFromResident(scene);
|
|
2022
|
+
// Incremental / first-in-topo: populate sig→id maps from other loaded modules
|
|
2023
|
+
// (deps at SIGNATURES) without persisting those files into this batch.
|
|
2024
|
+
this.seedMapsFromOtherLoadedFiles(scene, moduleFiles);
|
|
2025
|
+
this.indexViewTreesFromFiles(files);
|
|
2026
|
+
this.ingestModuleCallGraph(scene, module, files, moduleFiles);
|
|
2027
|
+
// CFG: only narrow `%unk` cross-module bridge (no same-module CFG calls).
|
|
2028
|
+
this.stitchModuleCfgCallsAndRefs(files, moduleFiles);
|
|
2029
|
+
// Drop live IR object keys so ArkAnalyzer can GC unloaded modules; keep sig→id maps.
|
|
2030
|
+
this.releaseLiveIrMaps();
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Index symbols from already-loaded non-target modules into sig/name maps only.
|
|
2034
|
+
* Used so incremental BODIES targets can still resolve cross-module RTA/ViewTree
|
|
2035
|
+
* edges against deps that stay at SIGNATURES (and are not re-persisted).
|
|
2036
|
+
*/
|
|
2037
|
+
seedMapsFromOtherLoadedFiles(scene, excludeFiles) {
|
|
2038
|
+
const seeded = [];
|
|
2039
|
+
for (const mod of scene.getModules()) {
|
|
2040
|
+
let filesMap;
|
|
2041
|
+
try {
|
|
2042
|
+
filesMap = mod.getFilesMap();
|
|
2043
|
+
}
|
|
2044
|
+
catch {
|
|
1184
2045
|
continue;
|
|
1185
|
-
|
|
2046
|
+
}
|
|
2047
|
+
for (const arkFile of filesMap.values()) {
|
|
2048
|
+
const rel = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
2049
|
+
if (!rel || excludeFiles.has(rel) || this.fileResults.has(rel))
|
|
2050
|
+
continue;
|
|
2051
|
+
if (!this.scanned.has(rel) || !isArkAnalyzerSourcePath(rel))
|
|
2052
|
+
continue;
|
|
2053
|
+
this.indexScannedFile(arkFile);
|
|
2054
|
+
if (this.fileResults.has(rel))
|
|
2055
|
+
seeded.push(rel);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
for (const rel of seeded) {
|
|
2059
|
+
this.fileResults.delete(rel);
|
|
1186
2060
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
2061
|
+
}
|
|
2062
|
+
bindLiveMapsFromResident(scene) {
|
|
2063
|
+
const cache = scene.getModuleCache();
|
|
2064
|
+
if (!cache)
|
|
2065
|
+
return;
|
|
2066
|
+
const files = [];
|
|
2067
|
+
for (const id of cache.getLoadedModules()) {
|
|
2068
|
+
const mod = scene.getModule(id);
|
|
2069
|
+
if (!mod || mod.getModuleType() === arkanalyzer_1.ModuleType.SDK)
|
|
2070
|
+
continue;
|
|
2071
|
+
if (mod.getLoadState() < arkanalyzer_1.ModuleLoadState.SIGNATURES)
|
|
1189
2072
|
continue;
|
|
1190
|
-
|
|
2073
|
+
files.push(...mod.getFilesMap().values());
|
|
2074
|
+
}
|
|
2075
|
+
if (files.length > 0)
|
|
2076
|
+
this.bindLiveMapsFromFiles(files);
|
|
2077
|
+
}
|
|
2078
|
+
/** Re-attach live Ark* objects to already-indexed HomeGraph node ids (sig maps). */
|
|
2079
|
+
bindLiveMapsFromFiles(files) {
|
|
2080
|
+
for (const arkFile of files) {
|
|
1191
2081
|
const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
1192
2082
|
if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
|
|
1193
2083
|
continue;
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
2084
|
+
this.bindLiveClass(arkFile.getDefaultClass());
|
|
2085
|
+
for (const cls of arkFile.getClasses()) {
|
|
2086
|
+
this.bindLiveClass(cls);
|
|
2087
|
+
}
|
|
2088
|
+
for (const ns of arkFile.getNamespaces()) {
|
|
2089
|
+
this.bindLiveNamespace(ns);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
bindLiveNamespace(ns) {
|
|
2094
|
+
this.bindLiveClass(ns.getDefaultClass());
|
|
2095
|
+
for (const cls of ns.getClasses()) {
|
|
2096
|
+
this.bindLiveClass(cls);
|
|
2097
|
+
}
|
|
2098
|
+
for (const child of ns.getNamespaces()) {
|
|
2099
|
+
this.bindLiveNamespace(child);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
bindLiveClass(cls) {
|
|
2103
|
+
try {
|
|
2104
|
+
const classSig = cls.getSignature().toString();
|
|
2105
|
+
const classId = this.classSigToId.get(classSig);
|
|
2106
|
+
if (classId) {
|
|
2107
|
+
this.classToId.set(cls, classId);
|
|
2108
|
+
if (cls.hasComponentDecorator()) {
|
|
2109
|
+
this.componentToId.set(cls, classId);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
for (const method of cls.getMethods(true)) {
|
|
2113
|
+
try {
|
|
2114
|
+
const mid = this.methodSigToId.get(method.getSignature().toString());
|
|
2115
|
+
if (mid)
|
|
2116
|
+
this.methodToId.set(method, mid);
|
|
2117
|
+
}
|
|
2118
|
+
catch {
|
|
2119
|
+
// skip
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
for (const field of cls.getFields()) {
|
|
2123
|
+
const fid = this.fieldSigToId.get(fieldSigKey(classSig, field.getName()));
|
|
2124
|
+
if (fid)
|
|
2125
|
+
this.fieldToId.set(field, fid);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
catch {
|
|
2129
|
+
// signature unavailable
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
finalizeCallGraph(scene) {
|
|
2133
|
+
this.scene = scene;
|
|
2134
|
+
// Modular path: intra-module RTA already ran per module. Do NOT run a
|
|
2135
|
+
// scene aggregator RTA — that re-walks overlapping reachable sets and was
|
|
2136
|
+
// a major wall-clock cost. Cross-module gaps link only via exact
|
|
2137
|
+
// signature / unique Class.method maps (no unresolved name-match seeds).
|
|
2138
|
+
if (this.moduleDummyMains.length > 0) {
|
|
1197
2139
|
try {
|
|
1198
|
-
|
|
2140
|
+
for (const m of this.moduleDummyMains) {
|
|
2141
|
+
this.indexArkanalyzerDummyMain(m);
|
|
2142
|
+
}
|
|
1199
2143
|
}
|
|
1200
|
-
catch
|
|
1201
|
-
|
|
1202
|
-
result.errors.push({
|
|
1203
|
-
message: `ViewTree indexing skipped for ${cls.getName()}: ${message}`,
|
|
1204
|
-
filePath: relativePath,
|
|
1205
|
-
severity: 'warning',
|
|
1206
|
-
});
|
|
2144
|
+
catch {
|
|
2145
|
+
// ignore
|
|
1207
2146
|
}
|
|
2147
|
+
return;
|
|
1208
2148
|
}
|
|
2149
|
+
const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
|
|
1209
2150
|
if (dummyMain) {
|
|
1210
2151
|
this.indexArkanalyzerDummyMain(dummyMain);
|
|
1211
2152
|
}
|
|
2153
|
+
if (entryPoints.length === 0)
|
|
2154
|
+
return;
|
|
1212
2155
|
let callGraph;
|
|
1213
2156
|
try {
|
|
1214
|
-
callGraph =
|
|
1215
|
-
entryPoints.length > 0 ? scene.makeCallGraphRTA(entryPoints) : null;
|
|
2157
|
+
callGraph = scene.makeCallGraphRTA(entryPoints);
|
|
1216
2158
|
}
|
|
1217
2159
|
catch {
|
|
1218
|
-
|
|
2160
|
+
return;
|
|
1219
2161
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
2162
|
+
this.ingestCallGraph(callGraph, null);
|
|
2163
|
+
}
|
|
2164
|
+
ingestModuleCallGraph(scene, module, files, moduleFiles) {
|
|
2165
|
+
let entryPoints = [];
|
|
2166
|
+
let dummyMain = null;
|
|
2167
|
+
try {
|
|
2168
|
+
// Prefer AA's forModule when present; otherwise DIY with the public ctor
|
|
2169
|
+
// (module-scoped name + this module's classes).
|
|
2170
|
+
const forModule = arkanalyzer_1.DummyMainCreater.forModule;
|
|
2171
|
+
let creater;
|
|
2172
|
+
if (typeof forModule === 'function') {
|
|
2173
|
+
creater = forModule(scene, module, true);
|
|
2174
|
+
}
|
|
2175
|
+
else {
|
|
2176
|
+
const classScope = collectComponentScopeFromFiles(files);
|
|
2177
|
+
const moduleName = module.getModuleName() || 'module';
|
|
2178
|
+
creater = new arkanalyzer_1.DummyMainCreater(scene, `@dummyMain@${moduleName}`, classScope.length > 0 ? classScope : undefined, true);
|
|
2179
|
+
}
|
|
2180
|
+
creater.createDummyMain();
|
|
2181
|
+
dummyMain = creater.getDummyMain();
|
|
2182
|
+
if (dummyMain) {
|
|
2183
|
+
entryPoints = [dummyMain.getSignature()];
|
|
2184
|
+
this.moduleDummyMains.push(dummyMain);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
catch {
|
|
2188
|
+
// Fall back to file-scoped DummyMain (components only) if creation fails.
|
|
2189
|
+
const resolved = resolveRtaEntryPointsFromFiles(scene, files);
|
|
2190
|
+
entryPoints = resolved.entryPoints;
|
|
2191
|
+
dummyMain = resolved.dummyMain;
|
|
2192
|
+
}
|
|
2193
|
+
if (dummyMain) {
|
|
2194
|
+
this.indexArkanalyzerDummyMain(dummyMain);
|
|
2195
|
+
}
|
|
2196
|
+
// Export surface = cross-module call-ins; seed them as RTA entries so
|
|
2197
|
+
// non-UI libraries (Manager.getInstance → …) grow intra-module edges
|
|
2198
|
+
// without waiting for a Component DummyMain to reach them.
|
|
2199
|
+
entryPoints = mergeRtaEntryPoints(entryPoints, collectExportedRtaEntryPointsFromFiles(files));
|
|
2200
|
+
if (entryPoints.length === 0)
|
|
2201
|
+
return;
|
|
2202
|
+
let callGraph;
|
|
2203
|
+
try {
|
|
2204
|
+
callGraph = scene.makeCallGraphRTA(entryPoints);
|
|
2205
|
+
}
|
|
2206
|
+
catch {
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
this.ingestCallGraph(callGraph, moduleFiles);
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Keep ArkAnalyzer RTA edges whose **caller** is in the current module.
|
|
2213
|
+
*
|
|
2214
|
+
* - Same-module: live IR id, exact MethodSignature map, or unique Class.method
|
|
2215
|
+
* name fallback (path-drift).
|
|
2216
|
+
* - Cross-module: **exact MethodSignature string or live callee only** — no
|
|
2217
|
+
* Class.method name fallback (that + CFG densified scene_board to ~400k
|
|
2218
|
+
* ark calls vs ~76k on an unlimited RTA index).
|
|
2219
|
+
*/
|
|
2220
|
+
ingestCallGraph(callGraph, moduleFiles) {
|
|
2221
|
+
for (const edge of callGraph.getCallEdges()) {
|
|
2222
|
+
const callerArk = callGraph.getArkMethodByFuncID(edge.getSrcID());
|
|
2223
|
+
const calleeArk = callGraph.getArkMethodByFuncID(edge.getDstID());
|
|
2224
|
+
const callerSig = callGraph.getMethodByFuncID(edge.getSrcID());
|
|
2225
|
+
const calleeSig = callGraph.getMethodByFuncID(edge.getDstID());
|
|
2226
|
+
if (callerArk &&
|
|
2227
|
+
shouldSkipArkMethod(callerArk) &&
|
|
2228
|
+
!isAnonymousArkMethod(callerArk) &&
|
|
2229
|
+
!callerArk.getDeclaringArkClass().isDefaultArkClass() &&
|
|
2230
|
+
!callerArk.isGenerated()) {
|
|
2231
|
+
continue;
|
|
2232
|
+
}
|
|
2233
|
+
const callerFile = (callerArk ? relPathForArkFile(this.rootDir, callerArk.getDeclaringArkFile()) : null) ??
|
|
2234
|
+
(callerSig ? this.relPathFromMethodSignature(callerSig) : null) ??
|
|
2235
|
+
'';
|
|
2236
|
+
if (moduleFiles && callerFile && !moduleFiles.has(callerFile)) {
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
const calleeFile = (calleeArk ? relPathForArkFile(this.rootDir, calleeArk.getDeclaringArkFile()) : null) ??
|
|
2240
|
+
(calleeSig ? this.relPathFromMethodSignature(calleeSig) : null);
|
|
2241
|
+
const callerId = (callerArk ? this.resolveMethodNodeId(callerArk) : null) ??
|
|
2242
|
+
(callerSig ? this.resolveMethodIdBySigExact(callerSig) : null);
|
|
2243
|
+
if (!callerId)
|
|
2244
|
+
continue;
|
|
2245
|
+
const sameModule = !!moduleFiles && !!calleeFile && moduleFiles.has(calleeFile);
|
|
2246
|
+
const dispatch = edge.hasDirectCall() ? 'direct' : 'indirect';
|
|
2247
|
+
// Full-scene path (no moduleFiles): keep prior resolveMethodIdBySig behavior.
|
|
2248
|
+
if (!moduleFiles) {
|
|
2249
|
+
const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
|
|
2250
|
+
(calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
|
|
2251
|
+
if (!calleeId)
|
|
1235
2252
|
continue;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
: undefined,
|
|
1245
|
-
},
|
|
1246
|
-
});
|
|
1247
|
-
const callerResult = this.fileResults.get(callerFile);
|
|
1248
|
-
if (callerResult && this.nodeInFile(calleeId, callerFile)) {
|
|
1249
|
-
callerResult.edges.push(callEdge);
|
|
2253
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
if (!sameModule) {
|
|
2257
|
+
const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
|
|
2258
|
+
(calleeSig ? this.resolveMethodIdBySigExact(calleeSig) : null);
|
|
2259
|
+
if (calleeId) {
|
|
2260
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
|
|
1250
2261
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
|
|
2265
|
+
(calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
|
|
2266
|
+
if (!calleeId)
|
|
2267
|
+
continue;
|
|
2268
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* CFG call stitch (modular): no same-module CFG call edges (those were the
|
|
2273
|
+
* ~150k `direct` flood past unlimited RTA). Only the narrow cross-module
|
|
2274
|
+
* `%unk` bridge for `return Lib.use(…)`-style static calls.
|
|
2275
|
+
*/
|
|
2276
|
+
stitchModuleCfgCallsAndRefs(files, _moduleFiles) {
|
|
2277
|
+
for (const arkFile of files) {
|
|
2278
|
+
const callerFile = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
2279
|
+
const visitClass = (cls) => {
|
|
2280
|
+
for (const method of cls.getMethods(true)) {
|
|
2281
|
+
if (shouldSkipArkMethod(method) &&
|
|
2282
|
+
!isAnonymousArkMethod(method) &&
|
|
2283
|
+
!method.isGenerated()) {
|
|
2284
|
+
continue;
|
|
2285
|
+
}
|
|
2286
|
+
const callerId = this.resolveMethodNodeId(method) ??
|
|
2287
|
+
(() => {
|
|
2288
|
+
try {
|
|
2289
|
+
return this.methodSigToId.get(method.getSignature().toString()) ?? null;
|
|
2290
|
+
}
|
|
2291
|
+
catch {
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
})();
|
|
2295
|
+
if (!callerId)
|
|
2296
|
+
continue;
|
|
2297
|
+
let cfg;
|
|
2298
|
+
try {
|
|
2299
|
+
cfg = method.getCfg();
|
|
2300
|
+
}
|
|
2301
|
+
catch {
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
if (!cfg)
|
|
2305
|
+
continue;
|
|
2306
|
+
for (const stmt of cfg.getStmts()) {
|
|
2307
|
+
let invoke;
|
|
2308
|
+
try {
|
|
2309
|
+
invoke = stmt.getInvokeExpr();
|
|
2310
|
+
}
|
|
2311
|
+
catch {
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
if (!invoke)
|
|
2315
|
+
continue;
|
|
2316
|
+
let calleeSig;
|
|
2317
|
+
try {
|
|
2318
|
+
calleeSig = invoke.getMethodSignature();
|
|
2319
|
+
}
|
|
2320
|
+
catch {
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
if (!calleeSig)
|
|
2324
|
+
continue;
|
|
2325
|
+
// Skip clear same-module paths — RTA owns those. Only `%unk` /
|
|
2326
|
+
// missing-path invokes may take the narrow cross-module bridge.
|
|
2327
|
+
const calleeFile = this.relPathFromMethodSignature(calleeSig);
|
|
2328
|
+
if (calleeFile)
|
|
2329
|
+
continue;
|
|
2330
|
+
this.tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, method, stmt, invoke, arkFile);
|
|
2331
|
+
}
|
|
1253
2332
|
}
|
|
2333
|
+
};
|
|
2334
|
+
visitClass(arkFile.getDefaultClass());
|
|
2335
|
+
for (const cls of arkFile.getClasses())
|
|
2336
|
+
visitClass(cls);
|
|
2337
|
+
const walkNs = (ns) => {
|
|
2338
|
+
visitClass(ns.getDefaultClass());
|
|
2339
|
+
for (const cls of ns.getClasses())
|
|
2340
|
+
visitClass(cls);
|
|
2341
|
+
for (const child of ns.getNamespaces())
|
|
2342
|
+
walkNs(child);
|
|
2343
|
+
};
|
|
2344
|
+
for (const ns of arkFile.getNamespaces())
|
|
2345
|
+
walkNs(ns);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk) {
|
|
2349
|
+
const edgeKey = `${callerId}\0${calleeId}\0calls\0${dispatch}`;
|
|
2350
|
+
if (this.emittedEdgeKeys.has(edgeKey))
|
|
2351
|
+
return;
|
|
2352
|
+
if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0direct`))
|
|
2353
|
+
return;
|
|
2354
|
+
if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0indirect`))
|
|
2355
|
+
return;
|
|
2356
|
+
this.emittedEdgeKeys.add(edgeKey);
|
|
2357
|
+
const callEdge = arkEdge(callerId, calleeId, 'calls', {
|
|
2358
|
+
metadata: {
|
|
2359
|
+
synthesizedBy: 'arkanalyzer',
|
|
2360
|
+
dispatch,
|
|
2361
|
+
sourceFile: callerFile || undefined,
|
|
2362
|
+
rtaEntry: callerArk?.isGenerated?.() && callerArk.getName() === '@dummyMain'
|
|
2363
|
+
? '@dummyMain'
|
|
2364
|
+
: undefined,
|
|
2365
|
+
},
|
|
2366
|
+
});
|
|
2367
|
+
const callerResult = callerFile ? this.fileResults.get(callerFile) : undefined;
|
|
2368
|
+
if (callerResult && this.nodeInFile(calleeId, callerFile)) {
|
|
2369
|
+
callerResult.edges.push(callEdge);
|
|
2370
|
+
}
|
|
2371
|
+
else {
|
|
2372
|
+
this.crossFileEdges.push(callEdge);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* CFG `%unk` bridge: recover Class.method / free function from invoke crumbs
|
|
2377
|
+
* (stmt text, UnclearReferenceType name, PascalCase base local), require
|
|
2378
|
+
* import + unique indexed target, skip fan-out blocklist. No unresolved refs.
|
|
2379
|
+
*/
|
|
2380
|
+
tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, callerArk, stmt, invoke, arkFile) {
|
|
2381
|
+
// Only for unclear / %unk signatures — clear typed cross-module invokes
|
|
2382
|
+
// stay RTA-only so we do not densify past unlimited RTA.
|
|
2383
|
+
if (!isUnclearArkMethodSignature(calleeSig)) {
|
|
2384
|
+
try {
|
|
2385
|
+
const className = calleeSig.getDeclaringClassSignature()?.getClassName?.() ?? '';
|
|
2386
|
+
if (isUsableArkTypeName(className) && !className.includes('%'))
|
|
2387
|
+
return;
|
|
1254
2388
|
}
|
|
2389
|
+
catch {
|
|
2390
|
+
// treat as unclear
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
const parsed = callReferenceNameFromSig(calleeSig, { stmt, invoke });
|
|
2394
|
+
if (!parsed || parsed.kind !== 'calls')
|
|
2395
|
+
return;
|
|
2396
|
+
if (parsed.freeFunction) {
|
|
2397
|
+
const fnName = parsed.name;
|
|
2398
|
+
if (!fnName || ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(fnName))
|
|
2399
|
+
return;
|
|
2400
|
+
if (!this.fileImportsLocalName(arkFile, fnName))
|
|
2401
|
+
return;
|
|
2402
|
+
const calleeId = this.resolveFunctionIdByName(fnName);
|
|
2403
|
+
if (!calleeId)
|
|
2404
|
+
return;
|
|
2405
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
const dot = parsed.name.indexOf('.');
|
|
2409
|
+
if (dot <= 0)
|
|
2410
|
+
return;
|
|
2411
|
+
const className = parsed.name.slice(0, dot);
|
|
2412
|
+
const methodName = parsed.name.slice(dot + 1);
|
|
2413
|
+
if (!className || !methodName)
|
|
2414
|
+
return;
|
|
2415
|
+
if (ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(methodName))
|
|
2416
|
+
return;
|
|
2417
|
+
if (isJunkArkCallSeed(className, methodName))
|
|
2418
|
+
return;
|
|
2419
|
+
if (!this.fileImportsLocalName(arkFile, className))
|
|
2420
|
+
return;
|
|
2421
|
+
const calleeId = this.resolveMethodIdByName(className, methodName);
|
|
2422
|
+
if (!calleeId)
|
|
2423
|
+
return;
|
|
2424
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
|
|
2425
|
+
}
|
|
2426
|
+
fileImportsLocalName(arkFile, localName) {
|
|
2427
|
+
try {
|
|
2428
|
+
return !!arkFile.getImportInfoBy(localName);
|
|
1255
2429
|
}
|
|
2430
|
+
catch {
|
|
2431
|
+
return false;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
/** Exact MethodSignature.toString() → node id (no Class.method name fallback). */
|
|
2435
|
+
resolveMethodIdBySigExact(sig) {
|
|
2436
|
+
try {
|
|
2437
|
+
return this.methodSigToId.get(sig.toString()) ?? null;
|
|
2438
|
+
}
|
|
2439
|
+
catch {
|
|
2440
|
+
return null;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
/** Project-relative path from a MethodSignature's declaring file (may be %unk). */
|
|
2444
|
+
relPathFromMethodSignature(sig) {
|
|
2445
|
+
try {
|
|
2446
|
+
const fileName = sig.getDeclaringClassSignature().getDeclaringFileSignature().getFileName();
|
|
2447
|
+
if (!fileName || fileName === '%unk' || fileName.includes('%unk'))
|
|
2448
|
+
return null;
|
|
2449
|
+
return normalizeRelPath(this.rootDir, fileName);
|
|
2450
|
+
}
|
|
2451
|
+
catch {
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
releaseLiveIrMaps() {
|
|
2456
|
+
this.methodToId.clear();
|
|
2457
|
+
this.classToId.clear();
|
|
2458
|
+
this.componentToId.clear();
|
|
2459
|
+
this.fieldToId.clear();
|
|
2460
|
+
}
|
|
2461
|
+
toBatchIndex(errors = []) {
|
|
1256
2462
|
return {
|
|
1257
2463
|
fileResults: this.fileResults,
|
|
1258
2464
|
crossFileEdges: this.crossFileEdges,
|
|
1259
2465
|
nodeIds: this.nodeIds,
|
|
1260
|
-
errors: [],
|
|
2466
|
+
errors: [...errors],
|
|
1261
2467
|
};
|
|
1262
2468
|
}
|
|
2469
|
+
indexScannedFile(arkFile) {
|
|
2470
|
+
const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
2471
|
+
if (!this.scanned.has(relativePath))
|
|
2472
|
+
return;
|
|
2473
|
+
if (!isArkAnalyzerSourcePath(relativePath))
|
|
2474
|
+
return;
|
|
2475
|
+
this.indexFile(arkFile);
|
|
2476
|
+
}
|
|
2477
|
+
indexViewTreesFromFiles(files) {
|
|
2478
|
+
for (const arkFile of files) {
|
|
2479
|
+
const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
2480
|
+
if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
|
|
2481
|
+
continue;
|
|
2482
|
+
const result = this.fileResults.get(relativePath);
|
|
2483
|
+
if (!result)
|
|
2484
|
+
continue;
|
|
2485
|
+
for (const cls of arkFile.getClasses()) {
|
|
2486
|
+
if (!cls.hasViewTree())
|
|
2487
|
+
continue;
|
|
2488
|
+
try {
|
|
2489
|
+
indexViewTreeForClass(this.viewTreeContext(), cls, result, relativePath);
|
|
2490
|
+
}
|
|
2491
|
+
catch (e) {
|
|
2492
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
2493
|
+
result.errors.push({
|
|
2494
|
+
message: `ViewTree indexing skipped for ${cls.getName()}: ${message}`,
|
|
2495
|
+
filePath: relativePath,
|
|
2496
|
+
severity: 'warning',
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
1263
2502
|
/** Index ArkAnalyzer's in-scene @dummyMain (DummyMainCreater output), not a HomeGraph synthetic node. */
|
|
1264
2503
|
indexArkanalyzerDummyMain(dummyMain) {
|
|
1265
2504
|
const result = this.ensureFileResult(ARKANALYZER_DUMMY_FILE, 1);
|
|
@@ -1414,6 +2653,13 @@ class ArkTSAdapter {
|
|
|
1414
2653
|
});
|
|
1415
2654
|
this.addNode(result, classNode);
|
|
1416
2655
|
this.classToId.set(cls, classNode.id);
|
|
2656
|
+
try {
|
|
2657
|
+
this.classSigToId.set(cls.getSignature().toString(), classNode.id);
|
|
2658
|
+
}
|
|
2659
|
+
catch {
|
|
2660
|
+
// signature unavailable
|
|
2661
|
+
}
|
|
2662
|
+
this.rememberClassName(displayName, classNode.id, relativePath, false);
|
|
1417
2663
|
result.edges.push(arkEdge(parentId, classNode.id, 'contains'));
|
|
1418
2664
|
this.indexDecorators(result, classNode.id, cls, relativePath, language, line);
|
|
1419
2665
|
if (cls.isAnonymousClass()) {
|
|
@@ -1434,6 +2680,13 @@ class ArkTSAdapter {
|
|
|
1434
2680
|
const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, { ...modelModifiersToNodeExtras(cls) });
|
|
1435
2681
|
this.addNode(result, componentNode);
|
|
1436
2682
|
this.componentToId.set(cls, componentNode.id);
|
|
2683
|
+
try {
|
|
2684
|
+
this.classSigToId.set(cls.getSignature().toString(), componentNode.id);
|
|
2685
|
+
}
|
|
2686
|
+
catch {
|
|
2687
|
+
// signature unavailable
|
|
2688
|
+
}
|
|
2689
|
+
this.rememberClassName(displayName, componentNode.id, relativePath, true);
|
|
1437
2690
|
result.edges.push(arkEdge(classNode.id, componentNode.id, 'contains'));
|
|
1438
2691
|
}
|
|
1439
2692
|
const superName = cls.getSuperClassName();
|
|
@@ -1518,6 +2771,12 @@ class ArkTSAdapter {
|
|
|
1518
2771
|
});
|
|
1519
2772
|
this.addNode(result, fieldNode);
|
|
1520
2773
|
this.fieldToId.set(field, fieldNode.id);
|
|
2774
|
+
try {
|
|
2775
|
+
this.fieldSigToId.set(fieldSigKey(cls.getSignature().toString(), name), fieldNode.id);
|
|
2776
|
+
}
|
|
2777
|
+
catch {
|
|
2778
|
+
// signature unavailable
|
|
2779
|
+
}
|
|
1521
2780
|
result.edges.push(arkEdge(parentId, fieldNode.id, 'contains'));
|
|
1522
2781
|
this.indexDecorators(result, fieldNode.id, field, relativePath, language, line);
|
|
1523
2782
|
}
|
|
@@ -1536,6 +2795,9 @@ class ArkTSAdapter {
|
|
|
1536
2795
|
addEdge: (result, source, target, kind, callerFile, via, line) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line),
|
|
1537
2796
|
ensureMethodNode: (method, relativePath, result, parentId) => this.ensureMethodNode(method, relativePath, result, parentId),
|
|
1538
2797
|
resolveClassNodeId: (cls) => this.resolveClassNodeId(cls),
|
|
2798
|
+
resolveClassIdBySig: (sig, importer) => this.resolveClassIdBySig(sig, importer),
|
|
2799
|
+
resolveMethodIdBySig: (sig, importer) => this.resolveMethodIdBySig(sig, importer),
|
|
2800
|
+
resolveFieldNodeId: (field) => this.resolveFieldNodeId(field),
|
|
1539
2801
|
markFieldStateDecorators: (field, fieldNodeId, result) => {
|
|
1540
2802
|
const kinds = stateDecoratorKinds(field);
|
|
1541
2803
|
if (kinds.length === 0)
|
|
@@ -1547,7 +2809,170 @@ class ArkTSAdapter {
|
|
|
1547
2809
|
};
|
|
1548
2810
|
}
|
|
1549
2811
|
resolveClassNodeId(cls) {
|
|
1550
|
-
|
|
2812
|
+
const live = this.componentToId.get(cls) ?? this.classToId.get(cls);
|
|
2813
|
+
if (live)
|
|
2814
|
+
return live;
|
|
2815
|
+
try {
|
|
2816
|
+
return this.classSigToId.get(cls.getSignature().toString()) ?? null;
|
|
2817
|
+
}
|
|
2818
|
+
catch {
|
|
2819
|
+
return null;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
rememberClassName(name, nodeId, filePath, isComponent) {
|
|
2823
|
+
if (!isUsableArkTypeName(name))
|
|
2824
|
+
return;
|
|
2825
|
+
let set = this.classNameToIds.get(name);
|
|
2826
|
+
if (!set) {
|
|
2827
|
+
set = new Set();
|
|
2828
|
+
this.classNameToIds.set(name, set);
|
|
2829
|
+
}
|
|
2830
|
+
set.add(nodeId);
|
|
2831
|
+
this.classIdFilePath.set(nodeId, filePath);
|
|
2832
|
+
if (isComponent)
|
|
2833
|
+
this.classIdIsComponent.set(nodeId, true);
|
|
2834
|
+
}
|
|
2835
|
+
rememberMethodName(className, methodName, nodeId, filePath) {
|
|
2836
|
+
const key = `${className}::${methodName}`;
|
|
2837
|
+
let set = this.methodNameKeyToIds.get(key);
|
|
2838
|
+
if (!set) {
|
|
2839
|
+
set = new Set();
|
|
2840
|
+
this.methodNameKeyToIds.set(key, set);
|
|
2841
|
+
}
|
|
2842
|
+
set.add(nodeId);
|
|
2843
|
+
this.methodIdFilePath.set(nodeId, filePath);
|
|
2844
|
+
}
|
|
2845
|
+
rememberFunctionName(functionName, nodeId, filePath) {
|
|
2846
|
+
if (!isUsableArkTypeName(functionName) || !/^[a-z]/.test(functionName))
|
|
2847
|
+
return;
|
|
2848
|
+
let set = this.functionNameToIds.get(functionName);
|
|
2849
|
+
if (!set) {
|
|
2850
|
+
set = new Set();
|
|
2851
|
+
this.functionNameToIds.set(functionName, set);
|
|
2852
|
+
}
|
|
2853
|
+
set.add(nodeId);
|
|
2854
|
+
this.methodIdFilePath.set(nodeId, filePath);
|
|
2855
|
+
}
|
|
2856
|
+
resolveFunctionIdByName(functionName) {
|
|
2857
|
+
const ids = this.functionNameToIds.get(functionName);
|
|
2858
|
+
if (!ids || ids.size !== 1)
|
|
2859
|
+
return null;
|
|
2860
|
+
return [...ids][0] ?? null;
|
|
2861
|
+
}
|
|
2862
|
+
resolveClassIdByName(className, importer) {
|
|
2863
|
+
const ids = this.classNameToIds.get(className);
|
|
2864
|
+
if (!ids || ids.size === 0)
|
|
2865
|
+
return null;
|
|
2866
|
+
let candidates = [...ids];
|
|
2867
|
+
// Same-file components (ForEach item builders, local @Component) have no import —
|
|
2868
|
+
// prefer ids from the importer's file before treating the name as ambiguous.
|
|
2869
|
+
if (importer) {
|
|
2870
|
+
try {
|
|
2871
|
+
const importerPath = normalizeRelPath(this.rootDir, importer.getDeclaringArkFile().getFilePath());
|
|
2872
|
+
const sameFile = candidates.filter((id) => this.classIdFilePath.get(id) === importerPath);
|
|
2873
|
+
if (sameFile.length > 0)
|
|
2874
|
+
candidates = sameFile;
|
|
2875
|
+
}
|
|
2876
|
+
catch {
|
|
2877
|
+
// keep full candidate set
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
const preferred = pickPreferredClassNodeId(candidates, (id) => !!this.classIdIsComponent.get(id));
|
|
2881
|
+
if (preferred)
|
|
2882
|
+
return preferred;
|
|
2883
|
+
if (!importer)
|
|
2884
|
+
return null;
|
|
2885
|
+
try {
|
|
2886
|
+
const importInfo = importer.getDeclaringArkFile().getImportInfoBy(className);
|
|
2887
|
+
if (!importInfo)
|
|
2888
|
+
return null;
|
|
2889
|
+
const exportInfo = importInfo.getLazyExportInfo?.() ?? null;
|
|
2890
|
+
const arkExport = exportInfo?.getArkExport?.() ?? null;
|
|
2891
|
+
if (arkExport instanceof arkanalyzer_1.ArkClass) {
|
|
2892
|
+
const byLive = this.resolveClassNodeId(arkExport);
|
|
2893
|
+
if (byLive)
|
|
2894
|
+
return byLive;
|
|
2895
|
+
}
|
|
2896
|
+
const from = importInfo.getFrom?.();
|
|
2897
|
+
if (!from)
|
|
2898
|
+
return null;
|
|
2899
|
+
const fromBase = path.basename(from).replace(/\.(ets|ts|d\.ts)$/i, '');
|
|
2900
|
+
const byPath = [...ids].filter((id) => {
|
|
2901
|
+
const fp = this.classIdFilePath.get(id);
|
|
2902
|
+
if (!fp)
|
|
2903
|
+
return false;
|
|
2904
|
+
const norm = fp.replace(/\\/g, '/');
|
|
2905
|
+
const fromNorm = from.replace(/\\/g, '/');
|
|
2906
|
+
return (norm.includes(fromNorm) ||
|
|
2907
|
+
fromNorm.includes(norm) ||
|
|
2908
|
+
path.basename(norm, path.extname(norm)) === fromBase ||
|
|
2909
|
+
path.basename(norm, path.extname(norm)) === className);
|
|
2910
|
+
});
|
|
2911
|
+
return pickPreferredClassNodeId(byPath, (id) => !!this.classIdIsComponent.get(id));
|
|
2912
|
+
}
|
|
2913
|
+
catch {
|
|
2914
|
+
return null;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
resolveMethodIdByName(className, methodName) {
|
|
2918
|
+
const key = `${className}::${methodName}`;
|
|
2919
|
+
const ids = this.methodNameKeyToIds.get(key);
|
|
2920
|
+
if (!ids || ids.size !== 1)
|
|
2921
|
+
return null;
|
|
2922
|
+
return [...ids][0] ?? null;
|
|
2923
|
+
}
|
|
2924
|
+
resolveClassIdBySig(sig, importer) {
|
|
2925
|
+
try {
|
|
2926
|
+
const exact = this.classSigToId.get(sig.toString());
|
|
2927
|
+
if (exact)
|
|
2928
|
+
return exact;
|
|
2929
|
+
}
|
|
2930
|
+
catch {
|
|
2931
|
+
// fall through to name fallback when possible
|
|
2932
|
+
}
|
|
2933
|
+
// Name fallback for %unk stubs AND clear-but-unmapped sig strings (path drift).
|
|
2934
|
+
// Same-file / import disambiguation happens inside resolveClassIdByName.
|
|
2935
|
+
try {
|
|
2936
|
+
const className = sig.getClassName();
|
|
2937
|
+
return className ? this.resolveClassIdByName(className, importer) : null;
|
|
2938
|
+
}
|
|
2939
|
+
catch {
|
|
2940
|
+
return null;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
resolveMethodIdBySig(sig, _importer) {
|
|
2944
|
+
try {
|
|
2945
|
+
const exact = this.methodSigToId.get(sig.toString());
|
|
2946
|
+
if (exact)
|
|
2947
|
+
return exact;
|
|
2948
|
+
}
|
|
2949
|
+
catch {
|
|
2950
|
+
// fall through
|
|
2951
|
+
}
|
|
2952
|
+
// Name fallback for %unk stubs AND path-drifted but clear signatures so
|
|
2953
|
+
// RTA edges to signature-only / unloaded callees still map to indexed nodes.
|
|
2954
|
+
try {
|
|
2955
|
+
const className = sig.getDeclaringClassSignature().getClassName();
|
|
2956
|
+
const methodName = sig.getMethodSubSignature().getMethodName();
|
|
2957
|
+
if (!className || !methodName)
|
|
2958
|
+
return null;
|
|
2959
|
+
return this.resolveMethodIdByName(className, methodName);
|
|
2960
|
+
}
|
|
2961
|
+
catch {
|
|
2962
|
+
return null;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
resolveFieldNodeId(field) {
|
|
2966
|
+
const live = this.fieldToId.get(field);
|
|
2967
|
+
if (live)
|
|
2968
|
+
return live;
|
|
2969
|
+
try {
|
|
2970
|
+
const classSig = field.getDeclaringArkClass().getSignature().toString();
|
|
2971
|
+
return this.fieldSigToId.get(fieldSigKey(classSig, field.getName())) ?? null;
|
|
2972
|
+
}
|
|
2973
|
+
catch {
|
|
2974
|
+
return null;
|
|
2975
|
+
}
|
|
1551
2976
|
}
|
|
1552
2977
|
/** Parent node id for a method: owning class/component, or file for default-class methods. */
|
|
1553
2978
|
resolveMethodParentId(method, relativePath, result) {
|
|
@@ -1570,6 +2995,16 @@ class ArkTSAdapter {
|
|
|
1570
2995
|
const existing = this.methodToId.get(method);
|
|
1571
2996
|
if (existing)
|
|
1572
2997
|
return existing;
|
|
2998
|
+
try {
|
|
2999
|
+
const bySig = this.methodSigToId.get(method.getSignature().toString());
|
|
3000
|
+
if (bySig) {
|
|
3001
|
+
this.methodToId.set(method, bySig);
|
|
3002
|
+
return bySig;
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
catch {
|
|
3006
|
+
// fall through
|
|
3007
|
+
}
|
|
1573
3008
|
if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
|
|
1574
3009
|
return null;
|
|
1575
3010
|
const displayName = arkMethodDisplayName(method);
|
|
@@ -1594,6 +3029,10 @@ class ArkTSAdapter {
|
|
|
1594
3029
|
return this.methodToId.get(method) ?? null;
|
|
1595
3030
|
}
|
|
1596
3031
|
addViewTreeEdge(result, source, target, kind, callerFile, via, line) {
|
|
3032
|
+
const edgeKey = `${source}\0${target}\0${kind}\0${via}`;
|
|
3033
|
+
if (this.emittedEdgeKeys.has(edgeKey))
|
|
3034
|
+
return;
|
|
3035
|
+
this.emittedEdgeKeys.add(edgeKey);
|
|
1597
3036
|
const edge = arkEdge(source, target, kind, {
|
|
1598
3037
|
metadata: {
|
|
1599
3038
|
synthesizedBy: 'viewtree',
|
|
@@ -1612,6 +3051,16 @@ class ArkTSAdapter {
|
|
|
1612
3051
|
const existing = this.methodToId.get(method);
|
|
1613
3052
|
if (existing)
|
|
1614
3053
|
return existing;
|
|
3054
|
+
try {
|
|
3055
|
+
const bySig = this.methodSigToId.get(method.getSignature().toString());
|
|
3056
|
+
if (bySig) {
|
|
3057
|
+
this.methodToId.set(method, bySig);
|
|
3058
|
+
return bySig;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
catch {
|
|
3062
|
+
// fall through
|
|
3063
|
+
}
|
|
1615
3064
|
if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
|
|
1616
3065
|
return null;
|
|
1617
3066
|
const displayName = arkMethodDisplayName(method);
|
|
@@ -1638,6 +3087,24 @@ class ArkTSAdapter {
|
|
|
1638
3087
|
});
|
|
1639
3088
|
this.addNode(result, methodNode);
|
|
1640
3089
|
this.methodToId.set(method, methodNode.id);
|
|
3090
|
+
try {
|
|
3091
|
+
this.methodSigToId.set(method.getSignature().toString(), methodNode.id);
|
|
3092
|
+
}
|
|
3093
|
+
catch {
|
|
3094
|
+
// signature unavailable
|
|
3095
|
+
}
|
|
3096
|
+
try {
|
|
3097
|
+
const ownerName = classDisplayName(cls);
|
|
3098
|
+
if (isUsableArkTypeName(ownerName) && isUsableArkTypeName(displayName)) {
|
|
3099
|
+
this.rememberMethodName(ownerName, displayName, methodNode.id, relativePath);
|
|
3100
|
+
}
|
|
3101
|
+
else if (cls.isDefaultArkClass() && kind === 'function') {
|
|
3102
|
+
this.rememberFunctionName(displayName, methodNode.id, relativePath);
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
catch {
|
|
3106
|
+
// class name unavailable
|
|
3107
|
+
}
|
|
1641
3108
|
result.edges.push(arkEdge(parentId, methodNode.id, 'contains'));
|
|
1642
3109
|
this.indexDecorators(result, methodNode.id, method, relativePath, language, line);
|
|
1643
3110
|
this.indexNativeNapiCalls(method, methodNode.id, relativePath, language, result);
|
|
@@ -1714,10 +3181,11 @@ function drainArkTSIndexNotices() {
|
|
|
1714
3181
|
arktsIndexNotices = [];
|
|
1715
3182
|
return out;
|
|
1716
3183
|
}
|
|
1717
|
-
function buildSceneConfig(rootDir) {
|
|
3184
|
+
function buildSceneConfig(rootDir, extra) {
|
|
1718
3185
|
return (0, arkanalyzer_1.buildSceneConfigFromProject)(rootDir, process.env.OHOS_SDK_HOME, {
|
|
1719
3186
|
supportFileExts: ['.ets', '.ts', '.d.ts'],
|
|
1720
3187
|
enableMethodBodyBuild: true,
|
|
3188
|
+
...(extra?.memoryLimitMB !== undefined ? { memoryLimitMB: extra.memoryLimitMB } : {}),
|
|
1721
3189
|
});
|
|
1722
3190
|
}
|
|
1723
3191
|
function tryBuildArkScene(rootDir) {
|
|
@@ -1742,22 +3210,158 @@ function emptyArkTSBatchIndex(errors) {
|
|
|
1742
3210
|
errors,
|
|
1743
3211
|
};
|
|
1744
3212
|
}
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
3213
|
+
/**
|
|
3214
|
+
* Harmony multi-module path: load each PROJECT module to BODIES, index into
|
|
3215
|
+
* HomeGraph structures in the callback, let ArkAnalyzer evict under memoryLimitMB.
|
|
3216
|
+
* When `targetModuleSrcPaths` is set, only those modules are BODIES targets
|
|
3217
|
+
* (deps still load at SIGNATURES) — used for incremental sync.
|
|
3218
|
+
*
|
|
3219
|
+
* Runs with the #850 liveness watchdog suspended: ArkAnalyzer's native BODIES
|
|
3220
|
+
* load between module callbacks routinely exceeds the 60s heartbeat window on
|
|
3221
|
+
* large Harmony projects, and there is no JS yield point inside that span.
|
|
3222
|
+
*/
|
|
3223
|
+
function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
|
|
3224
|
+
return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => buildArkTSIndexByModuleInner(rootDir, scannedFiles, options));
|
|
3225
|
+
}
|
|
3226
|
+
function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
|
|
3227
|
+
const errors = [];
|
|
3228
|
+
const memoryLimitMB = resolveArkTSSceneMemoryLimitMB();
|
|
3229
|
+
const sceneConfig = buildSceneConfig(rootDir, { memoryLimitMB });
|
|
3230
|
+
const scene = new arkanalyzer_1.Scene();
|
|
3231
|
+
const adapter = new ArkTSAdapter(rootDir, scannedFiles);
|
|
3232
|
+
let modulesIndexed = 0;
|
|
3233
|
+
const targetSrc = options?.targetModuleSrcPaths?.map(normalizeHarmonyModuleSrcPath);
|
|
3234
|
+
const targetSrcSet = targetSrc && targetSrc.length > 0 ? new Set(targetSrc) : null;
|
|
1750
3235
|
try {
|
|
1751
|
-
|
|
1752
|
-
const
|
|
1753
|
-
|
|
3236
|
+
scene.config(sceneConfig);
|
|
3237
|
+
const config = new arkanalyzer_1.ModuleAnalysisConfig();
|
|
3238
|
+
config.setLoadLevel(arkanalyzer_1.ModuleDepthLevel.BODIES);
|
|
3239
|
+
// SIGNATURES is enough for ViewTree stubs / types; BODIES on deps caused
|
|
3240
|
+
// each module's RTA to re-walk shared library CFGs (multi-hour indexes).
|
|
3241
|
+
config.setDependencyLoadLevel(arkanalyzer_1.ModuleDepthLevel.SIGNATURES);
|
|
3242
|
+
if (targetSrcSet) {
|
|
3243
|
+
// Prep + SDK only (no PROJECT targets) so we can resolve ModuleIDs.
|
|
3244
|
+
scene.analyseByModule(() => { }, new arkanalyzer_1.ModuleAnalysisConfig());
|
|
3245
|
+
const wantAbs = new Set([...targetSrcSet].map((src) => {
|
|
3246
|
+
const resolved = path.resolve(rootDir, src);
|
|
3247
|
+
try {
|
|
3248
|
+
return fs.realpathSync(resolved).toLowerCase();
|
|
3249
|
+
}
|
|
3250
|
+
catch {
|
|
3251
|
+
return resolved.toLowerCase();
|
|
3252
|
+
}
|
|
3253
|
+
}));
|
|
3254
|
+
const ids = [];
|
|
3255
|
+
for (const mod of scene.getModules()) {
|
|
3256
|
+
if (mod.getModuleType() !== arkanalyzer_1.ModuleType.PROJECT)
|
|
3257
|
+
continue;
|
|
3258
|
+
let modAbs;
|
|
3259
|
+
try {
|
|
3260
|
+
modAbs = fs.realpathSync(mod.getModulePath());
|
|
3261
|
+
}
|
|
3262
|
+
catch {
|
|
3263
|
+
modAbs = path.resolve(mod.getModulePath());
|
|
3264
|
+
}
|
|
3265
|
+
if (wantAbs.has(modAbs.toLowerCase())) {
|
|
3266
|
+
ids.push(scene.getModuleId(mod));
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
if (ids.length === 0) {
|
|
3270
|
+
errors.push({
|
|
3271
|
+
message: `ArkTS incremental: no modules matched [${[...targetSrcSet].join(', ')}]`,
|
|
3272
|
+
severity: 'warning',
|
|
3273
|
+
});
|
|
3274
|
+
return emptyArkTSBatchIndex(errors);
|
|
3275
|
+
}
|
|
3276
|
+
config.setTargetModuleIds(ids);
|
|
3277
|
+
process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB})...\x1b[0m\n`);
|
|
3278
|
+
}
|
|
3279
|
+
else {
|
|
3280
|
+
config.setIncludeType(arkanalyzer_1.ModuleType.PROJECT, true);
|
|
3281
|
+
process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule (PROJECT→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}, maxOldSpace=${(0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)()})...\x1b[0m\n`);
|
|
3282
|
+
}
|
|
3283
|
+
scene.analyseByModule((module, scn) => {
|
|
3284
|
+
adapter.indexModule(module, scn);
|
|
3285
|
+
modulesIndexed++;
|
|
3286
|
+
const name = module.getModuleName() || path.basename(module.getModulePath());
|
|
3287
|
+
process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name}\x1b[0m\n`);
|
|
3288
|
+
}, config);
|
|
3289
|
+
if (modulesIndexed === 0) {
|
|
3290
|
+
errors.push({
|
|
3291
|
+
message: 'ArkTS analyseByModule found no PROJECT modules (check build-profile.json5); falling back to full Scene build',
|
|
3292
|
+
severity: 'warning',
|
|
3293
|
+
});
|
|
3294
|
+
return emptyArkTSBatchIndex(errors);
|
|
3295
|
+
}
|
|
3296
|
+
try {
|
|
3297
|
+
adapter.finalizeCallGraph(scene);
|
|
3298
|
+
}
|
|
3299
|
+
catch (e) {
|
|
3300
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
3301
|
+
errors.push({
|
|
3302
|
+
message: `ArkTS modular leftover RTA skipped: ${message}`,
|
|
3303
|
+
severity: 'warning',
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
const index = adapter.toBatchIndex(errors);
|
|
3307
|
+
if (index.fileResults.size === 0) {
|
|
3308
|
+
errors.push({
|
|
3309
|
+
message: 'ArkTS analyseByModule indexed modules but no scanned source files matched',
|
|
3310
|
+
severity: 'warning',
|
|
3311
|
+
});
|
|
3312
|
+
return emptyArkTSBatchIndex(errors);
|
|
3313
|
+
}
|
|
1754
3314
|
return index;
|
|
1755
3315
|
}
|
|
1756
3316
|
catch (e) {
|
|
1757
3317
|
const message = e instanceof Error ? e.message : String(e);
|
|
1758
|
-
errors.push({
|
|
3318
|
+
errors.push({
|
|
3319
|
+
message: `ArkTS analyseByModule failed: ${message}; falling back to full Scene build`,
|
|
3320
|
+
severity: 'warning',
|
|
3321
|
+
});
|
|
1759
3322
|
return emptyArkTSBatchIndex(errors);
|
|
1760
3323
|
}
|
|
3324
|
+
finally {
|
|
3325
|
+
try {
|
|
3326
|
+
scene.dispose();
|
|
3327
|
+
}
|
|
3328
|
+
catch {
|
|
3329
|
+
// ignore dispose failures
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
function buildArkTSIndexFull(rootDir, scannedFiles, priorErrors = []) {
|
|
3334
|
+
// Full Scene build is the same opaque native stall as analyseByModule.
|
|
3335
|
+
return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => {
|
|
3336
|
+
const { scene, errors } = tryBuildArkScene(rootDir);
|
|
3337
|
+
const allErrors = [...priorErrors, ...errors];
|
|
3338
|
+
if (!scene) {
|
|
3339
|
+
return emptyArkTSBatchIndex(allErrors);
|
|
3340
|
+
}
|
|
3341
|
+
try {
|
|
3342
|
+
const adapter = new ArkTSAdapter(rootDir, scannedFiles);
|
|
3343
|
+
const index = adapter.build(scene);
|
|
3344
|
+
index.errors.push(...allErrors);
|
|
3345
|
+
return index;
|
|
3346
|
+
}
|
|
3347
|
+
catch (e) {
|
|
3348
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
3349
|
+
allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
|
|
3350
|
+
return emptyArkTSBatchIndex(allErrors);
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
function buildArkTSIndex(rootDir, scannedFiles) {
|
|
3355
|
+
if (shouldUseModularArkTSBuild(rootDir)) {
|
|
3356
|
+
const modular = buildArkTSIndexByModule(rootDir, scannedFiles);
|
|
3357
|
+
const fatal = modular.errors.find((e) => e.severity === 'error');
|
|
3358
|
+
if (modular.fileResults.size > 0 && !fatal) {
|
|
3359
|
+
return modular;
|
|
3360
|
+
}
|
|
3361
|
+
// Warnings like "no PROJECT modules" / modular failure → full Scene fallback.
|
|
3362
|
+
return buildArkTSIndexFull(rootDir, scannedFiles, modular.errors);
|
|
3363
|
+
}
|
|
3364
|
+
return buildArkTSIndexFull(rootDir, scannedFiles);
|
|
1761
3365
|
}
|
|
1762
3366
|
// =============================================================================
|
|
1763
3367
|
// OHOS SDK input resolution + prebuilt API database indexing
|