homegraph 1.1.3 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -18
- package/dist/arkts/ohos-api-index.d.ts +15 -0
- package/dist/arkts/ohos-api-index.d.ts.map +1 -0
- package/dist/arkts/ohos-api-index.js +190 -0
- package/dist/arkts/ohos-api-index.js.map +1 -0
- package/dist/arkts/ohos-sdk-input.d.ts +36 -0
- package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
- package/dist/arkts/ohos-sdk-input.js +214 -0
- package/dist/arkts/ohos-sdk-input.js.map +1 -0
- package/dist/bin/command-supervision.d.ts +12 -0
- package/dist/bin/command-supervision.d.ts.map +1 -0
- package/dist/bin/command-supervision.js +86 -0
- package/dist/bin/command-supervision.js.map +1 -0
- package/dist/bin/homegraph.d.ts +1 -1
- package/dist/bin/homegraph.js +709 -168
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +85 -0
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +84 -5
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +217 -14
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +58 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +131 -2
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +473 -9
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +52 -1
- package/dist/db/wal-valve.d.ts +106 -0
- package/dist/db/wal-valve.d.ts.map +1 -0
- package/dist/db/wal-valve.js +208 -0
- package/dist/db/wal-valve.js.map +1 -0
- package/dist/directory.d.ts +36 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +274 -0
- package/dist/directory.js.map +1 -1
- package/dist/extraction/arkts-batch-worker.d.ts +2 -0
- package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
- package/dist/extraction/arkts-batch-worker.js +28 -0
- package/dist/extraction/arkts-batch-worker.js.map +1 -0
- package/dist/extraction/cfml-extractor.d.ts +107 -0
- package/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/dist/extraction/cfml-extractor.js +494 -0
- package/dist/extraction/cfml-extractor.js.map +1 -0
- package/dist/extraction/context.d.ts +11 -0
- package/dist/extraction/context.d.ts.map +1 -1
- package/dist/extraction/context.js +20 -0
- package/dist/extraction/context.js.map +1 -1
- package/dist/extraction/grammars.d.ts +25 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +177 -24
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +55 -2
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +923 -381
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
- package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
- package/dist/extraction/languages/arkts-state-decorators.js +26 -0
- package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
- package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.js +21 -6
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +98 -2
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +1290 -61
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +98 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +563 -1
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/cfquery.d.ts +12 -0
- package/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/dist/extraction/languages/cfquery.js +28 -0
- package/dist/extraction/languages/cfquery.js.map +1 -0
- package/dist/extraction/languages/cfscript.d.ts +3 -0
- package/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/dist/extraction/languages/cfscript.js +73 -0
- package/dist/extraction/languages/cfscript.js.map +1 -0
- package/dist/extraction/languages/cobol.d.ts +33 -0
- package/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/dist/extraction/languages/cobol.js +499 -0
- package/dist/extraction/languages/cobol.js.map +1 -0
- package/dist/extraction/languages/erlang.d.ts +3 -0
- package/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/dist/extraction/languages/erlang.js +350 -0
- package/dist/extraction/languages/erlang.js.map +1 -0
- package/dist/extraction/languages/index.d.ts.map +1 -1
- package/dist/extraction/languages/index.js +16 -0
- package/dist/extraction/languages/index.js.map +1 -1
- package/dist/extraction/languages/nix.d.ts +3 -0
- package/dist/extraction/languages/nix.d.ts.map +1 -0
- package/dist/extraction/languages/nix.js +294 -0
- package/dist/extraction/languages/nix.js.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.js +283 -0
- package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
- package/dist/extraction/languages/solidity.d.ts +3 -0
- package/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/dist/extraction/languages/solidity.js +293 -0
- package/dist/extraction/languages/solidity.js.map +1 -0
- package/dist/extraction/languages/terraform.d.ts +3 -0
- package/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/dist/extraction/languages/terraform.js +641 -0
- package/dist/extraction/languages/terraform.js.map +1 -0
- package/dist/extraction/languages/vbnet.d.ts +11 -0
- package/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/dist/extraction/languages/vbnet.js +141 -0
- package/dist/extraction/languages/vbnet.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/dist/extraction/mybatis-extractor.js +140 -38
- package/dist/extraction/mybatis-extractor.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +152 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -0
- package/dist/extraction/parse-pool.js +387 -0
- package/dist/extraction/parse-pool.js.map +1 -0
- package/dist/extraction/parse-worker.js +9 -2
- package/dist/extraction/parse-worker.js.map +1 -1
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/dist/extraction/tree-sitter-helpers.js +1 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/dist/extraction/tree-sitter-types.d.ts +20 -1
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +59 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +952 -37
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +77 -18
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +85 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +426 -9
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +42 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +97 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/daemon-paths.d.ts +30 -3
- package/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/dist/mcp/daemon-paths.js +50 -10
- package/dist/mcp/daemon-paths.js.map +1 -1
- package/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/dist/mcp/daemon-registry.js +7 -3
- package/dist/mcp/daemon-registry.js.map +1 -1
- package/dist/mcp/daemon.d.ts +73 -3
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +258 -40
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/dist/mcp/dynamic-boundaries.js +2 -1
- package/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/dist/mcp/early-ppid.d.ts +26 -0
- package/dist/mcp/early-ppid.d.ts.map +1 -0
- package/dist/mcp/early-ppid.js +29 -0
- package/dist/mcp/early-ppid.js.map +1 -0
- package/dist/mcp/engine.d.ts +16 -0
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +100 -2
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +66 -49
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +73 -13
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/memory-budget.d.ts +30 -0
- package/dist/mcp/memory-budget.d.ts.map +1 -0
- package/dist/mcp/memory-budget.js +123 -0
- package/dist/mcp/memory-budget.js.map +1 -0
- package/dist/mcp/ppid-watchdog.d.ts +18 -0
- package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
- package/dist/mcp/ppid-watchdog.js +37 -0
- package/dist/mcp/ppid-watchdog.js.map +1 -1
- package/dist/mcp/proxy.d.ts.map +1 -1
- package/dist/mcp/proxy.js +88 -4
- package/dist/mcp/proxy.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +56 -0
- package/dist/mcp/query-cache.d.ts.map +1 -0
- package/dist/mcp/query-cache.js +261 -0
- package/dist/mcp/query-cache.js.map +1 -0
- package/dist/mcp/query-pool.d.ts +128 -0
- package/dist/mcp/query-pool.d.ts.map +1 -0
- package/dist/mcp/query-pool.js +374 -0
- package/dist/mcp/query-pool.js.map +1 -0
- package/dist/mcp/query-worker.d.ts +24 -0
- package/dist/mcp/query-worker.d.ts.map +1 -0
- package/dist/mcp/query-worker.js +87 -0
- package/dist/mcp/query-worker.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +4 -29
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +39 -86
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/session.d.ts +14 -0
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +28 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/startup-handshake.d.ts +44 -0
- package/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/dist/mcp/startup-handshake.js +73 -0
- package/dist/mcp/startup-handshake.js.map +1 -0
- package/dist/mcp/tools.d.ts +179 -2
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +3027 -192
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/project-config.d.ts +58 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +141 -2
- package/dist/project-config.js.map +1 -1
- package/dist/reasoning/login.js +1 -1
- package/dist/reasoning/login.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -29
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +866 -148
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +9 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +1232 -183
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/cooperative-yield.d.ts +32 -0
- package/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/dist/resolution/cooperative-yield.js +42 -0
- package/dist/resolution/cooperative-yield.js.map +1 -0
- package/dist/resolution/frameworks/cics.d.ts +20 -0
- package/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/dist/resolution/frameworks/cics.js +90 -0
- package/dist/resolution/frameworks/cics.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +3 -0
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/dist/resolution/frameworks/java.js +14 -6
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/dist/resolution/frameworks/terraform.js +277 -0
- package/dist/resolution/frameworks/terraform.js.map +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/dist/resolution/goframe-synthesizer.js +8 -3
- package/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +7 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +129 -4
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +65 -3
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +477 -59
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +22 -3
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +657 -39
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/strip-comments.d.ts +1 -1
- package/dist/resolution/strip-comments.d.ts.map +1 -1
- package/dist/resolution/strip-comments.js +49 -0
- package/dist/resolution/strip-comments.js.map +1 -1
- package/dist/resolution/types.d.ts +29 -0
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.d.ts +10 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.js +142 -4
- package/dist/resolution/workspace-packages.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +60 -0
- package/dist/search/identifier-segments.d.ts.map +1 -0
- package/dist/search/identifier-segments.js +176 -0
- package/dist/search/identifier-segments.js.map +1 -0
- package/dist/search/query-utils.d.ts +201 -0
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +919 -2
- package/dist/search/query-utils.js.map +1 -1
- package/dist/spec/config.d.ts +7 -11
- package/dist/spec/config.d.ts.map +1 -1
- package/dist/spec/config.js +77 -48
- package/dist/spec/config.js.map +1 -1
- package/dist/spec/db/fragment-node.d.ts +1 -0
- package/dist/spec/db/fragment-node.d.ts.map +1 -1
- package/dist/spec/db/fragment-node.js +8 -0
- package/dist/spec/db/fragment-node.js.map +1 -1
- package/dist/spec/db/fts.d.ts +14 -0
- package/dist/spec/db/fts.d.ts.map +1 -1
- package/dist/spec/db/fts.js +43 -4
- package/dist/spec/db/fts.js.map +1 -1
- package/dist/spec/db/schema.d.ts +6 -3
- package/dist/spec/db/schema.d.ts.map +1 -1
- package/dist/spec/db/schema.js +36 -4
- package/dist/spec/db/schema.js.map +1 -1
- package/dist/spec/db/schema.sql +11 -1
- package/dist/spec/evolve/logic-checker.d.ts +2 -2
- package/dist/spec/evolve/logic-checker.d.ts.map +1 -1
- package/dist/spec/evolve/logic-checker.js +3 -27
- package/dist/spec/evolve/logic-checker.js.map +1 -1
- package/dist/spec/evolve/pipeline.d.ts +21 -19
- package/dist/spec/evolve/pipeline.d.ts.map +1 -1
- package/dist/spec/evolve/pipeline.js +284 -31
- package/dist/spec/evolve/pipeline.js.map +1 -1
- package/dist/spec/evolve/spec-rewriter.d.ts +2 -2
- package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
- package/dist/spec/evolve/spec-rewriter.js +7 -31
- package/dist/spec/evolve/spec-rewriter.js.map +1 -1
- package/dist/spec/graph/queries.d.ts +97 -0
- package/dist/spec/graph/queries.d.ts.map +1 -1
- package/dist/spec/graph/queries.js +225 -0
- package/dist/spec/graph/queries.js.map +1 -1
- package/dist/spec/llm/client.d.ts +29 -0
- package/dist/spec/llm/client.d.ts.map +1 -0
- package/dist/spec/llm/client.js +123 -0
- package/dist/spec/llm/client.js.map +1 -0
- package/dist/spec/llm/index.d.ts +3 -0
- package/dist/spec/llm/index.d.ts.map +1 -0
- package/dist/spec/llm/index.js +11 -0
- package/dist/spec/llm/index.js.map +1 -0
- package/dist/spec/llm/prompts.d.ts +13 -0
- package/dist/spec/llm/prompts.d.ts.map +1 -0
- package/dist/spec/llm/prompts.js +75 -0
- package/dist/spec/llm/prompts.js.map +1 -0
- package/dist/spec/mining/git-scanner.d.ts +12 -0
- package/dist/spec/mining/git-scanner.d.ts.map +1 -1
- package/dist/spec/mining/git-scanner.js +41 -0
- package/dist/spec/mining/git-scanner.js.map +1 -1
- package/dist/spec/mining/pipeline.d.ts.map +1 -1
- package/dist/spec/mining/pipeline.js +14 -1
- package/dist/spec/mining/pipeline.js.map +1 -1
- package/dist/spec/utils.d.ts +2 -1
- package/dist/spec/utils.d.ts.map +1 -1
- package/dist/spec/utils.js +3 -1
- package/dist/spec/utils.js.map +1 -1
- package/dist/sync/git-hooks.d.ts.map +1 -1
- package/dist/sync/git-hooks.js +2 -0
- package/dist/sync/git-hooks.js.map +1 -1
- package/dist/sync/watcher.d.ts +10 -5
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +51 -14
- package/dist/sync/watcher.js.map +1 -1
- package/dist/sync/worktree.d.ts +9 -0
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +45 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/types.d.ts +25 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/ui/shimmer-progress.d.ts +2 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -1
- package/dist/ui/shimmer-progress.js +19 -2
- package/dist/ui/shimmer-progress.js.map +1 -1
- package/dist/upgrade/index.d.ts +32 -0
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +163 -15
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/remove-binary.d.ts +87 -0
- package/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/dist/upgrade/remove-binary.js +289 -0
- package/dist/upgrade/remove-binary.js.map +1 -0
- package/dist/upgrade/update-check.d.ts +92 -0
- package/dist/upgrade/update-check.d.ts.map +1 -0
- package/dist/upgrade/update-check.js +258 -0
- package/dist/upgrade/update-check.js.map +1 -0
- package/package.json +4 -4
- package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
- package/scripts/agent-eval/run-all.sh +6 -0
- package/scripts/build-bundle.sh +5 -0
- package/scripts/exp_boundary_eval/README.md +247 -0
- package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
- package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
- package/scripts/exp_boundary_eval/_utils.py +1116 -0
- package/scripts/exp_boundary_eval/analyze.py +1313 -0
- package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
- package/scripts/exp_boundary_eval/run_all.py +378 -0
- package/scripts/exp_boundary_eval/run_one.py +165 -0
- package/scripts/exp_boundary_eval/run_session.py +158 -0
- package/scripts/exp_boundary_eval/setup.py +120 -0
- package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
- package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
- package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
- package/scripts/npm-shim.js +32 -3
- package/scripts/ohos-sdk-publish.mjs +133 -0
- package/scripts/qa_eval/README.md +0 -407
- package/scripts/qa_eval/_test_deveco_probe.py +0 -41
- package/scripts/qa_eval/agent_runner.py +0 -526
- package/scripts/qa_eval/data/.gitignore +0 -4
- package/scripts/qa_eval/data/test-set.jsonl +0 -2
- package/scripts/qa_eval/eval_metrics.py +0 -274
- package/scripts/qa_eval/external_agent.py +0 -976
- package/scripts/qa_eval/llm_config.py +0 -92
- package/scripts/qa_eval/memory_monitor.py +0 -132
- package/scripts/qa_eval/my_answer_accuracy.py +0 -187
- package/scripts/qa_eval/requirements.txt +0 -2
- package/scripts/qa_eval/run_pipeline.py +0 -804
- package/scripts/qa_eval/stats_efficiency.py +0 -279
- package/scripts/qa_eval/stats_scores.py +0 -207
|
@@ -12,12 +12,41 @@ exports.crossesKnownFamily = crossesKnownFamily;
|
|
|
12
12
|
exports.matchFunctionRef = matchFunctionRef;
|
|
13
13
|
exports.matchByExactName = matchByExactName;
|
|
14
14
|
exports.matchByQualifiedName = matchByQualifiedName;
|
|
15
|
+
exports.preferCallSiteFile = preferCallSiteFile;
|
|
16
|
+
exports.resolveMethodOnType = resolveMethodOnType;
|
|
15
17
|
exports.matchCppCallChain = matchCppCallChain;
|
|
16
18
|
exports.matchScopedCallChain = matchScopedCallChain;
|
|
17
19
|
exports.matchDottedCallChain = matchDottedCallChain;
|
|
18
20
|
exports.matchMethodCall = matchMethodCall;
|
|
19
21
|
exports.matchFuzzy = matchFuzzy;
|
|
20
22
|
exports.matchReference = matchReference;
|
|
23
|
+
/**
|
|
24
|
+
* Ceiling on how many same-named definitions a FUZZY name-match strategy will
|
|
25
|
+
* score. A name defined more times than this is "ubiquitous" — a method/symbol
|
|
26
|
+
* re-declared across a vendored theme or SDK (e.g. `init`/`update`/`render` on
|
|
27
|
+
* every widget of a committed Metronic theme — #999). No directory-proximity or
|
|
28
|
+
* receiver-word-overlap score can reliably pick THE one true target among
|
|
29
|
+
* thousands, so the fuzzy strategies (matchByExactName's findBestMatch, and
|
|
30
|
+
* matchMethodCall Strategy 3) decline above the ceiling instead of emitting a
|
|
31
|
+
* low-confidence, almost-certainly-wrong edge. This also caps their per-ref cost
|
|
32
|
+
* at O(ceiling): without it, K same-named refs each scored K candidates — the
|
|
33
|
+
* O(K²) blow-up that pinned a core for 15-28 min at "Resolving refs … 94%" on a
|
|
34
|
+
* repo vendoring a large JS/TS theme (#999). The PRECISE strategies are
|
|
35
|
+
* unaffected: qualified-name, import-based, and class-name (Strategy 1/2)
|
|
36
|
+
* resolution all still run and resolve a ubiquitous name when the context names
|
|
37
|
+
* its exact target. Real repos top out near ~40 same-named methods, so a normal
|
|
38
|
+
* codebase never reaches this; only bulk-vendored code does. Tune via
|
|
39
|
+
* `CODEGRAPH_AMBIGUOUS_NAME_CEILING`.
|
|
40
|
+
*/
|
|
41
|
+
const DEFAULT_AMBIGUOUS_NAME_CEILING = 500;
|
|
42
|
+
function resolveAmbiguousNameCeiling() {
|
|
43
|
+
const raw = process.env.CODEGRAPH_AMBIGUOUS_NAME_CEILING;
|
|
44
|
+
if (!raw)
|
|
45
|
+
return DEFAULT_AMBIGUOUS_NAME_CEILING;
|
|
46
|
+
const parsed = Number.parseInt(raw, 10);
|
|
47
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_AMBIGUOUS_NAME_CEILING;
|
|
48
|
+
}
|
|
49
|
+
const AMBIGUOUS_NAME_CEILING = resolveAmbiguousNameCeiling();
|
|
21
50
|
/**
|
|
22
51
|
* Try to resolve a path-like reference (e.g., "snippets/drawer-menu.liquid")
|
|
23
52
|
* by matching the filename against file nodes.
|
|
@@ -111,7 +140,9 @@ function pickClosestFileNode(candidates, ref) {
|
|
|
111
140
|
const LANGUAGE_FAMILY = {
|
|
112
141
|
java: 'jvm', kotlin: 'jvm', scala: 'jvm',
|
|
113
142
|
swift: 'apple', objc: 'apple',
|
|
114
|
-
|
|
143
|
+
// ArkTS is a TS superset — every HarmonyOS project mixes `.ets` UI with
|
|
144
|
+
// `.ts` logic modules, so refs must cross freely between them.
|
|
145
|
+
typescript: 'web', tsx: 'web', javascript: 'web', jsx: 'web', arkts: 'web',
|
|
115
146
|
c: 'c', cpp: 'c',
|
|
116
147
|
// Razor/Blazor markup names C# types — same family so `@model Foo` /
|
|
117
148
|
// `<MyComponent/>` resolve to their `.cs` class through the cross-family gate.
|
|
@@ -193,6 +224,7 @@ function matchFunctionRef(ref, context) {
|
|
|
193
224
|
// implicit-self, Java/Kotlin method references.
|
|
194
225
|
const bareFnOnly = ref.language === 'typescript' || ref.language === 'tsx' ||
|
|
195
226
|
ref.language === 'javascript' || ref.language === 'jsx' ||
|
|
227
|
+
ref.language === 'arkts' ||
|
|
196
228
|
ref.language === 'cpp' || ref.language === 'python' ||
|
|
197
229
|
ref.language === 'php';
|
|
198
230
|
// Qualified member-pointer (`&Widget::on_click` → "Widget::on_click"):
|
|
@@ -324,6 +356,14 @@ function matchByExactName(ref, context) {
|
|
|
324
356
|
resolvedBy: 'exact-match',
|
|
325
357
|
};
|
|
326
358
|
}
|
|
359
|
+
// Ubiquitous-name ceiling (#999): above it, picking one target among K
|
|
360
|
+
// same-named defs by directory proximity is unreliable AND O(K) per ref — the
|
|
361
|
+
// quadratic behind the "Resolving refs" wedge on theme/SDK-vendoring repos.
|
|
362
|
+
// Decline; the precise strategies (qualified-name, import, class-name) already
|
|
363
|
+
// ran. Falls through to fuzzy, which itself only resolves a UNIQUE candidate.
|
|
364
|
+
if (candidates.length > AMBIGUOUS_NAME_CEILING) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
327
367
|
// Multiple matches - try to narrow down
|
|
328
368
|
const bestMatch = findBestMatch(ref, candidates, context);
|
|
329
369
|
if (bestMatch) {
|
|
@@ -347,7 +387,17 @@ function matchByQualifiedName(ref, context) {
|
|
|
347
387
|
if (!ref.referenceName.includes('::') && !ref.referenceName.includes('.')) {
|
|
348
388
|
return null;
|
|
349
389
|
}
|
|
350
|
-
|
|
390
|
+
// A method call `receiver.method()` can share an exact qualified name with a
|
|
391
|
+
// config-file key: `service.process()` (a `calls` ref named `service.process`)
|
|
392
|
+
// vs the yaml key `service.process`. Config keys are bound to their code refs
|
|
393
|
+
// upstream by the framework resolvers (`@Value` → `references`); a `calls` ref
|
|
394
|
+
// must never resolve to a yaml/properties config node — that's a wrong edge
|
|
395
|
+
// AND it hides the real callee. Drop those from both the exact and the partial
|
|
396
|
+
// candidate sets so resolution falls through to method resolution below (#1180).
|
|
397
|
+
const keepForRef = (nodes) => ref.referenceKind === 'calls'
|
|
398
|
+
? nodes.filter((n) => !(n.kind === 'constant' && (n.language === 'yaml' || n.language === 'properties')))
|
|
399
|
+
: nodes;
|
|
400
|
+
const candidates = keepForRef(context.getNodesByQualifiedName(ref.referenceName));
|
|
351
401
|
if (candidates.length === 1) {
|
|
352
402
|
return {
|
|
353
403
|
original: ref,
|
|
@@ -356,24 +406,63 @@ function matchByQualifiedName(ref, context) {
|
|
|
356
406
|
resolvedBy: 'qualified-name',
|
|
357
407
|
};
|
|
358
408
|
}
|
|
359
|
-
//
|
|
409
|
+
// Several symbols share this exact qualified name (e.g. `Logger::log` declared
|
|
410
|
+
// in two files — an ODR clash or separate translation units): prefer the one
|
|
411
|
+
// in the call site's own file before the partial-match fallback below, else
|
|
412
|
+
// the first-indexed def wins and a call in `b/svc` targets `a/svc` (#1079).
|
|
413
|
+
if (candidates.length > 1) {
|
|
414
|
+
const ordered = preferCallSiteFile(candidates, ref.filePath);
|
|
415
|
+
if (ordered[0].filePath === ref.filePath) {
|
|
416
|
+
return {
|
|
417
|
+
original: ref,
|
|
418
|
+
targetNodeId: ordered[0].id,
|
|
419
|
+
confidence: 0.95,
|
|
420
|
+
resolvedBy: 'qualified-name',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Try partial qualified name match — again preferring the call site's own
|
|
425
|
+
// file when more than one symbol's qualifiedName ends with the reference.
|
|
360
426
|
const parts = ref.referenceName.split(/[:.]/);
|
|
361
427
|
const lastName = parts[parts.length - 1];
|
|
362
428
|
if (lastName) {
|
|
363
|
-
const partialCandidates = context.getNodesByName(lastName)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
429
|
+
const partialCandidates = keepForRef(context.getNodesByName(lastName))
|
|
430
|
+
.filter((candidate) => candidate.qualifiedName.endsWith(ref.referenceName));
|
|
431
|
+
const chosen = preferCallSiteFile(partialCandidates, ref.filePath)[0];
|
|
432
|
+
if (chosen) {
|
|
433
|
+
return {
|
|
434
|
+
original: ref,
|
|
435
|
+
targetNodeId: chosen.id,
|
|
436
|
+
confidence: 0.85,
|
|
437
|
+
resolvedBy: 'qualified-name',
|
|
438
|
+
};
|
|
373
439
|
}
|
|
374
440
|
}
|
|
375
441
|
return null;
|
|
376
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* When a symbol name is ambiguous across files, prefer the candidate(s) declared
|
|
445
|
+
* in the call site's own file, keeping the rest in their original order (#1079).
|
|
446
|
+
* A same-file definition is the strongest language-agnostic signal for which of
|
|
447
|
+
* several same-named symbols a call means; without it, resolution collapses onto
|
|
448
|
+
* whichever was indexed first, so a call in `b/svc` wrongly targets `a/svc`.
|
|
449
|
+
* No-op when there are <2 candidates or none share the call site's file.
|
|
450
|
+
*/
|
|
451
|
+
function preferCallSiteFile(nodes, callSiteFile) {
|
|
452
|
+
if (nodes.length < 2)
|
|
453
|
+
return nodes;
|
|
454
|
+
const same = [];
|
|
455
|
+
const other = [];
|
|
456
|
+
for (const n of nodes) {
|
|
457
|
+
if (n.filePath === callSiteFile)
|
|
458
|
+
same.push(n);
|
|
459
|
+
else
|
|
460
|
+
other.push(n);
|
|
461
|
+
}
|
|
462
|
+
return same.length ? [...same, ...other] : nodes;
|
|
463
|
+
}
|
|
464
|
+
// Exported for the precedence unit tests (#1079): they assert the
|
|
465
|
+
// preferredFqn → same-file → matches[0] ordering directly.
|
|
377
466
|
function resolveMethodOnType(typeName, methodName, ref, context, confidence, resolvedBy,
|
|
378
467
|
/**
|
|
379
468
|
* Optional FQN that identifies WHICH class declaration `typeName`
|
|
@@ -391,17 +480,28 @@ depth = 0) {
|
|
|
391
480
|
// in-class (`class Foo { int bar() { ... } }`) or out-of-line in a separate
|
|
392
481
|
// file (`int Foo::bar() { ... }` in foo.cpp while class Foo is in foo.hpp).
|
|
393
482
|
// The previous same-file approach missed the latter — the typical C++ layout.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
483
|
+
// Prefer the context's per-(type, method) memo: the raw name lookup fetches
|
|
484
|
+
// EVERY node sharing the method name — tens of thousands of rows for a
|
|
485
|
+
// collision-heavy Java name like `execute` — and re-filtering that per ref
|
|
486
|
+
// was a dominant term in the #1122 watchdog kill on large repos. Only the
|
|
487
|
+
// ref-independent filter is memoized; per-ref disambiguation stays below.
|
|
488
|
+
let matches;
|
|
489
|
+
if (context.getMethodMatches) {
|
|
490
|
+
matches = context.getMethodMatches(typeName, methodName, ref.language);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
const methodCandidates = context.getNodesByName(methodName);
|
|
494
|
+
const want = `${typeName}::${methodName}`;
|
|
495
|
+
matches = [];
|
|
496
|
+
for (const m of methodCandidates) {
|
|
497
|
+
if (m.kind !== 'method')
|
|
498
|
+
continue;
|
|
499
|
+
if (m.language !== ref.language)
|
|
500
|
+
continue;
|
|
501
|
+
const qn = m.qualifiedName;
|
|
502
|
+
if (qn === want || qn.endsWith(`::${want}`)) {
|
|
503
|
+
matches.push(m);
|
|
504
|
+
}
|
|
405
505
|
}
|
|
406
506
|
}
|
|
407
507
|
if (matches.length === 0) {
|
|
@@ -437,9 +537,19 @@ depth = 0) {
|
|
|
437
537
|
};
|
|
438
538
|
}
|
|
439
539
|
}
|
|
540
|
+
// Language-agnostic disambiguation: when several same-named methods survive
|
|
541
|
+
// (e.g. two files each declaring `class Logger { void log(); }` — an ODR
|
|
542
|
+
// clash, an anonymous-namespace type, or separate translation units), prefer
|
|
543
|
+
// the definition in the CALL SITE's own file. Without this, every ambiguous
|
|
544
|
+
// call collapses onto the first-indexed definition, so a call in `b/svc.cpp`
|
|
545
|
+
// wrongly points at `a/svc.cpp` (#1079). This runs AFTER the `preferredFqn`
|
|
546
|
+
// block, so Java/Kotlin import disambiguation — whose target is intentionally
|
|
547
|
+
// in ANOTHER file (#314) — is unaffected: that block returns early whenever
|
|
548
|
+
// an import FQN pins the class.
|
|
549
|
+
const ordered = preferCallSiteFile(matches, ref.filePath);
|
|
440
550
|
return {
|
|
441
551
|
original: ref,
|
|
442
|
-
targetNodeId:
|
|
552
|
+
targetNodeId: ordered[0].id,
|
|
443
553
|
confidence,
|
|
444
554
|
resolvedBy,
|
|
445
555
|
};
|
|
@@ -478,10 +588,14 @@ function buildDeclaratorRegex(escapedReceiver) {
|
|
|
478
588
|
return new RegExp(`([A-Za-z_][\\w:]*(?:\\s*<[^;=(){}]+>)?(?:\\s*[*&]+)?)\\s*\\b${escapedReceiver}\\b\\s*(?=[;=,)\\[{(]|$)`);
|
|
479
589
|
}
|
|
480
590
|
function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
481
|
-
|
|
482
|
-
|
|
591
|
+
// Per-file lines cache when available — this runs per `receiver->method()`
|
|
592
|
+
// ref and re-splitting the file each time is the same quadratic as the
|
|
593
|
+
// shared inferrer's (#1122).
|
|
594
|
+
const lines = context.getFileLines
|
|
595
|
+
? context.getFileLines(ref.filePath)
|
|
596
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
597
|
+
if (!lines || lines.length === 0)
|
|
483
598
|
return null;
|
|
484
|
-
const lines = source.split(/\r?\n/);
|
|
485
599
|
const callLineIndex = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
486
600
|
const escapedReceiver = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
487
601
|
const receiverPattern = new RegExp(`\\b${escapedReceiver}\\b`);
|
|
@@ -514,10 +628,12 @@ function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
|
514
628
|
for (const headerPath of headerCandidates) {
|
|
515
629
|
if (!context.fileExists(headerPath))
|
|
516
630
|
continue;
|
|
517
|
-
const
|
|
518
|
-
|
|
631
|
+
const headerLines = context.getFileLines
|
|
632
|
+
? context.getFileLines(headerPath)
|
|
633
|
+
: (context.readFile(headerPath)?.split(/\r?\n/) ?? null);
|
|
634
|
+
if (!headerLines)
|
|
519
635
|
continue;
|
|
520
|
-
for (const line of
|
|
636
|
+
for (const line of headerLines) {
|
|
521
637
|
if (!receiverPattern.test(line))
|
|
522
638
|
continue;
|
|
523
639
|
const declaratorMatch = line.match(declaratorRegex);
|
|
@@ -845,6 +961,394 @@ function inferJavaFieldReceiverType(receiverName, ref, context) {
|
|
|
845
961
|
return null; // primitives / lowercase → skip
|
|
846
962
|
return lastPart;
|
|
847
963
|
}
|
|
964
|
+
// ── Local-variable receiver-type inference (#1108) ──────────────────────────
|
|
965
|
+
//
|
|
966
|
+
// Instance calls through a local variable (`const lg = new Logger(); lg.log()`)
|
|
967
|
+
// only resolved in C++ before this — no other language could learn the
|
|
968
|
+
// receiver's type. Local variables are not indexed as nodes (node-explosion),
|
|
969
|
+
// so, like the C++ inferrer above, we read the enclosing function's source and
|
|
970
|
+
// match the receiver's declaration/initializer to recover its type. The type is
|
|
971
|
+
// then handed to resolveMethodOnType, which VALIDATES that the type actually
|
|
972
|
+
// declares the method, so a mis-inference produces NO edge — the safety net
|
|
973
|
+
// that lets the patterns below stay simple. C++ keeps its dedicated inferrer
|
|
974
|
+
// (header scan + `auto`); this covers every other language.
|
|
975
|
+
// Tokens a loose pattern might capture that are never a user-defined type.
|
|
976
|
+
const NON_TYPE_RECEIVER_TOKENS = new Set([
|
|
977
|
+
'this', 'self', 'super', 'new', 'return', 'await', 'yield', 'typeof',
|
|
978
|
+
'null', 'nil', 'None', 'true', 'false', 'True', 'False', 'undefined',
|
|
979
|
+
]);
|
|
980
|
+
/**
|
|
981
|
+
* Normalize a captured type expression to a simple type name: drop generic
|
|
982
|
+
* args and pointer/ref markers, take the last `.`/`::`-qualified segment, and
|
|
983
|
+
* reject obvious non-types.
|
|
984
|
+
*/
|
|
985
|
+
function normalizeInferredTypeName(raw) {
|
|
986
|
+
const cleaned = raw.replace(/<[^>]*>/g, '').replace(/[&*]/g, '').trim();
|
|
987
|
+
const seg = cleaned.split(/[.:]+/).filter(Boolean).pop();
|
|
988
|
+
if (!seg)
|
|
989
|
+
return null;
|
|
990
|
+
if (NON_TYPE_RECEIVER_TOKENS.has(seg))
|
|
991
|
+
return null;
|
|
992
|
+
return seg;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Per-language patterns that recover a local variable's (or typed parameter's)
|
|
996
|
+
* type from its declaration/initializer. Each regex captures the type in group
|
|
997
|
+
* 1; `r` is the already-escaped receiver name. Ordered most-specific first.
|
|
998
|
+
* PascalCase is required in the capture where the language convention allows,
|
|
999
|
+
* as a cheap false-positive guard on top of resolveMethodOnType's validation.
|
|
1000
|
+
*/
|
|
1001
|
+
function localReceiverTypePatterns(language, r) {
|
|
1002
|
+
switch (language) {
|
|
1003
|
+
case 'typescript':
|
|
1004
|
+
case 'javascript':
|
|
1005
|
+
case 'tsx':
|
|
1006
|
+
case 'jsx':
|
|
1007
|
+
case 'arkts':
|
|
1008
|
+
return [
|
|
1009
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), // = new Logger()
|
|
1010
|
+
// No keyword requirement, so this matches BOTH a local annotation
|
|
1011
|
+
// (`const lg: Logger`) and a typed parameter (`function use(lg: Logger)`
|
|
1012
|
+
// / `(lg: Logger) =>`) — the parameter case the old `const|let|var`
|
|
1013
|
+
// prefix excluded (#1125). Mirrors Kotlin/Swift/Scala; the capture stops
|
|
1014
|
+
// at `<` so a generic-typed param (`repo: Repository<User>`) still yields
|
|
1015
|
+
// `Repository`. resolveMethodOnType validates the type actually declares
|
|
1016
|
+
// the method, so the looser match produces no edge on a mis-inference.
|
|
1017
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.$]*)`), // lg: Logger (annotation or typed param)
|
|
1018
|
+
];
|
|
1019
|
+
case 'python':
|
|
1020
|
+
return [
|
|
1021
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // lg = Logger(...)
|
|
1022
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // lg: Logger (PEP 526)
|
|
1023
|
+
];
|
|
1024
|
+
case 'java':
|
|
1025
|
+
return [
|
|
1026
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`), // = new Logger()
|
|
1027
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg; / param
|
|
1028
|
+
];
|
|
1029
|
+
case 'kotlin':
|
|
1030
|
+
return [
|
|
1031
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // val lg = Logger(...)
|
|
1032
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // val lg: Logger / param
|
|
1033
|
+
];
|
|
1034
|
+
case 'csharp':
|
|
1035
|
+
return [
|
|
1036
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`), // = new Logger()
|
|
1037
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg; / param
|
|
1038
|
+
];
|
|
1039
|
+
case 'swift':
|
|
1040
|
+
return [
|
|
1041
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // let lg = Logger(...)
|
|
1042
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // let lg: Logger / param
|
|
1043
|
+
];
|
|
1044
|
+
case 'rust':
|
|
1045
|
+
return [
|
|
1046
|
+
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\b(?:\\s*:[^=]+)?=\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg = Logger::new()/Logger{}/Logger
|
|
1047
|
+
// No `let`, so this covers a `let lg: Logger` binding AND a typed
|
|
1048
|
+
// parameter (`fn use(lg: &Logger)`, a closure `|lg: Logger|`) — the
|
|
1049
|
+
// parameter case the old `let`-anchored pattern excluded (#1125).
|
|
1050
|
+
new RegExp(`\\b${r}\\s*:\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // lg: Logger (binding or typed param)
|
|
1051
|
+
];
|
|
1052
|
+
case 'go':
|
|
1053
|
+
return [
|
|
1054
|
+
new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{}
|
|
1055
|
+
new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger
|
|
1056
|
+
// A typed parameter / method receiver (`func use(lg Logger)`,
|
|
1057
|
+
// `func (l Logger) M()`) — name-before-type with no `var`/`:=` (#1125).
|
|
1058
|
+
// PascalCase-guarded (unlike the anchored patterns above) to keep the
|
|
1059
|
+
// keyword-free `ident Type` shape from matching unrelated pairs; the
|
|
1060
|
+
// enclosing-scope bound already excludes package-level struct fields.
|
|
1061
|
+
new RegExp(`\\b${r}\\s+\\*?([A-Z][\\w.]*)`), // func use(lg Logger) / (l Logger)
|
|
1062
|
+
];
|
|
1063
|
+
case 'ruby':
|
|
1064
|
+
return [
|
|
1065
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w:]*)\\.new\\b`), // lg = Logger.new
|
|
1066
|
+
];
|
|
1067
|
+
case 'scala':
|
|
1068
|
+
return [
|
|
1069
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*(?:new\\s+)?([A-Z][\\w.]*)`), // val lg = new Logger / Logger(...)
|
|
1070
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // val lg: Logger / param
|
|
1071
|
+
];
|
|
1072
|
+
case 'dart':
|
|
1073
|
+
return [
|
|
1074
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // var lg = Logger(...)
|
|
1075
|
+
// Trailing `[=;,)]` (not just `[=;]`) so a typed parameter — `Logger lg)`
|
|
1076
|
+
// / `Logger lg,` — matches too, not only `Logger lg = ...` / `Logger lg;`
|
|
1077
|
+
// (#1125). Mirrors Java/C#.
|
|
1078
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg = ... / param
|
|
1079
|
+
];
|
|
1080
|
+
case 'php':
|
|
1081
|
+
return [
|
|
1082
|
+
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
|
1083
|
+
// A typed parameter (`function use(Logger $lg)`, `?Logger $lg`,
|
|
1084
|
+
// `\\App\\Logger $lg`, `&$lg` by-ref) and a typed `catch (E $e)` — the
|
|
1085
|
+
// type sits before the `$`-variable (#1125). Namespace `\\` allowed.
|
|
1086
|
+
new RegExp(`\\b([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // Logger $lg (typed param)
|
|
1087
|
+
];
|
|
1088
|
+
case 'lua':
|
|
1089
|
+
case 'luau':
|
|
1090
|
+
return [
|
|
1091
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\.new\\b`), // local lg = Logger.new()
|
|
1092
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\s*\\(`), // local lg = Logger(...) (callable table)
|
|
1093
|
+
// Luau annotation (`local lg: Logger`) / typed param — but Lua's
|
|
1094
|
+
// method-call syntax is the IDENTICAL `receiver:Name` shape, and the
|
|
1095
|
+
// backward scan starts on the call's own line, so without a gate any
|
|
1096
|
+
// PascalCase method call (`lg:Log()`, the Roblox convention)
|
|
1097
|
+
// self-matches as "type = Log" before the scan reaches the real
|
|
1098
|
+
// declaration (#1124). The lookahead rejects a capture followed by
|
|
1099
|
+
// any of Lua's three call forms — `(args)`, `"s"`/`'s'`/`[[s]]`,
|
|
1100
|
+
// `{t}` — and its leading `[\w.]` alternative stops backtracking from
|
|
1101
|
+
// shrinking the capture to dodge the gate (`lg:Log()` would otherwise
|
|
1102
|
+
// still match, as `Lo`).
|
|
1103
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)(?![\\w.]|\\s*[({"'\\[])`), // local lg: Logger / typed param
|
|
1104
|
+
];
|
|
1105
|
+
case 'r':
|
|
1106
|
+
return [
|
|
1107
|
+
new RegExp(`\\b${r}\\b\\s*(?:<-|<<-|=)\\s*([A-Z][\\w.]*)\\$new\\b`), // lg <- Logger$new() (R6)
|
|
1108
|
+
];
|
|
1109
|
+
case 'pascal':
|
|
1110
|
+
return [
|
|
1111
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w]*)`), // var lg: TLogger / param lg: TLogger
|
|
1112
|
+
new RegExp(`\\b${r}\\b\\s*:=\\s*([A-Z][\\w.]*)\\.Create\\b`), // lg := TLogger.Create
|
|
1113
|
+
];
|
|
1114
|
+
case 'cfml':
|
|
1115
|
+
case 'cfscript':
|
|
1116
|
+
return [
|
|
1117
|
+
// svc = new UserService() / new path.to.UserService() — dotted component
|
|
1118
|
+
// paths reduce to their final segment via normalizeInferredTypeName.
|
|
1119
|
+
// Also matches inside tag markup (`<cfset svc = new UserService()>`)
|
|
1120
|
+
// since the scan reads raw source lines.
|
|
1121
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`),
|
|
1122
|
+
// The classic form: svc = createObject("component", "path.to.UserService")
|
|
1123
|
+
// (casing of createObject varies in the wild), plus the modern
|
|
1124
|
+
// single-argument form createObject("path.to.UserService").
|
|
1125
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']component["']\\s*,\\s*["']([\\w.]+)["']`),
|
|
1126
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']([\\w.]+)["']\\s*\\)`),
|
|
1127
|
+
// Typed cfscript parameter: `function save(UserService svc)` /
|
|
1128
|
+
// `required UserService svc` — CFML's built-in types (string, numeric,
|
|
1129
|
+
// any, struct…) are lowercase by convention, so the PascalCase guard
|
|
1130
|
+
// excludes them.
|
|
1131
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`),
|
|
1132
|
+
// Tag-form typed argument, either attribute order:
|
|
1133
|
+
// <cfargument name="svc" type="path.to.UserService">
|
|
1134
|
+
new RegExp(`\\bcfargument[^>\\n]*\\bname\\s*=\\s*["']${r}["'][^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1135
|
+
new RegExp(`\\bcfargument[^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["'][^>\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1136
|
+
// Component property (incl. WireBox DI): `property name="svc"
|
|
1137
|
+
// inject="UserService";` / `<cfproperty name="svc" type="UserService">`,
|
|
1138
|
+
// either attribute order. An inject DSL value with a namespace
|
|
1139
|
+
// (`inject="svc@core"`) captures only the leading name and simply
|
|
1140
|
+
// fails type-validation — no edge, never a wrong one.
|
|
1141
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\bname\\s*=\\s*["']${r}["'][^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1142
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["'][^;\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1143
|
+
];
|
|
1144
|
+
default:
|
|
1145
|
+
return [];
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/** 1-based start line of the tightest function/method enclosing the call. */
|
|
1149
|
+
function enclosingScopeStartLine(ref, context) {
|
|
1150
|
+
let start = 1;
|
|
1151
|
+
for (const n of context.getNodesInFile(ref.filePath)) {
|
|
1152
|
+
if (n.kind !== 'function' && n.kind !== 'method')
|
|
1153
|
+
continue;
|
|
1154
|
+
if (n.language !== ref.language)
|
|
1155
|
+
continue;
|
|
1156
|
+
const end = n.endLine ?? n.startLine;
|
|
1157
|
+
if (n.startLine <= ref.line && end >= ref.line && n.startLine >= start) {
|
|
1158
|
+
start = n.startLine;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return start;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Infer a receiver's type from its local declaration/initializer in the
|
|
1165
|
+
* enclosing function body. Language-dispatched; returns null for languages
|
|
1166
|
+
* without patterns or when no declaration is found. Bounded to the enclosing
|
|
1167
|
+
* scope so a same-named variable in another function can't leak in.
|
|
1168
|
+
*/
|
|
1169
|
+
function inferLocalReceiverType(receiverName, ref, context) {
|
|
1170
|
+
// CFML scope prefixes: `variables.svc` / `this.svc` name a COMPONENT-scoped
|
|
1171
|
+
// field whose assignment or `property` declaration usually lives outside the
|
|
1172
|
+
// calling function (the init-pseudoconstructor / WireBox-injection pattern),
|
|
1173
|
+
// and `local.svc` is an explicit function-local. Strip the prefix so the
|
|
1174
|
+
// declaration patterns match (`variables.svc = new X()`, `property
|
|
1175
|
+
// name="svc" …`, `var svc = …` all bind the bare name), and widen the scan
|
|
1176
|
+
// to the whole file for the component-scoped forms — nearest-declaration-
|
|
1177
|
+
// backward still wins, so a function-local shadowing the field is preferred.
|
|
1178
|
+
let scanReceiver = receiverName;
|
|
1179
|
+
let componentScoped = false;
|
|
1180
|
+
if (ref.language === 'cfml' || ref.language === 'cfscript') {
|
|
1181
|
+
const scoped = receiverName.match(/^(variables|this|local|arguments)\.(.+)$/i);
|
|
1182
|
+
if (scoped) {
|
|
1183
|
+
scanReceiver = scoped[2];
|
|
1184
|
+
const scope = scoped[1].toLowerCase();
|
|
1185
|
+
componentScoped = scope === 'variables' || scope === 'this';
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
// PHP `$this->prop` receiver — the property's declaration lives outside the
|
|
1189
|
+
// calling method (a promoted constructor parameter `private readonly Foo $prop`,
|
|
1190
|
+
// a typed property `private Foo $prop;`, or a classic constructor parameter
|
|
1191
|
+
// `Foo $prop` assigned in __construct). Strip the prefix and widen the scan to
|
|
1192
|
+
// the whole file (the constructor may sit below the calling method), but —
|
|
1193
|
+
// unlike CFML's scopes above — switch to PROPERTY-shaped patterns: a plain
|
|
1194
|
+
// `$prop` local or parameter lives in a different namespace than `$this->prop`
|
|
1195
|
+
// and can never shadow it, so the generic local patterns would type the
|
|
1196
|
+
// property from unrelated same-named variables in other methods (a wrong
|
|
1197
|
+
// 0.9-confidence edge, not a missing one).
|
|
1198
|
+
let phpProperty = false;
|
|
1199
|
+
if (ref.language === 'php') {
|
|
1200
|
+
const scoped = receiverName.match(/^this->(.+)$/);
|
|
1201
|
+
if (scoped) {
|
|
1202
|
+
scanReceiver = scoped[1];
|
|
1203
|
+
componentScoped = true;
|
|
1204
|
+
phpProperty = true;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const escapedReceiver = scanReceiver.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1208
|
+
const patterns = phpProperty
|
|
1209
|
+
? phpPropertyTypePatterns(escapedReceiver)
|
|
1210
|
+
: localReceiverTypePatterns(ref.language, escapedReceiver);
|
|
1211
|
+
if (patterns.length === 0)
|
|
1212
|
+
return null;
|
|
1213
|
+
// Split through the context's per-file lines cache when available: this runs
|
|
1214
|
+
// for EVERY `receiver.method()` ref, and re-splitting the whole file per ref
|
|
1215
|
+
// was ~20% of total index CPU on Java-heavy repos (#1122).
|
|
1216
|
+
const lines = context.getFileLines
|
|
1217
|
+
? context.getFileLines(ref.filePath)
|
|
1218
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
1219
|
+
if (!lines || lines.length === 0)
|
|
1220
|
+
return null;
|
|
1221
|
+
const callIdx = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
1222
|
+
const startIdx = componentScoped
|
|
1223
|
+
? 0
|
|
1224
|
+
: Math.max(0, enclosingScopeStartLine(ref, context) - 1);
|
|
1225
|
+
const matchLine = (i) => {
|
|
1226
|
+
const line = lines[i];
|
|
1227
|
+
if (!line)
|
|
1228
|
+
return null;
|
|
1229
|
+
// A generated/minified line (one multi-KB statement) is not something a
|
|
1230
|
+
// human-written local declaration lives on, and regexing it per ref is
|
|
1231
|
+
// pure waste — skip it rather than scan it.
|
|
1232
|
+
if (line.length > 10_000)
|
|
1233
|
+
return null;
|
|
1234
|
+
for (const re of patterns) {
|
|
1235
|
+
const m = line.match(re);
|
|
1236
|
+
if (m && m[1]) {
|
|
1237
|
+
const type = normalizeInferredTypeName(m[1]);
|
|
1238
|
+
if (type)
|
|
1239
|
+
return type;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return null;
|
|
1243
|
+
};
|
|
1244
|
+
// Nearest declaration wins: scan backward from the call to the scope start.
|
|
1245
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1246
|
+
const type = matchLine(i);
|
|
1247
|
+
if (type)
|
|
1248
|
+
return type;
|
|
1249
|
+
}
|
|
1250
|
+
// A component-scoped field's declaration is position-independent — the
|
|
1251
|
+
// `variables.svc = new X()` pseudoconstructor assignment or `property`
|
|
1252
|
+
// declaration may sit BELOW the calling function in the file — so when the
|
|
1253
|
+
// backward pass finds nothing, sweep the remainder of the file too.
|
|
1254
|
+
if (componentScoped) {
|
|
1255
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1256
|
+
const type = matchLine(i);
|
|
1257
|
+
if (type)
|
|
1258
|
+
return type;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
// A PHP property with no statically-typed declaration (classic pre-7.4
|
|
1262
|
+
// style) may still be typed by what gets ASSIGNED to it — follow the
|
|
1263
|
+
// `$this->prop = $var` assignment to the assigned variable's own typed
|
|
1264
|
+
// declaration (a classic or multi-line constructor parameter, or a typed
|
|
1265
|
+
// setter's parameter).
|
|
1266
|
+
if (phpProperty) {
|
|
1267
|
+
return inferPhpAssignedPropertyType(escapedReceiver, lines, callIdx);
|
|
1268
|
+
}
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Patterns that recover a PHP class property's declared type for a
|
|
1273
|
+
* `$this->prop` receiver. Deliberately NOT localReceiverTypePatterns: only
|
|
1274
|
+
* property-shaped declarations qualify —
|
|
1275
|
+
* 1. a modifier-prefixed typed declaration, which covers both a typed
|
|
1276
|
+
* property (`private ?Foo $prop;`) and a promoted constructor parameter
|
|
1277
|
+
* (`private readonly Foo $prop`), and
|
|
1278
|
+
* 2. the pseudoconstructor assignment (`$this->prop = new Foo(...)`).
|
|
1279
|
+
* A bare `X $prop` parameter or `$prop = new X()` local elsewhere in the
|
|
1280
|
+
* file must NOT match: those variables can never alias `$this->prop`.
|
|
1281
|
+
* Union-typed properties (`Foo|Bar $prop`) yield no match and thus no edge —
|
|
1282
|
+
* silent beats wrong. The classic untyped-property-assigned-in-constructor
|
|
1283
|
+
* shape is handled by inferPhpAssignedPropertyType instead.
|
|
1284
|
+
*/
|
|
1285
|
+
function phpPropertyTypePatterns(r) {
|
|
1286
|
+
return [
|
|
1287
|
+
new RegExp(`\\b(?:(?:private|protected|public|readonly|static|final)(?:\\(set\\))?\\s+)+\\??([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // private readonly ?Foo $prop (typed property / promoted param)
|
|
1288
|
+
new RegExp(`\\$this->${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $this->prop = new Foo()
|
|
1289
|
+
];
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Second-chance typing for a PHP `$this->prop` receiver whose property
|
|
1293
|
+
* declaration carries no static type (classic pre-7.4 style): find the
|
|
1294
|
+
* `$this->prop = $var` assignment, then recover `$var`'s type from its own
|
|
1295
|
+
* declaration WITHIN the assignment's function — the constructor's (possibly
|
|
1296
|
+
* multi-line) parameter list, a typed setter's parameter, or a `= new X()`
|
|
1297
|
+
* local. The backward scan stops at the enclosing `function` line (checked
|
|
1298
|
+
* for a match first — a single-line `__construct(Foo $var) { ... }` carries
|
|
1299
|
+
* the typed parameter itself), so a same-named variable in another method
|
|
1300
|
+
* can never type the property.
|
|
1301
|
+
*/
|
|
1302
|
+
function inferPhpAssignedPropertyType(escapedProp, lines, callIdx) {
|
|
1303
|
+
const assignRe = new RegExp(`\\$this->${escapedProp}\\b\\s*=\\s*\\$(\\w+)\\b`);
|
|
1304
|
+
const assignAt = (i) => {
|
|
1305
|
+
const line = lines[i];
|
|
1306
|
+
if (!line || line.length > 10_000)
|
|
1307
|
+
return null;
|
|
1308
|
+
return line.match(assignRe);
|
|
1309
|
+
};
|
|
1310
|
+
// The assignment is position-independent relative to the call — nearest-
|
|
1311
|
+
// backward first, then sweep forward, same order as the componentScoped scan.
|
|
1312
|
+
let assignIdx = -1;
|
|
1313
|
+
let varName = null;
|
|
1314
|
+
for (let i = callIdx; i >= 0; i--) {
|
|
1315
|
+
const m = assignAt(i);
|
|
1316
|
+
if (m) {
|
|
1317
|
+
assignIdx = i;
|
|
1318
|
+
varName = m[1];
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
if (varName === null) {
|
|
1323
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1324
|
+
const m = assignAt(i);
|
|
1325
|
+
if (m) {
|
|
1326
|
+
assignIdx = i;
|
|
1327
|
+
varName = m[1];
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
if (varName === null)
|
|
1333
|
+
return null;
|
|
1334
|
+
const varPatterns = localReceiverTypePatterns('php', varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
1335
|
+
for (let i = assignIdx; i >= 0; i--) {
|
|
1336
|
+
const line = lines[i];
|
|
1337
|
+
if (line && line.length <= 10_000) {
|
|
1338
|
+
for (const re of varPatterns) {
|
|
1339
|
+
const m = line.match(re);
|
|
1340
|
+
if (m && m[1]) {
|
|
1341
|
+
const type = normalizeInferredTypeName(m[1]);
|
|
1342
|
+
if (type)
|
|
1343
|
+
return type;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (line && /\bfunction\b/.test(line))
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
848
1352
|
/**
|
|
849
1353
|
* Try to resolve by method name on a class/object
|
|
850
1354
|
*/
|
|
@@ -860,15 +1364,60 @@ function matchMethodCall(ref, context) {
|
|
|
860
1364
|
// `Guard.Against.X()`) matched no pattern and never resolved.
|
|
861
1365
|
const dotMatch = ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/);
|
|
862
1366
|
const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/);
|
|
863
|
-
|
|
1367
|
+
// Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`).
|
|
1368
|
+
// Recognize these receiver/method separators so local-variable receiver-type
|
|
1369
|
+
// inference (#1108) applies to them too — extraction already emits the ref in
|
|
1370
|
+
// this shape, but the resolver otherwise only understood `.` and `::`.
|
|
1371
|
+
const luaColonMatch = (ref.language === 'lua' || ref.language === 'luau')
|
|
1372
|
+
? ref.referenceName.match(/^([\w.]+):(\w+)$/)
|
|
1373
|
+
: null;
|
|
1374
|
+
const rDollarMatch = ref.language === 'r'
|
|
1375
|
+
? ref.referenceName.match(/^([\w.]+)\$(\w+)$/)
|
|
1376
|
+
: null;
|
|
1377
|
+
// PHP property receiver: `$this->prop->method()` reaches the resolver as
|
|
1378
|
+
// `this->prop.method` (the extractor records the receiver's raw text with the
|
|
1379
|
+
// leading `$` stripped). Resolve it EXCLUSIVELY through declared-type
|
|
1380
|
+
// inference + resolveMethodOnType validation — the name-similarity strategies
|
|
1381
|
+
// below must never see this shape, so a property whose type can't be
|
|
1382
|
+
// recovered stays unlinked rather than guessed (a wrong inference produces no
|
|
1383
|
+
// edge rather than a wrong one). Deeper chains (`this->a->b.method`) don't
|
|
1384
|
+
// match the single-property pattern and stay unlinked, same as before.
|
|
1385
|
+
const phpThisPropMatch = ref.language === 'php'
|
|
1386
|
+
? ref.referenceName.match(/^(this->\w+)\.(\w+)$/)
|
|
1387
|
+
: null;
|
|
1388
|
+
if (phpThisPropMatch) {
|
|
1389
|
+
const [, receiver, phpMethodName] = phpThisPropMatch;
|
|
1390
|
+
const inferredType = inferLocalReceiverType(receiver, ref, context);
|
|
1391
|
+
if (!inferredType)
|
|
1392
|
+
return null;
|
|
1393
|
+
return resolveMethodOnType(inferredType, phpMethodName, ref, context, 0.9, 'instance-method', importedFqnOf(inferredType, ref, context));
|
|
1394
|
+
}
|
|
1395
|
+
const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch;
|
|
864
1396
|
if (!match) {
|
|
865
1397
|
return null;
|
|
866
1398
|
}
|
|
867
1399
|
const [, objectOrClass, methodName] = match;
|
|
868
|
-
|
|
869
|
-
|
|
1400
|
+
// A simple `receiver.method` / `receiver:method` / `receiver$method` shape whose
|
|
1401
|
+
// receiver type we can try to infer from its local declaration.
|
|
1402
|
+
const inferableReceiver = dotMatch || luaColonMatch || rDollarMatch;
|
|
1403
|
+
// Infer the receiver's type from its local declaration/initializer in the
|
|
1404
|
+
// enclosing scope, then resolve the method on that type (#1108). C++ keeps its
|
|
1405
|
+
// dedicated inferrer (header scan + `auto`); every other language uses the
|
|
1406
|
+
// shared source-based inferrer. resolveMethodOnType validates the method
|
|
1407
|
+
// exists on the inferred type, so a mis-inference produces no edge.
|
|
1408
|
+
if (inferableReceiver) {
|
|
1409
|
+
const inferredType = ref.language === 'cpp'
|
|
1410
|
+
? inferCppReceiverType(objectOrClass, ref, context)
|
|
1411
|
+
: inferLocalReceiverType(objectOrClass, ref, context);
|
|
870
1412
|
if (inferredType) {
|
|
871
|
-
|
|
1413
|
+
// Java/Kotlin: when two classes share the simple name, the file's import
|
|
1414
|
+
// pins WHICH one (#314). Other languages disambiguate by call-site file.
|
|
1415
|
+
const importedFqn = ref.language === 'java' || ref.language === 'kotlin'
|
|
1416
|
+
? context
|
|
1417
|
+
.getImportMappings(ref.filePath, ref.language)
|
|
1418
|
+
.find((i) => i.localName === inferredType)?.source
|
|
1419
|
+
: undefined;
|
|
1420
|
+
const typedMatch = resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn);
|
|
872
1421
|
if (typedMatch) {
|
|
873
1422
|
return typedMatch;
|
|
874
1423
|
}
|
|
@@ -893,8 +1442,12 @@ function matchMethodCall(ref, context) {
|
|
|
893
1442
|
}
|
|
894
1443
|
}
|
|
895
1444
|
}
|
|
896
|
-
// Strategy 1: Direct class name match (existing logic)
|
|
897
|
-
|
|
1445
|
+
// Strategy 1: Direct class name match (existing logic). When the receiver
|
|
1446
|
+
// names a class that exists in several files (`Logger.log()` / `Logger::log()`
|
|
1447
|
+
// with a `Logger` in both `a/` and `b/`), try the class in the call site's
|
|
1448
|
+
// own file first — otherwise the first-indexed class wins and a call in `b/`
|
|
1449
|
+
// resolves to `a/`'s method (#1079).
|
|
1450
|
+
const classCandidates = preferCallSiteFile(context.getNodesByName(objectOrClass), ref.filePath);
|
|
898
1451
|
for (const classNode of classCandidates) {
|
|
899
1452
|
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
|
|
900
1453
|
// Skip cross-language class matches
|
|
@@ -918,7 +1471,7 @@ function matchMethodCall(ref, context) {
|
|
|
918
1471
|
// e.g., "permissionEngine" → look for classes containing "PermissionEngine"
|
|
919
1472
|
const capitalizedReceiver = objectOrClass.charAt(0).toUpperCase() + objectOrClass.slice(1);
|
|
920
1473
|
if (capitalizedReceiver !== objectOrClass) {
|
|
921
|
-
const fuzzyClassCandidates = context.getNodesByName(capitalizedReceiver);
|
|
1474
|
+
const fuzzyClassCandidates = preferCallSiteFile(context.getNodesByName(capitalizedReceiver), ref.filePath);
|
|
922
1475
|
for (const classNode of fuzzyClassCandidates) {
|
|
923
1476
|
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
|
|
924
1477
|
// Skip cross-language class matches
|
|
@@ -944,6 +1497,15 @@ function matchMethodCall(ref, context) {
|
|
|
944
1497
|
// names like permissionEngine → PermissionRuleEngine.
|
|
945
1498
|
if (methodName) {
|
|
946
1499
|
const methodCandidates = context.getNodesByName(methodName);
|
|
1500
|
+
// Ubiquitous-method ceiling (#999): a method name re-declared across a
|
|
1501
|
+
// vendored theme/SDK (Metronic's `init`/`update`/… on every widget) yields
|
|
1502
|
+
// K candidates that receiver-word overlap can't reliably disambiguate —
|
|
1503
|
+
// and filtering + scoring all K per call is the O(K²) cost that wedged
|
|
1504
|
+
// "Resolving refs" for 15-28 min. Bail before the O(K) work; Strategy 1/2
|
|
1505
|
+
// (class-name match) already had their precise shot above.
|
|
1506
|
+
if (methodCandidates.length > AMBIGUOUS_NAME_CEILING) {
|
|
1507
|
+
return null;
|
|
1508
|
+
}
|
|
947
1509
|
const methods = methodCandidates.filter((n) => n.kind === 'method' && n.name === methodName);
|
|
948
1510
|
// Filter to same-language candidates first
|
|
949
1511
|
const sameLanguageMethods = methods.filter(m => m.language === ref.language);
|
|
@@ -962,7 +1524,10 @@ function matchMethodCall(ref, context) {
|
|
|
962
1524
|
const receiverWords = splitCamelCase(objectOrClass);
|
|
963
1525
|
let bestMatch;
|
|
964
1526
|
let bestScore = 0;
|
|
965
|
-
|
|
1527
|
+
// Same-file candidates first, so a score tie (`score > bestScore` keeps
|
|
1528
|
+
// the first seen) resolves to the call site's own file rather than the
|
|
1529
|
+
// first-indexed duplicate (#1079).
|
|
1530
|
+
for (const method of preferCallSiteFile(targetMethods, ref.filePath)) {
|
|
966
1531
|
const classWords = splitCamelCase(method.qualifiedName);
|
|
967
1532
|
let score = receiverWords.filter(w => classWords.some(cw => cw.toLowerCase() === w.toLowerCase())).length;
|
|
968
1533
|
// Bonus for same language
|
|
@@ -1141,6 +1706,8 @@ function matchFuzzy(ref, context) {
|
|
|
1141
1706
|
/**
|
|
1142
1707
|
* Match all strategies in order of confidence
|
|
1143
1708
|
*/
|
|
1709
|
+
/** ArkUI attribute-helper decorators a `.attr(...)` chain may resolve to. */
|
|
1710
|
+
const ARKUI_ATTRIBUTE_DECORATORS = new Set(['Extend', 'Styles', 'AnimatableExtend', 'Builder']);
|
|
1144
1711
|
function matchReference(ref, context) {
|
|
1145
1712
|
// Function-as-value refs (#756) resolve ONLY through the dedicated matcher —
|
|
1146
1713
|
// never the fuzzy/qualified fallthrough below (a wrong callback edge is
|
|
@@ -1148,6 +1715,57 @@ function matchReference(ref, context) {
|
|
|
1148
1715
|
if (ref.referenceKind === 'function_ref') {
|
|
1149
1716
|
return matchFunctionRef(ref, context);
|
|
1150
1717
|
}
|
|
1718
|
+
// ArkTS chained UI attributes — emitted with a leading dot (`.titleStyle`,
|
|
1719
|
+
// `.width`) by the extractor — resolve ONLY to decorator-marked attribute
|
|
1720
|
+
// helpers: `@Extend`/`@Styles`/`@AnimatableExtend` functions (and global
|
|
1721
|
+
// `@Builder`s used attribute-position). Framework attributes (`.width`,
|
|
1722
|
+
// `.fontSize` — on nearly every UI line) match no such helper and stay
|
|
1723
|
+
// unresolved, NEVER falling through to bare-name matching: on a samples
|
|
1724
|
+
// monorepo that fallthrough manufactured 36k wrong edges, giving single
|
|
1725
|
+
// same-named properties thousands of false callers. Ambiguity rule matches
|
|
1726
|
+
// the rest of the file: several same-named helpers → prefer the call-site
|
|
1727
|
+
// file, still ambiguous → drop the ref rather than guess.
|
|
1728
|
+
if (ref.language === 'arkts' && ref.referenceName.startsWith('.')) {
|
|
1729
|
+
const base = ref.referenceName.slice(1);
|
|
1730
|
+
const candidates = context
|
|
1731
|
+
.getNodesByName(base)
|
|
1732
|
+
.filter((n) => n.language === 'arkts' &&
|
|
1733
|
+
n.kind === 'function' &&
|
|
1734
|
+
(n.decorators ?? []).some((d) => ARKUI_ATTRIBUTE_DECORATORS.has(d)));
|
|
1735
|
+
const chosen = candidates.length > 1 ? preferCallSiteFile(candidates, ref.filePath) : candidates;
|
|
1736
|
+
if (chosen.length !== 1)
|
|
1737
|
+
return null;
|
|
1738
|
+
return {
|
|
1739
|
+
original: ref,
|
|
1740
|
+
targetNodeId: chosen[0].id,
|
|
1741
|
+
confidence: 0.85,
|
|
1742
|
+
resolvedBy: 'exact-match',
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
// Erlang `-behaviour(m)` refs target a MODULE. Letting them fall through to
|
|
1746
|
+
// bare-name matching grabs any same-named symbol — on emqx,
|
|
1747
|
+
// `-behaviour(supervisor)` resolved to a `-define(supervisor, …)` macro
|
|
1748
|
+
// constant in an unrelated app. Resolve only to the behaviour module's
|
|
1749
|
+
// namespace; an out-of-repo behaviour (OTP's gen_server/supervisor) stays
|
|
1750
|
+
// unresolved rather than guessed. The same module-only rule applies to every
|
|
1751
|
+
// ref an `.app`/`.app.src` resource file emits — its `{mod, …}` callback and
|
|
1752
|
+
// `{applications, …}` dependency names can only mean modules, and on emqx
|
|
1753
|
+
// the `ssl` OTP app otherwise resolved to a test helper FUNCTION named ssl.
|
|
1754
|
+
if (ref.language === 'erlang' &&
|
|
1755
|
+
(ref.referenceKind === 'implements' || /\.app(?:\.src)?$/i.test(ref.filePath))) {
|
|
1756
|
+
const modules = context
|
|
1757
|
+
.getNodesByName(ref.referenceName)
|
|
1758
|
+
.filter((n) => n.language === 'erlang' && n.kind === 'namespace');
|
|
1759
|
+
const chosen = preferCallSiteFile(modules, ref.filePath)[0];
|
|
1760
|
+
if (!chosen)
|
|
1761
|
+
return null;
|
|
1762
|
+
return {
|
|
1763
|
+
original: ref,
|
|
1764
|
+
targetNodeId: chosen.id,
|
|
1765
|
+
confidence: 0.9,
|
|
1766
|
+
resolvedBy: 'exact-match',
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1151
1769
|
// Try strategies in order of confidence
|
|
1152
1770
|
let result;
|
|
1153
1771
|
// 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
|