homegraph 1.2.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -11
- package/dist/bin/homegraph.js +298 -72
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +117 -11
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +207 -6
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +461 -20
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +19 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +157 -3
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +426 -9
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +32 -1
- 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 +119 -0
- package/dist/db/wal-valve.d.ts.map +1 -0
- package/dist/db/wal-valve.js +290 -0
- package/dist/db/wal-valve.js.map +1 -0
- package/dist/directory.d.ts +9 -5
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +210 -19
- package/dist/directory.js.map +1 -1
- package/dist/extraction/arkts-batch-worker.js +3 -2
- package/dist/extraction/arkts-batch-worker.js.map +1 -1
- package/dist/extraction/cfml-extractor.d.ts +107 -0
- package/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/dist/extraction/cfml-extractor.js +494 -0
- package/dist/extraction/cfml-extractor.js.map +1 -0
- package/dist/extraction/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/grammars.d.ts +25 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +177 -24
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +52 -14
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +521 -314
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +90 -6
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +1579 -128
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +42 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +369 -4
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/cfquery.d.ts +12 -0
- package/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/dist/extraction/languages/cfquery.js +28 -0
- package/dist/extraction/languages/cfquery.js.map +1 -0
- package/dist/extraction/languages/cfscript.d.ts +3 -0
- package/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/dist/extraction/languages/cfscript.js +73 -0
- package/dist/extraction/languages/cfscript.js.map +1 -0
- package/dist/extraction/languages/cobol.d.ts +33 -0
- package/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/dist/extraction/languages/cobol.js +499 -0
- package/dist/extraction/languages/cobol.js.map +1 -0
- package/dist/extraction/languages/erlang.d.ts +3 -0
- package/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/dist/extraction/languages/erlang.js +350 -0
- package/dist/extraction/languages/erlang.js.map +1 -0
- package/dist/extraction/languages/index.d.ts.map +1 -1
- package/dist/extraction/languages/index.js +16 -0
- package/dist/extraction/languages/index.js.map +1 -1
- package/dist/extraction/languages/nix.d.ts +3 -0
- package/dist/extraction/languages/nix.d.ts.map +1 -0
- package/dist/extraction/languages/nix.js +294 -0
- package/dist/extraction/languages/nix.js.map +1 -0
- package/dist/extraction/languages/solidity.d.ts +3 -0
- package/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/dist/extraction/languages/solidity.js +293 -0
- package/dist/extraction/languages/solidity.js.map +1 -0
- package/dist/extraction/languages/terraform.d.ts +3 -0
- package/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/dist/extraction/languages/terraform.js +641 -0
- package/dist/extraction/languages/terraform.js.map +1 -0
- package/dist/extraction/languages/vbnet.d.ts +11 -0
- package/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/dist/extraction/languages/vbnet.js +141 -0
- package/dist/extraction/languages/vbnet.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/dist/extraction/mybatis-extractor.js +140 -38
- package/dist/extraction/mybatis-extractor.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +27 -1
- package/dist/extraction/parse-pool.d.ts.map +1 -1
- package/dist/extraction/parse-pool.js +75 -7
- package/dist/extraction/parse-pool.js.map +1 -1
- package/dist/extraction/parse-worker.js +9 -2
- package/dist/extraction/parse-worker.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-helpers.d.ts.map +1 -1
- package/dist/extraction/tree-sitter-helpers.js +1 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/dist/extraction/tree-sitter-types.d.ts +3 -1
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +38 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +801 -10
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/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/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +1 -1
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +74 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +436 -55
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +42 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +97 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/daemon.d.ts +25 -3
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +62 -8
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/dist/mcp/dynamic-boundaries.js +2 -1
- package/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/dist/mcp/early-ppid.d.ts +26 -0
- package/dist/mcp/early-ppid.d.ts.map +1 -0
- package/dist/mcp/early-ppid.js +29 -0
- package/dist/mcp/early-ppid.js.map +1 -0
- package/dist/mcp/engine.d.ts +4 -5
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +27 -1
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +41 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +73 -13
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/memory-budget.d.ts +30 -0
- package/dist/mcp/memory-budget.d.ts.map +1 -0
- package/dist/mcp/memory-budget.js +123 -0
- package/dist/mcp/memory-budget.js.map +1 -0
- package/dist/mcp/proxy.d.ts.map +1 -1
- package/dist/mcp/proxy.js +88 -4
- package/dist/mcp/proxy.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +35 -4
- package/dist/mcp/query-cache.d.ts.map +1 -1
- package/dist/mcp/query-cache.js +89 -19
- package/dist/mcp/query-cache.js.map +1 -1
- package/dist/mcp/query-pool.d.ts +46 -12
- package/dist/mcp/query-pool.d.ts.map +1 -1
- package/dist/mcp/query-pool.js +100 -23
- package/dist/mcp/query-pool.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.d.ts +4 -27
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +39 -84
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/session.d.ts +14 -0
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +28 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/startup-handshake.d.ts +44 -0
- package/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/dist/mcp/startup-handshake.js +73 -0
- package/dist/mcp/startup-handshake.js.map +1 -0
- package/dist/mcp/tools.d.ts +104 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +2432 -217
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/project-config.d.ts +38 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +101 -2
- package/dist/project-config.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +178 -146
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +54 -10
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +1299 -257
- 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/cics.d.ts +20 -0
- package/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/dist/resolution/frameworks/cics.js +90 -0
- package/dist/resolution/frameworks/cics.js.map +1 -0
- package/dist/resolution/frameworks/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/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +3 -0
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/dist/resolution/frameworks/java.js +14 -6
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/dist/resolution/frameworks/terraform.js +277 -0
- package/dist/resolution/frameworks/terraform.js.map +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/dist/resolution/goframe-synthesizer.js +8 -3
- package/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +9 -3
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +295 -11
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +146 -8
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +1089 -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 -4
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +793 -130
- 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/strip-comments.d.ts +1 -1
- package/dist/resolution/strip-comments.d.ts.map +1 -1
- package/dist/resolution/strip-comments.js +49 -0
- package/dist/resolution/strip-comments.js.map +1 -1
- package/dist/resolution/types.d.ts +32 -0
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.d.ts +10 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.js +142 -4
- package/dist/resolution/workspace-packages.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +60 -0
- package/dist/search/identifier-segments.d.ts.map +1 -0
- package/dist/search/identifier-segments.js +176 -0
- package/dist/search/identifier-segments.js.map +1 -0
- package/dist/search/query-utils.d.ts +201 -0
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +919 -2
- package/dist/search/query-utils.js.map +1 -1
- package/dist/spec/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/git-scanner.d.ts +93 -0
- package/dist/spec/build/git-scanner.d.ts.map +1 -0
- package/dist/spec/build/git-scanner.js +254 -0
- package/dist/spec/build/git-scanner.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/fts.d.ts.map +1 -1
- package/dist/spec/db/fts.js +19 -50
- 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 +175 -4
- 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 +4 -0
- package/dist/spec/db/schema.js.map +1 -1
- package/dist/spec/db/schema.sql +5 -1
- 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 +11 -5
- 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/git-utils.d.ts +8 -0
- package/dist/spec/git-utils.d.ts.map +1 -0
- package/dist/spec/git-utils.js +14 -0
- package/dist/spec/git-utils.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 +100 -50
- 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/clusterer.d.ts +63 -0
- package/dist/spec/mine/clusterer.d.ts.map +1 -0
- package/dist/spec/mine/clusterer.js +904 -0
- package/dist/spec/mine/clusterer.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/progress-handler.d.ts +22 -0
- package/dist/spec/mine/progress-handler.d.ts.map +1 -0
- package/dist/spec/mine/progress-handler.js +108 -0
- package/dist/spec/mine/progress-handler.js.map +1 -0
- package/dist/spec/mine/progress.d.ts +23 -0
- package/dist/spec/mine/progress.d.ts.map +1 -0
- package/dist/spec/mine/progress.js +12 -0
- package/dist/spec/mine/progress.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/spec/utils.d.ts +4 -16
- package/dist/spec/utils.d.ts.map +1 -1
- package/dist/spec/utils.js +6 -58
- package/dist/spec/utils.js.map +1 -1
- package/dist/sync/git-hooks.d.ts.map +1 -1
- package/dist/sync/git-hooks.js +2 -0
- package/dist/sync/git-hooks.js.map +1 -1
- package/dist/sync/watcher.d.ts +35 -6
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +127 -17
- package/dist/sync/watcher.js.map +1 -1
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +5 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/telemetry/index.js +3 -3
- package/dist/telemetry/index.js.map +1 -1
- package/dist/types.d.ts +22 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/upgrade/index.d.ts +32 -0
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +162 -14
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/remove-binary.d.ts +87 -0
- package/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/dist/upgrade/remove-binary.js +289 -0
- package/dist/upgrade/remove-binary.js.map +1 -0
- package/dist/upgrade/update-check.d.ts +92 -0
- package/dist/upgrade/update-check.d.ts.map +1 -0
- package/dist/upgrade/update-check.js +258 -0
- package/dist/upgrade/update-check.js.map +1 -0
- package/package.json +6 -2
- package/scripts/agent-eval/run-all.sh +6 -0
- package/scripts/build-bundle.sh +5 -0
- package/scripts/npm-shim.js +8 -1
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/data/agents.json +0 -109
- package/scripts/exp_boundary_eval/data/experiments.json +0 -140
- package/scripts/qa_eval/README.md +0 -407
- package/scripts/qa_eval/_test_deveco_probe.py +0 -41
- package/scripts/qa_eval/agent_runner.py +0 -526
- package/scripts/qa_eval/data/.gitignore +0 -4
- package/scripts/qa_eval/data/test-set.jsonl +0 -2
- package/scripts/qa_eval/eval_metrics.py +0 -274
- package/scripts/qa_eval/external_agent.py +0 -976
- package/scripts/qa_eval/llm_config.py +0 -92
- package/scripts/qa_eval/memory_monitor.py +0 -132
- package/scripts/qa_eval/my_answer_accuracy.py +0 -187
- package/scripts/qa_eval/requirements.txt +0 -2
- package/scripts/qa_eval/run_pipeline.py +0 -804
- package/scripts/qa_eval/stats_efficiency.py +0 -279
- package/scripts/qa_eval/stats_scores.py +0 -207
|
@@ -3,10 +3,24 @@
|
|
|
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. CFG does **not** stitch same-module calls (that
|
|
15
|
+
* densified past unlimited RTA). Cross-module edges come from RTA with an exact
|
|
16
|
+
* MethodSignature map, plus a CFG `%unk` bridge that recovers Class.method /
|
|
17
|
+
* free functions from stmt text, UnclearReferenceType names, or PascalCase base
|
|
18
|
+
* locals (import + unique target + fan-out blocklist). Deps load to SIGNATURES.
|
|
19
|
+
*
|
|
20
|
+
* Sync can re-index a **dirty module subset** (`primeArkTSBatch` + `changedFiles`)
|
|
21
|
+
* via `ModuleAnalysisConfig.setTargetModuleIds`, instead of rebuilding every
|
|
22
|
+
* PROJECT module. Unchanged modules stay in the DB; incoming cross-file edges
|
|
23
|
+
* into dirty files are snapshotted and re-bound like file-level sync (#899).
|
|
10
24
|
*/
|
|
11
25
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
26
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,18 +55,29 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
55
|
return result;
|
|
42
56
|
};
|
|
43
57
|
})();
|
|
58
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
59
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
60
|
+
};
|
|
44
61
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
62
|
exports.OHOS_API_DB_PATH_META = exports.OHOS_API_VERSION_META = exports.OHOS_API_FILE_PREFIX = exports.OhosSdkInputError = exports.ArkTSExtractor = void 0;
|
|
46
63
|
exports.cleanArkTypeString = cleanArkTypeString;
|
|
47
64
|
exports.normalizeArktsReturnType = normalizeArktsReturnType;
|
|
48
65
|
exports.buildArkMethodSignatureFields = buildArkMethodSignatureFields;
|
|
66
|
+
exports.shouldUseIsolatedArkTSBuild = shouldUseIsolatedArkTSBuild;
|
|
67
|
+
exports.normalizeHarmonyModuleSrcPath = normalizeHarmonyModuleSrcPath;
|
|
68
|
+
exports.listHarmonyProjectModules = listHarmonyProjectModules;
|
|
69
|
+
exports.resolveDirtyHarmonyModules = resolveDirtyHarmonyModules;
|
|
49
70
|
exports.isArkAnalyzerSourcePath = isArkAnalyzerSourcePath;
|
|
71
|
+
exports.scanEtsFiles = scanEtsFiles;
|
|
50
72
|
exports.runIsolatedArkTSBatchEntry = runIsolatedArkTSBatchEntry;
|
|
51
73
|
exports.primeArkTSBatch = primeArkTSBatch;
|
|
52
74
|
exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
|
|
53
75
|
exports.resetArkTSBatch = resetArkTSBatch;
|
|
54
76
|
exports.stateDecoratorKinds = stateDecoratorKinds;
|
|
55
77
|
exports.stateTransferViaForField = stateTransferViaForField;
|
|
78
|
+
exports.isUnclearArkClassSignature = isUnclearArkClassSignature;
|
|
79
|
+
exports.isUnclearArkMethodSignature = isUnclearArkMethodSignature;
|
|
80
|
+
exports.pickPreferredClassNodeId = pickPreferredClassNodeId;
|
|
56
81
|
exports.drainArkTSIndexNotices = drainArkTSIndexNotices;
|
|
57
82
|
exports.parseOhosToolsVersionFromName = parseOhosToolsVersionFromName;
|
|
58
83
|
exports.isArchivePath = isArchivePath;
|
|
@@ -82,11 +107,15 @@ const child_process_1 = require("child_process");
|
|
|
82
107
|
const arkanalyzer_1 = require("arkanalyzer");
|
|
83
108
|
const db_1 = require("../../db");
|
|
84
109
|
const queries_1 = require("../../db/queries");
|
|
110
|
+
const directory_1 = require("../../directory");
|
|
111
|
+
const project_config_1 = require("../../project-config");
|
|
85
112
|
const context_1 = require("../context");
|
|
113
|
+
const default_ignore_1 = require("../default-ignore");
|
|
86
114
|
const extraction_version_1 = require("../extraction-version");
|
|
87
115
|
const version_1 = require("../../mcp/version");
|
|
88
116
|
const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
|
|
89
117
|
const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
|
|
118
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
90
119
|
/** Primitive / void return types — not stored in nodes.return_type (inference-only column). */
|
|
91
120
|
const ARKTS_NON_CLASS_RETURN = new Set([
|
|
92
121
|
'void',
|
|
@@ -222,16 +251,148 @@ function isStackOverflowExitCode(code) {
|
|
|
222
251
|
return false;
|
|
223
252
|
return code === 3221225725 || code === -1073741571;
|
|
224
253
|
}
|
|
225
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Opt-in isolated ArkTS Scene build (child process + enlarged `--stack-size`).
|
|
256
|
+
* Default is in-process: faster and avoids a Windows-only auto-isolated path that
|
|
257
|
+
* could burn minutes on failed stack-size retries before giving up. Set
|
|
258
|
+
* `HOMEGRAPH_ARKTS_ISOLATED=1` only when an in-process Scene build stack-overflows
|
|
259
|
+
* the indexer (rare Photos-scale Windows cases).
|
|
260
|
+
*/
|
|
261
|
+
function shouldUseIsolatedArkTSBuild() {
|
|
226
262
|
const mode = process.env.HOMEGRAPH_ARKTS_ISOLATED?.trim();
|
|
227
|
-
|
|
228
|
-
|
|
263
|
+
return mode === '1' || mode === 'true';
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Scene eviction budget sits below the V8 heap cap (heap default 3584MB so process
|
|
267
|
+
* RSS stays near 4GB — native memory sits outside the heap). Small reserve only.
|
|
268
|
+
*/
|
|
269
|
+
const ARKTS_HOMEGRAPH_RESERVE_MB = 256;
|
|
270
|
+
/** Default Scene `memoryLimitMB`. Override with HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB. */
|
|
271
|
+
function defaultArkTSSceneMemoryLimitMB() {
|
|
272
|
+
return Math.max(512, (0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)() - ARKTS_HOMEGRAPH_RESERVE_MB);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* RSS limit passed to ArkAnalyzer `SceneOptions.memoryLimitMB`.
|
|
276
|
+
* When exceeded, `analyseByModule` evicts cached modules before loading the next.
|
|
277
|
+
* Set `HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB=0` to disable Scene eviction.
|
|
278
|
+
*/
|
|
279
|
+
function resolveArkTSSceneMemoryLimitMB() {
|
|
280
|
+
const raw = process.env.HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB?.trim();
|
|
281
|
+
if (raw !== undefined && raw !== '') {
|
|
282
|
+
const n = Number(raw);
|
|
283
|
+
if (Number.isFinite(n) && n >= 0)
|
|
284
|
+
return Math.floor(n);
|
|
285
|
+
}
|
|
286
|
+
return defaultArkTSSceneMemoryLimitMB();
|
|
287
|
+
}
|
|
288
|
+
/** Prefer modular Scene build when the project has Harmony module metadata. */
|
|
289
|
+
function shouldUseModularArkTSBuild(rootDir) {
|
|
290
|
+
const mode = process.env.HOMEGRAPH_ARKTS_MODULAR?.trim();
|
|
229
291
|
if (mode === '0' || mode === 'false')
|
|
230
292
|
return false;
|
|
231
|
-
|
|
232
|
-
if (process.platform === 'win32' && etsCount >= 500)
|
|
293
|
+
if (mode === '1' || mode === 'true')
|
|
233
294
|
return true;
|
|
234
|
-
return
|
|
295
|
+
return fs.existsSync(path.join(rootDir, 'build-profile.json5'));
|
|
296
|
+
}
|
|
297
|
+
/** Normalize build-profile `srcPath` / relative file paths for prefix matching. */
|
|
298
|
+
function normalizeHarmonyModuleSrcPath(srcPath) {
|
|
299
|
+
return srcPath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Read PROJECT modules from root `build-profile.json5` (name + srcPath).
|
|
303
|
+
* Returns [] when missing or unparsable — callers should fall back to full Scene.
|
|
304
|
+
*/
|
|
305
|
+
function listHarmonyProjectModules(rootDir) {
|
|
306
|
+
const profilePath = path.join(rootDir, 'build-profile.json5');
|
|
307
|
+
if (!fs.existsSync(profilePath))
|
|
308
|
+
return [];
|
|
309
|
+
let raw;
|
|
310
|
+
try {
|
|
311
|
+
raw = parseJson5Minimal(fs.readFileSync(profilePath, 'utf-8'));
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
if (!raw || typeof raw !== 'object')
|
|
317
|
+
return [];
|
|
318
|
+
const modules = raw.modules;
|
|
319
|
+
if (!Array.isArray(modules))
|
|
320
|
+
return [];
|
|
321
|
+
const out = [];
|
|
322
|
+
for (const entry of modules) {
|
|
323
|
+
if (!entry || typeof entry !== 'object')
|
|
324
|
+
continue;
|
|
325
|
+
const rec = entry;
|
|
326
|
+
if (typeof rec.name !== 'string' || typeof rec.srcPath !== 'string')
|
|
327
|
+
continue;
|
|
328
|
+
const srcPath = normalizeHarmonyModuleSrcPath(rec.srcPath);
|
|
329
|
+
if (!srcPath)
|
|
330
|
+
continue;
|
|
331
|
+
out.push({ name: rec.name, srcPath });
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
function findLongestHarmonyModule(fileRel, modules) {
|
|
336
|
+
const file = normIndexPath(fileRel);
|
|
337
|
+
let best = null;
|
|
338
|
+
for (const mod of modules) {
|
|
339
|
+
const src = mod.srcPath;
|
|
340
|
+
if (file === src || file.startsWith(`${src}/`)) {
|
|
341
|
+
if (!best || mod.srcPath.length > best.srcPath.length)
|
|
342
|
+
best = mod;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return best;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Map changed/removed paths to Harmony modules for incremental ArkTS sync.
|
|
349
|
+
* Structural project files (`build-profile.json5`, root `oh-package.json5`) or
|
|
350
|
+
* ArkAnalyzer sources outside every module force a full rebuild.
|
|
351
|
+
*/
|
|
352
|
+
function resolveDirtyHarmonyModules(rootDir, changedFiles) {
|
|
353
|
+
if (!shouldUseModularArkTSBuild(rootDir)) {
|
|
354
|
+
return { mode: 'full', reason: 'non-modular ArkTS project' };
|
|
355
|
+
}
|
|
356
|
+
const modules = listHarmonyProjectModules(rootDir);
|
|
357
|
+
if (modules.length === 0) {
|
|
358
|
+
return { mode: 'full', reason: 'no PROJECT modules in build-profile.json5' };
|
|
359
|
+
}
|
|
360
|
+
const changed = [...changedFiles].map(normIndexPath).filter(Boolean);
|
|
361
|
+
if (changed.length === 0)
|
|
362
|
+
return { mode: 'none' };
|
|
363
|
+
for (const f of changed) {
|
|
364
|
+
if (f === 'build-profile.json5' || f.endsWith('/build-profile.json5')) {
|
|
365
|
+
return { mode: 'full', reason: 'build-profile.json5 changed' };
|
|
366
|
+
}
|
|
367
|
+
if (f === 'oh-package.json5') {
|
|
368
|
+
return { mode: 'full', reason: 'root oh-package.json5 changed' };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const dirty = new Set();
|
|
372
|
+
const unmatched = [];
|
|
373
|
+
for (const f of changed) {
|
|
374
|
+
// Virtual ArkAnalyzer paths are batch artifacts, not Harmony module sources.
|
|
375
|
+
if (f.startsWith('@'))
|
|
376
|
+
continue;
|
|
377
|
+
const isModuleMeta = f.endsWith('/module.json5') || f.endsWith('/oh-package.json5');
|
|
378
|
+
const isArkSrc = isArkAnalyzerSourcePath(f);
|
|
379
|
+
if (!isArkSrc && !isModuleMeta)
|
|
380
|
+
continue;
|
|
381
|
+
const match = findLongestHarmonyModule(f, modules);
|
|
382
|
+
if (match)
|
|
383
|
+
dirty.add(match.srcPath);
|
|
384
|
+
else if (isArkSrc)
|
|
385
|
+
unmatched.push(f);
|
|
386
|
+
}
|
|
387
|
+
if (unmatched.length > 0) {
|
|
388
|
+
return {
|
|
389
|
+
mode: 'full',
|
|
390
|
+
reason: `ArkTS sources outside modules: ${unmatched.slice(0, 5).join(', ')}`,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
if (dirty.size === 0)
|
|
394
|
+
return { mode: 'none' };
|
|
395
|
+
return { mode: 'modules', moduleSrcPaths: [...dirty].sort() };
|
|
235
396
|
}
|
|
236
397
|
function homegraphDbPath(rootDir) {
|
|
237
398
|
return path.join(rootDir, '.homegraph', 'homegraph.db');
|
|
@@ -239,6 +400,9 @@ function homegraphDbPath(rootDir) {
|
|
|
239
400
|
const ARK_PROVENANCE = 'heuristic';
|
|
240
401
|
/** Virtual file path for ArkAnalyzer's in-scene dummy entry (not on disk). */
|
|
241
402
|
const ARKANALYZER_DUMMY_FILE = '@dummyFile.ets';
|
|
403
|
+
function fieldSigKey(classSig, fieldName) {
|
|
404
|
+
return `${classSig}::${fieldName}`;
|
|
405
|
+
}
|
|
242
406
|
/** HarmonyOS sources indexed via ArkAnalyzer Scene batch (`.ets`, `.ts`, `.d.ts`). */
|
|
243
407
|
function isArkAnalyzerSourcePath(filePath) {
|
|
244
408
|
const base = path.basename(filePath);
|
|
@@ -251,8 +415,24 @@ function isEtsFileName(name) {
|
|
|
251
415
|
function isCoLocatedArkTsFileName(name) {
|
|
252
416
|
return isArkAnalyzerSourcePath(name) && !isEtsFileName(name);
|
|
253
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Ignore matchers aligned with `scanDirectoryWalk`'s base set (built-in defaults
|
|
420
|
+
* + root `.gitignore` + `homegraph.json` `exclude`).
|
|
421
|
+
*/
|
|
422
|
+
function arktsScanIgnoreMatchers(rootDir) {
|
|
423
|
+
const matchers = [(0, default_ignore_1.buildDefaultIgnore)(rootDir)];
|
|
424
|
+
const exclude = (0, project_config_1.loadExcludePatterns)(rootDir);
|
|
425
|
+
if (exclude.length > 0)
|
|
426
|
+
matchers.push((0, ignore_1.default)().add(exclude));
|
|
427
|
+
return matchers;
|
|
428
|
+
}
|
|
429
|
+
function isArktsScanIgnored(relPath, isDir, matchers) {
|
|
430
|
+
const probe = isDir ? `${relPath.replace(/\/$/, '')}/` : relPath;
|
|
431
|
+
return matchers.some((ig) => ig.ignores(probe));
|
|
432
|
+
}
|
|
254
433
|
/** True when the tree looks like a HarmonyOS / ArkTS project (not a generic TS repo). */
|
|
255
434
|
function hasHarmonyProjectMarkers(rootDir) {
|
|
435
|
+
const matchers = arktsScanIgnoreMatchers(rootDir);
|
|
256
436
|
const found = [];
|
|
257
437
|
function walk(dir) {
|
|
258
438
|
if (found.length > 0)
|
|
@@ -266,13 +446,17 @@ function hasHarmonyProjectMarkers(rootDir) {
|
|
|
266
446
|
}
|
|
267
447
|
for (const entry of entries) {
|
|
268
448
|
const full = path.join(dir, entry.name);
|
|
449
|
+
const rel = path.relative(rootDir, full).replace(/\\/g, '/');
|
|
269
450
|
if (entry.isDirectory()) {
|
|
270
|
-
if (entry.name === '
|
|
451
|
+
if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
|
|
452
|
+
continue;
|
|
453
|
+
if (isArktsScanIgnored(rel, true, matchers))
|
|
271
454
|
continue;
|
|
272
|
-
}
|
|
273
455
|
walk(full);
|
|
274
456
|
}
|
|
275
457
|
else if (entry.isFile() && (isEtsFileName(entry.name) || entry.name === 'module.json5')) {
|
|
458
|
+
if (isArktsScanIgnored(rel, false, matchers))
|
|
459
|
+
continue;
|
|
276
460
|
found.push(full);
|
|
277
461
|
return;
|
|
278
462
|
}
|
|
@@ -306,9 +490,17 @@ function emptyResult(filePath, message, severity) {
|
|
|
306
490
|
durationMs: 0,
|
|
307
491
|
};
|
|
308
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Enumerate ArkAnalyzer sources under `rootDir`, using the same ignore rules as
|
|
495
|
+
* `scanDirectory` (defaults like `build/`/`dist/`, root `.gitignore`, exclude).
|
|
496
|
+
* Previously this walk only skipped `node_modules`/`.git`/`.homegraph`, so
|
|
497
|
+
* HarmonyOS build-output `.ets` files got written into the DB and non-git
|
|
498
|
+
* `homegraph status` reported them as Pending Removed.
|
|
499
|
+
*/
|
|
309
500
|
function scanEtsFiles(rootDir) {
|
|
310
501
|
const etsFiles = [];
|
|
311
502
|
const coLocated = [];
|
|
503
|
+
const matchers = arktsScanIgnoreMatchers(rootDir);
|
|
312
504
|
function walk(dir) {
|
|
313
505
|
let entries;
|
|
314
506
|
try {
|
|
@@ -319,13 +511,17 @@ function scanEtsFiles(rootDir) {
|
|
|
319
511
|
}
|
|
320
512
|
for (const entry of entries) {
|
|
321
513
|
const full = path.join(dir, entry.name);
|
|
514
|
+
const rel = path.relative(rootDir, full).replace(/\\/g, '/');
|
|
322
515
|
if (entry.isDirectory()) {
|
|
323
|
-
if (entry.name === '
|
|
516
|
+
if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
|
|
517
|
+
continue;
|
|
518
|
+
if (isArktsScanIgnored(rel, true, matchers))
|
|
324
519
|
continue;
|
|
325
520
|
walk(full);
|
|
326
521
|
}
|
|
327
522
|
else if (entry.isFile()) {
|
|
328
|
-
|
|
523
|
+
if (isArktsScanIgnored(rel, false, matchers))
|
|
524
|
+
continue;
|
|
329
525
|
if (isEtsFileName(entry.name)) {
|
|
330
526
|
etsFiles.push(rel);
|
|
331
527
|
}
|
|
@@ -358,12 +554,15 @@ let batchBuildCommitted = false;
|
|
|
358
554
|
function reportBatchProgress(subphase, current, total, currentFile) {
|
|
359
555
|
(0, context_1.reportArkTSBatchProgress)({ subphase, current, total, currentFile });
|
|
360
556
|
}
|
|
361
|
-
function persistFileResult(queries, filePath, content, stats, result) {
|
|
557
|
+
function persistFileResult(queries, filePath, content, stats, result, options) {
|
|
362
558
|
const contentHash = hashContent(content);
|
|
363
559
|
const existingFile = queries.getFileByPath(filePath);
|
|
364
|
-
if (existingFile?.contentHash === contentHash) {
|
|
560
|
+
if (!options?.force && existingFile?.contentHash === contentHash) {
|
|
365
561
|
return;
|
|
366
562
|
}
|
|
563
|
+
const crossFileIncomingEdges = existingFile && typeof queries.getCrossFileIncomingEdgesWithTarget === 'function'
|
|
564
|
+
? queries.getCrossFileIncomingEdgesWithTarget(filePath)
|
|
565
|
+
: [];
|
|
367
566
|
if (existingFile) {
|
|
368
567
|
queries.deleteFile(filePath);
|
|
369
568
|
}
|
|
@@ -378,6 +577,30 @@ function persistFileResult(queries, filePath, content, stats, result) {
|
|
|
378
577
|
queries.insertEdges(validEdges);
|
|
379
578
|
}
|
|
380
579
|
}
|
|
580
|
+
if (crossFileIncomingEdges.length > 0) {
|
|
581
|
+
const newNodesByKindName = new Map();
|
|
582
|
+
for (const n of validNodes) {
|
|
583
|
+
newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
|
|
584
|
+
}
|
|
585
|
+
const reinserted = [];
|
|
586
|
+
for (const e of crossFileIncomingEdges) {
|
|
587
|
+
const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
|
|
588
|
+
if (newTargetId) {
|
|
589
|
+
reinserted.push({
|
|
590
|
+
source: e.source,
|
|
591
|
+
target: newTargetId,
|
|
592
|
+
kind: e.kind,
|
|
593
|
+
metadata: e.metadata,
|
|
594
|
+
line: e.line,
|
|
595
|
+
column: e.column,
|
|
596
|
+
provenance: e.provenance,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (reinserted.length > 0) {
|
|
601
|
+
queries.insertEdges(reinserted);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
381
604
|
if (result.unresolvedReferences.length > 0) {
|
|
382
605
|
const insertedIds = new Set(validNodes.map((n) => n.id));
|
|
383
606
|
const refsWithContext = result.unresolvedReferences
|
|
@@ -403,6 +626,22 @@ function persistFileResult(queries, filePath, content, stats, result) {
|
|
|
403
626
|
};
|
|
404
627
|
queries.upsertFile(fileRecord);
|
|
405
628
|
}
|
|
629
|
+
/** Force-rewrite every file in an incremental module index (edges may change without content). */
|
|
630
|
+
function persistIncrementalModuleResults(rootDir, queries, index) {
|
|
631
|
+
const persistTotal = index.fileResults.size;
|
|
632
|
+
let persistIndex = 0;
|
|
633
|
+
for (const [filePath, fileResult] of index.fileResults) {
|
|
634
|
+
persistIndex++;
|
|
635
|
+
reportBatchProgress('persist', persistIndex, persistTotal, filePath);
|
|
636
|
+
const isVirtual = filePath.startsWith('@');
|
|
637
|
+
const full = path.join(rootDir, filePath);
|
|
638
|
+
const content = isVirtual ? '' : fs.readFileSync(full, 'utf-8');
|
|
639
|
+
const stats = isVirtual
|
|
640
|
+
? { size: 0, mtimeMs: Date.now() }
|
|
641
|
+
: fs.statSync(full);
|
|
642
|
+
persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
406
645
|
async function persistBatchResultsAsync(rootDir, queries, index) {
|
|
407
646
|
const entries = [...index.fileResults.entries()];
|
|
408
647
|
const persistTotal = entries.length;
|
|
@@ -501,8 +740,12 @@ function spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile) {
|
|
|
501
740
|
const workerPath = path.join(__dirname, '..', 'arkts-batch-worker.js');
|
|
502
741
|
let lastCode = null;
|
|
503
742
|
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], [
|
|
743
|
+
process.stderr.write(`\n\x1b[33m ArkTS: Scene build in isolated process (--stack-size=${stackKb} KB; modular if build-profile.json5)...\x1b[0m\n`);
|
|
744
|
+
const argv = (0, wasm_runtime_flags_1.buildRelaunchArgv)(workerPath, [rootDir, dbPath, triggerFile], [
|
|
745
|
+
...process.execArgv,
|
|
746
|
+
`--stack-size=${stackKb}`,
|
|
747
|
+
(0, wasm_runtime_flags_1.maxOldSpaceSizeFlag)(),
|
|
748
|
+
]);
|
|
506
749
|
const result = (0, child_process_1.spawnSync)(process.execPath, argv, {
|
|
507
750
|
stdio: 'inherit',
|
|
508
751
|
env: process.env,
|
|
@@ -558,7 +801,7 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
|
|
|
558
801
|
}
|
|
559
802
|
}
|
|
560
803
|
function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
|
|
561
|
-
if (shouldUseIsolatedArkTSBuild(
|
|
804
|
+
if (shouldUseIsolatedArkTSBuild()) {
|
|
562
805
|
const dbPath = homegraphDbPath(rootDir);
|
|
563
806
|
if (!fs.existsSync(dbPath)) {
|
|
564
807
|
throw new Error(`ArkTS isolated build requires ${dbPath}`);
|
|
@@ -569,7 +812,8 @@ function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
|
|
|
569
812
|
const spawned = spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile);
|
|
570
813
|
if (!spawned.ok) {
|
|
571
814
|
throw new Error(`ArkTS isolated Scene build failed (exit ${spawned.lastCode ?? 'unknown'}). ` +
|
|
572
|
-
'
|
|
815
|
+
'Unset HOMEGRAPH_ARKTS_ISOLATED to use the default in-process build, ' +
|
|
816
|
+
'or raise HOMEGRAPH_ARKTS_STACK_SIZES_KB (e.g. 65536,131072,262144)');
|
|
573
817
|
}
|
|
574
818
|
return markBatchCommittedAfterWorker(rootDir, triggerFile, etsFiles, batchKey);
|
|
575
819
|
}
|
|
@@ -605,12 +849,28 @@ function runArkTSBatch(rootDir, queries, triggerFile) {
|
|
|
605
849
|
return runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
|
|
606
850
|
}
|
|
607
851
|
/** Async batch build with parallel file reads during persist (used by indexAll). */
|
|
608
|
-
async function primeArkTSBatch(rootDir, queries, triggerFile) {
|
|
852
|
+
async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
|
|
609
853
|
const normalizedTrigger = normIndexPath(triggerFile);
|
|
610
854
|
const etsFiles = scanEtsFiles(rootDir);
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
855
|
+
const changed = options?.changedFiles
|
|
856
|
+
? [...options.changedFiles].map(normIndexPath).filter(Boolean)
|
|
857
|
+
: null;
|
|
858
|
+
if (changed) {
|
|
859
|
+
const resolution = resolveDirtyHarmonyModules(rootDir, changed);
|
|
860
|
+
if (resolution.mode === 'none') {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (resolution.mode === 'modules') {
|
|
864
|
+
await runArkTSBatchIncrementalModules(rootDir, queries, normalizedTrigger, etsFiles, resolution.moduleSrcPaths);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
// mode === 'full': fall through to full rebuild (ignore in-memory cache).
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
const cached = tryReturnCachedBatch(rootDir, normalizedTrigger, etsFiles);
|
|
871
|
+
if (cached) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
614
874
|
}
|
|
615
875
|
const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
|
|
616
876
|
batchTriggerFile = null;
|
|
@@ -620,7 +880,7 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
|
|
|
620
880
|
batchBuildCommitted = false;
|
|
621
881
|
return;
|
|
622
882
|
}
|
|
623
|
-
if (shouldUseIsolatedArkTSBuild(
|
|
883
|
+
if (shouldUseIsolatedArkTSBuild()) {
|
|
624
884
|
runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
|
|
625
885
|
return;
|
|
626
886
|
}
|
|
@@ -646,6 +906,56 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
|
|
|
646
906
|
(0, context_1.setArktsBatchRunning)(false);
|
|
647
907
|
}
|
|
648
908
|
}
|
|
909
|
+
async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, etsFiles, moduleSrcPaths) {
|
|
910
|
+
// Isolated worker has no module-subset protocol yet — fall back to full.
|
|
911
|
+
if (shouldUseIsolatedArkTSBuild()) {
|
|
912
|
+
const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
|
|
913
|
+
runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
batchTriggerFile = null;
|
|
917
|
+
batchPersistedPaths = new Set();
|
|
918
|
+
(0, context_1.setArktsBatchRunning)(true);
|
|
919
|
+
try {
|
|
920
|
+
reportBatchProgress('scene', 0, etsFiles.length);
|
|
921
|
+
const index = buildArkTSIndexByModule(rootDir, etsFiles, { targetModuleSrcPaths: moduleSrcPaths });
|
|
922
|
+
if (index.fileResults.size === 0) {
|
|
923
|
+
const fatal = index.errors.find((e) => e.severity === 'error');
|
|
924
|
+
// Fall back to full project batch when the subset produced nothing.
|
|
925
|
+
const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
|
|
926
|
+
if (fatal) {
|
|
927
|
+
process.stderr.write(`\n\x1b[33m ArkTS: incremental modules failed (${fatal.message}); full rebuild\x1b[0m\n`);
|
|
928
|
+
}
|
|
929
|
+
const full = buildArkTSIndex(rootDir, etsFiles);
|
|
930
|
+
if (full.fileResults.size === 0) {
|
|
931
|
+
throw new Error(fatal?.message ?? 'ArkTS incremental batch produced no indexed files');
|
|
932
|
+
}
|
|
933
|
+
queries.deleteArkTSCrossFileCallEdges();
|
|
934
|
+
await persistBatchResultsAsync(rootDir, queries, full);
|
|
935
|
+
if (full.crossFileEdges.length > 0) {
|
|
936
|
+
const valid = full.crossFileEdges.filter((e) => full.nodeIds.has(e.source) && full.nodeIds.has(e.target));
|
|
937
|
+
if (valid.length > 0)
|
|
938
|
+
queries.insertEdges(valid);
|
|
939
|
+
}
|
|
940
|
+
commitArkTSBatch(rootDir, batchKey, etsFiles, full, triggerFile);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
persistIncrementalModuleResults(rootDir, queries, index);
|
|
944
|
+
if (index.crossFileEdges.length > 0) {
|
|
945
|
+
const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
|
|
946
|
+
if (valid.length > 0) {
|
|
947
|
+
queries.insertEdges(valid);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
// Unique key so a partial index is never mistaken for a full-project cache hit.
|
|
951
|
+
const batchKey = `incremental:${moduleSrcPaths.join(',')}`;
|
|
952
|
+
commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
|
|
953
|
+
process.stderr.write(`\n\x1b[33m ArkTS: incremental module reindex done (${moduleSrcPaths.join(', ')})\x1b[0m\n`);
|
|
954
|
+
}
|
|
955
|
+
finally {
|
|
956
|
+
(0, context_1.setArktsBatchRunning)(false);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
649
959
|
/** True when this `.ets` file was already written by the in-memory ArkTS batch. */
|
|
650
960
|
function isArkTSBatchPersisted(filePath) {
|
|
651
961
|
return batchPersistedPaths.has(normIndexPath(filePath));
|
|
@@ -965,20 +1275,22 @@ function arkEdge(source, target, kind, extra) {
|
|
|
965
1275
|
...extra,
|
|
966
1276
|
};
|
|
967
1277
|
}
|
|
968
|
-
function
|
|
1278
|
+
function collectComponentScopeFromFiles(files) {
|
|
969
1279
|
const scope = [];
|
|
970
|
-
for (const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1280
|
+
for (const file of files) {
|
|
1281
|
+
for (const cls of file.getClasses()) {
|
|
1282
|
+
if (cls.isDefaultArkClass())
|
|
1283
|
+
continue;
|
|
1284
|
+
if (cls.hasComponentDecorator()) {
|
|
1285
|
+
scope.push(cls);
|
|
1286
|
+
}
|
|
975
1287
|
}
|
|
976
1288
|
}
|
|
977
1289
|
return scope;
|
|
978
1290
|
}
|
|
979
|
-
function
|
|
1291
|
+
function collectNonUiRtaEntryPointsFromFiles(files) {
|
|
980
1292
|
const entries = [];
|
|
981
|
-
for (const file of
|
|
1293
|
+
for (const file of files) {
|
|
982
1294
|
const defaultClass = file.getDefaultClass();
|
|
983
1295
|
for (const method of defaultClass.getMethods()) {
|
|
984
1296
|
if (shouldSkipArkMethod(method))
|
|
@@ -992,13 +1304,17 @@ function collectNonUiRtaEntryPoints(scene) {
|
|
|
992
1304
|
return entries;
|
|
993
1305
|
}
|
|
994
1306
|
function resolveRtaEntryPoints(scene) {
|
|
1307
|
+
return resolveRtaEntryPointsFromFiles(scene, scene.getFiles());
|
|
1308
|
+
}
|
|
1309
|
+
/** RTA entries scoped to the given files (one Harmony module while it is loaded). */
|
|
1310
|
+
function resolveRtaEntryPointsFromFiles(scene, files) {
|
|
995
1311
|
const explicit = scene.getEntryPoints();
|
|
996
1312
|
if (explicit.length > 0) {
|
|
997
1313
|
return { entryPoints: explicit, dummyMain: null };
|
|
998
1314
|
}
|
|
999
|
-
if (
|
|
1315
|
+
if (filesHaveArkUiEntries(files)) {
|
|
1000
1316
|
try {
|
|
1001
|
-
const componentScope =
|
|
1317
|
+
const componentScope = collectComponentScopeFromFiles(files);
|
|
1002
1318
|
const dummy = new arkanalyzer_1.DummyMainCreater(scene, undefined, componentScope.length > 0 ? componentScope : undefined, true);
|
|
1003
1319
|
dummy.createDummyMain();
|
|
1004
1320
|
const dummyMain = dummy.getDummyMain();
|
|
@@ -1008,11 +1324,11 @@ function resolveRtaEntryPoints(scene) {
|
|
|
1008
1324
|
// fall through
|
|
1009
1325
|
}
|
|
1010
1326
|
}
|
|
1011
|
-
const fallback =
|
|
1327
|
+
const fallback = collectNonUiRtaEntryPointsFromFiles(files);
|
|
1012
1328
|
return { entryPoints: fallback, dummyMain: null };
|
|
1013
1329
|
}
|
|
1014
|
-
function
|
|
1015
|
-
for (const file of
|
|
1330
|
+
function filesHaveArkUiEntries(files) {
|
|
1331
|
+
for (const file of files) {
|
|
1016
1332
|
for (const cls of file.getClasses()) {
|
|
1017
1333
|
if (cls.isDefaultArkClass())
|
|
1018
1334
|
continue;
|
|
@@ -1049,12 +1365,319 @@ function viewTreeLineFromStmt(stmt) {
|
|
|
1049
1365
|
return stmt?.getOriginFullPosition()?.getFirstLine() ?? 1;
|
|
1050
1366
|
}
|
|
1051
1367
|
function isViewTreeClassSignature(sig) {
|
|
1052
|
-
|
|
1368
|
+
// Prefer instanceof; duck-type fallback if duplicate arkanalyzer copies break instanceof.
|
|
1369
|
+
if (sig instanceof arkanalyzer_1.ClassSignature)
|
|
1370
|
+
return true;
|
|
1371
|
+
if (sig instanceof arkanalyzer_1.MethodSignature)
|
|
1372
|
+
return false;
|
|
1373
|
+
const anySig = sig;
|
|
1374
|
+
// Require ClassSignature shape — MethodSignature-like and other sigs must not enter getClass().
|
|
1375
|
+
return (typeof anySig.getClassName === 'function' &&
|
|
1376
|
+
typeof anySig.getDeclaringFileSignature === 'function' &&
|
|
1377
|
+
typeof anySig.getMethodSubSignature !== 'function');
|
|
1378
|
+
}
|
|
1379
|
+
/** Skip anonymous / synthetic Ark names for name→id fallback maps. */
|
|
1380
|
+
function isUsableArkTypeName(name) {
|
|
1381
|
+
return !!name && !name.startsWith('%') && !name.startsWith('<');
|
|
1382
|
+
}
|
|
1383
|
+
/** Escape a literal for use inside a RegExp source. */
|
|
1384
|
+
function escapeRegExpLiteral(s) {
|
|
1385
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* ArkUI framework IR receivers — `View.create` / `Column.pop` / `If.branch`
|
|
1389
|
+
* are synthesizer noise, not project call sites. Never seed name-match from them.
|
|
1390
|
+
*/
|
|
1391
|
+
const ARKUI_IR_CALL_RECEIVERS = new Set([
|
|
1392
|
+
'View',
|
|
1393
|
+
'If',
|
|
1394
|
+
'ForEach',
|
|
1395
|
+
'LazyForEach',
|
|
1396
|
+
'Repeat',
|
|
1397
|
+
'Column',
|
|
1398
|
+
'Row',
|
|
1399
|
+
'Stack',
|
|
1400
|
+
'Flex',
|
|
1401
|
+
'Grid',
|
|
1402
|
+
'List',
|
|
1403
|
+
'Swiper',
|
|
1404
|
+
'Tabs',
|
|
1405
|
+
'TabContent',
|
|
1406
|
+
'Button',
|
|
1407
|
+
'Text',
|
|
1408
|
+
'Image',
|
|
1409
|
+
'Scroll',
|
|
1410
|
+
'WaterFlow',
|
|
1411
|
+
'RelativeContainer',
|
|
1412
|
+
'Blank',
|
|
1413
|
+
'Divider',
|
|
1414
|
+
'Span',
|
|
1415
|
+
'Canvas',
|
|
1416
|
+
'XComponent',
|
|
1417
|
+
]);
|
|
1418
|
+
const ARKUI_IR_CALL_METHODS = new Set(['create', 'pop', 'branch', 'iterator']);
|
|
1419
|
+
const ARKTS_STDLIB_CALL_RECEIVERS = new Set([
|
|
1420
|
+
'Array',
|
|
1421
|
+
'Map',
|
|
1422
|
+
'Set',
|
|
1423
|
+
'Object',
|
|
1424
|
+
'Promise',
|
|
1425
|
+
'JSON',
|
|
1426
|
+
'String',
|
|
1427
|
+
'Number',
|
|
1428
|
+
'Boolean',
|
|
1429
|
+
'Error',
|
|
1430
|
+
'Date',
|
|
1431
|
+
'Math',
|
|
1432
|
+
'RegExp',
|
|
1433
|
+
'Symbol',
|
|
1434
|
+
]);
|
|
1435
|
+
function isJunkArkCallSeed(className, methodName) {
|
|
1436
|
+
if (ARKTS_STDLIB_CALL_RECEIVERS.has(className))
|
|
1437
|
+
return true;
|
|
1438
|
+
if (ARKUI_IR_CALL_RECEIVERS.has(className) && ARKUI_IR_CALL_METHODS.has(methodName)) {
|
|
1439
|
+
return true;
|
|
1440
|
+
}
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* CFG must not name-link these across modules: each is a unique `Class::method`
|
|
1445
|
+
* key that still fans out to tens of thousands of invoke sites (singletons,
|
|
1446
|
+
* loggers, constructors) — far denser than RTA reachability on unlimited indexes.
|
|
1447
|
+
*/
|
|
1448
|
+
const ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST = new Set([
|
|
1449
|
+
'getInstance',
|
|
1450
|
+
'constructor',
|
|
1451
|
+
'toString',
|
|
1452
|
+
'valueOf',
|
|
1453
|
+
'showInfo',
|
|
1454
|
+
'showError',
|
|
1455
|
+
'showWarn',
|
|
1456
|
+
'showDebug',
|
|
1457
|
+
'show',
|
|
1458
|
+
'create',
|
|
1459
|
+
'pop',
|
|
1460
|
+
'push',
|
|
1461
|
+
'splice',
|
|
1462
|
+
'isEmpty',
|
|
1463
|
+
'getContext',
|
|
1464
|
+
'copyFrom',
|
|
1465
|
+
// High fan-out helpers seen densifying same-module CFG on scene_board
|
|
1466
|
+
'getGridOccupyStatus',
|
|
1467
|
+
'isInStatusForGrid',
|
|
1468
|
+
'isInStatus',
|
|
1469
|
+
'getStatus',
|
|
1470
|
+
'isOccupied',
|
|
1471
|
+
'isFree',
|
|
1472
|
+
'markStatus',
|
|
1473
|
+
'markGridForCellAndSpan',
|
|
1474
|
+
'createRelativePath',
|
|
1475
|
+
'dealWantParams',
|
|
1476
|
+
'getPreviewContentWidth',
|
|
1477
|
+
'getIconArray',
|
|
1478
|
+
'getSizeX',
|
|
1479
|
+
'getSizeY',
|
|
1480
|
+
]);
|
|
1481
|
+
/**
|
|
1482
|
+
* When ArkAnalyzer leaves a cross-module static call as `%unk/.use()`, recover
|
|
1483
|
+
* `Lib.use` from the invoke's original source. Only PascalCase receivers — so
|
|
1484
|
+
* `obj.foo()` / `this.bar()` stay unresolved rather than bare-method magnets.
|
|
1485
|
+
*
|
|
1486
|
+
* Trust short snippets only. Allow `return` / simple `const|let|var x =` prefixes
|
|
1487
|
+
* (`const x = Lib.use(b)` is the common modular IR original-text shape).
|
|
1488
|
+
*/
|
|
1489
|
+
function recoverClassMethodFromStmt(stmt, methodName) {
|
|
1490
|
+
if (!stmt || !methodName)
|
|
1491
|
+
return null;
|
|
1492
|
+
let text;
|
|
1493
|
+
try {
|
|
1494
|
+
text = stmt.getOriginalText?.();
|
|
1495
|
+
}
|
|
1496
|
+
catch {
|
|
1497
|
+
text = undefined;
|
|
1498
|
+
}
|
|
1499
|
+
if (!text)
|
|
1500
|
+
return null;
|
|
1501
|
+
const trimmed = text.trim();
|
|
1502
|
+
if (trimmed.length === 0 || trimmed.length > 160)
|
|
1503
|
+
return null;
|
|
1504
|
+
// Mock / matcher wrappers are not real call seeds.
|
|
1505
|
+
if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
|
|
1506
|
+
return null;
|
|
1507
|
+
const re = new RegExp(`(?:^|(?:return\\s+)|(?:(?:const|let|var)\\s+[\\w$]+\\s*=\\s*))([A-Z][\\w$]*)\\.${escapeRegExpLiteral(methodName)}\\s*\\(`);
|
|
1508
|
+
const m = trimmed.match(re);
|
|
1509
|
+
return m?.[1] ? `${m[1]}.${methodName}` : null;
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Free-function call left as `%unk/.getOpaque()`: short text still contains
|
|
1513
|
+
* `getOpaque(` (not `Class.getOpaque`). Caller must import-gate + unique name.
|
|
1514
|
+
*/
|
|
1515
|
+
function recoverFreeFunctionFromStmt(stmt, methodName) {
|
|
1516
|
+
if (!stmt || !methodName)
|
|
1517
|
+
return null;
|
|
1518
|
+
if (!/^[a-z][\w$]*$/.test(methodName))
|
|
1519
|
+
return null;
|
|
1520
|
+
let text;
|
|
1521
|
+
try {
|
|
1522
|
+
text = stmt.getOriginalText?.();
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
text = undefined;
|
|
1526
|
+
}
|
|
1527
|
+
if (!text)
|
|
1528
|
+
return null;
|
|
1529
|
+
const trimmed = text.trim();
|
|
1530
|
+
if (trimmed.length === 0 || trimmed.length > 160)
|
|
1531
|
+
return null;
|
|
1532
|
+
if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
|
|
1533
|
+
return null;
|
|
1534
|
+
// Reject Class.method — that path belongs to recoverClassMethodFromStmt.
|
|
1535
|
+
if (new RegExp(`[A-Z][\\w$]*\\.${escapeRegExpLiteral(methodName)}\\s*\\(`).test(trimmed)) {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
const re = new RegExp(`(?:^|[^\\w.$])${escapeRegExpLiteral(methodName)}\\s*\\(`);
|
|
1539
|
+
return re.test(trimmed) ? methodName : null;
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Instance invoke whose MethodSignature is `%unk` may still carry a typed base:
|
|
1543
|
+
* ClassType (full sig) or UnclearReferenceType (type name string only — common
|
|
1544
|
+
* when deps are SIGNATURES-only).
|
|
1545
|
+
*/
|
|
1546
|
+
function recoverClassMethodFromInstanceType(invoke, methodName) {
|
|
1547
|
+
if (!invoke || !methodName)
|
|
1548
|
+
return null;
|
|
1549
|
+
try {
|
|
1550
|
+
const base = invoke.getBase?.();
|
|
1551
|
+
if (!base)
|
|
1552
|
+
return null;
|
|
1553
|
+
const type = base.getType?.();
|
|
1554
|
+
if (!type)
|
|
1555
|
+
return null;
|
|
1556
|
+
const fromClassSig = type.getClassSignature?.()?.getClassName?.() ??
|
|
1557
|
+
'';
|
|
1558
|
+
if (isUsableArkTypeName(fromClassSig) && !fromClassSig.includes('%')) {
|
|
1559
|
+
return `${fromClassSig}.${methodName}`;
|
|
1560
|
+
}
|
|
1561
|
+
// UnclearReferenceType: getName() === "Base" (no file path).
|
|
1562
|
+
const fromName = type.getName?.() ??
|
|
1563
|
+
'';
|
|
1564
|
+
if (isUsableArkTypeName(fromName) && !fromName.includes('%') && /^[A-Z]/.test(fromName)) {
|
|
1565
|
+
return `${fromName}.${methodName}`;
|
|
1566
|
+
}
|
|
1567
|
+
return null;
|
|
1568
|
+
}
|
|
1569
|
+
catch {
|
|
1570
|
+
return null;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Modular IR often models `Lib.use` as an instance invoke with base Local named
|
|
1575
|
+
* `Lib` and type unknown — recover from the PascalCase base name.
|
|
1576
|
+
*/
|
|
1577
|
+
function recoverClassMethodFromBaseName(invoke, methodName) {
|
|
1578
|
+
if (!invoke || !methodName)
|
|
1579
|
+
return null;
|
|
1580
|
+
try {
|
|
1581
|
+
const base = invoke.getBase?.();
|
|
1582
|
+
if (!base)
|
|
1583
|
+
return null;
|
|
1584
|
+
const baseName = base.getName?.() ?? '';
|
|
1585
|
+
if (!isUsableArkTypeName(baseName) || !/^[A-Z]/.test(baseName) || baseName.includes('%')) {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
return `${baseName}.${methodName}`;
|
|
1589
|
+
}
|
|
1590
|
+
catch {
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Cross-module `%unk` invoke → `Class.method`, free function name, or
|
|
1596
|
+
* `instantiates` class. Never emit a bare instance-method name alone.
|
|
1597
|
+
*/
|
|
1598
|
+
function callReferenceNameFromSig(sig, opts) {
|
|
1599
|
+
try {
|
|
1600
|
+
const methodName = sig.getMethodSubSignature()?.getMethodName?.() ?? '';
|
|
1601
|
+
if (!methodName || methodName.startsWith('%') || methodName.startsWith('<'))
|
|
1602
|
+
return null;
|
|
1603
|
+
const className = sig.getDeclaringClassSignature()?.getClassName?.() ?? '';
|
|
1604
|
+
if (isUsableArkTypeName(className) && !className.includes('%')) {
|
|
1605
|
+
if (methodName === 'constructor') {
|
|
1606
|
+
return { name: className, kind: 'instantiates' };
|
|
1607
|
+
}
|
|
1608
|
+
if (isJunkArkCallSeed(className, methodName))
|
|
1609
|
+
return null;
|
|
1610
|
+
return { name: `${className}.${methodName}`, kind: 'calls' };
|
|
1611
|
+
}
|
|
1612
|
+
if (methodName === 'constructor')
|
|
1613
|
+
return null;
|
|
1614
|
+
const recovered = recoverClassMethodFromStmt(opts?.stmt, methodName) ??
|
|
1615
|
+
recoverClassMethodFromInstanceType(opts?.invoke, methodName) ??
|
|
1616
|
+
recoverClassMethodFromBaseName(opts?.invoke, methodName);
|
|
1617
|
+
if (recovered) {
|
|
1618
|
+
const dot = recovered.indexOf('.');
|
|
1619
|
+
if (dot > 0 && isJunkArkCallSeed(recovered.slice(0, dot), recovered.slice(dot + 1))) {
|
|
1620
|
+
return null;
|
|
1621
|
+
}
|
|
1622
|
+
return { name: recovered, kind: 'calls' };
|
|
1623
|
+
}
|
|
1624
|
+
const free = recoverFreeFunctionFromStmt(opts?.stmt, methodName);
|
|
1625
|
+
if (free)
|
|
1626
|
+
return { name: free, kind: 'calls', freeFunction: true };
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
catch {
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* True when a ClassSignature still has ArkAnalyzer's placeholder file
|
|
1635
|
+
* (`%unk`) — common on ViewTree stubs after a dependency was evicted before
|
|
1636
|
+
* type inference could rewrite `new LiveCardListView` to a real file path.
|
|
1637
|
+
*/
|
|
1638
|
+
function isUnclearArkClassSignature(sig) {
|
|
1639
|
+
try {
|
|
1640
|
+
const fileSig = sig.getDeclaringFileSignature();
|
|
1641
|
+
const fileName = fileSig.getFileName();
|
|
1642
|
+
const projectName = fileSig.getProjectName();
|
|
1643
|
+
if (fileName === '%unk' || projectName === '%unk')
|
|
1644
|
+
return true;
|
|
1645
|
+
if (fileName.includes('%unk') || projectName.includes('%unk'))
|
|
1646
|
+
return true;
|
|
1647
|
+
return sig.toString().includes('%unk');
|
|
1648
|
+
}
|
|
1649
|
+
catch {
|
|
1650
|
+
return true;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
function isUnclearArkMethodSignature(sig) {
|
|
1654
|
+
try {
|
|
1655
|
+
return isUnclearArkClassSignature(sig.getDeclaringClassSignature());
|
|
1656
|
+
}
|
|
1657
|
+
catch {
|
|
1658
|
+
return true;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
/** Among candidate node ids, prefer a single `component` over same-name struct/class. */
|
|
1662
|
+
function pickPreferredClassNodeId(candidateIds, isComponent) {
|
|
1663
|
+
const ids = [...candidateIds];
|
|
1664
|
+
if (ids.length === 0)
|
|
1665
|
+
return null;
|
|
1666
|
+
if (ids.length === 1)
|
|
1667
|
+
return ids[0] ?? null;
|
|
1668
|
+
const components = ids.filter((id) => isComponent(id));
|
|
1669
|
+
if (components.length === 1)
|
|
1670
|
+
return components[0] ?? null;
|
|
1671
|
+
return null;
|
|
1053
1672
|
}
|
|
1054
|
-
|
|
1673
|
+
/** Walk ViewTree with cycle protection (shared/cloned nodes can form graphs). */
|
|
1674
|
+
function walkViewTree(node, visit, seen = new Set()) {
|
|
1675
|
+
if (seen.has(node))
|
|
1676
|
+
return;
|
|
1677
|
+
seen.add(node);
|
|
1055
1678
|
visit(node);
|
|
1056
1679
|
for (const child of node.children) {
|
|
1057
|
-
walkViewTree(child, visit);
|
|
1680
|
+
walkViewTree(child, visit, seen);
|
|
1058
1681
|
}
|
|
1059
1682
|
}
|
|
1060
1683
|
function indexViewTreeForClass(ctx, cls, result, relativePath) {
|
|
@@ -1084,7 +1707,7 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
|
|
|
1084
1707
|
ctx.addEdge(result, source, target, kind, relativePath, via, line);
|
|
1085
1708
|
};
|
|
1086
1709
|
for (const [field] of viewTree.getStateValues()) {
|
|
1087
|
-
const fieldId = ctx.
|
|
1710
|
+
const fieldId = ctx.resolveFieldNodeId(field);
|
|
1088
1711
|
if (fieldId) {
|
|
1089
1712
|
ctx.markFieldStateDecorators?.(field, fieldId, result);
|
|
1090
1713
|
const line = field.getOriginFullPosition?.()?.getFirstLine?.() ??
|
|
@@ -1096,45 +1719,83 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
|
|
|
1096
1719
|
walkViewTree(root, (node) => {
|
|
1097
1720
|
const sig = node.signature;
|
|
1098
1721
|
if (sig) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1722
|
+
try {
|
|
1723
|
+
if (isViewTreeClassSignature(sig)) {
|
|
1724
|
+
// Stub or live: prefer already-indexed sig→id (works after dep unload);
|
|
1725
|
+
// %unk stubs also fall back to class name / parent-file import.
|
|
1726
|
+
// Then fall back to live IR object maps when the class is still resident.
|
|
1727
|
+
// Guard getClass — a mis-typed signature must not abort the whole ViewTree walk.
|
|
1728
|
+
let childCls = null;
|
|
1729
|
+
try {
|
|
1730
|
+
childCls = ctx.scene.getClass(sig);
|
|
1731
|
+
}
|
|
1732
|
+
catch {
|
|
1733
|
+
childCls = null;
|
|
1734
|
+
}
|
|
1735
|
+
const childId = ctx.resolveClassIdBySig(sig, cls) ??
|
|
1736
|
+
(childCls ? ctx.resolveClassNodeId(childCls) : null);
|
|
1103
1737
|
if (childId) {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1738
|
+
let line = buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
|
|
1739
|
+
try {
|
|
1740
|
+
const firstAttr = node.attributes?.values()?.next()?.value;
|
|
1741
|
+
if (firstAttr)
|
|
1742
|
+
line = viewTreeLineFromStmt(firstAttr[0]);
|
|
1743
|
+
}
|
|
1744
|
+
catch {
|
|
1745
|
+
// keep build() line
|
|
1746
|
+
}
|
|
1108
1747
|
link(buildId, childId, 'references', 'child-component', line);
|
|
1109
1748
|
}
|
|
1110
1749
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1750
|
+
else {
|
|
1751
|
+
// MethodSignature: builder stub (sig only) or live ArkMethod.
|
|
1752
|
+
let builderMethod = null;
|
|
1753
|
+
try {
|
|
1754
|
+
builderMethod = ctx.scene.getMethod(sig);
|
|
1755
|
+
}
|
|
1756
|
+
catch {
|
|
1757
|
+
builderMethod = null;
|
|
1758
|
+
}
|
|
1759
|
+
const builderId = ctx.resolveMethodIdBySig(sig, cls) ??
|
|
1760
|
+
(builderMethod
|
|
1761
|
+
? ctx.ensureMethodNode(builderMethod, relativePath, result, classNodeId)
|
|
1762
|
+
: null);
|
|
1116
1763
|
if (builderId) {
|
|
1117
|
-
|
|
1764
|
+
const line = builderMethod
|
|
1765
|
+
? builderMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1
|
|
1766
|
+
: buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
|
|
1767
|
+
link(buildId, builderId, 'references', 'builder', line);
|
|
1118
1768
|
}
|
|
1119
1769
|
}
|
|
1120
1770
|
}
|
|
1771
|
+
catch {
|
|
1772
|
+
// Skip this node's signature edge; keep walking siblings/children.
|
|
1773
|
+
}
|
|
1121
1774
|
}
|
|
1122
1775
|
if (node.stateValuesTransfer) {
|
|
1123
1776
|
for (const [childField, parentValue] of node.stateValuesTransfer) {
|
|
1124
|
-
const childFieldId = ctx.
|
|
1777
|
+
const childFieldId = ctx.resolveFieldNodeId(childField);
|
|
1125
1778
|
if (parentValue instanceof arkanalyzer_1.ArkField) {
|
|
1126
1779
|
const via = stateTransferViaForField(childField);
|
|
1127
1780
|
if (!via || !childFieldId)
|
|
1128
1781
|
continue;
|
|
1129
1782
|
ctx.markFieldStateDecorators?.(childField, childFieldId, result);
|
|
1130
|
-
const parentFieldId = ctx.
|
|
1783
|
+
const parentFieldId = ctx.resolveFieldNodeId(parentValue);
|
|
1131
1784
|
if (parentFieldId) {
|
|
1132
1785
|
ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
|
|
1133
1786
|
link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
|
|
1134
1787
|
}
|
|
1135
1788
|
}
|
|
1136
1789
|
else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
|
|
1137
|
-
|
|
1790
|
+
let builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
|
|
1791
|
+
if (!builderId) {
|
|
1792
|
+
try {
|
|
1793
|
+
builderId = ctx.resolveMethodIdBySig(parentValue.getSignature());
|
|
1794
|
+
}
|
|
1795
|
+
catch {
|
|
1796
|
+
builderId = null;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1138
1799
|
if (childFieldId && builderId) {
|
|
1139
1800
|
link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
|
|
1140
1801
|
}
|
|
@@ -1146,12 +1807,15 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
|
|
|
1146
1807
|
continue;
|
|
1147
1808
|
const line = viewTreeLineFromStmt(stmt);
|
|
1148
1809
|
for (const v of values) {
|
|
1149
|
-
|
|
1810
|
+
const isMethodSig = v instanceof arkanalyzer_1.MethodSignature ||
|
|
1811
|
+
(v != null &&
|
|
1812
|
+
typeof v.getMethodSubSignature === 'function');
|
|
1813
|
+
if (!isMethodSig)
|
|
1150
1814
|
continue;
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1815
|
+
const methodSig = v;
|
|
1816
|
+
const handler = ctx.scene.getMethod(methodSig);
|
|
1817
|
+
const handlerId = ctx.resolveMethodIdBySig(methodSig, cls) ??
|
|
1818
|
+
(handler ? ctx.ensureMethodNode(handler, relativePath, result, classNodeId) : null);
|
|
1155
1819
|
if (handlerId)
|
|
1156
1820
|
link(buildId, handlerId, 'references', attr, line);
|
|
1157
1821
|
}
|
|
@@ -1166,100 +1830,532 @@ class ArkTSAdapter {
|
|
|
1166
1830
|
classToId = new Map();
|
|
1167
1831
|
componentToId = new Map();
|
|
1168
1832
|
fieldToId = new Map();
|
|
1833
|
+
/** Stable across module unload — signature string → HomeGraph node id. */
|
|
1834
|
+
methodSigToId = new Map();
|
|
1835
|
+
classSigToId = new Map();
|
|
1836
|
+
fieldSigToId = new Map();
|
|
1837
|
+
/**
|
|
1838
|
+
* Name → candidate node ids for ViewTree stub stitch when the signature is still
|
|
1839
|
+
* `@%unk/%unk: ClassName` after dependency eviction. Prefer `component` over
|
|
1840
|
+
* same-name struct/class; ambiguous multi-component names use the importer.
|
|
1841
|
+
*/
|
|
1842
|
+
classNameToIds = new Map();
|
|
1843
|
+
classIdFilePath = new Map();
|
|
1844
|
+
classIdIsComponent = new Map();
|
|
1845
|
+
/** `ClassName::methodName` → candidate method node ids (unclear MethodSignature stubs). */
|
|
1846
|
+
methodNameKeyToIds = new Map();
|
|
1847
|
+
/** Bare default-class function name → ids (unique import-gated CFG bridge). */
|
|
1848
|
+
functionNameToIds = new Map();
|
|
1849
|
+
methodIdFilePath = new Map();
|
|
1850
|
+
emittedEdgeKeys = new Set();
|
|
1169
1851
|
nodeIds = new Set();
|
|
1170
1852
|
fileResults = new Map();
|
|
1171
1853
|
crossFileEdges = [];
|
|
1854
|
+
/** Per-module `@dummyMain@…` created during analyseByModule (for scene aggregator). */
|
|
1855
|
+
moduleDummyMains = [];
|
|
1172
1856
|
constructor(rootDir, scannedFiles) {
|
|
1173
1857
|
this.rootDir = rootDir;
|
|
1174
1858
|
this.scanned = new Set(scannedFiles);
|
|
1175
1859
|
}
|
|
1176
1860
|
build(scene) {
|
|
1177
1861
|
this.scene = scene;
|
|
1178
|
-
const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
|
|
1179
1862
|
for (const arkFile of scene.getFiles()) {
|
|
1180
|
-
|
|
1181
|
-
if (!this.scanned.has(relativePath))
|
|
1182
|
-
continue;
|
|
1183
|
-
if (!isArkAnalyzerSourcePath(relativePath))
|
|
1184
|
-
continue;
|
|
1185
|
-
this.indexFile(arkFile);
|
|
1863
|
+
this.indexScannedFile(arkFile);
|
|
1186
1864
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1865
|
+
this.indexViewTreesFromFiles(scene.getFiles());
|
|
1866
|
+
this.finalizeCallGraph(scene);
|
|
1867
|
+
return this.toBatchIndex();
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Index one Harmony module while loaded (baseline = this module):
|
|
1871
|
+
* symbols → rebind resident deps → ViewTree/RTA from this module (B→A via
|
|
1872
|
+
* live IR or sig→id maps for already-indexed unloaded deps).
|
|
1873
|
+
*/
|
|
1874
|
+
indexModule(module, scene) {
|
|
1875
|
+
this.scene = scene;
|
|
1876
|
+
const files = [...module.getFilesMap().values()];
|
|
1877
|
+
const moduleFiles = new Set(files.map((f) => normalizeRelPath(this.rootDir, f.getFilePath())).filter(Boolean));
|
|
1878
|
+
for (const arkFile of files) {
|
|
1879
|
+
this.indexScannedFile(arkFile);
|
|
1880
|
+
}
|
|
1881
|
+
// Rebind still-resident deps so live getClass/getMethod works when topo kept them warm.
|
|
1882
|
+
this.bindLiveMapsFromResident(scene);
|
|
1883
|
+
// Incremental / first-in-topo: populate sig→id maps from other loaded modules
|
|
1884
|
+
// (deps at SIGNATURES) without persisting those files into this batch.
|
|
1885
|
+
this.seedMapsFromOtherLoadedFiles(scene, moduleFiles);
|
|
1886
|
+
this.indexViewTreesFromFiles(files);
|
|
1887
|
+
this.ingestModuleCallGraph(scene, module, files, moduleFiles);
|
|
1888
|
+
// CFG: only narrow `%unk` cross-module bridge (no same-module CFG calls).
|
|
1889
|
+
this.stitchModuleCfgCallsAndRefs(files, moduleFiles);
|
|
1890
|
+
// Drop live IR object keys so ArkAnalyzer can GC unloaded modules; keep sig→id maps.
|
|
1891
|
+
this.releaseLiveIrMaps();
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Index symbols from already-loaded non-target modules into sig/name maps only.
|
|
1895
|
+
* Used so incremental BODIES targets can still resolve cross-module RTA/ViewTree
|
|
1896
|
+
* edges against deps that stay at SIGNATURES (and are not re-persisted).
|
|
1897
|
+
*/
|
|
1898
|
+
seedMapsFromOtherLoadedFiles(scene, excludeFiles) {
|
|
1899
|
+
const seeded = [];
|
|
1900
|
+
for (const mod of scene.getModules()) {
|
|
1901
|
+
let filesMap;
|
|
1902
|
+
try {
|
|
1903
|
+
filesMap = mod.getFilesMap();
|
|
1904
|
+
}
|
|
1905
|
+
catch {
|
|
1189
1906
|
continue;
|
|
1190
|
-
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1907
|
+
}
|
|
1908
|
+
for (const arkFile of filesMap.values()) {
|
|
1909
|
+
const rel = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
1910
|
+
if (!rel || excludeFiles.has(rel) || this.fileResults.has(rel))
|
|
1911
|
+
continue;
|
|
1912
|
+
if (!this.scanned.has(rel) || !isArkAnalyzerSourcePath(rel))
|
|
1913
|
+
continue;
|
|
1914
|
+
this.indexScannedFile(arkFile);
|
|
1915
|
+
if (this.fileResults.has(rel))
|
|
1916
|
+
seeded.push(rel);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
for (const rel of seeded) {
|
|
1920
|
+
this.fileResults.delete(rel);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
bindLiveMapsFromResident(scene) {
|
|
1924
|
+
const cache = scene.getModuleCache();
|
|
1925
|
+
if (!cache)
|
|
1926
|
+
return;
|
|
1927
|
+
const files = [];
|
|
1928
|
+
for (const id of cache.getLoadedModules()) {
|
|
1929
|
+
const mod = scene.getModule(id);
|
|
1930
|
+
if (!mod || mod.getModuleType() === arkanalyzer_1.ModuleType.SDK)
|
|
1193
1931
|
continue;
|
|
1194
|
-
|
|
1195
|
-
if (!result)
|
|
1932
|
+
if (mod.getLoadState() < arkanalyzer_1.ModuleLoadState.SIGNATURES)
|
|
1196
1933
|
continue;
|
|
1934
|
+
files.push(...mod.getFilesMap().values());
|
|
1935
|
+
}
|
|
1936
|
+
if (files.length > 0)
|
|
1937
|
+
this.bindLiveMapsFromFiles(files);
|
|
1938
|
+
}
|
|
1939
|
+
/** Re-attach live Ark* objects to already-indexed HomeGraph node ids (sig maps). */
|
|
1940
|
+
bindLiveMapsFromFiles(files) {
|
|
1941
|
+
for (const arkFile of files) {
|
|
1942
|
+
const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
1943
|
+
if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
|
|
1944
|
+
continue;
|
|
1945
|
+
this.bindLiveClass(arkFile.getDefaultClass());
|
|
1946
|
+
for (const cls of arkFile.getClasses()) {
|
|
1947
|
+
this.bindLiveClass(cls);
|
|
1948
|
+
}
|
|
1949
|
+
for (const ns of arkFile.getNamespaces()) {
|
|
1950
|
+
this.bindLiveNamespace(ns);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
bindLiveNamespace(ns) {
|
|
1955
|
+
this.bindLiveClass(ns.getDefaultClass());
|
|
1956
|
+
for (const cls of ns.getClasses()) {
|
|
1957
|
+
this.bindLiveClass(cls);
|
|
1958
|
+
}
|
|
1959
|
+
for (const child of ns.getNamespaces()) {
|
|
1960
|
+
this.bindLiveNamespace(child);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
bindLiveClass(cls) {
|
|
1964
|
+
try {
|
|
1965
|
+
const classSig = cls.getSignature().toString();
|
|
1966
|
+
const classId = this.classSigToId.get(classSig);
|
|
1967
|
+
if (classId) {
|
|
1968
|
+
this.classToId.set(cls, classId);
|
|
1969
|
+
if (cls.hasComponentDecorator()) {
|
|
1970
|
+
this.componentToId.set(cls, classId);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
for (const method of cls.getMethods(true)) {
|
|
1974
|
+
try {
|
|
1975
|
+
const mid = this.methodSigToId.get(method.getSignature().toString());
|
|
1976
|
+
if (mid)
|
|
1977
|
+
this.methodToId.set(method, mid);
|
|
1978
|
+
}
|
|
1979
|
+
catch {
|
|
1980
|
+
// skip
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
for (const field of cls.getFields()) {
|
|
1984
|
+
const fid = this.fieldSigToId.get(fieldSigKey(classSig, field.getName()));
|
|
1985
|
+
if (fid)
|
|
1986
|
+
this.fieldToId.set(field, fid);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
catch {
|
|
1990
|
+
// signature unavailable
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
finalizeCallGraph(scene) {
|
|
1994
|
+
this.scene = scene;
|
|
1995
|
+
// Modular path: intra-module RTA already ran per module. Do NOT run a
|
|
1996
|
+
// scene aggregator RTA — that re-walks overlapping reachable sets and was
|
|
1997
|
+
// a major wall-clock cost. Cross-module gaps link only via exact
|
|
1998
|
+
// signature / unique Class.method maps (no unresolved name-match seeds).
|
|
1999
|
+
if (this.moduleDummyMains.length > 0) {
|
|
1197
2000
|
try {
|
|
1198
|
-
|
|
2001
|
+
for (const m of this.moduleDummyMains) {
|
|
2002
|
+
this.indexArkanalyzerDummyMain(m);
|
|
2003
|
+
}
|
|
1199
2004
|
}
|
|
1200
|
-
catch
|
|
1201
|
-
|
|
1202
|
-
result.errors.push({
|
|
1203
|
-
message: `ViewTree indexing skipped for ${cls.getName()}: ${message}`,
|
|
1204
|
-
filePath: relativePath,
|
|
1205
|
-
severity: 'warning',
|
|
1206
|
-
});
|
|
2005
|
+
catch {
|
|
2006
|
+
// ignore
|
|
1207
2007
|
}
|
|
2008
|
+
return;
|
|
1208
2009
|
}
|
|
2010
|
+
const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
|
|
1209
2011
|
if (dummyMain) {
|
|
1210
2012
|
this.indexArkanalyzerDummyMain(dummyMain);
|
|
1211
2013
|
}
|
|
2014
|
+
if (entryPoints.length === 0)
|
|
2015
|
+
return;
|
|
1212
2016
|
let callGraph;
|
|
1213
2017
|
try {
|
|
1214
|
-
callGraph =
|
|
1215
|
-
entryPoints.length > 0 ? scene.makeCallGraphRTA(entryPoints) : null;
|
|
2018
|
+
callGraph = scene.makeCallGraphRTA(entryPoints);
|
|
1216
2019
|
}
|
|
1217
2020
|
catch {
|
|
1218
|
-
|
|
2021
|
+
return;
|
|
1219
2022
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
2023
|
+
this.ingestCallGraph(callGraph, null);
|
|
2024
|
+
}
|
|
2025
|
+
ingestModuleCallGraph(scene, module, files, moduleFiles) {
|
|
2026
|
+
let entryPoints = [];
|
|
2027
|
+
let dummyMain = null;
|
|
2028
|
+
try {
|
|
2029
|
+
// Prefer AA's forModule when present; otherwise DIY with the public ctor
|
|
2030
|
+
// (module-scoped name + this module's classes).
|
|
2031
|
+
const forModule = arkanalyzer_1.DummyMainCreater.forModule;
|
|
2032
|
+
let creater;
|
|
2033
|
+
if (typeof forModule === 'function') {
|
|
2034
|
+
creater = forModule(scene, module, true);
|
|
2035
|
+
}
|
|
2036
|
+
else {
|
|
2037
|
+
const classScope = collectComponentScopeFromFiles(files);
|
|
2038
|
+
const moduleName = module.getModuleName() || 'module';
|
|
2039
|
+
creater = new arkanalyzer_1.DummyMainCreater(scene, `@dummyMain@${moduleName}`, classScope.length > 0 ? classScope : undefined, true);
|
|
2040
|
+
}
|
|
2041
|
+
creater.createDummyMain();
|
|
2042
|
+
dummyMain = creater.getDummyMain();
|
|
2043
|
+
if (dummyMain) {
|
|
2044
|
+
entryPoints = [dummyMain.getSignature()];
|
|
2045
|
+
this.moduleDummyMains.push(dummyMain);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
catch {
|
|
2049
|
+
// Fall back to file-scoped DummyMain (components only) if creation fails.
|
|
2050
|
+
const resolved = resolveRtaEntryPointsFromFiles(scene, files);
|
|
2051
|
+
entryPoints = resolved.entryPoints;
|
|
2052
|
+
dummyMain = resolved.dummyMain;
|
|
2053
|
+
}
|
|
2054
|
+
if (dummyMain) {
|
|
2055
|
+
this.indexArkanalyzerDummyMain(dummyMain);
|
|
2056
|
+
}
|
|
2057
|
+
if (entryPoints.length === 0)
|
|
2058
|
+
return;
|
|
2059
|
+
let callGraph;
|
|
2060
|
+
try {
|
|
2061
|
+
callGraph = scene.makeCallGraphRTA(entryPoints);
|
|
2062
|
+
}
|
|
2063
|
+
catch {
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
this.ingestCallGraph(callGraph, moduleFiles);
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Keep ArkAnalyzer RTA edges whose **caller** is in the current module.
|
|
2070
|
+
*
|
|
2071
|
+
* - Same-module: live IR id, exact MethodSignature map, or unique Class.method
|
|
2072
|
+
* name fallback (path-drift).
|
|
2073
|
+
* - Cross-module: **exact MethodSignature string or live callee only** — no
|
|
2074
|
+
* Class.method name fallback (that + CFG densified scene_board to ~400k
|
|
2075
|
+
* ark calls vs ~76k on an unlimited RTA index).
|
|
2076
|
+
*/
|
|
2077
|
+
ingestCallGraph(callGraph, moduleFiles) {
|
|
2078
|
+
for (const edge of callGraph.getCallEdges()) {
|
|
2079
|
+
const callerArk = callGraph.getArkMethodByFuncID(edge.getSrcID());
|
|
2080
|
+
const calleeArk = callGraph.getArkMethodByFuncID(edge.getDstID());
|
|
2081
|
+
const callerSig = callGraph.getMethodByFuncID(edge.getSrcID());
|
|
2082
|
+
const calleeSig = callGraph.getMethodByFuncID(edge.getDstID());
|
|
2083
|
+
if (callerArk &&
|
|
2084
|
+
shouldSkipArkMethod(callerArk) &&
|
|
2085
|
+
!isAnonymousArkMethod(callerArk) &&
|
|
2086
|
+
!callerArk.getDeclaringArkClass().isDefaultArkClass() &&
|
|
2087
|
+
!callerArk.isGenerated()) {
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
const callerFile = (callerArk ? relPathForArkFile(this.rootDir, callerArk.getDeclaringArkFile()) : null) ??
|
|
2091
|
+
(callerSig ? this.relPathFromMethodSignature(callerSig) : null) ??
|
|
2092
|
+
'';
|
|
2093
|
+
if (moduleFiles && callerFile && !moduleFiles.has(callerFile)) {
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
const calleeFile = (calleeArk ? relPathForArkFile(this.rootDir, calleeArk.getDeclaringArkFile()) : null) ??
|
|
2097
|
+
(calleeSig ? this.relPathFromMethodSignature(calleeSig) : null);
|
|
2098
|
+
const callerId = (callerArk ? this.resolveMethodNodeId(callerArk) : null) ??
|
|
2099
|
+
(callerSig ? this.resolveMethodIdBySigExact(callerSig) : null);
|
|
2100
|
+
if (!callerId)
|
|
2101
|
+
continue;
|
|
2102
|
+
const sameModule = !!moduleFiles && !!calleeFile && moduleFiles.has(calleeFile);
|
|
2103
|
+
const dispatch = edge.hasDirectCall() ? 'direct' : 'indirect';
|
|
2104
|
+
// Full-scene path (no moduleFiles): keep prior resolveMethodIdBySig behavior.
|
|
2105
|
+
if (!moduleFiles) {
|
|
2106
|
+
const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
|
|
2107
|
+
(calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
|
|
2108
|
+
if (!calleeId)
|
|
1235
2109
|
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);
|
|
2110
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
if (!sameModule) {
|
|
2114
|
+
const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
|
|
2115
|
+
(calleeSig ? this.resolveMethodIdBySigExact(calleeSig) : null);
|
|
2116
|
+
if (calleeId) {
|
|
2117
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
|
|
1250
2118
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
|
|
2122
|
+
(calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
|
|
2123
|
+
if (!calleeId)
|
|
2124
|
+
continue;
|
|
2125
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* CFG call stitch (modular): no same-module CFG call edges (those were the
|
|
2130
|
+
* ~150k `direct` flood past unlimited RTA). Only the narrow cross-module
|
|
2131
|
+
* `%unk` bridge for `return Lib.use(…)`-style static calls.
|
|
2132
|
+
*/
|
|
2133
|
+
stitchModuleCfgCallsAndRefs(files, _moduleFiles) {
|
|
2134
|
+
for (const arkFile of files) {
|
|
2135
|
+
const callerFile = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
2136
|
+
const visitClass = (cls) => {
|
|
2137
|
+
for (const method of cls.getMethods(true)) {
|
|
2138
|
+
if (shouldSkipArkMethod(method) &&
|
|
2139
|
+
!isAnonymousArkMethod(method) &&
|
|
2140
|
+
!method.isGenerated()) {
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
const callerId = this.resolveMethodNodeId(method) ??
|
|
2144
|
+
(() => {
|
|
2145
|
+
try {
|
|
2146
|
+
return this.methodSigToId.get(method.getSignature().toString()) ?? null;
|
|
2147
|
+
}
|
|
2148
|
+
catch {
|
|
2149
|
+
return null;
|
|
2150
|
+
}
|
|
2151
|
+
})();
|
|
2152
|
+
if (!callerId)
|
|
2153
|
+
continue;
|
|
2154
|
+
let cfg;
|
|
2155
|
+
try {
|
|
2156
|
+
cfg = method.getCfg();
|
|
2157
|
+
}
|
|
2158
|
+
catch {
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2161
|
+
if (!cfg)
|
|
2162
|
+
continue;
|
|
2163
|
+
for (const stmt of cfg.getStmts()) {
|
|
2164
|
+
let invoke;
|
|
2165
|
+
try {
|
|
2166
|
+
invoke = stmt.getInvokeExpr();
|
|
2167
|
+
}
|
|
2168
|
+
catch {
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
if (!invoke)
|
|
2172
|
+
continue;
|
|
2173
|
+
let calleeSig;
|
|
2174
|
+
try {
|
|
2175
|
+
calleeSig = invoke.getMethodSignature();
|
|
2176
|
+
}
|
|
2177
|
+
catch {
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
if (!calleeSig)
|
|
2181
|
+
continue;
|
|
2182
|
+
// Skip clear same-module paths — RTA owns those. Only `%unk` /
|
|
2183
|
+
// missing-path invokes may take the narrow cross-module bridge.
|
|
2184
|
+
const calleeFile = this.relPathFromMethodSignature(calleeSig);
|
|
2185
|
+
if (calleeFile)
|
|
2186
|
+
continue;
|
|
2187
|
+
this.tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, method, stmt, invoke, arkFile);
|
|
2188
|
+
}
|
|
1253
2189
|
}
|
|
2190
|
+
};
|
|
2191
|
+
visitClass(arkFile.getDefaultClass());
|
|
2192
|
+
for (const cls of arkFile.getClasses())
|
|
2193
|
+
visitClass(cls);
|
|
2194
|
+
const walkNs = (ns) => {
|
|
2195
|
+
visitClass(ns.getDefaultClass());
|
|
2196
|
+
for (const cls of ns.getClasses())
|
|
2197
|
+
visitClass(cls);
|
|
2198
|
+
for (const child of ns.getNamespaces())
|
|
2199
|
+
walkNs(child);
|
|
2200
|
+
};
|
|
2201
|
+
for (const ns of arkFile.getNamespaces())
|
|
2202
|
+
walkNs(ns);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk) {
|
|
2206
|
+
const edgeKey = `${callerId}\0${calleeId}\0calls\0${dispatch}`;
|
|
2207
|
+
if (this.emittedEdgeKeys.has(edgeKey))
|
|
2208
|
+
return;
|
|
2209
|
+
if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0direct`))
|
|
2210
|
+
return;
|
|
2211
|
+
if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0indirect`))
|
|
2212
|
+
return;
|
|
2213
|
+
this.emittedEdgeKeys.add(edgeKey);
|
|
2214
|
+
const callEdge = arkEdge(callerId, calleeId, 'calls', {
|
|
2215
|
+
metadata: {
|
|
2216
|
+
synthesizedBy: 'arkanalyzer',
|
|
2217
|
+
dispatch,
|
|
2218
|
+
sourceFile: callerFile || undefined,
|
|
2219
|
+
rtaEntry: callerArk?.isGenerated?.() && callerArk.getName() === '@dummyMain'
|
|
2220
|
+
? '@dummyMain'
|
|
2221
|
+
: undefined,
|
|
2222
|
+
},
|
|
2223
|
+
});
|
|
2224
|
+
const callerResult = callerFile ? this.fileResults.get(callerFile) : undefined;
|
|
2225
|
+
if (callerResult && this.nodeInFile(calleeId, callerFile)) {
|
|
2226
|
+
callerResult.edges.push(callEdge);
|
|
2227
|
+
}
|
|
2228
|
+
else {
|
|
2229
|
+
this.crossFileEdges.push(callEdge);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* CFG `%unk` bridge: recover Class.method / free function from invoke crumbs
|
|
2234
|
+
* (stmt text, UnclearReferenceType name, PascalCase base local), require
|
|
2235
|
+
* import + unique indexed target, skip fan-out blocklist. No unresolved refs.
|
|
2236
|
+
*/
|
|
2237
|
+
tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, callerArk, stmt, invoke, arkFile) {
|
|
2238
|
+
// Only for unclear / %unk signatures — clear typed cross-module invokes
|
|
2239
|
+
// stay RTA-only so we do not densify past unlimited RTA.
|
|
2240
|
+
if (!isUnclearArkMethodSignature(calleeSig)) {
|
|
2241
|
+
try {
|
|
2242
|
+
const className = calleeSig.getDeclaringClassSignature()?.getClassName?.() ?? '';
|
|
2243
|
+
if (isUsableArkTypeName(className) && !className.includes('%'))
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
catch {
|
|
2247
|
+
// treat as unclear
|
|
1254
2248
|
}
|
|
1255
2249
|
}
|
|
2250
|
+
const parsed = callReferenceNameFromSig(calleeSig, { stmt, invoke });
|
|
2251
|
+
if (!parsed || parsed.kind !== 'calls')
|
|
2252
|
+
return;
|
|
2253
|
+
if (parsed.freeFunction) {
|
|
2254
|
+
const fnName = parsed.name;
|
|
2255
|
+
if (!fnName || ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(fnName))
|
|
2256
|
+
return;
|
|
2257
|
+
if (!this.fileImportsLocalName(arkFile, fnName))
|
|
2258
|
+
return;
|
|
2259
|
+
const calleeId = this.resolveFunctionIdByName(fnName);
|
|
2260
|
+
if (!calleeId)
|
|
2261
|
+
return;
|
|
2262
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
const dot = parsed.name.indexOf('.');
|
|
2266
|
+
if (dot <= 0)
|
|
2267
|
+
return;
|
|
2268
|
+
const className = parsed.name.slice(0, dot);
|
|
2269
|
+
const methodName = parsed.name.slice(dot + 1);
|
|
2270
|
+
if (!className || !methodName)
|
|
2271
|
+
return;
|
|
2272
|
+
if (ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(methodName))
|
|
2273
|
+
return;
|
|
2274
|
+
if (isJunkArkCallSeed(className, methodName))
|
|
2275
|
+
return;
|
|
2276
|
+
if (!this.fileImportsLocalName(arkFile, className))
|
|
2277
|
+
return;
|
|
2278
|
+
const calleeId = this.resolveMethodIdByName(className, methodName);
|
|
2279
|
+
if (!calleeId)
|
|
2280
|
+
return;
|
|
2281
|
+
this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
|
|
2282
|
+
}
|
|
2283
|
+
fileImportsLocalName(arkFile, localName) {
|
|
2284
|
+
try {
|
|
2285
|
+
return !!arkFile.getImportInfoBy(localName);
|
|
2286
|
+
}
|
|
2287
|
+
catch {
|
|
2288
|
+
return false;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
/** Exact MethodSignature.toString() → node id (no Class.method name fallback). */
|
|
2292
|
+
resolveMethodIdBySigExact(sig) {
|
|
2293
|
+
try {
|
|
2294
|
+
return this.methodSigToId.get(sig.toString()) ?? null;
|
|
2295
|
+
}
|
|
2296
|
+
catch {
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
/** Project-relative path from a MethodSignature's declaring file (may be %unk). */
|
|
2301
|
+
relPathFromMethodSignature(sig) {
|
|
2302
|
+
try {
|
|
2303
|
+
const fileName = sig.getDeclaringClassSignature().getDeclaringFileSignature().getFileName();
|
|
2304
|
+
if (!fileName || fileName === '%unk' || fileName.includes('%unk'))
|
|
2305
|
+
return null;
|
|
2306
|
+
return normalizeRelPath(this.rootDir, fileName);
|
|
2307
|
+
}
|
|
2308
|
+
catch {
|
|
2309
|
+
return null;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
releaseLiveIrMaps() {
|
|
2313
|
+
this.methodToId.clear();
|
|
2314
|
+
this.classToId.clear();
|
|
2315
|
+
this.componentToId.clear();
|
|
2316
|
+
this.fieldToId.clear();
|
|
2317
|
+
}
|
|
2318
|
+
toBatchIndex(errors = []) {
|
|
1256
2319
|
return {
|
|
1257
2320
|
fileResults: this.fileResults,
|
|
1258
2321
|
crossFileEdges: this.crossFileEdges,
|
|
1259
2322
|
nodeIds: this.nodeIds,
|
|
1260
|
-
errors: [],
|
|
2323
|
+
errors: [...errors],
|
|
1261
2324
|
};
|
|
1262
2325
|
}
|
|
2326
|
+
indexScannedFile(arkFile) {
|
|
2327
|
+
const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
2328
|
+
if (!this.scanned.has(relativePath))
|
|
2329
|
+
return;
|
|
2330
|
+
if (!isArkAnalyzerSourcePath(relativePath))
|
|
2331
|
+
return;
|
|
2332
|
+
this.indexFile(arkFile);
|
|
2333
|
+
}
|
|
2334
|
+
indexViewTreesFromFiles(files) {
|
|
2335
|
+
for (const arkFile of files) {
|
|
2336
|
+
const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
|
|
2337
|
+
if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
|
|
2338
|
+
continue;
|
|
2339
|
+
const result = this.fileResults.get(relativePath);
|
|
2340
|
+
if (!result)
|
|
2341
|
+
continue;
|
|
2342
|
+
for (const cls of arkFile.getClasses()) {
|
|
2343
|
+
if (!cls.hasViewTree())
|
|
2344
|
+
continue;
|
|
2345
|
+
try {
|
|
2346
|
+
indexViewTreeForClass(this.viewTreeContext(), cls, result, relativePath);
|
|
2347
|
+
}
|
|
2348
|
+
catch (e) {
|
|
2349
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
2350
|
+
result.errors.push({
|
|
2351
|
+
message: `ViewTree indexing skipped for ${cls.getName()}: ${message}`,
|
|
2352
|
+
filePath: relativePath,
|
|
2353
|
+
severity: 'warning',
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
1263
2359
|
/** Index ArkAnalyzer's in-scene @dummyMain (DummyMainCreater output), not a HomeGraph synthetic node. */
|
|
1264
2360
|
indexArkanalyzerDummyMain(dummyMain) {
|
|
1265
2361
|
const result = this.ensureFileResult(ARKANALYZER_DUMMY_FILE, 1);
|
|
@@ -1414,6 +2510,13 @@ class ArkTSAdapter {
|
|
|
1414
2510
|
});
|
|
1415
2511
|
this.addNode(result, classNode);
|
|
1416
2512
|
this.classToId.set(cls, classNode.id);
|
|
2513
|
+
try {
|
|
2514
|
+
this.classSigToId.set(cls.getSignature().toString(), classNode.id);
|
|
2515
|
+
}
|
|
2516
|
+
catch {
|
|
2517
|
+
// signature unavailable
|
|
2518
|
+
}
|
|
2519
|
+
this.rememberClassName(displayName, classNode.id, relativePath, false);
|
|
1417
2520
|
result.edges.push(arkEdge(parentId, classNode.id, 'contains'));
|
|
1418
2521
|
this.indexDecorators(result, classNode.id, cls, relativePath, language, line);
|
|
1419
2522
|
if (cls.isAnonymousClass()) {
|
|
@@ -1434,6 +2537,13 @@ class ArkTSAdapter {
|
|
|
1434
2537
|
const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, { ...modelModifiersToNodeExtras(cls) });
|
|
1435
2538
|
this.addNode(result, componentNode);
|
|
1436
2539
|
this.componentToId.set(cls, componentNode.id);
|
|
2540
|
+
try {
|
|
2541
|
+
this.classSigToId.set(cls.getSignature().toString(), componentNode.id);
|
|
2542
|
+
}
|
|
2543
|
+
catch {
|
|
2544
|
+
// signature unavailable
|
|
2545
|
+
}
|
|
2546
|
+
this.rememberClassName(displayName, componentNode.id, relativePath, true);
|
|
1437
2547
|
result.edges.push(arkEdge(classNode.id, componentNode.id, 'contains'));
|
|
1438
2548
|
}
|
|
1439
2549
|
const superName = cls.getSuperClassName();
|
|
@@ -1518,6 +2628,12 @@ class ArkTSAdapter {
|
|
|
1518
2628
|
});
|
|
1519
2629
|
this.addNode(result, fieldNode);
|
|
1520
2630
|
this.fieldToId.set(field, fieldNode.id);
|
|
2631
|
+
try {
|
|
2632
|
+
this.fieldSigToId.set(fieldSigKey(cls.getSignature().toString(), name), fieldNode.id);
|
|
2633
|
+
}
|
|
2634
|
+
catch {
|
|
2635
|
+
// signature unavailable
|
|
2636
|
+
}
|
|
1521
2637
|
result.edges.push(arkEdge(parentId, fieldNode.id, 'contains'));
|
|
1522
2638
|
this.indexDecorators(result, fieldNode.id, field, relativePath, language, line);
|
|
1523
2639
|
}
|
|
@@ -1536,6 +2652,9 @@ class ArkTSAdapter {
|
|
|
1536
2652
|
addEdge: (result, source, target, kind, callerFile, via, line) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line),
|
|
1537
2653
|
ensureMethodNode: (method, relativePath, result, parentId) => this.ensureMethodNode(method, relativePath, result, parentId),
|
|
1538
2654
|
resolveClassNodeId: (cls) => this.resolveClassNodeId(cls),
|
|
2655
|
+
resolveClassIdBySig: (sig, importer) => this.resolveClassIdBySig(sig, importer),
|
|
2656
|
+
resolveMethodIdBySig: (sig, importer) => this.resolveMethodIdBySig(sig, importer),
|
|
2657
|
+
resolveFieldNodeId: (field) => this.resolveFieldNodeId(field),
|
|
1539
2658
|
markFieldStateDecorators: (field, fieldNodeId, result) => {
|
|
1540
2659
|
const kinds = stateDecoratorKinds(field);
|
|
1541
2660
|
if (kinds.length === 0)
|
|
@@ -1547,7 +2666,170 @@ class ArkTSAdapter {
|
|
|
1547
2666
|
};
|
|
1548
2667
|
}
|
|
1549
2668
|
resolveClassNodeId(cls) {
|
|
1550
|
-
|
|
2669
|
+
const live = this.componentToId.get(cls) ?? this.classToId.get(cls);
|
|
2670
|
+
if (live)
|
|
2671
|
+
return live;
|
|
2672
|
+
try {
|
|
2673
|
+
return this.classSigToId.get(cls.getSignature().toString()) ?? null;
|
|
2674
|
+
}
|
|
2675
|
+
catch {
|
|
2676
|
+
return null;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
rememberClassName(name, nodeId, filePath, isComponent) {
|
|
2680
|
+
if (!isUsableArkTypeName(name))
|
|
2681
|
+
return;
|
|
2682
|
+
let set = this.classNameToIds.get(name);
|
|
2683
|
+
if (!set) {
|
|
2684
|
+
set = new Set();
|
|
2685
|
+
this.classNameToIds.set(name, set);
|
|
2686
|
+
}
|
|
2687
|
+
set.add(nodeId);
|
|
2688
|
+
this.classIdFilePath.set(nodeId, filePath);
|
|
2689
|
+
if (isComponent)
|
|
2690
|
+
this.classIdIsComponent.set(nodeId, true);
|
|
2691
|
+
}
|
|
2692
|
+
rememberMethodName(className, methodName, nodeId, filePath) {
|
|
2693
|
+
const key = `${className}::${methodName}`;
|
|
2694
|
+
let set = this.methodNameKeyToIds.get(key);
|
|
2695
|
+
if (!set) {
|
|
2696
|
+
set = new Set();
|
|
2697
|
+
this.methodNameKeyToIds.set(key, set);
|
|
2698
|
+
}
|
|
2699
|
+
set.add(nodeId);
|
|
2700
|
+
this.methodIdFilePath.set(nodeId, filePath);
|
|
2701
|
+
}
|
|
2702
|
+
rememberFunctionName(functionName, nodeId, filePath) {
|
|
2703
|
+
if (!isUsableArkTypeName(functionName) || !/^[a-z]/.test(functionName))
|
|
2704
|
+
return;
|
|
2705
|
+
let set = this.functionNameToIds.get(functionName);
|
|
2706
|
+
if (!set) {
|
|
2707
|
+
set = new Set();
|
|
2708
|
+
this.functionNameToIds.set(functionName, set);
|
|
2709
|
+
}
|
|
2710
|
+
set.add(nodeId);
|
|
2711
|
+
this.methodIdFilePath.set(nodeId, filePath);
|
|
2712
|
+
}
|
|
2713
|
+
resolveFunctionIdByName(functionName) {
|
|
2714
|
+
const ids = this.functionNameToIds.get(functionName);
|
|
2715
|
+
if (!ids || ids.size !== 1)
|
|
2716
|
+
return null;
|
|
2717
|
+
return [...ids][0] ?? null;
|
|
2718
|
+
}
|
|
2719
|
+
resolveClassIdByName(className, importer) {
|
|
2720
|
+
const ids = this.classNameToIds.get(className);
|
|
2721
|
+
if (!ids || ids.size === 0)
|
|
2722
|
+
return null;
|
|
2723
|
+
let candidates = [...ids];
|
|
2724
|
+
// Same-file components (ForEach item builders, local @Component) have no import —
|
|
2725
|
+
// prefer ids from the importer's file before treating the name as ambiguous.
|
|
2726
|
+
if (importer) {
|
|
2727
|
+
try {
|
|
2728
|
+
const importerPath = normalizeRelPath(this.rootDir, importer.getDeclaringArkFile().getFilePath());
|
|
2729
|
+
const sameFile = candidates.filter((id) => this.classIdFilePath.get(id) === importerPath);
|
|
2730
|
+
if (sameFile.length > 0)
|
|
2731
|
+
candidates = sameFile;
|
|
2732
|
+
}
|
|
2733
|
+
catch {
|
|
2734
|
+
// keep full candidate set
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
const preferred = pickPreferredClassNodeId(candidates, (id) => !!this.classIdIsComponent.get(id));
|
|
2738
|
+
if (preferred)
|
|
2739
|
+
return preferred;
|
|
2740
|
+
if (!importer)
|
|
2741
|
+
return null;
|
|
2742
|
+
try {
|
|
2743
|
+
const importInfo = importer.getDeclaringArkFile().getImportInfoBy(className);
|
|
2744
|
+
if (!importInfo)
|
|
2745
|
+
return null;
|
|
2746
|
+
const exportInfo = importInfo.getLazyExportInfo?.() ?? null;
|
|
2747
|
+
const arkExport = exportInfo?.getArkExport?.() ?? null;
|
|
2748
|
+
if (arkExport instanceof arkanalyzer_1.ArkClass) {
|
|
2749
|
+
const byLive = this.resolveClassNodeId(arkExport);
|
|
2750
|
+
if (byLive)
|
|
2751
|
+
return byLive;
|
|
2752
|
+
}
|
|
2753
|
+
const from = importInfo.getFrom?.();
|
|
2754
|
+
if (!from)
|
|
2755
|
+
return null;
|
|
2756
|
+
const fromBase = path.basename(from).replace(/\.(ets|ts|d\.ts)$/i, '');
|
|
2757
|
+
const byPath = [...ids].filter((id) => {
|
|
2758
|
+
const fp = this.classIdFilePath.get(id);
|
|
2759
|
+
if (!fp)
|
|
2760
|
+
return false;
|
|
2761
|
+
const norm = fp.replace(/\\/g, '/');
|
|
2762
|
+
const fromNorm = from.replace(/\\/g, '/');
|
|
2763
|
+
return (norm.includes(fromNorm) ||
|
|
2764
|
+
fromNorm.includes(norm) ||
|
|
2765
|
+
path.basename(norm, path.extname(norm)) === fromBase ||
|
|
2766
|
+
path.basename(norm, path.extname(norm)) === className);
|
|
2767
|
+
});
|
|
2768
|
+
return pickPreferredClassNodeId(byPath, (id) => !!this.classIdIsComponent.get(id));
|
|
2769
|
+
}
|
|
2770
|
+
catch {
|
|
2771
|
+
return null;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
resolveMethodIdByName(className, methodName) {
|
|
2775
|
+
const key = `${className}::${methodName}`;
|
|
2776
|
+
const ids = this.methodNameKeyToIds.get(key);
|
|
2777
|
+
if (!ids || ids.size !== 1)
|
|
2778
|
+
return null;
|
|
2779
|
+
return [...ids][0] ?? null;
|
|
2780
|
+
}
|
|
2781
|
+
resolveClassIdBySig(sig, importer) {
|
|
2782
|
+
try {
|
|
2783
|
+
const exact = this.classSigToId.get(sig.toString());
|
|
2784
|
+
if (exact)
|
|
2785
|
+
return exact;
|
|
2786
|
+
}
|
|
2787
|
+
catch {
|
|
2788
|
+
// fall through to name fallback when possible
|
|
2789
|
+
}
|
|
2790
|
+
// Name fallback for %unk stubs AND clear-but-unmapped sig strings (path drift).
|
|
2791
|
+
// Same-file / import disambiguation happens inside resolveClassIdByName.
|
|
2792
|
+
try {
|
|
2793
|
+
const className = sig.getClassName();
|
|
2794
|
+
return className ? this.resolveClassIdByName(className, importer) : null;
|
|
2795
|
+
}
|
|
2796
|
+
catch {
|
|
2797
|
+
return null;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
resolveMethodIdBySig(sig, _importer) {
|
|
2801
|
+
try {
|
|
2802
|
+
const exact = this.methodSigToId.get(sig.toString());
|
|
2803
|
+
if (exact)
|
|
2804
|
+
return exact;
|
|
2805
|
+
}
|
|
2806
|
+
catch {
|
|
2807
|
+
// fall through
|
|
2808
|
+
}
|
|
2809
|
+
// Name fallback for %unk stubs AND path-drifted but clear signatures so
|
|
2810
|
+
// RTA edges to signature-only / unloaded callees still map to indexed nodes.
|
|
2811
|
+
try {
|
|
2812
|
+
const className = sig.getDeclaringClassSignature().getClassName();
|
|
2813
|
+
const methodName = sig.getMethodSubSignature().getMethodName();
|
|
2814
|
+
if (!className || !methodName)
|
|
2815
|
+
return null;
|
|
2816
|
+
return this.resolveMethodIdByName(className, methodName);
|
|
2817
|
+
}
|
|
2818
|
+
catch {
|
|
2819
|
+
return null;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
resolveFieldNodeId(field) {
|
|
2823
|
+
const live = this.fieldToId.get(field);
|
|
2824
|
+
if (live)
|
|
2825
|
+
return live;
|
|
2826
|
+
try {
|
|
2827
|
+
const classSig = field.getDeclaringArkClass().getSignature().toString();
|
|
2828
|
+
return this.fieldSigToId.get(fieldSigKey(classSig, field.getName())) ?? null;
|
|
2829
|
+
}
|
|
2830
|
+
catch {
|
|
2831
|
+
return null;
|
|
2832
|
+
}
|
|
1551
2833
|
}
|
|
1552
2834
|
/** Parent node id for a method: owning class/component, or file for default-class methods. */
|
|
1553
2835
|
resolveMethodParentId(method, relativePath, result) {
|
|
@@ -1570,6 +2852,16 @@ class ArkTSAdapter {
|
|
|
1570
2852
|
const existing = this.methodToId.get(method);
|
|
1571
2853
|
if (existing)
|
|
1572
2854
|
return existing;
|
|
2855
|
+
try {
|
|
2856
|
+
const bySig = this.methodSigToId.get(method.getSignature().toString());
|
|
2857
|
+
if (bySig) {
|
|
2858
|
+
this.methodToId.set(method, bySig);
|
|
2859
|
+
return bySig;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
catch {
|
|
2863
|
+
// fall through
|
|
2864
|
+
}
|
|
1573
2865
|
if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
|
|
1574
2866
|
return null;
|
|
1575
2867
|
const displayName = arkMethodDisplayName(method);
|
|
@@ -1594,6 +2886,10 @@ class ArkTSAdapter {
|
|
|
1594
2886
|
return this.methodToId.get(method) ?? null;
|
|
1595
2887
|
}
|
|
1596
2888
|
addViewTreeEdge(result, source, target, kind, callerFile, via, line) {
|
|
2889
|
+
const edgeKey = `${source}\0${target}\0${kind}\0${via}`;
|
|
2890
|
+
if (this.emittedEdgeKeys.has(edgeKey))
|
|
2891
|
+
return;
|
|
2892
|
+
this.emittedEdgeKeys.add(edgeKey);
|
|
1597
2893
|
const edge = arkEdge(source, target, kind, {
|
|
1598
2894
|
metadata: {
|
|
1599
2895
|
synthesizedBy: 'viewtree',
|
|
@@ -1612,6 +2908,16 @@ class ArkTSAdapter {
|
|
|
1612
2908
|
const existing = this.methodToId.get(method);
|
|
1613
2909
|
if (existing)
|
|
1614
2910
|
return existing;
|
|
2911
|
+
try {
|
|
2912
|
+
const bySig = this.methodSigToId.get(method.getSignature().toString());
|
|
2913
|
+
if (bySig) {
|
|
2914
|
+
this.methodToId.set(method, bySig);
|
|
2915
|
+
return bySig;
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
catch {
|
|
2919
|
+
// fall through
|
|
2920
|
+
}
|
|
1615
2921
|
if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
|
|
1616
2922
|
return null;
|
|
1617
2923
|
const displayName = arkMethodDisplayName(method);
|
|
@@ -1638,6 +2944,24 @@ class ArkTSAdapter {
|
|
|
1638
2944
|
});
|
|
1639
2945
|
this.addNode(result, methodNode);
|
|
1640
2946
|
this.methodToId.set(method, methodNode.id);
|
|
2947
|
+
try {
|
|
2948
|
+
this.methodSigToId.set(method.getSignature().toString(), methodNode.id);
|
|
2949
|
+
}
|
|
2950
|
+
catch {
|
|
2951
|
+
// signature unavailable
|
|
2952
|
+
}
|
|
2953
|
+
try {
|
|
2954
|
+
const ownerName = classDisplayName(cls);
|
|
2955
|
+
if (isUsableArkTypeName(ownerName) && isUsableArkTypeName(displayName)) {
|
|
2956
|
+
this.rememberMethodName(ownerName, displayName, methodNode.id, relativePath);
|
|
2957
|
+
}
|
|
2958
|
+
else if (cls.isDefaultArkClass() && kind === 'function') {
|
|
2959
|
+
this.rememberFunctionName(displayName, methodNode.id, relativePath);
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
catch {
|
|
2963
|
+
// class name unavailable
|
|
2964
|
+
}
|
|
1641
2965
|
result.edges.push(arkEdge(parentId, methodNode.id, 'contains'));
|
|
1642
2966
|
this.indexDecorators(result, methodNode.id, method, relativePath, language, line);
|
|
1643
2967
|
this.indexNativeNapiCalls(method, methodNode.id, relativePath, language, result);
|
|
@@ -1714,10 +3038,11 @@ function drainArkTSIndexNotices() {
|
|
|
1714
3038
|
arktsIndexNotices = [];
|
|
1715
3039
|
return out;
|
|
1716
3040
|
}
|
|
1717
|
-
function buildSceneConfig(rootDir) {
|
|
3041
|
+
function buildSceneConfig(rootDir, extra) {
|
|
1718
3042
|
return (0, arkanalyzer_1.buildSceneConfigFromProject)(rootDir, process.env.OHOS_SDK_HOME, {
|
|
1719
3043
|
supportFileExts: ['.ets', '.ts', '.d.ts'],
|
|
1720
3044
|
enableMethodBodyBuild: true,
|
|
3045
|
+
...(extra?.memoryLimitMB !== undefined ? { memoryLimitMB: extra.memoryLimitMB } : {}),
|
|
1721
3046
|
});
|
|
1722
3047
|
}
|
|
1723
3048
|
function tryBuildArkScene(rootDir) {
|
|
@@ -1742,22 +3067,148 @@ function emptyArkTSBatchIndex(errors) {
|
|
|
1742
3067
|
errors,
|
|
1743
3068
|
};
|
|
1744
3069
|
}
|
|
1745
|
-
|
|
3070
|
+
/**
|
|
3071
|
+
* Harmony multi-module path: load each PROJECT module to BODIES, index into
|
|
3072
|
+
* HomeGraph structures in the callback, let ArkAnalyzer evict under memoryLimitMB.
|
|
3073
|
+
* When `targetModuleSrcPaths` is set, only those modules are BODIES targets
|
|
3074
|
+
* (deps still load at SIGNATURES) — used for incremental sync.
|
|
3075
|
+
*/
|
|
3076
|
+
function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
|
|
3077
|
+
const errors = [];
|
|
3078
|
+
const memoryLimitMB = resolveArkTSSceneMemoryLimitMB();
|
|
3079
|
+
const sceneConfig = buildSceneConfig(rootDir, { memoryLimitMB });
|
|
3080
|
+
const scene = new arkanalyzer_1.Scene();
|
|
3081
|
+
const adapter = new ArkTSAdapter(rootDir, scannedFiles);
|
|
3082
|
+
let modulesIndexed = 0;
|
|
3083
|
+
const targetSrc = options?.targetModuleSrcPaths?.map(normalizeHarmonyModuleSrcPath);
|
|
3084
|
+
const targetSrcSet = targetSrc && targetSrc.length > 0 ? new Set(targetSrc) : null;
|
|
3085
|
+
try {
|
|
3086
|
+
scene.config(sceneConfig);
|
|
3087
|
+
const config = new arkanalyzer_1.ModuleAnalysisConfig();
|
|
3088
|
+
config.setLoadLevel(arkanalyzer_1.ModuleDepthLevel.BODIES);
|
|
3089
|
+
// SIGNATURES is enough for ViewTree stubs / types; BODIES on deps caused
|
|
3090
|
+
// each module's RTA to re-walk shared library CFGs (multi-hour indexes).
|
|
3091
|
+
config.setDependencyLoadLevel(arkanalyzer_1.ModuleDepthLevel.SIGNATURES);
|
|
3092
|
+
if (targetSrcSet) {
|
|
3093
|
+
// Prep + SDK only (no PROJECT targets) so we can resolve ModuleIDs.
|
|
3094
|
+
scene.analyseByModule(() => { }, new arkanalyzer_1.ModuleAnalysisConfig());
|
|
3095
|
+
const wantAbs = new Set([...targetSrcSet].map((src) => {
|
|
3096
|
+
const resolved = path.resolve(rootDir, src);
|
|
3097
|
+
try {
|
|
3098
|
+
return fs.realpathSync(resolved).toLowerCase();
|
|
3099
|
+
}
|
|
3100
|
+
catch {
|
|
3101
|
+
return resolved.toLowerCase();
|
|
3102
|
+
}
|
|
3103
|
+
}));
|
|
3104
|
+
const ids = [];
|
|
3105
|
+
for (const mod of scene.getModules()) {
|
|
3106
|
+
if (mod.getModuleType() !== arkanalyzer_1.ModuleType.PROJECT)
|
|
3107
|
+
continue;
|
|
3108
|
+
let modAbs;
|
|
3109
|
+
try {
|
|
3110
|
+
modAbs = fs.realpathSync(mod.getModulePath());
|
|
3111
|
+
}
|
|
3112
|
+
catch {
|
|
3113
|
+
modAbs = path.resolve(mod.getModulePath());
|
|
3114
|
+
}
|
|
3115
|
+
if (wantAbs.has(modAbs.toLowerCase())) {
|
|
3116
|
+
ids.push(scene.getModuleId(mod));
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
if (ids.length === 0) {
|
|
3120
|
+
errors.push({
|
|
3121
|
+
message: `ArkTS incremental: no modules matched [${[...targetSrcSet].join(', ')}]`,
|
|
3122
|
+
severity: 'warning',
|
|
3123
|
+
});
|
|
3124
|
+
return emptyArkTSBatchIndex(errors);
|
|
3125
|
+
}
|
|
3126
|
+
config.setTargetModuleIds(ids);
|
|
3127
|
+
process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB})...\x1b[0m\n`);
|
|
3128
|
+
}
|
|
3129
|
+
else {
|
|
3130
|
+
config.setIncludeType(arkanalyzer_1.ModuleType.PROJECT, true);
|
|
3131
|
+
process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule (PROJECT→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}, maxOldSpace=${(0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)()})...\x1b[0m\n`);
|
|
3132
|
+
}
|
|
3133
|
+
scene.analyseByModule((module, scn) => {
|
|
3134
|
+
adapter.indexModule(module, scn);
|
|
3135
|
+
modulesIndexed++;
|
|
3136
|
+
const name = module.getModuleName() || path.basename(module.getModulePath());
|
|
3137
|
+
process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name}\x1b[0m\n`);
|
|
3138
|
+
}, config);
|
|
3139
|
+
if (modulesIndexed === 0) {
|
|
3140
|
+
errors.push({
|
|
3141
|
+
message: 'ArkTS analyseByModule found no PROJECT modules (check build-profile.json5); falling back to full Scene build',
|
|
3142
|
+
severity: 'warning',
|
|
3143
|
+
});
|
|
3144
|
+
return emptyArkTSBatchIndex(errors);
|
|
3145
|
+
}
|
|
3146
|
+
try {
|
|
3147
|
+
adapter.finalizeCallGraph(scene);
|
|
3148
|
+
}
|
|
3149
|
+
catch (e) {
|
|
3150
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
3151
|
+
errors.push({
|
|
3152
|
+
message: `ArkTS modular leftover RTA skipped: ${message}`,
|
|
3153
|
+
severity: 'warning',
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
const index = adapter.toBatchIndex(errors);
|
|
3157
|
+
if (index.fileResults.size === 0) {
|
|
3158
|
+
errors.push({
|
|
3159
|
+
message: 'ArkTS analyseByModule indexed modules but no scanned source files matched',
|
|
3160
|
+
severity: 'warning',
|
|
3161
|
+
});
|
|
3162
|
+
return emptyArkTSBatchIndex(errors);
|
|
3163
|
+
}
|
|
3164
|
+
return index;
|
|
3165
|
+
}
|
|
3166
|
+
catch (e) {
|
|
3167
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
3168
|
+
errors.push({
|
|
3169
|
+
message: `ArkTS analyseByModule failed: ${message}; falling back to full Scene build`,
|
|
3170
|
+
severity: 'warning',
|
|
3171
|
+
});
|
|
3172
|
+
return emptyArkTSBatchIndex(errors);
|
|
3173
|
+
}
|
|
3174
|
+
finally {
|
|
3175
|
+
try {
|
|
3176
|
+
scene.dispose();
|
|
3177
|
+
}
|
|
3178
|
+
catch {
|
|
3179
|
+
// ignore dispose failures
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
function buildArkTSIndexFull(rootDir, scannedFiles, priorErrors = []) {
|
|
1746
3184
|
const { scene, errors } = tryBuildArkScene(rootDir);
|
|
3185
|
+
const allErrors = [...priorErrors, ...errors];
|
|
1747
3186
|
if (!scene) {
|
|
1748
|
-
return emptyArkTSBatchIndex(
|
|
3187
|
+
return emptyArkTSBatchIndex(allErrors);
|
|
1749
3188
|
}
|
|
1750
3189
|
try {
|
|
1751
3190
|
const adapter = new ArkTSAdapter(rootDir, scannedFiles);
|
|
1752
3191
|
const index = adapter.build(scene);
|
|
1753
|
-
index.errors.push(...
|
|
3192
|
+
index.errors.push(...allErrors);
|
|
1754
3193
|
return index;
|
|
1755
3194
|
}
|
|
1756
3195
|
catch (e) {
|
|
1757
3196
|
const message = e instanceof Error ? e.message : String(e);
|
|
1758
|
-
|
|
1759
|
-
return emptyArkTSBatchIndex(
|
|
3197
|
+
allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
|
|
3198
|
+
return emptyArkTSBatchIndex(allErrors);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
function buildArkTSIndex(rootDir, scannedFiles) {
|
|
3202
|
+
if (shouldUseModularArkTSBuild(rootDir)) {
|
|
3203
|
+
const modular = buildArkTSIndexByModule(rootDir, scannedFiles);
|
|
3204
|
+
const fatal = modular.errors.find((e) => e.severity === 'error');
|
|
3205
|
+
if (modular.fileResults.size > 0 && !fatal) {
|
|
3206
|
+
return modular;
|
|
3207
|
+
}
|
|
3208
|
+
// Warnings like "no PROJECT modules" / modular failure → full Scene fallback.
|
|
3209
|
+
return buildArkTSIndexFull(rootDir, scannedFiles, modular.errors);
|
|
1760
3210
|
}
|
|
3211
|
+
return buildArkTSIndexFull(rootDir, scannedFiles);
|
|
1761
3212
|
}
|
|
1762
3213
|
// =============================================================================
|
|
1763
3214
|
// OHOS SDK input resolution + prebuilt API database indexing
|