@ltm-alpha/alpha-cs 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/LICENSE +21 -0
- package/README.md +972 -0
- package/dist/bin/command-supervision.d.ts +49 -0
- package/dist/bin/command-supervision.d.ts.map +1 -0
- package/dist/bin/command-supervision.js +95 -0
- package/dist/bin/command-supervision.js.map +1 -0
- package/dist/bin/fatal-handler.d.ts +20 -0
- package/dist/bin/fatal-handler.d.ts.map +1 -0
- package/dist/bin/fatal-handler.js +118 -0
- package/dist/bin/fatal-handler.js.map +1 -0
- package/dist/bin/ltm-alpha-cs.d.ts +27 -0
- package/dist/bin/ltm-alpha-cs.d.ts.map +1 -0
- package/dist/bin/ltm-alpha-cs.js +2643 -0
- package/dist/bin/ltm-alpha-cs.js.map +1 -0
- package/dist/bin/node-version-check.d.ts +37 -0
- package/dist/bin/node-version-check.d.ts.map +1 -0
- package/dist/bin/node-version-check.js +79 -0
- package/dist/bin/node-version-check.js.map +1 -0
- package/dist/bin/uninstall.d.ts +14 -0
- package/dist/bin/uninstall.d.ts.map +1 -0
- package/dist/bin/uninstall.js +36 -0
- package/dist/bin/uninstall.js.map +1 -0
- package/dist/context/formatter.d.ts +36 -0
- package/dist/context/formatter.d.ts.map +1 -0
- package/dist/context/formatter.js +269 -0
- package/dist/context/formatter.js.map +1 -0
- package/dist/context/index.d.ts +131 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +1378 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/markers.d.ts +19 -0
- package/dist/context/markers.d.ts.map +1 -0
- package/dist/context/markers.js +22 -0
- package/dist/context/markers.js.map +1 -0
- package/dist/db/index.d.ts +385 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +902 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +44 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +236 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/queries.d.ts +1095 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +3285 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.sql +207 -0
- package/dist/db/sqlite-adapter.d.ts +55 -0
- package/dist/db/sqlite-adapter.d.ts.map +1 -0
- package/dist/db/sqlite-adapter.js +135 -0
- package/dist/db/sqlite-adapter.js.map +1 -0
- package/dist/db/wal-valve.d.ts +139 -0
- package/dist/db/wal-valve.d.ts.map +1 -0
- package/dist/db/wal-valve.js +333 -0
- package/dist/db/wal-valve.js.map +1 -0
- package/dist/directory.d.ts +232 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +877 -0
- package/dist/directory.js.map +1 -0
- package/dist/errors.d.ts +150 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +234 -0
- package/dist/errors.js.map +1 -0
- package/dist/extraction/astro-extractor.d.ts +79 -0
- package/dist/extraction/astro-extractor.d.ts.map +1 -0
- package/dist/extraction/astro-extractor.js +320 -0
- package/dist/extraction/astro-extractor.js.map +1 -0
- package/dist/extraction/cfml-extractor.d.ts +107 -0
- package/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/dist/extraction/cfml-extractor.js +505 -0
- package/dist/extraction/cfml-extractor.js.map +1 -0
- package/dist/extraction/dfm-extractor.d.ts +31 -0
- package/dist/extraction/dfm-extractor.d.ts.map +1 -0
- package/dist/extraction/dfm-extractor.js +151 -0
- package/dist/extraction/dfm-extractor.js.map +1 -0
- package/dist/extraction/extraction-version.d.ts +25 -0
- package/dist/extraction/extraction-version.d.ts.map +1 -0
- package/dist/extraction/extraction-version.js +28 -0
- package/dist/extraction/extraction-version.js.map +1 -0
- package/dist/extraction/function-ref.d.ts +118 -0
- package/dist/extraction/function-ref.d.ts.map +1 -0
- package/dist/extraction/function-ref.js +746 -0
- package/dist/extraction/function-ref.js.map +1 -0
- package/dist/extraction/generated-detection.d.ts +66 -0
- package/dist/extraction/generated-detection.d.ts.map +1 -0
- package/dist/extraction/generated-detection.js +248 -0
- package/dist/extraction/generated-detection.js.map +1 -0
- package/dist/extraction/grammars.d.ts +148 -0
- package/dist/extraction/grammars.d.ts.map +1 -0
- package/dist/extraction/grammars.js +743 -0
- package/dist/extraction/grammars.js.map +1 -0
- package/dist/extraction/index.d.ts +455 -0
- package/dist/extraction/index.d.ts.map +1 -0
- package/dist/extraction/index.js +3208 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/extraction/kernel/decode.d.ts +9 -0
- package/dist/extraction/kernel/decode.d.ts.map +1 -0
- package/dist/extraction/kernel/decode.js +162 -0
- package/dist/extraction/kernel/decode.js.map +1 -0
- package/dist/extraction/kernel/index.d.ts +64 -0
- package/dist/extraction/kernel/index.d.ts.map +1 -0
- package/dist/extraction/kernel/index.js +279 -0
- package/dist/extraction/kernel/index.js.map +1 -0
- package/dist/extraction/kernel/layout.d.ts +101 -0
- package/dist/extraction/kernel/layout.d.ts.map +1 -0
- package/dist/extraction/kernel/layout.js +104 -0
- package/dist/extraction/kernel/layout.js.map +1 -0
- package/dist/extraction/kernel/loader.d.ts +103 -0
- package/dist/extraction/kernel/loader.d.ts.map +1 -0
- package/dist/extraction/kernel/loader.js +146 -0
- package/dist/extraction/kernel/loader.js.map +1 -0
- package/dist/extraction/languages/arkts.d.ts +3 -0
- package/dist/extraction/languages/arkts.d.ts.map +1 -0
- package/dist/extraction/languages/arkts.js +127 -0
- package/dist/extraction/languages/arkts.js.map +1 -0
- package/dist/extraction/languages/c-cpp.d.ts +167 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
- package/dist/extraction/languages/c-cpp.js +1850 -0
- package/dist/extraction/languages/c-cpp.js.map +1 -0
- 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/csharp.d.ts +25 -0
- package/dist/extraction/languages/csharp.d.ts.map +1 -0
- package/dist/extraction/languages/csharp.js +175 -0
- package/dist/extraction/languages/csharp.js.map +1 -0
- package/dist/extraction/languages/dart.d.ts +3 -0
- package/dist/extraction/languages/dart.d.ts.map +1 -0
- package/dist/extraction/languages/dart.js +374 -0
- package/dist/extraction/languages/dart.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 +386 -0
- package/dist/extraction/languages/erlang.js.map +1 -0
- package/dist/extraction/languages/go.d.ts +3 -0
- package/dist/extraction/languages/go.d.ts.map +1 -0
- package/dist/extraction/languages/go.js +111 -0
- package/dist/extraction/languages/go.js.map +1 -0
- package/dist/extraction/languages/index.d.ts +10 -0
- package/dist/extraction/languages/index.d.ts.map +1 -0
- package/dist/extraction/languages/index.js +71 -0
- package/dist/extraction/languages/index.js.map +1 -0
- package/dist/extraction/languages/java.d.ts +3 -0
- package/dist/extraction/languages/java.d.ts.map +1 -0
- package/dist/extraction/languages/java.js +315 -0
- package/dist/extraction/languages/java.js.map +1 -0
- package/dist/extraction/languages/javascript.d.ts +3 -0
- package/dist/extraction/languages/javascript.d.ts.map +1 -0
- package/dist/extraction/languages/javascript.js +106 -0
- package/dist/extraction/languages/javascript.js.map +1 -0
- package/dist/extraction/languages/kotlin.d.ts +3 -0
- package/dist/extraction/languages/kotlin.d.ts.map +1 -0
- package/dist/extraction/languages/kotlin.js +512 -0
- package/dist/extraction/languages/kotlin.js.map +1 -0
- package/dist/extraction/languages/lua.d.ts +3 -0
- package/dist/extraction/languages/lua.d.ts.map +1 -0
- package/dist/extraction/languages/lua.js +153 -0
- package/dist/extraction/languages/lua.js.map +1 -0
- package/dist/extraction/languages/luau.d.ts +3 -0
- package/dist/extraction/languages/luau.d.ts.map +1 -0
- package/dist/extraction/languages/luau.js +37 -0
- package/dist/extraction/languages/luau.js.map +1 -0
- 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/objc.d.ts +3 -0
- package/dist/extraction/languages/objc.d.ts.map +1 -0
- package/dist/extraction/languages/objc.js +179 -0
- package/dist/extraction/languages/objc.js.map +1 -0
- package/dist/extraction/languages/pascal.d.ts +3 -0
- package/dist/extraction/languages/pascal.d.ts.map +1 -0
- package/dist/extraction/languages/pascal.js +77 -0
- package/dist/extraction/languages/pascal.js.map +1 -0
- package/dist/extraction/languages/php.d.ts +3 -0
- package/dist/extraction/languages/php.d.ts.map +1 -0
- package/dist/extraction/languages/php.js +196 -0
- package/dist/extraction/languages/php.js.map +1 -0
- package/dist/extraction/languages/python.d.ts +3 -0
- package/dist/extraction/languages/python.d.ts.map +1 -0
- package/dist/extraction/languages/python.js +56 -0
- package/dist/extraction/languages/python.js.map +1 -0
- package/dist/extraction/languages/r.d.ts +3 -0
- package/dist/extraction/languages/r.d.ts.map +1 -0
- package/dist/extraction/languages/r.js +314 -0
- package/dist/extraction/languages/r.js.map +1 -0
- package/dist/extraction/languages/ruby.d.ts +3 -0
- package/dist/extraction/languages/ruby.d.ts.map +1 -0
- package/dist/extraction/languages/ruby.js +149 -0
- package/dist/extraction/languages/ruby.js.map +1 -0
- package/dist/extraction/languages/rust.d.ts +23 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -0
- package/dist/extraction/languages/rust.js +172 -0
- package/dist/extraction/languages/rust.js.map +1 -0
- package/dist/extraction/languages/scala.d.ts +3 -0
- package/dist/extraction/languages/scala.d.ts.map +1 -0
- package/dist/extraction/languages/scala.js +219 -0
- package/dist/extraction/languages/scala.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/swift.d.ts +3 -0
- package/dist/extraction/languages/swift.d.ts.map +1 -0
- package/dist/extraction/languages/swift.js +152 -0
- package/dist/extraction/languages/swift.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/typescript.d.ts +16 -0
- package/dist/extraction/languages/typescript.d.ts.map +1 -0
- package/dist/extraction/languages/typescript.js +174 -0
- package/dist/extraction/languages/typescript.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/liquid-extractor.d.ts +59 -0
- package/dist/extraction/liquid-extractor.d.ts.map +1 -0
- package/dist/extraction/liquid-extractor.js +357 -0
- package/dist/extraction/liquid-extractor.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +68 -0
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
- package/dist/extraction/mybatis-extractor.js +300 -0
- package/dist/extraction/mybatis-extractor.js.map +1 -0
- package/dist/extraction/parse-pool.d.ts +167 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -0
- package/dist/extraction/parse-pool.js +421 -0
- package/dist/extraction/parse-pool.js.map +1 -0
- package/dist/extraction/parse-worker.d.ts +8 -0
- package/dist/extraction/parse-worker.d.ts.map +1 -0
- package/dist/extraction/parse-worker.js +138 -0
- package/dist/extraction/parse-worker.js.map +1 -0
- package/dist/extraction/razor-extractor.d.ts +42 -0
- package/dist/extraction/razor-extractor.d.ts.map +1 -0
- package/dist/extraction/razor-extractor.js +285 -0
- package/dist/extraction/razor-extractor.js.map +1 -0
- package/dist/extraction/store-worker.d.ts +24 -0
- package/dist/extraction/store-worker.d.ts.map +1 -0
- package/dist/extraction/store-worker.js +139 -0
- package/dist/extraction/store-worker.js.map +1 -0
- package/dist/extraction/store-writer.d.ts +63 -0
- package/dist/extraction/store-writer.d.ts.map +1 -0
- package/dist/extraction/store-writer.js +174 -0
- package/dist/extraction/store-writer.js.map +1 -0
- package/dist/extraction/svelte-extractor.d.ts +56 -0
- package/dist/extraction/svelte-extractor.d.ts.map +1 -0
- package/dist/extraction/svelte-extractor.js +275 -0
- package/dist/extraction/svelte-extractor.js.map +1 -0
- package/dist/extraction/syntax-tokens.d.ts +103 -0
- package/dist/extraction/syntax-tokens.d.ts.map +1 -0
- package/dist/extraction/syntax-tokens.js +381 -0
- package/dist/extraction/syntax-tokens.js.map +1 -0
- package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-helpers.js +153 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -0
- package/dist/extraction/tree-sitter-types.d.ts +279 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-types.js +10 -0
- package/dist/extraction/tree-sitter-types.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts +766 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -0
- package/dist/extraction/tree-sitter.js +7138 -0
- package/dist/extraction/tree-sitter.js.map +1 -0
- package/dist/extraction/vue-extractor.d.ts +51 -0
- package/dist/extraction/vue-extractor.d.ts.map +1 -0
- package/dist/extraction/vue-extractor.js +254 -0
- package/dist/extraction/vue-extractor.js.map +1 -0
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-r.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +53 -0
- package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
- package/dist/extraction/wasm-runtime-flags.js +130 -0
- package/dist/extraction/wasm-runtime-flags.js.map +1 -0
- package/dist/graph/branch-guards.d.ts +248 -0
- package/dist/graph/branch-guards.d.ts.map +1 -0
- package/dist/graph/branch-guards.js +2231 -0
- package/dist/graph/branch-guards.js.map +1 -0
- package/dist/graph/dead-code.d.ts +251 -0
- package/dist/graph/dead-code.d.ts.map +1 -0
- package/dist/graph/dead-code.js +756 -0
- package/dist/graph/dead-code.js.map +1 -0
- package/dist/graph/dynamic-boundary-report.d.ts +121 -0
- package/dist/graph/dynamic-boundary-report.d.ts.map +1 -0
- package/dist/graph/dynamic-boundary-report.js +283 -0
- package/dist/graph/dynamic-boundary-report.js.map +1 -0
- package/dist/graph/index.d.ts +12 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +28 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/named-symbol-flow.d.ts +125 -0
- package/dist/graph/named-symbol-flow.d.ts.map +1 -0
- package/dist/graph/named-symbol-flow.js +552 -0
- package/dist/graph/named-symbol-flow.js.map +1 -0
- package/dist/graph/queries.d.ts +106 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +341 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/symbol-lookup.d.ts +83 -0
- package/dist/graph/symbol-lookup.d.ts.map +1 -0
- package/dist/graph/symbol-lookup.js +181 -0
- package/dist/graph/symbol-lookup.js.map +1 -0
- package/dist/graph/traversal.d.ts +127 -0
- package/dist/graph/traversal.d.ts.map +1 -0
- package/dist/graph/traversal.js +599 -0
- package/dist/graph/traversal.js.map +1 -0
- package/dist/graph/type-hierarchy.d.ts +155 -0
- package/dist/graph/type-hierarchy.d.ts.map +1 -0
- package/dist/graph/type-hierarchy.js +382 -0
- package/dist/graph/type-hierarchy.js.map +1 -0
- package/dist/index.d.ts +874 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2005 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/config-writer.d.ts +28 -0
- package/dist/installer/config-writer.d.ts.map +1 -0
- package/dist/installer/config-writer.js +91 -0
- package/dist/installer/config-writer.js.map +1 -0
- package/dist/installer/index.d.ts +143 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +621 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/installer/instructions-template.d.ts +41 -0
- package/dist/installer/instructions-template.d.ts.map +1 -0
- package/dist/installer/instructions-template.js +53 -0
- package/dist/installer/instructions-template.js.map +1 -0
- package/dist/installer/targets/antigravity.d.ts +57 -0
- package/dist/installer/targets/antigravity.d.ts.map +1 -0
- package/dist/installer/targets/antigravity.js +308 -0
- package/dist/installer/targets/antigravity.js.map +1 -0
- package/dist/installer/targets/claude.d.ts +65 -0
- package/dist/installer/targets/claude.d.ts.map +1 -0
- package/dist/installer/targets/claude.js +514 -0
- package/dist/installer/targets/claude.js.map +1 -0
- package/dist/installer/targets/codex.d.ts +33 -0
- package/dist/installer/targets/codex.d.ts.map +1 -0
- package/dist/installer/targets/codex.js +212 -0
- package/dist/installer/targets/codex.js.map +1 -0
- package/dist/installer/targets/copilot-cli.d.ts +32 -0
- package/dist/installer/targets/copilot-cli.d.ts.map +1 -0
- package/dist/installer/targets/copilot-cli.js +215 -0
- package/dist/installer/targets/copilot-cli.js.map +1 -0
- package/dist/installer/targets/copilot-jetbrains.d.ts +42 -0
- package/dist/installer/targets/copilot-jetbrains.d.ts.map +1 -0
- package/dist/installer/targets/copilot-jetbrains.js +238 -0
- package/dist/installer/targets/copilot-jetbrains.js.map +1 -0
- package/dist/installer/targets/copilot-vscode.d.ts +47 -0
- package/dist/installer/targets/copilot-vscode.d.ts.map +1 -0
- package/dist/installer/targets/copilot-vscode.js +219 -0
- package/dist/installer/targets/copilot-vscode.js.map +1 -0
- package/dist/installer/targets/cursor.d.ts +35 -0
- package/dist/installer/targets/cursor.d.ts.map +1 -0
- package/dist/installer/targets/cursor.js +254 -0
- package/dist/installer/targets/cursor.js.map +1 -0
- package/dist/installer/targets/gemini.d.ts +26 -0
- package/dist/installer/targets/gemini.d.ts.map +1 -0
- package/dist/installer/targets/gemini.js +165 -0
- package/dist/installer/targets/gemini.js.map +1 -0
- package/dist/installer/targets/hermes.d.ts +18 -0
- package/dist/installer/targets/hermes.d.ts.map +1 -0
- package/dist/installer/targets/hermes.js +359 -0
- package/dist/installer/targets/hermes.js.map +1 -0
- package/dist/installer/targets/kiro.d.ts +27 -0
- package/dist/installer/targets/kiro.d.ts.map +1 -0
- package/dist/installer/targets/kiro.js +178 -0
- package/dist/installer/targets/kiro.js.map +1 -0
- package/dist/installer/targets/opencode.d.ts +49 -0
- package/dist/installer/targets/opencode.d.ts.map +1 -0
- package/dist/installer/targets/opencode.js +339 -0
- package/dist/installer/targets/opencode.js.map +1 -0
- package/dist/installer/targets/registry.d.ts +35 -0
- package/dist/installer/targets/registry.d.ts.map +1 -0
- package/dist/installer/targets/registry.js +97 -0
- package/dist/installer/targets/registry.js.map +1 -0
- package/dist/installer/targets/shared.d.ts +101 -0
- package/dist/installer/targets/shared.d.ts.map +1 -0
- package/dist/installer/targets/shared.js +264 -0
- package/dist/installer/targets/shared.js.map +1 -0
- package/dist/installer/targets/toml.d.ts +53 -0
- package/dist/installer/targets/toml.d.ts.map +1 -0
- package/dist/installer/targets/toml.js +226 -0
- package/dist/installer/targets/toml.js.map +1 -0
- package/dist/installer/targets/types.d.ts +108 -0
- package/dist/installer/targets/types.d.ts.map +1 -0
- package/dist/installer/targets/types.js +16 -0
- package/dist/installer/targets/types.js.map +1 -0
- package/dist/mcp/daemon-manager.d.ts +42 -0
- package/dist/mcp/daemon-manager.d.ts.map +1 -0
- package/dist/mcp/daemon-manager.js +141 -0
- package/dist/mcp/daemon-manager.js.map +1 -0
- package/dist/mcp/daemon-paths.d.ts +81 -0
- package/dist/mcp/daemon-paths.d.ts.map +1 -0
- package/dist/mcp/daemon-paths.js +228 -0
- package/dist/mcp/daemon-paths.js.map +1 -0
- package/dist/mcp/daemon-registry.d.ts +57 -0
- package/dist/mcp/daemon-registry.d.ts.map +1 -0
- package/dist/mcp/daemon-registry.js +323 -0
- package/dist/mcp/daemon-registry.js.map +1 -0
- package/dist/mcp/daemon.d.ts +294 -0
- package/dist/mcp/daemon.d.ts.map +1 -0
- package/dist/mcp/daemon.js +897 -0
- package/dist/mcp/daemon.js.map +1 -0
- package/dist/mcp/dynamic-boundaries.d.ts +32 -0
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -0
- package/dist/mcp/dynamic-boundaries.js +323 -0
- package/dist/mcp/dynamic-boundaries.js.map +1 -0
- 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 +133 -0
- package/dist/mcp/engine.d.ts.map +1 -0
- package/dist/mcp/engine.js +421 -0
- package/dist/mcp/engine.js.map +1 -0
- package/dist/mcp/explore-dedup.d.ts +140 -0
- package/dist/mcp/explore-dedup.d.ts.map +1 -0
- package/dist/mcp/explore-dedup.js +239 -0
- package/dist/mcp/explore-dedup.js.map +1 -0
- package/dist/mcp/explore-diagnostics.d.ts +289 -0
- package/dist/mcp/explore-diagnostics.d.ts.map +1 -0
- package/dist/mcp/explore-diagnostics.js +558 -0
- package/dist/mcp/explore-diagnostics.js.map +1 -0
- package/dist/mcp/explore-session-state.d.ts +217 -0
- package/dist/mcp/explore-session-state.d.ts.map +1 -0
- package/dist/mcp/explore-session-state.js +322 -0
- package/dist/mcp/explore-session-state.js.map +1 -0
- package/dist/mcp/index.d.ts +139 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +636 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/liveness-watchdog.d.ts +35 -0
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -0
- package/dist/mcp/liveness-watchdog.js +269 -0
- package/dist/mcp/liveness-watchdog.js.map +1 -0
- package/dist/mcp/ppid-watchdog.d.ts +62 -0
- package/dist/mcp/ppid-watchdog.d.ts.map +1 -0
- package/dist/mcp/ppid-watchdog.js +64 -0
- package/dist/mcp/ppid-watchdog.js.map +1 -0
- package/dist/mcp/proxy.d.ts +87 -0
- package/dist/mcp/proxy.d.ts.map +1 -0
- package/dist/mcp/proxy.js +672 -0
- package/dist/mcp/proxy.js.map +1 -0
- package/dist/mcp/query-pool.d.ts +108 -0
- package/dist/mcp/query-pool.d.ts.map +1 -0
- package/dist/mcp/query-pool.js +315 -0
- package/dist/mcp/query-pool.js.map +1 -0
- package/dist/mcp/query-worker.d.ts +24 -0
- package/dist/mcp/query-worker.d.ts.map +1 -0
- package/dist/mcp/query-worker.js +87 -0
- package/dist/mcp/query-worker.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +34 -0
- package/dist/mcp/server-instructions.d.ts.map +1 -0
- package/dist/mcp/server-instructions.js +113 -0
- package/dist/mcp/server-instructions.js.map +1 -0
- package/dist/mcp/session.d.ts +109 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +380 -0
- package/dist/mcp/session.js.map +1 -0
- 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/stdin-teardown.d.ts +27 -0
- package/dist/mcp/stdin-teardown.d.ts.map +1 -0
- package/dist/mcp/stdin-teardown.js +49 -0
- package/dist/mcp/stdin-teardown.js.map +1 -0
- package/dist/mcp/tools.d.ts +936 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +6611 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/transport.d.ts +188 -0
- package/dist/mcp/transport.d.ts.map +1 -0
- package/dist/mcp/transport.js +377 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/mcp/version.d.ts +19 -0
- package/dist/mcp/version.d.ts.map +1 -0
- package/dist/mcp/version.js +71 -0
- package/dist/mcp/version.js.map +1 -0
- package/dist/mcp/writer-lock.d.ts +47 -0
- package/dist/mcp/writer-lock.d.ts.map +1 -0
- package/dist/mcp/writer-lock.js +218 -0
- package/dist/mcp/writer-lock.js.map +1 -0
- package/dist/project-config.d.ts +117 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +409 -0
- package/dist/project-config.js.map +1 -0
- package/dist/resolution/alias-binding.d.ts +56 -0
- package/dist/resolution/alias-binding.d.ts.map +1 -0
- package/dist/resolution/alias-binding.js +118 -0
- package/dist/resolution/alias-binding.js.map +1 -0
- package/dist/resolution/c-fnptr-synthesizer.d.ts +6 -0
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -0
- package/dist/resolution/c-fnptr-synthesizer.js +1506 -0
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -0
- package/dist/resolution/callback-synthesizer.d.ts +78 -0
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
- package/dist/resolution/callback-synthesizer.js +4105 -0
- package/dist/resolution/callback-synthesizer.js.map +1 -0
- package/dist/resolution/cooperative-yield.d.ts +40 -0
- package/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/dist/resolution/cooperative-yield.js +44 -0
- package/dist/resolution/cooperative-yield.js.map +1 -0
- package/dist/resolution/expo-router-synthesizer.d.ts +32 -0
- package/dist/resolution/expo-router-synthesizer.d.ts.map +1 -0
- package/dist/resolution/expo-router-synthesizer.js +189 -0
- package/dist/resolution/expo-router-synthesizer.js.map +1 -0
- package/dist/resolution/frameworks/astro.d.ts +9 -0
- package/dist/resolution/frameworks/astro.d.ts.map +1 -0
- package/dist/resolution/frameworks/astro.js +169 -0
- package/dist/resolution/frameworks/astro.js.map +1 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
- package/dist/resolution/frameworks/cargo-workspace.js +225 -0
- package/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
- package/dist/resolution/frameworks/cics.d.ts +20 -0
- package/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/dist/resolution/frameworks/cics.js +90 -0
- package/dist/resolution/frameworks/cics.js.map +1 -0
- package/dist/resolution/frameworks/csharp.d.ts +8 -0
- package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
- package/dist/resolution/frameworks/csharp.js +335 -0
- package/dist/resolution/frameworks/csharp.js.map +1 -0
- package/dist/resolution/frameworks/drupal.d.ts +51 -0
- package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
- package/dist/resolution/frameworks/drupal.js +367 -0
- package/dist/resolution/frameworks/drupal.js.map +1 -0
- package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
- package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
- package/dist/resolution/frameworks/expo-modules.js +148 -0
- package/dist/resolution/frameworks/expo-modules.js.map +1 -0
- package/dist/resolution/frameworks/expo-router.d.ts +189 -0
- package/dist/resolution/frameworks/expo-router.d.ts.map +1 -0
- package/dist/resolution/frameworks/expo-router.js +785 -0
- package/dist/resolution/frameworks/expo-router.js.map +1 -0
- package/dist/resolution/frameworks/express.d.ts +8 -0
- package/dist/resolution/frameworks/express.d.ts.map +1 -0
- package/dist/resolution/frameworks/express.js +540 -0
- package/dist/resolution/frameworks/express.js.map +1 -0
- package/dist/resolution/frameworks/fabric.d.ts +3 -0
- package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
- package/dist/resolution/frameworks/fabric.js +354 -0
- package/dist/resolution/frameworks/fabric.js.map +1 -0
- package/dist/resolution/frameworks/go.d.ts +8 -0
- package/dist/resolution/frameworks/go.d.ts.map +1 -0
- package/dist/resolution/frameworks/go.js +184 -0
- package/dist/resolution/frameworks/go.js.map +1 -0
- package/dist/resolution/frameworks/goframe.d.ts +41 -0
- package/dist/resolution/frameworks/goframe.d.ts.map +1 -0
- package/dist/resolution/frameworks/goframe.js +112 -0
- package/dist/resolution/frameworks/goframe.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts +55 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -0
- package/dist/resolution/frameworks/index.js +203 -0
- package/dist/resolution/frameworks/index.js.map +1 -0
- package/dist/resolution/frameworks/java.d.ts +8 -0
- package/dist/resolution/frameworks/java.d.ts.map +1 -0
- package/dist/resolution/frameworks/java.js +539 -0
- package/dist/resolution/frameworks/java.js.map +1 -0
- package/dist/resolution/frameworks/laravel.d.ts +13 -0
- package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
- package/dist/resolution/frameworks/laravel.js +257 -0
- package/dist/resolution/frameworks/laravel.js.map +1 -0
- package/dist/resolution/frameworks/nestjs.d.ts +26 -0
- package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
- package/dist/resolution/frameworks/nestjs.js +690 -0
- package/dist/resolution/frameworks/nestjs.js.map +1 -0
- package/dist/resolution/frameworks/nextjs.d.ts +76 -0
- package/dist/resolution/frameworks/nextjs.d.ts.map +1 -0
- package/dist/resolution/frameworks/nextjs.js +328 -0
- package/dist/resolution/frameworks/nextjs.js.map +1 -0
- package/dist/resolution/frameworks/object-literal.d.ts +32 -0
- package/dist/resolution/frameworks/object-literal.d.ts.map +1 -0
- package/dist/resolution/frameworks/object-literal.js +139 -0
- package/dist/resolution/frameworks/object-literal.js.map +1 -0
- package/dist/resolution/frameworks/package-deps.d.ts +14 -0
- package/dist/resolution/frameworks/package-deps.d.ts.map +1 -0
- package/dist/resolution/frameworks/package-deps.js +78 -0
- package/dist/resolution/frameworks/package-deps.js.map +1 -0
- package/dist/resolution/frameworks/play.d.ts +19 -0
- package/dist/resolution/frameworks/play.d.ts.map +1 -0
- package/dist/resolution/frameworks/play.js +111 -0
- package/dist/resolution/frameworks/play.js.map +1 -0
- package/dist/resolution/frameworks/python.d.ts +10 -0
- package/dist/resolution/frameworks/python.d.ts.map +1 -0
- package/dist/resolution/frameworks/python.js +566 -0
- package/dist/resolution/frameworks/python.js.map +1 -0
- package/dist/resolution/frameworks/react-native.d.ts +29 -0
- package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
- package/dist/resolution/frameworks/react-native.js +549 -0
- package/dist/resolution/frameworks/react-native.js.map +1 -0
- package/dist/resolution/frameworks/react-router.d.ts +46 -0
- package/dist/resolution/frameworks/react-router.d.ts.map +1 -0
- package/dist/resolution/frameworks/react-router.js +187 -0
- package/dist/resolution/frameworks/react-router.js.map +1 -0
- package/dist/resolution/frameworks/react.d.ts +9 -0
- package/dist/resolution/frameworks/react.d.ts.map +1 -0
- package/dist/resolution/frameworks/react.js +257 -0
- package/dist/resolution/frameworks/react.js.map +1 -0
- package/dist/resolution/frameworks/ruby.d.ts +8 -0
- package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
- package/dist/resolution/frameworks/ruby.js +302 -0
- package/dist/resolution/frameworks/ruby.js.map +1 -0
- package/dist/resolution/frameworks/rust.d.ts +8 -0
- package/dist/resolution/frameworks/rust.d.ts.map +1 -0
- package/dist/resolution/frameworks/rust.js +304 -0
- package/dist/resolution/frameworks/rust.js.map +1 -0
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
- package/dist/resolution/frameworks/svelte.js +258 -0
- package/dist/resolution/frameworks/svelte.js.map +1 -0
- package/dist/resolution/frameworks/sveltekit-router.d.ts +36 -0
- package/dist/resolution/frameworks/sveltekit-router.d.ts.map +1 -0
- package/dist/resolution/frameworks/sveltekit-router.js +156 -0
- package/dist/resolution/frameworks/sveltekit-router.js.map +1 -0
- package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
- package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift-objc.js +252 -0
- package/dist/resolution/frameworks/swift-objc.js.map +1 -0
- package/dist/resolution/frameworks/swift.d.ts +10 -0
- package/dist/resolution/frameworks/swift.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift.js +410 -0
- package/dist/resolution/frameworks/swift.js.map +1 -0
- package/dist/resolution/frameworks/tanstack-router.d.ts +84 -0
- package/dist/resolution/frameworks/tanstack-router.d.ts.map +1 -0
- package/dist/resolution/frameworks/tanstack-router.js +437 -0
- package/dist/resolution/frameworks/tanstack-router.js.map +1 -0
- 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/frameworks/vue-router.d.ts +82 -0
- package/dist/resolution/frameworks/vue-router.d.ts.map +1 -0
- package/dist/resolution/frameworks/vue-router.js +377 -0
- package/dist/resolution/frameworks/vue-router.js.map +1 -0
- package/dist/resolution/frameworks/vue.d.ts +9 -0
- package/dist/resolution/frameworks/vue.d.ts.map +1 -0
- package/dist/resolution/frameworks/vue.js +303 -0
- package/dist/resolution/frameworks/vue.js.map +1 -0
- package/dist/resolution/go-module.d.ts +26 -0
- package/dist/resolution/go-module.d.ts.map +1 -0
- package/dist/resolution/go-module.js +78 -0
- package/dist/resolution/go-module.js.map +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +29 -0
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -0
- package/dist/resolution/goframe-synthesizer.js +163 -0
- package/dist/resolution/goframe-synthesizer.js.map +1 -0
- package/dist/resolution/import-resolver.d.ts +122 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -0
- package/dist/resolution/import-resolver.js +2490 -0
- package/dist/resolution/import-resolver.js.map +1 -0
- package/dist/resolution/index.d.ts +402 -0
- package/dist/resolution/index.d.ts.map +1 -0
- package/dist/resolution/index.js +2581 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/js-builtins.d.ts +11 -0
- package/dist/resolution/js-builtins.d.ts.map +1 -0
- package/dist/resolution/js-builtins.js +23 -0
- package/dist/resolution/js-builtins.js.map +1 -0
- package/dist/resolution/lru-cache.d.ts +24 -0
- package/dist/resolution/lru-cache.d.ts.map +1 -0
- package/dist/resolution/lru-cache.js +62 -0
- package/dist/resolution/lru-cache.js.map +1 -0
- 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 +196 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -0
- package/dist/resolution/name-matcher.js +3484 -0
- package/dist/resolution/name-matcher.js.map +1 -0
- package/dist/resolution/next-router-synthesizer.d.ts +27 -0
- package/dist/resolution/next-router-synthesizer.d.ts.map +1 -0
- package/dist/resolution/next-router-synthesizer.js +113 -0
- package/dist/resolution/next-router-synthesizer.js.map +1 -0
- package/dist/resolution/path-aliases.d.ts +70 -0
- package/dist/resolution/path-aliases.d.ts.map +1 -0
- package/dist/resolution/path-aliases.js +346 -0
- package/dist/resolution/path-aliases.js.map +1 -0
- package/dist/resolution/react-router-synthesizer.d.ts +33 -0
- package/dist/resolution/react-router-synthesizer.d.ts.map +1 -0
- package/dist/resolution/react-router-synthesizer.js +126 -0
- package/dist/resolution/react-router-synthesizer.js.map +1 -0
- 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 +33 -0
- package/dist/resolution/strip-comments.d.ts.map +1 -0
- package/dist/resolution/strip-comments.js +550 -0
- package/dist/resolution/strip-comments.js.map +1 -0
- package/dist/resolution/sveltekit-synthesizer.d.ts +58 -0
- package/dist/resolution/sveltekit-synthesizer.d.ts.map +1 -0
- package/dist/resolution/sveltekit-synthesizer.js +173 -0
- package/dist/resolution/sveltekit-synthesizer.js.map +1 -0
- package/dist/resolution/swift-objc-bridge.d.ts +134 -0
- package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
- package/dist/resolution/swift-objc-bridge.js +256 -0
- package/dist/resolution/swift-objc-bridge.js.map +1 -0
- package/dist/resolution/synth-utils.d.ts +26 -0
- package/dist/resolution/synth-utils.d.ts.map +1 -0
- package/dist/resolution/synth-utils.js +75 -0
- package/dist/resolution/synth-utils.js.map +1 -0
- package/dist/resolution/tanstack-router-synthesizer.d.ts +31 -0
- package/dist/resolution/tanstack-router-synthesizer.d.ts.map +1 -0
- package/dist/resolution/tanstack-router-synthesizer.js +114 -0
- package/dist/resolution/tanstack-router-synthesizer.js.map +1 -0
- package/dist/resolution/tier-synthesizer.d.ts +48 -0
- package/dist/resolution/tier-synthesizer.d.ts.map +1 -0
- package/dist/resolution/tier-synthesizer.js +907 -0
- package/dist/resolution/tier-synthesizer.js.map +1 -0
- package/dist/resolution/types.d.ts +323 -0
- package/dist/resolution/types.d.ts.map +1 -0
- package/dist/resolution/types.js +58 -0
- package/dist/resolution/types.js.map +1 -0
- package/dist/resolution/vue-router-synthesizer.d.ts +30 -0
- package/dist/resolution/vue-router-synthesizer.d.ts.map +1 -0
- package/dist/resolution/vue-router-synthesizer.js +115 -0
- package/dist/resolution/vue-router-synthesizer.js.map +1 -0
- package/dist/resolution/workspace-packages.d.ts +58 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -0
- package/dist/resolution/workspace-packages.js +346 -0
- package/dist/resolution/workspace-packages.js.map +1 -0
- package/dist/search/identifier-segments.d.ts +70 -0
- package/dist/search/identifier-segments.d.ts.map +1 -0
- package/dist/search/identifier-segments.js +205 -0
- package/dist/search/identifier-segments.js.map +1 -0
- package/dist/search/query-parser.d.ts +57 -0
- package/dist/search/query-parser.d.ts.map +1 -0
- package/dist/search/query-parser.js +177 -0
- package/dist/search/query-parser.js.map +1 -0
- package/dist/search/query-paths.d.ts +57 -0
- package/dist/search/query-paths.d.ts.map +1 -0
- package/dist/search/query-paths.js +298 -0
- package/dist/search/query-paths.js.map +1 -0
- package/dist/search/query-utils.d.ts +102 -0
- package/dist/search/query-utils.d.ts.map +1 -0
- package/dist/search/query-utils.js +487 -0
- package/dist/search/query-utils.js.map +1 -0
- package/dist/sync/git-hooks.d.ts +45 -0
- package/dist/sync/git-hooks.d.ts.map +1 -0
- package/dist/sync/git-hooks.js +227 -0
- package/dist/sync/git-hooks.js.map +1 -0
- package/dist/sync/index.d.ts +19 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +35 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/watch-policy.d.ts +48 -0
- package/dist/sync/watch-policy.d.ts.map +1 -0
- package/dist/sync/watch-policy.js +124 -0
- package/dist/sync/watch-policy.js.map +1 -0
- package/dist/sync/watcher.d.ts +402 -0
- package/dist/sync/watcher.d.ts.map +1 -0
- package/dist/sync/watcher.js +978 -0
- package/dist/sync/watcher.js.map +1 -0
- package/dist/sync/worktree.d.ts +63 -0
- package/dist/sync/worktree.d.ts.map +1 -0
- package/dist/sync/worktree.js +182 -0
- package/dist/sync/worktree.js.map +1 -0
- package/dist/telemetry/index.d.ts +145 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +592 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/types.d.ts +473 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +118 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/color.d.ts +21 -0
- package/dist/ui/color.d.ts.map +1 -0
- package/dist/ui/color.js +42 -0
- package/dist/ui/color.js.map +1 -0
- package/dist/ui/glyphs.d.ts +67 -0
- package/dist/ui/glyphs.d.ts.map +1 -0
- package/dist/ui/glyphs.js +125 -0
- package/dist/ui/glyphs.js.map +1 -0
- package/dist/ui/shimmer-progress.d.ts +11 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -0
- package/dist/ui/shimmer-progress.js +164 -0
- package/dist/ui/shimmer-progress.js.map +1 -0
- package/dist/ui/shimmer-worker.d.ts +2 -0
- package/dist/ui/shimmer-worker.d.ts.map +1 -0
- package/dist/ui/shimmer-worker.js +126 -0
- package/dist/ui/shimmer-worker.js.map +1 -0
- package/dist/ui/types.d.ts +15 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +3 -0
- package/dist/ui/types.js.map +1 -0
- package/dist/ui-server/api/deadcode.d.ts +84 -0
- package/dist/ui-server/api/deadcode.d.ts.map +1 -0
- package/dist/ui-server/api/deadcode.js +136 -0
- package/dist/ui-server/api/deadcode.js.map +1 -0
- package/dist/ui-server/api/effects.d.ts +87 -0
- package/dist/ui-server/api/effects.d.ts.map +1 -0
- package/dist/ui-server/api/effects.js +499 -0
- package/dist/ui-server/api/effects.js.map +1 -0
- package/dist/ui-server/api/entrypoints.d.ts +86 -0
- package/dist/ui-server/api/entrypoints.d.ts.map +1 -0
- package/dist/ui-server/api/entrypoints.js +279 -0
- package/dist/ui-server/api/entrypoints.js.map +1 -0
- package/dist/ui-server/api/events.d.ts +183 -0
- package/dist/ui-server/api/events.d.ts.map +1 -0
- package/dist/ui-server/api/events.js +430 -0
- package/dist/ui-server/api/events.js.map +1 -0
- package/dist/ui-server/api/file.d.ts +66 -0
- package/dist/ui-server/api/file.d.ts.map +1 -0
- package/dist/ui-server/api/file.js +235 -0
- package/dist/ui-server/api/file.js.map +1 -0
- package/dist/ui-server/api/filecode.d.ts +126 -0
- package/dist/ui-server/api/filecode.d.ts.map +1 -0
- package/dist/ui-server/api/filecode.js +205 -0
- package/dist/ui-server/api/filecode.js.map +1 -0
- package/dist/ui-server/api/flow.d.ts +226 -0
- package/dist/ui-server/api/flow.d.ts.map +1 -0
- package/dist/ui-server/api/flow.js +611 -0
- package/dist/ui-server/api/flow.js.map +1 -0
- package/dist/ui-server/api/hierarchy.d.ts +81 -0
- package/dist/ui-server/api/hierarchy.d.ts.map +1 -0
- package/dist/ui-server/api/hierarchy.js +92 -0
- package/dist/ui-server/api/hierarchy.js.map +1 -0
- package/dist/ui-server/api/index.d.ts +90 -0
- package/dist/ui-server/api/index.d.ts.map +1 -0
- package/dist/ui-server/api/index.js +332 -0
- package/dist/ui-server/api/index.js.map +1 -0
- package/dist/ui-server/api/map.d.ts +238 -0
- package/dist/ui-server/api/map.d.ts.map +1 -0
- package/dist/ui-server/api/map.js +681 -0
- package/dist/ui-server/api/map.js.map +1 -0
- package/dist/ui-server/api/node.d.ts +81 -0
- package/dist/ui-server/api/node.d.ts.map +1 -0
- package/dist/ui-server/api/node.js +388 -0
- package/dist/ui-server/api/node.js.map +1 -0
- package/dist/ui-server/api/nodes.d.ts +25 -0
- package/dist/ui-server/api/nodes.d.ts.map +1 -0
- package/dist/ui-server/api/nodes.js +45 -0
- package/dist/ui-server/api/nodes.js.map +1 -0
- package/dist/ui-server/api/program.d.ts +139 -0
- package/dist/ui-server/api/program.d.ts.map +1 -0
- package/dist/ui-server/api/program.js +315 -0
- package/dist/ui-server/api/program.js.map +1 -0
- package/dist/ui-server/api/respond.d.ts +76 -0
- package/dist/ui-server/api/respond.d.ts.map +1 -0
- package/dist/ui-server/api/respond.js +186 -0
- package/dist/ui-server/api/respond.js.map +1 -0
- package/dist/ui-server/api/route-roots.d.ts +39 -0
- package/dist/ui-server/api/route-roots.d.ts.map +1 -0
- package/dist/ui-server/api/route-roots.js +96 -0
- package/dist/ui-server/api/route-roots.js.map +1 -0
- package/dist/ui-server/api/routes.d.ts +59 -0
- package/dist/ui-server/api/routes.d.ts.map +1 -0
- package/dist/ui-server/api/routes.js +112 -0
- package/dist/ui-server/api/routes.js.map +1 -0
- package/dist/ui-server/api/screens.d.ts +117 -0
- package/dist/ui-server/api/screens.d.ts.map +1 -0
- package/dist/ui-server/api/screens.js +611 -0
- package/dist/ui-server/api/screens.js.map +1 -0
- package/dist/ui-server/api/search.d.ts +29 -0
- package/dist/ui-server/api/search.d.ts.map +1 -0
- package/dist/ui-server/api/search.js +232 -0
- package/dist/ui-server/api/search.js.map +1 -0
- package/dist/ui-server/api/session.d.ts +52 -0
- package/dist/ui-server/api/session.d.ts.map +1 -0
- package/dist/ui-server/api/session.js +199 -0
- package/dist/ui-server/api/session.js.map +1 -0
- package/dist/ui-server/api/source.d.ts +200 -0
- package/dist/ui-server/api/source.d.ts.map +1 -0
- package/dist/ui-server/api/source.js +419 -0
- package/dist/ui-server/api/source.js.map +1 -0
- package/dist/ui-server/api/stats.d.ts +27 -0
- package/dist/ui-server/api/stats.d.ts.map +1 -0
- package/dist/ui-server/api/stats.js +164 -0
- package/dist/ui-server/api/stats.js.map +1 -0
- package/dist/ui-server/api/steps.d.ts +274 -0
- package/dist/ui-server/api/steps.d.ts.map +1 -0
- package/dist/ui-server/api/steps.js +1446 -0
- package/dist/ui-server/api/steps.js.map +1 -0
- package/dist/ui-server/api/trail-store.d.ts +142 -0
- package/dist/ui-server/api/trail-store.d.ts.map +1 -0
- package/dist/ui-server/api/trail-store.js +323 -0
- package/dist/ui-server/api/trail-store.js.map +1 -0
- package/dist/ui-server/api/trails.d.ts +163 -0
- package/dist/ui-server/api/trails.d.ts.map +1 -0
- package/dist/ui-server/api/trails.js +369 -0
- package/dist/ui-server/api/trails.js.map +1 -0
- package/dist/ui-server/api/when.d.ts +125 -0
- package/dist/ui-server/api/when.d.ts.map +1 -0
- package/dist/ui-server/api/when.js +251 -0
- package/dist/ui-server/api/when.js.map +1 -0
- package/dist/ui-server/api/wire.d.ts +179 -0
- package/dist/ui-server/api/wire.d.ts.map +1 -0
- package/dist/ui-server/api/wire.js +231 -0
- package/dist/ui-server/api/wire.js.map +1 -0
- package/dist/ui-server/assets.d.ts +40 -0
- package/dist/ui-server/assets.d.ts.map +1 -0
- package/dist/ui-server/assets.js +110 -0
- package/dist/ui-server/assets.js.map +1 -0
- package/dist/ui-server/constants.d.ts +32 -0
- package/dist/ui-server/constants.d.ts.map +1 -0
- package/dist/ui-server/constants.js +35 -0
- package/dist/ui-server/constants.js.map +1 -0
- package/dist/ui-server/highlight/index.d.ts +113 -0
- package/dist/ui-server/highlight/index.d.ts.map +1 -0
- package/dist/ui-server/highlight/index.js +307 -0
- package/dist/ui-server/highlight/index.js.map +1 -0
- package/dist/ui-server/highlight/languages.d.ts +36 -0
- package/dist/ui-server/highlight/languages.d.ts.map +1 -0
- package/dist/ui-server/highlight/languages.js +50 -0
- package/dist/ui-server/highlight/languages.js.map +1 -0
- package/dist/ui-server/index.d.ts +92 -0
- package/dist/ui-server/index.d.ts.map +1 -0
- package/dist/ui-server/index.js +399 -0
- package/dist/ui-server/index.js.map +1 -0
- package/dist/ui-server/open-browser.d.ts +31 -0
- package/dist/ui-server/open-browser.d.ts.map +1 -0
- package/dist/ui-server/open-browser.js +79 -0
- package/dist/ui-server/open-browser.js.map +1 -0
- package/dist/ui-server/security.d.ts +127 -0
- package/dist/ui-server/security.d.ts.map +1 -0
- package/dist/ui-server/security.js +332 -0
- package/dist/ui-server/security.js.map +1 -0
- package/dist/ui-server/static.d.ts +45 -0
- package/dist/ui-server/static.d.ts.map +1 -0
- package/dist/ui-server/static.js +165 -0
- package/dist/ui-server/static.js.map +1 -0
- package/dist/upgrade/index.d.ts +164 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +649 -0
- package/dist/upgrade/index.js.map +1 -0
- 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/dist/utils.d.ts +238 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +601 -0
- package/dist/utils.js.map +1 -0
- package/dist/viewer/assets/archivo-latin-ext-wght-normal-C4zznr8T.woff2 +0 -0
- package/dist/viewer/assets/archivo-latin-wght-normal-E0tuGl4L.woff2 +0 -0
- package/dist/viewer/assets/archivo-vietnamese-wght-normal-XAtsl5Q_.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-italic-2syK4fUT.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-italic-CBJ8pzag.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-600-normal-CTOM6hUh.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-600-normal-fLZuRloM.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-italic-B4oTjJdl.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-italic-CBjNughH.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-600-normal-9HEixskS.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-600-normal-V-xxqcpd.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-400-italic-66oory27.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-400-italic-BqAiT5Ww.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-italic-B7_fu1kp.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-italic-Bg0ZHwF4.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-600-normal-D38SheWl.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-latin-ext-600-normal-DmB0ttJJ.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-italic-D6eaYXMU.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-italic-DRuN92E5.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-600-normal-D2EvbN8M.woff2 +0 -0
- package/dist/viewer/assets/ibm-plex-mono-vietnamese-600-normal-iLQfcSjf.woff +0 -0
- package/dist/viewer/assets/index-2LG7iXtP.js +97 -0
- package/dist/viewer/assets/index-AtMBttuU.css +1 -0
- package/dist/viewer/index.html +16 -0
- package/package.json +69 -0
- package/scripts/add-lang/bench.sh +60 -0
- package/scripts/add-lang/check-grammar.mjs +75 -0
- package/scripts/add-lang/dump-ast.mjs +103 -0
- package/scripts/add-lang/verify-extraction.mjs +70 -0
- package/scripts/agent-eval/ab-adoption.sh +91 -0
- package/scripts/agent-eval/ab-hook.sh +86 -0
- package/scripts/agent-eval/ab-impl.sh +78 -0
- package/scripts/agent-eval/ab-new-vs-baseline.sh +161 -0
- package/scripts/agent-eval/ab-sufficiency.sh +78 -0
- package/scripts/agent-eval/allocation-fixtures.json +344 -0
- package/scripts/agent-eval/arms-F.sh +21 -0
- package/scripts/agent-eval/arms-matrix.sh +37 -0
- package/scripts/agent-eval/audit.sh +68 -0
- package/scripts/agent-eval/bench-readme.sh +48 -0
- package/scripts/agent-eval/bench-why-repo.sh +22 -0
- package/scripts/agent-eval/block-read-hook.sh +19 -0
- package/scripts/agent-eval/compare-arms.mjs +209 -0
- package/scripts/agent-eval/diff-index-drift.mjs +102 -0
- package/scripts/agent-eval/hook-settings.json +15 -0
- package/scripts/agent-eval/itrun.sh +120 -0
- package/scripts/agent-eval/no-cli-shim.sh +96 -0
- package/scripts/agent-eval/offload-eval-3arm.sh +72 -0
- package/scripts/agent-eval/offload-eval-cost.mjs +133 -0
- package/scripts/agent-eval/offload-eval-effort.mjs +108 -0
- package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -0
- package/scripts/agent-eval/offload-eval-frontload.sh +47 -0
- package/scripts/agent-eval/offload-eval-ground-truth.json +18 -0
- package/scripts/agent-eval/offload-eval-hook.mjs +84 -0
- package/scripts/agent-eval/offload-eval-judge.mjs +103 -0
- package/scripts/agent-eval/offload-eval-matrix.sh +20 -0
- package/scripts/agent-eval/offload-eval-metrics.mjs +94 -0
- package/scripts/agent-eval/offload-eval-refs1.sh +50 -0
- package/scripts/agent-eval/offload-eval-setup.sh +24 -0
- package/scripts/agent-eval/offload-eval-styles.sh +72 -0
- package/scripts/agent-eval/offload-eval-summarize.mjs +68 -0
- package/scripts/agent-eval/offload-eval.md +76 -0
- package/scripts/agent-eval/parse-arms.mjs +116 -0
- package/scripts/agent-eval/parse-bench-readme.mjs +238 -0
- package/scripts/agent-eval/parse-run.mjs +1401 -0
- package/scripts/agent-eval/parse-session.mjs +130 -0
- package/scripts/agent-eval/probe-allocation.mjs +335 -0
- package/scripts/agent-eval/probe-context.mjs +21 -0
- package/scripts/agent-eval/probe-decl-only.mjs +198 -0
- package/scripts/agent-eval/probe-explore.mjs +40 -0
- package/scripts/agent-eval/probe-factory-closure.mjs +147 -0
- package/scripts/agent-eval/probe-file-spend.mjs +195 -0
- package/scripts/agent-eval/probe-named-symbol.mjs +163 -0
- package/scripts/agent-eval/probe-node.mjs +20 -0
- package/scripts/agent-eval/probe-suite-envelope.mjs +124 -0
- package/scripts/agent-eval/probe-sweep.mjs +119 -0
- package/scripts/agent-eval/probe-trace.mjs +20 -0
- package/scripts/agent-eval/redirect-read-hook.sh +38 -0
- package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
- package/scripts/agent-eval/run-agent.sh +34 -0
- package/scripts/agent-eval/run-all.sh +149 -0
- package/scripts/agent-eval/run-arms.sh +56 -0
- package/scripts/agent-eval/seq-matrix.mjs +137 -0
- package/scripts/benchmarks/measure-index.cjs +128 -0
- package/scripts/benchmarks/observe-index.py +91 -0
- package/scripts/build-bundle.sh +154 -0
- package/scripts/build-kernel.sh +87 -0
- package/scripts/check-ui-build.mjs +157 -0
- package/scripts/check-ui-package.mjs +192 -0
- package/scripts/clean-remaining.mjs +243 -0
- package/scripts/count-codegraph.mjs +26 -0
- package/scripts/deep-clean.mjs +66 -0
- package/scripts/dump-graph.mjs +57 -0
- package/scripts/extract-release-notes.mjs +130 -0
- package/scripts/final-codegraph-cleanup.mjs +57 -0
- package/scripts/go-rebrand.mjs +114 -0
- package/scripts/kernel-parity.mjs +283 -0
- package/scripts/local-install.sh +41 -0
- package/scripts/npm-sdk.js +75 -0
- package/scripts/npm-shim.js +279 -0
- package/scripts/pack-npm.sh +123 -0
- package/scripts/prepare-release.mjs +270 -0
- package/scripts/rebrand.mjs +129 -0
- package/scripts/sync-ui-version.mjs +47 -0
- package/scripts/try-repo.sh +112 -0
|
@@ -0,0 +1,3484 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Name Matcher
|
|
4
|
+
*
|
|
5
|
+
* Handles symbol name matching for reference resolution.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.matchByFilePath = matchByFilePath;
|
|
42
|
+
exports.sameLanguageFamily = sameLanguageFamily;
|
|
43
|
+
exports.isKnownLanguageFamily = isKnownLanguageFamily;
|
|
44
|
+
exports.crossesKnownFamily = crossesKnownFamily;
|
|
45
|
+
exports.matchFunctionRef = matchFunctionRef;
|
|
46
|
+
exports.isVisibleAcrossFiles = isVisibleAcrossFiles;
|
|
47
|
+
exports.matchByExactName = matchByExactName;
|
|
48
|
+
exports.matchByQualifiedName = matchByQualifiedName;
|
|
49
|
+
exports.preferCallSiteFile = preferCallSiteFile;
|
|
50
|
+
exports.resolveObjectLiteralMember = resolveObjectLiteralMember;
|
|
51
|
+
exports.resolveMethodOnType = resolveMethodOnType;
|
|
52
|
+
exports.matchCppCallChain = matchCppCallChain;
|
|
53
|
+
exports.matchScopedCallChain = matchScopedCallChain;
|
|
54
|
+
exports.matchDottedCallChain = matchDottedCallChain;
|
|
55
|
+
exports.normalizeInferredTypeName = normalizeInferredTypeName;
|
|
56
|
+
exports.clearNameMatcherMemos = clearNameMatcherMemos;
|
|
57
|
+
exports.localReceiverTypePatterns = localReceiverTypePatterns;
|
|
58
|
+
exports.matchMethodCall = matchMethodCall;
|
|
59
|
+
exports.rustFieldTypeName = rustFieldTypeName;
|
|
60
|
+
exports.matchJsStoreBindingCall = matchJsStoreBindingCall;
|
|
61
|
+
exports.isUnresolvedJsMemberCall = isUnresolvedJsMemberCall;
|
|
62
|
+
exports.matchFuzzy = matchFuzzy;
|
|
63
|
+
exports.dumpNameMatcherProfile = dumpNameMatcherProfile;
|
|
64
|
+
exports.matchReference = matchReference;
|
|
65
|
+
const path = __importStar(require("path"));
|
|
66
|
+
const types_1 = require("./types");
|
|
67
|
+
const strip_comments_1 = require("./strip-comments");
|
|
68
|
+
const js_builtins_1 = require("./js-builtins");
|
|
69
|
+
/**
|
|
70
|
+
* Ceiling on how many same-named definitions a FUZZY name-match strategy will
|
|
71
|
+
* score. A name defined more times than this is "ubiquitous" — a method/symbol
|
|
72
|
+
* re-declared across a vendored theme or SDK (e.g. `init`/`update`/`render` on
|
|
73
|
+
* every widget of a committed Metronic theme — #999). No directory-proximity or
|
|
74
|
+
* receiver-word-overlap score can reliably pick THE one true target among
|
|
75
|
+
* thousands, so the fuzzy strategies (matchByExactName's findBestMatch, and
|
|
76
|
+
* matchMethodCall Strategy 3) decline above the ceiling instead of emitting a
|
|
77
|
+
* low-confidence, almost-certainly-wrong edge. This also caps their per-ref cost
|
|
78
|
+
* at O(ceiling): without it, K same-named refs each scored K candidates — the
|
|
79
|
+
* O(K²) blow-up that pinned a core for 15-28 min at "Resolving refs … 94%" on a
|
|
80
|
+
* repo vendoring a large JS/TS theme (#999). The PRECISE strategies are
|
|
81
|
+
* unaffected: qualified-name, import-based, and class-name (Strategy 1/2)
|
|
82
|
+
* resolution all still run and resolve a ubiquitous name when the context names
|
|
83
|
+
* its exact target. Real repos top out near ~40 same-named methods, so a normal
|
|
84
|
+
* codebase never reaches this; only bulk-vendored code does. Tune via
|
|
85
|
+
* `CODEGRAPH_AMBIGUOUS_NAME_CEILING`.
|
|
86
|
+
*/
|
|
87
|
+
const DEFAULT_AMBIGUOUS_NAME_CEILING = 500;
|
|
88
|
+
function resolveAmbiguousNameCeiling() {
|
|
89
|
+
const raw = process.env.CODEGRAPH_AMBIGUOUS_NAME_CEILING;
|
|
90
|
+
if (!raw)
|
|
91
|
+
return DEFAULT_AMBIGUOUS_NAME_CEILING;
|
|
92
|
+
const parsed = Number.parseInt(raw, 10);
|
|
93
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_AMBIGUOUS_NAME_CEILING;
|
|
94
|
+
}
|
|
95
|
+
const AMBIGUOUS_NAME_CEILING = resolveAmbiguousNameCeiling();
|
|
96
|
+
/**
|
|
97
|
+
* Try to resolve a path-like reference (e.g., "snippets/drawer-menu.liquid")
|
|
98
|
+
* by matching the filename against file nodes.
|
|
99
|
+
*/
|
|
100
|
+
function matchByFilePath(ref, context) {
|
|
101
|
+
// Path-like (`a/b.liquid`) OR a bare filename ending in a short extension
|
|
102
|
+
// (`Foo.h` — an Objective-C `#import "Foo.h"`, resolved to the header by
|
|
103
|
+
// basename). A bare ref WITHOUT an extension is a symbol name, not a file, so
|
|
104
|
+
// leave it to the symbol-matching strategies.
|
|
105
|
+
if (!ref.referenceName.includes('/') && !/\.[A-Za-z][A-Za-z0-9]{0,3}$/.test(ref.referenceName)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
// Extract the filename from the path
|
|
109
|
+
const fileName = ref.referenceName.split('/').pop();
|
|
110
|
+
if (!fileName)
|
|
111
|
+
return null;
|
|
112
|
+
// Search for file nodes with this name
|
|
113
|
+
const candidates = context.getNodesByName(fileName);
|
|
114
|
+
const fileNodes = candidates.filter(n => n.kind === 'file');
|
|
115
|
+
if (fileNodes.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
// Prefer exact path match on qualified_name
|
|
118
|
+
const exactMatch = fileNodes.find(n => n.qualifiedName === ref.referenceName || n.filePath === ref.referenceName);
|
|
119
|
+
if (exactMatch) {
|
|
120
|
+
return {
|
|
121
|
+
original: ref,
|
|
122
|
+
targetNodeId: exactMatch.id,
|
|
123
|
+
confidence: 0.95,
|
|
124
|
+
resolvedBy: 'file-path',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Fall back to suffix match (e.g., ref="snippets/foo.liquid" matches
|
|
128
|
+
// "src/snippets/foo.liquid"). When several files share the basename — a
|
|
129
|
+
// `#include "RNCAsyncStorage.h"` with a same-named header on another platform
|
|
130
|
+
// (windows/code/ vs apple/) — prefer the one in the includer's own directory,
|
|
131
|
+
// then by directory proximity / same language family. A C/C++ include (and any
|
|
132
|
+
// bare-filename import) resolves relative to the including file, not to an
|
|
133
|
+
// arbitrary same-named header elsewhere in the tree.
|
|
134
|
+
const suffixMatches = fileNodes.filter(n => n.qualifiedName.endsWith(ref.referenceName) || n.filePath.endsWith(ref.referenceName));
|
|
135
|
+
if (suffixMatches.length > 0) {
|
|
136
|
+
return {
|
|
137
|
+
original: ref,
|
|
138
|
+
targetNodeId: pickClosestFileNode(suffixMatches, ref).id,
|
|
139
|
+
confidence: 0.85,
|
|
140
|
+
resolvedBy: 'file-path',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// If only one file node with this name, use it with lower confidence
|
|
144
|
+
if (fileNodes.length === 1) {
|
|
145
|
+
return {
|
|
146
|
+
original: ref,
|
|
147
|
+
targetNodeId: fileNodes[0].id,
|
|
148
|
+
confidence: 0.7,
|
|
149
|
+
resolvedBy: 'file-path',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Among several file nodes that all match a bare include/import by basename,
|
|
156
|
+
* pick the one closest to the referencing file: same directory first, then by
|
|
157
|
+
* directory-tree proximity, with the same language family as a tiebreak. A
|
|
158
|
+
* C/C++ `#include "X.h"` (and any bare-filename import) resolves relative to the
|
|
159
|
+
* including file — not to an arbitrary same-named header on another platform.
|
|
160
|
+
*/
|
|
161
|
+
function pickClosestFileNode(candidates, ref) {
|
|
162
|
+
const dirOf = (p) => {
|
|
163
|
+
const i = p.lastIndexOf('/');
|
|
164
|
+
return i >= 0 ? p.slice(0, i) : '';
|
|
165
|
+
};
|
|
166
|
+
const refDir = dirOf(ref.filePath);
|
|
167
|
+
const sameDir = candidates.filter((c) => dirOf(c.filePath) === refDir);
|
|
168
|
+
const pool = sameDir.length > 0 ? sameDir : candidates;
|
|
169
|
+
let best = pool[0];
|
|
170
|
+
let bestScore = -Infinity;
|
|
171
|
+
for (const c of pool) {
|
|
172
|
+
const score = computePathProximity(ref.filePath, c.filePath) +
|
|
173
|
+
(sameLanguageFamily(c.language, ref.language) ? 5 : 0);
|
|
174
|
+
if (score > bestScore) {
|
|
175
|
+
bestScore = score;
|
|
176
|
+
best = c;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return best;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Language families that share a type system / runtime, so a same-language-only
|
|
183
|
+
* reference may still resolve across them (a Kotlin `Foo.BAR` can name a Java
|
|
184
|
+
* `Foo`). Anything not listed forms its own singleton family.
|
|
185
|
+
*/
|
|
186
|
+
const LANGUAGE_FAMILY = {
|
|
187
|
+
java: 'jvm', kotlin: 'jvm', scala: 'jvm',
|
|
188
|
+
swift: 'apple', objc: 'apple',
|
|
189
|
+
// ArkTS is a TS superset — every HarmonyOS project mixes `.ets` UI with
|
|
190
|
+
// `.ts` logic modules, so refs must cross freely between them.
|
|
191
|
+
typescript: 'web', tsx: 'web', javascript: 'web', jsx: 'web', arkts: 'web',
|
|
192
|
+
c: 'c', cpp: 'c',
|
|
193
|
+
// Razor/Blazor markup names C# types — same family so `@model Foo` /
|
|
194
|
+
// `<MyComponent/>` resolve to their `.cs` class through the cross-family gate.
|
|
195
|
+
csharp: 'dotnet', razor: 'dotnet',
|
|
196
|
+
};
|
|
197
|
+
function sameLanguageFamily(a, b) {
|
|
198
|
+
if (a === b)
|
|
199
|
+
return true;
|
|
200
|
+
const fa = LANGUAGE_FAMILY[a];
|
|
201
|
+
return fa !== undefined && fa === LANGUAGE_FAMILY[b];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* True when `lang` belongs to a known multi-language family (jvm/apple/web/c).
|
|
205
|
+
* Languages not listed (php, python, go, ruby, rust, dart, …) and config
|
|
206
|
+
* formats (yaml/xml/blade) form their own singleton families and return
|
|
207
|
+
* `false` — used to leave config↔code framework bridges (whose config side is
|
|
208
|
+
* never a known programming-language family) out of the cross-family gate.
|
|
209
|
+
*/
|
|
210
|
+
function isKnownLanguageFamily(lang) {
|
|
211
|
+
return LANGUAGE_FAMILY[lang] !== undefined;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* True when `a` and `b` are two DIFFERENT *known* language families — the
|
|
215
|
+
* signature of a coincidental cross-language name collision (a TS `import
|
|
216
|
+
* React` matching a Swift `import React`, a C++ `#include "X.h"` matching a
|
|
217
|
+
* same-named ObjC header on another platform). The both-*known* test is
|
|
218
|
+
* deliberately weaker than {@link sameLanguageFamily}'s negation: a
|
|
219
|
+
* single-file-component language that carries its own tag (`vue`/`svelte`)
|
|
220
|
+
* importing a `.ts` module, or any singleton-family language (php/go/ruby/…),
|
|
221
|
+
* returns `false` here and is left alone.
|
|
222
|
+
*/
|
|
223
|
+
function crossesKnownFamily(a, b) {
|
|
224
|
+
return isKnownLanguageFamily(a) && isKnownLanguageFamily(b) && !sameLanguageFamily(a, b);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Drop cross-language candidates from a name lookup. Two regimes:
|
|
228
|
+
* - `references` (type-usage): a type named in language X resolves to a
|
|
229
|
+
* SAME-family type, never a coincidentally same-named symbol in another
|
|
230
|
+
* language (the Android `BatteryManager` system class vs a JS one). Strict
|
|
231
|
+
* same-family filter — cross-language communication is `calls`, not refs.
|
|
232
|
+
* - `imports` (import binding): an `import`/`#include` never crosses two
|
|
233
|
+
* KNOWN families (TS `import React` ↮ Swift `import React`). Weaker
|
|
234
|
+
* both-known filter so `.vue`/`.svelte` (own tag) importing `.ts` survives.
|
|
235
|
+
*/
|
|
236
|
+
function applyLanguageGate(candidates, ref) {
|
|
237
|
+
if (ref.referenceKind === 'references' || ref.referenceKind === 'function_ref') {
|
|
238
|
+
return candidates.filter((c) => sameLanguageFamily(c.language, ref.language));
|
|
239
|
+
}
|
|
240
|
+
if (ref.referenceKind === 'imports') {
|
|
241
|
+
return candidates.filter((c) => !crossesKnownFamily(c.language, ref.language));
|
|
242
|
+
}
|
|
243
|
+
return candidates;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Resolve a function-as-value reference (#756) — a function name used as a
|
|
247
|
+
* callback/function-pointer value (`register(handler)`, `o->cb = handler`,
|
|
248
|
+
* `{ .cb = handler }`, `signal(SIGINT, handler)`). The ONLY strategy allowed
|
|
249
|
+
* for `function_ref` refs: exact name, function/method targets only, same
|
|
250
|
+
* language family, same-file first, and cross-file only when the match is
|
|
251
|
+
* UNIQUE. No fuzzy fallback, no qualified-name walking — a wrong callback
|
|
252
|
+
* edge is worse than none.
|
|
253
|
+
*/
|
|
254
|
+
function matchFunctionRef(ref, context) {
|
|
255
|
+
// `this.<member>` refs are resolved ONLY by the class-scoped resolver in
|
|
256
|
+
// resolveOne (resolveThisMemberFnRef) — never by name matching here.
|
|
257
|
+
if (ref.referenceName.startsWith('this.'))
|
|
258
|
+
return null;
|
|
259
|
+
// In JS/TS/Python a bare identifier can never be a method value (methods
|
|
260
|
+
// are only reachable through a receiver — `this.m` / `self.m` /
|
|
261
|
+
// `Cls.m`), so bare fn-refs match FUNCTIONS only. This also sidesteps the
|
|
262
|
+
// pre-existing TS quirk of class fields extracting as method-kind nodes,
|
|
263
|
+
// which otherwise soaked up local names passed as arguments (excalidraw
|
|
264
|
+
// A/B finding; same pattern in vendored docopt.py). Python's `self.m`
|
|
265
|
+
// form keeps method targets via its own capture shape. C++ likewise: a
|
|
266
|
+
// bare identifier can only be a FREE function (member values need
|
|
267
|
+
// `&Cls::method`). PHP string callables name global FUNCTIONS (methods
|
|
268
|
+
// need the `[$obj, 'm']` array form, which carries its own shape). Other
|
|
269
|
+
// languages keep method targets: C# method groups, Swift/Dart
|
|
270
|
+
// implicit-self, Java/Kotlin method references.
|
|
271
|
+
const bareFnOnly = ref.language === 'typescript' || ref.language === 'tsx' ||
|
|
272
|
+
ref.language === 'javascript' || ref.language === 'jsx' ||
|
|
273
|
+
ref.language === 'arkts' ||
|
|
274
|
+
ref.language === 'cpp' || ref.language === 'python' ||
|
|
275
|
+
ref.language === 'php';
|
|
276
|
+
// Python additionally accepts CLASS targets for bare identifiers (#1478):
|
|
277
|
+
// class-as-value is a core Python idiom (`return SomeSerializer`,
|
|
278
|
+
// `Meta.model = Org`, registry dicts, `admin.site.register(Model, Admin)`)
|
|
279
|
+
// and, unlike TS, Python has no type-annotation recovery path. The
|
|
280
|
+
// false-positive mechanism behind the function-only rule was lowercase
|
|
281
|
+
// locals colliding with same-named METHODS (docopt.py) — a candidate must
|
|
282
|
+
// be an exact-name CLASS node here, and the extraction gate (same-file
|
|
283
|
+
// class ∪ imports) plus unique-or-drop still apply. Methods stay excluded.
|
|
284
|
+
const bareClassOk = ref.language === 'python';
|
|
285
|
+
// Qualified member-pointer (`&Widget::on_click` → "Widget::on_click"):
|
|
286
|
+
// resolve the member ON THAT SCOPE — exempt from bareFnOnly (the `&Cls::m`
|
|
287
|
+
// shape is an explicit member reference). Unique-or-drop like everything else.
|
|
288
|
+
if (ref.referenceName.includes('::')) {
|
|
289
|
+
const memberName = ref.referenceName.slice(ref.referenceName.lastIndexOf('::') + 2);
|
|
290
|
+
const scoped = context
|
|
291
|
+
.getNodesByName(memberName)
|
|
292
|
+
.filter((n) => (n.kind === 'function' || n.kind === 'method') &&
|
|
293
|
+
sameLanguageFamily(n.language, ref.language) &&
|
|
294
|
+
n.id !== ref.fromNodeId &&
|
|
295
|
+
(n.qualifiedName === ref.referenceName ||
|
|
296
|
+
n.qualifiedName.endsWith(`::${ref.referenceName}`)));
|
|
297
|
+
if (scoped.length === 0)
|
|
298
|
+
return null;
|
|
299
|
+
const sameFileScoped = scoped.filter((n) => n.filePath === ref.filePath);
|
|
300
|
+
const pool = sameFileScoped.length > 0 ? sameFileScoped : scoped;
|
|
301
|
+
if (sameFileScoped.length === 0 && scoped.length > 1)
|
|
302
|
+
return null;
|
|
303
|
+
const target = pool.reduce((a, b) => (a.startLine <= b.startLine ? a : b));
|
|
304
|
+
return {
|
|
305
|
+
original: ref,
|
|
306
|
+
targetNodeId: target.id,
|
|
307
|
+
confidence: 0.9,
|
|
308
|
+
resolvedBy: 'function-ref',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
let candidates = context
|
|
312
|
+
.getNodesByName(ref.referenceName)
|
|
313
|
+
.filter((n) => (n.kind === 'function' ||
|
|
314
|
+
(!bareFnOnly && n.kind === 'method') ||
|
|
315
|
+
(bareClassOk && n.kind === 'class')) &&
|
|
316
|
+
sameLanguageFamily(n.language, ref.language) &&
|
|
317
|
+
n.id !== ref.fromNodeId // a function registering itself is not a dependency edge
|
|
318
|
+
);
|
|
319
|
+
if (candidates.length === 0)
|
|
320
|
+
return null;
|
|
321
|
+
// Swift implicit-self: a bare identifier can name a METHOD only of the
|
|
322
|
+
// ENCLOSING type (`Button(action: handleTap)` written inside that type) —
|
|
323
|
+
// a same-named method on any OTHER class is a parameter collision
|
|
324
|
+
// (Alamofire: a `request` parameter resolving to EventMonitor::request).
|
|
325
|
+
// Scope method candidates to the from-symbol's type; top-level code has no
|
|
326
|
+
// implicit self, so method targets are excluded there entirely. Free
|
|
327
|
+
// functions are unaffected.
|
|
328
|
+
if (ref.language === 'swift' && candidates.some((n) => n.kind === 'method')) {
|
|
329
|
+
const fromNode = context.getNodeById?.(ref.fromNodeId);
|
|
330
|
+
const sep = fromNode ? fromNode.qualifiedName.lastIndexOf('::') : -1;
|
|
331
|
+
const classPrefix = fromNode && sep > 0 ? fromNode.qualifiedName.slice(0, sep) : null;
|
|
332
|
+
candidates = candidates.filter((n) => {
|
|
333
|
+
if (n.kind !== 'method')
|
|
334
|
+
return true;
|
|
335
|
+
if (!classPrefix)
|
|
336
|
+
return false;
|
|
337
|
+
const mSep = n.qualifiedName.lastIndexOf('::');
|
|
338
|
+
if (mSep <= 0)
|
|
339
|
+
return false;
|
|
340
|
+
const methodPrefix = n.qualifiedName.slice(0, mSep);
|
|
341
|
+
// Accept exact-scope matches plus suffix relationships either way, so
|
|
342
|
+
// extension-declared members (`Holder::m`) still match a nested
|
|
343
|
+
// from-scope (`Module::Holder::wire`) and vice versa.
|
|
344
|
+
return (methodPrefix === classPrefix ||
|
|
345
|
+
methodPrefix.endsWith(`::${classPrefix}`) ||
|
|
346
|
+
classPrefix.endsWith(`::${methodPrefix}`));
|
|
347
|
+
});
|
|
348
|
+
if (candidates.length === 0)
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
// Same-file definition wins — the extraction gate guarantees most survivors
|
|
352
|
+
// have one, and it's the dominant C pattern (static callback registered in
|
|
353
|
+
// a same-file ops struct).
|
|
354
|
+
const sameFile = candidates.filter((n) => n.filePath === ref.filePath);
|
|
355
|
+
if (sameFile.length > 0) {
|
|
356
|
+
// Swift: several same-named METHODS in one file is an API overload family
|
|
357
|
+
// (`Session.request(...)` × N), and a bare identifier hitting it is almost
|
|
358
|
+
// always a same-named parameter, not a method value (Alamofire A/B
|
|
359
|
+
// finding) — refuse rather than guess. A single method (SwiftUI's
|
|
360
|
+
// `action: handleTap`) still resolves.
|
|
361
|
+
if (ref.language === 'swift' &&
|
|
362
|
+
sameFile.length > 1 &&
|
|
363
|
+
sameFile.every((n) => n.kind === 'method')) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
// Same-name overloads in one file are the same conceptual symbol; pick
|
|
367
|
+
// the first by position for determinism.
|
|
368
|
+
const target = sameFile.reduce((a, b) => (a.startLine <= b.startLine ? a : b));
|
|
369
|
+
return {
|
|
370
|
+
original: ref,
|
|
371
|
+
targetNodeId: target.id,
|
|
372
|
+
confidence: sameFile.length === 1 ? 0.95 : 0.9,
|
|
373
|
+
resolvedBy: 'function-ref',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// Cross-file (imported names the import resolver didn't already claim):
|
|
377
|
+
// only an unambiguous match resolves.
|
|
378
|
+
if (candidates.length === 1) {
|
|
379
|
+
return {
|
|
380
|
+
original: ref,
|
|
381
|
+
targetNodeId: candidates[0].id,
|
|
382
|
+
confidence: 0.8,
|
|
383
|
+
resolvedBy: 'function-ref',
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
/** Languages with no nested named functions: nesting in the graph is never a scope. */
|
|
389
|
+
const NO_NESTED_FUNCTIONS = new Set(['c', 'cpp']);
|
|
390
|
+
/**
|
|
391
|
+
* A function nested inside another FUNCTION is only callable from within its
|
|
392
|
+
* container — Python, JS/TS, and every closure language scope it lexically.
|
|
393
|
+
* Resolving a bare name from elsewhere to a nested local fabricates an edge
|
|
394
|
+
* scope already rules out: `join(...)` in one function must never bind to a
|
|
395
|
+
* `join` defined inside a DIFFERENT function (#1230). A candidate whose
|
|
396
|
+
* qualifiedName parent is a same-file function/method is kept only when the
|
|
397
|
+
* ref originates inside that parent's line range. Class members are
|
|
398
|
+
* unaffected (their parent resolves to a class-like node), as are top-level
|
|
399
|
+
* symbols and C++ namespace-prefixed names (the prefix has no node).
|
|
400
|
+
*/
|
|
401
|
+
function isLexicallyReachable(candidate, ref, context) {
|
|
402
|
+
if (candidate.kind !== 'function')
|
|
403
|
+
return true;
|
|
404
|
+
// C and C++ have no nested named functions, so a function the graph shows
|
|
405
|
+
// inside another is an extraction artifact, not a scope: tree-sitter-c
|
|
406
|
+
// cannot parse a macro call whose arguments are designated initializers
|
|
407
|
+
// (betaflight's `RESET_CONFIG(pidProfile_t, pidProfile, .pid = {…})`), and
|
|
408
|
+
// its error recovery runs the enclosing function_definition to the end of
|
|
409
|
+
// the file, nesting every function after it. Trusting that nesting rejected
|
|
410
|
+
// 117 real calls into pid.c on that tree; the functions are reachable.
|
|
411
|
+
if (NO_NESTED_FUNCTIONS.has(candidate.language))
|
|
412
|
+
return true;
|
|
413
|
+
const qn = candidate.qualifiedName;
|
|
414
|
+
if (!qn || !qn.includes('::'))
|
|
415
|
+
return true;
|
|
416
|
+
const parentQn = qn.slice(0, qn.lastIndexOf('::'));
|
|
417
|
+
const containers = context
|
|
418
|
+
.getNodesByQualifiedName(parentQn)
|
|
419
|
+
.filter((p) => p.filePath === candidate.filePath &&
|
|
420
|
+
(p.kind === 'function' || p.kind === 'method') &&
|
|
421
|
+
p.startLine <= candidate.startLine &&
|
|
422
|
+
p.endLine >= candidate.endLine);
|
|
423
|
+
if (containers.length === 0)
|
|
424
|
+
return true;
|
|
425
|
+
return (ref.filePath === candidate.filePath &&
|
|
426
|
+
containers.some((p) => ref.line >= p.startLine && ref.line <= p.endLine));
|
|
427
|
+
}
|
|
428
|
+
/** Languages whose module boundary is `import`/`export` (or CommonJS). */
|
|
429
|
+
const ESM_FAMILY = new Set(['typescript', 'tsx', 'javascript', 'jsx', 'arkts']);
|
|
430
|
+
/**
|
|
431
|
+
* A line-initial `import` statement — the marker that a JS/TS file is a MODULE
|
|
432
|
+
* rather than a classic script. Line-anchored and followed by a name, brace,
|
|
433
|
+
* star or quote, so a dynamic `import(` and the word inside a comment or string
|
|
434
|
+
* do not match.
|
|
435
|
+
*/
|
|
436
|
+
const HAS_IMPORT_STATEMENT = /^[ \t]*import[\s{*'"]/m;
|
|
437
|
+
/**
|
|
438
|
+
* Anything the file could offer another file, in every form the extractor's own
|
|
439
|
+
* `isExported` flag misses. `^export` covers the declaration and later forms
|
|
440
|
+
* (`export const`, `export { x }`, `export default x`, `export *`); the
|
|
441
|
+
* CommonJS shapes cover files that never use ESM syntax at all, in both the dot
|
|
442
|
+
* and the bracket form; and `declare global` contributes names to every file
|
|
443
|
+
* whether or not the module exports anything of its own. Kept as a source test
|
|
444
|
+
* rather than a node scan precisely because `isExported` is set only from an
|
|
445
|
+
* `export_statement` ancestor, so `const x = …; export { x }` and
|
|
446
|
+
* `module.exports = { x }` both read as unexported on the node.
|
|
447
|
+
*/
|
|
448
|
+
const HAS_ESM_EXPORT = /^[ \t]*export[\s{*]|^[ \t]*declare\s+global\b/m;
|
|
449
|
+
const HAS_CJS_EXPORT = /\bmodule\.exports\b|\bexports\s*[.[]/;
|
|
450
|
+
/**
|
|
451
|
+
* Per-context memo of "this file is a module that exports nothing", asked once
|
|
452
|
+
* per candidate FILE rather than once per reference. Derived from file source,
|
|
453
|
+
* so it drops with the context's file caches — clearNameMatcherMemos deletes it
|
|
454
|
+
* alongside INFER_SCAN_STATES.
|
|
455
|
+
*/
|
|
456
|
+
const SEALED_MODULES = new WeakMap();
|
|
457
|
+
/**
|
|
458
|
+
* Whether `filePath` is a JS/TS module that exports NOTHING — an import
|
|
459
|
+
* statement present, no export of any form. No reference from another file can
|
|
460
|
+
* reach any binding in such a file, so every one of its symbols is a false
|
|
461
|
+
* candidate for a cross-file name match.
|
|
462
|
+
*
|
|
463
|
+
* This is the general case behind a package name capturing a same-named local:
|
|
464
|
+
* on `vitejs/vite`, 157 cross-file `imports` refs — every `import { defineConfig
|
|
465
|
+
* } from 'vite'` in the playground and the create-vite templates — resolved onto
|
|
466
|
+
* `playground/ssr-html/test-stacktrace.js::vite`, which is `const vite = await
|
|
467
|
+
* createServer(…)` at module scope in a file with zero exports. The existing
|
|
468
|
+
* guards cannot see it: `isLexicallyReachable` returns early for any candidate
|
|
469
|
+
* that is not a `function`, and the bare-import guard correctly declines because
|
|
470
|
+
* `vite` IS a workspace member, so the specifier really is project-local. What
|
|
471
|
+
* is wrong is only which node the name lands on.
|
|
472
|
+
*
|
|
473
|
+
* Deliberately narrow on three axes, because each is a class this would
|
|
474
|
+
* otherwise resolve wrongly in the opposite direction:
|
|
475
|
+
*
|
|
476
|
+
* - **A classic script is exempt.** Requiring an `import` statement means a
|
|
477
|
+
* non-module `.js` file — concatenated globals, a browser `<script>` — keeps
|
|
478
|
+
* its cross-file matches, where a top-level binding genuinely is reachable.
|
|
479
|
+
* - **CommonJS is exempt.** `module.exports` and `exports.x` are matched as
|
|
480
|
+
* exports, so a CJS file is never sealed.
|
|
481
|
+
* - **Other languages are exempt.** Go, Python, Java and the rest have no
|
|
482
|
+
* equivalent boundary, and several extractors hardcode `isExported`.
|
|
483
|
+
*/
|
|
484
|
+
function isSealedModule(filePath, context) {
|
|
485
|
+
let memo = SEALED_MODULES.get(context);
|
|
486
|
+
if (!memo) {
|
|
487
|
+
memo = new Map();
|
|
488
|
+
SEALED_MODULES.set(context, memo);
|
|
489
|
+
}
|
|
490
|
+
const hit = memo.get(filePath);
|
|
491
|
+
if (hit !== undefined)
|
|
492
|
+
return hit;
|
|
493
|
+
const source = context.readFile(filePath);
|
|
494
|
+
const code = source === null ? '' : (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(source, 'typescript'));
|
|
495
|
+
// CommonJS assignments can execute inside template interpolations, which the
|
|
496
|
+
// masker blanks. Keep the conservative raw-source exemption for those forms.
|
|
497
|
+
const sealed = source !== null && HAS_IMPORT_STATEMENT.test(code) &&
|
|
498
|
+
!context.getNodesInFile(filePath).some((n) => n.isExported) &&
|
|
499
|
+
!HAS_ESM_EXPORT.test(code) && !HAS_CJS_EXPORT.test(source);
|
|
500
|
+
memo.set(filePath, sealed);
|
|
501
|
+
return sealed;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Whether `candidate` can be named by a reference in `ref`'s file at all.
|
|
505
|
+
* Both name-based strategies validate their chosen candidate. Removing an
|
|
506
|
+
* unreachable candidate before ranking can promote an unrelated runner-up;
|
|
507
|
+
* rejecting the chosen target must leave the reference unresolved instead.
|
|
508
|
+
*/
|
|
509
|
+
function isCrossFileReachable(candidate, ref, context) {
|
|
510
|
+
if (ref.language !== 'markdown' && candidate.language === 'markdown')
|
|
511
|
+
return false;
|
|
512
|
+
if (ref.referenceKind === 'calls' && ESM_FAMILY.has(candidate.language) &&
|
|
513
|
+
(candidate.kind === 'constant' || candidate.kind === 'variable') &&
|
|
514
|
+
/^=\s*require\s*\(\s*(['"])[^'"]+\.json\1\s*\)\s*;?\s*$/.test(candidate.signature ?? ''))
|
|
515
|
+
return false;
|
|
516
|
+
return (candidate.filePath === ref.filePath ||
|
|
517
|
+
!ESM_FAMILY.has(candidate.language) ||
|
|
518
|
+
!isSealedModule(candidate.filePath, context));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Languages in which `visibility: 'private'` on a definition means no other
|
|
522
|
+
* FILE can name it: a Kotlin `private fun` is file- or class-local, and the
|
|
523
|
+
* same holds for Java, C#, Swift, Scala, Dart and PHP members.
|
|
524
|
+
*/
|
|
525
|
+
const PRIVATE_IS_FILE_LOCAL = new Set(['kotlin', 'java', 'csharp', 'swift', 'scala', 'dart', 'php']);
|
|
526
|
+
/** Per-context memo: node id → "this C/C++ function is declared `static`". */
|
|
527
|
+
const C_STATIC_MEMO = new WeakMap();
|
|
528
|
+
/**
|
|
529
|
+
* A C/C++ file that IS a translation unit. A `static` defined here is local
|
|
530
|
+
* to it. A `static` (typically `static inline`) in a header is a different
|
|
531
|
+
* thing: the header is textually included, so the function exists in every
|
|
532
|
+
* unit that includes it and is callable from each — MAVLink's generated
|
|
533
|
+
* `mavlink_msg_*.h` are nothing but such functions, 4,306 real calls on one
|
|
534
|
+
* betaflight tree.
|
|
535
|
+
*/
|
|
536
|
+
const C_SOURCE_EXT = /\.(c|cc|cpp|cxx|c\+\+|m|mm)$/i;
|
|
537
|
+
/**
|
|
538
|
+
* Whether a C/C++ function definition carries the `static` storage class —
|
|
539
|
+
* read from its first source line(s), since the extractor records no storage
|
|
540
|
+
* class and the kernel arm would need the same field. `static` on the line
|
|
541
|
+
* above the name (`static void\nfoo(void)`) is the common alternative layout.
|
|
542
|
+
*/
|
|
543
|
+
function isStaticCFunction(candidate, context) {
|
|
544
|
+
let memo = C_STATIC_MEMO.get(context);
|
|
545
|
+
if (!memo) {
|
|
546
|
+
memo = new Map();
|
|
547
|
+
C_STATIC_MEMO.set(context, memo);
|
|
548
|
+
}
|
|
549
|
+
const hit = memo.get(candidate.id);
|
|
550
|
+
if (hit !== undefined)
|
|
551
|
+
return hit;
|
|
552
|
+
const lines = context.getFileLines?.(candidate.filePath) ?? context.readFile(candidate.filePath)?.split('\n') ?? [];
|
|
553
|
+
const head = [lines[candidate.startLine - 2] ?? '', lines[candidate.startLine - 1] ?? ''].join('\n');
|
|
554
|
+
const isStatic = /(^|[\s;}])static\s/.test(head);
|
|
555
|
+
memo.set(candidate.id, isStatic);
|
|
556
|
+
return isStatic;
|
|
557
|
+
}
|
|
558
|
+
/** Per-context memo: node id → "this Rust method implements a trait". */
|
|
559
|
+
const RUST_TRAIT_IMPL_MEMO = new WeakMap();
|
|
560
|
+
/**
|
|
561
|
+
* Whether a Rust method sits in an `impl Trait for Type` block. Such a method
|
|
562
|
+
* carries no `pub` — the trait decides its visibility — so the extractor
|
|
563
|
+
* records it as private; it is reachable wherever the trait is. Read from the
|
|
564
|
+
* nearest enclosing `impl` header above the method, memoised per node.
|
|
565
|
+
*/
|
|
566
|
+
function isRustTraitImplMethod(candidate, context) {
|
|
567
|
+
if (candidate.kind !== 'method')
|
|
568
|
+
return false;
|
|
569
|
+
let memo = RUST_TRAIT_IMPL_MEMO.get(context);
|
|
570
|
+
if (!memo) {
|
|
571
|
+
memo = new Map();
|
|
572
|
+
RUST_TRAIT_IMPL_MEMO.set(context, memo);
|
|
573
|
+
}
|
|
574
|
+
const hit = memo.get(candidate.id);
|
|
575
|
+
if (hit !== undefined)
|
|
576
|
+
return hit;
|
|
577
|
+
const lines = context.getFileLines?.(candidate.filePath) ?? context.readFile(candidate.filePath)?.split('\n') ?? [];
|
|
578
|
+
let isTrait = false;
|
|
579
|
+
for (let i = candidate.startLine - 2; i >= 0; i--) {
|
|
580
|
+
const line = lines[i] ?? '';
|
|
581
|
+
if (/^\s*(pub(\([^)]*\))?\s+)?(unsafe\s+)?impl\b/.test(line)) {
|
|
582
|
+
isTrait = /\sfor\s/.test(line.replace(/\/\/.*$/, ''));
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
// A top-level item above the method means it was not inside an impl.
|
|
586
|
+
if (/^(pub(\([^)]*\))?\s+)?(fn|struct|enum|mod|trait|const|static|type)\b/.test(line))
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
memo.set(candidate.id, isTrait);
|
|
590
|
+
return isTrait;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* The directory a Rust file's private items are visible from: the file's own
|
|
594
|
+
* module subtree. `src/net.rs` and `src/net/mod.rs` own `src/net/`; a crate
|
|
595
|
+
* root (`lib.rs` / `main.rs`) owns its directory. A child module reaches its
|
|
596
|
+
* ancestors' private items (`super::`), a sibling or another crate never does.
|
|
597
|
+
*/
|
|
598
|
+
function rustModuleDir(filePath) {
|
|
599
|
+
const base = path.posix.basename(filePath);
|
|
600
|
+
const dir = path.posix.dirname(filePath);
|
|
601
|
+
if (base === 'mod.rs' || base === 'lib.rs' || base === 'main.rs')
|
|
602
|
+
return dir;
|
|
603
|
+
return path.posix.join(dir, base.replace(/\.rs$/, ''));
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Whether `candidate` can be NAMED from a reference in `ref`'s file at all,
|
|
607
|
+
* given what its language says about the definition's visibility. A
|
|
608
|
+
* definition the language makes file-local is not a candidate for a
|
|
609
|
+
* cross-file name match, however well the names agree:
|
|
610
|
+
*
|
|
611
|
+
* - **C / C++**: a `static` function defined in a SOURCE file is local to
|
|
612
|
+
* that translation unit; one in a header is part of every unit that
|
|
613
|
+
* includes it and stays visible. On a 2,109-file betaflight tree 145
|
|
614
|
+
* cross-file calls resolved onto a `static` in another `.c` (#1730) —
|
|
615
|
+
* `usbd_get_descriptor` onto the `static get_device_descriptor` of
|
|
616
|
+
* whichever USB class file ranked first.
|
|
617
|
+
* - **Kotlin, Java, C#, Swift, Scala, Dart, PHP**: `private` is class- or
|
|
618
|
+
* file-local. An Android `editor.apply()` resolved onto an unrelated class's
|
|
619
|
+
* `private fun apply`.
|
|
620
|
+
* - **Go**: an unexported (lowercase) identifier is package-local, and a
|
|
621
|
+
* package is a directory. Judged by the name's case: the extractor's
|
|
622
|
+
* `isExported` is unset for every Go method.
|
|
623
|
+
* - **Rust**: a non-`pub` item is visible to its module and that module's
|
|
624
|
+
* descendants, never to a sibling module or another crate — `.count()` on
|
|
625
|
+
* an iterator resolved onto a `fn count` in a different crate. A method in
|
|
626
|
+
* an `impl Trait for Type` block has the trait's visibility, not `private`.
|
|
627
|
+
* - **JS / TS / ArkTS**: a binding in a module that exports nothing (an
|
|
628
|
+
* `import` present, no `export` / CommonJS / `declare global`) is sealed —
|
|
629
|
+
* the vite playground's `const vite = await createServer(…)` took 157
|
|
630
|
+
* `import { defineConfig } from 'vite'` edges (#1719). Classic scripts,
|
|
631
|
+
* CommonJS, later `export { … }`, and ambient globals stay visible.
|
|
632
|
+
*
|
|
633
|
+
* Same-file candidates are always visible. Applied by ReferenceResolver to
|
|
634
|
+
* the target the whole name-matching pipeline settled on, so a rejection ends
|
|
635
|
+
* the reference unresolved: declining inside matchByExactName instead let the
|
|
636
|
+
* ref fall through to matchFuzzy, which then committed to a same-language
|
|
637
|
+
* namesake the ranking had passed over — eight such edges on one tree, all
|
|
638
|
+
* onto a local `const fail = …` arrow the graph does not hold. matchFuzzy
|
|
639
|
+
* checks its own survivor as well, since nothing runs after it.
|
|
640
|
+
*/
|
|
641
|
+
function isVisibleAcrossFiles(candidate, ref, context) {
|
|
642
|
+
if (candidate.filePath === ref.filePath)
|
|
643
|
+
return true;
|
|
644
|
+
const lang = candidate.language;
|
|
645
|
+
if (lang === 'c' || lang === 'cpp') {
|
|
646
|
+
return (candidate.kind !== 'function' ||
|
|
647
|
+
!C_SOURCE_EXT.test(candidate.filePath) ||
|
|
648
|
+
!isStaticCFunction(candidate, context));
|
|
649
|
+
}
|
|
650
|
+
if (lang === 'go') {
|
|
651
|
+
// By the name's first letter, not the extractor's flag: the flag is unset
|
|
652
|
+
// for every Go method, exported or not.
|
|
653
|
+
return /^[A-Z]/.test(candidate.name) || path.posix.dirname(candidate.filePath) === path.posix.dirname(ref.filePath);
|
|
654
|
+
}
|
|
655
|
+
if (lang === 'rust') {
|
|
656
|
+
if (candidate.visibility !== 'private')
|
|
657
|
+
return true;
|
|
658
|
+
if (isRustTraitImplMethod(candidate, context))
|
|
659
|
+
return true;
|
|
660
|
+
const owner = rustModuleDir(candidate.filePath);
|
|
661
|
+
return ref.filePath.startsWith(owner + '/');
|
|
662
|
+
}
|
|
663
|
+
if (PRIVATE_IS_FILE_LOCAL.has(lang))
|
|
664
|
+
return candidate.visibility !== 'private';
|
|
665
|
+
// JS/TS/ArkTS sealed modules + markdown/JSON call-target guards (#1719).
|
|
666
|
+
// Same predicate matchByExactName / matchFuzzy apply to their survivors so a
|
|
667
|
+
// rejection here cannot fall through to a promoted runner-up.
|
|
668
|
+
return isCrossFileReachable(candidate, ref, context);
|
|
669
|
+
}
|
|
670
|
+
const JS_FAMILY = new Set(['typescript', 'tsx', 'javascript', 'jsx']);
|
|
671
|
+
/**
|
|
672
|
+
* Whether a JS/TS `calls` ref is a RECEIVER-LESS call — `serialize(x)`, not
|
|
673
|
+
* `this.serialize(x)` / `obj.serialize(x)`. The extractor emits `this.m()`
|
|
674
|
+
* and `super.m()` under the bare method name, so the receiver is read back
|
|
675
|
+
* from the call site's own line: the text at the ref's column is the call
|
|
676
|
+
* expression, and it starts with the name itself only when nothing precedes
|
|
677
|
+
* it. In JS/TS a bare call can never bind to a class method (methods need a
|
|
678
|
+
* receiver), so a `method` node is not a candidate for it (#1714) — the
|
|
679
|
+
* enclosing method itself least of all, which the same-file proximity term
|
|
680
|
+
* used to pick over the module-scope function the call actually means.
|
|
681
|
+
*/
|
|
682
|
+
function isBareJsCall(ref, context) {
|
|
683
|
+
if (ref.referenceKind !== 'calls' || !JS_FAMILY.has(ref.language))
|
|
684
|
+
return false;
|
|
685
|
+
if (ref.referenceName.includes('.'))
|
|
686
|
+
return false;
|
|
687
|
+
const line = context.getFileLines?.(ref.filePath)?.[ref.line - 1]
|
|
688
|
+
?? context.readFile(ref.filePath)?.split('\n')[ref.line - 1];
|
|
689
|
+
if (line === undefined)
|
|
690
|
+
return false;
|
|
691
|
+
const at = line.slice(ref.column);
|
|
692
|
+
const nameEsc = ref.referenceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
693
|
+
if (!new RegExp('^' + nameEsc + '\\s*[(<]').test(at))
|
|
694
|
+
return false;
|
|
695
|
+
// Nothing but whitespace, an operator or an opener may precede a bare call.
|
|
696
|
+
return !/[.\w$\]\)]\s*$/.test(line.slice(0, ref.column)) || /\b(?:return|await|yield|typeof|void|new|else|case|throw|in|of|instanceof)\s*$/.test(line.slice(0, ref.column));
|
|
697
|
+
}
|
|
698
|
+
/** Per-context memo: `file\0name` → "the file binds this name locally". */
|
|
699
|
+
const LOCAL_BINDING_MEMO = new WeakMap();
|
|
700
|
+
/**
|
|
701
|
+
* Whether a JS/TS file binds `name` itself — as a `const`/`let`/`var`/
|
|
702
|
+
* `function`/`class` declaration (destructuring included) or as a parameter
|
|
703
|
+
* of a function or arrow. Such a binding shadows every same-named symbol in
|
|
704
|
+
* other files, so a bare call to it has no cross-file candidate: the
|
|
705
|
+
* `resolve` of `new Promise((resolve, reject) => …)`, a spec's
|
|
706
|
+
* `const transform = await makeTransform()`, a factory's `const now =
|
|
707
|
+
* options.now || (() => new Date())`. None of these is a node the graph
|
|
708
|
+
* holds (a parameter, a const bound to a call result), so without this the
|
|
709
|
+
* matcher hands the call to whichever other file defines the name — and
|
|
710
|
+
* once methods stop being candidates for a bare call (#1714), the function
|
|
711
|
+
* that was out-ranked steps in. Read from source, memoised per file+name.
|
|
712
|
+
*/
|
|
713
|
+
function isLocallyBoundJsName(name, filePath, context) {
|
|
714
|
+
let memo = LOCAL_BINDING_MEMO.get(context);
|
|
715
|
+
if (!memo) {
|
|
716
|
+
memo = new Map();
|
|
717
|
+
LOCAL_BINDING_MEMO.set(context, memo);
|
|
718
|
+
}
|
|
719
|
+
const key = filePath + '\0' + name;
|
|
720
|
+
const hit = memo.get(key);
|
|
721
|
+
if (hit !== undefined)
|
|
722
|
+
return hit;
|
|
723
|
+
const source = context.readFile(filePath) ?? '';
|
|
724
|
+
const n = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
725
|
+
// `const { name } = require('./m')` / `= await import('./m')` binds an IMPORT,
|
|
726
|
+
// not a shadow: the symbol lives in the other file and the call means it.
|
|
727
|
+
const declRe = new RegExp('\\b(?:const|let|var)\\s+(?:' + n + '\\b|[{\\[][^;=]*?\\b' + n + '\\b[^;=]*?[}\\]])\\s*(?:=\\s*([^;\\n]*))?', 'g');
|
|
728
|
+
let bound = false;
|
|
729
|
+
for (const m of source.matchAll(declRe)) {
|
|
730
|
+
if (!/^\s*(?:await\s+)?(?:require|import)\s*\(/.test(m[1] ?? '')) {
|
|
731
|
+
bound = true;
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (!bound) {
|
|
736
|
+
bound =
|
|
737
|
+
new RegExp('\\b(?:function|class)\\s+' + n + '\\b').test(source) ||
|
|
738
|
+
// a parameter: every token before the name in the list is itself a
|
|
739
|
+
// parameter (identifier, optional type, optional default) — so a string
|
|
740
|
+
// argument containing the word cannot match.
|
|
741
|
+
new RegExp('\\(\\s*(?:(?:\\.\\.\\.)?[\\w$]+(?:\\s*\\??\\s*:\\s*[^,()]+)?(?:\\s*=\\s*[^,()]+)?\\s*,\\s*)*' +
|
|
742
|
+
n + '\\b(?:\\s*\\??\\s*:[^,()]*)?(?:\\s*=[^,()]*)?(?:\\s*,\\s*[^()]*)?\\)\\s*(?::[^=;{]*)?(?:=>|\\{)').test(source) ||
|
|
743
|
+
new RegExp('(?:^|[^\\w$.])' + n + '\\s*=>').test(source);
|
|
744
|
+
}
|
|
745
|
+
memo.set(key, bound);
|
|
746
|
+
return bound;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Try to resolve a reference by exact name match
|
|
750
|
+
*/
|
|
751
|
+
function matchByExactName(ref, context) {
|
|
752
|
+
// `import`-kind nodes are import STATEMENTS, not definitions, so a reference
|
|
753
|
+
// resolving to a sibling file's `import` is a meaningless edge — the real
|
|
754
|
+
// import→definition resolution is the import resolver's job (resolveViaImport),
|
|
755
|
+
// never name-matching here. Excluding them also removes a quadratic blow-up:
|
|
756
|
+
// a ubiquitous package (`react`, `@superset-ui/core`, Python `logging`/`typing`)
|
|
757
|
+
// is re-declared as an `import` node in every file that imports it, so K
|
|
758
|
+
// unresolved import refs each scored K same-named import candidates through
|
|
759
|
+
// findBestMatch — O(K²) per package, the dominant cost of "Resolving refs" on
|
|
760
|
+
// large import-heavy (front-end + back-end) repos (#915).
|
|
761
|
+
const bareJs = isBareJsCall(ref, context);
|
|
762
|
+
if (bareJs) {
|
|
763
|
+
const storeAction = matchJsStoreBindingCall(ref, context);
|
|
764
|
+
if (storeAction)
|
|
765
|
+
return storeAction;
|
|
766
|
+
}
|
|
767
|
+
const candidates = applyLanguageGate(context.getNodesByName(ref.referenceName), ref)
|
|
768
|
+
.filter((n) => n.kind !== 'import')
|
|
769
|
+
// Nested locals are only reachable from inside their container (#1230).
|
|
770
|
+
.filter((n) => isLexicallyReachable(n, ref, context))
|
|
771
|
+
// Preserve import ranking; calls reject the winner without promoting another.
|
|
772
|
+
.filter((n) => ref.referenceKind !== 'imports' || n.filePath === ref.filePath ||
|
|
773
|
+
!ESM_FAMILY.has(n.language) || !isSealedModule(n.filePath, context))
|
|
774
|
+
// A receiver-less JS/TS call cannot reach a method (#1714).
|
|
775
|
+
.filter((n) => !(bareJs && n.kind === 'method'))
|
|
776
|
+
// A name the file binds itself (a parameter, a const) shadows every other
|
|
777
|
+
// file's symbol of that name, so a bare call has no cross-file candidate.
|
|
778
|
+
.filter((n) => !(bareJs && n.filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context)))
|
|
779
|
+
// An `extends`/`implements` ref names a supertype, so anything that can't
|
|
780
|
+
// BE one is not a candidate at all. This is eligibility, not
|
|
781
|
+
// ranking: kind is only a scoring bonus below (and none is awarded for
|
|
782
|
+
// inheritance refs), so without this a same-named `enum_member` outranked
|
|
783
|
+
// the real `trait`, and as the sole candidate was adopted outright by the
|
|
784
|
+
// single-match shortcut. Restricting the pool BEFORE ranking lets the
|
|
785
|
+
// legitimate supertype win instead of merely dropping the false edge.
|
|
786
|
+
.filter((n) => !(0, types_1.isInheritanceRef)(ref) || types_1.SUPERTYPE_TARGET_KINDS.has(n.kind))
|
|
787
|
+
// Likewise for `imports`: a member that only exists inside a type is not
|
|
788
|
+
// importable, so it is not a candidate. Without this a `path`/`id`/`url`
|
|
789
|
+
// import resolved to some interface's same-named property.
|
|
790
|
+
.filter((n) => ref.referenceKind !== 'imports' || (0, types_1.isImportableKind)(n.kind));
|
|
791
|
+
if (candidates.length === 0) {
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
// If only one match, use it — but penalize cross-language matches
|
|
795
|
+
if (candidates.length === 1) {
|
|
796
|
+
if (!isCrossFileReachable(candidates[0], ref, context))
|
|
797
|
+
return null;
|
|
798
|
+
const isCrossLanguage = candidates[0].language !== ref.language;
|
|
799
|
+
return {
|
|
800
|
+
original: ref,
|
|
801
|
+
targetNodeId: candidates[0].id,
|
|
802
|
+
confidence: isCrossLanguage ? 0.5 : 0.9,
|
|
803
|
+
resolvedBy: 'exact-match',
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
// Ubiquitous-name ceiling (#999): above it, picking one target among K
|
|
807
|
+
// same-named defs by directory proximity is unreliable AND O(K) per ref — the
|
|
808
|
+
// quadratic behind the "Resolving refs" wedge on theme/SDK-vendoring repos.
|
|
809
|
+
// Decline; the precise strategies (qualified-name, import, class-name) already
|
|
810
|
+
// ran. Falls through to fuzzy, which itself only resolves a UNIQUE candidate.
|
|
811
|
+
if (candidates.length > AMBIGUOUS_NAME_CEILING) {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
// Multiple matches - try to narrow down
|
|
815
|
+
const bestMatch = findBestMatch(ref, candidates, context);
|
|
816
|
+
if (bestMatch && isCrossFileReachable(bestMatch, ref, context)) {
|
|
817
|
+
// Lower confidence when the match is from a distant/unrelated module
|
|
818
|
+
const proximity = computePathProximity(ref.filePath, bestMatch.filePath);
|
|
819
|
+
const confidence = proximity >= 30 ? 0.7 : 0.4;
|
|
820
|
+
return {
|
|
821
|
+
original: ref,
|
|
822
|
+
targetNodeId: bestMatch.id,
|
|
823
|
+
confidence,
|
|
824
|
+
resolvedBy: 'exact-match',
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Try to resolve by qualified name
|
|
831
|
+
*/
|
|
832
|
+
function matchByQualifiedName(ref, context) {
|
|
833
|
+
// Check if the reference name looks qualified (contains :: or .)
|
|
834
|
+
if (!ref.referenceName.includes('::') && !ref.referenceName.includes('.')) {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
// A method call `receiver.method()` can share an exact qualified name with a
|
|
838
|
+
// config-file key: `service.process()` (a `calls` ref named `service.process`)
|
|
839
|
+
// vs the yaml key `service.process`. Config keys are bound to their code refs
|
|
840
|
+
// upstream by the framework resolvers (`@Value` → `references`); a `calls` ref
|
|
841
|
+
// must never resolve to a yaml/properties config node — that's a wrong edge
|
|
842
|
+
// AND it hides the real callee. Drop those from both the exact and the partial
|
|
843
|
+
// candidate sets so resolution falls through to method resolution below (#1180).
|
|
844
|
+
const keepForRef = (nodes) => ref.referenceKind === 'calls'
|
|
845
|
+
? nodes.filter((n) => !(n.kind === 'constant' && (n.language === 'yaml' || n.language === 'properties')))
|
|
846
|
+
: nodes;
|
|
847
|
+
const candidates = keepForRef(context.getNodesByQualifiedName(ref.referenceName));
|
|
848
|
+
if (candidates.length === 1) {
|
|
849
|
+
return {
|
|
850
|
+
original: ref,
|
|
851
|
+
targetNodeId: candidates[0].id,
|
|
852
|
+
confidence: 0.95,
|
|
853
|
+
resolvedBy: 'qualified-name',
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
// Several symbols share this exact qualified name (e.g. `Logger::log` declared
|
|
857
|
+
// in two files — an ODR clash or separate translation units): prefer the one
|
|
858
|
+
// in the call site's own file before the partial-match fallback below, else
|
|
859
|
+
// the first-indexed def wins and a call in `b/svc` targets `a/svc` (#1079).
|
|
860
|
+
if (candidates.length > 1) {
|
|
861
|
+
const ordered = preferCallSiteFile(candidates, ref.filePath);
|
|
862
|
+
if (ordered[0].filePath === ref.filePath) {
|
|
863
|
+
return {
|
|
864
|
+
original: ref,
|
|
865
|
+
targetNodeId: ordered[0].id,
|
|
866
|
+
confidence: 0.95,
|
|
867
|
+
resolvedBy: 'qualified-name',
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
// Erlang qualified refs (#1610): every erlang function's qualifiedName
|
|
872
|
+
// carries its arity (`mod::f/2`), and refs carry the call-site arity when it
|
|
873
|
+
// is statically known.
|
|
874
|
+
if (ref.language === 'erlang' && ref.referenceName.includes('::')) {
|
|
875
|
+
// A ref WITH arity that missed the exact lookup names an arity that isn't
|
|
876
|
+
// defined (or a module out of repo). Never fall through to the partial
|
|
877
|
+
// match — its "last segment" would be the arity digits — and never settle
|
|
878
|
+
// for a sibling arity: silent beats wrong.
|
|
879
|
+
if (/\/\d{1,3}$/.test(ref.referenceName))
|
|
880
|
+
return null;
|
|
881
|
+
// An arity-LESS qualified ref (dynamic MFA whose args list wasn't a
|
|
882
|
+
// static literal): resolve only when the module defines exactly ONE arity
|
|
883
|
+
// of that function; several arities with no signal is a guess.
|
|
884
|
+
const base = ref.referenceName.slice(ref.referenceName.lastIndexOf('::') + 2);
|
|
885
|
+
const prefix = `${ref.referenceName}/`;
|
|
886
|
+
const arityCands = keepForRef(context.getNodesByName(base)).filter((n) => n.qualifiedName.startsWith(prefix) && /^\d{1,3}$/.test(n.qualifiedName.slice(prefix.length)));
|
|
887
|
+
if (arityCands.length === 1) {
|
|
888
|
+
return {
|
|
889
|
+
original: ref,
|
|
890
|
+
targetNodeId: arityCands[0].id,
|
|
891
|
+
confidence: 0.85,
|
|
892
|
+
resolvedBy: 'qualified-name',
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
// Try partial qualified name match — again preferring the call site's own
|
|
898
|
+
// file when more than one symbol's qualifiedName ends with the reference.
|
|
899
|
+
const parts = ref.referenceName.split(/[:.]/);
|
|
900
|
+
const lastName = parts[parts.length - 1];
|
|
901
|
+
if (lastName) {
|
|
902
|
+
const partialCandidates = keepForRef(context.getNodesByName(lastName))
|
|
903
|
+
.filter((candidate) => candidate.qualifiedName.endsWith(ref.referenceName));
|
|
904
|
+
const chosen = preferCallSiteFile(partialCandidates, ref.filePath)[0];
|
|
905
|
+
if (chosen) {
|
|
906
|
+
return {
|
|
907
|
+
original: ref,
|
|
908
|
+
targetNodeId: chosen.id,
|
|
909
|
+
confidence: 0.85,
|
|
910
|
+
resolvedBy: 'qualified-name',
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* When a symbol name is ambiguous across files, prefer the candidate(s) declared
|
|
918
|
+
* in the call site's own file, keeping the rest in their original order (#1079).
|
|
919
|
+
* A same-file definition is the strongest language-agnostic signal for which of
|
|
920
|
+
* several same-named symbols a call means; without it, resolution collapses onto
|
|
921
|
+
* whichever was indexed first, so a call in `b/svc` wrongly targets `a/svc`.
|
|
922
|
+
* No-op when there are <2 candidates or none share the call site's file.
|
|
923
|
+
*/
|
|
924
|
+
function preferCallSiteFile(nodes, callSiteFile) {
|
|
925
|
+
if (nodes.length < 2)
|
|
926
|
+
return nodes;
|
|
927
|
+
const same = [];
|
|
928
|
+
const other = [];
|
|
929
|
+
for (const n of nodes) {
|
|
930
|
+
if (n.filePath === callSiteFile)
|
|
931
|
+
same.push(n);
|
|
932
|
+
else
|
|
933
|
+
other.push(n);
|
|
934
|
+
}
|
|
935
|
+
return same.length ? [...same, ...other] : nodes;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Languages whose object literals declare callable members — `export const
|
|
939
|
+
* api = { call() {…}, get: () => {…} }` used as a namespace (#1573).
|
|
940
|
+
*/
|
|
941
|
+
const OBJECT_LITERAL_LANGUAGES = new Set(['typescript', 'tsx', 'javascript', 'jsx', 'arkts']);
|
|
942
|
+
/** True when `inner`'s source range lies within `outer`'s (lines, then columns on a shared line). */
|
|
943
|
+
function rangeWithin(inner, outer) {
|
|
944
|
+
const innerEnd = inner.endLine ?? inner.startLine;
|
|
945
|
+
const outerEnd = outer.endLine ?? outer.startLine;
|
|
946
|
+
if (inner.startLine < outer.startLine || innerEnd > outerEnd)
|
|
947
|
+
return false;
|
|
948
|
+
if (inner.startLine === outer.startLine && inner.startColumn < outer.startColumn)
|
|
949
|
+
return false;
|
|
950
|
+
if (innerEnd === outerEnd && inner.endColumn > outer.endColumn)
|
|
951
|
+
return false;
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
function sameRange(a, b) {
|
|
955
|
+
return (a.startLine === b.startLine &&
|
|
956
|
+
a.startColumn === b.startColumn &&
|
|
957
|
+
(a.endLine ?? a.startLine) === (b.endLine ?? b.startLine) &&
|
|
958
|
+
a.endColumn === b.endColumn);
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Resolve `container.member` where `container` is a VALUE holding an object
|
|
962
|
+
* literal — `export const api = { call() {…}, get: () => {…} }` used as the
|
|
963
|
+
* module's namespace (#1573). The members are extracted as plain functions
|
|
964
|
+
* with BARE qualified names inside the constant's source extent (there is no
|
|
965
|
+
* `api::call`), so neither the `Container::member` lookup the class-shaped
|
|
966
|
+
* kinds use (#825) nor the declared-type inference for singleton instances
|
|
967
|
+
* (#1292) can reach them, and every such call resolved to nothing — or, via
|
|
968
|
+
* an import, to the constant itself. This looks the member up by CONTAINMENT:
|
|
969
|
+
* a node named `member` whose range lies inside the container's, in the
|
|
970
|
+
* container's own file. A helper declared inside a member's body is not a
|
|
971
|
+
* member and is skipped; nothing else in the file can donate a match. Calls
|
|
972
|
+
* take callable kinds only; other references accept value members too.
|
|
973
|
+
*/
|
|
974
|
+
function resolveObjectLiteralMember(container, member, ref, context, confidence, resolvedBy) {
|
|
975
|
+
if (container.kind !== 'constant' && container.kind !== 'variable')
|
|
976
|
+
return null;
|
|
977
|
+
if (!OBJECT_LITERAL_LANGUAGES.has(container.language))
|
|
978
|
+
return null;
|
|
979
|
+
if (!sameLanguageFamily(container.language, ref.language))
|
|
980
|
+
return null;
|
|
981
|
+
const inFile = context.getNodesInFile(container.filePath);
|
|
982
|
+
const callable = (n) => n.kind === 'function' || n.kind === 'method';
|
|
983
|
+
const valueMember = (n) => callable(n) || n.kind === 'property' || n.kind === 'variable' || n.kind === 'constant';
|
|
984
|
+
const accepts = ref.referenceKind === 'calls' ? callable : valueMember;
|
|
985
|
+
const inside = inFile.filter((n) => n.id !== container.id && rangeWithin(n, container));
|
|
986
|
+
let candidates = inside.filter((n) => n.name === member && accepts(n));
|
|
987
|
+
if (candidates.length === 0)
|
|
988
|
+
return null;
|
|
989
|
+
// Drop a candidate nested inside ANOTHER callable's body within the literal
|
|
990
|
+
// (`{ run() { const call = () => {}; } }` — `call` is `run`'s local, not a
|
|
991
|
+
// member). Strict containment: an identically-ranged sibling node for the
|
|
992
|
+
// same member (a property node over an arrow function) is not a body.
|
|
993
|
+
const bodies = inside.filter(callable);
|
|
994
|
+
candidates = candidates.filter((c) => !bodies.some((b) => b.id !== c.id && !sameRange(b, c) && rangeWithin(c, b)));
|
|
995
|
+
if (candidates.length === 0)
|
|
996
|
+
return null;
|
|
997
|
+
// Several survivors (a property AND a function for one arrow member, say):
|
|
998
|
+
// a callable first, then the earliest in source order.
|
|
999
|
+
candidates.sort((a, b) => {
|
|
1000
|
+
const ca = callable(a) ? 0 : 1;
|
|
1001
|
+
const cb = callable(b) ? 0 : 1;
|
|
1002
|
+
if (ca !== cb)
|
|
1003
|
+
return ca - cb;
|
|
1004
|
+
return a.startLine - b.startLine || a.startColumn - b.startColumn;
|
|
1005
|
+
});
|
|
1006
|
+
return {
|
|
1007
|
+
original: ref,
|
|
1008
|
+
targetNodeId: candidates[0].id,
|
|
1009
|
+
confidence,
|
|
1010
|
+
resolvedBy,
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
// Exported for the precedence unit tests (#1079): they assert the
|
|
1014
|
+
// preferredFqn → same-file → matches[0] ordering directly.
|
|
1015
|
+
function resolveMethodOnType(typeName, methodName, ref, context, confidence, resolvedBy,
|
|
1016
|
+
/**
|
|
1017
|
+
* Optional FQN that identifies WHICH class declaration `typeName`
|
|
1018
|
+
* refers to in the caller's file. When multiple candidates share
|
|
1019
|
+
* the same qualifiedName (`FooConverter::convert` in both
|
|
1020
|
+
* `dao/converter/` and `service/converter/`), the FQN's
|
|
1021
|
+
* file-path-suffix picks the right one — the disambiguation
|
|
1022
|
+
* signal Java imports carry but the call site doesn't (#314).
|
|
1023
|
+
*/
|
|
1024
|
+
preferredFqn,
|
|
1025
|
+
/** Recursion guard for the supertype/conformance walk. */
|
|
1026
|
+
depth = 0) {
|
|
1027
|
+
// Look up methods by name and match by qualifiedName ending in
|
|
1028
|
+
// `<typeName>::<methodName>`. This works whether the method is defined
|
|
1029
|
+
// in-class (`class Foo { int bar() { ... } }`) or out-of-line in a separate
|
|
1030
|
+
// file (`int Foo::bar() { ... }` in foo.cpp while class Foo is in foo.hpp).
|
|
1031
|
+
// The previous same-file approach missed the latter — the typical C++ layout.
|
|
1032
|
+
// Prefer the context's per-(type, method) memo: the raw name lookup fetches
|
|
1033
|
+
// EVERY node sharing the method name — tens of thousands of rows for a
|
|
1034
|
+
// collision-heavy Java name like `execute` — and re-filtering that per ref
|
|
1035
|
+
// was a dominant term in the #1122 watchdog kill on large repos. Only the
|
|
1036
|
+
// ref-independent filter is memoized; per-ref disambiguation stays below.
|
|
1037
|
+
let matches;
|
|
1038
|
+
if (context.getMethodMatches) {
|
|
1039
|
+
matches = context.getMethodMatches(typeName, methodName, ref.language);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
const methodCandidates = context.getNodesByName(methodName);
|
|
1043
|
+
const want = `${typeName}::${methodName}`;
|
|
1044
|
+
matches = [];
|
|
1045
|
+
for (const m of methodCandidates) {
|
|
1046
|
+
if (m.kind !== 'method')
|
|
1047
|
+
continue;
|
|
1048
|
+
if (!sameLanguageFamily(m.language, ref.language))
|
|
1049
|
+
continue;
|
|
1050
|
+
const qn = m.qualifiedName;
|
|
1051
|
+
if (qn === want || qn.endsWith(`::${want}`)) {
|
|
1052
|
+
matches.push(m);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (matches.length === 0) {
|
|
1057
|
+
// Conformance fallback: the method may be defined on a supertype `typeName`
|
|
1058
|
+
// extends, or on a protocol / trait it conforms to (e.g. a Swift protocol-
|
|
1059
|
+
// extension method, a C# default-interface or extension method, a Kotlin
|
|
1060
|
+
// extension on a supertype). Walk supertypes transitively (depth-capped) via
|
|
1061
|
+
// the resolved implements/extends edges — empty in the first resolution pass,
|
|
1062
|
+
// populated in the conformance pass. Still VALIDATED (the method must exist on
|
|
1063
|
+
// a supertype), so a wrong inference produces no edge.
|
|
1064
|
+
if (depth < 4 && context.getSupertypes) {
|
|
1065
|
+
const viaSupers = nmTimedT('rmot-supers', ref, () => {
|
|
1066
|
+
for (const supertype of context.getSupertypes(typeName, ref.language)) {
|
|
1067
|
+
const via = resolveMethodOnType(supertype, methodName, ref, context, confidence, resolvedBy, preferredFqn, depth + 1);
|
|
1068
|
+
if (via)
|
|
1069
|
+
return via;
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
});
|
|
1073
|
+
if (viaSupers)
|
|
1074
|
+
return viaSupers;
|
|
1075
|
+
}
|
|
1076
|
+
return null;
|
|
1077
|
+
}
|
|
1078
|
+
if (matches.length > 1 && preferredFqn) {
|
|
1079
|
+
const ext = ref.language === 'kotlin' ? '.kt' : '.java';
|
|
1080
|
+
const fqnPath = preferredFqn.replace(/\./g, '/') + ext;
|
|
1081
|
+
const chosen = matches.find((m) => {
|
|
1082
|
+
const fp = m.filePath.replace(/\\/g, '/');
|
|
1083
|
+
return fp.endsWith(fqnPath) || fp.endsWith('/' + fqnPath);
|
|
1084
|
+
});
|
|
1085
|
+
if (chosen) {
|
|
1086
|
+
return {
|
|
1087
|
+
original: ref,
|
|
1088
|
+
targetNodeId: chosen.id,
|
|
1089
|
+
confidence,
|
|
1090
|
+
resolvedBy,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
// Language-agnostic disambiguation: when several same-named methods survive
|
|
1095
|
+
// (e.g. two files each declaring `class Logger { void log(); }` — an ODR
|
|
1096
|
+
// clash, an anonymous-namespace type, or separate translation units), prefer
|
|
1097
|
+
// the definition in the CALL SITE's own file. Without this, every ambiguous
|
|
1098
|
+
// call collapses onto the first-indexed definition, so a call in `b/svc.cpp`
|
|
1099
|
+
// wrongly points at `a/svc.cpp` (#1079). This runs AFTER the `preferredFqn`
|
|
1100
|
+
// block, so Java/Kotlin import disambiguation — whose target is intentionally
|
|
1101
|
+
// in ANOTHER file (#314) — is unaffected: that block returns early whenever
|
|
1102
|
+
// an import FQN pins the class.
|
|
1103
|
+
const ordered = preferCallSiteFile(matches, ref.filePath);
|
|
1104
|
+
return {
|
|
1105
|
+
original: ref,
|
|
1106
|
+
targetNodeId: ordered[0].id,
|
|
1107
|
+
confidence,
|
|
1108
|
+
resolvedBy,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
// C++ keywords/control-flow tokens that can appear right before a receiver
|
|
1112
|
+
// (e.g. `return ptr->m()`) and must NOT be treated as a type.
|
|
1113
|
+
const CPP_NON_TYPE_TOKENS = new Set([
|
|
1114
|
+
'return', 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default',
|
|
1115
|
+
'break', 'continue', 'goto', 'throw', 'new', 'delete', 'co_await', 'co_yield',
|
|
1116
|
+
'co_return', 'static_cast', 'const_cast', 'dynamic_cast', 'reinterpret_cast',
|
|
1117
|
+
'sizeof', 'alignof', 'typeid', 'and', 'or', 'not', 'xor',
|
|
1118
|
+
]);
|
|
1119
|
+
function normalizeCppTypeName(typeName) {
|
|
1120
|
+
const normalized = typeName
|
|
1121
|
+
.replace(/\b(const|volatile|mutable|typename|class|struct)\b/g, ' ')
|
|
1122
|
+
.replace(/[&*]+/g, ' ')
|
|
1123
|
+
.replace(/<[^>]*>/g, ' ')
|
|
1124
|
+
.replace(/\s+/g, ' ')
|
|
1125
|
+
.trim();
|
|
1126
|
+
if (!normalized)
|
|
1127
|
+
return null;
|
|
1128
|
+
const parts = normalized.split(/::/).filter(Boolean);
|
|
1129
|
+
const last = parts[parts.length - 1];
|
|
1130
|
+
if (!last)
|
|
1131
|
+
return null;
|
|
1132
|
+
if (CPP_NON_TYPE_TOKENS.has(last))
|
|
1133
|
+
return null;
|
|
1134
|
+
return last;
|
|
1135
|
+
}
|
|
1136
|
+
// Declarator regex: matches `Type receiver`, `Type* receiver`, `Type *receiver`,
|
|
1137
|
+
// `Type*receiver`, `Type<X> receiver`, etc., REQUIRING a declarator terminator
|
|
1138
|
+
// (`;`, `=`, `,`, `)`, `[`, `{`, `(`, or end-of-line) after the receiver. The
|
|
1139
|
+
// terminator rules out uses like `return receiver->m()` where the preceding
|
|
1140
|
+
// token is a keyword, not a type.
|
|
1141
|
+
function buildDeclaratorRegex(escapedReceiver) {
|
|
1142
|
+
return new RegExp(`([A-Za-z_][\\w:]*(?:\\s*<[^;=(){}]+>)?(?:\\s*[*&]+)?)\\s*\\b${escapedReceiver}\\b\\s*(?=[;=,)\\[{(]|$)`);
|
|
1143
|
+
}
|
|
1144
|
+
function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
1145
|
+
// Per-file lines cache when available — this runs per `receiver->method()`
|
|
1146
|
+
// ref and re-splitting the file each time is the same quadratic as the
|
|
1147
|
+
// shared inferrer's (#1122).
|
|
1148
|
+
const lines = context.getFileLines
|
|
1149
|
+
? context.getFileLines(ref.filePath)
|
|
1150
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
1151
|
+
if (!lines || lines.length === 0)
|
|
1152
|
+
return null;
|
|
1153
|
+
const callLineIndex = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
1154
|
+
const escapedReceiver = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1155
|
+
const receiverPattern = new RegExp(`\\b${escapedReceiver}\\b`);
|
|
1156
|
+
const declaratorRegex = buildDeclaratorRegex(escapedReceiver);
|
|
1157
|
+
for (let i = callLineIndex; i >= 0; i--) {
|
|
1158
|
+
const line = lines[i];
|
|
1159
|
+
if (!line || !receiverPattern.test(line))
|
|
1160
|
+
continue;
|
|
1161
|
+
const declaratorMatch = line.match(declaratorRegex);
|
|
1162
|
+
if (declaratorMatch) {
|
|
1163
|
+
const normalized = normalizeCppTypeName(declaratorMatch[1] ?? '');
|
|
1164
|
+
if (normalized === 'auto') {
|
|
1165
|
+
// `auto x = Foo::instance();` — the declared type is deduced; recover it
|
|
1166
|
+
// from the initializer (call return type / construction) (#645).
|
|
1167
|
+
const initType = inferCppAutoInitializerType(line, receiverName, ref, context, depth);
|
|
1168
|
+
if (initType)
|
|
1169
|
+
return initType;
|
|
1170
|
+
// No usable initializer on this line — keep scanning earlier ones.
|
|
1171
|
+
}
|
|
1172
|
+
else if (normalized) {
|
|
1173
|
+
return normalized;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const headerCandidates = [
|
|
1178
|
+
ref.filePath.replace(/\.(?:c|cc|cpp|cxx)$/i, '.h'),
|
|
1179
|
+
ref.filePath.replace(/\.(?:c|cc|cpp|cxx)$/i, '.hpp'),
|
|
1180
|
+
ref.filePath.replace(/\.(?:c|cc|cpp|cxx)$/i, '.hxx'),
|
|
1181
|
+
].filter((candidate, index, arr) => arr.indexOf(candidate) === index && candidate !== ref.filePath);
|
|
1182
|
+
for (const headerPath of headerCandidates) {
|
|
1183
|
+
if (!context.fileExists(headerPath))
|
|
1184
|
+
continue;
|
|
1185
|
+
const headerLines = context.getFileLines
|
|
1186
|
+
? context.getFileLines(headerPath)
|
|
1187
|
+
: (context.readFile(headerPath)?.split(/\r?\n/) ?? null);
|
|
1188
|
+
if (!headerLines)
|
|
1189
|
+
continue;
|
|
1190
|
+
for (const line of headerLines) {
|
|
1191
|
+
if (!receiverPattern.test(line))
|
|
1192
|
+
continue;
|
|
1193
|
+
const declaratorMatch = line.match(declaratorRegex);
|
|
1194
|
+
if (!declaratorMatch)
|
|
1195
|
+
continue;
|
|
1196
|
+
const normalized = normalizeCppTypeName(declaratorMatch[1] ?? '');
|
|
1197
|
+
if (normalized && normalized !== 'auto')
|
|
1198
|
+
return normalized;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Last `::`-separated segment of a (possibly namespace-qualified) C++ name.
|
|
1205
|
+
*/
|
|
1206
|
+
function cppLastSegment(name) {
|
|
1207
|
+
const parts = name.split('::').filter(Boolean);
|
|
1208
|
+
return parts[parts.length - 1] ?? name;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Return type captured at extraction for `Class::method` (or a free function),
|
|
1212
|
+
* read off the indexed node's `returnType` — used by the C++ (#645) and PHP
|
|
1213
|
+
* (#608) chained-call resolvers. Language-filtered. Null when not indexed or no
|
|
1214
|
+
* return type was recorded (a `void`/primitive return).
|
|
1215
|
+
*/
|
|
1216
|
+
function lookupCalleeReturnType(callee, ref, context) {
|
|
1217
|
+
let method = callee;
|
|
1218
|
+
let cls = null;
|
|
1219
|
+
if (callee.includes('::')) {
|
|
1220
|
+
const parts = callee.split('::').filter(Boolean);
|
|
1221
|
+
method = parts[parts.length - 1] ?? callee;
|
|
1222
|
+
cls = parts.slice(0, -1).join('::');
|
|
1223
|
+
}
|
|
1224
|
+
const candidates = context.getNodesByName(method).filter((n) => (n.kind === 'method' || n.kind === 'function') &&
|
|
1225
|
+
n.language === ref.language &&
|
|
1226
|
+
!!n.returnType);
|
|
1227
|
+
if (cls) {
|
|
1228
|
+
const want = `${cls}::${method}`;
|
|
1229
|
+
// The call site may name the class with MORE namespace qualification than
|
|
1230
|
+
// the stored node (`details::registry::instance` at the call vs
|
|
1231
|
+
// `registry::instance` on the node — the receiver type only carries the
|
|
1232
|
+
// immediate class), or LESS. Accept an exact match or either being a
|
|
1233
|
+
// namespace-suffix of the other; the shared `::<class>::<method>` tail keeps
|
|
1234
|
+
// it specific.
|
|
1235
|
+
const m = candidates.find((n) => n.qualifiedName === want ||
|
|
1236
|
+
n.qualifiedName.endsWith(`::${want}`) ||
|
|
1237
|
+
want.endsWith(`::${n.qualifiedName}`));
|
|
1238
|
+
return m?.returnType ?? null;
|
|
1239
|
+
}
|
|
1240
|
+
return candidates.find((n) => n.kind === 'function')?.returnType ?? null;
|
|
1241
|
+
}
|
|
1242
|
+
/** Does the graph contain an aggregate type named `name`'s last segment? */
|
|
1243
|
+
function cppClassExists(name, ref, context) {
|
|
1244
|
+
const last = cppLastSegment(name);
|
|
1245
|
+
return context
|
|
1246
|
+
.getNodesByName(last)
|
|
1247
|
+
.some((n) => (n.kind === 'class' || n.kind === 'struct' || n.kind === 'union') && n.language === ref.language);
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Infer the class produced by a C++ call/construction expression, using return
|
|
1251
|
+
* types captured at extraction (#645). Handles, in order:
|
|
1252
|
+
* - `make_unique<T>()` / `make_shared<T>()` → T
|
|
1253
|
+
* - single-level member call `recv.method()` → recv's type, then method's return
|
|
1254
|
+
* - `Class::method()` / free `func()` → the callee's recorded return type
|
|
1255
|
+
* - direct construction `Type()` / `ns::Type()` → Type
|
|
1256
|
+
* Returns null when undeterminable. Callers MUST still validate the outer method
|
|
1257
|
+
* exists on the result before creating an edge, so a wrong guess stays silent.
|
|
1258
|
+
*/
|
|
1259
|
+
function resolveCppCallResultType(inner, ref, context, depth = 0) {
|
|
1260
|
+
if (depth > 3)
|
|
1261
|
+
return null; // guard against pathological mutual recursion
|
|
1262
|
+
const expr = inner.trim();
|
|
1263
|
+
const make = expr.match(/(?:^|::)(?:make_unique|make_shared)\s*<\s*([A-Za-z_]\w*)/);
|
|
1264
|
+
if (make)
|
|
1265
|
+
return make[1] ?? null;
|
|
1266
|
+
// Single-level member call `recv.method` (the `manager.view().render()` shape).
|
|
1267
|
+
const dotIdx = expr.lastIndexOf('.');
|
|
1268
|
+
if (dotIdx > 0) {
|
|
1269
|
+
const recv = expr.slice(0, dotIdx);
|
|
1270
|
+
const method = expr.slice(dotIdx + 1);
|
|
1271
|
+
if (recv.includes('.') || recv.includes('(') || recv.includes('::'))
|
|
1272
|
+
return null; // single level only
|
|
1273
|
+
const recvType = inferCppReceiverType(recv, ref, context, depth + 1);
|
|
1274
|
+
if (!recvType)
|
|
1275
|
+
return null;
|
|
1276
|
+
return lookupCalleeReturnType(`${recvType}::${method}`, ref, context);
|
|
1277
|
+
}
|
|
1278
|
+
const ret = lookupCalleeReturnType(expr, ref, context);
|
|
1279
|
+
if (ret)
|
|
1280
|
+
return ret;
|
|
1281
|
+
// Direct construction — the callee itself names a class/struct.
|
|
1282
|
+
if (cppClassExists(expr, ref, context))
|
|
1283
|
+
return cppLastSegment(expr);
|
|
1284
|
+
return null;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Recover the type of an `auto`-declared local from its initializer on the
|
|
1288
|
+
* declaration line — `auto x = Foo::instance();`, `auto w = make_unique<W>();`,
|
|
1289
|
+
* `auto p = new W();`, `auto w = Widget();` (#645).
|
|
1290
|
+
*/
|
|
1291
|
+
function inferCppAutoInitializerType(line, receiverName, ref, context, depth) {
|
|
1292
|
+
const escaped = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1293
|
+
const m = line.match(new RegExp(`\\b${escaped}\\b\\s*=\\s*([^;]+)`));
|
|
1294
|
+
if (!m || !m[1])
|
|
1295
|
+
return null;
|
|
1296
|
+
const init = m[1].trim();
|
|
1297
|
+
const neu = init.match(/^new\s+([A-Za-z_][\w:]*)/);
|
|
1298
|
+
if (neu && neu[1])
|
|
1299
|
+
return cppLastSegment(neu[1]);
|
|
1300
|
+
// A call or construction: `Foo(...)`, `A::b(...)`, `make_unique<T>(...)`.
|
|
1301
|
+
const call = init.match(/^([A-Za-z_][\w:]*(?:\s*<[^>;]*>)?)\s*\(/);
|
|
1302
|
+
if (call && call[1])
|
|
1303
|
+
return resolveCppCallResultType(call[1].replace(/\s+/g, ''), ref, context, depth + 1);
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Resolve a C++ chained call whose receiver is itself a call — encoded by the
|
|
1308
|
+
* extractor as `<innerCallee>().<method>` (#645). The receiver's type is what
|
|
1309
|
+
* the inner call returns; the outer method is then resolved and VALIDATED on it
|
|
1310
|
+
* (resolveMethodOnType requires `cls::method` to exist), so a wrong inference
|
|
1311
|
+
* produces no edge rather than a wrong one.
|
|
1312
|
+
*/
|
|
1313
|
+
function matchCppCallChain(ref, context) {
|
|
1314
|
+
const m = ref.referenceName.match(/^(.+)\(\)\.(\w+)$/);
|
|
1315
|
+
if (!m || !m[1] || !m[2])
|
|
1316
|
+
return null;
|
|
1317
|
+
const cls = resolveCppCallResultType(m[1], ref, context);
|
|
1318
|
+
if (!cls)
|
|
1319
|
+
return null;
|
|
1320
|
+
return resolveMethodOnType(cls, m[2], ref, context, 0.85, 'instance-method');
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Resolve a `::`-scoped factory chain whose receiver is a scoped/static call —
|
|
1324
|
+
* PHP `Cls::for($x)->method()` (#608, the per-credential Laravel client idiom) or
|
|
1325
|
+
* Rust `Foo::new().bar()` (an associated-function call) — both encoded by the
|
|
1326
|
+
* extractor as `Cls::factory().method`. The receiver's type is what `Cls::factory`
|
|
1327
|
+
* returns: a `self` marker (PHP `: self`/`: static`, Rust `-> Self`) resolves to
|
|
1328
|
+
* the factory's own type, a concrete return type to that type. The outer method is
|
|
1329
|
+
* then resolved and VALIDATED on it (resolveMethodOnType requires the method to
|
|
1330
|
+
* exist on the type or a supertype it conforms to), so a wrong inference yields no
|
|
1331
|
+
* edge rather than a wrong one. Shared by the `::`-receiver languages (PHP, Rust).
|
|
1332
|
+
*/
|
|
1333
|
+
function matchScopedCallChain(ref, context) {
|
|
1334
|
+
const m = ref.referenceName.match(/^(.+)\(\)\.(\w+)$/);
|
|
1335
|
+
if (!m || !m[1] || !m[2])
|
|
1336
|
+
return null;
|
|
1337
|
+
const inner = m[1];
|
|
1338
|
+
const method = m[2];
|
|
1339
|
+
if (!inner.includes('::'))
|
|
1340
|
+
return null; // only static-factory (`Cls::method`) chains
|
|
1341
|
+
const factoryClass = inner.slice(0, inner.lastIndexOf('::'));
|
|
1342
|
+
const ret = lookupCalleeReturnType(inner, ref, context);
|
|
1343
|
+
if (!ret)
|
|
1344
|
+
return null;
|
|
1345
|
+
// `self` (the extractor's marker for self/static/$this) → the factory's class.
|
|
1346
|
+
const resolvedClass = ret === 'self' ? factoryClass : ret;
|
|
1347
|
+
return resolveMethodOnType(resolvedClass, method, ref, context, 0.85, 'instance-method');
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Languages where an unprefixed capitalized call `Foo(args)` constructs the
|
|
1351
|
+
* class (so a `Foo(args).method()` receiver's type is `Foo`). Java/C# need `new`,
|
|
1352
|
+
* so a bare `Foo()` there is a method call, not construction — excluded. Scala's
|
|
1353
|
+
* `Foo(args)` is a case-class / companion `apply`, which conventionally returns
|
|
1354
|
+
* `Foo` — and resolveMethodOnType validates, so a non-conventional `apply` that
|
|
1355
|
+
* returns another type simply yields no edge rather than a wrong one. Pascal/Delphi:
|
|
1356
|
+
* a `TFoo(x)` is a TYPECAST whose result is a `TFoo`, so `TFoo(x).method()` resolves
|
|
1357
|
+
* the method on `TFoo` — same shape, same validation.
|
|
1358
|
+
*/
|
|
1359
|
+
const CONSTRUCTS_VIA_BARE_CALL = new Set(['kotlin', 'swift', 'scala', 'dart', 'pascal']);
|
|
1360
|
+
/**
|
|
1361
|
+
* Resolve a dotted chained call whose receiver is a static factory / fluent call —
|
|
1362
|
+
* `Foo.getInstance().bar()`, encoded by the extractor as `Foo.getInstance().bar`
|
|
1363
|
+
* (#645/#608 mechanism). The receiver's type is what `Foo.getInstance` returns
|
|
1364
|
+
* (its declared return type); the outer method is then resolved and VALIDATED on
|
|
1365
|
+
* it (resolveMethodOnType requires `Type::method` to exist), so a wrong inference
|
|
1366
|
+
* yields no edge rather than a wrong one (e.g. a same-named `bar()` on an
|
|
1367
|
+
* unrelated class is never matched). Shared by the dot-notation languages
|
|
1368
|
+
* (Java, Kotlin, C#, Swift) — same receiver shape, same `Class::method` qualified names.
|
|
1369
|
+
*/
|
|
1370
|
+
function matchDottedCallChain(ref, context) {
|
|
1371
|
+
const m = ref.referenceName.match(/^(.+)\(\)\.(\w+)$/);
|
|
1372
|
+
if (!m || !m[1] || !m[2])
|
|
1373
|
+
return null;
|
|
1374
|
+
const inner = m[1]; // `Foo.getInstance`
|
|
1375
|
+
const method = m[2]; // `bar`
|
|
1376
|
+
const lastDot = inner.lastIndexOf('.');
|
|
1377
|
+
if (lastDot <= 0) {
|
|
1378
|
+
// Go: bare package-level factory FUNCTION `New().method()` — the receiver's
|
|
1379
|
+
// type is what `New` returns; resolve the method on that.
|
|
1380
|
+
if (ref.language === 'go') {
|
|
1381
|
+
const ret = lookupCalleeReturnType(inner, ref, context);
|
|
1382
|
+
if (ret) {
|
|
1383
|
+
return resolveMethodOnType(ret, method, ref, context, 0.85, 'instance-method', importedFqnOf(ret, ref, context));
|
|
1384
|
+
}
|
|
1385
|
+
// `inner` isn't a function with a captured return type — typically a
|
|
1386
|
+
// package-level VARIABLE holding a function value (e.g. gin's `engine()`),
|
|
1387
|
+
// whose type we can't recover. Fall back to bare-name resolution of the
|
|
1388
|
+
// method so we don't DROP an edge the un-re-encoded bare path would have
|
|
1389
|
+
// found. (When `inner` IS a real factory function but the method doesn't
|
|
1390
|
+
// exist on its return type, `ret` is truthy and we returned no edge above —
|
|
1391
|
+
// the absent-method safety guarantee is preserved.)
|
|
1392
|
+
//
|
|
1393
|
+
// CRITICAL: resolve the TARGET via a synthetic bare-name ref, but return the
|
|
1394
|
+
// match tied to the ORIGINAL `ref` (referenceName `inner().method`). The
|
|
1395
|
+
// batched resolver (resolveAndPersistBatched) reads unresolved rows from
|
|
1396
|
+
// offset 0 every pass and relies on the post-batch cleanup (row-id delete
|
|
1397
|
+
// for DB-loaded refs, referenceName-keyed delete otherwise, #1269) to
|
|
1398
|
+
// clear each resolved row so the batch empties. If we propagated the
|
|
1399
|
+
// synthetic ref's bare `method` as `.original`, a key-based delete
|
|
1400
|
+
// would never match the stored `inner().method` row, the batch would
|
|
1401
|
+
// never drain, and the loop would re-resolve + re-insert forever (a runaway
|
|
1402
|
+
// that grew gin's graph to 5M edges / 1.4 GB before this fix).
|
|
1403
|
+
const bareRef = { ...ref, referenceName: method };
|
|
1404
|
+
const bareMatch = matchByExactName(bareRef, context) ?? matchFuzzy(bareRef, context);
|
|
1405
|
+
return bareMatch ? { ...bareMatch, original: ref } : null;
|
|
1406
|
+
}
|
|
1407
|
+
// Constructor receiver `Foo(args).method()` (encoded `Foo().method`): a bare,
|
|
1408
|
+
// capitalized inner is a class construction, so the receiver's type is the
|
|
1409
|
+
// class itself — resolve the method on it. Only in languages where an
|
|
1410
|
+
// unprefixed capitalized call constructs the class (Kotlin, Swift); in Java/C#
|
|
1411
|
+
// a bare `Foo()` is a method call (constructors need `new`), so we must not
|
|
1412
|
+
// assume construction. A lowercase bare inner is a top-level `factory().method()`
|
|
1413
|
+
// whose type we can't recover — bail.
|
|
1414
|
+
if (!CONSTRUCTS_VIA_BARE_CALL.has(ref.language) || !/^[A-Z]/.test(inner))
|
|
1415
|
+
return null;
|
|
1416
|
+
return resolveMethodOnType(inner, method, ref, context, 0.85, 'instance-method', importedFqnOf(inner, ref, context));
|
|
1417
|
+
}
|
|
1418
|
+
// Factory/fluent receiver `Receiver.factory(args).method()`: the receiver's
|
|
1419
|
+
// type is what `Receiver.factory` returns (its declared return type).
|
|
1420
|
+
const factoryClass = inner.slice(0, lastDot).split('.').pop(); // simple class name
|
|
1421
|
+
const factoryMethod = inner.slice(lastDot + 1);
|
|
1422
|
+
if (!factoryClass || !factoryMethod)
|
|
1423
|
+
return null;
|
|
1424
|
+
const ret = lookupCalleeReturnType(`${factoryClass}::${factoryMethod}`, ref, context);
|
|
1425
|
+
if (!ret) {
|
|
1426
|
+
// Objective-C: a class-message factory — `[X alloc]`, `[X new]`,
|
|
1427
|
+
// `[X sharedFoo]` — returns an instance of the RECEIVER class `X` by
|
|
1428
|
+
// convention (`instancetype`). So when the factory's own return type isn't
|
|
1429
|
+
// recoverable (its selector returns `instancetype`, or `alloc`/`new` aren't
|
|
1430
|
+
// user-defined nodes at all), the receiver's type is the class `X` itself.
|
|
1431
|
+
// This resolves the ubiquitous `[[X alloc] init]` and singleton chains.
|
|
1432
|
+
// resolveMethodOnType validates against X (and its supertypes), so a class
|
|
1433
|
+
// whose method actually lives elsewhere yields NO edge, not a wrong one — and
|
|
1434
|
+
// crucially this does NOT fire when a concrete return type WAS captured but
|
|
1435
|
+
// simply lacks the method (that already returned null above: absent-method
|
|
1436
|
+
// safety, so a same-named decoy is still never matched).
|
|
1437
|
+
if (ref.language === 'objc' && /^[A-Z]/.test(factoryClass)) {
|
|
1438
|
+
return resolveMethodOnType(factoryClass, method, ref, context, 0.8, 'instance-method', importedFqnOf(factoryClass, ref, context));
|
|
1439
|
+
}
|
|
1440
|
+
// Pascal/Delphi: the extractor only re-encodes a `TFoo`/`IFoo`-prefixed chain
|
|
1441
|
+
// (the type-naming convention), so `factoryClass` is always a real class here.
|
|
1442
|
+
// A factory whose return type wasn't captured is a CONSTRUCTOR
|
|
1443
|
+
// (`TFileMem.Create().SetCachePerformance` — `constructor Create` has no `:
|
|
1444
|
+
// TBar` annotation but returns its own class) or an unannotated function. In
|
|
1445
|
+
// both cases the receiver's type is the class itself, so resolve the method on
|
|
1446
|
+
// `factoryClass`. resolveMethodOnType validates against it (and its
|
|
1447
|
+
// supertypes), so a wrong inference yields no edge — and this never fires when
|
|
1448
|
+
// a return type WAS captured but lacks the method (absent-method safety above).
|
|
1449
|
+
if (ref.language === 'pascal' && /^[TI]/.test(factoryClass)) {
|
|
1450
|
+
return resolveMethodOnType(factoryClass, method, ref, context, 0.8, 'instance-method', importedFqnOf(factoryClass, ref, context));
|
|
1451
|
+
}
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
return resolveMethodOnType(ret, method, ref, context, 0.85, 'instance-method', importedFqnOf(ret, ref, context));
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* When several classes share a simple type name, the caller file's import of
|
|
1458
|
+
* that type is the only signal that names WHICH one (#314). Returns the imported
|
|
1459
|
+
* FQN for `typeName` in the ref's file, or undefined.
|
|
1460
|
+
*/
|
|
1461
|
+
function importedFqnOf(typeName, ref, context) {
|
|
1462
|
+
const imports = context.getImportMappings(ref.filePath, ref.language);
|
|
1463
|
+
return imports.find((i) => i.localName === typeName)?.source;
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Java/Kotlin: infer a receiver's declared type by walking field declarations
|
|
1467
|
+
* in the class enclosing the call site. The field's `signature` is already in
|
|
1468
|
+
* the form "<TypeName> <fieldName>" (set by tree-sitter.ts extractField), so we
|
|
1469
|
+
* pull the type from there. Handles Spring `@Resource UserBO userbo;` /
|
|
1470
|
+
* `@Autowired private UserService userService;` where the receiver field name
|
|
1471
|
+
* doesn't match the class name by Java naming convention.
|
|
1472
|
+
*
|
|
1473
|
+
* Returns the bare type name (generics stripped, dotted package stripped) or
|
|
1474
|
+
* null when no matching field is in the enclosing class.
|
|
1475
|
+
*/
|
|
1476
|
+
function inferJavaFieldReceiverType(receiverName, ref, context) {
|
|
1477
|
+
const inFile = context.getNodesInFile(ref.filePath);
|
|
1478
|
+
if (inFile.length === 0)
|
|
1479
|
+
return null;
|
|
1480
|
+
// Find the class enclosing the call line (tightest match by latest start).
|
|
1481
|
+
let enclosing = null;
|
|
1482
|
+
for (const n of inFile) {
|
|
1483
|
+
if (n.kind !== 'class' && n.kind !== 'interface')
|
|
1484
|
+
continue;
|
|
1485
|
+
if (n.language !== ref.language)
|
|
1486
|
+
continue;
|
|
1487
|
+
const end = n.endLine ?? n.startLine;
|
|
1488
|
+
if (n.startLine <= ref.line && end >= ref.line) {
|
|
1489
|
+
if (!enclosing || n.startLine >= enclosing.startLine)
|
|
1490
|
+
enclosing = n;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (!enclosing)
|
|
1494
|
+
return null;
|
|
1495
|
+
const enclosingEnd = enclosing.endLine ?? enclosing.startLine;
|
|
1496
|
+
const field = inFile.find((n) => n.kind === 'field' &&
|
|
1497
|
+
n.name === receiverName &&
|
|
1498
|
+
n.language === ref.language &&
|
|
1499
|
+
n.startLine >= enclosing.startLine &&
|
|
1500
|
+
(n.endLine ?? n.startLine) <= enclosingEnd);
|
|
1501
|
+
if (!field || !field.signature)
|
|
1502
|
+
return null;
|
|
1503
|
+
// Signature shape: "<TypeName> <fieldName>" (extractField). Pull the type,
|
|
1504
|
+
// strip generics + dotted package, drop array/varargs markers.
|
|
1505
|
+
const beforeName = field.signature.slice(0, field.signature.lastIndexOf(field.name));
|
|
1506
|
+
const typeRaw = beforeName.trim();
|
|
1507
|
+
if (!typeRaw)
|
|
1508
|
+
return null;
|
|
1509
|
+
const typeNoGenerics = typeRaw.replace(/<[^>]*>/g, '').trim();
|
|
1510
|
+
const typeNoArray = typeNoGenerics.replace(/\[\s*\]/g, '').replace(/\.\.\.$/, '').trim();
|
|
1511
|
+
const parts = typeNoArray.split(/[.\s]+/).filter(Boolean);
|
|
1512
|
+
const lastPart = parts[parts.length - 1];
|
|
1513
|
+
if (!lastPart)
|
|
1514
|
+
return null;
|
|
1515
|
+
if (!/^[A-Z]/.test(lastPart))
|
|
1516
|
+
return null; // primitives / lowercase → skip
|
|
1517
|
+
return lastPart;
|
|
1518
|
+
}
|
|
1519
|
+
// ── Local-variable receiver-type inference (#1108) ──────────────────────────
|
|
1520
|
+
//
|
|
1521
|
+
// Instance calls through a local variable (`const lg = new Logger(); lg.log()`)
|
|
1522
|
+
// only resolved in C++ before this — no other language could learn the
|
|
1523
|
+
// receiver's type. Local variables are not indexed as nodes (node-explosion),
|
|
1524
|
+
// so, like the C++ inferrer above, we read the enclosing function's source and
|
|
1525
|
+
// match the receiver's declaration/initializer to recover its type. The type is
|
|
1526
|
+
// then handed to resolveMethodOnType, which VALIDATES that the type actually
|
|
1527
|
+
// declares the method, so a mis-inference produces NO edge — the safety net
|
|
1528
|
+
// that lets the patterns below stay simple. C++ keeps its dedicated inferrer
|
|
1529
|
+
// (header scan + `auto`); this covers every other language.
|
|
1530
|
+
// Tokens a loose pattern might capture that are never a user-defined type.
|
|
1531
|
+
const NON_TYPE_RECEIVER_TOKENS = new Set([
|
|
1532
|
+
'this', 'self', 'super', 'new', 'return', 'await', 'yield', 'typeof',
|
|
1533
|
+
'null', 'nil', 'None', 'true', 'false', 'True', 'False', 'undefined',
|
|
1534
|
+
]);
|
|
1535
|
+
/**
|
|
1536
|
+
* Normalize a captured type expression to a simple type name: drop generic
|
|
1537
|
+
* args and pointer/ref markers, take the last `.`/`::`-qualified segment, and
|
|
1538
|
+
* reject obvious non-types.
|
|
1539
|
+
*/
|
|
1540
|
+
function normalizeInferredTypeName(raw) {
|
|
1541
|
+
const cleaned = raw.replace(/<[^>]*>/g, '').replace(/[&*]/g, '').trim();
|
|
1542
|
+
const seg = cleaned.split(/[.:]+/).filter(Boolean).pop();
|
|
1543
|
+
if (!seg)
|
|
1544
|
+
return null;
|
|
1545
|
+
if (NON_TYPE_RECEIVER_TOKENS.has(seg))
|
|
1546
|
+
return null;
|
|
1547
|
+
return seg;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Per-language patterns that recover a local variable's (or typed parameter's)
|
|
1551
|
+
* type from its declaration/initializer. Each regex captures the type in group
|
|
1552
|
+
* 1; `r` is the already-escaped receiver name. Ordered most-specific first.
|
|
1553
|
+
* PascalCase is required in the capture where the language convention allows,
|
|
1554
|
+
* as a cheap false-positive guard on top of resolveMethodOnType's validation.
|
|
1555
|
+
*/
|
|
1556
|
+
/**
|
|
1557
|
+
* Compiled-pattern memo for the receiver-type pattern builders below. They
|
|
1558
|
+
* run for EVERY `receiver.method()` ref the matcher attempts, compiling 2–4
|
|
1559
|
+
* fresh RegExp objects per call — and receivers repeat massively (`self`
|
|
1560
|
+
* alone accounts for tens of thousands of refs on a Lua repo, measured 41µs
|
|
1561
|
+
* per methodCall miss on kong with compilation a large slice). The patterns
|
|
1562
|
+
* are a pure function of (language, receiver) and non-global (`.match()`
|
|
1563
|
+
* never touches lastIndex), so shared instances are behavior-identical.
|
|
1564
|
+
* FIFO-capped with no per-get mutation (the §7a.6 LRU-churn lesson): a hit
|
|
1565
|
+
* costs one Map lookup, overflow evicts oldest, and an evicted entry simply
|
|
1566
|
+
* recompiles exactly as every call did before this memo.
|
|
1567
|
+
*/
|
|
1568
|
+
const PATTERN_MEMO = new Map();
|
|
1569
|
+
const PATTERN_MEMO_CAP = 8192;
|
|
1570
|
+
const INFER_SCAN_STATES = new WeakMap();
|
|
1571
|
+
const AWAITED_TYPE_MEMO = new WeakMap();
|
|
1572
|
+
const AWAITED_FILES = new WeakMap();
|
|
1573
|
+
function getInferScanStates(context) {
|
|
1574
|
+
let m = INFER_SCAN_STATES.get(context);
|
|
1575
|
+
if (!m) {
|
|
1576
|
+
m = new Map();
|
|
1577
|
+
INFER_SCAN_STATES.set(context, m);
|
|
1578
|
+
}
|
|
1579
|
+
return m;
|
|
1580
|
+
}
|
|
1581
|
+
/** Drop the per-context scan states (see ReferenceResolver.clearCaches). */
|
|
1582
|
+
function clearNameMatcherMemos(context) {
|
|
1583
|
+
INFER_SCAN_STATES.delete(context);
|
|
1584
|
+
AWAITED_TYPE_MEMO.delete(context);
|
|
1585
|
+
AWAITED_FILES.delete(context);
|
|
1586
|
+
C_STATIC_MEMO.delete(context);
|
|
1587
|
+
RUST_TRAIT_IMPL_MEMO.delete(context);
|
|
1588
|
+
SEALED_MODULES.delete(context);
|
|
1589
|
+
LOCAL_BINDING_MEMO.delete(context);
|
|
1590
|
+
SELECTOR_NAMES.delete(context);
|
|
1591
|
+
GET_STATE_FILES.delete(context);
|
|
1592
|
+
}
|
|
1593
|
+
function memoPatterns(key, build) {
|
|
1594
|
+
const hit = PATTERN_MEMO.get(key);
|
|
1595
|
+
if (hit)
|
|
1596
|
+
return hit;
|
|
1597
|
+
const patterns = build();
|
|
1598
|
+
if (PATTERN_MEMO.size >= PATTERN_MEMO_CAP) {
|
|
1599
|
+
const oldest = PATTERN_MEMO.keys().next().value;
|
|
1600
|
+
if (oldest !== undefined)
|
|
1601
|
+
PATTERN_MEMO.delete(oldest);
|
|
1602
|
+
}
|
|
1603
|
+
PATTERN_MEMO.set(key, patterns);
|
|
1604
|
+
return patterns;
|
|
1605
|
+
}
|
|
1606
|
+
function localReceiverTypePatterns(language, r) {
|
|
1607
|
+
return memoPatterns(`${language}|${r}`, () => buildLocalReceiverTypePatterns(language, r));
|
|
1608
|
+
}
|
|
1609
|
+
function buildLocalReceiverTypePatterns(language, r) {
|
|
1610
|
+
switch (language) {
|
|
1611
|
+
case 'typescript':
|
|
1612
|
+
case 'javascript':
|
|
1613
|
+
case 'tsx':
|
|
1614
|
+
case 'jsx':
|
|
1615
|
+
case 'arkts':
|
|
1616
|
+
return [
|
|
1617
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), // = new Logger()
|
|
1618
|
+
// No keyword requirement, so this matches BOTH a local annotation
|
|
1619
|
+
// (`const lg: Logger`) and a typed parameter (`function use(lg: Logger)`
|
|
1620
|
+
// / `(lg: Logger) =>`) — the parameter case the old `const|let|var`
|
|
1621
|
+
// prefix excluded (#1125). Mirrors Kotlin/Swift/Scala; the capture stops
|
|
1622
|
+
// at `<` so a generic-typed param (`repo: Repository<User>`) still yields
|
|
1623
|
+
// `Repository`. resolveMethodOnType validates the type actually declares
|
|
1624
|
+
// the method, so the looser match produces no edge on a mis-inference.
|
|
1625
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.$]*)`), // lg: Logger (annotation or typed param)
|
|
1626
|
+
];
|
|
1627
|
+
case 'python':
|
|
1628
|
+
return [
|
|
1629
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // lg = Logger(...)
|
|
1630
|
+
// A quoted forward reference (`lg: "Logger"`, `lg: 'pkg.Logger'`) is the
|
|
1631
|
+
// same annotation — and what every file under `from __future__ import
|
|
1632
|
+
// annotations` or with a not-yet-defined class writes. The unquoted
|
|
1633
|
+
// pattern below stopped at the quote and read no type at all, so the
|
|
1634
|
+
// call produced no edge (#1684). Tried first: it is the stricter shape.
|
|
1635
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*["']([A-Z][\\w.]*)["']`), // lg: "Logger"
|
|
1636
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // lg: Logger (PEP 526)
|
|
1637
|
+
];
|
|
1638
|
+
case 'java':
|
|
1639
|
+
return [
|
|
1640
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`), // = new Logger()
|
|
1641
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg; / param
|
|
1642
|
+
];
|
|
1643
|
+
case 'kotlin':
|
|
1644
|
+
return [
|
|
1645
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // val lg = Logger(...)
|
|
1646
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // val lg: Logger / param
|
|
1647
|
+
];
|
|
1648
|
+
case 'csharp':
|
|
1649
|
+
return [
|
|
1650
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`), // = new Logger()
|
|
1651
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg; / param
|
|
1652
|
+
];
|
|
1653
|
+
case 'swift':
|
|
1654
|
+
return [
|
|
1655
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // let lg = Logger(...)
|
|
1656
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // let lg: Logger / param
|
|
1657
|
+
];
|
|
1658
|
+
case 'rust':
|
|
1659
|
+
return [
|
|
1660
|
+
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\b(?:\\s*:[^=]+)?=\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg = Logger::new()/Logger{}/Logger
|
|
1661
|
+
// No `let`, so this covers a `let lg: Logger` binding AND a typed
|
|
1662
|
+
// parameter (`fn use(lg: &Logger)`, a closure `|lg: Logger|`) — the
|
|
1663
|
+
// parameter case the old `let`-anchored pattern excluded (#1125).
|
|
1664
|
+
new RegExp(`\\b${r}\\s*:\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // lg: Logger (binding or typed param)
|
|
1665
|
+
];
|
|
1666
|
+
case 'go':
|
|
1667
|
+
return [
|
|
1668
|
+
new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{}
|
|
1669
|
+
new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger
|
|
1670
|
+
// A typed parameter / method receiver (`func use(lg Logger)`,
|
|
1671
|
+
// `func (l Logger) M()`) — name-before-type with no `var`/`:=` (#1125).
|
|
1672
|
+
// PascalCase-guarded (unlike the anchored patterns above) to keep the
|
|
1673
|
+
// keyword-free `ident Type` shape from matching unrelated pairs; the
|
|
1674
|
+
// enclosing-scope bound already excludes package-level struct fields.
|
|
1675
|
+
new RegExp(`\\b${r}\\s+\\*?([A-Z][\\w.]*)`), // func use(lg Logger) / (l Logger)
|
|
1676
|
+
];
|
|
1677
|
+
case 'ruby':
|
|
1678
|
+
return [
|
|
1679
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w:]*)\\.new\\b`), // lg = Logger.new
|
|
1680
|
+
];
|
|
1681
|
+
case 'scala':
|
|
1682
|
+
return [
|
|
1683
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*(?:new\\s+)?([A-Z][\\w.]*)`), // val lg = new Logger / Logger(...)
|
|
1684
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // val lg: Logger / param
|
|
1685
|
+
];
|
|
1686
|
+
case 'dart':
|
|
1687
|
+
return [
|
|
1688
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // var lg = Logger(...)
|
|
1689
|
+
// Trailing `[=;,)]` (not just `[=;]`) so a typed parameter — `Logger lg)`
|
|
1690
|
+
// / `Logger lg,` — matches too, not only `Logger lg = ...` / `Logger lg;`
|
|
1691
|
+
// (#1125). Mirrors Java/C#.
|
|
1692
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg = ... / param
|
|
1693
|
+
];
|
|
1694
|
+
case 'php':
|
|
1695
|
+
return [
|
|
1696
|
+
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
|
1697
|
+
// A typed parameter (`function use(Logger $lg)`, `?Logger $lg`,
|
|
1698
|
+
// `\\App\\Logger $lg`, `&$lg` by-ref) and a typed `catch (E $e)` — the
|
|
1699
|
+
// type sits before the `$`-variable (#1125). Namespace `\\` allowed.
|
|
1700
|
+
new RegExp(`\\b([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // Logger $lg (typed param)
|
|
1701
|
+
];
|
|
1702
|
+
case 'lua':
|
|
1703
|
+
case 'luau':
|
|
1704
|
+
return [
|
|
1705
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\.new\\b`), // local lg = Logger.new()
|
|
1706
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\s*\\(`), // local lg = Logger(...) (callable table)
|
|
1707
|
+
// Luau annotation (`local lg: Logger`) / typed param — but Lua's
|
|
1708
|
+
// method-call syntax is the IDENTICAL `receiver:Name` shape, and the
|
|
1709
|
+
// backward scan starts on the call's own line, so without a gate any
|
|
1710
|
+
// PascalCase method call (`lg:Log()`, the Roblox convention)
|
|
1711
|
+
// self-matches as "type = Log" before the scan reaches the real
|
|
1712
|
+
// declaration (#1124). The lookahead rejects a capture followed by
|
|
1713
|
+
// any of Lua's three call forms — `(args)`, `"s"`/`'s'`/`[[s]]`,
|
|
1714
|
+
// `{t}` — and its leading `[\w.]` alternative stops backtracking from
|
|
1715
|
+
// shrinking the capture to dodge the gate (`lg:Log()` would otherwise
|
|
1716
|
+
// still match, as `Lo`).
|
|
1717
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)(?![\\w.]|\\s*[({"'\\[])`), // local lg: Logger / typed param
|
|
1718
|
+
];
|
|
1719
|
+
case 'r':
|
|
1720
|
+
return [
|
|
1721
|
+
new RegExp(`\\b${r}\\b\\s*(?:<-|<<-|=)\\s*([A-Z][\\w.]*)\\$new\\b`), // lg <- Logger$new() (R6)
|
|
1722
|
+
];
|
|
1723
|
+
case 'pascal':
|
|
1724
|
+
return [
|
|
1725
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w]*)`), // var lg: TLogger / param lg: TLogger
|
|
1726
|
+
new RegExp(`\\b${r}\\b\\s*:=\\s*([A-Z][\\w.]*)\\.Create\\b`), // lg := TLogger.Create
|
|
1727
|
+
];
|
|
1728
|
+
case 'cfml':
|
|
1729
|
+
case 'cfscript':
|
|
1730
|
+
return [
|
|
1731
|
+
// svc = new UserService() / new path.to.UserService() — dotted component
|
|
1732
|
+
// paths reduce to their final segment via normalizeInferredTypeName.
|
|
1733
|
+
// Also matches inside tag markup (`<cfset svc = new UserService()>`)
|
|
1734
|
+
// since the scan reads raw source lines.
|
|
1735
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`),
|
|
1736
|
+
// The classic form: svc = createObject("component", "path.to.UserService")
|
|
1737
|
+
// (casing of createObject varies in the wild), plus the modern
|
|
1738
|
+
// single-argument form createObject("path.to.UserService").
|
|
1739
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']component["']\\s*,\\s*["']([\\w.]+)["']`),
|
|
1740
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']([\\w.]+)["']\\s*\\)`),
|
|
1741
|
+
// Typed cfscript parameter: `function save(UserService svc)` /
|
|
1742
|
+
// `required UserService svc` — CFML's built-in types (string, numeric,
|
|
1743
|
+
// any, struct…) are lowercase by convention, so the PascalCase guard
|
|
1744
|
+
// excludes them.
|
|
1745
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`),
|
|
1746
|
+
// Tag-form typed argument, either attribute order:
|
|
1747
|
+
// <cfargument name="svc" type="path.to.UserService">
|
|
1748
|
+
new RegExp(`\\bcfargument[^>\\n]*\\bname\\s*=\\s*["']${r}["'][^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1749
|
+
new RegExp(`\\bcfargument[^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["'][^>\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1750
|
+
// Component property (incl. WireBox DI): `property name="svc"
|
|
1751
|
+
// inject="UserService";` / `<cfproperty name="svc" type="UserService">`,
|
|
1752
|
+
// either attribute order. An inject DSL value with a namespace
|
|
1753
|
+
// (`inject="svc@core"`) captures only the leading name and simply
|
|
1754
|
+
// fails type-validation — no edge, never a wrong one.
|
|
1755
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\bname\\s*=\\s*["']${r}["'][^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1756
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["'][^;\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1757
|
+
];
|
|
1758
|
+
default:
|
|
1759
|
+
return [];
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
/** 1-based start line of the tightest function/method enclosing the call. */
|
|
1763
|
+
function enclosingScopeStartLine(ref, context) {
|
|
1764
|
+
let start = 1;
|
|
1765
|
+
for (const n of context.getNodesInFile(ref.filePath)) {
|
|
1766
|
+
if (n.kind !== 'function' && n.kind !== 'method')
|
|
1767
|
+
continue;
|
|
1768
|
+
if (n.language !== ref.language)
|
|
1769
|
+
continue;
|
|
1770
|
+
const end = n.endLine ?? n.startLine;
|
|
1771
|
+
if (n.startLine <= ref.line && end >= ref.line && n.startLine >= start) {
|
|
1772
|
+
start = n.startLine;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return start;
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Infer a receiver's type from its local declaration/initializer in the
|
|
1779
|
+
* enclosing function body. Language-dispatched; returns null for languages
|
|
1780
|
+
* without patterns or when no declaration is found. Bounded to the enclosing
|
|
1781
|
+
* scope so a same-named variable in another function can't leak in.
|
|
1782
|
+
*/
|
|
1783
|
+
function inferLocalReceiverType(receiverName, ref, context) {
|
|
1784
|
+
// CFML scope prefixes: `variables.svc` / `this.svc` name a COMPONENT-scoped
|
|
1785
|
+
// field whose assignment or `property` declaration usually lives outside the
|
|
1786
|
+
// calling function (the init-pseudoconstructor / WireBox-injection pattern),
|
|
1787
|
+
// and `local.svc` is an explicit function-local. Strip the prefix so the
|
|
1788
|
+
// declaration patterns match (`variables.svc = new X()`, `property
|
|
1789
|
+
// name="svc" …`, `var svc = …` all bind the bare name), and widen the scan
|
|
1790
|
+
// to the whole file for the component-scoped forms — nearest-declaration-
|
|
1791
|
+
// backward still wins, so a function-local shadowing the field is preferred.
|
|
1792
|
+
let scanReceiver = receiverName;
|
|
1793
|
+
let componentScoped = false;
|
|
1794
|
+
if (ref.language === 'cfml' || ref.language === 'cfscript') {
|
|
1795
|
+
const scoped = receiverName.match(/^(variables|this|local|arguments)\.(.+)$/i);
|
|
1796
|
+
if (scoped) {
|
|
1797
|
+
scanReceiver = scoped[2];
|
|
1798
|
+
const scope = scoped[1].toLowerCase();
|
|
1799
|
+
componentScoped = scope === 'variables' || scope === 'this';
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
// PHP `$this->prop` receiver — the property's declaration lives outside the
|
|
1803
|
+
// calling method (a promoted constructor parameter `private readonly Foo $prop`,
|
|
1804
|
+
// a typed property `private Foo $prop;`, or a classic constructor parameter
|
|
1805
|
+
// `Foo $prop` assigned in __construct). Strip the prefix and widen the scan to
|
|
1806
|
+
// the whole file (the constructor may sit below the calling method), but —
|
|
1807
|
+
// unlike CFML's scopes above — switch to PROPERTY-shaped patterns: a plain
|
|
1808
|
+
// `$prop` local or parameter lives in a different namespace than `$this->prop`
|
|
1809
|
+
// and can never shadow it, so the generic local patterns would type the
|
|
1810
|
+
// property from unrelated same-named variables in other methods (a wrong
|
|
1811
|
+
// 0.9-confidence edge, not a missing one).
|
|
1812
|
+
let phpProperty = false;
|
|
1813
|
+
if (ref.language === 'php') {
|
|
1814
|
+
const scoped = receiverName.match(/^this->(.+)$/);
|
|
1815
|
+
if (scoped) {
|
|
1816
|
+
scanReceiver = scoped[1];
|
|
1817
|
+
componentScoped = true;
|
|
1818
|
+
phpProperty = true;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
const escapedReceiver = scanReceiver.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1822
|
+
const patterns = phpProperty
|
|
1823
|
+
? phpPropertyTypePatterns(escapedReceiver)
|
|
1824
|
+
: localReceiverTypePatterns(ref.language, escapedReceiver);
|
|
1825
|
+
if (patterns.length === 0)
|
|
1826
|
+
return null;
|
|
1827
|
+
// Split through the context's per-file lines cache when available: this runs
|
|
1828
|
+
// for EVERY `receiver.method()` ref, and re-splitting the whole file per ref
|
|
1829
|
+
// was ~20% of total index CPU on Java-heavy repos (#1122).
|
|
1830
|
+
const lines = context.getFileLines
|
|
1831
|
+
? context.getFileLines(ref.filePath)
|
|
1832
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
1833
|
+
if (!lines || lines.length === 0)
|
|
1834
|
+
return null;
|
|
1835
|
+
const callIdx = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
1836
|
+
const startIdx = componentScoped
|
|
1837
|
+
? 0
|
|
1838
|
+
: Math.max(0, enclosingScopeStartLine(ref, context) - 1);
|
|
1839
|
+
const matchLine = (i) => {
|
|
1840
|
+
const line = lines[i];
|
|
1841
|
+
if (!line)
|
|
1842
|
+
return null;
|
|
1843
|
+
// A generated/minified line (one multi-KB statement) is not something a
|
|
1844
|
+
// human-written local declaration lives on, and regexing it per ref is
|
|
1845
|
+
// pure waste — skip it rather than scan it.
|
|
1846
|
+
if (line.length > 10_000)
|
|
1847
|
+
return null;
|
|
1848
|
+
for (const re of patterns) {
|
|
1849
|
+
const m = line.match(re);
|
|
1850
|
+
if (m && m[1]) {
|
|
1851
|
+
const type = normalizeInferredTypeName(m[1]);
|
|
1852
|
+
if (type)
|
|
1853
|
+
return type;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return null;
|
|
1857
|
+
};
|
|
1858
|
+
// Incremental-scan memo (INFER_SCAN_STATES): this scan runs for EVERY
|
|
1859
|
+
// `receiver.method()` ref and was measured at 61µs/ref on kong (2.4s of
|
|
1860
|
+
// worker time, 99% misses — `self:` calls hunting a declaration Lua never
|
|
1861
|
+
// writes). Refs for the same (file, scope, receiver) arrive in ~ascending
|
|
1862
|
+
// line order, and the scan is a pure function of the file's immutable
|
|
1863
|
+
// lines, so each line pays its regex matches ONCE per key instead of once
|
|
1864
|
+
// per ref: query(c) = highest matching line in [startIdx..c]; a monotonic
|
|
1865
|
+
// call extends the stored watermark by scanning only (hi..c] (the region
|
|
1866
|
+
// at-or-below the previous answer is already proven empty above it); a
|
|
1867
|
+
// non-monotonic call (rare — refs are rowid-ordered) falls back to the
|
|
1868
|
+
// plain bounded scan and leaves the state alone. componentScoped is keyed
|
|
1869
|
+
// out — its position-independent whole-file sweep below has different
|
|
1870
|
+
// semantics.
|
|
1871
|
+
if (!componentScoped) {
|
|
1872
|
+
const states = getInferScanStates(context);
|
|
1873
|
+
const key = `${ref.filePath}|${startIdx}|${ref.language}|${scanReceiver}`;
|
|
1874
|
+
const state = states.get(key);
|
|
1875
|
+
if (!state) {
|
|
1876
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1877
|
+
const type = matchLine(i);
|
|
1878
|
+
if (type) {
|
|
1879
|
+
states.set(key, { hi: callIdx, ansIdx: i, ansType: type });
|
|
1880
|
+
return type;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
states.set(key, { hi: callIdx, ansIdx: -1, ansType: null });
|
|
1884
|
+
return null;
|
|
1885
|
+
}
|
|
1886
|
+
if (callIdx >= state.hi) {
|
|
1887
|
+
for (let i = callIdx; i > state.hi; i--) {
|
|
1888
|
+
const type = matchLine(i);
|
|
1889
|
+
if (type) {
|
|
1890
|
+
state.ansIdx = i;
|
|
1891
|
+
state.ansType = type;
|
|
1892
|
+
break;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
state.hi = callIdx;
|
|
1896
|
+
return state.ansIdx >= startIdx ? state.ansType : null;
|
|
1897
|
+
}
|
|
1898
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1899
|
+
const type = matchLine(i);
|
|
1900
|
+
if (type)
|
|
1901
|
+
return type;
|
|
1902
|
+
}
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
// Nearest declaration wins: scan backward from the call to the scope start.
|
|
1906
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1907
|
+
const type = matchLine(i);
|
|
1908
|
+
if (type)
|
|
1909
|
+
return type;
|
|
1910
|
+
}
|
|
1911
|
+
// A component-scoped field's declaration is position-independent — the
|
|
1912
|
+
// `variables.svc = new X()` pseudoconstructor assignment or `property`
|
|
1913
|
+
// declaration may sit BELOW the calling function in the file — so when the
|
|
1914
|
+
// backward pass finds nothing, sweep the remainder of the file too.
|
|
1915
|
+
if (componentScoped) {
|
|
1916
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1917
|
+
const type = matchLine(i);
|
|
1918
|
+
if (type)
|
|
1919
|
+
return type;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
// A PHP property with no statically-typed declaration (classic pre-7.4
|
|
1923
|
+
// style) may still be typed by what gets ASSIGNED to it — follow the
|
|
1924
|
+
// `$this->prop = $var` assignment to the assigned variable's own typed
|
|
1925
|
+
// declaration (a classic or multi-line constructor parameter, or a typed
|
|
1926
|
+
// setter's parameter).
|
|
1927
|
+
if (phpProperty) {
|
|
1928
|
+
return inferPhpAssignedPropertyType(escapedReceiver, lines, callIdx);
|
|
1929
|
+
}
|
|
1930
|
+
return null;
|
|
1931
|
+
}
|
|
1932
|
+
/** Infer only a visible awaited binding and its actual local/imported callee.
|
|
1933
|
+
* The signature already carries the return annotation in both extractors, so
|
|
1934
|
+
* multiline declarations and neighboring declarations cannot donate a type.
|
|
1935
|
+
* `null` means no awaited evidence; a null NAME means an awaited receiver whose
|
|
1936
|
+
* type is unknown, which must not fall back to an unrelated method name. */
|
|
1937
|
+
function inferEsmAwaitedCallType(receiverName, ref, context) {
|
|
1938
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(receiverName))
|
|
1939
|
+
return null;
|
|
1940
|
+
let files = AWAITED_FILES.get(context);
|
|
1941
|
+
if (!files) {
|
|
1942
|
+
files = new Map();
|
|
1943
|
+
AWAITED_FILES.set(context, files);
|
|
1944
|
+
}
|
|
1945
|
+
let file = files.get(ref.filePath);
|
|
1946
|
+
if (file === undefined) {
|
|
1947
|
+
const source = context.readFile(ref.filePath) ?? '';
|
|
1948
|
+
file = null;
|
|
1949
|
+
// Raw eligibility is cheap; sanitize and index scopes only when a ref
|
|
1950
|
+
// actually uses one of these names. Comments cannot donate a binding:
|
|
1951
|
+
// the names are checked again after sanitizing on the first real lookup.
|
|
1952
|
+
const names = new Set([...source.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*await\s+[\w$]+\s*\(/g)].map(m => m[1]));
|
|
1953
|
+
if (names.size)
|
|
1954
|
+
file = { code: source, ready: false, names, offsets: [], scopes: [], declarations: new Map() };
|
|
1955
|
+
if (files.size >= 256)
|
|
1956
|
+
files.delete(files.keys().next().value);
|
|
1957
|
+
files.set(ref.filePath, file);
|
|
1958
|
+
}
|
|
1959
|
+
if (!file?.names.has(receiverName))
|
|
1960
|
+
return null;
|
|
1961
|
+
if (!file.ready) {
|
|
1962
|
+
const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(file.code, 'typescript'));
|
|
1963
|
+
const names = new Set([...code.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*await\s+[\w$]+\s*\(/g)].map(m => m[1]));
|
|
1964
|
+
const offsets = [0];
|
|
1965
|
+
const scopes = [{ start: -1, end: code.length, parent: -1 }];
|
|
1966
|
+
const stack = [0];
|
|
1967
|
+
for (let i = 0; i < code.length; i++) {
|
|
1968
|
+
if (code[i] === '\n')
|
|
1969
|
+
offsets.push(i + 1);
|
|
1970
|
+
if (code[i] === '{') {
|
|
1971
|
+
scopes.push({ start: i, end: code.length, parent: stack[stack.length - 1] });
|
|
1972
|
+
stack.push(scopes.length - 1);
|
|
1973
|
+
}
|
|
1974
|
+
else if (code[i] === '}' && stack.length > 1)
|
|
1975
|
+
scopes[stack.pop()].end = i;
|
|
1976
|
+
}
|
|
1977
|
+
const declarations = new Map();
|
|
1978
|
+
for (const m of code.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*/g)) {
|
|
1979
|
+
if (!names.has(m[1]))
|
|
1980
|
+
continue;
|
|
1981
|
+
const entries = declarations.get(m[1]) ?? [];
|
|
1982
|
+
entries.push({ index: m.index, length: m[0].length });
|
|
1983
|
+
declarations.set(m[1], entries);
|
|
1984
|
+
}
|
|
1985
|
+
Object.assign(file, { code, ready: true, names, offsets, scopes, declarations });
|
|
1986
|
+
if (!names.has(receiverName))
|
|
1987
|
+
return null;
|
|
1988
|
+
}
|
|
1989
|
+
let memo = AWAITED_TYPE_MEMO.get(context);
|
|
1990
|
+
if (!memo) {
|
|
1991
|
+
memo = new Map();
|
|
1992
|
+
AWAITED_TYPE_MEMO.set(context, memo);
|
|
1993
|
+
}
|
|
1994
|
+
const key = `${ref.filePath}|${ref.line}|${ref.column}|${receiverName}`;
|
|
1995
|
+
if (memo.has(key))
|
|
1996
|
+
return memo.get(key);
|
|
1997
|
+
const result = resolveAwaitedCallType(receiverName, file, ref, context);
|
|
1998
|
+
if (memo.size >= PATTERN_MEMO_CAP)
|
|
1999
|
+
memo.delete(memo.keys().next().value);
|
|
2000
|
+
memo.set(key, result);
|
|
2001
|
+
return result;
|
|
2002
|
+
}
|
|
2003
|
+
function resolveAwaitedCallType(receiverName, file, ref, context) {
|
|
2004
|
+
const unknown = { name: null, filePath: ref.filePath };
|
|
2005
|
+
const end = (file.offsets[ref.line - 1] ?? file.code.length) + ref.column;
|
|
2006
|
+
const code = file.code.slice(0, end);
|
|
2007
|
+
const escaped = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2008
|
+
// Locate scopes in the precomputed brace tree. Rescanning the entire file
|
|
2009
|
+
// for every candidate binding made large test files quadratic in refs.
|
|
2010
|
+
const scopeAt = (offset) => {
|
|
2011
|
+
let lo = 0, hi = file.scopes.length;
|
|
2012
|
+
while (lo + 1 < hi) {
|
|
2013
|
+
const mid = (lo + hi) >>> 1;
|
|
2014
|
+
if (file.scopes[mid].start < offset)
|
|
2015
|
+
lo = mid;
|
|
2016
|
+
else
|
|
2017
|
+
hi = mid;
|
|
2018
|
+
}
|
|
2019
|
+
while (lo > 0 && file.scopes[lo].end < offset)
|
|
2020
|
+
lo = file.scopes[lo].parent;
|
|
2021
|
+
return lo;
|
|
2022
|
+
};
|
|
2023
|
+
const visibleAt = (declaration, use) => {
|
|
2024
|
+
const ancestor = scopeAt(declaration);
|
|
2025
|
+
for (let scope = scopeAt(use); scope >= 0; scope = file.scopes[scope].parent)
|
|
2026
|
+
if (scope === ancestor)
|
|
2027
|
+
return true;
|
|
2028
|
+
return false;
|
|
2029
|
+
};
|
|
2030
|
+
const binding = [...(file.declarations.get(receiverName) ?? [])].reverse()
|
|
2031
|
+
.find(m => m.index < end && visibleAt(m.index, end));
|
|
2032
|
+
if (!binding)
|
|
2033
|
+
return null;
|
|
2034
|
+
const init = code.slice(binding.index + binding.length);
|
|
2035
|
+
if (!/^await\b/.test(init))
|
|
2036
|
+
return null;
|
|
2037
|
+
// Only a bare call result, not a following member/index/conditional expression.
|
|
2038
|
+
const call = /^await\s+([A-Za-z_$][\w$]*)\s*\(/.exec(init);
|
|
2039
|
+
if (!call)
|
|
2040
|
+
return null;
|
|
2041
|
+
let depth = 1, callEnd = call[0].length;
|
|
2042
|
+
for (; callEnd < init.length && depth; callEnd++) {
|
|
2043
|
+
if (init[callEnd] === '(')
|
|
2044
|
+
depth++;
|
|
2045
|
+
else if (init[callEnd] === ')')
|
|
2046
|
+
depth--;
|
|
2047
|
+
}
|
|
2048
|
+
if (depth)
|
|
2049
|
+
return unknown;
|
|
2050
|
+
const tail = init.slice(callEnd);
|
|
2051
|
+
// A following property/index/call is not the callee's annotated value.
|
|
2052
|
+
if (!/^[ \t]*(?:;|\r?\n(?![ \t]*[.(\[?]))/.test(tail))
|
|
2053
|
+
return unknown;
|
|
2054
|
+
const rest = tail;
|
|
2055
|
+
if (new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${escaped}\\b|\\{[^}]*\\b${escaped}\\b)`).test(rest) ||
|
|
2056
|
+
new RegExp(`\\b${escaped}\\s*=(?!=)`).test(rest) || hasParameterBinding(rest, escaped))
|
|
2057
|
+
return unknown;
|
|
2058
|
+
const bindingLine = file.code.slice(0, binding.index).split('\n').length;
|
|
2059
|
+
const bindingRef = { ...ref, line: bindingLine, column: binding.index - file.offsets[bindingLine - 1] };
|
|
2060
|
+
const callee = call[1];
|
|
2061
|
+
const calleeEscaped = callee.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2062
|
+
if (context.getNodesInFile(ref.filePath).some(n => (n.kind === 'function' || n.kind === 'method') && n.startLine <= bindingLine && n.endLine >= bindingLine &&
|
|
2063
|
+
n.signature && hasParameterBinding(`${n.signature} {`, calleeEscaped)))
|
|
2064
|
+
return unknown;
|
|
2065
|
+
const imported = context.getImportMappings(ref.filePath, ref.language).some(m => m.localName === callee);
|
|
2066
|
+
let declaring;
|
|
2067
|
+
if (imported) {
|
|
2068
|
+
if (importShadowedAt(callee, bindingRef, context))
|
|
2069
|
+
return unknown;
|
|
2070
|
+
const resolved = context.resolveImport?.({ ...bindingRef, referenceName: callee, referenceKind: 'calls' });
|
|
2071
|
+
declaring = resolved ? context.getNodeById?.(resolved.targetNodeId) ?? undefined : undefined;
|
|
2072
|
+
}
|
|
2073
|
+
else {
|
|
2074
|
+
const local = context.getNodesByName(callee).filter(n => n.kind === 'function' &&
|
|
2075
|
+
n.filePath === ref.filePath && ESM_FAMILY.has(n.language) && isLexicallyReachable(n, bindingRef, context));
|
|
2076
|
+
if (local.length === 1)
|
|
2077
|
+
declaring = local[0];
|
|
2078
|
+
}
|
|
2079
|
+
if (!declaring || declaring.kind !== 'function' || !declaring.signature)
|
|
2080
|
+
return unknown;
|
|
2081
|
+
if (!imported) {
|
|
2082
|
+
const beforeBinding = code.slice(0, binding.index);
|
|
2083
|
+
const shadows = new RegExp(`\\b(?:const|let|var)\\s+${calleeEscaped}\\b`, 'g');
|
|
2084
|
+
for (const shadow of beforeBinding.matchAll(shadows)) {
|
|
2085
|
+
if (!visibleAt(shadow.index, binding.index))
|
|
2086
|
+
continue;
|
|
2087
|
+
// A typed arrow function may itself be the declared local factory.
|
|
2088
|
+
const line = file.code.slice(0, shadow.index).split('\n').length;
|
|
2089
|
+
if (line !== declaring.startLine || shadow.index - file.offsets[line - 1] > declaring.startColumn)
|
|
2090
|
+
return unknown;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
const signature = declaring.signature;
|
|
2094
|
+
const annotation = signature.slice(signature.lastIndexOf(')') + 1).match(/^\s*:\s*([\s\S]+)$/)?.[1]?.trim();
|
|
2095
|
+
if (!annotation)
|
|
2096
|
+
return unknown;
|
|
2097
|
+
// Do not turn unions, arrays, object/function types, or conditional types into
|
|
2098
|
+
// a project class. Await recursively unwraps promises, but this narrow path
|
|
2099
|
+
// accepts a single named Promise<T> layer only.
|
|
2100
|
+
const returned = annotation.match(/^Promise\s*<\s*([\w$]+)\s*>$/)?.[1] ?? annotation;
|
|
2101
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(returned))
|
|
2102
|
+
return unknown;
|
|
2103
|
+
if (js_builtins_1.TS_PRIMITIVE_TYPES.has(returned))
|
|
2104
|
+
return { name: returned, filePath: declaring.filePath };
|
|
2105
|
+
const typeRef = { ...bindingRef, fromNodeId: declaring.id, filePath: declaring.filePath,
|
|
2106
|
+
language: declaring.language, line: declaring.startLine, column: declaring.startColumn,
|
|
2107
|
+
referenceName: returned, referenceKind: 'references' };
|
|
2108
|
+
const typeImport = context.getImportMappings(declaring.filePath, declaring.language).some(m => m.localName === returned);
|
|
2109
|
+
const resolved = typeImport ? context.resolveImport?.(typeRef) : null;
|
|
2110
|
+
const typeNode = resolved ? context.getNodeById?.(resolved.targetNodeId) :
|
|
2111
|
+
context.getNodesByName(returned).find(n => n.filePath === declaring.filePath &&
|
|
2112
|
+
ESM_FAMILY.has(n.language) && (n.kind === 'class' || n.kind === 'interface'));
|
|
2113
|
+
if (!typeNode || (typeNode.kind !== 'class' && typeNode.kind !== 'interface'))
|
|
2114
|
+
return unknown;
|
|
2115
|
+
return { name: typeNode.name, filePath: typeNode.filePath };
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Patterns that recover a PHP class property's declared type for a
|
|
2119
|
+
* `$this->prop` receiver. Deliberately NOT localReceiverTypePatterns: only
|
|
2120
|
+
* property-shaped declarations qualify —
|
|
2121
|
+
* 1. a modifier-prefixed typed declaration, which covers both a typed
|
|
2122
|
+
* property (`private ?Foo $prop;`) and a promoted constructor parameter
|
|
2123
|
+
* (`private readonly Foo $prop`), and
|
|
2124
|
+
* 2. the pseudoconstructor assignment (`$this->prop = new Foo(...)`).
|
|
2125
|
+
* A bare `X $prop` parameter or `$prop = new X()` local elsewhere in the
|
|
2126
|
+
* file must NOT match: those variables can never alias `$this->prop`.
|
|
2127
|
+
* Union-typed properties (`Foo|Bar $prop`) yield no match and thus no edge —
|
|
2128
|
+
* silent beats wrong. The classic untyped-property-assigned-in-constructor
|
|
2129
|
+
* shape is handled by inferPhpAssignedPropertyType instead.
|
|
2130
|
+
*/
|
|
2131
|
+
function phpPropertyTypePatterns(r) {
|
|
2132
|
+
return memoPatterns(`php-prop|${r}`, () => buildPhpPropertyTypePatterns(r));
|
|
2133
|
+
}
|
|
2134
|
+
function buildPhpPropertyTypePatterns(r) {
|
|
2135
|
+
return [
|
|
2136
|
+
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)
|
|
2137
|
+
new RegExp(`\\$this->${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $this->prop = new Foo()
|
|
2138
|
+
];
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Second-chance typing for a PHP `$this->prop` receiver whose property
|
|
2142
|
+
* declaration carries no static type (classic pre-7.4 style): find the
|
|
2143
|
+
* `$this->prop = $var` assignment, then recover `$var`'s type from its own
|
|
2144
|
+
* declaration WITHIN the assignment's function — the constructor's (possibly
|
|
2145
|
+
* multi-line) parameter list, a typed setter's parameter, or a `= new X()`
|
|
2146
|
+
* local. The backward scan stops at the enclosing `function` line (checked
|
|
2147
|
+
* for a match first — a single-line `__construct(Foo $var) { ... }` carries
|
|
2148
|
+
* the typed parameter itself), so a same-named variable in another method
|
|
2149
|
+
* can never type the property.
|
|
2150
|
+
*/
|
|
2151
|
+
function inferPhpAssignedPropertyType(escapedProp, lines, callIdx) {
|
|
2152
|
+
const assignRe = new RegExp(`\\$this->${escapedProp}\\b\\s*=\\s*\\$(\\w+)\\b`);
|
|
2153
|
+
const assignAt = (i) => {
|
|
2154
|
+
const line = lines[i];
|
|
2155
|
+
if (!line || line.length > 10_000)
|
|
2156
|
+
return null;
|
|
2157
|
+
return line.match(assignRe);
|
|
2158
|
+
};
|
|
2159
|
+
// The assignment is position-independent relative to the call — nearest-
|
|
2160
|
+
// backward first, then sweep forward, same order as the componentScoped scan.
|
|
2161
|
+
let assignIdx = -1;
|
|
2162
|
+
let varName = null;
|
|
2163
|
+
for (let i = callIdx; i >= 0; i--) {
|
|
2164
|
+
const m = assignAt(i);
|
|
2165
|
+
if (m) {
|
|
2166
|
+
assignIdx = i;
|
|
2167
|
+
varName = m[1];
|
|
2168
|
+
break;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
if (varName === null) {
|
|
2172
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
2173
|
+
const m = assignAt(i);
|
|
2174
|
+
if (m) {
|
|
2175
|
+
assignIdx = i;
|
|
2176
|
+
varName = m[1];
|
|
2177
|
+
break;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
if (varName === null)
|
|
2182
|
+
return null;
|
|
2183
|
+
const varPatterns = localReceiverTypePatterns('php', varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
2184
|
+
for (let i = assignIdx; i >= 0; i--) {
|
|
2185
|
+
const line = lines[i];
|
|
2186
|
+
if (line && line.length <= 10_000) {
|
|
2187
|
+
for (const re of varPatterns) {
|
|
2188
|
+
const m = line.match(re);
|
|
2189
|
+
if (m && m[1]) {
|
|
2190
|
+
const type = normalizeInferredTypeName(m[1]);
|
|
2191
|
+
if (type)
|
|
2192
|
+
return type;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
if (line && /\bfunction\b/.test(line))
|
|
2197
|
+
break;
|
|
2198
|
+
}
|
|
2199
|
+
return null;
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Try to resolve by method name on a class/object
|
|
2203
|
+
*/
|
|
2204
|
+
function matchMethodCall(ref, context) {
|
|
2205
|
+
// Parse method call patterns like "obj.method" or "Class::method". The method
|
|
2206
|
+
// part allows trailing `:` keywords so Objective-C selectors resolve
|
|
2207
|
+
// (`SDImageCache.storeImage:`, `obj.setX:y:`); colons never appear in other
|
|
2208
|
+
// languages' method refs, so this is a no-op for them.
|
|
2209
|
+
// The receiver allows dots (`builder.Services.AddCoreServices`) so a CHAINED
|
|
2210
|
+
// call resolves by its last segment — Strategy 3 below name-matches the method
|
|
2211
|
+
// (with its existing single-candidate / receiver-overlap guards). Without this
|
|
2212
|
+
// a multi-dot extension-method call (C# DI `builder.Services.AddCoreServices()`,
|
|
2213
|
+
// `Guard.Against.X()`) matched no pattern and never resolved.
|
|
2214
|
+
// C++ explicit operator call `a.operator+(b)` reaches the resolver as
|
|
2215
|
+
// `a.operator+` (#1247) — the operator's symbol chars (`+`, `==`, `[]`, `()`)
|
|
2216
|
+
// fail the \w method part of the plain pattern, so admit them explicitly.
|
|
2217
|
+
// Names like `operatorTable` stay on the plain pattern (tried first); the
|
|
2218
|
+
// operator form requires at least one non-word char after `operator`, and
|
|
2219
|
+
// every downstream strategy compares the method part by exact string
|
|
2220
|
+
// equality, so a stray match can't invent an edge.
|
|
2221
|
+
const dotMatch = ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/) ??
|
|
2222
|
+
(ref.language === 'cpp'
|
|
2223
|
+
? ref.referenceName.match(/^([\w.]+)\.(operator[^\w\s.]+)$/)
|
|
2224
|
+
: null);
|
|
2225
|
+
const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/);
|
|
2226
|
+
// Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`).
|
|
2227
|
+
// Recognize these receiver/method separators so local-variable receiver-type
|
|
2228
|
+
// inference (#1108) applies to them too — extraction already emits the ref in
|
|
2229
|
+
// this shape, but the resolver otherwise only understood `.` and `::`.
|
|
2230
|
+
const luaColonMatch = (ref.language === 'lua' || ref.language === 'luau')
|
|
2231
|
+
? ref.referenceName.match(/^([\w.]+):(\w+)$/)
|
|
2232
|
+
: null;
|
|
2233
|
+
const rDollarMatch = ref.language === 'r'
|
|
2234
|
+
? ref.referenceName.match(/^([\w.]+)\$(\w+)$/)
|
|
2235
|
+
: null;
|
|
2236
|
+
// PHP property receiver: `$this->prop->method()` reaches the resolver as
|
|
2237
|
+
// `this->prop.method` (the extractor records the receiver's raw text with the
|
|
2238
|
+
// leading `$` stripped). Resolve it EXCLUSIVELY through declared-type
|
|
2239
|
+
// inference + resolveMethodOnType validation — the name-similarity strategies
|
|
2240
|
+
// below must never see this shape, so a property whose type can't be
|
|
2241
|
+
// recovered stays unlinked rather than guessed (a wrong inference produces no
|
|
2242
|
+
// edge rather than a wrong one). Deeper chains (`this->a->b.method`) don't
|
|
2243
|
+
// match the single-property pattern and stay unlinked, same as before.
|
|
2244
|
+
const phpThisPropMatch = ref.language === 'php'
|
|
2245
|
+
? ref.referenceName.match(/^(this->\w+)\.(\w+)$/)
|
|
2246
|
+
: null;
|
|
2247
|
+
if (phpThisPropMatch) {
|
|
2248
|
+
const [, receiver, phpMethodName] = phpThisPropMatch;
|
|
2249
|
+
const inferredType = inferLocalReceiverType(receiver, ref, context);
|
|
2250
|
+
if (!inferredType)
|
|
2251
|
+
return null;
|
|
2252
|
+
return resolveMethodOnType(inferredType, phpMethodName, ref, context, 0.9, 'instance-method', importedFqnOf(inferredType, ref, context));
|
|
2253
|
+
}
|
|
2254
|
+
const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch;
|
|
2255
|
+
if (!match) {
|
|
2256
|
+
return null;
|
|
2257
|
+
}
|
|
2258
|
+
const [, objectOrClass, methodName] = match;
|
|
2259
|
+
// A simple `receiver.method` / `receiver:method` / `receiver$method` shape whose
|
|
2260
|
+
// receiver type we can try to infer from its local declaration.
|
|
2261
|
+
const inferableReceiver = dotMatch || luaColonMatch || rDollarMatch;
|
|
2262
|
+
// Infer the receiver's type from its local declaration/initializer in the
|
|
2263
|
+
// enclosing scope, then resolve the method on that type (#1108). C++ keeps its
|
|
2264
|
+
// dedicated inferrer (header scan + `auto`); every other language uses the
|
|
2265
|
+
// shared source-based inferrer. resolveMethodOnType validates the method
|
|
2266
|
+
// exists on the inferred type, so a mis-inference produces no edge.
|
|
2267
|
+
if (inferableReceiver) {
|
|
2268
|
+
let inferredType = nmTimedT('mc-infer', ref, () => ref.language === 'cpp'
|
|
2269
|
+
? inferCppReceiverType(objectOrClass, ref, context)
|
|
2270
|
+
: inferLocalReceiverType(objectOrClass, ref, context));
|
|
2271
|
+
const awaited = !inferredType && ESM_FAMILY.has(ref.language)
|
|
2272
|
+
? inferEsmAwaitedCallType(objectOrClass, ref, context) : null;
|
|
2273
|
+
if (awaited) {
|
|
2274
|
+
if (!awaited.name || js_builtins_1.TS_PRIMITIVE_TYPES.has(awaited.name))
|
|
2275
|
+
return null;
|
|
2276
|
+
inferredType = awaited.name;
|
|
2277
|
+
}
|
|
2278
|
+
if (inferredType) {
|
|
2279
|
+
// Java/Kotlin: when two classes share the simple name, the file's import
|
|
2280
|
+
// pins WHICH one (#314). Other languages disambiguate by call-site file.
|
|
2281
|
+
const importedFqn = ref.language === 'java' || ref.language === 'kotlin'
|
|
2282
|
+
? context
|
|
2283
|
+
.getImportMappings(ref.filePath, ref.language)
|
|
2284
|
+
.find((i) => i.localName === inferredType)?.source
|
|
2285
|
+
: undefined;
|
|
2286
|
+
const typedMatch = nmTimedT('mc-rmot', ref, () => resolveMethodOnType(inferredType, methodName, awaited ? { ...ref, filePath: awaited.filePath } : ref, context, 0.9, 'instance-method', importedFqn));
|
|
2287
|
+
if (typedMatch) {
|
|
2288
|
+
if (awaited) {
|
|
2289
|
+
const target = context.getNodeById?.(typedMatch.targetNodeId);
|
|
2290
|
+
if (!target || (target.qualifiedName.startsWith(`${inferredType}::`) && target.filePath !== awaited.filePath))
|
|
2291
|
+
return null;
|
|
2292
|
+
return { ...typedMatch, original: ref };
|
|
2293
|
+
}
|
|
2294
|
+
return typedMatch;
|
|
2295
|
+
}
|
|
2296
|
+
if (awaited)
|
|
2297
|
+
return null;
|
|
2298
|
+
// A known JS/TS builtin receiver is external when it has no project
|
|
2299
|
+
// method (#1566). Inference already strips generics (`Map<K, V>` →
|
|
2300
|
+
// `Map`); do not let Strategy 3 guess an unrelated `get`/`set`/`has`.
|
|
2301
|
+
// Keep the validated match above for a project type shadowing a builtin.
|
|
2302
|
+
// A primitive receiver joins the builtins here: `listed.split()` on a
|
|
2303
|
+
// `string` is the built-in method, and Strategy 3 would otherwise hand
|
|
2304
|
+
// it whichever project class happens to declare a lone `split` (#1840).
|
|
2305
|
+
if (ESM_FAMILY.has(ref.language) &&
|
|
2306
|
+
(js_builtins_1.JS_BUILT_INS.has(inferredType) || js_builtins_1.TS_PRIMITIVE_TYPES.has(inferredType))) {
|
|
2307
|
+
return null;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
// Go 2-hop field chain `base.field.Method` (#1276): the base's type comes
|
|
2312
|
+
// from the enclosing scope (typed parameter / method receiver / local var),
|
|
2313
|
+
// the field's declared type from that struct's own declaration lines, and
|
|
2314
|
+
// the method is VALIDATED on the field's type by resolveMethodOnType. This
|
|
2315
|
+
// branch is EXCLUSIVE for chained Go receivers: when the hop can't be
|
|
2316
|
+
// inferred or the field's type is external (`conn *sql.DB` — no project
|
|
2317
|
+
// node), the ref stays unresolved rather than falling through to the
|
|
2318
|
+
// bare-name strategies below, which is exactly how `target.conn.Exec(...)`
|
|
2319
|
+
// fabricated a dependency on an unrelated local interface's same-named
|
|
2320
|
+
// method. Chained Go receivers were never emitted before #1276, so there
|
|
2321
|
+
// is no prior recall to preserve on the fallback path.
|
|
2322
|
+
if (ref.language === 'go' && dotMatch && objectOrClass.includes('.')) {
|
|
2323
|
+
return matchGoFieldChainCall(objectOrClass, methodName, ref, context);
|
|
2324
|
+
}
|
|
2325
|
+
// Rust call through a field of the enclosing type — `self.inner.run()`,
|
|
2326
|
+
// emitted as `self.inner.run` (#1585). Same discipline as the Go branch
|
|
2327
|
+
// above, and EXCLUSIVE for the same reason: validated field-type inference
|
|
2328
|
+
// or nothing. Letting this shape reach the bare-name strategies below is
|
|
2329
|
+
// how `self.inner.run()` resolved to a same-named method on an unrelated
|
|
2330
|
+
// type — or to the calling method itself, a self-edge the source doesn't
|
|
2331
|
+
// contain — whenever the field's type was external or merely shared a
|
|
2332
|
+
// method name with something nearby.
|
|
2333
|
+
if (ref.language === 'rust' && dotMatch && objectOrClass.startsWith('self.')) {
|
|
2334
|
+
return matchRustSelfFieldCall(objectOrClass.slice('self.'.length), methodName, ref, context);
|
|
2335
|
+
}
|
|
2336
|
+
// Rust call on the enclosing type itself — `self.reset()`, emitted as
|
|
2337
|
+
// `self.reset` (#1861). Same discipline as the field branch above, and
|
|
2338
|
+
// EXCLUSIVE for the same reason: the owner is written on the `impl` line and
|
|
2339
|
+
// carried in the calling method's qualified name, so it is not a guess.
|
|
2340
|
+
// Letting this shape reach the bare-name strategies below is how
|
|
2341
|
+
// `self.reset()` resolved to a same-named method on an unrelated type
|
|
2342
|
+
// whenever that type's method happened to sit nearer the call site.
|
|
2343
|
+
if (ref.language === 'rust' && dotMatch && objectOrClass === 'self') {
|
|
2344
|
+
return matchRustSelfCall(methodName, ref, context);
|
|
2345
|
+
}
|
|
2346
|
+
// TS/JS call through a field of the enclosing class — `this.mailer.send()`,
|
|
2347
|
+
// emitted as `this.mailer.send` (#1496). Same discipline as the Rust branch
|
|
2348
|
+
// above, and EXCLUSIVE for the same reason: the field's declared type off
|
|
2349
|
+
// the class's own declaration, validated by resolveMethodOnType, or nothing.
|
|
2350
|
+
// Letting the bare name through is how `this.mailer.send()` inside
|
|
2351
|
+
// `Notifier.send()` resolved to the calling method itself — a self-edge the
|
|
2352
|
+
// source does not contain — whenever the two shared a name.
|
|
2353
|
+
if ((ref.language === 'typescript' || ref.language === 'javascript' || ref.language === 'tsx' || ref.language === 'jsx') &&
|
|
2354
|
+
dotMatch &&
|
|
2355
|
+
objectOrClass.startsWith('this.')) {
|
|
2356
|
+
return matchTsThisFieldCall(objectOrClass.slice('this.'.length), methodName, ref, context);
|
|
2357
|
+
}
|
|
2358
|
+
// Java/Kotlin: receiver may be a field whose name doesn't match the type by
|
|
2359
|
+
// Java naming convention (`userbo` → class `UserBO`, abbreviated). Look up
|
|
2360
|
+
// the field in the enclosing class to get its declared type, then resolve
|
|
2361
|
+
// the method on that type. Covers Spring `@Resource`/`@Autowired` field
|
|
2362
|
+
// injection where the field type is the concrete bean class.
|
|
2363
|
+
if ((ref.language === 'java' || ref.language === 'kotlin') && dotMatch) {
|
|
2364
|
+
const inferredType = inferJavaFieldReceiverType(objectOrClass, ref, context);
|
|
2365
|
+
if (inferredType) {
|
|
2366
|
+
// When two classes share the same simple name, the caller file's
|
|
2367
|
+
// import is the only signal that names WHICH one — pass the
|
|
2368
|
+
// imported FQN so resolveMethodOnType can disambiguate (#314).
|
|
2369
|
+
const imports = context.getImportMappings(ref.filePath, ref.language);
|
|
2370
|
+
const importedFqn = imports.find((i) => i.localName === inferredType)?.source;
|
|
2371
|
+
const typedMatch = nmTimedT('mc-rmot', ref, () => resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn));
|
|
2372
|
+
if (typedMatch) {
|
|
2373
|
+
return typedMatch;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
// Object-literal namespace receiver (#1573): `api.call()` where `api` is a
|
|
2378
|
+
// same-file `const api = { call() {…}, get: () => {…} }`. Its members are
|
|
2379
|
+
// plain functions with bare names inside the constant's extent — no
|
|
2380
|
+
// `Container::member` qualified name — so none of the class-shaped
|
|
2381
|
+
// strategies below can see them (Strategy 3 only considers `method`
|
|
2382
|
+
// kinds) and the call resolved to nothing at all. Same file only: a
|
|
2383
|
+
// cross-file use reaches the same helper through the import path.
|
|
2384
|
+
if (dotMatch && !objectOrClass.includes('.') && OBJECT_LITERAL_LANGUAGES.has(ref.language)) {
|
|
2385
|
+
const literalMatch = nmTimedT('mc-literal', ref, () => {
|
|
2386
|
+
const holders = preferCallSiteFile(context.getNodesByName(objectOrClass), ref.filePath).filter((n) => (n.kind === 'constant' || n.kind === 'variable') && n.filePath === ref.filePath);
|
|
2387
|
+
for (const holder of holders) {
|
|
2388
|
+
const hit = resolveObjectLiteralMember(holder, methodName, ref, context, 0.85, 'instance-method');
|
|
2389
|
+
if (hit)
|
|
2390
|
+
return hit;
|
|
2391
|
+
}
|
|
2392
|
+
return null;
|
|
2393
|
+
});
|
|
2394
|
+
if (literalMatch)
|
|
2395
|
+
return literalMatch;
|
|
2396
|
+
}
|
|
2397
|
+
// Strategy 1: Direct class name match (existing logic). When the receiver
|
|
2398
|
+
// names a class that exists in several files (`Logger.log()` / `Logger::log()`
|
|
2399
|
+
// with a `Logger` in both `a/` and `b/`), try the class in the call site's
|
|
2400
|
+
// own file first — otherwise the first-indexed class wins and a call in `b/`
|
|
2401
|
+
// resolves to `a/`'s method (#1079).
|
|
2402
|
+
const strat1 = nmTimedT('mc-class', ref, () => {
|
|
2403
|
+
const classCandidates = preferCallSiteFile(context.getNodesByName(objectOrClass), ref.filePath);
|
|
2404
|
+
for (const classNode of classCandidates) {
|
|
2405
|
+
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'union' || classNode.kind === 'interface') {
|
|
2406
|
+
// Skip cross-language class matches
|
|
2407
|
+
if (classNode.language !== ref.language)
|
|
2408
|
+
continue;
|
|
2409
|
+
const nodesInFile = context.getNodesInFile(classNode.filePath);
|
|
2410
|
+
const methodNode = nodesInFile.find((n) => n.kind === 'method' &&
|
|
2411
|
+
n.name === methodName &&
|
|
2412
|
+
n.qualifiedName.includes(classNode.name));
|
|
2413
|
+
if (methodNode) {
|
|
2414
|
+
return {
|
|
2415
|
+
original: ref,
|
|
2416
|
+
targetNodeId: methodNode.id,
|
|
2417
|
+
confidence: 0.85,
|
|
2418
|
+
resolvedBy: 'qualified-name',
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
return null;
|
|
2424
|
+
});
|
|
2425
|
+
if (strat1)
|
|
2426
|
+
return strat1;
|
|
2427
|
+
// Strategy 2: Instance variable receiver - try capitalized form to find class
|
|
2428
|
+
// e.g., "permissionEngine" → look for classes containing "PermissionEngine"
|
|
2429
|
+
const capitalizedReceiver = objectOrClass.charAt(0).toUpperCase() + objectOrClass.slice(1);
|
|
2430
|
+
if (capitalizedReceiver !== objectOrClass) {
|
|
2431
|
+
const strat2 = nmTimedT('mc-capital', ref, () => {
|
|
2432
|
+
const fuzzyClassCandidates = preferCallSiteFile(context.getNodesByName(capitalizedReceiver), ref.filePath);
|
|
2433
|
+
for (const classNode of fuzzyClassCandidates) {
|
|
2434
|
+
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'union' || classNode.kind === 'interface') {
|
|
2435
|
+
// Skip cross-language class matches
|
|
2436
|
+
if (classNode.language !== ref.language)
|
|
2437
|
+
continue;
|
|
2438
|
+
const nodesInFile = context.getNodesInFile(classNode.filePath);
|
|
2439
|
+
const methodNode = nodesInFile.find((n) => n.kind === 'method' &&
|
|
2440
|
+
n.name === methodName &&
|
|
2441
|
+
n.qualifiedName.includes(classNode.name));
|
|
2442
|
+
if (methodNode) {
|
|
2443
|
+
return {
|
|
2444
|
+
original: ref,
|
|
2445
|
+
targetNodeId: methodNode.id,
|
|
2446
|
+
confidence: 0.8,
|
|
2447
|
+
resolvedBy: 'instance-method',
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
return null;
|
|
2453
|
+
});
|
|
2454
|
+
if (strat2)
|
|
2455
|
+
return strat2;
|
|
2456
|
+
}
|
|
2457
|
+
// Strategy 3: Find methods by name across the codebase, match by receiver
|
|
2458
|
+
// name similarity with the containing class. Handles abbreviated variable
|
|
2459
|
+
// names like permissionEngine → PermissionRuleEngine.
|
|
2460
|
+
if (methodName) {
|
|
2461
|
+
const strat3 = nmTimedT('mc-byname', ref, () => {
|
|
2462
|
+
const methodCandidates = context.getNodesByName(methodName);
|
|
2463
|
+
// Ubiquitous-method ceiling (#999): a method name re-declared across a
|
|
2464
|
+
// vendored theme/SDK (Metronic's `init`/`update`/… on every widget) yields
|
|
2465
|
+
// K candidates that receiver-word overlap can't reliably disambiguate —
|
|
2466
|
+
// and filtering + scoring all K per call is the O(K²) cost that wedged
|
|
2467
|
+
// "Resolving refs" for 15-28 min. Bail before the O(K) work; Strategy 1/2
|
|
2468
|
+
// (class-name match) already had their precise shot above.
|
|
2469
|
+
if (methodCandidates.length > AMBIGUOUS_NAME_CEILING) {
|
|
2470
|
+
return null;
|
|
2471
|
+
}
|
|
2472
|
+
const methods = methodCandidates.filter((n) => n.kind === 'method' && n.name === methodName);
|
|
2473
|
+
// Filter to same-language candidates first
|
|
2474
|
+
const sameLanguageMethods = methods.filter(m => m.language === ref.language);
|
|
2475
|
+
const targetMethods = sameLanguageMethods.length > 0 ? sameLanguageMethods : methods;
|
|
2476
|
+
// If only one same-language method with this name exists, use it
|
|
2477
|
+
if (targetMethods.length === 1 && targetMethods[0].language === ref.language) {
|
|
2478
|
+
return {
|
|
2479
|
+
original: ref,
|
|
2480
|
+
targetNodeId: targetMethods[0].id,
|
|
2481
|
+
confidence: 0.7,
|
|
2482
|
+
resolvedBy: 'instance-method',
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
// Multiple methods: score by receiver name word overlap with class name
|
|
2486
|
+
if (targetMethods.length > 1) {
|
|
2487
|
+
const receiverWords = splitCamelCase(objectOrClass);
|
|
2488
|
+
let bestMatch;
|
|
2489
|
+
let bestScore = 0;
|
|
2490
|
+
// Same-file candidates first, so a score tie (`score > bestScore` keeps
|
|
2491
|
+
// the first seen) resolves to the call site's own file rather than the
|
|
2492
|
+
// first-indexed duplicate (#1079).
|
|
2493
|
+
for (const method of preferCallSiteFile(targetMethods, ref.filePath)) {
|
|
2494
|
+
const classWords = splitCamelCase(method.qualifiedName);
|
|
2495
|
+
let score = receiverWords.filter(w => classWords.some(cw => cw.toLowerCase() === w.toLowerCase())).length;
|
|
2496
|
+
// Bonus for same language
|
|
2497
|
+
if (method.language === ref.language)
|
|
2498
|
+
score += 1;
|
|
2499
|
+
if (score > bestScore) {
|
|
2500
|
+
bestScore = score;
|
|
2501
|
+
bestMatch = method;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
if (bestMatch && bestScore >= 2) {
|
|
2505
|
+
return {
|
|
2506
|
+
original: ref,
|
|
2507
|
+
targetNodeId: bestMatch.id,
|
|
2508
|
+
confidence: 0.65,
|
|
2509
|
+
resolvedBy: 'instance-method',
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
return null;
|
|
2514
|
+
});
|
|
2515
|
+
if (strat3)
|
|
2516
|
+
return strat3;
|
|
2517
|
+
}
|
|
2518
|
+
return null;
|
|
2519
|
+
}
|
|
2520
|
+
/** Go builtin/primitive field types that can never carry a project method. */
|
|
2521
|
+
const GO_BUILTIN_FIELD_TYPES = new Set([
|
|
2522
|
+
'string', 'bool', 'byte', 'rune', 'error', 'any',
|
|
2523
|
+
'int', 'int8', 'int16', 'int32', 'int64',
|
|
2524
|
+
'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uintptr',
|
|
2525
|
+
'float32', 'float64', 'complex64', 'complex128',
|
|
2526
|
+
'chan', 'map', 'func', 'struct', 'interface',
|
|
2527
|
+
]);
|
|
2528
|
+
/**
|
|
2529
|
+
* Resolve a Go 2-hop field-chain call `base.field.Method(...)` (#1276):
|
|
2530
|
+
* `target.conn.Exec("insert")` where `func (target *Target) Write()` and
|
|
2531
|
+
* `type Target struct { conn *sql.DB }`. Two inference hops, both read from
|
|
2532
|
+
* source the same way #1108 does:
|
|
2533
|
+
* 1. `base`'s type from the enclosing scope (method receiver, typed
|
|
2534
|
+
* parameter, or local declaration) via inferLocalReceiverType;
|
|
2535
|
+
* 2. `field`'s declared type from the struct's own declaration lines.
|
|
2536
|
+
* The method is then resolved AND VALIDATED on the field's type. A field
|
|
2537
|
+
* whose type has no project node (`sql.DB`, any external dependency) yields
|
|
2538
|
+
* null — the caller treats this branch as exclusive for chained Go
|
|
2539
|
+
* receivers, so the ref stays unresolved instead of name-guessing.
|
|
2540
|
+
*/
|
|
2541
|
+
function matchGoFieldChainCall(receiverChain, methodName, ref, context) {
|
|
2542
|
+
const segs = receiverChain.split('.');
|
|
2543
|
+
if (segs.length !== 2 || !segs[0] || !segs[1])
|
|
2544
|
+
return null;
|
|
2545
|
+
const [base, field] = segs;
|
|
2546
|
+
const baseType = inferLocalReceiverType(base, ref, context);
|
|
2547
|
+
if (!baseType)
|
|
2548
|
+
return null;
|
|
2549
|
+
const fieldEsc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2550
|
+
const fieldTypeRe = new RegExp(`\\b${fieldEsc}\\s+\\*?\\[?\\]?([A-Za-z_][\\w.]*)`);
|
|
2551
|
+
const structs = preferCallSiteFile(context.getNodesByName(baseType), ref.filePath).filter((n) => (n.kind === 'struct' || n.kind === 'class') && n.language === 'go');
|
|
2552
|
+
for (const s of structs) {
|
|
2553
|
+
const source = context.readFile(s.filePath);
|
|
2554
|
+
if (!source)
|
|
2555
|
+
continue;
|
|
2556
|
+
// Only the struct's own declaration lines — a same-named identifier
|
|
2557
|
+
// elsewhere in the file can't donate a type. Matched LINE BY LINE with
|
|
2558
|
+
// comments stripped: chi's `Mux` has a doc comment reading "the tree
|
|
2559
|
+
// router" right above `tree *node`, and a whole-block match captured
|
|
2560
|
+
// `router` from the prose instead of `node` from the field.
|
|
2561
|
+
const declLines = source.split('\n').slice(Math.max(0, s.startLine - 1), s.endLine);
|
|
2562
|
+
for (const rawLine of declLines) {
|
|
2563
|
+
const line = rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
|
|
2564
|
+
const m = line.match(fieldTypeRe);
|
|
2565
|
+
if (!m || !m[1])
|
|
2566
|
+
continue;
|
|
2567
|
+
const rawType = m[1];
|
|
2568
|
+
// A package-qualified field type (`http.Handler`, `sql.DB`) is only
|
|
2569
|
+
// followed when the package is IN-MODULE: stripping the qualifier and
|
|
2570
|
+
// matching the bare name would conflate a stdlib/third-party type with
|
|
2571
|
+
// any same-named project type — on chi, `handler http.Handler` bound
|
|
2572
|
+
// to an example app's unrelated local `Handler`. That is the exact
|
|
2573
|
+
// fabrication this matcher exists to prevent (#1276).
|
|
2574
|
+
if (rawType.includes('.')) {
|
|
2575
|
+
const pkg = rawType.split('.')[0];
|
|
2576
|
+
const mod = context.getGoModule?.();
|
|
2577
|
+
const imp = context
|
|
2578
|
+
.getImportMappings(s.filePath, 'go')
|
|
2579
|
+
.find((i) => i.localName === pkg);
|
|
2580
|
+
const inModule = !!mod &&
|
|
2581
|
+
!!imp &&
|
|
2582
|
+
(imp.source === mod.modulePath || imp.source.startsWith(mod.modulePath + '/'));
|
|
2583
|
+
if (!inModule)
|
|
2584
|
+
continue;
|
|
2585
|
+
}
|
|
2586
|
+
// Unexported (lowercase) types are idiomatic Go and stay eligible —
|
|
2587
|
+
// chi's `mx.tree.FindRoute()` chains through `tree *node`. A
|
|
2588
|
+
// mis-capture is harmless: resolveMethodOnType only returns a
|
|
2589
|
+
// validated `<type>::<method>` match.
|
|
2590
|
+
const fieldType = rawType.split('.').pop();
|
|
2591
|
+
if (!fieldType || !/^[A-Za-z_]/.test(fieldType) || GO_BUILTIN_FIELD_TYPES.has(fieldType))
|
|
2592
|
+
continue;
|
|
2593
|
+
const resolved = resolveMethodOnType(fieldType, methodName, ref, context, 0.85, 'instance-method');
|
|
2594
|
+
if (resolved)
|
|
2595
|
+
return resolved;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
return null;
|
|
2599
|
+
}
|
|
2600
|
+
// Rust primitives and the prelude's own types: a field of one of these never
|
|
2601
|
+
// names a project type, so a `self.<field>.<method>()` on it stays unresolved.
|
|
2602
|
+
const RUST_NON_PROJECT_FIELD_TYPES = new Set([
|
|
2603
|
+
'bool', 'char', 'str', 'String',
|
|
2604
|
+
'i8', 'i16', 'i32', 'i64', 'i128', 'isize',
|
|
2605
|
+
'u8', 'u16', 'u32', 'u64', 'u128', 'usize',
|
|
2606
|
+
'f32', 'f64',
|
|
2607
|
+
'Self', 'self',
|
|
2608
|
+
]);
|
|
2609
|
+
/**
|
|
2610
|
+
* Reduce a Rust field's declared type text to the simple name of the type a
|
|
2611
|
+
* method call on that field auto-derefs to, or null when there is none we can
|
|
2612
|
+
* name. Only the layers Rust's method-call auto-deref looks through are
|
|
2613
|
+
* unwrapped: references (`&`, `&'a mut`) and the owning smart pointers
|
|
2614
|
+
* (`Box`, `Rc`, `Arc`) — `self.inner.run()` with `inner: Box<Inner>` calls
|
|
2615
|
+
* `Inner::run`. Containers that do NOT auto-deref to their parameter
|
|
2616
|
+
* (`Option<Inner>`, `Vec<Inner>`, `Mutex<Inner>`, `RefCell<Inner>`) keep their
|
|
2617
|
+
* own name and, having no project node, resolve to nothing — `self.items.push()`
|
|
2618
|
+
* must never become `Inner::push`. A trait object (`Box<dyn Source>`) yields
|
|
2619
|
+
* the trait, whose method node the interface-impl synthesizer fans out. A
|
|
2620
|
+
* generic parameter (`T`), a primitive, a tuple / array / raw pointer / fn
|
|
2621
|
+
* type, or a non-identifier yields null.
|
|
2622
|
+
*/
|
|
2623
|
+
function rustFieldTypeName(raw) {
|
|
2624
|
+
let t = raw.trim();
|
|
2625
|
+
for (;;) {
|
|
2626
|
+
const before = t;
|
|
2627
|
+
t = t.replace(/^&\s*(?:'\w+\s+)?(?:mut\s+)?/, '');
|
|
2628
|
+
t = t.replace(/^(?:Box|Rc|Arc)\s*<\s*/, '');
|
|
2629
|
+
t = t.replace(/^(?:dyn|impl)\s+/, '');
|
|
2630
|
+
if (t === before)
|
|
2631
|
+
break;
|
|
2632
|
+
}
|
|
2633
|
+
// Drop generic args, the closing `>`s of unwrapped pointers, and trait-object
|
|
2634
|
+
// bounds (`dyn Source + Send`); keep the last path segment.
|
|
2635
|
+
t = t.replace(/[<>+].*$/, '').trim();
|
|
2636
|
+
const seg = t.split('::').filter(Boolean).pop();
|
|
2637
|
+
if (!seg || !/^[A-Za-z_]\w*$/.test(seg))
|
|
2638
|
+
return null;
|
|
2639
|
+
if (RUST_NON_PROJECT_FIELD_TYPES.has(seg))
|
|
2640
|
+
return null;
|
|
2641
|
+
if (/^[A-Z]$/.test(seg))
|
|
2642
|
+
return null; // bare single-letter generic parameter
|
|
2643
|
+
return seg;
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* `self.method()` in Rust — the method on the type the call sits inside.
|
|
2647
|
+
*
|
|
2648
|
+
* The owner is the calling method's qualified-name prefix (`Target::run` →
|
|
2649
|
+
* `Target`), which is where the `impl` block's type ends up. A free function
|
|
2650
|
+
* has no `self`, so a caller whose qualified name carries no owner declines.
|
|
2651
|
+
* Exactly one candidate must belong to that owner: a project with two `impl`
|
|
2652
|
+
* blocks for the same type is normal, two same-named methods on it is not, and
|
|
2653
|
+
* guessing between them is the failure this replaces.
|
|
2654
|
+
*/
|
|
2655
|
+
function matchRustSelfCall(methodName, ref, context) {
|
|
2656
|
+
const caller = context.getNodeById?.(ref.fromNodeId);
|
|
2657
|
+
if (!caller?.qualifiedName)
|
|
2658
|
+
return null;
|
|
2659
|
+
const sep = caller.qualifiedName.lastIndexOf('::');
|
|
2660
|
+
if (sep <= 0)
|
|
2661
|
+
return null; // a free fn has no `self`
|
|
2662
|
+
const owner = caller.qualifiedName.slice(0, sep);
|
|
2663
|
+
let owned = context
|
|
2664
|
+
.getNodesByQualifiedName(`${owner}::${methodName}`)
|
|
2665
|
+
.filter((n) => n.kind === 'method' &&
|
|
2666
|
+
n.language === 'rust' &&
|
|
2667
|
+
n.qualifiedName === `${owner}::${methodName}`);
|
|
2668
|
+
// Rust's extracted qualified names omit module paths. Two modules can
|
|
2669
|
+
// each declare `Target`; matching just `Target::reset` does not establish
|
|
2670
|
+
// ownership. In that case require a single owner declaration in the
|
|
2671
|
+
// caller's file and a method in that file. Otherwise leave it unresolved.
|
|
2672
|
+
// A unique owner still permits ordinary impl blocks split across files.
|
|
2673
|
+
const owners = context.getNodesByQualifiedName(owner).filter((n) => n.language === 'rust' && ['struct', 'enum', 'union', 'trait', 'class'].includes(n.kind));
|
|
2674
|
+
if (owners.length > 1) {
|
|
2675
|
+
if (owners.filter((n) => n.filePath === caller.filePath).length !== 1)
|
|
2676
|
+
return null;
|
|
2677
|
+
owned = owned.filter((n) => n.filePath === caller.filePath);
|
|
2678
|
+
}
|
|
2679
|
+
if (owned.length !== 1)
|
|
2680
|
+
return null;
|
|
2681
|
+
return {
|
|
2682
|
+
original: ref,
|
|
2683
|
+
targetNodeId: owned[0].id,
|
|
2684
|
+
confidence: 0.9,
|
|
2685
|
+
resolvedBy: 'qualified-name',
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Resolve a Rust call through a field of the enclosing type —
|
|
2690
|
+
* `self.inner.run()`, emitted by the extractor as `self.inner.run` (#1585).
|
|
2691
|
+
* Mirrors the Go 2-hop precedent above (#1276): the owner type is the calling
|
|
2692
|
+
* method's qualified-name prefix (`Outer::run` → `Outer`), the field's declared
|
|
2693
|
+
* type comes from the owner struct's OWN declaration lines, and the method is
|
|
2694
|
+
* resolved AND VALIDATED on that type by resolveMethodOnType. The caller
|
|
2695
|
+
* treats this branch as exclusive for `self.<field>` receivers: a field whose
|
|
2696
|
+
* type is external (`std::vec::IntoIter`, `regex::Regex`), a generic
|
|
2697
|
+
* parameter, or not declared where we can see it yields null and the ref stays
|
|
2698
|
+
* unresolved. Rust struct fields are not graph nodes, so the declaration text
|
|
2699
|
+
* is the only place the type lives.
|
|
2700
|
+
*/
|
|
2701
|
+
function matchRustSelfFieldCall(field, methodName, ref, context) {
|
|
2702
|
+
// The extractor only ever emits a single field hop; anything else is not ours.
|
|
2703
|
+
if (!field || field.includes('.'))
|
|
2704
|
+
return null;
|
|
2705
|
+
const caller = context.getNodeById?.(ref.fromNodeId);
|
|
2706
|
+
if (!caller)
|
|
2707
|
+
return null;
|
|
2708
|
+
const sep = caller.qualifiedName.lastIndexOf('::');
|
|
2709
|
+
if (sep <= 0)
|
|
2710
|
+
return null; // a free fn has no `self`
|
|
2711
|
+
const owner = caller.qualifiedName.slice(0, sep).split('::').pop();
|
|
2712
|
+
if (!owner)
|
|
2713
|
+
return null;
|
|
2714
|
+
const owners = preferCallSiteFile(context.getNodesByName(owner), ref.filePath).filter((n) => (n.kind === 'struct' || n.kind === 'union' || n.kind === 'class') &&
|
|
2715
|
+
n.language === 'rust');
|
|
2716
|
+
const fieldEsc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2717
|
+
// `pub inner: Inner,` / `inner: Box<dyn Source>,` / `pub(crate) inner: T }` —
|
|
2718
|
+
// the type text runs to the field separator. A comma inside generic args
|
|
2719
|
+
// (`HashMap<K, V>`) truncates the capture, which rustFieldTypeName then
|
|
2720
|
+
// reduces to the container's own name — exactly the non-deref case it
|
|
2721
|
+
// refuses anyway.
|
|
2722
|
+
const fieldRe = new RegExp(`\\b${fieldEsc}\\s*:\\s*([^,{}]+)`);
|
|
2723
|
+
for (const s of owners) {
|
|
2724
|
+
const source = context.readFile(s.filePath);
|
|
2725
|
+
if (!source)
|
|
2726
|
+
continue;
|
|
2727
|
+
// Only the struct's own declaration lines, comment-stripped line by line —
|
|
2728
|
+
// same discipline as the Go helper: prose or a same-named identifier
|
|
2729
|
+
// elsewhere in the file can never donate a type.
|
|
2730
|
+
const declLines = source.split('\n').slice(Math.max(0, s.startLine - 1), s.endLine);
|
|
2731
|
+
for (const rawLine of declLines) {
|
|
2732
|
+
const line = rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
|
|
2733
|
+
const m = line.match(fieldRe);
|
|
2734
|
+
if (!m || !m[1])
|
|
2735
|
+
continue;
|
|
2736
|
+
const fieldType = rustFieldTypeName(m[1]);
|
|
2737
|
+
// The field is declared here; whether or not its type names a project
|
|
2738
|
+
// symbol, this owner is the answer — no other same-named struct applies.
|
|
2739
|
+
if (!fieldType)
|
|
2740
|
+
return null;
|
|
2741
|
+
return resolveMethodOnType(fieldType, methodName, ref, context, 0.85, 'instance-method');
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
return null;
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Resolve a TS/JS `this.<field>.<method>()` call (#1496) through the field's
|
|
2748
|
+
* declared type, read off the ENCLOSING class's own declaration lines:
|
|
2749
|
+
* a field or constructor-parameter property (`private mailer: Mailer`,
|
|
2750
|
+
* `mailer?: Mailer`, `readonly mailer: Mailer`) or an initializer
|
|
2751
|
+
* (`mailer = new Mailer()`, `this.mailer = new Mailer()`). The method is then
|
|
2752
|
+
* VALIDATED on that type by resolveMethodOnType. Null — never a bare-name
|
|
2753
|
+
* fallback — when the field is not declared there or its type is external,
|
|
2754
|
+
* a builtin (`this.items.push()`) or not spelled out.
|
|
2755
|
+
*/
|
|
2756
|
+
function matchTsThisFieldCall(field, methodName, ref, context) {
|
|
2757
|
+
if (!field || field.includes('.'))
|
|
2758
|
+
return null;
|
|
2759
|
+
const caller = context.getNodeById?.(ref.fromNodeId);
|
|
2760
|
+
if (!caller)
|
|
2761
|
+
return null;
|
|
2762
|
+
const sep = caller.qualifiedName.lastIndexOf('::');
|
|
2763
|
+
if (sep <= 0)
|
|
2764
|
+
return null; // not inside a class
|
|
2765
|
+
const owner = caller.qualifiedName.slice(0, sep).split('::').pop();
|
|
2766
|
+
if (!owner)
|
|
2767
|
+
return null;
|
|
2768
|
+
const owners = preferCallSiteFile(context.getNodesByName(owner), ref.filePath).filter((n) => (n.kind === 'class' || n.kind === 'component') && sameLanguageFamily(n.language, ref.language));
|
|
2769
|
+
const fieldEsc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2770
|
+
const patterns = [
|
|
2771
|
+
// `storage: typeof DraftHubStorage` — the type OF a value: an object
|
|
2772
|
+
// literal used as a namespace. Its members are bare-named functions inside
|
|
2773
|
+
// the constant's extent (#1573), so they are found by containment, not by
|
|
2774
|
+
// `Type::method`. Tried first: the declared-type pattern below would
|
|
2775
|
+
// otherwise capture the word `typeof`.
|
|
2776
|
+
{
|
|
2777
|
+
re: new RegExp(`\\b${fieldEsc}\\b\\s*[?!]?\\s*:\\s*(?:readonly\\s+)?typeof\\s+([A-Za-z_$][\\w.$]*)`),
|
|
2778
|
+
valueType: true,
|
|
2779
|
+
},
|
|
2780
|
+
// `private readonly mailer?: Mailer` — a class field or a constructor
|
|
2781
|
+
// parameter property; the capture stops at `<`, `[` or `|`, so a generic
|
|
2782
|
+
// or union type yields its head and resolveMethodOnType decides.
|
|
2783
|
+
{
|
|
2784
|
+
re: new RegExp(`\\b${fieldEsc}\\b\\s*[?!]?\\s*:\\s*(?:readonly\\s+)?([A-Za-z_$][\\w.$]*)`),
|
|
2785
|
+
valueType: false,
|
|
2786
|
+
},
|
|
2787
|
+
// `mailer = new Mailer()` / `this.mailer = new Mailer()`
|
|
2788
|
+
{ re: new RegExp(`\\b${fieldEsc}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), valueType: false },
|
|
2789
|
+
];
|
|
2790
|
+
for (const cls of owners) {
|
|
2791
|
+
const source = context.readFile(cls.filePath);
|
|
2792
|
+
if (!source)
|
|
2793
|
+
continue;
|
|
2794
|
+
const declLines = source.split('\n').slice(Math.max(0, cls.startLine - 1), cls.endLine);
|
|
2795
|
+
for (const rawLine of declLines) {
|
|
2796
|
+
const line = rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
|
|
2797
|
+
for (const { re, valueType } of patterns) {
|
|
2798
|
+
const m = line.match(re);
|
|
2799
|
+
if (!m || !m[1])
|
|
2800
|
+
continue;
|
|
2801
|
+
if (valueType) {
|
|
2802
|
+
// The value's declaration may live in another file (it is imported);
|
|
2803
|
+
// the call site's file is preferred when several share the name.
|
|
2804
|
+
const holderName = m[1].split('.').pop();
|
|
2805
|
+
const holders = preferCallSiteFile(context.getNodesByName(holderName), ref.filePath).filter((n) => (n.kind === 'constant' || n.kind === 'variable') && sameLanguageFamily(n.language, ref.language));
|
|
2806
|
+
for (const holder of holders) {
|
|
2807
|
+
const hit = resolveObjectLiteralMember(holder, methodName, ref, context, 0.85, 'instance-method');
|
|
2808
|
+
if (hit)
|
|
2809
|
+
return hit;
|
|
2810
|
+
}
|
|
2811
|
+
return null;
|
|
2812
|
+
}
|
|
2813
|
+
// `ns.Mailer` → `Mailer`; a primitive or builtin names no project type.
|
|
2814
|
+
const typeName = m[1].split('.').pop();
|
|
2815
|
+
if (!/^[A-Z]/.test(typeName))
|
|
2816
|
+
return null;
|
|
2817
|
+
// Two apps in one repo may each declare a `UserService`. The bare-name
|
|
2818
|
+
// path this replaces broke that tie by directory proximity, so keep the
|
|
2819
|
+
// same signal: among the type's declarations of the method, prefer the
|
|
2820
|
+
// one closest to the call site's directory (its own app), never index
|
|
2821
|
+
// order. resolveMethodOnType still answers the single-declaration and
|
|
2822
|
+
// supertype cases.
|
|
2823
|
+
const declared = context
|
|
2824
|
+
.getNodesByName(methodName)
|
|
2825
|
+
.filter((n) => n.kind === 'method' &&
|
|
2826
|
+
sameLanguageFamily(n.language, ref.language) &&
|
|
2827
|
+
(n.qualifiedName === `${typeName}::${methodName}` || n.qualifiedName.endsWith(`::${typeName}::${methodName}`)));
|
|
2828
|
+
if (declared.length > 1) {
|
|
2829
|
+
const callDirs = ref.filePath.split('/').slice(0, -1);
|
|
2830
|
+
const shared = (fp) => {
|
|
2831
|
+
const dirs = fp.split('/').slice(0, -1);
|
|
2832
|
+
let i = 0;
|
|
2833
|
+
while (i < dirs.length && i < callDirs.length && dirs[i] === callDirs[i])
|
|
2834
|
+
i++;
|
|
2835
|
+
return i;
|
|
2836
|
+
};
|
|
2837
|
+
const nearest = [...declared].sort((a, b) => shared(b.filePath) - shared(a.filePath) || a.filePath.localeCompare(b.filePath))[0];
|
|
2838
|
+
return { original: ref, targetNodeId: nearest.id, confidence: 0.85, resolvedBy: 'instance-method' };
|
|
2839
|
+
}
|
|
2840
|
+
return resolveMethodOnType(typeName, methodName, ref, context, 0.85, 'instance-method');
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
return null;
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* The one fallback a TS/JS/Python call-receiver chain keeps (#1683): a STORE
|
|
2848
|
+
* ACCESSOR. Zustand's `get()` inside the store factory and
|
|
2849
|
+
* `useStore.getState()` outside it hand back the store whose actions are
|
|
2850
|
+
* indexed as functions (#1573). JS/TS resolves the member within that store;
|
|
2851
|
+
* the existing Python fallback still requires a unique callable. Nothing else
|
|
2852
|
+
* qualifies: a chain rooted in a project value still says nothing about what
|
|
2853
|
+
* the inner call RETURNS — `db.prepare(sql).all()` would bind to any project
|
|
2854
|
+
* function named `all` — so it resolves to nothing, exactly like a chain
|
|
2855
|
+
* rooted in a parameter (`d.setdefault(k, []).append(v)`).
|
|
2856
|
+
*/
|
|
2857
|
+
function matchStoreAccessorChain(ref, context) {
|
|
2858
|
+
const m = ref.referenceName.match(/^([\w$.]+)\(\)\.(\w+)$/);
|
|
2859
|
+
if (!m || !m[1] || !m[2])
|
|
2860
|
+
return null;
|
|
2861
|
+
const inner = m[1];
|
|
2862
|
+
const method = m[2];
|
|
2863
|
+
if (!(inner === 'get' || inner === 'getState' || inner.endsWith('.getState')))
|
|
2864
|
+
return null;
|
|
2865
|
+
if (JS_FAMILY.has(ref.language)) {
|
|
2866
|
+
return resolveStoreAction(inner, method, ref, context);
|
|
2867
|
+
}
|
|
2868
|
+
const callables = context
|
|
2869
|
+
.getNodesByName(method)
|
|
2870
|
+
.filter((n) => (n.kind === 'function' || n.kind === 'method') && sameLanguageFamily(n.language, ref.language) && n.id !== ref.fromNodeId);
|
|
2871
|
+
if (callables.length !== 1)
|
|
2872
|
+
return null;
|
|
2873
|
+
return { original: ref, targetNodeId: callables[0].id, confidence: 0.6, resolvedBy: 'exact-match' };
|
|
2874
|
+
}
|
|
2875
|
+
/** Resolve the implementation inside the identified store, not a namesake or
|
|
2876
|
+
* an interface signature elsewhere in the project. Import resolution already
|
|
2877
|
+
* follows aliases/barrels; containment already excludes nested action locals. */
|
|
2878
|
+
function resolveStoreAction(inner, member, ref, context, selector = false) {
|
|
2879
|
+
let holders;
|
|
2880
|
+
if (inner === 'get' || inner === 'getState') {
|
|
2881
|
+
const caller = context.getNodeById?.(ref.fromNodeId);
|
|
2882
|
+
if (!caller)
|
|
2883
|
+
return null;
|
|
2884
|
+
holders = context.getNodesInFile(ref.filePath).filter((n) => {
|
|
2885
|
+
if ((n.kind !== 'constant' && n.kind !== 'variable') || !rangeWithin(caller, n))
|
|
2886
|
+
return false;
|
|
2887
|
+
const source = context.readFile(n.filePath)?.split('\n').slice(n.startLine - 1, caller.startLine).join('\n') ?? '';
|
|
2888
|
+
// The accessor must actually be a parameter of the enclosing factory.
|
|
2889
|
+
return new RegExp(`\\(\\s*[\\w$]+\\s*,\\s*${inner}\\s*(?:,\\s*[\\w$]+\\s*)?\\)\\s*=>`).test(source);
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
else {
|
|
2893
|
+
const name = inner.slice(0, -'.getState'.length);
|
|
2894
|
+
if (!/^[\w$]+$/.test(name))
|
|
2895
|
+
return null;
|
|
2896
|
+
const imported = context.resolveImport?.({ ...ref, referenceName: name, referenceKind: 'references' });
|
|
2897
|
+
const node = imported && context.getNodeById?.(imported.targetNodeId);
|
|
2898
|
+
if (node && importShadowedAt(name, ref, context))
|
|
2899
|
+
return null;
|
|
2900
|
+
holders = node ? [node] : context.getNodesByName(name).filter((n) => n.filePath === ref.filePath && isLexicallyReachable(n, ref, context));
|
|
2901
|
+
}
|
|
2902
|
+
if (holders.length !== 1)
|
|
2903
|
+
return null;
|
|
2904
|
+
const holder = holders[0];
|
|
2905
|
+
if (selector) {
|
|
2906
|
+
// Only a Zustand hook promises to return the selector's result. An
|
|
2907
|
+
// arbitrary function accepting that callback is not a store binding.
|
|
2908
|
+
const text = context.readFile(holder.filePath)?.split('\n').slice(holder.startLine - 1, holder.endLine).join('\n') ?? '';
|
|
2909
|
+
const escaped = holder.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2910
|
+
const factory = new RegExp(`\\b(?:const|let)\\s+${escaped}\\s*=\\s*([\\w$]+)\\s*[<(]`).exec(text)?.[1];
|
|
2911
|
+
if (!factory || !context.getImportMappings(holder.filePath, holder.language).some(m => m.localName === factory && m.source === 'zustand' && (m.exportedName === 'create' || m.isDefault)))
|
|
2912
|
+
return null;
|
|
2913
|
+
}
|
|
2914
|
+
return resolveObjectLiteralMember(holder, member, ref, context, 0.9, 'instance-method');
|
|
2915
|
+
}
|
|
2916
|
+
// Eligibility is a file property, not a call-site property. Cache both answers
|
|
2917
|
+
// within the same stable-source window as the resolver's file cache; sync drops
|
|
2918
|
+
// it via clearNameMatcherMemos. Keep only booleans, FIFO-capped like PATTERN_MEMO
|
|
2919
|
+
// to avoid per-hit LRU churn. Eviction merely repeats the source scan.
|
|
2920
|
+
const GET_STATE_FILES = new WeakMap();
|
|
2921
|
+
const GET_STATE_FILES_CAP = 8192;
|
|
2922
|
+
/** A const destructuring is a bound reference, so it is eligible even though
|
|
2923
|
+
* arbitrary locally-bound bare calls must never guess a cross-file target. */
|
|
2924
|
+
function matchDestructuredStoreCall(ref, context) {
|
|
2925
|
+
let files = GET_STATE_FILES.get(context);
|
|
2926
|
+
if (!files) {
|
|
2927
|
+
files = new Map();
|
|
2928
|
+
GET_STATE_FILES.set(context, files);
|
|
2929
|
+
}
|
|
2930
|
+
let eligible = files.get(ref.filePath);
|
|
2931
|
+
let source;
|
|
2932
|
+
if (eligible === undefined) {
|
|
2933
|
+
source = context.readFile(ref.filePath);
|
|
2934
|
+
eligible = source?.includes('.getState') ?? false;
|
|
2935
|
+
if (files.size >= GET_STATE_FILES_CAP) {
|
|
2936
|
+
const oldest = files.keys().next().value;
|
|
2937
|
+
if (oldest !== undefined)
|
|
2938
|
+
files.delete(oldest);
|
|
2939
|
+
}
|
|
2940
|
+
files.set(ref.filePath, eligible);
|
|
2941
|
+
}
|
|
2942
|
+
if (!eligible)
|
|
2943
|
+
return null;
|
|
2944
|
+
source ??= context.readFile(ref.filePath);
|
|
2945
|
+
if (!source)
|
|
2946
|
+
return null;
|
|
2947
|
+
const lines = source.split('\n');
|
|
2948
|
+
const start = enclosingScopeStartLine(ref, context) - 1;
|
|
2949
|
+
const before = lines.slice(start, ref.line - 1).concat(lines[ref.line - 1].slice(0, ref.column)).join('\n');
|
|
2950
|
+
const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(before, 'typescript'));
|
|
2951
|
+
const name = ref.referenceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2952
|
+
const binding = /\bconst\s*\{([^{}]*)\}\s*=\s*([\w$]+)\.getState\s*\(\s*\)/g;
|
|
2953
|
+
// Compare block identities, not just nesting depth: a binding in a sibling
|
|
2954
|
+
// or already-closed block is not in scope at this call.
|
|
2955
|
+
const stackAt = (end) => {
|
|
2956
|
+
const stack = [];
|
|
2957
|
+
for (let i = 0; i < end; i++) {
|
|
2958
|
+
if (code[i] === '{')
|
|
2959
|
+
stack.push(i);
|
|
2960
|
+
else if (code[i] === '}')
|
|
2961
|
+
stack.pop();
|
|
2962
|
+
}
|
|
2963
|
+
return stack;
|
|
2964
|
+
};
|
|
2965
|
+
const callScope = stackAt(code.length);
|
|
2966
|
+
for (const m of [...code.matchAll(binding)].reverse()) {
|
|
2967
|
+
// Plain named bindings only; defaults, rest and computed keys need their
|
|
2968
|
+
// own value tracing rather than a same-name guess.
|
|
2969
|
+
if (!m[1].split(',').some(part => part.trim() === ref.referenceName))
|
|
2970
|
+
continue;
|
|
2971
|
+
const scope = stackAt(m.index);
|
|
2972
|
+
if (!scope.every((pos, i) => callScope[i] === pos))
|
|
2973
|
+
continue;
|
|
2974
|
+
const rest = code.slice(m.index + m[0].length);
|
|
2975
|
+
// Keep the guard when another declaration shadows the captured const.
|
|
2976
|
+
if (new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${name}\\b|\\{[^}]*\\b${name}\\b)`).test(rest))
|
|
2977
|
+
return null;
|
|
2978
|
+
return resolveStoreAction(`${m[2]}.getState`, ref.referenceName, ref, context);
|
|
2979
|
+
}
|
|
2980
|
+
return null;
|
|
2981
|
+
}
|
|
2982
|
+
/** Bound action names need not have a same-named definition (selectors may
|
|
2983
|
+
* rename them). The resolver's symbol-existence prefilter must allow them. */
|
|
2984
|
+
function matchJsStoreBindingCall(ref, context) {
|
|
2985
|
+
if (!isBareJsCall(ref, context))
|
|
2986
|
+
return null;
|
|
2987
|
+
return matchDestructuredStoreCall(ref, context) ?? matchSelectedStoreCall(ref, context);
|
|
2988
|
+
}
|
|
2989
|
+
/** A qualified untyped chain is useful source evidence, not permission to
|
|
2990
|
+
* infer a property type. Framework resolution runs before this guard. */
|
|
2991
|
+
function isUnresolvedJsMemberCall(ref) {
|
|
2992
|
+
return ref.referenceKind === 'calls' && JS_FAMILY.has(ref.language) &&
|
|
2993
|
+
!/^(?:this|window)\./.test(ref.referenceName) &&
|
|
2994
|
+
/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*){2,}$/.test(ref.referenceName);
|
|
2995
|
+
}
|
|
2996
|
+
const SELECTOR_NAMES = new WeakMap();
|
|
2997
|
+
/** A selector returns the named action from one identified store. Keep the
|
|
2998
|
+
* lexical block identity so closures may capture it but sibling scopes and
|
|
2999
|
+
* shadowing parameters/declarations cannot donate a binding. */
|
|
3000
|
+
function matchSelectedStoreCall(ref, context) {
|
|
3001
|
+
const source = context.readFile(ref.filePath);
|
|
3002
|
+
if (!source?.includes('=>'))
|
|
3003
|
+
return null;
|
|
3004
|
+
let files = SELECTOR_NAMES.get(context);
|
|
3005
|
+
if (!files) {
|
|
3006
|
+
files = new Map();
|
|
3007
|
+
SELECTOR_NAMES.set(context, files);
|
|
3008
|
+
}
|
|
3009
|
+
let names = files.get(ref.filePath);
|
|
3010
|
+
if (!names) {
|
|
3011
|
+
names = new Set([...source.matchAll(/\bconst\s+([\w$]+)\s*=\s*[\w$]+\s*\(\s*(?:\(\s*[\w$]+\s*\)|[\w$]+)\s*=>/g)].map(m => m[1]));
|
|
3012
|
+
files.set(ref.filePath, names);
|
|
3013
|
+
}
|
|
3014
|
+
if (!names.has(ref.referenceName))
|
|
3015
|
+
return null;
|
|
3016
|
+
const name = ref.referenceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3017
|
+
const lines = source.split('\n');
|
|
3018
|
+
const before = lines.slice(0, ref.line - 1).concat(lines[ref.line - 1].slice(0, ref.column)).join('\n');
|
|
3019
|
+
const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(before, 'typescript'));
|
|
3020
|
+
const binding = new RegExp(`\\bconst\\s+${name}\\s*=\\s*([\\w$]+)\\s*\\(\\s*(?:\\(\\s*([\\w$]+)\\s*\\)|([\\w$]+))\\s*=>\\s*([\\w$]+)\\.([\\w$]+)\\s*\\)`, 'g');
|
|
3021
|
+
const stackAt = (end) => {
|
|
3022
|
+
const stack = [];
|
|
3023
|
+
for (let i = 0; i < end; i++) {
|
|
3024
|
+
if (code[i] === '{')
|
|
3025
|
+
stack.push(i);
|
|
3026
|
+
else if (code[i] === '}')
|
|
3027
|
+
stack.pop();
|
|
3028
|
+
}
|
|
3029
|
+
return stack;
|
|
3030
|
+
};
|
|
3031
|
+
const callScope = stackAt(code.length);
|
|
3032
|
+
for (const m of [...code.matchAll(binding)].reverse()) {
|
|
3033
|
+
if ((m[2] ?? m[3]) !== m[4])
|
|
3034
|
+
continue;
|
|
3035
|
+
if (!stackAt(m.index).every((pos, i) => callScope[i] === pos))
|
|
3036
|
+
continue;
|
|
3037
|
+
const rest = code.slice(m.index + m[0].length);
|
|
3038
|
+
if (new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${name}\\b|\\{[^}]*\\b${name}\\b)`).test(rest) ||
|
|
3039
|
+
hasParameterBinding(rest, name))
|
|
3040
|
+
return null;
|
|
3041
|
+
return resolveStoreAction(`${m[1]}.getState`, m[5], ref, context, true);
|
|
3042
|
+
}
|
|
3043
|
+
return null;
|
|
3044
|
+
}
|
|
3045
|
+
/** Import resolution names the module binding; a nearer parameter or block
|
|
3046
|
+
* declaration can shadow that binding at this particular call site. */
|
|
3047
|
+
function importShadowedAt(name, ref, context) {
|
|
3048
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3049
|
+
for (const fn of context.getNodesInFile(ref.filePath)) {
|
|
3050
|
+
if ((fn.kind === 'function' || fn.kind === 'method') && fn.startLine <= ref.line && fn.endLine >= ref.line &&
|
|
3051
|
+
fn.signature && hasParameterBinding(`${fn.signature} {`, escaped))
|
|
3052
|
+
return true;
|
|
3053
|
+
}
|
|
3054
|
+
const lines = (context.readFile(ref.filePath) ?? '').split('\n');
|
|
3055
|
+
const before = lines.slice(0, ref.line - 1).concat(lines[ref.line - 1]?.slice(0, ref.column) ?? '').join('\n');
|
|
3056
|
+
const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(before, 'typescript'));
|
|
3057
|
+
const stackAt = (end) => {
|
|
3058
|
+
const stack = [];
|
|
3059
|
+
for (let i = 0; i < end; i++) {
|
|
3060
|
+
if (code[i] === '{')
|
|
3061
|
+
stack.push(i);
|
|
3062
|
+
else if (code[i] === '}')
|
|
3063
|
+
stack.pop();
|
|
3064
|
+
}
|
|
3065
|
+
return stack;
|
|
3066
|
+
};
|
|
3067
|
+
const scope = stackAt(code.length);
|
|
3068
|
+
const declarations = new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${escaped}\\b|\\{[^}]*\\b${escaped}\\b)`, 'g');
|
|
3069
|
+
return [...code.matchAll(declarations)].some(m => stackAt(m.index).every((p, i) => scope[i] === p));
|
|
3070
|
+
}
|
|
3071
|
+
/** Balanced parameter lists also cover function-typed parameters, whose own
|
|
3072
|
+
* parentheses must not make the outer shadow invisible. Conservative when a
|
|
3073
|
+
* parameter's type mentions the same name: leave that call unresolved. */
|
|
3074
|
+
function hasParameterBinding(code, escapedName) {
|
|
3075
|
+
const name = new RegExp(`\\b${escapedName}\\b`);
|
|
3076
|
+
if (new RegExp(`\\b${escapedName}\\s*=>`).test(code))
|
|
3077
|
+
return true;
|
|
3078
|
+
for (let i = 0; i < code.length; i++) {
|
|
3079
|
+
if (code[i] !== '(' || /\b(?:if|while|for|switch|with)\s*$/.test(code.slice(0, i)))
|
|
3080
|
+
continue;
|
|
3081
|
+
let depth = 1, j = i + 1;
|
|
3082
|
+
for (; j < code.length && depth; j++) {
|
|
3083
|
+
if (code[j] === '(')
|
|
3084
|
+
depth++;
|
|
3085
|
+
else if (code[j] === ')')
|
|
3086
|
+
depth--;
|
|
3087
|
+
}
|
|
3088
|
+
if (depth === 0 && name.test(code.slice(i + 1, j - 1)) &&
|
|
3089
|
+
/^\s*(?::[^=;{]*)?(?:=>|\{)/.test(code.slice(j)))
|
|
3090
|
+
return true;
|
|
3091
|
+
}
|
|
3092
|
+
return false;
|
|
3093
|
+
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Split a camelCase or PascalCase string into words.
|
|
3096
|
+
*/
|
|
3097
|
+
function splitCamelCase(str) {
|
|
3098
|
+
return str.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
3099
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
3100
|
+
.split(/[\s._:\/\\]+/)
|
|
3101
|
+
.filter(w => w.length > 1);
|
|
3102
|
+
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Compute directory proximity from a pre-split list of directory segments
|
|
3105
|
+
* (`filePath1` minus its filename) and a second file path.
|
|
3106
|
+
* Returns a score based on the number of shared leading directory segments.
|
|
3107
|
+
* Higher score = closer in directory tree.
|
|
3108
|
+
*
|
|
3109
|
+
* Split into a pre-split variant because findBestMatch scores every candidate
|
|
3110
|
+
* against the SAME `ref.filePath`; re-splitting it per candidate was a hot spot
|
|
3111
|
+
* on large repos (#915), so the caller splits it once and passes the segments.
|
|
3112
|
+
*/
|
|
3113
|
+
function pathProximityFromDirs(dir1, filePath2) {
|
|
3114
|
+
const dir2 = filePath2.split('/');
|
|
3115
|
+
dir2.pop(); // drop filename — matches the original slice(0, -1) on both paths
|
|
3116
|
+
let shared = 0;
|
|
3117
|
+
const limit = Math.min(dir1.length, dir2.length);
|
|
3118
|
+
for (let i = 0; i < limit; i++) {
|
|
3119
|
+
if (dir1[i] === dir2[i]) {
|
|
3120
|
+
shared++;
|
|
3121
|
+
}
|
|
3122
|
+
else {
|
|
3123
|
+
break;
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
// Each shared directory segment contributes 15 points, capped at 80
|
|
3127
|
+
return Math.min(shared * 15, 80);
|
|
3128
|
+
}
|
|
3129
|
+
/**
|
|
3130
|
+
* Compute directory proximity between two file paths.
|
|
3131
|
+
* Returns a score based on the number of shared directory segments.
|
|
3132
|
+
*/
|
|
3133
|
+
function computePathProximity(filePath1, filePath2) {
|
|
3134
|
+
const dir1 = filePath1.split('/');
|
|
3135
|
+
dir1.pop();
|
|
3136
|
+
return pathProximityFromDirs(dir1, filePath2);
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Find the best matching node when there are multiple candidates
|
|
3140
|
+
*/
|
|
3141
|
+
function findBestMatch(ref, candidates, _context) {
|
|
3142
|
+
// Prioritization rules:
|
|
3143
|
+
// 1. Same file > different file
|
|
3144
|
+
// 2. Directory proximity (same module/package > different module)
|
|
3145
|
+
// 3. Same language > different language
|
|
3146
|
+
// 4. Functions/methods > classes/types (for call references)
|
|
3147
|
+
// 5. Exported > non-exported
|
|
3148
|
+
let bestScore = -1;
|
|
3149
|
+
let bestNode = null;
|
|
3150
|
+
// Split the ref's path once (it's the same across every candidate) instead of
|
|
3151
|
+
// re-splitting it inside computePathProximity per candidate (#915 hot spot).
|
|
3152
|
+
const refDirs = ref.filePath.split('/');
|
|
3153
|
+
refDirs.pop();
|
|
3154
|
+
// A same-language candidate ALWAYS outscores a cross-language one: same-language
|
|
3155
|
+
// scores at least +50 (language bonus), while a cross-language candidate maxes
|
|
3156
|
+
// out at +35 (−80 language, +80 proximity, +25 kind, +10 exported; it can never
|
|
3157
|
+
// be in the same file). So when any same-language candidate exists, skip the
|
|
3158
|
+
// cross-language ones — provably the same winner, without paying the per-candidate
|
|
3159
|
+
// scoring. Cuts the candidate set to same-language size on mixed front-end +
|
|
3160
|
+
// back-end repos (#915). When ALL candidates are cross-language (a legitimate
|
|
3161
|
+
// cross-language `calls` bridge), none are skipped and behavior is unchanged.
|
|
3162
|
+
const hasSameLanguage = candidates.some((c) => c.language === ref.language);
|
|
3163
|
+
for (const candidate of candidates) {
|
|
3164
|
+
if (hasSameLanguage && candidate.language !== ref.language)
|
|
3165
|
+
continue;
|
|
3166
|
+
let score = 0;
|
|
3167
|
+
// Same file bonus
|
|
3168
|
+
if (candidate.filePath === ref.filePath) {
|
|
3169
|
+
score += 100;
|
|
3170
|
+
}
|
|
3171
|
+
// Directory proximity bonus — strongly prefer same module/package
|
|
3172
|
+
score += pathProximityFromDirs(refDirs, candidate.filePath);
|
|
3173
|
+
// Language matching: strongly prefer same language, penalize cross-language
|
|
3174
|
+
if (candidate.language === ref.language) {
|
|
3175
|
+
score += 50;
|
|
3176
|
+
}
|
|
3177
|
+
else {
|
|
3178
|
+
score -= 80;
|
|
3179
|
+
}
|
|
3180
|
+
// For call references, prefer functions/methods
|
|
3181
|
+
if (ref.referenceKind === 'calls') {
|
|
3182
|
+
if (candidate.kind === 'function' || candidate.kind === 'method') {
|
|
3183
|
+
score += 25;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
// For instantiation references (`new Foo()`), prefer class-like
|
|
3187
|
+
// targets — without this, a function named `Foo` in another module
|
|
3188
|
+
// could outscore the actual class.
|
|
3189
|
+
if (ref.referenceKind === 'instantiates') {
|
|
3190
|
+
if (candidate.kind === 'class' ||
|
|
3191
|
+
candidate.kind === 'struct' ||
|
|
3192
|
+
candidate.kind === 'union' ||
|
|
3193
|
+
candidate.kind === 'interface') {
|
|
3194
|
+
score += 25;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
// For decorator references (`@Foo`), prefer functions. Class
|
|
3198
|
+
// decorators (Python `@SomeClass`, Java annotation interfaces)
|
|
3199
|
+
// also resolve here, hence the smaller class bonus.
|
|
3200
|
+
if (ref.referenceKind === 'decorates') {
|
|
3201
|
+
if (candidate.kind === 'function' || candidate.kind === 'method') {
|
|
3202
|
+
score += 25;
|
|
3203
|
+
}
|
|
3204
|
+
else if (candidate.kind === 'class' || candidate.kind === 'interface') {
|
|
3205
|
+
score += 15;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
// Exported bonus
|
|
3209
|
+
if (candidate.isExported) {
|
|
3210
|
+
score += 10;
|
|
3211
|
+
}
|
|
3212
|
+
// Closer line number (within same file)
|
|
3213
|
+
if (candidate.filePath === ref.filePath && candidate.startLine) {
|
|
3214
|
+
const distance = Math.abs(candidate.startLine - ref.line);
|
|
3215
|
+
score += Math.max(0, 20 - distance / 10);
|
|
3216
|
+
}
|
|
3217
|
+
if (score > bestScore) {
|
|
3218
|
+
bestScore = score;
|
|
3219
|
+
bestNode = candidate;
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
return bestNode;
|
|
3223
|
+
}
|
|
3224
|
+
/**
|
|
3225
|
+
* Fuzzy match - last resort with lower confidence
|
|
3226
|
+
*/
|
|
3227
|
+
function matchFuzzy(ref, context) {
|
|
3228
|
+
const lowerName = ref.referenceName.toLowerCase();
|
|
3229
|
+
// Use pre-built lowercase index for O(1) lookup instead of scanning all nodes
|
|
3230
|
+
const candidates = context.getNodesByLowerName(lowerName);
|
|
3231
|
+
// Filter to callable kinds only (function, method, class)
|
|
3232
|
+
const callableKinds = new Set(['function', 'method', 'class']);
|
|
3233
|
+
const callableCandidates = applyLanguageGate(candidates.filter((n) => callableKinds.has(n.kind)), ref);
|
|
3234
|
+
// Prefer same-language matches
|
|
3235
|
+
const sameLanguageCandidates = callableCandidates.filter(n => n.language === ref.language);
|
|
3236
|
+
const finalCandidates = sameLanguageCandidates.length > 0 ? sameLanguageCandidates : callableCandidates;
|
|
3237
|
+
// Both post-pipeline visibility guards (#1745 language-local + #1719 sealed
|
|
3238
|
+
// module). The sealed-module test rejects the survivor and never filters the
|
|
3239
|
+
// set that produced it: removing a sealed candidate from a crowd would leave
|
|
3240
|
+
// a lone one and manufacture a 0.5 guess out of an ambiguity fuzzy declines.
|
|
3241
|
+
// Also decline a bare JS/TS call whose only survivor is a method or a
|
|
3242
|
+
// cross-file name the file already binds locally (#1714).
|
|
3243
|
+
// A function nested inside another function is only callable from inside
|
|
3244
|
+
// its container (#1230), so a builtin method call (`res.text()`) whose only
|
|
3245
|
+
// same-named project symbol is some file's closure must decline (#1708).
|
|
3246
|
+
// The check sits on the ONE candidate this strategy would commit to, not on
|
|
3247
|
+
// the candidate set: filtering the unreachable ones out of a crowd would
|
|
3248
|
+
// leave a single survivor and hand it every call of that name — on vite,
|
|
3249
|
+
// `import { resolve } from 'node:path'` in a dozen playground configs onto
|
|
3250
|
+
// the one reachable `resolve` method (#1709). Reachability may reject a
|
|
3251
|
+
// unique guess; it must never manufacture one.
|
|
3252
|
+
if (finalCandidates.length === 1 &&
|
|
3253
|
+
isVisibleAcrossFiles(finalCandidates[0], ref, context) &&
|
|
3254
|
+
isCrossFileReachable(finalCandidates[0], ref, context) &&
|
|
3255
|
+
!(isBareJsCall(ref, context) &&
|
|
3256
|
+
(finalCandidates[0].kind === 'method' ||
|
|
3257
|
+
(finalCandidates[0].filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context)))) &&
|
|
3258
|
+
isLexicallyReachable(finalCandidates[0], ref, context)) {
|
|
3259
|
+
const isCrossLanguage = finalCandidates[0].language !== ref.language;
|
|
3260
|
+
return {
|
|
3261
|
+
original: ref,
|
|
3262
|
+
targetNodeId: finalCandidates[0].id,
|
|
3263
|
+
confidence: isCrossLanguage ? 0.3 : 0.5,
|
|
3264
|
+
resolvedBy: 'fuzzy',
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
return null;
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Match all strategies in order of confidence
|
|
3271
|
+
*/
|
|
3272
|
+
/** ArkUI attribute-helper decorators a `.attr(...)` chain may resolve to. */
|
|
3273
|
+
const ARKUI_ATTRIBUTE_DECORATORS = new Set(['Extend', 'Styles', 'AnimatableExtend', 'Builder']);
|
|
3274
|
+
/**
|
|
3275
|
+
* CODEGRAPH_RESOLVE_PROFILE=2 sub-stage attribution for matchReference's
|
|
3276
|
+
* strategy pipeline (`nm:<stage>|<refKind>|hit/miss`). Module-global because
|
|
3277
|
+
* the matcher is a free function; each thread (main + every pool worker) has
|
|
3278
|
+
* its own module instance, and dumpNameMatcherProfile is invoked from
|
|
3279
|
+
* ReferenceResolver.dumpResolveProfile so worker tables surface too.
|
|
3280
|
+
*/
|
|
3281
|
+
const NM_PROFILE = process.env.CODEGRAPH_RESOLVE_PROFILE === '2' ? new Map() : null;
|
|
3282
|
+
function nmTimedT(stage, ref, fn) {
|
|
3283
|
+
if (!NM_PROFILE)
|
|
3284
|
+
return fn();
|
|
3285
|
+
const t0 = process.hrtime.bigint();
|
|
3286
|
+
const r = fn();
|
|
3287
|
+
const dt = process.hrtime.bigint() - t0;
|
|
3288
|
+
const key = `nm:${stage}|${ref.referenceKind}|${r ? 'hit' : 'miss'}`;
|
|
3289
|
+
const slot = NM_PROFILE.get(key);
|
|
3290
|
+
if (slot) {
|
|
3291
|
+
slot.n++;
|
|
3292
|
+
slot.ns += dt;
|
|
3293
|
+
}
|
|
3294
|
+
else {
|
|
3295
|
+
NM_PROFILE.set(key, { n: 1, ns: dt });
|
|
3296
|
+
}
|
|
3297
|
+
return r;
|
|
3298
|
+
}
|
|
3299
|
+
function nmTimed(stage, ref, fn) {
|
|
3300
|
+
return nmTimedT(stage, ref, fn);
|
|
3301
|
+
}
|
|
3302
|
+
/** Dump this thread's matchReference sub-stage table to stderr (no-op unless =2). */
|
|
3303
|
+
function dumpNameMatcherProfile(label) {
|
|
3304
|
+
if (!NM_PROFILE || NM_PROFILE.size === 0)
|
|
3305
|
+
return;
|
|
3306
|
+
const rows = [...NM_PROFILE.entries()]
|
|
3307
|
+
.map(([k, v]) => ({ k, n: v.n, ms: Number(v.ns / 1000000n) }))
|
|
3308
|
+
.sort((a, b) => b.ms - a.ms);
|
|
3309
|
+
for (const r of rows) {
|
|
3310
|
+
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`);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
function matchReference(ref, context) {
|
|
3314
|
+
// Function-as-value refs (#756) resolve ONLY through the dedicated matcher —
|
|
3315
|
+
// never the fuzzy/qualified fallthrough below (a wrong callback edge is
|
|
3316
|
+
// worse than none).
|
|
3317
|
+
if (ref.referenceKind === 'function_ref') {
|
|
3318
|
+
return matchFunctionRef(ref, context);
|
|
3319
|
+
}
|
|
3320
|
+
// ArkTS chained UI attributes — emitted with a leading dot (`.titleStyle`,
|
|
3321
|
+
// `.width`) by the extractor — resolve ONLY to decorator-marked attribute
|
|
3322
|
+
// helpers: `@Extend`/`@Styles`/`@AnimatableExtend` functions (and global
|
|
3323
|
+
// `@Builder`s used attribute-position). Framework attributes (`.width`,
|
|
3324
|
+
// `.fontSize` — on nearly every UI line) match no such helper and stay
|
|
3325
|
+
// unresolved, NEVER falling through to bare-name matching: on a samples
|
|
3326
|
+
// monorepo that fallthrough manufactured 36k wrong edges, giving single
|
|
3327
|
+
// same-named properties thousands of false callers. Ambiguity rule matches
|
|
3328
|
+
// the rest of the file: several same-named helpers → prefer the call-site
|
|
3329
|
+
// file, still ambiguous → drop the ref rather than guess.
|
|
3330
|
+
if (ref.language === 'arkts' && ref.referenceName.startsWith('.')) {
|
|
3331
|
+
const base = ref.referenceName.slice(1);
|
|
3332
|
+
const candidates = context
|
|
3333
|
+
.getNodesByName(base)
|
|
3334
|
+
.filter((n) => n.language === 'arkts' &&
|
|
3335
|
+
n.kind === 'function' &&
|
|
3336
|
+
(n.decorators ?? []).some((d) => ARKUI_ATTRIBUTE_DECORATORS.has(d)));
|
|
3337
|
+
const chosen = candidates.length > 1 ? preferCallSiteFile(candidates, ref.filePath) : candidates;
|
|
3338
|
+
if (chosen.length !== 1)
|
|
3339
|
+
return null;
|
|
3340
|
+
return {
|
|
3341
|
+
original: ref,
|
|
3342
|
+
targetNodeId: chosen[0].id,
|
|
3343
|
+
confidence: 0.85,
|
|
3344
|
+
resolvedBy: 'exact-match',
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
// Erlang `-behaviour(m)` refs target a MODULE. Letting them fall through to
|
|
3348
|
+
// bare-name matching grabs any same-named symbol — on emqx,
|
|
3349
|
+
// `-behaviour(supervisor)` resolved to a `-define(supervisor, …)` macro
|
|
3350
|
+
// constant in an unrelated app. Resolve only to the behaviour module's
|
|
3351
|
+
// namespace; an out-of-repo behaviour (OTP's gen_server/supervisor) stays
|
|
3352
|
+
// unresolved rather than guessed. The same module-only rule applies to every
|
|
3353
|
+
// ref an `.app`/`.app.src` resource file emits — its `{mod, …}` callback and
|
|
3354
|
+
// `{applications, …}` dependency names can only mean modules, and on emqx
|
|
3355
|
+
// the `ssl` OTP app otherwise resolved to a test helper FUNCTION named ssl.
|
|
3356
|
+
if (ref.language === 'erlang' &&
|
|
3357
|
+
(ref.referenceKind === 'implements' || /\.app(?:\.src)?$/i.test(ref.filePath))) {
|
|
3358
|
+
const modules = context
|
|
3359
|
+
.getNodesByName(ref.referenceName)
|
|
3360
|
+
.filter((n) => n.language === 'erlang' && n.kind === 'namespace');
|
|
3361
|
+
const chosen = preferCallSiteFile(modules, ref.filePath)[0];
|
|
3362
|
+
if (!chosen)
|
|
3363
|
+
return null;
|
|
3364
|
+
return {
|
|
3365
|
+
original: ref,
|
|
3366
|
+
targetNodeId: chosen.id,
|
|
3367
|
+
confidence: 0.9,
|
|
3368
|
+
resolvedBy: 'exact-match',
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
// Erlang call/fun refs carry the call-site arity (`f/1` — #1610) because
|
|
3372
|
+
// arity is part of the function's identity and every erlang function's
|
|
3373
|
+
// qualifiedName carries it (`mod::f/1`). Resolve ONLY to a definition of
|
|
3374
|
+
// that exact arity: the call site's own file first (a local call targets its
|
|
3375
|
+
// own module by language semantics; `-import`ed functions ride the
|
|
3376
|
+
// cross-file branch), and when no definition of that arity exists anywhere,
|
|
3377
|
+
// resolve to NOTHING rather than a sibling arity — the real target may be
|
|
3378
|
+
// macro-generated or out of repo, and a wrong-arity edge is worse than none.
|
|
3379
|
+
if (ref.language === 'erlang' &&
|
|
3380
|
+
!ref.referenceName.includes('::') &&
|
|
3381
|
+
(ref.referenceKind === 'calls' || ref.referenceKind === 'references')) {
|
|
3382
|
+
const am = /^(.+)\/(\d{1,3})$/.exec(ref.referenceName);
|
|
3383
|
+
if (am) {
|
|
3384
|
+
// endsWith is length-anchored, so `/1` cannot match `…/11`.
|
|
3385
|
+
const arityTail = `/${am[2]}`;
|
|
3386
|
+
const candidates = context
|
|
3387
|
+
.getNodesByName(am[1])
|
|
3388
|
+
.filter((n) => n.language === 'erlang' && n.kind === 'function' && n.qualifiedName.endsWith(arityTail));
|
|
3389
|
+
if (candidates.length > 0) {
|
|
3390
|
+
const sameFile = candidates.find((n) => n.filePath === ref.filePath);
|
|
3391
|
+
if (sameFile) {
|
|
3392
|
+
return { original: ref, targetNodeId: sameFile.id, confidence: 0.95, resolvedBy: 'exact-match' };
|
|
3393
|
+
}
|
|
3394
|
+
if (candidates.length === 1) {
|
|
3395
|
+
return { original: ref, targetNodeId: candidates[0].id, confidence: 0.8, resolvedBy: 'exact-match' };
|
|
3396
|
+
}
|
|
3397
|
+
const best = findBestMatch(ref, candidates, context);
|
|
3398
|
+
if (best) {
|
|
3399
|
+
const proximity = computePathProximity(ref.filePath, best.filePath);
|
|
3400
|
+
return {
|
|
3401
|
+
original: ref,
|
|
3402
|
+
targetNodeId: best.id,
|
|
3403
|
+
confidence: proximity >= 30 ? 0.7 : 0.4,
|
|
3404
|
+
resolvedBy: 'exact-match',
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
return null;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
if (isUnresolvedJsMemberCall(ref))
|
|
3412
|
+
return null;
|
|
3413
|
+
// Try strategies in order of confidence
|
|
3414
|
+
let result;
|
|
3415
|
+
// 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
|
|
3416
|
+
result = nmTimed('filePath', ref, () => matchByFilePath(ref, context));
|
|
3417
|
+
if (result)
|
|
3418
|
+
return result;
|
|
3419
|
+
// 1. Qualified name match (highest confidence)
|
|
3420
|
+
result = nmTimed('qualifiedName', ref, () => matchByQualifiedName(ref, context));
|
|
3421
|
+
if (result)
|
|
3422
|
+
return result;
|
|
3423
|
+
// 1b. C++ chained call whose receiver is another call — `Foo::instance().bar()`
|
|
3424
|
+
// encoded as `Foo::instance().bar` by the extractor (#645). Resolve the
|
|
3425
|
+
// receiver's type from what the inner call returns, then the method on it.
|
|
3426
|
+
if (ref.language === 'cpp' || ref.language === 'c') {
|
|
3427
|
+
result = nmTimed('cppChain', ref, () => matchCppCallChain(ref, context));
|
|
3428
|
+
if (result)
|
|
3429
|
+
return result;
|
|
3430
|
+
}
|
|
3431
|
+
// 1c. `::`-scoped factory chain — PHP `Cls::for($x)->method()` (#608) or Rust
|
|
3432
|
+
// `Foo::new().bar()`, both encoded as `Cls::factory().method`. The receiver's
|
|
3433
|
+
// type is the factory's `self` (PHP `: self`/`: static`, Rust `-> Self`) or
|
|
3434
|
+
// concrete return type.
|
|
3435
|
+
if (ref.language === 'php' || ref.language === 'rust') {
|
|
3436
|
+
result = nmTimed('scopedChain', ref, () => matchScopedCallChain(ref, context));
|
|
3437
|
+
if (result)
|
|
3438
|
+
return result;
|
|
3439
|
+
}
|
|
3440
|
+
// 1d. Dotted chained static-factory / fluent call (Java / Kotlin / C# / Swift /
|
|
3441
|
+
// Go / Scala / Dart / Objective-C) — `Foo.getInstance().bar()` encoded as
|
|
3442
|
+
// `Foo.getInstance().bar`, Go's bare-factory `New().Method()` as `New().Method`,
|
|
3443
|
+
// Scala's companion factory, Dart's static factory / factory-constructor, or
|
|
3444
|
+
// ObjC's chained message send `[[Foo create] doIt]` encoded as `Foo.create().doIt`
|
|
3445
|
+
// (#645/#608 mechanism). Resolve the method's class from the inner call's
|
|
3446
|
+
// declared return type, then validate it.
|
|
3447
|
+
if (ref.language === 'java' ||
|
|
3448
|
+
ref.language === 'kotlin' ||
|
|
3449
|
+
ref.language === 'csharp' ||
|
|
3450
|
+
ref.language === 'swift' ||
|
|
3451
|
+
ref.language === 'go' ||
|
|
3452
|
+
ref.language === 'scala' ||
|
|
3453
|
+
ref.language === 'dart' ||
|
|
3454
|
+
ref.language === 'objc' ||
|
|
3455
|
+
ref.language === 'pascal') {
|
|
3456
|
+
result = nmTimed('dottedChain', ref, () => matchDottedCallChain(ref, context));
|
|
3457
|
+
if (result)
|
|
3458
|
+
return result;
|
|
3459
|
+
}
|
|
3460
|
+
// A call-receiver chain the extractor encoded as `<inner>().<method>` for a
|
|
3461
|
+
// language with no chain resolver above (TS/JS, Python — #1683) is a
|
|
3462
|
+
// receiver whose type is unknown. Nothing below may guess for it: the
|
|
3463
|
+
// method-call pattern rejects the parens, exact name never matches, but the
|
|
3464
|
+
// fuzzy strategy splits on `.` and would hand `make().run` to any `run` —
|
|
3465
|
+
// the fabricated edge the encoding exists to prevent.
|
|
3466
|
+
if (ref.referenceName.includes('().') &&
|
|
3467
|
+
(ref.language === 'typescript' || ref.language === 'javascript' || ref.language === 'tsx' || ref.language === 'jsx' || ref.language === 'python')) {
|
|
3468
|
+
return nmTimed('storeAccessorChain', ref, () => matchStoreAccessorChain(ref, context));
|
|
3469
|
+
}
|
|
3470
|
+
// 2. Method call pattern
|
|
3471
|
+
result = nmTimed('methodCall', ref, () => matchMethodCall(ref, context));
|
|
3472
|
+
if (result)
|
|
3473
|
+
return result;
|
|
3474
|
+
// 3. Exact name match
|
|
3475
|
+
result = nmTimed('exactName', ref, () => matchByExactName(ref, context));
|
|
3476
|
+
if (result)
|
|
3477
|
+
return result;
|
|
3478
|
+
// 4. Fuzzy match (lowest confidence)
|
|
3479
|
+
result = nmTimed('fuzzy', ref, () => matchFuzzy(ref, context));
|
|
3480
|
+
if (result)
|
|
3481
|
+
return result;
|
|
3482
|
+
return null;
|
|
3483
|
+
}
|
|
3484
|
+
//# sourceMappingURL=name-matcher.js.map
|