@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,3145 @@
1
+ import {
2
+ ASTEngine
3
+ } from "./chunk-3ZTOHHSW.js";
4
+ import "./chunk-BQ3OVQ3P.js";
5
+ import "./chunk-SK6YVHFZ.js";
6
+ import "./chunk-2BSKD7O3.js";
7
+ import "./chunk-KAET47ZL.js";
8
+ import "./chunk-KCDECRJY.js";
9
+ import "./chunk-MLKGABMK.js";
10
+
11
+ // src/ast/real-vb-analyzer.ts
12
+ var { SymbolKind, RelationshipType } = ASTEngine;
13
+ var IS_IMPORT = "isImport";
14
+ var INCLUDE_DEPENDENCY = "include_dependency";
15
+ var VB_BUILTIN_TYPE_NAMES = [
16
+ "Boolean",
17
+ "Byte",
18
+ "SByte",
19
+ "Char",
20
+ "Date",
21
+ "Decimal",
22
+ "Double",
23
+ "Integer",
24
+ "Long",
25
+ "Object",
26
+ "Short",
27
+ "Single",
28
+ "String",
29
+ "UInteger",
30
+ "ULong",
31
+ "UShort"
32
+ ];
33
+ var VB_BUILTIN_TYPE_CANONICAL = new Map(
34
+ VB_BUILTIN_TYPE_NAMES.map((n) => [n.toLowerCase(), n])
35
+ );
36
+ var WHITESPACE_CHARS = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\f", "\v", "\xA0", "\u2028", "\u2029", "\uFEFF"]);
37
+ function stripWhitespace(text) {
38
+ let out = "";
39
+ for (const ch of text) if (!WHITESPACE_CHARS.has(ch)) out += ch;
40
+ return out;
41
+ }
42
+ var RealVbAnalyzer = class _RealVbAnalyzer {
43
+ // Populated per analyze() call. The engine constructs one analyzer and reuses
44
+ // it across files, so nothing here may survive a call — see reset().
45
+ symbols = [];
46
+ relationships = [];
47
+ imports = [];
48
+ /** name -> the FIRST symbol declared under that name. */
49
+ index = /* @__PURE__ */ new Map();
50
+ /** Every name declared in THIS file, for same-file edge confidence. */
51
+ declaredNames = /* @__PURE__ */ new Set();
52
+ /** `<declaration>\u0000<attributeLeaf>\u0000<line>` — one `decorates` edge per attribute
53
+ * OCCURRENCE, so a node visited twice (a declared symbol and its declarator) cannot
54
+ * publish the same decoration twice. */
55
+ attributeEdgeKeys = /* @__PURE__ */ new Set();
56
+ /** The declaration chain we are inside, innermost last. */
57
+ ownerStack = [];
58
+ /** Scopes that take part in name resolution: namespaces and types. */
59
+ typeStack = [];
60
+ /** Member names of each type in this file, for unqualified call targets. */
61
+ memberNames = /* @__PURE__ */ new Map();
62
+ /**
63
+ * Names this file declares CALLABLE — a method, a property, a constructor, an
64
+ * operator or a delegate — and therefore names that cannot be an array index.
65
+ *
66
+ * This set exists because a VB call WITH ARGUMENTS is an `array_access_expression`
67
+ * in this grammar, so "is `Foo(1)` a call or an index?" has no syntactic answer
68
+ * and the only non-fabricating answer is: it is a call if `Foo` is something
69
+ * this file declares it could call. See `collectBodyEdges`.
70
+ */
71
+ callablesInFile = /* @__PURE__ */ new Set();
72
+ /**
73
+ * Names this file declares as a VARIABLE or FIELD, used to keep a statement-level
74
+ * `arr(0)` from being published as a call. A local array is the one index shape
75
+ * that also looks like a call statement, and this is what excludes it.
76
+ */
77
+ variablesInFile = /* @__PURE__ */ new Set();
78
+ /**
79
+ * Callables whose DECLARED TYPE is an array (`As T()`), because VB lets you index
80
+ * the result of a call: `SomePropertyReturningAnArray(0)` is legal and is an
81
+ * index, not a call. Read off the declaration's own `as_clause` -> `array_type`.
82
+ */
83
+ arrayValuedInFile = /* @__PURE__ */ new Set();
84
+ /**
85
+ * Names bound by a LOCAL declaration. See `precollectDeclarations` for why a local
86
+ * has to be excluded from the statement-level call signal: `Dim s3() As Decimal`
87
+ * then a bare `s3(4)` is an index, and publishing it as a call is fabrication.
88
+ */
89
+ localsInFile = /* @__PURE__ */ new Set();
90
+ /**
91
+ * `(source -> target)` pairs already published as `uses`, holding the EARLIEST
92
+ * site line seen for each — for the per-file dedup. A Map rather than a Set
93
+ * because "earliest" has to be compared, not just recorded.
94
+ */
95
+ usesEmitted = /* @__PURE__ */ new Map();
96
+ /**
97
+ * Node ids of the CALLEE EXPRESSIONS this walk published a `calls` edge for.
98
+ *
99
+ * `uses` is defined as a value read that is NEITHER A CALL NOR A WRITE, so a
100
+ * member access this walk already called must not also be published as a read —
101
+ * otherwise one site carries two labels and the `calls` tally and the `uses`
102
+ * tally each count rows the other one already has.
103
+ *
104
+ * Why the ids and not a re-derived climb: the exclusion used to re-derive
105
+ * callee position by walking UP the tree, and the climb cannot see this grammar's
106
+ * argument-bearing call, because there is no invocation node to climb to.
107
+ *
108
+ * obj.M() -> invocation_expression(function: member_access) climb works
109
+ * obj.M(3) -> array_access_expression climb stops here
110
+ * member_access_expression
111
+ * return_statement
112
+ *
113
+ * So `obj.M(3)` was published as BOTH `calls -> M` and `uses -> M`. Measured over
114
+ * 400 files of roslyn-vb: 1,816 `uses` edges across 180 files carried both labels,
115
+ * 11.16% of the 16,276 `uses` edges in that sample (`script/m14/vb-uses-call-overlap.ts`).
116
+ *
117
+ * Recording the id where the decision is MADE is also the direction that cannot
118
+ * recur: a second copy of the callee test is a second thing to get wrong, and it
119
+ * had already been wrong once. Ordering is not a hazard — `collectBodyEdges`
120
+ * descends top-down, so a member access is always reached after its own enclosing
121
+ * call node.
122
+ */
123
+ callCalleeNodes = /* @__PURE__ */ new Set();
124
+ /** Counters the lane discloses rather than hides. */
125
+ gaps = {};
126
+ /** `(source, target)` of every published `uses` edge, with its site count. */
127
+ useIndex = /* @__PURE__ */ new Map();
128
+ // -------------------------------------------------------------------------
129
+ // Node-type and field vocabulary
130
+ //
131
+ // Read off the pinned wasm, not off a grammar tutorial. Kept as Sets so a test
132
+ // is membership, not a pattern match, and so an unclassified type is a MISS
133
+ // from the set rather than a silent fallthrough.
134
+ // -------------------------------------------------------------------------
135
+ /** Grammar rules that declare a named type. Anything in the corpus that is not
136
+ * in here and not in SILENT_INTERNAL is a gap, and the census counts it. */
137
+ static DECLARATION_NODES = /* @__PURE__ */ new Set([
138
+ "namespace_declaration",
139
+ "class_declaration",
140
+ "structure_declaration",
141
+ "interface_declaration",
142
+ "module_declaration",
143
+ "enum_declaration",
144
+ // An enum member is a declaration, and `visitEnum` reads it directly rather
145
+ // than dispatching it, so it was never in this set. The behaviour was right
146
+ // and the classification was absent, which is precisely the state a census
147
+ // exists to make visible rather than to fix.
148
+ "enum_member_declaration",
149
+ "delegate_declaration",
150
+ "method_declaration",
151
+ "constructor_declaration",
152
+ "property_declaration",
153
+ "event_declaration",
154
+ "operator_declaration",
155
+ "declare_statement",
156
+ "field_declaration",
157
+ "imports_statement",
158
+ // An INTERFACE's members are declarations, and for a long time they were in
159
+ // neither this set nor SILENT_INTERNAL, which is the whole defect: nothing
160
+ // visited them, so an interface was published as an EMPTY SHELL. `IShape`
161
+ // appeared with a name and no members, while the class implementing it had
162
+ // all three. Found by the F8 node-type census, which found 104 unclassified
163
+ // node types against 42 classified -- the "every construct is accounted for"
164
+ // claim was 29% true and nothing had ever measured it.
165
+ //
166
+ // In an interface-driven codebase this is not a rare shape. DISMTools and
167
+ // P3D are both Windows Forms applications where the designer emits
168
+ // interfaces for every dialog, so a large fraction of the corpus's types had
169
+ // invisible members.
170
+ "interface_method_declaration",
171
+ "interface_property_declaration",
172
+ "interface_event_declaration"
173
+ ]);
174
+ /**
175
+ * The node types that are STRUCTURE inside a type body rather than code in it:
176
+ * the declaration's own attributes and modifiers, its heritage clauses, its type
177
+ * parameter list and its name. Everything else that is not a declaration and
178
+ * appears directly in a type body is code that type executes, and is where a body
179
+ * edge comes from. See `dispatchBody`.
180
+ */
181
+ static TYPE_BODY_STRUCTURE = /* @__PURE__ */ new Set([
182
+ "attribute_list",
183
+ "attribute",
184
+ "attribute_target",
185
+ "attribute_argument_list",
186
+ "named_argument",
187
+ "member_modifier",
188
+ "modifiers",
189
+ "local_declaration_modifier",
190
+ "inherits_statement",
191
+ "implements_statement",
192
+ "inherits_member_clause",
193
+ "implements_member_clause",
194
+ "type_parameter_list",
195
+ "type_parameter",
196
+ "type_parameter_constraints_clause",
197
+ "parameter_list",
198
+ "parameter",
199
+ "as_clause",
200
+ "identifier",
201
+ "simple_name",
202
+ "comment",
203
+ "preprocessor_directive",
204
+ "blank_line",
205
+ "option_statement",
206
+ "option_statements",
207
+ "global_attribute_section",
208
+ "end_statement",
209
+ "implements_clause",
210
+ "inherits_clause"
211
+ ]);
212
+ /** Nodes the grammar produces that carry no symbol of their own: they are
213
+ * containers, modifiers, or the parts of a declaration already read. They are
214
+ * classified here so the spec's "every node type is classified" rule is
215
+ * satisfied by this list rather than by an omission. */
216
+ // A node type that publishes NO SYMBOL but DOES derive a relationship. This set
217
+ // exists because the census it feeds found two whole kinds of defect that a
218
+ // two-way classification could not express, and both were invisible:
219
+ //
220
+ // - `array_creation_expression` sat in NEITHER set, which is what let the
221
+ // analyzer read a construction's callee from a `function` field the node
222
+ // does not have. 15,577 `New Foo(a, b)` calls went unpublished.
223
+ // - `implements_member_clause` sat in SILENT_INTERNAL, which is a claim
224
+ // that the node carries nothing. It carries a relationship, and the
225
+ // member-level heritage form was therefore uncollected on every member in
226
+ // the language.
227
+ //
228
+ // "Classified" is not the same as "handled", and the difference between those
229
+ // two words is what a two-way split threw away.
230
+ static RELATIONSHIP_SOURCES = /* @__PURE__ */ new Set([
231
+ "invocation_expression",
232
+ "object_creation_expression",
233
+ "array_creation_expression",
234
+ "generic_invocation_expression",
235
+ "implements_member_clause",
236
+ "inherits_member_clause",
237
+ "inherits_clause",
238
+ "implements_clause",
239
+ "inherits_statement",
240
+ "implements_statement",
241
+ "operator_declaration",
242
+ "constructor_declaration",
243
+ // M60/VB2. These three publish NO SYMBOL but DO derive a relationship, and
244
+ // leaving them in SILENT_INTERNAL is a claim that they carry nothing --
245
+ // which is the claim that hid the `uses` class for a whole wave.
246
+ // `member_access_expression` / `with_member_access_expression`: a name
247
+ // READ that is neither the callee nor the index is a `uses` edge.
248
+ // `as_clause`: an `as_clause` whose span begins `As New` is a
249
+ // CONSTRUCTION, and the fourth of VB's four construction surfaces.
250
+ // The two-way split is what the four-way classification exists to avoid, and
251
+ // `array_creation_expression` was already moved here for the same reason.
252
+ "member_access_expression",
253
+ "with_member_access_expression",
254
+ "as_clause"
255
+ ]);
256
+ // Classified as carrying nothing today, but a KNOWN candidate: the construct
257
+ // does name or bind something, and saying so here is what keeps "silent" from
258
+ // quietly meaning "nobody has looked". Each is a real relationship or symbol a
259
+ // future wave could derive; none is claimed to be derived now.
260
+ static RELATIONSHIP_CANDIDATES = /* @__PURE__ */ new Set([
261
+ // `: base(...)` / `: Me(...)` -- names the constructor being chained to. A
262
+ // `calls` edge, and the one call form this analyzer does not yet read.
263
+ "constructor_initializer",
264
+ // `AddHandler o.E, AddressOf h` -- an event binding between two names.
265
+ "add_handler_statement",
266
+ "remove_handler_statement",
267
+ // A lambda is a callable value: `AddressOf`, or an argument to `For Each`.
268
+ "lambda_expression",
269
+ // A property's accessors are members of the property.
270
+ "property_getter",
271
+ "property_setter",
272
+ // `RaiseEvent E` names the event being raised.
273
+ "raiseevent_statement",
274
+ // `Handles Me.Startup` -- the `Object.Event` half of a Handles clause, which
275
+ // names the event a handler is wired to. 2,602 in the corpus and a real
276
+ // `references` edge to each, so it is a candidate and not silence.
277
+ "event_member"
278
+ ]);
279
+ static SILENT_INTERNAL = /* @__PURE__ */ new Set([
280
+ "source_file",
281
+ "identifier",
282
+ "comment",
283
+ "preprocessor_directive",
284
+ "blank_line",
285
+ "type_parameter_list",
286
+ "type_parameter",
287
+ "parameter_list",
288
+ "parameter",
289
+ "modifiers",
290
+ "member_modifier",
291
+ "attribute_list",
292
+ "attribute",
293
+ "attribute_target",
294
+ "attribute_argument_list",
295
+ "named_argument",
296
+ "handles_clause",
297
+ "variable_declarator",
298
+ "array_rank_specifier",
299
+ "array_initializer",
300
+ "collection_initializer",
301
+ "dictionary_initializer",
302
+ "dictionary_element",
303
+ "object_initializer",
304
+ "member_initializer",
305
+ // The types below were UNCLASSIFIED when the F8 census first ran -- 101 of
306
+ // them, against 45 classified. None of them declares a symbol and none of
307
+ // them names a relationship this analyzer derives; the list is here so that
308
+ // "every construct in the corpus is accounted for" is a checked statement
309
+ // rather than an assumption, and so a new grammar node shows up as a FAILING
310
+ // census instead of as silence.
311
+ //
312
+ // Literals: a literal names nothing, so there is no symbol and no target.
313
+ "boolean_literal",
314
+ "character_literal",
315
+ "floating_point_literal",
316
+ "integer_literal",
317
+ "string_literal",
318
+ "nothing_literal",
319
+ // Name and type syntax: read by whichever construct owns it; declares nothing.
320
+ "predefined_type",
321
+ "simple_name",
322
+ "qualified_name",
323
+ "global_qualified_name",
324
+ "generic_name",
325
+ "type_argument_list",
326
+ "array_type",
327
+ "nullable_type",
328
+ "tuple_type",
329
+ "tuple_element",
330
+ "type_declaration_in_type",
331
+ "type_parameter_constraint",
332
+ "type_parameter_constraints_clause",
333
+ // Expressions: their names are read by the constructs above; they are not
334
+ // structure. (The construction and invocation EXPRESSIONS are relationship
335
+ // SOURCES and are classified as such, which is the distinction that matters.)
336
+ "argument_list",
337
+ "array_access_expression",
338
+ "assignment_expression",
339
+ "binary_expression",
340
+ "cast_expression",
341
+ "conditional_expression",
342
+ "if_expression",
343
+ "me_expression",
344
+ "mybase_expression",
345
+ "parenthesized_expression",
346
+ "unary_expression",
347
+ "typeof_expression",
348
+ "typeof_is_expression",
349
+ "with_expression",
350
+ "lambda_parameter",
351
+ "lambda_parameter_list",
352
+ "omitted_argument",
353
+ "collection_range_variable",
354
+ // Control flow. The contract is a project SYMBOL TABLE: a `While` is not a
355
+ // symbol, and the family does not publish locals or control-flow statements.
356
+ "assignment_statement",
357
+ "declaration_statement",
358
+ "expression_statement",
359
+ "if_statement",
360
+ "elseif_clause",
361
+ "else_clause",
362
+ "case_clause",
363
+ "case_else_clause",
364
+ "case_value",
365
+ "select_statement",
366
+ "select_clause",
367
+ "select_element",
368
+ "select_or_group_clause",
369
+ "for_statement",
370
+ "for_each_statement",
371
+ "while_statement",
372
+ "do_statement",
373
+ "exit_statement",
374
+ "continue_statement",
375
+ "goto_statement",
376
+ "labeled_statement",
377
+ "return_statement",
378
+ "throw_statement",
379
+ "try_statement",
380
+ "catch_block",
381
+ "finally_block",
382
+ "with_statement",
383
+ "using_statement",
384
+ "synclock_statement",
385
+ "on_error_statement",
386
+ "call_statement",
387
+ "stop_statement",
388
+ "redim_statement",
389
+ "redim_clause",
390
+ "yield_statement",
391
+ "await_expression",
392
+ "empty_statement",
393
+ "end_statement",
394
+ // Modifiers, attributes, options.
395
+ "local_declaration_modifier",
396
+ "global_attribute_section",
397
+ "option_statement",
398
+ "option_statements",
399
+ // LINQ query syntax, which this grammar surfaces as clauses rather than
400
+ // structure.
401
+ "query_expression",
402
+ "query_body_clause",
403
+ "from_clause",
404
+ "order_by_clause",
405
+ "ordering",
406
+ "where_clause"
407
+ ]);
408
+ // -------------------------------------------------------------------------
409
+ // Small AST helpers. Every one of these is a node-type or field test; there is
410
+ // no text matching in this file at all.
411
+ // -------------------------------------------------------------------------
412
+ /**
413
+ * The `index`-th NAMED child, or null when there is none.
414
+ *
415
+ * `web-tree-sitter` 0.25 types this `Node | null`, and it really can be null at
416
+ * the boundary, so every read goes through here rather than through a `!`. The
417
+ * alternative — asserting non-null at each of the ~40 call sites — would make
418
+ * "this index is in range" an unstated assumption repeated 40 times, which is
419
+ * the shape a crash takes when a grammar gains a child.
420
+ */
421
+ nc(node, index) {
422
+ if (index < 0 || index >= node.namedChildCount) return null;
423
+ return node.namedChild(index);
424
+ }
425
+ /** The `index`-th child of ANY kind (named or anonymous token). */
426
+ ac(node, index) {
427
+ if (index < 0 || index >= node.childCount) return null;
428
+ return node.child(index);
429
+ }
430
+ /**
431
+ * The node's NAMED children, with the nulls dropped.
432
+ *
433
+ * `web-tree-sitter` 0.25 types `namedChild(i)` as `Node | null` and
434
+ * `namedChildren` as `(Node | null)[]`. A `!` at each of the ~20 read sites
435
+ * would turn "this index is in range" into an unstated assumption repeated 20
436
+ * times, and a throw would trade a type error for a crash -- the lane's
437
+ * measured state is zero crash files and that is worth keeping. So the null is
438
+ * handled once, here, and every loop downstream iterates a real `Node[]`.
439
+ */
440
+ kids(node) {
441
+ const out = [];
442
+ for (let i = 0; i < node.namedChildCount; i++) {
443
+ const c = node.namedChild(i);
444
+ if (c) out.push(c);
445
+ }
446
+ return out;
447
+ }
448
+ /** The node's children of ANY kind, with the nulls dropped. */
449
+ allKids(node) {
450
+ const out = [];
451
+ for (let i = 0; i < node.childCount; i++) {
452
+ const c = node.child(i);
453
+ if (c) out.push(c);
454
+ }
455
+ return out;
456
+ }
457
+ /**
458
+ * The number of lines in `content`, counted with a character scan.
459
+ *
460
+ * This was `content.split(/\r?\n/).length`, and it was the ONE regex literal
461
+ * in this file — which made the header's "zero regex" claim false as written.
462
+ * Rather than soften the claim, the regex went.
463
+ *
464
+ * The scan counts a `\n` as a line break and treats a preceding `\r` as part of
465
+ * that break rather than as a character of its own, which is exactly what
466
+ * `\r?\n` meant. Equivalence was MEASURED, not argued: over 598 real `.vb`
467
+ * files from two corpora plus five edge cases, the two agree everywhere except
468
+ * the empty string, where the split form returns 1 and this returns 0 — and the
469
+ * old code's own `content.length === 0 ? 0 : …` guard already forced 0 there.
470
+ * (`script/m14/tmp/`-style probe: the fixture corpus (`probes/linecount-equiv.ts`).)
471
+ */
472
+ lineCount(content) {
473
+ if (content.length === 0) return 0;
474
+ let lines = 1;
475
+ for (let i = 0; i < content.length; i++) {
476
+ if (content.charCodeAt(i) === 10) lines++;
477
+ }
478
+ return lines;
479
+ }
480
+ /** 1-based line of a node, or null when there is no node. M59.A7. */
481
+ nodeLine(node) {
482
+ return node ? node.startPosition.row + 1 : null;
483
+ }
484
+ range(node) {
485
+ return {
486
+ startLine: node.startPosition.row + 1,
487
+ startColumn: node.startPosition.column + 1,
488
+ endLine: node.endPosition.row + 1,
489
+ endColumn: node.endPosition.column + 1,
490
+ startIndex: node.startIndex,
491
+ endIndex: node.endIndex
492
+ };
493
+ }
494
+ /**
495
+ * The node that NAMES a thing, descended to the last segment's own leaf.
496
+ *
497
+ * `qualified_name` spans `A.B.C` but the name a reader navigates to is written
498
+ * on the `C` leaf. A wrapped name — an alias, a qualified name inside a
499
+ * heritage clause — must therefore be stamped with ITS leaf's line, not the
500
+ * line the name starts on. This is the C# lane's `nodeNamingToken`, and it
501
+ * exists because a relationship stamped with the wrapper's line navigates to
502
+ * the wrong place in a file that mentions the same type on three lines.
503
+ */
504
+ namingToken(node, text) {
505
+ if (!node) return null;
506
+ let cur = node;
507
+ for (let guard = 0; guard < 64; guard++) {
508
+ const named = cur.childForFieldName("name");
509
+ if (named && named.id !== cur.id && named.type !== "identifier") {
510
+ cur = named;
511
+ continue;
512
+ }
513
+ if (cur.type === "identifier") return cur;
514
+ if (cur.type === "simple_name") {
515
+ const inner = this.nc(cur, 0);
516
+ if (inner && inner.type === "identifier") return inner;
517
+ return cur;
518
+ }
519
+ if (cur.type === "nullable_type") {
520
+ const inner = this.nc(cur, 0);
521
+ if (inner && inner.id !== cur.id) {
522
+ cur = inner;
523
+ continue;
524
+ }
525
+ }
526
+ if (cur.type === "array_type") {
527
+ const el = cur.childForFieldName("element_type");
528
+ if (el && el.id !== cur.id) {
529
+ cur = el;
530
+ continue;
531
+ }
532
+ }
533
+ if (cur.type === "generic_name") {
534
+ const inner = this.nc(cur, 0);
535
+ if (inner) {
536
+ cur = inner;
537
+ continue;
538
+ }
539
+ return cur;
540
+ }
541
+ if (cur.type === "qualified_name" || cur.type === "global_qualified_name") {
542
+ const inner = cur.childForFieldName("name");
543
+ if (inner && inner.id !== cur.id) {
544
+ cur = inner;
545
+ continue;
546
+ }
547
+ return cur;
548
+ }
549
+ return cur;
550
+ }
551
+ return cur;
552
+ }
553
+ /**
554
+ * The leaf that NAMES a callee — the target a reader navigates to.
555
+ *
556
+ * The rule is the LAST `identifier` leaf at or under the callee node, and it
557
+ * has to be that rather than the node's own text, because VB spells a callee
558
+ * in three shapes that all reduce to one name:
559
+ *
560
+ * `EntityID.ToLower()` -> `ToLower` (a `member_access_expression`)
561
+ * `New AnimatedBlock()` -> `AnimatedBlock`
562
+ * `CType(x, W).Init()` -> `Init`
563
+ *
564
+ * The construction case was the one that was broken, and it was broken for a
565
+ * specific, findable reason: `New X()` parses as
566
+ * `(object_creation_expression type: (simple_name (identifier)))` — the type is
567
+ * in a `type` FIELD, and `namingToken` only ever followed a `name` field, so it
568
+ * fell through and returned the whole construction node. The published target
569
+ * was the literal string `"New AnimatedBlock"`: not a name in the file, not
570
+ * navigable, and unmatchable by any independent oracle. Measured on P3D's
571
+ * Entity.vb — 31 of them in one method.
572
+ *
573
+ * A `type_argument_list` is skipped, so `New List(Of Integer)` is `List` and not
574
+ * `Integer`: the argument is not what is being constructed. That is the same
575
+ * exclusion the heritage path and the F5 oracle both apply, so the three agree
576
+ * on one definition of "the name of this type expression".
577
+ */
578
+ calleeLeaf(fn) {
579
+ const leaves = [];
580
+ const walk = (n, depth) => {
581
+ if (depth > 64) return;
582
+ if (n.type === "type_argument_list") return;
583
+ if (n.type === "identifier") {
584
+ leaves.push(n);
585
+ return;
586
+ }
587
+ for (let i = 0; i < n.childCount; i++) {
588
+ const c = n.child(i);
589
+ if (c) walk(c, depth + 1);
590
+ }
591
+ };
592
+ walk(fn, 0);
593
+ return leaves.length > 0 ? leaves[leaves.length - 1] : null;
594
+ }
595
+ /**
596
+ * The text of a name-bearing node, rebuilt from the tree.
597
+ *
598
+ * Char-scan, not regex: walk the node's named children and join the identifier
599
+ * leaves with the separator each wrapper contributes. `qualified_name` is
600
+ * `qualifier . name` in the grammar, so the dot is a CHILD TOKEN and is
601
+ * already in the node's text — reading `node.text` for the whole name and
602
+ * trimming is both simpler and more faithful than reassembling it.
603
+ */
604
+ nameText(node) {
605
+ if (!node) return "";
606
+ return node.text.trim();
607
+ }
608
+ /** The `name` field of a declaration, or its first identifier child. */
609
+ declaredName(node) {
610
+ const field = node.childForFieldName("name");
611
+ if (field) {
612
+ const token = this.namingToken(field, field.text);
613
+ return { text: this.nameText(token ?? field), node: token ?? field };
614
+ }
615
+ for (const c of this.kids(node)) {
616
+ if (c.type === "identifier" || c.type === "simple_name") {
617
+ return { text: this.nameText(c), node: this.namingToken(c, c.text) ?? c };
618
+ }
619
+ }
620
+ return { text: "", node: null };
621
+ }
622
+ namedChildrenOfType(node, type) {
623
+ const out = [];
624
+ for (const c of this.kids(node)) {
625
+ if (c.type === type) out.push(c);
626
+ }
627
+ return out;
628
+ }
629
+ /**
630
+ * VB visibility, read off the `member_modifier` list the grammar already
631
+ * separated out. A Set membership test per modifier, in the order VB resolves
632
+ * them, and the FIRST match wins — `Protected Friend` is `Protected` in VB's
633
+ * own spec ordering and getting that wrong changes access queries.
634
+ */
635
+ static VISIBILITY_ORDER = [
636
+ ["private", "private"],
637
+ ["protected", "protected"],
638
+ ["friend", "internal"],
639
+ ["public", "public"]
640
+ ];
641
+ /**
642
+ * The access levels ONE declaration actually grants, as a SET of level words.
643
+ *
644
+ * A SET and not one string, because VB has two COMBINED access modifiers and
645
+ * the combination is the meaning. `Protected Friend` is "protected OR friend",
646
+ * so it is reachable from another assembly precisely because of its friend
647
+ * half, while `Protected` alone is not. Reducing the pair to the single
648
+ * string `protected` before asking whether a declaration is exported answers
649
+ * `no` about a member the language says is assembly-visible, which is what an
650
+ * `exports` edge is for.
651
+ */
652
+ accessLevelsOf(node) {
653
+ const levels = /* @__PURE__ */ new Set();
654
+ for (const m of this.modifierWordsOf(node)) {
655
+ if (_RealVbAnalyzer.ACCESS_WORDS.has(m)) levels.add(m);
656
+ }
657
+ return levels;
658
+ }
659
+ /** The four words VB spells an access level with. */
660
+ static ACCESS_WORDS = /* @__PURE__ */ new Set([
661
+ "public",
662
+ "protected",
663
+ "friend",
664
+ "private"
665
+ ]);
666
+ /**
667
+ * The DEFAULT accessibility of a declaration that writes none.
668
+ *
669
+ * Taken from Microsoft's own table rather than from recollection, because the
670
+ * corpus exercises every row: the lane census counts 136 modifier-less
671
+ * members in a class, 20 in a module, 5 in a structure and 116 modifier-less
672
+ * types at namespace level (probes `exports:implicit_default:*` and
673
+ * `exports:type_implicit_default:*`), so a wrong answer here is wrong on real
674
+ * files rather than on a synthetic one. Source: "Declaration Contexts and
675
+ * Default Access Levels",
676
+ * learn.microsoft.com/dotnet/visual-basic/language-reference/statements/
677
+ * declaration-contexts-and-default-access-levels
678
+ *
679
+ * element namespace level | module level
680
+ * Variable / Constant not allowed | Private (Public in Structure)
681
+ * Class / Structure / Module / Interface / Friend | Public
682
+ * Enum / Delegate
683
+ * Procedure / Property / Event / Operator / not allowed | Public
684
+ * Declare
685
+ *
686
+ * The row that is easy to get wrong, and the one VB is famous for disagreeing
687
+ * with C# on, is Procedure/Property/Event: they default to PUBLIC. A
688
+ * `Sub M()` written without a modifier inside a Class is callable from another
689
+ * assembly. FIELDS and CONSTANTS are the ones that default to Private.
690
+ *
691
+ * `friend` at the namespace level is what makes a modifier-less `Class C`
692
+ * NOT an assembly export, which is the whole `exports` answer for the 116
693
+ * top-level types the corpus writes without a modifier.
694
+ */
695
+ defaultAccessLevel(kind) {
696
+ if (kind === ASTEngine.SymbolKind.EnumMember) return "public";
697
+ const ownerKind = this.currentOwner()?.kind;
698
+ if (ownerKind === void 0 || ownerKind === ASTEngine.SymbolKind.Namespace) return "friend";
699
+ switch (kind) {
700
+ case ASTEngine.SymbolKind.Variable:
701
+ case ASTEngine.SymbolKind.Constant:
702
+ return ownerKind === ASTEngine.SymbolKind.Struct ? "public" : "private";
703
+ default:
704
+ return "public";
705
+ }
706
+ }
707
+ /**
708
+ * Does this set of access levels put the declaration in the assembly's public
709
+ * surface? The question `exports` exists to answer, asked on the levels
710
+ * rather than on the single `visibility` string, so `Protected Friend`
711
+ * (assembly-reachable through its friend half) is not confused with
712
+ * `Protected` (subclass-only).
713
+ */
714
+ static isAssemblyVisible(levels) {
715
+ if (levels.has("public")) return true;
716
+ return levels.has("friend") && levels.has("protected");
717
+ }
718
+ /**
719
+ * The single-string accessibility the symbol record carries.
720
+ *
721
+ * It applies the DOCUMENTED DEFAULT when the declaration writes none, rather
722
+ * than falling through to `internal`. That fall-through was a real published
723
+ * fact and it was wrong for every declaration that omits its modifier: VB's
724
+ * default for a Procedure / Property / Event is Public, not Private, so a
725
+ * modifier-less `Sub M()` was published `internal` and is in fact callable from
726
+ * another assembly. The corpus exercises this: 136 modifier-less members in a
727
+ * class and 20 in a module, of which the procedures, properties and events are
728
+ * Public by the language's own table.
729
+ *
730
+ * The set form is still available and is what `exports` asks, because the
731
+ * combined modifiers do not reduce to one word.
732
+ */
733
+ visibilityOf(node, kind) {
734
+ const levels = this.accessLevelsOf(node);
735
+ for (const [mod, vis] of _RealVbAnalyzer.VISIBILITY_ORDER) {
736
+ if (levels.has(mod)) return vis;
737
+ }
738
+ return _RealVbAnalyzer.spellAccessLevel(this.defaultAccessLevel(kind ?? _RealVbAnalyzer.kindOfNode(node)));
739
+ }
740
+ /** The symbol kind a declaration node is, for the default table. */
741
+ static kindOfNode(node) {
742
+ switch (node.type) {
743
+ case "class_declaration":
744
+ return ASTEngine.SymbolKind.Class;
745
+ case "structure_declaration":
746
+ return ASTEngine.SymbolKind.Struct;
747
+ case "interface_declaration":
748
+ return ASTEngine.SymbolKind.Interface;
749
+ case "module_declaration":
750
+ return ASTEngine.SymbolKind.Module;
751
+ case "enum_declaration":
752
+ return ASTEngine.SymbolKind.Enum;
753
+ case "enum_member_declaration":
754
+ return ASTEngine.SymbolKind.EnumMember;
755
+ case "delegate_declaration":
756
+ return ASTEngine.SymbolKind.Type;
757
+ case "constructor_declaration":
758
+ return ASTEngine.SymbolKind.Constructor;
759
+ case "property_declaration":
760
+ case "interface_property_declaration":
761
+ return ASTEngine.SymbolKind.Property;
762
+ case "event_declaration":
763
+ case "interface_event_declaration":
764
+ return ASTEngine.SymbolKind.Event;
765
+ case "field_declaration":
766
+ case "variable_declarator":
767
+ return ASTEngine.SymbolKind.Variable;
768
+ default:
769
+ return ASTEngine.SymbolKind.Method;
770
+ }
771
+ }
772
+ /** The symbol-record spelling of an access-level word. */
773
+ static spellAccessLevel(level) {
774
+ return level === "friend" ? "internal" : level;
775
+ }
776
+ /**
777
+ * Every modifier word on a declaration, ONE ENTRY PER WORD.
778
+ *
779
+ * The split is load-bearing and it was absent. This grammar's
780
+ * `member_modifier` can hold a SEQUENCE of words: `Protected Friend Sub PF()`
781
+ * parses as ONE `member_modifier` node whose text is `Protected Friend`, so
782
+ * adding that node's text verbatim added the single token `"protected friend"`,
783
+ * which matches no entry in `VISIBILITY_ORDER` and falls through to
784
+ * `internal` -- a real declaration published with the wrong accessibility.
785
+ * Measured on the lane corpus: 12 occurrences of a multi-word
786
+ * `member_modifier`, every one of them `protected friend` (census probe
787
+ * `MULTIWORD_MODIFIER`, over 2,658 files).
788
+ *
789
+ * A word that is not a modifier this grammar's `VB_KEYWORD_HEADS` already
790
+ * names is DROPPED rather than admitted, so splitting cannot invent a
791
+ * modifier the file does not write.
792
+ */
793
+ modifierWordsOf(node) {
794
+ const out = /* @__PURE__ */ new Set();
795
+ const addText = (text) => {
796
+ let start = 0;
797
+ const flush = (end) => {
798
+ if (end <= start) return;
799
+ const word = text.slice(start, end).toLowerCase();
800
+ if (_RealVbAnalyzer.VB_KEYWORD_HEADS.has(word)) out.add(word);
801
+ start = end + 1;
802
+ };
803
+ for (let i = 0; i < text.length; i++) {
804
+ const c = text.charCodeAt(i);
805
+ if (c === 32 || c === 9 || c === 13 || c === 10) flush(i);
806
+ }
807
+ flush(text.length);
808
+ };
809
+ const collect = (n, depth) => {
810
+ if (depth > 4) return;
811
+ for (const c of this.allKids(n)) {
812
+ if (c.type === "member_modifier" || c.type === "local_declaration_modifier") {
813
+ addText(c.text);
814
+ continue;
815
+ }
816
+ if (_RealVbAnalyzer.DECLARATION_NODES.has(c.type)) continue;
817
+ collect(c, depth + 1);
818
+ }
819
+ };
820
+ collect(node, 0);
821
+ return out;
822
+ }
823
+ modifierNames(node) {
824
+ return this.modifierWordsOf(node);
825
+ }
826
+ /**
827
+ * The `overrides` edge for a member declared `Overrides`.
828
+ *
829
+ * Extracted so that METHODS, PROPERTIES and EVENTS all get one. It was inline
830
+ * in the method visitor only, so `Public Overrides Property Description` —
831
+ * which is how P3D's whole `Item` hierarchy is written, 56 of the missing rows
832
+ * in one frozen draw — published nothing while the oracle, reading the modifier
833
+ * off the declaration, derived the edge and reported the analyzer as
834
+ * incomplete. VB has overridable properties and events, and they are not rare:
835
+ * they are the shape a designer emits.
836
+ *
837
+ * The edge is QUALIFIED by the base type when this file says what the base type
838
+ * is, because the unqualified form is a self-edge (`Go -> Go`) and a self-edge
839
+ * cannot answer "what does this override?". The base type is usually right here
840
+ * — the owning type's own `Inherits` was already read to publish `extends` — so
841
+ * the edge can name `Item.Description` instead of guessing. When the base type
842
+ * is not derivable, the edge keeps the same name and says `baseTypeKnown: false`
843
+ * rather than inventing a type it never read.
844
+ */
845
+ pushOverride(node, nameNode, sym, owner) {
846
+ const type = this.ownerStack.slice().reverse().find((e) => e !== sym && e.ownerKind === "class" || e !== sym && (e.kind === ASTEngine.SymbolKind.Class || e.kind === ASTEngine.SymbolKind.Struct || e.kind === ASTEngine.SymbolKind.Interface || e.kind === ASTEngine.SymbolKind.Module || e.kind === ASTEngine.SymbolKind.Enum));
847
+ const declaring = type ?? owner;
848
+ if (!declaring) return;
849
+ const baseType = this.relationships.find(
850
+ (r) => r.source === declaring.name && r.type === ASTEngine.RelationshipType.Extends
851
+ );
852
+ const baseKnown = typeof baseType?.target === "string" && baseType.target.length > 0;
853
+ this.relationships.push({
854
+ source: sym.name,
855
+ target: baseKnown ? `${baseType.target}.${sym.name}` : sym.name,
856
+ type: ASTEngine.RelationshipType.Overrides,
857
+ confidence: baseKnown ? 0.9 : 0.6,
858
+ line_number: this.nodeLine(nameNode ?? node),
859
+ metadata: {
860
+ baseMemberName: sym.name,
861
+ baseTypeName: baseKnown ? baseType.target : void 0,
862
+ baseTypeKnown: baseKnown,
863
+ fromModifier: "Overrides",
864
+ declaringType: declaring.name
865
+ }
866
+ });
867
+ }
868
+ isStatic(node) {
869
+ return this.modifierNames(node).has("shared");
870
+ }
871
+ isAbstract(node) {
872
+ const mods = this.modifierNames(node);
873
+ return mods.has("abstract") || mods.has("mustinherit");
874
+ }
875
+ // -------------------------------------------------------------------------
876
+ // Entry point
877
+ // -------------------------------------------------------------------------
878
+ analyze(rootNode, content, filePath, context) {
879
+ this.reset();
880
+ this.precollectDeclarations(rootNode);
881
+ this.walk(rootNode, content, context);
882
+ this.dedupeRelationships();
883
+ this.stampEvidenceClasses();
884
+ const lines = this.lineCount(content);
885
+ return {
886
+ symbols: this.symbols,
887
+ relationships: this.relationships,
888
+ imports: this.imports,
889
+ metadata: {
890
+ complexity: this.complexity(rootNode),
891
+ linesOfCode: lines,
892
+ cyclomaticComplexity: this.cyclomaticComplexity(rootNode),
893
+ analysisVersion: "m60-vb-1",
894
+ // The lane's honesty block. `gaps` is what the grammar could not express
895
+ // and is therefore NOT covered; it is disclosed so a consumer can see
896
+ // the boundary instead of inferring completeness from silence.
897
+ vbGaps: { ...this.gaps },
898
+ hasTypeInference: false,
899
+ hasFrameworkIntelligence: false
900
+ }
901
+ };
902
+ }
903
+ /**
904
+ * The same relationship row, emitted more than once in one file, is emitted
905
+ * ONCE.
906
+ *
907
+ * MEASURED DEFECT this closes. `script/m14/vb-edge-integrity.ts` over 2,658
908
+ * corpus files, keying on (source, target, type, line) within one file:
909
+ *
910
+ * references 3,813 rows
911
+ * instantiates 1,840 rows
912
+ * calls 935 rows
913
+ * imports 2 rows
914
+ * -------
915
+ * 6,590 rows that were the same row as a row already present
916
+ *
917
+ * Two shapes produce them, and both are real reads of the source rather than
918
+ * parser noise:
919
+ *
920
+ * - three `As String` on ONE line -- `Public Function F(a As String, b As
921
+ * String) As String` publishes `references F -> String` three times, once
922
+ * per parameter plus once for the return, and the three differ only in a
923
+ * `role` nobody queries.
924
+ * - two constructions of the same type on one line -- `New StackFrame(1)…
925
+ * & New StackFrame(2)…` publishes `instantiates LogMessage -> StackFrame`
926
+ * twice at the same line.
927
+ *
928
+ * WHY IT MATTERS DOWNSTREAM rather than aesthetically: `bb_impact` and
929
+ * `bb_graph` count DEGREES. A file with three `String` references in one
930
+ * signature reports a node three times as depended-upon, which is a number a
931
+ * consumer cannot tell from a real three-call dependency.
932
+ *
933
+ * WHAT IS KEPT. The FIRST row survives untouched apart from two added
934
+ * metadata keys, so the line and the confidence stay exactly as the emitting
935
+ * site set them:
936
+ *
937
+ * duplicateRows how many identical rows collapsed into this one — so the
938
+ * raw count is still visible and nothing is hidden
939
+ * roles the distinct `role` values of the collapsed copies, when
940
+ * they differ; omitted when they do not, because a key with
941
+ * one value in it is noise
942
+ *
943
+ * Rows that differ in ANY of the four key components are KEPT. Two calls to the
944
+ * same method on different lines are two facts about the program and stay two
945
+ * rows — this is not a per-pair dedupe, which would silently discard the
946
+ * per-site line the M59.A7 contract requires.
947
+ */
948
+ dedupeRelationships() {
949
+ const kept = [];
950
+ const index = /* @__PURE__ */ new Map();
951
+ let collapsed = 0;
952
+ for (const rel of this.relationships) {
953
+ const key = `${rel.source}\0${rel.target}\0${rel.type}\0${rel.line_number ?? 0}`;
954
+ const prior = index.get(key);
955
+ if (prior === void 0) {
956
+ index.set(key, rel);
957
+ kept.push(rel);
958
+ continue;
959
+ }
960
+ collapsed++;
961
+ const md = prior.metadata ?? {};
962
+ const n = typeof md.duplicateRows === "number" ? md.duplicateRows : 1;
963
+ const role = rel.metadata?.role;
964
+ let roles;
965
+ if (typeof role === "string" && role.length > 0) {
966
+ const existing = Array.isArray(md.roles) ? md.roles : typeof md.role === "string" ? [md.role] : [];
967
+ if (!existing.includes(role)) roles = [...existing, role].sort();
968
+ }
969
+ prior.metadata = { ...md, duplicateRows: n + 1, ...roles ? { roles } : {} };
970
+ }
971
+ if (collapsed > 0) {
972
+ this.note("relationship-duplicate-rows-collapsed");
973
+ this.relationships = kept;
974
+ }
975
+ }
976
+ /**
977
+ * One `evidenceClass` per relationship, in one place, at the end.
978
+ *
979
+ * MEASURED DEFECT this closes: over 2,658 corpus files every one of the
980
+ * analyzer's 102,295 edges carried `evidenceClass: (none)` -- the label the
981
+ * honesty contract requires on any edge below confidence 1.0, and 94% of the
982
+ * `calls` population plus 100% of `references` is below it. `src/lsp/
983
+ * evidence_tier.ts` then re-derives a tier from the KIND, so the product had
984
+ * a badge and the stored evidence said nothing; the M14 contract's rule 4
985
+ * ("carry a machine-readable reason code, not just a number") was not
986
+ * satisfiable.
987
+ *
988
+ * The rule is the legend's own five tiers, applied here exactly once so no
989
+ * call site has to remember it and no call site can disagree with another:
990
+ *
991
+ * imports import_based (a stated import)
992
+ * references usage_contextual (a name, not a symbol)
993
+ * uses usage_contextual (a read; see pushUse)
994
+ * everything below the 0.7 confidence floor inferred (the legend's own
995
+ * wording for a guess)
996
+ * everything else structural
997
+ *
998
+ * `contains`, `exports`, `extends`, `implements`, `instantiates`, `calls` and
999
+ * `overrides` are structural reads of the tree; the sub-floor clause is what
1000
+ * catches the cross-file `calls`/`instantiates`/`references` edges whose target
1001
+ * is a name this file does not declare. An `evidenceClass` an analyzer already
1002
+ * wrote is never overwritten.
1003
+ */
1004
+ stampEvidenceClasses() {
1005
+ for (const rel of this.relationships) {
1006
+ const md = rel.metadata;
1007
+ if (md && typeof md.evidenceClass === "string") continue;
1008
+ const conf = typeof rel.confidence === "number" ? rel.confidence : 1;
1009
+ let cls;
1010
+ if (conf < 0.7) cls = "inferred";
1011
+ else if (rel.type === RelationshipType.Imports) cls = "import_based";
1012
+ else if (rel.type === RelationshipType.References || rel.type === RelationshipType.Uses) cls = "usage_contextual";
1013
+ else cls = "structural";
1014
+ rel.metadata = { evidenceClass: cls, ...md ?? {} };
1015
+ }
1016
+ }
1017
+ reset() {
1018
+ this.symbols = [];
1019
+ this.relationships = [];
1020
+ this.imports = [];
1021
+ this.index = /* @__PURE__ */ new Map();
1022
+ this.declaredNames = /* @__PURE__ */ new Set();
1023
+ this.attributeEdgeKeys = /* @__PURE__ */ new Set();
1024
+ this.ownerStack = [];
1025
+ this.typeStack = [];
1026
+ this.memberNames = /* @__PURE__ */ new Map();
1027
+ this.callablesInFile = /* @__PURE__ */ new Set();
1028
+ this.variablesInFile = /* @__PURE__ */ new Set();
1029
+ this.arrayValuedInFile = /* @__PURE__ */ new Set();
1030
+ this.localsInFile = /* @__PURE__ */ new Set();
1031
+ this.usesEmitted = /* @__PURE__ */ new Map();
1032
+ this.callCalleeNodes = /* @__PURE__ */ new Set();
1033
+ this.gaps = {};
1034
+ this.useIndex = /* @__PURE__ */ new Map();
1035
+ }
1036
+ note(kind) {
1037
+ this.gaps[kind] = (this.gaps[kind] ?? 0) + 1;
1038
+ }
1039
+ /**
1040
+ * Read every declaration in the file BEFORE any body is walked, so the three
1041
+ * resolvability sets do not depend on where a declaration sits relative to its
1042
+ * use. See the `analyze()` note for the measured defect this removes.
1043
+ *
1044
+ * The node set is the same one `pushSymbol` acts on, and the array-ness of a
1045
+ * declaration is read off its own `as_clause` -> `array_type` rather than off its
1046
+ * text, so `SomePropertyReturningAnArray(0)` is recognised as an index and not a
1047
+ * call. One extra tree walk per file; the cost is in the lane's
1048
+ * payload/latency instrument.
1049
+ */
1050
+ precollectDeclarations(root) {
1051
+ const CALLABLE = /* @__PURE__ */ new Set([
1052
+ "method_declaration",
1053
+ "constructor_declaration",
1054
+ "property_declaration",
1055
+ "operator_declaration",
1056
+ "interface_method_declaration",
1057
+ "interface_property_declaration",
1058
+ "declare_statement"
1059
+ ]);
1060
+ const VARIABLE = /* @__PURE__ */ new Set(["field_declaration"]);
1061
+ const LOCAL_BINDING = /* @__PURE__ */ new Set([
1062
+ "declaration_statement",
1063
+ "local_declaration_modifier",
1064
+ "dim_statement"
1065
+ ]);
1066
+ const stack = [root];
1067
+ while (stack.length > 0) {
1068
+ const n = stack.pop();
1069
+ if (CALLABLE.has(n.type)) {
1070
+ const field = n.childForFieldName("name");
1071
+ if (field) {
1072
+ const token = this.namingToken(field, field.text);
1073
+ const name = this.nameText(token ?? field);
1074
+ if (name.length > 0) {
1075
+ this.callablesInFile.add(name);
1076
+ if (this.isArrayValued(n)) this.arrayValuedInFile.add(name);
1077
+ }
1078
+ }
1079
+ } else if (n.type === "constructor_declaration") {
1080
+ this.callablesInFile.add("constructor");
1081
+ } else if (n.type === "delegate_declaration") {
1082
+ const field = n.childForFieldName("name");
1083
+ if (field) {
1084
+ const token = this.namingToken(field, field.text);
1085
+ const name = this.nameText(token ?? field);
1086
+ if (name.length > 0) this.callablesInFile.add(name);
1087
+ }
1088
+ }
1089
+ if (VARIABLE.has(n.type)) {
1090
+ for (const d of this.namedChildrenOfType(n, "variable_declarator")) {
1091
+ const { text } = this.declaredName(d);
1092
+ if (text.length > 0) this.variablesInFile.add(text);
1093
+ }
1094
+ }
1095
+ if (LOCAL_BINDING.has(n.type)) {
1096
+ for (const d of this.namedChildrenOfType(n, "variable_declarator")) {
1097
+ const { text } = this.declaredName(d);
1098
+ if (text.length > 0) this.localsInFile.add(text);
1099
+ }
1100
+ }
1101
+ if (n.type === "for_each_statement") {
1102
+ const v = n.childForFieldName("variable");
1103
+ if (v) {
1104
+ const { text } = this.declaredName(v);
1105
+ const name = text.length > 0 ? text : this.nameText(v);
1106
+ if (name.length > 0) this.localsInFile.add(name);
1107
+ }
1108
+ }
1109
+ if (n.type === "using_statement") {
1110
+ const resource = n.childForFieldName("resource");
1111
+ if (resource) {
1112
+ const { text } = this.declaredName(resource);
1113
+ if (text.length > 0) this.localsInFile.add(text);
1114
+ }
1115
+ }
1116
+ for (const c of this.kids(n)) stack.push(c);
1117
+ }
1118
+ }
1119
+ /** Does this declaration's `as_clause` declare an ARRAY type? */
1120
+ isArrayValued(node) {
1121
+ const asClause = this.namedChildrenOfType(node, "as_clause")[0];
1122
+ if (!asClause) return false;
1123
+ const declared = asClause.childForFieldName("declared_type");
1124
+ if (!declared) return false;
1125
+ if (declared.type === "array_type") return true;
1126
+ for (const c of this.kids(declared)) {
1127
+ if (c.type === "array_type" || c.type === "array_rank_specifier") return true;
1128
+ }
1129
+ return false;
1130
+ }
1131
+ // -------------------------------------------------------------------------
1132
+ // The walk
1133
+ // -------------------------------------------------------------------------
1134
+ walk(node, content, context) {
1135
+ switch (node.type) {
1136
+ case "namespace_declaration":
1137
+ return this.visitNamespace(node, content, context);
1138
+ case "class_declaration":
1139
+ case "structure_declaration":
1140
+ case "interface_declaration":
1141
+ case "module_declaration":
1142
+ return this.visitType(node, content, context);
1143
+ case "enum_declaration":
1144
+ return this.visitEnum(node, content, context);
1145
+ case "delegate_declaration":
1146
+ return this.visitDelegate(node, content, context);
1147
+ case "imports_statement":
1148
+ return this.visitImports(node);
1149
+ case "method_declaration":
1150
+ case "constructor_declaration":
1151
+ // An interface method has the SAME shape as a class method -- `name`,
1152
+ // `parameters`, `as_clause` -- and no body, so the visitor is reused
1153
+ // unchanged. The one thing it must not do is invent a body: `collectBodyEdges`
1154
+ // and the member-heritage read both walk children, and an interface member
1155
+ // has none to walk.
1156
+ case "interface_method_declaration":
1157
+ case "declare_statement":
1158
+ return this.visitMethod(node, content, context);
1159
+ case "property_declaration":
1160
+ case "interface_property_declaration":
1161
+ return this.visitProperty(node, content, context);
1162
+ case "event_declaration":
1163
+ case "interface_event_declaration":
1164
+ return this.visitEvent(node, content, context);
1165
+ case "operator_declaration":
1166
+ return this.visitOperator(node, content, context);
1167
+ case "field_declaration":
1168
+ return this.visitField(node, content);
1169
+ // AN `ERROR` NODE IS CODE THE GRAMMAR COULD NOT CLASSIFY, and it is often a
1170
+ // SIBLING of the declaration whose body it used to be.
1171
+ //
1172
+ // Measured: UseCompoundAssignment/Utilities.vb's
1173
+ // `ImmutableArray.Create((A,B,C), (D,E,F), ...)` initializer leaves a
1174
+ // `field_declaration` spanning rows 9-12 and puts rows 12-19 in ERROR siblings
1175
+ // beside it. Nothing else visits them, so 24 member reads in that one file
1176
+ // were unreachable — and the lane's differential derived all 24 and reported
1177
+ // them MISSING, which is how a grammar's recovery cost 24 edges in the index
1178
+ // and no gate could see it.
1179
+ //
1180
+ // Dispatching an ERROR node is the fix that generalises: its declaration-
1181
+ // shaped children go to `walk` and everything else is body code attributed to
1182
+ // the enclosing declaration, which is what it is.
1183
+ case "ERROR":
1184
+ return this.visitRecovery(node, content, context);
1185
+ default:
1186
+ break;
1187
+ }
1188
+ for (const child of this.kids(node)) this.walk(child, content, context);
1189
+ }
1190
+ /**
1191
+ * Dispatch an `ERROR` node: declaration-shaped children to their own visitors,
1192
+ * everything else as body code of the enclosing declaration.
1193
+ */
1194
+ visitRecovery(node, content, context) {
1195
+ this.dispatchBody(node, content, context);
1196
+ }
1197
+ /**
1198
+ * Does this node, or anything under it, declare a symbol? Early-exits on the first
1199
+ * one found.
1200
+ *
1201
+ * The first version carried a `if (cur.id === n.id) continue` guard so the walk
1202
+ * would not re-test the node it started from — and that guard also SKIPPED THE
1203
+ * START NODE'S CHILDREN, so it answered "is this node itself a declaration?" for
1204
+ * every node and "is there a declaration below?" for none. It reported false for
1205
+ * `type_declaration_in_type`, whose only child is a `class_declaration`, and the
1206
+ * 168 such nodes across 60 of 403 sampled files were then dispatched as body code
1207
+ * and never reached `visitType` — 6 missing `contains`, 1 missing `extends` and 6
1208
+ * missing `overrides` in one file, which is how the differential found it.
1209
+ */
1210
+ hasDeclarationDescendant(n) {
1211
+ const stack = [n];
1212
+ while (stack.length > 0) {
1213
+ const cur = stack.pop();
1214
+ if (_RealVbAnalyzer.DECLARATION_NODES.has(cur.type)) return true;
1215
+ for (const c of this.kids(cur)) stack.push(c);
1216
+ }
1217
+ return false;
1218
+ }
1219
+ /** The nearest enclosing namespace or type, for `ownerName`. */
1220
+ /** The `role` recorded on a containment edge, derived from the KIND so that
1221
+ * every kind names itself and no visitor has to remember to say so. */
1222
+ containmentRole(kind) {
1223
+ switch (kind) {
1224
+ case ASTEngine.SymbolKind.Constructor:
1225
+ return "constructor";
1226
+ case ASTEngine.SymbolKind.Property:
1227
+ return "property";
1228
+ case ASTEngine.SymbolKind.Event:
1229
+ return "event";
1230
+ case ASTEngine.SymbolKind.Method:
1231
+ return "method";
1232
+ case ASTEngine.SymbolKind.Constant:
1233
+ return "const";
1234
+ case ASTEngine.SymbolKind.Variable:
1235
+ return "field";
1236
+ case ASTEngine.SymbolKind.EnumMember:
1237
+ return "enumMember";
1238
+ case ASTEngine.SymbolKind.Enum:
1239
+ return "enum";
1240
+ case ASTEngine.SymbolKind.Class:
1241
+ return "class";
1242
+ case ASTEngine.SymbolKind.Struct:
1243
+ return "structure";
1244
+ case ASTEngine.SymbolKind.Interface:
1245
+ return "interface";
1246
+ case ASTEngine.SymbolKind.Module:
1247
+ return "module";
1248
+ case ASTEngine.SymbolKind.Namespace:
1249
+ return "namespace";
1250
+ case ASTEngine.SymbolKind.Type:
1251
+ return "delegate";
1252
+ default:
1253
+ return String(kind);
1254
+ }
1255
+ }
1256
+ currentOwner() {
1257
+ return this.ownerStack.length > 0 ? this.ownerStack[this.ownerStack.length - 1] : null;
1258
+ }
1259
+ /**
1260
+ * THE NODE THAT CARRIES THIS DECLARATION'S ATTRIBUTE LISTS.
1261
+ *
1262
+ * The attributes do NOT always sit on the node the symbol is published from, and that is a
1263
+ * grammar fact rather than a choice: `field_declaration` carries
1264
+ * `repeat(field("attributes", $.attribute_list))` and then `commaSep1($.variable_declarator)`,
1265
+ * while this analyzer publishes one symbol per DECLARATOR (visitField's `pushSymbol(text, kind,
1266
+ * decl, ...)`). Reading only the declarator's own children would lose every attribute on every
1267
+ * field and every local — silently, which is the class of gap a completeness audit exists for.
1268
+ *
1269
+ * SO THE CLIMB IS EXACTLY TWO SHAPES AND NO MORE. A declarator is reached from
1270
+ * `field_declaration` or `declaration_statement`; anything else is refused rather than walked, so
1271
+ * a member can never inherit the enclosing TYPE's attributes (which is what an unbounded climb
1272
+ * would do on `Public Class C` followed by `Sub M()`: M is not decorated by C's `<Obsolete>`).
1273
+ */
1274
+ attributeBearingNode(declNode) {
1275
+ let cur = declNode;
1276
+ for (let i = 0; cur !== null && i < 3; i++) {
1277
+ if (cur.namedChildCount > 0 && this.namedChildrenOfType(cur, "attribute_list").length > 0) {
1278
+ return cur;
1279
+ }
1280
+ const parent = cur.parent;
1281
+ if (parent === null) return null;
1282
+ if (parent.type !== "field_declaration" && parent.type !== "declaration_statement") {
1283
+ return null;
1284
+ }
1285
+ cur = parent;
1286
+ }
1287
+ return null;
1288
+ }
1289
+ /**
1290
+ * DECORATES — a declaration's attribute lists, published as edges.
1291
+ *
1292
+ * `<Obsolete("use V2")> Public Class Legacy` is ONE syntactic event carrying TWO facts, and the
1293
+ * fleet's vocabulary keeps them apart: the attribute NAME is a type mention (not a value read,
1294
+ * which is why `inAttributePosition` suppresses it from `references` — a differential finding, not
1295
+ * an oversight), and the APPLYING declaration is what the attribute decorates. This publishes the
1296
+ * second as `decorates <declaration> -> <attribute leaf>`.
1297
+ *
1298
+ * WHY THE TARGET IS THE LEAF: the arbiter binds a target by NAME against the project's own
1299
+ * declarations, so `System.Obsolete` is unresolvable BY CONSTRUCTION while `Obsolete` resolves
1300
+ * exactly when the project declares its own attribute type (rust's ruling for this type, applied
1301
+ * here). The written spelling is not lost — it rides in `attributeName`, so the leaf is the KEY
1302
+ * and the spelling is the EVIDENCE.
1303
+ *
1304
+ * GRAMMAR BASIS, MEASURED RATHER THAN ASSUMED: in grammars/tree-sitter-vbnet/grammar.js every
1305
+ * declaration production carries `repeat(field("attributes", $.attribute_list))` — class,
1306
+ * structure, interface, module, enum, enum_member, delegate, field, property, getter, setter,
1307
+ * method, constructor, event, operator, the four interface member forms, parameter and
1308
+ * declaration_statement — and `attribute` is
1309
+ * `seq(field("name", $._name_reference), optional(field("arguments", $.attribute_argument_list)))`.
1310
+ * The name is therefore a FIELD read, not a positional guess.
1311
+ *
1312
+ * WHAT IS NOT REACHABLE, AND IS COUNTED RATHER THAN SILENT: `<Assembly: …>` / `<Module: …>` live in
1313
+ * `global_attribute_section` and a file-level `file_attribute_section` decorates the FILE. Neither
1314
+ * has a declaration to name as a source, so neither publishes an edge here; each is counted by
1315
+ * `note("attribute-without-declaration")` and ruled in the lane's spec, which is the difference
1316
+ * between a disclosed gap and a gap.
1317
+ */
1318
+ pushAttributeDecorations(declNode, symName, kind) {
1319
+ const holder = this.attributeBearingNode(declNode);
1320
+ if (holder === null) return;
1321
+ for (const list of this.namedChildrenOfType(holder, "attribute_list")) {
1322
+ for (const attr of this.namedChildrenOfType(list, "attribute")) {
1323
+ const nameNode = attr.childForFieldName("name") ?? this.namedChildrenOfType(attr, "qualified_name")[0] ?? this.namedChildrenOfType(attr, "global_qualified_name")[0] ?? this.namedChildrenOfType(attr, "identifier")[0];
1324
+ if (!nameNode) {
1325
+ this.note("attribute-without-name");
1326
+ continue;
1327
+ }
1328
+ const written = this.nameText(nameNode);
1329
+ const leaf = written.split(".").pop() ?? written;
1330
+ if (leaf.length === 0) {
1331
+ this.note("attribute-without-name");
1332
+ continue;
1333
+ }
1334
+ const line = this.nodeLine(nameNode);
1335
+ const key = `${symName}\0${leaf}\0${line}`;
1336
+ if (this.attributeEdgeKeys.has(key)) continue;
1337
+ this.attributeEdgeKeys.add(key);
1338
+ const resolved = this.declaredNames.has(leaf);
1339
+ const args = attr.childForFieldName("arguments");
1340
+ const attributeTarget = this.namedChildrenOfType(holder, "attribute_target")[0];
1341
+ this.relationships.push({
1342
+ source: symName,
1343
+ target: leaf,
1344
+ type: RelationshipType.Decorates,
1345
+ confidence: resolved ? 1 : 0.8,
1346
+ line_number: line,
1347
+ metadata: {
1348
+ // THE WRITTEN SPELLING, preserved: the edge's target is the resolvable key, this is
1349
+ // the evidence a reader (and the differential) checks it against.
1350
+ attributeName: written,
1351
+ attributeLeaf: leaf,
1352
+ // `declaration` for a member/type attribute; a target prefix (`Assembly:`) is carried
1353
+ // when the grammar recorded one, which is how a global section is visibly different.
1354
+ attributeForm: attributeTarget ? "target" : "declaration",
1355
+ ...attributeTarget ? { attributeTarget: attributeTarget.text } : {},
1356
+ argumentCount: args ? args.namedChildCount : 0,
1357
+ decoratedKind: kind,
1358
+ resolved
1359
+ }
1360
+ });
1361
+ }
1362
+ }
1363
+ }
1364
+ pushSymbol(name, kind, node, extra = {}, nameNode = null) {
1365
+ if (name.length === 0) {
1366
+ this.note("declaration-without-name");
1367
+ return null;
1368
+ }
1369
+ const owner = this.currentOwner();
1370
+ const { metadata: extraMetadata, ...rest } = extra;
1371
+ const qualifiedName = owner && owner.name !== name ? `${owner.name}.${name}` : name;
1372
+ const sym = {
1373
+ name,
1374
+ qualifiedName,
1375
+ kind,
1376
+ range: this.range(node),
1377
+ ...owner ? { ownerName: owner.name, ownerKind: owner.kind } : {},
1378
+ // THE SYMBOL'S OWN ACCESSIBILITY, stamped where the symbol is published so a
1379
+ // consumer (and `pushExport`'s `ownerAccessLevel`) reads ONE record instead of
1380
+ // re-deriving the modifiers from the tree. It used to be absent, and the
1381
+ // `?? "assembly"` fallback in `pushExport` then reported a `Friend` type's
1382
+ // member as `ownerAccessLevel: assembly` -- the default standing in for a
1383
+ // measured fact, on the one row whose whole point is that the default differs.
1384
+ metadata: {
1385
+ ...extraMetadata ?? {},
1386
+ accessLevel: [...this.exportLevelsOf(node, kind).levels].join("+")
1387
+ },
1388
+ ...rest
1389
+ };
1390
+ this.symbols.push(sym);
1391
+ this.declaredNames.add(name);
1392
+ this.pushAttributeDecorations(node, name, kind);
1393
+ if (owner && owner.name !== name) {
1394
+ this.relationships.push({
1395
+ source: owner.name,
1396
+ target: name,
1397
+ type: RelationshipType.Contains,
1398
+ confidence: 1,
1399
+ line_number: this.nodeLine(nameNode ?? node),
1400
+ metadata: { role: this.containmentRole(kind) }
1401
+ });
1402
+ }
1403
+ this.pushExport(node, name, kind, nameNode, owner);
1404
+ if (!this.index.has(name)) this.index.set(name, sym);
1405
+ if (kind === ASTEngine.SymbolKind.Method || kind === ASTEngine.SymbolKind.Constructor || kind === ASTEngine.SymbolKind.Property) {
1406
+ this.callablesInFile.add(name);
1407
+ }
1408
+ if (kind === ASTEngine.SymbolKind.Variable) this.variablesInFile.add(name);
1409
+ if (kind === ASTEngine.SymbolKind.Type && sym.metadata?.delegate === true) {
1410
+ this.callablesInFile.add(name);
1411
+ }
1412
+ const ownerType = this.currentType();
1413
+ if (ownerType) {
1414
+ let list = this.memberNames.get(ownerType.name);
1415
+ if (!list) {
1416
+ list = [];
1417
+ this.memberNames.set(ownerType.name, list);
1418
+ }
1419
+ list.push(name);
1420
+ }
1421
+ return sym;
1422
+ }
1423
+ currentType() {
1424
+ return this.typeStack.length > 0 ? this.typeStack[this.typeStack.length - 1] : null;
1425
+ }
1426
+ /**
1427
+ * The node whose ACCESS MODIFIERS describe this declaration.
1428
+ *
1429
+ * `pushSymbol` is called with the node that carries the NAME, and for a field
1430
+ * that is the `variable_declarator` -- while the modifiers are on its parent
1431
+ * `field_declaration` (`Public Value As Integer = 3` is
1432
+ * `field_declaration(members: member_modifier) variable_declarator(Value ...)`).
1433
+ * Reading modifiers off the declarator therefore found none, which is why a
1434
+ * `Public` field and an undecorated one were indistinguishable. One level up,
1435
+ * and only for that exact pair of node types, is the whole correction.
1436
+ */
1437
+ declarationNodeOf(node) {
1438
+ if (node.type === "variable_declarator") {
1439
+ const parent = node.parent;
1440
+ if (parent && parent.type === "field_declaration") return parent;
1441
+ }
1442
+ return node;
1443
+ }
1444
+ /** The access levels in force for a symbol, explicit or defaulted. */
1445
+ exportLevelsOf(node, kind) {
1446
+ const levels = this.accessLevelsOf(this.declarationNodeOf(node));
1447
+ if (levels.size > 0) return { levels, explicit: true };
1448
+ const dflt = this.defaultAccessLevel(kind);
1449
+ return { levels: /* @__PURE__ */ new Set([dflt]), explicit: false };
1450
+ }
1451
+ /**
1452
+ * `exports`: the assembly's public surface.
1453
+ *
1454
+ * VB HAS the construct, so this is a GAP and not a ruling. The language
1455
+ * expresses export through ACCESSIBILITY rather than through a keyword: a
1456
+ * declaration is reachable from outside its assembly when its effective
1457
+ * accessibility is `Public`, and there is no second mechanism -- measured over
1458
+ * 2,658 corpus files, `exports_statement`, `export_list` and `assembly_attribute`
1459
+ * are all node types the grammar HAS RULES for and that occur ZERO times
1460
+ * (census probe, node inventory), so a keyword-shaped `exports` in VB would be
1461
+ * an edge the language cannot express. `Public` is a `member_modifier` and a
1462
+ * grammar inventory is blind to a convention by construction, which is exactly
1463
+ * how json's `exports` ruling was overturned; so the number behind this
1464
+ * verdict is a MODIFIER count (15,489 `public` modifiers in 2,344 files) and a
1465
+ * DEFAULTED-accessibility count (136 modifier-less class members, 20 in a
1466
+ * module, 5 in a structure, 116 modifier-less top-level types), not a node
1467
+ * type count.
1468
+ *
1469
+ * DIRECTION. Source is the declaring type, or `root` for a type declared at
1470
+ * namespace level -- the assembly is the unit of export in a .NET assembly, and
1471
+ * `imports` already uses `root` as the source for the same reason.
1472
+ *
1473
+ * CONTAINMENT IS NOT COMPOUNDED INTO THE DECISION. VB's documented rule is
1474
+ * that a Public member of an internal type is not assembly-visible ("code that
1475
+ * cannot access a containing element cannot access any of its contained
1476
+ * elements"), so the edge COULD be suppressed for a member of a `Friend` type.
1477
+ * It is not, and the reason is that the edge as emitted answers a question a
1478
+ * consumer can ask ("which members does this type declare as Public?") without
1479
+ * the analyzer having to decide a transitive-visibility question it cannot
1480
+ * fully answer -- a `Friend` type is assembly-visible to a FRIEND ASSEMBLY,
1481
+ * which is per-project configuration this analyzer never sees. `ownerExported`
1482
+ * records the containing type's own visibility so the compounding is one
1483
+ * filter away instead of being baked into the edge's existence.
1484
+ */
1485
+ pushExport(node, name, kind, nameNode, owner) {
1486
+ const { levels, explicit } = this.exportLevelsOf(node, kind);
1487
+ if (!_RealVbAnalyzer.isAssemblyVisible(levels)) return;
1488
+ if (owner && owner.kind === ASTEngine.SymbolKind.Namespace) return;
1489
+ this.relationships.push({
1490
+ source: owner ? owner.name : "root",
1491
+ target: name,
1492
+ type: RelationshipType.Exports,
1493
+ confidence: 1,
1494
+ line_number: this.nodeLine(nameNode ?? node),
1495
+ metadata: {
1496
+ role: this.containmentRole(kind),
1497
+ accessLevel: [...levels].join("+"),
1498
+ accessFromModifier: explicit,
1499
+ // The containing type's OWN accessibility, read off the symbol record
1500
+ // `pushSymbol` wrote when it published the owner. Recorded rather than
1501
+ // acted on; see the header for why the edge does not depend on it.
1502
+ ownerAccessLevel: owner?.metadata?.accessLevel ?? "assembly"
1503
+ }
1504
+ });
1505
+ }
1506
+ // -------------------------------------------------------------------------
1507
+ // Declarations
1508
+ // -------------------------------------------------------------------------
1509
+ visitNamespace(node, content, context) {
1510
+ const { text, node: nameNode } = this.declaredName(node);
1511
+ const sym = this.pushSymbol(text, SymbolKind.Namespace, node, {
1512
+ metadata: {
1513
+ // Uniform with every other declaration. The node-type yield audit walks
1514
+ // the corpus counting declaration NODES and then counts symbols by their
1515
+ // `grammarNode`, and a declaration that records none of its own node type
1516
+ // is invisible to that audit -- which is how a whole kind can stop being
1517
+ // produced with nothing left to notice. The audit found this one missing
1518
+ // and could not tell a dead visitor from an unlabelled symbol; the fix is
1519
+ // to label it.
1520
+ grammarNode: node.type,
1521
+ // The two namespace forms are ONE node type in this grammar (two
1522
+ // alternatives of one rule), and the analyzer's job is to say which one
1523
+ // it read. The discriminator is the ABSENCE of `End Namespace`: the
1524
+ // file-scoped form has no terminator to close it.
1525
+ //
1526
+ // (This line previously tested a `blank_line` node that this grammar
1527
+ // does not have -- an invented reference, which is the LUA-00 defect
1528
+ // class and which M14.0.F1 caught as an unknown node type. The
1529
+ // expression was also wrong independently of that: it ANDed the
1530
+ // discriminator with a test for a node that never appears, so it could
1531
+ // only ever be false.)
1532
+ fileScoped: !this.hasEndNamespace(node)
1533
+ }
1534
+ });
1535
+ if (sym) {
1536
+ this.typeStack.push(sym);
1537
+ this.ownerStack.push(sym);
1538
+ }
1539
+ for (const child of this.kids(node)) this.walk(child, content, context);
1540
+ if (sym) {
1541
+ this.typeStack.pop();
1542
+ this.ownerStack.pop();
1543
+ }
1544
+ }
1545
+ /**
1546
+ * Whether this namespace has an `End Namespace`.
1547
+ *
1548
+ * The grammar does not expose it as a node — `End` and `Namespace` are
1549
+ * anonymous tokens inside the rule. So this reads the node's LAST named
1550
+ * child's extent against the namespace's own text: the block form's last
1551
+ * named child ends at the `End Namespace`, the file-scoped form's last child
1552
+ * is the final declaration. A char-scan over the tail, not a regex: find the
1553
+ * last occurrence of the keyword by walking backwards.
1554
+ */
1555
+ hasEndNamespace(node) {
1556
+ if (node.namedChildCount === 0) return false;
1557
+ const named = this.kids(node);
1558
+ const last = named[named.length - 1];
1559
+ if (!last) return false;
1560
+ const tail = node.text.slice(last.endIndex - node.startIndex);
1561
+ return _RealVbAnalyzer.endsWithKeyword(tail, "end namespace");
1562
+ }
1563
+ /** Char-scan: does `s` end with `words` (ASCII, case-insensitive, any run of spaces/tabs between)? */
1564
+ static endsWithKeyword(s, words) {
1565
+ let i = s.length - 1;
1566
+ let w = words.length - 1;
1567
+ while (w >= 0) {
1568
+ while (i >= 0 && (s.charCodeAt(i) === 32 || s.charCodeAt(i) === 9 || s.charCodeAt(i) === 13 || s.charCodeAt(i) === 10)) i--;
1569
+ if (w < 0) break;
1570
+ const want = words.charCodeAt(w);
1571
+ let got = s.charCodeAt(i);
1572
+ if (got >= 65 && got <= 90) got += 32;
1573
+ if (want >= 65 && want <= 90) want;
1574
+ const wantFolded = want >= 65 && want <= 90 ? want + 32 : want;
1575
+ if (got !== wantFolded) return false;
1576
+ i--;
1577
+ w--;
1578
+ }
1579
+ while (i >= 0) {
1580
+ const c = s.charCodeAt(i);
1581
+ if (c !== 32 && c !== 9 && c !== 13 && c !== 10 && c !== 59) return false;
1582
+ i--;
1583
+ }
1584
+ return true;
1585
+ }
1586
+ /**
1587
+ * Dispatch a type's or a module's BODY: declarations to their own visitors,
1588
+ * structure to nothing, and everything else as body code of the type.
1589
+ *
1590
+ * This is what recovered 24 member reads in UseCompoundAssignment/Utilities.vb and
1591
+ * the whole class declaration in
1592
+ * VisualBasicRemoveUnnecessaryPragmaSuppressionsDiagnosticAnalyzer.vb: the
1593
+ * grammar's recovery leaves a type body's statements as DIRECT children of the
1594
+ * type, outside any declaration, and nothing was reading them. A type that
1595
+ * executes code publishes nothing about what it executes.
1596
+ */
1597
+ dispatchBody(node, content, context) {
1598
+ for (const child of this.kids(node)) {
1599
+ if (_RealVbAnalyzer.DECLARATION_NODES.has(child.type)) {
1600
+ this.walk(child, content, context);
1601
+ continue;
1602
+ }
1603
+ if (_RealVbAnalyzer.TYPE_BODY_STRUCTURE.has(child.type)) continue;
1604
+ if (this.hasDeclarationDescendant(child)) {
1605
+ this.dispatchBody(child, content, context);
1606
+ continue;
1607
+ }
1608
+ this.collectBodyEdges(child, null);
1609
+ }
1610
+ }
1611
+ visitType(node, content, context) {
1612
+ const kind = _RealVbAnalyzer.typeKindOf(node.type);
1613
+ const { text, node: nameNode } = this.declaredName(node);
1614
+ const mods = this.modifierNames(node);
1615
+ const sym = this.pushSymbol(text, kind, node, {
1616
+ visibility: this.visibilityOf(node, kind),
1617
+ isStatic: mods.has("shared"),
1618
+ isAbstract: this.isAbstract(node),
1619
+ metadata: {
1620
+ grammarNode: node.type,
1621
+ // `NotInheritable` is the VB spelling of C#'s `sealed`; recording it
1622
+ // under the same key the family's csharp lane uses is what lets a
1623
+ // cross-language query ask "sealed types" without knowing the language.
1624
+ sealed: mods.has("notinheritable") || mods.has("notoverridable"),
1625
+ partial: mods.has("partial")
1626
+ }
1627
+ });
1628
+ if (sym) {
1629
+ this.typeStack.push(sym);
1630
+ this.ownerStack.push(sym);
1631
+ }
1632
+ if (sym) this.visitHeritage(node, text, sym, context);
1633
+ this.dispatchBody(node, content, context);
1634
+ if (sym) {
1635
+ this.typeStack.pop();
1636
+ this.ownerStack.pop();
1637
+ }
1638
+ }
1639
+ static typeKindOf(nodeType) {
1640
+ switch (nodeType) {
1641
+ case "class_declaration":
1642
+ return SymbolKind.Class;
1643
+ case "structure_declaration":
1644
+ return SymbolKind.Struct;
1645
+ case "interface_declaration":
1646
+ return SymbolKind.Interface;
1647
+ case "module_declaration":
1648
+ return SymbolKind.Module;
1649
+ default:
1650
+ return SymbolKind.Class;
1651
+ }
1652
+ }
1653
+ /**
1654
+ * VB spells heritage two ways and BOTH are emitted:
1655
+ * - a following line: `Inherits B` / `Implements I, J`
1656
+ * - on the declaration header: `Class C : Inherits B, Implements I`
1657
+ * and, for an interface or a structure, the keyword-less
1658
+ * `Interface I : IDisposable, IEnumerable`
1659
+ *
1660
+ * The header form is not a node — the grammar folds it into a hidden rule
1661
+ * whose children are anonymous tokens plus `name_reference` leaves. So the
1662
+ * heritage names are read as the identifier-bearing children of the header
1663
+ * position, and the keyword that precedes them decides whether each is an
1664
+ * `extends` or an `implements` edge. Which keyword it was comes from the
1665
+ * ORDER: `Inherits` precedes `Implements`, and a structure/interface list has
1666
+ * neither, so every name after the first `Inherits` run and before an
1667
+ * `Implements` token is an extends target and the rest are implements.
1668
+ */
1669
+ visitHeritage(node, selfName, sym, context) {
1670
+ for (const st of this.namedChildrenOfType(node, "inherits_statement")) {
1671
+ for (const nameNode of this.nameReferencesOf(st)) {
1672
+ this.pushHeritage(selfName, nameNode, RelationshipType.Extends, sym, context);
1673
+ }
1674
+ }
1675
+ for (const st of this.namedChildrenOfType(node, "implements_statement")) {
1676
+ for (const nameNode of this.nameReferencesOf(st)) {
1677
+ this.pushHeritage(selfName, nameNode, RelationshipType.Implements, sym, context);
1678
+ }
1679
+ }
1680
+ for (const st of this.namedChildrenOfType(node, "implements_member_clause")) {
1681
+ for (const nameNode of this.nameReferencesOf(st)) {
1682
+ const ownerSym = this.index.get(selfName);
1683
+ if (ownerSym) this.pushHeritage(selfName, nameNode, RelationshipType.Implements, ownerSym, context);
1684
+ }
1685
+ }
1686
+ const colon = this.headerColonIndex(node);
1687
+ if (colon < 0) return;
1688
+ const headerKids = this.allKids(node);
1689
+ const firstNameIdx = headerKids.findIndex((c, i) => i > colon && this.isNameNode(c));
1690
+ const keyword = firstNameIdx < 0 ? "" : this.spanKeywordBetween(node, headerKids[colon], headerKids[firstNameIdx], ["inherits", "implements"]);
1691
+ const headerLabel = keyword === "inherits" ? RelationshipType.Extends : keyword === "implements" || node.type === "interface_declaration" ? RelationshipType.Implements : RelationshipType.Extends;
1692
+ for (let i = colon + 1; i < headerKids.length; i++) {
1693
+ const child = headerKids[i];
1694
+ if (child.type === ",") continue;
1695
+ if (!this.isNameNode(child)) break;
1696
+ const word = this.nameText(child).trim().toLowerCase();
1697
+ if (word === "implements" || word === "inherits") continue;
1698
+ this.pushHeritage(selfName, child, headerLabel, sym, context);
1699
+ }
1700
+ }
1701
+ /**
1702
+ * Which of `words` appears in the source span between two child nodes.
1703
+ *
1704
+ * A char scan, not a regex and not a node test, for the stated reason: this
1705
+ * grammar inlines the heritage keywords away, so the only place the word
1706
+ * survives is the node's own text. Word-bounded on both sides, so
1707
+ * `InheritsFoo` and `MyImplements` cannot match. Returns "" when none is
1708
+ * present, which is a real answer for the keyword-less interface header.
1709
+ */
1710
+ spanKeywordBetween(parent, from, to, words) {
1711
+ const start = from.endIndex - parent.startIndex;
1712
+ const end = to.startIndex - parent.startIndex;
1713
+ if (end <= start || end > parent.text.length) return "";
1714
+ const span = parent.text.slice(start, end);
1715
+ const isWordChar = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95;
1716
+ for (const w of words) {
1717
+ for (let i = 0; i + w.length <= span.length; i++) {
1718
+ if (i > 0 && isWordChar(span.charCodeAt(i - 1))) continue;
1719
+ let ok = true;
1720
+ for (let k = 0; k < w.length; k++) {
1721
+ let c = span.charCodeAt(i + k);
1722
+ if (c >= 65 && c <= 90) c += 32;
1723
+ if (c !== w.charCodeAt(k)) {
1724
+ ok = false;
1725
+ break;
1726
+ }
1727
+ }
1728
+ if (!ok) continue;
1729
+ const after = i + w.length;
1730
+ if (after < span.length && isWordChar(span.charCodeAt(after))) continue;
1731
+ return w;
1732
+ }
1733
+ }
1734
+ return "";
1735
+ }
1736
+ /**
1737
+ * The index of the anonymous `:` token of the declaration header, or -1.
1738
+ *
1739
+ * THE ORDERING IS BY `startIndex`, and it used to be by `id` -- which made this
1740
+ * branch DEAD on every header clause in the corpus. A `web-tree-sitter` node
1741
+ * `id` is an internal subtree pointer, not a document position: measured on
1742
+ * `Public Class B : Inherits A`, the `:` token has id 4064760 and the name
1743
+ * field has id 4065216, so `kids[i].id > from` is FALSE for the colon this
1744
+ * method exists to find, and the whole header branch was unreachable. The
1745
+ * grammar audit could not see it: `id` is not a node type and not a field
1746
+ * name, so a vocabulary audit reads this line as a well-formed field read.
1747
+ *
1748
+ * What it cost, measured on the lane corpus by scanning for the shape
1749
+ * directly (2 header clauses exist, and this is every one of them):
1750
+ *
1751
+ * Public NotInheritable Class MyGroupCollectionAttribute : Inherits Attribute
1752
+ * Public Class entry_ : Implements IComparable(Of entry_)
1753
+ *
1754
+ * published NO heritage edge at all. Not a mislabelled one -- none. So the
1755
+ * mislabelling the `seenInherits = -1` flag would have caused was never
1756
+ * reached either, and both header forms were silently absent: `extends` was
1757
+ * 2 edges short and `implements` 1, on real declarations, and every number the
1758
+ * lane published counted them correctly as missing rather than as present.
1759
+ */
1760
+ headerColonIndex(node) {
1761
+ const nameField = node.childForFieldName("name");
1762
+ const from = nameField ? nameField.startIndex : node.startIndex;
1763
+ const kids = this.allKids(node);
1764
+ for (let i = 0; i < kids.length; i++) {
1765
+ if (kids[i].startIndex > from && kids[i].type === ":") return i;
1766
+ }
1767
+ return -1;
1768
+ }
1769
+ /**
1770
+ * Is this node one of the grammar's NAME types?
1771
+ *
1772
+ * The set is the grammar's real vocabulary, read off the pinned wasm: an
1773
+ * identifier, or one of the three name wrappers (`simple_name`,
1774
+ * `qualified_name`, `global_qualified_name`) or the generic name. Two
1775
+ * earlier versions of this test named `name_reference` and `namespace_name`,
1776
+ * neither of which is a node type in this grammar -- invented references that
1777
+ * are dead branches, which is the LUA-00 defect and what M14.0.F1 is for.
1778
+ * Naming them from the pinned surface rather than from a habit is the fix.
1779
+ */
1780
+ isNameNode(n) {
1781
+ switch (n.type) {
1782
+ case "identifier":
1783
+ case "simple_name":
1784
+ case "qualified_name":
1785
+ case "global_qualified_name":
1786
+ case "generic_name":
1787
+ return true;
1788
+ default:
1789
+ return false;
1790
+ }
1791
+ }
1792
+ /** The name-bearing children of a heritage statement. */
1793
+ nameReferencesOf(node) {
1794
+ const out = [];
1795
+ const visit = (n) => {
1796
+ for (const c of this.kids(n)) {
1797
+ if (this.isNameNode(c)) {
1798
+ out.push(c);
1799
+ } else {
1800
+ visit(c);
1801
+ }
1802
+ }
1803
+ };
1804
+ visit(node);
1805
+ return out;
1806
+ }
1807
+ /**
1808
+ * One heritage edge, with the line of the node that NAMES the base.
1809
+ *
1810
+ * The target is the type's own name (`B`, not `Namespace.B`) because that is
1811
+ * what a symbol in this file would be called, and an edge is only navigable if
1812
+ * its target names something. Confidence is 1.0 for a name the file also
1813
+ * declares (an in-file base type is certain) and lower for one it does not,
1814
+ * because a cross-file target is a name, not a resolved symbol.
1815
+ */
1816
+ /**
1817
+ * A MEMBER-LEVEL heritage clause, read on the member that carries it.
1818
+ *
1819
+ * `Public Sub Refresh() Implements IThing.Refresh` is a real, separate
1820
+ * relationship from the class's own `Implements IThing`, and it used to be
1821
+ * dropped: `visitHeritage` was called from exactly one place -- the TYPE
1822
+ * visitor -- so nothing ever read a member's clause. The whole member-level
1823
+ * form was simply not collected, on every member in the language.
1824
+ *
1825
+ * The TARGET IS THE FULL QUALIFIED NAME, deliberately, and this is ONE OF TWO
1826
+ * places the last-segment naming rule does not apply — the other is
1827
+ * `visitImports`, whose target is a namespace PATH (`System.Collections.Generic`),
1828
+ * changed 2026-10-28 for the reason recorded there. Everywhere else a dotted
1829
+ * name is reduced to its last segment, because that is the name a reader
1830
+ * navigates to. Here the member is only identified BY its interface: the method
1831
+ * is named `Refresh` precisely because it implements `IThing.Refresh`, so
1832
+ * last-segment reduction yields `implements Refresh -> Refresh` -- an edge from
1833
+ * a symbol to itself, which is not navigable and answers none of the questions
1834
+ * anyone asks of it ("where is `IThing.Refresh` implemented?"). The full
1835
+ * `IThing.Refresh` is what makes the edge mean something.
1836
+ *
1837
+ * Found by the full-corpus F5 sweep, not by a fixture: 17 of 23 missing rows on
1838
+ * a 483-file draw were this, all of them reading as self-edges for exactly the
1839
+ * reason above.
1840
+ */
1841
+ visitMemberHeritage(node, sym, context) {
1842
+ for (const [clause, rel] of [
1843
+ ["implements_member_clause", RelationshipType.Implements],
1844
+ ["inherits_member_clause", RelationshipType.Extends]
1845
+ ]) {
1846
+ for (const st of this.namedChildrenOfType(node, clause)) {
1847
+ for (const nameNode of this.nameReferencesOf(st)) {
1848
+ const target = this.typeArgumentsStrippedText(nameNode);
1849
+ if (target.length === 0) continue;
1850
+ this.relationships.push({
1851
+ source: sym.name,
1852
+ target,
1853
+ type: rel,
1854
+ // A member-level interface is a type this file may not declare, and
1855
+ // the honest confidence for that is the unresolved-heritage one.
1856
+ confidence: this.declaredNames.has(target) ? 1 : 0.7,
1857
+ line_number: this.nodeLine(nameNode),
1858
+ metadata: {
1859
+ [rel === RelationshipType.Extends ? "baseType" : "interfaceName"]: target,
1860
+ heritageForm: "member",
1861
+ resolved: this.declaredNames.has(target),
1862
+ // The reduction is recorded rather than silent, so a reader can
1863
+ // see that `IEnumerable(Of T).GetEnumerator` and
1864
+ // `IEnumerable.GetEnumerator` are the same edge and not two.
1865
+ openGenericArity: this.typeArgumentArity(nameNode)
1866
+ }
1867
+ });
1868
+ }
1869
+ }
1870
+ }
1871
+ }
1872
+ /**
1873
+ * The name node with every `type_argument_list` REMOVED from its own text.
1874
+ *
1875
+ * `qualified_name` is `qualifier . name`, and the qualifier may be a
1876
+ * `generic_name`, so `IEnumerable(Of CustomEvent).GetEnumerator` is
1877
+ * (qualified_name
1878
+ * qualifier: (generic_name name: (identifier IEnumerable)
1879
+ * (type_argument_list (simple_name (identifier))))
1880
+ * name: (simple_name (identifier GetEnumerator)))
1881
+ * The type arguments live inside the qualifier, not beside the target, so the
1882
+ * `(Of …)` has to be removed from the SPAN rather than from a trailing child.
1883
+ *
1884
+ * A char scan with the equivalence argued, and it has to be the same reduction
1885
+ * `calleeLeaf` applies so the two agree: every `type_argument_list` SPAN is
1886
+ * dropped and the text around it is kept, so the span between the wrapper's
1887
+ * start and the list's start, and everything from the last list's end to the
1888
+ * wrapper's end, both survive. A span scan is required because `node.text` is
1889
+ * the only place the parentheses survive — the `type_argument_list` node's own
1890
+ * text is `(Of CustomEvent)` but no field says where inside the qualifier it
1891
+ * sits, and it is the QUALIFIER that is generic, not the target.
1892
+ *
1893
+ * The result is a STRING rather than a synthesised `Node`. An earlier version
1894
+ * returned `{ text } as unknown as Node`, which happened to satisfy the single
1895
+ * caller because it only read `.text` — and a lie that satisfies its one caller
1896
+ * is exactly the kind of thing that survives until a second caller reads
1897
+ * `startIndex` off it. The signature says what it is.
1898
+ *
1899
+ * When there is nothing to remove the node's own text is returned, so the
1900
+ * common case costs one tree walk and no allocation.
1901
+ */
1902
+ typeArgumentsStrippedText(n) {
1903
+ if (!this.containsTypeArgumentList(n)) return this.nameText(n).trim();
1904
+ const start = n.startIndex - n.startIndex;
1905
+ const end = n.endIndex - n.startIndex;
1906
+ if (!(end > start)) return this.nameText(n).trim();
1907
+ const text = n.text;
1908
+ const spans = [];
1909
+ const walk = (x, depth) => {
1910
+ if (depth > 16) return;
1911
+ if (x.type === "type_argument_list") {
1912
+ spans.push([x.startIndex - n.startIndex, x.endIndex - n.startIndex]);
1913
+ return;
1914
+ }
1915
+ for (const c of this.allKids(x)) walk(c, depth + 1);
1916
+ };
1917
+ walk(n, 0);
1918
+ spans.sort((a, b) => a[0] - b[0]);
1919
+ let out = "";
1920
+ let cursor = start;
1921
+ for (const [s, e] of spans) {
1922
+ if (s < cursor || e > end) continue;
1923
+ out += text.slice(cursor, s);
1924
+ cursor = e;
1925
+ }
1926
+ out += text.slice(cursor, end);
1927
+ return out.trim();
1928
+ }
1929
+ /** Is there a `type_argument_list` anywhere at or under this node? */
1930
+ containsTypeArgumentList(n) {
1931
+ let found = false;
1932
+ const walk = (x, depth) => {
1933
+ if (found || depth > 16) return;
1934
+ if (x.type === "type_argument_list") {
1935
+ found = true;
1936
+ return;
1937
+ }
1938
+ for (const c of this.allKids(x)) walk(c, depth + 1);
1939
+ };
1940
+ walk(n, 0);
1941
+ return found;
1942
+ }
1943
+ /** How many type arguments the name carries, for the edge's own metadata. */
1944
+ typeArgumentArity(n) {
1945
+ let arity = 0;
1946
+ const walk = (x, depth) => {
1947
+ if (depth > 16) return;
1948
+ if (x.type === "type_argument_list") {
1949
+ arity += x.namedChildCount;
1950
+ return;
1951
+ }
1952
+ for (const c of this.allKids(x)) walk(c, depth + 1);
1953
+ };
1954
+ walk(n, 0);
1955
+ return arity;
1956
+ }
1957
+ pushHeritage(selfName, nameNode, type, selfSym, context) {
1958
+ const token = this.namingToken(nameNode, nameNode.text);
1959
+ const target = this.nameText(token ?? nameNode);
1960
+ if (target.length === 0) return;
1961
+ if (target === selfName) {
1962
+ this.note("self-heritage");
1963
+ return;
1964
+ }
1965
+ const inFile = this.declaredNames.has(target);
1966
+ this.relationships.push({
1967
+ source: selfName,
1968
+ target,
1969
+ type,
1970
+ confidence: inFile ? 1 : 0.7,
1971
+ line_number: this.nodeLine(token ?? nameNode),
1972
+ metadata: {
1973
+ [type === RelationshipType.Extends ? "baseType" : "interfaceName"]: target,
1974
+ heritageForm: this.heritageFormOf(nameNode),
1975
+ resolved: inFile
1976
+ }
1977
+ });
1978
+ }
1979
+ /** `line` for a heritage statement's names, `header` for a header-clause name. */
1980
+ heritageFormOf(nameNode) {
1981
+ let cur = nameNode;
1982
+ for (let i = 0; cur !== null && i < 8; i++) {
1983
+ const p = cur.parent;
1984
+ if (p === null) break;
1985
+ if (p.type === "inherits_statement" || p.type === "implements_statement" || p.type === "implements_member_clause") return "statement";
1986
+ if (p.type === "class_declaration" || p.type === "structure_declaration" || p.type === "interface_declaration" || p.type === "module_declaration") return "header";
1987
+ cur = p;
1988
+ }
1989
+ return "unknown";
1990
+ }
1991
+ visitEnum(node, content, context) {
1992
+ const { text } = this.declaredName(node);
1993
+ const sym = this.pushSymbol(text, SymbolKind.Enum, node, {
1994
+ visibility: this.visibilityOf(node, SymbolKind.Enum),
1995
+ metadata: { grammarNode: node.type }
1996
+ });
1997
+ if (sym) {
1998
+ this.typeStack.push(sym);
1999
+ this.ownerStack.push(sym);
2000
+ const enumAsClause = this.namedChildrenOfType(node, "as_clause")[0];
2001
+ const enumDeclared = enumAsClause ? enumAsClause.childForFieldName("declared_type") : null;
2002
+ if (enumDeclared) this.pushTypeReference(sym, enumDeclared, "enumUnderlyingType");
2003
+ }
2004
+ for (const child of this.kids(node)) {
2005
+ if (child.type !== "enum_member_declaration" && child.type !== "variable_declarator") continue;
2006
+ const mName = this.declaredName(child);
2007
+ if (mName.text.length === 0) {
2008
+ this.note("enum-member-without-name");
2009
+ continue;
2010
+ }
2011
+ const init = child.childForFieldName("initializer");
2012
+ const member = this.pushSymbol(mName.text, SymbolKind.EnumMember, child, {
2013
+ ownerName: sym ? sym.name : void 0,
2014
+ ownerKind: sym ? sym.kind : void 0,
2015
+ metadata: {
2016
+ grammarNode: child.type,
2017
+ ...init ? { value: init.text.trim() } : {},
2018
+ valueLine: init ? this.nodeLine(this.namingToken(init, init.text) ?? init) : null
2019
+ }
2020
+ });
2021
+ }
2022
+ for (const child of this.kids(node)) this.walk(child, content, context);
2023
+ if (sym) {
2024
+ this.typeStack.pop();
2025
+ this.ownerStack.pop();
2026
+ }
2027
+ }
2028
+ visitDelegate(node, content, context) {
2029
+ const { text } = this.declaredName(node);
2030
+ const sym = this.pushSymbol(text, SymbolKind.Type, node, {
2031
+ visibility: this.visibilityOf(node, SymbolKind.Type),
2032
+ metadata: { grammarNode: node.type, delegate: true }
2033
+ });
2034
+ if (sym) {
2035
+ this.typeStack.push(sym);
2036
+ this.ownerStack.push(sym);
2037
+ }
2038
+ const params = node.childForFieldName("parameters");
2039
+ if (params && sym) this.readParameters(params, sym);
2040
+ const asClause = this.namedChildrenOfType(node, "as_clause")[0];
2041
+ if (asClause) {
2042
+ const declared = asClause.childForFieldName("declared_type");
2043
+ if (declared) {
2044
+ sym.returnType = this.nameText(declared);
2045
+ this.pushTypeReference(sym, declared, "returnType");
2046
+ }
2047
+ }
2048
+ for (const child of this.kids(node)) this.walk(child, content, context);
2049
+ if (sym) {
2050
+ this.typeStack.pop();
2051
+ this.ownerStack.pop();
2052
+ }
2053
+ }
2054
+ // -------------------------------------------------------------------------
2055
+ // Imports
2056
+ // -------------------------------------------------------------------------
2057
+ /**
2058
+ * `Imports A, B` — one edge PER NAME, each stamped with the line of the name it
2059
+ * writes, and each carrying `isImport` + `include_dependency` so the family's
2060
+ * completeness section-B pin and the tier classifier can mark it import-based.
2061
+ * An import is a name in VB, not a module path, so `module` holds the name and
2062
+ * `isNamespace` is true.
2063
+ */
2064
+ visitImports(node) {
2065
+ const names = this.nameReferencesOf(node);
2066
+ for (const nameNode of names) {
2067
+ const token = this.namingToken(nameNode, nameNode.text);
2068
+ const written = stripWhitespace(nameNode.text);
2069
+ const text = written.length > 0 ? written : this.nameText(token ?? nameNode);
2070
+ if (text.length === 0) continue;
2071
+ const range = this.range(nameNode);
2072
+ this.imports.push({
2073
+ module: text,
2074
+ items: [],
2075
+ isNamespace: true,
2076
+ isNamespaceImport: true,
2077
+ importType: "namespace",
2078
+ range,
2079
+ metadata: { grammarNode: node.type }
2080
+ });
2081
+ this.relationships.push({
2082
+ source: "root",
2083
+ target: text,
2084
+ type: RelationshipType.Imports,
2085
+ confidence: 1,
2086
+ line_number: this.nodeLine(token ?? nameNode),
2087
+ metadata: {
2088
+ [IS_IMPORT]: true,
2089
+ [INCLUDE_DEPENDENCY]: true,
2090
+ importForm: "imports-statement"
2091
+ }
2092
+ });
2093
+ }
2094
+ }
2095
+ // -------------------------------------------------------------------------
2096
+ // Members
2097
+ // -------------------------------------------------------------------------
2098
+ visitMethod(node, content, context) {
2099
+ const isCtor = node.type === "constructor_declaration";
2100
+ const { text, node: nameNode } = this.declaredName(node);
2101
+ if (text.length === 0 && !isCtor) {
2102
+ this.note("method-without-name");
2103
+ return;
2104
+ }
2105
+ const owner = this.currentOwner();
2106
+ const mods = this.modifierNames(node);
2107
+ const isDeclare = node.type === "declare_statement";
2108
+ const kind = isCtor ? SymbolKind.Constructor : SymbolKind.Method;
2109
+ const effectiveName = isCtor ? "constructor" : text;
2110
+ const overridesBase = mods.has("overrides");
2111
+ const sym = this.pushSymbol(effectiveName, kind, node, {
2112
+ visibility: this.visibilityOf(node, kind),
2113
+ isStatic: this.isStatic(node),
2114
+ isAbstract: this.isAbstract(node),
2115
+ isAsync: mods.has("async"),
2116
+ ...isDeclare ? { metadata: { pInvoke: true } } : {},
2117
+ metadata: {
2118
+ grammarNode: node.type,
2119
+ ...isDeclare ? { pInvoke: true } : {},
2120
+ ...overridesBase ? { overridesModifier: true } : {},
2121
+ ...mods.has("partial") ? { partial: true } : {},
2122
+ // The P/Invoke's target library and entry point are on the DECLARATION,
2123
+ // and they are the only place a VB.NET file names an unmanaged symbol.
2124
+ ...isDeclare ? this.readPInvoke(node) : {}
2125
+ }
2126
+ });
2127
+ if (!sym) return;
2128
+ const params = node.childForFieldName("parameters");
2129
+ if (params && sym) this.readParameters(params, sym);
2130
+ const asClause = this.namedChildrenOfType(node, "as_clause")[0];
2131
+ if (asClause) {
2132
+ const declared = asClause.childForFieldName("declared_type");
2133
+ if (declared) {
2134
+ sym.returnType = this.nameText(declared);
2135
+ this.pushTypeReference(sym, declared, "returnType");
2136
+ }
2137
+ }
2138
+ this.ownerStack.push(sym);
2139
+ this.visitMemberHeritage(node, sym, context);
2140
+ if (this.modifierNames(node).has("overrides")) this.pushOverride(node, null, sym, this.currentOwner());
2141
+ this.collectBodyEdges(node, sym);
2142
+ for (const child of this.kids(node)) this.walk(child, content, context);
2143
+ this.ownerStack.pop();
2144
+ }
2145
+ /** `Lib "user32.dll" Alias "MessageBoxA"` — the unmanaged target of a Declare. */
2146
+ readPInvoke(node) {
2147
+ const out = {};
2148
+ const lib = node.childForFieldName("library");
2149
+ if (lib) out.pInvokeLibrary = lib.text.trim();
2150
+ const alias = node.childForFieldName("alias");
2151
+ if (alias) out.pInvokeEntryPoint = alias.text.trim();
2152
+ const kind = node.childForFieldName("kind");
2153
+ if (kind) out.pInvokeKind = kind.text.trim();
2154
+ return out;
2155
+ }
2156
+ readParameters(params, sym) {
2157
+ const out = [];
2158
+ for (const p of this.kids(params)) {
2159
+ if (p.type !== "parameter") continue;
2160
+ const nameNode = p.childForFieldName("name") ?? (p.namedChildCount > 0 ? this.nc(p, 0) : null);
2161
+ const name = this.nameText(nameNode);
2162
+ if (name.length === 0) continue;
2163
+ const byVal = p.text.includes("ByVal") || p.text.includes("byval");
2164
+ const asClause = this.namedChildrenOfType(p, "as_clause")[0];
2165
+ const declared = asClause ? asClause.childForFieldName("declared_type") : null;
2166
+ const optional = p.text.includes("Optional") || p.text.includes("optional");
2167
+ const param = {
2168
+ name,
2169
+ ...declared ? { type: this.nameText(declared) } : {},
2170
+ ...optional ? { isOptional: true } : {},
2171
+ defaultValue: this.readDefaultOf(p),
2172
+ metadata: { passingMode: byVal ? "ByVal" : "ByRef" }
2173
+ };
2174
+ out.push(param);
2175
+ if (declared) this.pushTypeReference(sym, declared, "parameter");
2176
+ }
2177
+ if (out.length > 0) sym.parameters = out;
2178
+ }
2179
+ /** An `= default` on a parameter, read off the node's own text span. */
2180
+ readDefaultOf(p) {
2181
+ const idx = p.text.indexOf("=");
2182
+ if (idx < 0) return void 0;
2183
+ const tail = p.text.slice(idx + 1).trim();
2184
+ return tail.length > 0 ? tail : void 0;
2185
+ }
2186
+ visitProperty(node, content, context) {
2187
+ const { text, node: nameNode } = this.declaredName(node);
2188
+ if (text.length === 0) {
2189
+ this.note("property-without-name");
2190
+ return;
2191
+ }
2192
+ const mods = this.modifierNames(node);
2193
+ const sym = this.pushSymbol(text, SymbolKind.Property, node, {
2194
+ visibility: this.visibilityOf(node, SymbolKind.Property),
2195
+ isStatic: this.isStatic(node),
2196
+ isAbstract: this.isAbstract(node),
2197
+ metadata: {
2198
+ grammarNode: node.type,
2199
+ ...mods.has("readonly") ? { readOnly: true } : {},
2200
+ ...mods.has("writeonly") ? { writeOnly: true } : {},
2201
+ ...mods.has("default") ? { defaultMember: true } : {},
2202
+ ...mods.has("overrides") ? { overridesModifier: true } : {},
2203
+ autoImplemented: this.namedChildrenOfType(node, "property_getter").length === 0
2204
+ }
2205
+ });
2206
+ if (!sym) return;
2207
+ const asClause = this.namedChildrenOfType(node, "as_clause")[0];
2208
+ if (asClause) {
2209
+ const declared = asClause.childForFieldName("declared_type");
2210
+ if (declared) {
2211
+ sym.returnType = this.nameText(declared);
2212
+ this.pushTypeReference(sym, declared, "propertyType");
2213
+ }
2214
+ }
2215
+ const owner = this.currentOwner();
2216
+ this.ownerStack.push(sym);
2217
+ this.visitMemberHeritage(node, sym, context);
2218
+ if (this.modifierNames(node).has("overrides")) this.pushOverride(node, null, sym, this.currentOwner());
2219
+ this.collectBodyEdges(node, sym);
2220
+ for (const child of this.kids(node)) this.walk(child, content, context);
2221
+ this.ownerStack.pop();
2222
+ }
2223
+ visitEvent(node, content, context) {
2224
+ const { text, node: nameNode } = this.declaredName(node);
2225
+ if (text.length === 0) {
2226
+ this.note("event-without-name");
2227
+ return;
2228
+ }
2229
+ const sym = this.pushSymbol(text, SymbolKind.Event, node, {
2230
+ visibility: this.visibilityOf(node, SymbolKind.Event),
2231
+ isStatic: this.isStatic(node),
2232
+ metadata: { grammarNode: node.type, withEvents: this.modifierNames(node).has("withevents") }
2233
+ });
2234
+ if (!sym) return;
2235
+ const owner = this.currentOwner();
2236
+ const asClause = this.namedChildrenOfType(node, "as_clause")[0];
2237
+ if (asClause) {
2238
+ const declared = asClause.childForFieldName("declared_type");
2239
+ if (declared) {
2240
+ sym.returnType = this.nameText(declared);
2241
+ this.pushTypeReference(sym, declared, "eventType");
2242
+ }
2243
+ }
2244
+ this.ownerStack.push(sym);
2245
+ this.visitMemberHeritage(node, sym, context);
2246
+ if (this.modifierNames(node).has("overrides")) this.pushOverride(node, null, sym, this.currentOwner());
2247
+ for (const child of this.kids(node)) this.walk(child, content, context);
2248
+ this.ownerStack.pop();
2249
+ }
2250
+ visitOperator(node, content, context) {
2251
+ const STRUCTURAL = /* @__PURE__ */ new Set(["(", ")", ",", ":", "=", "New", "Operator"]);
2252
+ let opName = "";
2253
+ for (const c of this.allKids(node)) {
2254
+ if (c.isNamed) continue;
2255
+ const t = c.text.trim();
2256
+ if (t.length === 0) continue;
2257
+ if (STRUCTURAL.has(t)) continue;
2258
+ if (c.type === "=") continue;
2259
+ if (_RealVbAnalyzer.isWordLike(t) && _RealVbAnalyzer.isKeyword(t)) continue;
2260
+ opName = t;
2261
+ break;
2262
+ }
2263
+ const sym = this.pushSymbol("operator", SymbolKind.Method, node, {
2264
+ visibility: this.visibilityOf(node, ASTEngine.SymbolKind.Method),
2265
+ isStatic: this.isStatic(node),
2266
+ metadata: {
2267
+ grammarNode: node.type,
2268
+ ...opName.length > 0 ? { operatorSymbol: opName } : {}
2269
+ }
2270
+ });
2271
+ if (!sym) return;
2272
+ const opParams = node.childForFieldName("parameters");
2273
+ if (opParams) this.readParameters(opParams, sym);
2274
+ const opAs = this.namedChildrenOfType(node, "as_clause")[0];
2275
+ if (opAs) {
2276
+ const declared = opAs.childForFieldName("declared_type");
2277
+ if (declared) {
2278
+ sym.returnType = this.nameText(declared);
2279
+ this.pushTypeReference(sym, declared, "operatorReturnType");
2280
+ }
2281
+ }
2282
+ this.ownerStack.push(sym);
2283
+ this.collectBodyEdges(node, sym);
2284
+ for (const child of this.kids(node)) this.walk(child, content, context);
2285
+ this.ownerStack.pop();
2286
+ }
2287
+ /** Does this token start with a letter or underscore? */
2288
+ static isWordLike(t) {
2289
+ const c = t.charCodeAt(0);
2290
+ return Number.isFinite(c) && (c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95);
2291
+ }
2292
+ /** A char-set membership test, not a keyword list lookup by regex. */
2293
+ static isKeyword(t) {
2294
+ let i = 0;
2295
+ let sawAlpha = false;
2296
+ for (; i < t.length; i++) {
2297
+ const c = t.charCodeAt(i);
2298
+ const alpha = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
2299
+ if (!alpha) break;
2300
+ sawAlpha = true;
2301
+ }
2302
+ if (!sawAlpha) return true;
2303
+ return _RealVbAnalyzer.VB_KEYWORD_HEADS.has(t.slice(0, i).toLowerCase());
2304
+ }
2305
+ static VB_KEYWORD_HEADS = /* @__PURE__ */ new Set([
2306
+ "public",
2307
+ "private",
2308
+ "protected",
2309
+ "friend",
2310
+ "shared",
2311
+ "overridable",
2312
+ "overrides",
2313
+ "notoverridable",
2314
+ "mustoverride",
2315
+ "notinheritable",
2316
+ "mustinherit",
2317
+ "partial",
2318
+ "readonly",
2319
+ "writeonly",
2320
+ "default",
2321
+ "withevents",
2322
+ "shadows",
2323
+ "overloads",
2324
+ "widening",
2325
+ "narrowing",
2326
+ "async",
2327
+ "iterator",
2328
+ "static",
2329
+ "const"
2330
+ ]);
2331
+ visitField(node, content) {
2332
+ const mods = this.modifierNames(node);
2333
+ const isConst = mods.has("const");
2334
+ const owner = this.currentOwner();
2335
+ for (const decl of this.kids(node)) {
2336
+ if (decl.type !== "variable_declarator") continue;
2337
+ const { text, node: nameNode } = this.declaredName(decl);
2338
+ if (text.length === 0) {
2339
+ this.note("field-without-name");
2340
+ continue;
2341
+ }
2342
+ const init = decl.childForFieldName("initializer");
2343
+ const asClause = this.namedChildrenOfType(decl, "as_clause")[0];
2344
+ const declared = asClause ? asClause.childForFieldName("declared_type") : null;
2345
+ const sym = this.pushSymbol(text, isConst ? SymbolKind.Constant : SymbolKind.Variable, decl, {
2346
+ visibility: this.visibilityOf(node, isConst ? SymbolKind.Constant : SymbolKind.Variable),
2347
+ isStatic: this.isStatic(node) || isConst || mods.has("shared"),
2348
+ ...declared ? { returnType: this.nameText(declared) } : {},
2349
+ ...init ? { metadata: { initializer: init.text.trim() } } : {},
2350
+ metadata: {
2351
+ grammarNode: decl.type,
2352
+ ...isConst ? { constant: true } : {},
2353
+ ...mods.has("withevents") ? { withEvents: true } : {},
2354
+ ...mods.has("readonly") ? { readOnly: true } : {},
2355
+ fieldLine: this.nodeLine(decl)
2356
+ }
2357
+ });
2358
+ if (!sym) continue;
2359
+ if (declared) this.pushTypeReference(sym, declared, "fieldType");
2360
+ if (asClause && this.asClauseIsConstruction(asClause)) {
2361
+ const tyNode = declared ?? asClause;
2362
+ this.pushConstruction(tyNode, owner ?? sym);
2363
+ }
2364
+ }
2365
+ this.collectBodyEdges(node, null);
2366
+ }
2367
+ // -------------------------------------------------------------------------
2368
+ // Calls, instantiations, member reads and type references
2369
+ // -------------------------------------------------------------------------
2370
+ /**
2371
+ * Every body edge in a subtree: `calls`, `instantiates` and `uses`, all stamped
2372
+ * with the line of the node that NAMES the thing.
2373
+ *
2374
+ * ## THE CALL SHAPE THIS GRAMMAR ACTUALLY PRODUCES, and why `calls` was silent
2375
+ *
2376
+ * Measured through the pinned wasm, minimal repro, tree-only:
2377
+ *
2378
+ * Foo() -> invocation_expression
2379
+ * Foo(1) -> array_access_expression <- with ARGUMENTS
2380
+ * obj.Foo(1) -> array_access_expression
2381
+ * a(i) -> array_access_expression <- an INDEX
2382
+ * New Foo(1) -> array_creation_expression
2383
+ *
2384
+ * **A VB call with arguments is an `array_access_expression`, and this analyzer
2385
+ * read only `invocation_expression`.** Every argument-bearing call in the
2386
+ * language was therefore un-published. Measured over 636 corpus files:
2387
+ *
2388
+ * invocation_expression 5,120 (the only shape read, before)
2389
+ * array_access_expression 21,392 (not read at all)
2390
+ *
2391
+ * A 100-line file published 3 call edges from ~46 member accesses and calls. The
2392
+ * F5 differential could not see it: the lane's oracle block was removed from
2393
+ * `script/m14/differential.ts` (the lane has 4 DERIVATION findings — calls,
2394
+ * contains, imports, references — which is the gate's word for "compared against
2395
+ * nothing"), and a whole-corpus COUNT cannot see a missing class of edge either,
2396
+ * because the counts stayed healthy.
2397
+ *
2398
+ * ## WHY NOT EVERY `array_access_expression` IS A CALL
2399
+ *
2400
+ * The grammar cannot tell `Foo(1)` (a call) from `a(i)` (an index). Emitting an
2401
+ * edge for all of them would be a fabricated call edge, which the workstream
2402
+ * calls out as the most expensive mistake available. So two SIGNALS are
2403
+ * required, and a site with neither is COUNTED in `vbGaps` rather than guessed:
2404
+ *
2405
+ * A. The callee names something THIS FILE DECLARES CALLABLE -- a method, a
2406
+ * property, a constructor, an operator or a delegate -- and that declaration
2407
+ * is NOT array-valued. An index needs an array, so a name this file declares
2408
+ * as a method is a call and cannot be an index. The array-valued exclusion is
2409
+ * read off the declaration's own `as_clause` -> `array_type`, because
2410
+ * `SomePropertyReturningAnArray(0)` is legal VB and would otherwise be
2411
+ * published as a call to an array.
2412
+ *
2413
+ * B. The node is the WHOLE content of an `expression_statement` -- a call
2414
+ * statement -- AND the callee name is not a variable/field this file
2415
+ * declares. VB has no statement-level index expression, so a parenthesised
2416
+ * name at statement level is a call; excluding the file's own variables
2417
+ * removes the `arr(0)` case where `arr` is declared locally.
2418
+ *
2419
+ * Measured over 636 files: 11,269 of the 21,392 carry at least one signal. The
2420
+ * other 10,123 are counted as `index-or-call-ambiguous` and stay un-emitted,
2421
+ * because guessing between an index and a call is exactly the fabrication the
2422
+ * ontology forbids.
2423
+ *
2424
+ * ## WHAT ELSE IS DECIDED HERE, each a real question
2425
+ *
2426
+ * - WHICH node names the callee. For `a.b.C(1)` the callee is the `C` leaf, so
2427
+ * the edge is stamped on the line `C` is written on, not the line the
2428
+ * statement started on. A three-line chained call then has one site line.
2429
+ * - WHICH node is the SOURCE. The innermost enclosing declaration, not the
2430
+ * file: an edge from `root` to a callee cannot be navigated to a method.
2431
+ * - WHEN there is no source, there is no edge. A call in a field initializer
2432
+ * has no enclosing method, and an edge from nothing is a phantom — so it is
2433
+ * counted as a gap instead of invented.
2434
+ * - `uses` DEDUPLICATES per (source, target) per file, keeping the FIRST site.
2435
+ * That is the family's own choice for this label (csharp's CS-D4 field-access
2436
+ * edge does exactly this) and it is a payload decision as much as a semantic
2437
+ * one: 536,141 member reads exist in the corpus and `uses` at one edge per
2438
+ * site would more than double this lane's whole graph.
2439
+ */
2440
+ collectBodyEdges(scope, owner) {
2441
+ const source = owner ?? this.currentOwner();
2442
+ const scopeId = scope.id;
2443
+ const visit = (n) => {
2444
+ if (n.id !== scopeId && _RealVbAnalyzer.DECLARATION_NODES.has(n.type)) {
2445
+ for (const c of this.kids(n)) visit(c);
2446
+ return;
2447
+ }
2448
+ if (n.type === "variable_declarator" && source) {
2449
+ let p = n.parent;
2450
+ let fieldOwned = false;
2451
+ for (let guard = 0; guard < 8 && p; guard++, p = p.parent) {
2452
+ if (p.type === "field_declaration") {
2453
+ fieldOwned = true;
2454
+ break;
2455
+ }
2456
+ if (p.type === "declaration_statement" || p.type === "using_statement" || p.type === "for_each_statement") break;
2457
+ }
2458
+ if (!fieldOwned) {
2459
+ const asClause = this.namedChildrenOfType(n, "as_clause")[0];
2460
+ if (asClause && this.asClauseIsConstruction(asClause)) {
2461
+ this.pushConstruction(asClause.childForFieldName("declared_type") ?? asClause, source);
2462
+ }
2463
+ }
2464
+ }
2465
+ if (n.type === "array_creation_expression") {
2466
+ this.pushConstruction(n, source);
2467
+ for (const c of this.kids(n)) visit(c);
2468
+ return;
2469
+ }
2470
+ if (n.type === "member_access_expression" || n.type === "with_member_access_expression") {
2471
+ if (!this.isCalleeOrIndexSlot(n) && !this.isAssignmentLhs(n) && !this.inAttributePosition(n)) {
2472
+ const token = this.calleeLeaf(n);
2473
+ const target = this.nameText(token);
2474
+ if (target.length > 0) this.pushUse(source, target, n, token);
2475
+ }
2476
+ }
2477
+ if (n.type === "invocation_expression") {
2478
+ const fn = n.childForFieldName("function");
2479
+ const args = n.childForFieldName("arguments");
2480
+ if (fn && !this.calleeIsConstruction(fn)) {
2481
+ const token = this.calleeLeaf(fn);
2482
+ const target = this.nameText(token);
2483
+ if (target.length > 0) {
2484
+ if (source) {
2485
+ const argCount = args ? args.namedChildCount : 0;
2486
+ this.relationships.push({
2487
+ source: source.name,
2488
+ target,
2489
+ type: RelationshipType.Calls,
2490
+ confidence: this.declaredNames.has(target) ? 1 : 0.6,
2491
+ line_number: this.nodeLine(token ?? fn),
2492
+ metadata: {
2493
+ // The argument count is read off the AST's own child list, so
2494
+ // it excludes `comment` extras rather than counting them the
2495
+ // way a text scan of the parens would.
2496
+ argumentCount: argCount,
2497
+ calleeIsQualified: fn.type === "member_access_expression",
2498
+ callLine: this.nodeLine(token ?? fn),
2499
+ constructed: fn.type === "object_creation_expression",
2500
+ ...fn.type === "object_creation_expression" ? { constructionNode: fn.type } : {}
2501
+ }
2502
+ });
2503
+ if (fn.type === "object_creation_expression") {
2504
+ this.pushInstantiation(source, target, token ?? fn, fn.type);
2505
+ }
2506
+ this.callCalleeNodes.add(fn.id);
2507
+ } else {
2508
+ this.note("call-without-enclosing-declaration");
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+ if (n.type === "object_creation_expression") {
2514
+ this.pushConstruction(n, source);
2515
+ }
2516
+ if (n.type === "array_access_expression") {
2517
+ const calleeNode = this.nc(n, 0);
2518
+ const token = calleeNode ? this.calleeLeaf(calleeNode) : null;
2519
+ const target = this.nameText(token);
2520
+ if (target.length > 0) {
2521
+ const argCount = n.namedChildCount > 1 ? n.namedChildCount - 1 : 0;
2522
+ const viaDeclaration = this.callablesInFile.has(target) && !this.arrayValuedInFile.has(target);
2523
+ const viaStatement = this.isStatementLevelCall(n) && !this.variablesInFile.has(target) && !this.localsInFile.has(target);
2524
+ if (viaDeclaration || viaStatement) {
2525
+ if (source) {
2526
+ this.relationships.push({
2527
+ source: source.name,
2528
+ target,
2529
+ type: RelationshipType.Calls,
2530
+ confidence: viaDeclaration && this.declaredNames.has(target) ? 1 : 0.7,
2531
+ line_number: this.nodeLine(token ?? n),
2532
+ metadata: {
2533
+ argumentCount: argCount,
2534
+ calleeIsQualified: calleeNode?.type === "member_access_expression",
2535
+ callLine: this.nodeLine(token ?? n),
2536
+ constructed: false,
2537
+ // `callShape` names the node the grammar gave this call, because
2538
+ // `array_access_expression` is not what a reader expects to see on
2539
+ // a `calls` edge and a consumer filtering on the node type would
2540
+ // otherwise conclude the edge is an index.
2541
+ callShape: n.type,
2542
+ callSignal: viaDeclaration ? "callee-declared-callable-in-file" : "call-statement"
2543
+ }
2544
+ });
2545
+ if (calleeNode) this.callCalleeNodes.add(calleeNode.id);
2546
+ } else {
2547
+ this.note("call-without-enclosing-declaration");
2548
+ }
2549
+ } else {
2550
+ this.note("index-or-call-ambiguous");
2551
+ }
2552
+ }
2553
+ }
2554
+ if (n.type === "member_access_expression" || n.type === "with_member_access_expression") {
2555
+ const leaf = this.calleeLeaf(n);
2556
+ const target = this.nameText(leaf);
2557
+ if (source && target.length > 0 && !this.isCalleePosition(n) && !this.isAssignmentLhs(n) && !this.inAttributePosition(n)) {
2558
+ const key = `${source.name}->${target}`;
2559
+ const line = this.nodeLine(leaf ?? n);
2560
+ const prior = this.usesEmitted.get(key);
2561
+ if (prior === void 0) {
2562
+ this.usesEmitted.set(key, { key, line_number: line });
2563
+ this.relationships.push({
2564
+ source: source.name,
2565
+ target,
2566
+ type: RelationshipType.Uses,
2567
+ confidence: this.declaredNames.has(target) ? 0.9 : 0.7,
2568
+ line_number: line,
2569
+ metadata: {
2570
+ relationshipType: "vb_member_read",
2571
+ readKind: n.type,
2572
+ receiverIsQualified: this.calleeLeafIsNotFirst(n),
2573
+ // The reason code the honesty contract §4 requires for any
2574
+ // confidence below 1.0: without it, 0.7 is a number with no
2575
+ // machine-readable reason attached.
2576
+ resolvedInFile: this.declaredNames.has(target)
2577
+ }
2578
+ });
2579
+ } else if (line !== null && line < (prior.line_number ?? Number.MAX_SAFE_INTEGER)) {
2580
+ prior.line_number = line;
2581
+ this.relineUses(key, line);
2582
+ }
2583
+ }
2584
+ }
2585
+ for (const c of this.kids(n)) visit(c);
2586
+ };
2587
+ visit(scope);
2588
+ }
2589
+ /** One `instantiates` edge, sourced at the declaration that constructs. */
2590
+ pushInstantiation(source, target, nameNode, nodeType) {
2591
+ this.relationships.push({
2592
+ source: source.name,
2593
+ target,
2594
+ type: RelationshipType.Instantiates,
2595
+ confidence: this.declaredNames.has(target) ? 1 : 0.6,
2596
+ line_number: this.nodeLine(nameNode),
2597
+ metadata: {
2598
+ isNew: true,
2599
+ constructedType: target,
2600
+ // `New Integer(10)` is an ARRAY construction and the grammar reports it as
2601
+ // `array_creation_expression`. Recording that rather than hiding it is the
2602
+ // difference between "instantiates Integer" being a fact a reader can check
2603
+ // and being a claim they have to take on trust.
2604
+ constructionNode: nodeType,
2605
+ constructsArray: nodeType === "array_creation_expression",
2606
+ resolvedInFile: this.declaredNames.has(target)
2607
+ }
2608
+ });
2609
+ }
2610
+ /** Re-stamp an already-published `uses` edge with an earlier site line. */
2611
+ relineUses(key, line) {
2612
+ for (const r of this.relationships) {
2613
+ if (r.type !== RelationshipType.Uses) continue;
2614
+ if (`${r.source}->${r.target}` === key) {
2615
+ r.line_number = line;
2616
+ return;
2617
+ }
2618
+ }
2619
+ }
2620
+ /**
2621
+ * Is this `array_access_expression` the whole content of an expression
2622
+ * statement? `CheckMemberAndReportDiagnostic(context, member)` is a CALL
2623
+ * statement; `x = a(i)` is an index inside an assignment. VB has no
2624
+ * statement-level index expression, so the shape decides — and the caller adds
2625
+ * the second half of the test (the callee is not one of this file's own
2626
+ * variables), which is what removes `arr(0)`.
2627
+ */
2628
+ isStatementLevelCall(n) {
2629
+ const p = n.parent;
2630
+ if (!p) return false;
2631
+ if (p.type !== "expression_statement") return false;
2632
+ return p.namedChildCount === 1;
2633
+ }
2634
+ /**
2635
+ * Is this member access (or the chain above it) the CALLEE of an invocation?
2636
+ *
2637
+ * `obj.M()` is `invocation_expression(function: member_access_expression)`. And
2638
+ * `obj.M(args)` — the shape this grammar produces for a call WITH arguments — is
2639
+ * an `array_access_expression` whose first named child is the member access, so
2640
+ * the climb has to pass through `array_access_expression` as well or every
2641
+ * chained call's leading segment is published as a read of the wrong member.
2642
+ */
2643
+ isCalleePosition(n) {
2644
+ if (this.callCalleeNodes.has(n.id)) return true;
2645
+ let a = n;
2646
+ for (let i = 0; a !== null && i < 12; i++) {
2647
+ const p = a.parent;
2648
+ if (!p) return false;
2649
+ if (p.type === "member_access_expression" || p.type === "with_member_access_expression" || p.type === "array_access_expression" || p.type === "parenthesized_expression") {
2650
+ a = p;
2651
+ continue;
2652
+ }
2653
+ if (p.type === "invocation_expression") {
2654
+ const fn = p.childForFieldName("function");
2655
+ return fn !== null && fn !== void 0 && fn.id === a.id;
2656
+ }
2657
+ return false;
2658
+ }
2659
+ return false;
2660
+ }
2661
+ /**
2662
+ * Is this identifier on the LEFT of an assignment? `this.count = 0` is a write,
2663
+ * not a read — the family's SCD1 contract, shared by java JAD1, kotlin KAD1 and
2664
+ * scala SCD1. Containment is by SPAN, so `this.count = this.count + 1` keeps its
2665
+ * right-hand read.
2666
+ */
2667
+ isAssignmentLhs(n) {
2668
+ let a = n.parent;
2669
+ for (let i = 0; a !== null && i < 6; i++) {
2670
+ if (a.type === "member_access_expression" || a.type === "with_member_access_expression" || a.type === "identifier" || a.type === "array_access_expression") {
2671
+ a = a.parent;
2672
+ continue;
2673
+ }
2674
+ break;
2675
+ }
2676
+ if (!a) return false;
2677
+ if (a.type !== "assignment_expression" && a.type !== "assignment_statement") return false;
2678
+ const left = a.childForFieldName("left") ?? a.childForFieldName("target");
2679
+ if (!left) return false;
2680
+ return left.startIndex <= n.startIndex && left.endIndex >= n.endIndex;
2681
+ }
2682
+ /**
2683
+ * Is this member access inside an ATTRIBUTE rather than inside code?
2684
+ *
2685
+ * `<Obsolete(MefConstruction.ImportingConstructorMessage, True)>` on a
2686
+ * constructor is read by the COMPILER at type-load time, not by the constructor,
2687
+ * so `uses -> constructor -> ImportingConstructorMessage` is not a read the
2688
+ * constructor performs. The lane's differential reported exactly these three rows
2689
+ * as UNEXPLAINED, on three separate corpus files.
2690
+ */
2691
+ inAttributePosition(n) {
2692
+ let a = n.parent;
2693
+ for (let i = 0; a !== null && i < 12; i++) {
2694
+ if (a.type === "attribute_list" || a.type === "attribute" || a.type === "attribute_argument_list" || a.type === "global_attribute_section") return true;
2695
+ if (a.type === "identifier" || a.type === "simple_name" || a.type === "qualified_name" || a.type === "global_qualified_name" || a.type === "generic_name" || a.type === "member_access_expression" || a.type === "with_member_access_expression" || a.type === "named_argument") {
2696
+ a = a.parent;
2697
+ continue;
2698
+ }
2699
+ return false;
2700
+ }
2701
+ return false;
2702
+ }
2703
+ /** Is the accessed member the SECOND half of the chain, i.e. is there a receiver? */
2704
+ calleeLeafIsNotFirst(n) {
2705
+ return this.namedChildrenOfType(n, "identifier").length > 1;
2706
+ }
2707
+ /**
2708
+ * Is the thing being invoked a CONSTRUCTION?
2709
+ *
2710
+ * Read down the receiver chain to the leftmost node: `New Foo()` is an
2711
+ * `invocation_expression` whose `function` is the `object_creation_expression`
2712
+ * itself, and `New System.Drawing.Size()` is an `invocation_expression` whose
2713
+ * `function` is a three-deep `member_access_expression` whose innermost
2714
+ * `object` is the construction. Both are constructions the grammar wrapped in
2715
+ * an invocation, and both already produce an `instantiates` edge naming the
2716
+ * same type, so publishing a `calls` edge as well would count one syntactic
2717
+ * event twice under two labels.
2718
+ */
2719
+ calleeIsConstruction(fn) {
2720
+ let cur = fn;
2721
+ for (let guard = 0; guard < 32; guard++) {
2722
+ if (cur.type === "object_creation_expression" || cur.type === "array_creation_expression") return true;
2723
+ if (cur.type !== "member_access_expression" && cur.type !== "with_member_access_expression") return false;
2724
+ const obj = cur.childForFieldName("object");
2725
+ if (!obj || obj.id === cur.id) return false;
2726
+ cur = obj;
2727
+ }
2728
+ return false;
2729
+ }
2730
+ /**
2731
+ * Is this qualified access the thing being invoked, or the thing being indexed?
2732
+ *
2733
+ * `function` is the callee slot and `array` is the indexed slot, and those two
2734
+ * are the only positions in this grammar where a name is being APPLIED rather
2735
+ * than read. `with_member_access_expression` has neither field -- the census
2736
+ * measured 1,017 of them and 1,017 with no `member` field either -- so it
2737
+ * always reads, which is the right answer for a `With` block: `.Value = 1`
2738
+ * inside `With obj` reads `Value` off `obj`, it does not call it.
2739
+ */
2740
+ isCalleeOrIndexSlot(n) {
2741
+ const p = n.parent;
2742
+ if (!p) return false;
2743
+ if (p.type === "with_member_access_expression") return false;
2744
+ const fn = p.childForFieldName("function");
2745
+ if (fn && fn.id === n.id) return true;
2746
+ const arr = p.childForFieldName("array");
2747
+ if (arr && arr.id === n.id) return true;
2748
+ return false;
2749
+ }
2750
+ /**
2751
+ * `uses`: the enclosing declaration READS this name.
2752
+ *
2753
+ * VB HAS the construct -- it is the commonest expression in the language -- so
2754
+ * this is a GAP and not a ruling. Measured on the lane corpus: 323,540
2755
+ * `member_access_expression` and 1,017 `with_member_access_expression` nodes
2756
+ * are a name read rather than a callee or an index, in 1,921 of 2,658 files.
2757
+ *
2758
+ * WHAT THE TARGET IS. The NAME the file writes, which is the last identifier
2759
+ * leaf of the access chain -- `TimeSpan.FromSeconds` gives `FromSeconds`, and
2760
+ * `System.Collections.Generic.List(Of String)` gives `List` because
2761
+ * `calleeLeaf` skips a `type_argument_list`. That is the same name the
2762
+ * `calls` edge would carry for the same expression, so the two labels are
2763
+ * comparable and a consumer can ask either question of the same grammar.
2764
+ *
2765
+ * WHAT THE TARGET IS NOT. A resolved symbol. A qualified read names a member
2766
+ * of a type this file usually does not declare, so the edge resolves at both
2767
+ * ends about as often as a `calls` edge does (12.0% measured on the current
2768
+ * `calls` population) and that is the honest number for a cross-file name, not
2769
+ * a defect. The family convention agrees: `real-csharp-analyzer.ts` publishes
2770
+ * its identifier reads as `uses` at confidence 1.0 with the same meaning --
2771
+ * the name is written in the file, so the READ is a fact, and whether the
2772
+ * name binds to something elsewhere is a resolution question the edge does not
2773
+ * claim to have answered.
2774
+ *
2775
+ * DEDUPLICATED PER (source, target), keeping the FIRST site, with the site
2776
+ * count on the edge. This matches the family's own `uses` emission (csharp
2777
+ * keys on `source::uses::name`) and it is what makes the class affordable:
2778
+ * 323,540 read sites collapse to the distinct pairs. `sites` keeps the raw
2779
+ * count visible, so the dedup hides nothing.
2780
+ */
2781
+ pushUse(source, target, n, token) {
2782
+ const src = source ?? this.currentOwner();
2783
+ if (!src) {
2784
+ this.note("use-without-enclosing-declaration");
2785
+ return;
2786
+ }
2787
+ const key = `${src.name}\0${target}`;
2788
+ const line = this.nodeLine(token ?? n);
2789
+ const prior = this.useIndex.get(key);
2790
+ if (prior !== void 0) {
2791
+ prior.sites++;
2792
+ prior.edge.metadata = { ...prior.edge.metadata, sites: prior.sites };
2793
+ return;
2794
+ }
2795
+ const edge = {
2796
+ source: src.name,
2797
+ target,
2798
+ type: RelationshipType.Uses,
2799
+ confidence: 1,
2800
+ line_number: line,
2801
+ metadata: { readNode: n.type, sites: 1 }
2802
+ };
2803
+ this.useIndex.set(key, { sites: 1, line, edge });
2804
+ this.relationships.push(edge);
2805
+ }
2806
+ /**
2807
+ * The type name a CONSTRUCTION builds, when the construction is the leading
2808
+ * segment of a qualified name rather than a whole one.
2809
+ *
2810
+ * MEASURED, not guessed. `New System.Drawing.Size(3, 4)` does not parse as
2811
+ * one construction: it is
2812
+ * (array_access_expression
2813
+ * array: (member_access_expression
2814
+ * object: (member_access_expression
2815
+ * object: (object_creation_expression System)
2816
+ * member: Drawing)
2817
+ * member: Size)
2818
+ * indices: 3, 4)
2819
+ * so the `object_creation_expression` holds only `New System` and the real
2820
+ * target is the LAST leaf of the enclosing access chain. Publishing the node's
2821
+ * own `type` field here would publish `instantiates -> System`, which is a
2822
+ * namespace and not a type anybody constructs -- the grammar's fragmentation
2823
+ * turned into a fact about the program, which is the fabrication the
2824
+ * workstream names. Counted over the lane corpus: 15,233
2825
+ * `object_creation_expression` and 310 `array_creation_expression` nodes are
2826
+ * fragments of a qualified name, against 1,612 and 15,181 that stand alone
2827
+ * (census probes `instantiates:*:fragment_of_qualified` /
2828
+ * `instantiates:*:standalone`) -- so this is the COMMON case in this grammar,
2829
+ * not an edge one.
2830
+ */
2831
+ constructionTargetLeaf(n) {
2832
+ if (this.constructionIsFragment(n)) {
2833
+ let outer = n;
2834
+ for (let guard = 0; guard < 32; guard++) {
2835
+ const p = outer.parent;
2836
+ if (!p) break;
2837
+ const isAccess = p.type === "member_access_expression" || p.type === "with_member_access_expression";
2838
+ if (!isAccess) break;
2839
+ const obj = p.childForFieldName("object");
2840
+ if (!obj || obj.id !== outer.id) break;
2841
+ outer = p;
2842
+ }
2843
+ const leaf = this.calleeLeaf(outer);
2844
+ if (leaf) return leaf;
2845
+ }
2846
+ const ty = n.childForFieldName("type");
2847
+ if (ty) return this.calleeLeaf(ty);
2848
+ return this.calleeLeaf(n);
2849
+ }
2850
+ /**
2851
+ * Is this construction the leading NAMESPACE SEGMENT of a qualified type name,
2852
+ * rather than a whole construction that happens to be the RECEIVER of an access?
2853
+ *
2854
+ * THE PARENT TEST ALONE WAS NOT ENOUGH, and it fabricated an edge on 322
2855
+ * construction nodes across the lane corpus. "Is my parent a
2856
+ * `member_access_expression` whose `object` I am?" is true for two completely
2857
+ * different programs:
2858
+ *
2859
+ * `New System.Text.StringBuilder` the construction holds `New System` and
2860
+ * `Text.StringBuilder` continues the TYPE in the chain, so the last leaf
2861
+ * of the chain (`StringBuilder`) is the constructed type. CLIMB.
2862
+ *
2863
+ * `New StackFrame(1).GetMethod().Name` the construction is COMPLETE and
2864
+ * `.GetMethod` is a method call ON THE OBJECT JUST BUILT, so the last
2865
+ * leaf of the chain (`Name`) is not a type at all. DO NOT CLIMB.
2866
+ *
2867
+ * The second shape published `instantiates -> GetMethod` — a method, not a
2868
+ * type, which is the fabrication `docs/analyzers/ONTOLOGY-WORKSTREAM.md`
2869
+ * Part 2 names as the thing that poisons `bb_graph`. An independent
2870
+ * source-text oracle (`script/m14/vb-instantiates-oracle.ts`, which reads no
2871
+ * grammar at all) put the error at 1.07% of all `instantiates` edges on a
2872
+ * 200-file draw, and named the targets: `ToList` 59, `Message` 6, `Length` 4,
2873
+ * `GetMethod` 4, `Contains` 4, `Clone` 2, `Status`, `LayeredDriver`, `Get`,
2874
+ * `TextInfo`. None of them is a constructed type.
2875
+ *
2876
+ * THE DISCRIMINATOR, measured over all 15,543 construction nodes that sit in an
2877
+ * access chain across the corpus (`script/m14/tmp-vb/frag3.mts`):
2878
+ *
2879
+ * a construction is a NAMESPACE PREFIX iff its `type` field is a BARE
2880
+ * `simple_name` and it has no other named child
2881
+ *
2882
+ * 15,221 bare `type`, no other child -> a namespace prefix; climbing is right
2883
+ * 322 anything else -> the construction is complete
2884
+ *
2885
+ * Every one of the 322 is a real defect of one of two kinds, and both are in
2886
+ * the sample set:
2887
+ *
2888
+ * - the construction carries its own arguments, so the chain is a member
2889
+ * access on the value: `New StackFrame(1).GetMethod()`,
2890
+ * `New CultureInfo("en-US", False).TextInfo`,
2891
+ * `BattleQuery.Add(New TextQueryObject(...))` -> `-> Add`, and
2892
+ * `New String() {…}.ToList()` (whose arguments are an `array_initializer`,
2893
+ * not a `bounds` field, so a `bounds`-only test misses it — hence the
2894
+ * named-child count rather than a field test).
2895
+ * - the construction node's SPAN was mis-anchored by the grammar's error
2896
+ * recovery onto the following statement: `Dim SpawnedEntity = New
2897
+ * BattleAnimation3D(...)` followed by `SpawnedEntity.Opacity = Opacity`
2898
+ * yields `-> Opacity`. Its `type` is a `generic_name` rather than a bare
2899
+ * `simple_name`, which is what the second half of the test catches.
2900
+ */
2901
+ constructionIsFragment(n) {
2902
+ const p = n.parent;
2903
+ if (!p) return false;
2904
+ if (p.type !== "member_access_expression" && p.type !== "with_member_access_expression") return false;
2905
+ const obj = p.childForFieldName("object");
2906
+ if (!obj || obj.id !== n.id) return false;
2907
+ if (n.namedChildCount !== 1) return false;
2908
+ const ty = n.childForFieldName("type");
2909
+ if (!ty) return false;
2910
+ return ty.type === "simple_name" || ty.type === "identifier";
2911
+ }
2912
+ /**
2913
+ * `instantiates`: a construction is not a call.
2914
+ *
2915
+ * VB HAS four distinct construction surfaces and the grammar gives each of
2916
+ * them a different shape, so the count is four counts and not one:
2917
+ *
2918
+ * `New Foo` / `New Foo()` object_creation_expression
2919
+ * `New Foo(a, b)` array_creation_expression (a `type` field
2920
+ * and a `bounds` field, and NO `function`
2921
+ * field -- the node a caller must read to find
2922
+ * the constructed type)
2923
+ * `As New Foo()` an `as_clause` whose text begins `As New`,
2924
+ * where the declared_type is an `array_type`
2925
+ * because the `()` reads as bounds
2926
+ * `New A.B.C(x)` an access chain (see
2927
+ * `constructionTargetLeaf`)
2928
+ *
2929
+ * Measured over 2,658 corpus files: 16,845 `object_creation_expression` and
2930
+ * 15,491 `array_creation_expression` nodes, plus 2,499 `as_clause`s that begin
2931
+ * `As New`. The `As New` half cannot be read from the tree at all -- both
2932
+ * keywords are inlined away by the grammar's `as_clause` rule, measured at 0
2933
+ * of 30,001 `as_clause` nodes carrying either token -- so it is a char scan
2934
+ * over the node's own span with the equivalence argued in
2935
+ * `startsWithAsNewClause`, and the census runs the same predicate with the
2936
+ * same controls.
2937
+ *
2938
+ * LABEL. This edge REPLACES the `calls` edge the analyzer used to publish for
2939
+ * a construction rather than joining it. Publishing both would double-count
2940
+ * one syntactic event under two labels and put a construction in the call
2941
+ * graph, which is what makes `bb_graph`'s call view wrong. The family's own
2942
+ * convention agrees: `real-csharp-analyzer.ts` emits `instantiates` for
2943
+ * `object_creation_expression` and its `calls` rule explicitly excludes it.
2944
+ *
2945
+ * CONFIDENCE follows the same rule every other VB edge follows: 1.0 when this
2946
+ * file also declares the constructed type, 0.6 when it does not, because the
2947
+ * target is then a name and not a resolved symbol. Under the honesty
2948
+ * contract's 0.7 floor the 0.6 edges read `inferred`, which is stamped by the
2949
+ * one evidence-class pass at the end of `analyze()`.
2950
+ */
2951
+ pushConstruction(n, source) {
2952
+ const token = this.constructionTargetLeaf(n);
2953
+ const tyField = n.childForFieldName("type");
2954
+ const predefined = token === null && tyField !== null && tyField.type === "predefined_type" ? tyField : null;
2955
+ const target = this.nameText(token ?? predefined);
2956
+ if (target.length === 0) {
2957
+ this.note("construction-without-readable-name");
2958
+ return;
2959
+ }
2960
+ if (!source) {
2961
+ this.note("instantiation-without-enclosing-declaration");
2962
+ return;
2963
+ }
2964
+ this.relationships.push({
2965
+ source: source.name,
2966
+ target,
2967
+ type: RelationshipType.Instantiates,
2968
+ confidence: this.declaredNames.has(target) ? 1 : 0.6,
2969
+ line_number: this.nodeLine(token ?? predefined ?? n),
2970
+ metadata: {
2971
+ constructionNode: n.type,
2972
+ constructionForm: this.constructionFormOf(n),
2973
+ qualifiedConstruction: this.constructionIsFragment(n),
2974
+ // THE ARRAY-NODE DISTINCTION, kept on the row because the two facts differ:
2975
+ // this grammar gives every construction WITH arguments an
2976
+ // `array_creation_expression`, so `New Gauge(1)` and `New Gauge()` are the
2977
+ // same language construct on two different nodes. Recording which node
2978
+ // produced the edge is what stops a reader inferring an array from it.
2979
+ constructsArray: n.type === "array_creation_expression",
2980
+ // The instantiation facts `pushInstantiation` used to stamp on the second
2981
+ // copy of this row, kept here so one emitter carries the whole shape.
2982
+ isNew: true,
2983
+ constructedType: target,
2984
+ resolvedInFile: this.declaredNames.has(target)
2985
+ }
2986
+ });
2987
+ }
2988
+ /** Which of the four VB construction surfaces this is, for a reader triaging. */
2989
+ constructionFormOf(n) {
2990
+ if (n.type === "array_creation_expression") {
2991
+ return n.childForFieldName("bounds") !== null ? "new-with-bounds" : "new-array-initializer";
2992
+ }
2993
+ if (n.parent?.type === "as_clause" && this.asClauseIsConstruction(n.parent)) return "as-new";
2994
+ return n.parent?.type === "invocation_expression" ? "new-then-invoke" : "new";
2995
+ }
2996
+ /**
2997
+ * Does this `as_clause` begin with the two VB words `As` `New`?
2998
+ *
2999
+ * A char scan with the equivalence argued, because the tree cannot answer it:
3000
+ * `as_clause` is `seq(ci("As"), ci("New"), field("declared_type", ...))` and
3001
+ * both keywords are ABSENT from the produced tree -- measured over the corpus,
3002
+ * 0 of 30,001 `as_clause` nodes carry an `As` or `New` token, and a printed
3003
+ * tree of `Dim l As New List(Of String)` shows an `as_clause` whose FIRST
3004
+ * child is the `generic_name`. A node-type or token test on the tree has
3005
+ * nothing to read, so this reads the node's own span instead.
3006
+ *
3007
+ * Word boundary means: the byte before the word is start-of-span or not a
3008
+ * letter/digit/underscore, and the byte after it is the same or end-of-span.
3009
+ * `As Namespace` and `As NewType` must NOT match, and both are rejected
3010
+ * because the adjacent byte is a letter. The leading whitespace is skipped
3011
+ * first because `as_clause`'s span can begin at the line's indentation.
3012
+ */
3013
+ static startsWithAsNewClause(s) {
3014
+ const isWordChar = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95;
3015
+ const skipSpace = (i2) => {
3016
+ let k = i2;
3017
+ while (k < s.length) {
3018
+ const c = s.charCodeAt(k);
3019
+ if (c === 32 || c === 9 || c === 13 || c === 10) k++;
3020
+ else break;
3021
+ }
3022
+ return k;
3023
+ };
3024
+ const startsAt = (i2, w) => {
3025
+ if (i2 > 0 && isWordChar(s.charCodeAt(i2 - 1))) return false;
3026
+ if (i2 + w.length > s.length) return false;
3027
+ for (let k = 0; k < w.length; k++) {
3028
+ let c = s.charCodeAt(i2 + k);
3029
+ if (c >= 65 && c <= 90) c += 32;
3030
+ if (c !== w.charCodeAt(k)) return false;
3031
+ }
3032
+ return true;
3033
+ };
3034
+ const endsAt = (i2) => i2 >= s.length || !isWordChar(s.charCodeAt(i2));
3035
+ const i = skipSpace(0);
3036
+ if (!startsAt(i, "as")) return false;
3037
+ const j = skipSpace(i + 2);
3038
+ if (!startsAt(j, "new")) return false;
3039
+ return endsAt(j + 3);
3040
+ }
3041
+ /**
3042
+ * Is this `as_clause` a CONSTRUCTION (`As New T`) rather than a plain type
3043
+ * annotation (`As T`)? Two signals, one per shape, because the grammar
3044
+ * inlines both keywords away and the argument-less form is otherwise
3045
+ * byte-identical to the plain one:
3046
+ *
3047
+ * `As New T(a, b)` carries an `arguments` field the plain form never has;
3048
+ * `As New T` / `As New T()` does not, so the node's own span is read with
3049
+ * the word-bounded scan above (`As Namespace` and `As NewType` are both
3050
+ * rejected there by the boundary test, which is why the span is scanned
3051
+ * and not matched by substring).
3052
+ */
3053
+ asClauseIsConstruction(asClause) {
3054
+ if (asClause.childForFieldName("arguments") !== null) return true;
3055
+ return _RealVbAnalyzer.startsWithAsNewClause(asClause.text);
3056
+ }
3057
+ /**
3058
+ * A type NAME this file uses, as a `references` edge.
3059
+ *
3060
+ * Emitted only for a name this file does not itself declare, because a type
3061
+ * this file declares has a `contains`/`extends` edge already and a second edge
3062
+ * between the same pair is noise a consumer has to filter.
3063
+ */
3064
+ pushTypeReference(from, typeNode, role) {
3065
+ if (typeNode.type === "tuple_type") {
3066
+ let emitted = 0;
3067
+ for (const el of this.namedChildrenOfType(typeNode, "tuple_element")) {
3068
+ const elType = el.childForFieldName("type") ?? el;
3069
+ this.pushTypeReference(from, elType, role);
3070
+ emitted++;
3071
+ }
3072
+ if (emitted === 0) this.note("tuple-type-has-no-single-name");
3073
+ return;
3074
+ }
3075
+ const token = this.namingToken(typeNode, typeNode.text);
3076
+ const builtin = token !== null && token.type === "predefined_type" ? VB_BUILTIN_TYPE_CANONICAL.get(this.nameText(token).toLowerCase()) : void 0;
3077
+ const target = builtin ?? this.nameText(token ?? typeNode);
3078
+ if (target.length === 0) return;
3079
+ if (this.declaredNames.has(target)) return;
3080
+ this.relationships.push({
3081
+ source: from.name,
3082
+ target,
3083
+ type: RelationshipType.References,
3084
+ confidence: 0.6,
3085
+ line_number: this.nodeLine(token ?? typeNode),
3086
+ metadata: {
3087
+ role,
3088
+ qualified: typeNode.type === "qualified_name" || typeNode.type === "global_qualified_name",
3089
+ ...builtin !== void 0 ? {
3090
+ languageNamespace: [...VB_BUILTIN_TYPE_NAMES],
3091
+ languageNamespaceBasis: "vb-grammar-predefined-type"
3092
+ } : {}
3093
+ }
3094
+ });
3095
+ }
3096
+ // -------------------------------------------------------------------------
3097
+ // Complexity
3098
+ // -------------------------------------------------------------------------
3099
+ /**
3100
+ * Cyclomatic complexity, counted off DECISION NODES in the AST.
3101
+ *
3102
+ * The node set is the set of grammar rules that introduce a branch, and each
3103
+ * is named rather than matched, so a grammar change that adds a branching rule
3104
+ * shows up as a number that moved and a spec entry that is now wrong. No text
3105
+ * scan: counting `If` in a comment would be a fabrication.
3106
+ */
3107
+ static DECISION_NODES = /* @__PURE__ */ new Set([
3108
+ "if_statement",
3109
+ "while_statement",
3110
+ "do_statement",
3111
+ "for_statement",
3112
+ "for_each_statement",
3113
+ "case_clause",
3114
+ "catch_clause",
3115
+ "catch_block",
3116
+ "case_else_clause",
3117
+ "else_clause",
3118
+ "catch_filter_clause",
3119
+ "when_clause"
3120
+ ]);
3121
+ cyclomaticComplexity(root) {
3122
+ let n = 1;
3123
+ const visit = (node) => {
3124
+ if (_RealVbAnalyzer.DECISION_NODES.has(node.type)) n++;
3125
+ for (const c of this.kids(node)) visit(c);
3126
+ };
3127
+ visit(root);
3128
+ return n;
3129
+ }
3130
+ /** A file-level figure: nodes per 100 lines, so it is comparable across files. */
3131
+ complexity(root) {
3132
+ let count = 0;
3133
+ const visit = (node) => {
3134
+ count++;
3135
+ for (const c of this.kids(node)) visit(c);
3136
+ };
3137
+ visit(root);
3138
+ return count;
3139
+ }
3140
+ };
3141
+ var real_vb_analyzer_default = RealVbAnalyzer;
3142
+ export {
3143
+ RealVbAnalyzer,
3144
+ real_vb_analyzer_default as default
3145
+ };