@backbencher/brain 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-Q5APTJQL.js +17 -0
  8. package/dist/brain-relationship-enrichment-MYR2AM4F.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-22V37YTK.js +1758 -0
  11. package/dist/chunk-2BSKD7O3.js +40 -0
  12. package/dist/chunk-2DE27DVC.js +1314 -0
  13. package/dist/chunk-2RRBMNBQ.js +322 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3ZTOHHSW.js +2104 -0
  17. package/dist/chunk-4H22TLOT.js +543 -0
  18. package/dist/chunk-5BVAH26D.js +340 -0
  19. package/dist/chunk-62DHLYW6.js +3427 -0
  20. package/dist/chunk-7AN7U5IZ.js +371 -0
  21. package/dist/chunk-7DRPNVIN.js +782 -0
  22. package/dist/chunk-7MBYT777.js +174 -0
  23. package/dist/chunk-AHDWGM7Q.js +236 -0
  24. package/dist/chunk-B5GEAAYF.js +818 -0
  25. package/dist/chunk-BEIZZ73B.js +1479 -0
  26. package/dist/chunk-BQ3OVQ3P.js +125 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DWKXZ6IF.js +336 -0
  30. package/dist/chunk-DYM4FQEE.js +90 -0
  31. package/dist/chunk-ENW4BTBA.js +930 -0
  32. package/dist/chunk-EWMWK7WN.js +75 -0
  33. package/dist/chunk-EY5ZZUUA.js +626 -0
  34. package/dist/chunk-GBI7V7KY.js +69 -0
  35. package/dist/chunk-GEFFO6O2.js +152 -0
  36. package/dist/chunk-HJEWO5EV.js +247 -0
  37. package/dist/chunk-HQSPJDHI.js +613 -0
  38. package/dist/chunk-KAET47ZL.js +125 -0
  39. package/dist/chunk-KCDECRJY.js +440 -0
  40. package/dist/chunk-L5TDYKYK.js +76 -0
  41. package/dist/chunk-MLKGABMK.js +9 -0
  42. package/dist/chunk-NO3U3FJQ.js +201 -0
  43. package/dist/chunk-OWISBAOF.js +61 -0
  44. package/dist/chunk-QKTRECYS.js +1119 -0
  45. package/dist/chunk-QLK6ED4Q.js +235 -0
  46. package/dist/chunk-R2GW4F6R.js +19 -0
  47. package/dist/chunk-RCMTFSEF.js +87 -0
  48. package/dist/chunk-RM3PY6TG.js +376 -0
  49. package/dist/chunk-ROMM6KMC.js +433 -0
  50. package/dist/chunk-SGGCSHAX.js +12050 -0
  51. package/dist/chunk-SK6YVHFZ.js +211 -0
  52. package/dist/chunk-SM7RFQRO.js +214 -0
  53. package/dist/chunk-UGR4QIGS.js +67 -0
  54. package/dist/chunk-UQNQZXHY.js +711 -0
  55. package/dist/chunk-UUNHHX4M.js +13 -0
  56. package/dist/chunk-VL52PL2T.js +71 -0
  57. package/dist/chunk-WIG36E67.js +196 -0
  58. package/dist/chunk-XHK3WWGB.js +7436 -0
  59. package/dist/chunk-XTQM2IP3.js +2863 -0
  60. package/dist/chunk-ZE2LG76D.js +1296 -0
  61. package/dist/chunk-ZETTGPAW.js +6950 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-7XIP555Q.js +593 -0
  65. package/dist/database-76RP44JG.js +9 -0
  66. package/dist/doctor-ANGETYMN.js +395 -0
  67. package/dist/edit-engine-CG72LDZD.js +51 -0
  68. package/dist/engine-BKEOGN7N.js +11 -0
  69. package/dist/engine-VIW6NSKO.js +18 -0
  70. package/dist/fingerprint-OBLIMZLP.js +33 -0
  71. package/dist/framework-intelligence-3LORTPOX.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-HBSQNPTB.js +21 -0
  74. package/dist/graph_request-Q6NKB6N2.js +350 -0
  75. package/dist/impact_request-LYP672WE.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-7OVGYSGW.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-FK2XKZXY.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-6XPENVAA.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-ZJ3DVUGO.js +355 -0
  90. package/dist/real-bash-analyzer-EJAU5LW5.js +2201 -0
  91. package/dist/real-c-analyzer-HBD3FNNH.js +5942 -0
  92. package/dist/real-clean-analyzer-4EEZLPV5.js +3470 -0
  93. package/dist/real-clojure-analyzer-Q55EBOO2.js +2818 -0
  94. package/dist/real-cpp-analyzer-IVVOD25R.js +4101 -0
  95. package/dist/real-csharp-analyzer-ZBNLNJZJ.js +3298 -0
  96. package/dist/real-css-analyzer-6XRQLUN7.js +15 -0
  97. package/dist/real-dart-analyzer-OLR72EFW.js +3944 -0
  98. package/dist/real-elixir-analyzer-MQWGAH2I.js +2192 -0
  99. package/dist/real-erlang-analyzer-7BVPQEHO.js +1731 -0
  100. package/dist/real-fsharp-analyzer-C2UPFNOA.js +2390 -0
  101. package/dist/real-go-analyzer-UKGI43YS.js +4126 -0
  102. package/dist/real-graphql-analyzer-VE2EEFII.js +1442 -0
  103. package/dist/real-groovy-analyzer-W26VACFW.js +2285 -0
  104. package/dist/real-haskell-analyzer-5CVVAJRX.js +2533 -0
  105. package/dist/real-hcl-analyzer-5ZT4U43N.js +725 -0
  106. package/dist/real-html-analyzer-JWNX3C6I.js +1759 -0
  107. package/dist/real-java-analyzer-GU42VBGQ.js +4677 -0
  108. package/dist/real-javascript-analyzer-QA4QOJ6W.js +14 -0
  109. package/dist/real-json-analyzer-MPA6PRXL.js +1640 -0
  110. package/dist/real-julia-analyzer-22CWYQMN.js +2085 -0
  111. package/dist/real-kotlin-analyzer-QDZJLXXU.js +3956 -0
  112. package/dist/real-lua-analyzer-G7NHLYKS.js +1957 -0
  113. package/dist/real-makefile-analyzer-OCY3GT5N.js +920 -0
  114. package/dist/real-markdown-analyzer-TEJK2JXJ.js +1215 -0
  115. package/dist/real-matlab-analyzer-Q5SWLN7Z.js +480 -0
  116. package/dist/real-nim-analyzer-YLXSK563.js +3603 -0
  117. package/dist/real-objective-c-analyzer-D23ACC2R.js +1518 -0
  118. package/dist/real-perl-analyzer-WWTRHYDT.js +1865 -0
  119. package/dist/real-php-analyzer-JNKJHTCL.js +9368 -0
  120. package/dist/real-powershell-analyzer-XRSNQERV.js +2915 -0
  121. package/dist/real-protobuf-analyzer-3OKUCZ7T.js +867 -0
  122. package/dist/real-python-analyzer-VXAFWI72.js +4880 -0
  123. package/dist/real-r-analyzer-ZGAIDVI4.js +2596 -0
  124. package/dist/real-ruby-analyzer-X2KTLVBH.js +2429 -0
  125. package/dist/real-rust-analyzer-OFFHPL2A.js +3359 -0
  126. package/dist/real-scala-analyzer-TSTW27J6.js +3293 -0
  127. package/dist/real-scss-analyzer-AVWYPSOQ.js +1515 -0
  128. package/dist/real-solidity-analyzer-UCI2KWUB.js +2608 -0
  129. package/dist/real-sql-analyzer-F77ZMQXR.js +3840 -0
  130. package/dist/real-svelte-analyzer-SF7NRUO5.js +2532 -0
  131. package/dist/real-swift-analyzer-MN5QT3QN.js +2311 -0
  132. package/dist/real-toml-analyzer-66L7BDCQ.js +929 -0
  133. package/dist/real-typescript-analyzer-6KEMVDWQ.js +14 -0
  134. package/dist/real-vb-analyzer-VICVPOP7.js +3145 -0
  135. package/dist/real-vba-analyzer-HXLTTHKM.js +466 -0
  136. package/dist/real-vue-analyzer-QXR56CFV.js +2680 -0
  137. package/dist/real-xml-analyzer-YL465ZJW.js +2405 -0
  138. package/dist/real-yaml-analyzer-SUBCBNSI.js +3528 -0
  139. package/dist/real-zig-analyzer-HIQLZMGY.js +2430 -0
  140. package/dist/regex-search-NJKHMZNO.js +239 -0
  141. package/dist/rules-ACHPB7QU.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-NT5ZHYNE.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-GZJIM2J2.js +16 -0
  146. package/dist/taint-KXUHHPIE.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-OJDC35DI.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-Q57YMMBX.js +32 -0
  151. package/dist/type-inference-QMT2RMLV.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-ABP2TUL2.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,2085 @@
1
+ import {
2
+ ASTEngine
3
+ } from "./chunk-3ZTOHHSW.js";
4
+ import "./chunk-BQ3OVQ3P.js";
5
+ import "./chunk-SK6YVHFZ.js";
6
+ import "./chunk-2BSKD7O3.js";
7
+ import {
8
+ BINDING_SEP
9
+ } from "./chunk-KAET47ZL.js";
10
+ import "./chunk-KCDECRJY.js";
11
+ import "./chunk-MLKGABMK.js";
12
+
13
+ // src/ast/real-julia-analyzer.ts
14
+ import { readFileSync } from "fs";
15
+ import * as path from "path";
16
+ var JULIA_PACKAGE_CACHE = /* @__PURE__ */ new Map();
17
+ function projectTomlName(text) {
18
+ let inTable = false;
19
+ for (const rawLine of text.split("\n")) {
20
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
21
+ const t = line.trim();
22
+ if (!t || t.startsWith("#")) continue;
23
+ if (t.startsWith("[")) {
24
+ inTable = true;
25
+ continue;
26
+ }
27
+ if (inTable) continue;
28
+ const eq = t.indexOf("=");
29
+ if (eq < 0 || t.slice(0, eq).trim() !== "name") continue;
30
+ let value = t.slice(eq + 1).trim();
31
+ if (!value) return "";
32
+ const quote = value[0];
33
+ if (quote === '"' || quote === "'") {
34
+ const close = value.indexOf(quote, 1);
35
+ return close > 0 ? value.slice(1, close).trim() : "";
36
+ }
37
+ const hash = value.indexOf("#");
38
+ if (hash >= 0) value = value.slice(0, hash);
39
+ return value.trim();
40
+ }
41
+ return "";
42
+ }
43
+ function juliaPackageName(projectRoot) {
44
+ if (!projectRoot) return "";
45
+ const hit = JULIA_PACKAGE_CACHE.get(projectRoot);
46
+ if (hit !== void 0) return hit;
47
+ let name = "";
48
+ try {
49
+ name = projectTomlName(readFileSync(path.join(projectRoot, "Project.toml"), "utf8"));
50
+ } catch {
51
+ }
52
+ JULIA_PACKAGE_CACHE.set(projectRoot, name);
53
+ return name;
54
+ }
55
+ var TYPE_DECLARATIONS = /* @__PURE__ */ new Set(["abstract_definition", "primitive_definition", "struct_definition"]);
56
+ var GLOBAL_SCOPE = "global";
57
+ var IMPLICIT_OR_CONSTANT = /* @__PURE__ */ new Set([
58
+ "nothing",
59
+ "missing",
60
+ "undef",
61
+ "ARGS",
62
+ "ENV",
63
+ "C_NULL",
64
+ "stdout",
65
+ "stderr",
66
+ "stdin"
67
+ ]);
68
+ var SCOPE_TRANSPARENT_MACROS = /* @__PURE__ */ new Set(["eval", "static", "sync", "async", "time"]);
69
+ var SCOPE_INTRODUCING_NODES = /* @__PURE__ */ new Set([
70
+ "function_definition",
71
+ "macro_definition",
72
+ "let_statement",
73
+ "for_statement",
74
+ "while_statement",
75
+ "do_clause",
76
+ "quote_expression",
77
+ "quote_statement",
78
+ "struct_definition",
79
+ "abstract_definition",
80
+ "primitive_definition",
81
+ "arrow_function_expression",
82
+ "comprehension_expression"
83
+ ]);
84
+ function stripSigil(text, sigil) {
85
+ const t = text.trim();
86
+ return t.startsWith(sigil) ? t.slice(sigil.length) : t;
87
+ }
88
+ function hasLayout(text) {
89
+ for (const ch of text) if (ch.trim() === "" || ch === "#") return true;
90
+ return false;
91
+ }
92
+ function macroNameOf(text) {
93
+ const t = stripSigil(text, "@");
94
+ for (let i = 0; i < t.length; i++) {
95
+ const c = t.charCodeAt(i);
96
+ if (c === 10 || c === 13 || c === 9 || c === 32) {
97
+ const first = t.slice(0, i);
98
+ return hasLayout(first) ? "" : first;
99
+ }
100
+ }
101
+ return hasLayout(t) ? "" : t;
102
+ }
103
+ function collapseSpace(text) {
104
+ const words = [];
105
+ let word = "";
106
+ for (const ch of text) {
107
+ if (ch.trim() === "") {
108
+ if (word) {
109
+ words.push(word);
110
+ word = "";
111
+ }
112
+ } else {
113
+ word += ch;
114
+ }
115
+ }
116
+ if (word) words.push(word);
117
+ return words.join(" ");
118
+ }
119
+ var CONDITIONAL_OR_GENERATED = /* @__PURE__ */ new Set([
120
+ // conditional
121
+ "if_statement",
122
+ "else_clause",
123
+ "elseif_clause",
124
+ "while_statement",
125
+ "for_statement",
126
+ "try_statement",
127
+ "catch_clause",
128
+ "finally_clause",
129
+ "ternary_expression",
130
+ "macrocall_expression",
131
+ // generated, not declared here
132
+ "quote_expression",
133
+ "quote_statement"
134
+ ]);
135
+ var LOCAL_BINDING_SCOPE = /* @__PURE__ */ new Set([
136
+ "function_definition",
137
+ "macro_definition",
138
+ "do_clause",
139
+ "let_statement",
140
+ "arrow_function_expression"
141
+ ]);
142
+ var RealJuliaAnalyzer = class {
143
+ /**
144
+ * Call sites under an `@ccall`, recorded when the macro is visited and consulted when the
145
+ * nested `call_expression` is emitted. Instance state rather than a local because the macro
146
+ * case and the call case are different methods, and CLEARED at the top of every `analyze()` so
147
+ * a reused instance cannot carry one file's C symbols into the next file's graph — which would
148
+ * mark an unrelated call as foreign and is precisely the kind of cross-file bleed a shared
149
+ * analyzer instance invites.
150
+ */
151
+ foreignMarked = /* @__PURE__ */ new Set();
152
+ /** Set while the walk is inside a `macro_definition`, whose body is QUOTED code. */
153
+ inMacroBody = false;
154
+ /**
155
+ * `call_expression` nodes that are the LEFT-HAND SIDE of a short-form declaration, i.e.
156
+ * `f(x) = …` rather than an invocation. Julia writes a function definition with the same node
157
+ * type as a call, so without this the declaration reads as the function calling itself.
158
+ * Cleared per `analyze()` for the same reason as `foreignMarked`.
159
+ */
160
+ declSites = /* @__PURE__ */ new Set();
161
+ /**
162
+ * `type_head` nodes belonging to a type DECLARATION, so a type is not recorded as referencing
163
+ * itself. Cleared per `analyze()` like the other per-file sets.
164
+ */
165
+ typeHeadSites = /* @__PURE__ */ new Set();
166
+ /**
167
+ * Declaration nodes that REPLACE an earlier declaration of the same method, keyed by node id.
168
+ *
169
+ * COLLECTED IN A PRE-PASS for the same reason `declaredTypes` is: the question "has this exact
170
+ * method already been defined in this file?" is only answerable once the whole file has been
171
+ * seen, and answering it during a single walk would classify the FIRST declaration as the
172
+ * override and leave the second — the one that actually replaces it — unrecorded.
173
+ *
174
+ * M60/julia `overrides`. The lane's file header declined this type on the grounds that julia has
175
+ * multiple dispatch rather than overriding, and the argument is sound for the case it names: two
176
+ * methods of the same name with DIFFERENT signatures are different methods, both live, and
177
+ * neither replaces the other. It does not cover the case julia actually has. Julia's method table
178
+ * holds exactly one entry per (name, signature) tuple, so a second declaration of the SAME pair
179
+ * REPLACES the first — julia itself says so, printing `WARNING: Method definition f(Int) in
180
+ * module Main at line 12 overwritten at line 40` under `--warn-overwrite=yes`. That is a
181
+ * replacement, and it is the only sense in which the language has one, which is the same
182
+ * criterion the bash lane's `overrides` ruling used to convict this very omission
183
+ * (`script/m14/ontology-coverage-gate.ts` header, BD8b).
184
+ *
185
+ * MEASURED: filled in from `validation/m14/fixtures/julia/census.ts` over the admitted corpus —
186
+ * see `docs/analyzers/julia.md` for the counts and the probe that produces them.
187
+ */
188
+ redefinedAt = /* @__PURE__ */ new Map();
189
+ /**
190
+ * The kind of every symbol this file declares, by name. Populated at each `symbols.push`, which is
191
+ * the single point every declaration in this analyzer goes through, so the table cannot fall behind
192
+ * the symbol list. Cleared per `analyze()` like the other per-file state.
193
+ *
194
+ * It exists for one question: does this file declare `owner` as a module or a type, and `target` as
195
+ * a method? See `ownsAMethodTable`.
196
+ */
197
+ declaredSymbolKinds = /* @__PURE__ */ new Map();
198
+ /**
199
+ * THE FOURTH PARAMETER IS NEW, AND ITS ABSENCE WAS A WIRING GAP OF EXACTLY THE KIND THIS PROGRAMME
200
+ * HAS NOW FOUND THREE TIMES: `engine.ts` passes an `AnalyzeContext` to every analyzer, python's once
201
+ * stopped at three parameters so the companion "never reached it at all", and this analyzer did the
202
+ * same — the context was available at every call site and dropped here. It is ADDITIVE: a caller that
203
+ * passes nothing gets the previous behaviour byte for byte, which is what the five fixtures assert.
204
+ */
205
+ analyze(rootNode, content, filePath, context) {
206
+ this.inMacroBody = false;
207
+ this.foreignMarked.clear();
208
+ this.declSites.clear();
209
+ this.typeHeadSites.clear();
210
+ this.redefinedAt.clear();
211
+ this.declaredSymbolKinds.clear();
212
+ const symbols = [];
213
+ const pushSym = (s) => {
214
+ if (s.ownerName && !s.qualifiedName) s.qualifiedName = `${s.ownerName}.${s.name}`;
215
+ else if (!s.ownerName && !fileDeclaresModule && packageName) s.qualifiedName = `${packageName}.${s.name}`;
216
+ symbols.push(s);
217
+ this.declaredSymbolKinds.set(s.name, s.kind);
218
+ };
219
+ const memberKey = (owner, name) => owner ? `${owner}.${name}` : name;
220
+ const relationships = [];
221
+ const imports = [];
222
+ const exports = [];
223
+ const errors = [];
224
+ const frameworks = /* @__PURE__ */ new Set();
225
+ const declaredTypes = /* @__PURE__ */ new Set();
226
+ const declaredModules = /* @__PURE__ */ new Set();
227
+ {
228
+ const collect = (n) => {
229
+ if (TYPE_DECLARATIONS.has(n.type)) {
230
+ const head = this.firstNamedChildOfType(n, "type_head");
231
+ const name = head ? this.typeHeadName(head) : "";
232
+ if (name) declaredTypes.add(name);
233
+ }
234
+ if (n.type === "module_definition") {
235
+ const nm = n.childForFieldName("name");
236
+ const m = nm ? nm.text.trim() : "";
237
+ if (m) declaredModules.add(m);
238
+ }
239
+ for (const c of this.namedChildren(n)) collect(c);
240
+ };
241
+ collect(rootNode);
242
+ }
243
+ let fileDeclaresModule = false;
244
+ {
245
+ const scan = (n) => {
246
+ if (n.type === "module_definition") fileDeclaresModule = true;
247
+ for (const c of this.namedChildren(n)) if (!fileDeclaresModule) scan(c);
248
+ };
249
+ scan(rootNode);
250
+ }
251
+ const packageName = juliaPackageName(context?.projectRoot);
252
+ const bindingsByScope = /* @__PURE__ */ new Map();
253
+ {
254
+ const bindName = (b) => {
255
+ if (!b) return "";
256
+ if (b.type === "identifier") return b.text.trim();
257
+ if (b.type === "typed_expression" || b.type === "unary_typed_expression") {
258
+ const ks = this.namedChildren(b);
259
+ if (ks.length < 2) return "";
260
+ return bindName(ks[0]);
261
+ }
262
+ if (b.type === "splat_expression" || b.type === "named_argument") {
263
+ const inner = this.pos(b, 0);
264
+ return inner ? bindName(inner) : "";
265
+ }
266
+ if (b.type === "juxtaposition_expression" || b.type === "parenthesized_expression") {
267
+ const inner = this.pos(b, 0);
268
+ return inner ? bindName(inner) : "";
269
+ }
270
+ return "";
271
+ };
272
+ const add = (scope, n) => {
273
+ if (!scope || !n) return;
274
+ const set = bindingsByScope.get(scope) ?? /* @__PURE__ */ new Set();
275
+ set.add(n);
276
+ bindingsByScope.set(scope, set);
277
+ };
278
+ const addParams = (call, scope) => {
279
+ if (!call || !scope) return;
280
+ const args = this.namedChildren(call).find((c) => c.type === "argument_list");
281
+ if (!args) return;
282
+ for (const a of this.namedChildren(args)) add(scope, bindName(a));
283
+ };
284
+ const scopeOf = (n) => {
285
+ if (n.type === "module_definition") {
286
+ const nn = n.childForFieldName("name");
287
+ return nn ? nn.text.trim() : "";
288
+ }
289
+ if (n.type === "struct_definition" || n.type === "abstract_definition" || n.type === "primitive_definition") {
290
+ const head = this.firstNamedChildOfType(n, "type_head");
291
+ return head ? this.typeHeadName(head) : "";
292
+ }
293
+ if (n.type === "function_definition" || n.type === "macro_definition") {
294
+ const sig = this.firstNamedChildOfType(n, "signature");
295
+ return sig ? this.signatureName(sig) : "";
296
+ }
297
+ if (n.type === "assignment") {
298
+ const lhs = this.pos(n, 0);
299
+ if (!lhs) return "";
300
+ const u = this.unwrapWhere(lhs);
301
+ return u.type === "call_expression" ? this.calleeName(u) ?? "" : "";
302
+ }
303
+ return "";
304
+ };
305
+ const collect = (n, scope) => {
306
+ const inner = scopeOf(n) || scope;
307
+ if (n.type === "signature") {
308
+ let cur = n;
309
+ for (let i = 0; i < 4 && cur && cur.type !== "call_expression"; i++) {
310
+ cur = this.namedChildren(cur).find(
311
+ (c) => c.type === "call_expression" || c.type === "where_expression" || c.type === "parenthesized_expression"
312
+ ) ?? null;
313
+ }
314
+ addParams(cur && cur.type === "call_expression" ? cur : null, inner);
315
+ }
316
+ if (n.type === "assignment") {
317
+ const lhs = this.pos(n, 0);
318
+ if (lhs) {
319
+ const u = this.unwrapWhere(lhs);
320
+ addParams(u.type === "call_expression" ? u : null, inner);
321
+ }
322
+ }
323
+ for (const c of this.namedChildren(n)) collect(c, inner);
324
+ };
325
+ collect(rootNode, "");
326
+ }
327
+ {
328
+ const firstDecl = /* @__PURE__ */ new Map();
329
+ const scan = (n, scope, scopeAt, localDepth) => {
330
+ let childScope = scope;
331
+ let childScopeAt = scopeAt;
332
+ let childLocal = localDepth;
333
+ if (TYPE_DECLARATIONS.has(n.type)) {
334
+ const head = this.firstNamedChildOfType(n, "type_head");
335
+ const name = head ? this.typeHeadName(head) : "";
336
+ if (name) {
337
+ childScope = name;
338
+ childScopeAt = n.startPosition.row + 1;
339
+ }
340
+ } else if (n.type === "module_definition") {
341
+ const nameNode = n.childForFieldName("name");
342
+ const name = nameNode ? nameNode.text.trim() : "";
343
+ if (name) {
344
+ childScope = name;
345
+ childScopeAt = n.startPosition.row + 1;
346
+ }
347
+ }
348
+ if (LOCAL_BINDING_SCOPE.has(n.type)) childLocal = localDepth + 1;
349
+ const site = this.declarationSiteOf(n);
350
+ if (site && site.name && site.signature && localDepth === 0) {
351
+ const key = `${childScope}@${childScopeAt}|${site.name}|${site.signature}`;
352
+ const prior = firstDecl.get(key);
353
+ const safe = this.isUnconditionalPosition(n);
354
+ if (prior && prior.safe && safe) {
355
+ this.redefinedAt.set(n.id, { name: site.name, replacesLine: prior.line, signature: site.signature, scope: childScope });
356
+ }
357
+ if (!prior) firstDecl.set(key, { line: n.startPosition.row + 1, safe });
358
+ }
359
+ for (const c of this.namedChildren(n)) scan(c, childScope, childScopeAt, childLocal);
360
+ };
361
+ scan(rootNode, "", 0, 0);
362
+ }
363
+ let activeScope = "";
364
+ let containerScope = true;
365
+ let depth = 0;
366
+ const walkStack = [{ node: rootNode, activeScope: "", containerScope: true, inMacroBody: false, isExit: false }];
367
+ while (walkStack.length > 0) {
368
+ const frame = walkStack.pop();
369
+ const node = frame.node;
370
+ if (frame.isExit) {
371
+ depth--;
372
+ activeScope = frame.activeScope;
373
+ containerScope = frame.containerScope;
374
+ this.inMacroBody = frame.inMacroBody;
375
+ continue;
376
+ }
377
+ const scope = frame.activeScope;
378
+ containerScope = frame.containerScope;
379
+ const parentContainerScope = containerScope;
380
+ const parentInMacroBody = this.inMacroBody;
381
+ switch (node.type) {
382
+ // ---------------------------------------------------------------- modules
383
+ case "module_definition": {
384
+ const nameNode = node.childForFieldName("name");
385
+ const name = nameNode ? this.nameOf(nameNode) : "";
386
+ if (name) {
387
+ pushSym({
388
+ name,
389
+ kind: ASTEngine.SymbolKind.Module,
390
+ range: this.range(node),
391
+ complexity: this.complexity(node),
392
+ ownerName: scope || void 0,
393
+ metadata: { juliaSpecific: true, isBareModule: this.firstNamedChildType(node) === "baremodule" }
394
+ });
395
+ if (scope && containerScope) relationships.push(this.rel(scope, ASTEngine.RelationshipType.Contains, memberKey(scope, name), node, 1));
396
+ activeScope = name;
397
+ containerScope = true;
398
+ }
399
+ break;
400
+ }
401
+ // ---------------------------------------------------------------- type declarations
402
+ case "abstract_definition":
403
+ case "primitive_definition":
404
+ case "struct_definition": {
405
+ const nameNode = this.firstNamedChildOfType(node, "type_head");
406
+ const headInner = nameNode ? this.pos(nameNode, 0) : null;
407
+ if (headInner && headInner.type === "interpolation_expression") break;
408
+ const name = nameNode ? this.typeHeadName(nameNode) : "";
409
+ if (!name) break;
410
+ const kind = node.type === "struct_definition" ? ASTEngine.SymbolKind.Class : ASTEngine.SymbolKind.Interface;
411
+ pushSym({
412
+ name,
413
+ kind,
414
+ range: this.range(node),
415
+ complexity: this.complexity(node),
416
+ ownerName: scope || void 0,
417
+ metadata: { juliaSpecific: true, declarationForm: node.type, isMutable: this.hasToken(node, "mutable") }
418
+ });
419
+ declaredTypes.add(name);
420
+ if (scope && containerScope) relationships.push(this.rel(scope, ASTEngine.RelationshipType.Contains, memberKey(scope, name), node, 1));
421
+ if (nameNode) this.typeHeadSites.add(nameNode.id);
422
+ activeScope = name;
423
+ containerScope = true;
424
+ for (const sup of this.supertypesOf(node)) {
425
+ if (sup === name) continue;
426
+ relationships.push(this.rel(name, ASTEngine.RelationshipType.Extends, sup, node, 1, { juliaSpecific: true, isSubtype: true }));
427
+ }
428
+ for (const field of this.structFields(node)) {
429
+ pushSym({
430
+ name: field.name,
431
+ kind: ASTEngine.SymbolKind.Property,
432
+ range: this.range(field.node),
433
+ complexity: this.complexity(field.node),
434
+ ownerName: name,
435
+ metadata: { juliaSpecific: true, isStructField: true }
436
+ });
437
+ relationships.push(this.rel(name, ASTEngine.RelationshipType.Contains, memberKey(name, field.name), field.node, 1));
438
+ }
439
+ break;
440
+ }
441
+ // ---------------------------------------------------------------- functions
442
+ case "function_definition":
443
+ case "macro_definition": {
444
+ const sig = this.firstNamedChildOfType(node, "signature");
445
+ const name = sig ? this.signatureName(sig) : "";
446
+ if (!name) break;
447
+ if (sig) {
448
+ let callable = this.pos(sig, 0);
449
+ for (let i = 0; i < 4 && callable && callable.type !== "call_expression"; i++) {
450
+ callable = this.pos(callable, 0);
451
+ }
452
+ if (callable && callable.type === "call_expression") {
453
+ this.declSites.add(callable.id);
454
+ const callee = this.pos(callable, 0);
455
+ this.typeHeadSites.add(callee ? callee.id : callable.id);
456
+ }
457
+ }
458
+ const isMacro = node.type === "macro_definition";
459
+ const wasInMacro = this.inMacroBody;
460
+ if (isMacro) this.inMacroBody = true;
461
+ pushSym({
462
+ name,
463
+ kind: isMacro ? ASTEngine.SymbolKind.Macro : ASTEngine.SymbolKind.Function,
464
+ range: this.range(node),
465
+ complexity: this.complexity(node),
466
+ ownerName: scope || void 0,
467
+ metadata: { juliaSpecific: true, isMacro, hasBlockBody: !!this.firstNamedChildOfType(node, "compound_statement") }
468
+ });
469
+ if (scope && containerScope) relationships.push(this.rel(scope, ASTEngine.RelationshipType.Contains, memberKey(scope, name), node, 1));
470
+ activeScope = name;
471
+ containerScope = false;
472
+ const ovr = this.overrideEdgeFor(node, scope);
473
+ if (ovr) relationships.push(ovr);
474
+ break;
475
+ }
476
+ // ---------------------------------------------------------------- SHORT-FORM function definition
477
+ //
478
+ // `f(x) = expr` and `A.B(x) = expr` are NOT `function_definition` nodes. The grammar
479
+ // gives `(assignment (call_expression (identifier) (argument_list …)) (operator) rhs)`, so
480
+ // Julia's single most common way of writing a function has no node of its own. Handling
481
+ // only `function … end` found 4 functions in a sample where the file has 7, and — worse —
482
+ // attributed every call INSIDE a short-form body to the enclosing module, because the scope
483
+ // never advanced. Julia's own stdlib writes this way far more often than the long form.
484
+ case "assignment": {
485
+ let lhs = this.unwrapWhere(this.pos(node, 0) ?? node);
486
+ if (lhs.type === "typed_expression") {
487
+ const inner = this.pos(lhs, 0);
488
+ if (inner && inner.type === "call_expression") lhs = inner;
489
+ }
490
+ if (lhs.type === "parametrized_type_expression") {
491
+ const wrapper = node.parent ? node.parent.type : "";
492
+ if (wrapper === "const_statement" || wrapper === "global_statement" || wrapper === "local_statement") break;
493
+ const base = this.pos(lhs, 0);
494
+ const alias = base && base.type === "identifier" ? base.text.trim() : "";
495
+ if (!alias || IMPLICIT_OR_CONSTANT.has(alias)) break;
496
+ if (!containerScope) break;
497
+ pushSym({
498
+ name: alias,
499
+ kind: ASTEngine.SymbolKind.Class,
500
+ range: this.range(node),
501
+ complexity: this.complexity(node),
502
+ ownerName: scope || void 0,
503
+ metadata: { juliaSpecific: true, declarationForm: "parametric_alias" }
504
+ });
505
+ if (scope) relationships.push(this.rel(scope, ASTEngine.RelationshipType.Contains, memberKey(scope, alias), node, 1));
506
+ break;
507
+ }
508
+ if (lhs.type !== "call_expression") {
509
+ const wrapper = node.parent ? node.parent.type : "";
510
+ if (wrapper === "const_statement" || wrapper === "global_statement" || wrapper === "local_statement") break;
511
+ if (!containerScope) break;
512
+ const binding = this.plainModuleBinding(node);
513
+ if (!binding || IMPLICIT_OR_CONSTANT.has(binding)) break;
514
+ pushSym({
515
+ name: binding,
516
+ kind: ASTEngine.SymbolKind.Variable,
517
+ range: this.range(node),
518
+ complexity: this.complexity(node),
519
+ ownerName: scope || void 0,
520
+ metadata: { juliaSpecific: true, declarationForm: "module_assignment" }
521
+ });
522
+ if (scope) relationships.push(this.rel(scope, ASTEngine.RelationshipType.Contains, memberKey(scope, binding), node, 1));
523
+ break;
524
+ }
525
+ const name = this.calleeName(lhs);
526
+ if (!name || IMPLICIT_OR_CONSTANT.has(name)) break;
527
+ pushSym({
528
+ name,
529
+ kind: ASTEngine.SymbolKind.Function,
530
+ range: this.range(node),
531
+ complexity: this.complexity(node),
532
+ ownerName: scope || void 0,
533
+ metadata: { juliaSpecific: true, declarationForm: "short_form_assignment", hasBlockBody: false }
534
+ });
535
+ this.declSites.add(lhs.id);
536
+ if (scope && containerScope) relationships.push(this.rel(scope, ASTEngine.RelationshipType.Contains, memberKey(scope, name), node, 1));
537
+ const ovr = this.overrideEdgeFor(node, scope);
538
+ if (ovr) relationships.push(ovr);
539
+ activeScope = name;
540
+ containerScope = false;
541
+ break;
542
+ }
543
+ // ---------------------------------------------------------------- const / global / local
544
+ case "const_statement":
545
+ case "global_statement":
546
+ case "local_statement": {
547
+ const assign = this.firstNamedChildOfType(node, "assignment");
548
+ if (!assign) break;
549
+ const lhs = this.pos(assign, 0);
550
+ if (!lhs || lhs.type === "call_expression") break;
551
+ const name = this.nameOf(lhs);
552
+ if (!name || IMPLICIT_OR_CONSTANT.has(name)) break;
553
+ const isGlobal = node.type === "global_statement" || this.hasToken(node, "global");
554
+ const owner = isGlobal ? this.enclosingModuleName(node) : scope;
555
+ if (this.inMacroBody && node.type !== "const_statement") break;
556
+ pushSym({
557
+ name,
558
+ kind: node.type === "const_statement" ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Variable,
559
+ range: this.range(node),
560
+ complexity: this.complexity(node),
561
+ ownerName: owner || void 0,
562
+ metadata: { juliaSpecific: true, binding: node.type.replace("_statement", "") }
563
+ });
564
+ if (owner && (isGlobal || containerScope)) relationships.push(this.rel(owner, ASTEngine.RelationshipType.Contains, memberKey(owner, name), node, 1));
565
+ break;
566
+ }
567
+ // ---------------------------------------------------------------- imports
568
+ case "import_statement":
569
+ case "using_statement": {
570
+ for (const t of this.importTargets(node)) {
571
+ imports.push({
572
+ module: t.name,
573
+ items: t.items,
574
+ importType: node.type === "import_statement" ? "import" : "using",
575
+ lineNumber: node.startPosition.row + 1,
576
+ isExternal: !t.name.startsWith(".")
577
+ });
578
+ relationships.push(this.rel("root", ASTEngine.RelationshipType.References, t.name, node, 1, { juliaSpecific: true, isImport: true, importForm: node.type }));
579
+ relationships.push(
580
+ this.rel(scope || "root", ASTEngine.RelationshipType.Imports, t.name, node, 1, {
581
+ juliaSpecific: true,
582
+ importForm: node.type,
583
+ isExternal: !t.name.startsWith("."),
584
+ importRenamed: t.renamed === true
585
+ })
586
+ );
587
+ }
588
+ break;
589
+ }
590
+ case "export_statement":
591
+ case "public_statement": {
592
+ if (this.namedChildren(node)[0]?.type === "ERROR") break;
593
+ for (const child of this.namedChildren(node)) {
594
+ const name = this.exportableName(child);
595
+ if (!name) continue;
596
+ exports.push({
597
+ name,
598
+ // The kind is deliberately NOT asserted. `export X` exports a BINDING whose kind
599
+ // depends on what X turns out to be — a struct in one file, a function in another —
600
+ // and Julia allows the export to precede the declaration. The first version pushed a
601
+ // `SymbolKind.Function` for every `export`, which produced a SECOND `Circle` beside
602
+ // the real `class:Circle`, and the dedup did not merge them because its key is
603
+ // `kind:owner:name` and the kinds differed. So `export` records an export and marks
604
+ // nothing: the declaration is the declaration.
605
+ type: node.type === "export_statement" ? "value" : "type",
606
+ lineNumber: node.startPosition.row + 1,
607
+ metadata: { juliaSpecific: true, exportForm: node.type, declaresNothing: true }
608
+ });
609
+ for (const sym of symbols) {
610
+ if (sym.name !== name) continue;
611
+ sym.metadata = { ...sym.metadata ?? {}, isExported: true, exportForm: node.type };
612
+ }
613
+ relationships.push(
614
+ this.rel(scope || "root", ASTEngine.RelationshipType.Exports, name, node, 1, {
615
+ juliaSpecific: true,
616
+ exportForm: node.type,
617
+ declaresNothing: true
618
+ })
619
+ );
620
+ }
621
+ break;
622
+ }
623
+ // ---------------------------------------------------------------- two call shapes with no `call_expression`
624
+ //
625
+ // Both were INVISIBLE: the walk recursed into them, found nothing to declare, and returned no
626
+ // edge, so a real invocation in Julia's two most idiomatic forms simply did not appear in the
627
+ // graph. Found by classifying every node type the analyzer names against every node type the
628
+ // grammar produces — 53 produced types the analyzer never mentions, and these two are calls.
629
+ //
630
+ // MEASURED on the corpus: `adjoint_expression` occurs in 22 files and
631
+ // `broadcast_call_expression` in 74.
632
+ case "adjoint_expression": {
633
+ if (this.namedChildren(node).length === 1) {
634
+ relationships.push(this.rel(
635
+ scope || GLOBAL_SCOPE,
636
+ ASTEngine.RelationshipType.Calls,
637
+ "adjoint",
638
+ node,
639
+ 1,
640
+ { juliaSpecific: true, isOperatorApplication: true, spelling: "postfix_prime" }
641
+ ));
642
+ }
643
+ break;
644
+ }
645
+ case "broadcast_call_expression": {
646
+ const callee = this.calleeName(node);
647
+ if (callee && !IMPLICIT_OR_CONSTANT.has(callee)) {
648
+ relationships.push(this.rel(
649
+ scope || GLOBAL_SCOPE,
650
+ ASTEngine.RelationshipType.Calls,
651
+ callee,
652
+ node,
653
+ 1,
654
+ { juliaSpecific: true, isBroadcastCall: true }
655
+ ));
656
+ }
657
+ break;
658
+ }
659
+ // ---------------------------------------------------------------- calls and construction
660
+ case "call_expression": {
661
+ if (this.declSites.has(node.id)) break;
662
+ const callee = this.calleeName(node);
663
+ if (!callee || IMPLICIT_OR_CONSTANT.has(callee)) break;
664
+ if (callee === "include") {
665
+ const part = this.includePart(node);
666
+ if (part) {
667
+ relationships.push(this.rel(scope || GLOBAL_SCOPE, ASTEngine.RelationshipType.PartOf, part.target, node, 1, {
668
+ partRole: "library",
669
+ partPath: part.path,
670
+ partSymbol: part.target,
671
+ isJuliaInclude: true
672
+ }));
673
+ break;
674
+ }
675
+ relationships.push(this.rel(scope || GLOBAL_SCOPE, ASTEngine.RelationshipType.Calls, callee, node, 1, {
676
+ juliaSpecific: true,
677
+ isInclude: true,
678
+ isDynamicInclude: true
679
+ }));
680
+ break;
681
+ }
682
+ const dot = callee.indexOf(".");
683
+ const qualifier = dot > 0 ? callee.slice(0, dot) : "";
684
+ const ctorLeaf = dot > 0 ? callee.slice(callee.lastIndexOf(".") + 1) : callee;
685
+ const sameFileCtor = dot > 0 && declaredModules.has(qualifier) && declaredTypes.has(ctorLeaf);
686
+ const isCtor = declaredTypes.has(callee) || sameFileCtor || TYPE_DECLARATIONS.has(callee);
687
+ const foreign = callee === "ccall" ? this.ccallTarget(node) : null;
688
+ const meta = isCtor ? { juliaSpecific: true, isTypeConstruction: true } : foreign ? { juliaSpecific: true, isForeignCall: true, foreignSymbol: foreign.symbol, foreignLibrary: foreign.library, via: "ccall" } : this.foreignMarked.has(this.textKey(node, callee)) ? { juliaSpecific: true, isForeignCall: true, via: "@ccall" } : { juliaSpecific: true };
689
+ {
690
+ relationships.push(this.rel(
691
+ scope || GLOBAL_SCOPE,
692
+ isCtor ? ASTEngine.RelationshipType.Instantiates : ASTEngine.RelationshipType.Calls,
693
+ sameFileCtor ? ctorLeaf : callee,
694
+ node,
695
+ isCtor ? 0.9 : 1,
696
+ meta
697
+ ));
698
+ }
699
+ if (foreign) {
700
+ relationships.push(this.rel(
701
+ scope || GLOBAL_SCOPE,
702
+ ASTEngine.RelationshipType.Calls,
703
+ foreign.symbol,
704
+ node,
705
+ 1,
706
+ {
707
+ juliaSpecific: true,
708
+ isForeignCall: true,
709
+ foreignSymbol: foreign.symbol,
710
+ foreignLibrary: foreign.library,
711
+ via: "ccall"
712
+ }
713
+ ));
714
+ }
715
+ break;
716
+ }
717
+ // ---------------------------------------------------------------- macro calls
718
+ case "macrocall_expression": {
719
+ const nameNode = this.firstNamedChildOfType(node, "macro_identifier");
720
+ const name = nameNode ? macroNameOf(nameNode.text) : "";
721
+ if (name === "ccall") this.markForeignCallees(node);
722
+ if (!name) break;
723
+ const decorated = this.decoratedDeclarations(node);
724
+ if (decorated.length > 0) {
725
+ for (const d of decorated) {
726
+ relationships.push(this.rel(d.name, ASTEngine.RelationshipType.Decorates, name, node, 1, {
727
+ decoratorName: name,
728
+ decoratedKind: d.kind,
729
+ isMacroDecorator: true
730
+ }));
731
+ }
732
+ break;
733
+ }
734
+ relationships.push(this.rel(scope || GLOBAL_SCOPE, ASTEngine.RelationshipType.Calls, name, node, 1, { juliaSpecific: true, isMacroCall: true }));
735
+ break;
736
+ }
737
+ // ---------------------------------------------------------------- type annotations
738
+ case "unary_typed_expression":
739
+ case "typed_expression": {
740
+ const typedKids = this.namedChildren(node);
741
+ const anonymous = node.type === "unary_typed_expression" || node.text.trimStart().startsWith("::");
742
+ const typeNode = anonymous ? typedKids[0] : typedKids.length >= 2 ? typedKids[1] : typedKids[0];
743
+ const typeName = typeNode ? this.typeRefName(typeNode) : "";
744
+ if (typeName) relationships.push(this.rel(scope || GLOBAL_SCOPE, ASTEngine.RelationshipType.References, typeName, node, 1, { juliaSpecific: true, isTypeReference: true }));
745
+ break;
746
+ }
747
+ case "parametrized_type_expression": {
748
+ for (const inner of this.namedChildren(node)) {
749
+ if (inner.type !== "curly_expression") continue;
750
+ for (const b of this.namedChildren(inner)) {
751
+ for (const link of this.subtypeChainLinks(b)) {
752
+ relationships.push(this.rel(
753
+ link.from,
754
+ ASTEngine.RelationshipType.Extends,
755
+ link.to,
756
+ link.at,
757
+ 1,
758
+ { juliaSpecific: true, isSubtype: true, isWhereBound: true }
759
+ ));
760
+ }
761
+ }
762
+ }
763
+ if (this.withinTypeHead(node)) break;
764
+ const baseNode = this.pos(node, 0);
765
+ const base = baseNode ? this.fieldPath(baseNode) : "";
766
+ if (base) relationships.push(this.rel(scope || GLOBAL_SCOPE, ASTEngine.RelationshipType.References, base, node, 1, { juliaSpecific: true, isTypeReference: true }));
767
+ break;
768
+ }
769
+ case "where_expression": {
770
+ const whereKids = this.namedChildren(node);
771
+ const lastWhereKid = whereKids[whereKids.length - 1];
772
+ for (const child of whereKids) {
773
+ if (child.type === "curly_expression") {
774
+ for (const inner of this.namedChildren(child)) {
775
+ for (const link of this.subtypeChainLinks(inner)) {
776
+ relationships.push(this.rel(link.from, ASTEngine.RelationshipType.Extends, link.to, link.at, 1, { juliaSpecific: true, isSubtype: true, isWhereBound: true }));
777
+ }
778
+ }
779
+ continue;
780
+ }
781
+ if (child.type !== "binary_expression") continue;
782
+ if (child !== lastWhereKid && node.parent?.type !== "curly_expression") continue;
783
+ for (const link of this.subtypeChainLinks(child)) {
784
+ relationships.push(this.rel(link.from, ASTEngine.RelationshipType.Extends, link.to, link.at, 1, { juliaSpecific: true, isSubtype: true, isWhereBound: true }));
785
+ }
786
+ }
787
+ break;
788
+ }
789
+ // ---------------------------------------------------------------- field access
790
+ case "field_expression": {
791
+ if (node.parent?.type === "field_expression") break;
792
+ const value = node.childForFieldName("value");
793
+ if (!value) break;
794
+ if (value.type === "field_expression") {
795
+ const path2 = this.fieldPath(node);
796
+ if (path2) {
797
+ relationships.push(this.rel(
798
+ scope || GLOBAL_SCOPE,
799
+ ASTEngine.RelationshipType.References,
800
+ path2,
801
+ node,
802
+ 0.95,
803
+ { juliaSpecific: true, isQualifiedReference: true }
804
+ ));
805
+ }
806
+ break;
807
+ }
808
+ const member = this.namedChildren(node).find((c) => c.type === "identifier" && c.id !== value.id) ?? this.namedChildren(node).find((c) => c.type === "identifier" && c.id !== value.id) ?? this.namedChildren(node).find((c) => c.id !== value.id && (c.type === "quote_expression" || c.type === "operator"));
809
+ if (!member) break;
810
+ const memberName = member.type === "quote_expression" ? this.quotedName(member) : member.text.trim();
811
+ if (!memberName) break;
812
+ relationships.push(this.rel(
813
+ scope || GLOBAL_SCOPE,
814
+ ASTEngine.RelationshipType.Uses,
815
+ memberName,
816
+ node,
817
+ 0.9,
818
+ { juliaSpecific: true, isFieldRead: true, isOperatorMember: member.type !== "identifier" }
819
+ ));
820
+ break;
821
+ }
822
+ }
823
+ walkStack.push({ node, activeScope: scope, containerScope: parentContainerScope, inMacroBody: parentInMacroBody, isExit: true });
824
+ const childScope = activeScope;
825
+ let opaqueArgs = null;
826
+ const opaqueArgsOf = (parent) => {
827
+ const set = /* @__PURE__ */ new Set();
828
+ for (const c of this.namedChildren(parent)) {
829
+ if (c.type === "macro_identifier" || c.type === "macro_argument_list") continue;
830
+ if (!this.directDeclarationArg(c)) set.add(c.id);
831
+ }
832
+ return set;
833
+ };
834
+ if (node.type === "macrocall_expression" && !this.macroIsScopeTransparent(node)) {
835
+ opaqueArgs = opaqueArgsOf(node);
836
+ }
837
+ if (node.type === "macro_argument_list" && node.parent && node.parent.type === "macrocall_expression" && !this.macroIsScopeTransparent(node.parent)) {
838
+ opaqueArgs = opaqueArgsOf(node);
839
+ }
840
+ depth++;
841
+ const fieldTypeChild = node.type === "typed_expression" && node.parent && node.parent.type === "struct_definition" ? this.pos(node, 1) : null;
842
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
843
+ const c = node.namedChild(i);
844
+ if (!c) continue;
845
+ const typePositionChild = (node.type === "struct_definition" || node.type === "abstract_definition" || node.type === "primitive_definition") && c.type === "type_head" || (fieldTypeChild ? c.id === fieldTypeChild.id : false);
846
+ walkStack.push({ node: c, activeScope: childScope, containerScope: typePositionChild || opaqueArgs?.has(c.id) ? false : containerScope, inMacroBody: this.inMacroBody, isExit: false });
847
+ }
848
+ }
849
+ const symbolMap = /* @__PURE__ */ new Map();
850
+ for (const s of symbols) symbolMap.set(`${s.kind}:${s.ownerName || ""}:${s.name}`, s);
851
+ const uniqueSymbols = [...symbolMap.values()];
852
+ const declsByName = /* @__PURE__ */ new Map();
853
+ for (const s of uniqueSymbols) {
854
+ const list = declsByName.get(s.name) ?? [];
855
+ list.push(s);
856
+ declsByName.set(s.name, list);
857
+ }
858
+ if (bindingsByScope.size > 0) {
859
+ for (const r of relationships) {
860
+ if (r.type !== ASTEngine.RelationshipType.Calls && r.type !== ASTEngine.RelationshipType.References) continue;
861
+ const tgt = String(r.target ?? "");
862
+ if (!tgt || tgt.includes(".") || tgt.includes(BINDING_SEP)) continue;
863
+ if (!bindingsByScope.get(String(r.source ?? ""))?.has(tgt)) continue;
864
+ r.target = `${r.source}${BINDING_SEP}${tgt}`;
865
+ r.metadata = { ...r.metadata ?? {}, localBinding: true };
866
+ }
867
+ }
868
+ for (const r of relationships) {
869
+ if (r.type !== ASTEngine.RelationshipType.References) continue;
870
+ const tgt = String(r.target ?? "");
871
+ if (!tgt || tgt.includes(".")) continue;
872
+ const decls = declsByName.get(tgt);
873
+ if (!decls || decls.length !== 1) continue;
874
+ const q = decls[0].qualifiedName;
875
+ if (typeof q === "string" && q) r.target = q;
876
+ }
877
+ const relMap = /* @__PURE__ */ new Map();
878
+ for (const r of relationships) {
879
+ const key = `${r.type}|${r.source}->${r.target}`;
880
+ const prev = relMap.get(key);
881
+ if (prev) {
882
+ if (prev.line_number === void 0 && r.line_number !== void 0) prev.line_number = r.line_number;
883
+ } else relMap.set(key, r);
884
+ }
885
+ const uniqueRelationships = [...relMap.values()];
886
+ this.classifyEvidenceTier(uniqueRelationships);
887
+ const includeOwners = new Set(
888
+ uniqueSymbols.filter((s) => s.name === "include" && s.kind !== ASTEngine.SymbolKind.Module).map((s) => s.ownerName ?? "")
889
+ );
890
+ if (includeOwners.size > 0) {
891
+ for (const r of uniqueRelationships) {
892
+ const md = r.metadata;
893
+ if (!md || md.isJuliaInclude !== true) continue;
894
+ const src = String(r.source ?? "");
895
+ const shadowed = [...includeOwners].some((owner) => owner === "" || src === owner || src.startsWith(owner + "."));
896
+ if (!shadowed) continue;
897
+ r.type = ASTEngine.RelationshipType.Calls;
898
+ r.target = "include";
899
+ r.metadata = { juliaSpecific: true, isInclude: true, isShadowedInclude: true };
900
+ }
901
+ }
902
+ const declaredNames = new Set(uniqueSymbols.map((s) => s.name));
903
+ for (const r of uniqueRelationships) {
904
+ if (r.type !== ASTEngine.RelationshipType.Imports) continue;
905
+ const md = r.metadata;
906
+ if (!md || md.isExternal !== true) continue;
907
+ const tgt = String(r.target ?? "");
908
+ const dot = tgt.indexOf(".");
909
+ const dotted = dot > 0;
910
+ const root = dotted ? tgt.slice(0, dot) : tgt;
911
+ if (dotted || declaredNames.has(root)) md.isExternal = false;
912
+ }
913
+ return {
914
+ language: "julia",
915
+ symbols: uniqueSymbols,
916
+ relationships: uniqueRelationships,
917
+ imports,
918
+ exports,
919
+ errors,
920
+ metadata: {
921
+ complexity: uniqueSymbols.length,
922
+ linesOfCode: content.split("\n").length,
923
+ declaredTypes: [...declaredTypes]
924
+ }
925
+ };
926
+ }
927
+ // ------------------------------------------------------------------ helpers
928
+ /**
929
+ * The declaration this node carries, as the pair julia's method table keys on: a NAME and a
930
+ * SIGNATURE. Returns null for a node that declares no method.
931
+ *
932
+ * The signature is the WHOLE argument list, normalised for whitespace, plus the `where` clause
933
+ * when the declaration has one — because `f(x) where {T} = …` and `f(x) where {S} = …` are two
934
+ * DIFFERENT methods and `f(x) = …` and `f(x, y) = …` are two different methods, while
935
+ * `f(x) = 1` and `f( x ) = 2` are the same one. Keying on the name alone would conflate every
936
+ * method of a generic function, and a generic function's methods are precisely the ones the
937
+ * `overrides` argument in this file's header says are NOT overrides.
938
+ *
939
+ * Both declaration FORMS are read, because julia has two and the corpus uses both heavily: the
940
+ * long `function f(x) … end` (whose `signature` holds the call) and the short `f(x) = …` (whose
941
+ * assignment's left-hand side is the call, possibly behind a `where`, a return-type annotation
942
+ * and parentheses — the same wrappers `unwrapWhere` already knows about).
943
+ */
944
+ declarationSiteOf(node) {
945
+ if (node.type === "function_definition" || node.type === "macro_definition") {
946
+ const sig = this.firstNamedChildOfType(node, "signature");
947
+ if (!sig) return null;
948
+ const name2 = this.signatureName(sig);
949
+ return name2 ? { name: name2, signature: this.methodSignatureKey(sig) } : null;
950
+ }
951
+ if (node.type !== "assignment") return null;
952
+ const rawLhs = this.pos(node, 0) ?? node;
953
+ let lhs = this.unwrapWhere(rawLhs);
954
+ if (lhs.type === "typed_expression") {
955
+ const inner = this.pos(lhs, 0);
956
+ if (inner && inner.type === "call_expression") lhs = inner;
957
+ }
958
+ if (lhs.type !== "call_expression") return null;
959
+ const name = this.calleeName(lhs);
960
+ if (!name || IMPLICIT_OR_CONSTANT.has(name)) return null;
961
+ return { name, signature: this.methodSignatureKey(rawLhs, lhs) };
962
+ }
963
+ /**
964
+ * The `(name, signature)` half that distinguishes two methods of the same generic function.
965
+ *
966
+ * `root` is the node the declaration's text hangs off — a `signature` for the long form, the
967
+ * assignment's un-unwrapped left-hand side for the short one — and `call` is the callable inside
968
+ * it. Both are needed because they are not the same node: `where` wraps the call in one and the
969
+ * other has no `where` at all, so a single argument could only ever capture one of the two shapes.
970
+ */
971
+ methodSignatureKey(root, call) {
972
+ const callNode = call ?? (root.type === "call_expression" ? root : this.namedChildren(root).find((c) => c.type === "call_expression"));
973
+ if (!callNode) return "";
974
+ const args = this.firstNamedChildOfType(callNode, "argument_list");
975
+ const whereNode = root.type === "where_expression" ? root : this.namedChildren(root).find((c) => c.type === "where_expression");
976
+ if (!args && !whereNode) return "";
977
+ return `${args ? collapseSpace(args.text) : ""}${whereNode ? `|where ${collapseSpace(whereNode.text)}` : ""}`;
978
+ }
979
+ /**
980
+ * Is this declaration reached unconditionally, with text julia itself wrote?
981
+ *
982
+ * Walks ANCESTORS rather than the parent, because the conditional node is not always adjacent:
983
+ * a declaration inside `else` sits under `else_clause` and `if_statement`, and a declaration
984
+ * inside `catch` sits under `catch_clause` and `try_statement`, so a parent-only test reads
985
+ * `try_statement` as unconditional. The whole list is what the test is — adding a construct to
986
+ * it makes the edge class narrower, which is the direction that errs toward refusing rather than
987
+ * toward asserting.
988
+ *
989
+ * `macrocall_expression` and the quote forms are in the set because their content is not julia text
990
+ * in this file: `@eval f(x) = …` and `quote f(x) = … end` are code GENERATED at expansion, and the
991
+ * tree shows the generated text, not what the file declares.
992
+ *
993
+ * `&&` and `||` are not in the set — they are `binary_expression` nodes, which are the most common
994
+ * node in julia and cannot be listed wholesale — so the short-circuit case is tested by OPERATOR.
995
+ * In `cond && function f() … end` the right operand runs only when `cond` holds, and a declaration
996
+ * there is as conditional as one inside an `if`. Only the RIGHT operand is conditional; the left
997
+ * one always runs, which is why the test looks at the second OPERAND rather than both.
998
+ *
999
+ * `&&` is the live operator and `||` is not: measured over the 971 admitted corpus files there are
1000
+ * 11,549 `&&` binary_expressions and 5,145 `||` ones, and of the `&&` set exactly ONE has a
1001
+ * declaration node as its right operand — `julia/JuliaSyntax/src/core/parse_stream.jl:316`,
1002
+ *
1003
+ * VERSION < v"1.11.0-DEV.753" && function ParseStream(io::IOBuffer; version=VERSION)
1004
+ *
1005
+ * which is a version-guarded method definition and is exactly the shape that must not publish an
1006
+ * edge. One is not zero, so this is a predicate the grammar can reach rather than a safety net
1007
+ * that never fires; `||` is left out because no `||` in the corpus has one, and a branch that
1008
+ * cannot match is what honesty contract §2's L2 audit exists to report.
1009
+ *
1010
+ * THE OPERAND INDEX IS NOT 1. The operator is a NAMED child of `binary_expression` in this
1011
+ * grammar, so the operand list is `[left, operator, right]` over named children and the right
1012
+ * operand sits at index 2. The first version read index 1, compared it to the declaration's own
1013
+ * node id, never matched, and published `overrides` for the one guarded definition in the corpus
1014
+ * — found by running the fixture rather than by reading the code, which is the only way a
1015
+ * predicate that is wrong in the direction of asserting anything gets caught.
1016
+ */
1017
+ isUnconditionalPosition(node) {
1018
+ let cur = node;
1019
+ for (let i = 0; i < 24 && cur; i++) {
1020
+ if (CONDITIONAL_OR_GENERATED.has(cur.type)) return false;
1021
+ if (cur.type === "binary_expression" && this.shortCircuitOperator(cur) === "&&") {
1022
+ const named = this.namedChildren(cur).filter((c) => c.type !== "operator");
1023
+ const right = named.length > 1 ? named[named.length - 1] : null;
1024
+ if (right && right.id === node.id) return false;
1025
+ }
1026
+ cur = cur.parent ?? null;
1027
+ }
1028
+ return true;
1029
+ }
1030
+ /** The operator token of a `binary_expression`, or "" — in this grammar the operator is a NAMED
1031
+ * child of type `operator`, the same shape `binOp` reads in the census instrument. */
1032
+ shortCircuitOperator(node) {
1033
+ for (let i = 0; i < node.childCount; i++) {
1034
+ const c = node.child(i);
1035
+ if (c && c.type === "operator") return c.text.trim();
1036
+ }
1037
+ return "";
1038
+ }
1039
+ /**
1040
+ * The `overrides` edge for a declaration that replaces an earlier one.
1041
+ *
1042
+ * ## The edge shape, and why it is not `name -> name`
1043
+ *
1044
+ * A Julia method-table replacement has both of its endpoints at the SAME symbol: the earlier
1045
+ * declaration and the later one are both named `f` with the same signature, so the second
1046
+ * definition replaces the first IN PLACE — it acquires no supertype, no owner and no new name. A
1047
+ * self-loop `f -overrides-> f` is therefore the most literally accurate shape available, and it is
1048
+ * exactly the shape this codebase already ruled out three times:
1049
+ *
1050
+ * - `src/ast/real-php-analyzer.ts:2496` — "UNRESOLVABLE and emit NO edge (a self-loop is always
1051
+ * fabrication, and a …)"
1052
+ * - `src/lsp/rich_hover.ts:222` — "Degenerate self-loop rows … are not connections and not
1053
+ * usages: skip them."
1054
+ * - `src/intelligence/patterns/orphans.ts:818` — a self-loop `overrides` row is listed under
1055
+ * "REFUSED EVIDENCE", and the rake `listen` regression is the case it cites: a self-loop row
1056
+ * was the only thing standing between a real method and a dead-code report.
1057
+ *
1058
+ * A corpus full of rows every consumer discards is not coverage; it is volume that makes the
1059
+ * signal harder to see. So the SOURCE is the enclosing DECLARED scope — a `module` or a type, a
1060
+ * symbol this file really declares — and the TARGET is the method whose entry is replaced.
1061
+ * `module M … f(x) = 1; f(x) = 2 … end` gives `M -overrides-> f` with both ends binding to real
1062
+ * symbols in the same file, which is the same shape the bash lane settled on for the same
1063
+ * construct (`real-bash-analyzer.ts:322-348`, BD8b) and the shape
1064
+ * `src/ast/override-retarget.ts` is written to consume.
1065
+ *
1066
+ * ## Why the module-less case is REFUSED rather than published as `global`
1067
+ *
1068
+ * 71.9% of Julia corpus files declare no module at all — measured, see this file's `GLOBAL_SCOPE`
1069
+ * note — so the refused case is the MAJORITY one and it is worth being explicit that it is a
1070
+ * refusal rather than an oversight. Publishing `global -overrides-> f` would give the edge a
1071
+ * source that resolves to nothing, which is the same dangling identity this file's `contains` case
1072
+ * declines to invent, and `global -calls-> f` is already the largest unresolvable-source bucket in
1073
+ * the census. The edge is dropped instead, and the drop is counted by the census rather than left
1074
+ * invisible.
1075
+ *
1076
+ * Confidence 1.0: this is a byte comparison of two declaration signatures in one file, which is a
1077
+ * parse fact and not a guess. `evidenceClass` is `structural` for the same reason (see
1078
+ * `classifyEvidenceTier`), so the edge is never counted as a heuristic.
1079
+ */
1080
+ overrideEdgeFor(node, scope) {
1081
+ const hit = this.redefinedAt.get(node.id);
1082
+ if (!hit) return null;
1083
+ if (!scope || scope === hit.name) return null;
1084
+ if (!this.ownsAMethodTable(scope, hit.name)) return null;
1085
+ return this.rel(scope, ASTEngine.RelationshipType.Overrides, hit.name, node, 1, {
1086
+ juliaSpecific: true,
1087
+ isMethodRedefinition: true,
1088
+ redefinition: true,
1089
+ via: "identical_signature_redefinition",
1090
+ replacesLine: hit.replacesLine,
1091
+ signature: hit.signature.slice(0, 120),
1092
+ declaredIn: scope
1093
+ });
1094
+ }
1095
+ /**
1096
+ * Does this file declare `owner` as a module or a type — the two things whose bodies hold a method
1097
+ * table?
1098
+ *
1099
+ * Answered from the symbol table rather than from the walk's `activeScope`, because `activeScope`
1100
+ * names FUNCTIONS too and a function is not a method-table owner. The symbol kinds are the
1101
+ * analyzer's own: `SymbolKind.Module` for a `module`/`baremodule`, `SymbolKind.Interface` for an
1102
+ * `abstract type`/`primitive type` and `SymbolKind.Class` for a `struct`. Cost is a scan of the
1103
+ * file's own symbols, once per admitted override edge, over a list that is already in memory.
1104
+ */
1105
+ ownsAMethodTable(owner, target) {
1106
+ const syms = this.declaredSymbolKinds;
1107
+ if (!syms) return false;
1108
+ const kind = syms.get(owner);
1109
+ if (kind !== ASTEngine.SymbolKind.Module && kind !== ASTEngine.SymbolKind.Interface && kind !== ASTEngine.SymbolKind.Class) return false;
1110
+ const t = syms.get(target);
1111
+ return t === ASTEngine.SymbolKind.Function || t === ASTEngine.SymbolKind.Macro;
1112
+ }
1113
+ /**
1114
+ * The one place a relationship is constructed.
1115
+ *
1116
+ * `type` is `ASTEngine.RelationshipType` and NOT `RelationshipType | string`, and that narrowing is
1117
+ * the point of this entry rather than a typing preference. The lane used to pass serialized string
1118
+ * literals ("contains", "extends"), which type-checked because the parameter accepted `string` — and
1119
+ * which `script/m14/verify-doc-truth-emissions.ts` cannot see: its header declares the limit
1120
+ * outright, "A serialized kind emitted by assigning a STRING literal would be invisible to this
1121
+ * gate", and it derives a lane's emitted surface from `RelationshipType.X` references in code. So
1122
+ * every relationship this analyzer published was outside the one gate whose whole job is to catch a
1123
+ * doc claiming a channel the analyzer does not fill. Naming the enum member makes the surface
1124
+ * readable by that gate and makes a typo in a type label a compile error rather than a row nothing
1125
+ * groups.
1126
+ *
1127
+ * The serialized values are identical to the literals that were here (`RelationshipType.Contains`
1128
+ * is `"contains"`, `RelationshipType.Imports` is `"brain_import"`), so this changes the label's
1129
+ * provenance and nothing about the bytes stored.
1130
+ */
1131
+ rel(source, type, target, node, confidence, metadata) {
1132
+ return {
1133
+ source,
1134
+ target,
1135
+ type,
1136
+ confidence,
1137
+ line_number: node.startPosition.row + 1,
1138
+ metadata: { juliaSpecific: true, ...metadata ?? {} }
1139
+ };
1140
+ }
1141
+ /**
1142
+ * Is this macro ARGUMENT itself a declaration — the form a decorator applies to?
1143
+ *
1144
+ * The same shapes `decoratedDeclarations` admits, asked from the other side: a long-form
1145
+ * definition, or a short-form `f(x) = …` assignment whose left-hand side unwraps to a call. Used
1146
+ * to keep `@kwdef struct S` a module member while everything inside `@testset begin … end` is not.
1147
+ */
1148
+ /** Is this macro one whose arguments are evaluated in the scope they are written in? */
1149
+ macroIsScopeTransparent(macrocall) {
1150
+ const m = this.firstNamedChildOfType(macrocall, "macro_identifier");
1151
+ return SCOPE_TRANSPARENT_MACROS.has(m ? macroNameOf(m.text) : "");
1152
+ }
1153
+ directDeclarationArg(arg) {
1154
+ if (arg.type === "function_definition" || arg.type === "macro_definition" || arg.type === "struct_definition" || arg.type === "abstract_definition" || arg.type === "primitive_definition" || arg.type === "module_definition") return true;
1155
+ if (arg.type !== "assignment") return false;
1156
+ const lhs = this.pos(arg, 0);
1157
+ const u = lhs ? this.unwrapWhere(lhs) : null;
1158
+ return !!u && u.type === "call_expression";
1159
+ }
1160
+ /**
1161
+ * The declarations a macro is APPLIED TO, read from its own `macro_argument_list`.
1162
+ *
1163
+ * `@doc "…" function g` puts the declaration after a string; `@kwdef mutable struct S` puts it
1164
+ * first; `@inline h(x) = x` is the short form, whose declaration is the ASSIGNMENT's left-hand
1165
+ * call. The scan is over the argument list, not the first child, because a macro that decorates a
1166
+ * declaration can also carry a docstring or a configuration expression before it. A macro with no
1167
+ * declaration argument returns [] and stays a `calls` edge — `@time f()` runs a call, it does not
1168
+ * decorate a declaration.
1169
+ */
1170
+ decoratedDeclarations(call) {
1171
+ const args = this.namedChildren(call).find((c) => c.type === "macro_argument_list");
1172
+ if (!args) return [];
1173
+ if (this.namedChildren(args).length > 4) return [];
1174
+ const out = [];
1175
+ const seen = /* @__PURE__ */ new Set();
1176
+ const add = (name, kind) => {
1177
+ const n = name.trim();
1178
+ if (!n || seen.has(n)) return;
1179
+ seen.add(n);
1180
+ out.push({ name: n, kind });
1181
+ };
1182
+ for (const arg of this.namedChildren(args)) {
1183
+ if (arg.type === "function_definition" || arg.type === "macro_definition") {
1184
+ const sig = this.firstNamedChildOfType(arg, "signature");
1185
+ add(sig ? this.signatureName(sig) : "", arg.type);
1186
+ } else if (arg.type === "struct_definition" || arg.type === "abstract_definition" || arg.type === "primitive_definition") {
1187
+ const head = this.firstNamedChildOfType(arg, "type_head");
1188
+ add(head ? this.typeHeadName(head) : "", arg.type);
1189
+ } else if (arg.type === "module_definition") {
1190
+ const nameNode = arg.childForFieldName("name");
1191
+ add(nameNode ? nameNode.text : "", arg.type);
1192
+ } else if (arg.type === "assignment") {
1193
+ const lhs = this.pos(arg, 0);
1194
+ const u = lhs ? this.unwrapWhere(lhs) : null;
1195
+ if (u && u.type === "call_expression") add(this.calleeName(u) ?? "", "short_function");
1196
+ }
1197
+ }
1198
+ return out;
1199
+ }
1200
+ /**
1201
+ * The PART an `include("path")` names, or null when the argument is not one literal string.
1202
+ *
1203
+ * THE TARGET IS THE BASENAME WITH ITS EXTENSION (`b.jl`), the spelling bash's BD2 module symbol
1204
+ * uses, and the path as written is kept in `metadata.partPath`. A path is not a symbol name; the
1205
+ * arbiter resolves target STRINGS against symbols, so the basename is the only spelling a file's
1206
+ * own module symbol could carry. The written path is evidence, never a silent replacement.
1207
+ *
1208
+ * REJECTED, each because it does not name a part: a string with an interpolation or an escape
1209
+ * sequence (the bytes are not the path), a path ending at a separator (no basename), `.`/`..`, and
1210
+ * every non-`string_literal` argument (`joinpath(...)`, a variable, a `raw"…"`). A rejected
1211
+ * literal is a DYNAMIC include and stays a call with `isDynamicInclude` — a different, true fact.
1212
+ */
1213
+ includePart(call) {
1214
+ const args = this.namedChildren(call).find((c) => c.type === "argument_list");
1215
+ if (!args) return null;
1216
+ const first = this.namedChildren(args)[0];
1217
+ if (!first || first.type !== "string_literal") return null;
1218
+ if (this.namedChildren(first).some((c) => c.type === "string_interpolation" || c.type === "escape_sequence")) return null;
1219
+ const text = first.text;
1220
+ let body = "";
1221
+ if (text.startsWith('"""') && text.endsWith('"""') && text.length >= 6) body = text.slice(3, -3);
1222
+ else if (text.startsWith('"') && text.endsWith('"') && text.length >= 2) body = text.slice(1, -1);
1223
+ else return null;
1224
+ if (!body || body.endsWith("/") || body.endsWith("\\")) return null;
1225
+ const cut = Math.max(body.lastIndexOf("/"), body.lastIndexOf("\\"));
1226
+ const target = body.slice(cut + 1);
1227
+ if (!target || target === "." || target === "..") return null;
1228
+ return { path: body, target };
1229
+ }
1230
+ range(node) {
1231
+ return {
1232
+ startLine: node.startPosition.row + 1,
1233
+ startColumn: node.startPosition.column + 1,
1234
+ endLine: node.endPosition.row + 1,
1235
+ endColumn: node.endPosition.column + 1,
1236
+ startIndex: node.startIndex,
1237
+ endIndex: node.endIndex
1238
+ };
1239
+ }
1240
+ /**
1241
+ * This grammar is almost entirely POSITIONAL.
1242
+ *
1243
+ * Measured from the vendored `src/node-types.json`: **7 of 151 named node types carry field
1244
+ * names** — `module_definition.name`, `field_expression.value`, `if_statement.condition` /
1245
+ * `.alternative`, `elseif_clause.condition`, `while_statement.condition`, and the two prefixed
1246
+ * string/command literals. Every other node, including `binary_expression`, `typed_expression`,
1247
+ * `signature`, `call_expression` and `type_head`, must be navigated by child ORDER.
1248
+ *
1249
+ * The first version of this analyzer reached for `childForFieldName("right")` and
1250
+ * `childForFieldName("name")` throughout, which returns null for a node that has no such
1251
+ * field. It did not throw and it did not crash — it silently emitted **zero `extends` edges**
1252
+ * and **zero struct fields** for a corpus that has thousands of both, and the symptom was a
1253
+ * plausible-looking symbol list. That is the F1 node-type-literal class of defect the C lane's
1254
+ * pin record warns about ("written from memory of a different tree-sitter release, which F1
1255
+ * caught only because the vocabulary happened to be checked"), and the reason `pos()` exists
1256
+ * here rather than another `childForFieldName`.
1257
+ */
1258
+ pos(node, index) {
1259
+ const c = node.namedChild(index);
1260
+ return c ?? null;
1261
+ }
1262
+ /**
1263
+ * The two operands of a `binary_expression`.
1264
+ *
1265
+ * `binary_expression` here is `(left) (operator) (right)` where the OPERATOR IS A NAMED CHILD —
1266
+ * measured, not assumed: `isNamed=true`, `type="operator"`, sitting at index 1. So the naive
1267
+ * "left is child 0, right is child 1" is wrong twice over: index 1 is the operator, not the
1268
+ * right operand, and an operand can itself be an operator token. Right is therefore the LAST
1269
+ * named child, and left the first, which holds for every binary shape in the grammar
1270
+ * (`a + b`, `a::T`, `a <: B`, `a where {B}`).
1271
+ *
1272
+ * Both halves of this were wrong in the first version — `tokenText` scanned only ANONYMOUS
1273
+ * children for the operator, so it never found it, and `pos(node,1)` handed back the operator
1274
+ * where the right operand belonged. The visible effect was **zero `extends` edges** over a
1275
+ * corpus where `<:` is everywhere, on a file that parsed with zero ERROR nodes.
1276
+ */
1277
+ binOperands(node) {
1278
+ const named = this.namedChildren(node).filter((c) => c.type !== "operator");
1279
+ return {
1280
+ op: this.tokenText(node, "operator"),
1281
+ left: named[0] ?? null,
1282
+ right: named.length > 1 ? named[named.length - 1] : null
1283
+ };
1284
+ }
1285
+ /**
1286
+ * A `<:` / `>:` CHAIN states one subtype fact per ADJACENT PAIR, and both ends must be NAMES.
1287
+ *
1288
+ * `A where LB <: X <: UB` parses as `(binary (binary LB <: X) <: UB)`. The previous code read
1289
+ * `left.text` for the source, so it published an `extends` edge whose SOURCE was `LB <: X` — a
1290
+ * source fragment is not a symbol, and the edge can resolve to nothing. MEASURED on the whole
1291
+ * 1,985-file population by `direct-oracle-check.ts`: 25 of the 25 unexplained `extends` rows carry
1292
+ * either a fragment source (`LB <: X`, `X <: f()`, `S <: V`) or a doubled target (`UB.UB`), and
1293
+ * every one of the first ten is in `julia/JuliaLowering/test/typedefs_ir.jl` — a file of deliberate
1294
+ * lowering-error probes whose `where` chains the grammar recovers around. The same defect occurs
1295
+ * in a WELL-FORMED chain, which is the reason it is fixed rather than filed as an ERROR-shape
1296
+ * artefact: a fragment source is a lie the graph tells on any file that writes the chain.
1297
+ *
1298
+ * The rule, stated rather than inferred: flatten the chain left to right, emit `a -> b` and
1299
+ * `b -> c`, take each source with `typeHeadName` (never `.text`), and take each target through
1300
+ * `boundTypeName`, which REFUSES a call/literal bound — `X <: f() <: Z` is a lowering error, and
1301
+ * naming `f` as a type would invent a relation the source says is invalid.
1302
+ */
1303
+ subtypeChainLinks(node) {
1304
+ const out = [];
1305
+ const link = (left, right, at) => {
1306
+ const from = left ? this.boundTypeName(left) : "";
1307
+ const to = right ? this.boundTypeName(right) : "";
1308
+ if (from && to && from !== to) out.push({ from, to, at });
1309
+ };
1310
+ const walk = (n) => {
1311
+ if (n.type !== "binary_expression") return;
1312
+ const { op, left, right } = this.binOperands(n);
1313
+ if (op !== "<:" && op !== ">:") return;
1314
+ let fromNode = left;
1315
+ if (left && left.type === "binary_expression") {
1316
+ const inner = this.binOperands(left);
1317
+ if (inner.op === "<:" || inner.op === ">:") {
1318
+ walk(left);
1319
+ fromNode = inner.right;
1320
+ }
1321
+ }
1322
+ link(fromNode, right, n);
1323
+ };
1324
+ walk(node);
1325
+ return out;
1326
+ }
1327
+ /**
1328
+ * The name a subtype BOUND states, or "" when the tree states none.
1329
+ *
1330
+ * `typeHeadName` accepts a call expression through its first-identifier fallback, which is right
1331
+ * for a declaration head (`f(x) = …`) and wrong for a bound: `A where X <: f() <: Z` is a
1332
+ * lowering error, and publishing `f` as the supertype would invent a type relation the source
1333
+ * explicitly rejects. A bound is an identifier, a dotted path, or a parameterised/wrapped form of
1334
+ * one — anything else is refused.
1335
+ */
1336
+ boundTypeName(node) {
1337
+ if (!node) return "";
1338
+ if (node.type === "identifier" || node.type === "operator") return node.text.trim();
1339
+ if (node.type === "macro_identifier") return macroNameOf(node.text);
1340
+ if (node.type === "field_expression") return this.fieldPath(node);
1341
+ if (node.type === "parametrized_type_expression" || node.type === "where_expression" || node.type === "parenthesized_expression" || node.type === "curly_expression" || node.type === "type_head") {
1342
+ return this.boundTypeName(this.pos(node, 0));
1343
+ }
1344
+ return "";
1345
+ }
1346
+ /**
1347
+ * The operator token of a `binary_expression`. It is a NAMED child of type `operator` in this
1348
+ * grammar — verified by walking the children of a real `struct Circle{T<:Real} <: Shape` —
1349
+ * so a scan restricted to anonymous children finds nothing and the operator reads as empty.
1350
+ */
1351
+ tokenText(node, token) {
1352
+ for (let i = 0; i < node.childCount; i++) {
1353
+ const c = node.child(i);
1354
+ if (c && c.type === token) return c.text.trim();
1355
+ }
1356
+ return "";
1357
+ }
1358
+ namedChildren(node) {
1359
+ const out = [];
1360
+ for (let i = 0; i < node.namedChildCount; i++) {
1361
+ const c = node.namedChild(i);
1362
+ if (c) out.push(c);
1363
+ }
1364
+ return out;
1365
+ }
1366
+ firstNamedChildOfType(node, type) {
1367
+ for (const c of this.namedChildren(node)) if (c.type === type) return c;
1368
+ return null;
1369
+ }
1370
+ firstNamedChildType(node) {
1371
+ for (let i = 0; i < node.childCount; i++) {
1372
+ const c = node.child(i);
1373
+ if (c && c.isNamed) return c.type;
1374
+ }
1375
+ return "";
1376
+ }
1377
+ hasToken(node, token) {
1378
+ for (let i = 0; i < node.childCount; i++) {
1379
+ const c = node.child(i);
1380
+ if (c && !c.isNamed && c.type === token) return true;
1381
+ }
1382
+ return false;
1383
+ }
1384
+ /**
1385
+ * `type_head` is positional: `Name`, `Name{Params}`, or `Name{T} <: Super`.
1386
+ *
1387
+ * The `<:` case is the trap. `struct Circle{T<:Real} <: Shape` makes the head
1388
+ * `(binary_expression (parametrized_type_expression Circle{T<:Real}) <: (identifier Shape))`, so
1389
+ * the first identifier ANYWHERE in that subtree is **Shape** — the supertype — and the first
1390
+ * version named every such struct after its supertype: one `class:Shape` with
1391
+ * `contains -> radius`, and no `extends` edge, because the name and the supertype had been
1392
+ * swapped by the same mistake. A declared name is the subtree's LEFT-most name, so a binary
1393
+ * expression contributes its left operand and the supertype is read separately by
1394
+ * `supertypesOf`. That is a plausible-looking wrong answer, which is the whole difficulty: the
1395
+ * symbol list looked like a valid parse of a valid file.
1396
+ */
1397
+ typeRefName(node) {
1398
+ if (node.type === "interpolation_expression") {
1399
+ const inner = this.pos(node, 0);
1400
+ return inner ? this.fieldPath(inner) : "";
1401
+ }
1402
+ return this.typeHeadName(node);
1403
+ }
1404
+ typeHeadName(node) {
1405
+ if (node.type === "interpolation_expression") return "";
1406
+ if (node.type === "identifier") return node.text.trim();
1407
+ if (node.type === "macro_identifier") return macroNameOf(node.text);
1408
+ if (node.type === "operator") return node.text.trim();
1409
+ if (node.type === "binary_expression") {
1410
+ const { left } = this.binOperands(node);
1411
+ return left ? this.typeHeadName(left) : "";
1412
+ }
1413
+ if (node.type === "field_expression") {
1414
+ const path2 = this.fieldPath(node);
1415
+ if (path2) return path2;
1416
+ }
1417
+ const first = this.pos(node, 0);
1418
+ if (first && first.type === "identifier") return first.text.trim();
1419
+ if (first && first.type === "interpolation_expression") return "";
1420
+ const direct = this.firstNamedChildOfType(node, "identifier");
1421
+ if (direct) return direct.text.trim();
1422
+ for (const c of this.namedChildren(node)) {
1423
+ if (c.type === "curly_expression" || c.type === "parametrized_type_expression" || c.type === "binary_expression" || c.type === "field_expression") {
1424
+ const inner = this.typeHeadName(c);
1425
+ if (inner) return inner;
1426
+ }
1427
+ if (c.type === "where_expression") {
1428
+ const inner = this.typeHeadName(c);
1429
+ if (inner) return inner;
1430
+ }
1431
+ }
1432
+ return "";
1433
+ }
1434
+ /** `signature` is positional: an identifier, a call, an argument list, a typed expr, or a where-expr. */
1435
+ /**
1436
+ * Strip a `where` clause to the callable underneath it. The callable is ALWAYS `pos(0)`.
1437
+ *
1438
+ * `f(x::T) where {T} = body` is `(assignment (where_expression (call_expression …) {T}) …)` and
1439
+ * `function f(x::T) where T … end` is `(function_definition (signature (where_expression
1440
+ * (call_expression …) T)) …)`. The corpus audit is what found this: `Compiler/test/inline.jl:664`
1441
+ * declares `g41299(f::Tf, args::Vararg{Any,N}) where {Tf,N}`, nothing unwrapped the wrapper, so
1442
+ * the declaration was never published and the call to it came out UNRESOLVED against a runtime
1443
+ * that resolves it perfectly. Worse, the TYPE VARIABLE was read as the function's name.
1444
+ *
1445
+ * And note the two forms differ: `where {T}` puts a `curly_expression` after the call, but
1446
+ * BARE `where T` puts the type variable there as a plain sibling `identifier`. That is why
1447
+ * scanning for an identifier is unsound here — for `where T` it finds `T`, the parameter, and
1448
+ * returns it as the function's name, fabricating a function called `T`. Positional access is
1449
+ * not a workaround for this grammar; it is the only correct navigation.
1450
+ */
1451
+ unwrapWhere(node) {
1452
+ const WRAPPERS = /* @__PURE__ */ new Set(["where_expression", "curly_expression", "typed_expression", "parenthesized_expression"]);
1453
+ let cur = node;
1454
+ for (let guard = 0; guard < 8 && WRAPPERS.has(cur.type); guard++) {
1455
+ const first = this.pos(cur, 0);
1456
+ if (!first) break;
1457
+ cur = first;
1458
+ }
1459
+ return cur;
1460
+ }
1461
+ /**
1462
+ * The nearest enclosing `module … end`, by name. Used by the `global` rule: the keyword declares a
1463
+ * MODULE binding at any nesting, so the owner is this module and not the nearest container.
1464
+ */
1465
+ enclosingModuleName(node) {
1466
+ let cur = node.parent ?? null;
1467
+ while (cur) {
1468
+ if (cur.type === "module_definition") {
1469
+ const nm = cur.childForFieldName("name");
1470
+ return nm ? nm.text.trim() : "";
1471
+ }
1472
+ cur = cur.parent ?? null;
1473
+ }
1474
+ return "";
1475
+ }
1476
+ /**
1477
+ * The binding name a PLAIN module-scope assignment declares, or "" when this is not one.
1478
+ *
1479
+ * Julia admits a top-level assignment with no `const` (`x = 1`) and it creates a global binding in
1480
+ * the module, which is what `contains` is for. Three things disqualify a node here, and each is a
1481
+ * different fact:
1482
+ *
1483
+ * - it is not written in a module's own scope (a `let`, a loop, a short-form body, a type body);
1484
+ * - its left side is not a NAME: `A.x = 1` assigns a field of another module, `a, b = 1, 2`
1485
+ * destructures (the grammar's tuple LHS), and a call/`where` head is a method definition;
1486
+ * - the name is a constant Julia implies (`nothing`, `missing`, …) rather than a binding.
1487
+ *
1488
+ * A `typed_expression` left side (`x::Int = 0`) is read through to its first child, the same rule
1489
+ * the `const` case applies: the annotation is a reference, not part of the name.
1490
+ */
1491
+ plainModuleBinding(node) {
1492
+ if (!this.inModuleBindingScope(node)) return "";
1493
+ const lhs = this.pos(node, 0);
1494
+ if (!lhs) return "";
1495
+ const head = lhs.type === "typed_expression" ? this.pos(lhs, 0) : lhs;
1496
+ if (!head) return "";
1497
+ if (head.type === "identifier" || head.type === "operator" || head.type === "prefixed_string_literal") {
1498
+ return head.text.trim();
1499
+ }
1500
+ return "";
1501
+ }
1502
+ /**
1503
+ * Is this node written in a MODULE's own scope — not inside a body, loop, `let` or quote?
1504
+ *
1505
+ * The scope-introducing constructs are Julia's own list, not a heuristic: a binding written inside
1506
+ * any of them is local to it. `begin`, `if` and `try` are transparent here (`if` is documented as
1507
+ * introducing no scope; a top-level `begin` is the module body continuing). A SHORT-FORM FUNCTION
1508
+ * is an `assignment` whose left side is a call, so the ancestor test has to recognise that shape
1509
+ * as a body — otherwise `f(x) = (y = 1; y)` would publish `y` as a member of the module.
1510
+ */
1511
+ inModuleBindingScope(node) {
1512
+ let cur = node.parent ?? null;
1513
+ while (cur) {
1514
+ if (cur.type === "module_definition") return true;
1515
+ if (SCOPE_INTRODUCING_NODES.has(cur.type)) return false;
1516
+ if (cur.type === "assignment") {
1517
+ const head = this.unwrapWhere(this.pos(cur, 0) ?? cur);
1518
+ const inner = head.type === "typed_expression" ? this.pos(head, 0) : head;
1519
+ if (inner && inner.type === "call_expression") return false;
1520
+ }
1521
+ cur = cur.parent ?? null;
1522
+ }
1523
+ return false;
1524
+ }
1525
+ /** The `call_expression` at a definition's head, through the wrappers a head can carry. */
1526
+ definitionCall(head) {
1527
+ let cur = head;
1528
+ for (let i = 0; i < 8 && cur; i++) {
1529
+ if (cur.type === "call_expression") return cur;
1530
+ if (cur.type === "where_expression") cur = this.unwrapWhere(cur);
1531
+ else if (cur.type === "signature" || cur.type === "typed_expression" || cur.type === "parenthesized_expression" || cur.type === "macro_argument_list") cur = this.pos(cur, 0);
1532
+ else return null;
1533
+ }
1534
+ return null;
1535
+ }
1536
+ signatureName(sig) {
1537
+ if (sig.type === "typed_expression") {
1538
+ const callable = this.pos(sig, 0);
1539
+ if (callable) return this.signatureName(callable);
1540
+ }
1541
+ if (sig.type === "macrocall_expression") {
1542
+ const m = this.firstNamedChildOfType(sig, "macro_identifier");
1543
+ return m ? macroNameOf(m.text) : "";
1544
+ }
1545
+ if (sig.type === "call_expression") return this.calleeName(sig) ?? "";
1546
+ if (sig.type === "where_expression") {
1547
+ const callable = this.unwrapWhere(sig);
1548
+ if (callable.type === "call_expression") return this.calleeName(callable) ?? "";
1549
+ }
1550
+ const direct = this.firstNamedChildOfType(sig, "identifier");
1551
+ if (direct) return direct.text.trim();
1552
+ const call = this.firstNamedChildOfType(sig, "call_expression");
1553
+ if (call) return this.calleeName(call) ?? "";
1554
+ for (const c of this.namedChildren(sig)) {
1555
+ if (c.type === "where_expression" || c.type === "typed_expression" || c.type === "parenthesized_expression") {
1556
+ const inner = this.signatureName(c);
1557
+ if (inner) return inner;
1558
+ }
1559
+ }
1560
+ return "";
1561
+ }
1562
+ /**
1563
+ /**
1564
+ * Is this node inside the `type_head` of a type DECLARATION?
1565
+ *
1566
+ * Checked by walking ancestors rather than the immediate parent, because the depth varies with
1567
+ * the shape of the head: `struct Circle <: Shape` puts the `parametrized_type_expression` two
1568
+ * levels down (`type_head > binary_expression > …`) while `struct Circle{T} <: Shape` puts it
1569
+ * three, and a parent-only test silently missed the second. A guard that only sometimes fires
1570
+ * is worse than none, because it leaves a self-reference edge on exactly the generic types that
1571
+ * most need to be right.
1572
+ */
1573
+ withinTypeHead(node) {
1574
+ let cur = node;
1575
+ while (cur) {
1576
+ if (this.typeHeadSites.has(cur.id)) return true;
1577
+ if (cur.type === "function_definition" || cur.type === "macro_definition" || cur.type === "module_definition" || cur.type === "source_file") return false;
1578
+ cur = cur.parent ?? null;
1579
+ }
1580
+ return false;
1581
+ }
1582
+ /** The C symbol a `ccall(...)` call targets, with its library when the two-tuple form gives one. */
1583
+ ccallTarget(call) {
1584
+ const args = this.firstNamedChildOfType(call, "argument_list");
1585
+ const first = args ? this.pos(args, 0) : null;
1586
+ if (!first) return null;
1587
+ if (first.type === "quote_expression") {
1588
+ const sym = this.pos(first, 0);
1589
+ return sym ? { symbol: stripSigil(sym.text, ":"), library: null } : null;
1590
+ }
1591
+ if (first.type === "tuple_expression") {
1592
+ const elem = (q) => {
1593
+ if (!q) return "";
1594
+ if (q.type === "quote_expression") {
1595
+ const inner = this.pos(q, 0);
1596
+ return inner ? stripSigil(inner.text.trim(), ":") : "";
1597
+ }
1598
+ return q.text.trim();
1599
+ };
1600
+ const parts = this.namedChildren(first).map(elem);
1601
+ if (parts.length >= 2 && parts[0] && parts[1]) return { symbol: parts[0], library: parts[1] };
1602
+ if (parts.length === 1 && parts[0]) return { symbol: parts[0], library: null };
1603
+ }
1604
+ return null;
1605
+ }
1606
+ /** Stable key for a call site, so a `@ccall` target can be recognised again when it is emitted. */
1607
+ textKey(node, callee) {
1608
+ return `${node.startIndex}:${callee}`;
1609
+ }
1610
+ /**
1611
+ * Record every call site under an `@ccall` as a call to a FOREIGN symbol.
1612
+ *
1613
+ * `@ccall strlen(s)::Csize_t` puts the C function in an ordinary `call_expression` inside
1614
+ * `macro_argument_list`, so without this the emit site cannot tell it from `f(s)` and asserts a
1615
+ * Julia binding. Collected by node start index rather than by name, because two different C
1616
+ * symbols can share a name and only the call SITE says which one this edge is.
1617
+ */
1618
+ markForeignCallees(macroNode) {
1619
+ const args = this.firstNamedChildOfType(macroNode, "macro_argument_list");
1620
+ if (!args) return;
1621
+ const visit = (n) => {
1622
+ if (n.type === "call_expression") {
1623
+ const callee = this.calleeName(n);
1624
+ if (callee) this.foreignMarked.add(this.textKey(n, callee));
1625
+ }
1626
+ for (const c of this.namedChildren(n)) visit(c);
1627
+ };
1628
+ visit(args);
1629
+ }
1630
+ /**
1631
+ * The dotted path of a `field_expression` chain — this grammar's qualified access.
1632
+ *
1633
+ * MEASURED, and the measurement contradicted the code. There is NO `scoped_identifier` node in
1634
+ * this grammar: scanning 400 corpus files for `scoped_identifier` under a `call_expression`
1635
+ * callee found **0**, while `field_expression` accounted for every qualified callee. `Core
1636
+ * .Intrinsics.bitcast` is `field_expression(field_expression(Core, Intrinsics), bitcast)` —
1637
+ * left-nested, so the path is built by recursion, not by listing segments.
1638
+ *
1639
+ * The `scoped_identifier` branch this replaced was dead code written from a different grammar,
1640
+ * and because it never matched, `calleeName` returned null and the analyzer emitted **nothing at
1641
+ * all** for a file of qualified calls: zero symbols, zero relationships, no error. A silent empty
1642
+ * graph is the worst failure shape there is — it reads as "nothing to see here" rather than
1643
+ * "this analyzer is blind".
1644
+ *
1645
+ * The dotted path is kept WHOLE on purpose. Collapsing it to the last segment was measured to
1646
+ * invent merges: over 400 files, 1,352 bare segments appear behind a qualified path and **72 of
1647
+ * them are reachable by more than one distinct path**. `parse` alone is `Meta.parse`,
1648
+ * `Base.parse`, `TOML.parse` and `Dates.parse` — four unrelated functions. `bitcast` is
1649
+ * `Base.bitcast`, `Core.bitcast` and `Intrinsics.bitcast`. `isreadable` is
1650
+ * `Filesystem.isreadable`, `Base.isreadable` and `Sys.isreadable`. One node per bare name would
1651
+ * silently fuse them, and a merged node makes every edge into and out of it a statement about
1652
+ * the wrong function. In Julia the qualified path IS the identity of the binding.
1653
+ *
1654
+ * `Base.:(==)` puts the operator in a `quote_expression`, so the last segment can be either.
1655
+ */
1656
+ /**
1657
+ * THE NAME A NODE DECLARES, or "" when the node names nothing.
1658
+ *
1659
+ * One resolver for every position that must publish a NAME — a binding's left-hand side, a
1660
+ * `where` bound's parameter — because the alternative is what this lane shipped: a `.text.trim()`
1661
+ * at each site, each of which is correct for the shapes its author had in mind and a source SPAN
1662
+ * for every other shape. The defect is not that any one site was careless; it is that "take the
1663
+ * node's text" is a rule that has no answer to "what if the node is not a name".
1664
+ *
1665
+ * The walk descends the LEFT-most positional child through every wrapper this grammar can place
1666
+ * around a declared name, and STOPS at a node it can spell:
1667
+ *
1668
+ * typed_expression `x::Int` -> `x` (child 1 is the type, a reference)
1669
+ * binary_expression `T<:Real` -> `T` (left operand; the right is a bound)
1670
+ * parametrized_type `Foo{T}` -> `Foo` (the base name, NOT the parameter list)
1671
+ * field_expression `A.B` -> `A.B` (the whole path is the identity)
1672
+ * parenthesized/wrapped forms -> their first child
1673
+ *
1674
+ * and returns "" for anything else — an `ERROR` node from a broken parse, a `tuple_expression`
1675
+ * (which declares SEVERAL names and is therefore not one name), a literal, a comment. A refusal
1676
+ * costs a symbol the grammar could not spell; publishing the node's text instead costs a key
1677
+ * that no lookup can ever match, which is strictly worse.
1678
+ *
1679
+ * MEASURED on the whole corpus by `validation/m14/fixtures/julia/key-shape-census.ts`.
1680
+ */
1681
+ nameOf(node, depth = 0) {
1682
+ if (depth > 8) return "";
1683
+ switch (node.type) {
1684
+ case "identifier":
1685
+ case "operator":
1686
+ return node.text.trim();
1687
+ case "macro_identifier":
1688
+ return stripSigil(node.text, "@");
1689
+ case "scoped_identifier":
1690
+ case "field_expression":
1691
+ case "prefixed_string_literal":
1692
+ return node.type === "prefixed_string_literal" ? node.text.trim() : this.fieldPath(node);
1693
+ case "quote_expression":
1694
+ return this.quotedName(node);
1695
+ case "typed_expression":
1696
+ case "unary_typed_expression":
1697
+ case "binary_expression":
1698
+ case "parenthesized_expression":
1699
+ case "where_expression":
1700
+ case "parametrized_type_expression":
1701
+ case "curly_expression": {
1702
+ const first = this.pos(node, 0);
1703
+ return first ? this.nameOf(first, depth + 1) : "";
1704
+ }
1705
+ default:
1706
+ return "";
1707
+ }
1708
+ }
1709
+ /**
1710
+ * The name a `quote_expression` spells — the OPERATOR for a quoted operator (`:(==)` → `==`), the
1711
+ * quoted symbol itself for a Symbol value (`:foo`), or "" when the node's text is a SPAN rather
1712
+ * than a spelling.
1713
+ *
1714
+ * The span rule is the whole point of the guard below. A `quote_expression` recovered over a
1715
+ * broken region spans the reparsed remainder of the file —
1716
+ * `julia/base/broadcast.jl`'s `:.>:` followed by the next line's `Expr(...)` came out as
1717
+ * `":.>:\n Expr"` and was published as a FUNCTION's name, with seven relationships
1718
+ * sourced from it. A node whose text carries layout is not a name at all, so this refuses: the
1719
+ * inner symbol is not read either, because when the wrapper spans a region the parser did not
1720
+ * understand, the "inner operator" is whatever token it recovered on and spelling it would be a
1721
+ * fabricated name rather than a missing one.
1722
+ */
1723
+ quotedName(node) {
1724
+ const t = node.text.trim();
1725
+ if (hasLayout(t)) return "";
1726
+ const inner = this.pos(node, 0);
1727
+ const plain = (n) => n && (n.type === "operator" || n.type === "identifier") && !hasLayout(n.text) ? n.text.trim() : "";
1728
+ if (inner) {
1729
+ const direct = plain(inner);
1730
+ if (direct) return direct;
1731
+ if (inner.type === "parenthesized_expression") {
1732
+ const op = plain(this.pos(inner, 0));
1733
+ if (op) return op;
1734
+ }
1735
+ }
1736
+ return t;
1737
+ }
1738
+ fieldPath(node) {
1739
+ if (node.type === "identifier") return node.text.trim();
1740
+ if (node.type === "parametrized_type_expression") {
1741
+ const base = this.pos(node, 0);
1742
+ return base ? this.fieldPath(base) : "";
1743
+ }
1744
+ if (node.type === "quote_expression") return this.quotedName(node);
1745
+ if (node.type === "macro_identifier") return macroNameOf(node.text);
1746
+ if (node.type === "prefixed_string_literal") return node.text.trim();
1747
+ if (node.type === "operator") return node.text.trim();
1748
+ if (node.type === "typed_expression" || node.type === "unary_typed_expression") {
1749
+ const inner = this.pos(node, 0);
1750
+ return inner ? this.fieldPath(inner) : "";
1751
+ }
1752
+ if (node.type === "where_expression" || node.type === "parenthesized_expression") {
1753
+ const inner = this.pos(node, 0);
1754
+ return inner ? this.fieldPath(inner) : "";
1755
+ }
1756
+ if (node.type === "field_expression") {
1757
+ const kids = this.namedChildren(node);
1758
+ const receiver = kids[0];
1759
+ const member = kids[kids.length - 1];
1760
+ if (!receiver || !member) return "";
1761
+ const head = this.fieldPath(receiver);
1762
+ const tail = this.fieldPath(member);
1763
+ return head && tail ? `${head}.${tail}` : head || tail || "";
1764
+ }
1765
+ return "";
1766
+ }
1767
+ /** The callee of a call: a bare name, or the WHOLE dotted path when it is qualified. */
1768
+ calleeName(call) {
1769
+ if (call.type === "where_expression") {
1770
+ const callable = this.unwrapWhere(call);
1771
+ if (callable !== call) return this.calleeName(callable);
1772
+ }
1773
+ const calleeNode = this.pos(call, 0);
1774
+ if (calleeNode) {
1775
+ if (calleeNode.type === "parenthesized_expression") {
1776
+ const inner = this.pos(calleeNode, 0);
1777
+ let base = inner;
1778
+ for (let i = 0; i < 4 && base && (base.type === "where_expression" || base.type === "parenthesized_expression"); i++) {
1779
+ base = this.pos(base, 0);
1780
+ }
1781
+ if (base && (base.type === "typed_expression" || base.type === "unary_typed_expression" || base.type === "interpolation_expression")) return null;
1782
+ if (base && this.subtreeHasInterpolation(base)) return null;
1783
+ }
1784
+ const path2 = this.fieldPath(calleeNode);
1785
+ if (path2) return path2;
1786
+ }
1787
+ for (const c of this.namedChildren(call)) {
1788
+ if (c.type === "where_expression" || c.type === "parenthesized_expression") {
1789
+ const inner = this.calleeName(c);
1790
+ if (inner) return inner;
1791
+ }
1792
+ }
1793
+ return null;
1794
+ }
1795
+ /**
1796
+ * Does this subtree splice a runtime value (`$x`) into a NAME?
1797
+ *
1798
+ * A splice is not a spelling: `(Base.$fJ)(… ) = …` declares whichever binding the loop variable
1799
+ * holds, and the source never names it. Walking the subtree rather than testing the top node is
1800
+ * the point — the splice sits under a `field_expression` member, one level below the paren that
1801
+ * `calleeName` inspects.
1802
+ */
1803
+ subtreeHasInterpolation(node) {
1804
+ if (node.type === "interpolation_expression") return true;
1805
+ for (const c of this.namedChildren(node)) if (this.subtreeHasInterpolation(c)) return true;
1806
+ return false;
1807
+ }
1808
+ /**
1809
+ * The supertype a type head STATES — the head's own `<:`, and never a comparison inside it.
1810
+ *
1811
+ * THE HEAD IS A WALL, NOT A ROAD, and this function used to descend it looking for `<:` at any
1812
+ * depth. Two facts made every one of those edges wrong in a different way:
1813
+ *
1814
+ * - a `<:` inside a supertype EXPRESSION is a COMPARISON, not a subtype of the declared type.
1815
+ * `struct S <: (S <: Real ? Any : Real)` published `S extends Real` — the CONDITION's right
1816
+ * operand read as the supertype, `1 of 1` corpus sites in `julia/test/typegroup.jl:665`.
1817
+ * - a `<:` chain inside a `where` clause bounds a PARAMETER (`(AbstractArray{T, N} where T <:
1818
+ * Integer where N)`), which the `where_expression` case below publishes with `T` as the
1819
+ * source; descending here also published `Foo extends N` and `Foo extends Integer`.
1820
+ *
1821
+ * Both were found by the direct oracle (`validation/m14/fixtures/julia/direct-oracle-check.ts`),
1822
+ * whose own rule has been "the head's top-level binary, and the operand must NAME a type" since
1823
+ * it was written — a second derivation of the same fact that disagreed, which is the only reason
1824
+ * a reader could see it.
1825
+ *
1826
+ * A PARAMETER's bound is still published, by the `parametrized_type_expression`/
1827
+ * `where_expression` cases, with the parameter as the source. That is a fact about `T`, not about
1828
+ * the type, and it is a different edge.
1829
+ */
1830
+ supertypesOf(node) {
1831
+ const head = this.firstNamedChildOfType(node, "type_head");
1832
+ if (!head) return [];
1833
+ const bin = this.firstNamedChildOfType(head, "binary_expression");
1834
+ if (!bin) return [];
1835
+ const { op, right } = this.binOperands(bin);
1836
+ if (op !== "<:" && op !== ">:" || !right) return [];
1837
+ const name = this.supertypeName(right);
1838
+ return name ? [name] : [];
1839
+ }
1840
+ /**
1841
+ * The NAME a supertype operand spells, or "" when the operand is a computed VALUE.
1842
+ *
1843
+ * MEASURED over the whole 1,985-file population (validation/m14/measurements/julia-supertype-
1844
+ * shapes.json): 1,313 of the 1,324 `<:` sites in a type head are a NAME — identifier 891,
1845
+ * `parametrized_type_expression` 346 (`AbstractVector{T}`), `field_expression` 76
1846
+ * (`Base.AbstractSet`) — and the remaining 11 are the shapes below, every one of which this
1847
+ * function REFUSES rather than guesses:
1848
+ *
1849
+ * - `$typ <: $sup` (3, `julia/stdlib/LibGit2/src/types.jl:1084`): a runtime VALUE. The old
1850
+ * fallback published the interpolation's own identifier, `typ extends sup` — 3 edges saying a
1851
+ * type is a subtype of a loop variable.
1852
+ * - a statement BLOCK `(global g; g(::S) = 1; Any)` (2, `julia/test/typegroup.jl:652`): the
1853
+ * value is its last statement, `Any`, which happens to be a type — but the operand as written
1854
+ * states no name, and the block also DEFINES a method, which the walker published as
1855
+ * `contains S -> g`.
1856
+ * - `typeof(Core.apply_type)` (1), `0x01` (1), `@NamedTuple{x::Int}` (1), a TERNARY (1), and a
1857
+ * `where` chain (2) — a call, a literal, a macro call, a conditional and a UnionAll. The old
1858
+ * fallback read the call's CALLEE as the supertype (`Foo extends typeof`,
1859
+ * `julia/test/errorshow.jl:1540`) and the first bare identifier it could find inside the
1860
+ * others.
1861
+ *
1862
+ * A parenthesised NAME is still a name (`struct S <: (Any)`), and a `where`/`parametrized` wrapper
1863
+ * is unwrapped to the type it is built from (`Tuple{T} where T` names `Tuple`, which is what the
1864
+ * oracle derives and what a reader can follow). What is inside a CURLY parameter list is never
1865
+ * read as the supertype: those are the type's ARGUMENTS.
1866
+ */
1867
+ supertypeName(node) {
1868
+ switch (node.type) {
1869
+ case "identifier":
1870
+ case "operator":
1871
+ return node.text.trim();
1872
+ case "field_expression":
1873
+ return this.fieldPath(node) || "";
1874
+ case "parametrized_type_expression":
1875
+ case "curly_expression":
1876
+ case "parenthesized_expression":
1877
+ case "where_expression": {
1878
+ const inner = this.pos(node, 0);
1879
+ return inner ? this.supertypeName(inner) : "";
1880
+ }
1881
+ default:
1882
+ return "";
1883
+ }
1884
+ }
1885
+ /** A struct body's `x::T` entries. The grammar has no dedicated field node for them. */
1886
+ structFields(node) {
1887
+ const out = [];
1888
+ const fieldName = (c) => {
1889
+ if (c.type === "typed_expression") return this.pos(c, 0)?.text.trim() ?? "";
1890
+ if (c.type === "where_expression") {
1891
+ const inner = this.pos(c, 0);
1892
+ return inner ? fieldName(inner) : "";
1893
+ }
1894
+ if (c.type === "identifier") return c.text.trim();
1895
+ if (c.type === "macrocall_expression") {
1896
+ const args = this.namedChildren(c).find((n) => n.type === "macro_argument_list");
1897
+ if (!args) return "";
1898
+ for (const a of this.namedChildren(args)) {
1899
+ const nm = fieldName(a);
1900
+ if (nm) return nm;
1901
+ }
1902
+ return "";
1903
+ }
1904
+ if (c.type === "binary_expression") {
1905
+ const { left } = this.binOperands(c);
1906
+ if (left && left.type === "typed_expression") return this.pos(left, 0)?.text.trim() ?? "";
1907
+ return "";
1908
+ }
1909
+ if (c.type === "const_statement") {
1910
+ const inner = this.namedChildren(c).find((n) => n.type === "assignment" || n.type === "typed_expression" || n.type === "identifier");
1911
+ return inner ? fieldName(inner) : "";
1912
+ }
1913
+ if (c.type === "assignment") {
1914
+ const lhs = this.pos(c, 0);
1915
+ if (!lhs) return "";
1916
+ if (lhs.type === "typed_expression") return this.pos(lhs, 0)?.text.trim() ?? "";
1917
+ if (lhs.type === "identifier") return lhs.text.trim();
1918
+ }
1919
+ return "";
1920
+ };
1921
+ for (const c of this.namedChildren(node)) {
1922
+ const name = fieldName(c);
1923
+ if (name) out.push({ name, node: c });
1924
+ }
1925
+ return out;
1926
+ }
1927
+ /**
1928
+ * `import A: b, c` / `using A, B` — the module and the names brought in.
1929
+ *
1930
+ * ## WHY THIS IS NOW STATELESS, and what it cost to leave it stateful
1931
+ *
1932
+ * The previous version walked the statement's named children IN ORDER carrying a `pending`
1933
+ * variable, and one of its branches read: *"a bare identifier when something is already pending,
1934
+ * with no items yet, and a different name — that is an ITEM of the pending target."*
1935
+ *
1936
+ * That branch cannot tell a second MODULE from an item of the first. `using A, B` names two
1937
+ * modules, and the corpus's second most common multi-path import shape by volume is exactly that:
1938
+ * 32 two-path and 12 three-path statements in a 400-file draw (`validation/m14/fixtures/julia/
1939
+ * import-export-shapes.ts`, which enumerates every observed shape with counts). The consequence was
1940
+ * silent and total for the tail of a long list — `DataFrames.jl/src/DataFrames.jl:3-19` is one
1941
+ * `using` statement naming **17 modules** and this function published ONE of them, calling the
1942
+ * other sixteen "items" of `Base.Sort`.
1943
+ *
1944
+ * Measured over the whole admitted corpus by the oracle this change was written against
1945
+ * (`script/m14/oracles/julia-imports.ts`, an independent derivation sharing no rule with this one):
1946
+ * **167 of 440 `imports` edges were missing on a 200-file draw — 38% of the type.** Every one of
1947
+ * the `imports` RELATIONSHIPS, every `ImportInfo[]` row and every `references` row carrying
1948
+ * `isImport: true` lost the same dependencies, because all three are written from this one walk.
1949
+ *
1950
+ * The previous instrument could not have found it. `census.ts`'s construct-versus-edge
1951
+ * reconciliation counts FILES containing the construct against files whose edge fired, and a file
1952
+ * whose FIRST module produced an edge passed — the per-file granularity cannot see a missing second
1953
+ * target. A differential against an independent derivation can, and did.
1954
+ *
1955
+ * ## THE RULE, WHICH IS ABOUT THE CHILD AND NOT ITS SIBLINGS
1956
+ *
1957
+ * Every named child is classified on its own, and the question asked of each is "is this child a
1958
+ * module path, or is it a name inside a path's list?" — answerable from the child's own position in
1959
+ * ITS parent, with no knowledge of what came before. Two siblings of the same type therefore cannot
1960
+ * be confused for one another, which is the whole defect class.
1961
+ *
1962
+ * Shapes, all measured over the corpus rather than read out of `grammar.js`:
1963
+ *
1964
+ * `using A` / `import A.B.C` bare path → `identifier` | `scoped_identifier` | `import_path`
1965
+ * `using A, B` two bare paths → two of the above as SIBLINGS — the fixed case
1966
+ * `using A: x, y` path + names → `selected_import`, module is its child 0
1967
+ * `import A as B` renamed import → `import_alias`, module is its child 0
1968
+ *
1969
+ * The last one was dropped entirely before: the `import_alias` branch required `pending`, and a
1970
+ * statement whose ONLY named child is an `import_alias` never had one, so `import ChainRulesCore as
1971
+ * CRC` (`StaticArrays.jl/ext/StaticArraysChainRulesCoreExt.jl`) published nothing at all.
1972
+ *
1973
+ * `import_path` and `scoped_identifier` are taken WHOLE, leading dots and all: the dots are what
1974
+ * distinguishes `using .Foo` from `using ..Foo`, and a dotted path's last segment is not the module
1975
+ * a statement imports (`using Base.GMP.MPZ` imports `Base.GMP.MPZ`, not `MPZ`).
1976
+ *
1977
+ * ## What this function still does not do
1978
+ *
1979
+ * 45 of the corpus's 3,570 import/export statements carry an ERROR descendant, and almost all of
1980
+ * them are legitimate julia the grammar cannot fully parse — `import .Base.Math.@horner`
1981
+ * (`julia/base/special/log.jl:12`, in julia's own stdlib), `import ..test_mod.@old_hyg` (32
1982
+ * statements in `julia/JuliaLowering/test/scopes.jl`), `import B.@mac as @mac2`. The module path
1983
+ * around the unparsed part still parses, so taking the child's whole text records the dependency
1984
+ * that exists. **Refusing these would trade 45 real edges for 2 fabricated ones, which is why the
1985
+ * ERROR rule is applied to `export`/`public` only and for a different reason** — see the
1986
+ * `export_statement` case, where the statement node ITSELF is wrong rather than one part of it.
1987
+ */
1988
+ importTargets(node) {
1989
+ const out = [];
1990
+ for (const c of this.namedChildren(node)) {
1991
+ if (c.type === "selected_import" || c.type === "import_alias") {
1992
+ const inner = this.namedChildren(c);
1993
+ if (!inner.length) continue;
1994
+ const modName = inner[0].text.trim();
1995
+ if (!modName) continue;
1996
+ const items = c.type === "selected_import" ? inner.slice(1).map((x) => x.text.trim()).filter((x) => x.length > 0) : [];
1997
+ out.push({ name: modName, items, renamed: c.type === "import_alias" || void 0 });
1998
+ continue;
1999
+ }
2000
+ if (c.type === "import_path" || c.type === "identifier" || c.type === "scoped_identifier") {
2001
+ const name = c.text.trim();
2002
+ if (name) out.push({ name, items: [] });
2003
+ }
2004
+ }
2005
+ return out;
2006
+ }
2007
+ exportableName(node) {
2008
+ if (node.type === "identifier") return node.text.trim();
2009
+ if (node.type === "operator") return node.text.trim();
2010
+ if (node.type === "scoped_identifier") {
2011
+ const segs = this.namedChildren(node).filter((x) => x.type === "identifier");
2012
+ return segs.length ? segs[segs.length - 1].text.trim() : node.text.trim();
2013
+ }
2014
+ if (node.type === "macro_identifier") return macroNameOf(node.text);
2015
+ if (node.type === "call_expression") return this.calleeName(node) ?? "";
2016
+ if (node.type === "tuple_expression" || node.type === "parenthesized_expression") {
2017
+ for (const c of this.namedChildren(node)) {
2018
+ const n = this.exportableName(c);
2019
+ if (n) return n;
2020
+ }
2021
+ }
2022
+ return "";
2023
+ }
2024
+ /**
2025
+ * Evidence tier, the same rules as the swift/dart/csharp/objc family.
2026
+ */
2027
+ classifyEvidenceTier(rels) {
2028
+ for (const r of rels) {
2029
+ const md = r.metadata ?? {};
2030
+ const t = String(r.type ?? "");
2031
+ if (t === "contains") {
2032
+ md.evidenceClass = "structural";
2033
+ continue;
2034
+ }
2035
+ if (t === "imports" || t === "exports" || t === "references") {
2036
+ md.evidenceClass = "import_based";
2037
+ continue;
2038
+ }
2039
+ if (t === "overrides") {
2040
+ md.evidenceClass = "structural";
2041
+ continue;
2042
+ }
2043
+ if (t === "calls" || t === "uses" || t === "instantiates") {
2044
+ md.evidenceClass = (r.confidence ?? 0) >= 0.85 ? "usage_contextual" : "inferred";
2045
+ continue;
2046
+ }
2047
+ if (t === "extends" || t === "implements") {
2048
+ md.evidenceClass = (r.confidence ?? 0) >= 0.9 ? "structural" : "inferred";
2049
+ continue;
2050
+ }
2051
+ }
2052
+ }
2053
+ /**
2054
+ * Cyclomatic complexity, counted over the tree.
2055
+ *
2056
+ * The walk is GUARDED at 200,000 node visits, and that guard is inherited from the other lanes
2057
+ * rather than invented — the objective-c review measured its own guard at 3.33% of the cap on
2058
+ * the worst corpus file, so the bound is real but never approached there. A truncated value
2059
+ * would be indistinguishable from a true one in the artifact, which is why the cap exists at all
2060
+ * and why its margin is measured rather than assumed.
2061
+ */
2062
+ complexity(root) {
2063
+ let c = 1;
2064
+ const decisions = /* @__PURE__ */ new Set(["if_statement", "elseif_clause", "for_statement", "while_statement"]);
2065
+ const stack = [root];
2066
+ let guard = 0;
2067
+ while (stack.length > 0 && guard++ < 2e5) {
2068
+ const n = stack.pop();
2069
+ if (decisions.has(n.type)) c++;
2070
+ else if (n.type === "binary_expression") {
2071
+ const opNode = this.pos(n, 1);
2072
+ const op = opNode && opNode.type === "operator" ? opNode.text.trim() : "";
2073
+ if (op === "&&" || op === "||") c++;
2074
+ }
2075
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
2076
+ const ch = n.namedChild(i);
2077
+ if (ch) stack.push(ch);
2078
+ }
2079
+ }
2080
+ return c;
2081
+ }
2082
+ };
2083
+ export {
2084
+ RealJuliaAnalyzer
2085
+ };