@aroman22/codegraph-vba-darwin-arm64 1.5.1 → 1.6.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/bin/codegraph +5 -0
- package/lib/dist/bin/codegraph.d.ts +1 -1
- package/lib/dist/bin/codegraph.d.ts.map +1 -1
- package/lib/dist/bin/codegraph.js +307 -81
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/command-supervision.d.ts +38 -1
- package/lib/dist/bin/command-supervision.d.ts.map +1 -1
- package/lib/dist/bin/command-supervision.js +22 -3
- package/lib/dist/bin/command-supervision.js.map +1 -1
- package/lib/dist/db/index.d.ts +84 -5
- package/lib/dist/db/index.d.ts.map +1 -1
- package/lib/dist/db/index.js +209 -12
- package/lib/dist/db/index.js.map +1 -1
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +80 -3
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/queries.d.ts +127 -4
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +317 -7
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/db/schema.sql +43 -1
- package/lib/dist/db/wal-valve.d.ts +106 -0
- package/lib/dist/db/wal-valve.d.ts.map +1 -0
- package/lib/dist/db/wal-valve.js +208 -0
- package/lib/dist/db/wal-valve.js.map +1 -0
- package/lib/dist/directory.d.ts +9 -5
- package/lib/dist/directory.d.ts.map +1 -1
- package/lib/dist/directory.js +210 -19
- package/lib/dist/directory.js.map +1 -1
- package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
- package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/lib/dist/extraction/cfml-extractor.js +494 -0
- package/lib/dist/extraction/cfml-extractor.js.map +1 -0
- package/lib/dist/extraction/grammars.d.ts +25 -1
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +177 -23
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +47 -2
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +568 -51
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/arkts.d.ts +3 -0
- package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
- package/lib/dist/extraction/languages/arkts.js +127 -0
- package/lib/dist/extraction/languages/arkts.js.map +1 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts +98 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +563 -1
- package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
- package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
- package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfquery.js +28 -0
- package/lib/dist/extraction/languages/cfquery.js.map +1 -0
- package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
- package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfscript.js +73 -0
- package/lib/dist/extraction/languages/cfscript.js.map +1 -0
- package/lib/dist/extraction/languages/cobol.d.ts +33 -0
- package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cobol.js +499 -0
- package/lib/dist/extraction/languages/cobol.js.map +1 -0
- package/lib/dist/extraction/languages/erlang.d.ts +3 -0
- package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/lib/dist/extraction/languages/erlang.js +350 -0
- package/lib/dist/extraction/languages/erlang.js.map +1 -0
- package/lib/dist/extraction/languages/index.d.ts.map +1 -1
- package/lib/dist/extraction/languages/index.js +18 -0
- package/lib/dist/extraction/languages/index.js.map +1 -1
- package/lib/dist/extraction/languages/nix.d.ts +3 -0
- package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
- package/lib/dist/extraction/languages/nix.js +294 -0
- package/lib/dist/extraction/languages/nix.js.map +1 -0
- package/lib/dist/extraction/languages/solidity.d.ts +3 -0
- package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/lib/dist/extraction/languages/solidity.js +293 -0
- package/lib/dist/extraction/languages/solidity.js.map +1 -0
- package/lib/dist/extraction/languages/terraform.d.ts +3 -0
- package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/lib/dist/extraction/languages/terraform.js +641 -0
- package/lib/dist/extraction/languages/terraform.js.map +1 -0
- package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
- package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/lib/dist/extraction/languages/vbnet.js +141 -0
- package/lib/dist/extraction/languages/vbnet.js.map +1 -0
- package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/mybatis-extractor.js +140 -38
- package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
- package/lib/dist/extraction/parse-pool.d.ts +28 -1
- package/lib/dist/extraction/parse-pool.d.ts.map +1 -1
- package/lib/dist/extraction/parse-pool.js +76 -7
- package/lib/dist/extraction/parse-pool.js.map +1 -1
- package/lib/dist/extraction/parse-worker.js +11 -4
- package/lib/dist/extraction/parse-worker.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
- package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts +20 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.d.ts +60 -1
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +905 -20
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/vba/call-sweep.d.ts +3 -0
- package/lib/dist/extraction/vba/call-sweep.d.ts.map +1 -0
- package/lib/dist/extraction/vba/call-sweep.js +206 -0
- package/lib/dist/extraction/vba/call-sweep.js.map +1 -0
- package/lib/dist/extraction/vba/calls.d.ts +55 -0
- package/lib/dist/extraction/vba/calls.d.ts.map +1 -0
- package/lib/dist/extraction/vba/calls.js +439 -0
- package/lib/dist/extraction/vba/calls.js.map +1 -0
- package/lib/dist/extraction/vba/constants.d.ts +42 -0
- package/lib/dist/extraction/vba/constants.d.ts.map +1 -0
- package/lib/dist/extraction/vba/constants.js +112 -0
- package/lib/dist/extraction/vba/constants.js.map +1 -0
- package/lib/dist/extraction/vba/context.d.ts +175 -0
- package/lib/dist/extraction/vba/context.d.ts.map +1 -0
- package/lib/dist/extraction/vba/context.js +268 -0
- package/lib/dist/extraction/vba/context.js.map +1 -0
- package/lib/dist/extraction/vba/controls.d.ts +24 -0
- package/lib/dist/extraction/vba/controls.d.ts.map +1 -0
- package/lib/dist/extraction/vba/controls.js +89 -0
- package/lib/dist/extraction/vba/controls.js.map +1 -0
- package/lib/dist/extraction/vba/declarations.d.ts +3 -0
- package/lib/dist/extraction/vba/declarations.d.ts.map +1 -0
- package/lib/dist/extraction/vba/declarations.js +154 -0
- package/lib/dist/extraction/vba/declarations.js.map +1 -0
- package/lib/dist/extraction/vba/dims.d.ts +3 -0
- package/lib/dist/extraction/vba/dims.d.ts.map +1 -0
- package/lib/dist/extraction/vba/dims.js +186 -0
- package/lib/dist/extraction/vba/dims.js.map +1 -0
- package/lib/dist/extraction/vba/docmd.d.ts +16 -0
- package/lib/dist/extraction/vba/docmd.d.ts.map +1 -0
- package/lib/dist/extraction/vba/docmd.js +179 -0
- package/lib/dist/extraction/vba/docmd.js.map +1 -0
- package/lib/dist/extraction/vba/enums-consts.d.ts +15 -0
- package/lib/dist/extraction/vba/enums-consts.d.ts.map +1 -0
- package/lib/dist/extraction/vba/enums-consts.js +178 -0
- package/lib/dist/extraction/vba/enums-consts.js.map +1 -0
- package/lib/dist/extraction/vba/implements.d.ts +3 -0
- package/lib/dist/extraction/vba/implements.d.ts.map +1 -0
- package/lib/dist/extraction/vba/implements.js +52 -0
- package/lib/dist/extraction/vba/implements.js.map +1 -0
- package/lib/dist/extraction/vba/procedures.d.ts +3 -0
- package/lib/dist/extraction/vba/procedures.d.ts.map +1 -0
- package/lib/dist/extraction/vba/procedures.js +239 -0
- package/lib/dist/extraction/vba/procedures.js.map +1 -0
- package/lib/dist/extraction/vba/sql-wrapper.d.ts +17 -0
- package/lib/dist/extraction/vba/sql-wrapper.d.ts.map +1 -0
- package/lib/dist/extraction/vba/sql-wrapper.js +212 -0
- package/lib/dist/extraction/vba/sql-wrapper.js.map +1 -0
- package/lib/dist/extraction/vba/tempvars.d.ts +10 -0
- package/lib/dist/extraction/vba/tempvars.d.ts.map +1 -0
- package/lib/dist/extraction/vba/tempvars.js +138 -0
- package/lib/dist/extraction/vba/tempvars.js.map +1 -0
- package/lib/dist/extraction/vba/text-utils.d.ts +73 -0
- package/lib/dist/extraction/vba/text-utils.d.ts.map +1 -0
- package/lib/dist/extraction/vba/text-utils.js +195 -0
- package/lib/dist/extraction/vba/text-utils.js.map +1 -0
- package/lib/dist/extraction/vba-extractor.d.ts +3 -951
- package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-extractor.js +46 -2934
- package/lib/dist/extraction/vba-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-preprocess.d.ts +1 -1
- package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
- package/lib/dist/extraction/vba-preprocess.js +300 -88
- package/lib/dist/extraction/vba-preprocess.js.map +1 -1
- package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/lib/dist/graph/traversal.d.ts.map +1 -1
- package/lib/dist/graph/traversal.js +76 -17
- package/lib/dist/graph/traversal.js.map +1 -1
- package/lib/dist/index.d.ts +81 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +467 -10
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts +42 -0
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +97 -2
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +35 -3
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +101 -9
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.js +2 -1
- package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/lib/dist/mcp/early-ppid.d.ts +26 -0
- package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
- package/lib/dist/mcp/early-ppid.js +29 -0
- package/lib/dist/mcp/early-ppid.js.map +1 -0
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +29 -4
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/lib/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/lib/dist/mcp/liveness-watchdog.js +73 -13
- package/lib/dist/mcp/liveness-watchdog.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +30 -4
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/query-pool.d.ts +14 -0
- package/lib/dist/mcp/query-pool.d.ts.map +1 -1
- package/lib/dist/mcp/query-pool.js +18 -0
- package/lib/dist/mcp/query-pool.js.map +1 -1
- package/lib/dist/mcp/session.d.ts +14 -0
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +28 -1
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/startup-handshake.d.ts +44 -0
- package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/lib/dist/mcp/startup-handshake.js +73 -0
- package/lib/dist/mcp/startup-handshake.js.map +1 -0
- package/lib/dist/mcp/tools.d.ts +22 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +289 -23
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/mcp/transport.d.ts.map +1 -1
- package/lib/dist/mcp/transport.js +18 -0
- package/lib/dist/mcp/transport.js.map +1 -1
- package/lib/dist/project-config.d.ts +44 -0
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +151 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.js +170 -146
- package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +1109 -97
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
- package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/lib/dist/resolution/cooperative-yield.js +42 -0
- package/lib/dist/resolution/cooperative-yield.js.map +1 -0
- package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
- package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/cics.js +90 -0
- package/lib/dist/resolution/frameworks/cics.js.map +1 -0
- package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/index.js +6 -0
- package/lib/dist/resolution/frameworks/index.js.map +1 -1
- package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/java.js +14 -6
- package/lib/dist/resolution/frameworks/java.js.map +1 -1
- package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/terraform.js +277 -0
- package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
- package/lib/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/goframe-synthesizer.js +8 -3
- package/lib/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/lib/dist/resolution/import-resolver.d.ts +7 -0
- package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/import-resolver.js +129 -4
- package/lib/dist/resolution/import-resolver.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +85 -2
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +535 -58
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +22 -3
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +613 -39
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/resolution/strip-comments.d.ts +1 -1
- package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
- package/lib/dist/resolution/strip-comments.js +49 -0
- package/lib/dist/resolution/strip-comments.js.map +1 -1
- package/lib/dist/resolution/types.d.ts +29 -0
- package/lib/dist/resolution/types.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.d.ts +10 -0
- package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.js +142 -4
- package/lib/dist/resolution/workspace-packages.js.map +1 -1
- package/lib/dist/search/identifier-segments.d.ts +60 -0
- package/lib/dist/search/identifier-segments.d.ts.map +1 -0
- package/lib/dist/search/identifier-segments.js +176 -0
- package/lib/dist/search/identifier-segments.js.map +1 -0
- package/lib/dist/sync/git-hooks.d.ts.map +1 -1
- package/lib/dist/sync/git-hooks.js +2 -0
- package/lib/dist/sync/git-hooks.js.map +1 -1
- package/lib/dist/sync/watcher.d.ts +10 -5
- package/lib/dist/sync/watcher.d.ts.map +1 -1
- package/lib/dist/sync/watcher.js +51 -14
- package/lib/dist/sync/watcher.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts +9 -0
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +45 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/types.d.ts +25 -2
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +10 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/dist/upgrade/index.d.ts +32 -0
- package/lib/dist/upgrade/index.d.ts.map +1 -1
- package/lib/dist/upgrade/index.js +163 -14
- package/lib/dist/upgrade/index.js.map +1 -1
- package/lib/dist/upgrade/remove-binary.d.ts +87 -0
- package/lib/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/lib/dist/upgrade/remove-binary.js +289 -0
- package/lib/dist/upgrade/remove-binary.js.map +1 -0
- package/lib/dist/upgrade/update-check.d.ts +92 -0
- package/lib/dist/upgrade/update-check.d.ts.map +1 -0
- package/lib/dist/upgrade/update-check.js +258 -0
- package/lib/dist/upgrade/update-check.js.map +1 -0
- package/lib/node_modules/.modules.yaml +1 -1
- package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/lib/dist/reasoning/config.d.ts +0 -45
- package/lib/dist/reasoning/config.d.ts.map +0 -1
- package/lib/dist/reasoning/config.js +0 -171
- package/lib/dist/reasoning/config.js.map +0 -1
- package/lib/dist/reasoning/credentials.d.ts +0 -5
- package/lib/dist/reasoning/credentials.d.ts.map +0 -1
- package/lib/dist/reasoning/credentials.js +0 -83
- package/lib/dist/reasoning/credentials.js.map +0 -1
- package/lib/dist/reasoning/login.d.ts +0 -21
- package/lib/dist/reasoning/login.d.ts.map +0 -1
- package/lib/dist/reasoning/login.js +0 -85
- package/lib/dist/reasoning/login.js.map +0 -1
- package/lib/dist/reasoning/reasoner.d.ts +0 -43
- package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
- package/lib/dist/reasoning/reasoner.js +0 -308
- package/lib/dist/reasoning/reasoner.js.map +0 -1
|
@@ -49,6 +49,7 @@ const name_matcher_1 = require("./name-matcher");
|
|
|
49
49
|
const import_resolver_1 = require("./import-resolver");
|
|
50
50
|
const frameworks_1 = require("./frameworks");
|
|
51
51
|
const callback_synthesizer_1 = require("./callback-synthesizer");
|
|
52
|
+
const cooperative_yield_1 = require("./cooperative-yield");
|
|
52
53
|
const path_aliases_1 = require("./path-aliases");
|
|
53
54
|
const go_module_1 = require("./go-module");
|
|
54
55
|
const workspace_packages_1 = require("./workspace-packages");
|
|
@@ -67,6 +68,8 @@ const CHAIN_LANGUAGES = new Set(['java', 'kotlin', 'csharp', 'swift', 'rust', 'g
|
|
|
67
68
|
const SCOPED_CHAIN_LANGUAGES = new Set(['rust']);
|
|
68
69
|
/** The extractor's chained-receiver encoding: `<inner>().<method>`. */
|
|
69
70
|
const CHAIN_SHAPE = /^(.+)\(\)\.(\w+)$/;
|
|
71
|
+
/** PHP `$this->prop->method()` encoded as `this->prop.method` — no `()`, so CHAIN_SHAPE misses it. */
|
|
72
|
+
const PHP_PROP_SHAPE = /^this->\w+\.\w+$/;
|
|
70
73
|
/**
|
|
71
74
|
* Cache size limits. Each per-resolver cache is bounded so memory
|
|
72
75
|
* stays flat on large codebases (20k+ files). Sizes were chosen to
|
|
@@ -239,6 +242,17 @@ class ReferenceResolver {
|
|
|
239
242
|
nameCache; // name → nodes cache
|
|
240
243
|
lowerNameCache; // lower(name) → nodes cache
|
|
241
244
|
qualifiedNameCache; // qualified_name → nodes cache
|
|
245
|
+
fileLinesCache; // file → split lines cache
|
|
246
|
+
methodMatchCache; // lang\0Type::method → matching method nodes
|
|
247
|
+
// Node kinds are a small fixed set (~24), so this is a plain Map, not an LRU.
|
|
248
|
+
// getNodesByKind returns the FULL node list for a kind; it was previously
|
|
249
|
+
// uncached — a per-ref `SELECT * FROM nodes WHERE kind=?` + row-mapping. Called
|
|
250
|
+
// for every dotted call ref by the Spring resolver (constants) and every
|
|
251
|
+
// `hook_` ref by the Drupal resolver (functions), that scan dominated
|
|
252
|
+
// resolution on large repos (#1180). The node set is stable within a
|
|
253
|
+
// resolution pass (same lifetime assumption as nameCache); clearCaches() resets
|
|
254
|
+
// it between passes. Callers must treat the returned array as read-only.
|
|
255
|
+
nodesByKindCache = new Map();
|
|
242
256
|
knownNames = null; // all known symbol names for fast pre-filtering
|
|
243
257
|
knownFiles = null;
|
|
244
258
|
cachesWarmed = false;
|
|
@@ -264,6 +278,10 @@ class ReferenceResolver {
|
|
|
264
278
|
this.nameCache = new lru_cache_1.LRUCache(limit);
|
|
265
279
|
this.lowerNameCache = new lru_cache_1.LRUCache(limit);
|
|
266
280
|
this.qualifiedNameCache = new lru_cache_1.LRUCache(limit);
|
|
281
|
+
// Split-lines arrays are heavier than content strings; refs arrive
|
|
282
|
+
// file-ordered, so a small cache still hits nearly always.
|
|
283
|
+
this.fileLinesCache = new lru_cache_1.LRUCache(contentLimit);
|
|
284
|
+
this.methodMatchCache = new lru_cache_1.LRUCache(limit);
|
|
267
285
|
this.context = this.createContext();
|
|
268
286
|
}
|
|
269
287
|
/**
|
|
@@ -319,6 +337,28 @@ class ReferenceResolver {
|
|
|
319
337
|
this.knownNames = new Set(this.queries.getAllNodeNames());
|
|
320
338
|
this.cachesWarmed = true;
|
|
321
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* warmCaches for the async resolution entry points: streams the distinct
|
|
342
|
+
* name set with periodic yields instead of one synchronous `.all()`. On a
|
|
343
|
+
* multi-million-node index the DISTINCT scan is a solid multi-second block
|
|
344
|
+
* (measured up to 28s inside `codegraph sync` on the Linux kernel index),
|
|
345
|
+
* long enough to matter to the #850 watchdog on slower hardware. Same
|
|
346
|
+
* result, same memory — only the event loop keeps turning.
|
|
347
|
+
*/
|
|
348
|
+
async warmCachesYielding(onYield) {
|
|
349
|
+
if (this.cachesWarmed)
|
|
350
|
+
return;
|
|
351
|
+
this.knownFiles = new Set(this.queries.getAllFilePaths());
|
|
352
|
+
const names = new Set();
|
|
353
|
+
let scanned = 0;
|
|
354
|
+
for (const name of this.queries.iterateNodeNames()) {
|
|
355
|
+
names.add(name);
|
|
356
|
+
if ((++scanned & 8191) === 0)
|
|
357
|
+
await onYield();
|
|
358
|
+
}
|
|
359
|
+
this.knownNames = names;
|
|
360
|
+
this.cachesWarmed = true;
|
|
361
|
+
}
|
|
322
362
|
/**
|
|
323
363
|
* Clear internal caches
|
|
324
364
|
*/
|
|
@@ -330,10 +370,30 @@ class ReferenceResolver {
|
|
|
330
370
|
this.nameCache.clear();
|
|
331
371
|
this.lowerNameCache.clear();
|
|
332
372
|
this.qualifiedNameCache.clear();
|
|
373
|
+
this.fileLinesCache.clear();
|
|
374
|
+
this.methodMatchCache.clear();
|
|
375
|
+
this.nodesByKindCache.clear();
|
|
333
376
|
this.knownNames = null;
|
|
334
377
|
this.knownFiles = null;
|
|
335
378
|
this.cachesWarmed = false;
|
|
336
379
|
}
|
|
380
|
+
/** `readFile` through the LRU content cache (null = read failed, also cached). */
|
|
381
|
+
readFileCached(filePath) {
|
|
382
|
+
if (this.fileCache.has(filePath)) {
|
|
383
|
+
return this.fileCache.get(filePath);
|
|
384
|
+
}
|
|
385
|
+
const fullPath = path.join(this.projectRoot, filePath);
|
|
386
|
+
try {
|
|
387
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
388
|
+
this.fileCache.set(filePath, content);
|
|
389
|
+
return content;
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
(0, errors_1.logDebug)('Failed to read file for resolution', { filePath, error: String(error) });
|
|
393
|
+
this.fileCache.set(filePath, null);
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
337
397
|
/**
|
|
338
398
|
* Create the resolution context
|
|
339
399
|
*/
|
|
@@ -353,6 +413,30 @@ class ReferenceResolver {
|
|
|
353
413
|
this.nameCache.set(name, result);
|
|
354
414
|
return result;
|
|
355
415
|
},
|
|
416
|
+
getMethodMatches: (typeName, methodName, language) => {
|
|
417
|
+
const key = `${language} ${typeName}::${methodName}`;
|
|
418
|
+
const cached = this.methodMatchCache.get(key);
|
|
419
|
+
if (cached !== undefined)
|
|
420
|
+
return cached;
|
|
421
|
+
let candidates = this.nameCache.get(methodName);
|
|
422
|
+
if (candidates === undefined) {
|
|
423
|
+
candidates = this.queries.getNodesByName(methodName);
|
|
424
|
+
this.nameCache.set(methodName, candidates);
|
|
425
|
+
}
|
|
426
|
+
const want = `${typeName}::${methodName}`;
|
|
427
|
+
const matches = [];
|
|
428
|
+
for (const m of candidates) {
|
|
429
|
+
if (m.kind !== 'method')
|
|
430
|
+
continue;
|
|
431
|
+
if (m.language !== language)
|
|
432
|
+
continue;
|
|
433
|
+
const qn = m.qualifiedName;
|
|
434
|
+
if (qn === want || qn.endsWith(`::${want}`))
|
|
435
|
+
matches.push(m);
|
|
436
|
+
}
|
|
437
|
+
this.methodMatchCache.set(key, matches);
|
|
438
|
+
return matches;
|
|
439
|
+
},
|
|
356
440
|
getNodesByQualifiedName: (qualifiedName) => {
|
|
357
441
|
const cached = this.qualifiedNameCache.get(qualifiedName);
|
|
358
442
|
if (cached !== undefined)
|
|
@@ -362,8 +446,18 @@ class ReferenceResolver {
|
|
|
362
446
|
return result;
|
|
363
447
|
},
|
|
364
448
|
getNodesByKind: (kind) => {
|
|
365
|
-
|
|
449
|
+
const cached = this.nodesByKindCache.get(kind);
|
|
450
|
+
if (cached !== undefined)
|
|
451
|
+
return cached;
|
|
452
|
+
const result = this.queries.getNodesByKind(kind);
|
|
453
|
+
this.nodesByKindCache.set(kind, result);
|
|
454
|
+
return result;
|
|
366
455
|
},
|
|
456
|
+
// Streamed, uncached — synthesizers scan-and-filter whole kinds, and
|
|
457
|
+
// both the materialized array AND the per-kind cache retention are
|
|
458
|
+
// O(nodes) memory (#1212). Per-ref resolvers keep the cached array
|
|
459
|
+
// variant above.
|
|
460
|
+
iterateNodesByKind: (kind) => this.queries.iterateNodesByKind(kind),
|
|
367
461
|
fileExists: (filePath) => {
|
|
368
462
|
// Check pre-built known files set first (O(1))
|
|
369
463
|
if (this.knownFiles) {
|
|
@@ -382,21 +476,15 @@ class ReferenceResolver {
|
|
|
382
476
|
return false;
|
|
383
477
|
}
|
|
384
478
|
},
|
|
385
|
-
readFile: (filePath) =>
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
catch (error) {
|
|
396
|
-
(0, errors_1.logDebug)('Failed to read file for resolution', { filePath, error: String(error) });
|
|
397
|
-
this.fileCache.set(filePath, null);
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
479
|
+
readFile: (filePath) => this.readFileCached(filePath),
|
|
480
|
+
getFileLines: (filePath) => {
|
|
481
|
+
const cached = this.fileLinesCache.get(filePath);
|
|
482
|
+
if (cached !== undefined)
|
|
483
|
+
return cached;
|
|
484
|
+
const source = this.readFileCached(filePath);
|
|
485
|
+
const lines = source === null ? null : source.split(/\r?\n/);
|
|
486
|
+
this.fileLinesCache.set(filePath, lines);
|
|
487
|
+
return lines;
|
|
400
488
|
},
|
|
401
489
|
getProjectRoot: () => this.projectRoot,
|
|
402
490
|
getAllFiles: () => {
|
|
@@ -499,7 +587,7 @@ class ReferenceResolver {
|
|
|
499
587
|
// `.ts` index barrel and silently break the chain (#629). Re-key
|
|
500
588
|
// the parse on the barrel's extension so the chase works no matter
|
|
501
589
|
// what kind of file imports through it.
|
|
502
|
-
const isJsFamily = /\.(?:d\.ts|[cm]?tsx?|[cm]?jsx
|
|
590
|
+
const isJsFamily = /\.(?:d\.ts|[cm]?tsx?|[cm]?jsx?|ets)$/i.test(filePath);
|
|
503
591
|
const reExports = (0, import_resolver_1.extractReExports)(content, isJsFamily ? 'typescript' : language);
|
|
504
592
|
this.reExportCache.set(filePath, reExports);
|
|
505
593
|
return reExports;
|
|
@@ -614,6 +702,24 @@ class ReferenceResolver {
|
|
|
614
702
|
return true;
|
|
615
703
|
}
|
|
616
704
|
}
|
|
705
|
+
// Lua/Luau method calls use a single `:` (`lg:log`); R uses `$` (`lg$log`).
|
|
706
|
+
// Check the member (and receiver) around these separators too, so the ref
|
|
707
|
+
// isn't dropped here before the method-call resolver ever sees it. The `:`
|
|
708
|
+
// case is skipped when the name actually contains `::` (handled above).
|
|
709
|
+
for (const sep of [':', '$']) {
|
|
710
|
+
if (sep === ':' && name.includes('::'))
|
|
711
|
+
continue;
|
|
712
|
+
const sepIdx = name.indexOf(sep);
|
|
713
|
+
if (sepIdx > 0) {
|
|
714
|
+
const receiver = name.substring(0, sepIdx);
|
|
715
|
+
const member = name.substring(sepIdx + 1);
|
|
716
|
+
if (this.knownNames.has(member) || this.knownNames.has(receiver))
|
|
717
|
+
return true;
|
|
718
|
+
const capitalized = receiver.charAt(0).toUpperCase() + receiver.slice(1);
|
|
719
|
+
if (this.knownNames.has(capitalized))
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
617
723
|
// For path-like references (e.g., "snippets/drawer-menu.liquid"), check the filename
|
|
618
724
|
const slashIdx = name.lastIndexOf('/');
|
|
619
725
|
if (slashIdx > 0) {
|
|
@@ -648,13 +754,36 @@ class ReferenceResolver {
|
|
|
648
754
|
if (this.isBuiltInOrExternal(ref)) {
|
|
649
755
|
return null;
|
|
650
756
|
}
|
|
757
|
+
// CFML component paths in inheritance (#1152): `extends="coldbox.system.web.
|
|
758
|
+
// Controller"` names the supertype by its dot-separated path (or `extends=
|
|
759
|
+
// "../base"` by relative file path) — the graph indexes the class under its
|
|
760
|
+
// final segment only, so these die at the fast pre-filter below and never
|
|
761
|
+
// resolved. Handled by a dedicated path-corroborated matcher, gated to
|
|
762
|
+
// inheritance refs only (a dotted `calls` ref is a member-access chain, not
|
|
763
|
+
// a component path). No fallthrough on miss: the full path string can only
|
|
764
|
+
// ever mis-match downstream, and an unresolvable supertype usually lives in
|
|
765
|
+
// an out-of-repo library (mxunit, testbox) — silent beats wrong.
|
|
766
|
+
if ((ref.language === 'cfml' || ref.language === 'cfscript') &&
|
|
767
|
+
(ref.referenceKind === 'extends' || ref.referenceKind === 'implements') &&
|
|
768
|
+
(ref.referenceName.includes('.') || ref.referenceName.includes('/'))) {
|
|
769
|
+
return this.resolveCfmlComponentPath(ref);
|
|
770
|
+
}
|
|
651
771
|
// Fast pre-filter: skip if no symbol with this name exists anywhere
|
|
652
772
|
// AND the name doesn't match a local import. The import escape is
|
|
653
773
|
// necessary because re-export rename chains (`import { login }
|
|
654
774
|
// from './barrel'` where the barrel has `export { signIn as login }
|
|
655
775
|
// from './auth'`) intentionally call a name that has no
|
|
656
776
|
// declaration anywhere — only the renamed upstream symbol does.
|
|
657
|
-
|
|
777
|
+
// ArkTS chained-attribute refs carry a leading dot (`.titleStyle`) that
|
|
778
|
+
// routes them to the decorator-gated matcher; the symbol itself is
|
|
779
|
+
// indexed under the bare name, so the existence check strips the dot.
|
|
780
|
+
// Nix static path imports (`import ./x.nix`) name a FILE, not a symbol —
|
|
781
|
+
// they bypass the symbol-existence check and resolve via resolveViaImport.
|
|
782
|
+
const existenceName = ref.language === 'arkts' && ref.referenceName.startsWith('.')
|
|
783
|
+
? ref.referenceName.slice(1)
|
|
784
|
+
: ref.referenceName;
|
|
785
|
+
if (!(0, import_resolver_1.isNixPathImportRef)(ref) &&
|
|
786
|
+
!this.hasAnyPossibleMatch(existenceName) &&
|
|
658
787
|
!this.matchesAnyImport(ref) &&
|
|
659
788
|
!this.frameworks.some((f) => f.claimsReference?.(ref.referenceName))) {
|
|
660
789
|
return null;
|
|
@@ -720,13 +849,40 @@ class ReferenceResolver {
|
|
|
720
849
|
// If that didn't find the file, do NOT fall back to the symbol
|
|
721
850
|
// name-matcher — it would mis-connect e.g. "inc/db.php" to an unrelated
|
|
722
851
|
// db.php elsewhere in the tree (a wrong edge is worse than none, #660).
|
|
723
|
-
|
|
852
|
+
// Terraform refs are directory-scoped by language semantics — the
|
|
853
|
+
// framework resolver IS the whole rulebook (`var.X` can never legally
|
|
854
|
+
// bind outside its module directory), so the name-matcher's
|
|
855
|
+
// qualified-name fallback would only ever add wrong cross-module edges.
|
|
856
|
+
// Nix static path imports are file references for the same reason —
|
|
857
|
+
// falling through would let "./x.nix" name-match an unrelated node.
|
|
858
|
+
if ((0, import_resolver_1.isPhpIncludePathRef)(ref) || (0, import_resolver_1.isCobolCopybookRef)(ref) || (0, import_resolver_1.isNixPathImportRef)(ref) || ref.language === 'terraform') {
|
|
724
859
|
return candidates.length > 0
|
|
725
860
|
? candidates.reduce((best, curr) => curr.confidence > best.confidence ? curr : best)
|
|
726
861
|
: null;
|
|
727
862
|
}
|
|
728
863
|
// Strategy 3: Try name matching
|
|
729
|
-
|
|
864
|
+
let nameResult = this.gateLanguage((0, name_matcher_1.matchReference)(ref, this.context), ref);
|
|
865
|
+
// Nix has no ambient cross-file namespace — a callee binds lexically
|
|
866
|
+
// (same file) or through explicit import/callPackage wiring (the import
|
|
867
|
+
// path above). A cross-file name match is wrong by construction: every
|
|
868
|
+
// module `inherit (lib) mkOption`s the same nixpkgs helpers, so the
|
|
869
|
+
// matcher would link each `mkOption` call to whichever file's inherit
|
|
870
|
+
// binding it happened to pick. Same-file matches only.
|
|
871
|
+
if (nameResult) {
|
|
872
|
+
const target = this.queries.getNodeById(nameResult.targetNodeId);
|
|
873
|
+
if (ref.language === 'nix') {
|
|
874
|
+
if (!target || target.filePath !== ref.filePath) {
|
|
875
|
+
nameResult = null;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
else if (target && target.language === 'nix') {
|
|
879
|
+
// The reverse direction is just as impossible: no other language can
|
|
880
|
+
// symbolically call into a .nix binding (interop is eval/CLI, never a
|
|
881
|
+
// linkable symbol) — without this, a Python script's `split()` lands
|
|
882
|
+
// on some module's `split = ...` binding as a low-confidence match.
|
|
883
|
+
nameResult = null;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
730
886
|
if (nameResult) {
|
|
731
887
|
candidates.push(nameResult);
|
|
732
888
|
}
|
|
@@ -739,6 +895,15 @@ class ReferenceResolver {
|
|
|
739
895
|
CHAIN_SHAPE.test(ref.referenceName)) {
|
|
740
896
|
this.deferredChainRefs.push(ref);
|
|
741
897
|
}
|
|
898
|
+
else if (
|
|
899
|
+
// PHP `$this->prop->method()` (encoded `this->prop.method`): its method
|
|
900
|
+
// may live on the property's declared supertype, resolvable only once
|
|
901
|
+
// implements/extends edges exist — defer to the same conformance pass.
|
|
902
|
+
ref.referenceKind === 'calls' &&
|
|
903
|
+
ref.language === 'php' &&
|
|
904
|
+
PHP_PROP_SHAPE.test(ref.referenceName)) {
|
|
905
|
+
this.deferredChainRefs.push(ref);
|
|
906
|
+
}
|
|
742
907
|
return null;
|
|
743
908
|
}
|
|
744
909
|
// Return highest confidence candidate
|
|
@@ -785,6 +950,18 @@ class ReferenceResolver {
|
|
|
785
950
|
metadata: {
|
|
786
951
|
confidence: ref.confidence,
|
|
787
952
|
resolvedBy: ref.resolvedBy,
|
|
953
|
+
// The ORIGINAL reference text (and kind, when edge-kind promotion
|
|
954
|
+
// rewrote it — calls→instantiates, extends→implements,
|
|
955
|
+
// function_ref→references). If this edge's target is later removed
|
|
956
|
+
// by a re-index, the edge is resurrected as exactly this ref and
|
|
957
|
+
// re-resolved (#1240 removal case) — a faithful resurrection, so
|
|
958
|
+
// re-resolution can never bind anywhere a full re-index wouldn't.
|
|
959
|
+
// Reconstruction from the target node's name instead would strip
|
|
960
|
+
// receiver/qualifier context (`h.greet` → `greet`) and risk a
|
|
961
|
+
// wrong rebind; edges without refName (pre-#1240, synthesized) are
|
|
962
|
+
// deliberately NOT resurrected for the same reason.
|
|
963
|
+
refName: ref.original.referenceName,
|
|
964
|
+
...(ref.original.referenceKind !== kind ? { refKind: ref.original.referenceKind } : {}),
|
|
788
965
|
// Uniform marker for function-as-value edges (#756), regardless of
|
|
789
966
|
// which strategy resolved them (import vs matchFunctionRef) — lets
|
|
790
967
|
// tooling label "callback registration" and lets validation diff
|
|
@@ -813,6 +990,60 @@ class ReferenceResolver {
|
|
|
813
990
|
referenceKind: r.original.referenceKind,
|
|
814
991
|
})));
|
|
815
992
|
}
|
|
993
|
+
// Park unresolvable refs as status='failed' — parity with
|
|
994
|
+
// resolveAndPersistBatched. Deleting them was wrong (#1240): a ref whose
|
|
995
|
+
// own file never changes is otherwise gone forever, so when a DIFFERENT
|
|
996
|
+
// file later gains the export/symbol that would satisfy it, no sync can
|
|
997
|
+
// recreate the edge — only a full re-index. Failed rows are excluded from
|
|
998
|
+
// the pending readers, which preserves the #1187 orphan sweep's
|
|
999
|
+
// invariant in status form: after a COMPLETED pass nothing it processed
|
|
1000
|
+
// is still 'pending', so any pending row at rest belongs to an
|
|
1001
|
+
// interrupted run and the sweep can key off the pending count.
|
|
1002
|
+
if (result.unresolved.length > 0) {
|
|
1003
|
+
this.queries.markReferencesFailed(result.unresolved.map((r) => ({
|
|
1004
|
+
fromNodeId: r.fromNodeId,
|
|
1005
|
+
referenceName: r.referenceName,
|
|
1006
|
+
referenceKind: r.referenceKind,
|
|
1007
|
+
})));
|
|
1008
|
+
}
|
|
1009
|
+
return result;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Yielding counterpart of {@link resolveAndPersist} for a caller-supplied
|
|
1013
|
+
* ref list — used by sync's failed-ref retry pass (#1240). Same persistence
|
|
1014
|
+
* semantics: resolved refs become edges and their rows are deleted;
|
|
1015
|
+
* still-unresolvable refs are (re-)marked failed (a no-op for rows already
|
|
1016
|
+
* in that status). Yields per-ref because sync can run on the daemon's
|
|
1017
|
+
* liveness-watchdog thread (#850/#1091) and a retry set is unbounded when
|
|
1018
|
+
* a large edit lands many popular symbol names at once.
|
|
1019
|
+
*/
|
|
1020
|
+
async resolveAndPersistListYielding(refs) {
|
|
1021
|
+
const maybeYield = (0, cooperative_yield_1.createYielder)();
|
|
1022
|
+
const result = await this.resolveBatchYielding(refs, maybeYield);
|
|
1023
|
+
const PERSIST_CHUNK = 1000;
|
|
1024
|
+
const edges = this.createEdges(result.resolved);
|
|
1025
|
+
for (let i = 0; i < edges.length; i += PERSIST_CHUNK) {
|
|
1026
|
+
this.queries.insertEdges(edges.slice(i, i + PERSIST_CHUNK));
|
|
1027
|
+
await maybeYield();
|
|
1028
|
+
}
|
|
1029
|
+
const resolvedKeys = result.resolved.map((r) => ({
|
|
1030
|
+
fromNodeId: r.original.fromNodeId,
|
|
1031
|
+
referenceName: r.original.referenceName,
|
|
1032
|
+
referenceKind: r.original.referenceKind,
|
|
1033
|
+
}));
|
|
1034
|
+
for (let i = 0; i < resolvedKeys.length; i += PERSIST_CHUNK) {
|
|
1035
|
+
this.queries.deleteSpecificResolvedReferences(resolvedKeys.slice(i, i + PERSIST_CHUNK));
|
|
1036
|
+
await maybeYield();
|
|
1037
|
+
}
|
|
1038
|
+
const unresolvedKeys = result.unresolved.map((r) => ({
|
|
1039
|
+
fromNodeId: r.fromNodeId,
|
|
1040
|
+
referenceName: r.referenceName,
|
|
1041
|
+
referenceKind: r.referenceKind,
|
|
1042
|
+
}));
|
|
1043
|
+
for (let i = 0; i < unresolvedKeys.length; i += PERSIST_CHUNK) {
|
|
1044
|
+
this.queries.markReferencesFailed(unresolvedKeys.slice(i, i + PERSIST_CHUNK));
|
|
1045
|
+
await maybeYield();
|
|
1046
|
+
}
|
|
816
1047
|
return result;
|
|
817
1048
|
}
|
|
818
1049
|
/**
|
|
@@ -828,7 +1059,7 @@ class ReferenceResolver {
|
|
|
828
1059
|
* (re-resolving an already-resolved ref is a no-op since it's been deleted).
|
|
829
1060
|
* Returns the number of newly-created edges.
|
|
830
1061
|
*/
|
|
831
|
-
resolveChainedCallsViaConformance() {
|
|
1062
|
+
async resolveChainedCallsViaConformance() {
|
|
832
1063
|
const deferred = this.deferredChainRefs;
|
|
833
1064
|
this.deferredChainRefs = [];
|
|
834
1065
|
if (deferred.length === 0)
|
|
@@ -837,16 +1068,25 @@ class ReferenceResolver {
|
|
|
837
1068
|
// these refs were deferred). matchDottedCallChain now resolves a method on a
|
|
838
1069
|
// supertype via context.getSupertypes -> resolveMethodOnType's conformance walk.
|
|
839
1070
|
this.clearCaches();
|
|
1071
|
+
// This post-pass runs synchronously on the indexer's main thread; yield
|
|
1072
|
+
// periodically so the #850 liveness watchdog heartbeat can fire on a repo
|
|
1073
|
+
// with many deferred chained calls (#1091).
|
|
1074
|
+
const maybeYield = (0, cooperative_yield_1.createYielder)();
|
|
840
1075
|
const resolved = [];
|
|
841
1076
|
for (const ref of deferred) {
|
|
842
|
-
//
|
|
1077
|
+
// PHP `this->prop.method` resolves via matchMethodCall (declared-type
|
|
1078
|
+
// inference + resolveMethodOnType conformance walk); `::`-receiver
|
|
1079
|
+
// languages (Rust) split on `::` (matchScopedCallChain); other
|
|
843
1080
|
// dotted-receiver languages on `.` (matchDottedCallChain).
|
|
844
|
-
const chainMatch =
|
|
845
|
-
? (0, name_matcher_1.
|
|
846
|
-
:
|
|
1081
|
+
const chainMatch = (ref.language === 'php' && PHP_PROP_SHAPE.test(ref.referenceName))
|
|
1082
|
+
? (0, name_matcher_1.matchMethodCall)(ref, this.context)
|
|
1083
|
+
: SCOPED_CHAIN_LANGUAGES.has(ref.language)
|
|
1084
|
+
? (0, name_matcher_1.matchScopedCallChain)(ref, this.context)
|
|
1085
|
+
: (0, name_matcher_1.matchDottedCallChain)(ref, this.context);
|
|
847
1086
|
const match = this.gateLanguage(chainMatch, ref);
|
|
848
1087
|
if (match)
|
|
849
1088
|
resolved.push(match);
|
|
1089
|
+
await maybeYield();
|
|
850
1090
|
}
|
|
851
1091
|
if (resolved.length === 0)
|
|
852
1092
|
return 0;
|
|
@@ -857,13 +1097,69 @@ class ReferenceResolver {
|
|
|
857
1097
|
}
|
|
858
1098
|
return edges.length;
|
|
859
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Resolve one batch with a yield checkpoint between EVERY ref so the #850
|
|
1102
|
+
* liveness heartbeat can fire on a slow/dense batch (#1091). The checkpoint
|
|
1103
|
+
* granularity is per-ref — not per-N-refs — because per-ref cost is unbounded
|
|
1104
|
+
* in the worst case (a collision-heavy method name whose candidate set misses
|
|
1105
|
+
* the LRU re-fetches tens of thousands of rows): any fixed N multiplies that
|
|
1106
|
+
* worst case into the watchdog window, which is how v1.2.0 still got killed
|
|
1107
|
+
* at "Resolving refs" on large Java monorepos (#1122). `maybeYield()` is a
|
|
1108
|
+
* ~ns time check when under budget, so per-ref checkpoints cost nothing.
|
|
1109
|
+
* Behaviourally identical to `resolveAll(batch)`: `warmCaches()` is
|
|
1110
|
+
* idempotent (guarded) and `resolveOne` is independent per ref, so yielding
|
|
1111
|
+
* between refs changes only timing, never which edges get created.
|
|
1112
|
+
*/
|
|
1113
|
+
async resolveBatchYielding(batch, maybeYield) {
|
|
1114
|
+
this.warmCaches();
|
|
1115
|
+
const resolved = [];
|
|
1116
|
+
const unresolved = [];
|
|
1117
|
+
const byMethod = {};
|
|
1118
|
+
for (const raw of batch) {
|
|
1119
|
+
const ref = {
|
|
1120
|
+
fromNodeId: raw.fromNodeId,
|
|
1121
|
+
referenceName: raw.referenceName,
|
|
1122
|
+
referenceKind: raw.referenceKind,
|
|
1123
|
+
line: raw.line,
|
|
1124
|
+
column: raw.column,
|
|
1125
|
+
filePath: raw.filePath || this.getFilePathFromNodeId(raw.fromNodeId),
|
|
1126
|
+
language: raw.language || this.getLanguageFromNodeId(raw.fromNodeId),
|
|
1127
|
+
};
|
|
1128
|
+
const result = this.resolveOne(ref);
|
|
1129
|
+
if (result) {
|
|
1130
|
+
resolved.push(result);
|
|
1131
|
+
byMethod[result.resolvedBy] = (byMethod[result.resolvedBy] || 0) + 1;
|
|
1132
|
+
}
|
|
1133
|
+
else {
|
|
1134
|
+
unresolved.push(ref);
|
|
1135
|
+
}
|
|
1136
|
+
await maybeYield();
|
|
1137
|
+
}
|
|
1138
|
+
return {
|
|
1139
|
+
resolved,
|
|
1140
|
+
unresolved,
|
|
1141
|
+
stats: {
|
|
1142
|
+
total: batch.length,
|
|
1143
|
+
resolved: resolved.length,
|
|
1144
|
+
unresolved: unresolved.length,
|
|
1145
|
+
byMethod,
|
|
1146
|
+
},
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
860
1149
|
/**
|
|
861
1150
|
* Resolve and persist in batches to keep memory bounded.
|
|
862
1151
|
* Processes unresolved references in chunks, persisting edges and cleaning
|
|
863
1152
|
* up resolved refs after each batch to avoid accumulating large arrays.
|
|
864
1153
|
*/
|
|
865
1154
|
async resolveAndPersistBatched(onProgress, batchSize = 5000) {
|
|
866
|
-
|
|
1155
|
+
// Resolution runs on the indexer's MAIN thread, and the #850 liveness
|
|
1156
|
+
// watchdog SIGKILLs a process whose event loop stalls past its window (60s
|
|
1157
|
+
// by default). A single dense batch's resolveAll — or the synthesis pass
|
|
1158
|
+
// below — can exceed that on a large repo, killing a VALID in-progress index
|
|
1159
|
+
// (#1091). A shared yielder lets both give the watchdog heartbeat a regular
|
|
1160
|
+
// window to fire; see ./cooperative-yield.
|
|
1161
|
+
const maybeYield = (0, cooperative_yield_1.createYielder)();
|
|
1162
|
+
await this.warmCachesYielding(maybeYield);
|
|
867
1163
|
const total = this.queries.getUnresolvedReferencesCount();
|
|
868
1164
|
let processed = 0;
|
|
869
1165
|
const aggregateStats = {
|
|
@@ -872,34 +1168,52 @@ class ReferenceResolver {
|
|
|
872
1168
|
unresolved: 0,
|
|
873
1169
|
byMethod: {},
|
|
874
1170
|
};
|
|
875
|
-
// Process in batches. We always read from offset 0 because
|
|
876
|
-
//
|
|
1171
|
+
// Process in batches. We always read from offset 0 because every ref the
|
|
1172
|
+
// batch processed leaves the pending set (resolved rows are deleted,
|
|
1173
|
+
// unresolvable ones flip to status='failed'), shifting the remaining
|
|
1174
|
+
// pending rows forward.
|
|
877
1175
|
let prevRemaining = Number.POSITIVE_INFINITY;
|
|
878
1176
|
while (true) {
|
|
879
1177
|
const batch = this.queries.getUnresolvedReferencesBatch(0, batchSize);
|
|
880
1178
|
if (batch.length === 0)
|
|
881
1179
|
break;
|
|
882
|
-
const result = this.
|
|
1180
|
+
const result = await this.resolveBatchYielding(batch, maybeYield);
|
|
1181
|
+
// Persist in bounded sub-transactions with yields between: a whole
|
|
1182
|
+
// batch's edge insert / keyed deletes are otherwise one solid
|
|
1183
|
+
// synchronous span each on a multi-GB index, sitting BETWEEN the
|
|
1184
|
+
// per-ref yields — the last unyielded stretch of the resolution loop.
|
|
1185
|
+
// Crash semantics are unchanged (already several transactions): edges
|
|
1186
|
+
// land before their refs are deleted, so a kill mid-way re-resolves
|
|
1187
|
+
// the remainder idempotently on the next run/sweep (#1187).
|
|
1188
|
+
const PERSIST_CHUNK = 1000;
|
|
883
1189
|
// Persist edges immediately
|
|
884
1190
|
const edges = this.createEdges(result.resolved);
|
|
885
|
-
|
|
886
|
-
this.queries.insertEdges(edges);
|
|
1191
|
+
for (let i = 0; i < edges.length; i += PERSIST_CHUNK) {
|
|
1192
|
+
this.queries.insertEdges(edges.slice(i, i + PERSIST_CHUNK));
|
|
1193
|
+
await maybeYield();
|
|
887
1194
|
}
|
|
888
1195
|
// Clean up resolved refs so they don't appear in the next batch
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1196
|
+
const resolvedKeys = result.resolved.map((r) => ({
|
|
1197
|
+
fromNodeId: r.original.fromNodeId,
|
|
1198
|
+
referenceName: r.original.referenceName,
|
|
1199
|
+
referenceKind: r.original.referenceKind,
|
|
1200
|
+
}));
|
|
1201
|
+
for (let i = 0; i < resolvedKeys.length; i += PERSIST_CHUNK) {
|
|
1202
|
+
this.queries.deleteSpecificResolvedReferences(resolvedKeys.slice(i, i + PERSIST_CHUNK));
|
|
1203
|
+
await maybeYield();
|
|
895
1204
|
}
|
|
896
|
-
//
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1205
|
+
// Park unresolvable refs from this batch as status='failed' so they
|
|
1206
|
+
// leave the pending set (the batch reader and non-progress guard below
|
|
1207
|
+
// only see pending rows) but stay retryable when a later sync adds a
|
|
1208
|
+
// symbol that could satisfy them (#1240).
|
|
1209
|
+
const unresolvedKeys = result.unresolved.map((r) => ({
|
|
1210
|
+
fromNodeId: r.fromNodeId,
|
|
1211
|
+
referenceName: r.referenceName,
|
|
1212
|
+
referenceKind: r.referenceKind,
|
|
1213
|
+
}));
|
|
1214
|
+
for (let i = 0; i < unresolvedKeys.length; i += PERSIST_CHUNK) {
|
|
1215
|
+
this.queries.markReferencesFailed(unresolvedKeys.slice(i, i + PERSIST_CHUNK));
|
|
1216
|
+
await maybeYield();
|
|
903
1217
|
}
|
|
904
1218
|
// Aggregate stats
|
|
905
1219
|
aggregateStats.total += result.stats.total;
|
|
@@ -912,19 +1226,22 @@ class ReferenceResolver {
|
|
|
912
1226
|
onProgress?.(processed, total);
|
|
913
1227
|
// Yield so progress UI can render between batches
|
|
914
1228
|
await new Promise(resolve => setImmediate(resolve));
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1229
|
+
// NOTE: there used to be an extra early break here when a batch resolved
|
|
1230
|
+
// nothing (`result.unresolved.length === batch.length`). That was wrong:
|
|
1231
|
+
// an all-unresolvable batch still DELETES its rows (progress), yet the
|
|
1232
|
+
// break abandoned every batch after it in the same run — on a repo whose
|
|
1233
|
+
// first 5000 refs are all external/stdlib calls, resolution stopped at
|
|
1234
|
+
// batch one and left the rest of the table as permanent orphans (#1187).
|
|
1235
|
+
// The count-based guard below catches the true no-progress case.
|
|
920
1236
|
// Non-progress guard (defense-in-depth). Because we re-read from offset 0
|
|
921
|
-
// each pass, the
|
|
922
|
-
//
|
|
1237
|
+
// each pass, the PENDING population MUST shrink every iteration — resolved
|
|
1238
|
+
// refs are deleted and unresolvable ones are marked failed above, and both
|
|
1239
|
+
// leave the pending set the batch reader sees. If it didn't shrink, a
|
|
923
1240
|
// resolver returned a match whose `original.referenceName` differs from the
|
|
924
|
-
// stored row, so the keyed delete no-ops, and we'd re-read +
|
|
925
|
-
// re-insert the same rows forever (the runaway that grew a
|
|
926
|
-
// 5M edges / 1.4 GB before the Go-fallback fix). Stop
|
|
927
|
-
// graph without bound.
|
|
1241
|
+
// stored row, so the keyed delete/update no-ops, and we'd re-read +
|
|
1242
|
+
// re-resolve + re-insert the same rows forever (the runaway that grew a
|
|
1243
|
+
// 99-file repo to 5M edges / 1.4 GB before the Go-fallback fix). Stop
|
|
1244
|
+
// rather than grow the graph without bound.
|
|
928
1245
|
const remaining = this.queries.getUnresolvedReferencesCount();
|
|
929
1246
|
if (remaining >= prevRemaining)
|
|
930
1247
|
break;
|
|
@@ -935,7 +1252,7 @@ class ReferenceResolver {
|
|
|
935
1252
|
// callbacks) that static parsing leaves out. Best-effort — never fail the
|
|
936
1253
|
// index on it. See docs/design/callback-edge-synthesis.md.
|
|
937
1254
|
try {
|
|
938
|
-
aggregateStats.byMethod['callback-synthesis'] = (0, callback_synthesizer_1.synthesizeCallbackEdges)(this.queries, this.context);
|
|
1255
|
+
aggregateStats.byMethod['callback-synthesis'] = await (0, callback_synthesizer_1.synthesizeCallbackEdges)(this.queries, this.context);
|
|
939
1256
|
}
|
|
940
1257
|
catch {
|
|
941
1258
|
// synthesis is additive and optional; ignore failures
|
|
@@ -958,11 +1275,18 @@ class ReferenceResolver {
|
|
|
958
1275
|
isBuiltInOrExternal(ref) {
|
|
959
1276
|
const name = ref.referenceName;
|
|
960
1277
|
const isJsTs = ref.language === 'typescript' || ref.language === 'javascript'
|
|
961
|
-
|| ref.language === 'tsx' || ref.language === 'jsx';
|
|
1278
|
+
|| ref.language === 'tsx' || ref.language === 'jsx' || ref.language === 'arkts';
|
|
962
1279
|
// JavaScript/TypeScript built-ins
|
|
963
1280
|
if (isJsTs && JS_BUILT_INS.has(name)) {
|
|
964
1281
|
return true;
|
|
965
1282
|
}
|
|
1283
|
+
// ArkTS resource-reference intrinsics — `$r('app.string.x')` /
|
|
1284
|
+
// `$rawfile('x.png')` are framework-provided and appear dozens of times
|
|
1285
|
+
// per UI file; without this they can resolve to a stray same-named
|
|
1286
|
+
// symbol (e.g. a checked-in hvigor wrapper's `$r`).
|
|
1287
|
+
if (ref.language === 'arkts' && (name === '$r' || name === '$rawfile')) {
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
966
1290
|
// Common JS/TS library calls (console.log, Math.floor, JSON.parse)
|
|
967
1291
|
if (isJsTs && (name.startsWith('console.') || name.startsWith('Math.') || name.startsWith('JSON.'))) {
|
|
968
1292
|
return true;
|
|
@@ -1127,6 +1451,96 @@ class ReferenceResolver {
|
|
|
1127
1451
|
const target = found.values().next().value;
|
|
1128
1452
|
return { original: ref, targetNodeId: target.id, confidence: 0.9, resolvedBy: 'import' };
|
|
1129
1453
|
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Resolve a CFML inheritance reference written as a component path (#1152).
|
|
1456
|
+
* Two forms exist in real code:
|
|
1457
|
+
*
|
|
1458
|
+
* - Dotted: `extends="coldbox.system.web.Controller"` — dots are directory
|
|
1459
|
+
* separators from the webroot or a CFML mapping. Mappings live in server
|
|
1460
|
+
* config / Application.cfc, so the leading segments may not exist in the
|
|
1461
|
+
* repo at all (in the coldbox repo itself the path is `system/web/
|
|
1462
|
+
* Controller.cfc` — the `coldbox.` root IS the repo). Matched by final
|
|
1463
|
+
* segment (the class), corroborated right-to-left against the candidate's
|
|
1464
|
+
* parent directories.
|
|
1465
|
+
* - Relative: `extends="../base"` / `extends="./base"` (the FW/1 style) —
|
|
1466
|
+
* resolved against the referencing file's own directory.
|
|
1467
|
+
*
|
|
1468
|
+
* Conservative by design: a candidate needs at least one corroborating
|
|
1469
|
+
* directory segment (a dotted path whose only same-named class sits in an
|
|
1470
|
+
* unrelated directory is almost always an out-of-repo library supertype —
|
|
1471
|
+
* mxunit/testbox/coldbox-as-dependency), and a corroboration tie yields no
|
|
1472
|
+
* edge. Directory comparison is case-insensitive (CFML path resolution is);
|
|
1473
|
+
* the class segment itself is matched exactly, which real code satisfies —
|
|
1474
|
+
* dotted paths are written to match the on-disk file name.
|
|
1475
|
+
*/
|
|
1476
|
+
resolveCfmlComponentPath(ref) {
|
|
1477
|
+
const cfmlCandidates = (name) => this.context
|
|
1478
|
+
.getNodesByName(name)
|
|
1479
|
+
.filter((n) => (n.kind === 'class' || n.kind === 'interface') &&
|
|
1480
|
+
(n.language === 'cfml' || n.language === 'cfscript'));
|
|
1481
|
+
const norm = (p) => p.replace(/\\/g, '/').toLowerCase();
|
|
1482
|
+
// Relative-path form: `../base`, `./base`, `sub/thing` — resolve against
|
|
1483
|
+
// the referencing file's directory and require an exact (case-insensitive)
|
|
1484
|
+
// file match.
|
|
1485
|
+
if (ref.referenceName.includes('/')) {
|
|
1486
|
+
const rel = ref.referenceName.replace(/\.cfc$/i, '');
|
|
1487
|
+
const fromDir = ref.filePath.replace(/\\/g, '/').split('/').slice(0, -1);
|
|
1488
|
+
const parts = [...fromDir];
|
|
1489
|
+
for (const seg of rel.split('/')) {
|
|
1490
|
+
if (seg === '' || seg === '.')
|
|
1491
|
+
continue;
|
|
1492
|
+
if (seg === '..') {
|
|
1493
|
+
if (parts.length === 0)
|
|
1494
|
+
return null; // escapes the project root
|
|
1495
|
+
parts.pop();
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
parts.push(seg);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const wantPath = norm(parts.join('/') + '.cfc');
|
|
1502
|
+
const className = parts[parts.length - 1];
|
|
1503
|
+
if (!className)
|
|
1504
|
+
return null;
|
|
1505
|
+
const target = cfmlCandidates(className).find((c) => norm(c.filePath) === wantPath);
|
|
1506
|
+
return target
|
|
1507
|
+
? { original: ref, targetNodeId: target.id, confidence: 0.95, resolvedBy: 'file-path' }
|
|
1508
|
+
: null;
|
|
1509
|
+
}
|
|
1510
|
+
// Dotted form.
|
|
1511
|
+
const segments = ref.referenceName.split('.').map((s) => s.trim()).filter(Boolean);
|
|
1512
|
+
if (segments.length < 2)
|
|
1513
|
+
return null;
|
|
1514
|
+
const className = segments[segments.length - 1];
|
|
1515
|
+
const dirSegments = segments.slice(0, -1);
|
|
1516
|
+
let best = null;
|
|
1517
|
+
let bestScore = 0;
|
|
1518
|
+
let tie = false;
|
|
1519
|
+
for (const cand of cfmlCandidates(className)) {
|
|
1520
|
+
const dirs = cand.filePath.replace(/\\/g, '/').split('/').slice(0, -1);
|
|
1521
|
+
// Count matching directory segments right-to-left: for
|
|
1522
|
+
// `coldbox.system.web.Controller` vs `system/web/Controller.cfc`,
|
|
1523
|
+
// `web` and `system` match, then the repo root ends the run → score 2.
|
|
1524
|
+
let score = 0;
|
|
1525
|
+
while (score < dirSegments.length &&
|
|
1526
|
+
score < dirs.length &&
|
|
1527
|
+
dirSegments[dirSegments.length - 1 - score].toLowerCase() ===
|
|
1528
|
+
dirs[dirs.length - 1 - score].toLowerCase()) {
|
|
1529
|
+
score++;
|
|
1530
|
+
}
|
|
1531
|
+
if (score > bestScore) {
|
|
1532
|
+
best = cand;
|
|
1533
|
+
bestScore = score;
|
|
1534
|
+
tie = false;
|
|
1535
|
+
}
|
|
1536
|
+
else if (score === bestScore && score > 0) {
|
|
1537
|
+
tie = true;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
if (!best || bestScore === 0 || tie)
|
|
1541
|
+
return null;
|
|
1542
|
+
return { original: ref, targetNodeId: best.id, confidence: 0.9, resolvedBy: 'qualified-name' };
|
|
1543
|
+
}
|
|
1130
1544
|
/**
|
|
1131
1545
|
* Resolve a `this.<member>` function-as-value reference (#756/#808) to the
|
|
1132
1546
|
* ENCLOSING CLASS's own member — never a same-named symbol elsewhere. The
|
|
@@ -1186,14 +1600,18 @@ class ReferenceResolver {
|
|
|
1186
1600
|
* Mirrors resolveChainedCallsViaConformance's lifecycle. Returns the number
|
|
1187
1601
|
* of newly-created edges.
|
|
1188
1602
|
*/
|
|
1189
|
-
resolveDeferredThisMemberRefs() {
|
|
1603
|
+
async resolveDeferredThisMemberRefs() {
|
|
1190
1604
|
const deferred = this.deferredThisMemberRefs;
|
|
1191
1605
|
this.deferredThisMemberRefs = [];
|
|
1192
1606
|
if (deferred.length === 0)
|
|
1193
1607
|
return 0;
|
|
1194
1608
|
this.clearCaches();
|
|
1609
|
+
// Synchronous main-thread post-pass with a per-ref supertype BFS — yield
|
|
1610
|
+
// periodically so the #850 liveness watchdog heartbeat can fire (#1091).
|
|
1611
|
+
const maybeYield = (0, cooperative_yield_1.createYielder)();
|
|
1195
1612
|
const resolved = [];
|
|
1196
1613
|
for (const ref of deferred) {
|
|
1614
|
+
await maybeYield();
|
|
1197
1615
|
const member = ref.referenceName.slice('this.'.length);
|
|
1198
1616
|
const fromNode = this.queries.getNodeById(ref.fromNodeId);
|
|
1199
1617
|
if (!fromNode || !member)
|
|
@@ -1361,6 +1779,65 @@ class ReferenceResolver {
|
|
|
1361
1779
|
}
|
|
1362
1780
|
return repointedCount;
|
|
1363
1781
|
}
|
|
1782
|
+
/**
|
|
1783
|
+
* VBA #78 — post-extraction resolver pass for `references` edges that
|
|
1784
|
+
* target synthetic `class` nodes created by the extractor for cross-file
|
|
1785
|
+
* type references (`Dim x As MyEnum`, `Set x = New Foo`).
|
|
1786
|
+
*
|
|
1787
|
+
* The extractor creates a synthetic `class` node keyed on
|
|
1788
|
+
* `(filePath, 'class', name, 0)` in the REFERENCING file. When the real
|
|
1789
|
+
* type lives in a different file (e.g. `enum MyEnum` in `Module1.bas`),
|
|
1790
|
+
* the `references` edge points to the synthetic stub instead of the real
|
|
1791
|
+
* type node. This method repoints those edges to the real type node when
|
|
1792
|
+
* uniquely resolvable by name + kind, then deletes the synthetic stub.
|
|
1793
|
+
*
|
|
1794
|
+
* Resolution strategy: for each VBA `class` stub, find all real project
|
|
1795
|
+
* nodes with the same `name` whose `kind` is one of the type-like kinds
|
|
1796
|
+
* (enum, class, interface, struct, type_alias). Exactly one match →
|
|
1797
|
+
* repoint; zero or 2+ → decline (leave the stub as-is).
|
|
1798
|
+
*
|
|
1799
|
+
* Duplicate `(source, target, 'references')` rows after repointing are
|
|
1800
|
+
* collapsed the same way `resolveVbaCallStubs` handles F1 duplicates.
|
|
1801
|
+
*/
|
|
1802
|
+
resolveVbaReferenceStubs() {
|
|
1803
|
+
const stubs = this.queries.getVbaReferenceStubs();
|
|
1804
|
+
if (stubs.length === 0)
|
|
1805
|
+
return 0;
|
|
1806
|
+
const TYPE_KINDS = new Set(['enum', 'class', 'interface', 'struct', 'type_alias']);
|
|
1807
|
+
const stubIds = new Set(stubs.map((s) => s.id));
|
|
1808
|
+
const seenTuples = new Set();
|
|
1809
|
+
let repointedCount = 0;
|
|
1810
|
+
for (const stub of stubs) {
|
|
1811
|
+
// Find real type nodes with the same name, excluding other stubs
|
|
1812
|
+
const candidates = this.queries
|
|
1813
|
+
.getNodesByName(stub.name)
|
|
1814
|
+
.filter((n) => n.id !== stub.id &&
|
|
1815
|
+
!stubIds.has(n.id) &&
|
|
1816
|
+
TYPE_KINDS.has(n.kind) &&
|
|
1817
|
+
n.language === 'vba');
|
|
1818
|
+
if (candidates.length !== 1)
|
|
1819
|
+
continue; // 0 or 2+ → decline
|
|
1820
|
+
const real = candidates[0];
|
|
1821
|
+
const incoming = this.queries.getIncomingEdges(stub.id, ['references']);
|
|
1822
|
+
for (const edge of incoming) {
|
|
1823
|
+
if (edge.id === undefined)
|
|
1824
|
+
continue;
|
|
1825
|
+
const tupleKey = `${edge.source}\0${real.id}`;
|
|
1826
|
+
if (seenTuples.has(tupleKey) ||
|
|
1827
|
+
this.queries.edgeExists(edge.source, real.id, 'references')) {
|
|
1828
|
+
this.queries.deleteEdgeById(edge.id);
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
seenTuples.add(tupleKey);
|
|
1832
|
+
const meta = { ...(edge.metadata ?? {}), resolvedBy: 'vba-reference-stub' };
|
|
1833
|
+
this.queries.repointEdgeTarget(edge.id, real.id, JSON.stringify(meta));
|
|
1834
|
+
repointedCount++;
|
|
1835
|
+
}
|
|
1836
|
+
// Stub is no longer referenced — safe to delete
|
|
1837
|
+
this.queries.deleteNode(stub.id);
|
|
1838
|
+
}
|
|
1839
|
+
return repointedCount;
|
|
1840
|
+
}
|
|
1364
1841
|
/**
|
|
1365
1842
|
* Resolve a single VBA call-stub node to its real target, or `null` when
|
|
1366
1843
|
* unresolvable/ambiguous. See `resolveVbaCallStubs` for the two-step
|