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
|
@@ -17,8 +17,12 @@ exports.resolveMethodOnType = resolveMethodOnType;
|
|
|
17
17
|
exports.matchCppCallChain = matchCppCallChain;
|
|
18
18
|
exports.matchScopedCallChain = matchScopedCallChain;
|
|
19
19
|
exports.matchDottedCallChain = matchDottedCallChain;
|
|
20
|
+
exports.normalizeInferredTypeName = normalizeInferredTypeName;
|
|
21
|
+
exports.clearNameMatcherMemos = clearNameMatcherMemos;
|
|
22
|
+
exports.localReceiverTypePatterns = localReceiverTypePatterns;
|
|
20
23
|
exports.matchMethodCall = matchMethodCall;
|
|
21
24
|
exports.matchFuzzy = matchFuzzy;
|
|
25
|
+
exports.dumpNameMatcherProfile = dumpNameMatcherProfile;
|
|
22
26
|
exports.matchReference = matchReference;
|
|
23
27
|
/**
|
|
24
28
|
* Ceiling on how many same-named definitions a FUZZY name-match strategy will
|
|
@@ -36,11 +40,11 @@ exports.matchReference = matchReference;
|
|
|
36
40
|
* resolution all still run and resolve a ubiquitous name when the context names
|
|
37
41
|
* its exact target. Real repos top out near ~40 same-named methods, so a normal
|
|
38
42
|
* codebase never reaches this; only bulk-vendored code does. Tune via
|
|
39
|
-
* `
|
|
43
|
+
* `HOMEGRAPH_AMBIGUOUS_NAME_CEILING`.
|
|
40
44
|
*/
|
|
41
45
|
const DEFAULT_AMBIGUOUS_NAME_CEILING = 500;
|
|
42
46
|
function resolveAmbiguousNameCeiling() {
|
|
43
|
-
const raw = process.env.
|
|
47
|
+
const raw = process.env.HOMEGRAPH_AMBIGUOUS_NAME_CEILING;
|
|
44
48
|
if (!raw)
|
|
45
49
|
return DEFAULT_AMBIGUOUS_NAME_CEILING;
|
|
46
50
|
const parsed = Number.parseInt(raw, 10);
|
|
@@ -140,7 +144,9 @@ function pickClosestFileNode(candidates, ref) {
|
|
|
140
144
|
const LANGUAGE_FAMILY = {
|
|
141
145
|
java: 'jvm', kotlin: 'jvm', scala: 'jvm',
|
|
142
146
|
swift: 'apple', objc: 'apple',
|
|
143
|
-
|
|
147
|
+
// ArkTS is a TS superset — every HarmonyOS project mixes `.ets` UI with
|
|
148
|
+
// `.ts` logic modules, so refs must cross freely between them.
|
|
149
|
+
typescript: 'web', tsx: 'web', javascript: 'web', jsx: 'web', arkts: 'web',
|
|
144
150
|
c: 'c', cpp: 'c',
|
|
145
151
|
// Razor/Blazor markup names C# types — same family so `@model Foo` /
|
|
146
152
|
// `<MyComponent/>` resolve to their `.cs` class through the cross-family gate.
|
|
@@ -222,6 +228,7 @@ function matchFunctionRef(ref, context) {
|
|
|
222
228
|
// implicit-self, Java/Kotlin method references.
|
|
223
229
|
const bareFnOnly = ref.language === 'typescript' || ref.language === 'tsx' ||
|
|
224
230
|
ref.language === 'javascript' || ref.language === 'jsx' ||
|
|
231
|
+
ref.language === 'arkts' ||
|
|
225
232
|
ref.language === 'cpp' || ref.language === 'python' ||
|
|
226
233
|
ref.language === 'php';
|
|
227
234
|
// Qualified member-pointer (`&Widget::on_click` → "Widget::on_click"):
|
|
@@ -325,6 +332,35 @@ function matchFunctionRef(ref, context) {
|
|
|
325
332
|
}
|
|
326
333
|
return null;
|
|
327
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* A function nested inside another FUNCTION is only callable from within its
|
|
337
|
+
* container — Python, JS/TS, and every closure language scope it lexically.
|
|
338
|
+
* Resolving a bare name from elsewhere to a nested local fabricates an edge
|
|
339
|
+
* scope already rules out: `join(...)` in one function must never bind to a
|
|
340
|
+
* `join` defined inside a DIFFERENT function (#1230). A candidate whose
|
|
341
|
+
* qualifiedName parent is a same-file function/method is kept only when the
|
|
342
|
+
* ref originates inside that parent's line range. Class members are
|
|
343
|
+
* unaffected (their parent resolves to a class-like node), as are top-level
|
|
344
|
+
* symbols and C++ namespace-prefixed names (the prefix has no node).
|
|
345
|
+
*/
|
|
346
|
+
function isLexicallyReachable(candidate, ref, context) {
|
|
347
|
+
if (candidate.kind !== 'function')
|
|
348
|
+
return true;
|
|
349
|
+
const qn = candidate.qualifiedName;
|
|
350
|
+
if (!qn || !qn.includes('::'))
|
|
351
|
+
return true;
|
|
352
|
+
const parentQn = qn.slice(0, qn.lastIndexOf('::'));
|
|
353
|
+
const containers = context
|
|
354
|
+
.getNodesByQualifiedName(parentQn)
|
|
355
|
+
.filter((p) => p.filePath === candidate.filePath &&
|
|
356
|
+
(p.kind === 'function' || p.kind === 'method') &&
|
|
357
|
+
p.startLine <= candidate.startLine &&
|
|
358
|
+
p.endLine >= candidate.endLine);
|
|
359
|
+
if (containers.length === 0)
|
|
360
|
+
return true;
|
|
361
|
+
return (ref.filePath === candidate.filePath &&
|
|
362
|
+
containers.some((p) => ref.line >= p.startLine && ref.line <= p.endLine));
|
|
363
|
+
}
|
|
328
364
|
/**
|
|
329
365
|
* Try to resolve a reference by exact name match
|
|
330
366
|
*/
|
|
@@ -339,7 +375,9 @@ function matchByExactName(ref, context) {
|
|
|
339
375
|
// findBestMatch — O(K²) per package, the dominant cost of "Resolving refs" on
|
|
340
376
|
// large import-heavy (front-end + back-end) repos (#915).
|
|
341
377
|
const candidates = applyLanguageGate(context.getNodesByName(ref.referenceName), ref)
|
|
342
|
-
.filter((n) => n.kind !== 'import')
|
|
378
|
+
.filter((n) => n.kind !== 'import')
|
|
379
|
+
// Nested locals are only reachable from inside their container (#1230).
|
|
380
|
+
.filter((n) => isLexicallyReachable(n, ref, context));
|
|
343
381
|
if (candidates.length === 0) {
|
|
344
382
|
return null;
|
|
345
383
|
}
|
|
@@ -384,7 +422,17 @@ function matchByQualifiedName(ref, context) {
|
|
|
384
422
|
if (!ref.referenceName.includes('::') && !ref.referenceName.includes('.')) {
|
|
385
423
|
return null;
|
|
386
424
|
}
|
|
387
|
-
|
|
425
|
+
// A method call `receiver.method()` can share an exact qualified name with a
|
|
426
|
+
// config-file key: `service.process()` (a `calls` ref named `service.process`)
|
|
427
|
+
// vs the yaml key `service.process`. Config keys are bound to their code refs
|
|
428
|
+
// upstream by the framework resolvers (`@Value` → `references`); a `calls` ref
|
|
429
|
+
// must never resolve to a yaml/properties config node — that's a wrong edge
|
|
430
|
+
// AND it hides the real callee. Drop those from both the exact and the partial
|
|
431
|
+
// candidate sets so resolution falls through to method resolution below (#1180).
|
|
432
|
+
const keepForRef = (nodes) => ref.referenceKind === 'calls'
|
|
433
|
+
? nodes.filter((n) => !(n.kind === 'constant' && (n.language === 'yaml' || n.language === 'properties')))
|
|
434
|
+
: nodes;
|
|
435
|
+
const candidates = keepForRef(context.getNodesByQualifiedName(ref.referenceName));
|
|
388
436
|
if (candidates.length === 1) {
|
|
389
437
|
return {
|
|
390
438
|
original: ref,
|
|
@@ -413,8 +461,7 @@ function matchByQualifiedName(ref, context) {
|
|
|
413
461
|
const parts = ref.referenceName.split(/[:.]/);
|
|
414
462
|
const lastName = parts[parts.length - 1];
|
|
415
463
|
if (lastName) {
|
|
416
|
-
const partialCandidates = context
|
|
417
|
-
.getNodesByName(lastName)
|
|
464
|
+
const partialCandidates = keepForRef(context.getNodesByName(lastName))
|
|
418
465
|
.filter((candidate) => candidate.qualifiedName.endsWith(ref.referenceName));
|
|
419
466
|
const chosen = preferCallSiteFile(partialCandidates, ref.filePath)[0];
|
|
420
467
|
if (chosen) {
|
|
@@ -468,17 +515,28 @@ depth = 0) {
|
|
|
468
515
|
// in-class (`class Foo { int bar() { ... } }`) or out-of-line in a separate
|
|
469
516
|
// file (`int Foo::bar() { ... }` in foo.cpp while class Foo is in foo.hpp).
|
|
470
517
|
// The previous same-file approach missed the latter — the typical C++ layout.
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
518
|
+
// Prefer the context's per-(type, method) memo: the raw name lookup fetches
|
|
519
|
+
// EVERY node sharing the method name — tens of thousands of rows for a
|
|
520
|
+
// collision-heavy Java name like `execute` — and re-filtering that per ref
|
|
521
|
+
// was a dominant term in the #1122 watchdog kill on large repos. Only the
|
|
522
|
+
// ref-independent filter is memoized; per-ref disambiguation stays below.
|
|
523
|
+
let matches;
|
|
524
|
+
if (context.getMethodMatches) {
|
|
525
|
+
matches = context.getMethodMatches(typeName, methodName, ref.language);
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
const methodCandidates = context.getNodesByName(methodName);
|
|
529
|
+
const want = `${typeName}::${methodName}`;
|
|
530
|
+
matches = [];
|
|
531
|
+
for (const m of methodCandidates) {
|
|
532
|
+
if (m.kind !== 'method')
|
|
533
|
+
continue;
|
|
534
|
+
if (m.language !== ref.language)
|
|
535
|
+
continue;
|
|
536
|
+
const qn = m.qualifiedName;
|
|
537
|
+
if (qn === want || qn.endsWith(`::${want}`)) {
|
|
538
|
+
matches.push(m);
|
|
539
|
+
}
|
|
482
540
|
}
|
|
483
541
|
}
|
|
484
542
|
if (matches.length === 0) {
|
|
@@ -490,11 +548,16 @@ depth = 0) {
|
|
|
490
548
|
// populated in the conformance pass. Still VALIDATED (the method must exist on
|
|
491
549
|
// a supertype), so a wrong inference produces no edge.
|
|
492
550
|
if (depth < 4 && context.getSupertypes) {
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
551
|
+
const viaSupers = nmTimedT('rmot-supers', ref, () => {
|
|
552
|
+
for (const supertype of context.getSupertypes(typeName, ref.language)) {
|
|
553
|
+
const via = resolveMethodOnType(supertype, methodName, ref, context, confidence, resolvedBy, preferredFqn, depth + 1);
|
|
554
|
+
if (via)
|
|
555
|
+
return via;
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
});
|
|
559
|
+
if (viaSupers)
|
|
560
|
+
return viaSupers;
|
|
498
561
|
}
|
|
499
562
|
return null;
|
|
500
563
|
}
|
|
@@ -565,10 +628,14 @@ function buildDeclaratorRegex(escapedReceiver) {
|
|
|
565
628
|
return new RegExp(`([A-Za-z_][\\w:]*(?:\\s*<[^;=(){}]+>)?(?:\\s*[*&]+)?)\\s*\\b${escapedReceiver}\\b\\s*(?=[;=,)\\[{(]|$)`);
|
|
566
629
|
}
|
|
567
630
|
function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
568
|
-
|
|
569
|
-
|
|
631
|
+
// Per-file lines cache when available — this runs per `receiver->method()`
|
|
632
|
+
// ref and re-splitting the file each time is the same quadratic as the
|
|
633
|
+
// shared inferrer's (#1122).
|
|
634
|
+
const lines = context.getFileLines
|
|
635
|
+
? context.getFileLines(ref.filePath)
|
|
636
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
637
|
+
if (!lines || lines.length === 0)
|
|
570
638
|
return null;
|
|
571
|
-
const lines = source.split(/\r?\n/);
|
|
572
639
|
const callLineIndex = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
573
640
|
const escapedReceiver = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
574
641
|
const receiverPattern = new RegExp(`\\b${escapedReceiver}\\b`);
|
|
@@ -601,10 +668,12 @@ function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
|
601
668
|
for (const headerPath of headerCandidates) {
|
|
602
669
|
if (!context.fileExists(headerPath))
|
|
603
670
|
continue;
|
|
604
|
-
const
|
|
605
|
-
|
|
671
|
+
const headerLines = context.getFileLines
|
|
672
|
+
? context.getFileLines(headerPath)
|
|
673
|
+
: (context.readFile(headerPath)?.split(/\r?\n/) ?? null);
|
|
674
|
+
if (!headerLines)
|
|
606
675
|
continue;
|
|
607
|
-
for (const line of
|
|
676
|
+
for (const line of headerLines) {
|
|
608
677
|
if (!receiverPattern.test(line))
|
|
609
678
|
continue;
|
|
610
679
|
const declaratorMatch = line.match(declaratorRegex);
|
|
@@ -810,10 +879,11 @@ function matchDottedCallChain(ref, context) {
|
|
|
810
879
|
// CRITICAL: resolve the TARGET via a synthetic bare-name ref, but return the
|
|
811
880
|
// match tied to the ORIGINAL `ref` (referenceName `inner().method`). The
|
|
812
881
|
// batched resolver (resolveAndPersistBatched) reads unresolved rows from
|
|
813
|
-
// offset 0 every pass and relies on
|
|
814
|
-
//
|
|
815
|
-
//
|
|
816
|
-
//
|
|
882
|
+
// offset 0 every pass and relies on the post-batch cleanup (row-id delete
|
|
883
|
+
// for DB-loaded refs, referenceName-keyed delete otherwise, #1269) to
|
|
884
|
+
// clear each resolved row so the batch empties. If we propagated the
|
|
885
|
+
// synthetic ref's bare `method` as `.original`, a key-based delete
|
|
886
|
+
// would never match the stored `inner().method` row, the batch would
|
|
817
887
|
// never drain, and the loop would re-resolve + re-insert forever (a runaway
|
|
818
888
|
// that grew gin's graph to 5M edges / 1.4 GB before this fix).
|
|
819
889
|
const bareRef = { ...ref, referenceName: method };
|
|
@@ -969,15 +1039,66 @@ function normalizeInferredTypeName(raw) {
|
|
|
969
1039
|
* PascalCase is required in the capture where the language convention allows,
|
|
970
1040
|
* as a cheap false-positive guard on top of resolveMethodOnType's validation.
|
|
971
1041
|
*/
|
|
1042
|
+
/**
|
|
1043
|
+
* Compiled-pattern memo for the receiver-type pattern builders below. They
|
|
1044
|
+
* run for EVERY `receiver.method()` ref the matcher attempts, compiling 2–4
|
|
1045
|
+
* fresh RegExp objects per call — and receivers repeat massively (`self`
|
|
1046
|
+
* alone accounts for tens of thousands of refs on a Lua repo, measured 41µs
|
|
1047
|
+
* per methodCall miss on kong with compilation a large slice). The patterns
|
|
1048
|
+
* are a pure function of (language, receiver) and non-global (`.match()`
|
|
1049
|
+
* never touches lastIndex), so shared instances are behavior-identical.
|
|
1050
|
+
* FIFO-capped with no per-get mutation (the §7a.6 LRU-churn lesson): a hit
|
|
1051
|
+
* costs one Map lookup, overflow evicts oldest, and an evicted entry simply
|
|
1052
|
+
* recompiles exactly as every call did before this memo.
|
|
1053
|
+
*/
|
|
1054
|
+
const PATTERN_MEMO = new Map();
|
|
1055
|
+
const PATTERN_MEMO_CAP = 8192;
|
|
1056
|
+
const INFER_SCAN_STATES = new WeakMap();
|
|
1057
|
+
function getInferScanStates(context) {
|
|
1058
|
+
let m = INFER_SCAN_STATES.get(context);
|
|
1059
|
+
if (!m) {
|
|
1060
|
+
m = new Map();
|
|
1061
|
+
INFER_SCAN_STATES.set(context, m);
|
|
1062
|
+
}
|
|
1063
|
+
return m;
|
|
1064
|
+
}
|
|
1065
|
+
/** Drop the per-context scan states (see ReferenceResolver.clearCaches). */
|
|
1066
|
+
function clearNameMatcherMemos(context) {
|
|
1067
|
+
INFER_SCAN_STATES.delete(context);
|
|
1068
|
+
}
|
|
1069
|
+
function memoPatterns(key, build) {
|
|
1070
|
+
const hit = PATTERN_MEMO.get(key);
|
|
1071
|
+
if (hit)
|
|
1072
|
+
return hit;
|
|
1073
|
+
const patterns = build();
|
|
1074
|
+
if (PATTERN_MEMO.size >= PATTERN_MEMO_CAP) {
|
|
1075
|
+
const oldest = PATTERN_MEMO.keys().next().value;
|
|
1076
|
+
if (oldest !== undefined)
|
|
1077
|
+
PATTERN_MEMO.delete(oldest);
|
|
1078
|
+
}
|
|
1079
|
+
PATTERN_MEMO.set(key, patterns);
|
|
1080
|
+
return patterns;
|
|
1081
|
+
}
|
|
972
1082
|
function localReceiverTypePatterns(language, r) {
|
|
1083
|
+
return memoPatterns(`${language}|${r}`, () => buildLocalReceiverTypePatterns(language, r));
|
|
1084
|
+
}
|
|
1085
|
+
function buildLocalReceiverTypePatterns(language, r) {
|
|
973
1086
|
switch (language) {
|
|
974
1087
|
case 'typescript':
|
|
975
1088
|
case 'javascript':
|
|
976
1089
|
case 'tsx':
|
|
977
1090
|
case 'jsx':
|
|
1091
|
+
case 'arkts':
|
|
978
1092
|
return [
|
|
979
1093
|
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), // = new Logger()
|
|
980
|
-
|
|
1094
|
+
// No keyword requirement, so this matches BOTH a local annotation
|
|
1095
|
+
// (`const lg: Logger`) and a typed parameter (`function use(lg: Logger)`
|
|
1096
|
+
// / `(lg: Logger) =>`) — the parameter case the old `const|let|var`
|
|
1097
|
+
// prefix excluded (#1125). Mirrors Kotlin/Swift/Scala; the capture stops
|
|
1098
|
+
// at `<` so a generic-typed param (`repo: Repository<User>`) still yields
|
|
1099
|
+
// `Repository`. resolveMethodOnType validates the type actually declares
|
|
1100
|
+
// the method, so the looser match produces no edge on a mis-inference.
|
|
1101
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.$]*)`), // lg: Logger (annotation or typed param)
|
|
981
1102
|
];
|
|
982
1103
|
case 'python':
|
|
983
1104
|
return [
|
|
@@ -1007,12 +1128,21 @@ function localReceiverTypePatterns(language, r) {
|
|
|
1007
1128
|
case 'rust':
|
|
1008
1129
|
return [
|
|
1009
1130
|
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\b(?:\\s*:[^=]+)?=\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg = Logger::new()/Logger{}/Logger
|
|
1010
|
-
|
|
1131
|
+
// No `let`, so this covers a `let lg: Logger` binding AND a typed
|
|
1132
|
+
// parameter (`fn use(lg: &Logger)`, a closure `|lg: Logger|`) — the
|
|
1133
|
+
// parameter case the old `let`-anchored pattern excluded (#1125).
|
|
1134
|
+
new RegExp(`\\b${r}\\s*:\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // lg: Logger (binding or typed param)
|
|
1011
1135
|
];
|
|
1012
1136
|
case 'go':
|
|
1013
1137
|
return [
|
|
1014
1138
|
new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{}
|
|
1015
1139
|
new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger
|
|
1140
|
+
// A typed parameter / method receiver (`func use(lg Logger)`,
|
|
1141
|
+
// `func (l Logger) M()`) — name-before-type with no `var`/`:=` (#1125).
|
|
1142
|
+
// PascalCase-guarded (unlike the anchored patterns above) to keep the
|
|
1143
|
+
// keyword-free `ident Type` shape from matching unrelated pairs; the
|
|
1144
|
+
// enclosing-scope bound already excludes package-level struct fields.
|
|
1145
|
+
new RegExp(`\\b${r}\\s+\\*?([A-Z][\\w.]*)`), // func use(lg Logger) / (l Logger)
|
|
1016
1146
|
];
|
|
1017
1147
|
case 'ruby':
|
|
1018
1148
|
return [
|
|
@@ -1026,18 +1156,35 @@ function localReceiverTypePatterns(language, r) {
|
|
|
1026
1156
|
case 'dart':
|
|
1027
1157
|
return [
|
|
1028
1158
|
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // var lg = Logger(...)
|
|
1029
|
-
|
|
1159
|
+
// Trailing `[=;,)]` (not just `[=;]`) so a typed parameter — `Logger lg)`
|
|
1160
|
+
// / `Logger lg,` — matches too, not only `Logger lg = ...` / `Logger lg;`
|
|
1161
|
+
// (#1125). Mirrors Java/C#.
|
|
1162
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg = ... / param
|
|
1030
1163
|
];
|
|
1031
1164
|
case 'php':
|
|
1032
1165
|
return [
|
|
1033
1166
|
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
|
1167
|
+
// A typed parameter (`function use(Logger $lg)`, `?Logger $lg`,
|
|
1168
|
+
// `\\App\\Logger $lg`, `&$lg` by-ref) and a typed `catch (E $e)` — the
|
|
1169
|
+
// type sits before the `$`-variable (#1125). Namespace `\\` allowed.
|
|
1170
|
+
new RegExp(`\\b([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // Logger $lg (typed param)
|
|
1034
1171
|
];
|
|
1035
1172
|
case 'lua':
|
|
1036
1173
|
case 'luau':
|
|
1037
1174
|
return [
|
|
1038
1175
|
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\.new\\b`), // local lg = Logger.new()
|
|
1039
1176
|
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\s*\\(`), // local lg = Logger(...) (callable table)
|
|
1040
|
-
|
|
1177
|
+
// Luau annotation (`local lg: Logger`) / typed param — but Lua's
|
|
1178
|
+
// method-call syntax is the IDENTICAL `receiver:Name` shape, and the
|
|
1179
|
+
// backward scan starts on the call's own line, so without a gate any
|
|
1180
|
+
// PascalCase method call (`lg:Log()`, the Roblox convention)
|
|
1181
|
+
// self-matches as "type = Log" before the scan reaches the real
|
|
1182
|
+
// declaration (#1124). The lookahead rejects a capture followed by
|
|
1183
|
+
// any of Lua's three call forms — `(args)`, `"s"`/`'s'`/`[[s]]`,
|
|
1184
|
+
// `{t}` — and its leading `[\w.]` alternative stops backtracking from
|
|
1185
|
+
// shrinking the capture to dodge the gate (`lg:Log()` would otherwise
|
|
1186
|
+
// still match, as `Lo`).
|
|
1187
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)(?![\\w.]|\\s*[({"'\\[])`), // local lg: Logger / typed param
|
|
1041
1188
|
];
|
|
1042
1189
|
case 'r':
|
|
1043
1190
|
return [
|
|
@@ -1048,6 +1195,36 @@ function localReceiverTypePatterns(language, r) {
|
|
|
1048
1195
|
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w]*)`), // var lg: TLogger / param lg: TLogger
|
|
1049
1196
|
new RegExp(`\\b${r}\\b\\s*:=\\s*([A-Z][\\w.]*)\\.Create\\b`), // lg := TLogger.Create
|
|
1050
1197
|
];
|
|
1198
|
+
case 'cfml':
|
|
1199
|
+
case 'cfscript':
|
|
1200
|
+
return [
|
|
1201
|
+
// svc = new UserService() / new path.to.UserService() — dotted component
|
|
1202
|
+
// paths reduce to their final segment via normalizeInferredTypeName.
|
|
1203
|
+
// Also matches inside tag markup (`<cfset svc = new UserService()>`)
|
|
1204
|
+
// since the scan reads raw source lines.
|
|
1205
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`),
|
|
1206
|
+
// The classic form: svc = createObject("component", "path.to.UserService")
|
|
1207
|
+
// (casing of createObject varies in the wild), plus the modern
|
|
1208
|
+
// single-argument form createObject("path.to.UserService").
|
|
1209
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']component["']\\s*,\\s*["']([\\w.]+)["']`),
|
|
1210
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']([\\w.]+)["']\\s*\\)`),
|
|
1211
|
+
// Typed cfscript parameter: `function save(UserService svc)` /
|
|
1212
|
+
// `required UserService svc` — CFML's built-in types (string, numeric,
|
|
1213
|
+
// any, struct…) are lowercase by convention, so the PascalCase guard
|
|
1214
|
+
// excludes them.
|
|
1215
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`),
|
|
1216
|
+
// Tag-form typed argument, either attribute order:
|
|
1217
|
+
// <cfargument name="svc" type="path.to.UserService">
|
|
1218
|
+
new RegExp(`\\bcfargument[^>\\n]*\\bname\\s*=\\s*["']${r}["'][^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1219
|
+
new RegExp(`\\bcfargument[^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["'][^>\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1220
|
+
// Component property (incl. WireBox DI): `property name="svc"
|
|
1221
|
+
// inject="UserService";` / `<cfproperty name="svc" type="UserService">`,
|
|
1222
|
+
// either attribute order. An inject DSL value with a namespace
|
|
1223
|
+
// (`inject="svc@core"`) captures only the leading name and simply
|
|
1224
|
+
// fails type-validation — no edge, never a wrong one.
|
|
1225
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\bname\\s*=\\s*["']${r}["'][^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1226
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["'][^;\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1227
|
+
];
|
|
1051
1228
|
default:
|
|
1052
1229
|
return [];
|
|
1053
1230
|
}
|
|
@@ -1074,20 +1251,70 @@ function enclosingScopeStartLine(ref, context) {
|
|
|
1074
1251
|
* scope so a same-named variable in another function can't leak in.
|
|
1075
1252
|
*/
|
|
1076
1253
|
function inferLocalReceiverType(receiverName, ref, context) {
|
|
1077
|
-
|
|
1254
|
+
// CFML scope prefixes: `variables.svc` / `this.svc` name a COMPONENT-scoped
|
|
1255
|
+
// field whose assignment or `property` declaration usually lives outside the
|
|
1256
|
+
// calling function (the init-pseudoconstructor / WireBox-injection pattern),
|
|
1257
|
+
// and `local.svc` is an explicit function-local. Strip the prefix so the
|
|
1258
|
+
// declaration patterns match (`variables.svc = new X()`, `property
|
|
1259
|
+
// name="svc" …`, `var svc = …` all bind the bare name), and widen the scan
|
|
1260
|
+
// to the whole file for the component-scoped forms — nearest-declaration-
|
|
1261
|
+
// backward still wins, so a function-local shadowing the field is preferred.
|
|
1262
|
+
let scanReceiver = receiverName;
|
|
1263
|
+
let componentScoped = false;
|
|
1264
|
+
if (ref.language === 'cfml' || ref.language === 'cfscript') {
|
|
1265
|
+
const scoped = receiverName.match(/^(variables|this|local|arguments)\.(.+)$/i);
|
|
1266
|
+
if (scoped) {
|
|
1267
|
+
scanReceiver = scoped[2];
|
|
1268
|
+
const scope = scoped[1].toLowerCase();
|
|
1269
|
+
componentScoped = scope === 'variables' || scope === 'this';
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
// PHP `$this->prop` receiver — the property's declaration lives outside the
|
|
1273
|
+
// calling method (a promoted constructor parameter `private readonly Foo $prop`,
|
|
1274
|
+
// a typed property `private Foo $prop;`, or a classic constructor parameter
|
|
1275
|
+
// `Foo $prop` assigned in __construct). Strip the prefix and widen the scan to
|
|
1276
|
+
// the whole file (the constructor may sit below the calling method), but —
|
|
1277
|
+
// unlike CFML's scopes above — switch to PROPERTY-shaped patterns: a plain
|
|
1278
|
+
// `$prop` local or parameter lives in a different namespace than `$this->prop`
|
|
1279
|
+
// and can never shadow it, so the generic local patterns would type the
|
|
1280
|
+
// property from unrelated same-named variables in other methods (a wrong
|
|
1281
|
+
// 0.9-confidence edge, not a missing one).
|
|
1282
|
+
let phpProperty = false;
|
|
1283
|
+
if (ref.language === 'php') {
|
|
1284
|
+
const scoped = receiverName.match(/^this->(.+)$/);
|
|
1285
|
+
if (scoped) {
|
|
1286
|
+
scanReceiver = scoped[1];
|
|
1287
|
+
componentScoped = true;
|
|
1288
|
+
phpProperty = true;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const escapedReceiver = scanReceiver.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1292
|
+
const patterns = phpProperty
|
|
1293
|
+
? phpPropertyTypePatterns(escapedReceiver)
|
|
1294
|
+
: localReceiverTypePatterns(ref.language, escapedReceiver);
|
|
1078
1295
|
if (patterns.length === 0)
|
|
1079
1296
|
return null;
|
|
1080
|
-
|
|
1081
|
-
|
|
1297
|
+
// Split through the context's per-file lines cache when available: this runs
|
|
1298
|
+
// for EVERY `receiver.method()` ref, and re-splitting the whole file per ref
|
|
1299
|
+
// was ~20% of total index CPU on Java-heavy repos (#1122).
|
|
1300
|
+
const lines = context.getFileLines
|
|
1301
|
+
? context.getFileLines(ref.filePath)
|
|
1302
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
1303
|
+
if (!lines || lines.length === 0)
|
|
1082
1304
|
return null;
|
|
1083
|
-
const lines = source.split(/\r?\n/);
|
|
1084
1305
|
const callIdx = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
1085
|
-
const startIdx =
|
|
1086
|
-
|
|
1087
|
-
|
|
1306
|
+
const startIdx = componentScoped
|
|
1307
|
+
? 0
|
|
1308
|
+
: Math.max(0, enclosingScopeStartLine(ref, context) - 1);
|
|
1309
|
+
const matchLine = (i) => {
|
|
1088
1310
|
const line = lines[i];
|
|
1089
1311
|
if (!line)
|
|
1090
|
-
|
|
1312
|
+
return null;
|
|
1313
|
+
// A generated/minified line (one multi-KB statement) is not something a
|
|
1314
|
+
// human-written local declaration lives on, and regexing it per ref is
|
|
1315
|
+
// pure waste — skip it rather than scan it.
|
|
1316
|
+
if (line.length > 10_000)
|
|
1317
|
+
return null;
|
|
1091
1318
|
for (const re of patterns) {
|
|
1092
1319
|
const m = line.match(re);
|
|
1093
1320
|
if (m && m[1]) {
|
|
@@ -1096,6 +1323,163 @@ function inferLocalReceiverType(receiverName, ref, context) {
|
|
|
1096
1323
|
return type;
|
|
1097
1324
|
}
|
|
1098
1325
|
}
|
|
1326
|
+
return null;
|
|
1327
|
+
};
|
|
1328
|
+
// Incremental-scan memo (INFER_SCAN_STATES): this scan runs for EVERY
|
|
1329
|
+
// `receiver.method()` ref and was measured at 61µs/ref on kong (2.4s of
|
|
1330
|
+
// worker time, 99% misses — `self:` calls hunting a declaration Lua never
|
|
1331
|
+
// writes). Refs for the same (file, scope, receiver) arrive in ~ascending
|
|
1332
|
+
// line order, and the scan is a pure function of the file's immutable
|
|
1333
|
+
// lines, so each line pays its regex matches ONCE per key instead of once
|
|
1334
|
+
// per ref: query(c) = highest matching line in [startIdx..c]; a monotonic
|
|
1335
|
+
// call extends the stored watermark by scanning only (hi..c] (the region
|
|
1336
|
+
// at-or-below the previous answer is already proven empty above it); a
|
|
1337
|
+
// non-monotonic call (rare — refs are rowid-ordered) falls back to the
|
|
1338
|
+
// plain bounded scan and leaves the state alone. componentScoped is keyed
|
|
1339
|
+
// out — its position-independent whole-file sweep below has different
|
|
1340
|
+
// semantics.
|
|
1341
|
+
if (!componentScoped) {
|
|
1342
|
+
const states = getInferScanStates(context);
|
|
1343
|
+
const key = `${ref.filePath}|${startIdx}|${ref.language}|${scanReceiver}`;
|
|
1344
|
+
const state = states.get(key);
|
|
1345
|
+
if (!state) {
|
|
1346
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1347
|
+
const type = matchLine(i);
|
|
1348
|
+
if (type) {
|
|
1349
|
+
states.set(key, { hi: callIdx, ansIdx: i, ansType: type });
|
|
1350
|
+
return type;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
states.set(key, { hi: callIdx, ansIdx: -1, ansType: null });
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
if (callIdx >= state.hi) {
|
|
1357
|
+
for (let i = callIdx; i > state.hi; i--) {
|
|
1358
|
+
const type = matchLine(i);
|
|
1359
|
+
if (type) {
|
|
1360
|
+
state.ansIdx = i;
|
|
1361
|
+
state.ansType = type;
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
state.hi = callIdx;
|
|
1366
|
+
return state.ansIdx >= startIdx ? state.ansType : null;
|
|
1367
|
+
}
|
|
1368
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1369
|
+
const type = matchLine(i);
|
|
1370
|
+
if (type)
|
|
1371
|
+
return type;
|
|
1372
|
+
}
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
// Nearest declaration wins: scan backward from the call to the scope start.
|
|
1376
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1377
|
+
const type = matchLine(i);
|
|
1378
|
+
if (type)
|
|
1379
|
+
return type;
|
|
1380
|
+
}
|
|
1381
|
+
// A component-scoped field's declaration is position-independent — the
|
|
1382
|
+
// `variables.svc = new X()` pseudoconstructor assignment or `property`
|
|
1383
|
+
// declaration may sit BELOW the calling function in the file — so when the
|
|
1384
|
+
// backward pass finds nothing, sweep the remainder of the file too.
|
|
1385
|
+
if (componentScoped) {
|
|
1386
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1387
|
+
const type = matchLine(i);
|
|
1388
|
+
if (type)
|
|
1389
|
+
return type;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// A PHP property with no statically-typed declaration (classic pre-7.4
|
|
1393
|
+
// style) may still be typed by what gets ASSIGNED to it — follow the
|
|
1394
|
+
// `$this->prop = $var` assignment to the assigned variable's own typed
|
|
1395
|
+
// declaration (a classic or multi-line constructor parameter, or a typed
|
|
1396
|
+
// setter's parameter).
|
|
1397
|
+
if (phpProperty) {
|
|
1398
|
+
return inferPhpAssignedPropertyType(escapedReceiver, lines, callIdx);
|
|
1399
|
+
}
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Patterns that recover a PHP class property's declared type for a
|
|
1404
|
+
* `$this->prop` receiver. Deliberately NOT localReceiverTypePatterns: only
|
|
1405
|
+
* property-shaped declarations qualify —
|
|
1406
|
+
* 1. a modifier-prefixed typed declaration, which covers both a typed
|
|
1407
|
+
* property (`private ?Foo $prop;`) and a promoted constructor parameter
|
|
1408
|
+
* (`private readonly Foo $prop`), and
|
|
1409
|
+
* 2. the pseudoconstructor assignment (`$this->prop = new Foo(...)`).
|
|
1410
|
+
* A bare `X $prop` parameter or `$prop = new X()` local elsewhere in the
|
|
1411
|
+
* file must NOT match: those variables can never alias `$this->prop`.
|
|
1412
|
+
* Union-typed properties (`Foo|Bar $prop`) yield no match and thus no edge —
|
|
1413
|
+
* silent beats wrong. The classic untyped-property-assigned-in-constructor
|
|
1414
|
+
* shape is handled by inferPhpAssignedPropertyType instead.
|
|
1415
|
+
*/
|
|
1416
|
+
function phpPropertyTypePatterns(r) {
|
|
1417
|
+
return memoPatterns(`php-prop|${r}`, () => buildPhpPropertyTypePatterns(r));
|
|
1418
|
+
}
|
|
1419
|
+
function buildPhpPropertyTypePatterns(r) {
|
|
1420
|
+
return [
|
|
1421
|
+
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)
|
|
1422
|
+
new RegExp(`\\$this->${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $this->prop = new Foo()
|
|
1423
|
+
];
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Second-chance typing for a PHP `$this->prop` receiver whose property
|
|
1427
|
+
* declaration carries no static type (classic pre-7.4 style): find the
|
|
1428
|
+
* `$this->prop = $var` assignment, then recover `$var`'s type from its own
|
|
1429
|
+
* declaration WITHIN the assignment's function — the constructor's (possibly
|
|
1430
|
+
* multi-line) parameter list, a typed setter's parameter, or a `= new X()`
|
|
1431
|
+
* local. The backward scan stops at the enclosing `function` line (checked
|
|
1432
|
+
* for a match first — a single-line `__construct(Foo $var) { ... }` carries
|
|
1433
|
+
* the typed parameter itself), so a same-named variable in another method
|
|
1434
|
+
* can never type the property.
|
|
1435
|
+
*/
|
|
1436
|
+
function inferPhpAssignedPropertyType(escapedProp, lines, callIdx) {
|
|
1437
|
+
const assignRe = new RegExp(`\\$this->${escapedProp}\\b\\s*=\\s*\\$(\\w+)\\b`);
|
|
1438
|
+
const assignAt = (i) => {
|
|
1439
|
+
const line = lines[i];
|
|
1440
|
+
if (!line || line.length > 10_000)
|
|
1441
|
+
return null;
|
|
1442
|
+
return line.match(assignRe);
|
|
1443
|
+
};
|
|
1444
|
+
// The assignment is position-independent relative to the call — nearest-
|
|
1445
|
+
// backward first, then sweep forward, same order as the componentScoped scan.
|
|
1446
|
+
let assignIdx = -1;
|
|
1447
|
+
let varName = null;
|
|
1448
|
+
for (let i = callIdx; i >= 0; i--) {
|
|
1449
|
+
const m = assignAt(i);
|
|
1450
|
+
if (m) {
|
|
1451
|
+
assignIdx = i;
|
|
1452
|
+
varName = m[1];
|
|
1453
|
+
break;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
if (varName === null) {
|
|
1457
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1458
|
+
const m = assignAt(i);
|
|
1459
|
+
if (m) {
|
|
1460
|
+
assignIdx = i;
|
|
1461
|
+
varName = m[1];
|
|
1462
|
+
break;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (varName === null)
|
|
1467
|
+
return null;
|
|
1468
|
+
const varPatterns = localReceiverTypePatterns('php', varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
1469
|
+
for (let i = assignIdx; i >= 0; i--) {
|
|
1470
|
+
const line = lines[i];
|
|
1471
|
+
if (line && line.length <= 10_000) {
|
|
1472
|
+
for (const re of varPatterns) {
|
|
1473
|
+
const m = line.match(re);
|
|
1474
|
+
if (m && m[1]) {
|
|
1475
|
+
const type = normalizeInferredTypeName(m[1]);
|
|
1476
|
+
if (type)
|
|
1477
|
+
return type;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
if (line && /\bfunction\b/.test(line))
|
|
1482
|
+
break;
|
|
1099
1483
|
}
|
|
1100
1484
|
return null;
|
|
1101
1485
|
}
|
|
@@ -1112,7 +1496,17 @@ function matchMethodCall(ref, context) {
|
|
|
1112
1496
|
// (with its existing single-candidate / receiver-overlap guards). Without this
|
|
1113
1497
|
// a multi-dot extension-method call (C# DI `builder.Services.AddCoreServices()`,
|
|
1114
1498
|
// `Guard.Against.X()`) matched no pattern and never resolved.
|
|
1115
|
-
|
|
1499
|
+
// C++ explicit operator call `a.operator+(b)` reaches the resolver as
|
|
1500
|
+
// `a.operator+` (#1247) — the operator's symbol chars (`+`, `==`, `[]`, `()`)
|
|
1501
|
+
// fail the \w method part of the plain pattern, so admit them explicitly.
|
|
1502
|
+
// Names like `operatorTable` stay on the plain pattern (tried first); the
|
|
1503
|
+
// operator form requires at least one non-word char after `operator`, and
|
|
1504
|
+
// every downstream strategy compares the method part by exact string
|
|
1505
|
+
// equality, so a stray match can't invent an edge.
|
|
1506
|
+
const dotMatch = ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/) ??
|
|
1507
|
+
(ref.language === 'cpp'
|
|
1508
|
+
? ref.referenceName.match(/^([\w.]+)\.(operator[^\w\s.]+)$/)
|
|
1509
|
+
: null);
|
|
1116
1510
|
const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/);
|
|
1117
1511
|
// Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`).
|
|
1118
1512
|
// Recognize these receiver/method separators so local-variable receiver-type
|
|
@@ -1124,6 +1518,24 @@ function matchMethodCall(ref, context) {
|
|
|
1124
1518
|
const rDollarMatch = ref.language === 'r'
|
|
1125
1519
|
? ref.referenceName.match(/^([\w.]+)\$(\w+)$/)
|
|
1126
1520
|
: null;
|
|
1521
|
+
// PHP property receiver: `$this->prop->method()` reaches the resolver as
|
|
1522
|
+
// `this->prop.method` (the extractor records the receiver's raw text with the
|
|
1523
|
+
// leading `$` stripped). Resolve it EXCLUSIVELY through declared-type
|
|
1524
|
+
// inference + resolveMethodOnType validation — the name-similarity strategies
|
|
1525
|
+
// below must never see this shape, so a property whose type can't be
|
|
1526
|
+
// recovered stays unlinked rather than guessed (a wrong inference produces no
|
|
1527
|
+
// edge rather than a wrong one). Deeper chains (`this->a->b.method`) don't
|
|
1528
|
+
// match the single-property pattern and stay unlinked, same as before.
|
|
1529
|
+
const phpThisPropMatch = ref.language === 'php'
|
|
1530
|
+
? ref.referenceName.match(/^(this->\w+)\.(\w+)$/)
|
|
1531
|
+
: null;
|
|
1532
|
+
if (phpThisPropMatch) {
|
|
1533
|
+
const [, receiver, phpMethodName] = phpThisPropMatch;
|
|
1534
|
+
const inferredType = inferLocalReceiverType(receiver, ref, context);
|
|
1535
|
+
if (!inferredType)
|
|
1536
|
+
return null;
|
|
1537
|
+
return resolveMethodOnType(inferredType, phpMethodName, ref, context, 0.9, 'instance-method', importedFqnOf(inferredType, ref, context));
|
|
1538
|
+
}
|
|
1127
1539
|
const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch;
|
|
1128
1540
|
if (!match) {
|
|
1129
1541
|
return null;
|
|
@@ -1138,9 +1550,9 @@ function matchMethodCall(ref, context) {
|
|
|
1138
1550
|
// shared source-based inferrer. resolveMethodOnType validates the method
|
|
1139
1551
|
// exists on the inferred type, so a mis-inference produces no edge.
|
|
1140
1552
|
if (inferableReceiver) {
|
|
1141
|
-
const inferredType = ref.language === 'cpp'
|
|
1553
|
+
const inferredType = nmTimedT('mc-infer', ref, () => ref.language === 'cpp'
|
|
1142
1554
|
? inferCppReceiverType(objectOrClass, ref, context)
|
|
1143
|
-
: inferLocalReceiverType(objectOrClass, ref, context);
|
|
1555
|
+
: inferLocalReceiverType(objectOrClass, ref, context));
|
|
1144
1556
|
if (inferredType) {
|
|
1145
1557
|
// Java/Kotlin: when two classes share the simple name, the file's import
|
|
1146
1558
|
// pins WHICH one (#314). Other languages disambiguate by call-site file.
|
|
@@ -1149,12 +1561,26 @@ function matchMethodCall(ref, context) {
|
|
|
1149
1561
|
.getImportMappings(ref.filePath, ref.language)
|
|
1150
1562
|
.find((i) => i.localName === inferredType)?.source
|
|
1151
1563
|
: undefined;
|
|
1152
|
-
const typedMatch = resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn);
|
|
1564
|
+
const typedMatch = nmTimedT('mc-rmot', ref, () => resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn));
|
|
1153
1565
|
if (typedMatch) {
|
|
1154
1566
|
return typedMatch;
|
|
1155
1567
|
}
|
|
1156
1568
|
}
|
|
1157
1569
|
}
|
|
1570
|
+
// Go 2-hop field chain `base.field.Method` (#1276): the base's type comes
|
|
1571
|
+
// from the enclosing scope (typed parameter / method receiver / local var),
|
|
1572
|
+
// the field's declared type from that struct's own declaration lines, and
|
|
1573
|
+
// the method is VALIDATED on the field's type by resolveMethodOnType. This
|
|
1574
|
+
// branch is EXCLUSIVE for chained Go receivers: when the hop can't be
|
|
1575
|
+
// inferred or the field's type is external (`conn *sql.DB` — no project
|
|
1576
|
+
// node), the ref stays unresolved rather than falling through to the
|
|
1577
|
+
// bare-name strategies below, which is exactly how `target.conn.Exec(...)`
|
|
1578
|
+
// fabricated a dependency on an unrelated local interface's same-named
|
|
1579
|
+
// method. Chained Go receivers were never emitted before #1276, so there
|
|
1580
|
+
// is no prior recall to preserve on the fallback path.
|
|
1581
|
+
if (ref.language === 'go' && dotMatch && objectOrClass.includes('.')) {
|
|
1582
|
+
return matchGoFieldChainCall(objectOrClass, methodName, ref, context);
|
|
1583
|
+
}
|
|
1158
1584
|
// Java/Kotlin: receiver may be a field whose name doesn't match the type by
|
|
1159
1585
|
// Java naming convention (`userbo` → class `UserBO`, abbreviated). Look up
|
|
1160
1586
|
// the field in the enclosing class to get its declared type, then resolve
|
|
@@ -1168,7 +1594,7 @@ function matchMethodCall(ref, context) {
|
|
|
1168
1594
|
// imported FQN so resolveMethodOnType can disambiguate (#314).
|
|
1169
1595
|
const imports = context.getImportMappings(ref.filePath, ref.language);
|
|
1170
1596
|
const importedFqn = imports.find((i) => i.localName === inferredType)?.source;
|
|
1171
|
-
const typedMatch = resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn);
|
|
1597
|
+
const typedMatch = nmTimedT('mc-rmot', ref, () => resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn));
|
|
1172
1598
|
if (typedMatch) {
|
|
1173
1599
|
return typedMatch;
|
|
1174
1600
|
}
|
|
@@ -1179,33 +1605,23 @@ function matchMethodCall(ref, context) {
|
|
|
1179
1605
|
// with a `Logger` in both `a/` and `b/`), try the class in the call site's
|
|
1180
1606
|
// own file first — otherwise the first-indexed class wins and a call in `b/`
|
|
1181
1607
|
// resolves to `a/`'s method (#1079).
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
return {
|
|
1194
|
-
original: ref,
|
|
1195
|
-
targetNodeId: methodNode.id,
|
|
1196
|
-
confidence: 0.85,
|
|
1197
|
-
resolvedBy: 'qualified-name',
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1608
|
+
const strat1 = nmTimedT('mc-class', ref, () => {
|
|
1609
|
+
// ArkTS: same-file OR imported local name only. Unrestricted project-wide
|
|
1610
|
+
// matches welded every CFG `Foo.getInstance` seed onto one method; requiring
|
|
1611
|
+
// `import { Foo }` (or a same-file class) keeps cross-module `Lib.use` while
|
|
1612
|
+
// dropping ambient / unimported stubs.
|
|
1613
|
+
let classCandidates = preferCallSiteFile(context.getNodesByName(objectOrClass), ref.filePath);
|
|
1614
|
+
if (ref.language === 'arkts') {
|
|
1615
|
+
const imported = context
|
|
1616
|
+
.getImportMappings(ref.filePath, ref.language)
|
|
1617
|
+
.some((i) => i.localName === objectOrClass);
|
|
1618
|
+
classCandidates = classCandidates.filter((n) => n.filePath === ref.filePath || imported);
|
|
1200
1619
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
const fuzzyClassCandidates = preferCallSiteFile(context.getNodesByName(capitalizedReceiver), ref.filePath);
|
|
1207
|
-
for (const classNode of fuzzyClassCandidates) {
|
|
1208
|
-
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
|
|
1620
|
+
for (const classNode of classCandidates) {
|
|
1621
|
+
if (classNode.kind === 'class' ||
|
|
1622
|
+
classNode.kind === 'struct' ||
|
|
1623
|
+
classNode.kind === 'interface' ||
|
|
1624
|
+
(ref.language === 'arkts' && classNode.kind === 'component')) {
|
|
1209
1625
|
// Skip cross-language class matches
|
|
1210
1626
|
if (classNode.language !== ref.language)
|
|
1211
1627
|
continue;
|
|
@@ -1217,67 +1633,192 @@ function matchMethodCall(ref, context) {
|
|
|
1217
1633
|
return {
|
|
1218
1634
|
original: ref,
|
|
1219
1635
|
targetNodeId: methodNode.id,
|
|
1220
|
-
confidence: 0.
|
|
1221
|
-
resolvedBy: '
|
|
1636
|
+
confidence: 0.85,
|
|
1637
|
+
resolvedBy: 'qualified-name',
|
|
1222
1638
|
};
|
|
1223
1639
|
}
|
|
1224
1640
|
}
|
|
1225
1641
|
}
|
|
1642
|
+
return null;
|
|
1643
|
+
});
|
|
1644
|
+
if (strat1)
|
|
1645
|
+
return strat1;
|
|
1646
|
+
// Strategy 2: Instance variable receiver - try capitalized form to find class
|
|
1647
|
+
// e.g., "permissionEngine" → look for classes containing "PermissionEngine"
|
|
1648
|
+
const capitalizedReceiver = objectOrClass.charAt(0).toUpperCase() + objectOrClass.slice(1);
|
|
1649
|
+
// ArkTS: skip — cross-module seeds are already `Class.method`; capitalizing
|
|
1650
|
+
// locals caused wrong bindings on large Harmony trees.
|
|
1651
|
+
if (ref.language !== 'arkts' && capitalizedReceiver !== objectOrClass) {
|
|
1652
|
+
const strat2 = nmTimedT('mc-capital', ref, () => {
|
|
1653
|
+
const fuzzyClassCandidates = preferCallSiteFile(context.getNodesByName(capitalizedReceiver), ref.filePath);
|
|
1654
|
+
for (const classNode of fuzzyClassCandidates) {
|
|
1655
|
+
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
|
|
1656
|
+
// Skip cross-language class matches
|
|
1657
|
+
if (classNode.language !== ref.language)
|
|
1658
|
+
continue;
|
|
1659
|
+
const nodesInFile = context.getNodesInFile(classNode.filePath);
|
|
1660
|
+
const methodNode = nodesInFile.find((n) => n.kind === 'method' &&
|
|
1661
|
+
n.name === methodName &&
|
|
1662
|
+
n.qualifiedName.includes(classNode.name));
|
|
1663
|
+
if (methodNode) {
|
|
1664
|
+
return {
|
|
1665
|
+
original: ref,
|
|
1666
|
+
targetNodeId: methodNode.id,
|
|
1667
|
+
confidence: 0.8,
|
|
1668
|
+
resolvedBy: 'instance-method',
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
return null;
|
|
1674
|
+
});
|
|
1675
|
+
if (strat2)
|
|
1676
|
+
return strat2;
|
|
1226
1677
|
}
|
|
1227
1678
|
// Strategy 3: Find methods by name across the codebase, match by receiver
|
|
1228
1679
|
// name similarity with the containing class. Handles abbreviated variable
|
|
1229
1680
|
// names like permissionEngine → PermissionRuleEngine.
|
|
1681
|
+
// ArkTS: never — unique/score fallthrough turns `Foo.getInstance` into a
|
|
1682
|
+
// project-wide getInstance magnet when Class is wrong/missing.
|
|
1683
|
+
if (ref.language === 'arkts') {
|
|
1684
|
+
return null;
|
|
1685
|
+
}
|
|
1230
1686
|
if (methodName) {
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
const methods = methodCandidates.filter((n) => n.kind === 'method' && n.name === methodName);
|
|
1242
|
-
// Filter to same-language candidates first
|
|
1243
|
-
const sameLanguageMethods = methods.filter(m => m.language === ref.language);
|
|
1244
|
-
const targetMethods = sameLanguageMethods.length > 0 ? sameLanguageMethods : methods;
|
|
1245
|
-
// If only one same-language method with this name exists, use it
|
|
1246
|
-
if (targetMethods.length === 1 && targetMethods[0].language === ref.language) {
|
|
1247
|
-
return {
|
|
1248
|
-
original: ref,
|
|
1249
|
-
targetNodeId: targetMethods[0].id,
|
|
1250
|
-
confidence: 0.7,
|
|
1251
|
-
resolvedBy: 'instance-method',
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
// Multiple methods: score by receiver name word overlap with class name
|
|
1255
|
-
if (targetMethods.length > 1) {
|
|
1256
|
-
const receiverWords = splitCamelCase(objectOrClass);
|
|
1257
|
-
let bestMatch;
|
|
1258
|
-
let bestScore = 0;
|
|
1259
|
-
// Same-file candidates first, so a score tie (`score > bestScore` keeps
|
|
1260
|
-
// the first seen) resolves to the call site's own file rather than the
|
|
1261
|
-
// first-indexed duplicate (#1079).
|
|
1262
|
-
for (const method of preferCallSiteFile(targetMethods, ref.filePath)) {
|
|
1263
|
-
const classWords = splitCamelCase(method.qualifiedName);
|
|
1264
|
-
let score = receiverWords.filter(w => classWords.some(cw => cw.toLowerCase() === w.toLowerCase())).length;
|
|
1265
|
-
// Bonus for same language
|
|
1266
|
-
if (method.language === ref.language)
|
|
1267
|
-
score += 1;
|
|
1268
|
-
if (score > bestScore) {
|
|
1269
|
-
bestScore = score;
|
|
1270
|
-
bestMatch = method;
|
|
1271
|
-
}
|
|
1687
|
+
const strat3 = nmTimedT('mc-byname', ref, () => {
|
|
1688
|
+
const methodCandidates = context.getNodesByName(methodName);
|
|
1689
|
+
// Ubiquitous-method ceiling (#999): a method name re-declared across a
|
|
1690
|
+
// vendored theme/SDK (Metronic's `init`/`update`/… on every widget) yields
|
|
1691
|
+
// K candidates that receiver-word overlap can't reliably disambiguate —
|
|
1692
|
+
// and filtering + scoring all K per call is the O(K²) cost that wedged
|
|
1693
|
+
// "Resolving refs" for 15-28 min. Bail before the O(K) work; Strategy 1/2
|
|
1694
|
+
// (class-name match) already had their precise shot above.
|
|
1695
|
+
if (methodCandidates.length > AMBIGUOUS_NAME_CEILING) {
|
|
1696
|
+
return null;
|
|
1272
1697
|
}
|
|
1273
|
-
|
|
1698
|
+
const methods = methodCandidates.filter((n) => n.kind === 'method' && n.name === methodName);
|
|
1699
|
+
// Filter to same-language candidates first
|
|
1700
|
+
const sameLanguageMethods = methods.filter(m => m.language === ref.language);
|
|
1701
|
+
const targetMethods = sameLanguageMethods.length > 0 ? sameLanguageMethods : methods;
|
|
1702
|
+
// If only one same-language method with this name exists, use it
|
|
1703
|
+
if (targetMethods.length === 1 && targetMethods[0].language === ref.language) {
|
|
1274
1704
|
return {
|
|
1275
1705
|
original: ref,
|
|
1276
|
-
targetNodeId:
|
|
1277
|
-
confidence: 0.
|
|
1706
|
+
targetNodeId: targetMethods[0].id,
|
|
1707
|
+
confidence: 0.7,
|
|
1278
1708
|
resolvedBy: 'instance-method',
|
|
1279
1709
|
};
|
|
1280
1710
|
}
|
|
1711
|
+
// Multiple methods: score by receiver name word overlap with class name
|
|
1712
|
+
if (targetMethods.length > 1) {
|
|
1713
|
+
const receiverWords = splitCamelCase(objectOrClass);
|
|
1714
|
+
let bestMatch;
|
|
1715
|
+
let bestScore = 0;
|
|
1716
|
+
// Same-file candidates first, so a score tie (`score > bestScore` keeps
|
|
1717
|
+
// the first seen) resolves to the call site's own file rather than the
|
|
1718
|
+
// first-indexed duplicate (#1079).
|
|
1719
|
+
for (const method of preferCallSiteFile(targetMethods, ref.filePath)) {
|
|
1720
|
+
const classWords = splitCamelCase(method.qualifiedName);
|
|
1721
|
+
let score = receiverWords.filter(w => classWords.some(cw => cw.toLowerCase() === w.toLowerCase())).length;
|
|
1722
|
+
// Bonus for same language
|
|
1723
|
+
if (method.language === ref.language)
|
|
1724
|
+
score += 1;
|
|
1725
|
+
if (score > bestScore) {
|
|
1726
|
+
bestScore = score;
|
|
1727
|
+
bestMatch = method;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (bestMatch && bestScore >= 2) {
|
|
1731
|
+
return {
|
|
1732
|
+
original: ref,
|
|
1733
|
+
targetNodeId: bestMatch.id,
|
|
1734
|
+
confidence: 0.65,
|
|
1735
|
+
resolvedBy: 'instance-method',
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
return null;
|
|
1740
|
+
});
|
|
1741
|
+
if (strat3)
|
|
1742
|
+
return strat3;
|
|
1743
|
+
}
|
|
1744
|
+
return null;
|
|
1745
|
+
}
|
|
1746
|
+
/** Go builtin/primitive field types that can never carry a project method. */
|
|
1747
|
+
const GO_BUILTIN_FIELD_TYPES = new Set([
|
|
1748
|
+
'string', 'bool', 'byte', 'rune', 'error', 'any',
|
|
1749
|
+
'int', 'int8', 'int16', 'int32', 'int64',
|
|
1750
|
+
'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uintptr',
|
|
1751
|
+
'float32', 'float64', 'complex64', 'complex128',
|
|
1752
|
+
'chan', 'map', 'func', 'struct', 'interface',
|
|
1753
|
+
]);
|
|
1754
|
+
/**
|
|
1755
|
+
* Resolve a Go 2-hop field-chain call `base.field.Method(...)` (#1276):
|
|
1756
|
+
* `target.conn.Exec("insert")` where `func (target *Target) Write()` and
|
|
1757
|
+
* `type Target struct { conn *sql.DB }`. Two inference hops, both read from
|
|
1758
|
+
* source the same way #1108 does:
|
|
1759
|
+
* 1. `base`'s type from the enclosing scope (method receiver, typed
|
|
1760
|
+
* parameter, or local declaration) via inferLocalReceiverType;
|
|
1761
|
+
* 2. `field`'s declared type from the struct's own declaration lines.
|
|
1762
|
+
* The method is then resolved AND VALIDATED on the field's type. A field
|
|
1763
|
+
* whose type has no project node (`sql.DB`, any external dependency) yields
|
|
1764
|
+
* null — the caller treats this branch as exclusive for chained Go
|
|
1765
|
+
* receivers, so the ref stays unresolved instead of name-guessing.
|
|
1766
|
+
*/
|
|
1767
|
+
function matchGoFieldChainCall(receiverChain, methodName, ref, context) {
|
|
1768
|
+
const segs = receiverChain.split('.');
|
|
1769
|
+
if (segs.length !== 2 || !segs[0] || !segs[1])
|
|
1770
|
+
return null;
|
|
1771
|
+
const [base, field] = segs;
|
|
1772
|
+
const baseType = inferLocalReceiverType(base, ref, context);
|
|
1773
|
+
if (!baseType)
|
|
1774
|
+
return null;
|
|
1775
|
+
const fieldEsc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1776
|
+
const fieldTypeRe = new RegExp(`\\b${fieldEsc}\\s+\\*?\\[?\\]?([A-Za-z_][\\w.]*)`);
|
|
1777
|
+
const structs = preferCallSiteFile(context.getNodesByName(baseType), ref.filePath).filter((n) => (n.kind === 'struct' || n.kind === 'class') && n.language === 'go');
|
|
1778
|
+
for (const s of structs) {
|
|
1779
|
+
const source = context.readFile(s.filePath);
|
|
1780
|
+
if (!source)
|
|
1781
|
+
continue;
|
|
1782
|
+
// Only the struct's own declaration lines — a same-named identifier
|
|
1783
|
+
// elsewhere in the file can't donate a type. Matched LINE BY LINE with
|
|
1784
|
+
// comments stripped: chi's `Mux` has a doc comment reading "the tree
|
|
1785
|
+
// router" right above `tree *node`, and a whole-block match captured
|
|
1786
|
+
// `router` from the prose instead of `node` from the field.
|
|
1787
|
+
const declLines = source.split('\n').slice(Math.max(0, s.startLine - 1), s.endLine);
|
|
1788
|
+
for (const rawLine of declLines) {
|
|
1789
|
+
const line = rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
|
|
1790
|
+
const m = line.match(fieldTypeRe);
|
|
1791
|
+
if (!m || !m[1])
|
|
1792
|
+
continue;
|
|
1793
|
+
const rawType = m[1];
|
|
1794
|
+
// A package-qualified field type (`http.Handler`, `sql.DB`) is only
|
|
1795
|
+
// followed when the package is IN-MODULE: stripping the qualifier and
|
|
1796
|
+
// matching the bare name would conflate a stdlib/third-party type with
|
|
1797
|
+
// any same-named project type — on chi, `handler http.Handler` bound
|
|
1798
|
+
// to an example app's unrelated local `Handler`. That is the exact
|
|
1799
|
+
// fabrication this matcher exists to prevent (#1276).
|
|
1800
|
+
if (rawType.includes('.')) {
|
|
1801
|
+
const pkg = rawType.split('.')[0];
|
|
1802
|
+
const mod = context.getGoModule?.();
|
|
1803
|
+
const imp = context
|
|
1804
|
+
.getImportMappings(s.filePath, 'go')
|
|
1805
|
+
.find((i) => i.localName === pkg);
|
|
1806
|
+
const inModule = !!mod &&
|
|
1807
|
+
!!imp &&
|
|
1808
|
+
(imp.source === mod.modulePath || imp.source.startsWith(mod.modulePath + '/'));
|
|
1809
|
+
if (!inModule)
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
// Unexported (lowercase) types are idiomatic Go and stay eligible —
|
|
1813
|
+
// chi's `mx.tree.FindRoute()` chains through `tree *node`. A
|
|
1814
|
+
// mis-capture is harmless: resolveMethodOnType only returns a
|
|
1815
|
+
// validated `<type>::<method>` match.
|
|
1816
|
+
const fieldType = rawType.split('.').pop();
|
|
1817
|
+
if (!fieldType || !/^[A-Za-z_]/.test(fieldType) || GO_BUILTIN_FIELD_TYPES.has(fieldType))
|
|
1818
|
+
continue;
|
|
1819
|
+
const resolved = resolveMethodOnType(fieldType, methodName, ref, context, 0.85, 'instance-method');
|
|
1820
|
+
if (resolved)
|
|
1821
|
+
return resolved;
|
|
1281
1822
|
}
|
|
1282
1823
|
}
|
|
1283
1824
|
return null;
|
|
@@ -1438,6 +1979,51 @@ function matchFuzzy(ref, context) {
|
|
|
1438
1979
|
/**
|
|
1439
1980
|
* Match all strategies in order of confidence
|
|
1440
1981
|
*/
|
|
1982
|
+
/** ArkUI attribute-helper decorators a `.attr(...)` chain may resolve to. */
|
|
1983
|
+
const ARKUI_ATTRIBUTE_DECORATORS = new Set(['Extend', 'Styles', 'AnimatableExtend', 'Builder']);
|
|
1984
|
+
/**
|
|
1985
|
+
* HOMEGRAPH_RESOLVE_PROFILE=2 sub-stage attribution for matchReference's
|
|
1986
|
+
* strategy pipeline (`nm:<stage>|<refKind>|hit/miss`). Module-global because
|
|
1987
|
+
* the matcher is a free function; each thread (main + every pool worker) has
|
|
1988
|
+
* its own module instance, and dumpNameMatcherProfile is invoked from
|
|
1989
|
+
* ReferenceResolver.dumpResolveProfile so worker tables surface too.
|
|
1990
|
+
* (CODEGRAPH_RESOLVE_PROFILE still accepted for upstream-compat.)
|
|
1991
|
+
*/
|
|
1992
|
+
const NM_PROFILE = process.env.HOMEGRAPH_RESOLVE_PROFILE === '2' ||
|
|
1993
|
+
process.env.CODEGRAPH_RESOLVE_PROFILE === '2'
|
|
1994
|
+
? new Map()
|
|
1995
|
+
: null;
|
|
1996
|
+
function nmTimedT(stage, ref, fn) {
|
|
1997
|
+
if (!NM_PROFILE)
|
|
1998
|
+
return fn();
|
|
1999
|
+
const t0 = process.hrtime.bigint();
|
|
2000
|
+
const r = fn();
|
|
2001
|
+
const dt = process.hrtime.bigint() - t0;
|
|
2002
|
+
const key = `nm:${stage}|${ref.referenceKind}|${r ? 'hit' : 'miss'}`;
|
|
2003
|
+
const slot = NM_PROFILE.get(key);
|
|
2004
|
+
if (slot) {
|
|
2005
|
+
slot.n++;
|
|
2006
|
+
slot.ns += dt;
|
|
2007
|
+
}
|
|
2008
|
+
else {
|
|
2009
|
+
NM_PROFILE.set(key, { n: 1, ns: dt });
|
|
2010
|
+
}
|
|
2011
|
+
return r;
|
|
2012
|
+
}
|
|
2013
|
+
function nmTimed(stage, ref, fn) {
|
|
2014
|
+
return nmTimedT(stage, ref, fn);
|
|
2015
|
+
}
|
|
2016
|
+
/** Dump this thread's matchReference sub-stage table to stderr (no-op unless =2). */
|
|
2017
|
+
function dumpNameMatcherProfile(label) {
|
|
2018
|
+
if (!NM_PROFILE || NM_PROFILE.size === 0)
|
|
2019
|
+
return;
|
|
2020
|
+
const rows = [...NM_PROFILE.entries()]
|
|
2021
|
+
.map(([k, v]) => ({ k, n: v.n, ms: Number(v.ns / 1000000n) }))
|
|
2022
|
+
.sort((a, b) => b.ms - a.ms);
|
|
2023
|
+
for (const r of rows) {
|
|
2024
|
+
console.error(`[resolve-profile] ${label} ${r.k}: n=${r.n} total=${(r.ms / 1000).toFixed(1)}s avg=${((r.ms * 1000) / Math.max(1, r.n)).toFixed(0)}µs`);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1441
2027
|
function matchReference(ref, context) {
|
|
1442
2028
|
// Function-as-value refs (#756) resolve ONLY through the dedicated matcher —
|
|
1443
2029
|
// never the fuzzy/qualified fallthrough below (a wrong callback edge is
|
|
@@ -1445,21 +2031,72 @@ function matchReference(ref, context) {
|
|
|
1445
2031
|
if (ref.referenceKind === 'function_ref') {
|
|
1446
2032
|
return matchFunctionRef(ref, context);
|
|
1447
2033
|
}
|
|
2034
|
+
// ArkTS chained UI attributes — emitted with a leading dot (`.titleStyle`,
|
|
2035
|
+
// `.width`) by the extractor — resolve ONLY to decorator-marked attribute
|
|
2036
|
+
// helpers: `@Extend`/`@Styles`/`@AnimatableExtend` functions (and global
|
|
2037
|
+
// `@Builder`s used attribute-position). Framework attributes (`.width`,
|
|
2038
|
+
// `.fontSize` — on nearly every UI line) match no such helper and stay
|
|
2039
|
+
// unresolved, NEVER falling through to bare-name matching: on a samples
|
|
2040
|
+
// monorepo that fallthrough manufactured 36k wrong edges, giving single
|
|
2041
|
+
// same-named properties thousands of false callers. Ambiguity rule matches
|
|
2042
|
+
// the rest of the file: several same-named helpers → prefer the call-site
|
|
2043
|
+
// file, still ambiguous → drop the ref rather than guess.
|
|
2044
|
+
if (ref.language === 'arkts' && ref.referenceName.startsWith('.')) {
|
|
2045
|
+
const base = ref.referenceName.slice(1);
|
|
2046
|
+
const candidates = context
|
|
2047
|
+
.getNodesByName(base)
|
|
2048
|
+
.filter((n) => n.language === 'arkts' &&
|
|
2049
|
+
n.kind === 'function' &&
|
|
2050
|
+
(n.decorators ?? []).some((d) => ARKUI_ATTRIBUTE_DECORATORS.has(d)));
|
|
2051
|
+
const chosen = candidates.length > 1 ? preferCallSiteFile(candidates, ref.filePath) : candidates;
|
|
2052
|
+
if (chosen.length !== 1)
|
|
2053
|
+
return null;
|
|
2054
|
+
return {
|
|
2055
|
+
original: ref,
|
|
2056
|
+
targetNodeId: chosen[0].id,
|
|
2057
|
+
confidence: 0.85,
|
|
2058
|
+
resolvedBy: 'exact-match',
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
// Erlang `-behaviour(m)` refs target a MODULE. Letting them fall through to
|
|
2062
|
+
// bare-name matching grabs any same-named symbol — on emqx,
|
|
2063
|
+
// `-behaviour(supervisor)` resolved to a `-define(supervisor, …)` macro
|
|
2064
|
+
// constant in an unrelated app. Resolve only to the behaviour module's
|
|
2065
|
+
// namespace; an out-of-repo behaviour (OTP's gen_server/supervisor) stays
|
|
2066
|
+
// unresolved rather than guessed. The same module-only rule applies to every
|
|
2067
|
+
// ref an `.app`/`.app.src` resource file emits — its `{mod, …}` callback and
|
|
2068
|
+
// `{applications, …}` dependency names can only mean modules, and on emqx
|
|
2069
|
+
// the `ssl` OTP app otherwise resolved to a test helper FUNCTION named ssl.
|
|
2070
|
+
if (ref.language === 'erlang' &&
|
|
2071
|
+
(ref.referenceKind === 'implements' || /\.app(?:\.src)?$/i.test(ref.filePath))) {
|
|
2072
|
+
const modules = context
|
|
2073
|
+
.getNodesByName(ref.referenceName)
|
|
2074
|
+
.filter((n) => n.language === 'erlang' && n.kind === 'namespace');
|
|
2075
|
+
const chosen = preferCallSiteFile(modules, ref.filePath)[0];
|
|
2076
|
+
if (!chosen)
|
|
2077
|
+
return null;
|
|
2078
|
+
return {
|
|
2079
|
+
original: ref,
|
|
2080
|
+
targetNodeId: chosen.id,
|
|
2081
|
+
confidence: 0.9,
|
|
2082
|
+
resolvedBy: 'exact-match',
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
1448
2085
|
// Try strategies in order of confidence
|
|
1449
2086
|
let result;
|
|
1450
2087
|
// 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
|
|
1451
|
-
result = matchByFilePath(ref, context);
|
|
2088
|
+
result = nmTimed('filePath', ref, () => matchByFilePath(ref, context));
|
|
1452
2089
|
if (result)
|
|
1453
2090
|
return result;
|
|
1454
2091
|
// 1. Qualified name match (highest confidence)
|
|
1455
|
-
result = matchByQualifiedName(ref, context);
|
|
2092
|
+
result = nmTimed('qualifiedName', ref, () => matchByQualifiedName(ref, context));
|
|
1456
2093
|
if (result)
|
|
1457
2094
|
return result;
|
|
1458
2095
|
// 1b. C++ chained call whose receiver is another call — `Foo::instance().bar()`
|
|
1459
2096
|
// encoded as `Foo::instance().bar` by the extractor (#645). Resolve the
|
|
1460
2097
|
// receiver's type from what the inner call returns, then the method on it.
|
|
1461
2098
|
if (ref.language === 'cpp' || ref.language === 'c') {
|
|
1462
|
-
result = matchCppCallChain(ref, context);
|
|
2099
|
+
result = nmTimed('cppChain', ref, () => matchCppCallChain(ref, context));
|
|
1463
2100
|
if (result)
|
|
1464
2101
|
return result;
|
|
1465
2102
|
}
|
|
@@ -1468,7 +2105,7 @@ function matchReference(ref, context) {
|
|
|
1468
2105
|
// type is the factory's `self` (PHP `: self`/`: static`, Rust `-> Self`) or
|
|
1469
2106
|
// concrete return type.
|
|
1470
2107
|
if (ref.language === 'php' || ref.language === 'rust') {
|
|
1471
|
-
result = matchScopedCallChain(ref, context);
|
|
2108
|
+
result = nmTimed('scopedChain', ref, () => matchScopedCallChain(ref, context));
|
|
1472
2109
|
if (result)
|
|
1473
2110
|
return result;
|
|
1474
2111
|
}
|
|
@@ -1488,20 +2125,46 @@ function matchReference(ref, context) {
|
|
|
1488
2125
|
ref.language === 'dart' ||
|
|
1489
2126
|
ref.language === 'objc' ||
|
|
1490
2127
|
ref.language === 'pascal') {
|
|
1491
|
-
result = matchDottedCallChain(ref, context);
|
|
2128
|
+
result = nmTimed('dottedChain', ref, () => matchDottedCallChain(ref, context));
|
|
1492
2129
|
if (result)
|
|
1493
2130
|
return result;
|
|
1494
2131
|
}
|
|
1495
2132
|
// 2. Method call pattern
|
|
1496
|
-
result = matchMethodCall(ref, context);
|
|
2133
|
+
result = nmTimed('methodCall', ref, () => matchMethodCall(ref, context));
|
|
1497
2134
|
if (result)
|
|
1498
2135
|
return result;
|
|
2136
|
+
// ArkTS calls / instantiates: no bare exact-match or fuzzy. Cross-module
|
|
2137
|
+
// seeds are `Class.method` (handled above) or a class name for `new`
|
|
2138
|
+
// (`instantiates`). Falling through previously linked every `when`/`toString`
|
|
2139
|
+
// invoke to a single same-named symbol (100k+ false edges on scene_board).
|
|
2140
|
+
if (ref.language === 'arkts' &&
|
|
2141
|
+
(ref.referenceKind === 'calls' || ref.referenceKind === 'instantiates')) {
|
|
2142
|
+
if (ref.referenceKind === 'instantiates' && !ref.referenceName.includes('.')) {
|
|
2143
|
+
// Same-file only — project-wide unique class still over-links constructor
|
|
2144
|
+
// seeds (`GridOccupyStatus` × thousands). Cross-module `new Foo` stays
|
|
2145
|
+
// unresolved rather than guessed (import path is also disabled above).
|
|
2146
|
+
const sameFile = context
|
|
2147
|
+
.getNodesByName(ref.referenceName)
|
|
2148
|
+
.filter((n) => n.language === 'arkts' &&
|
|
2149
|
+
n.filePath === ref.filePath &&
|
|
2150
|
+
(n.kind === 'class' || n.kind === 'struct' || n.kind === 'component'));
|
|
2151
|
+
if (sameFile.length === 1) {
|
|
2152
|
+
return {
|
|
2153
|
+
original: ref,
|
|
2154
|
+
targetNodeId: sameFile[0].id,
|
|
2155
|
+
confidence: 0.85,
|
|
2156
|
+
resolvedBy: 'exact-match',
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
return null;
|
|
2161
|
+
}
|
|
1499
2162
|
// 3. Exact name match
|
|
1500
|
-
result = matchByExactName(ref, context);
|
|
2163
|
+
result = nmTimed('exactName', ref, () => matchByExactName(ref, context));
|
|
1501
2164
|
if (result)
|
|
1502
2165
|
return result;
|
|
1503
2166
|
// 4. Fuzzy match (lowest confidence)
|
|
1504
|
-
result = matchFuzzy(ref, context);
|
|
2167
|
+
result = nmTimed('fuzzy', ref, () => matchFuzzy(ref, context));
|
|
1505
2168
|
if (result)
|
|
1506
2169
|
return result;
|
|
1507
2170
|
return null;
|