@backbencher/brain 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +298 -0
- package/EMBEDDINGS.md +355 -0
- package/LICENSE +21 -0
- package/README.md +415 -2
- package/bin/bb +13 -0
- package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
- package/dist/brain-index-health-Q5APTJQL.js +17 -0
- package/dist/brain-relationship-enrichment-MYR2AM4F.js +558 -0
- package/dist/brain.sql-EIM4WS4D.js +29 -0
- package/dist/chunk-22V37YTK.js +1758 -0
- package/dist/chunk-2BSKD7O3.js +40 -0
- package/dist/chunk-2DE27DVC.js +1314 -0
- package/dist/chunk-2RRBMNBQ.js +322 -0
- package/dist/chunk-2WXYNLBA.js +29 -0
- package/dist/chunk-3CTFIJJB.js +17 -0
- package/dist/chunk-3ZTOHHSW.js +2104 -0
- package/dist/chunk-4H22TLOT.js +543 -0
- package/dist/chunk-5BVAH26D.js +340 -0
- package/dist/chunk-62DHLYW6.js +3427 -0
- package/dist/chunk-7AN7U5IZ.js +371 -0
- package/dist/chunk-7DRPNVIN.js +782 -0
- package/dist/chunk-7MBYT777.js +174 -0
- package/dist/chunk-AHDWGM7Q.js +236 -0
- package/dist/chunk-B5GEAAYF.js +818 -0
- package/dist/chunk-BEIZZ73B.js +1479 -0
- package/dist/chunk-BQ3OVQ3P.js +125 -0
- package/dist/chunk-D2Q4Y47A.js +53 -0
- package/dist/chunk-DIYAGQJL.js +34 -0
- package/dist/chunk-DWKXZ6IF.js +336 -0
- package/dist/chunk-DYM4FQEE.js +90 -0
- package/dist/chunk-ENW4BTBA.js +930 -0
- package/dist/chunk-EWMWK7WN.js +75 -0
- package/dist/chunk-EY5ZZUUA.js +626 -0
- package/dist/chunk-GBI7V7KY.js +69 -0
- package/dist/chunk-GEFFO6O2.js +152 -0
- package/dist/chunk-HJEWO5EV.js +247 -0
- package/dist/chunk-HQSPJDHI.js +613 -0
- package/dist/chunk-KAET47ZL.js +125 -0
- package/dist/chunk-KCDECRJY.js +440 -0
- package/dist/chunk-L5TDYKYK.js +76 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-NO3U3FJQ.js +201 -0
- package/dist/chunk-OWISBAOF.js +61 -0
- package/dist/chunk-QKTRECYS.js +1119 -0
- package/dist/chunk-QLK6ED4Q.js +235 -0
- package/dist/chunk-R2GW4F6R.js +19 -0
- package/dist/chunk-RCMTFSEF.js +87 -0
- package/dist/chunk-RM3PY6TG.js +376 -0
- package/dist/chunk-ROMM6KMC.js +433 -0
- package/dist/chunk-SGGCSHAX.js +12050 -0
- package/dist/chunk-SK6YVHFZ.js +211 -0
- package/dist/chunk-SM7RFQRO.js +214 -0
- package/dist/chunk-UGR4QIGS.js +67 -0
- package/dist/chunk-UQNQZXHY.js +711 -0
- package/dist/chunk-UUNHHX4M.js +13 -0
- package/dist/chunk-VL52PL2T.js +71 -0
- package/dist/chunk-WIG36E67.js +196 -0
- package/dist/chunk-XHK3WWGB.js +7436 -0
- package/dist/chunk-XTQM2IP3.js +2863 -0
- package/dist/chunk-ZE2LG76D.js +1296 -0
- package/dist/chunk-ZETTGPAW.js +6950 -0
- package/dist/cli/index.js +37 -0
- package/dist/config-HZAAYDP3.js +10 -0
- package/dist/content-search-7XIP555Q.js +593 -0
- package/dist/database-76RP44JG.js +9 -0
- package/dist/doctor-ANGETYMN.js +395 -0
- package/dist/edit-engine-CG72LDZD.js +51 -0
- package/dist/engine-BKEOGN7N.js +11 -0
- package/dist/engine-VIW6NSKO.js +18 -0
- package/dist/fingerprint-OBLIMZLP.js +33 -0
- package/dist/framework-intelligence-3LORTPOX.js +860 -0
- package/dist/freshness-3K6VU5OT.js +174 -0
- package/dist/graph-engine-HBSQNPTB.js +21 -0
- package/dist/graph_request-Q6NKB6N2.js +350 -0
- package/dist/impact_request-LYP672WE.js +68 -0
- package/dist/index-space-IUUN5WYV.js +15 -0
- package/dist/index-status-7OVGYSGW.js +34 -0
- package/dist/index.js +159 -0
- package/dist/instance-FO6QZRLH.js +19 -0
- package/dist/language-detection-DFCAU5DZ.js +11 -0
- package/dist/lsp/server.js +4301 -0
- package/dist/mcp/server.js +132 -0
- package/dist/onboard-DIQZV2NW.js +161 -0
- package/dist/override-companion-pass-XR7MHTP7.js +117 -0
- package/dist/parallel-indexer-FK2XKZXY.js +88 -0
- package/dist/pool-worker.js +1153 -0
- package/dist/project-health-6XPENVAA.js +94 -0
- package/dist/project-index-5SDPHITE.js +57 -0
- package/dist/project-registry-ZJ3DVUGO.js +355 -0
- package/dist/real-bash-analyzer-EJAU5LW5.js +2201 -0
- package/dist/real-c-analyzer-HBD3FNNH.js +5942 -0
- package/dist/real-clean-analyzer-4EEZLPV5.js +3470 -0
- package/dist/real-clojure-analyzer-Q55EBOO2.js +2818 -0
- package/dist/real-cpp-analyzer-IVVOD25R.js +4101 -0
- package/dist/real-csharp-analyzer-ZBNLNJZJ.js +3298 -0
- package/dist/real-css-analyzer-6XRQLUN7.js +15 -0
- package/dist/real-dart-analyzer-OLR72EFW.js +3944 -0
- package/dist/real-elixir-analyzer-MQWGAH2I.js +2192 -0
- package/dist/real-erlang-analyzer-7BVPQEHO.js +1731 -0
- package/dist/real-fsharp-analyzer-C2UPFNOA.js +2390 -0
- package/dist/real-go-analyzer-UKGI43YS.js +4126 -0
- package/dist/real-graphql-analyzer-VE2EEFII.js +1442 -0
- package/dist/real-groovy-analyzer-W26VACFW.js +2285 -0
- package/dist/real-haskell-analyzer-5CVVAJRX.js +2533 -0
- package/dist/real-hcl-analyzer-5ZT4U43N.js +725 -0
- package/dist/real-html-analyzer-JWNX3C6I.js +1759 -0
- package/dist/real-java-analyzer-GU42VBGQ.js +4677 -0
- package/dist/real-javascript-analyzer-QA4QOJ6W.js +14 -0
- package/dist/real-json-analyzer-MPA6PRXL.js +1640 -0
- package/dist/real-julia-analyzer-22CWYQMN.js +2085 -0
- package/dist/real-kotlin-analyzer-QDZJLXXU.js +3956 -0
- package/dist/real-lua-analyzer-G7NHLYKS.js +1957 -0
- package/dist/real-makefile-analyzer-OCY3GT5N.js +920 -0
- package/dist/real-markdown-analyzer-TEJK2JXJ.js +1215 -0
- package/dist/real-matlab-analyzer-Q5SWLN7Z.js +480 -0
- package/dist/real-nim-analyzer-YLXSK563.js +3603 -0
- package/dist/real-objective-c-analyzer-D23ACC2R.js +1518 -0
- package/dist/real-perl-analyzer-WWTRHYDT.js +1865 -0
- package/dist/real-php-analyzer-JNKJHTCL.js +9368 -0
- package/dist/real-powershell-analyzer-XRSNQERV.js +2915 -0
- package/dist/real-protobuf-analyzer-3OKUCZ7T.js +867 -0
- package/dist/real-python-analyzer-VXAFWI72.js +4880 -0
- package/dist/real-r-analyzer-ZGAIDVI4.js +2596 -0
- package/dist/real-ruby-analyzer-X2KTLVBH.js +2429 -0
- package/dist/real-rust-analyzer-OFFHPL2A.js +3359 -0
- package/dist/real-scala-analyzer-TSTW27J6.js +3293 -0
- package/dist/real-scss-analyzer-AVWYPSOQ.js +1515 -0
- package/dist/real-solidity-analyzer-UCI2KWUB.js +2608 -0
- package/dist/real-sql-analyzer-F77ZMQXR.js +3840 -0
- package/dist/real-svelte-analyzer-SF7NRUO5.js +2532 -0
- package/dist/real-swift-analyzer-MN5QT3QN.js +2311 -0
- package/dist/real-toml-analyzer-66L7BDCQ.js +929 -0
- package/dist/real-typescript-analyzer-6KEMVDWQ.js +14 -0
- package/dist/real-vb-analyzer-VICVPOP7.js +3145 -0
- package/dist/real-vba-analyzer-HXLTTHKM.js +466 -0
- package/dist/real-vue-analyzer-QXR56CFV.js +2680 -0
- package/dist/real-xml-analyzer-YL465ZJW.js +2405 -0
- package/dist/real-yaml-analyzer-SUBCBNSI.js +3528 -0
- package/dist/real-zig-analyzer-HIQLZMGY.js +2430 -0
- package/dist/regex-search-NJKHMZNO.js +239 -0
- package/dist/rules-ACHPB7QU.js +152 -0
- package/dist/scoped-path-MJYWJZQX.js +11 -0
- package/dist/setup-show-NT5ZHYNE.js +15 -0
- package/dist/setup-wizard-G5YYR5T6.js +24 -0
- package/dist/symbol-search-GZJIM2J2.js +16 -0
- package/dist/taint-KXUHHPIE.js +202 -0
- package/dist/taint-analyzer-N7YLZU3N.js +163 -0
- package/dist/tool-handlers-OJDC35DI.js +72 -0
- package/dist/tool-registry-BPEJNSZG.js +17 -0
- package/dist/tool-validation-Q57YMMBX.js +32 -0
- package/dist/type-inference-QMT2RMLV.js +13 -0
- package/dist/vector-binary-LMY4QGAN.js +19 -0
- package/dist/version-ABP2TUL2.js +9 -0
- package/dist/wasm-NMWO3WXY.js +16 -0
- package/docs/EMBEDDINGS.md +132 -0
- package/docs/README.md +178 -0
- package/docs/data/m10-baseline.json +602 -0
- package/docs/data/m10-benchmark-baseline.json +57 -0
- package/docs/data/m10-benchmark-last-run.json +127 -0
- package/docs/data/m10-final-audit.json +215 -0
- package/docs/data/m12-final-audit.json +138 -0
- package/docs/data/m12-surface-matrix.json +50 -0
- package/docs/data/public-tools.json +1902 -0
- package/docs/indexing/DEAD-ASSETS.md +88 -0
- package/docs/indexing/INDEXER-REVIEW.md +634 -0
- package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
- package/docs/indexing/README.md +427 -0
- package/docs/indexing/discovery-matrix.md +41 -0
- package/docs/indexing/embeddings.md +325 -0
- package/docs/indexing/environment-variables.md +77 -0
- package/docs/indexing/performance.md +323 -0
- package/docs/indexing/resume.md +345 -0
- package/docs/indexing/troubleshooting.md +391 -0
- package/docs/man/bb.1 +190 -0
- package/docs/tools/bb_analyze.md +147 -0
- package/docs/tools/bb_annotate.md +110 -0
- package/docs/tools/bb_annotations.md +106 -0
- package/docs/tools/bb_ask.md +101 -0
- package/docs/tools/bb_cross_language.md +103 -0
- package/docs/tools/bb_doc.md +169 -0
- package/docs/tools/bb_edit.md +185 -0
- package/docs/tools/bb_graph.md +169 -0
- package/docs/tools/bb_health.md +156 -0
- package/docs/tools/bb_impact.md +117 -0
- package/docs/tools/bb_index.md +108 -0
- package/docs/tools/bb_migrate.md +135 -0
- package/docs/tools/bb_patterns.md +140 -0
- package/docs/tools/bb_search.md +150 -0
- package/docs/tools/bb_security.md +100 -0
- package/docs/tools/bb_visualize.md +184 -0
- package/grammars/README.md +57 -0
- package/grammars/THIRD-PARTY-NOTICES.md +50 -0
- package/grammars/tree-sitter-c/LICENSE +21 -0
- package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
- package/grammars/tree-sitter-cpp/LICENSE +21 -0
- package/grammars/tree-sitter-csharp/LICENSE +21 -0
- package/grammars/tree-sitter-css/LICENSE +21 -0
- package/grammars/tree-sitter-css/README.md +21 -0
- package/grammars/tree-sitter-dart/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/README.md +18 -0
- package/grammars/tree-sitter-elixir/LICENSE +176 -0
- package/grammars/tree-sitter-elixir/README.md +11 -0
- package/grammars/tree-sitter-fsharp/LICENSE +21 -0
- package/grammars/tree-sitter-fsharp/README.md +45 -0
- package/grammars/tree-sitter-graphql/LICENSE +21 -0
- package/grammars/tree-sitter-graphql/README.md +106 -0
- package/grammars/tree-sitter-groovy/LICENSE +21 -0
- package/grammars/tree-sitter-groovy/README.md +4 -0
- package/grammars/tree-sitter-haskell/LICENSE +21 -0
- package/grammars/tree-sitter-haskell/README.md +394 -0
- package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
- package/grammars/tree-sitter-hcl/LICENSE +201 -0
- package/grammars/tree-sitter-hcl/README.md +39 -0
- package/grammars/tree-sitter-html/LICENSE +21 -0
- package/grammars/tree-sitter-html/README.md +21 -0
- package/grammars/tree-sitter-julia/LICENSE +21 -0
- package/grammars/tree-sitter-julia/README.md +24 -0
- package/grammars/tree-sitter-kotlin/LICENSE +9 -0
- package/grammars/tree-sitter-lua/LICENSE.md +21 -0
- package/grammars/tree-sitter-lua/README.md +29 -0
- package/grammars/tree-sitter-make/LICENSE +21 -0
- package/grammars/tree-sitter-matlab/LICENSE +19 -0
- package/grammars/tree-sitter-matlab/README.md +99 -0
- package/grammars/tree-sitter-nim/LICENSE +373 -0
- package/grammars/tree-sitter-nim/README.md +46 -0
- package/grammars/tree-sitter-objc/LICENSE +21 -0
- package/grammars/tree-sitter-objc/README.md +20 -0
- package/grammars/tree-sitter-perl/LICENSE +7 -0
- package/grammars/tree-sitter-perl/README.md +100 -0
- package/grammars/tree-sitter-powershell/LICENSE +21 -0
- package/grammars/tree-sitter-powershell/README.md +7 -0
- package/grammars/tree-sitter-proto/LICENSE +21 -0
- package/grammars/tree-sitter-proto/README.md +105 -0
- package/grammars/tree-sitter-r/LICENSE +9 -0
- package/grammars/tree-sitter-r/README.md +66 -0
- package/grammars/tree-sitter-ruby/LICENSE +22 -0
- package/grammars/tree-sitter-scss/LICENSE +21 -0
- package/grammars/tree-sitter-scss/README.md +17 -0
- package/grammars/tree-sitter-solidity/LICENSE +7 -0
- package/grammars/tree-sitter-sql/LICENSE +21 -0
- package/grammars/tree-sitter-typescript/LICENSE +21 -0
- package/grammars/tree-sitter-vba/LICENSE +21 -0
- package/grammars/tree-sitter-vba/README.md +738 -0
- package/grammars/tree-sitter-vbnet/LICENSE +21 -0
- package/grammars/tree-sitter-vbnet/README.md +322 -0
- package/grammars/tree-sitter-xml/LICENSE +19 -0
- package/grammars/tree-sitter-yaml/LICENSE +20 -0
- package/grammars/tree-sitter-yaml/README.md +21 -0
- package/package.json +184 -4
- package/wasm/dart.wasm +0 -0
- package/wasm/tree-sitter-bash.wasm +0 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-clean.wasm +0 -0
- package/wasm/tree-sitter-clojure.wasm +0 -0
- package/wasm/tree-sitter-core_schema.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-css.wasm +0 -0
- package/wasm/tree-sitter-dart.wasm +0 -0
- package/wasm/tree-sitter-elisp.wasm +0 -0
- package/wasm/tree-sitter-elixir.wasm +0 -0
- package/wasm/tree-sitter-elm.wasm +0 -0
- package/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/wasm/tree-sitter-erlang.wasm +0 -0
- package/wasm/tree-sitter-fsharp.wasm +0 -0
- package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-graphql.wasm +0 -0
- package/wasm/tree-sitter-groovy.wasm +0 -0
- package/wasm/tree-sitter-haskell.wasm +0 -0
- package/wasm/tree-sitter-hcl.wasm +0 -0
- package/wasm/tree-sitter-html.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-json.wasm +0 -0
- package/wasm/tree-sitter-json_schema.wasm +0 -0
- package/wasm/tree-sitter-julia.wasm +0 -0
- package/wasm/tree-sitter-kotlin.wasm +0 -0
- package/wasm/tree-sitter-lua.wasm +0 -0
- package/wasm/tree-sitter-make.wasm +0 -0
- package/wasm/tree-sitter-markdown.wasm +0 -0
- package/wasm/tree-sitter-markdown_inline.wasm +0 -0
- package/wasm/tree-sitter-matlab.wasm +0 -0
- package/wasm/tree-sitter-nim.wasm +0 -0
- package/wasm/tree-sitter-objc.wasm +0 -0
- package/wasm/tree-sitter-ocaml.wasm +0 -0
- package/wasm/tree-sitter-perl.wasm +0 -0
- package/wasm/tree-sitter-php.wasm +0 -0
- package/wasm/tree-sitter-php_only.wasm +0 -0
- package/wasm/tree-sitter-powershell.wasm +0 -0
- package/wasm/tree-sitter-protobuf.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ql.wasm +0 -0
- package/wasm/tree-sitter-r.wasm +0 -0
- package/wasm/tree-sitter-rescript.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-scala.wasm +0 -0
- package/wasm/tree-sitter-scss.wasm +0 -0
- package/wasm/tree-sitter-solidity.wasm +0 -0
- package/wasm/tree-sitter-sql.wasm +0 -0
- package/wasm/tree-sitter-svelte.wasm +0 -0
- package/wasm/tree-sitter-swift.wasm +0 -0
- package/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/wasm/tree-sitter-terraform.wasm +0 -0
- package/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/wasm/tree-sitter-toml.wasm +0 -0
- package/wasm/tree-sitter-tsx.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter-vba.wasm +0 -0
- package/wasm/tree-sitter-vbnet.wasm +0 -0
- package/wasm/tree-sitter-vue.wasm +0 -0
- package/wasm/tree-sitter-xml.wasm +0 -0
- package/wasm/tree-sitter-yaml.wasm +0 -0
- package/wasm/tree-sitter-zig.wasm +0 -0
- package/wasm/tree-sitter.wasm +0 -0
- package/wasm/tsx.wasm +0 -0
- package/wasm/typescript.wasm +0 -0
|
@@ -0,0 +1,3359 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FILE_CLAIM_MAX_BYTES
|
|
3
|
+
} from "./chunk-EWMWK7WN.js";
|
|
4
|
+
import {
|
|
5
|
+
ASTEngine
|
|
6
|
+
} from "./chunk-3ZTOHHSW.js";
|
|
7
|
+
import "./chunk-BQ3OVQ3P.js";
|
|
8
|
+
import "./chunk-SK6YVHFZ.js";
|
|
9
|
+
import "./chunk-2BSKD7O3.js";
|
|
10
|
+
import "./chunk-KAET47ZL.js";
|
|
11
|
+
import "./chunk-KCDECRJY.js";
|
|
12
|
+
import "./chunk-MLKGABMK.js";
|
|
13
|
+
|
|
14
|
+
// src/ast/real-rust-analyzer.ts
|
|
15
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
16
|
+
import * as path from "path";
|
|
17
|
+
var ROUTE_VERBS = ["get", "post", "put", "delete", "patch", "head", "options", "trace", "route"];
|
|
18
|
+
function routeVerbOf(text) {
|
|
19
|
+
if (!text.startsWith("#[")) return "";
|
|
20
|
+
const rest = text.slice(2);
|
|
21
|
+
for (const verb of ROUTE_VERBS) {
|
|
22
|
+
if (!rest.startsWith(verb)) continue;
|
|
23
|
+
const next = rest[verb.length] ?? "";
|
|
24
|
+
if (next === "(" || next === " " || next === " ") return verb;
|
|
25
|
+
}
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
function extractRoutePath(text) {
|
|
29
|
+
if (!routeVerbOf(text)) return null;
|
|
30
|
+
const open = text.indexOf("(", 2);
|
|
31
|
+
if (open < 0) return null;
|
|
32
|
+
const q1 = text.indexOf('"', open);
|
|
33
|
+
if (q1 < 0) return null;
|
|
34
|
+
const q2 = text.indexOf('"', q1 + 1);
|
|
35
|
+
if (q2 <= q1 + 1) return null;
|
|
36
|
+
return text.slice(q1 + 1, q2);
|
|
37
|
+
}
|
|
38
|
+
function hasBareWord(hay, word) {
|
|
39
|
+
const isWordChar = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
|
|
40
|
+
let i = hay.indexOf(word);
|
|
41
|
+
while (i >= 0) {
|
|
42
|
+
const before = i > 0 ? hay.charCodeAt(i - 1) : 0;
|
|
43
|
+
const after = i + word.length < hay.length ? hay.charCodeAt(i + word.length) : 0;
|
|
44
|
+
if (!isWordChar(before) && !isWordChar(after)) return true;
|
|
45
|
+
i = hay.indexOf(word, i + 1);
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
function startsUpperAscii(s) {
|
|
50
|
+
if (s.length === 0) return false;
|
|
51
|
+
const c = s.charCodeAt(0);
|
|
52
|
+
return c >= 65 && c <= 90;
|
|
53
|
+
}
|
|
54
|
+
function macroTokenTreeOf(inv) {
|
|
55
|
+
for (let i = 0; i < inv.namedChildCount; i++) {
|
|
56
|
+
const c = inv.namedChild(i);
|
|
57
|
+
if (c && c.type === "token_tree") return c;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function isTwoSegmentIdentPath(s) {
|
|
62
|
+
const sep2 = s.indexOf("::");
|
|
63
|
+
if (sep2 <= 0 || sep2 + 3 > s.length) return false;
|
|
64
|
+
if (s.indexOf("::", sep2 + 2) >= 0) return false;
|
|
65
|
+
const isStart = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
|
|
66
|
+
const isPart = (c) => isStart(c) || c >= 48 && c <= 57;
|
|
67
|
+
if (!isStart(s.charCodeAt(0))) return false;
|
|
68
|
+
for (let k = 1; k < sep2; k++) if (!isPart(s.charCodeAt(k))) return false;
|
|
69
|
+
const tail = sep2 + 2;
|
|
70
|
+
if (!isStart(s.charCodeAt(tail))) return false;
|
|
71
|
+
for (let k = tail + 1; k < s.length; k++) if (!isPart(s.charCodeAt(k))) return false;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
function rustCrateSourceRoot(filePath, projectRoot) {
|
|
75
|
+
const dir = path.dirname(filePath);
|
|
76
|
+
const dirName = path.basename(dir);
|
|
77
|
+
if (dirName === "bin" || dirName === "tests" || dirName === "examples" || dirName === "benches") return dir;
|
|
78
|
+
const stop = path.resolve(projectRoot);
|
|
79
|
+
let d = dir;
|
|
80
|
+
for (; ; ) {
|
|
81
|
+
for (const rootFile of ["lib.rs", "main.rs"]) {
|
|
82
|
+
try {
|
|
83
|
+
if (statSync(path.join(d, rootFile)).isFile()) return d;
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const current = path.resolve(d);
|
|
88
|
+
if (current === stop) break;
|
|
89
|
+
const up = path.dirname(d);
|
|
90
|
+
if (up === d || !path.resolve(up).startsWith(stop)) break;
|
|
91
|
+
d = up;
|
|
92
|
+
}
|
|
93
|
+
return dir;
|
|
94
|
+
}
|
|
95
|
+
function rustModuleFileCandidates(module, filePath, projectRoot, atFileScope) {
|
|
96
|
+
const parts = module.split("::").filter((p) => p.length > 0);
|
|
97
|
+
if (parts.length === 0) return null;
|
|
98
|
+
const rootWord = parts[0];
|
|
99
|
+
const leafIsItem = parts.length >= 2 && startsUpperAscii(parts[parts.length - 1]);
|
|
100
|
+
const eff = leafIsItem ? parts.slice(0, -1) : parts;
|
|
101
|
+
if (rootWord === "crate" || rootWord === "self" || rootWord === "super") {
|
|
102
|
+
if (rootWord !== "crate" && !atFileScope) return null;
|
|
103
|
+
const dir = path.dirname(filePath);
|
|
104
|
+
if (rootWord === "crate") {
|
|
105
|
+
const base2 = rustCrateSourceRoot(filePath, projectRoot);
|
|
106
|
+
if (eff.length === 1 || eff.length === 2 && startsUpperAscii(eff[1])) {
|
|
107
|
+
return crateRootFileCandidates(base2);
|
|
108
|
+
}
|
|
109
|
+
const stem3 = path.join(base2, ...eff.slice(1));
|
|
110
|
+
return [`${stem3}.rs`, path.join(stem3, "mod.rs")];
|
|
111
|
+
}
|
|
112
|
+
const isModRs = path.basename(filePath) === "mod.rs";
|
|
113
|
+
if (rootWord === "self") {
|
|
114
|
+
if (eff.length < 2) return null;
|
|
115
|
+
const stem3 = path.join(dir, ...eff.slice(1));
|
|
116
|
+
return [`${stem3}.rs`, path.join(stem3, "mod.rs")];
|
|
117
|
+
}
|
|
118
|
+
const base = isModRs ? path.dirname(dir) : dir;
|
|
119
|
+
if (eff.length === 1) return [`${base}.rs`, path.join(base, "mod.rs")];
|
|
120
|
+
const stem2 = path.join(base, ...eff.slice(1));
|
|
121
|
+
return [`${stem2}.rs`, path.join(stem2, "mod.rs")];
|
|
122
|
+
}
|
|
123
|
+
if (rootWord === "std" || rootWord === "core" || rootWord === "alloc") return null;
|
|
124
|
+
const libFile = rustWorkspaceCrates(filePath, projectRoot).get(rootWord);
|
|
125
|
+
if (libFile === void 0) return null;
|
|
126
|
+
const localBase = rustCrateSourceRoot(filePath, projectRoot);
|
|
127
|
+
const localStem = path.join(localBase, rootWord);
|
|
128
|
+
if (isFile(localStem + ".rs") || isFile(path.join(localStem, "mod.rs"))) return null;
|
|
129
|
+
if (parts.length === 1) return [libFile];
|
|
130
|
+
const crateSrc = path.dirname(libFile);
|
|
131
|
+
const stem = path.join(crateSrc, ...parts.slice(1));
|
|
132
|
+
return [`${stem}.rs`, path.join(stem, "mod.rs")];
|
|
133
|
+
}
|
|
134
|
+
var RUST_SYSTEM_NAMESPACES = ["std", "core", "alloc"];
|
|
135
|
+
function rustSystemNamespaceOf(modulePath) {
|
|
136
|
+
if (!modulePath.includes("::")) return null;
|
|
137
|
+
for (const root of RUST_SYSTEM_NAMESPACES) {
|
|
138
|
+
if (modulePath.startsWith(root + "::")) return root;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
var RUST_WORKSPACE_CRATES = /* @__PURE__ */ new Map();
|
|
143
|
+
function isFile(p) {
|
|
144
|
+
try {
|
|
145
|
+
return statSync(p).isFile();
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function crateRootFileCandidates(base) {
|
|
151
|
+
const lib = path.join(base, "lib.rs");
|
|
152
|
+
const main = path.join(base, "main.rs");
|
|
153
|
+
const hasLib = isFile(lib);
|
|
154
|
+
const hasMain = isFile(main);
|
|
155
|
+
if (hasLib && hasMain) return null;
|
|
156
|
+
if (hasLib) return [lib];
|
|
157
|
+
if (hasMain) return [main];
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function readManifest(dir) {
|
|
161
|
+
try {
|
|
162
|
+
return readFileSync(path.join(dir, "Cargo.toml"), "utf8");
|
|
163
|
+
} catch {
|
|
164
|
+
return "";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function rustManifestFacts(text) {
|
|
168
|
+
const facts = { hasWorkspace: false, packageName: null, libPath: null, workspaceMembers: [] };
|
|
169
|
+
const n = text.length;
|
|
170
|
+
let i = 0;
|
|
171
|
+
let section = "";
|
|
172
|
+
const at = (k) => k >= 0 && k < n ? text.charCodeAt(k) : -1;
|
|
173
|
+
const skipTrivia = () => {
|
|
174
|
+
for (; ; ) {
|
|
175
|
+
while (i < n && (text.charCodeAt(i) === 32 || text.charCodeAt(i) === 9 || text.charCodeAt(i) === 13 || text.charCodeAt(i) === 10)) i++;
|
|
176
|
+
if (i < n && text.charCodeAt(i) === 35) {
|
|
177
|
+
while (i < n && text.charCodeAt(i) !== 10) i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const parseString = () => {
|
|
184
|
+
const q = at(i);
|
|
185
|
+
if (q !== 34 && q !== 39) return null;
|
|
186
|
+
const triple = at(i + 1) === q && at(i + 2) === q;
|
|
187
|
+
i += triple ? 3 : 1;
|
|
188
|
+
let out = "";
|
|
189
|
+
while (i < n) {
|
|
190
|
+
const c = text.charCodeAt(i);
|
|
191
|
+
if (triple) {
|
|
192
|
+
if (c === q && at(i + 1) === q && at(i + 2) === q) {
|
|
193
|
+
i += 3;
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
} else if (c === q) {
|
|
197
|
+
i++;
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
if (c === 92 && q === 34) {
|
|
201
|
+
const e = at(i + 1);
|
|
202
|
+
if (e === 34) {
|
|
203
|
+
out += '"';
|
|
204
|
+
i += 2;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (e === 92) {
|
|
208
|
+
out += "\\";
|
|
209
|
+
i += 2;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (e === 110) {
|
|
213
|
+
out += "\n";
|
|
214
|
+
i += 2;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (e === 116) {
|
|
218
|
+
out += " ";
|
|
219
|
+
i += 2;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (e === 114) {
|
|
223
|
+
out += "\r";
|
|
224
|
+
i += 2;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (e === 98) {
|
|
228
|
+
out += "\b";
|
|
229
|
+
i += 2;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (e === 102) {
|
|
233
|
+
out += "\f";
|
|
234
|
+
i += 2;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (e === 117 || e === 85) {
|
|
238
|
+
const len = e === 117 ? 4 : 8;
|
|
239
|
+
let hex = "";
|
|
240
|
+
i += 2;
|
|
241
|
+
for (let k = 0; k < len && i < n; k++, i++) hex += text[i];
|
|
242
|
+
const cp = parseInt(hex, 16);
|
|
243
|
+
if (Number.isFinite(cp) && cp >= 0 && cp <= 1114111) out += String.fromCodePoint(cp);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (i + 1 < n) out += text[i + 1];
|
|
247
|
+
i += 2;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
out += text[i];
|
|
251
|
+
i++;
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
};
|
|
255
|
+
const parseKey = () => {
|
|
256
|
+
const parts = [];
|
|
257
|
+
for (; ; ) {
|
|
258
|
+
while (i < n && (text.charCodeAt(i) === 32 || text.charCodeAt(i) === 9)) i++;
|
|
259
|
+
const c = at(i);
|
|
260
|
+
let seg = "";
|
|
261
|
+
if (c === 34 || c === 39) {
|
|
262
|
+
const s = parseString();
|
|
263
|
+
if (s === null) return null;
|
|
264
|
+
seg = s;
|
|
265
|
+
} else {
|
|
266
|
+
const start = i;
|
|
267
|
+
while (i < n) {
|
|
268
|
+
const d = text.charCodeAt(i);
|
|
269
|
+
const bare = d >= 48 && d <= 57 || d >= 65 && d <= 90 || d >= 97 && d <= 122 || d === 95 || d === 45;
|
|
270
|
+
if (!bare) break;
|
|
271
|
+
i++;
|
|
272
|
+
}
|
|
273
|
+
if (i === start) return null;
|
|
274
|
+
seg = text.slice(start, i);
|
|
275
|
+
}
|
|
276
|
+
parts.push(seg);
|
|
277
|
+
while (i < n && (text.charCodeAt(i) === 32 || text.charCodeAt(i) === 9)) i++;
|
|
278
|
+
if (at(i) === 46) {
|
|
279
|
+
i++;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
return parts.join(".");
|
|
285
|
+
};
|
|
286
|
+
const skipValue = () => {
|
|
287
|
+
let depth = 0;
|
|
288
|
+
while (i < n) {
|
|
289
|
+
const c = text.charCodeAt(i);
|
|
290
|
+
if (c === 34 || c === 39) {
|
|
291
|
+
if (parseString() === null) return;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (c === 91 || c === 123) {
|
|
295
|
+
depth++;
|
|
296
|
+
i++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (c === 93 || c === 125) {
|
|
300
|
+
if (depth === 0) return;
|
|
301
|
+
depth--;
|
|
302
|
+
i++;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (depth === 0 && (c === 10 || c === 44)) return;
|
|
306
|
+
if (depth === 0 && c === 35) {
|
|
307
|
+
while (i < n && text.charCodeAt(i) !== 10) i++;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
i++;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
const parseValue = () => {
|
|
314
|
+
while (i < n && (text.charCodeAt(i) === 32 || text.charCodeAt(i) === 9)) i++;
|
|
315
|
+
const c = at(i);
|
|
316
|
+
if (c === 34 || c === 39) {
|
|
317
|
+
const s = parseString();
|
|
318
|
+
return s === null ? { kind: "other" } : { kind: "string", value: s };
|
|
319
|
+
}
|
|
320
|
+
if (c === 91) {
|
|
321
|
+
i++;
|
|
322
|
+
const items = [];
|
|
323
|
+
for (; ; ) {
|
|
324
|
+
skipTrivia();
|
|
325
|
+
if (i >= n) break;
|
|
326
|
+
const d = text.charCodeAt(i);
|
|
327
|
+
if (d === 93) {
|
|
328
|
+
i++;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
if (d === 44) {
|
|
332
|
+
i++;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (d === 34 || d === 39) {
|
|
336
|
+
const s = parseString();
|
|
337
|
+
if (s === null) break;
|
|
338
|
+
items.push(s);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const before = i;
|
|
342
|
+
skipValue();
|
|
343
|
+
if (i === before) i++;
|
|
344
|
+
}
|
|
345
|
+
return { kind: "array", items };
|
|
346
|
+
}
|
|
347
|
+
skipValue();
|
|
348
|
+
return { kind: "other" };
|
|
349
|
+
};
|
|
350
|
+
for (; ; ) {
|
|
351
|
+
skipTrivia();
|
|
352
|
+
if (i >= n) break;
|
|
353
|
+
if (text.charCodeAt(i) === 91) {
|
|
354
|
+
i++;
|
|
355
|
+
const arrayOfTables = at(i) === 91;
|
|
356
|
+
if (arrayOfTables) i++;
|
|
357
|
+
const key2 = parseKey();
|
|
358
|
+
while (i < n && text.charCodeAt(i) !== 93) i++;
|
|
359
|
+
if (i < n) i++;
|
|
360
|
+
if (arrayOfTables && at(i) === 93) i++;
|
|
361
|
+
if (key2 !== null) {
|
|
362
|
+
section = key2;
|
|
363
|
+
if (key2 === "workspace") facts.hasWorkspace = true;
|
|
364
|
+
}
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const key = parseKey();
|
|
368
|
+
if (key === null) {
|
|
369
|
+
i++;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
skipTrivia();
|
|
373
|
+
if (at(i) !== 61) continue;
|
|
374
|
+
i++;
|
|
375
|
+
const value = parseValue();
|
|
376
|
+
let effSection = section;
|
|
377
|
+
let effKey = key;
|
|
378
|
+
if (effSection === "") {
|
|
379
|
+
const dot = key.lastIndexOf(".");
|
|
380
|
+
if (dot > 0) {
|
|
381
|
+
effSection = key.slice(0, dot);
|
|
382
|
+
effKey = key.slice(dot + 1);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (effSection === "package" && effKey === "name" && value.kind === "string" && facts.packageName === null) {
|
|
386
|
+
facts.packageName = value.value;
|
|
387
|
+
}
|
|
388
|
+
if (effSection === "lib" && effKey === "path" && value.kind === "string" && facts.libPath === null) {
|
|
389
|
+
facts.libPath = value.value;
|
|
390
|
+
}
|
|
391
|
+
if (effSection === "workspace" && effKey === "members" && value.kind === "array" && facts.workspaceMembers.length === 0) {
|
|
392
|
+
facts.workspaceMembers = value.items;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return facts;
|
|
396
|
+
}
|
|
397
|
+
var RUST_MANIFEST_FACTS = /* @__PURE__ */ new Map();
|
|
398
|
+
function manifestFactsOf(dir) {
|
|
399
|
+
const cached = RUST_MANIFEST_FACTS.get(dir);
|
|
400
|
+
if (cached !== void 0) return cached;
|
|
401
|
+
const facts = rustManifestFacts(readManifest(dir));
|
|
402
|
+
if (RUST_MANIFEST_FACTS.size > 4096) RUST_MANIFEST_FACTS.clear();
|
|
403
|
+
RUST_MANIFEST_FACTS.set(dir, facts);
|
|
404
|
+
return facts;
|
|
405
|
+
}
|
|
406
|
+
function manifestPackageName(dir) {
|
|
407
|
+
const raw = manifestFactsOf(dir).packageName;
|
|
408
|
+
return raw === null ? null : raw.split("-").join("_");
|
|
409
|
+
}
|
|
410
|
+
function manifestLibFile(dir) {
|
|
411
|
+
const facts = manifestFactsOf(dir);
|
|
412
|
+
const libFile = facts.libPath !== null ? path.resolve(dir, facts.libPath) : path.join(dir, "src", "lib.rs");
|
|
413
|
+
return isFile(libFile) ? libFile : null;
|
|
414
|
+
}
|
|
415
|
+
function expandMemberPattern(workspaceDir, pattern) {
|
|
416
|
+
let bases = [workspaceDir];
|
|
417
|
+
for (const seg of pattern.split("/")) {
|
|
418
|
+
if (seg === "*") {
|
|
419
|
+
const next = [];
|
|
420
|
+
for (const base of bases) {
|
|
421
|
+
try {
|
|
422
|
+
for (const e of readdirSync(base, { withFileTypes: true })) {
|
|
423
|
+
if (e.isDirectory() && !e.name.startsWith(".")) next.push(path.join(base, e.name));
|
|
424
|
+
}
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
bases = next;
|
|
429
|
+
} else {
|
|
430
|
+
bases = bases.map((base) => path.join(base, seg));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return bases;
|
|
434
|
+
}
|
|
435
|
+
var RUST_WORKSPACE_DIRS = /* @__PURE__ */ new Map();
|
|
436
|
+
function findWorkspaceDir(filePath, projectRoot) {
|
|
437
|
+
const startDir = path.dirname(filePath);
|
|
438
|
+
const cacheKey = `${projectRoot}\0${startDir}`;
|
|
439
|
+
const cached = RUST_WORKSPACE_DIRS.get(cacheKey);
|
|
440
|
+
if (cached !== void 0) return cached;
|
|
441
|
+
const found = findWorkspaceDirUncached(filePath, projectRoot);
|
|
442
|
+
if (RUST_WORKSPACE_DIRS.size > 4096) RUST_WORKSPACE_DIRS.clear();
|
|
443
|
+
RUST_WORKSPACE_DIRS.set(cacheKey, found);
|
|
444
|
+
return found;
|
|
445
|
+
}
|
|
446
|
+
function findWorkspaceDirUncached(filePath, projectRoot) {
|
|
447
|
+
const stop = path.resolve(projectRoot);
|
|
448
|
+
let dir = path.dirname(filePath);
|
|
449
|
+
let nearestPackageDir = null;
|
|
450
|
+
for (; ; ) {
|
|
451
|
+
if (isFile(path.join(dir, "Cargo.toml"))) {
|
|
452
|
+
if (nearestPackageDir === null) nearestPackageDir = dir;
|
|
453
|
+
if (manifestFactsOf(dir).hasWorkspace) return dir;
|
|
454
|
+
}
|
|
455
|
+
const current = path.resolve(dir);
|
|
456
|
+
if (current === stop) break;
|
|
457
|
+
const up = path.dirname(dir);
|
|
458
|
+
if (up === dir || !path.resolve(up).startsWith(stop)) break;
|
|
459
|
+
dir = up;
|
|
460
|
+
}
|
|
461
|
+
return nearestPackageDir;
|
|
462
|
+
}
|
|
463
|
+
function rustWorkspaceCrates(filePath, projectRoot) {
|
|
464
|
+
const workspaceDir = findWorkspaceDir(filePath, projectRoot);
|
|
465
|
+
if (workspaceDir === null) return /* @__PURE__ */ new Map();
|
|
466
|
+
const cached = RUST_WORKSPACE_CRATES.get(workspaceDir);
|
|
467
|
+
if (cached !== void 0) return cached;
|
|
468
|
+
const out = /* @__PURE__ */ new Map();
|
|
469
|
+
const addCrate = (dir) => {
|
|
470
|
+
const name = manifestPackageName(dir);
|
|
471
|
+
if (name === null || out.has(name)) return;
|
|
472
|
+
const libFile = manifestLibFile(dir);
|
|
473
|
+
if (libFile !== null) out.set(name, libFile);
|
|
474
|
+
};
|
|
475
|
+
addCrate(workspaceDir);
|
|
476
|
+
for (const pattern of manifestFactsOf(workspaceDir).workspaceMembers) {
|
|
477
|
+
for (const dir of expandMemberPattern(workspaceDir, pattern)) addCrate(dir);
|
|
478
|
+
}
|
|
479
|
+
if (RUST_WORKSPACE_CRATES.size > 32) RUST_WORKSPACE_CRATES.clear();
|
|
480
|
+
RUST_WORKSPACE_CRATES.set(workspaceDir, out);
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
function confirmModuleFile(candidates, projectRoot) {
|
|
484
|
+
const root = path.resolve(projectRoot);
|
|
485
|
+
for (const cand of candidates) {
|
|
486
|
+
const rel = path.relative(root, cand).split(path.sep).join("/");
|
|
487
|
+
if (rel.length === 0 || rel.startsWith("../") || path.isAbsolute(rel)) continue;
|
|
488
|
+
if (!rel.toLowerCase().endsWith(".rs")) continue;
|
|
489
|
+
try {
|
|
490
|
+
const st = statSync(cand);
|
|
491
|
+
if (!st.isFile() || st.size > FILE_CLAIM_MAX_BYTES) continue;
|
|
492
|
+
} catch {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
return rel;
|
|
496
|
+
}
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
var RUST_FRAMEWORK_PRIORITY = [
|
|
500
|
+
"actix_web",
|
|
501
|
+
"axum",
|
|
502
|
+
"rocket",
|
|
503
|
+
"tokio",
|
|
504
|
+
"clap",
|
|
505
|
+
"serde",
|
|
506
|
+
"thiserror",
|
|
507
|
+
"tracing"
|
|
508
|
+
];
|
|
509
|
+
var RUST_UNNAMED_IMPL = "anonymous_impl";
|
|
510
|
+
var RUST_FILE_SOURCE = "(file)";
|
|
511
|
+
var RealRustAnalyzer = class {
|
|
512
|
+
// RSD3: per-file set of symbol ids already handed out, so impl blocks and
|
|
513
|
+
// other same-(owner,name) declarations never share an id (struct Foo + two
|
|
514
|
+
// `impl Foo` blocks previously produced THREE symbols all id "Foo"; 2,852
|
|
515
|
+
// duplicates across cargo+ripgrep). Reset per analyze() call.
|
|
516
|
+
usedIds = /* @__PURE__ */ new Set();
|
|
517
|
+
/**
|
|
518
|
+
* M59.A7 (#825): the 1-based line of a syntax node, or `undefined` when the
|
|
519
|
+
* node is absent. Never 0 — a site that cannot be named is OMITTED from the
|
|
520
|
+
* edge (the #820 rule), so a reader can tell "no line" from "line 1".
|
|
521
|
+
*/
|
|
522
|
+
nodeLine(node) {
|
|
523
|
+
if (!node || !node.startPosition || typeof node.startPosition.row !== "number") return void 0;
|
|
524
|
+
return node.startPosition.row + 1;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* R7, the SYMBOL-level half: the name of the declaration that owns an edge, or
|
|
528
|
+
* `null` when there is none.
|
|
529
|
+
*
|
|
530
|
+
* The old expression at six call sites was `parent?.name || "global"`, and the
|
|
531
|
+
* fallback was the literal the rule exists to forbid: over the 110-file ripgrep
|
|
532
|
+
* corpus it put `global` into 1,169 edge endpoints. Measured causes, by the
|
|
533
|
+
* enclosing node at the edge's own line (`script/incoming-rust/rust-r7-origin.ts`):
|
|
534
|
+
*
|
|
535
|
+
* 443 `imports` file-scope `use_declaration` -> no enclosing declaration
|
|
536
|
+
* 350 `calls` file-scope `macro_invocation` body -> no enclosing declaration
|
|
537
|
+
* 140 `exports` a `pub` item, whose OWN name is the target
|
|
538
|
+
* 108 `uses` a `const` initializer -> the const declares it
|
|
539
|
+
* 60 `imports` a `use` inside a function body -> no enclosing declaration
|
|
540
|
+
* 16 `calls` a `const` initializer
|
|
541
|
+
* 36 `uses` file-scope `macro_invocation` body
|
|
542
|
+
*
|
|
543
|
+
* Two of those have a real owner and are fixed by handing the const/static
|
|
544
|
+
* symbol back to the walk. Three genuinely have none, and for those the rule's
|
|
545
|
+
* own words govern: "When a fallback has nothing truthful to say the honest
|
|
546
|
+
* state is ABSENCE, not the string 'unknown'". So the caller OMITS the edge and
|
|
547
|
+
* records the omission in `metadata.unattributedEdges`, which is what makes the
|
|
548
|
+
* loss countable rather than invisible.
|
|
549
|
+
*/
|
|
550
|
+
edgeSource(parent) {
|
|
551
|
+
const n = parent?.name;
|
|
552
|
+
return n && n.length > 0 ? n : null;
|
|
553
|
+
}
|
|
554
|
+
/** Record one refused edge and return `null`, for `const s = this.edgeSource(p); if (!s) return`. */
|
|
555
|
+
noOwner(ctx, type) {
|
|
556
|
+
ctx.unattributed.set(type, (ctx.unattributed.get(type) ?? 0) + 1);
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
analyze(rootNode, content, filePath, context) {
|
|
560
|
+
const ctx = {
|
|
561
|
+
tree: { rootNode },
|
|
562
|
+
content,
|
|
563
|
+
filePath,
|
|
564
|
+
// Absent for a per-file-only caller (the fixture runner calls `analyze` with no context):
|
|
565
|
+
// the file claim is then not emitted at all, which is the safe answer, never a guess.
|
|
566
|
+
projectRoot: typeof context?.projectRoot === "string" ? context.projectRoot : "",
|
|
567
|
+
symbols: [],
|
|
568
|
+
imports: [],
|
|
569
|
+
relationships: [],
|
|
570
|
+
exports: [],
|
|
571
|
+
metadata: {
|
|
572
|
+
language: "rust",
|
|
573
|
+
// R8 (M60 standing rule): the family contract's three keys, ALL
|
|
574
|
+
// declared here. `framework` is the deterministic primary pick and its
|
|
575
|
+
// VALUE is assigned after the walk (below), because detection happens
|
|
576
|
+
// in the attribute handler as the tree is traversed — but the key is
|
|
577
|
+
// part of what this analyzer publishes, so it is declared at
|
|
578
|
+
// construction exactly as the compliant lanes (java's
|
|
579
|
+
// `pickJavaFramework`, go's `frameworks[0]?.name`) declare theirs.
|
|
580
|
+
framework: void 0,
|
|
581
|
+
detectedFrameworks: [],
|
|
582
|
+
frameworkPatterns: []
|
|
583
|
+
},
|
|
584
|
+
emittedUses: /* @__PURE__ */ new Set(),
|
|
585
|
+
unattributed: /* @__PURE__ */ new Map(),
|
|
586
|
+
macroArgumentReads: 0,
|
|
587
|
+
pendingInstantiations: [],
|
|
588
|
+
awaitedCalls: /* @__PURE__ */ new Set()
|
|
589
|
+
};
|
|
590
|
+
this.usedIds = /* @__PURE__ */ new Set();
|
|
591
|
+
const valueOwners = /* @__PURE__ */ new Map();
|
|
592
|
+
const useImported = /* @__PURE__ */ new Set();
|
|
593
|
+
this.collectValueOwners(rootNode, valueOwners, useImported);
|
|
594
|
+
const fnBindings = this.collectFunctionBindings(rootNode, valueOwners, useImported);
|
|
595
|
+
const tupleShapeScopes = (() => {
|
|
596
|
+
const out = /* @__PURE__ */ new Map();
|
|
597
|
+
const walkShapes = (n, scopePath) => {
|
|
598
|
+
if (!n) return;
|
|
599
|
+
if (n.type === "mod_item") {
|
|
600
|
+
const nm = n.childForFieldName?.("name") ?? n.namedChild(0);
|
|
601
|
+
const childPath = [...scopePath, nm ? String(nm.text) : ""];
|
|
602
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
603
|
+
const c = n.namedChild(i);
|
|
604
|
+
if (c && c.type === "declaration_list") walkShapes(c, childPath);
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (n.type === "struct_item") {
|
|
609
|
+
let tupleShape = false;
|
|
610
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
611
|
+
const c = n.namedChild(i);
|
|
612
|
+
if (c && c.type === "ordered_field_declaration_list") tupleShape = true;
|
|
613
|
+
}
|
|
614
|
+
if (tupleShape) {
|
|
615
|
+
const nm = n.childForFieldName?.("name") ?? n.namedChild(0);
|
|
616
|
+
if (nm) {
|
|
617
|
+
const list = out.get(String(nm.text)) ?? [];
|
|
618
|
+
list.push(scopePath);
|
|
619
|
+
out.set(String(nm.text), list);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
for (let i = 0; i < n.namedChildCount; i++) walkShapes(n.namedChild(i), scopePath);
|
|
624
|
+
};
|
|
625
|
+
walkShapes(rootNode, []);
|
|
626
|
+
return out;
|
|
627
|
+
})();
|
|
628
|
+
const scopePrefixOf = (outer, inner) => {
|
|
629
|
+
if (outer.length > inner.length) return false;
|
|
630
|
+
for (let i = 0; i < outer.length; i++) if (outer[i] !== inner[i]) return false;
|
|
631
|
+
return true;
|
|
632
|
+
};
|
|
633
|
+
const bareTupleCtorVisible = (name, callScope) => {
|
|
634
|
+
for (const declScope of tupleShapeScopes.get(name) ?? []) if (scopePrefixOf(declScope, callScope)) return true;
|
|
635
|
+
return false;
|
|
636
|
+
};
|
|
637
|
+
if (rootNode.hasError) {
|
|
638
|
+
const errStack = [rootNode];
|
|
639
|
+
while (errStack.length > 0) {
|
|
640
|
+
const n = errStack.pop();
|
|
641
|
+
if (n.type === "ERROR" || n.isMissing) {
|
|
642
|
+
if (!ctx.metadata.errors) ctx.metadata.errors = [];
|
|
643
|
+
ctx.metadata.errors.push({
|
|
644
|
+
message: `Syntax ${n.type} detected. The Brain Engine is bypassing this broken node.`,
|
|
645
|
+
range: {
|
|
646
|
+
startLine: n.startPosition.row + 1,
|
|
647
|
+
startColumn: n.startPosition.column + 1,
|
|
648
|
+
endLine: n.endPosition.row + 1,
|
|
649
|
+
endColumn: n.endPosition.column + 1,
|
|
650
|
+
startIndex: n.startIndex,
|
|
651
|
+
endIndex: n.endIndex
|
|
652
|
+
},
|
|
653
|
+
severity: "warning"
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
657
|
+
const child = n.namedChild(i);
|
|
658
|
+
if (child) errStack.push(child);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const stack = [{ node: rootNode, parent: null }];
|
|
663
|
+
while (stack.length > 0) {
|
|
664
|
+
const popped = stack.pop();
|
|
665
|
+
if (!popped) continue;
|
|
666
|
+
const { node, parent } = popped;
|
|
667
|
+
let currentParent = parent;
|
|
668
|
+
if (node.type === "struct_item" || node.type === "union_item") {
|
|
669
|
+
currentParent = this.handleStruct(node, ctx, parent) ?? parent;
|
|
670
|
+
} else if (node.type === "trait_item") {
|
|
671
|
+
currentParent = this.handleTrait(node, ctx, parent) ?? parent;
|
|
672
|
+
} else if (node.type === "impl_item") {
|
|
673
|
+
currentParent = this.handleImpl(node, ctx, parent) ?? parent;
|
|
674
|
+
} else if (node.type === "function_item" || node.type === "function_signature_item") {
|
|
675
|
+
currentParent = this.handleFunction(node, ctx, parent) ?? parent;
|
|
676
|
+
} else if (node.type === "enum_item") {
|
|
677
|
+
currentParent = this.handleEnum(node, ctx, parent) ?? parent;
|
|
678
|
+
} else if (node.type === "const_item" || node.type === "static_item") {
|
|
679
|
+
currentParent = this.handleVariable(node, ctx, parent) ?? parent;
|
|
680
|
+
} else if (node.type === "let_declaration") {
|
|
681
|
+
this.handleVariable(node, ctx, parent);
|
|
682
|
+
} else if (node.type === "use_declaration") {
|
|
683
|
+
this.handleUseDeclaration(node, ctx, parent);
|
|
684
|
+
} else if (node.type === "macro_definition") {
|
|
685
|
+
this.handleMacro(node, ctx, parent);
|
|
686
|
+
} else if (node.type === "mod_item") {
|
|
687
|
+
currentParent = this.handleMod(node, ctx, parent) ?? parent;
|
|
688
|
+
} else if (node.type === "attribute_item") {
|
|
689
|
+
this.handleAttribute(node, ctx);
|
|
690
|
+
} else if (node.type === "inner_attribute_item") {
|
|
691
|
+
this.noOwner(ctx, "decorates:inner_attribute");
|
|
692
|
+
} else if (node.type === "call_expression") {
|
|
693
|
+
this.handleCallExpression(node, ctx, parent);
|
|
694
|
+
} else if (node.type === "field_expression") {
|
|
695
|
+
const field = node.childForFieldName("field");
|
|
696
|
+
const value = node.childForFieldName("value");
|
|
697
|
+
const par = node.parent;
|
|
698
|
+
let isAssignLhs = false;
|
|
699
|
+
if (par && (par.type === "assignment_expression" || par.type === "compound_assignment_expr")) {
|
|
700
|
+
const left = par.namedChild(0);
|
|
701
|
+
if (left && left.startIndex === node.startIndex && left.endIndex === node.endIndex) isAssignLhs = true;
|
|
702
|
+
}
|
|
703
|
+
if (!isAssignLhs && field && field.type === "field_identifier" && field.text.trim() && node.parent?.type !== "call_expression") {
|
|
704
|
+
const source = this.edgeSource(parent);
|
|
705
|
+
if (!source) {
|
|
706
|
+
this.noOwner(ctx, "uses:field_expression");
|
|
707
|
+
} else if (!ctx.emittedUses.has(`${source}::uses::${field.text}`)) {
|
|
708
|
+
ctx.emittedUses.add(`${source}::uses::${field.text}`);
|
|
709
|
+
ctx.relationships.push({
|
|
710
|
+
source,
|
|
711
|
+
target: field.text,
|
|
712
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
713
|
+
confidence: 0.9,
|
|
714
|
+
// M59.A7 (#825): the FIELD identifier's line, not the receiver's —
|
|
715
|
+
// a `field_expression` starts at its value.
|
|
716
|
+
...this.nodeLine(field) ? { line_number: this.nodeLine(field) } : {},
|
|
717
|
+
metadata: { relationshipType: "field_access", accessedFrom: value?.text }
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
} else if (node.type === "struct_expression") {
|
|
722
|
+
const typeNode = node.childForFieldName("name") || node.namedChild(0);
|
|
723
|
+
if (typeNode && (typeNode.type === "type_identifier" || typeNode.type === "scoped_type_identifier")) {
|
|
724
|
+
let leaf = this.stripGenerics(typeNode.text).split("::").filter(Boolean).pop() ?? typeNode.text;
|
|
725
|
+
if (leaf === "Self") leaf = this.enclosingImplType(node);
|
|
726
|
+
const source = leaf ? this.edgeSource(parent) : null;
|
|
727
|
+
if (leaf && source) {
|
|
728
|
+
ctx.relationships.push({
|
|
729
|
+
source,
|
|
730
|
+
target: leaf,
|
|
731
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
732
|
+
confidence: 0.8,
|
|
733
|
+
// M59.A7 (#825): the constructed TYPE's own line.
|
|
734
|
+
...this.nodeLine(typeNode) ? { line_number: this.nodeLine(typeNode) } : {},
|
|
735
|
+
metadata: { relationshipType: "struct_literal", isConstructor: true }
|
|
736
|
+
});
|
|
737
|
+
} else if (leaf) {
|
|
738
|
+
this.noOwner(ctx, "instantiates");
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
} else if (node.type === "macro_invocation") {
|
|
742
|
+
this.handleMacroInvocation(node, ctx, parent);
|
|
743
|
+
} else if (node.type === "enum_variant") {
|
|
744
|
+
const variantName = this.extractIdentifier(node, "name");
|
|
745
|
+
if (variantName && parent && parent.kind === ASTEngine.SymbolKind.Enum) {
|
|
746
|
+
const sym = this.createSymbol(variantName, ASTEngine.SymbolKind.EnumMember, node, parent);
|
|
747
|
+
ctx.symbols.push(sym);
|
|
748
|
+
}
|
|
749
|
+
} else if (node.type === "type_item" || node.type === "associated_type") {
|
|
750
|
+
const name = this.extractIdentifier(node, "name");
|
|
751
|
+
if (name) {
|
|
752
|
+
const sym = this.createSymbol(name, ASTEngine.SymbolKind.Type, node, parent, { type: "type_alias" });
|
|
753
|
+
ctx.symbols.push(sym);
|
|
754
|
+
if (this.isUnrestrictedPublic(node) && !parent) {
|
|
755
|
+
ctx.exports.push({
|
|
756
|
+
name,
|
|
757
|
+
type: ASTEngine.SymbolKind.Type,
|
|
758
|
+
range: sym.range,
|
|
759
|
+
isType: true
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
} else if (node.type === "closure_expression") {
|
|
764
|
+
} else if (node.type === "extern_crate_declaration") {
|
|
765
|
+
const name = this.extractIdentifier(node, "name");
|
|
766
|
+
if (name) {
|
|
767
|
+
ctx.imports.push({
|
|
768
|
+
module: name,
|
|
769
|
+
items: ["*"],
|
|
770
|
+
range: {
|
|
771
|
+
startLine: node.startPosition.row + 1,
|
|
772
|
+
startColumn: node.startPosition.column + 1,
|
|
773
|
+
endLine: node.endPosition.row + 1,
|
|
774
|
+
endColumn: node.endPosition.column + 1,
|
|
775
|
+
startIndex: node.startIndex,
|
|
776
|
+
endIndex: node.endIndex
|
|
777
|
+
},
|
|
778
|
+
metadata: { rustSpecific: true, externalCrate: true }
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
} else if (node.type === "unsafe_block") {
|
|
782
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
783
|
+
if (ctx.metadata.patterns.indexOf("Unsafe Code") === -1) ctx.metadata.patterns.push("Unsafe Code");
|
|
784
|
+
} else if (node.type === "await_expression") {
|
|
785
|
+
const inner = node.namedChild(0);
|
|
786
|
+
if (inner && (inner.type === "call_expression" || inner.type === "macro_invocation")) {
|
|
787
|
+
ctx.awaitedCalls.add(inner.startIndex);
|
|
788
|
+
}
|
|
789
|
+
} else if (node.type === "if_expression" || node.type === "while_expression" || node.type === "loop_expression" || node.type === "for_expression") {
|
|
790
|
+
if (!ctx.metadata.cyclomaticComplexity) ctx.metadata.cyclomaticComplexity = 1;
|
|
791
|
+
ctx.metadata.cyclomaticComplexity++;
|
|
792
|
+
} else if (node.type === "match_arm") {
|
|
793
|
+
if (!ctx.metadata.cyclomaticComplexity) ctx.metadata.cyclomaticComplexity = 1;
|
|
794
|
+
ctx.metadata.cyclomaticComplexity++;
|
|
795
|
+
} else if (node.type === "binary_expression") {
|
|
796
|
+
const op = node.childForFieldName("operator")?.text;
|
|
797
|
+
if (op === "&&" || op === "||") {
|
|
798
|
+
if (!ctx.metadata.cyclomaticComplexity) ctx.metadata.cyclomaticComplexity = 1;
|
|
799
|
+
ctx.metadata.cyclomaticComplexity++;
|
|
800
|
+
}
|
|
801
|
+
} else if (node.type === "try_expression") {
|
|
802
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
803
|
+
if (ctx.metadata.patterns.indexOf("Error Propagation (?)") === -1) ctx.metadata.patterns.push("Error Propagation (?)");
|
|
804
|
+
} else if (node.type === "where_clause") {
|
|
805
|
+
const genericParams = /* @__PURE__ */ new Set();
|
|
806
|
+
let gcur = node.parent;
|
|
807
|
+
while (gcur) {
|
|
808
|
+
if (gcur.type === "function_item" || gcur.type === "function_signature_item" || gcur.type === "trait_item" || gcur.type === "struct_item" || gcur.type === "enum_item" || gcur.type === "type_item" || gcur.type === "impl_item") {
|
|
809
|
+
const tp = gcur.childForFieldName("type_parameters");
|
|
810
|
+
if (tp) {
|
|
811
|
+
const pw = [tp];
|
|
812
|
+
while (pw.length > 0) {
|
|
813
|
+
const m = pw.pop();
|
|
814
|
+
if (m.type === "type_identifier" || m.type === "identifier") genericParams.add(m.text);
|
|
815
|
+
for (let k = 0; k < m.namedChildCount; k++) {
|
|
816
|
+
const c = m.namedChild(k);
|
|
817
|
+
if (c) pw.push(c);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
gcur = gcur.parent;
|
|
823
|
+
}
|
|
824
|
+
const boundNames = (n) => {
|
|
825
|
+
if (!n) return [];
|
|
826
|
+
if (n.type === "scoped_type_identifier" || n.type === "scoped_identifier" || n.type === "qualified_type") {
|
|
827
|
+
const nm = n.childForFieldName("name");
|
|
828
|
+
if (nm) return [nm];
|
|
829
|
+
const last = n.namedChild(n.namedChildCount - 1);
|
|
830
|
+
return last && (last.type === "type_identifier" || last.type === "identifier") ? [last] : [];
|
|
831
|
+
}
|
|
832
|
+
const out = [];
|
|
833
|
+
const w = [n];
|
|
834
|
+
while (w.length > 0) {
|
|
835
|
+
const m = w.pop();
|
|
836
|
+
if (m.type === "lifetime") continue;
|
|
837
|
+
if (m.type === "type_identifier") out.push(m);
|
|
838
|
+
for (let k = m.namedChildCount - 1; k >= 0; k--) {
|
|
839
|
+
const ch = m.namedChild(k);
|
|
840
|
+
if (ch) w.push(ch);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return out;
|
|
844
|
+
};
|
|
845
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
846
|
+
const c = node.namedChild(i);
|
|
847
|
+
if (!c || c.type !== "where_predicate") continue;
|
|
848
|
+
const left = c.namedChild(0);
|
|
849
|
+
for (const leaf of boundNames(left)) {
|
|
850
|
+
if (!leaf.text || genericParams.has(leaf.text)) continue;
|
|
851
|
+
const boundSource = this.edgeSource(parent);
|
|
852
|
+
if (!boundSource) {
|
|
853
|
+
this.noOwner(ctx, "references");
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
ctx.relationships.push({
|
|
857
|
+
source: boundSource,
|
|
858
|
+
target: leaf.text,
|
|
859
|
+
type: ASTEngine.RelationshipType.References,
|
|
860
|
+
// M59.A7 (#825): the bound's own line.
|
|
861
|
+
...this.nodeLine(leaf) ? { line_number: this.nodeLine(leaf) } : {},
|
|
862
|
+
metadata: { rustSpecific: true, whereBound: true }
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
} else if (node.type === "lifetime") {
|
|
867
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
868
|
+
if (ctx.metadata.patterns.indexOf("Explicit Lifetimes") === -1) ctx.metadata.patterns.push("Explicit Lifetimes");
|
|
869
|
+
} else if (node.type === "scoped_type_identifier") {
|
|
870
|
+
const name = node.childForFieldName("name") || node.namedChild(node.namedChildCount - 1);
|
|
871
|
+
if (name) {
|
|
872
|
+
const n = name.text;
|
|
873
|
+
if (["Send", "Sync", "Sized", "Unpin", "UnwindSafe", "RefUnwindSafe"].includes(n)) {
|
|
874
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
875
|
+
if (ctx.metadata.patterns.indexOf("Auto Traits") === -1) ctx.metadata.patterns.push("Auto Traits");
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
} else if (node.type === "use_as_clause" || node.type === "scoped_use_list") {
|
|
879
|
+
if (this.isPublic(node.parent || node) && node.parent?.type === "use_declaration") {
|
|
880
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
881
|
+
if (ctx.metadata.patterns.indexOf("Re-exports") === -1) ctx.metadata.patterns.push("Re-exports");
|
|
882
|
+
}
|
|
883
|
+
} else if (node.type === "identifier") {
|
|
884
|
+
this.handleIdentifierRead(node, ctx, parent, valueOwners, useImported, fnBindings);
|
|
885
|
+
}
|
|
886
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
887
|
+
const child = node.namedChild(i);
|
|
888
|
+
if (child) {
|
|
889
|
+
stack.push({ node: child, parent: currentParent });
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
for (const s of ctx.symbols) {
|
|
894
|
+
const _owner = s.ownerName || s.scope;
|
|
895
|
+
if (!_owner) continue;
|
|
896
|
+
ctx.relationships.push({
|
|
897
|
+
source: _owner,
|
|
898
|
+
target: s.name,
|
|
899
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
900
|
+
confidence: 1,
|
|
901
|
+
// M59.A7 (#825): the member's own declaration line.
|
|
902
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
903
|
+
metadata: {}
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
for (const sym of ctx.symbols) {
|
|
907
|
+
if (sym.qualified_calls && sym.qualified_calls.length > 0) {
|
|
908
|
+
sym.qualified_calls = [...new Set(sym.qualified_calls)];
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
{
|
|
912
|
+
const inFile = /* @__PURE__ */ new Set();
|
|
913
|
+
for (const sym of ctx.symbols) {
|
|
914
|
+
if (sym.kind === ASTEngine.SymbolKind.Struct || sym.kind === ASTEngine.SymbolKind.Enum || sym.kind === ASTEngine.SymbolKind.Type || sym.kind === ASTEngine.SymbolKind.Impl) inFile.add(sym.name);
|
|
915
|
+
}
|
|
916
|
+
for (const cand of ctx.pendingInstantiations) {
|
|
917
|
+
if (!inFile.has(cand.target)) continue;
|
|
918
|
+
if (cand.bareSpelling && !bareTupleCtorVisible(cand.target, cand.callScope ?? [])) continue;
|
|
919
|
+
ctx.relationships.push({
|
|
920
|
+
source: cand.source,
|
|
921
|
+
target: cand.target,
|
|
922
|
+
fromSymbol: cand.source,
|
|
923
|
+
toSymbol: cand.qualified,
|
|
924
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
925
|
+
confidence: 0.8,
|
|
926
|
+
// M59.A7 (#825): recorded at the call site (the type that names the
|
|
927
|
+
// construction), carried through the deferred resolution pass.
|
|
928
|
+
...cand.line ? { line_number: cand.line } : {},
|
|
929
|
+
metadata: { isConstructor: true }
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
ctx.metadata.complexity = ctx.metadata.cyclomaticComplexity ?? 1;
|
|
934
|
+
ctx.metadata.framework = RUST_FRAMEWORK_PRIORITY.find(
|
|
935
|
+
(fw) => (ctx.metadata.detectedFrameworks ?? []).includes(fw)
|
|
936
|
+
);
|
|
937
|
+
this.extractRustDeclaredTypeRefs(rootNode, ctx);
|
|
938
|
+
this.emitRustModuleSurface(rootNode, ctx);
|
|
939
|
+
this.emitRustTraitOverrides(rootNode, ctx);
|
|
940
|
+
this.publishQualifiedCallTargets(ctx);
|
|
941
|
+
if (ctx.macroArgumentReads > 0) {
|
|
942
|
+
;
|
|
943
|
+
ctx.metadata.macroArgumentReads = ctx.macroArgumentReads;
|
|
944
|
+
}
|
|
945
|
+
if (ctx.unattributed.size > 0) {
|
|
946
|
+
;
|
|
947
|
+
ctx.metadata.unattributedEdges = Object.fromEntries(
|
|
948
|
+
[...ctx.unattributed.entries()].sort((a, b) => a[0].localeCompare(b[0]))
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
this.classifyEvidenceTier(ctx.relationships);
|
|
952
|
+
return {
|
|
953
|
+
symbols: ctx.symbols,
|
|
954
|
+
relationships: ctx.relationships,
|
|
955
|
+
imports: ctx.imports,
|
|
956
|
+
exports: ctx.exports,
|
|
957
|
+
metadata: ctx.metadata
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
/** R13: declared-type `references` (see the call site for the contract). */
|
|
961
|
+
extractRustDeclaredTypeRefs(rootNode, ctx) {
|
|
962
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
963
|
+
const genericParamsOf = (item) => {
|
|
964
|
+
const out = /* @__PURE__ */ new Set();
|
|
965
|
+
let gcur = item;
|
|
966
|
+
while (gcur) {
|
|
967
|
+
if (gcur.type === "function_item" || gcur.type === "trait_item" || gcur.type === "struct_item" || gcur.type === "enum_item" || gcur.type === "type_item" || gcur.type === "impl_item") {
|
|
968
|
+
const tp = gcur.childForFieldName("type_parameters");
|
|
969
|
+
if (tp) {
|
|
970
|
+
const pw = [tp];
|
|
971
|
+
while (pw.length > 0) {
|
|
972
|
+
const m = pw.pop();
|
|
973
|
+
if (m.type === "type_identifier" || m.type === "identifier") out.add(m.text);
|
|
974
|
+
for (let k = 0; k < m.namedChildCount; k++) {
|
|
975
|
+
const c = m.namedChild(k);
|
|
976
|
+
if (c) pw.push(c);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
gcur = gcur.parent;
|
|
983
|
+
}
|
|
984
|
+
return out;
|
|
985
|
+
};
|
|
986
|
+
const push = (source, leafNode, gparams) => {
|
|
987
|
+
if (!source) {
|
|
988
|
+
this.noOwner(ctx, "references");
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
let target = leafNode.text;
|
|
992
|
+
if (!target || target === "Self") {
|
|
993
|
+
const resolved = this.enclosingImplType(leafNode);
|
|
994
|
+
if (!resolved) return;
|
|
995
|
+
target = resolved;
|
|
996
|
+
}
|
|
997
|
+
if (gparams.has(target)) return;
|
|
998
|
+
const key = `${source}::references::${target}`;
|
|
999
|
+
if (emitted.has(key)) return;
|
|
1000
|
+
emitted.add(key);
|
|
1001
|
+
ctx.relationships.push({
|
|
1002
|
+
source,
|
|
1003
|
+
target,
|
|
1004
|
+
type: ASTEngine.RelationshipType.References,
|
|
1005
|
+
confidence: 0.9,
|
|
1006
|
+
// M59.A7 (#825): the leaf that names the type.
|
|
1007
|
+
...this.nodeLine(leafNode) ? { line_number: this.nodeLine(leafNode) } : {},
|
|
1008
|
+
metadata: { relationshipType: "declared_type" }
|
|
1009
|
+
});
|
|
1010
|
+
};
|
|
1011
|
+
const collectLeaves = (typeNode) => {
|
|
1012
|
+
const out = [];
|
|
1013
|
+
if (!typeNode) return out;
|
|
1014
|
+
const w = [typeNode];
|
|
1015
|
+
while (w.length > 0) {
|
|
1016
|
+
const m = w.pop();
|
|
1017
|
+
if (m !== typeNode && (m.type === "where_clause" || m.type === "type_parameters")) continue;
|
|
1018
|
+
if (m.type === "type_identifier") out.push(m);
|
|
1019
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1020
|
+
const c = m.namedChild(i);
|
|
1021
|
+
if (c) w.push(c);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return out;
|
|
1025
|
+
};
|
|
1026
|
+
const walk = [rootNode];
|
|
1027
|
+
while (walk.length > 0) {
|
|
1028
|
+
const n = walk.pop();
|
|
1029
|
+
const t = n.type;
|
|
1030
|
+
if (t === "macro_definition") continue;
|
|
1031
|
+
if (t === "function_item") {
|
|
1032
|
+
const src = this.extractIdentifier(n, "name") ?? this.noOwner(ctx, "references");
|
|
1033
|
+
const gparams = genericParamsOf(n);
|
|
1034
|
+
const params = n.childForFieldName("parameters");
|
|
1035
|
+
if (params) {
|
|
1036
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
1037
|
+
const p = params.namedChild(i);
|
|
1038
|
+
if (!p) continue;
|
|
1039
|
+
if (p.type === "self_parameter") continue;
|
|
1040
|
+
const ty = p.type === "parameter" ? p.childForFieldName("type") : null;
|
|
1041
|
+
for (const leaf of collectLeaves(ty)) push(src, leaf, gparams);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const ret = n.childForFieldName("return_type");
|
|
1045
|
+
for (const leaf of collectLeaves(ret)) push(src, leaf, gparams);
|
|
1046
|
+
} else if (t === "struct_item" || t === "union_item") {
|
|
1047
|
+
const src = this.extractIdentifier(n, "name") ?? this.noOwner(ctx, "references");
|
|
1048
|
+
const gparams = genericParamsOf(n);
|
|
1049
|
+
const fw = [n];
|
|
1050
|
+
while (fw.length > 0) {
|
|
1051
|
+
const m = fw.pop();
|
|
1052
|
+
if (m !== n && (m.type === "where_clause" || m.type === "type_parameters")) continue;
|
|
1053
|
+
if (m.type === "field_declaration") {
|
|
1054
|
+
for (let i = 0; i < m.namedChildCount; i++) {
|
|
1055
|
+
const c = m.namedChild(i);
|
|
1056
|
+
if (c && c.type !== "field_identifier" && c.type !== "visibility_modifier" && c.type !== "attribute_item") {
|
|
1057
|
+
for (const leaf of collectLeaves(c)) push(src, leaf, gparams);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
if (m.type === "ordered_field_declaration_list") {
|
|
1063
|
+
for (let i = 0; i < m.namedChildCount; i++) {
|
|
1064
|
+
const c = m.namedChild(i);
|
|
1065
|
+
if (c) for (const leaf of collectLeaves(c)) push(src, leaf, gparams);
|
|
1066
|
+
}
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1070
|
+
const c = m.namedChild(i);
|
|
1071
|
+
if (c) fw.push(c);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
} else if (t === "enum_item") {
|
|
1075
|
+
const src = this.extractIdentifier(n, "name") ?? this.noOwner(ctx, "references");
|
|
1076
|
+
const gparams = genericParamsOf(n);
|
|
1077
|
+
const ew = [n];
|
|
1078
|
+
while (ew.length > 0) {
|
|
1079
|
+
const m = ew.pop();
|
|
1080
|
+
if (m !== n && (m.type === "where_clause" || m.type === "type_parameters")) continue;
|
|
1081
|
+
if (m.type === "field_declaration") {
|
|
1082
|
+
for (let i = 0; i < m.namedChildCount; i++) {
|
|
1083
|
+
const c = m.namedChild(i);
|
|
1084
|
+
if (c && c.type !== "field_identifier" && c.type !== "visibility_modifier" && c.type !== "attribute_item") {
|
|
1085
|
+
for (const leaf of collectLeaves(c)) push(src, leaf, gparams);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (m.type === "ordered_field_declaration_list") {
|
|
1091
|
+
for (let i = 0; i < m.namedChildCount; i++) {
|
|
1092
|
+
const c = m.namedChild(i);
|
|
1093
|
+
if (c) for (const leaf of collectLeaves(c)) push(src, leaf, gparams);
|
|
1094
|
+
}
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1098
|
+
const c = m.namedChild(i);
|
|
1099
|
+
if (c) ew.push(c);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
} else if (t === "type_item" || t === "associated_type") {
|
|
1103
|
+
const src = this.extractIdentifier(n, "name") ?? this.noOwner(ctx, "references");
|
|
1104
|
+
const gparams = genericParamsOf(n);
|
|
1105
|
+
const nameNode = n.childForFieldName("name") ?? n.namedChild(0);
|
|
1106
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1107
|
+
const c = n.namedChild(i);
|
|
1108
|
+
if (!c || c.type === "type_parameters" || c.type === "where_clause") continue;
|
|
1109
|
+
if (nameNode && c.startIndex === nameNode.startIndex && c.endIndex === nameNode.endIndex) continue;
|
|
1110
|
+
for (const leaf of collectLeaves(c)) push(src, leaf, gparams);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
} else if (t === "const_item" || t === "static_item") {
|
|
1114
|
+
const nm = n.childForFieldName("name")?.text;
|
|
1115
|
+
if (nm && nm !== "_") {
|
|
1116
|
+
const ty = n.childForFieldName("type");
|
|
1117
|
+
const gparams = genericParamsOf(n);
|
|
1118
|
+
for (const leaf of collectLeaves(ty)) push(nm, leaf, gparams);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1122
|
+
const c = n.namedChild(i);
|
|
1123
|
+
if (c) walk.push(c);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* INCOMING_RUST — the MODULE SURFACE: `imports` from `use` declarations and
|
|
1129
|
+
* `exports` from the `pub` surface.
|
|
1130
|
+
*
|
|
1131
|
+
* ## Why both, and why they were missing
|
|
1132
|
+
*
|
|
1133
|
+
* The lane measured 0 `imports` edges and 0 `exports` edges over 110 real
|
|
1134
|
+
* `.rs` files of ripgrep through the production entry, and the two are the
|
|
1135
|
+
* edges an INBOUND query is made of: "who imports this module" and "what does
|
|
1136
|
+
* this module publish". Rust states both explicitly — `use a::b::C;` and
|
|
1137
|
+
* `pub fn f` — and this analyzer was already reading both nodes. It recorded
|
|
1138
|
+
* them as an `ImportInfo` row and an `ExportInfo` row and stopped there, so
|
|
1139
|
+
* `bb_graph`'s file-level reader (`src/lsp/graph_usages.ts` filters
|
|
1140
|
+
* `relationship_type IN ('imports','exports')`) could not see either. The
|
|
1141
|
+
* information existed; nothing joined on it.
|
|
1142
|
+
*
|
|
1143
|
+
* The doc's "no references edge (Java-family contract)" rule stays true and is
|
|
1144
|
+
* why these are their own KIND: a module path is not a symbol name, and putting
|
|
1145
|
+
* `std::io` in a `references` target is the c/#924 defect (a target no
|
|
1146
|
+
* declaration can match). `imports` is the family's label for a dependency.
|
|
1147
|
+
*
|
|
1148
|
+
* ## `imports` — one edge per MODULE PATH
|
|
1149
|
+
*
|
|
1150
|
+
* Target is the path the `use` writes, verbatim: `std::io`, `crate::flags`,
|
|
1151
|
+
* `super::util`, `grep_matcher::Matcher`, `::libc::c_int`. That is a real,
|
|
1152
|
+
* stable Rust path — the same string the compiler resolves — not a leaf, not a
|
|
1153
|
+
* file name, and not a guess. A brace group's PREFIX is the module
|
|
1154
|
+
* (`use std::io::{Read, Write}` → `std::io`) because that is what the group is
|
|
1155
|
+
* a use OF; the leaves are bindings, and a binding is the `exports`/`contains`
|
|
1156
|
+
* surface of wherever it lives, not a dependency edge.
|
|
1157
|
+
*
|
|
1158
|
+
* Source is the enclosing inline module, or `global` at file scope — the same
|
|
1159
|
+
* convention every other rust edge in this analyzer already uses, so an
|
|
1160
|
+
* `imports` row joins with the rest of the file's graph on the same source.
|
|
1161
|
+
*
|
|
1162
|
+
* ## `exports` — one edge per PUBLISHED NAME
|
|
1163
|
+
*
|
|
1164
|
+
* Target is a name this file's own symbol table publishes. The gate is
|
|
1165
|
+
* `publishedNames.has(name)`, not "the export list is non-empty": an export row
|
|
1166
|
+
* whose name is a path prefix (`pub use storage::MemoryStorage` records
|
|
1167
|
+
* `storage`) is a PATH and cannot be a target, and `pub use` of a foreign item
|
|
1168
|
+
* names something this file does not declare. Those are refused rather than
|
|
1169
|
+
* published, which is R7/R10: an unresolvable endpoint is a lie the graph
|
|
1170
|
+
* tells. Re-exported ITEM names are published instead, because the item name is
|
|
1171
|
+
* what a consumer of this module can actually import.
|
|
1172
|
+
*
|
|
1173
|
+
* The one pre-existing `exports` edge — an axum/actix route attribute naming a
|
|
1174
|
+
* URL — is untouched, and is still not counted here: its target is a route, not
|
|
1175
|
+
* a symbol.
|
|
1176
|
+
*/
|
|
1177
|
+
emitRustModuleSurface(rootNode, ctx) {
|
|
1178
|
+
const importableUnits = /* @__PURE__ */ new Set();
|
|
1179
|
+
for (const s of ctx.symbols) if (s.name && !s.ownerName) importableUnits.add(s.name);
|
|
1180
|
+
const importRowByKey = /* @__PURE__ */ new Map();
|
|
1181
|
+
const seenExports = /* @__PURE__ */ new Set();
|
|
1182
|
+
const pushedExportNames = new Set(ctx.exports.map((e) => e.name));
|
|
1183
|
+
const publishExport = (source, name, line, kind, symbolKind, allowForeignName = false) => {
|
|
1184
|
+
if (!name || !allowForeignName && !importableUnits.has(name)) return;
|
|
1185
|
+
const key = `${source}::exports::${name}`;
|
|
1186
|
+
if (seenExports.has(key)) return;
|
|
1187
|
+
seenExports.add(key);
|
|
1188
|
+
ctx.relationships.push({
|
|
1189
|
+
source,
|
|
1190
|
+
target: name,
|
|
1191
|
+
fromSymbol: source,
|
|
1192
|
+
toSymbol: name,
|
|
1193
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
1194
|
+
confidence: 1,
|
|
1195
|
+
...line ? { line_number: line } : {},
|
|
1196
|
+
metadata: {
|
|
1197
|
+
rustSpecific: true,
|
|
1198
|
+
relationshipType: kind,
|
|
1199
|
+
symbolKind,
|
|
1200
|
+
// R7 / M60 #910: an export is published BY the module, so a file-scope
|
|
1201
|
+
// export names the FILE as its source. Without this the store wrote
|
|
1202
|
+
// `source_external = "global"` and the row was unreachable from the
|
|
1203
|
+
// one reader that displays file-level exports.
|
|
1204
|
+
...source === RUST_FILE_SOURCE ? { fileLevel: true } : {}
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
};
|
|
1208
|
+
const walk = [rootNode];
|
|
1209
|
+
while (walk.length > 0) {
|
|
1210
|
+
const n = walk.pop();
|
|
1211
|
+
if (n.type === "use_declaration") {
|
|
1212
|
+
const mods = this.enclosingMods(n);
|
|
1213
|
+
const scope = (() => {
|
|
1214
|
+
let cur = n.parent ?? null;
|
|
1215
|
+
let guard = 0;
|
|
1216
|
+
while (cur && guard++ < 64) {
|
|
1217
|
+
if (cur.type === "function_item") {
|
|
1218
|
+
const fnName = cur.childForFieldName?.("name");
|
|
1219
|
+
return { kind: "function", name: fnName ? String(fnName.text) : RUST_FILE_SOURCE };
|
|
1220
|
+
}
|
|
1221
|
+
if (cur.type === "mod_item") {
|
|
1222
|
+
const modName = cur.childForFieldName?.("name") ?? cur.namedChild(0);
|
|
1223
|
+
return { kind: "module", name: modName ? String(modName.text) : RUST_FILE_SOURCE };
|
|
1224
|
+
}
|
|
1225
|
+
cur = cur.parent;
|
|
1226
|
+
}
|
|
1227
|
+
return { kind: "file", name: RUST_FILE_SOURCE };
|
|
1228
|
+
})();
|
|
1229
|
+
const atFileScope = mods.length === 0;
|
|
1230
|
+
const source = scope.name;
|
|
1231
|
+
const isPublic = this.isUnrestrictedPublic(n);
|
|
1232
|
+
const exportSource = isPublic && atFileScope ? RUST_FILE_SOURCE : null;
|
|
1233
|
+
for (const row of this.rustImportRows(n)) {
|
|
1234
|
+
const key = `${source}::imports::${row.module}`;
|
|
1235
|
+
const reExports = exportSource ? row.items.filter((x) => !!x && x !== "*") : [];
|
|
1236
|
+
if (exportSource) {
|
|
1237
|
+
for (const item of reExports) {
|
|
1238
|
+
publishExport(exportSource, item, row.line, "re_export", "symbol", true);
|
|
1239
|
+
if (importableUnits.has(item) && !pushedExportNames.has(item)) {
|
|
1240
|
+
pushedExportNames.add(item);
|
|
1241
|
+
ctx.exports.push({
|
|
1242
|
+
name: item,
|
|
1243
|
+
type: ASTEngine.SymbolKind.Module,
|
|
1244
|
+
range: {
|
|
1245
|
+
startLine: n.startPosition.row + 1,
|
|
1246
|
+
startColumn: n.startPosition.column + 1,
|
|
1247
|
+
endLine: n.endPosition.row + 1,
|
|
1248
|
+
endColumn: n.endPosition.column + 1,
|
|
1249
|
+
startIndex: n.startIndex,
|
|
1250
|
+
endIndex: n.endIndex
|
|
1251
|
+
},
|
|
1252
|
+
isType: false
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
const existing = importRowByKey.get(key);
|
|
1258
|
+
if (existing) {
|
|
1259
|
+
if (reExports.length > 0) {
|
|
1260
|
+
const md = existing.metadata ?? {};
|
|
1261
|
+
md.reExports = [.../* @__PURE__ */ new Set([...Array.isArray(md.reExports) ? md.reExports : [], ...reExports])];
|
|
1262
|
+
existing.metadata = md;
|
|
1263
|
+
}
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
const moduleFiles = ctx.projectRoot.length > 0 && path.isAbsolute(ctx.filePath) ? rustModuleFileCandidates(row.module, ctx.filePath, ctx.projectRoot, atFileScope) : null;
|
|
1267
|
+
const projectFile = moduleFiles === null ? null : confirmModuleFile(moduleFiles, ctx.projectRoot);
|
|
1268
|
+
importRowByKey.set(key, {
|
|
1269
|
+
source,
|
|
1270
|
+
target: row.module,
|
|
1271
|
+
fromSymbol: source,
|
|
1272
|
+
toSymbol: row.module,
|
|
1273
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
1274
|
+
confidence: 1,
|
|
1275
|
+
...row.line ? { line_number: row.line } : {},
|
|
1276
|
+
metadata: {
|
|
1277
|
+
rustSpecific: true,
|
|
1278
|
+
isImport: true,
|
|
1279
|
+
// RUST-D31 declared scope contract (file / module / function) — the fixture pins all three.
|
|
1280
|
+
importScope: scope.kind,
|
|
1281
|
+
evidenceClass: "import_based",
|
|
1282
|
+
relationshipType: "use_import",
|
|
1283
|
+
modulePath: row.module,
|
|
1284
|
+
moduleKind: row.kind,
|
|
1285
|
+
// R7 / M60 #910: the source of a FILE-SCOPE dependency is the IMPORTING FILE, and this
|
|
1286
|
+
// flag is what makes the store write the project-relative path. It keys on the DECLARED
|
|
1287
|
+
// scope, not on "no inline mod": a function-local `use` is sourced at the function, so
|
|
1288
|
+
// it must not be rewritten as a file-level edge.
|
|
1289
|
+
...scope.kind === "file" ? { fileLevel: true } : {},
|
|
1290
|
+
// The names the declaration brings into scope. Carried, not
|
|
1291
|
+
// emitted as edges: a binding is not a dependency, and the same
|
|
1292
|
+
// binding is re-declared by every file that imports it.
|
|
1293
|
+
items: row.items,
|
|
1294
|
+
...reExports.length > 0 ? { reExports } : {},
|
|
1295
|
+
// THE ARBITER'S FILE CLAIM (`FILE_CLAIM_KEY` in script/m14/incoming-rule.ts).
|
|
1296
|
+
// A project-relative POSIX path the project's own admitted list can CONFIRM; the
|
|
1297
|
+
// claim is never written unconfirmed, which is what keeps extFAIL at 0. Not a
|
|
1298
|
+
// declaration claim: it says the module this path names is a file this project
|
|
1299
|
+
// has, and it is the right terminal state for an import whose declaring file is
|
|
1300
|
+
// outside the run's population (the 400-file sample, or a build target).
|
|
1301
|
+
...projectFile !== null ? { importProjectFile: projectFile } : {},
|
|
1302
|
+
// THE NAMESPACE CLAIM (`NAMESPACE_CLAIM_KEY`), emitted only where the file claim did
|
|
1303
|
+
// NOT fire: a path rooted at a system crate names no project file, and before the
|
|
1304
|
+
// `::` separator landed in the arbiter's matcher this class was unclaimable. The
|
|
1305
|
+
// authority is the analyzer's own declared-root list above, so a root the list does
|
|
1306
|
+
// not name reads `externalRefuted` — the pre-registered falsifier is any extFAIL
|
|
1307
|
+
// here, which is why the list is three literals and not a pattern.
|
|
1308
|
+
...projectFile === null && rustSystemNamespaceOf(row.module) !== null ? { languageNamespace: [rustSystemNamespaceOf(row.module)] } : {}
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1314
|
+
const c = n.namedChild(i);
|
|
1315
|
+
if (c) walk.push(c);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
for (const row of importRowByKey.values()) ctx.relationships.push(row);
|
|
1319
|
+
for (const e of ctx.exports) {
|
|
1320
|
+
publishExport(RUST_FILE_SOURCE, e.name, e.range?.startLine, "public_item", String(e.type ?? ""));
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* The `use` declarations of one `use_declaration` node, as
|
|
1325
|
+
* (dependency path, bound names, 1-based line, form) rows.
|
|
1326
|
+
*
|
|
1327
|
+
* Shares no state with `handleUseDeclaration`'s ImportInfo assembly on
|
|
1328
|
+
* purpose: that one answers "what does the index store per BINDING" (a row
|
|
1329
|
+
* per name, module = the prefix), this one answers "what module does this file
|
|
1330
|
+
* DEPEND ON" (a row per PATH, and the leaf is a binding rather than a
|
|
1331
|
+
* dependency). The two therefore disagree about `use std::collections::HashMap`
|
|
1332
|
+
* — one row there, one dependency here — and they must: a binding is
|
|
1333
|
+
* re-declared by every importer, so a binding is not a dependency.
|
|
1334
|
+
*
|
|
1335
|
+
* A path's LEAF is never the dependency. `use crate::flags::Flag` depends on
|
|
1336
|
+
* `crate::flags`; `use serde;` depends on `serde` (a one-segment path is
|
|
1337
|
+
* itself the dependency, and it binds its own name).
|
|
1338
|
+
*/
|
|
1339
|
+
rustImportRows(node) {
|
|
1340
|
+
const out = [];
|
|
1341
|
+
const pathOf = (n) => {
|
|
1342
|
+
if (!n) return null;
|
|
1343
|
+
if (n.type === "identifier" || n.type === "type_identifier") return n.text;
|
|
1344
|
+
if (n.type === "crate" || n.type === "super" || n.type === "self") return n.text;
|
|
1345
|
+
if (n.type === "scoped_identifier") {
|
|
1346
|
+
const p = n.childForFieldName("path");
|
|
1347
|
+
const name = n.childForFieldName("name") || n.namedChild(n.namedChildCount - 1);
|
|
1348
|
+
if (p) {
|
|
1349
|
+
const base = pathOf(p);
|
|
1350
|
+
if (base === null) return null;
|
|
1351
|
+
return `${base}::${name ? name.text : ""}`;
|
|
1352
|
+
}
|
|
1353
|
+
return `${n.text.startsWith("::") ? "::" : ""}${name ? name.text : ""}`;
|
|
1354
|
+
}
|
|
1355
|
+
return null;
|
|
1356
|
+
};
|
|
1357
|
+
const splitLeaf = (full) => {
|
|
1358
|
+
const parts = full.split("::");
|
|
1359
|
+
if (parts.length < 2) return { module: full, item: full };
|
|
1360
|
+
const prefix = parts.slice(0, -1).join("::");
|
|
1361
|
+
const item = parts[parts.length - 1];
|
|
1362
|
+
if (prefix === "super" || prefix === "crate" || prefix === "self") {
|
|
1363
|
+
return { module: full, item };
|
|
1364
|
+
}
|
|
1365
|
+
return { module: prefix, item };
|
|
1366
|
+
};
|
|
1367
|
+
const pushLeafPath = (full, alias, at, kind) => {
|
|
1368
|
+
if (!full) return;
|
|
1369
|
+
const { module, item } = splitLeaf(full);
|
|
1370
|
+
if (!module) return;
|
|
1371
|
+
out.push({ module, items: [alias ?? item], line: this.nodeLine(at), kind });
|
|
1372
|
+
};
|
|
1373
|
+
const groupLeaves = (list, base) => {
|
|
1374
|
+
const items = [];
|
|
1375
|
+
if (!list || list.type !== "use_list") return items;
|
|
1376
|
+
for (let j = 0; j < list.namedChildCount; j++) {
|
|
1377
|
+
const leaf = list.namedChild(j);
|
|
1378
|
+
if (!leaf || leaf.type === ",") continue;
|
|
1379
|
+
if (leaf.type === "use_as_clause") {
|
|
1380
|
+
const al = leaf.childForFieldName("alias") || leaf.namedChild(leaf.namedChildCount - 1);
|
|
1381
|
+
if (al) items.push(al.text);
|
|
1382
|
+
} else if (leaf.type === "identifier" || leaf.type === "type_identifier") {
|
|
1383
|
+
items.push(leaf.text);
|
|
1384
|
+
} else if (leaf.type === "self") {
|
|
1385
|
+
const last = base.split("::").filter(Boolean).pop();
|
|
1386
|
+
if (last) items.push(last);
|
|
1387
|
+
} else if (leaf.type === "scoped_identifier") {
|
|
1388
|
+
const inner = pathOf(leaf);
|
|
1389
|
+
if (inner) {
|
|
1390
|
+
const { module, item } = splitLeaf(inner);
|
|
1391
|
+
items.push(item);
|
|
1392
|
+
if (module && module !== base) {
|
|
1393
|
+
out.push({ module: `${base}::${module}`, items: [], line: this.nodeLine(leaf), kind: "nested_group" });
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
} else if (leaf.type === "scoped_use_list") {
|
|
1397
|
+
const innerBase = pathOf(leaf.namedChild(0));
|
|
1398
|
+
if (innerBase) {
|
|
1399
|
+
for (const it of groupLeaves(leaf.namedChild(1), innerBase)) items.push(it);
|
|
1400
|
+
if (innerBase !== base) {
|
|
1401
|
+
out.push({ module: innerBase, items: [], line: this.nodeLine(leaf), kind: "nested_group" });
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
return items;
|
|
1407
|
+
};
|
|
1408
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1409
|
+
const c = node.namedChild(i);
|
|
1410
|
+
if (!c || c.type === "visibility_modifier" || c.type === "use" || c.type === "crate") continue;
|
|
1411
|
+
if (c.type === "scoped_use_list") {
|
|
1412
|
+
const base = pathOf(c.namedChild(0));
|
|
1413
|
+
if (base) out.push({ module: base, items: groupLeaves(c.namedChild(1), base), line: this.nodeLine(c), kind: "group" });
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
if (c.type === "use_list") {
|
|
1417
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
1418
|
+
const leaf = c.namedChild(j);
|
|
1419
|
+
if (!leaf || leaf.type === ",") continue;
|
|
1420
|
+
if (leaf.type === "scoped_use_list") {
|
|
1421
|
+
const base = pathOf(leaf.namedChild(0));
|
|
1422
|
+
if (base) out.push({ module: base, items: groupLeaves(leaf.namedChild(1), base), line: this.nodeLine(leaf), kind: "top_level_group" });
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
if (leaf.type === "use_as_clause") {
|
|
1426
|
+
pushLeafPath(pathOf(leaf.childForFieldName("path") || leaf.namedChild(0)), leaf.childForFieldName("alias")?.text ?? null, leaf, "top_level_group");
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
pushLeafPath(pathOf(leaf), null, leaf, "top_level_group");
|
|
1430
|
+
}
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
if (c.type === "use_wildcard") {
|
|
1434
|
+
const base = pathOf(c.namedChild(0));
|
|
1435
|
+
if (base) out.push({ module: base, items: ["*"], line: this.nodeLine(c), kind: "wildcard" });
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (c.type === "use_as_clause") {
|
|
1439
|
+
pushLeafPath(pathOf(c.childForFieldName("path") || c.namedChild(0)), c.childForFieldName("alias")?.text ?? null, c, "alias");
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
pushLeafPath(pathOf(c), null, c, "path");
|
|
1443
|
+
}
|
|
1444
|
+
return out;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* INCOMING_RUST — `overrides`.
|
|
1448
|
+
*
|
|
1449
|
+
* ## What the language has, and what it does not
|
|
1450
|
+
*
|
|
1451
|
+
* Rust has no `override` keyword. Its override is the trait implementation:
|
|
1452
|
+
* `impl Display for Foo { fn fmt(&self, …) }` REPLACES `Display::fmt` for the
|
|
1453
|
+
* type `Foo`, and the author's writing of both the trait and the method is the
|
|
1454
|
+
* assertion — there is nothing to infer. Measured on this lane's corpus
|
|
1455
|
+
* (110 ripgrep files): 252 `implements` edges over 44 files, so the construct
|
|
1456
|
+
* is not rare, and the previous ruling ("Rust trait impls are captured as
|
|
1457
|
+
* `implements`") was a prose justification for a type the language expresses —
|
|
1458
|
+
* which is the exact failure the ontology gate was built to catch.
|
|
1459
|
+
*
|
|
1460
|
+
* What Rust does NOT have is a second declaration replacing a first. A second
|
|
1461
|
+
* `impl Foo { }` block RE-OPENS the type; it cannot redefine a method (E0592,
|
|
1462
|
+
* duplicate definitions with the same name), so no such edge is derivable and
|
|
1463
|
+
* none is emitted. The corpus found 107 such re-opens across 40 files, which
|
|
1464
|
+
* is the measurement behind that half of the ruling.
|
|
1465
|
+
*
|
|
1466
|
+
* ## Endpoints, and why they are dotted
|
|
1467
|
+
*
|
|
1468
|
+
* `Type.method` → `Trait.method`, the shape java, go and csharp already use,
|
|
1469
|
+
* for one reason that is not cosmetic: `parallel-indexer` resolves a relationship
|
|
1470
|
+
* target by (1) exact `qualified_name`, (2) a bare `name` that is UNIQUE across
|
|
1471
|
+
* the project, (3) everything after the first `.` of a dotted target. This
|
|
1472
|
+
* analyzer publishes no `qualifiedName`, so every rust symbol's stored
|
|
1473
|
+
* `qualified_name` IS its bare name and a bare target can only resolve while it
|
|
1474
|
+
* is project-unique — which for a member name it almost never is. A dotted
|
|
1475
|
+
* target takes strategy 3. A bare `method` target would name the right method
|
|
1476
|
+
* and still not say WHICH type's, so the qualification is what makes the edge
|
|
1477
|
+
* answerable for an incoming query ("who overrides `Trait::method`").
|
|
1478
|
+
*
|
|
1479
|
+
* ## Confidence: two tiers, and the split is the point
|
|
1480
|
+
*
|
|
1481
|
+
* 1.0 `local-trait-signature` — the trait is DECLARED IN THIS FILE, so both
|
|
1482
|
+
* ends are facts read out of this file's own items.
|
|
1483
|
+
* 0.85 `cross-file-trait-signature` — the trait is not declared here. The
|
|
1484
|
+
* edge is still what the source says (`impl Display for Foo` + `fn
|
|
1485
|
+
* fmt`), so it is not a guess, but the target resolves only once the
|
|
1486
|
+
* whole project is indexed, and it is below the `patterns.ts` default
|
|
1487
|
+
* threshold, so a thresholded consumer never treats it as ground truth.
|
|
1488
|
+
*
|
|
1489
|
+
* `Drop::drop` is included: `impl Drop for Foo { fn drop(&mut self) }` is
|
|
1490
|
+
* Rust's most common override and a reader asking "who overrides drop" wants
|
|
1491
|
+
* the answer.
|
|
1492
|
+
*/
|
|
1493
|
+
emitRustTraitOverrides(rootNode, ctx) {
|
|
1494
|
+
const localTraitMethods = /* @__PURE__ */ new Map();
|
|
1495
|
+
const impls = [];
|
|
1496
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1497
|
+
const walk = [rootNode];
|
|
1498
|
+
while (walk.length > 0) {
|
|
1499
|
+
const n = walk.pop();
|
|
1500
|
+
if (n.type === "trait_item") {
|
|
1501
|
+
const nm = n.childForFieldName("name")?.text;
|
|
1502
|
+
if (nm) {
|
|
1503
|
+
const ms = /* @__PURE__ */ new Set();
|
|
1504
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1505
|
+
const c = n.namedChild(i);
|
|
1506
|
+
if (c?.type !== "declaration_list") continue;
|
|
1507
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
1508
|
+
const f = c.namedChild(j);
|
|
1509
|
+
if (f?.type === "function_signature_item" || f?.type === "function_item") {
|
|
1510
|
+
const mn = f.childForFieldName("name")?.text;
|
|
1511
|
+
if (mn) ms.add(mn);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
localTraitMethods.set(nm, ms);
|
|
1516
|
+
}
|
|
1517
|
+
} else if (n.type === "impl_item") {
|
|
1518
|
+
const traitNode = n.childForFieldName("trait");
|
|
1519
|
+
const typeNode = n.childForFieldName("type");
|
|
1520
|
+
if (!traitNode || !typeNode) {
|
|
1521
|
+
} else {
|
|
1522
|
+
const typeName = this.implSelfTypeName(typeNode);
|
|
1523
|
+
const traitName = this.extractTraitName(traitNode);
|
|
1524
|
+
if (typeName && traitName) {
|
|
1525
|
+
const ms = /* @__PURE__ */ new Set();
|
|
1526
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1527
|
+
const c = n.namedChild(i);
|
|
1528
|
+
if (c?.type !== "declaration_list") continue;
|
|
1529
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
1530
|
+
const f = c.namedChild(j);
|
|
1531
|
+
if (f?.type === "function_item") {
|
|
1532
|
+
const mn = f.childForFieldName("name")?.text;
|
|
1533
|
+
if (mn) ms.add(mn);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
impls.push({ type: typeName, trait: traitName, methods: ms, node: n });
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1542
|
+
const c = n.namedChild(i);
|
|
1543
|
+
if (c) walk.push(c);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
const genericParamsOfImpl = (implNode) => {
|
|
1547
|
+
const out = /* @__PURE__ */ new Set();
|
|
1548
|
+
for (let i = 0; i < implNode.namedChildCount; i++) {
|
|
1549
|
+
const tp = implNode.namedChild(i);
|
|
1550
|
+
if (!tp || tp.type !== "type_parameters") continue;
|
|
1551
|
+
const stack2 = [tp];
|
|
1552
|
+
while (stack2.length > 0) {
|
|
1553
|
+
const n = stack2.pop();
|
|
1554
|
+
if (n.type === "type_parameter") {
|
|
1555
|
+
const id = n.namedChild(0);
|
|
1556
|
+
if (id && (id.type === "type_identifier" || id.type === "identifier")) out.add(String(id.text));
|
|
1557
|
+
}
|
|
1558
|
+
for (let j = 0; j < n.namedChildCount; j++) {
|
|
1559
|
+
const c = n.namedChild(j);
|
|
1560
|
+
if (c) stack2.push(c);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return out;
|
|
1565
|
+
};
|
|
1566
|
+
for (const impl of impls) {
|
|
1567
|
+
const genericParams = genericParamsOfImpl(impl.node);
|
|
1568
|
+
if (genericParams.has(impl.type)) continue;
|
|
1569
|
+
for (const m of impl.methods) {
|
|
1570
|
+
const declared = localTraitMethods.get(impl.trait);
|
|
1571
|
+
if (declared && !declared.has(m)) continue;
|
|
1572
|
+
const key = `${impl.type}.${m}::${impl.trait}.${m}`;
|
|
1573
|
+
if (seen.has(key)) continue;
|
|
1574
|
+
seen.add(key);
|
|
1575
|
+
const local = !!declared;
|
|
1576
|
+
const line = this.overrideLine(rootNode, impl.type, impl.trait, m);
|
|
1577
|
+
ctx.relationships.push({
|
|
1578
|
+
source: `${impl.type}.${m}`,
|
|
1579
|
+
// THE TARGET IS THE MEMBER NAME, NOT `Owner.member`: the resolver binds against
|
|
1580
|
+
// DECLARATIONS, and a declaration is named by its leaf. Measured: 100% of this lane's
|
|
1581
|
+
// overrides targets are dotted, 100% have their leaf declared in the edge's own file, and
|
|
1582
|
+
// 100% classify unresolved — a naming problem, not a mechanism problem. `localEdgeName`
|
|
1583
|
+
// derives the leaf AFTER the paren deletion `localEdgeLabel` performs, so a dotted
|
|
1584
|
+
// parameter list cannot be cut into a non-name (see its doc).
|
|
1585
|
+
target: m,
|
|
1586
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
1587
|
+
confidence: local ? 1 : 0.85,
|
|
1588
|
+
...line ? { line_number: line } : {},
|
|
1589
|
+
metadata: {
|
|
1590
|
+
rustSpecific: true,
|
|
1591
|
+
relationshipType: "trait_method_impl",
|
|
1592
|
+
reasonCode: local ? "local-trait-signature" : "cross-file-trait-signature",
|
|
1593
|
+
implementsType: impl.type,
|
|
1594
|
+
trait: impl.trait,
|
|
1595
|
+
method: m
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
/** The line of the overriding method, found by re-walking to the same impl. */
|
|
1602
|
+
overrideLine(rootNode, type, trait, method) {
|
|
1603
|
+
const walk = [rootNode];
|
|
1604
|
+
while (walk.length > 0) {
|
|
1605
|
+
const n = walk.pop();
|
|
1606
|
+
if (n.type === "impl_item") {
|
|
1607
|
+
const traitNode = n.childForFieldName("trait");
|
|
1608
|
+
const typeNode = n.childForFieldName("type");
|
|
1609
|
+
if (traitNode && typeNode && this.implSelfTypeName(typeNode) === type && this.extractTraitName(traitNode) === trait) {
|
|
1610
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1611
|
+
const c = n.namedChild(i);
|
|
1612
|
+
if (c?.type !== "declaration_list") continue;
|
|
1613
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
1614
|
+
const f = c.namedChild(j);
|
|
1615
|
+
if (f?.type === "function_item" && f.childForFieldName("name")?.text === method) {
|
|
1616
|
+
return this.nodeLine(f.childForFieldName("name"));
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1623
|
+
const c = n.namedChild(i);
|
|
1624
|
+
if (c) walk.push(c);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return void 0;
|
|
1628
|
+
}
|
|
1629
|
+
/** #419-D evidence-tier classification (family rules). */
|
|
1630
|
+
classifyEvidenceTier(relationships) {
|
|
1631
|
+
for (const r of relationships) {
|
|
1632
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
1633
|
+
if (md.evidenceClass) continue;
|
|
1634
|
+
const t = String(r.type ?? "");
|
|
1635
|
+
const conf = typeof r.confidence === "number" ? r.confidence : 1;
|
|
1636
|
+
if (t === "contains" || t === "exports") {
|
|
1637
|
+
md.evidenceClass = "structural";
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
if (t === "references") {
|
|
1641
|
+
md.evidenceClass = md.whereBound ? "reference_qualified" : "import_based";
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (t === "imports") {
|
|
1645
|
+
md.evidenceClass = "import_based";
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
if (t === "extends" || t === "implements" || t === "overrides") {
|
|
1649
|
+
md.evidenceClass = conf >= 1 ? "structural" : conf >= 0.85 ? "usage_contextual" : "inferred";
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
if (t === "instantiates") {
|
|
1653
|
+
md.evidenceClass = conf >= 0.8 && md.isConstructor ? "usage_contextual" : "structural";
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
md.evidenceClass = "usage_contextual";
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
handleAttribute(node, ctx) {
|
|
1660
|
+
const text = node.text;
|
|
1661
|
+
{
|
|
1662
|
+
const decorated = (() => {
|
|
1663
|
+
let sib = node.nextSibling;
|
|
1664
|
+
while (sib) {
|
|
1665
|
+
const st = String(sib.type);
|
|
1666
|
+
if (st === "line_comment" || st === "block_comment" || st === "attribute_item") {
|
|
1667
|
+
sib = sib.nextSibling;
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
break;
|
|
1671
|
+
}
|
|
1672
|
+
return sib;
|
|
1673
|
+
})();
|
|
1674
|
+
if (decorated) {
|
|
1675
|
+
const declName = decorated.childForFieldName?.("name") ?? (() => {
|
|
1676
|
+
for (let k = 0; k < decorated.namedChildCount; k++) {
|
|
1677
|
+
const c = decorated.namedChild(k);
|
|
1678
|
+
const ct = String(c?.type ?? "");
|
|
1679
|
+
if (ct === "type_identifier" || ct === "identifier") return c;
|
|
1680
|
+
}
|
|
1681
|
+
return null;
|
|
1682
|
+
})();
|
|
1683
|
+
if (!declName || !declName.text || declName.text.length === 0) {
|
|
1684
|
+
this.noOwner(ctx, "decorates:unnamed_declaration");
|
|
1685
|
+
} else {
|
|
1686
|
+
const attr = node.namedChild(0);
|
|
1687
|
+
const nameNode = attr ? attr.childForFieldName?.("path") ?? attr.namedChild(0) : null;
|
|
1688
|
+
const attrName = nameNode ? String(nameNode.text) : "";
|
|
1689
|
+
if (attrName.length === 0) {
|
|
1690
|
+
this.noOwner(ctx, "decorates:attribute_without_name");
|
|
1691
|
+
} else {
|
|
1692
|
+
const BUILTIN = /* @__PURE__ */ new Set([
|
|
1693
|
+
"derive",
|
|
1694
|
+
"allow",
|
|
1695
|
+
"deny",
|
|
1696
|
+
"warn",
|
|
1697
|
+
"cfg",
|
|
1698
|
+
"cfg_attr",
|
|
1699
|
+
"inline",
|
|
1700
|
+
"test",
|
|
1701
|
+
"repr",
|
|
1702
|
+
"doc",
|
|
1703
|
+
"path",
|
|
1704
|
+
"no_mangle",
|
|
1705
|
+
"cold",
|
|
1706
|
+
"deprecated",
|
|
1707
|
+
"must_use",
|
|
1708
|
+
"feature",
|
|
1709
|
+
"macro_use",
|
|
1710
|
+
"macro_export",
|
|
1711
|
+
"should_panic",
|
|
1712
|
+
"ignore",
|
|
1713
|
+
"bench",
|
|
1714
|
+
"global_allocator",
|
|
1715
|
+
"windows_sdk",
|
|
1716
|
+
"no_std",
|
|
1717
|
+
"panic_handler",
|
|
1718
|
+
"proc_macro",
|
|
1719
|
+
"proc_macro_derive",
|
|
1720
|
+
"proc_macro_attribute"
|
|
1721
|
+
]);
|
|
1722
|
+
const qualified = attrName.includes("::");
|
|
1723
|
+
const kind = BUILTIN.has(attrName) ? "builtin_directive" : "applied_callable_candidate";
|
|
1724
|
+
ctx.relationships.push({
|
|
1725
|
+
source: String(declName.text),
|
|
1726
|
+
target: attrName,
|
|
1727
|
+
fromSymbol: String(declName.text),
|
|
1728
|
+
toSymbol: `attribute:${attrName}`,
|
|
1729
|
+
type: ASTEngine.RelationshipType.Decorates,
|
|
1730
|
+
// Both endpoints are read from the tree — the declaration's name node and the
|
|
1731
|
+
// attribute's own name node — and the sibling relation is structural, so 1.0.
|
|
1732
|
+
confidence: 1,
|
|
1733
|
+
...this.nodeLine(nameNode) ? { line_number: this.nodeLine(nameNode) } : {},
|
|
1734
|
+
metadata: {
|
|
1735
|
+
attributeKind: kind,
|
|
1736
|
+
isQualifiedPath: qualified,
|
|
1737
|
+
rustSpecific: true,
|
|
1738
|
+
decoratesDeclaration: String(decorated.type)
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
} else {
|
|
1744
|
+
this.noOwner(ctx, "decorates:no_declaration_sibling");
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
const detectFw = (name) => {
|
|
1748
|
+
if (!ctx.metadata.detectedFrameworks?.includes(name)) {
|
|
1749
|
+
ctx.metadata.detectedFrameworks = [...ctx.metadata.detectedFrameworks || [], name];
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
{
|
|
1753
|
+
const decoratedMix = (() => {
|
|
1754
|
+
let sib = node.nextSibling;
|
|
1755
|
+
while (sib) {
|
|
1756
|
+
const st = String(sib.type);
|
|
1757
|
+
if (st === "line_comment" || st === "block_comment" || st === "attribute_item") {
|
|
1758
|
+
sib = sib.nextSibling;
|
|
1759
|
+
continue;
|
|
1760
|
+
}
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
return sib;
|
|
1764
|
+
})();
|
|
1765
|
+
const attrOuterMix = node.namedChild(0);
|
|
1766
|
+
const attrNameMix = attrOuterMix ? attrOuterMix.childForFieldName?.("path") ?? attrOuterMix.namedChild(0) : null;
|
|
1767
|
+
if (decoratedMix && attrNameMix && String(attrNameMix.text) === "derive") {
|
|
1768
|
+
const declNameMix = decoratedMix.childForFieldName?.("name") ?? (() => {
|
|
1769
|
+
for (let k = 0; k < decoratedMix.namedChildCount; k++) {
|
|
1770
|
+
const c = decoratedMix.namedChild(k);
|
|
1771
|
+
const ct = String(c?.type ?? "");
|
|
1772
|
+
if (ct === "type_identifier" || ct === "identifier") return c;
|
|
1773
|
+
}
|
|
1774
|
+
return null;
|
|
1775
|
+
})();
|
|
1776
|
+
if (declNameMix && declNameMix.text) {
|
|
1777
|
+
const ttMix = attrOuterMix ? attrOuterMix.childForFieldName?.("arguments") ?? attrOuterMix.namedChild(1) : null;
|
|
1778
|
+
if (ttMix) {
|
|
1779
|
+
const seenMix = /* @__PURE__ */ new Set();
|
|
1780
|
+
const args = [];
|
|
1781
|
+
for (let k = 0; k < ttMix.childCount; k++) {
|
|
1782
|
+
const c = ttMix.child(k);
|
|
1783
|
+
if (!c) continue;
|
|
1784
|
+
const isIdent = String(c.type) === "identifier";
|
|
1785
|
+
args.push({ text: String(c.text), isIdent, node: c });
|
|
1786
|
+
}
|
|
1787
|
+
let k2 = 0;
|
|
1788
|
+
while (k2 < args.length) {
|
|
1789
|
+
if (!args[k2].isIdent) {
|
|
1790
|
+
k2++;
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
let pathEnd = k2;
|
|
1794
|
+
while (pathEnd + 2 < args.length && args[pathEnd + 1].text === "::" && args[pathEnd + 2].isIdent) pathEnd += 2;
|
|
1795
|
+
const traitName = args[pathEnd].text;
|
|
1796
|
+
const lineNode = args[pathEnd].node;
|
|
1797
|
+
if (traitName.length > 0 && !seenMix.has(traitName)) {
|
|
1798
|
+
seenMix.add(traitName);
|
|
1799
|
+
ctx.relationships.push({
|
|
1800
|
+
source: String(declNameMix.text),
|
|
1801
|
+
target: traitName,
|
|
1802
|
+
fromSymbol: String(declNameMix.text),
|
|
1803
|
+
toSymbol: `derive:${traitName}`,
|
|
1804
|
+
type: ASTEngine.RelationshipType.MixesIn,
|
|
1805
|
+
// The trait name and the declaration name are both tree-read nodes, and the
|
|
1806
|
+
// derive args are structural children of the attribute — 1.0.
|
|
1807
|
+
confidence: 1,
|
|
1808
|
+
...this.nodeLine(lineNode) ? { line_number: this.nodeLine(lineNode) } : {},
|
|
1809
|
+
metadata: {
|
|
1810
|
+
isMixin: true,
|
|
1811
|
+
deriveComposition: true,
|
|
1812
|
+
rustSpecific: true,
|
|
1813
|
+
decoratesDeclaration: String(decoratedMix.type)
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
k2 = pathEnd + 1;
|
|
1818
|
+
}
|
|
1819
|
+
if (seenMix.size === 0) this.noOwner(ctx, "mixes_in:derive_no_traits");
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
if (text.startsWith("#[")) {
|
|
1825
|
+
const dStart = text.indexOf("derive(");
|
|
1826
|
+
if (dStart >= 0) {
|
|
1827
|
+
const body = text.slice(dStart + 7);
|
|
1828
|
+
if (hasBareWord(body, "Serialize") || hasBareWord(body, "Deserialize")) detectFw("serde");
|
|
1829
|
+
if (hasBareWord(body, "Parser") || hasBareWord(body, "Subcommand") || hasBareWord(body, "Args") || hasBareWord(body, "ValueEnum")) detectFw("clap");
|
|
1830
|
+
if (text.includes("thiserror")) detectFw("thiserror");
|
|
1831
|
+
}
|
|
1832
|
+
if (text.includes("tokio::main") || text.includes("tokio::test") || text.includes("tokio::flavor")) detectFw("tokio");
|
|
1833
|
+
if (text.includes("axum::")) detectFw("axum");
|
|
1834
|
+
if (text.includes("rocket::")) detectFw("rocket");
|
|
1835
|
+
if (text.includes("tracing::instrument")) detectFw("tracing");
|
|
1836
|
+
if (text.includes("derive(Parser")) detectFw("clap");
|
|
1837
|
+
}
|
|
1838
|
+
if (routeVerbOf(text)) {
|
|
1839
|
+
detectFw("actix_web");
|
|
1840
|
+
let existing = void 0;
|
|
1841
|
+
if (ctx.metadata.frameworkPatterns) {
|
|
1842
|
+
for (let i = 0; i < ctx.metadata.frameworkPatterns.length; i++) {
|
|
1843
|
+
if (ctx.metadata.frameworkPatterns[i].framework === "Actix Web") {
|
|
1844
|
+
existing = ctx.metadata.frameworkPatterns[i];
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
if (!existing) {
|
|
1850
|
+
ctx.metadata.frameworkPatterns = [
|
|
1851
|
+
...ctx.metadata.frameworkPatterns || [],
|
|
1852
|
+
{ framework: "Actix Web", patterns: ["route_handlers"] }
|
|
1853
|
+
];
|
|
1854
|
+
}
|
|
1855
|
+
let targetNode = node.parent;
|
|
1856
|
+
if (targetNode?.type === "source_file" || targetNode?.type === "declaration_list") {
|
|
1857
|
+
targetNode = node.nextNamedSibling;
|
|
1858
|
+
}
|
|
1859
|
+
if (targetNode && targetNode.type === "function_item") {
|
|
1860
|
+
const nameNode = targetNode.childForFieldName("name");
|
|
1861
|
+
if (nameNode) {
|
|
1862
|
+
const routePath = extractRoutePath(text);
|
|
1863
|
+
if (routePath) {
|
|
1864
|
+
ctx.relationships.push({
|
|
1865
|
+
source: nameNode.text,
|
|
1866
|
+
target: routePath,
|
|
1867
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
1868
|
+
// M59.A7 (#825): the function that carries the attribute.
|
|
1869
|
+
...this.nodeLine(nameNode) ? { line_number: this.nodeLine(nameNode) } : {},
|
|
1870
|
+
metadata: {}
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
if (text.includes("#[test]") || text.includes("#[cfg(test)") || text.includes("#[tokio::test")) {
|
|
1877
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
1878
|
+
if (ctx.metadata.patterns.indexOf("Testing") === -1) ctx.metadata.patterns.push("Testing");
|
|
1879
|
+
}
|
|
1880
|
+
if (text.includes("derive")) {
|
|
1881
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
1882
|
+
if (ctx.metadata.patterns.indexOf("Derive Macros") === -1) ctx.metadata.patterns.push("Derive Macros");
|
|
1883
|
+
}
|
|
1884
|
+
if (text.includes("must_use")) {
|
|
1885
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
1886
|
+
if (ctx.metadata.patterns.indexOf("Must Use") === -1) ctx.metadata.patterns.push("Must Use");
|
|
1887
|
+
}
|
|
1888
|
+
if (text.includes("deprecated")) {
|
|
1889
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
1890
|
+
if (ctx.metadata.patterns.indexOf("Deprecated API") === -1) ctx.metadata.patterns.push("Deprecated API");
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
extractIdentifier(node, field) {
|
|
1894
|
+
const child = node.childForFieldName(field);
|
|
1895
|
+
if (child) return child.text;
|
|
1896
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1897
|
+
const c = node.namedChild(i);
|
|
1898
|
+
if (c && (c.type === "identifier" || c.type === "type_identifier" || c.type === "scoped_identifier")) {
|
|
1899
|
+
return c.text;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
/** Strip a trailing generic parameter list: `UserService<T>` → `UserService`. */
|
|
1905
|
+
stripGenerics(name) {
|
|
1906
|
+
const lt = name.indexOf("<");
|
|
1907
|
+
return lt >= 0 ? name.slice(0, lt) : name;
|
|
1908
|
+
}
|
|
1909
|
+
isPublic(node) {
|
|
1910
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1911
|
+
const child = node.namedChild(i);
|
|
1912
|
+
if (child && child.type === "visibility_modifier") {
|
|
1913
|
+
return true;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return false;
|
|
1917
|
+
}
|
|
1918
|
+
/** D3 (rust second review): unrestricted `pub` only. `pub(crate)` /
|
|
1919
|
+
* `pub(super)` items are not externally importable and must not enter
|
|
1920
|
+
* the export list (the M49 importable-units invariant). Symbol
|
|
1921
|
+
* visibility fields are untouched — only export gating changes. */
|
|
1922
|
+
isUnrestrictedPublic(node) {
|
|
1923
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1924
|
+
const child = node.namedChild(i);
|
|
1925
|
+
if (child && child.type === "visibility_modifier" && child.text.trim() === "pub") {
|
|
1926
|
+
return true;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return false;
|
|
1930
|
+
}
|
|
1931
|
+
/** D2 (rust second review): the `Self` type of the enclosing impl block
|
|
1932
|
+
* (`Self { x }` constructs the implementing type). Trait bodies have no
|
|
1933
|
+
* impl to resolve (null → existing filtered behavior).
|
|
1934
|
+
*
|
|
1935
|
+
* R10 (M60 standing rule): `Self` is the implemented TYPE, so it resolves to
|
|
1936
|
+
* that type's NOMINAL name — and when the type has no nominal name the
|
|
1937
|
+
* honest answer is ABSENCE (R7), not the impl block's synthetic name:
|
|
1938
|
+
* `Self` in `impl ToPkgId for (T, U)` is `(T, U)`, which is not
|
|
1939
|
+
* `anonymous_impl` and is not a name at all, so no edge is emitted. */
|
|
1940
|
+
enclosingImplType(node) {
|
|
1941
|
+
let cur = node ? node.parent : null;
|
|
1942
|
+
while (cur) {
|
|
1943
|
+
if (cur.type === "impl_item") return this.implSelfTypeName(cur.childForFieldName("type"));
|
|
1944
|
+
cur = cur.parent;
|
|
1945
|
+
}
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* M14 RUST RESIDUAL — the receiver forms whose type Rust fixes at PARSE time.
|
|
1950
|
+
*
|
|
1951
|
+
* `self` in a method body IS the enclosing impl type, and `Self` is that type's own
|
|
1952
|
+
* keyword alias, so `self.m()` / `Self::m()` name the enclosing type with no inference.
|
|
1953
|
+
* The turbofish wrappers (`self.m::<T>()`, `Self::m::<T>()`) are unwrapped to the same
|
|
1954
|
+
* base. Every other callee shape this analyzer sees — `self.repository.find()`,
|
|
1955
|
+
* `x.y.z()`, `Type::new()` — is either an arbitrary receiver EXPRESSION or a path whose
|
|
1956
|
+
* first segment is a module or an external type, and none of those is decidable from the
|
|
1957
|
+
* tree alone. Returns `null` outside an `impl` block (a trait default body has no impl
|
|
1958
|
+
* type, and `Self` there resolves to the trait — not to a member key this lane
|
|
1959
|
+
* publishes).
|
|
1960
|
+
*/
|
|
1961
|
+
selfReceiverImplType(node, functionNode) {
|
|
1962
|
+
const base = functionNode.type === "generic_function" ? functionNode.childForFieldName("function") ?? functionNode : functionNode;
|
|
1963
|
+
if (base.type === "field_expression") {
|
|
1964
|
+
if (base.childForFieldName("value")?.text !== "self") return null;
|
|
1965
|
+
} else if (base.type === "scoped_identifier") {
|
|
1966
|
+
if (!base.text.startsWith("Self::")) return null;
|
|
1967
|
+
} else {
|
|
1968
|
+
return null;
|
|
1969
|
+
}
|
|
1970
|
+
return this.enclosingImplType(node);
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* M14 RUST RESIDUAL — publish the qualifier the lane already computes, ON BOTH ENDS.
|
|
1974
|
+
*
|
|
1975
|
+
* ## The defect, measured
|
|
1976
|
+
*
|
|
1977
|
+
* `incoming-gate.ts rust rs 400` reads 42,930 edges / 23,637 unresolved, and the largest
|
|
1978
|
+
* unresolved bucket is the call whose callee carries a receiver (13,423). `handleCallExpression`
|
|
1979
|
+
* sets `metadata.qualified` (the receiver expression) on every call edge and resolves on the
|
|
1980
|
+
* BARE LEAF — and a member name is not identifying, so the edge has no answer.
|
|
1981
|
+
*
|
|
1982
|
+
* ## What this pass does, and the three things it refuses
|
|
1983
|
+
*
|
|
1984
|
+
* It re-spells the target as `<ImplType>::<member>` for a `self.m()` / `Self::m()` call
|
|
1985
|
+
* **only when THIS FILE declares that exact member key in an `impl` block** (the keys are the
|
|
1986
|
+
* `metadata.fqn` values `createSymbol` publishes). Three refusals, each one a measurement:
|
|
1987
|
+
*
|
|
1988
|
+
* 1. **No dot, ever.** `parallel-indexer` strategy 3 fires on any target containing a `.`:
|
|
1989
|
+
* it reduces the target to everything after the first dot and binds `MIN(sym.id)` — the
|
|
1990
|
+
* arbitrary tie-break the indexer measures ~75% wrong. `::` cannot reach that branch, so
|
|
1991
|
+
* a miss here is an honest NULL rather than a confident wrong answer.
|
|
1992
|
+
* 2. **Own file only.** A cross-file member would resolve too, and would move the edge from
|
|
1993
|
+
* `sameFileUnproven` to `arbitraryTwin` in the gate's binding-intent test — the
|
|
1994
|
+
* file-crossing class that must not grow. The own-file test is what keeps it flat.
|
|
1995
|
+
* 3. **Calls only.** An `overrides` target rewritten to `<Trait>::<m>` has the same
|
|
1996
|
+
* cross-file hazard (the trait is usually declared elsewhere) and the emitter's own note
|
|
1997
|
+
* records that a dotted variant was measured 100% unresolved; it is left alone.
|
|
1998
|
+
*
|
|
1999
|
+
* `toSymbol` and `metadata.qualified` are NOT touched: they are the `qualified_calls` PAYLOAD
|
|
2000
|
+
* (pinned by `validation/m14/fixtures/rust/qualified-calls-ownership.rs`), a different fact
|
|
2001
|
+
* from the resolution key, and re-spelling them would change a pinned surface to serve a
|
|
2002
|
+
* resolution it does not perform.
|
|
2003
|
+
*/
|
|
2004
|
+
publishQualifiedCallTargets(ctx) {
|
|
2005
|
+
const declaredMembers = /* @__PURE__ */ new Set();
|
|
2006
|
+
for (const s of ctx.symbols) {
|
|
2007
|
+
const fqn = s.metadata?.fqn;
|
|
2008
|
+
if (typeof fqn === "string" && fqn) declaredMembers.add(fqn);
|
|
2009
|
+
}
|
|
2010
|
+
if (declaredMembers.size === 0) return;
|
|
2011
|
+
for (const rel of ctx.relationships) {
|
|
2012
|
+
if (rel.type !== ASTEngine.RelationshipType.Calls) continue;
|
|
2013
|
+
const target = String(rel.target ?? "");
|
|
2014
|
+
if (!target || target.includes("::")) continue;
|
|
2015
|
+
let key = ctx.qualifiedCallCandidates?.get(rel) ?? null;
|
|
2016
|
+
if (!key) {
|
|
2017
|
+
const q = String(rel.metadata?.qualified ?? "");
|
|
2018
|
+
if (isTwoSegmentIdentPath(q) && q.endsWith(`::${target}`)) key = q;
|
|
2019
|
+
}
|
|
2020
|
+
if (!key || !declaredMembers.has(key)) continue;
|
|
2021
|
+
rel.target = key;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* R10 (M60 standing rule) — the NAME of the type an impl implements.
|
|
2026
|
+
*
|
|
2027
|
+
* An impl symbol is named after the type it implements (stable identity, see
|
|
2028
|
+
* `handleImpl`), and an `&mut`/`&`/`*const`/`(...)` wrapper does not change
|
|
2029
|
+
* what the type is NAMED: `&mut ProcessBuilder` is the type `ProcessBuilder`
|
|
2030
|
+
* behind a reference, so those wrappers are TRANSPARENT here and the
|
|
2031
|
+
* innermost nominal name is the answer. The old rule took
|
|
2032
|
+
* `stripGenerics(typeNode.text)` verbatim, so for a wrapped type it published
|
|
2033
|
+
* SOURCE TEXT as a name — `&mut ProcessBuilder` rode the impl SYMBOL, the
|
|
2034
|
+
* `implements` edge's SOURCE and every `Self` resolution inside the block,
|
|
2035
|
+
* and the M60 target-spelling census convicted it (6 `references` targets over
|
|
2036
|
+
* 2 files: `&mut ProcessBuilder` x5, `&mut Execs` x1). The differential could
|
|
2037
|
+
* never see it: its truth derives from the same rule, so both sides spelled
|
|
2038
|
+
* the fabrication identically.
|
|
2039
|
+
*
|
|
2040
|
+
* A type with NO nominal name — a slice `[u8]`, a tuple `(T, U)`, a unit, a
|
|
2041
|
+
* never type, a function pointer, `dyn Trait`, `<T as Trait>::Assoc` — has no
|
|
2042
|
+
* name to publish, so this returns `null` and each caller decides the honest
|
|
2043
|
+
* state: the SYMBOL falls back to the family's declared synthetic
|
|
2044
|
+
* (`RUST_UNNAMED_IMPL`, keeping the source spelling in metadata where it is
|
|
2045
|
+
* evidence rather than an identifier), and `Self` RESOLUTION falls back to
|
|
2046
|
+
* ABSENCE.
|
|
2047
|
+
*
|
|
2048
|
+
* The two callers then read the SAME unwrapped node two different ways, and
|
|
2049
|
+
* the difference is deliberate and pre-existing:
|
|
2050
|
+
*
|
|
2051
|
+
* `implTypeSymbolName` keeps a path's QUALIFICATION (`impl From<X> for
|
|
2052
|
+
* std::io::Error` publishes `std::io::Error`) because the deferred
|
|
2053
|
+
* constructor resolution reads in-file SYMBOL names — flattening a foreign
|
|
2054
|
+
* `io::Error` to `Error` would make `Error::new()` inside the impl look like
|
|
2055
|
+
* a local construction. Measured: flattening it added 12 spurious
|
|
2056
|
+
* `instantiates` edges on ripgrep alone.
|
|
2057
|
+
* `implSelfTypeName` takes the LEAF (`Error`), because a `references` or
|
|
2058
|
+
* `instantiates` TARGET is a leaf by the family's leaf-target contract
|
|
2059
|
+
* (#419-B).
|
|
2060
|
+
*/
|
|
2061
|
+
nominalTypeNode(typeNode) {
|
|
2062
|
+
let cur = typeNode;
|
|
2063
|
+
for (let guard = 0; cur && guard < 16; guard++) {
|
|
2064
|
+
const t2 = cur.type;
|
|
2065
|
+
if (t2 === "reference_type" || t2 === "pointer_type" || t2 === "parenthesized_type") {
|
|
2066
|
+
const inner = cur.childForFieldName("type") ?? cur.namedChild(cur.namedChildCount - 1);
|
|
2067
|
+
if (!inner) return null;
|
|
2068
|
+
cur = inner;
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
2071
|
+
break;
|
|
2072
|
+
}
|
|
2073
|
+
if (!cur) return null;
|
|
2074
|
+
const t = cur.type;
|
|
2075
|
+
if (t === "type_identifier" || t === "primitive_type" || t === "scoped_type_identifier" || t === "generic_type") {
|
|
2076
|
+
return cur;
|
|
2077
|
+
}
|
|
2078
|
+
return null;
|
|
2079
|
+
}
|
|
2080
|
+
/** The impl SYMBOL's name: the unwrapped type's own spelling, generics
|
|
2081
|
+
* stripped, path qualification PRESERVED (`std::io::Error`, `Vec`). */
|
|
2082
|
+
implTypeSymbolName(typeNode) {
|
|
2083
|
+
const n = this.nominalTypeNode(typeNode);
|
|
2084
|
+
if (!n) return null;
|
|
2085
|
+
return this.stripGenerics(n.text) || null;
|
|
2086
|
+
}
|
|
2087
|
+
/** The name `Self` resolves to inside the impl: the unwrapped type's LEAF
|
|
2088
|
+
* (`Error` for `std::io::Error`), or `null` when the type has no nominal
|
|
2089
|
+
* name at all. */
|
|
2090
|
+
implSelfTypeName(typeNode) {
|
|
2091
|
+
const n = this.nominalTypeNode(typeNode);
|
|
2092
|
+
if (!n) return null;
|
|
2093
|
+
return this.stripGenerics(n.text).split("::").filter(Boolean).pop() || null;
|
|
2094
|
+
}
|
|
2095
|
+
createSymbol(name, kind, node, parent, extraMetadata = {}) {
|
|
2096
|
+
let ownerName = parent?.name;
|
|
2097
|
+
let ownerKind = parent?.kind;
|
|
2098
|
+
if (parent?.metadata?.type === "impl") {
|
|
2099
|
+
ownerName = parent.metadata?.target ?? parent.name;
|
|
2100
|
+
}
|
|
2101
|
+
if (parent?.kind === ASTEngine.SymbolKind.Trait) {
|
|
2102
|
+
ownerKind = ASTEngine.SymbolKind.Interface;
|
|
2103
|
+
}
|
|
2104
|
+
if (parent?.kind === ASTEngine.SymbolKind.Interface) {
|
|
2105
|
+
ownerKind = ASTEngine.SymbolKind.Interface;
|
|
2106
|
+
}
|
|
2107
|
+
const symbol = {
|
|
2108
|
+
name,
|
|
2109
|
+
kind,
|
|
2110
|
+
// Rust default visibility is private - never leave it undefined.
|
|
2111
|
+
visibility: "private",
|
|
2112
|
+
range: {
|
|
2113
|
+
startLine: node.startPosition.row + 1,
|
|
2114
|
+
// Columns are 1-based family-wide (+1 was missing).
|
|
2115
|
+
startColumn: node.startPosition.column + 1,
|
|
2116
|
+
endLine: node.endPosition.row + 1,
|
|
2117
|
+
endColumn: node.endPosition.column + 1,
|
|
2118
|
+
startIndex: node.startIndex,
|
|
2119
|
+
endIndex: node.endIndex
|
|
2120
|
+
},
|
|
2121
|
+
id: `${parent ? parent.id + "." : ""}${name}`,
|
|
2122
|
+
parent: parent ? parent.name : void 0,
|
|
2123
|
+
ownerName,
|
|
2124
|
+
ownerKind,
|
|
2125
|
+
metadata: { ...extraMetadata }
|
|
2126
|
+
};
|
|
2127
|
+
symbol.id = this.uniqueId(symbol.id);
|
|
2128
|
+
if (kind === ASTEngine.SymbolKind.Function || kind === ASTEngine.SymbolKind.Method) {
|
|
2129
|
+
const memberList = node.parent;
|
|
2130
|
+
const implNode = memberList?.parent;
|
|
2131
|
+
if (memberList?.type === "declaration_list" && implNode?.type === "impl_item") {
|
|
2132
|
+
const implType = this.implSelfTypeName(implNode.childForFieldName("type"));
|
|
2133
|
+
if (implType) symbol.metadata.fqn = `${implType}::${name}`;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return symbol;
|
|
2137
|
+
}
|
|
2138
|
+
/** Hand out a per-file-unique id: first claim wins plain, later claims get
|
|
2139
|
+
* a deterministic numeric suffix (`Foo`, `Foo#2`, …). RSD3. */
|
|
2140
|
+
uniqueId(base) {
|
|
2141
|
+
let id = base;
|
|
2142
|
+
let n = 2;
|
|
2143
|
+
while (this.usedIds.has(id)) {
|
|
2144
|
+
id = `${base}#${n}`;
|
|
2145
|
+
n++;
|
|
2146
|
+
}
|
|
2147
|
+
this.usedIds.add(id);
|
|
2148
|
+
return id;
|
|
2149
|
+
}
|
|
2150
|
+
handleStruct(node, ctx, parent) {
|
|
2151
|
+
const name = this.extractIdentifier(node, "name");
|
|
2152
|
+
if (!name) return null;
|
|
2153
|
+
const symbol = this.createSymbol(name, ASTEngine.SymbolKind.Struct, node, parent);
|
|
2154
|
+
if (this.isPublic(node)) symbol.visibility = "public";
|
|
2155
|
+
ctx.symbols.push(symbol);
|
|
2156
|
+
let fieldDeclarationList = null;
|
|
2157
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2158
|
+
const child = node.namedChild(i);
|
|
2159
|
+
if (child && child.type === "field_declaration_list") {
|
|
2160
|
+
fieldDeclarationList = child;
|
|
2161
|
+
break;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
if (fieldDeclarationList) {
|
|
2165
|
+
for (let i = 0; i < fieldDeclarationList.namedChildCount; i++) {
|
|
2166
|
+
const fieldNode = fieldDeclarationList.namedChild(i);
|
|
2167
|
+
if (fieldNode && fieldNode.type === "field_declaration") {
|
|
2168
|
+
const fieldName = this.extractIdentifier(fieldNode, "name");
|
|
2169
|
+
if (fieldName) {
|
|
2170
|
+
const fieldSymbol = this.createSymbol(fieldName, ASTEngine.SymbolKind.Property, fieldNode, symbol, {
|
|
2171
|
+
type: "field"
|
|
2172
|
+
});
|
|
2173
|
+
fieldSymbol.visibility = this.isPublic(fieldNode) ? "public" : "private";
|
|
2174
|
+
ctx.symbols.push(fieldSymbol);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
if (this.isUnrestrictedPublic(node) && !parent) {
|
|
2180
|
+
ctx.exports.push({
|
|
2181
|
+
name,
|
|
2182
|
+
type: ASTEngine.SymbolKind.Class,
|
|
2183
|
+
range: symbol.range,
|
|
2184
|
+
isType: true
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
return symbol;
|
|
2188
|
+
}
|
|
2189
|
+
handleTrait(node, ctx, parent) {
|
|
2190
|
+
const nameNode = node.childForFieldName("name");
|
|
2191
|
+
let name = nameNode ? nameNode.text : null;
|
|
2192
|
+
if (!name) return null;
|
|
2193
|
+
const genericParams = [];
|
|
2194
|
+
const nextSibling = nameNode ? nameNode.nextNamedSibling : null;
|
|
2195
|
+
if (nextSibling && nextSibling.type === "type_parameters") {
|
|
2196
|
+
genericParams.push(nextSibling.text);
|
|
2197
|
+
}
|
|
2198
|
+
const symbol = this.createSymbol(name, ASTEngine.SymbolKind.Trait, node, parent, {
|
|
2199
|
+
type: "trait",
|
|
2200
|
+
...genericParams.length > 0 ? { genericParameters: genericParams } : {}
|
|
2201
|
+
});
|
|
2202
|
+
if (genericParams.length > 0) symbol.genericParameters = genericParams;
|
|
2203
|
+
ctx.symbols.push(symbol);
|
|
2204
|
+
const boundsNode = node.childForFieldName("bounds");
|
|
2205
|
+
if (boundsNode) {
|
|
2206
|
+
for (let i = 0; i < boundsNode.namedChildCount; i++) {
|
|
2207
|
+
let bound = boundsNode.namedChild(i);
|
|
2208
|
+
if (bound?.type === "higher_ranked_trait_bound") {
|
|
2209
|
+
for (let j = 0; j < bound.namedChildCount; j++) {
|
|
2210
|
+
const inner = bound.namedChild(j);
|
|
2211
|
+
if (inner && (inner.type === "type_identifier" || inner.type === "scoped_type_identifier" || inner.type === "generic_type")) {
|
|
2212
|
+
bound = inner;
|
|
2213
|
+
break;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
if (bound && (bound.type === "type_identifier" || bound.type === "scoped_type_identifier" || bound.type === "generic_type")) {
|
|
2218
|
+
ctx.relationships.push({
|
|
2219
|
+
source: name,
|
|
2220
|
+
target: this.extractTraitName(bound),
|
|
2221
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
2222
|
+
confidence: 1,
|
|
2223
|
+
// M59.A7 (#825): the supertrait's own line in the bound list.
|
|
2224
|
+
...this.nodeLine(bound) ? { line_number: this.nodeLine(bound) } : {},
|
|
2225
|
+
metadata: { rustSpecific: true, traitBound: true }
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
if (this.isUnrestrictedPublic(node) && !parent) {
|
|
2231
|
+
ctx.exports.push({
|
|
2232
|
+
name,
|
|
2233
|
+
type: ASTEngine.SymbolKind.Trait,
|
|
2234
|
+
range: symbol.range,
|
|
2235
|
+
isType: true
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
return symbol;
|
|
2239
|
+
}
|
|
2240
|
+
handleImpl(node, ctx, parent) {
|
|
2241
|
+
let traitNode = null;
|
|
2242
|
+
let typeNode = null;
|
|
2243
|
+
traitNode = node.childForFieldName("trait");
|
|
2244
|
+
typeNode = node.childForFieldName("type");
|
|
2245
|
+
if (!traitNode && !typeNode) {
|
|
2246
|
+
let forIndex = -1;
|
|
2247
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2248
|
+
const c = node.namedChild(i);
|
|
2249
|
+
if (c && c.type === "for") {
|
|
2250
|
+
forIndex = i;
|
|
2251
|
+
break;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2255
|
+
const c = node.namedChild(i);
|
|
2256
|
+
if (!c) continue;
|
|
2257
|
+
if (c.type === "type_identifier" || c.type === "scoped_type_identifier" || c.type === "generic_type") {
|
|
2258
|
+
if (forIndex !== -1) {
|
|
2259
|
+
if (i < forIndex) traitNode = c;
|
|
2260
|
+
if (i > forIndex) typeNode = c;
|
|
2261
|
+
} else {
|
|
2262
|
+
typeNode = c;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
if (!typeNode) return null;
|
|
2268
|
+
const traitName = traitNode ? traitNode.text : void 0;
|
|
2269
|
+
const nominal = this.implTypeSymbolName(typeNode);
|
|
2270
|
+
const implName = nominal === null ? RUST_UNNAMED_IMPL : `${nominal}::r#impl`;
|
|
2271
|
+
const symbol = this.createSymbol(
|
|
2272
|
+
implName,
|
|
2273
|
+
ASTEngine.SymbolKind.Impl,
|
|
2274
|
+
node,
|
|
2275
|
+
parent,
|
|
2276
|
+
nominal ? { type: "impl", target: nominal } : { type: "impl", implementedType: typeNode.text }
|
|
2277
|
+
);
|
|
2278
|
+
ctx.symbols.push(symbol);
|
|
2279
|
+
if (traitNode && traitName) {
|
|
2280
|
+
ctx.relationships.push({
|
|
2281
|
+
source: nominal ?? implName,
|
|
2282
|
+
target: this.extractTraitName(traitNode),
|
|
2283
|
+
type: ASTEngine.RelationshipType.Implements,
|
|
2284
|
+
// INCOMING_RUST: same absent-confidence defect as `calls` — 252 of 252
|
|
2285
|
+
// `implements` rows on this lane's corpus carried none. 1.0: the
|
|
2286
|
+
// `impl Trait for Type` clause names both ends.
|
|
2287
|
+
confidence: 1,
|
|
2288
|
+
// M59.A7 (#825): the TRAIT reference's line — the site that names the
|
|
2289
|
+
// target, not the implementing type's.
|
|
2290
|
+
...this.nodeLine(traitNode) ? { line_number: this.nodeLine(traitNode) } : {},
|
|
2291
|
+
metadata: {}
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
return symbol;
|
|
2295
|
+
}
|
|
2296
|
+
/** Leaf type name of a trait reference: `super::Repository<User>` → `Repository`. */
|
|
2297
|
+
extractTraitName(node) {
|
|
2298
|
+
const nameField = node.childForFieldName("name") || node.namedChild(0);
|
|
2299
|
+
if (nameField && nameField.type === "type_identifier") return nameField.text;
|
|
2300
|
+
if (nameField && nameField.type === "scoped_type_identifier") {
|
|
2301
|
+
const last = nameField.namedChild(nameField.namedChildCount - 1);
|
|
2302
|
+
if (last && last.type === "type_identifier") return last.text;
|
|
2303
|
+
}
|
|
2304
|
+
if (node.type === "type_identifier") return node.text;
|
|
2305
|
+
return this.stripGenerics(node.text).split("::").pop() || node.text;
|
|
2306
|
+
}
|
|
2307
|
+
handleMod(node, ctx, parent) {
|
|
2308
|
+
const name = this.extractIdentifier(node, "name");
|
|
2309
|
+
if (!name) return null;
|
|
2310
|
+
const symbol = this.createSymbol(name, ASTEngine.SymbolKind.Module, node, parent, { type: "mod" });
|
|
2311
|
+
ctx.symbols.push(symbol);
|
|
2312
|
+
if (this.isUnrestrictedPublic(node) && !parent) {
|
|
2313
|
+
ctx.exports.push({
|
|
2314
|
+
name,
|
|
2315
|
+
type: ASTEngine.SymbolKind.Module,
|
|
2316
|
+
range: symbol.range,
|
|
2317
|
+
isType: false
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
return symbol;
|
|
2321
|
+
}
|
|
2322
|
+
handleEnum(node, ctx, parent) {
|
|
2323
|
+
const name = this.extractIdentifier(node, "name");
|
|
2324
|
+
if (!name) return null;
|
|
2325
|
+
const symbol = this.createSymbol(name, ASTEngine.SymbolKind.Enum, node, parent, { type: "enum" });
|
|
2326
|
+
ctx.symbols.push(symbol);
|
|
2327
|
+
if (this.isUnrestrictedPublic(node) && !parent) {
|
|
2328
|
+
ctx.exports.push({
|
|
2329
|
+
name,
|
|
2330
|
+
type: ASTEngine.SymbolKind.Enum,
|
|
2331
|
+
range: symbol.range,
|
|
2332
|
+
isType: true
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
return symbol;
|
|
2336
|
+
}
|
|
2337
|
+
handleFunction(node, ctx, parent) {
|
|
2338
|
+
const name = this.extractIdentifier(node, "name");
|
|
2339
|
+
if (!name) return null;
|
|
2340
|
+
const isMethod = parent && (parent.metadata?.type === "impl" || parent.metadata?.type === "trait" || parent.kind === ASTEngine.SymbolKind.Trait || parent.kind === ASTEngine.SymbolKind.Impl);
|
|
2341
|
+
let isConstFn = false;
|
|
2342
|
+
let isAsyncFn = false;
|
|
2343
|
+
let isUnsafeFn = false;
|
|
2344
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2345
|
+
const c = node.namedChild(i);
|
|
2346
|
+
if (c && c.type === "const") isConstFn = true;
|
|
2347
|
+
if (c && c.type === "async") isAsyncFn = true;
|
|
2348
|
+
if (c && c.type === "unsafe") isUnsafeFn = true;
|
|
2349
|
+
}
|
|
2350
|
+
const symbol = this.createSymbol(
|
|
2351
|
+
name,
|
|
2352
|
+
isMethod ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function,
|
|
2353
|
+
node,
|
|
2354
|
+
parent,
|
|
2355
|
+
{
|
|
2356
|
+
type: "function",
|
|
2357
|
+
isConstFn,
|
|
2358
|
+
isAsyncFn,
|
|
2359
|
+
isUnsafeFn,
|
|
2360
|
+
// #612 store-phase: a rust `function_item` carries a body; a
|
|
2361
|
+
// `function_signature_item` does not — that node is a trait method
|
|
2362
|
+
// signature or an `extern "C" { fn f(); }` FFI declaration. Both used
|
|
2363
|
+
// to publish an indistinguishable `function` symbol, so the extern
|
|
2364
|
+
// declaration could win name resolution against the definition that
|
|
2365
|
+
// implements it in another file. (The node-type discriminator already
|
|
2366
|
+
// existed here for scope purposes; only the published fact was absent.)
|
|
2367
|
+
...node.type === "function_signature_item" ? { isDeclaration: true } : {}
|
|
2368
|
+
}
|
|
2369
|
+
);
|
|
2370
|
+
if (isConstFn) symbol.metadata = { ...symbol.metadata, isConst: true };
|
|
2371
|
+
if (isAsyncFn) symbol.isAsync = true;
|
|
2372
|
+
if (isUnsafeFn) symbol.metadata = { ...symbol.metadata, isUnsafe: true };
|
|
2373
|
+
let visibility = "private";
|
|
2374
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2375
|
+
const c = node.namedChild(i);
|
|
2376
|
+
if (c && c.type === "visibility_modifier") {
|
|
2377
|
+
visibility = c.text.includes("crate") ? "crate" : c.text.includes("super") ? "super" : "public";
|
|
2378
|
+
break;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
symbol.visibility = visibility;
|
|
2382
|
+
const returnTypeNode = node.childForFieldName("return_type");
|
|
2383
|
+
if (returnTypeNode) {
|
|
2384
|
+
symbol.returnType = returnTypeNode.text;
|
|
2385
|
+
}
|
|
2386
|
+
const typeParametersNode = node.childForFieldName("type_parameters");
|
|
2387
|
+
if (typeParametersNode) {
|
|
2388
|
+
symbol.genericParameters = [typeParametersNode.text];
|
|
2389
|
+
}
|
|
2390
|
+
symbol.complexity = this.functionComplexityRust(node);
|
|
2391
|
+
const parametersNode = node.childForFieldName("parameters");
|
|
2392
|
+
if (parametersNode) {
|
|
2393
|
+
const params = [];
|
|
2394
|
+
for (let i = 0; i < parametersNode.namedChildCount; i++) {
|
|
2395
|
+
const p = parametersNode.namedChild(i);
|
|
2396
|
+
if (p && (p.type === "parameter" || p.type === "self_parameter")) {
|
|
2397
|
+
const pName = p.childForFieldName("pattern") || (p.type === "self_parameter" ? p : null);
|
|
2398
|
+
const pType = p.childForFieldName("type");
|
|
2399
|
+
const name2 = pName ? pName.text.replace("&", "").replace("mut ", "").trim() : "";
|
|
2400
|
+
if (name2) params.push({ name: name2, type: pType ? pType.text : "any" });
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
if (params.length > 0) symbol.parameters = params;
|
|
2404
|
+
}
|
|
2405
|
+
ctx.symbols.push(symbol);
|
|
2406
|
+
if (this.isUnrestrictedPublic(node) && !isMethod && !parent) {
|
|
2407
|
+
ctx.exports.push({
|
|
2408
|
+
name,
|
|
2409
|
+
type: ASTEngine.SymbolKind.Function,
|
|
2410
|
+
range: symbol.range,
|
|
2411
|
+
isType: false
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
return symbol;
|
|
2415
|
+
}
|
|
2416
|
+
/** R1: file-visible VALUE names mapped to owner sets ("" = file scope).
|
|
2417
|
+
* Kinds that name run-time entities readable bare: Constant
|
|
2418
|
+
* (const/static), Function (callbacks), EnumMember (variants),
|
|
2419
|
+
* Struct/Enum/Union/Type (constructors and unit values). Modules,
|
|
2420
|
+
* macros, traits, methods, properties never resolve bare. Function
|
|
2421
|
+
* bodies and const/static bodies contribute no declarations (locals);
|
|
2422
|
+
* impl/trait members are associated items (never bare-readable).
|
|
2423
|
+
* Use-imported leaves resolve file-wide (imports bind names too). */
|
|
2424
|
+
collectValueOwners(rootNode, owners, useImported) {
|
|
2425
|
+
const add = (name, modOwners) => {
|
|
2426
|
+
if (!name) return;
|
|
2427
|
+
let s = owners.get(name);
|
|
2428
|
+
if (!s) {
|
|
2429
|
+
s = /* @__PURE__ */ new Set();
|
|
2430
|
+
owners.set(name, s);
|
|
2431
|
+
}
|
|
2432
|
+
if (modOwners.length === 0) s.add("");
|
|
2433
|
+
else for (const m of modOwners) s.add(m);
|
|
2434
|
+
};
|
|
2435
|
+
const leftmostId = (n) => {
|
|
2436
|
+
let cur = n;
|
|
2437
|
+
while (cur) {
|
|
2438
|
+
if (cur.type === "identifier") return cur.text;
|
|
2439
|
+
cur = cur.namedChild(0);
|
|
2440
|
+
}
|
|
2441
|
+
return null;
|
|
2442
|
+
};
|
|
2443
|
+
const stack = [
|
|
2444
|
+
{ n: rootNode, mods: [], inFn: false, inImpl: false }
|
|
2445
|
+
];
|
|
2446
|
+
while (stack.length > 0) {
|
|
2447
|
+
const { n, mods, inFn, inImpl } = stack.pop();
|
|
2448
|
+
const t = n.type;
|
|
2449
|
+
if (t === "mod_item") {
|
|
2450
|
+
const nm = n.childForFieldName("name")?.text;
|
|
2451
|
+
const body = n.namedChild(n.namedChildCount - 1);
|
|
2452
|
+
const hasBody = !!body && body.type === "declaration_list";
|
|
2453
|
+
if (nm && hasBody) {
|
|
2454
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2455
|
+
const c = n.namedChild(i);
|
|
2456
|
+
if (c) stack.push({ n: c, mods: [...mods, nm], inFn, inImpl });
|
|
2457
|
+
}
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
} else if (t === "function_item") {
|
|
2461
|
+
if (!inImpl) {
|
|
2462
|
+
const nm = n.childForFieldName("name")?.text;
|
|
2463
|
+
if (nm) {
|
|
2464
|
+
if (mods.length === 0) add(nm, []);
|
|
2465
|
+
else add(nm, mods);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2469
|
+
const c = n.namedChild(i);
|
|
2470
|
+
if (c) stack.push({ n: c, mods, inFn: true, inImpl });
|
|
2471
|
+
}
|
|
2472
|
+
continue;
|
|
2473
|
+
} else if (t === "impl_item" || t === "trait_item") {
|
|
2474
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2475
|
+
const c = n.namedChild(i);
|
|
2476
|
+
if (c) stack.push({ n: c, mods, inFn, inImpl: true });
|
|
2477
|
+
}
|
|
2478
|
+
continue;
|
|
2479
|
+
} else if (!inFn && (t === "const_item" || t === "static_item")) {
|
|
2480
|
+
const nm = n.childForFieldName("name")?.text ?? leftmostId(n);
|
|
2481
|
+
if (nm && nm !== "_" && !inImpl) add(nm, mods);
|
|
2482
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2483
|
+
const c = n.namedChild(i);
|
|
2484
|
+
if (c) stack.push({ n: c, mods, inFn: true, inImpl });
|
|
2485
|
+
}
|
|
2486
|
+
continue;
|
|
2487
|
+
} else if (!inFn && !inImpl && (t === "struct_item" || t === "enum_item" || t === "union_item" || t === "type_item")) {
|
|
2488
|
+
const nm = n.childForFieldName("name")?.text;
|
|
2489
|
+
if (nm) add(nm, mods);
|
|
2490
|
+
} else if (t === "enum_variant") {
|
|
2491
|
+
const nm = n.childForFieldName("name")?.text ?? n.namedChild(0)?.text;
|
|
2492
|
+
if (nm && !inFn) add(nm, mods);
|
|
2493
|
+
} else if (t === "use_declaration") {
|
|
2494
|
+
for (const leaf of this.useLeafNames(n)) {
|
|
2495
|
+
if (leaf && leaf !== "self" && leaf !== "*") useImported.add(leaf);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2499
|
+
const c = n.namedChild(i);
|
|
2500
|
+
if (c) stack.push({ n: c, mods, inFn, inImpl });
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
/** R1: leaf names bound by one `use` (alias-aware, self-aware). Mirrors
|
|
2505
|
+
* handleUseDeclaration's leaf logic without building ImportInfo rows. */
|
|
2506
|
+
useLeafNames(node) {
|
|
2507
|
+
const out = [];
|
|
2508
|
+
const walk = (n) => {
|
|
2509
|
+
if (!n) return;
|
|
2510
|
+
if (n.type === "identifier" || n.type === "type_identifier") {
|
|
2511
|
+
if (n.parent && (n.parent.type === "use_declaration" || n.parent.type === "use_list")) out.push(n.text);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
if (n.type === "scoped_identifier") {
|
|
2515
|
+
const par = n.parent;
|
|
2516
|
+
if (par && (par.type === "use_declaration" || par.type === "use_list")) {
|
|
2517
|
+
const last = n.namedChild(n.namedChildCount - 1);
|
|
2518
|
+
if (last && (last.type === "identifier" || last.type === "type_identifier")) {
|
|
2519
|
+
const t = last.text;
|
|
2520
|
+
if (t && t !== "self" && t !== "super" && t !== "crate") out.push(t);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
if (n.type === "use_as_clause") {
|
|
2526
|
+
const alias = n.childForFieldName("alias") || n.namedChild(n.namedChildCount - 1);
|
|
2527
|
+
if (alias) out.push(alias.text);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
if (n.type === "self") return;
|
|
2531
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2532
|
+
const c = n.namedChild(i);
|
|
2533
|
+
if (c) walk(c);
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2537
|
+
const c = node.namedChild(i);
|
|
2538
|
+
if (c && c.type !== "visibility_modifier" && c.type !== "use" && c.type !== "crate") walk(c);
|
|
2539
|
+
}
|
|
2540
|
+
return out;
|
|
2541
|
+
}
|
|
2542
|
+
/** R1: per-function bound names (parameters + all pattern bindings in the
|
|
2543
|
+
* body, closures folded in). Keyed by function_item startIndex. */
|
|
2544
|
+
/** R1: enclosing inline-module names for one node (outermost first). */
|
|
2545
|
+
enclosingMods(node) {
|
|
2546
|
+
const out = [];
|
|
2547
|
+
let cur = node ? node.parent : null;
|
|
2548
|
+
while (cur) {
|
|
2549
|
+
if (cur.type === "mod_item") {
|
|
2550
|
+
const nm = cur.childForFieldName("name")?.text;
|
|
2551
|
+
if (nm) out.unshift(nm);
|
|
2552
|
+
}
|
|
2553
|
+
cur = cur.parent;
|
|
2554
|
+
}
|
|
2555
|
+
return out;
|
|
2556
|
+
}
|
|
2557
|
+
/** R1: identifiers bound by one PATTERN subtree. In DECLARATION
|
|
2558
|
+
* positions (let/param/for/let-condition patterns) every lowercase
|
|
2559
|
+
* identifier binds, and UPPERCASE ones bind too (the grammar declares —
|
|
2560
|
+
* `let G`, `fn f(G)`). In MATCH positions (arms, or-patterns) lowercase
|
|
2561
|
+
* binds but UPPERCASE is a path pattern (`None`, `RED`) — reported via
|
|
2562
|
+
* `uses` when the probe says resolvable, otherwise bound (harmless).
|
|
2563
|
+
* Tuple-struct HEADS (`Some` in `Some(v)`) never bind anywhere — they
|
|
2564
|
+
* name the constructor (emitted as uses when resolvable). Accepted
|
|
2565
|
+
* residual: UPPERCASE locals read as uses when a same-named global
|
|
2566
|
+
* exists AND the binding sits in match position. */
|
|
2567
|
+
collectPatternIds(node, out, resolves, isDecl) {
|
|
2568
|
+
if (!node) return;
|
|
2569
|
+
if (node.type === "identifier") {
|
|
2570
|
+
const nm = node.text;
|
|
2571
|
+
if (!nm || nm === "_" || nm.startsWith("$")) return;
|
|
2572
|
+
const first = nm.charCodeAt(0);
|
|
2573
|
+
if (first >= 65 && first <= 90 && !isDecl && resolves(nm)) return;
|
|
2574
|
+
out.add(nm);
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (node.type === "type_identifier" || node.type === "scoped_identifier" || node.type === "field_identifier" || node.type === "lifetime" || node.type === "primitive_type") return;
|
|
2578
|
+
if (node.type === "tuple_struct_pattern") {
|
|
2579
|
+
for (let i = 1; i < node.namedChildCount; i++) {
|
|
2580
|
+
const c = node.namedChild(i);
|
|
2581
|
+
if (c) this.collectPatternIds(c, out, resolves, isDecl);
|
|
2582
|
+
}
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2586
|
+
const c = node.namedChild(i);
|
|
2587
|
+
if (c) this.collectPatternIds(c, out, resolves, isDecl);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
/** R1: per-function bound-name sets. The uppercase path-pattern rule
|
|
2591
|
+
* needs resolvability: probe = file-scope owners, use-imported leaves,
|
|
2592
|
+
* or the function's own enclosing modules. */
|
|
2593
|
+
collectFunctionBindings(rootNode, valueOwners, useImported) {
|
|
2594
|
+
const out = /* @__PURE__ */ new Map();
|
|
2595
|
+
const stack = [rootNode];
|
|
2596
|
+
const fns = [];
|
|
2597
|
+
while (stack.length > 0) {
|
|
2598
|
+
const n = stack.pop();
|
|
2599
|
+
if (n.type === "function_item") fns.push(n);
|
|
2600
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2601
|
+
const c = n.namedChild(i);
|
|
2602
|
+
if (c) stack.push(c);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
for (const fn of fns) {
|
|
2606
|
+
const bound = /* @__PURE__ */ new Set();
|
|
2607
|
+
const fnMods = this.enclosingMods(fn);
|
|
2608
|
+
const resolves = (nm) => {
|
|
2609
|
+
const owners = valueOwners.get(nm);
|
|
2610
|
+
if (!owners) return useImported.has(nm);
|
|
2611
|
+
if (owners.has("")) return true;
|
|
2612
|
+
if (useImported.has(nm)) return true;
|
|
2613
|
+
for (const m of fnMods) if (owners.has(m)) return true;
|
|
2614
|
+
return false;
|
|
2615
|
+
};
|
|
2616
|
+
const params = fn.childForFieldName("parameters");
|
|
2617
|
+
if (params) {
|
|
2618
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
2619
|
+
const p = params.namedChild(i);
|
|
2620
|
+
if (!p) continue;
|
|
2621
|
+
if (p.type === "self_parameter") continue;
|
|
2622
|
+
const pat = p.childForFieldName("pattern") ?? p;
|
|
2623
|
+
this.collectPatternIds(pat, bound, resolves, true);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
const body = [fn];
|
|
2627
|
+
while (body.length > 0) {
|
|
2628
|
+
const m = body.pop();
|
|
2629
|
+
if (m.type === "let_declaration") {
|
|
2630
|
+
const pat = m.childForFieldName("pattern") ?? m.namedChild(0);
|
|
2631
|
+
this.collectPatternIds(pat, bound, resolves, true);
|
|
2632
|
+
} else if (m.type === "let_condition") {
|
|
2633
|
+
this.collectPatternIds(m.namedChild(0), bound, resolves, false);
|
|
2634
|
+
} else if (m.type === "for_expression") {
|
|
2635
|
+
this.collectPatternIds(m.namedChild(0), bound, resolves, true);
|
|
2636
|
+
} else if (m.type === "closure_expression") {
|
|
2637
|
+
for (let i = 0; i < m.namedChildCount; i++) {
|
|
2638
|
+
const c = m.namedChild(i);
|
|
2639
|
+
if (c && c.type === "closure_parameters") {
|
|
2640
|
+
this.collectPatternIds(c, bound, resolves, true);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
} else if (m.type === "match_arm") {
|
|
2644
|
+
const patField = m.childForFieldName("pattern");
|
|
2645
|
+
if (patField) {
|
|
2646
|
+
const psub = [patField];
|
|
2647
|
+
while (psub.length > 0) {
|
|
2648
|
+
const q = psub.pop();
|
|
2649
|
+
if (q !== patField && (q.type === "let_declaration" || q.type === "let_condition" || q.type === "for_expression")) continue;
|
|
2650
|
+
if (q.type === "match_pattern" || q.type === "identifier" || q.type === "tuple_pattern" || q.type === "struct_pattern" || q.type === "tuple_struct_pattern" || q.type === "ref_pattern" || q.type === "or_pattern" || q.type === "slice_pattern") {
|
|
2651
|
+
this.collectPatternIds(q, bound, resolves, false);
|
|
2652
|
+
}
|
|
2653
|
+
for (let i = q.namedChildCount - 1; i >= 0; i--) {
|
|
2654
|
+
const c = q.namedChild(i);
|
|
2655
|
+
if (c) psub.push(c);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
const sub = [m];
|
|
2660
|
+
while (sub.length > 0) {
|
|
2661
|
+
const q = sub.pop();
|
|
2662
|
+
if (q !== m && (q.type === "let_declaration" || q.type === "let_condition" || q.type === "for_expression")) continue;
|
|
2663
|
+
if (q.type === "tuple_pattern" || q.type === "struct_pattern" || q.type === "tuple_struct_pattern" || q.type === "ref_pattern" || q.type === "or_pattern" || q.type === "slice_pattern") {
|
|
2664
|
+
this.collectPatternIds(q, bound, resolves, false);
|
|
2665
|
+
}
|
|
2666
|
+
for (let i = q.namedChildCount - 1; i >= 0; i--) {
|
|
2667
|
+
const c = q.namedChild(i);
|
|
2668
|
+
if (c) sub.push(c);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
if (m !== fn && (m.type === "function_item" || m.type === "const_item" || m.type === "static_item")) continue;
|
|
2673
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
2674
|
+
const c = m.namedChild(i);
|
|
2675
|
+
if (c) body.push(c);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
out.set(fn.startIndex, bound);
|
|
2679
|
+
}
|
|
2680
|
+
return out;
|
|
2681
|
+
}
|
|
2682
|
+
/** R1: one bare `identifier` occurrence — emit Uses iff it names a
|
|
2683
|
+
* file-visible VALUE and survives the exclusion discipline (declaration
|
|
2684
|
+
* positions incl. leftmost rule, call callees incl. turbofish climb,
|
|
2685
|
+
* member leaves, qualified non-leaves, preprocessor/macros/attributes/
|
|
2686
|
+
* use-paths, LHS writes, where-bounds, shadowing). */
|
|
2687
|
+
handleIdentifierRead(node, ctx, parent, valueOwners, useImported, fnBindings) {
|
|
2688
|
+
const name = node.text;
|
|
2689
|
+
if (!name || name === "_" || name === "self" || name === "Self" || name === "super" || name === "crate" || name.startsWith("$")) return;
|
|
2690
|
+
const p = node.parent;
|
|
2691
|
+
if (this.hasAncestor(node, "macro_definition", ["macro_invocation"])) return;
|
|
2692
|
+
{
|
|
2693
|
+
let mtop = node;
|
|
2694
|
+
let mup = p;
|
|
2695
|
+
while (mup && (mup.type === "scoped_identifier" || mup.type === "generic_function")) {
|
|
2696
|
+
mtop = mup;
|
|
2697
|
+
mup = mup.parent;
|
|
2698
|
+
}
|
|
2699
|
+
if (mup && mup.type === "macro_invocation") {
|
|
2700
|
+
const mfield = mup.childForFieldName("macro");
|
|
2701
|
+
if (mfield && mfield.startIndex === mtop.startIndex && mfield.endIndex >= mtop.endIndex) return;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
if (this.hasAncestor(node, "attribute_item", ["function_item", "struct_item", "enum_item", "impl_item", "trait_item", "mod_item"])) return;
|
|
2705
|
+
if (this.hasAncestor(node, "use_declaration", null)) return;
|
|
2706
|
+
if (p && p.type === "parameter") {
|
|
2707
|
+
const params = p.parent;
|
|
2708
|
+
const holder = params?.parent;
|
|
2709
|
+
if (params?.type === "parameters" && holder && holder.type === "function_type") return;
|
|
2710
|
+
if (params?.type === "parameters") {
|
|
2711
|
+
let owner = params.parent;
|
|
2712
|
+
while (owner && owner.type !== "function_item" && owner.type !== "function_signature_item" && owner.type !== "closure_expression") owner = owner.parent;
|
|
2713
|
+
if (owner && owner.type === "function_signature_item") return;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
if (this.hasAncestor(node, "where_predicate", null)) return;
|
|
2717
|
+
let top = node;
|
|
2718
|
+
let up = p;
|
|
2719
|
+
while (up && (up.type === "generic_function" || up.type === "parenthesized_expression")) {
|
|
2720
|
+
top = up;
|
|
2721
|
+
up = up.parent;
|
|
2722
|
+
}
|
|
2723
|
+
if (up && up.type === "call_expression") {
|
|
2724
|
+
const fnField = up.childForFieldName("function");
|
|
2725
|
+
if (fnField && fnField.startIndex === top.startIndex && fnField.endIndex >= top.endIndex) return;
|
|
2726
|
+
const first = up.namedChild(0);
|
|
2727
|
+
if (first && first.startIndex === top.startIndex && first.endIndex >= top.endIndex) return;
|
|
2728
|
+
}
|
|
2729
|
+
if (p && p.type === "field_expression") {
|
|
2730
|
+
const field = p.childForFieldName("field");
|
|
2731
|
+
if (field && field.startIndex === node.startIndex) return;
|
|
2732
|
+
}
|
|
2733
|
+
if (p && p.type === "scoped_identifier") {
|
|
2734
|
+
const last = p.namedChild(p.namedChildCount - 1);
|
|
2735
|
+
if (!last || last.startIndex !== node.startIndex) return;
|
|
2736
|
+
} else if (this.hasAncestor(node, "scoped_identifier", ["call_expression", "field_expression"])) {
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
if (p && (p.type === "assignment_expression" || p.type === "compound_assignment_expr")) {
|
|
2740
|
+
const lhs = p.namedChild(0);
|
|
2741
|
+
if (lhs && lhs.startIndex === node.startIndex && lhs.endIndex >= node.endIndex) return;
|
|
2742
|
+
}
|
|
2743
|
+
if (p && this.isItemNamePosition(node, p)) return;
|
|
2744
|
+
let fnNode = p;
|
|
2745
|
+
while (fnNode && fnNode.type !== "function_item") fnNode = fnNode.parent;
|
|
2746
|
+
if (fnNode) {
|
|
2747
|
+
const bound = fnBindings.get(fnNode.startIndex);
|
|
2748
|
+
if (bound && bound.has(name)) return;
|
|
2749
|
+
}
|
|
2750
|
+
const owners = valueOwners.get(name);
|
|
2751
|
+
let ok = false;
|
|
2752
|
+
if (owners) {
|
|
2753
|
+
if (owners.has("")) ok = true;
|
|
2754
|
+
else if (useImported.has(name)) ok = true;
|
|
2755
|
+
else {
|
|
2756
|
+
const mods = this.enclosingMods(node);
|
|
2757
|
+
for (const m of mods) {
|
|
2758
|
+
if (owners.has(m)) {
|
|
2759
|
+
ok = true;
|
|
2760
|
+
break;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
} else if (useImported.has(name)) {
|
|
2765
|
+
ok = true;
|
|
2766
|
+
}
|
|
2767
|
+
if (!ok) return;
|
|
2768
|
+
const source = this.edgeSource(parent);
|
|
2769
|
+
if (!source) {
|
|
2770
|
+
this.noOwner(ctx, "uses:identifier_read");
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
const relKey = `${source}::uses::${name}`;
|
|
2774
|
+
if (ctx.emittedUses.has(relKey)) return;
|
|
2775
|
+
ctx.emittedUses.add(relKey);
|
|
2776
|
+
const inMacroArgument = this.hasAncestor(node, "macro_invocation", null);
|
|
2777
|
+
ctx.relationships.push({
|
|
2778
|
+
source,
|
|
2779
|
+
target: name,
|
|
2780
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2781
|
+
confidence: inMacroArgument ? 0.6 : 1,
|
|
2782
|
+
// M59.A7 (#825): the identifier's own line.
|
|
2783
|
+
...this.nodeLine(node) ? { line_number: this.nodeLine(node) } : {},
|
|
2784
|
+
metadata: {
|
|
2785
|
+
relationshipType: "identifier_usage",
|
|
2786
|
+
nodeType: node.type,
|
|
2787
|
+
...inMacroArgument ? { macroArgument: true, reasonCode: "unexpanded-macro-argument" } : {}
|
|
2788
|
+
}
|
|
2789
|
+
});
|
|
2790
|
+
if (inMacroArgument) ctx.macroArgumentReads++;
|
|
2791
|
+
}
|
|
2792
|
+
/** True when `node` sits under an ancestor of one of `types` before
|
|
2793
|
+
* crossing any node of a stop type (null stops = never stop early). */
|
|
2794
|
+
hasAncestor(node, types, stopAt) {
|
|
2795
|
+
const want = Array.isArray(types) ? types : [types];
|
|
2796
|
+
let cur = node.parent;
|
|
2797
|
+
while (cur) {
|
|
2798
|
+
if (want.includes(cur.type)) return true;
|
|
2799
|
+
if (stopAt) {
|
|
2800
|
+
for (const s of stopAt) {
|
|
2801
|
+
if (cur.type === s) return false;
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
cur = cur.parent;
|
|
2805
|
+
}
|
|
2806
|
+
return false;
|
|
2807
|
+
}
|
|
2808
|
+
/** True when `node` is the defining NAME occurrence of its item parent
|
|
2809
|
+
* (first identifier-ish named child of an entity-declaring node). */
|
|
2810
|
+
isItemNamePosition(node, parent) {
|
|
2811
|
+
switch (parent.type) {
|
|
2812
|
+
case "function_item":
|
|
2813
|
+
// RUST-MR: a TRAIT method DECLARATION is a `function_signature_item` (no
|
|
2814
|
+
// body), and the switch below listed `function_item` only — so a trait
|
|
2815
|
+
// method's own NAME fell through to the read sweep. When the name also
|
|
2816
|
+
// resolved (a use-imported leaf of the same name, or a file-visible value)
|
|
2817
|
+
// it published a `uses` edge for its own DECLARATION, and at
|
|
2818
|
+
// cargo-test-support/src/lib.rs:1477 that edge was a SELF-EDGE
|
|
2819
|
+
// (`uses current_dir -> current_dir`). The IMPLEMENTATION of the same
|
|
2820
|
+
// method never did, which is what made the class look like a trait quirk
|
|
2821
|
+
// rather than a missing node type. 10 sites survived the macro-callee fix
|
|
2822
|
+
// in the same 550-file slice; the whole class is trait method signatures
|
|
2823
|
+
// whose name collides with an import.
|
|
2824
|
+
case "function_signature_item":
|
|
2825
|
+
case "struct_item":
|
|
2826
|
+
case "enum_item":
|
|
2827
|
+
case "trait_item":
|
|
2828
|
+
case "mod_item":
|
|
2829
|
+
case "type_item":
|
|
2830
|
+
case "associated_type":
|
|
2831
|
+
case "static_item":
|
|
2832
|
+
case "const_item":
|
|
2833
|
+
case "enum_variant":
|
|
2834
|
+
case "field_declaration":
|
|
2835
|
+
case "extern_crate_declaration":
|
|
2836
|
+
break;
|
|
2837
|
+
default:
|
|
2838
|
+
return false;
|
|
2839
|
+
}
|
|
2840
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
2841
|
+
const c = parent.namedChild(i);
|
|
2842
|
+
if (c && (c.type === "identifier" || c.type === "type_identifier" || c.type === "field_identifier")) {
|
|
2843
|
+
return c.startIndex === node.startIndex && c.endIndex === node.endIndex;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
return false;
|
|
2847
|
+
}
|
|
2848
|
+
/** Returns the symbol it created, so a `const`/`static` can own its own
|
|
2849
|
+
* initializer's reads (R7). `null` when it declares nothing. */
|
|
2850
|
+
handleVariable(node, ctx, parent) {
|
|
2851
|
+
let patternNode = node.childForFieldName("pattern");
|
|
2852
|
+
if (!patternNode) {
|
|
2853
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2854
|
+
const child = node.namedChild(i);
|
|
2855
|
+
if (child && child.type === "identifier") {
|
|
2856
|
+
patternNode = child;
|
|
2857
|
+
break;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
if (!patternNode) return null;
|
|
2862
|
+
const name = patternNode.text;
|
|
2863
|
+
if (name === "_") return null;
|
|
2864
|
+
if (node.type === "let_declaration") return null;
|
|
2865
|
+
const symbol = this.createSymbol(name, ASTEngine.SymbolKind.Variable, node, parent, { type: node.type });
|
|
2866
|
+
ctx.symbols.push(symbol);
|
|
2867
|
+
if (this.isUnrestrictedPublic(node) && !parent) {
|
|
2868
|
+
ctx.exports.push({
|
|
2869
|
+
name,
|
|
2870
|
+
type: ASTEngine.SymbolKind.Variable,
|
|
2871
|
+
range: symbol.range,
|
|
2872
|
+
isType: false
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
return symbol;
|
|
2876
|
+
}
|
|
2877
|
+
handleUseDeclaration(node, ctx, parent) {
|
|
2878
|
+
const isPublic = this.isPublic(node);
|
|
2879
|
+
const collect = (n) => {
|
|
2880
|
+
if (n.type === "identifier" || n.type === "type_identifier") {
|
|
2881
|
+
return { path: n.text, items: [n.text], node: n };
|
|
2882
|
+
}
|
|
2883
|
+
if (n.type === "crate" || n.type === "super" || n.type === "self") {
|
|
2884
|
+
return { path: n.text, items: [n.text], node: n };
|
|
2885
|
+
}
|
|
2886
|
+
if (n.type === "scoped_identifier") {
|
|
2887
|
+
const pathNode = n.childForFieldName("path");
|
|
2888
|
+
const name = n.childForFieldName("name") || n.namedChild(n.namedChildCount - 1);
|
|
2889
|
+
const p = pathNode ? collect(pathNode).path + "::" : n.text.startsWith("::") ? "::" : "";
|
|
2890
|
+
return { path: p + (name ? name.text : ""), items: [name ? name.text : ""], node: n };
|
|
2891
|
+
}
|
|
2892
|
+
if (n.type === "scoped_use_list") {
|
|
2893
|
+
const pathNode = n.namedChild(0);
|
|
2894
|
+
const listNode = n.namedChild(1);
|
|
2895
|
+
const base = pathNode ? collect(pathNode).path : "";
|
|
2896
|
+
const items = [];
|
|
2897
|
+
if (listNode && listNode.type === "use_list") {
|
|
2898
|
+
for (let i = 0; i < listNode.namedChildCount; i++) {
|
|
2899
|
+
const c = listNode.namedChild(i);
|
|
2900
|
+
if (!c) continue;
|
|
2901
|
+
if (c.type === "self") {
|
|
2902
|
+
const leaf = base.split("::").filter(Boolean).pop();
|
|
2903
|
+
if (leaf) items.push(leaf);
|
|
2904
|
+
} else if (c.type === "use_as_clause") {
|
|
2905
|
+
const aliasName = c.childForFieldName("alias") || c.namedChild(c.namedChildCount - 1);
|
|
2906
|
+
if (aliasName) items.push(aliasName.text);
|
|
2907
|
+
} else if (c.type === "identifier" || c.type === "type_identifier") {
|
|
2908
|
+
items.push(c.text);
|
|
2909
|
+
} else if (c.type === "scoped_identifier") {
|
|
2910
|
+
const leaf = c.namedChild(c.namedChildCount - 1);
|
|
2911
|
+
if (leaf) items.push(leaf.text);
|
|
2912
|
+
} else if (c.type === "scoped_use_list") {
|
|
2913
|
+
const inner = collect(c);
|
|
2914
|
+
for (const it of inner.items) items.push(it);
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
return { path: base, items, node: n };
|
|
2919
|
+
}
|
|
2920
|
+
if (n.type === "use_list") {
|
|
2921
|
+
const items = [];
|
|
2922
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2923
|
+
const c = n.namedChild(i);
|
|
2924
|
+
if (!c) continue;
|
|
2925
|
+
if (c.type === "use_as_clause") {
|
|
2926
|
+
const aliasName = c.childForFieldName("alias") || c.namedChild(c.namedChildCount - 1);
|
|
2927
|
+
if (aliasName) items.push(aliasName.text);
|
|
2928
|
+
} else if (c.type === "identifier" || c.type === "type_identifier") {
|
|
2929
|
+
items.push(c.text);
|
|
2930
|
+
} else if (c.type === "scoped_identifier") {
|
|
2931
|
+
const leaf = c.namedChild(c.namedChildCount - 1);
|
|
2932
|
+
if (leaf) items.push(leaf.text);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
return { path: "", items, node: n };
|
|
2936
|
+
}
|
|
2937
|
+
if (n.type === "use_as_clause") {
|
|
2938
|
+
const path2 = n.childForFieldName("path") || n.namedChild(0);
|
|
2939
|
+
const aliasName = n.childForFieldName("alias") || n.namedChild(n.namedChildCount - 1);
|
|
2940
|
+
return { path: path2 ? collect(path2).path : n.text, items: [aliasName ? aliasName.text : path2 ? collect(path2).items[0] : n.text], node: n };
|
|
2941
|
+
}
|
|
2942
|
+
if (n.type === "use_wildcard") {
|
|
2943
|
+
const inner = n.namedChild(0);
|
|
2944
|
+
const path2 = inner ? collect(inner).path : n.text;
|
|
2945
|
+
return { path: path2, items: ["*"], node: n };
|
|
2946
|
+
}
|
|
2947
|
+
if (n.type === "use_as_clause" || n.type === "scoped_use_list" || n.type === "use_list" || n.type === "scoped_identifier") {
|
|
2948
|
+
return { path: n.text, items: [n.text], node: n };
|
|
2949
|
+
}
|
|
2950
|
+
return { path: "", items: [], node: n };
|
|
2951
|
+
};
|
|
2952
|
+
const rows = [];
|
|
2953
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2954
|
+
const c = node.namedChild(i);
|
|
2955
|
+
if (!c || c.type === "visibility_modifier" || c.type === "use" || c.type === "crate") continue;
|
|
2956
|
+
if (c.type === "use_list") {
|
|
2957
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
2958
|
+
const sub = c.namedChild(j);
|
|
2959
|
+
if (!sub || sub.type === ",") continue;
|
|
2960
|
+
const r2 = collect(sub);
|
|
2961
|
+
if (r2.items.length) rows.push(r2);
|
|
2962
|
+
}
|
|
2963
|
+
continue;
|
|
2964
|
+
}
|
|
2965
|
+
const r = collect(c);
|
|
2966
|
+
if (r.items.length) rows.push(r);
|
|
2967
|
+
}
|
|
2968
|
+
if (rows.length === 0) return;
|
|
2969
|
+
const mergedRows = [];
|
|
2970
|
+
for (const r of rows) {
|
|
2971
|
+
const ex = mergedRows.find((m) => m.path === r.path && m.items[0] !== "*" && r.items[0] !== "*");
|
|
2972
|
+
if (ex) ex.items.push(...r.items);
|
|
2973
|
+
else mergedRows.push({ path: r.path, items: [...r.items], node: r.node });
|
|
2974
|
+
}
|
|
2975
|
+
const modulePath = mergedRows[0].path;
|
|
2976
|
+
for (const row of mergedRows) {
|
|
2977
|
+
ctx.imports.push({
|
|
2978
|
+
module: row.path,
|
|
2979
|
+
source: row.path,
|
|
2980
|
+
items: row.items,
|
|
2981
|
+
// RUST-D27: the range is the PATH's, not the whole declaration's. A
|
|
2982
|
+
// multi-line group (`use {\n aho_corasick::AhoCorasick,\n bstr,\n};`)
|
|
2983
|
+
// gave every one of its rows the line of the `use` keyword, so the record
|
|
2984
|
+
// pointed at a line that does not bear the path it names — 56 such records
|
|
2985
|
+
// in ripgrep alone, found by the first audit ever run over this channel.
|
|
2986
|
+
range: (() => {
|
|
2987
|
+
const n2 = row.node ?? node;
|
|
2988
|
+
return {
|
|
2989
|
+
startLine: n2.startPosition.row + 1,
|
|
2990
|
+
startColumn: n2.startPosition.column + 1,
|
|
2991
|
+
endLine: n2.endPosition.row + 1,
|
|
2992
|
+
endColumn: n2.endPosition.column + 1,
|
|
2993
|
+
startIndex: n2.startIndex,
|
|
2994
|
+
endIndex: n2.endIndex
|
|
2995
|
+
};
|
|
2996
|
+
})(),
|
|
2997
|
+
metadata: { rustSpecific: true, isPublic }
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
void modulePath;
|
|
3001
|
+
}
|
|
3002
|
+
// Extract function calls at any scope (not just inside function bodies)
|
|
3003
|
+
handleCallExpression(node, ctx, parent) {
|
|
3004
|
+
const functionNode = node.childForFieldName("function");
|
|
3005
|
+
if (!functionNode) return;
|
|
3006
|
+
let target = "";
|
|
3007
|
+
let qualified = "";
|
|
3008
|
+
let selfSpelling = false;
|
|
3009
|
+
let calleeNameNode = null;
|
|
3010
|
+
if (functionNode.type === "field_expression") {
|
|
3011
|
+
const fieldNode = functionNode.childForFieldName("field");
|
|
3012
|
+
const valueNode = functionNode.childForFieldName("value");
|
|
3013
|
+
if (fieldNode) {
|
|
3014
|
+
target = fieldNode.text;
|
|
3015
|
+
qualified = valueNode ? `${valueNode.text}.${fieldNode.text}` : fieldNode.text;
|
|
3016
|
+
calleeNameNode = fieldNode;
|
|
3017
|
+
}
|
|
3018
|
+
} else if (functionNode.type === "scoped_identifier") {
|
|
3019
|
+
const nameNode = functionNode.childForFieldName("name") || functionNode.namedChild(functionNode.namedChildCount - 1);
|
|
3020
|
+
if (nameNode) {
|
|
3021
|
+
target = nameNode.text;
|
|
3022
|
+
qualified = functionNode.text;
|
|
3023
|
+
calleeNameNode = nameNode;
|
|
3024
|
+
}
|
|
3025
|
+
} else if (functionNode.type === "generic_function") {
|
|
3026
|
+
const baseNode = functionNode.childForFieldName("function");
|
|
3027
|
+
if (baseNode) {
|
|
3028
|
+
if (baseNode.type === "field_expression") {
|
|
3029
|
+
const fieldNode = baseNode.childForFieldName("field");
|
|
3030
|
+
if (fieldNode) {
|
|
3031
|
+
target = fieldNode.text;
|
|
3032
|
+
qualified = baseNode.text;
|
|
3033
|
+
calleeNameNode = fieldNode;
|
|
3034
|
+
}
|
|
3035
|
+
} else if (baseNode.type === "scoped_identifier") {
|
|
3036
|
+
const nameNode = baseNode.childForFieldName("name") || baseNode.namedChild(baseNode.namedChildCount - 1);
|
|
3037
|
+
if (nameNode) {
|
|
3038
|
+
target = nameNode.text;
|
|
3039
|
+
qualified = baseNode.text;
|
|
3040
|
+
calleeNameNode = nameNode;
|
|
3041
|
+
}
|
|
3042
|
+
} else if (baseNode.text === "Self") {
|
|
3043
|
+
const resolved = this.enclosingImplType(node);
|
|
3044
|
+
if (!resolved) return;
|
|
3045
|
+
target = resolved;
|
|
3046
|
+
qualified = resolved;
|
|
3047
|
+
calleeNameNode = baseNode;
|
|
3048
|
+
} else {
|
|
3049
|
+
target = baseNode.text;
|
|
3050
|
+
qualified = baseNode.text;
|
|
3051
|
+
calleeNameNode = baseNode;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
} else {
|
|
3055
|
+
if (functionNode.type === "identifier") {
|
|
3056
|
+
if (functionNode.text === "Self") {
|
|
3057
|
+
const resolved = this.enclosingImplType(node);
|
|
3058
|
+
if (!resolved) return;
|
|
3059
|
+
target = resolved;
|
|
3060
|
+
qualified = resolved;
|
|
3061
|
+
calleeNameNode = functionNode;
|
|
3062
|
+
selfSpelling = true;
|
|
3063
|
+
} else {
|
|
3064
|
+
target = functionNode.text;
|
|
3065
|
+
qualified = target;
|
|
3066
|
+
calleeNameNode = functionNode;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (!target) return;
|
|
3071
|
+
const source = this.edgeSource(parent);
|
|
3072
|
+
if (!source) {
|
|
3073
|
+
this.noOwner(ctx, "calls:call_expression");
|
|
3074
|
+
return;
|
|
3075
|
+
}
|
|
3076
|
+
const metadata = { qualified };
|
|
3077
|
+
if (ctx.awaitedCalls.has(node.startIndex)) {
|
|
3078
|
+
metadata.rustSpecific = true;
|
|
3079
|
+
metadata.isAwait = true;
|
|
3080
|
+
}
|
|
3081
|
+
const callRel = {
|
|
3082
|
+
source,
|
|
3083
|
+
target,
|
|
3084
|
+
fromSymbol: source,
|
|
3085
|
+
toSymbol: qualified,
|
|
3086
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
3087
|
+
// INCOMING_RUST: this edge published NO confidence at all — 17,083 of
|
|
3088
|
+
// 17,083 rows on this lane's corpus read `undefined`. Two consequences,
|
|
3089
|
+
// both invisible from inside the analyzer: `classifyEvidenceTier` defaults
|
|
3090
|
+
// a missing value to 1, so the evidence tier looked right, and every
|
|
3091
|
+
// consumer that computes a heuristic/deterministic ratio (honesty
|
|
3092
|
+
// contract §4) got no number to compute. The call site is a callee NAME
|
|
3093
|
+
// read from the tree, so 1.0 is the honest value and it is the value the
|
|
3094
|
+
// lane's own relationship table already claimed.
|
|
3095
|
+
confidence: 1,
|
|
3096
|
+
...this.nodeLine(calleeNameNode) ? { line_number: this.nodeLine(calleeNameNode) } : {},
|
|
3097
|
+
metadata
|
|
3098
|
+
};
|
|
3099
|
+
ctx.relationships.push(callRel);
|
|
3100
|
+
const selfImplType = this.selfReceiverImplType(node, functionNode);
|
|
3101
|
+
if (selfImplType) {
|
|
3102
|
+
const candidates = ctx.qualifiedCallCandidates ?? (ctx.qualifiedCallCandidates = /* @__PURE__ */ new Map());
|
|
3103
|
+
candidates.set(callRel, `${selfImplType}::${target}`);
|
|
3104
|
+
}
|
|
3105
|
+
const qcOwner = parent && (parent.kind === ASTEngine.SymbolKind.Function || parent.kind === ASTEngine.SymbolKind.Method);
|
|
3106
|
+
if (qcOwner) {
|
|
3107
|
+
const arr = parent.qualified_calls ?? [];
|
|
3108
|
+
arr.push(qualified);
|
|
3109
|
+
parent.qualified_calls = arr;
|
|
3110
|
+
}
|
|
3111
|
+
let instTarget = null;
|
|
3112
|
+
let bareSpelling = false;
|
|
3113
|
+
if (functionNode.type === "field_expression" || functionNode.type === "scoped_identifier") {
|
|
3114
|
+
const receiver = functionNode.childForFieldName("value") ?? functionNode.childForFieldName("path") ?? functionNode.namedChild(functionNode.namedChildCount - 2);
|
|
3115
|
+
if (target === "new" && receiver) {
|
|
3116
|
+
const recvText = this.stripGenerics(receiver.text);
|
|
3117
|
+
let leaf = recvText.split("::").filter(Boolean).pop() ?? "";
|
|
3118
|
+
if (leaf === "Self") leaf = this.enclosingImplType(node) ?? "";
|
|
3119
|
+
if (leaf.length > 0 && leaf.charCodeAt(0) >= 65 && leaf.charCodeAt(0) <= 90) instTarget = leaf;
|
|
3120
|
+
}
|
|
3121
|
+
} else if (target.length > 0 && target.charCodeAt(0) >= 65 && target.charCodeAt(0) <= 90) {
|
|
3122
|
+
instTarget = target;
|
|
3123
|
+
if (!selfSpelling) bareSpelling = true;
|
|
3124
|
+
}
|
|
3125
|
+
if (instTarget === "Self") instTarget = this.enclosingImplType(node) ?? null;
|
|
3126
|
+
if (instTarget) {
|
|
3127
|
+
const instNameNode = functionNode.type === "identifier" ? functionNode : functionNode.type === "scoped_identifier" ? functionNode.childForFieldName("path") ?? functionNode : functionNode.childForFieldName("value") ?? functionNode;
|
|
3128
|
+
ctx.pendingInstantiations.push({
|
|
3129
|
+
source,
|
|
3130
|
+
target: instTarget,
|
|
3131
|
+
qualified,
|
|
3132
|
+
line: this.nodeLine(instNameNode),
|
|
3133
|
+
bareSpelling,
|
|
3134
|
+
...bareSpelling ? { callScope: (() => {
|
|
3135
|
+
const scopePath = [];
|
|
3136
|
+
let cur = node.parent ?? null;
|
|
3137
|
+
let guard = 0;
|
|
3138
|
+
while (cur && guard++ < 128) {
|
|
3139
|
+
if (cur.type === "mod_item") {
|
|
3140
|
+
const nm = cur.childForFieldName?.("name") ?? cur.namedChild(0);
|
|
3141
|
+
scopePath.push(nm ? String(nm.text) : "");
|
|
3142
|
+
}
|
|
3143
|
+
cur = cur.parent;
|
|
3144
|
+
}
|
|
3145
|
+
return scopePath.reverse();
|
|
3146
|
+
})() } : {}
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
/** Per-function cyclomatic complexity (T1 family contract). */
|
|
3151
|
+
functionComplexityRust(node) {
|
|
3152
|
+
const decisionTypes = /* @__PURE__ */ new Set([
|
|
3153
|
+
// match ARMS are counted (per-case family granularity) - counting the
|
|
3154
|
+
// match_expression TOO would double-count every dispatch.
|
|
3155
|
+
"if_expression",
|
|
3156
|
+
"match_arm",
|
|
3157
|
+
"while_expression",
|
|
3158
|
+
"loop_expression",
|
|
3159
|
+
"for_expression",
|
|
3160
|
+
"try_expression"
|
|
3161
|
+
]);
|
|
3162
|
+
let c = 1;
|
|
3163
|
+
const st = [node];
|
|
3164
|
+
while (st.length) {
|
|
3165
|
+
const n = st.pop();
|
|
3166
|
+
if (decisionTypes.has(n.type)) c++;
|
|
3167
|
+
else if (n.type === "binary_expression") {
|
|
3168
|
+
const op = n.childForFieldName("operator")?.text;
|
|
3169
|
+
if (op === "&&" || op === "||") c++;
|
|
3170
|
+
}
|
|
3171
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
3172
|
+
const ch = n.namedChild(i);
|
|
3173
|
+
if (ch) st.push(ch);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
return c;
|
|
3177
|
+
}
|
|
3178
|
+
// Extract macro invocations: println!(), vec![], format!(), etc.
|
|
3179
|
+
handleMacroInvocation(node, ctx, parent) {
|
|
3180
|
+
const macroNode = node.childForFieldName("macro");
|
|
3181
|
+
if (!macroNode) return;
|
|
3182
|
+
let macroName = "";
|
|
3183
|
+
let macroNameNode = macroNode;
|
|
3184
|
+
if (macroNode.type === "scoped_identifier") {
|
|
3185
|
+
const nameNode = macroNode.childForFieldName("name") || macroNode.namedChild(macroNode.namedChildCount - 1);
|
|
3186
|
+
macroName = nameNode ? nameNode.text : macroNode.text;
|
|
3187
|
+
macroNameNode = nameNode ?? macroNode;
|
|
3188
|
+
} else {
|
|
3189
|
+
macroName = macroNode.text;
|
|
3190
|
+
}
|
|
3191
|
+
if (!macroName) return;
|
|
3192
|
+
if (macroName === "include") {
|
|
3193
|
+
const tt = macroTokenTreeOf(node);
|
|
3194
|
+
let includePath = null;
|
|
3195
|
+
if (tt) {
|
|
3196
|
+
for (let k = 0; k < tt.namedChildCount; k++) {
|
|
3197
|
+
const c = tt.namedChild(k);
|
|
3198
|
+
if (c && c.type === "string_literal") {
|
|
3199
|
+
const raw = c.text;
|
|
3200
|
+
includePath = raw.length >= 2 ? raw.slice(1, -1) : raw;
|
|
3201
|
+
break;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
if (includePath !== null && includePath.length > 0) {
|
|
3206
|
+
const scope = (() => {
|
|
3207
|
+
let cur = node.parent ?? null;
|
|
3208
|
+
let guard = 0;
|
|
3209
|
+
while (cur && guard++ < 64) {
|
|
3210
|
+
if (cur.type === "function_item") {
|
|
3211
|
+
const fn = cur.childForFieldName?.("name");
|
|
3212
|
+
return fn ? String(fn.text) : RUST_FILE_SOURCE;
|
|
3213
|
+
}
|
|
3214
|
+
if (cur.type === "mod_item") {
|
|
3215
|
+
const mn = cur.childForFieldName?.("name") ?? cur.namedChild(0);
|
|
3216
|
+
return mn ? String(mn.text) : RUST_FILE_SOURCE;
|
|
3217
|
+
}
|
|
3218
|
+
cur = cur.parent;
|
|
3219
|
+
}
|
|
3220
|
+
return RUST_FILE_SOURCE;
|
|
3221
|
+
})();
|
|
3222
|
+
let projectFile = null;
|
|
3223
|
+
if (ctx.projectRoot.length > 0 && path.isAbsolute(ctx.filePath)) {
|
|
3224
|
+
const cand = path.resolve(path.dirname(ctx.filePath), includePath);
|
|
3225
|
+
projectFile = confirmModuleFile([cand], ctx.projectRoot);
|
|
3226
|
+
}
|
|
3227
|
+
const partMetadata = {
|
|
3228
|
+
partRole: "library",
|
|
3229
|
+
isFileInclusion: true,
|
|
3230
|
+
rustSpecific: true,
|
|
3231
|
+
macroInvocation: true,
|
|
3232
|
+
...scope === RUST_FILE_SOURCE ? { fileLevel: true } : {}
|
|
3233
|
+
};
|
|
3234
|
+
if (projectFile) partMetadata.importProjectFile = projectFile;
|
|
3235
|
+
ctx.relationships.push({
|
|
3236
|
+
source: scope,
|
|
3237
|
+
target: includePath,
|
|
3238
|
+
fromSymbol: scope,
|
|
3239
|
+
toSymbol: includePath,
|
|
3240
|
+
type: ASTEngine.RelationshipType.PartOf,
|
|
3241
|
+
// Read from the tree (the literal's own node), and the claim is statted — 1.0.
|
|
3242
|
+
confidence: 1,
|
|
3243
|
+
...this.nodeLine(macroNameNode) ? { line_number: this.nodeLine(macroNameNode) } : {},
|
|
3244
|
+
metadata: partMetadata
|
|
3245
|
+
});
|
|
3246
|
+
} else if (tt) {
|
|
3247
|
+
this.noOwner(ctx, "part_of:include_non_literal");
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
if (macroName === "view" || macroName === "html" || macroName === "rsx" || macroName === "render" || macroName === "maud" || macroName === "markup") {
|
|
3251
|
+
const ttR = macroTokenTreeOf(node);
|
|
3252
|
+
if (ttR) {
|
|
3253
|
+
const flat = [];
|
|
3254
|
+
for (let k = 0; k < ttR.childCount; k++) {
|
|
3255
|
+
const c = ttR.child(k);
|
|
3256
|
+
if (!c) continue;
|
|
3257
|
+
flat.push({ text: String(c.text), type: String(c.type) });
|
|
3258
|
+
}
|
|
3259
|
+
const BARE_HTML = /* @__PURE__ */ new Set(["div", "span", "p", "a", "ul", "li", "ol", "h1", "h2", "h3", "h4", "h5", "h6", "br", "hr", "img", "input", "button", "form", "table", "tr", "td", "th", "section", "header", "footer", "nav", "main", "aside", "article", "label", "select", "option", "textarea", "canvas", "svg", "path", "g", "template", "slot"]);
|
|
3260
|
+
for (let k = 0; k + 1 < flat.length; k++) {
|
|
3261
|
+
if (flat[k].text === "<" && flat[k + 1].type === "identifier") {
|
|
3262
|
+
const tag = flat[k + 1].text;
|
|
3263
|
+
if (tag.length > 0 && !BARE_HTML.has(tag.toLowerCase())) {
|
|
3264
|
+
const hasSymbol = ctx.symbols.some((sym) => sym.name === tag);
|
|
3265
|
+
if (hasSymbol) {
|
|
3266
|
+
const renderScope = this.edgeSource(parent) ?? RUST_FILE_SOURCE;
|
|
3267
|
+
ctx.relationships.push({
|
|
3268
|
+
source: renderScope,
|
|
3269
|
+
target: tag,
|
|
3270
|
+
fromSymbol: renderScope,
|
|
3271
|
+
toSymbol: tag,
|
|
3272
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
3273
|
+
// The tag identifier and the same-file symbol match are both tree-read — 1.0.
|
|
3274
|
+
confidence: 1,
|
|
3275
|
+
...this.nodeLine(macroNameNode) ? { line_number: this.nodeLine(macroNameNode) } : {},
|
|
3276
|
+
metadata: {
|
|
3277
|
+
renderMacro: macroName,
|
|
3278
|
+
isComponentTag: true,
|
|
3279
|
+
corpusUnexercised: true,
|
|
3280
|
+
rustSpecific: true
|
|
3281
|
+
}
|
|
3282
|
+
});
|
|
3283
|
+
} else {
|
|
3284
|
+
this.noOwner(ctx, "renders:tag_not_a_same_file_symbol");
|
|
3285
|
+
}
|
|
3286
|
+
} else {
|
|
3287
|
+
this.noOwner(ctx, "renders:bare_html_tag");
|
|
3288
|
+
}
|
|
3289
|
+
k++;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
const source = this.edgeSource(parent);
|
|
3295
|
+
if (!source) {
|
|
3296
|
+
this.noOwner(ctx, "calls:macro_invocation");
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
const metadata = { macroInvocation: true };
|
|
3300
|
+
if (ctx.awaitedCalls.has(node.startIndex)) {
|
|
3301
|
+
metadata.rustSpecific = true;
|
|
3302
|
+
metadata.isAwait = true;
|
|
3303
|
+
}
|
|
3304
|
+
ctx.relationships.push({
|
|
3305
|
+
source,
|
|
3306
|
+
target: macroName,
|
|
3307
|
+
fromSymbol: source,
|
|
3308
|
+
toSymbol: `macro:${macroName}`,
|
|
3309
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
3310
|
+
// INCOMING_RUST: same absent-confidence defect as the call edge above —
|
|
3311
|
+
// a macro invocation's NAME is a callee read from the tree, so 1.0.
|
|
3312
|
+
confidence: 1,
|
|
3313
|
+
...this.nodeLine(macroNameNode) ? { line_number: this.nodeLine(macroNameNode) } : {},
|
|
3314
|
+
metadata
|
|
3315
|
+
});
|
|
3316
|
+
const qcOwner = parent && (parent.kind === ASTEngine.SymbolKind.Function || parent.kind === ASTEngine.SymbolKind.Method);
|
|
3317
|
+
if (qcOwner) {
|
|
3318
|
+
const arr = parent.qualified_calls ?? [];
|
|
3319
|
+
arr.push(`macro:${macroName}`);
|
|
3320
|
+
parent.qualified_calls = arr;
|
|
3321
|
+
}
|
|
3322
|
+
if (["panic", "todo", "unimplemented", "unreachable"].includes(macroName)) {
|
|
3323
|
+
if (!ctx.metadata.patterns) ctx.metadata.patterns = [];
|
|
3324
|
+
const pName = macroName + "!";
|
|
3325
|
+
if (ctx.metadata.patterns.indexOf(pName) === -1) ctx.metadata.patterns.push(pName);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
handleMacro(node, ctx, parent) {
|
|
3329
|
+
let nameNode = node.childForFieldName("name");
|
|
3330
|
+
if (!nameNode) {
|
|
3331
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3332
|
+
const child = node.child(i);
|
|
3333
|
+
if (child && child.type === "identifier") {
|
|
3334
|
+
nameNode = child;
|
|
3335
|
+
break;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
if (!nameNode) return null;
|
|
3340
|
+
const name = nameNode.text;
|
|
3341
|
+
const symbol = this.createSymbol(name, ASTEngine.SymbolKind.Macro, node, parent);
|
|
3342
|
+
ctx.symbols.push(symbol);
|
|
3343
|
+
if (this.isPublic(node) || node.text.includes("#[macro_export]")) {
|
|
3344
|
+
ctx.exports.push({
|
|
3345
|
+
name,
|
|
3346
|
+
type: "function",
|
|
3347
|
+
range: symbol.range,
|
|
3348
|
+
isType: false
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
return symbol;
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
export {
|
|
3355
|
+
RealRustAnalyzer,
|
|
3356
|
+
rustManifestFacts,
|
|
3357
|
+
rustModuleFileCandidates,
|
|
3358
|
+
rustSystemNamespaceOf
|
|
3359
|
+
};
|