@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,2596 @@
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-r-analyzer.ts
12
+ var LEFT_BINDING_OPERATORS = /* @__PURE__ */ new Set(["<-", "<<-", "="]);
13
+ var RIGHT_BINDING_OPERATORS = /* @__PURE__ */ new Set(["->", "->>"]);
14
+ var ASSIGNMENT_OPERATORS = /* @__PURE__ */ new Set([...LEFT_BINDING_OPERATORS, ...RIGHT_BINDING_OPERATORS]);
15
+ var NAMESPACE_OPERATORS = /* @__PURE__ */ new Set(["::", ":::"]);
16
+ var APPLY_FAMILY = /* @__PURE__ */ new Set([
17
+ "apply",
18
+ "sapply",
19
+ "lapply",
20
+ "vapply",
21
+ "mapply",
22
+ "rapply",
23
+ "tapply",
24
+ "Map",
25
+ "mapply",
26
+ "Reduce",
27
+ "Filter",
28
+ "Negate"
29
+ ]);
30
+ var TIDYVERSE_NSE = /* @__PURE__ */ new Set([
31
+ "mutate",
32
+ "select",
33
+ "filter",
34
+ "summarise",
35
+ "summarize",
36
+ "group_by",
37
+ "arrange",
38
+ "aes",
39
+ "vars",
40
+ "transmute",
41
+ "distinct",
42
+ "slice",
43
+ "pull",
44
+ "rename",
45
+ "relocate"
46
+ ]);
47
+ var REACTIVE_CONSTRUCTORS = /* @__PURE__ */ new Set([
48
+ "reactive",
49
+ "observe",
50
+ "observeEvent",
51
+ "eventReactive",
52
+ "reactiveVal",
53
+ "reactiveValues",
54
+ "reactiveExpr",
55
+ "reactivePoll",
56
+ "reactiveFileReader",
57
+ "bindCache",
58
+ "freezeReactiveValue"
59
+ ]);
60
+ var STATISTICAL_MODELS = /* @__PURE__ */ new Set(["lm", "glm", "summary", "glm.nb", "MASS::rlm"]);
61
+ var DATA_FRAME_CONSTRUCTORS = /* @__PURE__ */ new Set(["data.frame", "tibble", "as.data.frame", "data.table"]);
62
+ var ERROR_HANDLERS = /* @__PURE__ */ new Set(["tryCatch", "withCallingHandlers", "try", "tryCatch"]);
63
+ var CLEANUP_HANDLERS = /* @__PURE__ */ new Set(["on.exit", "onLoad", "onUnload"]);
64
+ var NSE_CAPTURERS = /* @__PURE__ */ new Set([
65
+ "substitute",
66
+ "quote",
67
+ "bquote",
68
+ "match.call",
69
+ "match.arg",
70
+ "enquo",
71
+ "enquos",
72
+ "expr",
73
+ "enexpr",
74
+ "eval",
75
+ "rlang::enquo",
76
+ "ensym",
77
+ "enname",
78
+ "quo",
79
+ "quos"
80
+ ]);
81
+ var CLASS_MEMBER_LISTS = /* @__PURE__ */ new Set(["public", "private", "active"]);
82
+ var R5_MEMBER_LISTS = /* @__PURE__ */ new Set(["fields", "methods"]);
83
+ var DATA_MEMBER_LISTS = /* @__PURE__ */ new Set(["fields"]);
84
+ var PACKAGE_NAMESPACE_SUBTYPES = /* @__PURE__ */ new Set([
85
+ "library_import",
86
+ "library_dependency",
87
+ "namespace_access"
88
+ ]);
89
+ function isPackageNameShape(name) {
90
+ if (name.length === 0) return false;
91
+ const first = name.charCodeAt(0);
92
+ const firstIsLetter = first >= 65 && first <= 90 || first >= 97 && first <= 122;
93
+ if (!firstIsLetter) return false;
94
+ if (name.length === 1) return true;
95
+ const last = name.charCodeAt(name.length - 1);
96
+ const lastIsAlnum = last >= 65 && last <= 90 || last >= 97 && last <= 122 || last >= 48 && last <= 57;
97
+ if (!lastIsAlnum) return false;
98
+ for (let i = 1; i < name.length - 1; i++) {
99
+ const c = name.charCodeAt(i);
100
+ const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 46;
101
+ if (!ok) return false;
102
+ }
103
+ return true;
104
+ }
105
+ var PACKAGE_NAMESPACE_LABELS = /* @__PURE__ */ new Set([
106
+ ASTEngine.RelationshipType.Imports,
107
+ ASTEngine.RelationshipType.References
108
+ ]);
109
+ var RealRAnalyzer = class {
110
+ /**
111
+ * UTF-16 code-unit index -> byte offset, set for the duration of an `analyze` call and
112
+ * cleared on return, so that every `getRange` call populates `byteStart`/`byteEnd`
113
+ * without each of them having to be handed the source text.
114
+ */
115
+ byteOffsetOf = null;
116
+ analyze(rootNode, content, filePath) {
117
+ const symbols = [];
118
+ const imports = [];
119
+ const exports = [];
120
+ const relationships = [];
121
+ const declaredTypes = /* @__PURE__ */ new Set();
122
+ const metadata = {
123
+ detectedFrameworks: [],
124
+ patterns: [],
125
+ architecturalInsights: [],
126
+ cyclomaticComplexity: 1,
127
+ linesOfCode: 0,
128
+ dataManipulationIntensity: 0,
129
+ statisticalFocus: 0,
130
+ codeQuality: "excellent",
131
+ architecturalScore: 100
132
+ };
133
+ if (!rootNode) return { symbols, imports, errors: [] };
134
+ const nonAscii = [];
135
+ const surplus = [];
136
+ for (let i = 0, extra = 0; i < content.length; i++) {
137
+ const c = content.charCodeAt(i);
138
+ if (c < 128) continue;
139
+ const cp = c >= 55296 && c <= 56319 ? content.codePointAt(i) : c;
140
+ const units = cp > 65535 ? 2 : 1;
141
+ const bytes = cp <= 127 ? 1 : cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
142
+ nonAscii.push(i);
143
+ surplus.push(extra);
144
+ extra += bytes - units;
145
+ if (units === 2) i++;
146
+ }
147
+ const byteOffset = (utf16Index) => {
148
+ if (nonAscii.length === 0) return utf16Index;
149
+ let lo = 0;
150
+ let hi = nonAscii.length - 1;
151
+ let lastBefore = -1;
152
+ while (lo <= hi) {
153
+ const mid = lo + hi >> 1;
154
+ if (nonAscii[mid] <= utf16Index) {
155
+ lastBefore = mid;
156
+ lo = mid + 1;
157
+ } else {
158
+ hi = mid - 1;
159
+ }
160
+ }
161
+ return lastBefore < 0 ? utf16Index : utf16Index + surplus[lastBefore];
162
+ };
163
+ this.byteOffsetOf = byteOffset;
164
+ const symbolLine = /* @__PURE__ */ new Map();
165
+ const memberLine = /* @__PURE__ */ new Map();
166
+ const classDecls = /* @__PURE__ */ new Map();
167
+ const ambiguity = {
168
+ callsUnresolvedScope: 0,
169
+ callsAttributedThroughClosure: 0,
170
+ heuristicS3Generic: 0,
171
+ // A call whose callee is not a name at all -- `(if (uplo == "U") triu else
172
+ // tril)(x)`. Counted, because the alternative is that these vanish from the totals
173
+ // and a reader cannot tell "no such calls" from "not measured".
174
+ callsToUnnameableCallee: 0,
175
+ // `uses` accounting. The three numbers the `uses` surface cannot hide behind:
176
+ // a member read already carried by `calls`, a member WRITE, and a read with no
177
+ // enclosing named symbol to attribute to. Each is counted rather than dropped,
178
+ // for the reason the counter above states.
179
+ memberReadsSuppressedByCall: 0,
180
+ memberWrites: 0,
181
+ memberReadsUnresolvedScope: 0,
182
+ memberReadsWithComputedIndex: 0,
183
+ // `overrides` accounting, for the same reason: the pass declines to assert an edge
184
+ // when no EARLIER declaration of the base name exists in the file, and the count of
185
+ // those declines is otherwise indistinguishable from "there was nothing to inherit".
186
+ overridesEdgesEmitted: 0,
187
+ overridesBasesNotDeclaredEarlierInFile: 0
188
+ };
189
+ if (rootNode.hasError) {
190
+ const errStack = [rootNode];
191
+ while (errStack.length > 0) {
192
+ const n = errStack.pop();
193
+ if (n.type === "ERROR" || n.isMissing) {
194
+ if (!metadata.errors) metadata.errors = [];
195
+ metadata.errors.push({
196
+ message: n.type === "ERROR" ? `Unrecoverable syntax region: tree-sitter-r could not parse this subtree. Extraction continued on a best-effort basis, so symbols and relationships inside this range may be missing.` : `Missing ${n.type} token: the source is syntactically incomplete at this point. Extraction continued on a best-effort basis.`,
197
+ range: this.getRange(n),
198
+ severity: "warning"
199
+ });
200
+ }
201
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
202
+ const child = n.namedChild(i);
203
+ if (child) errStack.push(child);
204
+ }
205
+ }
206
+ }
207
+ let loc = 1;
208
+ for (let i = 0; i < content.length; i++) {
209
+ if (content[i] === "\n") loc++;
210
+ }
211
+ metadata.linesOfCode = loc;
212
+ const EXIT_MARKER = /* @__PURE__ */ Symbol("r-exit");
213
+ const stack = [rootNode];
214
+ const fnStack = [];
215
+ while (stack.length > 0) {
216
+ const item = stack.pop();
217
+ if (item === EXIT_MARKER) {
218
+ fnStack.pop();
219
+ continue;
220
+ }
221
+ if (!item) continue;
222
+ const current = item;
223
+ const type = current.type;
224
+ if (type === "binary_operator") {
225
+ this.handleBinaryOperator(current, symbols, exports, relationships, metadata, declaredTypes, fnStack.length > 0);
226
+ } else if (type === "call") {
227
+ this.handleCall(current, symbols, imports, exports, relationships, metadata, declaredTypes, ambiguity, fnStack);
228
+ } else if (type === "comment") {
229
+ this.handleComment(current, metadata);
230
+ } else if (type === "function_definition") {
231
+ const fnParent = current.parent;
232
+ const isNestedWrapper = fnParent && fnParent.type === "function_definition";
233
+ const isAssignmentTarget = fnParent && fnParent.type === "binary_operator" && this.assignmentTarget(fnParent) === current;
234
+ if (!isNestedWrapper && !isAssignmentTarget) {
235
+ this.pushIfUnique(metadata.patterns, "Anonymous Functions");
236
+ }
237
+ fnStack.push({ node: current, attribution: this.resolveFunctionOwner(current) });
238
+ stack.push(EXIT_MARKER);
239
+ } else if (type === "subset" || type === "subset2") {
240
+ this.pushIfUnique(metadata.patterns, "Vectorized Subsetting");
241
+ if (type === "subset2" && this.stringSubsetIndex(current)) {
242
+ this.handleMemberRead(current, fnStack, relationships, ambiguity);
243
+ }
244
+ } else if (type === "extract_operator") {
245
+ this.handleMemberRead(current, fnStack, relationships, ambiguity);
246
+ } else if (type === "unary_operator" && current.childForFieldName("operator")?.text === "~") {
247
+ this.pushIfUnique(metadata.patterns, "Formula Interface");
248
+ if (metadata.statisticalFocus !== void 0) metadata.statisticalFocus += 10;
249
+ }
250
+ if (type === "if_statement" || type === "for_statement" || type === "while_statement" || type === "repeat_statement") {
251
+ if (metadata.cyclomaticComplexity !== void 0) {
252
+ metadata.cyclomaticComplexity++;
253
+ }
254
+ }
255
+ if (type === "if_statement" && this.isStaticallyFalseCondition(current)) {
256
+ const elseBranch = current.childForFieldName("alternative");
257
+ if (elseBranch) {
258
+ for (let i = elseBranch.childCount - 1; i >= 0; i--) {
259
+ const c = elseBranch.child(i);
260
+ if (c) stack.push(c);
261
+ }
262
+ }
263
+ continue;
264
+ }
265
+ for (let i = current.childCount - 1; i >= 0; i--) {
266
+ const child = current.child(i);
267
+ if (child) {
268
+ stack.push(child);
269
+ }
270
+ }
271
+ }
272
+ for (const s of symbols) {
273
+ if (!symbolLine.has(s.name)) symbolLine.set(s.name, this.nameLineOf(s, content));
274
+ if (s.kind !== ASTEngine.SymbolKind.Class) continue;
275
+ const line = s.range.startLine;
276
+ const bases = s.metadata?.baseClasses ?? [];
277
+ const parent = s.metadata?.parent;
278
+ const all = Array.isArray(parent) ? parent : bases;
279
+ const decls = classDecls.get(s.name) ?? [];
280
+ decls.push({ line, endLine: s.range.endLine, members: /* @__PURE__ */ new Map(), bases: all });
281
+ classDecls.set(s.name, decls);
282
+ }
283
+ for (const decls of classDecls.values()) decls.sort((a, b) => a.line - b.line);
284
+ for (const s of symbols) {
285
+ if (!s.ownerName) continue;
286
+ if (s.kind !== ASTEngine.SymbolKind.Method && s.kind !== ASTEngine.SymbolKind.Property) continue;
287
+ const line = this.nameLineOf(s, content);
288
+ const decls = classDecls.get(s.ownerName);
289
+ if (!decls?.length) continue;
290
+ let owner = decls.find((d) => line >= d.line && line <= d.endLine);
291
+ if (!owner) {
292
+ const prior = decls.filter((d) => d.line < line);
293
+ owner = prior.length ? prior[prior.length - 1] : decls[0];
294
+ }
295
+ if (!owner.members.has(s.name)) owner.members.set(s.name, line);
296
+ }
297
+ for (const e of exports) {
298
+ this.pushRel(
299
+ relationships,
300
+ "global",
301
+ e.name,
302
+ ASTEngine.RelationshipType.Exports,
303
+ 1,
304
+ { rSpecific: true, relationshipSubType: "roxygen_export", declaredKind: e.kind },
305
+ symbolLine.get(e.name) ?? null
306
+ );
307
+ }
308
+ let overridesEdges = 0;
309
+ let overridesBasesUnresolved = 0;
310
+ for (const [className, decls] of classDecls) {
311
+ for (const decl of decls) {
312
+ if (!decl.members.size || !decl.bases.length) continue;
313
+ for (const base of decl.bases) {
314
+ const prior = (classDecls.get(base) ?? []).filter((d) => d.line < decl.line);
315
+ const baseDecl = prior.length ? prior[prior.length - 1] : null;
316
+ if (!baseDecl) {
317
+ overridesBasesUnresolved++;
318
+ continue;
319
+ }
320
+ for (const member of baseDecl.members.keys()) {
321
+ if (!decl.members.has(member)) continue;
322
+ overridesEdges++;
323
+ this.pushRel(relationships, className, member, ASTEngine.RelationshipType.Overrides, 0.95, {
324
+ rSpecific: true,
325
+ relationshipSubType: "object_system_method_override",
326
+ overridingMember: `${className}$${member}`,
327
+ overriddenMember: `${base}$${member}`,
328
+ overriddenClass: base,
329
+ reasonCode: "base_class_declared_in_same_file",
330
+ reason: `\`${className}$${member}\` shadows \`${base}$${member}\`, whose class is declared earlier in this file, so the shadowing member is provably an override`
331
+ // The OVERRIDING declaration's line, not the overridden one's. Taking the
332
+ // line out of `baseDecl` instead of `decl` put `BC -overrides-> x` on AC's
333
+ // `x` at R6/tests/testthat/test-portable-inheritance.R:194, four lines
334
+ // above the `BC` that declares it.
335
+ }, decl.members.get(member));
336
+ }
337
+ }
338
+ }
339
+ }
340
+ ambiguity.overridesBasesNotDeclaredEarlierInFile = overridesBasesUnresolved;
341
+ ambiguity.overridesEdgesEmitted = overridesEdges;
342
+ this.detectFrameworksFromImports(imports, metadata);
343
+ const containsKeys = /* @__PURE__ */ new Set();
344
+ for (const rel of relationships) {
345
+ if (rel.type === ASTEngine.RelationshipType.Contains && rel.source && rel.target) {
346
+ containsKeys.add(`${rel.source}${rel.target}`);
347
+ }
348
+ }
349
+ for (const s of symbols) {
350
+ const _owner = s.ownerName || s.scope || "global";
351
+ if (_owner) {
352
+ const key = `${_owner}${s.name}`;
353
+ if (containsKeys.has(key)) continue;
354
+ containsKeys.add(key);
355
+ this.pushRel(
356
+ relationships,
357
+ _owner,
358
+ s.name,
359
+ ASTEngine.RelationshipType.Contains,
360
+ 1,
361
+ {},
362
+ symbolLine.get(s.name) ?? s.range
363
+ );
364
+ }
365
+ }
366
+ const uniqueRelationships = [];
367
+ const relationshipKeys = /* @__PURE__ */ new Set();
368
+ for (const rel of relationships) {
369
+ const key = `${rel.type || rel.relationshipType || ""}${rel.source || rel.from || ""}${rel.target || rel.to || ""}`;
370
+ if (relationshipKeys.has(key)) continue;
371
+ relationshipKeys.add(key);
372
+ uniqueRelationships.push(rel);
373
+ }
374
+ this.certifyPackageNamespaces(uniqueRelationships, symbols);
375
+ const overridesInResult = uniqueRelationships.filter(
376
+ (r) => (r.type || r.relationshipType) === ASTEngine.RelationshipType.Overrides
377
+ ).length;
378
+ const uniqueImports = [];
379
+ const importKeys = /* @__PURE__ */ new Set();
380
+ for (const imp of imports) {
381
+ const r = imp.range;
382
+ const key = `${imp.module ?? ""}${r ? `${r.startLine}:${r.startColumn}-${r.endLine}:${r.endColumn}` : ""}`;
383
+ if (importKeys.has(key)) continue;
384
+ importKeys.add(key);
385
+ uniqueImports.push(imp);
386
+ }
387
+ metadata.architecturalInsights = [
388
+ `r.ambiguity.callsUnresolvedScope=${ambiguity.callsUnresolvedScope}`,
389
+ `r.ambiguity.callsAttributedThroughClosure=${ambiguity.callsAttributedThroughClosure}`,
390
+ `r.ambiguity.heuristicS3Generic=${ambiguity.heuristicS3Generic}`
391
+ ];
392
+ metadata.callAttribution = {
393
+ unresolvedScope: ambiguity.callsUnresolvedScope,
394
+ throughClosure: ambiguity.callsAttributedThroughClosure,
395
+ heuristicS3Generic: ambiguity.heuristicS3Generic,
396
+ unnameableCallee: ambiguity.callsToUnnameableCallee ?? 0,
397
+ memberReadsSuppressedByCall: ambiguity.memberReadsSuppressedByCall,
398
+ memberWrites: ambiguity.memberWrites,
399
+ memberReadsUnresolvedScope: ambiguity.memberReadsUnresolvedScope,
400
+ memberReadsWithComputedIndex: ambiguity.memberReadsWithComputedIndex,
401
+ // Counts what the RESULT carries, after the (type, source, target) collapse — not the
402
+ // emissions the pass made. See `overridesInResult` above; the two disagree by exactly the
403
+ // number of times two bases of one class declare the same member name.
404
+ overridesEdgesEmitted: overridesInResult,
405
+ /** The pass's raw emission count, before the collapse. */
406
+ overridesEmissionsBeforeDedup: ambiguity.overridesEdgesEmitted,
407
+ /** Emissions collapsed into an edge already present: 23 over the lane's corpus, not lost. */
408
+ overridesEmissionsCollapsedByDedup: ambiguity.overridesEdgesEmitted - overridesInResult,
409
+ // Bases named by `inherit=`/`contains=`/`parent=` that no EARLIER declaration of
410
+ // that name in the same file satisfies. Counted rather than dropped so a reader can
411
+ // see how much of the inheritance graph this pass declines to assert.
412
+ overridesBasesNotDeclaredEarlierInFile: ambiguity.overridesBasesNotDeclaredEarlierInFile
413
+ };
414
+ this.byteOffsetOf = null;
415
+ return {
416
+ symbols,
417
+ imports: uniqueImports,
418
+ relationships: uniqueRelationships,
419
+ exports,
420
+ metadata,
421
+ errors: metadata.errors || []
422
+ };
423
+ }
424
+ detectFrameworksFromImports(imports, metadata) {
425
+ let hasTidyverse = false;
426
+ let hasGgplot2 = false;
427
+ let hasShiny = false;
428
+ let hasPlumber = false;
429
+ let hasDataTable = false;
430
+ let hasRcpp = false;
431
+ for (let i = 0; i < imports.length; i++) {
432
+ const module = imports[i].module;
433
+ if (module === "dplyr" || module === "ggplot2" || module === "tidyr" || module === "tidyverse")
434
+ hasTidyverse = true;
435
+ if (module === "ggplot2") hasGgplot2 = true;
436
+ if (module === "shiny") hasShiny = true;
437
+ if (module === "plumber") hasPlumber = true;
438
+ if (module === "data.table") hasDataTable = true;
439
+ if (module === "Rcpp") hasRcpp = true;
440
+ }
441
+ if (hasTidyverse) this.pushIfUnique(metadata.detectedFrameworks, "Tidyverse");
442
+ if (hasGgplot2) this.pushIfUnique(metadata.detectedFrameworks, "ggplot2");
443
+ if (hasShiny) {
444
+ this.pushIfUnique(metadata.detectedFrameworks, "Shiny");
445
+ if (!metadata.frameworkPatterns) metadata.frameworkPatterns = [];
446
+ metadata.frameworkPatterns.push({ framework: "Shiny", pattern: "ui/server" });
447
+ }
448
+ if (hasPlumber) this.pushIfUnique(metadata.detectedFrameworks, "Plumber");
449
+ if (hasDataTable) this.pushIfUnique(metadata.detectedFrameworks, "data.table");
450
+ if (hasRcpp) this.pushIfUnique(metadata.detectedFrameworks, "Rcpp");
451
+ }
452
+ pushIfUnique(arr, value) {
453
+ if (!arr) return;
454
+ let exists = false;
455
+ for (let i = 0; i < arr.length; i++) {
456
+ if (arr[i] === value) {
457
+ exists = true;
458
+ break;
459
+ }
460
+ }
461
+ if (!exists) arr.push(value);
462
+ }
463
+ // ---------------------------------------------------------------- assignments
464
+ /**
465
+ * The two SIDES of an assignment, without deciding which is a declaration.
466
+ *
467
+ * `<-`/`<<-`/`=` bind their LEFT operand. `->`/`->>` bind their RIGHT operand and
468
+ * nothing else — the left side is always a VALUE.
469
+ *
470
+ * This is not a stylistic choice, it is what R does, and it was measured
471
+ * against R 4.4.3's own runtime rather than assumed:
472
+ *
473
+ * 2 -> b OK, binds b
474
+ * 2 ->> cc OK, binds cc
475
+ * identity(1) -> gg OK, binds gg
476
+ * bb -> 2 Error: invalid (do_set) left-hand side to assignment
477
+ * cc ->> 2 Error: invalid (do_set) left-hand side to assignment
478
+ *
479
+ * R desugars both rightward forms to `<-`(value, name) at parse time, so a
480
+ * name on the LEFT of `->` is not a declaration — it is a runtime error, and
481
+ * publishing a symbol for it would invent a binding R refuses to make.
482
+ * tree-sitter-r does NOT desugar (it keeps `->` as an operator), which is
483
+ * exactly why the two sides have to be distinguished here.
484
+ *
485
+ * An earlier revision of this analyzer read whichever side happened to be an
486
+ * identifier. That published `variable e` for `e -> 6` — a construct R
487
+ * rejects. Caught by the R oracle, not by the fixtures, because the fixture
488
+ * had been written to match the same wrong belief.
489
+ */
490
+ assignmentSides(node) {
491
+ const operator = node.childForFieldName("operator")?.text;
492
+ if (operator === void 0) return { target: null, value: null };
493
+ const lhs = node.childForFieldName("lhs");
494
+ const rhs = node.childForFieldName("rhs");
495
+ if (LEFT_BINDING_OPERATORS.has(operator)) return { target: lhs ?? null, value: rhs ?? null };
496
+ if (RIGHT_BINDING_OPERATORS.has(operator)) return { target: rhs ?? null, value: lhs ?? null };
497
+ return { target: null, value: null };
498
+ }
499
+ /**
500
+ * The IDENTIFIER node an assignment binds, or null when it binds something
501
+ * other than a simple name. `->>` is handled here too; it was absent from the
502
+ * operator set entirely, so rightward superassignment produced no symbol.
503
+ */
504
+ assignmentTarget(node) {
505
+ const { target } = this.assignmentSides(node);
506
+ return target && target.type === "identifier" ? target : null;
507
+ }
508
+ handleBinaryOperator(node, symbols, exports, relationships, metadata, declaredTypes, insideFunction) {
509
+ const operator = node.childForFieldName("operator")?.text;
510
+ if (operator === void 0) return;
511
+ if (operator === "%>%" || operator === "|>") {
512
+ this.pushIfUnique(metadata.patterns, "Pipe Operations");
513
+ if (metadata.dataManipulationIntensity !== void 0) metadata.dataManipulationIntensity += 20;
514
+ }
515
+ if (!ASSIGNMENT_OPERATORS.has(operator)) return;
516
+ const { target: targetNode, value } = this.assignmentSides(node);
517
+ if (!targetNode || !value) return;
518
+ if (targetNode.type === "call") {
519
+ this.handleS7Assignment(node, targetNode, value, symbols, relationships, metadata, insideFunction);
520
+ return;
521
+ }
522
+ if (targetNode.type === "string") {
523
+ const strName = this.unquoteString(targetNode.text);
524
+ if (!strName) return;
525
+ if (insideFunction) return;
526
+ if (this.hasLocalBoundary(node)) return;
527
+ symbols.push({
528
+ name: strName,
529
+ kind: value.type === "function_definition" ? ASTEngine.SymbolKind.Function : ASTEngine.SymbolKind.Variable,
530
+ range: this.getRange(node),
531
+ docstring: this.extractDocstring(node),
532
+ metadata: { rSpecific: true, quotedAssignmentTarget: true, declarationOperator: node.childForFieldName("operator")?.text }
533
+ });
534
+ return;
535
+ }
536
+ if (targetNode.type !== "identifier") return;
537
+ const name = this.unquoteIdentifier(targetNode.text);
538
+ if (!name) return;
539
+ const doc = this.extractDocstring(node);
540
+ if (insideFunction) return;
541
+ if (this.hasLocalBoundary(node)) return;
542
+ if (value.type === "function_definition") {
543
+ const isS3Method = name.indexOf(".") > 0;
544
+ symbols.push({
545
+ name,
546
+ kind: isS3Method ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function,
547
+ range: this.getRange(node),
548
+ signature: this.extractSignature(value),
549
+ docstring: doc,
550
+ parameters: this.extractParameters(value),
551
+ metadata: { rSpecific: true, isS3Method, declarationOperator: operator }
552
+ });
553
+ if (isS3Method) {
554
+ this.pushIfUnique(metadata.patterns, "S3 Object System");
555
+ const toName = name.substring(0, name.indexOf("."));
556
+ this.pushRel(
557
+ relationships,
558
+ name,
559
+ toName,
560
+ ASTEngine.RelationshipType.Implements,
561
+ 0.8,
562
+ {
563
+ relationshipSubType: "s3_method",
564
+ reasonCode: "s3_dotted_name_convention",
565
+ reason: "R registers S3 methods by the `generic.class` naming convention; the generic itself is not declared in this file, so the target is inferred from the name."
566
+ },
567
+ node
568
+ );
569
+ }
570
+ if (doc && this.roxygenHasTag(doc, "@export"))
571
+ exports.push({ name, kind: isS3Method ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function });
572
+ return;
573
+ }
574
+ if (value.type === "call") {
575
+ const handled = this.handleClassSystemAssignment(
576
+ node,
577
+ name,
578
+ value,
579
+ symbols,
580
+ exports,
581
+ relationships,
582
+ metadata,
583
+ declaredTypes,
584
+ doc,
585
+ insideFunction
586
+ );
587
+ if (handled) return;
588
+ }
589
+ symbols.push({
590
+ name,
591
+ kind: this.isAllCapsName(name) ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Variable,
592
+ range: this.getRange(node),
593
+ docstring: doc,
594
+ metadata: { rSpecific: true, declarationOperator: operator }
595
+ });
596
+ if (doc && this.roxygenHasTag(doc, "@export"))
597
+ exports.push({ name, kind: this.isAllCapsName(name) ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Variable });
598
+ }
599
+ /**
600
+ * Declare an R5 reference class from its constructor call, for BOTH spellings.
601
+ *
602
+ * R5's `setRefClass("Name", fields=, methods=, contains=)` registers a generator under
603
+ * the DECLARED name whether or not the call is bound to a variable — reference classes
604
+ * are registered in the package's namespace at evaluation time. The corpus says so
605
+ * plainly: `r-Rcpp/R/00_classes.R` declares `C++Field`, `C++OverloadedMethods` and
606
+ * `C++Constructor` as BARE statements, and the analyzer published none of them, because
607
+ * the only path that recognised the constructor was the assignment-RHS one. The loss is
608
+ * not only three symbols: a statement-form class's `contains=` clause produced no
609
+ * `extends`/`mixes_in` edge either.
610
+ *
611
+ * `rangeNode` is the node the symbol's range is taken from: the assignment when the call
612
+ * is bound to a name (the declaration begins at the binding), the CALL itself when it is
613
+ * a statement.
614
+ *
615
+ * The class name must be a literal string in the first positional argument. An R5 class
616
+ * whose name is computed at runtime (`setRefClass(Class, ...)` in a factory) has no
617
+ * publishable identity, and naming it after a variable would fabricate a declaration —
618
+ * so those publish nothing, deliberately.
619
+ */
620
+ declareR5Class(rangeNode, name, callNode, symbols, exports, relationships, declaredTypes, doc, patterns) {
621
+ const baseClasses = this.extractNamedArguments(callNode, "contains");
622
+ const composesList = this.compositionClauseIsList(callNode, "contains");
623
+ symbols.push({
624
+ name,
625
+ kind: ASTEngine.SymbolKind.Class,
626
+ range: this.getRange(rangeNode),
627
+ docstring: doc,
628
+ metadata: { rSpecific: true, type: "R5", isR5Class: true, baseClasses }
629
+ });
630
+ declaredTypes.add(name);
631
+ if (patterns) this.pushIfUnique(patterns, "R5 Reference Classes");
632
+ for (const bc of baseClasses) {
633
+ const type = composesList ? ASTEngine.RelationshipType.MixesIn : ASTEngine.RelationshipType.Extends;
634
+ const subType = composesList ? "r5_composes" : "r5_inherits";
635
+ this.pushRel(relationships, name, bc, type, 1, { relationshipSubType: subType }, this.namedArgumentNode(callNode, "contains", bc));
636
+ }
637
+ this.extractR5Members(callNode, name, symbols, relationships, doc);
638
+ if (doc && this.roxygenHasTag(doc, "@export")) exports.push({ name, kind: ASTEngine.SymbolKind.Class });
639
+ }
640
+ /** Returns true when the call was recognized as a class-system constructor. */
641
+ handleClassSystemAssignment(node, name, callNode, symbols, exports, relationships, metadata, declaredTypes, doc, insideFunction) {
642
+ const fnNode = callNode.childForFieldName("function");
643
+ const fnName = this.calleeName(fnNode);
644
+ if (fnName === "R6Class") {
645
+ const baseClasses = this.extractNamedArguments(callNode, "inherit");
646
+ symbols.push({
647
+ name,
648
+ kind: ASTEngine.SymbolKind.Class,
649
+ range: this.getRange(node),
650
+ docstring: doc,
651
+ metadata: { rSpecific: true, type: "R6", isR6Class: true, baseClasses }
652
+ });
653
+ declaredTypes.add(name);
654
+ for (const bc of baseClasses) {
655
+ this.pushRel(relationships, name, bc, ASTEngine.RelationshipType.Extends, 1, { relationshipSubType: "r6_inherits" }, this.namedArgumentNode(callNode, "inherit", bc));
656
+ }
657
+ this.extractR6Members(callNode, name, symbols, relationships, doc);
658
+ if (doc && this.roxygenHasTag(doc, "@export")) exports.push({ name, kind: ASTEngine.SymbolKind.Class });
659
+ return true;
660
+ }
661
+ if (fnName === "setRefClass") {
662
+ this.declareR5Class(node, name, callNode, symbols, exports, relationships, declaredTypes, doc, metadata.patterns);
663
+ return true;
664
+ }
665
+ if (fnName === "moduleServer") {
666
+ symbols.push({
667
+ name,
668
+ kind: ASTEngine.SymbolKind.Function,
669
+ // Mapped as functional component
670
+ range: this.getRange(node),
671
+ docstring: doc,
672
+ metadata: { rSpecific: true, isShinyModuleServer: true, framework: "Shiny" }
673
+ });
674
+ this.pushIfUnique(metadata.patterns, "Shiny Modules");
675
+ if (doc && this.roxygenHasTag(doc, "@export")) exports.push({ name, kind: ASTEngine.SymbolKind.Function });
676
+ return true;
677
+ }
678
+ if (fnName === "cppFunction" || fnName === "cxxfunction") {
679
+ symbols.push({
680
+ name,
681
+ kind: ASTEngine.SymbolKind.Function,
682
+ range: this.getRange(node),
683
+ docstring: doc,
684
+ metadata: { rSpecific: true, isInlineCpp: true, crossLanguage: "cpp" }
685
+ });
686
+ this.pushIfUnique(metadata.patterns, "Rcpp / Inline C++");
687
+ if (doc && this.roxygenHasTag(doc, "@export")) exports.push({ name, kind: ASTEngine.SymbolKind.Function });
688
+ return true;
689
+ }
690
+ if (REACTIVE_CONSTRUCTORS.has(fnName)) {
691
+ symbols.push({
692
+ name,
693
+ kind: ASTEngine.SymbolKind.Function,
694
+ range: this.getRange(node),
695
+ docstring: doc,
696
+ metadata: { rSpecific: true, isReactive: true, reactiveType: fnName }
697
+ });
698
+ return true;
699
+ }
700
+ if (fnName === "new_class" || fnName === "new_base_class") {
701
+ const s7Args = this.getArgumentsNode(callNode);
702
+ const declaredNode = this.stringArgumentNodeAt(s7Args, 0);
703
+ const declared = declaredNode ? this.stringNodeText(declaredNode) : void 0;
704
+ const className = declared || name;
705
+ const classNameNode = declaredNode ?? node;
706
+ symbols.push({
707
+ name: className,
708
+ kind: ASTEngine.SymbolKind.Class,
709
+ range: this.getRange(node),
710
+ docstring: doc,
711
+ metadata: { rSpecific: true, type: "S7", isS7Class: true, parent: this.extractNamedArguments(callNode, "parent") }
712
+ });
713
+ declaredTypes.add(className);
714
+ this.pushIfUnique(metadata.patterns, "S7 Object System");
715
+ if (declared && declared !== name) {
716
+ symbols.push({
717
+ name,
718
+ kind: ASTEngine.SymbolKind.Variable,
719
+ range: this.getRange(node),
720
+ docstring: doc,
721
+ metadata: { rSpecific: true, isS7ClassBinding: true, boundClass: className }
722
+ });
723
+ this.pushRel(relationships, name, className, ASTEngine.RelationshipType.References, 1, { rSpecific: true, relationshipSubType: "s7_class_binding" }, classNameNode);
724
+ }
725
+ for (const parent of this.extractNamedArguments(callNode, "parent")) {
726
+ this.pushRel(relationships, className, parent, ASTEngine.RelationshipType.Extends, 1, { rSpecific: true, relationshipSubType: "s7_parent" }, this.namedArgumentNode(callNode, "parent", parent));
727
+ }
728
+ const propertyHolder = this.namedArgumentValue(callNode, "properties");
729
+ if (propertyHolder && propertyHolder.type === "call" && this.calleeName(propertyHolder.childForFieldName("function")) === "list") {
730
+ const entries = this.getArgumentsNode(propertyHolder);
731
+ if (entries) {
732
+ for (let i = 0; i < entries.namedChildCount; i++) {
733
+ const entry = entries.namedChild(i);
734
+ if (entry?.type !== "argument") continue;
735
+ const propName = entry.childForFieldName("name");
736
+ if (!propName) continue;
737
+ const prop = this.unquoteIdentifier(propName.text);
738
+ symbols.push({
739
+ name: prop,
740
+ kind: ASTEngine.SymbolKind.Property,
741
+ range: this.getRange(propName),
742
+ ownerName: className,
743
+ ownerKind: ASTEngine.SymbolKind.Class,
744
+ docstring: "",
745
+ metadata: { rSpecific: true, isS7Property: true, objectSystem: "S7" }
746
+ });
747
+ this.pushRel(relationships, className, prop, ASTEngine.RelationshipType.Contains, 1, { rSpecific: true, relationshipSubType: "s7_property" }, propName);
748
+ }
749
+ }
750
+ }
751
+ if (doc && this.roxygenHasTag(doc, "@export")) {
752
+ exports.push({ name, kind: declared && declared !== name ? ASTEngine.SymbolKind.Variable : ASTEngine.SymbolKind.Class });
753
+ }
754
+ return true;
755
+ }
756
+ if (fnName === "setGeneric" || fnName === "setMethod" || fnName === "setClass" || fnName === "setValidity") {
757
+ symbols.push({
758
+ name,
759
+ kind: ASTEngine.SymbolKind.Variable,
760
+ range: this.getRange(node),
761
+ docstring: doc,
762
+ metadata: {
763
+ rSpecific: true,
764
+ isS4Binding: true,
765
+ bindingFor: fnName,
766
+ note: "R binds the S4 generator/definition to this name; the declaration itself is emitted under its declared class/generic name."
767
+ }
768
+ });
769
+ this.pushRel(relationships, name, name, ASTEngine.RelationshipType.References, 1, { rSpecific: true, relationshipSubType: "s4_binding" }, node);
770
+ return true;
771
+ }
772
+ return false;
773
+ }
774
+ sameNode(a, b) {
775
+ if (!a || !b) return false;
776
+ return a.startIndex === b.startIndex && a.endIndex === b.endIndex;
777
+ }
778
+ /**
779
+ * Push one relationship, stamping `line_number` with the 1-based line of the
780
+ * node that NAMES the edge.
781
+ *
782
+ * M59.A7 (#825) established this convention across the analyzer family (33 of
783
+ * 44 analyzers carry it): the line is the node the edge's TARGET is written as
784
+ * — a call's callee leaf, a member's own entry, a base class's argument — not
785
+ * the construct that merely contains it. A missing line is honest, so a call
786
+ * with no node to ask omits the field rather than writing a fabricated 0.
787
+ * The R analyzer previously carried no line on any edge, so every R
788
+ * relationship was unlocatable in the graph and the LSP.
789
+ */
790
+ pushRel(out, source, target, type, confidence, metadata, namingNode) {
791
+ if (!source || !target) return;
792
+ const rel = { source, target, type, confidence, metadata };
793
+ let line;
794
+ if (typeof namingNode === "number") line = namingNode;
795
+ else if (namingNode) {
796
+ const row = namingNode.startPosition?.row;
797
+ line = typeof row === "number" ? row + 1 : namingNode.startLine;
798
+ }
799
+ if (typeof line === "number" && Number.isFinite(line) && line > 0) rel.line_number = line;
800
+ out.push(rel);
801
+ }
802
+ /**
803
+ * CERTIFY THE EDGES WHOSE TARGET IS A PACKAGE NAMESPACE — R's base and dependency packages are
804
+ * not repositories in this corpus, so no key space it can build will ever answer them.
805
+ *
806
+ * THE DEFECT THIS CLOSES. `library(cli)` and `cli::cli_abort()` reach the graph as a dependency
807
+ * on `cli`, and the arbiter reads `imports`/`references` target `cli` and finds no declaration
808
+ * of it anywhere in the project — which is TRUE, and which is what `unresolved` means. The lane
809
+ * measures this class at **660 of its 8,785-edge residual** on its own 400-file draw
810
+ * (`validation/m14/verification/R-FIX.md` §3: `imports:bare-name 330 · references:bare-name
811
+ * 330`, top targets `cli 61 · withr 38 · lifecycle 19 · utils 18 · testthat 14 · Rcpp 13`), while
812
+ * publishing NOTHING the arbiter reads. This is the fleet's finding (a) in its claim form: the
813
+ * analyzer already HOLDS the namespace — it is the edge's target — and never declares it.
814
+ *
815
+ * WHY THIS IS NOT A BLANKET EXTERNAL MARKER, and the distinction is the one that matters. The
816
+ * claim does not say "this did not resolve". It says **the target's ROOT is a language
817
+ * namespace, and here is the closed list of packages this FILE names** — decidable from R's own
818
+ * semantics, because `library(x)` LOADS a package and `x::f` is only legal when `x` IS one. It is
819
+ * the shape five other lanes already carry: hcl's `BUILTIN_ROOTS`, python's
820
+ * `PY_BUILTIN_NAMESPACE` (whose class is this class in its own words — *"Python's runtime
821
+ * namespace is not a project, so `dict`, `tuple`, `object` … can never be answered by any key
822
+ * space this corpus builds"*), zig's `["std"]`, powershell's `["System"]`, graphql's built-in
823
+ * scalars. The evidence list is the file's own dependency set, so widening it is a visible act on
824
+ * the edge rather than an opaque boolean (property 2 of `NAMESPACE_CLAIM_KEY`).
825
+ *
826
+ * FIVE GUARDS, each evidential, mirroring the python lane's `certifyLanguageNamespaceRoutes`:
827
+ * 1. the edge's SUBTYPE is one whose target is a package by construction
828
+ * (`PACKAGE_NAMESPACE_SUBTYPES`) — never inferred from the spelling alone;
829
+ * 2. the target IS in the file's declared package list — the claim's evidence;
830
+ * 3. the target is package-name-shaped (`isPackageNameShape`) as well as listed;
831
+ * 4. the file does not DECLARE the name itself — `cli <- function(...)` makes `cli` a
832
+ * declaration in this file and the claim would then be false;
833
+ * 5. the edge carries no claim already (`languageNamespace` is undefined), so an edge that
834
+ * another mechanism already explains keeps that mechanism.
835
+ *
836
+ * THE TARGET IS NEVER REWRITTEN AND NO PRE-EXISTING FIELD CHANGES: one additive metadata key.
837
+ * Every other byte of every edge this lane emitted is identical, which is what makes the
838
+ * before/after table a measurement of this claim and of nothing else.
839
+ *
840
+ * WHAT IT CANNOT DO, stated here rather than discovered later. The analyzer sees ONE FILE, so
841
+ * guard 4 covers only the file's own declarations: where the PROJECT declares a symbol that
842
+ * shares a package's name — `methods()` in S7, the `Matrix` CLASS in the Matrix package — the
843
+ * claim still wins and the edge reads `externalVerified` instead of `resolved`. That is 40 edges
844
+ * on the 400-file draw, each one a name-coincidence in ANOTHER file (gate intent:
845
+ * `arbitraryTwin`, the class this programme's brief calls the cross-file hazard), and they are
846
+ * enumerated in `namespace-claim-price.ts` and in the report rather than absorbed.
847
+ */
848
+ certifyPackageNamespaces(relationships, symbols) {
849
+ const packages = /* @__PURE__ */ new Set();
850
+ for (const rel of relationships) {
851
+ const label = String(rel.type || rel.relationshipType || "");
852
+ if (!PACKAGE_NAMESPACE_LABELS.has(label)) continue;
853
+ const md = rel.metadata ?? {};
854
+ if (!PACKAGE_NAMESPACE_SUBTYPES.has(String(md.relationshipSubType ?? ""))) continue;
855
+ const tgt = String(rel.target ?? "");
856
+ if (isPackageNameShape(tgt)) packages.add(tgt);
857
+ }
858
+ if (packages.size === 0) return 0;
859
+ const roots = [...packages].sort();
860
+ const declaredHere = /* @__PURE__ */ new Set();
861
+ for (const s of symbols) {
862
+ const name = String(s?.name ?? "");
863
+ if (name.length > 0) declaredHere.add(name);
864
+ }
865
+ let certified = 0;
866
+ for (const rel of relationships) {
867
+ const md = rel.metadata;
868
+ if (!md || typeof md !== "object") continue;
869
+ if (md.languageNamespace !== void 0) continue;
870
+ const label = String(rel.type || rel.relationshipType || "");
871
+ if (!PACKAGE_NAMESPACE_LABELS.has(label)) continue;
872
+ if (!PACKAGE_NAMESPACE_SUBTYPES.has(String(md.relationshipSubType ?? ""))) continue;
873
+ const tgt = String(rel.target ?? "");
874
+ if (!packages.has(tgt)) continue;
875
+ if (declaredHere.has(tgt)) continue;
876
+ md.languageNamespace = [...roots];
877
+ certified++;
878
+ }
879
+ return certified;
880
+ }
881
+ /**
882
+ * The bare name a call/namespace refers to, read from grammar FIELDS.
883
+ *
884
+ * identifier -> its own text
885
+ * namespace_operator -> the `rhs` field (`R6::R6Class` -> `R6Class`,
886
+ * `R6:::R6Class` -> `R6Class`, `base::sqrt` -> `sqrt`)
887
+ * extract_operator -> the `rhs` field (`obj$method` -> `method`)
888
+ *
889
+ * The previous `fnNode.text.split("::")` was both a source-text scan and
890
+ * wrong: it turned `R6:::R6Class` into `:R6Class`, and it silently missed every
891
+ * namespaced call whose `::` form differed from the bare name.
892
+ */
893
+ calleeName(fnNode) {
894
+ if (!fnNode) return "";
895
+ if (fnNode.type === "namespace_operator" || fnNode.type === "extract_operator") {
896
+ const rhs = fnNode.childForFieldName("rhs");
897
+ if (rhs) {
898
+ if (rhs.type === "identifier" || rhs.type === "dots") return this.unquoteIdentifier(rhs.text);
899
+ return this.unquoteIdentifier(rhs.text);
900
+ }
901
+ return "";
902
+ }
903
+ if (fnNode.type === "identifier") return this.unquoteIdentifier(fnNode.text);
904
+ if (this.isRName(fnNode.text)) return this.unquoteIdentifier(fnNode.text);
905
+ return "";
906
+ }
907
+ /**
908
+ * Is this text an R name? R's rule: a letter or a `.` not followed by a digit, then
909
+ * letters, digits, `.` and `_`; or a backtick-quoted name, inside which almost
910
+ * anything is legal. Letters beyond ASCII are accepted, since R source is frequently
911
+ * UTF-8 and a name is a name in any alphabet.
912
+ *
913
+ * Character codes rather than a regexp, to keep the analyzer free of pattern matching.
914
+ * This is the only place the analyzer inspects a node's TEXT to decide what it is -- the
915
+ * grammar's node types do not carry a "this is a name" flag -- and it is listed in the
916
+ * documentation's inventory of text operations.
917
+ */
918
+ isRName(text) {
919
+ if (text.length === 0) return false;
920
+ if (text.charCodeAt(0) === 96) {
921
+ return text.length >= 2 && text.charCodeAt(text.length - 1) === 96;
922
+ }
923
+ const isLetter = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 192 && c <= 591 || c >= 880 && c <= 8191;
924
+ const isDigit = (c) => c >= 48 && c <= 57;
925
+ const first = text.charCodeAt(0);
926
+ let i = 0;
927
+ if (first === 46) {
928
+ i = 1;
929
+ if (i < text.length && isDigit(text.charCodeAt(i))) return false;
930
+ } else if (!isLetter(first)) {
931
+ return false;
932
+ }
933
+ for (; i < text.length; i++) {
934
+ const c = text.charCodeAt(i);
935
+ if (isLetter(c) || isDigit(c) || c === 46 || c === 95) continue;
936
+ return false;
937
+ }
938
+ return true;
939
+ }
940
+ /**
941
+ * R identifiers may be backtick-quoted; the backticks are syntax, not part of
942
+ * the name. tree-sitter-r reports a quoted identifier as a plain `identifier`
943
+ * whose text still carries the backticks, so exactly one leading/trailing
944
+ * backtick pair is removed. Nothing else is inspected.
945
+ */
946
+ unquoteIdentifier(text) {
947
+ if (text.length >= 2 && text.charCodeAt(0) === 96 && text.charCodeAt(text.length - 1) === 96) {
948
+ return text.substring(1, text.length - 1);
949
+ }
950
+ return text;
951
+ }
952
+ /**
953
+ * S7 (rlang's object system) declarations, whose assigned name is a CALL:
954
+ *
955
+ * `Foo <- S7::new_class("Foo", parent = Bar, properties = list(...))`
956
+ * `S7::method(print, class_foo) <- function(self, ...) ...`
957
+ *
958
+ * Neither has an identifier on the left, so both were previously invisible:
959
+ * no symbol, and calls inside the body attributed to a source string that was
960
+ * never a symbol.
961
+ */
962
+ handleS7Assignment(node, targetCall, value, symbols, relationships, metadata, insideFunction) {
963
+ const base = this.calleeName(targetCall.childForFieldName("function"));
964
+ if (insideFunction) return;
965
+ if (base === "method" || base === "S7_method") {
966
+ if (value.type !== "function_definition") return;
967
+ const tArgs = this.getArgumentsNode(targetCall);
968
+ const genericNode = this.firstPositionalValue(tArgs);
969
+ const classNode = this.secondPositionalValue(tArgs);
970
+ if (!genericNode) return;
971
+ const generic = this.nodeAsName(genericNode);
972
+ let owner;
973
+ if (classNode) {
974
+ owner = this.nodeAsName(classNode) || void 0;
975
+ if (!owner && classNode.type === "call" && this.calleeName(classNode.childForFieldName("function")) === "list") {
976
+ const listArgs = this.getArgumentsNode(classNode);
977
+ for (let i = 0; listArgs && i < listArgs.namedChildCount; i++) {
978
+ const entry = listArgs.namedChild(i);
979
+ if (entry?.type !== "argument") continue;
980
+ if (entry.childForFieldName("name")) continue;
981
+ const v = this.nodeAsName(entry.childForFieldName("value"));
982
+ if (v) {
983
+ owner = v;
984
+ break;
985
+ }
986
+ }
987
+ }
988
+ }
989
+ if (!generic) return;
990
+ this.pushIfUnique(metadata.patterns, "S7 Object System");
991
+ symbols.push({
992
+ name: generic,
993
+ kind: ASTEngine.SymbolKind.Method,
994
+ range: this.getRange(node),
995
+ ownerName: owner,
996
+ ownerKind: owner ? ASTEngine.SymbolKind.Class : void 0,
997
+ signature: this.extractSignature(value),
998
+ parameters: this.extractParameters(value),
999
+ docstring: this.extractDocstring(node),
1000
+ metadata: {
1001
+ rSpecific: true,
1002
+ isS7Method: true,
1003
+ s7Generic: generic,
1004
+ s7Class: owner,
1005
+ objectSystem: "S7"
1006
+ }
1007
+ });
1008
+ if (owner) {
1009
+ this.pushRel(relationships, owner, generic, ASTEngine.RelationshipType.Contains, 1, { rSpecific: true, relationshipSubType: "s7_method" }, genericNode);
1010
+ }
1011
+ return;
1012
+ }
1013
+ if (base === "new_class" || base === "new_base_class") {
1014
+ const cArgs = this.getArgumentsNode(targetCall);
1015
+ const declared = cArgs ? this.stringArgumentAt(cArgs, 0) : void 0;
1016
+ const name = declared || (value.type === "identifier" ? this.unquoteIdentifier(value.text) : "");
1017
+ if (!name) return;
1018
+ this.pushIfUnique(metadata.patterns, "S7 Object System");
1019
+ symbols.push({
1020
+ name,
1021
+ kind: ASTEngine.SymbolKind.Class,
1022
+ range: this.getRange(node),
1023
+ docstring: this.extractDocstring(node),
1024
+ metadata: { rSpecific: true, type: "S7", isS7Class: true }
1025
+ });
1026
+ for (const parent of this.extractNamedArguments(targetCall, "parent")) {
1027
+ this.pushRel(relationships, name, parent, ASTEngine.RelationshipType.Extends, 1, { rSpecific: true, relationshipSubType: "s7_parent" }, this.namedArgumentNode(targetCall, "parent", parent));
1028
+ }
1029
+ const propertyHolder = this.namedArgumentValue(targetCall, "properties");
1030
+ if (propertyHolder && propertyHolder.type === "call" && this.calleeName(propertyHolder.childForFieldName("function")) === "list") {
1031
+ const entries = this.getArgumentsNode(propertyHolder);
1032
+ if (entries) {
1033
+ for (let i = 0; i < entries.namedChildCount; i++) {
1034
+ const entry = entries.namedChild(i);
1035
+ if (entry?.type !== "argument") continue;
1036
+ const propName = entry.childForFieldName("name");
1037
+ if (!propName) continue;
1038
+ symbols.push({
1039
+ name: this.unquoteIdentifier(propName.text),
1040
+ kind: ASTEngine.SymbolKind.Property,
1041
+ range: this.getRange(propName),
1042
+ ownerName: name,
1043
+ ownerKind: ASTEngine.SymbolKind.Class,
1044
+ docstring: "",
1045
+ metadata: { rSpecific: true, isS7Property: true, objectSystem: "S7" }
1046
+ });
1047
+ this.pushRel(relationships, name, this.unquoteIdentifier(propName.text), ASTEngine.RelationshipType.Contains, 1, { rSpecific: true, relationshipSubType: "s7_property" }, propName);
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ }
1053
+ /** An identifier or string node read as a name. */
1054
+ nodeAsName(node) {
1055
+ if (!node) return "";
1056
+ if (node.type === "string") return this.stringNodeText(node);
1057
+ if (node.type === "identifier" || node.type === "dots") return this.unquoteIdentifier(node.text);
1058
+ return "";
1059
+ }
1060
+ /** The value of a named argument of a call, or null. */
1061
+ /**
1062
+ * True when the node sits inside a `local({ ... })` body.
1063
+ *
1064
+ * `local()` is the one construct in ordinary R that evaluates an expression in a
1065
+ * NEW environment without introducing a `function_definition` node, so the
1066
+ * function-ancestor test cannot see it. Assignments inside it are NOT file-level
1067
+ * bindings: R's `local()` creates a child environment and nothing escapes.
1068
+ *
1069
+ * Found by the R oracle, not by reading: dplyr's test-across.R binds
1070
+ * `_local_var` inside `local({ ... })` and the analyzer was publishing it as a
1071
+ * global variable. A plain `{ ... }` block inside a call is deliberately NOT
1072
+ * treated as a boundary, because R evaluates that in the CALLING frame and the
1073
+ * binding really is global.
1074
+ */
1075
+ hasLocalBoundary(node) {
1076
+ let current = node;
1077
+ while (current) {
1078
+ const parent = current.parent;
1079
+ if (!parent) return false;
1080
+ if (parent.type === "braced_expression" || parent.type === "parenthesized_expression") {
1081
+ let owner = parent;
1082
+ for (let hop = 0; hop < 3 && owner; hop++) {
1083
+ const up = owner.parent;
1084
+ if (!up) break;
1085
+ if (up.type === "call") {
1086
+ const fn = up.childForFieldName("function");
1087
+ if (fn && this.calleeName(fn) === "local") return true;
1088
+ break;
1089
+ }
1090
+ owner = up;
1091
+ }
1092
+ }
1093
+ current = parent;
1094
+ }
1095
+ return false;
1096
+ }
1097
+ /**
1098
+ * The line a symbol's NAME is written on.
1099
+ *
1100
+ * A declaration's range starts where the declaration starts, which for a
1101
+ * multi-line `Cls <- R6Class(` is the line ABOVE the name, and for an S4 slot
1102
+ * is the whole `setClass` call. M59.A7's convention is the line that NAMES the
1103
+ * edge, so a `contains` edge is stamped with the name's own line, found by
1104
+ * searching the symbol's own range for its name text. Falls back to the range's
1105
+ * start when the name is synthesized (an S4 `validity` method has no literal
1106
+ * token in the source).
1107
+ */
1108
+ /**
1109
+ * Whether `at` is the start of the IDENTIFIER `name` in `segment`, rather than a place
1110
+ * where those characters merely occur.
1111
+ *
1112
+ * R identifiers continue with letters, digits, `.` and `_`, so `theme` occurs inside
1113
+ * `class_theme` and `sizes` inside `resize`. A substring search cannot tell those apart
1114
+ * from the real thing, and the consequence is a relationship pointing at a line that
1115
+ * does not name its target: `class_theme <- S7::new_class(` on line 219 matched
1116
+ * `theme`, so `global exports theme` and `global contains theme` were attributed to 219
1117
+ * while the class name is written on 220. Eight such edges in ggplot2's all-classes.R.
1118
+ *
1119
+ * A backtick-quoted name is a special case: inside backticks almost anything is legal,
1120
+ * so only the closing backtick is excluded, and the backticks themselves are not part of
1121
+ * the name.
1122
+ */
1123
+ isIdentifierOccurrence(segment, at, name) {
1124
+ const isPart = (ch) => {
1125
+ if (ch === void 0) return false;
1126
+ const c = ch.charCodeAt(0);
1127
+ return c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 46 || c === 95;
1128
+ };
1129
+ const before = at > 0 ? segment[at - 1] : void 0;
1130
+ if (before === "`") {
1131
+ return segment[at + name.length] === "`";
1132
+ }
1133
+ if (isPart(before)) return false;
1134
+ const after = segment[at + name.length];
1135
+ return !isPart(after);
1136
+ }
1137
+ nameLineOf(symbol, content) {
1138
+ const start = symbol.range.startIndex;
1139
+ const end = symbol.range.endIndex;
1140
+ if (typeof start !== "number" || typeof end !== "number" || end > content.length || end <= start) {
1141
+ return symbol.range.startLine;
1142
+ }
1143
+ const segment = content.substring(start, end);
1144
+ let at = -1;
1145
+ for (let from = 0; from <= segment.length - symbol.name.length; from++) {
1146
+ const i = segment.indexOf(symbol.name, from);
1147
+ if (i === -1) break;
1148
+ if (this.isIdentifierOccurrence(segment, i, symbol.name)) {
1149
+ at = i;
1150
+ break;
1151
+ }
1152
+ from = i;
1153
+ }
1154
+ if (at === -1) return symbol.range.startLine;
1155
+ let line = symbol.range.startLine;
1156
+ for (let i = 0; i < at; i++) if (segment.charCodeAt(i) === 10) line++;
1157
+ return line;
1158
+ }
1159
+ /**
1160
+ * The node that NAMES `wanted` inside a named argument's value, used to stamp a
1161
+ * relationship's line. Looks in `c(...)`/`list(...)` element lists first, then
1162
+ * in the argument's own value, so `inherit = c("A","B")`, `inherit = "A"` and
1163
+ * `parent = Bar` all resolve to the node that actually spells the name.
1164
+ */
1165
+ namedArgumentNode(node, argName, wanted) {
1166
+ const args = this.getArgumentsNode(node);
1167
+ if (!args) return null;
1168
+ for (let i = 0; i < args.namedChildCount; i++) {
1169
+ const arg = args.namedChild(i);
1170
+ if (arg?.type !== "argument") continue;
1171
+ if (arg.childForFieldName("name")?.text !== argName) continue;
1172
+ const value = arg.childForFieldName("value");
1173
+ if (!value) continue;
1174
+ if (value.type === "call") {
1175
+ const inner = this.getArgumentsNode(value);
1176
+ if (inner) {
1177
+ for (let j = 0; j < inner.namedChildCount; j++) {
1178
+ const entry = inner.namedChild(j);
1179
+ if (!entry) continue;
1180
+ if (entry.type === "argument") {
1181
+ const nm = entry.childForFieldName("name");
1182
+ const v = entry.childForFieldName("value");
1183
+ const text = nm?.text ?? (v ? this.baseClassNodeName(v) : "");
1184
+ if (text === wanted) return nm ?? v ?? entry;
1185
+ } else {
1186
+ if (this.baseClassNodeName(entry) === wanted) return entry;
1187
+ }
1188
+ }
1189
+ }
1190
+ if (this.baseClassNodeName(value) === wanted) return value;
1191
+ continue;
1192
+ }
1193
+ if (this.baseClassNodeName(value) === wanted) return value;
1194
+ }
1195
+ return null;
1196
+ }
1197
+ namedArgumentValue(node, argName) {
1198
+ const args = this.getArgumentsNode(node);
1199
+ if (!args) return null;
1200
+ for (let i = 0; i < args.namedChildCount; i++) {
1201
+ const arg = args.namedChild(i);
1202
+ if (arg?.type !== "argument") continue;
1203
+ if (arg.childForFieldName("name")?.text === argName) return arg.childForFieldName("value");
1204
+ }
1205
+ return null;
1206
+ }
1207
+ /** The second positional argument value of a call. */
1208
+ secondPositionalValue(args) {
1209
+ if (!args) return null;
1210
+ let seen = 0;
1211
+ for (let i = 0; i < args.namedChildCount; i++) {
1212
+ const arg = args.namedChild(i);
1213
+ if (!arg || arg.type === "comma") continue;
1214
+ if (arg.type === "argument") {
1215
+ if (arg.childForFieldName("name")) continue;
1216
+ const v = arg.childForFieldName("value");
1217
+ if (seen === 1) return v;
1218
+ seen++;
1219
+ continue;
1220
+ }
1221
+ if (seen === 1) return arg;
1222
+ seen++;
1223
+ }
1224
+ return null;
1225
+ }
1226
+ isAllCapsName(name) {
1227
+ if (!name || name.length < 2) return false;
1228
+ let hasLetter = false;
1229
+ for (let i = 0; i < name.length; i++) {
1230
+ const c = name[i];
1231
+ if (c >= "a" && c <= "z" || c >= "A" && c <= "Z") {
1232
+ hasLetter = true;
1233
+ if (c >= "a" && c <= "z") return false;
1234
+ }
1235
+ }
1236
+ return hasLetter;
1237
+ }
1238
+ // ------------------------------------------------------------ R6 / R5 members
1239
+ /**
1240
+ * R6 members. Inside `public=`/`private=`/`active=` a named entry is either a
1241
+ * METHOD (value is a `function_definition`) or a DATA MEMBER (any other value).
1242
+ * Data members are declarations too: they are the class's public surface and
1243
+ * are read as `self$name`, so omitting them hides the class API entirely.
1244
+ *
1245
+ * Only entries with a real `name` FIELD are members. The earlier
1246
+ * `childForFieldName("name") || child(0)` fallback turned an unnamed list
1247
+ * element holding a function into a Method whose "name" was the function's own
1248
+ * source text — a phantom declaration invented from syntax.
1249
+ */
1250
+ extractR6Members(callNode, className, symbols, relationships, _doc) {
1251
+ this.extractMemberLists(callNode, className, symbols, relationships, CLASS_MEMBER_LISTS, {
1252
+ methodMeta: { isR6Method: true },
1253
+ methodSubType: "r6_method",
1254
+ system: "R6"
1255
+ });
1256
+ }
1257
+ extractR5Members(callNode, className, symbols, relationships, _doc) {
1258
+ this.extractMemberLists(callNode, className, symbols, relationships, R5_MEMBER_LISTS, {
1259
+ methodMeta: { isR5Method: true },
1260
+ methodSubType: "r5_method",
1261
+ system: "R5"
1262
+ });
1263
+ }
1264
+ extractMemberLists(callNode, className, symbols, relationships, listNames, opts) {
1265
+ const argsNode = this.getArgumentsNode(callNode);
1266
+ if (!argsNode) return;
1267
+ for (let i = 0; i < argsNode.childCount; i++) {
1268
+ const arg = argsNode.child(i);
1269
+ if (!arg || arg.type !== "argument") continue;
1270
+ const argNameNode = arg.childForFieldName("name");
1271
+ if (!argNameNode) continue;
1272
+ const listName = argNameNode.text;
1273
+ if (!listNames.has(listName)) continue;
1274
+ const listNode = arg.childForFieldName("value");
1275
+ if (!listNode || listNode.type !== "call") continue;
1276
+ if (this.calleeName(listNode.childForFieldName("function")) !== "list") continue;
1277
+ const entries = this.getArgumentsNode(listNode);
1278
+ if (!entries) continue;
1279
+ for (let j = 0; j < entries.childCount; j++) {
1280
+ const entry = entries.child(j);
1281
+ if (!entry || entry.type !== "argument") continue;
1282
+ const memberNameNode = entry.childForFieldName("name");
1283
+ const memberValue = entry.childForFieldName("value");
1284
+ if (!memberNameNode || !memberValue) continue;
1285
+ const memberName = this.unquoteIdentifier(memberNameNode.text);
1286
+ if (!memberName) continue;
1287
+ const isMethod = memberValue.type === "function_definition" && !DATA_MEMBER_LISTS.has(listName);
1288
+ if (isMethod) {
1289
+ symbols.push({
1290
+ name: memberName,
1291
+ kind: ASTEngine.SymbolKind.Method,
1292
+ range: this.getDeclarationRange(memberNameNode, memberValue),
1293
+ signature: this.extractSignature(memberValue),
1294
+ parameters: this.extractParameters(memberValue),
1295
+ docstring: "",
1296
+ ownerName: className,
1297
+ ownerKind: ASTEngine.SymbolKind.Class,
1298
+ metadata: {
1299
+ rSpecific: true,
1300
+ ...opts.methodMeta,
1301
+ memberList: listName,
1302
+ ownerClass: className,
1303
+ objectSystem: opts.system
1304
+ }
1305
+ });
1306
+ } else {
1307
+ symbols.push({
1308
+ name: memberName,
1309
+ kind: ASTEngine.SymbolKind.Property,
1310
+ range: this.getDeclarationRange(memberNameNode, memberValue),
1311
+ ownerName: className,
1312
+ ownerKind: ASTEngine.SymbolKind.Class,
1313
+ docstring: "",
1314
+ metadata: {
1315
+ rSpecific: true,
1316
+ isObjectSystemField: true,
1317
+ memberList: listName,
1318
+ ownerClass: className,
1319
+ objectSystem: opts.system
1320
+ }
1321
+ });
1322
+ }
1323
+ this.pushRel(
1324
+ relationships,
1325
+ className,
1326
+ memberName,
1327
+ ASTEngine.RelationshipType.Contains,
1328
+ 1,
1329
+ {
1330
+ relationshipSubType: isMethod ? opts.methodSubType : `${opts.system.toLowerCase()}_field`,
1331
+ visibility: listName,
1332
+ memberKind: isMethod ? "method" : "field"
1333
+ },
1334
+ memberNameNode
1335
+ );
1336
+ }
1337
+ }
1338
+ }
1339
+ // ----------------------------------------------------------------- parameters
1340
+ getParametersNode(node) {
1341
+ const byField = node.childForFieldName("parameters");
1342
+ if (byField) return byField;
1343
+ for (let i = 0; i < node.childCount; i++) {
1344
+ const c = node.child(i);
1345
+ if (c && c.type === "parameters") return c;
1346
+ }
1347
+ return null;
1348
+ }
1349
+ /**
1350
+ * A parameter's default lives in the grammar's `default` FIELD. The analyzer
1351
+ * asked for `default_value`, which this grammar does not define, so the lookup
1352
+ * was always null and the analyzer fell back to scanning the parameter's raw
1353
+ * text for an `=`. Using the field removes the dead branch, removes the text
1354
+ * scan, and additionally recovers the default EXPRESSION, which was being
1355
+ * computed and then discarded.
1356
+ *
1357
+ * `...` arrives as a `parameter` whose `name` field is a `dots` node, so rest
1358
+ * parameters are recognized by node type rather than by their text.
1359
+ */
1360
+ extractParameters(node) {
1361
+ const params = [];
1362
+ const parametersNode = this.getParametersNode(node);
1363
+ if (parametersNode) {
1364
+ for (let i = 0; i < parametersNode.childCount; i++) {
1365
+ const p = parametersNode.child(i);
1366
+ if (p && p.type === "parameter") {
1367
+ const nameNode = p.childForFieldName("name");
1368
+ if (!nameNode) continue;
1369
+ const defaultValueNode = p.childForFieldName("default");
1370
+ const isRest = nameNode.type === "dots";
1371
+ const param = {
1372
+ name: this.unquoteIdentifier(nameNode.text),
1373
+ isOptional: !!defaultValueNode
1374
+ };
1375
+ if (defaultValueNode) param.defaultValue = defaultValueNode.text;
1376
+ if (isRest) param.isRest = true;
1377
+ params.push(param);
1378
+ }
1379
+ }
1380
+ }
1381
+ return params;
1382
+ }
1383
+ handleComment(node, metadata) {
1384
+ if (node.text.startsWith("#'")) {
1385
+ this.pushIfUnique(metadata.patterns, "Roxygen Documentation");
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Roxygen tags live inside a single `comment` token — the grammar exposes no
1390
+ * sub-structure for them — so a tag is recognised only at a tag position
1391
+ * (start of line, after `#'` and optional spaces). A loose substring test also
1392
+ * fired on prose such as "not @exported".
1393
+ */
1394
+ roxygenHasTag(doc, tag) {
1395
+ let lineStart = 0;
1396
+ while (lineStart <= doc.length) {
1397
+ let lineEnd = doc.indexOf("\n", lineStart);
1398
+ if (lineEnd === -1) lineEnd = doc.length;
1399
+ const line = doc.substring(lineStart, lineEnd);
1400
+ if (line.startsWith("#'")) {
1401
+ let i = 2;
1402
+ while (i < line.length && (line[i] === " " || line[i] === " ")) i++;
1403
+ if (line.startsWith(tag, i)) {
1404
+ const after = line.charAt(i + tag.length);
1405
+ if (after === "" || after === " " || after === " ") return true;
1406
+ }
1407
+ }
1408
+ lineStart = lineEnd + 1;
1409
+ }
1410
+ return false;
1411
+ }
1412
+ getArgumentsNode(node) {
1413
+ const byField = node.childForFieldName("arguments");
1414
+ if (byField) return byField;
1415
+ for (let i = 0; i < node.childCount; i++) {
1416
+ const c = node.child(i);
1417
+ if (c && c.type === "arguments") return c;
1418
+ }
1419
+ return null;
1420
+ }
1421
+ /** Positional arguments of a call, in source order, `argument` nodes unwrapped. */
1422
+ positionalArguments(args) {
1423
+ const out = [];
1424
+ for (let i = 0; i < args.namedChildCount; i++) {
1425
+ const arg = args.namedChild(i);
1426
+ if (!arg || arg.type === "comma") continue;
1427
+ if (arg.type !== "argument") {
1428
+ out.push(arg);
1429
+ continue;
1430
+ }
1431
+ out.push(arg);
1432
+ }
1433
+ return out;
1434
+ }
1435
+ /** The first POSITIONAL (unnamed) argument value of a call, or null. */
1436
+ firstPositionalValue(args) {
1437
+ if (!args) return null;
1438
+ for (let i = 0; i < args.namedChildCount; i++) {
1439
+ const arg = args.namedChild(i);
1440
+ if (!arg || arg.type === "comma") continue;
1441
+ if (arg.type === "argument") {
1442
+ if (arg.childForFieldName("name")) continue;
1443
+ return arg.childForFieldName("value");
1444
+ }
1445
+ return arg;
1446
+ }
1447
+ return null;
1448
+ }
1449
+ // ------------------------------------------------------------------ member reads
1450
+ /**
1451
+ * The `arguments` of a `subset2`, i.e. `x[[ ... ]]` -- not the same node as the
1452
+ * `arguments` of a `call`. The grammar gives the field the same name in both, so
1453
+ * reading it off the wrong node kind returns null and the count is 0 rather than
1454
+ * wrong, which is the failure mode worth avoiding here.
1455
+ */
1456
+ subset2Arguments(node) {
1457
+ if (node.type !== "subset2") return null;
1458
+ return node.childForFieldName("arguments");
1459
+ }
1460
+ /**
1461
+ * The NAME a `subset2` indexes by, when it indexes by a string literal:
1462
+ * `df[["col"]]` -> `col`. Returns null for `df[[i]]` and `df[[get_col()]]`,
1463
+ * because a computed index does not name a member and naming one anyway would be
1464
+ * the fabrication this lane's ontology workstream warns about.
1465
+ *
1466
+ * Only `[[` is read, never `[`. In R the two are not interchangeable: `df[["col"]]`
1467
+ * is a member lookup, while `df["col"]` on a plain list is a CHARACTER SUBSET that
1468
+ * evaluates to NULL, and on a data.frame it is a column selection by a rule R
1469
+ * documents separately (exact, then partial match). Publishing the string index of
1470
+ * a `[` as a member name would put a `uses` edge on an expression R does not treat
1471
+ * as a member read at all.
1472
+ */
1473
+ stringSubsetIndex(node) {
1474
+ const args = this.subset2Arguments(node);
1475
+ if (!args) return null;
1476
+ for (let i = 0; i < args.namedChildCount; i++) {
1477
+ const arg = args.namedChild(i);
1478
+ if (!arg || arg.type !== "argument") continue;
1479
+ const value = arg.childForFieldName("value");
1480
+ if (value && value.type === "string") {
1481
+ const name = this.stringNodeText(value);
1482
+ if (name) return name;
1483
+ }
1484
+ }
1485
+ return null;
1486
+ }
1487
+ /**
1488
+ * The MEMBER NAME an `extract_operator` reads, or null when the right side does
1489
+ * not name one. `obj$member` and `obj@slot` name a member by construction; a
1490
+ * computed right side (`obj[[which()]]` is a different node, but `obj$f()`-shaped
1491
+ * and interpolated forms exist) does not, so no edge is emitted and the case is
1492
+ * counted.
1493
+ */
1494
+ extractOperatorMember(node) {
1495
+ if (node.type !== "extract_operator") return null;
1496
+ const operator = node.childForFieldName("operator")?.text;
1497
+ if (operator !== "$" && operator !== "@") return null;
1498
+ const rhs = node.childForFieldName("rhs");
1499
+ if (!rhs) return null;
1500
+ if (rhs.type === "identifier") return this.unquoteIdentifier(rhs.text) || null;
1501
+ if (rhs.type === "string") return this.stringNodeText(rhs) || null;
1502
+ return null;
1503
+ }
1504
+ /**
1505
+ * Is this node the `function` field of an enclosing `call`?
1506
+ *
1507
+ * Compared by RANGE and not by object identity: web-tree-sitter allocates a fresh
1508
+ * JS wrapper on every accessor call, so `parent.childForFieldName("function") === n`
1509
+ * is false for the very node it just returned. The walk that built this gate first
1510
+ * compared identities and reported zero callee-position nodes out of 11,216 --
1511
+ * a number that reads as a fact about R and is a bug in the gate.
1512
+ *
1513
+ * `pkg::obj$fn(...)` is the wrapped form the second clause exists for: the
1514
+ * `namespace_operator` occupies the call's function field and the `extract_operator`
1515
+ * is its `rhs`.
1516
+ */
1517
+ isCalleePosition(node) {
1518
+ const parent = node.parent;
1519
+ if (!parent) return false;
1520
+ if (parent.type === "call") return this.sameNode(parent.childForFieldName("function"), node);
1521
+ if (parent.type === "extract_operator" || parent.type === "namespace_operator") {
1522
+ if (this.sameNode(parent.childForFieldName("rhs"), node)) return this.isCalleePosition(parent);
1523
+ }
1524
+ return false;
1525
+ }
1526
+ /**
1527
+ * Is this member access the TARGET of an assignment, i.e. a write?
1528
+ * `obj$field <- v` and `obj$field <<- v` store into the member; they do not read
1529
+ * it. `x <- obj$field` reads. The two sides are read off the grammar's operator
1530
+ * field through `assignmentSides`, so the rightward forms are handled by the same
1531
+ * rule that already decides which side an assignment binds -- not by a second,
1532
+ * possibly disagreeing, test.
1533
+ */
1534
+ isMemberWrite(node) {
1535
+ const parent = node.parent;
1536
+ if (!parent || parent.type !== "binary_operator") return false;
1537
+ return this.sameNode(this.assignmentSides(parent).target, node);
1538
+ }
1539
+ /**
1540
+ * A `uses` edge for one member read of a value.
1541
+ *
1542
+ * THE RULE, and why it is this rule: in R, `obj$member`, `obj@slot` and
1543
+ * `df[["col"]]` all READ a named member of something. Reading a member is not
1544
+ * calling anything, so it is not a `calls` edge, and it is not a package
1545
+ * dependency, so it is not an `imports` edge. It is the same construct the Python
1546
+ * and Julia lanes publish as `uses` (`python:1425` `isMemberRead`,
1547
+ * `script/m14/oracles/julia-refs.ts` on `a.b`), and it is by a wide margin the
1548
+ * largest thing this analyzer was not reading: measured over 1,890 real R files
1549
+ * (`validation/m14/fixtures/r/probe-surfaces.ts`), the corpus holds 9,108
1550
+ * `extract_operator` nodes of which **1,012 are the callee of a call** and
1551
+ * **8,096 are not**. Before this method all 8,096 produced nothing.
1552
+ *
1553
+ * FOUR GATES, each of which exists because the alternative is a false edge:
1554
+ *
1555
+ * 1. not the callee of a call -- `obj$method()` is already a `calls` edge whose
1556
+ * target is `obj$method`. Publishing a second edge to `method` would be the
1557
+ * same fact under two labels, which is the `uses`/`references`/`calls`
1558
+ * confusion the workstream names as question 4.
1559
+ * 2. not the target of an assignment -- `obj$field <- v` writes.
1560
+ * 3. the member must be NAMEABLE -- a computed right side yields no edge, and is
1561
+ * counted rather than dropped.
1562
+ * 4. an enclosing named symbol must exist -- the same attribution rule `calls`
1563
+ * uses, including the 0.7 closure case, so the two surfaces cannot disagree
1564
+ * about who owns a body. 55.6% of R's call sites are at file scope, so this
1565
+ * gate suppresses a large number and that number is reported in
1566
+ * `metadata.callAttribution.memberReadsUnresolvedScope` rather than lost.
1567
+ *
1568
+ * CONFIDENCE is the ATTRIBUTION's, not a constant, and that is the whole point of
1569
+ * reading it from `enclosingAttribution` rather than writing 1.0: the member name
1570
+ * is a certain parse fact, but the OWNER of a body reached through an anonymous
1571
+ * function is inferred, so the edge is 0.7 there exactly as a `calls` edge in the
1572
+ * same body is. A `uses` edge that reported 1.0 while the `calls` edge beside it
1573
+ * reported 0.7 would be a confidence that measures the label rather than the
1574
+ * evidence, and the honesty contract §4 ratio would then understate this lane's
1575
+ * guesswork by exactly the closure cases. Every edge below 1.0 carries a
1576
+ * `reasonCode` and a `reason`.
1577
+ *
1578
+ * The RECEIVER is recorded in metadata, not in the edge target, so the edge binds
1579
+ * to a symbol with the member's own name -- which is what makes it joinable with
1580
+ * the `property` symbols R6/R5/S4/S7 publish, and is the convention the Python and
1581
+ * Julia lanes follow.
1582
+ */
1583
+ handleMemberRead(node, fnStack, relationships, ambiguity) {
1584
+ const member = node.type === "extract_operator" ? this.extractOperatorMember(node) : this.stringSubsetIndex(node);
1585
+ if (!member) {
1586
+ if (node.type === "extract_operator") ambiguity.memberReadsWithComputedIndex++;
1587
+ return;
1588
+ }
1589
+ if (this.isCalleePosition(node)) {
1590
+ ambiguity.memberReadsSuppressedByCall++;
1591
+ return;
1592
+ }
1593
+ if (this.isMemberWrite(node)) {
1594
+ ambiguity.memberWrites++;
1595
+ return;
1596
+ }
1597
+ const attribution = this.enclosingAttribution(fnStack);
1598
+ if (!attribution) {
1599
+ ambiguity.memberReadsUnresolvedScope++;
1600
+ return;
1601
+ }
1602
+ const receiver = this.memberReadReceiver(node);
1603
+ const inferred = attribution.subType !== "function";
1604
+ this.pushRel(
1605
+ relationships,
1606
+ attribution.name,
1607
+ member,
1608
+ ASTEngine.RelationshipType.Uses,
1609
+ attribution.confidence,
1610
+ {
1611
+ rSpecific: true,
1612
+ relationshipSubType: "member_read",
1613
+ memberAccessOperator: node.type === "extract_operator" ? node.childForFieldName("operator")?.text : "[[",
1614
+ ...receiver ? { receiver } : {},
1615
+ ...inferred ? { attributionSubType: attribution.subType, reasonCode: attribution.reason, reason: attribution.reason } : {}
1616
+ },
1617
+ this.memberNameNode(node)
1618
+ );
1619
+ }
1620
+ /**
1621
+ * The receiver of a member read, when it is itself a plain NAME, for metadata
1622
+ * only. A receiver that is an expression (`pkg::obj$fn`, `get_obj()$field`) is
1623
+ * omitted rather than interpolated: the edge target never carries source text, and
1624
+ * a metadata field that quotes the input back is the same smell in a place where
1625
+ * nobody is looking at it.
1626
+ */
1627
+ memberReadReceiver(node) {
1628
+ const lhs = node.type === "extract_operator" ? node.childForFieldName("lhs") : node.childForFieldName("function");
1629
+ if (!lhs) return null;
1630
+ if (lhs.type === "identifier") return this.unquoteIdentifier(lhs.text) || null;
1631
+ return null;
1632
+ }
1633
+ /** The node that WRITES the member name, which is where the edge's line comes from. */
1634
+ memberNameNode(node) {
1635
+ if (node.type === "extract_operator") {
1636
+ const rhs = node.childForFieldName("rhs");
1637
+ if (rhs && (rhs.type === "identifier" || rhs.type === "string")) return rhs;
1638
+ }
1639
+ const args = this.subset2Arguments(node);
1640
+ if (args) {
1641
+ for (let i = 0; i < args.namedChildCount; i++) {
1642
+ const arg = args.namedChild(i);
1643
+ if (!arg || arg.type !== "argument") continue;
1644
+ const value = arg.childForFieldName("value");
1645
+ if (value && value.type === "string") return value;
1646
+ }
1647
+ }
1648
+ return node;
1649
+ }
1650
+ // ----------------------------------------------------------------------- call
1651
+ handleCall(node, symbols, imports, exports, relationships, metadata, declaredTypes, ambiguity, fnStack) {
1652
+ const fnNode = node.childForFieldName("function");
1653
+ if (!fnNode) return;
1654
+ const fnName = this.calleeName(fnNode);
1655
+ const args = this.getArgumentsNode(node);
1656
+ if (fnNode.type === "namespace_operator") {
1657
+ const ns = fnNode.childForFieldName("lhs");
1658
+ const op = fnNode.childForFieldName("operator")?.text;
1659
+ if (ns && op && NAMESPACE_OPERATORS.has(op)) {
1660
+ imports.push({
1661
+ module: this.unquoteIdentifier(ns.text),
1662
+ names: [],
1663
+ range: this.getRange(fnNode),
1664
+ isWildcard: true,
1665
+ metadata: { rSpecific: true, accessOperator: op }
1666
+ });
1667
+ this.pushRel(relationships, "global", this.unquoteIdentifier(ns.text), ASTEngine.RelationshipType.Imports, 1, {
1668
+ rSpecific: true,
1669
+ relationshipSubType: "namespace_access",
1670
+ accessOperator: op
1671
+ }, ns);
1672
+ this.pushRel(relationships, "global", this.unquoteIdentifier(ns.text), ASTEngine.RelationshipType.References, 1, {
1673
+ rSpecific: true,
1674
+ relationshipSubType: "namespace_access",
1675
+ accessOperator: op
1676
+ }, ns);
1677
+ }
1678
+ }
1679
+ if (fnName === "UseMethod") {
1680
+ this.pushIfUnique(metadata.patterns, "S3 Object System (Generics)");
1681
+ }
1682
+ if (APPLY_FAMILY.has(fnName)) this.pushIfUnique(metadata.patterns, "Apply Family");
1683
+ if (DATA_FRAME_CONSTRUCTORS.has(fnName)) this.pushIfUnique(metadata.patterns, "Data Frames");
1684
+ if (STATISTICAL_MODELS.has(fnName) || STATISTICAL_MODELS.has(fnNode.text)) {
1685
+ if (metadata.statisticalFocus !== void 0) metadata.statisticalFocus += 30;
1686
+ }
1687
+ if (TIDYVERSE_NSE.has(fnName)) {
1688
+ this.pushIfUnique(metadata.patterns, "Tidyverse Data Masking (NSE)");
1689
+ if (metadata.dataManipulationIntensity !== void 0) metadata.dataManipulationIntensity += 10;
1690
+ }
1691
+ if (fnName === "NS") this.pushIfUnique(metadata.patterns, "Shiny Module Namespace (NS)");
1692
+ if (ERROR_HANDLERS.has(fnName)) this.pushIfUnique(metadata.patterns, "Error Handling");
1693
+ if (CLEANUP_HANDLERS.has(fnName)) this.pushIfUnique(metadata.patterns, "Cleanup Handlers");
1694
+ if (NSE_CAPTURERS.has(fnName)) this.pushIfUnique(metadata.patterns, "Non-Standard Evaluation (NSE)");
1695
+ const attribution = this.enclosingAttribution(fnStack);
1696
+ if (attribution) {
1697
+ const relMeta = { rSpecific: true };
1698
+ if (attribution.subType !== "function") {
1699
+ relMeta.relationshipSubType = attribution.subType;
1700
+ if (attribution.reason) {
1701
+ relMeta.reasonCode = attribution.reason;
1702
+ relMeta.reason = attribution.reason;
1703
+ }
1704
+ }
1705
+ const callTarget = this.callTargetName(fnNode, fnName);
1706
+ if (!callTarget) ambiguity.callsToUnnameableCallee++;
1707
+ else this.pushRel(relationships, attribution.name, callTarget, ASTEngine.RelationshipType.Calls, attribution.confidence, relMeta, this.callTargetNode(fnNode, callTarget));
1708
+ if (attribution.confidence < 1) ambiguity.callsAttributedThroughClosure++;
1709
+ const constructorName = this.constructorTarget(fnNode);
1710
+ if (constructorName && declaredTypes.has(constructorName)) {
1711
+ this.pushRel(relationships, attribution.name, constructorName, ASTEngine.RelationshipType.Instantiates, 0.9, { isRConstructor: true, reasonCode: "declared_class_constructor" }, fnNode);
1712
+ }
1713
+ if (fnName === "new") {
1714
+ const cls = this.firstStringArgumentOf(node, 0);
1715
+ if (cls && declaredTypes.has(cls)) {
1716
+ this.pushRel(relationships, attribution.name, cls, ASTEngine.RelationshipType.Instantiates, 0.9, { isS4Constructor: true, reasonCode: "declared_class_constructor" }, fnNode);
1717
+ }
1718
+ }
1719
+ } else {
1720
+ ambiguity.callsUnresolvedScope++;
1721
+ }
1722
+ if (fnName === "library" || fnName === "require") {
1723
+ this.handleLibraryCall(node, args, imports, relationships);
1724
+ return;
1725
+ }
1726
+ if (fnName === "rm" && fnStack.length === 0) {
1727
+ const removed = this.removeTopLevelBindings(node, args, symbols, relationships, metadata);
1728
+ if (removed) {
1729
+ if (metadata.ambiguity === void 0) metadata.ambiguity = {};
1730
+ metadata.ambiguity.removedByTopLevelRm = (metadata.ambiguity.removedByTopLevelRm ?? 0) + removed;
1731
+ }
1732
+ return;
1733
+ }
1734
+ if (fnName === "sourceCpp") {
1735
+ this.pushIfUnique(metadata.patterns, "Rcpp / C++ Integration");
1736
+ this.handleSourceCall(node, args, imports, relationships, {
1737
+ relationshipSubType: "rcpp_source",
1738
+ language: "cpp",
1739
+ crossLanguage: true
1740
+ });
1741
+ return;
1742
+ }
1743
+ if (fnName === "source") {
1744
+ this.handleSourceCall(node, args, imports, relationships, { relationshipSubType: "r_source" });
1745
+ return;
1746
+ }
1747
+ if (fnName === "setClass") {
1748
+ this.pushIfUnique(metadata.patterns, "S4 Object System");
1749
+ this.handleSetClass(node, args, symbols, exports, relationships, declaredTypes);
1750
+ return;
1751
+ }
1752
+ if (fnName === "setRefClass") {
1753
+ const parent = node.parent;
1754
+ const isAssignmentValue = parent?.type === "binary_operator" && this.sameNode(this.assignmentSides(parent).value, node);
1755
+ if (!isAssignmentValue && fnStack.length === 0 && !this.hasLocalBoundary(node)) {
1756
+ const declared = this.firstStringArgumentOf(node, 0);
1757
+ if (declared) {
1758
+ this.declareR5Class(
1759
+ node,
1760
+ declared,
1761
+ node,
1762
+ symbols,
1763
+ exports,
1764
+ relationships,
1765
+ declaredTypes,
1766
+ this.extractDocstring(node),
1767
+ metadata.patterns
1768
+ );
1769
+ }
1770
+ }
1771
+ return;
1772
+ }
1773
+ if (fnName === "setGeneric") {
1774
+ this.handleSetGenericOrMethod(node, args, symbols, exports, relationships, ambiguity);
1775
+ return;
1776
+ }
1777
+ if (fnName === "setMethod") {
1778
+ this.handleSetGenericOrMethod(node, args, symbols, exports, relationships, ambiguity);
1779
+ return;
1780
+ }
1781
+ if (fnName === "setValidity") {
1782
+ this.handleSetValidity(node, args, symbols, relationships);
1783
+ return;
1784
+ }
1785
+ }
1786
+ /**
1787
+ * `library(pkg)` / `require(pkg)` declare ONE dependency: the first POSITIONAL
1788
+ * argument. Every other argument is an option — `warn.conflicts`, `quietly`,
1789
+ * `character.only`, `pos`, `lib.loc`, `mask.ok`, `exclude`, `include.only`,
1790
+ * `attach.required` — and the previous implementation turned each of those
1791
+ * option NAMES into both an import and a `references` edge, publishing phantom
1792
+ * packages. `library(dplyr, warn.conflicts = FALSE)` is the polite form and is
1793
+ * everywhere in CRAN code.
1794
+ *
1795
+ * Confidence is 1.0 for a string literal (`library("dplyr")` names the package
1796
+ * outright) and 0.8 for a bare identifier, which with `character.only = TRUE`
1797
+ * may hold a computed package name.
1798
+ */
1799
+ handleLibraryCall(node, args, imports, relationships) {
1800
+ if (!args) return;
1801
+ let value = null;
1802
+ for (let i = 0; i < args.namedChildCount && !value; i++) {
1803
+ const arg = args.namedChild(i);
1804
+ if (!arg || arg.type === "comma") continue;
1805
+ if (arg.type === "argument") {
1806
+ const nameNode = arg.childForFieldName("name");
1807
+ if (nameNode) {
1808
+ if (nameNode.text === "package") value = arg.childForFieldName("value");
1809
+ continue;
1810
+ }
1811
+ value = arg.childForFieldName("value");
1812
+ } else {
1813
+ value = arg;
1814
+ }
1815
+ }
1816
+ if (!value) return;
1817
+ let moduleName = "";
1818
+ let confidence = 0.8;
1819
+ if (value.type === "string") {
1820
+ moduleName = this.stringNodeText(value);
1821
+ confidence = 1;
1822
+ } else if (value.type === "identifier") {
1823
+ moduleName = this.unquoteIdentifier(value.text);
1824
+ }
1825
+ if (!moduleName) return;
1826
+ imports.push({
1827
+ module: moduleName,
1828
+ names: [],
1829
+ range: this.getRange(node),
1830
+ isWildcard: true,
1831
+ metadata: { rSpecific: true, declaredBy: "library" }
1832
+ });
1833
+ this.pushRel(
1834
+ relationships,
1835
+ "global",
1836
+ moduleName,
1837
+ ASTEngine.RelationshipType.References,
1838
+ confidence,
1839
+ {
1840
+ rSpecific: true,
1841
+ relationshipSubType: "library_dependency",
1842
+ reasonCode: confidence < 1 ? "library_bare_identifier" : "library_string_literal"
1843
+ },
1844
+ value
1845
+ );
1846
+ this.pushRel(relationships, "global", moduleName, ASTEngine.RelationshipType.Imports, confidence, {
1847
+ rSpecific: true,
1848
+ relationshipSubType: "library_import",
1849
+ reasonCode: confidence < 1 ? "library_bare_identifier" : "library_string_literal"
1850
+ }, value);
1851
+ }
1852
+ /**
1853
+ * `source("helpers.R")` / `sourceCpp("code.cpp")` declare a file dependency.
1854
+ * The first argument is read as the first `argument` child of the `arguments`
1855
+ * node; indexing `child(0)` instead returned the `(` token, so the guard never
1856
+ * matched and these calls produced no dependency at all while still advertising
1857
+ * the Rcpp pattern in metadata.
1858
+ */
1859
+ handleSourceCall(node, args, imports, relationships, meta) {
1860
+ const value = this.firstPositionalValue(args);
1861
+ if (!value || value.type !== "string") return;
1862
+ const path = this.stringNodeText(value);
1863
+ if (!path) return;
1864
+ imports.push({
1865
+ module: path,
1866
+ names: [],
1867
+ range: this.getRange(node),
1868
+ isWildcard: true,
1869
+ metadata: { rSpecific: true, ...meta }
1870
+ });
1871
+ this.pushRel(relationships, "global", path, ASTEngine.RelationshipType.References, 1, { ...meta, relationshipSubType: meta.relationshipSubType }, value);
1872
+ this.pushRel(relationships, "global", path, ASTEngine.RelationshipType.Imports, 1, { ...meta, relationshipSubType: meta.relationshipSubType }, value);
1873
+ }
1874
+ handleSetClass(node, args, symbols, exports, relationships, declaredTypes) {
1875
+ if (!args) return;
1876
+ const className = this.firstStringArgumentOf(node, 0);
1877
+ if (!className) return;
1878
+ const baseClasses = this.extractNamedArguments(node, "contains");
1879
+ const composesList = this.compositionClauseIsList(node, "contains");
1880
+ const doc = this.extractDocstring(node);
1881
+ symbols.push({
1882
+ name: className,
1883
+ kind: ASTEngine.SymbolKind.Class,
1884
+ range: this.getRange(node),
1885
+ docstring: doc,
1886
+ metadata: { rSpecific: true, type: "S4", isS4Class: true, baseClasses }
1887
+ });
1888
+ declaredTypes.add(className);
1889
+ const slotNames = /* @__PURE__ */ new Map();
1890
+ for (const slot of this.extractSlotKeys(node)) {
1891
+ if (!slotNames.has(slot.name)) slotNames.set(slot.name, slot);
1892
+ }
1893
+ for (const slotName of slotNames.values()) {
1894
+ symbols.push({
1895
+ name: slotName.name,
1896
+ kind: ASTEngine.SymbolKind.Property,
1897
+ range: slotName.range ?? this.getRange(node),
1898
+ ownerName: className,
1899
+ ownerKind: ASTEngine.SymbolKind.Class,
1900
+ docstring: "",
1901
+ metadata: { rSpecific: true, isS4Slot: true, declaredVia: slotName.via }
1902
+ });
1903
+ }
1904
+ for (const bc of baseClasses) {
1905
+ const type = composesList ? ASTEngine.RelationshipType.MixesIn : ASTEngine.RelationshipType.Extends;
1906
+ const subType = composesList ? "s4_composes" : "s4_inherits";
1907
+ this.pushRel(relationships, className, bc, type, 1, { relationshipSubType: subType }, this.namedArgumentNode(node, "contains", bc));
1908
+ }
1909
+ if (doc && this.roxygenHasTag(doc, "@export")) exports.push({ name: className, kind: ASTEngine.SymbolKind.Class });
1910
+ }
1911
+ handleSetGenericOrMethod(node, args, symbols, exports, relationships, ambiguity) {
1912
+ if (!args) return;
1913
+ const fnName = this.calleeName(node.childForFieldName("function"));
1914
+ const methodName = this.firstStringArgumentOf(node, 0);
1915
+ if (!methodName) return;
1916
+ let ownerName;
1917
+ if (fnName === "setMethod") {
1918
+ ownerName = this.setMethodOwner(args);
1919
+ }
1920
+ const body = this.lastPositionalValue(args);
1921
+ let parameters;
1922
+ let signature;
1923
+ if (body && body.type === "function_definition") {
1924
+ parameters = this.extractParameters(body);
1925
+ signature = this.extractSignature(body);
1926
+ }
1927
+ const doc = this.extractDocstring(node);
1928
+ symbols.push({
1929
+ name: methodName,
1930
+ kind: fnName === "setMethod" ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function,
1931
+ range: this.getRange(node),
1932
+ ownerName,
1933
+ ownerKind: ownerName ? ASTEngine.SymbolKind.Class : void 0,
1934
+ signature,
1935
+ parameters,
1936
+ docstring: doc,
1937
+ metadata: {
1938
+ rSpecific: true,
1939
+ type: fnName === "setGeneric" ? "S4_Generic" : "S4_Method",
1940
+ isS4Generic: fnName === "setGeneric",
1941
+ isS4Method: fnName === "setMethod"
1942
+ }
1943
+ });
1944
+ if (doc && this.roxygenHasTag(doc, "@export"))
1945
+ exports.push({
1946
+ name: methodName,
1947
+ kind: fnName === "setMethod" ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function
1948
+ });
1949
+ if (fnName === "setMethod" && ownerName) {
1950
+ this.pushRel(relationships, ownerName, methodName, ASTEngine.RelationshipType.Contains, 1, { rSpecific: true, relationshipSubType: "s4_method" }, node);
1951
+ }
1952
+ void ambiguity;
1953
+ }
1954
+ /**
1955
+ * `setValidity("Class", function(object) ...)` declares a class-level
1956
+ * validator. It was not handled at all, so a validation rule attached to an
1957
+ * S4 class was invisible.
1958
+ */
1959
+ handleSetValidity(node, args, symbols, relationships) {
1960
+ if (!args) return;
1961
+ const className = this.firstStringArgumentOf(node, 0);
1962
+ if (!className) return;
1963
+ const body = this.lastPositionalValue(args);
1964
+ let parameters;
1965
+ let signature;
1966
+ if (body && body.type === "function_definition") {
1967
+ parameters = this.extractParameters(body);
1968
+ signature = this.extractSignature(body);
1969
+ }
1970
+ symbols.push({
1971
+ name: "validity",
1972
+ kind: ASTEngine.SymbolKind.Method,
1973
+ range: this.getRange(node),
1974
+ ownerName: className,
1975
+ ownerKind: ASTEngine.SymbolKind.Class,
1976
+ signature,
1977
+ parameters,
1978
+ docstring: this.extractDocstring(node),
1979
+ metadata: { rSpecific: true, isS4Validity: true, ownerClass: className, objectSystem: "S4" }
1980
+ });
1981
+ this.pushRel(relationships, className, "validity", ASTEngine.RelationshipType.Contains, 1, { rSpecific: true, relationshipSubType: "s4_validity" }, node);
1982
+ }
1983
+ /** The class named by a `setMethod` second argument, string or `signature(...)`. */
1984
+ setMethodOwner(args) {
1985
+ let seen = 0;
1986
+ for (let i = 0; i < args.namedChildCount; i++) {
1987
+ const arg = args.namedChild(i);
1988
+ if (!arg || arg.type === "comma") continue;
1989
+ if (arg.type === "argument") {
1990
+ if (arg.childForFieldName("name")) continue;
1991
+ const value = arg.childForFieldName("value");
1992
+ if (!value) continue;
1993
+ if (seen === 1) {
1994
+ if (value.type === "string") return this.stringNodeText(value);
1995
+ if (value.type === "call" && this.calleeName(value.childForFieldName("function")) === "signature") {
1996
+ const inner = this.getArgumentsNode(value);
1997
+ const first = this.firstPositionalValue(inner);
1998
+ if (first && first.type === "string") return this.stringNodeText(first);
1999
+ }
2000
+ return void 0;
2001
+ }
2002
+ seen++;
2003
+ continue;
2004
+ }
2005
+ if (seen === 1) {
2006
+ if (arg.type === "string") return this.stringNodeText(arg);
2007
+ return void 0;
2008
+ }
2009
+ seen++;
2010
+ }
2011
+ return void 0;
2012
+ }
2013
+ /**
2014
+ * Attribute a call to the nearest enclosing symbol that the SYNTAX actually
2015
+ * names, and say how sure we are.
2016
+ *
2017
+ * Resolution order for each enclosing `function_definition`, innermost first:
2018
+ * 1. assigned to a name -> confidence 1.0
2019
+ * 2. a named R6/R5 list member -> confidence 1.0
2020
+ * 3. the body of a `setGeneric`/`setMethod` -> confidence 1.0
2021
+ * 4. any other closure -> attributed to the nearest
2022
+ * enclosing NAMED symbol, confidence 0.7, with a reason code; if there is
2023
+ * no enclosing symbol the call is counted unresolved rather than given an
2024
+ * invented source.
2025
+ *
2026
+ * Case 4 is the common case in idiomatic R: `lapply(x, function(v) f(v))`,
2027
+ * `tryCatch(..., error = function(e) ...)`, `shiny::moduleServer(id, function(id)
2028
+ * ...)`. The earlier implementation returned null for every one of them, so
2029
+ * idiomatic functional R contributed no call edges at all.
2030
+ */
2031
+ enclosingAttribution(fnStack) {
2032
+ for (let i = fnStack.length - 1; i >= 0; i--) {
2033
+ const resolved = fnStack[i].attribution;
2034
+ if (!resolved) continue;
2035
+ if (i === fnStack.length - 1) return resolved;
2036
+ return {
2037
+ name: resolved.name,
2038
+ confidence: 0.7,
2039
+ subType: "closure_call",
2040
+ reason: "call appears inside an anonymous function expression; attributed to the nearest enclosing named symbol"
2041
+ };
2042
+ }
2043
+ return null;
2044
+ }
2045
+ /** The symbol a `function_definition` node is the body of, if the syntax names one. */
2046
+ resolveFunctionOwner(fnDef) {
2047
+ const parent = fnDef.parent;
2048
+ if (!parent) return null;
2049
+ if (parent.type === "binary_operator") {
2050
+ const { target, value } = this.assignmentSides(parent);
2051
+ if (target?.type === "identifier" && value && this.sameNode(value, fnDef)) {
2052
+ return {
2053
+ name: this.unquoteIdentifier(target.text),
2054
+ confidence: 1,
2055
+ subType: "function",
2056
+ reason: ""
2057
+ };
2058
+ }
2059
+ if (value && this.sameNode(value, fnDef)) {
2060
+ const s7 = this.findOwningS7Method(fnDef);
2061
+ if (s7) return { name: s7, confidence: 1, subType: "s7_method", reason: "" };
2062
+ }
2063
+ return null;
2064
+ }
2065
+ if (parent.type === "argument") {
2066
+ const memberOwner = this.findOwningObjectSystem(fnDef);
2067
+ if (memberOwner) {
2068
+ return {
2069
+ name: memberOwner.methodName,
2070
+ confidence: 1,
2071
+ subType: memberOwner.subType,
2072
+ reason: ""
2073
+ };
2074
+ }
2075
+ const s4 = this.findOwningS4Declaration(fnDef);
2076
+ if (s4) {
2077
+ return { name: s4, confidence: 1, subType: "s4_method", reason: "" };
2078
+ }
2079
+ return null;
2080
+ }
2081
+ return null;
2082
+ }
2083
+ /** If this function is the body of an `S7::method(...) <-` definition, name the generic. */
2084
+ findOwningS7Method(fnDef) {
2085
+ const parent = fnDef.parent;
2086
+ if (!parent || parent.type !== "binary_operator") return null;
2087
+ const { target, value } = this.assignmentSides(parent);
2088
+ if (!value || !this.sameNode(value, fnDef)) return null;
2089
+ if (!target || target.type !== "call") return null;
2090
+ const base = this.calleeName(target.childForFieldName("function"));
2091
+ if (base !== "method" && base !== "S7_method") return null;
2092
+ const generic = this.firstPositionalValue(this.getArgumentsNode(target));
2093
+ return generic ? this.nodeAsName(generic) || null : null;
2094
+ }
2095
+ /**
2096
+ * If this function is a member of an R6Class/setRefClass list, name its class.
2097
+ *
2098
+ * The chain is: function_definition -> argument (the `go = function() ...`
2099
+ * entry) -> arguments (of the `list(...)`) -> call (`list`) -> argument
2100
+ * (`public = list(...)`) -> arguments (of `R6Class`) -> call (`R6Class`).
2101
+ * The member-list NAME is on the argument wrapping the `list` call, not on the
2102
+ * method's own argument.
2103
+ */
2104
+ findOwningObjectSystem(fnDef) {
2105
+ const methodArg = fnDef.parent;
2106
+ if (!methodArg || methodArg.type !== "argument") return null;
2107
+ const methodName = methodArg.childForFieldName("name")?.text ?? "";
2108
+ if (!methodName) return null;
2109
+ const listArgs = methodArg.parent;
2110
+ if (!listArgs || listArgs.type !== "arguments") return null;
2111
+ const listCall = listArgs.parent;
2112
+ if (!listCall || listCall.type !== "call") return null;
2113
+ if (this.calleeName(listCall.childForFieldName("function")) !== "list") return null;
2114
+ const memberArg = listCall.parent;
2115
+ if (!memberArg || memberArg.type !== "argument") return null;
2116
+ const listName = memberArg.childForFieldName("name")?.text ?? "";
2117
+ if (!CLASS_MEMBER_LISTS.has(listName) && !R5_MEMBER_LISTS.has(listName)) return null;
2118
+ const classArgs = memberArg.parent;
2119
+ if (!classArgs || classArgs.type !== "arguments") return null;
2120
+ const classCall = classArgs.parent;
2121
+ if (!classCall || classCall.type !== "call") return null;
2122
+ const base = this.calleeName(classCall.childForFieldName("function"));
2123
+ if (base !== "R6Class" && base !== "setRefClass") return null;
2124
+ return {
2125
+ className: this.assignmentTargetForCall(classCall) ?? "",
2126
+ methodName,
2127
+ subType: base === "R6Class" ? "r6_method" : "r5_method"
2128
+ };
2129
+ }
2130
+ /** If this function is the body of a setGeneric/setMethod, return the declared name. */
2131
+ findOwningS4Declaration(fnDef) {
2132
+ const parent = fnDef.parent;
2133
+ if (!parent) return null;
2134
+ let p = parent;
2135
+ let depth = 0;
2136
+ while (p && depth < 4) {
2137
+ if (p.type === "call") {
2138
+ const base = this.calleeName(p.childForFieldName("function"));
2139
+ if (base === "setGeneric" || base === "setMethod") {
2140
+ const args = this.getArgumentsNode(p);
2141
+ const first = this.firstPositionalValue(args);
2142
+ if (first && first.type === "string") return this.stringNodeText(first);
2143
+ }
2144
+ return null;
2145
+ }
2146
+ if (p.type === "function_definition") return null;
2147
+ p = p.parent;
2148
+ depth++;
2149
+ }
2150
+ return null;
2151
+ }
2152
+ /** The name a class-constructor call is assigned to, used to label R6 members. */
2153
+ assignmentTargetForCall(callNode) {
2154
+ const parent = callNode.parent;
2155
+ if (parent && parent.type === "binary_operator") {
2156
+ const target = this.assignmentTarget(parent);
2157
+ if (target) return this.unquoteIdentifier(target.text);
2158
+ }
2159
+ return null;
2160
+ }
2161
+ /**
2162
+ * The receiver of a constructor call, read from grammar FIELDS.
2163
+ * `Cls$new()` -> extract_operator(lhs=Cls, operator=$, rhs=new)
2164
+ * `pkg::Cls$new()` -> namespace_operator(rhs=extract_operator(...))
2165
+ * The previous `text.indexOf("$new")` was a source-text scan that also fired on
2166
+ * arguments and strings containing that sequence.
2167
+ */
2168
+ constructorTarget(fnNode) {
2169
+ const candidate = fnNode.type === "namespace_operator" ? fnNode.childForFieldName("rhs") : fnNode;
2170
+ if (!candidate) return null;
2171
+ if (candidate.type !== "extract_operator") return null;
2172
+ const op = candidate.childForFieldName("operator")?.text;
2173
+ if (op !== "$" && op !== "@") return null;
2174
+ const rhs = candidate.childForFieldName("rhs");
2175
+ if (!rhs || rhs.type !== "identifier" || rhs.text !== "new") return null;
2176
+ const lhs = candidate.childForFieldName("lhs");
2177
+ if (!lhs || lhs.type !== "identifier") return null;
2178
+ return this.unquoteIdentifier(lhs.text);
2179
+ }
2180
+ /**
2181
+ * The name a `calls` edge should carry: the callee as written, so a reader can
2182
+ * see `dplyr::filter`, `obj$method` and `Cls$new` rather than three different
2183
+ * functions that all end in the same word. Falls back to the bare name when the
2184
+ * function slot is not a simple callable form.
2185
+ */
2186
+ callTargetName(fnNode, bare) {
2187
+ if (!bare) return "";
2188
+ if (fnNode.type === "identifier") return bare;
2189
+ if (fnNode.type === "namespace_operator" || fnNode.type === "extract_operator") {
2190
+ const ns = fnNode.childForFieldName("lhs");
2191
+ const op = fnNode.childForFieldName("operator")?.text;
2192
+ const rhs = fnNode.childForFieldName("rhs");
2193
+ if (ns && op && rhs) {
2194
+ const receiver = this.calleeName(ns);
2195
+ if (!receiver) return bare;
2196
+ const rhsName = rhs.type === "identifier" ? this.unquoteIdentifier(rhs.text) : rhs.text;
2197
+ return `${receiver}${op}${rhsName}`;
2198
+ }
2199
+ return bare;
2200
+ }
2201
+ return bare;
2202
+ }
2203
+ /**
2204
+ * The node that WRITES the edge's target, which is the node the edge's `line_number`
2205
+ * must come from.
2206
+ *
2207
+ * The rule is: **the line where the target's LAST name component is written.** For
2208
+ * `pkg::fn` and `obj$method` on one line that is the same line either way, so the
2209
+ * common case is unaffected. It matters when R splits a chain, which the promises
2210
+ * package does routinely:
2211
+ *
2212
+ * p$value <- p$value$ <- line 743: `value` is here
2213
+ * then(function(value) { <- line 744: `then` is HERE
2214
+ *
2215
+ * The target is `value$then`, and the method name is what identifies the call and what
2216
+ * a reader searches for. Preferring the rhs puts the edge on 744. Preferring the
2217
+ * expression start put it on 743, a line that does not contain the string `then` at
2218
+ * all -- and "the line where the expression happens to begin" is not a stable property
2219
+ * of the call, it is an artifact of where the author pressed return.
2220
+ */
2221
+ callTargetNode(fnNode, _target) {
2222
+ if (fnNode.type !== "namespace_operator" && fnNode.type !== "extract_operator") return fnNode;
2223
+ const rhs = fnNode.childForFieldName("rhs");
2224
+ if (rhs && rhs.type === "identifier") return rhs;
2225
+ return fnNode;
2226
+ }
2227
+ stringArgumentAt(args, index) {
2228
+ const n = this.stringArgumentNodeAt(args, index);
2229
+ return n ? this.stringNodeText(n) : void 0;
2230
+ }
2231
+ /**
2232
+ * The Nth positional STRING argument of a call, as the NODE rather than the text.
2233
+ *
2234
+ * The node matters because an edge's `line_number` is the line of the node that NAMES
2235
+ * the target, and the name of an S7 class lives in a string that need not be on the
2236
+ * same line as the call:
2237
+ *
2238
+ * class_theme <- S7::new_class( <- line 219
2239
+ * "theme", class_S3_gg, <- line 220: this is where `theme` is named
2240
+ * ...
2241
+ *
2242
+ * Attributing the edge to the call put `class_theme references theme` on line 219, a
2243
+ * line that does not contain the word `theme` anywhere. The R-positions oracle found 6
2244
+ * such edges in ggplot2 alone. Passing the string node puts the edge on the line that
2245
+ * actually names the class, which is the line a reader needs.
2246
+ */
2247
+ stringArgumentNodeAt(args, index) {
2248
+ if (!args) return null;
2249
+ let seen = 0;
2250
+ for (let i = 0; i < args.namedChildCount; i++) {
2251
+ const arg = args.namedChild(i);
2252
+ if (!arg || arg.type === "comma") continue;
2253
+ if (arg.type === "argument") {
2254
+ if (arg.childForFieldName("name")) continue;
2255
+ const value = arg.childForFieldName("value");
2256
+ if (!value) continue;
2257
+ if (seen === index) return value.type === "string" ? value : null;
2258
+ seen++;
2259
+ continue;
2260
+ }
2261
+ if (seen === index) return arg.type === "string" ? arg : null;
2262
+ seen++;
2263
+ }
2264
+ return null;
2265
+ }
2266
+ /** The Nth positional STRING argument of a call, unquoted. */
2267
+ firstStringArgumentOf(node, index) {
2268
+ return this.stringArgumentAt(this.getArgumentsNode(node), index);
2269
+ }
2270
+ /** The last positional argument value of a call (the function body, for setGeneric/setMethod). */
2271
+ lastPositionalValue(args) {
2272
+ if (!args) return null;
2273
+ let last = null;
2274
+ for (let i = 0; i < args.namedChildCount; i++) {
2275
+ const arg = args.namedChild(i);
2276
+ if (!arg || arg.type === "comma") continue;
2277
+ if (arg.type === "argument") {
2278
+ if (arg.childForFieldName("name")) continue;
2279
+ last = arg.childForFieldName("value");
2280
+ } else {
2281
+ last = arg;
2282
+ }
2283
+ }
2284
+ return last;
2285
+ }
2286
+ /** The text of a `string` node: the grammar's `content` field, else the raw token. */
2287
+ stringNodeText(value) {
2288
+ const content = value.childForFieldName("content");
2289
+ if (content) return content.text;
2290
+ return this.unquoteString(value.text);
2291
+ }
2292
+ /**
2293
+ * Slot names of an S4 class, each with the RANGE OF ITS OWN NAME NODE so a
2294
+ * slot's symbol points at the slot rather than at the whole `setClass` call.
2295
+ *
2296
+ * Two shapes declare slots and both are in real use:
2297
+ * slots = c(a = "numeric") the current idiom
2298
+ * representation(a = "numeric") the original S4 idiom, still written by
2299
+ * R Core itself in Matrix
2300
+ * Reading only the first left every `representation()` class with no members.
2301
+ */
2302
+ extractSlotKeys(node) {
2303
+ const results = [];
2304
+ const args = this.getArgumentsNode(node);
2305
+ if (!args) return results;
2306
+ for (let i = 0; i < args.namedChildCount; i++) {
2307
+ const arg = args.namedChild(i);
2308
+ if (arg?.type !== "argument") continue;
2309
+ const nameNode = arg.childForFieldName("name");
2310
+ const value = arg.childForFieldName("value");
2311
+ if (!value) continue;
2312
+ const argName = nameNode?.text;
2313
+ const callee = value.type === "call" ? this.calleeName(value.childForFieldName("function")) : "";
2314
+ if (argName === "slots" && callee === "c") {
2315
+ for (const slot of this.namedArgumentNameKeys(value, "slots")) results.push(slot);
2316
+ continue;
2317
+ }
2318
+ if ((argName === "representation" || callee === "representation") && value.type === "call") {
2319
+ for (const slot of this.namedArgumentNameKeys(value, "representation")) results.push(slot);
2320
+ }
2321
+ }
2322
+ return results;
2323
+ }
2324
+ /** The names (with ranges) of the named arguments of a call. */
2325
+ namedArgumentNameKeys(callNode, via) {
2326
+ const results = [];
2327
+ const args = this.getArgumentsNode(callNode);
2328
+ if (!args) return results;
2329
+ for (let i = 0; i < args.namedChildCount; i++) {
2330
+ const entry = args.namedChild(i);
2331
+ if (entry?.type !== "argument") continue;
2332
+ const entryName = entry.childForFieldName("name");
2333
+ if (entryName?.text) {
2334
+ const name = this.unquoteString(this.unquoteIdentifier(entryName.text));
2335
+ results.push({ name, range: this.getRange(entryName), via });
2336
+ }
2337
+ }
2338
+ return results;
2339
+ }
2340
+ unquoteString(text) {
2341
+ const trimmed = text.trim();
2342
+ if (trimmed.length >= 2) {
2343
+ const first = trimmed[0];
2344
+ const last = trimmed[trimmed.length - 1];
2345
+ if ((first === '"' || first === "'") && first === last) return trimmed.substring(1, trimmed.length - 1);
2346
+ }
2347
+ return trimmed;
2348
+ }
2349
+ extractSignature(node) {
2350
+ const parameters = this.getParametersNode(node);
2351
+ return parameters ? parameters.text : void 0;
2352
+ }
2353
+ extractDocstring(node) {
2354
+ let prev = node.previousSibling;
2355
+ const docsStack = [];
2356
+ while (prev) {
2357
+ if (prev.type === "comment") {
2358
+ docsStack.push(prev.text);
2359
+ prev = prev.previousSibling;
2360
+ } else if (prev.type === "return") {
2361
+ prev = prev.previousSibling;
2362
+ } else {
2363
+ break;
2364
+ }
2365
+ }
2366
+ if (docsStack.length === 0) return void 0;
2367
+ let combined = "";
2368
+ for (let i = docsStack.length - 1; i >= 0; i--) {
2369
+ combined += docsStack[i];
2370
+ if (i > 0) combined += "\n";
2371
+ }
2372
+ return combined;
2373
+ }
2374
+ extractNamedArguments(node, argName) {
2375
+ const results = [];
2376
+ const args = this.getArgumentsNode(node);
2377
+ if (!args) return results;
2378
+ for (let i = 0; i < args.namedChildCount; i++) {
2379
+ const arg = args.namedChild(i);
2380
+ if (!arg || arg.type !== "argument") continue;
2381
+ const nameNode = arg.childForFieldName("name");
2382
+ if (!nameNode || nameNode.text !== argName) continue;
2383
+ const valNode = arg.childForFieldName("value");
2384
+ if (!valNode) continue;
2385
+ if (valNode.type === "call" && this.calleeName(valNode.childForFieldName("function")) === "c") {
2386
+ const cArgs = this.getArgumentsNode(valNode);
2387
+ if (cArgs) {
2388
+ for (let j = 0; j < cArgs.namedChildCount; j++) {
2389
+ const ca = cArgs.namedChild(j);
2390
+ if (!ca || ca.type === "comma") continue;
2391
+ if (ca.type === "argument") {
2392
+ const v = ca.childForFieldName("value");
2393
+ const text = v ? this.baseClassNodeName(v) : "";
2394
+ if (text) results.push(text);
2395
+ } else {
2396
+ const text = this.baseClassNodeName(ca);
2397
+ if (text) results.push(text);
2398
+ }
2399
+ }
2400
+ }
2401
+ } else {
2402
+ const text = this.baseClassNodeName(valNode);
2403
+ if (text) results.push(text);
2404
+ }
2405
+ }
2406
+ return results;
2407
+ }
2408
+ /**
2409
+ * Whether a class-construction argument is a LIST-shaped composition clause.
2410
+ *
2411
+ * `setClass(contains=)` and `setRefClass(contains=)` are R's composition clauses, and they
2412
+ * come in two shapes that are not the same construct:
2413
+ *
2414
+ * contains = "Base" ONE class — a single supertype; R's own `is()` and
2415
+ * `extends()` answer TRUE for it, so the relation is a
2416
+ * conformance edge (`extends`).
2417
+ * contains = c("A", "B") SEVERAL classes — a member-set clause with no single
2418
+ * base. This is the shape the fleet's `mixes_in` names
2419
+ * ("no single base and the `with` list is not an
2420
+ * inheritance clause", `src/ast/types.ts:156-160`), the
2421
+ * same reading the Scala `extends A with B, C` clause
2422
+ * takes for its later positions.
2423
+ *
2424
+ * The count is taken from the VALUE node, not from the names that resolve: `c("A", pkg)` has
2425
+ * two entries even though only one is nameable, and the clause is a list either way. `comma`
2426
+ * is a named node type in this grammar, so the entries alternate argument/comma; commas are
2427
+ * skipped or every two-element list would read as four.
2428
+ *
2429
+ * A single-base `contains=` is deliberately NOT relabelled: R says it is conformance, and
2430
+ * moving it to `mixes_in` would publish a false edge. The conformance fact is carried as a
2431
+ * stated caveat on the list-shaped branch rather than dropped — R's `contains=` also makes
2432
+ * `is()` TRUE for the composed classes, and the fleet's label is the composition-clause
2433
+ * reading, not a claim that no conformance exists.
2434
+ */
2435
+ compositionClauseIsList(node, argName) {
2436
+ const args = this.getArgumentsNode(node);
2437
+ if (!args) return false;
2438
+ for (let i = 0; i < args.namedChildCount; i++) {
2439
+ const arg = args.namedChild(i);
2440
+ if (!arg || arg.type !== "argument") continue;
2441
+ if (arg.childForFieldName("name")?.text !== argName) continue;
2442
+ const value = arg.childForFieldName("value");
2443
+ if (!value || value.type !== "call") return false;
2444
+ const callee = this.calleeName(value.childForFieldName("function"));
2445
+ if (callee !== "c" && callee !== "list") return false;
2446
+ const entries = this.getArgumentsNode(value);
2447
+ if (!entries) return false;
2448
+ let count = 0;
2449
+ for (let j = 0; j < entries.namedChildCount; j++) {
2450
+ const entry = entries.namedChild(j);
2451
+ if (!entry || entry.type === "comma") continue;
2452
+ if (entry.type === "argument" && !entry.childForFieldName("value")) continue;
2453
+ count++;
2454
+ }
2455
+ return count > 1;
2456
+ }
2457
+ return false;
2458
+ }
2459
+ /**
2460
+ * The class name a base-class ARGUMENT node carries, or "".
2461
+ *
2462
+ * R writes a base class three ways and this reads all three:
2463
+ *
2464
+ * inherit = Parent identifier -> Parent
2465
+ * contains = "Parent" string -> Parent
2466
+ * inherit = pkg::Parent namespace_operator
2467
+ *
2468
+ * The third was dropped. R6 documents it — `inherit` may name a class in another package,
2469
+ * and R6's own test suite uses it: `R6/tests/manual/test-inheritance.R:126` writes
2470
+ * `BC <- R6Class(inherit = R6testA::AC, ...)`, where `AC` is declared 9 lines above in the
2471
+ * same file inside the `R6testA = {...}` argument. A census of every class-constructor call
2472
+ * in the corpus (738 of them across 2,342 R files: `new_class` 283, `R6Class` 229,
2473
+ * `setClass` 201, `new_base_class` 12, `setRefClass` 9) found exactly ONE qualified base
2474
+ * class, so this is a 1-edge gap and is recorded as one rather than dressed up. It is a
2475
+ * deterministic parse fact, so the edge it produces is a fact and not a heuristic.
2476
+ *
2477
+ * The qualifier is dropped and the BARE name kept: `pkg::Parent` names the class `Parent`,
2478
+ * which is what `class(x)` returns and what dispatch keys on, and a symbol named
2479
+ * `pkg::Parent` binds to nothing in any file.
2480
+ */
2481
+ baseClassNodeName(node) {
2482
+ if (!node) return "";
2483
+ if (node.type === "string") return this.stringNodeText(node);
2484
+ if (node.type === "identifier") return this.unquoteIdentifier(node.text);
2485
+ if (node.type === "namespace_operator") {
2486
+ const rhs = node.childForFieldName("rhs");
2487
+ return rhs ? this.unquoteIdentifier(rhs.text) : "";
2488
+ }
2489
+ return "";
2490
+ }
2491
+ /**
2492
+ * The range of a member DECLARATION: from the declared name to the end of its value.
2493
+ *
2494
+ * A member is written `name = value`, and a range taken from `value` alone starts at
2495
+ * the value. For an R6 or R5 method that means the range begins at the `function`
2496
+ * keyword and so EXCLUDES the name the symbol is published under -- a range that does
2497
+ * not contain the thing it names. The R-positions oracle found 833 of these across the
2498
+ * pinned corpora (dplyr, shiny, testthat), every one a method, all of them off by the
2499
+ * length of `name = `.
2500
+ *
2501
+ * Both nodes are required, so a member with no name is not representable here and the
2502
+ * caller is forced to decide what it means rather than being handed a range that
2503
+ * silently omits the name.
2504
+ */
2505
+ getDeclarationRange(nameNode, valueNode) {
2506
+ const r = {
2507
+ startLine: nameNode.startPosition.row + 1,
2508
+ startColumn: nameNode.startPosition.column + 1,
2509
+ endLine: valueNode.endPosition.row + 1,
2510
+ endColumn: valueNode.endPosition.column + 1,
2511
+ startIndex: nameNode.startIndex,
2512
+ endIndex: valueNode.endIndex
2513
+ };
2514
+ const toBytes = this.byteOffsetOf;
2515
+ if (toBytes) {
2516
+ r.byteStart = toBytes(nameNode.startIndex);
2517
+ r.byteEnd = toBytes(valueNode.endIndex);
2518
+ }
2519
+ return r;
2520
+ }
2521
+ /**
2522
+ * The range of a node.
2523
+ *
2524
+ * `startIndex`/`endIndex` are whatever the parser reported -- UTF-16 code units, for
2525
+ * web-tree-sitter -- and are passed through unchanged so nothing that already reads them
2526
+ * moves. `byteStart`/`byteEnd` are the same positions in BYTES, added alongside, which is
2527
+ * what a consumer slicing a Buffer needs. `byteOffset` is supplied by `analyze`, which is
2528
+ * the only place that has the source text; a node range built outside an analyze call
2529
+ * simply omits the byte fields rather than reporting a wrong one.
2530
+ */
2531
+ /**
2532
+ * Is this `if` condition the literal FALSE, so the `then` branch never executes?
2533
+ *
2534
+ * Only the literals. A variable holding FALSE is not statically knowable, and R does not
2535
+ * constant-fold, so guessing there would drop real declarations.
2536
+ */
2537
+ /**
2538
+ * Remove the symbols a top-level `rm(...)` unbinds, and any relationship that named one
2539
+ * as its source. Returns how many were removed, so the removal is countable rather than
2540
+ * silent.
2541
+ */
2542
+ removeTopLevelBindings(node, args, symbols, relationships, metadata) {
2543
+ if (!args) return 0;
2544
+ const doomed = /* @__PURE__ */ new Set();
2545
+ for (let i = 0; i < args.namedChildCount; i++) {
2546
+ const arg = args.namedChild(i);
2547
+ if (!arg) continue;
2548
+ if (arg.childForFieldName("name")) continue;
2549
+ const v = arg.childForFieldName("value") ?? (arg.type === "identifier" ? arg : null);
2550
+ if (!v) continue;
2551
+ const nm = v.type === "identifier" ? this.unquoteIdentifier(v.text) : v.type === "string" ? this.unquoteString(v.text) : null;
2552
+ if (nm) doomed.add(nm);
2553
+ }
2554
+ if (doomed.size === 0) return 0;
2555
+ const before = symbols.length;
2556
+ for (let i = symbols.length - 1; i >= 0; i--) {
2557
+ const s = symbols[i];
2558
+ if (s.ownerName || !doomed.has(s.name)) continue;
2559
+ symbols.splice(i, 1);
2560
+ }
2561
+ for (let i = relationships.length - 1; i >= 0; i--) {
2562
+ const r = relationships[i];
2563
+ if (doomed.has(String(r.source))) relationships.splice(i, 1);
2564
+ }
2565
+ void node;
2566
+ void metadata;
2567
+ return before - symbols.length;
2568
+ }
2569
+ isStaticallyFalseCondition(ifNode) {
2570
+ const cond = ifNode.childForFieldName("condition");
2571
+ if (!cond) return false;
2572
+ if (cond.type === "false") return true;
2573
+ if (cond.type !== "identifier") return false;
2574
+ const t = this.unquoteIdentifier(cond.text);
2575
+ return t === "F" || t === "FALSE" || t === "false";
2576
+ }
2577
+ getRange(node, byteOffset) {
2578
+ const toBytes = byteOffset ?? this.byteOffsetOf;
2579
+ const r = {
2580
+ startLine: node.startPosition.row + 1,
2581
+ startColumn: node.startPosition.column + 1,
2582
+ endLine: node.endPosition.row + 1,
2583
+ endColumn: node.endPosition.column + 1,
2584
+ startIndex: node.startIndex,
2585
+ endIndex: node.endIndex
2586
+ };
2587
+ if (toBytes) {
2588
+ r.byteStart = toBytes(node.startIndex);
2589
+ r.byteEnd = toBytes(node.endIndex);
2590
+ }
2591
+ return r;
2592
+ }
2593
+ };
2594
+ export {
2595
+ RealRAnalyzer
2596
+ };