@backbencher/brain 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/EMBEDDINGS.md +355 -0
  3. package/LICENSE +21 -0
  4. package/README.md +415 -2
  5. package/bin/bb +13 -0
  6. package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
  7. package/dist/brain-index-health-KY7K552E.js +17 -0
  8. package/dist/brain-relationship-enrichment-QH7OOHUF.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-2BSKD7O3.js +40 -0
  11. package/dist/chunk-2DE27DVC.js +1314 -0
  12. package/dist/chunk-2RRBMNBQ.js +322 -0
  13. package/dist/chunk-2RTN2DFU.js +711 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3LFTSMIV.js +174 -0
  17. package/dist/chunk-47HKX4AJ.js +930 -0
  18. package/dist/chunk-4H22TLOT.js +543 -0
  19. package/dist/chunk-5SD45FLC.js +818 -0
  20. package/dist/chunk-66A5ZFYA.js +1758 -0
  21. package/dist/chunk-6AIRWAO2.js +247 -0
  22. package/dist/chunk-6WNGD3NN.js +196 -0
  23. package/dist/chunk-7AN7U5IZ.js +371 -0
  24. package/dist/chunk-BHB7BYYI.js +626 -0
  25. package/dist/chunk-BQ3OVQ3P.js +125 -0
  26. package/dist/chunk-CW4XXSNN.js +1479 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DYM4FQEE.js +90 -0
  30. package/dist/chunk-EWMWK7WN.js +75 -0
  31. package/dist/chunk-FVVQTSIM.js +3427 -0
  32. package/dist/chunk-FWSL4VG6.js +1290 -0
  33. package/dist/chunk-GBI7V7KY.js +69 -0
  34. package/dist/chunk-GEFFO6O2.js +152 -0
  35. package/dist/chunk-HOWAQXJO.js +236 -0
  36. package/dist/chunk-HQSPJDHI.js +613 -0
  37. package/dist/chunk-IFZNR5UJ.js +2863 -0
  38. package/dist/chunk-J4PMBXQQ.js +336 -0
  39. package/dist/chunk-KAET47ZL.js +125 -0
  40. package/dist/chunk-KCDECRJY.js +440 -0
  41. package/dist/chunk-KLK3PBTO.js +1119 -0
  42. package/dist/chunk-L5TDYKYK.js +76 -0
  43. package/dist/chunk-MLKGABMK.js +9 -0
  44. package/dist/chunk-OWISBAOF.js +61 -0
  45. package/dist/chunk-PJTNGD3Z.js +6924 -0
  46. package/dist/chunk-QLK6ED4Q.js +235 -0
  47. package/dist/chunk-R2GW4F6R.js +19 -0
  48. package/dist/chunk-RCMTFSEF.js +87 -0
  49. package/dist/chunk-RH7KC5HU.js +12050 -0
  50. package/dist/chunk-RM3PY6TG.js +376 -0
  51. package/dist/chunk-ROMM6KMC.js +433 -0
  52. package/dist/chunk-SK6YVHFZ.js +211 -0
  53. package/dist/chunk-SM7RFQRO.js +214 -0
  54. package/dist/chunk-UFE77L67.js +782 -0
  55. package/dist/chunk-UGR4QIGS.js +67 -0
  56. package/dist/chunk-UUNHHX4M.js +13 -0
  57. package/dist/chunk-V44PGPA6.js +2104 -0
  58. package/dist/chunk-VL52PL2T.js +71 -0
  59. package/dist/chunk-VURYFIHW.js +201 -0
  60. package/dist/chunk-XJKJYIPP.js +7436 -0
  61. package/dist/chunk-YI6FF2ZR.js +340 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-642F7E5V.js +593 -0
  65. package/dist/database-TCTZDLE5.js +9 -0
  66. package/dist/doctor-GXCKSJOX.js +395 -0
  67. package/dist/edit-engine-KM7MPFEC.js +51 -0
  68. package/dist/engine-42TZFNIZ.js +11 -0
  69. package/dist/engine-LO6L4JLQ.js +18 -0
  70. package/dist/fingerprint-XDXFRWO6.js +33 -0
  71. package/dist/framework-intelligence-DWFCKV44.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-PL5YA7BA.js +21 -0
  74. package/dist/graph_request-MM4REP6H.js +350 -0
  75. package/dist/impact_request-WDE47IZ5.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-6BD7UBHR.js +34 -0
  78. package/dist/index.js +159 -0
  79. package/dist/instance-FO6QZRLH.js +19 -0
  80. package/dist/language-detection-DFCAU5DZ.js +11 -0
  81. package/dist/lsp/server.js +4301 -0
  82. package/dist/mcp/server.js +132 -0
  83. package/dist/onboard-DIQZV2NW.js +161 -0
  84. package/dist/override-companion-pass-XR7MHTP7.js +117 -0
  85. package/dist/parallel-indexer-4PW3AFXP.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-ZS5TS2Z6.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-DGZBIZS7.js +355 -0
  90. package/dist/real-bash-analyzer-SXSV67E2.js +2201 -0
  91. package/dist/real-c-analyzer-SY7FTZ6W.js +5942 -0
  92. package/dist/real-clean-analyzer-IGDHHLZK.js +3470 -0
  93. package/dist/real-clojure-analyzer-4OH7IWLB.js +2818 -0
  94. package/dist/real-cpp-analyzer-7LTDRJRJ.js +4101 -0
  95. package/dist/real-csharp-analyzer-KICU3TLH.js +3298 -0
  96. package/dist/real-css-analyzer-HPU277Q6.js +15 -0
  97. package/dist/real-dart-analyzer-7LZ5LCMW.js +3944 -0
  98. package/dist/real-elixir-analyzer-BE3MEVZO.js +2192 -0
  99. package/dist/real-erlang-analyzer-BZMAKYBK.js +1731 -0
  100. package/dist/real-fsharp-analyzer-5QLTOV3E.js +2390 -0
  101. package/dist/real-go-analyzer-POD7MZQR.js +4126 -0
  102. package/dist/real-graphql-analyzer-MAQRZ6QW.js +1442 -0
  103. package/dist/real-groovy-analyzer-MEWSUPMJ.js +2285 -0
  104. package/dist/real-haskell-analyzer-I5L2GR3Y.js +2533 -0
  105. package/dist/real-hcl-analyzer-DBOPXSWJ.js +725 -0
  106. package/dist/real-html-analyzer-O63XZLZD.js +1759 -0
  107. package/dist/real-java-analyzer-CHCW3EKA.js +4677 -0
  108. package/dist/real-javascript-analyzer-QAAJQYZE.js +14 -0
  109. package/dist/real-json-analyzer-UGBR7YQN.js +1640 -0
  110. package/dist/real-julia-analyzer-35Q4GHOZ.js +2085 -0
  111. package/dist/real-kotlin-analyzer-GRAYCWW2.js +3956 -0
  112. package/dist/real-lua-analyzer-5BAPDZDF.js +1957 -0
  113. package/dist/real-makefile-analyzer-YJKAHNAS.js +920 -0
  114. package/dist/real-markdown-analyzer-UKUQZO4F.js +1215 -0
  115. package/dist/real-matlab-analyzer-FVIKQZKG.js +480 -0
  116. package/dist/real-nim-analyzer-M7FAIABG.js +3603 -0
  117. package/dist/real-objective-c-analyzer-XMYQIGQI.js +1518 -0
  118. package/dist/real-perl-analyzer-ZLY6M2DZ.js +1865 -0
  119. package/dist/real-php-analyzer-KI2ZAM7X.js +9368 -0
  120. package/dist/real-powershell-analyzer-B3T5ZP6U.js +2915 -0
  121. package/dist/real-protobuf-analyzer-23OZ75XV.js +867 -0
  122. package/dist/real-python-analyzer-I4TIUB6D.js +4880 -0
  123. package/dist/real-r-analyzer-FJZJXXS6.js +2596 -0
  124. package/dist/real-ruby-analyzer-M3UFIVTQ.js +2429 -0
  125. package/dist/real-rust-analyzer-LVSONPAN.js +3359 -0
  126. package/dist/real-scala-analyzer-CWPI33Q2.js +3293 -0
  127. package/dist/real-scss-analyzer-TGRJQ7TH.js +1515 -0
  128. package/dist/real-solidity-analyzer-4P2VBVWA.js +2608 -0
  129. package/dist/real-sql-analyzer-J7DYZOZR.js +3840 -0
  130. package/dist/real-svelte-analyzer-NHEN2SX5.js +2532 -0
  131. package/dist/real-swift-analyzer-2NSYULWI.js +2311 -0
  132. package/dist/real-toml-analyzer-L3C66L7Q.js +929 -0
  133. package/dist/real-typescript-analyzer-FTC6ETRQ.js +14 -0
  134. package/dist/real-vb-analyzer-ORY7LAQS.js +3145 -0
  135. package/dist/real-vba-analyzer-JH2TTUFI.js +466 -0
  136. package/dist/real-vue-analyzer-OO4FUGBV.js +2680 -0
  137. package/dist/real-xml-analyzer-6CQVR477.js +2405 -0
  138. package/dist/real-yaml-analyzer-J6MQG3YX.js +3528 -0
  139. package/dist/real-zig-analyzer-EFKCPJNU.js +2430 -0
  140. package/dist/regex-search-OPTLKUN5.js +239 -0
  141. package/dist/rules-I453JTRC.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-Y5GB5KXA.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-KB4MU4SI.js +16 -0
  146. package/dist/taint-YOTN5X55.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-G4NAGJIH.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-RF675A3C.js +32 -0
  151. package/dist/type-inference-4Y7WTMFE.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-LXGNFO37.js +9 -0
  154. package/dist/wasm-NMWO3WXY.js +16 -0
  155. package/docs/EMBEDDINGS.md +132 -0
  156. package/docs/README.md +178 -0
  157. package/docs/data/m10-baseline.json +602 -0
  158. package/docs/data/m10-benchmark-baseline.json +57 -0
  159. package/docs/data/m10-benchmark-last-run.json +127 -0
  160. package/docs/data/m10-final-audit.json +215 -0
  161. package/docs/data/m12-final-audit.json +138 -0
  162. package/docs/data/m12-surface-matrix.json +50 -0
  163. package/docs/data/public-tools.json +1902 -0
  164. package/docs/indexing/DEAD-ASSETS.md +88 -0
  165. package/docs/indexing/INDEXER-REVIEW.md +634 -0
  166. package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
  167. package/docs/indexing/README.md +427 -0
  168. package/docs/indexing/discovery-matrix.md +41 -0
  169. package/docs/indexing/embeddings.md +325 -0
  170. package/docs/indexing/environment-variables.md +77 -0
  171. package/docs/indexing/performance.md +323 -0
  172. package/docs/indexing/resume.md +345 -0
  173. package/docs/indexing/troubleshooting.md +391 -0
  174. package/docs/man/bb.1 +190 -0
  175. package/docs/tools/bb_analyze.md +147 -0
  176. package/docs/tools/bb_annotate.md +110 -0
  177. package/docs/tools/bb_annotations.md +106 -0
  178. package/docs/tools/bb_ask.md +101 -0
  179. package/docs/tools/bb_cross_language.md +103 -0
  180. package/docs/tools/bb_doc.md +169 -0
  181. package/docs/tools/bb_edit.md +185 -0
  182. package/docs/tools/bb_graph.md +169 -0
  183. package/docs/tools/bb_health.md +156 -0
  184. package/docs/tools/bb_impact.md +117 -0
  185. package/docs/tools/bb_index.md +108 -0
  186. package/docs/tools/bb_migrate.md +135 -0
  187. package/docs/tools/bb_patterns.md +140 -0
  188. package/docs/tools/bb_search.md +150 -0
  189. package/docs/tools/bb_security.md +100 -0
  190. package/docs/tools/bb_visualize.md +184 -0
  191. package/grammars/README.md +57 -0
  192. package/grammars/THIRD-PARTY-NOTICES.md +50 -0
  193. package/grammars/tree-sitter-c/LICENSE +21 -0
  194. package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
  195. package/grammars/tree-sitter-cpp/LICENSE +21 -0
  196. package/grammars/tree-sitter-csharp/LICENSE +21 -0
  197. package/grammars/tree-sitter-css/LICENSE +21 -0
  198. package/grammars/tree-sitter-css/README.md +21 -0
  199. package/grammars/tree-sitter-dart/LICENSE +20 -0
  200. package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
  201. package/grammars/tree-sitter-dart-upstream/README.md +18 -0
  202. package/grammars/tree-sitter-elixir/LICENSE +176 -0
  203. package/grammars/tree-sitter-elixir/README.md +11 -0
  204. package/grammars/tree-sitter-fsharp/LICENSE +21 -0
  205. package/grammars/tree-sitter-fsharp/README.md +45 -0
  206. package/grammars/tree-sitter-graphql/LICENSE +21 -0
  207. package/grammars/tree-sitter-graphql/README.md +106 -0
  208. package/grammars/tree-sitter-groovy/LICENSE +21 -0
  209. package/grammars/tree-sitter-groovy/README.md +4 -0
  210. package/grammars/tree-sitter-haskell/LICENSE +21 -0
  211. package/grammars/tree-sitter-haskell/README.md +394 -0
  212. package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
  213. package/grammars/tree-sitter-hcl/LICENSE +201 -0
  214. package/grammars/tree-sitter-hcl/README.md +39 -0
  215. package/grammars/tree-sitter-html/LICENSE +21 -0
  216. package/grammars/tree-sitter-html/README.md +21 -0
  217. package/grammars/tree-sitter-julia/LICENSE +21 -0
  218. package/grammars/tree-sitter-julia/README.md +24 -0
  219. package/grammars/tree-sitter-kotlin/LICENSE +9 -0
  220. package/grammars/tree-sitter-lua/LICENSE.md +21 -0
  221. package/grammars/tree-sitter-lua/README.md +29 -0
  222. package/grammars/tree-sitter-make/LICENSE +21 -0
  223. package/grammars/tree-sitter-matlab/LICENSE +19 -0
  224. package/grammars/tree-sitter-matlab/README.md +99 -0
  225. package/grammars/tree-sitter-nim/LICENSE +373 -0
  226. package/grammars/tree-sitter-nim/README.md +46 -0
  227. package/grammars/tree-sitter-objc/LICENSE +21 -0
  228. package/grammars/tree-sitter-objc/README.md +20 -0
  229. package/grammars/tree-sitter-perl/LICENSE +7 -0
  230. package/grammars/tree-sitter-perl/README.md +100 -0
  231. package/grammars/tree-sitter-powershell/LICENSE +21 -0
  232. package/grammars/tree-sitter-powershell/README.md +7 -0
  233. package/grammars/tree-sitter-proto/LICENSE +21 -0
  234. package/grammars/tree-sitter-proto/README.md +105 -0
  235. package/grammars/tree-sitter-r/LICENSE +9 -0
  236. package/grammars/tree-sitter-r/README.md +66 -0
  237. package/grammars/tree-sitter-ruby/LICENSE +22 -0
  238. package/grammars/tree-sitter-scss/LICENSE +21 -0
  239. package/grammars/tree-sitter-scss/README.md +17 -0
  240. package/grammars/tree-sitter-solidity/LICENSE +7 -0
  241. package/grammars/tree-sitter-sql/LICENSE +21 -0
  242. package/grammars/tree-sitter-typescript/LICENSE +21 -0
  243. package/grammars/tree-sitter-vba/LICENSE +21 -0
  244. package/grammars/tree-sitter-vba/README.md +738 -0
  245. package/grammars/tree-sitter-vbnet/LICENSE +21 -0
  246. package/grammars/tree-sitter-vbnet/README.md +322 -0
  247. package/grammars/tree-sitter-xml/LICENSE +19 -0
  248. package/grammars/tree-sitter-yaml/LICENSE +20 -0
  249. package/grammars/tree-sitter-yaml/README.md +21 -0
  250. package/package.json +184 -4
  251. package/wasm/dart.wasm +0 -0
  252. package/wasm/tree-sitter-bash.wasm +0 -0
  253. package/wasm/tree-sitter-c.wasm +0 -0
  254. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  255. package/wasm/tree-sitter-clean.wasm +0 -0
  256. package/wasm/tree-sitter-clojure.wasm +0 -0
  257. package/wasm/tree-sitter-core_schema.wasm +0 -0
  258. package/wasm/tree-sitter-cpp.wasm +0 -0
  259. package/wasm/tree-sitter-css.wasm +0 -0
  260. package/wasm/tree-sitter-dart.wasm +0 -0
  261. package/wasm/tree-sitter-elisp.wasm +0 -0
  262. package/wasm/tree-sitter-elixir.wasm +0 -0
  263. package/wasm/tree-sitter-elm.wasm +0 -0
  264. package/wasm/tree-sitter-embedded_template.wasm +0 -0
  265. package/wasm/tree-sitter-erlang.wasm +0 -0
  266. package/wasm/tree-sitter-fsharp.wasm +0 -0
  267. package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
  268. package/wasm/tree-sitter-go.wasm +0 -0
  269. package/wasm/tree-sitter-graphql.wasm +0 -0
  270. package/wasm/tree-sitter-groovy.wasm +0 -0
  271. package/wasm/tree-sitter-haskell.wasm +0 -0
  272. package/wasm/tree-sitter-hcl.wasm +0 -0
  273. package/wasm/tree-sitter-html.wasm +0 -0
  274. package/wasm/tree-sitter-java.wasm +0 -0
  275. package/wasm/tree-sitter-javascript.wasm +0 -0
  276. package/wasm/tree-sitter-json.wasm +0 -0
  277. package/wasm/tree-sitter-json_schema.wasm +0 -0
  278. package/wasm/tree-sitter-julia.wasm +0 -0
  279. package/wasm/tree-sitter-kotlin.wasm +0 -0
  280. package/wasm/tree-sitter-lua.wasm +0 -0
  281. package/wasm/tree-sitter-make.wasm +0 -0
  282. package/wasm/tree-sitter-markdown.wasm +0 -0
  283. package/wasm/tree-sitter-markdown_inline.wasm +0 -0
  284. package/wasm/tree-sitter-matlab.wasm +0 -0
  285. package/wasm/tree-sitter-nim.wasm +0 -0
  286. package/wasm/tree-sitter-objc.wasm +0 -0
  287. package/wasm/tree-sitter-ocaml.wasm +0 -0
  288. package/wasm/tree-sitter-perl.wasm +0 -0
  289. package/wasm/tree-sitter-php.wasm +0 -0
  290. package/wasm/tree-sitter-php_only.wasm +0 -0
  291. package/wasm/tree-sitter-powershell.wasm +0 -0
  292. package/wasm/tree-sitter-protobuf.wasm +0 -0
  293. package/wasm/tree-sitter-python.wasm +0 -0
  294. package/wasm/tree-sitter-ql.wasm +0 -0
  295. package/wasm/tree-sitter-r.wasm +0 -0
  296. package/wasm/tree-sitter-rescript.wasm +0 -0
  297. package/wasm/tree-sitter-ruby.wasm +0 -0
  298. package/wasm/tree-sitter-rust.wasm +0 -0
  299. package/wasm/tree-sitter-scala.wasm +0 -0
  300. package/wasm/tree-sitter-scss.wasm +0 -0
  301. package/wasm/tree-sitter-solidity.wasm +0 -0
  302. package/wasm/tree-sitter-sql.wasm +0 -0
  303. package/wasm/tree-sitter-svelte.wasm +0 -0
  304. package/wasm/tree-sitter-swift.wasm +0 -0
  305. package/wasm/tree-sitter-systemrdl.wasm +0 -0
  306. package/wasm/tree-sitter-terraform.wasm +0 -0
  307. package/wasm/tree-sitter-tlaplus.wasm +0 -0
  308. package/wasm/tree-sitter-toml.wasm +0 -0
  309. package/wasm/tree-sitter-tsx.wasm +0 -0
  310. package/wasm/tree-sitter-typescript.wasm +0 -0
  311. package/wasm/tree-sitter-vba.wasm +0 -0
  312. package/wasm/tree-sitter-vbnet.wasm +0 -0
  313. package/wasm/tree-sitter-vue.wasm +0 -0
  314. package/wasm/tree-sitter-xml.wasm +0 -0
  315. package/wasm/tree-sitter-yaml.wasm +0 -0
  316. package/wasm/tree-sitter-zig.wasm +0 -0
  317. package/wasm/tree-sitter.wasm +0 -0
  318. package/wasm/tsx.wasm +0 -0
  319. 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-V44PGPA6.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
+ };