@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,4677 @@
1
+ import {
2
+ findDeclaringParent,
3
+ localEdgeLabel,
4
+ localEdgeName
5
+ } from "./chunk-OWISBAOF.js";
6
+ import {
7
+ ASTEngine
8
+ } from "./chunk-3ZTOHHSW.js";
9
+ import "./chunk-BQ3OVQ3P.js";
10
+ import "./chunk-SK6YVHFZ.js";
11
+ import "./chunk-2BSKD7O3.js";
12
+ import "./chunk-KAET47ZL.js";
13
+ import "./chunk-KCDECRJY.js";
14
+ import "./chunk-MLKGABMK.js";
15
+
16
+ // src/ast/real-java-analyzer.ts
17
+ function stringIncludes(str, val) {
18
+ return str.indexOf(val) !== -1;
19
+ }
20
+ function isPlainJavaIdentifier(name) {
21
+ if (name.length === 0) return false;
22
+ const isStart = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 36 || c === 95;
23
+ const isPart = (c) => isStart(c) || c >= 48 && c <= 57;
24
+ if (!isStart(name.charCodeAt(0))) return false;
25
+ for (let i = 1; i < name.length; i++) {
26
+ if (!isPart(name.charCodeAt(i))) return false;
27
+ }
28
+ return true;
29
+ }
30
+ function javaSigHasTypeVar(sig) {
31
+ const n = sig.length;
32
+ let i = 0;
33
+ while (i < n) {
34
+ const c = sig[i];
35
+ if (c === "(" || c === "," || c === ":" || c === "|" || c === "[" || c === "<" || c === "&" || i === 0) {
36
+ let j = i;
37
+ while (j < n && (sig[j] === "(" || sig[j] === "[" || sig[j] === " " || sig[j] === ":" || sig[j] === ",")) j++;
38
+ if (j < n && sig[j] >= "A" && sig[j] <= "Z") {
39
+ let end = j + 1;
40
+ while (end < n && (sig[end] >= "A" && sig[end] <= "Z" || sig[end] >= "a" && sig[end] <= "z" || sig[end] >= "0" && sig[end] <= "9" || sig[end] === "_")) end++;
41
+ if (end === j + 1) return true;
42
+ i = end;
43
+ continue;
44
+ }
45
+ i = j > i ? j : i + 1;
46
+ continue;
47
+ }
48
+ i++;
49
+ }
50
+ return false;
51
+ }
52
+ function splitByChar(text, delim) {
53
+ const out = [];
54
+ let start = 0;
55
+ for (let i = 0; i < text.length; i++) {
56
+ if (text[i] === delim) {
57
+ out.push(text.slice(start, i));
58
+ start = i + 1;
59
+ }
60
+ }
61
+ out.push(text.slice(start));
62
+ return out;
63
+ }
64
+ function tokenizeWhitespace(text) {
65
+ const out = [];
66
+ let cur = "";
67
+ const flush = () => {
68
+ if (cur) {
69
+ out.push(cur);
70
+ cur = "";
71
+ }
72
+ };
73
+ for (let i = 0; i < text.length; i++) {
74
+ const ch = text[i];
75
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v") {
76
+ flush();
77
+ } else {
78
+ cur += ch;
79
+ }
80
+ }
81
+ flush();
82
+ return out;
83
+ }
84
+ function stripJavaAnnotations(text) {
85
+ let out = "";
86
+ let i = 0;
87
+ while (i < text.length) {
88
+ const ch = text[i];
89
+ if (ch === "@") {
90
+ i++;
91
+ while (i < text.length) {
92
+ const c = text[i];
93
+ const isWord = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "." || c === "$";
94
+ if (!isWord) break;
95
+ i++;
96
+ }
97
+ while (i < text.length && (text[i] === " " || text[i] === " " || text[i] === "\n" || text[i] === "\r")) i++;
98
+ if (text[i] === "(") {
99
+ let depth = 0;
100
+ while (i < text.length) {
101
+ if (text[i] === "(") depth++;
102
+ else if (text[i] === ")") {
103
+ depth--;
104
+ if (depth === 0) {
105
+ i++;
106
+ break;
107
+ }
108
+ }
109
+ i++;
110
+ }
111
+ }
112
+ while (i < text.length && (text[i] === " " || text[i] === " " || text[i] === "\n" || text[i] === "\r")) i++;
113
+ continue;
114
+ }
115
+ out += ch;
116
+ i++;
117
+ }
118
+ return out;
119
+ }
120
+ var JAVA_FRAMEWORK_PRIORITY = [
121
+ "spring_data",
122
+ "spring_mvc",
123
+ "spring_core",
124
+ "spring",
125
+ "jpa",
126
+ "hibernate",
127
+ "mybatis",
128
+ "quarkus",
129
+ "micronaut",
130
+ "jackson",
131
+ "slf4j"
132
+ ];
133
+ var JAVA_RESERVED = /* @__PURE__ */ new Set([
134
+ "abstract",
135
+ "assert",
136
+ "boolean",
137
+ "break",
138
+ "byte",
139
+ "case",
140
+ "catch",
141
+ "char",
142
+ "class",
143
+ "const",
144
+ "continue",
145
+ "default",
146
+ "do",
147
+ "double",
148
+ "else",
149
+ "enum",
150
+ "extends",
151
+ "final",
152
+ "finally",
153
+ "float",
154
+ "for",
155
+ "goto",
156
+ "if",
157
+ "implements",
158
+ "import",
159
+ "instanceof",
160
+ "int",
161
+ "interface",
162
+ "long",
163
+ "native",
164
+ "new",
165
+ "package",
166
+ "private",
167
+ "protected",
168
+ "public",
169
+ "return",
170
+ "short",
171
+ "static",
172
+ "strictfp",
173
+ "super",
174
+ "switch",
175
+ "synchronized",
176
+ "this",
177
+ "throw",
178
+ "throws",
179
+ "transient",
180
+ "try",
181
+ "void",
182
+ "volatile",
183
+ "while",
184
+ "true",
185
+ "false",
186
+ "null",
187
+ "_",
188
+ "var",
189
+ "yield",
190
+ "record",
191
+ "sealed",
192
+ "permits",
193
+ "non-sealed"
194
+ ]);
195
+ var JAVA_LANG_TYPES = /* @__PURE__ */ new Set([
196
+ "AbstractMethodError",
197
+ "AbstractStringBuilder",
198
+ "Appendable",
199
+ "ApplicationShutdownHooks",
200
+ "ArithmeticException",
201
+ "ArrayIndexOutOfBoundsException",
202
+ "ArrayStoreException",
203
+ "AssertionError",
204
+ "AssertionStatusDirectives",
205
+ "AutoCloseable",
206
+ "Boolean",
207
+ "BootstrapMethodError",
208
+ "Byte",
209
+ "Character",
210
+ "CharacterData",
211
+ "CharacterData00",
212
+ "CharacterData01",
213
+ "CharacterData02",
214
+ "CharacterData03",
215
+ "CharacterData0E",
216
+ "CharacterDataLatin1",
217
+ "CharacterDataPrivateUse",
218
+ "CharacterDataUndefined",
219
+ "CharacterName",
220
+ "CharSequence",
221
+ "Class",
222
+ "ClassCastException",
223
+ "ClassCircularityError",
224
+ "ClassFormatError",
225
+ "ClassLoader",
226
+ "ClassNotFoundException",
227
+ "ClassValue",
228
+ "Cloneable",
229
+ "CloneNotSupportedException",
230
+ "Comparable",
231
+ "Compiler",
232
+ "CompoundEnumeration",
233
+ "ConditionalSpecialCasing",
234
+ "Deprecated",
235
+ "Double",
236
+ "Enum",
237
+ "EnumConstantNotPresentException",
238
+ "Error",
239
+ "Exception",
240
+ "ExceptionInInitializerError",
241
+ "FdLibm",
242
+ "Float",
243
+ "FunctionalInterface",
244
+ "IllegalAccessError",
245
+ "IllegalAccessException",
246
+ "IllegalArgumentException",
247
+ "IllegalCallerException",
248
+ "IllegalMonitorStateException",
249
+ "IllegalStateException",
250
+ "IllegalThreadStateException",
251
+ "IncompatibleClassChangeError",
252
+ "IndexOutOfBoundsException",
253
+ "InheritableThreadLocal",
254
+ "InstantiationError",
255
+ "InstantiationException",
256
+ "Integer",
257
+ "InternalError",
258
+ "InterruptedException",
259
+ "Iterable",
260
+ "LayerInstantiationException",
261
+ "LinkageError",
262
+ "LiveStackFrame",
263
+ "LiveStackFrameInfo",
264
+ "Long",
265
+ "Math",
266
+ "Module",
267
+ "ModuleLayer",
268
+ "NamedPackage",
269
+ "NegativeArraySizeException",
270
+ "NoClassDefFoundError",
271
+ "NoSuchFieldError",
272
+ "NoSuchFieldException",
273
+ "NoSuchMethodError",
274
+ "NoSuchMethodException",
275
+ "NullPointerException",
276
+ "Number",
277
+ "NumberFormatException",
278
+ "Object",
279
+ "OutOfMemoryError",
280
+ "Override",
281
+ "Package",
282
+ "Process",
283
+ "ProcessBuilder",
284
+ "ProcessEnvironment",
285
+ "ProcessHandle",
286
+ "ProcessHandleImpl",
287
+ "ProcessImpl",
288
+ "PublicMethods",
289
+ "Readable",
290
+ "Record",
291
+ "ReflectiveOperationException",
292
+ "Runnable",
293
+ "Runtime",
294
+ "RuntimeException",
295
+ "RuntimePermission",
296
+ "SafeVarargs",
297
+ "SecurityException",
298
+ "SecurityManager",
299
+ "Short",
300
+ "Shutdown",
301
+ "StackFrameInfo",
302
+ "StackOverflowError",
303
+ "StackStreamFactory",
304
+ "StackTraceElement",
305
+ "StackWalker",
306
+ "StrictMath",
307
+ "String",
308
+ "StringBuffer",
309
+ "StringBuilder",
310
+ "StringCoding",
311
+ "StringConcatHelper",
312
+ "StringIndexOutOfBoundsException",
313
+ "StringLatin1",
314
+ "StringUTF16",
315
+ "SuppressWarnings",
316
+ "System",
317
+ "Terminator",
318
+ "Thread",
319
+ "ThreadDeath",
320
+ "ThreadGroup",
321
+ "ThreadLocal",
322
+ "Throwable",
323
+ "TypeNotPresentException",
324
+ "UnknownError",
325
+ "UnsatisfiedLinkError",
326
+ "UnsupportedClassVersionError",
327
+ "UnsupportedOperationException",
328
+ "VerifyError",
329
+ "VersionProps",
330
+ "VirtualMachineError",
331
+ "Void",
332
+ "WeakPairMap"
333
+ ]);
334
+ function pickJavaFramework(frameworks) {
335
+ for (let i = 0; i < JAVA_FRAMEWORK_PRIORITY.length; i++) {
336
+ if (inArray(frameworks, JAVA_FRAMEWORK_PRIORITY[i])) return JAVA_FRAMEWORK_PRIORITY[i];
337
+ }
338
+ const sorted = [...frameworks].sort();
339
+ return sorted[0];
340
+ }
341
+ function inArray(arr, val) {
342
+ for (let i = 0; i < arr.length; i++) {
343
+ if (arr[i] === val) return true;
344
+ }
345
+ return false;
346
+ }
347
+ var JAVA_MODULE_DIRECTIVE_FIELDS = {
348
+ // requires [transitive|static] MODULE ;
349
+ requires_module_directive: [{ field: "module", role: "requires_module" }],
350
+ // exports PACKAGE [to field('modules', M) (, field('modules', M))*] ;
351
+ exports_module_directive: [
352
+ { field: "package", role: "module_exports_package" },
353
+ { field: "modules", role: "module_exports_package_to", unfieldedAfter: "package" }
354
+ ],
355
+ // opens PACKAGE [to field('modules', M) (, field('modules', M))*] ;
356
+ opens_module_directive: [
357
+ { field: "package", role: "module_opens_package" },
358
+ { field: "modules", role: "module_opens_package_to", unfieldedAfter: "package" }
359
+ ],
360
+ // uses TYPE ;
361
+ uses_module_directive: [{ field: "type", role: "module_uses_provided_type" }],
362
+ // provides field('provided', S) with I (, field('provider', I))* ; <- first I unfielded
363
+ provides_module_directive: [
364
+ { field: "provided", role: "module_provides_service" },
365
+ { field: "provider", role: "module_provides_service_with", unfieldedAfter: "provided" }
366
+ ]
367
+ };
368
+ function isJavaNameNode(n) {
369
+ return !!n && (n.type === "identifier" || n.type === "scoped_identifier");
370
+ }
371
+ function javaTypeBaseName(node) {
372
+ if (!node) return "";
373
+ const t = node.type;
374
+ if (t === "type_identifier" || t === "identifier") return node.text;
375
+ if (t === "integral_type" || t === "floating_point_type" || t === "boolean_type" || t === "void_type") return node.text;
376
+ if (t === "generic_type") {
377
+ for (let i = 0; i < node.namedChildCount; i++) {
378
+ const c = node.namedChild(i);
379
+ if (!c) continue;
380
+ if (c.type === "type_identifier" || c.type === "scoped_type_identifier" || c.type === "generic_type") {
381
+ const b = javaTypeBaseName(c);
382
+ if (b) return b;
383
+ }
384
+ }
385
+ return "";
386
+ }
387
+ if (t === "scoped_type_identifier") {
388
+ const parts = [];
389
+ for (let i = 0; i < node.namedChildCount; i++) {
390
+ const c = node.namedChild(i);
391
+ if (!c) continue;
392
+ if (c.type === "annotation" || c.type === "marker_annotation") continue;
393
+ const b = javaTypeBaseName(c);
394
+ if (b) parts.push(b);
395
+ }
396
+ return parts.join(".");
397
+ }
398
+ if (t === "annotated_type") {
399
+ for (let i = 0; i < node.namedChildCount; i++) {
400
+ const c = node.namedChild(i);
401
+ if (!c) continue;
402
+ if (c.type === "annotation" || c.type === "marker_annotation") continue;
403
+ const b = javaTypeBaseName(c);
404
+ if (b) return b;
405
+ }
406
+ return "";
407
+ }
408
+ if (t === "array_type") {
409
+ return javaTypeBaseName(node.childForFieldName("element"));
410
+ }
411
+ if (t === "field_access") {
412
+ const links = [];
413
+ let cur = node;
414
+ let guard = 0;
415
+ let done = false;
416
+ while (cur && guard++ < 64) {
417
+ if (cur.type === "field_access") {
418
+ const f = cur.childForFieldName("field");
419
+ const o = cur.childForFieldName("object");
420
+ if (!f || f.type !== "identifier") return "";
421
+ links.unshift(f.text);
422
+ cur = o;
423
+ continue;
424
+ }
425
+ if (cur.type === "identifier" || cur.type === "type_identifier" || cur.type === "scoped_identifier") {
426
+ links.unshift(cur.text);
427
+ done = true;
428
+ break;
429
+ }
430
+ return "";
431
+ }
432
+ if (!done) return "";
433
+ return links.join(".");
434
+ }
435
+ return "";
436
+ }
437
+ var JAVA_LIBRARY_NAMESPACE_CITATIONS = ["jakarta", "androidx", "dotty.tools", "org.yaml"];
438
+ var JAVA_EXTERNAL_STATIC_IMPORT_ROOTS = ["java.", "javax.", "jakarta.", "jdk.", "sun.", "com.sun.", "androidx.", "dotty.tools.", "org.yaml."];
439
+ var RealJavaAnalyzer = class _RealJavaAnalyzer {
440
+ analyze(rootNode, content, filePath, context) {
441
+ const symbols = [];
442
+ const relationships = [];
443
+ const imports = [];
444
+ const exports = [];
445
+ const errors = [];
446
+ const frameworks = {};
447
+ const patterns = {};
448
+ let currentPackage = "";
449
+ let moduleName = "";
450
+ let cyclomaticComplexity = 0;
451
+ const symbolMap = /* @__PURE__ */ new Map();
452
+ const classHierarchy = /* @__PURE__ */ new Map();
453
+ const overrideTasks = [];
454
+ const methodsByOwner = /* @__PURE__ */ new Map();
455
+ const scopeStack = [];
456
+ const stack = [{ node: rootNode }];
457
+ const receiverTypes = this.collectJavaReceiverTypeIndex(rootNode);
458
+ while (stack.length > 0) {
459
+ const item = stack.pop();
460
+ const { node, parentContext, isExit } = item;
461
+ if (isExit) {
462
+ if (parentContext) {
463
+ this.processPhantomSymbols(parentContext, symbols, symbolMap, frameworks);
464
+ if (scopeStack.length > 0 && scopeStack[scopeStack.length - 1] === parentContext) {
465
+ scopeStack.pop();
466
+ }
467
+ }
468
+ continue;
469
+ }
470
+ if (node.isMissing || node.hasError) {
471
+ if (node.type === "ERROR" || node.type === "MISSING") {
472
+ errors.push({
473
+ message: `Syntax ${node.type} detected.`,
474
+ range: this.nodeToRange(node),
475
+ severity: "warning"
476
+ });
477
+ }
478
+ }
479
+ let newContext = parentContext;
480
+ const type = node.type;
481
+ let pushedScope = false;
482
+ if (type === "if_statement" || type === "for_statement" || type === "enhanced_for_statement" || type === "while_statement" || type === "do_statement" || type === "catch_clause" || type === "switch_block_statement_group" || type === "switch_rule" || type === "ternary_expression") {
483
+ cyclomaticComplexity++;
484
+ } else if (type === "binary_expression") {
485
+ const op = node.childForFieldName("operator")?.text;
486
+ if (op === "&&" || op === "||") cyclomaticComplexity++;
487
+ }
488
+ if (type === "package_declaration") {
489
+ const pkgName = this.childText(node, "scoped_identifier") || this.childText(node, "identifier");
490
+ if (pkgName) currentPackage = pkgName;
491
+ } else if (type === "module_declaration") {
492
+ moduleName = node.childForFieldName("name")?.text ?? "";
493
+ this.emitJavaModuleDeclaration(node, symbols, relationships, imports);
494
+ } else if (type === "import_declaration") {
495
+ let isStatic = false;
496
+ let isAsterisk = false;
497
+ for (let k = 0; k < node.childCount; k++) {
498
+ const kid = node.child(k);
499
+ if (!kid) continue;
500
+ if (kid.type === "static") isStatic = true;
501
+ else if (kid.type === "asterisk") isAsterisk = true;
502
+ }
503
+ let moduleName2 = this.childText(node, "scoped_identifier") || this.childText(node, "identifier");
504
+ if (moduleName2) {
505
+ imports.push({
506
+ module: moduleName2,
507
+ items: [],
508
+ isDefault: false,
509
+ range: this.nodeToRange(node),
510
+ isNamespace: isAsterisk,
511
+ metadata: { isWildcard: isAsterisk, isStatic }
512
+ });
513
+ this.detectFrameworksFromImport(moduleName2, frameworks);
514
+ relationships.push({
515
+ source: currentPackage || "root",
516
+ target: moduleName2,
517
+ type: ASTEngine.RelationshipType.Imports,
518
+ confidence: 1,
519
+ // M59 (#825): the line the imported module is NAMED on. 1.0 and not
520
+ // 0.9: `import_declaration` names the module in the grammar's own
521
+ // `scoped_identifier`, so this is the dependency the module pass reads,
522
+ // not a name matched from text. The `references` mention beside it keeps
523
+ // 0.9, which is the tier the family uses for a name-resolved mention.
524
+ //
525
+ // THE TARGET STAYS THE MODULE PATH, and the re-spelling tried here is
526
+ // MEASURED AND REJECTED rather than forgotten. `d5385c960` restated local
527
+ // imports to their leaf (+ `module` metadata) and measured "0 false locals" —
528
+ // a true statement about LOCALITY that is not this gate's question. Re-measured
529
+ // at cap 400 with `--index`: the gate indexes 4,020 qualifiedName aliases, so
530
+ // `com.example.core.Foo` ALREADY resolves to the declaration's qualified name,
531
+ // and the re-spelling moved `imports` resolved 104 → 64 with amb(decl) 0 → 40
532
+ // (unresolved unchanged at 263). A leaf whose FQN was unique can collide by
533
+ // name; the FQN cannot. The declaration is reached; the re-spelling lost it.
534
+ ...this.lineField(this.findChildByType(node, "scoped_identifier") || this.findChildByType(node, "identifier"), node),
535
+ metadata: { relationshipType: "import_declaration", isJavaImport: true, isWildcard: isAsterisk, isStatic, isModuleDependency: true }
536
+ });
537
+ relationships.push({
538
+ source: currentPackage || "root",
539
+ target: moduleName2,
540
+ type: ASTEngine.RelationshipType.References,
541
+ confidence: 0.9,
542
+ // M59 (#825): the line the imported module is NAMED on.
543
+ ...this.lineField(this.findChildByType(node, "scoped_identifier") || this.findChildByType(node, "identifier"), node),
544
+ metadata: { relationshipType: "import_declaration_mention", isJavaImport: true, isWildcard: isAsterisk, isStatic }
545
+ });
546
+ relationships.push({
547
+ source: currentPackage || "root",
548
+ target: moduleName2,
549
+ type: ASTEngine.RelationshipType.Imports,
550
+ confidence: 1,
551
+ ...this.lineField(this.findChildByType(node, "scoped_identifier") || this.findChildByType(node, "identifier"), node),
552
+ metadata: {
553
+ relationshipType: isStatic ? "static_member_import" : isAsterisk ? "type_on_demand_import" : "single_type_import",
554
+ isJavaImport: true,
555
+ isWildcard: isAsterisk,
556
+ isStatic,
557
+ fileLevel: true
558
+ }
559
+ });
560
+ }
561
+ } else if (type === "class_declaration" || type === "record_declaration" || type === "interface_declaration" || type === "enum_declaration" || type === "annotation_type_declaration") {
562
+ const name = this.childText(node, "identifier");
563
+ if (name) {
564
+ const kind = this.mapTypeToSymbolKind(type);
565
+ const modifiers = this.getJavaModifiers(node);
566
+ const annotations = this.getJavaAnnotations(node);
567
+ const visibility = this.getJavaVisibility(modifiers);
568
+ const fqn = this.buildFqn(currentPackage, scopeStack, name);
569
+ const inheritance = this.extractJavaInheritance(node);
570
+ const parents = [];
571
+ if (inheritance.extendsClass && inheritance.extendsClass.name) parents.push(inheritance.extendsClass.name);
572
+ for (let i = 0; i < inheritance.extendsInterfaces.length; i++) {
573
+ if (inheritance.extendsInterfaces[i].name) parents.push(inheritance.extendsInterfaces[i].name);
574
+ }
575
+ for (let i = 0; i < inheritance.implementsInterfaces.length; i++) {
576
+ if (inheritance.implementsInterfaces[i].name) parents.push(inheritance.implementsInterfaces[i].name);
577
+ }
578
+ classHierarchy.set(fqn, parents);
579
+ for (let k = 0; k < inheritance.permitsSubtypes.length; k++) {
580
+ if (!inheritance.permitsSubtypes[k].name) continue;
581
+ relationships.push({
582
+ source: name,
583
+ target: inheritance.permitsSubtypes[k].name,
584
+ type: ASTEngine.RelationshipType.References,
585
+ confidence: 1,
586
+ // M59 (#825): the subtype is named in the `permits` clause.
587
+ line_number: inheritance.permitsSubtypes[k].line,
588
+ metadata: { relationshipType: "permits" }
589
+ });
590
+ }
591
+ if (inheritance.extendsClass && inheritance.extendsClass.name) {
592
+ relationships.push({
593
+ source: name,
594
+ target: inheritance.extendsClass.name,
595
+ type: ASTEngine.RelationshipType.Inheritance,
596
+ confidence: 1,
597
+ // M59 (#825): the parent's OWN line — a long `extends A\n implements B`
598
+ // list puts each parent on its own line, and the clause's start line
599
+ // named only the first of them.
600
+ line_number: inheritance.extendsClass.line,
601
+ metadata: { relationshipType: type === "interface_declaration" ? "extends_interface" : "extends" }
602
+ });
603
+ }
604
+ for (let k = 0; k < inheritance.extendsInterfaces.length; k++) {
605
+ const iface = inheritance.extendsInterfaces[k];
606
+ if (!iface.name) continue;
607
+ relationships.push({
608
+ source: name,
609
+ target: iface.name,
610
+ type: ASTEngine.RelationshipType.Inheritance,
611
+ confidence: 1,
612
+ line_number: iface.line,
613
+ metadata: { relationshipType: "extends_interface" }
614
+ });
615
+ }
616
+ for (let k = 0; k < inheritance.implementsInterfaces.length; k++) {
617
+ const iface = inheritance.implementsInterfaces[k];
618
+ if (!iface.name) continue;
619
+ relationships.push({
620
+ source: name,
621
+ target: iface.name,
622
+ type: ASTEngine.RelationshipType.Implementation,
623
+ confidence: 1,
624
+ line_number: iface.line,
625
+ metadata: { relationshipType: "implements" }
626
+ });
627
+ }
628
+ const annNames = this.getJavaAnnotationNames(node);
629
+ let isSpringBean = false;
630
+ let isEntity = false;
631
+ for (let i = 0; i < annNames.length; i++) {
632
+ const n = annNames[i];
633
+ if (n === "Service" || n === "Repository" || n === "RestController" || n === "Component") isSpringBean = true;
634
+ if (n === "Entity" || n === "Table") isEntity = true;
635
+ }
636
+ let tableName = name.toLowerCase();
637
+ let tableNameIsDeclared = false;
638
+ if (isEntity) {
639
+ frameworks["jpa"] = true;
640
+ patterns["entity_pattern"] = true;
641
+ if (inArray(annNames, "Table")) {
642
+ const n = this.getAnnotationValue(node, "Table", "name");
643
+ if (n) {
644
+ tableName = n;
645
+ tableNameIsDeclared = true;
646
+ }
647
+ }
648
+ relationships.push({
649
+ source: name,
650
+ target: tableName,
651
+ type: ASTEngine.RelationshipType.Uses,
652
+ // JD-J9 (M60 java deep review, 2026-09-27): a DECLARED table name
653
+ // is written in this file, so 1.0 is honest. The bare-`@Entity`
654
+ // default is not: `@Entity class Item` contains the string "item"
655
+ // nowhere, and JPA's documented default is the ENTITY NAME
656
+ // UNCHANGED (`AuctionItem`), so the lowercased form matches no
657
+ // standard naming strategy either. Publishing that at 1.0 with no
658
+ // marker states a certainty the source does not contain — and
659
+ // bb_graph, bb_impact and hover all repeat it verbatim as a real
660
+ // relationship to a table that may not exist.
661
+ //
662
+ // Measured consequence: it was also a standing FALSE FABRICATION in
663
+ // the corpus. `uses` joined java's compare set in this same wave
664
+ // (JD-J2), and the very first fresh draw reported
665
+ // `UNEXPLAINED Item.java :: uses Item -> item` at conf 1.0 — an
666
+ // unmatched CONFIDENT edge, which the harness counts as a
667
+ // fabrication, not a heuristic. No language-derived oracle can
668
+ // match it, because no language rule declares that name.
669
+ //
670
+ // So: declared stays 1.0; the inferred default drops below the
671
+ // 0.7 fabrication threshold and is marked, making it declared
672
+ // suspicion — visible in the heuristic bucket (surfaced by JD-J1)
673
+ // rather than counted as a relationship. Nothing is lost: the
674
+ // edge still exists, and a consumer can filter on `inferred`.
675
+ confidence: tableNameIsDeclared ? 1 : 0.6,
676
+ // M59 (#825): the table is named inside `@Table`, so that is the
677
+ // line the name is written on. Absent for the inferred default —
678
+ // there is no line to point at, and borrowing the class
679
+ // declaration's line would invent one (#823).
680
+ ...this.annotationLine(node, "Table") ? { line_number: this.annotationLine(node, "Table") } : {},
681
+ metadata: { isDatabase: true, relationshipType: "mapped_table", table: tableName, inferred: !tableNameIsDeclared }
682
+ });
683
+ }
684
+ if (isSpringBean) {
685
+ frameworks["spring"] = true;
686
+ for (let i = 0; i < annNames.length; i++) {
687
+ const a = annNames[i];
688
+ if (a === "Service") {
689
+ patterns["service_layer"] = true;
690
+ break;
691
+ }
692
+ if (a === "Repository") {
693
+ patterns["repository_pattern"] = true;
694
+ break;
695
+ }
696
+ if (a === "RestController" || a === "Controller") {
697
+ patterns["mvc_pattern"] = true;
698
+ break;
699
+ }
700
+ }
701
+ }
702
+ if (inArray(annNames, "FeignClient")) {
703
+ const targetService = this.getAnnotationValue(node, "FeignClient", "name") || this.getAnnotationValue(node, "FeignClient", "value");
704
+ if (targetService) {
705
+ relationships.push({
706
+ source: name,
707
+ target: targetService,
708
+ type: ASTEngine.RelationshipType.Calls,
709
+ confidence: 1,
710
+ // M59 (#825): the service is named inside `@FeignClient`.
711
+ ...this.annotationLine(node, "FeignClient") ? { line_number: this.annotationLine(node, "FeignClient") } : {},
712
+ metadata: { isFeignClient: true, microservice: targetService }
713
+ });
714
+ }
715
+ }
716
+ const javadoc = this.extractJavadocComment(node);
717
+ const sym = {
718
+ name,
719
+ kind,
720
+ // M14 INCOMING — the declaration's QUALIFIED name, published.
721
+ //
722
+ // The analyzer has always computed this (`buildFqn`, above, is the
723
+ // JLS name: package + nesting + own name) and used it internally for
724
+ // `classHierarchy`, then dropped it: no java symbol carried a
725
+ // `qualifiedName`, so `pool-worker.ts:1154` filled the field with its
726
+ // last-resort fallback and `script/m14/incoming-gate.ts` measured
727
+ // `qualifiedNameAliases: 0` for this lane — against 33,750 for
728
+ // typescript. What that costs is resolution, not tidiness: an edge
729
+ // whose target is spelled `com.acme.Foo` (a fully-qualified reference,
730
+ // an import's module, another package's type) has nothing to match,
731
+ // because the only key the pool publishes for this declaration is the
732
+ // leaf `Foo`.
733
+ qualifiedName: fqn,
734
+ // M59 (#825): the declaration's line, annotations skipped.
735
+ range: this.symbolRange(node),
736
+ visibility,
737
+ comments: javadoc ? [javadoc] : [],
738
+ decorators: annotations,
739
+ genericParameters: this.getJavaGenericParameters(node),
740
+ ownerName: parentContext?.name,
741
+ ownerKind: parentContext?.kind,
742
+ metadata: {
743
+ nodeType: type,
744
+ modifiers,
745
+ annotations,
746
+ isPublic: visibility === "public",
747
+ isAbstract: modifiers.indexOf("abstract") !== -1 || type === "interface_declaration",
748
+ ...isSpringBean ? { spring_bean: true } : {}
749
+ }
750
+ };
751
+ if (type === "record_declaration") {
752
+ sym.parameters = this.extractJavaParameters(node, relationships, name);
753
+ const formalParams = this.findChildByType(node, "formal_parameters");
754
+ if (formalParams) {
755
+ for (let i = 0; i < formalParams.namedChildCount; i++) {
756
+ const comp = formalParams.namedChild(i);
757
+ if (!comp) continue;
758
+ if (comp.type !== "formal_parameter" && comp.type !== "spread_parameter") continue;
759
+ const compName = comp.type === "spread_parameter" ? (() => {
760
+ const decl = this.findChildByType(comp, "variable_declarator");
761
+ return decl ? this.findChildByType(decl, "identifier") : null;
762
+ })() : comp.childForFieldName("name");
763
+ if (!compName) continue;
764
+ let compReturnType = void 0;
765
+ if (comp.type === "spread_parameter") {
766
+ for (let cIdx = 0; cIdx < comp.namedChildCount; cIdx++) {
767
+ const c = comp.namedChild(cIdx);
768
+ if (c && c.type !== "modifiers" && !stringIncludes(c.type, "annotation") && c.type !== "variable_declarator") {
769
+ compReturnType = c.text + "[]";
770
+ break;
771
+ }
772
+ }
773
+ } else {
774
+ compReturnType = this.extractJavaParameterType(comp);
775
+ }
776
+ const compSym = {
777
+ name: compName.text,
778
+ kind: ASTEngine.SymbolKind.Property,
779
+ // M59 (#825): a record component's own line (its modifiers,
780
+ // which carry any parameter annotations, are skipped).
781
+ range: this.symbolRange(comp),
782
+ returnType: compReturnType,
783
+ visibility: "public",
784
+ ownerName: name,
785
+ ownerKind: kind,
786
+ metadata: { nodeType: "record_component" }
787
+ };
788
+ symbols.push(compSym);
789
+ }
790
+ }
791
+ }
792
+ symbols.push(sym);
793
+ symbolMap.set(fqn, sym);
794
+ if (!symbolMap.has(name)) symbolMap.set(name, sym);
795
+ this.emitJavaTypeParameterSymbols(node, name, kind, type, symbols);
796
+ newContext = {
797
+ node,
798
+ name,
799
+ kind,
800
+ fqn,
801
+ symbolRef: sym,
802
+ visibility,
803
+ isAbstract: inArray(modifiers, "abstract"),
804
+ isInterface: type === "interface_declaration",
805
+ isRecord: type === "record_declaration",
806
+ annotations,
807
+ fields: [],
808
+ parents
809
+ };
810
+ scopeStack.push(newContext);
811
+ pushedScope = true;
812
+ }
813
+ } else if (type === "method_declaration" || type === "constructor_declaration" || type === "compact_constructor_declaration" || type === "annotation_type_element_declaration") {
814
+ const nameNode = this.findChildByType(node, "identifier");
815
+ const name = nameNode ? nameNode.text : null;
816
+ if (name) {
817
+ const isConstructor = type === "constructor_declaration" || type === "compact_constructor_declaration";
818
+ const isCompactCtor = type === "compact_constructor_declaration";
819
+ const modifiers = this.getJavaModifiers(node);
820
+ const annotations = this.getJavaAnnotations(node);
821
+ const visibility = this.getJavaVisibility(modifiers);
822
+ const parameters = isCompactCtor && parentContext ? this.extractJavaParameters(parentContext.node, relationships, parentContext.name) : this.extractJavaParameters(node, relationships, parentContext ? parentContext.name : void 0);
823
+ let sigParams = "";
824
+ for (let i = 0; i < parameters.length; i++) {
825
+ if (i > 0) sigParams += ",";
826
+ sigParams += parameters[i].type || "unknown";
827
+ }
828
+ const signature = `(${sigParams})`;
829
+ const fqn = parentContext ? `${parentContext.fqn}.${name}${signature}` : `${name}${signature}`;
830
+ const localOwnerPrefix = parentContext ? `${parentContext.name}.${name}${signature}` : `${name}${signature}`;
831
+ const returnType = isConstructor ? void 0 : this.extractJavaReturnType(node);
832
+ if (!isConstructor && parentContext) {
833
+ const retTypeNode = node.childForFieldName("type");
834
+ if (retTypeNode) {
835
+ for (const { name: t, line } of this.collectJavaTypeNames(retTypeNode)) {
836
+ if (t === "var") continue;
837
+ relationships.push({
838
+ source: parentContext.name,
839
+ target: t,
840
+ type: ASTEngine.RelationshipType.References,
841
+ confidence: 0.95,
842
+ // M59 (#825): the line the return TYPE is named on.
843
+ line_number: line,
844
+ metadata: { relationshipType: "declared_type", declaredOn: "return_type" }
845
+ });
846
+ }
847
+ }
848
+ }
849
+ let isApiEndpoint = false;
850
+ let fullRoute = "";
851
+ let mappingAnnotation = "";
852
+ const methodAnnNames = this.getJavaAnnotationNames(node);
853
+ for (let i = 0; i < methodAnnNames.length; i++) {
854
+ const n = methodAnnNames[i];
855
+ if (n === "GetMapping" || n === "PostMapping" || n === "PutMapping" || n === "DeleteMapping" || n === "PatchMapping" || n === "RequestMapping" || n === "Path") {
856
+ mappingAnnotation = n;
857
+ break;
858
+ }
859
+ }
860
+ if (mappingAnnotation) {
861
+ isApiEndpoint = true;
862
+ let httpMethod = "GET";
863
+ if (mappingAnnotation === "PostMapping") httpMethod = "POST";
864
+ else if (mappingAnnotation === "PutMapping") httpMethod = "PUT";
865
+ else if (mappingAnnotation === "DeleteMapping") httpMethod = "DELETE";
866
+ else if (mappingAnnotation === "PatchMapping") httpMethod = "PATCH";
867
+ else if (mappingAnnotation === "Path") httpMethod = "ANY";
868
+ else if (mappingAnnotation === "RequestMapping") {
869
+ const m = this.getAnnotationValue(node, "RequestMapping", "method");
870
+ if (m) {
871
+ let verb = "";
872
+ for (let ci = 0; ci < m.length; ci++) {
873
+ const c = m[ci];
874
+ if (c >= "A" && c <= "Z" || c >= "a" && c <= "z") verb += c;
875
+ }
876
+ verb = verb.toUpperCase();
877
+ if (verb === "GET" || verb === "POST" || verb === "PUT" || verb === "DELETE" || verb === "PATCH" || verb === "HEAD" || verb === "OPTIONS") httpMethod = verb;
878
+ else httpMethod = "ANY";
879
+ } else httpMethod = "ANY";
880
+ }
881
+ const methodRoute = this.getAnnotationValue(node, mappingAnnotation, "value") || this.getAnnotationValue(node, mappingAnnotation, "path") || "";
882
+ let basePath = "";
883
+ if (parentContext) {
884
+ const classAnnNames = this.getJavaAnnotationNames(parentContext.node);
885
+ let classMapping = "";
886
+ for (let i = 0; i < classAnnNames.length; i++) {
887
+ if (classAnnNames[i] === "RequestMapping" || classAnnNames[i] === "Path") {
888
+ classMapping = classAnnNames[i];
889
+ break;
890
+ }
891
+ }
892
+ if (classMapping) {
893
+ basePath = this.getAnnotationValue(parentContext.node, classMapping, "value") || this.getAnnotationValue(parentContext.node, classMapping, "path") || "";
894
+ }
895
+ }
896
+ if (basePath || methodRoute) {
897
+ fullRoute = "";
898
+ for (let i = 0; i < basePath.length + methodRoute.length; i++) {
899
+ const ch = i < basePath.length ? basePath[i] : methodRoute[i - basePath.length];
900
+ if (ch === "/" && fullRoute.length > 0 && fullRoute[fullRoute.length - 1] === "/") continue;
901
+ fullRoute += ch;
902
+ }
903
+ if (!fullRoute.startsWith("/")) fullRoute = "/" + fullRoute;
904
+ relationships.push({
905
+ source: localOwnerPrefix,
906
+ target: fullRoute,
907
+ type: ASTEngine.RelationshipType.Exports,
908
+ confidence: 1,
909
+ // M59 (#825): the route text is written inside the mapping
910
+ // annotation, so that is the line that carries the name.
911
+ ...this.annotationLine(node, mappingAnnotation) ? { line_number: this.annotationLine(node, mappingAnnotation) } : {},
912
+ metadata: { isApiEndpoint: true, httpMethod, route: fullRoute, relationshipType: "api_endpoint" }
913
+ });
914
+ } else {
915
+ isApiEndpoint = false;
916
+ }
917
+ if (isApiEndpoint) frameworks["spring_mvc"] = true;
918
+ }
919
+ let dbMapping = "";
920
+ const methodDbAnnNames = this.getJavaAnnotationNames(node);
921
+ for (let idx = 0; idx < methodDbAnnNames.length; idx++) {
922
+ const n = methodDbAnnNames[idx];
923
+ if (n === "Column" || n === "OneToMany" || n === "ManyToOne" || n === "OneToOne" || n === "ManyToMany") {
924
+ dbMapping = n;
925
+ break;
926
+ }
927
+ }
928
+ if (dbMapping && parentContext) {
929
+ let target = name.startsWith("get") ? name.charAt(3).toLowerCase() + name.slice(4) : name;
930
+ const explicit = this.getAnnotationValue(node, dbMapping, "name");
931
+ if (explicit) target = explicit;
932
+ relationships.push({
933
+ source: localOwnerPrefix,
934
+ target,
935
+ type: ASTEngine.RelationshipType.Uses,
936
+ confidence: 1,
937
+ // M59 (#825): the column is named inside its mapping annotation.
938
+ ...this.annotationLine(node, dbMapping) ? { line_number: this.annotationLine(node, dbMapping) } : {},
939
+ metadata: { isDatabase: true, relationshipType: "mapped_column" }
940
+ });
941
+ }
942
+ const hasBody = !!this.findChildByType(node, "block");
943
+ const isAbstract = modifiers.indexOf("abstract") !== -1 || !!(parentContext?.isInterface && !hasBody && modifiers.indexOf("default") === -1);
944
+ const effectiveVisibility = visibility ?? (parentContext?.isInterface ? "public" : void 0);
945
+ const isAnnotatedOverride = annotations.indexOf("@Override") !== -1;
946
+ if (isAnnotatedOverride && parentContext) {
947
+ overrideTasks.push({
948
+ fqn,
949
+ methodName: name,
950
+ signature,
951
+ ownerFqn: parentContext.fqn,
952
+ arity: parameters.length,
953
+ explicit: true,
954
+ // M59 (#825): the OVERRIDING declaration's own line (annotations
955
+ // skipped, so an `@Override` method does not report the
956
+ // annotation) — captured here while the node is in hand rather
957
+ // than resolved later by name, which could match namesakes.
958
+ line: this.declarationLineNode(node).startPosition.row + 1
959
+ });
960
+ } else if (parentContext && !isConstructor && modifiers.indexOf("static") === -1 && visibility !== "private") {
961
+ overrideTasks.push({
962
+ fqn,
963
+ methodName: name,
964
+ signature,
965
+ ownerFqn: parentContext.fqn,
966
+ arity: parameters.length,
967
+ explicit: false,
968
+ line: this.declarationLineNode(node).startPosition.row + 1
969
+ });
970
+ }
971
+ const methodDoc = this.extractJavadocComment(node);
972
+ const memberName = isConstructor ? parentContext?.name ?? name : name;
973
+ const sym = {
974
+ name: memberName,
975
+ kind: isConstructor ? ASTEngine.SymbolKind.Constructor : ASTEngine.SymbolKind.Method,
976
+ // M14 INCOMING — the member's QUALIFIED name, WITHOUT the signature.
977
+ //
978
+ // `fqn` above carries it (`Owner.method(int,String)`) and that is the
979
+ // right key for the override BFS, but no edge spells a signature: an
980
+ // `overrides` target is `Owner.method` and a `calls` target is
981
+ // `receiver.method`. Publishing the signature-bearing form would add a
982
+ // key nothing can match. Overloads therefore share one qualified name —
983
+ // correct for the JLS (a method's qualified name is `Type.method`;
984
+ // overloading is a signature matter) and harmless to the candidate
985
+ // count, which counts DECLARING FILES, so N overloads in one file are
986
+ // still one candidate.
987
+ qualifiedName: parentContext ? `${parentContext.fqn}.${memberName}` : memberName,
988
+ // M59 (#825): the declaration's line, annotations skipped.
989
+ range: this.symbolRange(node),
990
+ comments: methodDoc ? [methodDoc] : [],
991
+ complexity: this.calculateJavaMethodComplexity(node),
992
+ visibility: effectiveVisibility,
993
+ parameters,
994
+ returnType,
995
+ qualified_calls: [],
996
+ apiContractPath: isApiEndpoint ? fullRoute : void 0,
997
+ isStatic: modifiers.indexOf("static") !== -1,
998
+ isAbstract,
999
+ decorators: annotations,
1000
+ genericParameters: this.getJavaGenericParameters(node),
1001
+ ownerName: parentContext?.name,
1002
+ ownerKind: parentContext?.kind,
1003
+ metadata: { nodeType: type, modifiers, annotations }
1004
+ };
1005
+ symbols.push(sym);
1006
+ symbolMap.set(fqn, sym);
1007
+ symbolMap.set(localOwnerPrefix, sym);
1008
+ if (parentContext?.fqn) {
1009
+ const leaf = parentContext.fqn.slice(parentContext.fqn.lastIndexOf(".") + 1);
1010
+ for (const key of parentContext.fqn === leaf ? [leaf] : [parentContext.fqn, leaf]) {
1011
+ let byName = methodsByOwner.get(key);
1012
+ if (!byName) {
1013
+ byName = /* @__PURE__ */ new Map();
1014
+ methodsByOwner.set(key, byName);
1015
+ }
1016
+ const arr = byName.get(name);
1017
+ if (arr) arr.push(sym);
1018
+ else byName.set(name, [sym]);
1019
+ }
1020
+ }
1021
+ this.emitJavaTypeParameterSymbols(node, name, ASTEngine.SymbolKind.Method, type, symbols);
1022
+ newContext = {
1023
+ node,
1024
+ name,
1025
+ kind: ASTEngine.SymbolKind.Method,
1026
+ fqn,
1027
+ symbolRef: sym,
1028
+ visibility: effectiveVisibility,
1029
+ isAbstract,
1030
+ isInterface: false,
1031
+ isRecord: false,
1032
+ annotations,
1033
+ fields: [],
1034
+ parents: []
1035
+ };
1036
+ scopeStack.push(newContext);
1037
+ pushedScope = true;
1038
+ }
1039
+ } else if (type === "constant_declaration") {
1040
+ const fieldType = this.extractJavaFieldType(node);
1041
+ for (let decIdx = 0; decIdx < node.namedChildCount; decIdx++) {
1042
+ const declarator = node.namedChild(decIdx);
1043
+ if (!declarator || declarator.type !== "variable_declarator") continue;
1044
+ const nameNode = this.findChildByType(declarator, "identifier");
1045
+ if (!nameNode || !parentContext) continue;
1046
+ symbols.push({
1047
+ name: nameNode.text,
1048
+ kind: ASTEngine.SymbolKind.Constant,
1049
+ range: this.nodeToRange(declarator),
1050
+ returnType: fieldType,
1051
+ visibility: "public",
1052
+ ownerName: parentContext.name,
1053
+ ownerKind: parentContext.kind,
1054
+ metadata: { nodeType: type }
1055
+ });
1056
+ }
1057
+ const constTypeNode = node.childForFieldName("type");
1058
+ if (constTypeNode && parentContext) {
1059
+ for (const { name: t, line } of this.collectJavaTypeNames(constTypeNode)) {
1060
+ relationships.push({
1061
+ source: parentContext.name,
1062
+ target: t,
1063
+ type: ASTEngine.RelationshipType.References,
1064
+ confidence: 0.95,
1065
+ // M59 (#825): the line the constant's TYPE is named on.
1066
+ line_number: line,
1067
+ metadata: { relationshipType: "declared_type", declaredOn: type }
1068
+ });
1069
+ }
1070
+ }
1071
+ } else if (type === "field_declaration") {
1072
+ const modifiers = this.getJavaModifiers(node);
1073
+ const annotations = this.getJavaAnnotations(node);
1074
+ const visibility = this.getJavaVisibility(modifiers);
1075
+ const isStatic = inArray(modifiers, "static");
1076
+ const isConstant = modifiers.indexOf("final") !== -1 && isStatic;
1077
+ const fieldType = this.extractJavaFieldType(node);
1078
+ const fieldTypeNode = node.childForFieldName("type");
1079
+ if (fieldTypeNode && parentContext) {
1080
+ for (const { name: t, line } of this.collectJavaTypeNames(fieldTypeNode)) {
1081
+ relationships.push({
1082
+ source: parentContext.name,
1083
+ target: t,
1084
+ type: ASTEngine.RelationshipType.References,
1085
+ confidence: 0.95,
1086
+ // M59 (#825): the line the field's TYPE is named on.
1087
+ line_number: line,
1088
+ metadata: { relationshipType: "declared_type", declaredOn: type }
1089
+ });
1090
+ }
1091
+ }
1092
+ const declarators = this.findAllChildrenByType(node, "variable_declarator");
1093
+ for (let decIdx = 0; decIdx < declarators.length; decIdx++) {
1094
+ const declarator = declarators[decIdx];
1095
+ const nameNode = this.findChildByType(declarator, "identifier");
1096
+ if (nameNode && parentContext) {
1097
+ const fieldName = nameNode.text;
1098
+ const localOwnerPrefix = `${parentContext.name}.${fieldName}`;
1099
+ const fieldDbAnnNames = this.getJavaAnnotationNames(node);
1100
+ let dbMapping = "";
1101
+ for (let idx = 0; idx < fieldDbAnnNames.length; idx++) {
1102
+ const n = fieldDbAnnNames[idx];
1103
+ if (n === "Column" || n === "OneToMany" || n === "ManyToOne" || n === "OneToOne" || n === "ManyToMany") {
1104
+ dbMapping = n;
1105
+ break;
1106
+ }
1107
+ }
1108
+ if (dbMapping) {
1109
+ const explicit = this.getAnnotationValue(node, dbMapping, "name");
1110
+ const target = explicit ?? fieldName;
1111
+ const columnNameIsDeclared = !!explicit;
1112
+ relationships.push({
1113
+ source: localOwnerPrefix,
1114
+ target,
1115
+ type: ASTEngine.RelationshipType.Uses,
1116
+ confidence: columnNameIsDeclared ? 1 : 0.6,
1117
+ // M59 (#825): the column name is written inside its mapping annotation when explicit; the field line when implicit.
1118
+ ...explicit ? { line_number: this.annotationLine(node, dbMapping, explicit) } : { line_number: nameNode.startPosition.row + 1 },
1119
+ metadata: { isDatabase: true, relationshipType: "mapped_column", inferred: !columnNameIsDeclared }
1120
+ });
1121
+ }
1122
+ if (inArray(this.getJavaAnnotationNames(node), "Value")) {
1123
+ const valMatch = this.getAnnotationValue(node, "Value", "value");
1124
+ if (valMatch && valMatch.startsWith("${") && valMatch.endsWith("}")) {
1125
+ let configKey = valMatch.substring(2, valMatch.length - 1);
1126
+ if (stringIncludes(configKey, ":")) configKey = configKey.split(":")[0];
1127
+ relationships.push({
1128
+ source: localOwnerPrefix,
1129
+ target: configKey,
1130
+ type: ASTEngine.RelationshipType.Uses,
1131
+ confidence: 1,
1132
+ // M59 (#825): the key is written inside `@Value`.
1133
+ ...this.annotationLine(node, "Value") ? { line_number: this.annotationLine(node, "Value") } : {},
1134
+ metadata: { isConfiguration: true, relationshipType: "config_property" }
1135
+ });
1136
+ }
1137
+ }
1138
+ const fieldDoc = this.extractJavadocComment(node);
1139
+ const sym = {
1140
+ name: fieldName,
1141
+ kind: isConstant ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Property,
1142
+ range: this.nodeToRange(declarator),
1143
+ comments: fieldDoc ? [fieldDoc] : [],
1144
+ returnType: fieldType,
1145
+ visibility,
1146
+ isStatic,
1147
+ decorators: annotations,
1148
+ genericParameters: this.getJavaGenericParameters(node),
1149
+ ownerName: parentContext.name,
1150
+ ownerKind: parentContext.kind,
1151
+ metadata: { nodeType: type, modifiers, annotations, isConstant }
1152
+ };
1153
+ symbols.push(sym);
1154
+ parentContext.fields.push(sym);
1155
+ }
1156
+ }
1157
+ } else if (type === "enum_constant") {
1158
+ const nameNode = this.findChildByType(node, "identifier");
1159
+ if (nameNode && parentContext) {
1160
+ symbols.push({
1161
+ name: nameNode.text,
1162
+ kind: ASTEngine.SymbolKind.EnumMember,
1163
+ // M59 (#825): the constant's own line, annotations skipped.
1164
+ range: this.symbolRange(node),
1165
+ visibility: "public",
1166
+ ownerName: parentContext.name,
1167
+ ownerKind: parentContext.kind,
1168
+ metadata: { nodeType: type }
1169
+ });
1170
+ const constantBody = this.findChildByType(node, "class_body");
1171
+ if (constantBody) {
1172
+ const anonName = `anonymous_${nameNode.text}`;
1173
+ const anonFqn = parentContext.fqn ? `${parentContext.fqn}.${anonName}` : anonName;
1174
+ const anonSym = {
1175
+ name: anonName,
1176
+ kind: ASTEngine.SymbolKind.Class,
1177
+ range: this.nodeToRange(constantBody),
1178
+ visibility: "package-private",
1179
+ ownerName: parentContext.name,
1180
+ ownerKind: parentContext.kind,
1181
+ metadata: { nodeType: type, isAnonymousClass: true, enumConstantBody: true, superTypes: [parentContext.name] }
1182
+ };
1183
+ symbols.push(anonSym);
1184
+ symbolMap.set(anonFqn, anonSym);
1185
+ classHierarchy.set(anonFqn, [parentContext.name]);
1186
+ newContext = {
1187
+ node: constantBody,
1188
+ name: anonName,
1189
+ kind: ASTEngine.SymbolKind.Class,
1190
+ fqn: anonFqn,
1191
+ symbolRef: anonSym,
1192
+ // A Scope's visibility union has no `package-private` member (the
1193
+ // SYMBOL can spell it; the scope carries the enclosing declaration's
1194
+ // visibility, exactly as the object_creation anonymous-class site does).
1195
+ visibility: parentContext.visibility,
1196
+ isAbstract: false,
1197
+ isInterface: false,
1198
+ isRecord: false,
1199
+ annotations: [],
1200
+ fields: [],
1201
+ parents: [parentContext.name]
1202
+ };
1203
+ scopeStack.push(newContext);
1204
+ pushedScope = true;
1205
+ }
1206
+ }
1207
+ } else if (type === "method_invocation") {
1208
+ const callExpression = this.buildJavaCallExpression(node);
1209
+ if (callExpression && !this.isJavaBuiltin(callExpression)) {
1210
+ const receiverOwner = this.javaReceiverOwnerType(node, receiverTypes, context?.projectIndex);
1211
+ const receiverMetadata = receiverOwner ? { owner: receiverOwner.type, ownerEvidence: receiverOwner.evidence } : {};
1212
+ if (parentContext && parentContext.kind === ASTEngine.SymbolKind.Method && parentContext.symbolRef) {
1213
+ if (!parentContext.symbolRef.qualified_calls) parentContext.symbolRef.qualified_calls = [];
1214
+ parentContext.symbolRef.qualified_calls.push(callExpression);
1215
+ let argumentCount;
1216
+ const argsNode = node.childForFieldName("arguments");
1217
+ if (argsNode) {
1218
+ argumentCount = 0;
1219
+ for (let ai = 0; ai < argsNode.namedChildCount; ai++) {
1220
+ const ac = argsNode.namedChild(ai);
1221
+ if (ac && ac.type !== "line_comment" && ac.type !== "block_comment") argumentCount++;
1222
+ }
1223
+ }
1224
+ relationships.push({
1225
+ source: parentContext.name || parentContext.symbolRef.name,
1226
+ target: callExpression.split(".").pop() || callExpression,
1227
+ type: ASTEngine.RelationshipType.Calls,
1228
+ confidence: 0.7,
1229
+ // M59 (#825): the line is where the CALL IS WRITTEN — the name
1230
+ // field of the method_invocation, not the invocation node's own
1231
+ // start. Measured with the M59 oracle on real java: a
1232
+ // `method_invocation` starts at its RECEIVER, so `a.b()\n .c()`
1233
+ // put c on the line a.b() opened on. java, kotlin and groovy
1234
+ // share this node shape, so this is the family pattern.
1235
+ ...this.lineField(node.childForFieldName("name"), node),
1236
+ metadata: { javaCallExpression: callExpression, ...argumentCount !== void 0 ? { argumentCount } : {}, ...receiverMetadata }
1237
+ });
1238
+ } else {
1239
+ const typeScope = this.resolveEnclosingTypeScope(scopeStack);
1240
+ if (typeScope && typeScope.name) {
1241
+ relationships.push({
1242
+ source: typeScope.name,
1243
+ target: callExpression.split(".").pop() || callExpression,
1244
+ type: ASTEngine.RelationshipType.Calls,
1245
+ confidence: 0.7,
1246
+ // M59 (#825): same rule as the in-method branch above.
1247
+ ...this.lineField(node.childForFieldName("name"), node),
1248
+ metadata: { javaCallExpression: callExpression, callSite: parentContext?.node?.type ?? node.parent?.type, inInitializer: true, ...receiverMetadata }
1249
+ });
1250
+ }
1251
+ }
1252
+ }
1253
+ } else if (type === "explicit_constructor_invocation") {
1254
+ const text = node.text.trim();
1255
+ const source = parentContext?.name || parentContext?.symbolRef?.name || "global";
1256
+ let target = "super";
1257
+ let ctorKind = "super";
1258
+ if (text.startsWith("this")) {
1259
+ target = "this";
1260
+ ctorKind = "this";
1261
+ } else if (text.startsWith("super")) {
1262
+ target = "super";
1263
+ ctorKind = "super";
1264
+ } else {
1265
+ const q = text.split("(")[0].trim().split(".");
1266
+ target = q[0] + ".super";
1267
+ ctorKind = "qualified";
1268
+ }
1269
+ relationships.push({
1270
+ source,
1271
+ target,
1272
+ type: ASTEngine.RelationshipType.Calls,
1273
+ confidence: 1,
1274
+ // M59 (#825): the delegation is written exactly where the node is, and
1275
+ // `super`/`this` are unnamed keyword children — the invocation node's
1276
+ // own line is the only site that carries the name.
1277
+ line_number: node.startPosition.row + 1,
1278
+ metadata: { relationshipType: "constructor_invocation", constructorReceiver: ctorKind, callExpression: text }
1279
+ });
1280
+ } else if (type === "object_creation_expression") {
1281
+ const typeNode = this.findChildByType(node, "type_identifier") || this.findChildByType(node, "generic_type") || this.findChildByType(node, "scoped_type_identifier") || this.findChildByType(node, "identifier");
1282
+ if (typeNode) {
1283
+ const source = parentContext?.name || parentContext?.symbolRef?.name || "global";
1284
+ const typeName = typeNode.type === "identifier" ? typeNode.text : this.extractJavaTypeBaseName(typeNode);
1285
+ if (!typeName) continue;
1286
+ relationships.push({
1287
+ source,
1288
+ target: typeName,
1289
+ type: ASTEngine.RelationshipType.Instantiates,
1290
+ confidence: 0.95,
1291
+ // M59 (#825): the instantiated type is NAMED by its type node — that
1292
+ // node's line, not the statement's, which matters for
1293
+ // `new\n Foo()` and for a type named inside a multi-line argument.
1294
+ line_number: typeNode.startPosition.row + 1,
1295
+ metadata: { relationshipType: "instantiates", isNew: true }
1296
+ });
1297
+ const classBody = this.findChildByType(node, "class_body");
1298
+ if (classBody && parentContext) {
1299
+ const anonName = typeName ? `anonymous_${typeName}` : "anonymous_class";
1300
+ const anonFqn = parentContext.fqn ? `${parentContext.fqn}.${anonName}` : anonName;
1301
+ const anonSym = {
1302
+ name: anonName,
1303
+ kind: ASTEngine.SymbolKind.Class,
1304
+ range: this.nodeToRange(node),
1305
+ visibility: parentContext.visibility || "package-private",
1306
+ ownerName: parentContext.name,
1307
+ ownerKind: parentContext.kind,
1308
+ metadata: { nodeType: type, isAnonymousClass: true, superTypes: typeName ? [typeName] : [] }
1309
+ };
1310
+ symbols.push(anonSym);
1311
+ symbolMap.set(anonFqn, anonSym);
1312
+ classHierarchy.set(anonFqn, typeName ? [typeName] : []);
1313
+ newContext = {
1314
+ node: classBody,
1315
+ name: anonName,
1316
+ kind: ASTEngine.SymbolKind.Class,
1317
+ fqn: anonFqn,
1318
+ symbolRef: anonSym,
1319
+ visibility: parentContext.visibility,
1320
+ isAbstract: false,
1321
+ isInterface: false,
1322
+ isRecord: false,
1323
+ annotations: [],
1324
+ fields: [],
1325
+ parents: typeName ? [typeName] : []
1326
+ };
1327
+ scopeStack.push(newContext);
1328
+ pushedScope = true;
1329
+ }
1330
+ }
1331
+ } else if (type === "cast_expression" || type === "instanceof_expression" || type === "class_literal") {
1332
+ const typeNode = node.childForFieldName("type") || node.childForFieldName("right") || this.findChildByType(node, "type_identifier") || this.findChildByType(node, "generic_type") || this.findChildByType(node, "scoped_type_identifier") || // EXPORTS_JAVA2: `Foo[].class` — a class literal over an ARRAY type.
1333
+ // The `array_type` was missing from this list, and because the three
1334
+ // searches above only look at DIRECT children, `Foo[].class` resolved
1335
+ // to nothing and emitted NO edge at all (probe: `class X { Object o =
1336
+ // Foo[].class; }` produced only the `declared_type` edge for Object).
1337
+ // That is a missing edge rather than a wrong one, and it also left
1338
+ // `javaTypeBaseName`'s `array_type` branch unreachable from every
1339
+ // emission site — a branch that cannot match is dead code, which is
1340
+ // the question this lane is here to answer.
1341
+ this.findChildByType(node, "array_type");
1342
+ if (typeNode) {
1343
+ const typeName = typeNode.type === "type_identifier" ? typeNode.text : this.extractJavaTypeBaseName(typeNode);
1344
+ if (typeName && typeName !== "var") {
1345
+ const castTarget = typeName;
1346
+ relationships.push({
1347
+ source: parentContext?.name || parentContext?.symbolRef?.name || "global",
1348
+ // Cast arrays: `(String[]) o` — the EDGE TARGET is the element
1349
+ // type (String), not the JVM-erasure text with brackets
1350
+ // (probe 2026-09-08: `String[]` target joined nothing). The
1351
+ // declaredOn shape stays visible in metadata below.
1352
+ target: castTarget,
1353
+ type: ASTEngine.RelationshipType.References,
1354
+ confidence: 0.95,
1355
+ // M59 (#825): the cast/instanceof/class-literal type is named by
1356
+ // its type node.
1357
+ ...this.lineField(typeNode, node),
1358
+ metadata: {
1359
+ relationshipType: type === "cast_expression" ? "cast_target" : type === "instanceof_expression" ? "instanceof_check" : "class_literal"
1360
+ }
1361
+ });
1362
+ }
1363
+ }
1364
+ } else if (type === "record_pattern") {
1365
+ for (let i = 0; i < node.namedChildCount; i++) {
1366
+ const c = node.namedChild(i);
1367
+ if (c && c.type !== "record_pattern_body") {
1368
+ const typeName = c.type === "type_identifier" ? c.text : this.extractJavaTypeBaseName(c);
1369
+ if (typeName && typeName !== "var") {
1370
+ relationships.push({
1371
+ source: parentContext?.name || parentContext?.symbolRef?.name || "global",
1372
+ target: typeName,
1373
+ type: ASTEngine.RelationshipType.References,
1374
+ confidence: 0.95,
1375
+ // M59 (#825): the pattern's type is named by the pattern node.
1376
+ ...this.lineField(c, node),
1377
+ metadata: { relationshipType: "record_pattern" }
1378
+ });
1379
+ }
1380
+ break;
1381
+ }
1382
+ }
1383
+ } else if (type === "array_creation_expression") {
1384
+ const elemNode = node.childForFieldName("type");
1385
+ if (elemNode) {
1386
+ const typeBearing = elemNode.type === "type_identifier" || elemNode.type === "scoped_type_identifier" || elemNode.type === "generic_type";
1387
+ const typeName = typeBearing ? elemNode.type === "type_identifier" ? elemNode.text : this.extractJavaTypeBaseName(elemNode) : null;
1388
+ if (typeName) {
1389
+ relationships.push({
1390
+ source: parentContext?.name || parentContext?.symbolRef?.name || "global",
1391
+ target: typeName,
1392
+ type: ASTEngine.RelationshipType.References,
1393
+ confidence: 0.95,
1394
+ // M59 (#825): `new T[...]` names T on the element-type node.
1395
+ ...this.lineField(elemNode, node),
1396
+ metadata: { relationshipType: "array_creation" }
1397
+ });
1398
+ }
1399
+ }
1400
+ } else if (type === "method_reference") {
1401
+ const kids = [];
1402
+ for (let i = 0; i < node.namedChildCount; i++) {
1403
+ const k = node.namedChild(i);
1404
+ if (k) kids.push(k);
1405
+ }
1406
+ const refSource = parentContext?.name || parentContext?.symbolRef?.name || "global";
1407
+ if (kids.length > 0) {
1408
+ const recv = kids[0];
1409
+ const recvType = recv.type;
1410
+ const recvName = recvType === "type_identifier" || recvType === "identifier" ? recv.text : recvType === "scoped_type_identifier" || recvType === "generic_type" || recvType === "field_access" ? this.extractJavaTypeBaseName(recv) : null;
1411
+ const isKeywordReceiver = recvName === "this" || recvName === "super";
1412
+ const isArrayTypeRef = recv.type === "array_type" || recv.text.endsWith("[]");
1413
+ const isQualifiedThis = recvName !== null && (recvName === "this" || recvName.endsWith(".this"));
1414
+ if (recvName && !isKeywordReceiver && !isArrayTypeRef && !isQualifiedThis && this.isJavaNameShape(recvName)) {
1415
+ relationships.push({
1416
+ source: refSource,
1417
+ target: recvName,
1418
+ type: ASTEngine.RelationshipType.References,
1419
+ confidence: 0.9,
1420
+ // M59 (#825): the referenced type is named by the receiver.
1421
+ ...this.lineField(recv, node),
1422
+ metadata: { relationshipType: "method_reference_receiver", javaCallExpression: node.text }
1423
+ });
1424
+ }
1425
+ }
1426
+ if (kids.length >= 2) {
1427
+ relationships.push({
1428
+ source: refSource,
1429
+ target: kids[kids.length - 1].text,
1430
+ type: ASTEngine.RelationshipType.Calls,
1431
+ confidence: 0.8,
1432
+ // M59 (#825): the referenced method is named by its own node.
1433
+ ...this.lineField(kids[kids.length - 1], node),
1434
+ metadata: { javaCallExpression: node.text }
1435
+ });
1436
+ }
1437
+ } else if (type === "switch_label") {
1438
+ const pat = this.findChildByType(node, "pattern");
1439
+ if (pat) {
1440
+ const tp = this.findChildByType(pat, "type_pattern") || pat;
1441
+ const tnode = this.findChildByType(tp, "type_identifier") || this.findChildByType(tp, "generic_type") || this.findChildByType(tp, "scoped_type_identifier");
1442
+ if (tnode) {
1443
+ const typeName = tnode.type === "type_identifier" ? tnode.text : this.extractJavaTypeBaseName(tnode);
1444
+ if (typeName) {
1445
+ relationships.push({
1446
+ source: parentContext?.name || parentContext?.symbolRef?.name || "global",
1447
+ target: typeName,
1448
+ type: ASTEngine.RelationshipType.References,
1449
+ confidence: 0.95,
1450
+ // M59 (#825): the case pattern's type is named by its node.
1451
+ ...this.lineField(tnode, node),
1452
+ metadata: { relationshipType: "switch_pattern" }
1453
+ });
1454
+ }
1455
+ }
1456
+ }
1457
+ } else if (type === "local_variable_declaration" || type === "formal_parameter" || type === "spread_parameter" || type === "catch_formal_parameter" || // JD-J10 (M60 java deep review, 2026-09-27): two MORE declared-variable
1458
+ // positions were missing, so the branch above did not implement the rule
1459
+ // its own comment states. Both name a type in a declaration:
1460
+ // `for (Widget b : xs)` JLS 14.14 — the for-each variable
1461
+ // `try (Widget w = open())` JLS 14.20.3 — a try-with-resources
1462
+ // Both are `enhanced_for_statement` and `resource` in this grammar, and
1463
+ // NEITHER was in the list, so neither emitted a References edge.
1464
+ //
1465
+ // Found by turning `references` comparable for the first time (JD-J3):
1466
+ // the truth derives the for-each loop type, the analyzer had no edge for
1467
+ // it, and the differential reported it as MISSING. Verified with controls
1468
+ // so the finding could not be an artifact of the probe — `Widget` in a
1469
+ // local, a formal parameter and a catch parameter all emit, and `Widget`
1470
+ // appears ONLY in the loop variable and is silently absent. (An earlier
1471
+ // probe of mine appeared to show the for-each working; it was wrong —
1472
+ // the `Object` it saw came from a `List<Object>` in the method SIGNATURE,
1473
+ // emitted as `formal_parameter`. The loop type was missing in both
1474
+ // cases. Recording that because the first measurement was the misleading
1475
+ // one.)
1476
+ type === "enhanced_for_statement" || type === "resource") {
1477
+ const typeNode = node.childForFieldName("type") || this.findChildByType(node, "catch_type") || this.findChildByType(node, "type_identifier");
1478
+ if (typeNode) {
1479
+ for (const { name: t, line } of this.collectJavaTypeNames(typeNode)) {
1480
+ if (t === "var") continue;
1481
+ relationships.push({
1482
+ source: parentContext?.name || parentContext?.symbolRef?.name || "global",
1483
+ target: t,
1484
+ type: ASTEngine.RelationshipType.References,
1485
+ confidence: 0.95,
1486
+ // M59 (#825): the line the declared TYPE is named on.
1487
+ line_number: line,
1488
+ metadata: { relationshipType: "declared_type", declaredOn: type }
1489
+ });
1490
+ }
1491
+ }
1492
+ } else if (type === "annotation" || type === "marker_annotation") {
1493
+ const annotationNameNode = this.javaAnnotationNameNode(node);
1494
+ if (annotationNameNode) {
1495
+ const annotationSources = this.javaAnnotatedDeclarationSources(node, parentContext);
1496
+ for (let annotationIdx = 0; annotationIdx < annotationSources.length; annotationIdx++) {
1497
+ const annotationSource = annotationSources[annotationIdx];
1498
+ relationships.push({
1499
+ source: annotationSource.name,
1500
+ target: annotationNameNode.text,
1501
+ type: ASTEngine.RelationshipType.References,
1502
+ confidence: 0.95,
1503
+ // M59 (#825): the annotation TYPE is named by its name node — the
1504
+ // `@` sigil and an argument list are not the name.
1505
+ line_number: annotationNameNode.startPosition.row + 1,
1506
+ metadata: {
1507
+ relationshipType: "annotation",
1508
+ isAnnotation: true,
1509
+ annotatedKind: annotationSource.declKind,
1510
+ evidenceClass: "structural"
1511
+ }
1512
+ });
1513
+ }
1514
+ }
1515
+ } else if (type === "type_parameter") {
1516
+ const boundNode = this.findChildByType(node, "type_bound");
1517
+ if (boundNode) {
1518
+ const paramNameNode = node.childForFieldName("name") || this.findChildByType(node, "type_identifier") || this.findChildByType(node, "identifier");
1519
+ const boundReadSource = parentContext?.name || parentContext?.symbolRef?.name || "global";
1520
+ for (const { name: boundType, line } of this.collectJavaTypeNames(boundNode)) {
1521
+ relationships.push({
1522
+ source: boundReadSource,
1523
+ target: boundType,
1524
+ type: ASTEngine.RelationshipType.References,
1525
+ confidence: 0.95,
1526
+ // M59 (#825): the line the BOUND type is named on.
1527
+ line_number: line,
1528
+ metadata: {
1529
+ relationshipType: "type_parameter_bound",
1530
+ ...paramNameNode ? { boundFor: paramNameNode.text } : {},
1531
+ evidenceClass: "structural"
1532
+ }
1533
+ });
1534
+ }
1535
+ }
1536
+ } else if (type === "throws") {
1537
+ for (let i = 0; i < node.namedChildCount; i++) {
1538
+ const child = node.namedChild(i);
1539
+ if (!child) continue;
1540
+ for (const { name: t, line } of this.collectJavaTypeNames(child)) {
1541
+ relationships.push({
1542
+ source: parentContext?.name || parentContext?.symbolRef?.name || "global",
1543
+ target: t,
1544
+ type: ASTEngine.RelationshipType.References,
1545
+ confidence: 0.95,
1546
+ // M59 (#825): the line the THROWN type is named on.
1547
+ line_number: line,
1548
+ metadata: { relationshipType: "throws_type" }
1549
+ });
1550
+ }
1551
+ }
1552
+ }
1553
+ if (type === "identifier") {
1554
+ const pt = node.parent?.type;
1555
+ if (pt === "class_declaration" || pt === "interface_declaration" || pt === "enum_declaration" || pt === "record_declaration") {
1556
+ const pat = node.text.endsWith("Factory") ? "factory" : node.text === "Singleton" ? "singleton" : node.text.endsWith("Builder") ? "builder" : null;
1557
+ if (pat) patterns[pat] = true;
1558
+ }
1559
+ }
1560
+ if (pushedScope) {
1561
+ stack.push({ node, parentContext: newContext, isExit: true });
1562
+ }
1563
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
1564
+ const child = node.namedChild(i);
1565
+ if (child) {
1566
+ stack.push({ node: child, parentContext: newContext });
1567
+ }
1568
+ }
1569
+ }
1570
+ this.resolveOverrides(overrideTasks, classHierarchy, symbolMap, relationships, context?.overrideCompanion, methodsByOwner);
1571
+ const primaryType = symbols.find((s) => {
1572
+ if (s.ownerName || s.scope) return false;
1573
+ return s.kind === ASTEngine.SymbolKind.Class || s.kind === ASTEngine.SymbolKind.Interface || s.kind === ASTEngine.SymbolKind.Enum;
1574
+ });
1575
+ if (primaryType) {
1576
+ for (const rel of relationships) {
1577
+ const isFileLevel = (rel.metadata ?? {}).fileLevel === true;
1578
+ if (!isFileLevel && (rel.type === ASTEngine.RelationshipType.References || rel.type === ASTEngine.RelationshipType.Imports) && (rel.source === currentPackage || rel.source === "root")) {
1579
+ rel.source = primaryType.name;
1580
+ continue;
1581
+ }
1582
+ if (!isFileLevel && rel.type === ASTEngine.RelationshipType.Imports && (rel.metadata ?? {}).isJavaImport === true && (rel.source === currentPackage || rel.source === "root")) {
1583
+ rel.source = primaryType.name;
1584
+ }
1585
+ }
1586
+ }
1587
+ const finalFrameworks = [];
1588
+ for (const k in frameworks) finalFrameworks.push(k);
1589
+ const finalPatterns = [];
1590
+ for (const k in patterns) finalPatterns.push(k);
1591
+ let hasSpring = false;
1592
+ for (let i = 0; i < finalFrameworks.length; i++) {
1593
+ if (finalFrameworks[i] === "spring") {
1594
+ hasSpring = true;
1595
+ break;
1596
+ }
1597
+ }
1598
+ if (hasSpring) {
1599
+ let hasSpringCore = false;
1600
+ for (let i = 0; i < symbols.length; i++) {
1601
+ if (this.hasJavaAnnotation(symbols[i], "Service") || this.hasJavaAnnotation(symbols[i], "Repository") || this.hasJavaAnnotation(symbols[i], "Component")) {
1602
+ hasSpringCore = true;
1603
+ break;
1604
+ }
1605
+ }
1606
+ if (hasSpringCore) finalFrameworks.push("spring_core");
1607
+ }
1608
+ for (const s of symbols) {
1609
+ const _owner = s.ownerName || s.scope;
1610
+ if (_owner) {
1611
+ const isPhantomMember = s.metadata?.isPhantom === true;
1612
+ relationships.push({
1613
+ source: _owner,
1614
+ target: s.name,
1615
+ type: ASTEngine.RelationshipType.Contains,
1616
+ confidence: isPhantomMember ? 0.6 : 1,
1617
+ // M59 (#825): a containment edge has no call site, but it does have a
1618
+ // navigable one — the member's own declaration line, taken from the
1619
+ // symbol so the edge and the symbol cannot disagree. Omitted, never
1620
+ // zeroed, when the symbol has no line (#820/#821).
1621
+ //
1622
+ // A SYNTHESISED member has no declaration at all: java's phantoms
1623
+ // (Spring-Data repository methods carry the INTERFACE's range, lombok
1624
+ // getters/setters carry the FIELD's) name methods that are written
1625
+ // nowhere in the source, so the class/field line can never be the site
1626
+ // of the name. Same contract and same reasoning as kotlin's data-class
1627
+ // phantoms — the line is omitted, not guessed.
1628
+ ...s.metadata?.isPhantom !== true && s.range?.startLine ? { line_number: s.range.startLine } : {},
1629
+ metadata: { isContained: true, ...isPhantomMember ? { inferred: true } : {} }
1630
+ });
1631
+ }
1632
+ }
1633
+ this.collectFieldAccessUses(rootNode, relationships, symbols, receiverTypes, context?.projectIndex);
1634
+ this.attachJavaImportedFrom(relationships, receiverTypes, symbols);
1635
+ this.attachJavaLibraryNamespaceCitations(relationships, symbols, receiverTypes, context?.projectIndex);
1636
+ this.attachJavaImportedBindingClaims(relationships, symbols, receiverTypes);
1637
+ this.attachJavaSingleTypeImportBindingClaims(relationships, symbols, receiverTypes, context?.projectIndex);
1638
+ for (let i = 0; i < symbols.length; i++) {
1639
+ const s = symbols[i];
1640
+ const exportNodeType = String(s.metadata?.nodeType ?? "");
1641
+ const isTopLevelTypeDecl = exportNodeType === "class_declaration" || exportNodeType === "interface_declaration" || exportNodeType === "enum_declaration" || exportNodeType === "record_declaration" || exportNodeType === "annotation_type_declaration" || // JLS 7.7 — a MODULE is an importable unit too, named by another
1642
+ // module's `requires M` clause: `module com.example.widget` is written
1643
+ // once, in this file, and that is the name a `requires` directive binds.
1644
+ // java/module-exports-jpms pins it in `exports[]` with `kind: module`.
1645
+ // (The module symbol carries no package, so the export-EDGE post-pass
1646
+ // below declines it — `exportsWithNoPackage` — rather than fabricating a
1647
+ // `package -> module` row; the Namespace symbols it owns are members and
1648
+ // ride `contains`.)
1649
+ exportNodeType === "module_declaration";
1650
+ if (!s.metadata?.isPhantom && !s.ownerName && isTopLevelTypeDecl) {
1651
+ exports.push({ name: s.name, kind: s.kind, range: s.range, visibility: s.visibility });
1652
+ }
1653
+ }
1654
+ let exportsWithNoPackage = 0;
1655
+ if (exports.length > 0) {
1656
+ const exported = /* @__PURE__ */ new Set();
1657
+ for (let i = 0; i < exports.length; i++) {
1658
+ const e = exports[i];
1659
+ const target = e.name;
1660
+ if (!target) continue;
1661
+ if (exported.has(target)) continue;
1662
+ exported.add(target);
1663
+ const isPublic = e.visibility === "public";
1664
+ if (!currentPackage) {
1665
+ exportsWithNoPackage++;
1666
+ continue;
1667
+ }
1668
+ relationships.push({
1669
+ source: currentPackage,
1670
+ target,
1671
+ type: ASTEngine.RelationshipType.Exports,
1672
+ confidence: isPublic ? 1 : 0.6,
1673
+ // M59 (#825): the exported DECLARATION's own line — the name is
1674
+ // written there, not at whatever construct encloses it. Absent rather
1675
+ // than 0 when the symbol carried no range (#823).
1676
+ ...e.range?.startLine ? { line_number: e.range.startLine } : {},
1677
+ metadata: {
1678
+ relationshipType: "java_exportable_unit",
1679
+ exportForm: isPublic ? "public_top_level_type" : "package_private_top_level_type",
1680
+ ...isPublic ? {} : { reasonCode: "package_private_top_level_type" },
1681
+ // The folded site's keys (see the note at the type-declaration
1682
+ // branch, where this edge used to be emitted a second time).
1683
+ //
1684
+ // `isJavaExport` is TRUE only on the importable tier: JLS 7.6 makes a
1685
+ // PUBLIC top-level type importable from another package, while a
1686
+ // package-private one is reachable by name only from its own package
1687
+ // (JLS 6.6.1) — which is exactly the claim the 0.6 tier and its
1688
+ // `reasonCode` discount, and marking it as an export outright would
1689
+ // assert the stronger fact.
1690
+ ...isPublic ? { isJavaExport: true } : {},
1691
+ kind: String(e.kind ?? ""),
1692
+ // The join key, stated because it is not obvious: an `exports` edge is
1693
+ // `package -> simpleName`, so `source + "." + target` reconstructs the
1694
+ // dotted path an `imports` edge targets and the two labels meet without
1695
+ // a resolver.
1696
+ dottedName: `${currentPackage}.${target}`
1697
+ }
1698
+ });
1699
+ }
1700
+ }
1701
+ this.classifyEvidenceTier(relationships);
1702
+ this.markExternalTargets(relationships, symbols, imports, context);
1703
+ return {
1704
+ symbols,
1705
+ relationships,
1706
+ imports,
1707
+ exports,
1708
+ errors,
1709
+ metadata: {
1710
+ // +1 base: the walk above counts raw decision points; the stored
1711
+ // metric is textbook cyclomatic complexity (family contract).
1712
+ complexity: cyclomaticComplexity + 1,
1713
+ linesOfCode: content.split("\n").length,
1714
+ cyclomaticComplexity: cyclomaticComplexity + 1,
1715
+ // Deterministic single-framework pick (hygiene 2026-09-08): the old
1716
+ // finalFrameworks[0] depended on import ORDER. Fixed priority list,
1717
+ // alphabetical tiebreak — same file always reports the same value.
1718
+ framework: pickJavaFramework(finalFrameworks),
1719
+ patterns: finalPatterns,
1720
+ currentPackage,
1721
+ // EXPORTS_JAVA2: the JPMS module this file declares ("" when the file is
1722
+ // not a module-info.java). Absent means "no module declared"; it never
1723
+ // means "a module nobody read" — that distinction is the whole reason the
1724
+ // field exists.
1725
+ ...moduleName ? { moduleName } : {},
1726
+ detectedFrameworks: finalFrameworks,
1727
+ frameworkPatterns: this.getJavaFrameworkPatterns(finalFrameworks, symbols),
1728
+ hasTypeInference: true,
1729
+ hasFrameworkIntelligence: true,
1730
+ analysisVersion: "4.0.0-java-single-pass-stateless"
1731
+ }
1732
+ };
1733
+ }
1734
+ /**
1735
+ * M14 #299 - evidence-tier classification for EVERY emitted relationship.
1736
+ * Tiers: structural (grammar clauses, conf 1.0) | usage_contextual (calls,
1737
+ * field access, companion-apply idioms) | import_based | reference_qualified
1738
+ * | inferred (sub-1.0 resolutions). Applied centrally at end of analyze()
1739
+ * so no emission site can forget it.
1740
+ */
1741
+ classifyEvidenceTier(relationships) {
1742
+ for (const r of relationships) {
1743
+ const md = r.metadata ?? (r.metadata = {});
1744
+ if (md.evidenceClass) continue;
1745
+ const t = String(r.type ?? r.relationshipType ?? "");
1746
+ const conf = typeof r.confidence === "number" ? r.confidence : 1;
1747
+ if (t === "contains" || t === "exports") {
1748
+ md.evidenceClass = "structural";
1749
+ continue;
1750
+ }
1751
+ if (t === "imports" || t === "brain_import") {
1752
+ md.evidenceClass = "import_based";
1753
+ continue;
1754
+ }
1755
+ if (t === "references") {
1756
+ md.evidenceClass = md.isJavaImport || md.isGroovyImport || md.isImport || md.isModuleDirective ? "import_based" : "reference_qualified";
1757
+ continue;
1758
+ }
1759
+ if (t === "extends" || t === "implements") {
1760
+ md.evidenceClass = conf >= 1 ? "structural" : "inferred";
1761
+ continue;
1762
+ }
1763
+ if (t === "overrides") {
1764
+ md.evidenceClass = conf >= 1 ? "structural" : conf >= 0.85 ? "usage_contextual" : "inferred";
1765
+ continue;
1766
+ }
1767
+ if (t === "instantiates") {
1768
+ md.evidenceClass = md.isNew || conf >= 0.9 ? "structural" : "usage_contextual";
1769
+ continue;
1770
+ }
1771
+ if (t === "imports") {
1772
+ md.evidenceClass = "import_based";
1773
+ continue;
1774
+ }
1775
+ md.evidenceClass = "usage_contextual";
1776
+ }
1777
+ }
1778
+ /**
1779
+ * EXPORTS_JAVA2: one JPMS module-directive edge.
1780
+ *
1781
+ * `source` is the module that writes the directive and `target` is the name the
1782
+ * directive names — a package for `exports`/`opens`, a module for `requires`, a
1783
+ * type for `uses`, a service and its implementations for `provides`. The line is
1784
+ * the DIRECTIVE's own line, because that is where the name is written; the
1785
+ * grammar gives the named target a span inside it, and a `to a, b` list puts
1786
+ * every target on the directive's line anyway.
1787
+ *
1788
+ * Confidence 1.0 and `reasonCode: "module_directive"`: the dependency is
1789
+ * written in the file. Nothing here is inferred — the one thing a static reader
1790
+ * cannot know (whether the required module is on the module path at compile
1791
+ * time) is not what this edge claims.
1792
+ */
1793
+ pushModuleDirective(relationships, module, targetNode, role) {
1794
+ const target = targetNode.text;
1795
+ if (!module || !target) return;
1796
+ const line = targetNode.startPosition.row >= 0 ? { line_number: targetNode.startPosition.row + 1 } : {};
1797
+ relationships.push({
1798
+ source: module,
1799
+ target,
1800
+ type: ASTEngine.RelationshipType.References,
1801
+ confidence: 1,
1802
+ ...line,
1803
+ metadata: {
1804
+ relationshipType: role,
1805
+ isModuleDirective: true,
1806
+ isJavaModuleDirective: true,
1807
+ reasonCode: "module_directive",
1808
+ moduleName: module
1809
+ }
1810
+ });
1811
+ }
1812
+ findChild(node, predicate) {
1813
+ for (let i = 0; i < node.childCount; i++) {
1814
+ const child = node.child(i);
1815
+ if (child && predicate(child)) return child;
1816
+ }
1817
+ return null;
1818
+ }
1819
+ resolveOverrides(tasks, classHierarchy, symbolMap, relationships, companion, methodsByOwner) {
1820
+ let suffixIndex = null;
1821
+ for (let taskIdx = 0; taskIdx < tasks.length; taskIdx++) {
1822
+ const task = tasks[taskIdx];
1823
+ let targetFqn = null;
1824
+ let confidence = 1;
1825
+ let reasonCode = "exact-in-file";
1826
+ if (!suffixIndex) {
1827
+ suffixIndex = /* @__PURE__ */ new Map();
1828
+ for (const fqn of classHierarchy.keys()) {
1829
+ const bare = fqn.slice(fqn.lastIndexOf(".") + 1);
1830
+ const arr = suffixIndex.get(bare);
1831
+ if (arr) arr.push(fqn);
1832
+ else suffixIndex.set(bare, [fqn]);
1833
+ }
1834
+ }
1835
+ const queue = [];
1836
+ const ownerH = classHierarchy.get(task.ownerFqn);
1837
+ if (ownerH) {
1838
+ for (let i = 0; i < ownerH.length; i++) queue.push(ownerH[i]);
1839
+ }
1840
+ const seen = /* @__PURE__ */ new Set();
1841
+ let head = 0;
1842
+ while (head < queue.length && !targetFqn) {
1843
+ const parent = queue[head++];
1844
+ if (seen.has(parent)) continue;
1845
+ seen.add(parent);
1846
+ const candidates = suffixIndex.get(parent) ?? [parent];
1847
+ for (const cand of candidates) {
1848
+ const potentialTargetFqn = `${cand}.${task.methodName}${task.signature}`;
1849
+ const baseSym = symbolMap.get(potentialTargetFqn);
1850
+ if (baseSym) {
1851
+ if (baseSym.visibility !== "private") {
1852
+ targetFqn = potentialTargetFqn;
1853
+ break;
1854
+ }
1855
+ continue;
1856
+ }
1857
+ const members = methodsByOwner?.get(cand)?.get(task.methodName);
1858
+ if (members) {
1859
+ for (const member of members) {
1860
+ if (member.visibility === "private") continue;
1861
+ const params = member.parameters ?? [];
1862
+ if (params.length !== task.arity) continue;
1863
+ const baseSig = `(${params.map((p) => p.type || "unknown").join(",")})`;
1864
+ if (!javaSigHasTypeVar(baseSig) && !javaSigHasTypeVar(task.signature)) continue;
1865
+ targetFqn = `${cand}.${task.methodName}${task.signature}`;
1866
+ confidence = 0.9;
1867
+ reasonCode = "generic-substitution";
1868
+ break;
1869
+ }
1870
+ if (targetFqn) break;
1871
+ }
1872
+ }
1873
+ if (targetFqn) break;
1874
+ for (const cand of candidates) {
1875
+ const pH = classHierarchy.get(cand);
1876
+ if (pH) {
1877
+ for (let i = 0; i < pH.length; i++) queue.push(pH[i]);
1878
+ }
1879
+ }
1880
+ }
1881
+ if (!targetFqn && companion && ownerH && ownerH.length > 0) {
1882
+ const declaring = findDeclaringParent(
1883
+ task.methodName,
1884
+ ownerH,
1885
+ companion,
1886
+ (t) => classHierarchy.get(t) ?? []
1887
+ );
1888
+ if (declaring) {
1889
+ targetFqn = `${declaring}.${task.methodName}${task.signature}`;
1890
+ confidence = 0.85;
1891
+ reasonCode = "companion-index";
1892
+ }
1893
+ }
1894
+ if (!targetFqn && task.explicit && ownerH && ownerH.length > 0) {
1895
+ targetFqn = `${ownerH[0]}.${task.methodName}${task.signature}`;
1896
+ confidence = 0.5;
1897
+ reasonCode = "first-parent-guess";
1898
+ }
1899
+ if (targetFqn) {
1900
+ const localSource = localEdgeLabel(task.fqn);
1901
+ const localTarget = localEdgeName(targetFqn);
1902
+ const localQualified = localEdgeLabel(targetFqn);
1903
+ const declaringType = localQualified.lastIndexOf(".") > 0 ? localQualified.slice(0, localQualified.lastIndexOf(".")) : "";
1904
+ relationships.push({
1905
+ source: localSource,
1906
+ target: localTarget,
1907
+ type: ASTEngine.RelationshipType.Overrides,
1908
+ confidence,
1909
+ // M59 (#825): the override occurs at the OVERRIDING declaration, which
1910
+ // the task carries from the node itself. Omitted, never zeroed, when it
1911
+ // is somehow absent (#821).
1912
+ ...task.line ? { line_number: task.line } : {},
1913
+ metadata: {
1914
+ relationshipType: "method_override",
1915
+ isJavaOverride: true,
1916
+ reasonCode,
1917
+ ...declaringType ? { declaringType } : {}
1918
+ }
1919
+ });
1920
+ }
1921
+ }
1922
+ }
1923
+ processPhantomSymbols(scope, symbols, symbolMap, frameworks) {
1924
+ const isSpringData = frameworks["spring_data"] || frameworks["spring"];
1925
+ if (isSpringData && scope.isInterface) {
1926
+ let isRepo = false;
1927
+ for (let i = 0; i < scope.parents.length; i++) {
1928
+ if (scope.parents[i].indexOf("JpaRepository") !== -1 || scope.parents[i].indexOf("CrudRepository") !== -1) {
1929
+ isRepo = true;
1930
+ break;
1931
+ }
1932
+ }
1933
+ if (isRepo) {
1934
+ const defaultMethods = ["save", "findById", "findAll", "deleteById", "delete", "count"];
1935
+ for (let j = 0; j < defaultMethods.length; j++) {
1936
+ const method = defaultMethods[j];
1937
+ const sym = {
1938
+ name: method,
1939
+ kind: ASTEngine.SymbolKind.Method,
1940
+ scope: scope.name,
1941
+ ownerName: scope.name,
1942
+ ownerKind: ASTEngine.SymbolKind.Interface,
1943
+ modifiers: ["phantom", "framework_generated", "spring-data"],
1944
+ range: scope.node ? this.nodeToRange(scope.node) : { startLine: 0, startColumn: 0, endLine: 0, endColumn: 0 },
1945
+ // Payload consistency (claim 21): the symbolMap key carries the
1946
+ // REAL signature (findById(Object), save(Object)) while the symbol
1947
+ // payload said parameters:[] — resolution and display disagreed.
1948
+ // A phantom method with an arity-carrying key has arity.
1949
+ parameters: method === "findById" || method === "deleteById" || method === "save" || method === "delete" ? [{ name: "entity", type: "Object", isOptional: false }] : [],
1950
+ returnType: "Object",
1951
+ metadata: { isPhantom: true }
1952
+ };
1953
+ symbols.push(sym);
1954
+ let phantomSig = "()";
1955
+ if (method === "findById" || method === "deleteById" || method === "save" || method === "delete") {
1956
+ phantomSig = "(Object)";
1957
+ }
1958
+ symbolMap.set(`${scope.fqn}.${method}${phantomSig}`, sym);
1959
+ }
1960
+ }
1961
+ }
1962
+ if (!scope.isInterface && !scope.isEnum) {
1963
+ let isDataOrGetter = false;
1964
+ let isDataOrSetter = false;
1965
+ for (let i = 0; i < scope.annotations.length; i++) {
1966
+ const d = scope.annotations[i];
1967
+ let shortName = d.startsWith("@") ? d.slice(1) : d;
1968
+ const paren = shortName.indexOf("(");
1969
+ if (paren !== -1) shortName = shortName.slice(0, paren);
1970
+ const lastDot = shortName.lastIndexOf(".");
1971
+ if (lastDot !== -1) shortName = shortName.slice(lastDot + 1);
1972
+ if (shortName === "Data" || shortName === "Getter") isDataOrGetter = true;
1973
+ if (shortName === "Data" || shortName === "Setter") isDataOrSetter = true;
1974
+ }
1975
+ if (isDataOrGetter || isDataOrSetter) {
1976
+ for (let idx = 0; idx < scope.fields.length; idx++) {
1977
+ const prop = scope.fields[idx];
1978
+ const capitalizedName = prop.name.charAt(0).toUpperCase() + prop.name.slice(1);
1979
+ if (isDataOrGetter) {
1980
+ const sym = {
1981
+ name: `get${capitalizedName}`,
1982
+ kind: ASTEngine.SymbolKind.Method,
1983
+ scope: scope.name,
1984
+ ownerName: scope.name,
1985
+ ownerKind: ASTEngine.SymbolKind.Class,
1986
+ modifiers: ["phantom", "lombok_generated"],
1987
+ range: prop.range,
1988
+ parameters: [],
1989
+ returnType: prop.returnType || "Object",
1990
+ metadata: { isPhantom: true }
1991
+ };
1992
+ symbols.push(sym);
1993
+ symbolMap.set(`${scope.fqn}.get${capitalizedName}()`, sym);
1994
+ }
1995
+ if (isDataOrSetter) {
1996
+ const sym = {
1997
+ name: `set${capitalizedName}`,
1998
+ kind: ASTEngine.SymbolKind.Method,
1999
+ scope: scope.name,
2000
+ ownerName: scope.name,
2001
+ ownerKind: ASTEngine.SymbolKind.Class,
2002
+ modifiers: ["phantom", "lombok_generated"],
2003
+ range: prop.range,
2004
+ parameters: [{ name: "value", type: prop.returnType || "Object" }],
2005
+ returnType: "void",
2006
+ metadata: { isPhantom: true }
2007
+ };
2008
+ symbols.push(sym);
2009
+ symbolMap.set(`${scope.fqn}.set${capitalizedName}(${prop.returnType || "Object"})`, sym);
2010
+ }
2011
+ }
2012
+ }
2013
+ }
2014
+ }
2015
+ detectFrameworksFromImport(mod, frameworks) {
2016
+ if (mod.startsWith("org.springframework")) frameworks["spring"] = true;
2017
+ if (mod.startsWith("org.springframework.web")) frameworks["spring_mvc"] = true;
2018
+ if (mod.startsWith("org.springframework.data")) frameworks["spring_data"] = true;
2019
+ if (mod.startsWith("javax.persistence") || mod.startsWith("jakarta.persistence")) frameworks["jpa"] = true;
2020
+ if (mod.startsWith("org.hibernate")) frameworks["hibernate"] = true;
2021
+ if (mod.startsWith("org.mybatis")) frameworks["mybatis"] = true;
2022
+ if (mod.startsWith("io.quarkus")) frameworks["quarkus"] = true;
2023
+ if (mod.startsWith("io.micronaut")) frameworks["micronaut"] = true;
2024
+ if (mod.startsWith("org.junit")) frameworks["junit"] = true;
2025
+ if (mod.startsWith("org.mockito")) frameworks["mockito"] = true;
2026
+ if (mod.startsWith("javax.servlet") || mod.startsWith("jakarta.servlet")) frameworks["servlet"] = true;
2027
+ if (mod.startsWith("javax.ws.rs") || mod.startsWith("jakarta.ws.rs")) frameworks["jax_rs"] = true;
2028
+ }
2029
+ mapTypeToSymbolKind(type) {
2030
+ if (type === "class_declaration" || type === "record_declaration") return ASTEngine.SymbolKind.Class;
2031
+ if (type === "interface_declaration") return ASTEngine.SymbolKind.Interface;
2032
+ if (type === "enum_declaration") return ASTEngine.SymbolKind.Enum;
2033
+ return ASTEngine.SymbolKind.Class;
2034
+ }
2035
+ buildFqn(pkg, scopeStack, name) {
2036
+ const parts = [];
2037
+ if (pkg) parts.push(pkg);
2038
+ for (let idx = 0; idx < scopeStack.length; idx++) {
2039
+ const scope = scopeStack[idx];
2040
+ if (scope.kind !== ASTEngine.SymbolKind.Method) {
2041
+ parts.push(scope.name);
2042
+ }
2043
+ }
2044
+ parts.push(name);
2045
+ return parts.join(".");
2046
+ }
2047
+ /**
2048
+ * Javadoc extraction: block_comment starting with '/**' immediately
2049
+ * preceding the declaration (previous sibling). Previously java symbols
2050
+ * carried NO comments - docstrings were silently lost (M14 #383).
2051
+ */
2052
+ /**
2053
+ * Per-method cyclomatic complexity (1 + decision points). Previously java
2054
+ * method symbols stored complexity=1 always - only the file-level counter
2055
+ * was real. Kotlin parity (M14 #301 prerequisite).
2056
+ */
2057
+ calculateJavaMethodComplexity(node) {
2058
+ let complexity = 1;
2059
+ const stack = [node];
2060
+ while (stack.length > 0) {
2061
+ const n = stack.pop();
2062
+ if (n.type === "if_statement" || n.type === "for_statement" || n.type === "enhanced_for_statement" || n.type === "while_statement" || n.type === "do_statement" || n.type === "switch_rule" || n.type === "switch_block_statement_group" || n.type === "catch_clause" || n.type === "ternary_expression") {
2063
+ complexity++;
2064
+ } else if (n.type === "binary_expression") {
2065
+ const op = n.childForFieldName("operator")?.text;
2066
+ if (op === "&&" || op === "||") complexity++;
2067
+ }
2068
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
2069
+ const child = n.namedChild(i);
2070
+ if (child) stack.push(child);
2071
+ }
2072
+ }
2073
+ return complexity;
2074
+ }
2075
+ extractJavadocComment(node) {
2076
+ const parent = node.parent;
2077
+ if (!parent) return void 0;
2078
+ let selfIdx = -1;
2079
+ for (let i = 0; i < parent.namedChildCount; i++) {
2080
+ const c = parent.namedChild(i);
2081
+ if (c && c.startIndex === node.startIndex && c.endIndex === node.endIndex) {
2082
+ selfIdx = i;
2083
+ break;
2084
+ }
2085
+ }
2086
+ for (let i = selfIdx - 1; i >= 0; i--) {
2087
+ const sibling = parent.namedChild(i);
2088
+ if (!sibling) continue;
2089
+ if (sibling.type === "block_comment" && sibling.text.startsWith("/**")) return sibling.text;
2090
+ break;
2091
+ }
2092
+ return void 0;
2093
+ }
2094
+ nodeToRange(node) {
2095
+ return {
2096
+ startLine: node.startPosition.row + 1,
2097
+ startColumn: node.startPosition.column + 1,
2098
+ endLine: node.endPosition.row + 1,
2099
+ endColumn: node.endPosition.column + 1,
2100
+ startIndex: node.startIndex,
2101
+ endIndex: node.endIndex
2102
+ };
2103
+ }
2104
+ /**
2105
+ * M59 (#825): the `line_number` field for a relationship, taken from the node
2106
+ * that CARRIES THE NAME the edge is about.
2107
+ *
2108
+ * java shared scala's defect class in full: a `method_invocation` starts at
2109
+ * its receiver, a type expression's start is not the line of the types inside
2110
+ * it, and a definition node can start at its modifiers. The rule that holds in
2111
+ * all cases is one sentence — the line belongs to the name, never to the
2112
+ * construct that contains it — so callers pass the NAME node and this returns
2113
+ * nothing at all when there is none (never 0: #820/#821, 0 means "unknown" to
2114
+ * every reader, so unknown must be absent).
2115
+ *
2116
+ * `fallback` exists for sites where the analyzed construct has no name-bearing
2117
+ * child at all (a `this(...)`/`super(...)` delegation, a `new` with a diamond).
2118
+ */
2119
+ lineField(nameNode, fallback) {
2120
+ const n = nameNode ?? fallback;
2121
+ return n ? { line_number: n.startPosition.row + 1 } : {};
2122
+ }
2123
+ /**
2124
+ * M59 (#825): the node whose start is the DECLARATION's own line.
2125
+ *
2126
+ * An annotation is a modifier — `@SuppressWarnings("unused")` decorates the
2127
+ * class, it is not where the class is written. Measured, not assumed: on a
2128
+ * 300-file real java sample, **25 of 93 symbols (27%)** had their line on an
2129
+ * annotation, so a tool navigating to a java symbol landed on `@Override` or
2130
+ * `@SuppressWarnings` instead of the declaration. tree-sitter-java nests the
2131
+ * annotations in a `modifiers` child, which is what is skipped here; the
2132
+ * contract is scala's (settled first and pinned there), applied to java's node
2133
+ * shape so kotlin and groovy can copy one rule instead of two.
2134
+ *
2135
+ * The symbol range and its `contains` edge move together, exactly as in scala:
2136
+ * a symbol and the edge that names it must never disagree about the line.
2137
+ */
2138
+ declarationLineNode(node) {
2139
+ const named = this.declarationNameNode(node);
2140
+ if (named) return named;
2141
+ for (let i = 0; i < node.namedChildCount; i++) {
2142
+ const c = node.namedChild(i);
2143
+ if (!c) continue;
2144
+ if (c.type === "modifiers") continue;
2145
+ if (c.type === "line_comment" || c.type === "block_comment") continue;
2146
+ return c;
2147
+ }
2148
+ return node;
2149
+ }
2150
+ /**
2151
+ * The NAME node of a declaration, or null when the shape has none.
2152
+ *
2153
+ * `childForFieldName("name")` is the grammar's own answer for every shape that
2154
+ * has one — class/interface/enum/record/annotation-type declarations, methods,
2155
+ * constructors, enum constants and type parameters. A FIELD is the exception:
2156
+ * its name lives in a nested `variable_declarator`, so the declarator is asked
2157
+ * instead. Multi-declarator fields (`int a, b;`) carry one declarator each; the
2158
+ * FIRST one is the declaration's own line, which is also the line the field
2159
+ * symbol is published under.
2160
+ */
2161
+ declarationNameNode(node) {
2162
+ const field = node.childForFieldName?.("name");
2163
+ if (field && (field.type === "identifier" || field.type === "type_identifier")) return field;
2164
+ if (node.type === "field_declaration") {
2165
+ const declarator = this.findChildByType(node, "variable_declarator");
2166
+ const name = declarator?.childForFieldName?.("name");
2167
+ if (name) return name;
2168
+ }
2169
+ return null;
2170
+ }
2171
+ /**
2172
+ * M59 (#825): the line of the ANNOTATION that names this edge's target.
2173
+ *
2174
+ * Annotation-derived edges (a JPA table, a mapped column, a FeignClients
2175
+ * service, a `@Value` config key, a route) carry a target whose NAME IS WRITTEN
2176
+ * INSIDE THE ANNOTATION. The declaration's line is not where that name is, so
2177
+ * pointing the edge at the declaration would fail the very source check this
2178
+ * milestone exists for — the check looks for the target name on the claimed
2179
+ * line. Returns undefined when the annotation is absent, and callers omit the
2180
+ * field rather than writing 0 (#821).
2181
+ */
2182
+ annotationLine(node, name, targetName) {
2183
+ const mods = this.findChildByType(node, "modifiers") ?? node;
2184
+ for (let i = 0; i < mods.namedChildCount; i++) {
2185
+ const c = mods.namedChild(i);
2186
+ if (!c) continue;
2187
+ if (c.type !== "annotation" && c.type !== "marker_annotation") continue;
2188
+ const id = c.childForFieldName?.("name") ?? this.findChildByType(c, "identifier") ?? this.findChildByType(c, "scoped_identifier");
2189
+ const leaf = id ? String(id.text).split(".").pop() : "";
2190
+ if (leaf === name) {
2191
+ if (targetName) {
2192
+ const quoted = `"${targetName}"`;
2193
+ const found = this.findStringToken(c, quoted);
2194
+ if (found) return found.startPosition.row + 1;
2195
+ }
2196
+ return c.startPosition.row + 1;
2197
+ }
2198
+ }
2199
+ return void 0;
2200
+ }
2201
+ /** First descendant whose text is exactly the quoted token (the string literal that carries an annotation target name). */
2202
+ findStringToken(node, quoted) {
2203
+ if (node.text === quoted) return node;
2204
+ for (let i = 0; i < node.namedChildCount; i++) {
2205
+ const c = node.namedChild(i);
2206
+ if (!c) continue;
2207
+ const hit = this.findStringToken(c, quoted);
2208
+ if (hit) return hit;
2209
+ }
2210
+ return null;
2211
+ }
2212
+ /** The range of a SYMBOL (not of an import or an error): annotations skipped. */
2213
+ symbolRange(node) {
2214
+ const decl = this.declarationLineNode(node);
2215
+ const base = this.nodeToRange(node);
2216
+ return {
2217
+ ...base,
2218
+ startLine: decl.startPosition.row + 1,
2219
+ startColumn: decl.startPosition.column + 1,
2220
+ startIndex: decl.startIndex
2221
+ };
2222
+ }
2223
+ findChildByType(node, type) {
2224
+ for (let i = 0; i < node.namedChildCount; i++) {
2225
+ const child = node.namedChild(i);
2226
+ if (child && child.type === type) return child;
2227
+ }
2228
+ return null;
2229
+ }
2230
+ findAllChildrenByType(node, type) {
2231
+ const results = [];
2232
+ for (let i = 0; i < node.namedChildCount; i++) {
2233
+ const child = node.namedChild(i);
2234
+ if (child && child.type === type) results.push(child);
2235
+ }
2236
+ return results;
2237
+ }
2238
+ /**
2239
+ * All named types referenced inside a type subtree: the base of a generic
2240
+ * (`List<X>` → List), each type argument recursively (`Map<String,
2241
+ * List<X>>` → Map, String, X), scoped names in full dotted form per the
2242
+ * M14 dotted-name convention (`java.util.List` stays dotted), and array
2243
+ * element types. Union catch types are visited via their catch_type parent.
2244
+ * Primitives never match these node shapes and are correctly excluded.
2245
+ */
2246
+ /**
2247
+ * M59 (#825): `{name, line}` pairs, not bare names.
2248
+ *
2249
+ * Every edge these names feed is a `references` edge, and its line must be the
2250
+ * line the TYPE IS NAMED — not the line the enclosing type expression starts
2251
+ * on. java writes long type expressions across lines (`Map<String,\n List<X>>`),
2252
+ * so the two differ in exactly the places a tool would point at the wrong code.
2253
+ * The shape is the one scala settled on for the same reason: collect the node
2254
+ * with the name, then the line cannot be taken from the parent by accident.
2255
+ */
2256
+ collectJavaTypeNames(node) {
2257
+ const out = [];
2258
+ const seen = /* @__PURE__ */ new Set();
2259
+ const visit = (n) => {
2260
+ if (n.type === "type_identifier" || n.type === "scoped_type_identifier") {
2261
+ const nm = n.type === "type_identifier" ? n.text : javaTypeBaseName(n);
2262
+ if (nm && !seen.has(nm)) {
2263
+ seen.add(nm);
2264
+ out.push({ name: nm, line: n.startPosition.row + 1 });
2265
+ }
2266
+ return;
2267
+ }
2268
+ for (let i = 0; i < n.namedChildCount; i++) {
2269
+ const child = n.namedChild(i);
2270
+ if (child) visit(child);
2271
+ }
2272
+ };
2273
+ visit(node);
2274
+ return out;
2275
+ }
2276
+ /**
2277
+ * THE IMPORTED-BINDING CLAIM FOR STATIC IMPORTS (Lead claim kind, live 2026-10-19).
2278
+ *
2279
+ * Two halves, and BOTH are needed because the arbiter verifies one against the other:
2280
+ *
2281
+ * 1. EVIDENCE ON THE IMPORT EDGE. For every `import static pkg.Type.member;` the analyzer
2282
+ * publishes `metadata.importedBindings = [member]` and `metadata.module = pkg.Type` (the
2283
+ * CONTAINER as written — the gate's evidence map is keyed by exactly that specifier). The
2284
+ * written target stays the full member path; this is additive metadata.
2285
+ *
2286
+ * 2. THE CLAIM ON THE USE EDGE. A calls row whose call expression is a BARE name (no receiver)
2287
+ * or a uses row that is a BARE READ, whose target is the bound name and whose import's
2288
+ * container root is EXTERNAL, carries
2289
+ * metadata.importedBinding = { specifier: pkg.Type, binding: member }.
2290
+ * The arbiter verifies it against the evidence map (own column: bindOK), never folds it into
2291
+ * resolved, and refutes a claim whose evidence contradicts it.
2292
+ *
2293
+ * REFUSALS, each a way the claim could become FALSE: a container that is NOT external (a project
2294
+ * class's static member is project code and must resolve internally); a name the FILE itself
2295
+ * declares (the declaration shadows the import — the call/read is not the imported binding); a
2296
+ * receiver-carrying call (its target is a member leaf, not the bound name); an on-demand static
2297
+ * import (`import static X.*` binds no name this file states).
2298
+ */
2299
+ attachJavaImportedBindingClaims(relationships, symbols, index) {
2300
+ if (index.staticImportPaths.size === 0) return;
2301
+ const fileDeclared = /* @__PURE__ */ new Set();
2302
+ for (const s of symbols) if (s && s.name) fileDeclared.add(s.name);
2303
+ const isExternalContainer = (container) => {
2304
+ for (const root of JAVA_EXTERNAL_STATIC_IMPORT_ROOTS) if (container.startsWith(root)) return true;
2305
+ for (const root of JAVA_LIBRARY_NAMESPACE_CITATIONS) if (container.startsWith(`${root}.`)) return true;
2306
+ return false;
2307
+ };
2308
+ for (const r of relationships) {
2309
+ if (String(r.type ?? "") !== "imports") continue;
2310
+ const md = r.metadata;
2311
+ if (!md || typeof md !== "object") continue;
2312
+ if (md.isStatic !== true || md.isWildcard === true) continue;
2313
+ const target = String(r.target ?? "");
2314
+ const dot = target.lastIndexOf(".");
2315
+ if (dot <= 0 || dot >= target.length - 1) continue;
2316
+ const container = target.slice(0, dot);
2317
+ const member = target.slice(dot + 1);
2318
+ if (!isExternalContainer(container)) continue;
2319
+ md.importedBindings = [member];
2320
+ md.module = container;
2321
+ }
2322
+ for (const r of relationships) {
2323
+ const label = String(r.type ?? "");
2324
+ if (label !== "calls" && label !== "uses") continue;
2325
+ const md = r.metadata;
2326
+ if (!md || typeof md !== "object") continue;
2327
+ if (md.importedBinding !== void 0) continue;
2328
+ const target = String(r.target ?? "");
2329
+ if (!isPlainJavaIdentifier(target)) continue;
2330
+ if (fileDeclared.has(target)) continue;
2331
+ const container = index.staticImportPaths.get(target);
2332
+ if (!container || !isExternalContainer(container)) continue;
2333
+ if (label === "calls") {
2334
+ const callExpr = typeof md.javaCallExpression === "string" ? md.javaCallExpression : "";
2335
+ if (callExpr.length === 0 || callExpr.indexOf(".") !== -1) continue;
2336
+ } else if (md.isBareRead !== true) {
2337
+ continue;
2338
+ }
2339
+ md.importedBinding = { specifier: container, binding: target };
2340
+ }
2341
+ }
2342
+ /**
2343
+ * THE PROJECT'S OWN KEY SPACE, read the way the ARBITER reads it: how many declarations answer
2344
+ * to a name. The arbiter's key space counts every published symbol by its SIMPLE name — a type,
2345
+ * a method, a field, a constant, an enum constant, a constructor — and a declaration's simple
2346
+ * name is exactly what the name-keyed resolver looks up.
2347
+ *
2348
+ * THE INDEX'S TWO CHANNELS ARE THE SAME COUNT, SPLIT. `symbolCounts` holds the TYPES by name;
2349
+ * `typeMembers` holds the MEMBERS by owner. A name the project declares ONCE as a type and ONCE
2350
+ * as a field — the `public static final Foo Foo = new Foo();` idiom, which is what most of the
2351
+ * lane's AMBIGUOUS type-shaped rows are (measured: 34,306 of 39,779) — answers to the name
2352
+ * TWICE, and the type channel alone would say once. So the count is the sum of the two, and it
2353
+ * is cached per index because the index is per REPOSITORY and the answer is per name.
2354
+ */
2355
+ static javaDeclCountCache = /* @__PURE__ */ new WeakMap();
2356
+ static javaDeclCounts(index) {
2357
+ const hit = _RealJavaAnalyzer.javaDeclCountCache.get(index);
2358
+ if (hit) return hit;
2359
+ const counts = /* @__PURE__ */ new Map();
2360
+ for (const [name, n] of index.symbolCounts) counts.set(name, (counts.get(name) ?? 0) + n);
2361
+ for (const members of index.typeMembers.values()) {
2362
+ for (const name of members) counts.set(name, (counts.get(name) ?? 0) + 1);
2363
+ }
2364
+ _RealJavaAnalyzer.javaDeclCountCache.set(index, counts);
2365
+ return counts;
2366
+ }
2367
+ /**
2368
+ * THE EXTERNAL SINGLE-TYPE-IMPORT BINDING CLAIM (java lane, 2026-10-20).
2369
+ *
2370
+ * THE SHAPE IS THE ARBITER'S, NOT THIS LANE'S. `metadata.importedBinding = { specifier, binding }`
2371
+ * says *"the name `binding` is bound, in this file, by the import `specifier`"*, and the arbiter
2372
+ * verifies it against the run's OWN import evidence — the `importedBindings` + `module` metadata
2373
+ * an `imports` edge publishes — in its OWN column (`bindOK`), never in `resolved`. The
2374
+ * static-import arm proved the shape on this lane: **391 verified claims, bindFAIL 0** (the
2375
+ * `java/library-import-binding` fixture, 76/0). This method applies the SAME shape to the OTHER
2376
+ * import form java has: the SINGLE-TYPE import, `import java.util.List;`, which binds the simple
2377
+ * name `List`.
2378
+ *
2379
+ * WHY IT IS A TRUE STATEMENT (JLS 6.4.1). A single-type-import declaration d in a compilation unit
2380
+ * c of package p that imports a type named n shadows, throughout c, the declarations of any top
2381
+ * level type named n declared in another compilation unit of p, and any type named n imported on
2382
+ * demand in c. A type in another package is not in scope at all unless it is imported. So INSIDE
2383
+ * THE FILE the simple name is UNAMBIGUOUS: it names the imported type and nothing else. The
2384
+ * store's key space is NAME-KEYED and repo-wide, so it answers a different question ("how many
2385
+ * declarations in this repo are called `List`?") — which is why the row stays a tie, and why the
2386
+ * tie is a NAME-MODEL artefact rather than a fact about the file.
2387
+ *
2388
+ * THE CLAIM IS A TIE-BREAKER, AND IT IS ONLY EMITTED WHERE A TIE EXISTS. Three conditions, each
2389
+ * one a way the claim could become FALSE or pointless:
2390
+ *
2391
+ * 1. **THE PROJECT'S KEY SPACE GIVES THE SIMPLE NAME MORE THAN ONE ANSWER.** This is the wall
2392
+ * the claim exists to close. With EXACTLY ONE answer the name model RESOLVES the row, and a
2393
+ * claim there would trade a resolution for a claim — the campaign's "claims are never
2394
+ * merged into resolution", read in the direction that keeps the strongest class. With NO
2395
+ * answer the analyzer's own external claim ("the project declares no such name") already
2396
+ * verifies the row, and a second claim on it would replace one verified claim with another
2397
+ * and move the row between two verified columns for no gain at all. So the claim is emitted
2398
+ * for the TIE and for nothing else.
2399
+ * **A COMPLETE PROJECT INDEX IS REQUIRED TO SEE A TIE AT ALL**, so the rule is fail-closed:
2400
+ * no complete index ⇒ no claim ⇒ the no-index arm is byte-identical to its previous value.
2401
+ * 2. **THE IMPORTED TYPE IS OUTSIDE THE PROJECT.** With the complete index in hand, the
2402
+ * project must NOT declare the qualified path: the index saw the project's whole admitted
2403
+ * file set, so "the project declares no such type" is a STATEMENT and not a guess — and it
2404
+ * REFUTES the claim when the project DOES declare it. A project-internal import names
2405
+ * project code, which must resolve through the owner/name model.
2406
+ * 3. **THE FILE DOES NOT DECLARE THE NAME, AND NO STATIC IMPORT BINDS IT.** A name the file
2407
+ * declares shadows the import (JLS 6.4.1); a name a static import also binds is the static
2408
+ * arm's shape (two imports of one name is a compile error).
2409
+ *
2410
+ * AND THE CLAIM IS ONLY PUT ON A ROW WHERE THE TARGET IS THE BOUND NAME, IN A TYPE POSITION.
2411
+ * `references` / `instantiates` / `extends` / `implements` only: a read or a call target is a
2412
+ * member leaf, and a local variable may shadow a type name in an expression (JLS 6.5.5.1), so
2413
+ * only a type position is safe. A dotted target is not the bound name either.
2414
+ */
2415
+ attachJavaSingleTypeImportBindingClaims(relationships, symbols, index, projectIndex) {
2416
+ if (projectIndex?.complete !== true) return;
2417
+ if (index.importSinglePaths.size === 0) return;
2418
+ const fileDeclared = /* @__PURE__ */ new Set();
2419
+ for (const s of symbols) if (s && s.name) fileDeclared.add(s.name);
2420
+ const declCounts = _RealJavaAnalyzer.javaDeclCounts(projectIndex);
2421
+ const eligible = /* @__PURE__ */ new Map();
2422
+ for (const [leaf, path] of index.importSinglePaths) {
2423
+ if (leaf.length === 0 || path.length === 0) continue;
2424
+ if (fileDeclared.has(leaf)) continue;
2425
+ if (index.staticImportPaths.has(leaf)) continue;
2426
+ if ((declCounts.get(leaf) ?? 0) < 2) continue;
2427
+ if (projectIndex.typeKinds.has(path) || projectIndex.symbolNames.has(path)) continue;
2428
+ eligible.set(leaf, path);
2429
+ }
2430
+ if (eligible.size === 0) return;
2431
+ for (const r of relationships) {
2432
+ if (String(r.type ?? "") !== "imports") continue;
2433
+ const md = r.metadata;
2434
+ if (!md || typeof md !== "object") continue;
2435
+ if (md.isStatic === true || md.isWildcard === true) continue;
2436
+ const target = String(r.target ?? "");
2437
+ const dot = target.lastIndexOf(".");
2438
+ if (dot <= 0 || dot >= target.length - 1) continue;
2439
+ const leaf = target.slice(dot + 1);
2440
+ const path = eligible.get(leaf);
2441
+ if (!path || path !== target) continue;
2442
+ md.importedBindings = [leaf];
2443
+ md.module = path;
2444
+ }
2445
+ const TYPE_LABELS = /* @__PURE__ */ new Set(["references", "instantiates", "extends", "implements"]);
2446
+ for (const r of relationships) {
2447
+ const label = String(r.type ?? "");
2448
+ if (!TYPE_LABELS.has(label)) continue;
2449
+ const md = r.metadata;
2450
+ if (!md || typeof md !== "object") continue;
2451
+ if (md.importedBinding !== void 0) continue;
2452
+ const target = String(r.target ?? "");
2453
+ if (!isPlainJavaIdentifier(target)) continue;
2454
+ const path = eligible.get(target);
2455
+ if (!path) continue;
2456
+ md.importedBinding = { specifier: path, binding: target };
2457
+ }
2458
+ }
2459
+ /**
2460
+ * LIBRARY-NAMESPACE CITATIONS — attach the arbiter's `languageNamespace` claim to targets whose
2461
+ * LEADING SEGMENTS name a root in the shared registry `java-platform-libraries`.
2462
+ *
2463
+ * WHAT IT MOVES AND WHAT IT CANNOT. The arbiter's namespace claim is a CITATION: a target whose
2464
+ * leading segments equal a declared root is `externalVerified` (`verifiedBy: "namespace"`), is
2465
+ * counted in `extOK`, and is NEVER folded into `resolved`. It is refutable by construction (a
2466
+ * root not in the declared list REFUTES), which is why the declared list is the registry's
2467
+ * subset and nothing else. A BARE target (a type name with no dots) can never match a prefix
2468
+ * segment and is therefore NOT claimed here — those rows need the imported-binding claim kind
2469
+ * (Lead task-19), which is why the depth wave leaves them as a sized residual.
2470
+ *
2471
+ * Measured before this: the depth residual `ext?` 2,446 carried 1,160 non-reserved dotted rows
2472
+ * (jakarta 1,114 · androidx.media3 10 · org.yaml 4 · dotty.tools 2) whose roots are exactly this
2473
+ * registry entry. Nothing is claimed for roots outside it.
2474
+ */
2475
+ attachJavaLibraryNamespaceCitations(relationships, symbols, index, projectIndex) {
2476
+ if (projectIndex?.complete !== true) return;
2477
+ const projectOwnsQualified = (qualified) => projectIndex.typeKinds.has(qualified) || projectIndex.symbolNames.has(qualified);
2478
+ const fileDeclared = /* @__PURE__ */ new Set();
2479
+ for (const s of symbols) if (s && s.name) fileDeclared.add(s.name);
2480
+ const roots = [...JAVA_LIBRARY_NAMESPACE_CITATIONS].sort((a, b) => b.length - a.length);
2481
+ for (const r of relationships) {
2482
+ const md = r.metadata;
2483
+ if (!md || typeof md !== "object") continue;
2484
+ if (md.languageNamespace !== void 0) continue;
2485
+ const target = String(r.target ?? "");
2486
+ if (target.length === 0) continue;
2487
+ for (const root of roots) {
2488
+ if (target !== root && !target.startsWith(`${root}.`)) continue;
2489
+ if (projectOwnsQualified(target)) return;
2490
+ md.languageNamespace = [root];
2491
+ break;
2492
+ }
2493
+ if (md.languageNamespace !== void 0) continue;
2494
+ if (fileDeclared.has(target)) continue;
2495
+ const importedPath = index.importSinglePaths.get(target);
2496
+ if (!importedPath) continue;
2497
+ for (const root of roots) {
2498
+ if (!importedPath.startsWith(`${root}.`)) continue;
2499
+ if (projectOwnsQualified(importedPath)) return;
2500
+ md.languageNamespace = [target];
2501
+ break;
2502
+ }
2503
+ }
2504
+ }
2505
+ /**
2506
+ * THE IMPORTED-NAME SEAM — `metadata.importedFrom` on type-shaped reference edges whose
2507
+ * first name segment is a KNOWN single-type import of this file.
2508
+ *
2509
+ * WHAT IT IS FOR (task-17 item 2 / D7): the wall `references type-shaped` is 84,751
2510
+ * ambiguous+unresolved rows whose targets are bare type names; for 32,176 of them the
2511
+ * FILE's own import states which package the name comes from. The store's name-keyed
2512
+ * resolver cannot see the file's imports, so the row stays a tie — but joining the
2513
+ * import path to the target yields the declaration's QUALIFIED NAME, which the store's
2514
+ * symbol table already carries (`qualifiedName` aliases are indexed). This method is the
2515
+ * ANALYZER HALF of that instrument: it publishes the seam; the resolver change that
2516
+ * consumes it is Lead-owned (`parallel-indexer.resolveRelationships`), and the sizing
2517
+ * comes from the lane's ambiguity classifier (same-file 81,448 · single-type-import
2518
+ * 39,828 over all labels).
2519
+ *
2520
+ * ADDITIVE ONLY: the target spelling is untouched (JD20's bare-leaf contract and the
2521
+ * differential oracle both stay), the key is not in any claim channel, and a row that
2522
+ * already carries `importedFrom` keeps it. Nested spellings join the remainder too —
2523
+ * `Map.Entry` under `import java.util.Map` publishes `java.util.Map.Entry`, which is
2524
+ * exactly the qualified form of the nested type.
2525
+ */
2526
+ attachJavaImportedFrom(relationships, index, symbols) {
2527
+ if (index.importSinglePaths.size === 0) return;
2528
+ const declared = /* @__PURE__ */ new Set();
2529
+ for (const s of symbols) if (s && s.name) declared.add(s.name);
2530
+ const TYPE_LABELS = /* @__PURE__ */ new Set(["references", "instantiates", "extends", "implements"]);
2531
+ const MEMBER_LABELS = /* @__PURE__ */ new Set(["calls", "uses"]);
2532
+ for (const r of relationships) {
2533
+ const label = String(r.type ?? "");
2534
+ if (!TYPE_LABELS.has(label) && !MEMBER_LABELS.has(label)) continue;
2535
+ const md = r.metadata;
2536
+ if (!md || typeof md !== "object" || md.importedFrom !== void 0) continue;
2537
+ const target = String(r.target ?? "");
2538
+ if (target.length === 0) continue;
2539
+ if (MEMBER_LABELS.has(label) && index.staticImportPaths.has(target) && !declared.has(target)) {
2540
+ md.importedFrom = `${index.staticImportPaths.get(target)}.${target}`;
2541
+ continue;
2542
+ }
2543
+ if (!TYPE_LABELS.has(label)) continue;
2544
+ const first = target.split(".")[0];
2545
+ if (first.length === 0 || declared.has(first)) continue;
2546
+ const imported = index.importSinglePaths.get(first);
2547
+ if (!imported) continue;
2548
+ const remainder = target.length > first.length ? target.slice(first.length) : "";
2549
+ if (remainder.length > 0) {
2550
+ let shapeOk = remainder.startsWith(".");
2551
+ for (let i = 1; shapeOk && i < remainder.length; i++) {
2552
+ const code = remainder.charCodeAt(i);
2553
+ if (code === 46) continue;
2554
+ const isStart = code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 36 || code === 95;
2555
+ const isPart = isStart || code >= 48 && code <= 57;
2556
+ if (!isPart) {
2557
+ shapeOk = false;
2558
+ break;
2559
+ }
2560
+ if (i > 0 && code !== 46) {
2561
+ const prev = remainder.charCodeAt(i - 1);
2562
+ const prevIsDot = prev === 46;
2563
+ if (prevIsDot && !isStart) {
2564
+ shapeOk = false;
2565
+ break;
2566
+ }
2567
+ }
2568
+ }
2569
+ if (!shapeOk) continue;
2570
+ }
2571
+ md.importedFrom = remainder.length > 0 ? `${imported}${remainder}` : imported;
2572
+ }
2573
+ }
2574
+ /**
2575
+ * Collect Uses relationships from field_access expressions (this.name, obj.method)
2576
+ */
2577
+ collectFieldAccessUses(rootNode, relationships, symbols, receiverTypes, projectIndex) {
2578
+ const membersByOwner = /* @__PURE__ */ new Map();
2579
+ for (const s of symbols) {
2580
+ const o = s.ownerName || s.scope;
2581
+ if (!o) continue;
2582
+ const set = membersByOwner.get(o);
2583
+ if (set) set.add(s.name);
2584
+ else membersByOwner.set(o, /* @__PURE__ */ new Set([s.name]));
2585
+ }
2586
+ const stack = [
2587
+ { node: rootNode, owner: null, method: null, locals: /* @__PURE__ */ new Set() }
2588
+ ];
2589
+ const seen = /* @__PURE__ */ new Set();
2590
+ while (stack.length > 0) {
2591
+ const item = stack.pop();
2592
+ const { node, owner, method, locals } = item;
2593
+ if (node.type === "class_declaration" || node.type === "interface_declaration" || node.type === "enum_declaration" || node.type === "record_declaration" || // M37.21 C17: member reads INSIDE an @interface body previously kept
2594
+ // the outer owner — @interface is a type declaration like the others,
2595
+ // so its member declarations must rebind the read owner.
2596
+ node.type === "annotation_type_declaration") {
2597
+ const nameNode = this.findChildByType(node, "identifier");
2598
+ const nextOwner = nameNode ? nameNode.text : owner;
2599
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2600
+ const child = node.namedChild(i);
2601
+ if (child) stack.push({ node: child, owner: nextOwner, method: null, locals: /* @__PURE__ */ new Set() });
2602
+ }
2603
+ continue;
2604
+ }
2605
+ let nextMethod = method;
2606
+ let nextLocals = locals;
2607
+ if (node.type === "method_declaration" || node.type === "constructor_declaration" || node.type === "compact_constructor_declaration") {
2608
+ const nameNode = node.childForFieldName("name") || this.findChildByType(node, "identifier");
2609
+ nextMethod = node.type === "constructor_declaration" || node.type === "compact_constructor_declaration" ? owner ?? "constructor" : nameNode?.text ?? method;
2610
+ nextLocals = this.collectBoundNames(node);
2611
+ }
2612
+ if (node.type === "field_access") {
2613
+ const parts = node.text.split(".");
2614
+ if (parts.length >= 2) {
2615
+ const first = parts[0];
2616
+ const readOwner = method ?? owner;
2617
+ const source = first === "this" || first === "super" ? method ?? owner ?? first : readOwner ?? first;
2618
+ const parent = node.parent;
2619
+ const lhs = parent && parent.type === "assignment_expression" ? parent.namedChild(0) : null;
2620
+ const isWrite = !!lhs && lhs.startIndex === node.startIndex && lhs.endIndex === node.endIndex;
2621
+ const key = `${source}.${parts[parts.length - 1]}`;
2622
+ if (!isWrite && !seen.has(key)) {
2623
+ seen.add(key);
2624
+ const readReceiver = this.javaReceiverOwnerType(node, receiverTypes, projectIndex, "uses");
2625
+ relationships.push({
2626
+ source,
2627
+ target: parts[parts.length - 1],
2628
+ type: ASTEngine.RelationshipType.Uses,
2629
+ confidence: 0.6,
2630
+ // M59 (#825): the READ is where the field NAME is written — the
2631
+ // `field` node, not the field_access (which starts at its
2632
+ // receiver, the same defect class as the call site).
2633
+ ...this.lineField(node.childForFieldName("field"), node),
2634
+ metadata: {
2635
+ isFieldAccess: true,
2636
+ ...readReceiver ? { owner: readReceiver.type, ownerEvidence: readReceiver.evidence } : {}
2637
+ }
2638
+ });
2639
+ }
2640
+ }
2641
+ }
2642
+ if (node.type === "array_access") {
2643
+ const arrayNode = node.childForFieldName("array");
2644
+ if (arrayNode) {
2645
+ const parent = node.parent;
2646
+ const lhs = parent && parent.type === "assignment_expression" ? parent.namedChild(0) : null;
2647
+ const isWrite = !!lhs && lhs.startIndex === node.startIndex && lhs.endIndex === node.endIndex;
2648
+ const at = arrayNode.type;
2649
+ let source = null;
2650
+ let target = null;
2651
+ if (at === "identifier") {
2652
+ const readOwner = method ?? owner;
2653
+ source = readOwner ?? arrayNode.text;
2654
+ target = arrayNode.text;
2655
+ } else if (at === "field_access" || at === "scoped_identifier") {
2656
+ const parts = arrayNode.text.split(".");
2657
+ if (parts.length >= 2) {
2658
+ const first = parts[0];
2659
+ const readOwner = method ?? owner;
2660
+ source = first === "this" || first === "super" ? method ?? owner ?? first : readOwner ?? first;
2661
+ target = parts[parts.length - 1];
2662
+ }
2663
+ }
2664
+ if (source && target && !isWrite) {
2665
+ const key = `${source}.${target}`;
2666
+ if (!seen.has(key)) {
2667
+ seen.add(key);
2668
+ relationships.push({
2669
+ source,
2670
+ target,
2671
+ type: ASTEngine.RelationshipType.Uses,
2672
+ confidence: 0.6,
2673
+ // M59 (#825): the array read is named by the array expression.
2674
+ ...this.lineField(arrayNode, node),
2675
+ metadata: { isArrayAccess: true }
2676
+ });
2677
+ }
2678
+ }
2679
+ }
2680
+ }
2681
+ if (node.type === "identifier") {
2682
+ const txt = node.text;
2683
+ if (txt !== "this" && txt !== "super") {
2684
+ const parent = node.parent;
2685
+ const memberish = parent?.type === "field_access" || parent?.type === "method_invocation" || parent?.type === "scoped_identifier" || parent?.type === "array_access";
2686
+ const annotationName = parent?.type === "marker_annotation" || parent?.type === "annotation";
2687
+ const declName = (parent?.type === "variable_declarator" || parent?.type === "formal_parameter" || parent?.type === "method_declaration" || parent?.type === "constructor_declaration" || parent?.type === "class_declaration" || parent?.type === "interface_declaration" || parent?.type === "enum_declaration" || parent?.type === "record_declaration" || parent?.type === "annotation_type_declaration" || parent?.type === "enum_constant" || parent?.type === "catch_formal_parameter" || parent?.type === "enhanced_for_statement" || // A COMPACT CONSTRUCTOR (JLS 8.10.4) and an ANNOTATION TYPE ELEMENT
2688
+ // (JLS 9.6.1) both NAME a member: `R { ... }` and `Class<?> value();`.
2689
+ // The first is the constructor's own name and the second is an element
2690
+ // declaration, so neither is a read of an enclosing member — but the
2691
+ // list above did not include them and probe on the pinned grammar
2692
+ // showed both fabricated a bare read: record R(int x) { R { ... } }
2693
+ // emitted `uses R -> R` and `@interface Ann { List<String> list(); }`
2694
+ // emitted `uses Ann -> list`. A name is not a read; GATE A below then
2695
+ // let the fabricated edge through because the member map carried it.
2696
+ parent?.type === "compact_constructor_declaration" || parent?.type === "annotation_type_element_declaration") && parent.childForFieldName("name")?.startIndex === node.startIndex;
2697
+ let isWrite = false;
2698
+ if (parent?.type === "assignment_expression") {
2699
+ const lhs = parent.childForFieldName("left") ?? parent.namedChild(0);
2700
+ isWrite = !!lhs && lhs.startIndex === node.startIndex;
2701
+ }
2702
+ const isDeclaredMember = !!owner && (membersByOwner.get(owner)?.has(txt) ?? false);
2703
+ const readSource = method ?? owner;
2704
+ const key = readSource ? `${readSource}.#${txt}` : null;
2705
+ if (!memberish && !annotationName && !declName && !isWrite && !locals.has(txt) && isDeclaredMember && readSource && key && !seen.has(key)) {
2706
+ seen.add(key);
2707
+ let bareOwner = null;
2708
+ if (owner && this.javaTypeEligible(owner, receiverTypes, projectIndex) && this.javaOwnerResolvable(owner, txt, "uses", receiverTypes, projectIndex)) {
2709
+ bareOwner = owner;
2710
+ }
2711
+ relationships.push({
2712
+ source: readSource,
2713
+ target: txt,
2714
+ type: ASTEngine.RelationshipType.Uses,
2715
+ // 1.0, not the 0.6 an inferred read carries. This edge is gated on
2716
+ // `isDeclaredMember`, i.e. the owner was CHECKED to declare the member
2717
+ // (`membersByOwner`), so "this type declares this field" is verified rather
2718
+ // than guessed. At 0.6 the harness classified it as emitted-but-not-claimed
2719
+ // (below the 0.7 threshold) while truth says "resolved" -- a false negative
2720
+ // for a correct edge.
2721
+ confidence: 1,
2722
+ // M59 (#825): the bare read IS its identifier node.
2723
+ line_number: node.startPosition.row + 1,
2724
+ metadata: { isBareRead: true, ...bareOwner ? { owner: bareOwner, ownerEvidence: "this" } : {} }
2725
+ });
2726
+ }
2727
+ }
2728
+ }
2729
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2730
+ const child = node.namedChild(i);
2731
+ if (child) stack.push({ node: child, owner, method: nextMethod, locals: nextLocals });
2732
+ }
2733
+ }
2734
+ }
2735
+ /**
2736
+ * M49 #739 GATE B: the names a function body BINDS — parameters, locals,
2737
+ * catch/enhanced-for/lambda/resource bindings. A bare read of any of these
2738
+ * is not a member read (CGD15 family rule shared with c/cpp).
2739
+ *
2740
+ * Deliberately method-wide rather than block-scoped: a local declared AFTER
2741
+ * a use still suppresses that use, which can only LOSE an edge, never
2742
+ * fabricate one — the correct direction under the precision rule. Nested
2743
+ * type bodies reset this set (the class branch above), so a nested class's
2744
+ * member reads are never suppressed by the enclosing method.
2745
+ */
2746
+ collectBoundNames(fnNode) {
2747
+ const names = /* @__PURE__ */ new Set();
2748
+ const stack = [fnNode];
2749
+ while (stack.length > 0) {
2750
+ const n = stack.pop();
2751
+ if (n.type === "variable_declarator" || n.type === "formal_parameter" || n.type === "catch_formal_parameter" || n.type === "enhanced_for_statement" || n.type === "resource") {
2752
+ const nm = n.childForFieldName("name") || this.findChildByType(n, "identifier");
2753
+ if (nm) names.add(nm.text);
2754
+ } else if (n.type === "lambda_expression") {
2755
+ const params = n.childForFieldName("parameters");
2756
+ if (params) {
2757
+ if (params.type === "identifier") names.add(params.text);
2758
+ else for (let i = 0; i < params.namedChildCount; i++) {
2759
+ const c = params.namedChild(i);
2760
+ if (c) names.add(c.text);
2761
+ }
2762
+ }
2763
+ }
2764
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
2765
+ const c = n.namedChild(i);
2766
+ if (c) stack.push(c);
2767
+ }
2768
+ }
2769
+ return names;
2770
+ }
2771
+ childText(node, type) {
2772
+ const child = this.findChildByType(node, type);
2773
+ return child ? child.text : null;
2774
+ }
2775
+ getJavaModifiers(node) {
2776
+ const modifiers = [];
2777
+ const modifiersNode = this.findChildByType(node, "modifiers");
2778
+ if (modifiersNode) {
2779
+ for (let i = 0; i < modifiersNode.childCount; i++) {
2780
+ const modifier = modifiersNode.child(i);
2781
+ if (modifier && !stringIncludes(modifier.type, "annotation")) {
2782
+ modifiers.push(modifier.text);
2783
+ }
2784
+ }
2785
+ }
2786
+ return modifiers;
2787
+ }
2788
+ getJavaAnnotations(node) {
2789
+ const annotations = [];
2790
+ const modifiersNode = this.findChildByType(node, "modifiers");
2791
+ const target = modifiersNode || node;
2792
+ for (let i = 0; i < target.namedChildCount; i++) {
2793
+ const child = target.namedChild(i);
2794
+ if (child && (child.type === "annotation" || child.type === "marker_annotation")) {
2795
+ annotations.push(child.text);
2796
+ }
2797
+ }
2798
+ return annotations;
2799
+ }
2800
+ /**
2801
+ * The NAME child of an annotation / marker_annotation node — the annotation
2802
+ * TYPE as the file wrote it. The grammar serializes it as
2803
+ * `$._name = choice(identifier, scoped_identifier)` (probe-verified: `@Override`
2804
+ * has a direct `identifier`, `@org.springframework.stereotype.Service` a
2805
+ * `scoped_identifier`), and `findChildByType` only inspects DIRECT children, so
2806
+ * an identifier inside an argument list can never be mistaken for the name.
2807
+ */
2808
+ javaAnnotationNameNode(ann) {
2809
+ return this.findChildByType(ann, "identifier") || this.findChildByType(ann, "scoped_identifier");
2810
+ }
2811
+ /** The declaration kinds whose annotations this lane attributes to a published
2812
+ * name. Every entry is a node type of the pinned tree-sitter-java grammar; the
2813
+ * list is exhaustive for the positions Java permits a type annotation in
2814
+ * (`modifiers`, type-use, parameter, type-parameter, receiver, record
2815
+ * component, enum constant). */
2816
+ static JAVA_ANNOTATED_DECLARATIONS = /* @__PURE__ */ new Set([
2817
+ "class_declaration",
2818
+ "interface_declaration",
2819
+ "enum_declaration",
2820
+ "record_declaration",
2821
+ "annotation_type_declaration",
2822
+ "module_declaration",
2823
+ "method_declaration",
2824
+ "constructor_declaration",
2825
+ "compact_constructor_declaration",
2826
+ "annotation_type_element_declaration",
2827
+ "field_declaration",
2828
+ "constant_declaration",
2829
+ "local_variable_declaration",
2830
+ "enum_constant",
2831
+ "formal_parameter",
2832
+ "spread_parameter",
2833
+ "catch_formal_parameter",
2834
+ "receiver_parameter",
2835
+ "type_parameter"
2836
+ ]);
2837
+ /** Positions whose annotated thing is NOT a published symbol of its own: the
2838
+ * annotation belongs to the declaration that carries the position. */
2839
+ static JAVA_ANNOTATION_CARRIER_POSITIONS = /* @__PURE__ */ new Set([
2840
+ "formal_parameter",
2841
+ "spread_parameter",
2842
+ "catch_formal_parameter",
2843
+ "receiver_parameter",
2844
+ "type_parameter",
2845
+ "local_variable_declaration"
2846
+ ]);
2847
+ /**
2848
+ * The declaration an annotation node decorates, found by climbing ancestors to
2849
+ * the first node type in JAVA_ANNOTATED_DECLARATIONS.
2850
+ *
2851
+ * WHY A CLIMB AND NOT `parent.parent`: a declaration annotation sits under the
2852
+ * declaration's `modifiers` child (one hop), but a TYPE-USE annotation
2853
+ * (`List<@Nullable String>`, JLS 9.7.4) is nested inside the type node
2854
+ * (`annotated_type` > `type_arguments` > `generic_type` > …), and a parameter /
2855
+ * record-component annotation sits under `formal_parameter`'s modifiers. The
2856
+ * fixed-hop version would work for exactly one of those shapes and silently
2857
+ * emit nothing for the others.
2858
+ */
2859
+ javaAnnotatedDeclarationNode(ann) {
2860
+ let cur = ann.parent;
2861
+ let guard = 0;
2862
+ while (cur && guard++ < 64) {
2863
+ if (_RealJavaAnalyzer.JAVA_ANNOTATED_DECLARATIONS.has(cur.type)) return cur;
2864
+ cur = cur.parent;
2865
+ }
2866
+ return null;
2867
+ }
2868
+ /** The name of the nearest enclosing named declaration (method / constructor /
2869
+ * type), used for positions that publish no symbol of their own. */
2870
+ javaEnclosingDeclarationName(node) {
2871
+ let cur = node.parent;
2872
+ let guard = 0;
2873
+ while (cur && guard++ < 64) {
2874
+ const named = this.findChildByType(cur, "type_identifier");
2875
+ if (cur.type === "class_declaration" || cur.type === "interface_declaration" || cur.type === "enum_declaration" || cur.type === "record_declaration" || cur.type === "annotation_type_declaration" || cur.type === "module_declaration" || cur.type === "method_declaration" || cur.type === "constructor_declaration" || cur.type === "compact_constructor_declaration" || cur.type === "annotation_type_element_declaration") {
2876
+ const namedChild = cur.childForFieldName("name") || this.findChildByType(cur, "identifier") || named;
2877
+ if (namedChild) return namedChild.text;
2878
+ }
2879
+ cur = cur.parent;
2880
+ }
2881
+ return void 0;
2882
+ }
2883
+ /**
2884
+ * The names an annotation edge is emitted FROM, with the annotated declaration's
2885
+ * node type beside each. See the `annotation` branch in the walk for the
2886
+ * contract; this helper exists so the branch reads as one edge shape.
2887
+ */
2888
+ javaAnnotatedDeclarationSources(ann, parentContext) {
2889
+ const fallback = parentContext?.name ? [{ name: String(parentContext.name), declKind: "enclosing_scope" }] : [];
2890
+ const decl = this.javaAnnotatedDeclarationNode(ann);
2891
+ if (!decl) {
2892
+ let root = ann;
2893
+ let rootGuard = 0;
2894
+ while (root.parent && root.type !== "program" && rootGuard++ < 64) root = root.parent;
2895
+ if (root.type === "program") {
2896
+ for (let i = 0; i < root.namedChildCount; i++) {
2897
+ const top = root.namedChild(i);
2898
+ if (!top || top.type !== "package_declaration") continue;
2899
+ const pkgName = this.findChildByType(top, "scoped_identifier") || this.findChildByType(top, "identifier");
2900
+ if (pkgName) return [{ name: pkgName.text, declKind: "package_declaration" }];
2901
+ }
2902
+ }
2903
+ return fallback;
2904
+ }
2905
+ const declKind = decl.type;
2906
+ if (declKind === "field_declaration" || declKind === "constant_declaration") {
2907
+ const names = [];
2908
+ for (let i = 0; i < decl.namedChildCount; i++) {
2909
+ const c = decl.namedChild(i);
2910
+ if (!c || c.type !== "variable_declarator") continue;
2911
+ const n = c.childForFieldName("name") || this.findChildByType(c, "identifier");
2912
+ if (n) names.push({ name: n.text, declKind });
2913
+ }
2914
+ return names.length > 0 ? names : fallback;
2915
+ }
2916
+ if (_RealJavaAnalyzer.JAVA_ANNOTATION_CARRIER_POSITIONS.has(declKind)) {
2917
+ const carrier = this.javaEnclosingDeclarationName(decl);
2918
+ return carrier ? [{ name: carrier, declKind }] : fallback;
2919
+ }
2920
+ const ownName = decl.childForFieldName("name") || this.findChildByType(decl, "identifier") || this.findChildByType(decl, "type_identifier");
2921
+ return ownName ? [{ name: ownName.text, declKind }] : fallback;
2922
+ }
2923
+ // ── RECEIVER TYPING — the pre-pass and the resolver ─────────────────────────────────────────────
2924
+ /**
2925
+ * Build the per-file receiver-type index in ONE pre-pass over the same tree.
2926
+ *
2927
+ * WHY A PRE-PASS. The main walk is single-pass and visits a call site before it
2928
+ * visits declarations written BELOW it — `x.foo()` above `void foo()` is the
2929
+ * normal shape, not the exception — and Java allows fields and methods in any
2930
+ * order. Reading what the file states therefore has to happen before the
2931
+ * emission that consumes it. The pre-pass visits the same tree and emits nothing.
2932
+ *
2933
+ * WHAT IS INDEXED, and each entry is a DECLARATION, not an inference:
2934
+ * * type scopes (class/interface/enum/record/@interface bodies, plus anonymous
2935
+ * class bodies) with their FIELDS;
2936
+ * * method/constructor scopes with their FORMAL PARAMETERS, and their LOCALS
2937
+ * (a local is recorded with its enclosing block's range, so a declaration in
2938
+ * a sibling block cannot bind a use outside it);
2939
+ * * method name -> declared return type, only when this file declares it and
2940
+ * the name has ONE distinct return type in the file (an overload set with
2941
+ * differing return types is refused, not guessed);
2942
+ * * type names declared in the file and single-type-import local names, which
2943
+ * is how a CLASS-QUALIFIED static call (`Helper.help()`) is told apart from a
2944
+ * variable read.
2945
+ */
2946
+ collectJavaReceiverTypeIndex(rootNode) {
2947
+ const index = {
2948
+ methodScopes: [],
2949
+ typeScopes: [],
2950
+ typeNames: /* @__PURE__ */ new Set(),
2951
+ typeKindsByName: /* @__PURE__ */ new Map(),
2952
+ importSingleTypes: /* @__PURE__ */ new Set(),
2953
+ importSinglePaths: /* @__PURE__ */ new Map(),
2954
+ methodReturns: /* @__PURE__ */ new Map(),
2955
+ typeScopesByName: /* @__PURE__ */ new Map(),
2956
+ staticImportLeaves: /* @__PURE__ */ new Set(),
2957
+ hasStaticOnDemand: false,
2958
+ staticImportPaths: /* @__PURE__ */ new Map()
2959
+ };
2960
+ const TYPE_DECLARATIONS = /* @__PURE__ */ new Set([
2961
+ "class_declaration",
2962
+ "interface_declaration",
2963
+ "enum_declaration",
2964
+ "record_declaration",
2965
+ "annotation_type_declaration"
2966
+ ]);
2967
+ const BLOCK_NODES = /* @__PURE__ */ new Set([
2968
+ "block",
2969
+ "constructor_body",
2970
+ "switch_block",
2971
+ "for_statement",
2972
+ "enhanced_for_statement",
2973
+ "try_statement",
2974
+ "catch_clause",
2975
+ "finally_clause",
2976
+ "synchronized_statement",
2977
+ "lambda_expression",
2978
+ "static_initializer"
2979
+ ]);
2980
+ const addFields = (scope, body) => {
2981
+ for (let i = 0; i < body.namedChildCount; i++) {
2982
+ const child = body.namedChild(i);
2983
+ if (!child) continue;
2984
+ if (child.type === "enum_body_declarations") {
2985
+ for (let j = 0; j < child.namedChildCount; j++) {
2986
+ const inner = child.namedChild(j);
2987
+ if (inner && (inner.type === "field_declaration" || inner.type === "constant_declaration")) {
2988
+ this.addJavaDeclaredMembers(inner, "field", scope, body, index);
2989
+ }
2990
+ }
2991
+ continue;
2992
+ }
2993
+ if (child.type === "field_declaration" || child.type === "constant_declaration") {
2994
+ this.addJavaDeclaredMembers(child, "field", scope, body, index);
2995
+ }
2996
+ }
2997
+ };
2998
+ const addMemberNamesAndEnumConstants = (scope, body) => {
2999
+ const names = scope.memberNames;
3000
+ const methods = scope.methodNames;
3001
+ if (!names) return;
3002
+ const addName = (decl) => {
3003
+ if (!decl) return;
3004
+ const nameNode = decl.childForFieldName("name");
3005
+ if (nameNode && nameNode.text) {
3006
+ names.add(nameNode.text);
3007
+ if (decl.type === "method_declaration" || decl.type === "constructor_declaration" || decl.type === "annotation_type_element_declaration") {
3008
+ methods?.add(nameNode.text);
3009
+ }
3010
+ return;
3011
+ }
3012
+ if (decl.type === "field_declaration" || decl.type === "constant_declaration") {
3013
+ for (let k = 0; k < decl.namedChildCount; k++) {
3014
+ const d = decl.namedChild(k);
3015
+ if (!d || d.type !== "variable_declarator") continue;
3016
+ const n = d.childForFieldName("name");
3017
+ if (n && n.text) names.add(n.text);
3018
+ }
3019
+ }
3020
+ };
3021
+ for (let i = 0; i < body.namedChildCount; i++) {
3022
+ const child = body.namedChild(i);
3023
+ if (!child) continue;
3024
+ if (child.type === "enum_body_declarations") {
3025
+ for (let j = 0; j < child.namedChildCount; j++) addName(child.namedChild(j));
3026
+ continue;
3027
+ }
3028
+ if (child.type === "method_declaration" || child.type === "constructor_declaration" || child.type === "compact_constructor_declaration" || child.type === "field_declaration" || child.type === "constant_declaration" || TYPE_DECLARATIONS.has(child.type)) {
3029
+ addName(child);
3030
+ continue;
3031
+ }
3032
+ if (child.type === "enum_constant") {
3033
+ const nameNode = child.childForFieldName("name") || this.findChildByType(child, "identifier");
3034
+ if (!nameNode || !nameNode.text) continue;
3035
+ names.add(nameNode.text);
3036
+ if (scope.name) {
3037
+ scope.members.set(nameNode.text, {
3038
+ type: scope.name,
3039
+ evidence: "field",
3040
+ declStart: child.startIndex,
3041
+ scopeStart: scope.start,
3042
+ scopeEnd: scope.end
3043
+ });
3044
+ }
3045
+ }
3046
+ }
3047
+ };
3048
+ const makeTypeScope = (body, name, parents) => {
3049
+ const scope = {
3050
+ start: body.startIndex,
3051
+ end: body.endIndex,
3052
+ members: /* @__PURE__ */ new Map(),
3053
+ name,
3054
+ parents,
3055
+ memberNames: /* @__PURE__ */ new Set(),
3056
+ methodNames: /* @__PURE__ */ new Set()
3057
+ };
3058
+ addFields(scope, body);
3059
+ addMemberNamesAndEnumConstants(scope, body);
3060
+ if (name) index.typeScopesByName.set(name, scope);
3061
+ index.typeScopes.push(scope);
3062
+ return scope;
3063
+ };
3064
+ const makeBodyScope = (n) => {
3065
+ const body = this.findChildByType(n, "block") || this.findChildByType(n, "constructor_body");
3066
+ const scope = {
3067
+ start: body ? body.startIndex : n.startIndex,
3068
+ end: body ? body.endIndex : n.endIndex,
3069
+ members: /* @__PURE__ */ new Map()
3070
+ };
3071
+ const params = this.findChildByType(n, "formal_parameters");
3072
+ if (params) {
3073
+ for (let i = 0; i < params.namedChildCount; i++) {
3074
+ const p = params.namedChild(i);
3075
+ if (!p) continue;
3076
+ if (p.type !== "formal_parameter" && p.type !== "spread_parameter") continue;
3077
+ const typeNode = p.childForFieldName("type");
3078
+ const nameNode = p.childForFieldName("name") || this.findChildByType(p, "identifier");
3079
+ const typeName = this.javaDeclaredTypeBaseName(typeNode);
3080
+ if (typeName && nameNode) {
3081
+ scope.members.set(nameNode.text, {
3082
+ type: typeName,
3083
+ evidence: "param",
3084
+ declStart: p.startIndex,
3085
+ scopeStart: scope.start,
3086
+ scopeEnd: scope.end
3087
+ });
3088
+ }
3089
+ }
3090
+ }
3091
+ index.methodScopes.push(scope);
3092
+ return scope;
3093
+ };
3094
+ const visit = (n, methodScope, typeScopes, block) => {
3095
+ const t = n.type;
3096
+ let nextMethod = methodScope;
3097
+ let nextTypeScopes = typeScopes;
3098
+ const nextBlock = BLOCK_NODES.has(t) ? n : block;
3099
+ if (TYPE_DECLARATIONS.has(t)) {
3100
+ const nameNode = n.childForFieldName("name");
3101
+ const typeName = nameNode?.text;
3102
+ if (typeName) {
3103
+ index.typeNames.add(typeName);
3104
+ index.typeKindsByName.set(typeName, t);
3105
+ }
3106
+ const body = this.findChildByType(n, "class_body") || this.findChildByType(n, "enum_body") || this.findChildByType(n, "interface_body") || this.findChildByType(n, "annotation_type_body");
3107
+ if (body) {
3108
+ nextTypeScopes = [...typeScopes, makeTypeScope(body, typeName, typeName ? this.javaDeclaredParentNames(n) : void 0)];
3109
+ }
3110
+ }
3111
+ if (t === "object_creation_expression") {
3112
+ const body = this.findChildByType(n, "class_body");
3113
+ if (body) {
3114
+ const superNode = this.findChildByType(n, "type_identifier") || this.findChildByType(n, "generic_type") || this.findChildByType(n, "scoped_type_identifier");
3115
+ const superName = superNode ? this.javaDeclaredTypeBaseName(superNode) : null;
3116
+ nextTypeScopes = [...typeScopes, makeTypeScope(body, void 0, superName ? [superName] : void 0)];
3117
+ }
3118
+ }
3119
+ if (t === "method_declaration" || t === "constructor_declaration" || t === "compact_constructor_declaration") {
3120
+ nextMethod = makeBodyScope(n);
3121
+ if (t === "method_declaration") {
3122
+ const nameNode = n.childForFieldName("name");
3123
+ const returnType = this.javaDeclaredTypeBaseName(n.childForFieldName("type"));
3124
+ if (nameNode && returnType) {
3125
+ const set = index.methodReturns.get(nameNode.text) ?? /* @__PURE__ */ new Set();
3126
+ set.add(returnType);
3127
+ index.methodReturns.set(nameNode.text, set);
3128
+ }
3129
+ }
3130
+ }
3131
+ if (t === "import_declaration") {
3132
+ let isStatic = false;
3133
+ for (let ci = 0; ci < n.childCount; ci++) {
3134
+ const childNode = n.child(ci);
3135
+ if (childNode && childNode.type === "static") {
3136
+ isStatic = true;
3137
+ break;
3138
+ }
3139
+ }
3140
+ const isWildcard = this.findChildByType(n, "asterisk") !== null;
3141
+ if (isStatic) {
3142
+ const staticName = this.findChildByType(n, "scoped_identifier") || this.findChildByType(n, "identifier");
3143
+ if (staticName) {
3144
+ const text = staticName.text;
3145
+ const leaf = text.slice(text.lastIndexOf(".") + 1);
3146
+ if (isWildcard) index.hasStaticOnDemand = true;
3147
+ else if (leaf) {
3148
+ index.staticImportLeaves.add(leaf);
3149
+ const container = text.slice(0, text.lastIndexOf("."));
3150
+ if (container.length > 0) index.staticImportPaths.set(leaf, container);
3151
+ }
3152
+ }
3153
+ }
3154
+ if (!isStatic && !isWildcard) {
3155
+ const nameNode = this.findChildByType(n, "scoped_identifier") || this.findChildByType(n, "identifier");
3156
+ if (nameNode) {
3157
+ const text = nameNode.text;
3158
+ const leaf = text.slice(text.lastIndexOf(".") + 1);
3159
+ if (leaf) {
3160
+ index.importSingleTypes.add(leaf);
3161
+ index.importSinglePaths.set(leaf, text);
3162
+ }
3163
+ }
3164
+ }
3165
+ }
3166
+ if (t === "lambda_expression") {
3167
+ const lambdaScope = { start: n.startIndex, end: n.endIndex, members: /* @__PURE__ */ new Map() };
3168
+ const paramsNode = n.childForFieldName("parameters");
3169
+ const paramChildren = paramsNode ? paramsNode.type === "identifier" ? [paramsNode] : Array.from({ length: paramsNode.namedChildCount }, (_, i) => paramsNode.namedChild(i)) : [];
3170
+ for (const p of paramChildren) {
3171
+ if (!p) continue;
3172
+ if (p.type !== "formal_parameter" && p.type !== "spread_parameter" && p.type !== "identifier") continue;
3173
+ const typeNode = p.type === "identifier" ? null : p.childForFieldName("type");
3174
+ const nameNode = p.type === "identifier" ? p : p.childForFieldName("name") || this.findChildByType(p, "identifier");
3175
+ const lambdaType = this.javaDeclaredTypeBaseName(typeNode);
3176
+ if (lambdaType && nameNode) {
3177
+ lambdaScope.members.set(nameNode.text, {
3178
+ type: lambdaType,
3179
+ evidence: "param",
3180
+ declStart: p.startIndex,
3181
+ scopeStart: lambdaScope.start,
3182
+ scopeEnd: lambdaScope.end
3183
+ });
3184
+ }
3185
+ }
3186
+ index.methodScopes.push(lambdaScope);
3187
+ nextMethod = lambdaScope;
3188
+ }
3189
+ if (t === "local_variable_declaration" && methodScope) {
3190
+ const scopeNode = block ?? (n.parent ?? n);
3191
+ this.addJavaDeclaredMembers(n, "local", methodScope, scopeNode, index);
3192
+ }
3193
+ if (methodScope && t === "catch_formal_parameter") {
3194
+ const typeNode = n.childForFieldName("type") || this.findChildByType(n, "catch_type");
3195
+ const nameNode = n.childForFieldName("name") || this.findChildByType(n, "identifier");
3196
+ const catchNames = typeNode && typeNode.type === "catch_type" ? this.collectJavaTypeNames(typeNode) : [];
3197
+ const catchType = catchNames.length === 1 ? catchNames[0].name : this.javaDeclaredTypeBaseName(typeNode);
3198
+ if (catchType && nameNode) {
3199
+ let catchScopeNode = n;
3200
+ let catchClimb = n.parent;
3201
+ let catchGuard = 0;
3202
+ while (catchClimb && catchGuard++ < 8) {
3203
+ if (catchClimb.type === "catch_clause") {
3204
+ catchScopeNode = catchClimb;
3205
+ break;
3206
+ }
3207
+ catchClimb = catchClimb.parent;
3208
+ }
3209
+ methodScope.members.set(nameNode.text, {
3210
+ type: catchType,
3211
+ evidence: "local",
3212
+ declStart: n.startIndex,
3213
+ scopeStart: catchScopeNode.startIndex,
3214
+ scopeEnd: catchScopeNode.endIndex
3215
+ });
3216
+ }
3217
+ }
3218
+ if (methodScope && t === "enhanced_for_statement") {
3219
+ const typeNode = n.childForFieldName("type");
3220
+ const nameNode = n.childForFieldName("name");
3221
+ const forType = this.javaDeclaredTypeBaseName(typeNode);
3222
+ if (forType && nameNode) {
3223
+ methodScope.members.set(nameNode.text, {
3224
+ type: forType,
3225
+ evidence: "local",
3226
+ declStart: n.startIndex,
3227
+ scopeStart: n.startIndex,
3228
+ scopeEnd: n.endIndex
3229
+ });
3230
+ }
3231
+ }
3232
+ if (methodScope && t === "resource") {
3233
+ const typeNode = n.childForFieldName("type");
3234
+ const nameNode = n.childForFieldName("name") || this.findChildByType(n, "identifier");
3235
+ const resType = this.javaDeclaredTypeBaseName(typeNode);
3236
+ if (resType && nameNode) {
3237
+ let scopeNode = n;
3238
+ let climb = n.parent;
3239
+ let guard = 0;
3240
+ while (climb && guard++ < 8) {
3241
+ if (climb.type === "try_statement" || climb.type === "try_with_resources_statement") {
3242
+ scopeNode = climb;
3243
+ break;
3244
+ }
3245
+ climb = climb.parent;
3246
+ }
3247
+ methodScope.members.set(nameNode.text, {
3248
+ type: resType,
3249
+ evidence: "local",
3250
+ declStart: n.startIndex,
3251
+ scopeStart: scopeNode.startIndex,
3252
+ scopeEnd: scopeNode.endIndex
3253
+ });
3254
+ }
3255
+ }
3256
+ for (let i = 0; i < n.namedChildCount; i++) {
3257
+ const child = n.namedChild(i);
3258
+ if (child) visit(child, nextMethod, nextTypeScopes, nextBlock);
3259
+ }
3260
+ };
3261
+ visit(rootNode, null, [], null);
3262
+ return index;
3263
+ }
3264
+ /** Record every declarator a field/constant/local declaration binds, with the
3265
+ * declaration's written type and the range in which the binding is legal. A
3266
+ * declaration whose type is not a named type (a primitive, `var`) records
3267
+ * nothing: a primitive receiver has no type whose members could be resolved. */
3268
+ addJavaDeclaredMembers(decl, evidence, scope, scopeNode, index) {
3269
+ const typeNode = decl.childForFieldName("type");
3270
+ const isVarForm = typeNode?.type === "type_identifier" && typeNode.text === "var";
3271
+ let arrayElement = null;
3272
+ if (typeNode?.type === "array_type") {
3273
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
3274
+ const c = typeNode.namedChild(i);
3275
+ if (!c) continue;
3276
+ if (c.type === "type_identifier" || c.type === "generic_type" || c.type === "scoped_type_identifier") {
3277
+ const base = this.javaDeclaredTypeBaseName(c);
3278
+ if (base) {
3279
+ arrayElement = base;
3280
+ break;
3281
+ }
3282
+ }
3283
+ }
3284
+ }
3285
+ const typeName = isVarForm ? null : arrayElement ?? this.javaDeclaredTypeBaseName(typeNode);
3286
+ if (!typeName && !isVarForm) return;
3287
+ for (let i = 0; i < decl.namedChildCount; i++) {
3288
+ const declarator = decl.namedChild(i);
3289
+ if (!declarator || declarator.type !== "variable_declarator") continue;
3290
+ const nameNode = declarator.childForFieldName("name") || this.findChildByType(declarator, "identifier");
3291
+ if (!nameNode) continue;
3292
+ let memberType = typeName;
3293
+ let memberEvidence = evidence;
3294
+ if (isVarForm) {
3295
+ if (!index) continue;
3296
+ const inferred = this.javaInferVarInitializerType(declarator, index);
3297
+ if (!inferred) continue;
3298
+ memberType = inferred;
3299
+ memberEvidence = "initializer";
3300
+ }
3301
+ if (!memberType) continue;
3302
+ scope.members.set(nameNode.text, {
3303
+ type: memberType,
3304
+ evidence: memberEvidence,
3305
+ declStart: declarator.startIndex,
3306
+ scopeStart: evidence === "field" ? scope.start : scopeNode.startIndex,
3307
+ scopeEnd: evidence === "field" ? scope.end : scopeNode.endIndex,
3308
+ ...arrayElement !== null ? { isArray: true } : {}
3309
+ });
3310
+ }
3311
+ }
3312
+ /**
3313
+ * The statically stated type of a `var` declarator's INITIALIZER (JLS 14.4 / 10.3.2:
3314
+ * `var` takes the type of its initializer, it is not inferred from later use).
3315
+ *
3316
+ * Only shapes whose type the FILE WRITES are read: `new Foo()`, a cast's type, and a
3317
+ * method invocation whose name has exactly ONE declared return type in this file (an
3318
+ * overload set with differing returns is refused, the same rule the `return` evidence
3319
+ * class already states). String literals, chained calls on foreign types, lambdas and
3320
+ * arrays are refused: their type is not written in this file, and a wrong owner is
3321
+ * worse than none.
3322
+ */
3323
+ javaInferVarInitializerType(declarator, index) {
3324
+ let value = declarator.childForFieldName("value");
3325
+ for (let guard = 0; guard < 8 && value; guard++) {
3326
+ if (value.type !== "parenthesized_expression") break;
3327
+ value = value.namedChild(0);
3328
+ }
3329
+ if (!value) return null;
3330
+ if (value.type === "object_creation_expression") {
3331
+ const typeNode = this.findChildByType(value, "type_identifier") || this.findChildByType(value, "generic_type") || this.findChildByType(value, "scoped_type_identifier");
3332
+ return typeNode ? this.javaDeclaredTypeBaseName(typeNode) : null;
3333
+ }
3334
+ if (value.type === "cast_expression") {
3335
+ return this.javaDeclaredTypeBaseName(value.childForFieldName("type"));
3336
+ }
3337
+ if (value.type === "method_invocation") {
3338
+ const nameNode = value.childForFieldName("name");
3339
+ const name = nameNode ? nameNode.text : "";
3340
+ if (!name || this.isJavaBuiltin(name)) return null;
3341
+ const returns = index.methodReturns.get(name);
3342
+ return returns && returns.size === 1 ? [...returns][0] : null;
3343
+ }
3344
+ return null;
3345
+ }
3346
+ /** The type name a declared-type node writes, or null when the node is a
3347
+ * primitive / `var` / malformed. `javaTypeBaseName` keeps a qualified spelling
3348
+ * (`java.util.List`) as written, which is what "the receiver's statically known
3349
+ * type" means to this lane. */
3350
+ javaDeclaredTypeBaseName(typeNode) {
3351
+ if (!typeNode) return null;
3352
+ const t = typeNode.type;
3353
+ if (t === "integral_type" || t === "floating_point_type" || t === "boolean_type" || t === "void_type") return null;
3354
+ const base = javaTypeBaseName(typeNode);
3355
+ if (!base || base === "var" || base === "void") return null;
3356
+ return base;
3357
+ }
3358
+ /** The innermost scope of `scopes` whose range contains `pos` (innermost = the one
3359
+ * that starts latest; equal starts keep the first seen, which is deterministic
3360
+ * over one tree). */
3361
+ javaInnermostScope(scopes, pos) {
3362
+ let best = null;
3363
+ for (const scope of scopes) {
3364
+ if (scope.start <= pos && pos <= scope.end) {
3365
+ if (!best || scope.start >= best.start) best = scope;
3366
+ }
3367
+ }
3368
+ return best;
3369
+ }
3370
+ /** The base names a type declaration writes in its heritage clauses (JLS 8.1.4/8.1.5,
3371
+ * 9.1.3, 8.10.3): superclass first, then the interface list. Used ONLY by the depth
3372
+ * campaign's `super` receiver class — ancestry is otherwise never inferred. */
3373
+ javaDeclaredParentNames(declNode) {
3374
+ const inheritance = this.extractJavaInheritance(declNode);
3375
+ const names = [];
3376
+ if (inheritance.extendsClass?.name) names.push(inheritance.extendsClass.name);
3377
+ for (const p of inheritance.extendsInterfaces) if (p.name) names.push(p.name);
3378
+ for (const p of inheritance.implementsInterfaces) if (p.name) names.push(p.name);
3379
+ return names;
3380
+ }
3381
+ /** The declared type a bare identifier is BOUND to: method/lambda scopes first (innermost
3382
+ * first, with the declaration-before-use rule of JLS 6.3), then the enclosing types' fields.
3383
+ * Extracted from the receiver resolver so the array-element class can reuse the SAME schema
3384
+ * walk — a second, divergent copy is what this file's audits exist to catch. */
3385
+ javaResolveIdentifierBinding(name, pos, index) {
3386
+ const coveringMethods = [];
3387
+ for (const scope of index.methodScopes) {
3388
+ if (scope.start <= pos && pos <= scope.end) coveringMethods.push(scope);
3389
+ }
3390
+ coveringMethods.sort((a, b) => b.start - a.start);
3391
+ for (const methodScope of coveringMethods) {
3392
+ const hit = methodScope.members.get(name);
3393
+ if (hit && hit.declStart < pos && hit.scopeStart <= pos && pos <= hit.scopeEnd) return hit;
3394
+ }
3395
+ const coveringTypes = [];
3396
+ for (const scope of index.typeScopes) {
3397
+ if (scope.start <= pos && pos <= scope.end) coveringTypes.push(scope);
3398
+ }
3399
+ coveringTypes.sort((a, b) => b.start - a.start);
3400
+ for (const scope of coveringTypes) {
3401
+ const hit = scope.members.get(name);
3402
+ if (hit) return hit;
3403
+ }
3404
+ return null;
3405
+ }
3406
+ /** The innermost TYPE scope containing `pos` that has a NAME. An anonymous class body
3407
+ * deliberately has none: it is not a project declaration, so it can never be an owner. */
3408
+ javaEnclosingTypeScope(index, pos) {
3409
+ let best = null;
3410
+ for (const scope of index.typeScopes) {
3411
+ if (!scope.name) continue;
3412
+ if (scope.start <= pos && pos <= scope.end) {
3413
+ if (!best || scope.start >= best.start) best = scope;
3414
+ }
3415
+ }
3416
+ return best;
3417
+ }
3418
+ /** WOULD THE OWNER CHANNEL RESOLVE THIS ROW? — the guard, now shaped exactly like
3419
+ * `member-owner-resolution.ts` rather than merely "some member of that name exists".
3420
+ *
3421
+ * The channel's candidate model (its own code, mirrored):
3422
+ * 1. candidates are the owner's members with the relationship's KIND filter —
3423
+ * `calls` accepts function/method/constructor, `uses` accepts any member;
3424
+ * 2. if the OWNER declares the name, the row resolves there (even when an ancestor
3425
+ * also declares it: the direct hit wins);
3426
+ * 3. otherwise the members collected across the ancestor chain must be EXACTLY ONE —
3427
+ * two ancestors declaring the name is `ambiguousInChain`, a decline.
3428
+ *
3429
+ * Java's ProjectIndex CARRIES ancestry since 2026-10-20 — the spec-local `baseResolver` in
3430
+ * `JAVA_DECLARATION_SPEC` reads each type's DIRECT bases (measured: java's `typeBases` went
3431
+ * from EMPTY to populated; the fix is confined to the java spec block, and the other
3432
+ * languages' `typeBases` are byte-identical before and after). **THIS CHANNEL STILL WALKS
3433
+ * ONLY IN-FILE ANCESTRY**: the walk below reads `index.typeScopesByName`, and when a parent
3434
+ * is not in this file it consults the project's MEMBERS for that parent but does not walk
3435
+ * the PROJECT's ancestry. So a row whose only chance is a CROSS-FILE inherited member is
3436
+ * still refused (missing, never wrong).
3437
+ *
3438
+ * **THE PROJECT ANCESTRY IS NOW AVAILABLE TO BE USED HERE — AND THAT IS A SEPARATE INSTRUMENT,
3439
+ * NOT THIS COMMENT.** Walking `projectIndex.typeBases` would let this channel decide the
3440
+ * cross-file inherited-member rows the project index now knows the shape of; it would also
3441
+ * change the lane's recorded arm, so it is not a lane's unilateral change. Until it is
3442
+ * decided, the behaviour is exactly what it was. */
3443
+ javaOwnerResolvable(ownerName, memberName, rowType, index, projectIndex) {
3444
+ if (!ownerName || !memberName) return false;
3445
+ const wantsMethod = rowType === "calls";
3446
+ const fileScope = index.typeScopesByName.get(ownerName);
3447
+ const directFile = wantsMethod ? fileScope?.methodNames?.has(memberName) ?? false : fileScope?.memberNames?.has(memberName) ?? false;
3448
+ const directProject = projectIndex?.complete === true ? projectIndex.typeMembers.get(ownerName)?.has(memberName) ?? false : false;
3449
+ if (directFile || directProject) return true;
3450
+ let hits = 0;
3451
+ const walk = (name, depth) => {
3452
+ if (depth > 8) return;
3453
+ const scope = index.typeScopesByName.get(name);
3454
+ for (const parent of scope?.parents ?? []) {
3455
+ const parentScope = index.typeScopesByName.get(parent);
3456
+ if (parentScope) {
3457
+ const has = wantsMethod ? parentScope.methodNames?.has(memberName) ?? false : parentScope.memberNames?.has(memberName) ?? false;
3458
+ if (has) hits++;
3459
+ walk(parent, depth + 1);
3460
+ continue;
3461
+ }
3462
+ if (projectIndex?.complete === true && (projectIndex.typeMembers.get(parent)?.has(memberName) ?? false)) hits++;
3463
+ }
3464
+ };
3465
+ walk(ownerName, 0);
3466
+ return hits === 1;
3467
+ }
3468
+ /** Can an owner spelled `ownerName` be attached at all?
3469
+ *
3470
+ * A type DECLARED IN THIS FILE is eligible: the enclosing-type classes are exactly the
3471
+ * declarations this file contains. A type declared only in the PROJECT is eligible when a
3472
+ * COMPLETE index declares it exactly once; completeness is the bit that turns "declared
3473
+ * once in the project" from a fragment's opinion into a statement. When the complete
3474
+ * index shows the simple name declared MORE THAN ONCE the owner is refused: the channel
3475
+ * reports `ownerAmbiguous` and would withdraw the name binding for nothing. */
3476
+ javaTypeEligible(ownerName, index, projectIndex) {
3477
+ if (!ownerName) return false;
3478
+ const inFile = index.typeNames.has(ownerName);
3479
+ if (projectIndex?.complete === true) {
3480
+ const count = projectIndex.symbolCounts.get(ownerName) ?? 0;
3481
+ if (count > 1) return false;
3482
+ if (inFile) return true;
3483
+ return projectIndex.typeKinds.has(ownerName) && count === 1;
3484
+ }
3485
+ return inFile;
3486
+ }
3487
+ /**
3488
+ * The declared type of a call's receiver — the depth campaign's evidence classes.
3489
+ *
3490
+ * The 2026-10-18 contract classes (field / param / local / return) are read from THE
3491
+ * FILE'S OWN DECLARATIONS by `javaReceiverOwnerTypeFromFile` and are byte-identical to
3492
+ * that contract.
3493
+ *
3494
+ * The 2026-10-19 depth classes are evidence the file states, each guarded by
3495
+ * `javaOwnerResolvable` (the owner channel's own kind + ancestor rule) and
3496
+ * `javaTypeEligible`:
3497
+ * * `this` — an implicit-`this` call (JLS 15.12.1) and an explicit `this.m()`;
3498
+ * also implicit-`this` READS. Refused when an unqualified name could
3499
+ * come from a static import (JLS 7.5.3/7.5.4).
3500
+ * * `super` — `super.m()`: the first IN-FILE base that declares the member.
3501
+ * * `enum` — `Mode.ONE.act()` and a READ of the constant: its static type IS the
3502
+ * enum (JLS 8.9.1).
3503
+ * * `initializer` — `var` whose initializer states the type (JLS 14.4).
3504
+ * * `new` — a receiver that IS an object-creation expression (`new Foo().m()`):
3505
+ * the created type is written.
3506
+ * * `cast` — a receiver that is a cast (`((Foo) x).m()`): the cast type is written.
3507
+ * * `element` — an array-element receiver (`foos[i].m()`): the declared element type
3508
+ * of the array binding is written (the binding comes from the schema
3509
+ * classes above).
3510
+ *
3511
+ * Returns null — never a guess — for a receiver whose type no static evidence here states:
3512
+ * an inherited field, a chained call on a foreign/unknown return type, a library-typed
3513
+ * parameter, an untyped lambda parameter, multi-catch, and a CLASS-QUALIFIED STATIC CALL
3514
+ * (`Helper.help()` — its receiver already names its owner, which the 2026-10-18 contract
3515
+ * and its fixture deliberately keep owner-less).
3516
+ */
3517
+ javaReceiverOwnerType(callNode, index, projectIndex, rowType = "calls") {
3518
+ const fieldLocal = this.javaReceiverOwnerTypeFromFile(callNode, index);
3519
+ const targetNode = callNode.childForFieldName("name") ?? callNode.childForFieldName("field");
3520
+ const targetName = targetNode ? targetNode.text : "";
3521
+ if (fieldLocal) {
3522
+ if (fieldLocal.evidence !== "initializer") return fieldLocal;
3523
+ if (!this.javaTypeEligible(fieldLocal.type, index, projectIndex)) return null;
3524
+ if (!this.javaOwnerResolvable(fieldLocal.type, targetName, rowType, index, projectIndex)) return null;
3525
+ return fieldLocal;
3526
+ }
3527
+ if (!targetName) return null;
3528
+ const objectNode = callNode.childForFieldName("object");
3529
+ const pos = callNode.startIndex;
3530
+ const enclosing = this.javaEnclosingTypeScope(index, pos);
3531
+ const emit = (type, evidence) => {
3532
+ if (!this.javaTypeEligible(type, index, projectIndex)) return null;
3533
+ if (!this.javaOwnerResolvable(type, targetName, rowType, index, projectIndex)) return null;
3534
+ return { type, evidence };
3535
+ };
3536
+ if (!objectNode) {
3537
+ if (!enclosing?.name) return null;
3538
+ if (index.staticImportLeaves.has(targetName) || index.hasStaticOnDemand) return null;
3539
+ return emit(enclosing.name, "this");
3540
+ }
3541
+ const ot = objectNode.type;
3542
+ if (ot === "this") {
3543
+ if (!enclosing?.name) return null;
3544
+ return emit(enclosing.name, "this");
3545
+ }
3546
+ if (ot === "super") {
3547
+ if (!enclosing?.name) return null;
3548
+ for (const parent of enclosing.parents ?? []) {
3549
+ const hit = emit(parent, "super");
3550
+ if (hit) return hit;
3551
+ }
3552
+ return null;
3553
+ }
3554
+ if (ot === "object_creation_expression") {
3555
+ const createdNode = this.findChildByType(objectNode, "type_identifier") || this.findChildByType(objectNode, "generic_type") || this.findChildByType(objectNode, "scoped_type_identifier");
3556
+ const created = createdNode ? this.javaDeclaredTypeBaseName(createdNode) : null;
3557
+ if (!created) return null;
3558
+ return emit(created, "new");
3559
+ }
3560
+ if (ot === "cast_expression" || ot === "parenthesized_expression") {
3561
+ let expr = objectNode;
3562
+ for (let guard = 0; guard < 8 && expr && expr.type === "parenthesized_expression"; guard++) {
3563
+ expr = expr.namedChild(0);
3564
+ }
3565
+ if (expr && expr.type === "cast_expression") {
3566
+ const castType = this.javaDeclaredTypeBaseName(expr.childForFieldName("type"));
3567
+ if (!castType) return null;
3568
+ return emit(castType, "cast");
3569
+ }
3570
+ return null;
3571
+ }
3572
+ if (ot === "array_access") {
3573
+ const arrayNode = objectNode.childForFieldName("array");
3574
+ if (!arrayNode || arrayNode.type !== "identifier") return null;
3575
+ const binding = this.javaResolveIdentifierBinding(arrayNode.text, pos, index);
3576
+ if (!binding || !binding.isArray) return null;
3577
+ return emit(binding.type, "element");
3578
+ }
3579
+ if (ot === "identifier" && callNode.type === "field_access") {
3580
+ const enumName = objectNode.text;
3581
+ if (index.typeKindsByName.get(enumName) !== "enum_declaration") return null;
3582
+ return emit(enumName, "enum") ?? null;
3583
+ }
3584
+ if (ot === "field_access") {
3585
+ const parts = objectNode.text.split(".");
3586
+ if (parts.length === 2 && index.typeKindsByName.get(parts[0]) === "enum_declaration") {
3587
+ if (!index.typeScopesByName.get(parts[0])?.memberNames?.has(parts[1])) return null;
3588
+ const enumName = parts[0];
3589
+ return emit(enumName, "enum");
3590
+ }
3591
+ return null;
3592
+ }
3593
+ return null;
3594
+ }
3595
+ /**
3596
+ * The 2026-10-18 contract: the declared type of a receiver from THE FILE'S OWN
3597
+ * DECLARATIONS. Returns null — never a guess — for every receiver whose type this file
3598
+ * does not state: an implicit `this`, a class-qualified static call, `super`, chained
3599
+ * access on an untyped receiver, an inherited field, and a `var` local. The four
3600
+ * evidence classes are the declarations the contract names: field / param /
3601
+ * local / return.
3602
+ */
3603
+ javaReceiverOwnerTypeFromFile(callNode, index) {
3604
+ const objectNode = callNode.childForFieldName("object");
3605
+ if (!objectNode) return null;
3606
+ const pos = callNode.startIndex;
3607
+ if (objectNode.type === "identifier") {
3608
+ const binding = this.javaResolveIdentifierBinding(objectNode.text, pos, index);
3609
+ if (binding && !binding.isArray) return { type: binding.type, evidence: binding.evidence };
3610
+ return null;
3611
+ }
3612
+ if (objectNode.type === "field_access") {
3613
+ const parts = objectNode.text.split(".");
3614
+ if (parts.length === 2 && parts[0] === "this") {
3615
+ const name = parts[1];
3616
+ for (const scope of index.typeScopes) {
3617
+ if (!(scope.start <= pos && pos <= scope.end)) continue;
3618
+ const hit = scope.members.get(name);
3619
+ if (hit) return { type: hit.type, evidence: "field" };
3620
+ }
3621
+ }
3622
+ return null;
3623
+ }
3624
+ if (objectNode.type === "method_invocation") {
3625
+ const nameNode = objectNode.childForFieldName("name");
3626
+ const name = nameNode ? nameNode.text : null;
3627
+ if (!name || this.isJavaBuiltin(name)) return null;
3628
+ const returns = index.methodReturns.get(name);
3629
+ if (returns && returns.size === 1) {
3630
+ const only = [...returns][0];
3631
+ return { type: only, evidence: "return" };
3632
+ }
3633
+ return null;
3634
+ }
3635
+ return null;
3636
+ }
3637
+ /**
3638
+ * Short annotation name from an annotation/marker_annotation node's name
3639
+ * child. The pinned grammar serializes the name as $._name =
3640
+ * choice(identifier, scoped_identifier) — probe-verified: `@Override` has a
3641
+ * direct identifier, `@org.springframework.stereotype.Service` has a
3642
+ * scoped_identifier. The short name is the last dotted segment; bare names
3643
+ * pass through unchanged.
3644
+ *
3645
+ * F6 audit: a third `_reserved_identifier` fallback had been added here on
3646
+ * the claim that the grammar's `_name` was a triple choice including it.
3647
+ * That name is an underscore-prefixed HIDDEN rule, so `.type` can never
3648
+ * equal it and `findChildByType` can never match it — a branch that could
3649
+ * not fire. Removed; the two real shapes cover every annotation name.
3650
+ */
3651
+ getJavaAnnotationShortName(ann) {
3652
+ const nameNode = this.findChildByType(ann, "identifier") || this.findChildByType(ann, "scoped_identifier");
3653
+ if (!nameNode) return null;
3654
+ const text = nameNode.text;
3655
+ const lastDot = text.lastIndexOf(".");
3656
+ return lastDot === -1 ? text : text.slice(lastDot + 1);
3657
+ }
3658
+ /**
3659
+ * Bare annotation names extracted from the AST (@Service → Service), read
3660
+ * from the identifier field of each annotation node — never from text.
3661
+ */
3662
+ getJavaAnnotationNames(node) {
3663
+ const names = [];
3664
+ const modifiersNode = this.findChildByType(node, "modifiers");
3665
+ const target = modifiersNode || node;
3666
+ for (let i = 0; i < target.namedChildCount; i++) {
3667
+ const child = target.namedChild(i);
3668
+ if (child && (child.type === "annotation" || child.type === "marker_annotation")) {
3669
+ const name = this.getJavaAnnotationShortName(child);
3670
+ if (name) names.push(name);
3671
+ }
3672
+ }
3673
+ return names;
3674
+ }
3675
+ /**
3676
+ * Semantic text of a value node: for string_literal nodes the concatenated
3677
+ * string_fragment children (unquoted) instead of raw quoted text; for all
3678
+ * other node types the raw text.
3679
+ */
3680
+ extractStringValue(node) {
3681
+ if (!node) return void 0;
3682
+ if (node.type === "string_literal") {
3683
+ let fragmentText = "";
3684
+ for (let i = 0; i < node.namedChildCount; i++) {
3685
+ const c = node.namedChild(i);
3686
+ if (c && (c.type === "string_fragment" || c.type === "multiline_string_fragment")) fragmentText += c.text;
3687
+ }
3688
+ if (fragmentText) return fragmentText;
3689
+ }
3690
+ if (node.type === "binary_expression") {
3691
+ return this.extractJavaConstantString(node);
3692
+ }
3693
+ return node.text;
3694
+ }
3695
+ /** Strict constant-string folder: literals/text blocks/parens/+ chains
3696
+ * only. Any non-literal operand makes the whole expression unresolved. */
3697
+ extractJavaConstantString(node) {
3698
+ if (node.type === "parenthesized_expression") {
3699
+ const inner = node.namedChild(0);
3700
+ return inner ? this.extractJavaConstantString(inner) : void 0;
3701
+ }
3702
+ if (node.type === "string_literal") {
3703
+ let fragmentText = "";
3704
+ for (let i = 0; i < node.namedChildCount; i++) {
3705
+ const c = node.namedChild(i);
3706
+ if (c && (c.type === "string_fragment" || c.type === "multiline_string_fragment")) fragmentText += c.text;
3707
+ }
3708
+ return fragmentText || void 0;
3709
+ }
3710
+ if (node.type === "binary_expression") {
3711
+ const op = node.childForFieldName("operator")?.text;
3712
+ if (op !== "+") return void 0;
3713
+ const l = node.namedChild(0);
3714
+ const r = node.namedChild(1);
3715
+ if (!l || !r) return void 0;
3716
+ const lt = this.extractJavaConstantString(l);
3717
+ const rt = this.extractJavaConstantString(r);
3718
+ if (lt === void 0 || rt === void 0) return void 0;
3719
+ const joined = lt + rt;
3720
+ return joined || void 0;
3721
+ }
3722
+ return void 0;
3723
+ }
3724
+ getJavaVisibility(modifiers) {
3725
+ if (modifiers.indexOf("public") !== -1) return "public";
3726
+ if (modifiers.indexOf("private") !== -1) return "private";
3727
+ if (modifiers.indexOf("protected") !== -1) return "protected";
3728
+ return void 0;
3729
+ }
3730
+ /**
3731
+ * Extract the base type name from a type node, stripping type arguments so
3732
+ * inheritance targets are clean symbol names (Repository<T> → Repository).
3733
+ * A scoped type (BaseService.Entity) keeps its full dotted name.
3734
+ */
3735
+ /**
3736
+ * M14 #419 G4: nearest enclosing type scope for call attribution when a
3737
+ * method_invocation sits outside any method body (initializer blocks,
3738
+ * field initializers, enum constant bodies). Scans the scope stack from
3739
+ * the top, skipping member-level scopes (Property/Constant/EnumMember),
3740
+ * and returns the nearest type-level scope. Defined in-class so the
3741
+ * handler above can reach it without exposing Scope publicly.
3742
+ */
3743
+ resolveEnclosingTypeScope(scopeStack) {
3744
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
3745
+ const s = scopeStack[i];
3746
+ if (s.kind === ASTEngine.SymbolKind.Class || s.kind === ASTEngine.SymbolKind.Interface || s.kind === ASTEngine.SymbolKind.Enum || s.kind === ASTEngine.SymbolKind.Type) {
3747
+ return { name: s.name, kind: s.kind };
3748
+ }
3749
+ }
3750
+ return void 0;
3751
+ }
3752
+ /**
3753
+ * Can this string be a java NAME? Letters, digits, `_`, `$`, and `.` for a
3754
+ * qualified name. No regex (analyzers carry no regex literals, R2 register) —
3755
+ * a character walk, the same shape as the census instrument's.
3756
+ *
3757
+ * Used to refuse an edge whose target is not a name at all, rather than to
3758
+ * invent one. `newArrayList(v3(), v4())` and `"com.google.common."` both fail
3759
+ * it; `V`, `com.google.common.collect`, `ImmutableList.Builder` and
3760
+ * `CollectionFeature.NONE` all pass.
3761
+ */
3762
+ isJavaNameShape(s) {
3763
+ if (!s) return false;
3764
+ for (let i = 0; i < s.length; i++) {
3765
+ const c = s[i];
3766
+ const ok = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "$" || c === ".";
3767
+ if (!ok) return false;
3768
+ }
3769
+ return true;
3770
+ }
3771
+ extractJavaTypeBaseName(typeNode) {
3772
+ return javaTypeBaseName(typeNode);
3773
+ }
3774
+ /**
3775
+ * M59 (#825): every parent is `{name, line}` — the line the PARENT IS NAMED on.
3776
+ *
3777
+ * java writes a long heritage list on separate lines
3778
+ * (`class A extends B\n implements C, D`), so a clause-start line named only
3779
+ * the first entry. Same rule as scala's `extractSupertypes` and the reason the
3780
+ * shape is pairs rather than names.
3781
+ */
3782
+ extractJavaInheritance(classNode) {
3783
+ const inheritance = {
3784
+ extendsClass: null,
3785
+ // interface_declaration > extends_interfaces > type_list: EVERY parent is an extends (JLS 9.1.3)
3786
+ extendsInterfaces: [],
3787
+ implementsInterfaces: [],
3788
+ // sealed class permits clause (JLS 8.1.1.2): permitted subtypes are
3789
+ // CHILDREN of the sealed class, not ancestors — kept out of parents[]
3790
+ // and surfaced separately so callers can emit directional edges.
3791
+ permitsSubtypes: []
3792
+ };
3793
+ const superclass = this.findChildByType(classNode, "superclass");
3794
+ if (superclass) {
3795
+ const typeIdentifier = this.findChildByType(superclass, "scoped_type_identifier") || this.findChildByType(superclass, "generic_type") || this.findChildByType(superclass, "type_identifier");
3796
+ if (typeIdentifier) inheritance.extendsClass = { name: this.extractJavaTypeBaseName(typeIdentifier), line: typeIdentifier.startPosition.row + 1 };
3797
+ }
3798
+ const superInterfaces = this.findChildByType(classNode, "super_interfaces") || this.findChildByType(classNode, "extends_interfaces");
3799
+ if (superInterfaces) {
3800
+ const targetList = this.findChildByType(superInterfaces, "type_list") || superInterfaces;
3801
+ const isInterfaceExtends = superInterfaces.type === "extends_interfaces";
3802
+ const bucket = isInterfaceExtends ? inheritance.extendsInterfaces : inheritance.implementsInterfaces;
3803
+ for (let i = 0; i < targetList.namedChildCount; i++) {
3804
+ const interfaceType = targetList.namedChild(i);
3805
+ if (interfaceType && (interfaceType.type === "type_identifier" || interfaceType.type === "generic_type" || interfaceType.type === "scoped_type_identifier")) {
3806
+ bucket.push({ name: this.extractJavaTypeBaseName(interfaceType), line: interfaceType.startPosition.row + 1 });
3807
+ }
3808
+ }
3809
+ }
3810
+ const permitsNode = this.findChildByType(classNode, "permits");
3811
+ if (permitsNode) {
3812
+ const permitsList = this.findChildByType(permitsNode, "type_list") || permitsNode;
3813
+ for (let i = 0; i < permitsList.namedChildCount; i++) {
3814
+ const subtype = permitsList.namedChild(i);
3815
+ if (subtype && (subtype.type === "type_identifier" || subtype.type === "generic_type" || subtype.type === "scoped_type_identifier")) {
3816
+ inheritance.permitsSubtypes.push({ name: this.extractJavaTypeBaseName(subtype), line: subtype.startPosition.row + 1 });
3817
+ }
3818
+ }
3819
+ }
3820
+ return inheritance;
3821
+ }
3822
+ extractJavaParameters(node, relationships, ownerPrefix) {
3823
+ const parameters = [];
3824
+ const formalParams = this.findChildByType(node, "formal_parameters");
3825
+ const targetNode = formalParams ? formalParams : node;
3826
+ for (let i = 0; i < targetNode.namedChildCount; i++) {
3827
+ const param = targetNode.namedChild(i);
3828
+ if (param && param.type === "formal_parameter") {
3829
+ const nameNode = param.childForFieldName("name") || this.findChildByType(param, "identifier");
3830
+ if (nameNode) {
3831
+ const paramType = this.extractJavaParameterType(param);
3832
+ parameters.push({ name: nameNode.text, type: paramType, isOptional: false });
3833
+ if (relationships && ownerPrefix) {
3834
+ const valueAnn = inArray(this.getJavaAnnotationNames(param), "Value");
3835
+ if (valueAnn) {
3836
+ const valMatch = this.getAnnotationValue(param, "Value", "value");
3837
+ if (valMatch && valMatch.startsWith("${") && valMatch.endsWith("}")) {
3838
+ relationships.push({
3839
+ source: ownerPrefix + "." + nameNode.text,
3840
+ target: valMatch.substring(2, valMatch.length - 1).split(":")[0],
3841
+ type: ASTEngine.RelationshipType.Uses,
3842
+ confidence: 1,
3843
+ // M59 (#825): the key is written in the PARAMETER's own
3844
+ // `@Value` — the parameter is the node that carries it.
3845
+ ...this.annotationLine(param, "Value") ? { line_number: this.annotationLine(param, "Value") } : {},
3846
+ metadata: { isConfiguration: true, relationshipType: "config_property" }
3847
+ });
3848
+ }
3849
+ }
3850
+ }
3851
+ }
3852
+ } else if (param && param.type === "spread_parameter") {
3853
+ const decl = this.findChildByType(param, "variable_declarator");
3854
+ const nameNode = decl ? this.findChildByType(decl, "identifier") : null;
3855
+ if (nameNode) {
3856
+ let spreadType = void 0;
3857
+ for (let cIdx = 0; cIdx < param.namedChildCount; cIdx++) {
3858
+ const c = param.namedChild(cIdx);
3859
+ if (c && c.type !== "modifiers" && !stringIncludes(c.type, "annotation") && c.type !== "variable_declarator") {
3860
+ spreadType = c.text;
3861
+ break;
3862
+ }
3863
+ }
3864
+ parameters.push({ name: nameNode.text, type: (spreadType || "unknown") + "[]", isOptional: false });
3865
+ }
3866
+ } else if (param && param.type === "ERROR") {
3867
+ let rawText = param.text;
3868
+ if (rawText.startsWith(",")) rawText = rawText.slice(1);
3869
+ rawText = rawText.trim();
3870
+ if (rawText) {
3871
+ const parts = splitByChar(rawText, ",");
3872
+ for (const p of parts) {
3873
+ const pt = p.trim();
3874
+ if (!pt) continue;
3875
+ const tokens = tokenizeWhitespace(pt);
3876
+ let name = tokens[tokens.length - 1];
3877
+ let cleanName = "";
3878
+ for (let ci = 0; ci < name.length; ci++) {
3879
+ const ch = name[ci];
3880
+ if (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_") cleanName += ch;
3881
+ }
3882
+ name = cleanName;
3883
+ if (name) {
3884
+ const lastToken = tokens[tokens.length - 1];
3885
+ let typeGuess = pt.length > lastToken.length ? pt.slice(0, pt.length - lastToken.length).trim() : "";
3886
+ typeGuess = stripJavaAnnotations(typeGuess).split("...").join("[]").trim() || "unknown";
3887
+ parameters.push({ name, type: typeGuess, isOptional: false });
3888
+ }
3889
+ }
3890
+ }
3891
+ }
3892
+ }
3893
+ return parameters;
3894
+ }
3895
+ extractJavaParameterType(paramNode) {
3896
+ const typeNode = paramNode.childForFieldName("type");
3897
+ if (typeNode) return typeNode.text;
3898
+ for (let i = 0; i < paramNode.namedChildCount; i++) {
3899
+ const child = paramNode.namedChild(i);
3900
+ if (child && child.type !== "identifier" && child.type !== "modifiers" && !stringIncludes(child.type, "annotation")) return child.text;
3901
+ }
3902
+ return void 0;
3903
+ }
3904
+ extractJavaReturnType(node) {
3905
+ const typeNode = node.childForFieldName("type");
3906
+ if (typeNode) return typeNode.text;
3907
+ for (let i = 0; i < node.namedChildCount; i++) {
3908
+ const child = node.namedChild(i);
3909
+ if (child && (child.type === "type_identifier" || child.type === "generic_type" || child.type === "array_type" || child.type === "scoped_type_identifier" || child.type === "void_type" || child.type === "integral_type" || child.type === "floating_point_type" || child.type === "boolean_type")) {
3910
+ return child.text;
3911
+ }
3912
+ }
3913
+ return void 0;
3914
+ }
3915
+ extractJavaFieldType(fieldNode) {
3916
+ return this.extractJavaReturnType(fieldNode);
3917
+ }
3918
+ buildJavaCallExpression(invocationNode) {
3919
+ const objectNode = invocationNode.childForFieldName("object");
3920
+ const nameNode = invocationNode.childForFieldName("name");
3921
+ if (!nameNode) {
3922
+ const text = invocationNode.text;
3923
+ const paren = text.indexOf("(");
3924
+ return paren === -1 ? text : text.slice(0, paren);
3925
+ }
3926
+ if (objectNode) {
3927
+ const objectText = objectNode.type === "method_invocation" ? this.buildJavaCallExpression(objectNode) : objectNode.text;
3928
+ return `${objectText}.${nameNode.text}`;
3929
+ }
3930
+ return nameNode.text;
3931
+ }
3932
+ isJavaBuiltin(call) {
3933
+ const builtins = [
3934
+ "System.out.println",
3935
+ "System.out.print",
3936
+ "System.out.printf",
3937
+ "System.err.println",
3938
+ "System.err.print",
3939
+ "System.exit",
3940
+ "System.gc",
3941
+ "System.currentTimeMillis",
3942
+ "System.nanoTime",
3943
+ "System.getProperty",
3944
+ "System.getenv",
3945
+ "System.identityHashCode",
3946
+ "System.arraycopy",
3947
+ "toString",
3948
+ "hashCode",
3949
+ "equals",
3950
+ "getClass",
3951
+ "notify",
3952
+ "notifyAll",
3953
+ "wait",
3954
+ "clone",
3955
+ "finalize"
3956
+ ];
3957
+ for (let i = 0; i < builtins.length; i++) {
3958
+ if (builtins[i] === call) return true;
3959
+ }
3960
+ const firstDot = call.indexOf(".");
3961
+ const root = firstDot > 0 ? call.slice(0, firstDot) : call;
3962
+ const STDLIB_ROOTS = /* @__PURE__ */ new Set([
3963
+ // java.lang
3964
+ "System",
3965
+ "String",
3966
+ "Integer",
3967
+ "Long",
3968
+ "Double",
3969
+ "Float",
3970
+ "Boolean",
3971
+ "Byte",
3972
+ "Short",
3973
+ "Character",
3974
+ "Math",
3975
+ "StrictMath",
3976
+ "Object",
3977
+ "Class",
3978
+ "Thread",
3979
+ "Runnable",
3980
+ "Runtime",
3981
+ "Process",
3982
+ "ProcessBuilder",
3983
+ "Throwable",
3984
+ "Error",
3985
+ "Exception",
3986
+ "StackTraceElement",
3987
+ "Enum",
3988
+ "Void",
3989
+ "Compiler",
3990
+ "Package",
3991
+ // java.util
3992
+ "ArrayList",
3993
+ "LinkedList",
3994
+ "HashMap",
3995
+ "HashSet",
3996
+ "TreeMap",
3997
+ "TreeSet",
3998
+ "LinkedHashMap",
3999
+ "LinkedHashSet",
4000
+ "WeakHashMap",
4001
+ "IdentityHashMap",
4002
+ "Collections",
4003
+ "Arrays",
4004
+ "Objects",
4005
+ "Optional",
4006
+ "OptionalInt",
4007
+ "OptionalLong",
4008
+ "OptionalDouble",
4009
+ "Stream",
4010
+ "Collectors",
4011
+ "Comparator",
4012
+ "Iterator",
4013
+ "ListIterator",
4014
+ "Spliterator",
4015
+ "Random",
4016
+ "UUID",
4017
+ "Date",
4018
+ "Calendar",
4019
+ "TimeZone",
4020
+ "Locale",
4021
+ "Currency",
4022
+ "Base64",
4023
+ "Scanner",
4024
+ "Formatter",
4025
+ "StringJoiner",
4026
+ "AbstractList",
4027
+ "AbstractMap",
4028
+ "AbstractSet",
4029
+ "AbstractCollection",
4030
+ "EnumMap",
4031
+ "EnumSet",
4032
+ "Queue",
4033
+ "Deque",
4034
+ "Stack",
4035
+ "Vector",
4036
+ "Hashtable",
4037
+ "Properties",
4038
+ "BitSet",
4039
+ "Dictionary",
4040
+ "List",
4041
+ "Map",
4042
+ "Set",
4043
+ "Collection",
4044
+ "Iterable",
4045
+ // java.io / java.nio
4046
+ "File",
4047
+ "Files",
4048
+ "Path",
4049
+ "Paths",
4050
+ "InputStream",
4051
+ "OutputStream",
4052
+ "Reader",
4053
+ "Writer",
4054
+ "BufferedReader",
4055
+ "BufferedWriter",
4056
+ "FileInputStream",
4057
+ "FileOutputStream",
4058
+ "FileReader",
4059
+ "FileWriter",
4060
+ "ByteArrayInputStream",
4061
+ "ByteArrayOutputStream",
4062
+ "StringReader",
4063
+ "StringWriter",
4064
+ "PrintWriter",
4065
+ "PrintStream",
4066
+ "IOException",
4067
+ "FileNotFoundException",
4068
+ "EOFException",
4069
+ // java.net / java.sql / javax
4070
+ "URL",
4071
+ "URI",
4072
+ "HttpURLConnection",
4073
+ "URLEncoder",
4074
+ "URLDecoder",
4075
+ "Connection",
4076
+ "DriverManager",
4077
+ "ResultSet",
4078
+ "Statement",
4079
+ "PreparedStatement",
4080
+ "SQLException",
4081
+ "DataSource",
4082
+ "javax",
4083
+ "jakarta",
4084
+ // kotlin stdlib
4085
+ "kotlin",
4086
+ "Unit",
4087
+ "Nothing",
4088
+ "Any",
4089
+ "Int",
4090
+ "String",
4091
+ // Common JDK prefixes
4092
+ "java",
4093
+ "jdk",
4094
+ "sun",
4095
+ // Logger frameworks (always stdlib when called by these names)
4096
+ "Logger",
4097
+ "LoggerFactory",
4098
+ "LogManager",
4099
+ "Level",
4100
+ "LogRecord",
4101
+ "Marker",
4102
+ "MDC",
4103
+ "NDC",
4104
+ // Common annotations (never architectural)
4105
+ "Override",
4106
+ "Deprecated",
4107
+ "SuppressWarnings",
4108
+ "SafeVarargs",
4109
+ "FunctionalInterface",
4110
+ "Nullable",
4111
+ "NotNull",
4112
+ "NonNls",
4113
+ "Test",
4114
+ "Before",
4115
+ "After",
4116
+ "BeforeClass",
4117
+ "AfterClass",
4118
+ "Ignore",
4119
+ "RunWith",
4120
+ "Suite",
4121
+ "Parameterized",
4122
+ // org.slf4j / logging
4123
+ "slf4j",
4124
+ "log4j",
4125
+ "logback"
4126
+ ]);
4127
+ const segments = call.split(".");
4128
+ if (segments.length > 0 && STDLIB_ROOTS.has(segments[0])) return true;
4129
+ if (STDLIB_ROOTS.has(call)) return true;
4130
+ return false;
4131
+ }
4132
+ /**
4133
+ * M14 incoming — the external-target marker, and only for the PROVABLE cases.
4134
+ *
4135
+ * `script/m14/incoming-gate.ts` counts an edge as resolved (one declaration), ambiguous
4136
+ * (several), or unresolved. A JDK symbol is a fourth thing — legitimately outside the
4137
+ * corpus — and an unmarked edge of that kind is indistinguishable from a broken one, so it
4138
+ * counts against the lane. Measured before this marker existed on java (200 files,
4139
+ * `npx tsx script/m14/incoming-gate.ts java java 200 --json`): **16,021 unresolved of 26,095
4140
+ * edges, `markedExternal 0` on EVERY type**, and `imports` 2,716 of 2,716 unresolved.
4141
+ *
4142
+ * Only two facts are provable from a single file, so only two are asserted:
4143
+ * - the target carries a JVM-platform / Kotlin-stdlib qualifier (`java.`, `javax.`,
4144
+ * `jakarta.`, `jdk.`, `sun.`, `com.sun.`, `kotlin.`, `kotlinx.`);
4145
+ * - the target's ROOT segment is a local name the file IMPORTS from such a module, and the
4146
+ * file does not declare that name itself.
4147
+ *
4148
+ * Deliberately NOT marked, because the prefix proves nothing about a single file:
4149
+ * - `org.` / `com.` third-party packages — in a multi-module build `com.acme` IS project
4150
+ * source, the same reason `com.sun` and `com.mycompany` were separated in `isJavaBuiltin`;
4151
+ * - wildcard imports (`import java.util.*`) — the file never names the type;
4152
+ * - java.lang simple names used with no import (`String`, `Object`, `System`). They are
4153
+ * *probably* the platform types, and "probably" is what this marker must not say: an
4154
+ * unproven marker hides a genuine unresolved edge behind a claim the file cannot make.
4155
+ * They stay counted against the lane until a rule can prove them.
4156
+ */
4157
+ markExternalTargets(relationships, symbols, imports, context) {
4158
+ const JVM_ROOTS = ["java.", "javax.", "jakarta.", "jdk.", "sun.", "com.sun."];
4159
+ const isJvmModule = (name) => JVM_ROOTS.some((r) => name.startsWith(r));
4160
+ const declared = /* @__PURE__ */ new Set();
4161
+ for (const s of symbols) {
4162
+ const n = String(s?.name ?? "");
4163
+ if (n.length > 0) declared.add(n);
4164
+ const q = String(s?.qualifiedName ?? "");
4165
+ if (q.length > 0) declared.add(q);
4166
+ }
4167
+ const importedFromJvm = /* @__PURE__ */ new Map();
4168
+ for (const im of imports) {
4169
+ const module = String(im?.module ?? "");
4170
+ if (module.length === 0 || !isJvmModule(module)) continue;
4171
+ if (im?.isNamespace === true || im?.metadata?.isWildcard === true || module.endsWith(".*")) continue;
4172
+ const items = Array.isArray(im?.items) ? im.items : [];
4173
+ let named = false;
4174
+ for (const it of items) {
4175
+ const n = String(it?.name ?? it?.alias ?? "");
4176
+ if (n.length > 0) {
4177
+ importedFromJvm.set(n, module);
4178
+ named = true;
4179
+ }
4180
+ }
4181
+ if (!named) {
4182
+ const leaf = module.slice(module.lastIndexOf(".") + 1);
4183
+ if (leaf.length > 0 && leaf !== "*") importedFromJvm.set(leaf, module);
4184
+ }
4185
+ }
4186
+ let marked = 0;
4187
+ for (const r of relationships) {
4188
+ const target = String(r?.target ?? "");
4189
+ if (target.length === 0) continue;
4190
+ const md = r.metadata ?? (r.metadata = {});
4191
+ if (md.isExternalTarget === true) continue;
4192
+ if (declared.has(target)) continue;
4193
+ if (String(r?.type ?? "") === "references" && md.relationshipType === "import_declaration_mention" && isJvmModule(target) && !declared.has(target)) {
4194
+ md.languageNamespace = [target];
4195
+ marked++;
4196
+ continue;
4197
+ }
4198
+ const CLAIMED_KINDS = /* @__PURE__ */ new Set(["references", "extends", "implements", "instantiates"]);
4199
+ if (JAVA_LANG_TYPES.has(target) && !importedFromJvm.has(target) && CLAIMED_KINDS.has(String(r?.type ?? ""))) {
4200
+ md.languageNamespace = [target];
4201
+ marked++;
4202
+ continue;
4203
+ }
4204
+ const colon = target.indexOf(":");
4205
+ if (colon > 0 && !target.startsWith("/") && !target.includes("/")) {
4206
+ const module2 = target.slice(0, colon);
4207
+ if (isJvmModule(module2)) {
4208
+ md.isExternalTarget = true;
4209
+ md.externalReason = "builtin-module";
4210
+ md.externalSpecifier = module2;
4211
+ marked++;
4212
+ }
4213
+ continue;
4214
+ }
4215
+ if (isJvmModule(target)) {
4216
+ md.isExternalTarget = true;
4217
+ md.externalReason = "builtin-lib-type";
4218
+ md.externalSpecifier = JVM_ROOTS.find((p) => target.startsWith(p)) ?? "";
4219
+ marked++;
4220
+ continue;
4221
+ }
4222
+ const root = target.split(".")[0];
4223
+ if (root.length === 0 || declared.has(root)) continue;
4224
+ const module = importedFromJvm.get(root);
4225
+ if (module !== void 0) {
4226
+ md.isExternalTarget = true;
4227
+ md.externalReason = "builtin-lib-type";
4228
+ md.externalReceiver = root;
4229
+ md.externalSpecifier = module;
4230
+ marked++;
4231
+ continue;
4232
+ }
4233
+ if (md.isExternalTarget === true) continue;
4234
+ if (context?.projectIndex?.complete !== true) continue;
4235
+ if (String(r?.type ?? "") === "imports" || String(r?.type ?? "") === "brain_import") continue;
4236
+ if (!isPlainJavaIdentifier(target)) continue;
4237
+ if (JAVA_RESERVED.has(target)) continue;
4238
+ if (context.projectIndex.symbolNames.has(target)) continue;
4239
+ md.isExternalTarget = true;
4240
+ md.externalReason = "project-scope-bare-name";
4241
+ md.externalProof = `ProjectIndex.complete === true; symbolNames has no "${target}"`;
4242
+ marked++;
4243
+ }
4244
+ return marked;
4245
+ }
4246
+ getAnnotationValue(node, annotationName, key) {
4247
+ const findAllAnnotations = (n) => {
4248
+ const results = [];
4249
+ for (let i = 0; i < n.namedChildCount; i++) {
4250
+ const child = n.namedChild(i);
4251
+ if (child && (child.type === "annotation" || child.type === "marker_annotation")) results.push(child);
4252
+ }
4253
+ return results;
4254
+ };
4255
+ let annotations = findAllAnnotations(node);
4256
+ const modifiersNode = (() => {
4257
+ for (let i = 0; i < node.namedChildCount; i++) {
4258
+ const child = node.namedChild(i);
4259
+ if (child && child.type === "modifiers") return child;
4260
+ }
4261
+ return null;
4262
+ })();
4263
+ if (modifiersNode) {
4264
+ annotations = annotations.concat(findAllAnnotations(modifiersNode));
4265
+ }
4266
+ for (let j = 0; j < annotations.length; j++) {
4267
+ const ann = annotations[j];
4268
+ const name = this.getJavaAnnotationShortName(ann);
4269
+ if (name && name === annotationName) {
4270
+ const argsNode = this.findChildByType(ann, "annotation_argument_list");
4271
+ if (!argsNode) return void 0;
4272
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
4273
+ const arg = argsNode.namedChild(i);
4274
+ if (!arg) continue;
4275
+ if (arg.type === "element_value_pair") {
4276
+ const keyNode = this.findChildByType(arg, "identifier");
4277
+ if (keyNode && keyNode.text === key) {
4278
+ const valNode = arg.namedChild(arg.namedChildCount - 1);
4279
+ return this.extractStringValue(valNode);
4280
+ }
4281
+ } else if (key === "value" && arg.type === "element_value_array_initializer") {
4282
+ const first = arg.namedChild(0);
4283
+ if (first) return this.extractStringValue(first);
4284
+ return void 0;
4285
+ } else if (key === "value" && arg.type !== "element_value_pair") {
4286
+ return this.extractStringValue(arg);
4287
+ }
4288
+ }
4289
+ }
4290
+ }
4291
+ return void 0;
4292
+ }
4293
+ getJavaGenericParameters(node) {
4294
+ const typeParamsNode = this.findChildByType(node, "type_parameters");
4295
+ if (!typeParamsNode) return void 0;
4296
+ const params = [];
4297
+ for (let i = 0; i < typeParamsNode.namedChildCount; i++) {
4298
+ const child = typeParamsNode.namedChild(i);
4299
+ if (child && child.type === "type_parameter") {
4300
+ const nameChild = child.childForFieldName("name") || this.findChildByType(child, "type_identifier");
4301
+ params.push(nameChild ? nameChild.text : child.text);
4302
+ }
4303
+ }
4304
+ return params.length > 0 ? params : void 0;
4305
+ }
4306
+ /**
4307
+ * JLS 7.7 — a `module-info.java` compilation unit. The ONE implementation.
4308
+ *
4309
+ * WHAT IS EMITTED, PER DIRECTIVE SHAPE, and every row below is pinned by a
4310
+ * fixture (three of them are INCOMING_JAVA fixtures written for this file):
4311
+ *
4312
+ * `module M { … }` a `Module` symbol. It carries no `ownerName`, so
4313
+ * the existing post-pass also lists it in `exports[]`
4314
+ * — correct, because `requires M` is how another
4315
+ * module names it (java/module-exports-jpms).
4316
+ * `exports P [to M…];` a `Namespace` symbol for P owned by M, an `exports`
4317
+ * edge M -> P (`module_exports`, with the `to` list in
4318
+ * `qualifiedTo`), a `references` MENTION of P
4319
+ * (`module_exports_mention`), and a `references` row
4320
+ * per `to` module (`module_exports_package_to`).
4321
+ * `requires [t|s] M2;` an `imports` edge M -> M2 (`module_requires`), a
4322
+ * `references` mention (`module_requires_mention`),
4323
+ * and an `imports[]` entry (ImportInfo) — the channel
4324
+ * `brain_import` reads.
4325
+ * `opens`/`uses`/`provides` one `references` row per named target, labelled
4326
+ * from JAVA_MODULE_DIRECTIVE_FIELDS.
4327
+ *
4328
+ * BOTH LABELS PER DIRECTIVE is the family rule this lane already states for
4329
+ * `import` declarations at the analyzer's import site ("`imports` is the
4330
+ * DEPENDENCY … and `references` is the MENTION"), and erlang's analyzer states
4331
+ * it for its own `-import`. java/jpms-module-exports pins it row by row; the
4332
+ * `mentions` are what the reference-resolution surface joins against, the
4333
+ * dependencies are what the module-dependency pass reads.
4334
+ *
4335
+ * WHY `requires` IS AN `imports` EDGE AND `exports` AN `exports` EDGE. JLS 7.7.1
4336
+ * makes `requires M` a compile-time MODULE DEPENDENCY, which is the same fact an
4337
+ * `import` states, so it takes the same kind — the two halves of JPMS must not
4338
+ * share a label or "what does this file depend on" becomes unanswerable
4339
+ * (java/jpms-module-exports forbids an `imports` row for an exported package,
4340
+ * and an `exports` row for a required module). JLS 7.7.2 makes `exports P`
4341
+ * "this unit makes P visible to importers", which is exactly the `exports`
4342
+ * kind's claim, and java/module-info + java/jpms-module-exports pin the row.
4343
+ *
4344
+ * `opens` grants deep REFLECTIVE access and is not an export in any sense the
4345
+ * `exports` kind asserts, so no `exports` row is emitted for it (pinned by
4346
+ * java/jpms-module-exports' forbid); it keeps its `references` row. The `to`
4347
+ * clause RESTRICTS who may read P, so an `exports` edge from M to a `to` target
4348
+ * would assert the opposite of what the clause says: it stays a mention plus
4349
+ * `qualifiedTo` metadata.
4350
+ *
4351
+ * `requires static` is an OPTIONAL dependency at compile time and REQUIRED at
4352
+ * run time, which is a different claim from a plain `requires`, so it is
4353
+ * published at 0.9 with `inferred: true` and
4354
+ * `reasonCode: "optional-module-dependency"` (java/module-info pins
4355
+ * confidenceMin AND confidenceMax at 0.9 for exactly that row) rather than
4356
+ * being spelled as a second label.
4357
+ *
4358
+ * `provides`/`uses` rows are the two remaining directive shapes in the grammar.
4359
+ * This corpus contains 0 of both (probe: 0 `opens_module_directive`, 0
4360
+ * `provides_module_directive`, 0 `uses_module_directive` over 3,275 files), so
4361
+ * their labels are pinned by fixtures rather than by corpus frequency and are
4362
+ * recorded as such.
4363
+ *
4364
+ * Node shapes are probe-verified against the pinned wasm, not assumed
4365
+ * (java-node-shape-probe.mts):
4366
+ * module_declaration field `name` -> scoped_identifier | identifier,
4367
+ * field `body` -> module_body
4368
+ * exports_module_directive FIRST named child is the package; any `to`
4369
+ * clause follows the anonymous `to` token
4370
+ * requires_module_directive first named child is the module name, UNLESS a
4371
+ * `requires_modifier` (`transitive`/`static`)
4372
+ * comes first — so the modifier is skipped by node
4373
+ * type, never by position
4374
+ */
4375
+ emitJavaModuleDeclaration(declNode, symbols, relationships, imports) {
4376
+ const nameNode = declNode.childForFieldName("name") || this.findChildByType(declNode, "scoped_identifier") || this.findChildByType(declNode, "identifier");
4377
+ if (!nameNode) return;
4378
+ const moduleName = nameNode.text;
4379
+ if (!moduleName) return;
4380
+ symbols.push({
4381
+ name: moduleName,
4382
+ kind: ASTEngine.SymbolKind.Module,
4383
+ range: this.nodeToRange(nameNode),
4384
+ visibility: "public",
4385
+ comments: [],
4386
+ metadata: { nodeType: "module_declaration", isJavaModule: true }
4387
+ });
4388
+ const body = declNode.childForFieldName("body") || this.findChildByType(declNode, "module_body");
4389
+ if (!body) return;
4390
+ const emittedPackages = /* @__PURE__ */ new Set();
4391
+ for (let i = 0; i < body.namedChildCount; i++) {
4392
+ const n = body.namedChild(i);
4393
+ if (!n) continue;
4394
+ if (n.type === "exports_module_directive") {
4395
+ const pkgNode = this.findChildByType(n, "scoped_identifier") || this.findChildByType(n, "identifier");
4396
+ if (pkgNode && pkgNode.text && !emittedPackages.has(pkgNode.text)) {
4397
+ const pkgName = pkgNode.text;
4398
+ emittedPackages.add(pkgName);
4399
+ const qualifiedTo = this.readJavaQualifiedExportTargets(n);
4400
+ symbols.push({
4401
+ name: pkgName,
4402
+ kind: ASTEngine.SymbolKind.Namespace,
4403
+ // The package is named on the `exports` line, not on `module`.
4404
+ range: this.nodeToRange(pkgNode),
4405
+ visibility: "public",
4406
+ ownerName: moduleName,
4407
+ ownerKind: ASTEngine.SymbolKind.Module,
4408
+ metadata: { nodeType: "exports_module_directive", isExportedPackage: true }
4409
+ });
4410
+ relationships.push({
4411
+ source: moduleName,
4412
+ target: pkgName,
4413
+ type: ASTEngine.RelationshipType.Exports,
4414
+ // JLS 7.7.2 is structural syntax: the directive is written, so this
4415
+ // is a parse fact and not a heuristic.
4416
+ confidence: 1,
4417
+ // The line the PACKAGE is named on — the directive's own name node.
4418
+ line_number: pkgNode.startPosition.row + 1,
4419
+ metadata: {
4420
+ relationshipType: "module_exports",
4421
+ isModuleDirective: true,
4422
+ isJavaModuleDirective: true,
4423
+ moduleName,
4424
+ ...qualifiedTo ? { qualifiedTo } : {}
4425
+ }
4426
+ });
4427
+ relationships.push({
4428
+ source: moduleName,
4429
+ target: pkgName,
4430
+ type: ASTEngine.RelationshipType.References,
4431
+ confidence: 1,
4432
+ line_number: pkgNode.startPosition.row + 1,
4433
+ metadata: {
4434
+ relationshipType: "module_exports_mention",
4435
+ isModuleDirective: true,
4436
+ isJavaModuleDirective: true,
4437
+ reasonCode: "module_directive",
4438
+ moduleName
4439
+ }
4440
+ });
4441
+ }
4442
+ } else if (n.type === "requires_module_directive") {
4443
+ let modNode = null;
4444
+ let transitive = false;
4445
+ let isStatic = false;
4446
+ for (let j = 0; j < n.namedChildCount; j++) {
4447
+ const c = n.namedChild(j);
4448
+ if (!c) continue;
4449
+ if (c.type === "requires_modifier") {
4450
+ if (c.text.trim() === "transitive") transitive = true;
4451
+ else if (c.text.trim() === "static") isStatic = true;
4452
+ continue;
4453
+ }
4454
+ if (isJavaNameNode(c)) {
4455
+ modNode = c;
4456
+ break;
4457
+ }
4458
+ }
4459
+ const required = modNode?.text ?? "";
4460
+ if (!required) continue;
4461
+ const line = modNode.startPosition.row + 1;
4462
+ const shared = {
4463
+ isModuleDirective: true,
4464
+ isJavaModuleDirective: true,
4465
+ moduleName,
4466
+ isModuleRequires: true
4467
+ };
4468
+ relationships.push({
4469
+ source: moduleName,
4470
+ target: required,
4471
+ type: ASTEngine.RelationshipType.Imports,
4472
+ // 0.9 for `requires static`: the module is OPTIONAL at compile time and
4473
+ // required at run time, so the dependency claim is discounted and the
4474
+ // reason is machine-readable rather than silent (honesty contract §4).
4475
+ confidence: isStatic ? 0.9 : 1,
4476
+ line_number: line,
4477
+ metadata: {
4478
+ relationshipType: "module_requires",
4479
+ ...shared,
4480
+ isStatic,
4481
+ isTransitive: transitive,
4482
+ fileLevel: true,
4483
+ ...isStatic ? { inferred: true, reasonCode: "optional-module-dependency" } : { reasonCode: "module_directive" }
4484
+ }
4485
+ });
4486
+ relationships.push({
4487
+ source: moduleName,
4488
+ target: required,
4489
+ type: ASTEngine.RelationshipType.References,
4490
+ confidence: 1,
4491
+ line_number: line,
4492
+ metadata: {
4493
+ relationshipType: "module_requires_mention",
4494
+ ...shared,
4495
+ reasonCode: "module_directive"
4496
+ }
4497
+ });
4498
+ imports.push({
4499
+ module: required,
4500
+ items: [],
4501
+ isDefault: false,
4502
+ range: this.nodeToRange(modNode),
4503
+ metadata: {
4504
+ isModuleRequires: true,
4505
+ ...transitive ? { transitive: true } : {},
4506
+ ...isStatic ? { isStatic: true } : {}
4507
+ },
4508
+ importType: transitive ? "java-module-requires-transitive" : "java-module-requires"
4509
+ });
4510
+ continue;
4511
+ }
4512
+ const shape = JAVA_MODULE_DIRECTIVE_FIELDS[n.type];
4513
+ if (!shape) continue;
4514
+ for (let k = 0; k < shape.length; k++) {
4515
+ const { field, role, unfieldedAfter } = shape[k];
4516
+ if (role === "module_exports_package") continue;
4517
+ const targets = n.childrenForFieldName?.(field) ?? [];
4518
+ if (unfieldedAfter) {
4519
+ const anchor = n.childForFieldName(unfieldedAfter);
4520
+ for (let j = 0; j < n.namedChildCount; j++) {
4521
+ const c = n.namedChild(j);
4522
+ if (isJavaNameNode(c) && c.startIndex !== anchor?.startIndex && c.endIndex !== anchor?.endIndex) {
4523
+ targets.push(c);
4524
+ }
4525
+ }
4526
+ }
4527
+ for (let j = 0; j < targets.length; j++) {
4528
+ const t = targets[j];
4529
+ if (t) this.pushModuleDirective(relationships, moduleName, t, role);
4530
+ }
4531
+ }
4532
+ }
4533
+ }
4534
+ /** The `to M1, M2` target list of a qualified export, or null when unqualified. */
4535
+ readJavaQualifiedExportTargets(directive) {
4536
+ const out = [];
4537
+ let seenPackage = false;
4538
+ for (let i = 0; i < directive.namedChildCount; i++) {
4539
+ const c = directive.namedChild(i);
4540
+ if (!c) continue;
4541
+ const isName = c.type === "scoped_identifier" || c.type === "identifier";
4542
+ if (!isName) continue;
4543
+ if (!seenPackage) {
4544
+ seenPackage = true;
4545
+ continue;
4546
+ }
4547
+ out.push(c.text);
4548
+ }
4549
+ return out.length > 0 ? out : null;
4550
+ }
4551
+ /**
4552
+ * JLS 4.4 — publish each DECLARED type variable as a `SymbolKind.Type` symbol.
4553
+ *
4554
+ * WHY. The analyzer already read every one of them: `getJavaGenericParameters`
4555
+ * above put `["K","V"]` on the class symbol's `genericParameters`, and the
4556
+ * declared-type walk emitted a `references` edge to each string it met in a
4557
+ * field type, parameter type, return type, cast or `throws` clause. So the
4558
+ * GRAPH already asserted that `Box` references `K` — and no symbol named `K`
4559
+ * existed anywhere, so the assertion had nothing to bind to. The rule-R7 /
4560
+ * endpoint question ("do both ends resolve to a real symbol?") could not be
4561
+ * answered for those edges at any rate above zero, and the honest cause was
4562
+ * not cross-file reach: the declaration was in the SAME FILE and was simply
4563
+ * never published.
4564
+ *
4565
+ * MEASURED, not assumed (instrument
4566
+ * `validation/m14/measurements/java/java-construct-probe.mts`, whole corpus,
4567
+ * 3,275 `.java` files, all parsed, 0 failed): 9,780 `type_parameter` nodes
4568
+ * over 1,543 files (47.1% of the corpus), 55 distinct declared variable
4569
+ * names. On the 250-file store slice this produced 1,588 `references` rows
4570
+ * naming a declaration that existed nowhere in the index (`V` 815, `K` 773).
4571
+ *
4572
+ * WHY `Type` AND NOT SOMETHING ELSE. `SymbolKind.Type = "type"` is declared
4573
+ * at `src/ast/types.ts:18`, `CONTAINER_TYPES_LOOKUP` in parallel-indexer
4574
+ * already treats `type` as a container kind, and this analyzer's own
4575
+ * evidence classifier at `resolveEnclosingTypeScope` already tests for it.
4576
+ *
4577
+ * THE CONTAINMENT EDGE IS NOT INVENTED HERE. The symbol carries `ownerName`,
4578
+ * so the existing post-pass (`for (const s of symbols) { const _owner =
4579
+ * s.ownerName || s.scope; … }`) emits `owner -> variable` at confidence 1.0
4580
+ * with the variable's own declaration line — one code path for every member,
4581
+ * not a second spelling of containment.
4582
+ *
4583
+ * WHY THE NAME STAYS BARE. A `references -> V` edge names `V`. Publishing
4584
+ * the symbol as `Box.V` would leave every one of those edges unresolved
4585
+ * again, which is the defect being fixed. The cost is honest and recorded:
4586
+ * a class type variable and a method type variable that share a spelling in
4587
+ * one file (`class A<T> { <T> T m(T t) {} }`) are two symbols with one name,
4588
+ * and the store's name resolution picks one. That is a genuine ambiguity, not
4589
+ * a fabrication — both declarations are real, and JLS 6.3 scopes both to the
4590
+ * body that names them.
4591
+ *
4592
+ * The `contains` post-pass skips nothing here: `ownerName` is set, so these
4593
+ * do NOT reach the `exports[]` channel (a type variable is not an importable
4594
+ * top-level unit — `requires Box.V` is not a thing any module can write).
4595
+ */
4596
+ emitJavaTypeParameterSymbols(declNode, ownerName, ownerKind, declaredOn, symbols) {
4597
+ const typeParamsNode = this.findChildByType(declNode, "type_parameters");
4598
+ if (!typeParamsNode) return;
4599
+ for (let i = 0; i < typeParamsNode.namedChildCount; i++) {
4600
+ const tp = typeParamsNode.namedChild(i);
4601
+ if (!tp || tp.type !== "type_parameter") continue;
4602
+ const nameNode = tp.childForFieldName("name") || this.findChildByType(tp, "type_identifier");
4603
+ if (!nameNode) continue;
4604
+ const tpName = nameNode.text;
4605
+ if (!tpName) continue;
4606
+ const bound = this.findChildByType(tp, "type_bound");
4607
+ symbols.push({
4608
+ name: tpName,
4609
+ kind: ASTEngine.SymbolKind.Type,
4610
+ // The VARIABLE's own line, so the symbol and the `contains` edge that
4611
+ // names it cannot disagree (M59 #825's rule, applied to the position).
4612
+ range: this.nodeToRange(nameNode),
4613
+ ownerName,
4614
+ ownerKind,
4615
+ metadata: {
4616
+ nodeType: "type_parameter",
4617
+ isTypeParameter: true,
4618
+ declaredOn,
4619
+ declarationOrder: i,
4620
+ ...bound ? { bound: bound.text } : {}
4621
+ }
4622
+ });
4623
+ }
4624
+ }
4625
+ /**
4626
+ * Exact short-name annotation test on a symbol's decorators. Decorator text
4627
+ * is raw source ("@Service", "@org.x.Service", "@Service(...)") — normalize
4628
+ * to the short name before comparing so fully-qualified and parameterized
4629
+ * annotations match exactly like bare ones.
4630
+ */
4631
+ hasJavaAnnotation(symbol, annotationName) {
4632
+ if (!symbol.decorators) return false;
4633
+ for (let i = 0; i < symbol.decorators.length; i++) {
4634
+ let t = symbol.decorators[i];
4635
+ if (t.startsWith("@")) t = t.slice(1);
4636
+ const paren = t.indexOf("(");
4637
+ if (paren !== -1) t = t.slice(0, paren);
4638
+ const dot = t.lastIndexOf(".");
4639
+ if (dot !== -1) t = t.slice(dot + 1);
4640
+ if (t === annotationName) return true;
4641
+ }
4642
+ return false;
4643
+ }
4644
+ getJavaFrameworkPatterns(frameworks, symbols) {
4645
+ const patterns = [];
4646
+ if (inArray(frameworks, "spring")) {
4647
+ const springPatterns = [];
4648
+ let hasService = false, hasRepo = false, hasController = false;
4649
+ for (let i = 0; i < symbols.length; i++) {
4650
+ if (!hasService && this.hasJavaAnnotation(symbols[i], "Service")) hasService = true;
4651
+ if (!hasRepo && this.hasJavaAnnotation(symbols[i], "Repository")) hasRepo = true;
4652
+ if (!hasController && (this.hasJavaAnnotation(symbols[i], "Controller") || this.hasJavaAnnotation(symbols[i], "RestController"))) hasController = true;
4653
+ }
4654
+ if (hasService) springPatterns.push("service_layer");
4655
+ if (hasRepo) springPatterns.push("repository_pattern");
4656
+ if (hasController) {
4657
+ springPatterns.push("mvc_pattern");
4658
+ springPatterns.push("api_endpoints");
4659
+ }
4660
+ if (springPatterns.length > 0) patterns.push({ framework: "Spring Framework", patterns: springPatterns });
4661
+ }
4662
+ if (inArray(frameworks, "spring_mvc")) {
4663
+ patterns.push({ framework: "Spring MVC", patterns: ["mvc_pattern", "api_endpoints"] });
4664
+ }
4665
+ if (inArray(frameworks, "spring_data")) {
4666
+ patterns.push({ framework: "Spring Data", patterns: ["repository_pattern", "orm_mapping"] });
4667
+ } else if (inArray(frameworks, "jpa")) {
4668
+ patterns.push({ framework: "JPA", patterns: ["orm_mapping"] });
4669
+ } else if (inArray(frameworks, "hibernate")) {
4670
+ patterns.push({ framework: "Hibernate", patterns: ["orm_mapping"] });
4671
+ }
4672
+ return patterns;
4673
+ }
4674
+ };
4675
+ export {
4676
+ RealJavaAnalyzer
4677
+ };