@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,1865 @@
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-perl-analyzer.ts
12
+ var PERL_MAGIC_TOKENS = /* @__PURE__ */ new Set(["__PACKAGE__", "__FILE__", "__LINE__", "__SUB__", "__DATA__", "__END__"]);
13
+ var isPerlIdentStart = (c) => c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
14
+ var isPerlIdentChar = (c) => isPerlIdentStart(c) || c >= "0" && c <= "9";
15
+ var PERL_BUILTINS = /* @__PURE__ */ new Set([
16
+ "abs",
17
+ "accept",
18
+ "alarm",
19
+ "atan2",
20
+ "bind",
21
+ "binmode",
22
+ "bless",
23
+ "break",
24
+ "caller",
25
+ "catch",
26
+ "chdir",
27
+ "chmod",
28
+ "chomp",
29
+ "chop",
30
+ "chown",
31
+ "chr",
32
+ "chroot",
33
+ "close",
34
+ "closedir",
35
+ "cmp",
36
+ "connect",
37
+ "continue",
38
+ "cos",
39
+ "crypt",
40
+ "dbmclose",
41
+ "dbmopen",
42
+ "defined",
43
+ "delete",
44
+ "die",
45
+ "do",
46
+ "dump",
47
+ "each",
48
+ "eof",
49
+ "eval",
50
+ "exec",
51
+ "exists",
52
+ "exit",
53
+ "exp",
54
+ "fc",
55
+ "fcntl",
56
+ "fileno",
57
+ "flock",
58
+ "fork",
59
+ "format",
60
+ "formline",
61
+ "getc",
62
+ "getgrent",
63
+ "getgrgid",
64
+ "getgrnam",
65
+ "gethostbyaddr",
66
+ "gethostbyname",
67
+ "getlogin",
68
+ "getnetbyaddr",
69
+ "getnetbyname",
70
+ "getpeername",
71
+ "getpgrp",
72
+ "getppid",
73
+ "getpriority",
74
+ "getprotobyname",
75
+ "getprotobynumber",
76
+ "getpwnam",
77
+ "getpwuid",
78
+ "getservbyname",
79
+ "getservbyport",
80
+ "getsockname",
81
+ "getsockopt",
82
+ "glob",
83
+ "gmtime",
84
+ "goto",
85
+ "grep",
86
+ "hex",
87
+ "if",
88
+ "index",
89
+ "int",
90
+ "ioctl",
91
+ "join",
92
+ "keys",
93
+ "kill",
94
+ "last",
95
+ "lc",
96
+ "lcfirst",
97
+ "length",
98
+ "link",
99
+ "listen",
100
+ "local",
101
+ "localtime",
102
+ "lock",
103
+ "log",
104
+ "lstat",
105
+ "map",
106
+ "mkdir",
107
+ "msgctl",
108
+ "msgget",
109
+ "msgrcv",
110
+ "msgsnd",
111
+ "my",
112
+ "next",
113
+ "oct",
114
+ "open",
115
+ "opendir",
116
+ "ord",
117
+ "pack",
118
+ "pipe",
119
+ "pop",
120
+ "pos",
121
+ "print",
122
+ "printf",
123
+ "prototype",
124
+ "push",
125
+ "quotemeta",
126
+ "rand",
127
+ "read",
128
+ "readdir",
129
+ "readline",
130
+ "readlink",
131
+ "readpipe",
132
+ "recv",
133
+ "redo",
134
+ "ref",
135
+ "rename",
136
+ "require",
137
+ "reset",
138
+ "return",
139
+ "reverse",
140
+ "rewinddir",
141
+ "rindex",
142
+ "rmdir",
143
+ "say",
144
+ "scalar",
145
+ "seek",
146
+ "seekdir",
147
+ "select",
148
+ "semctl",
149
+ "semget",
150
+ "semop",
151
+ "send",
152
+ "setpgrp",
153
+ "setpriority",
154
+ "setsockopt",
155
+ "shift",
156
+ "shmctl",
157
+ "shmget",
158
+ "shmread",
159
+ "shmwrite",
160
+ "shutdown",
161
+ "sin",
162
+ "sleep",
163
+ "socket",
164
+ "socketpair",
165
+ "sort",
166
+ "splice",
167
+ "split",
168
+ "sprintf",
169
+ "sqrt",
170
+ "srand",
171
+ "stat",
172
+ "state",
173
+ "study",
174
+ "substr",
175
+ "symlink",
176
+ "syscall",
177
+ "sysopen",
178
+ "sysread",
179
+ "sysseek",
180
+ "system",
181
+ "syswrite",
182
+ "tell",
183
+ "telldir",
184
+ "tie",
185
+ "tied",
186
+ "time",
187
+ "times",
188
+ "truncate",
189
+ "uc",
190
+ "ucfirst",
191
+ "umask",
192
+ "undef",
193
+ "unlink",
194
+ "unpack",
195
+ "unshift",
196
+ "untie",
197
+ "until",
198
+ "use",
199
+ "utime",
200
+ "values",
201
+ "vec",
202
+ "wait",
203
+ "waitpid",
204
+ "wantarray",
205
+ "warn",
206
+ "while",
207
+ "write",
208
+ "xor",
209
+ "our"
210
+ ]);
211
+ var PERL_REASON_CODES = {
212
+ nameOnlyCallee: "name-only-callee",
213
+ dynamicReceiver: "dynamic-receiver",
214
+ concreteInvocant: "concrete-invocant",
215
+ useParentDeclaration: "use-parent-declaration",
216
+ isaArray: "isa-array",
217
+ mojoBaseDeclaration: "mojo-base-declaration",
218
+ mooRoleComposition: "moo-role-composition",
219
+ mooExtendsKeyword: "moo-extends-keyword",
220
+ indirectObjectInvocation: "indirect-object-invocation",
221
+ useRoleComposition: "use-role-composition",
222
+ // THE IMPORT AND EXPORT EDGES ✓✓ — 2026-10-21 ✓✓.
223
+ //
224
+ // The corpus EXPRESSES both ✓✓ (3,539 use/require sites ✓✓, 33 @EXPORT
225
+ // conventions in 11 files ✓✓) and the ARM's TABLE 1 carried `imports 0` and
226
+ // `exports 0` ✓✓, because the facts went into the analyzer's OUTPUT SHAPE ✓✓
227
+ // and never became RELATIONSHIPS ✓✓. These two codes are the receipt ✓✓.
228
+ moduleImport: "module-import",
229
+ exportedSymbol: "exported-symbol",
230
+ exportedArrayPush: "exporter-array-push",
231
+ exportedArrayDeclaration: "exporter-array-declaration",
232
+ exporterSurface: "exporter-surface",
233
+ useModuleLoad: "use-module-load",
234
+ runtimeRequire: "runtime-require",
235
+ isaArrayPush: "isa-array-push",
236
+ baseDeclaresMethod: "base-declares-method",
237
+ firstParentGuess: "first-parent-guess",
238
+ magicTokenInvocant: "magic-token-invocant",
239
+ codeReference: "code-reference",
240
+ qualifiedVariableRead: "qualified-variable-read",
241
+ qualifiedNameAccess: "qualified-name-access",
242
+ blessClassLiteral: "bless-class-literal",
243
+ refClassComparison: "ref-class-comparison",
244
+ universalIsaLiteral: "universal-isa-literal",
245
+ isaClassLiteral: "isa-class-literal",
246
+ doesRoleLiteral: "does-role-literal",
247
+ typeQueryLiteral: "type-query-literal"
248
+ };
249
+ var REASON = PERL_REASON_CODES;
250
+ function perlFindChildByType(node, type) {
251
+ for (let i = 0; i < node.namedChildCount; i++) {
252
+ const child = node.namedChild(i);
253
+ if (child?.type === type) return child;
254
+ }
255
+ return void 0;
256
+ }
257
+ function perlWordValues(node) {
258
+ if (node.type === "string_content" || node.type === "autoquoted_bareword" || node.type === "bareword" || node.type === "package") return node.text ? splitWords(node.text) : [];
259
+ if (node.type === "quoted_word_list") {
260
+ const content = node.childForFieldName("content") || perlFindChildByType(node, "string_content");
261
+ return content ? splitWords(content.text) : [];
262
+ }
263
+ if (node.type === "string_literal") {
264
+ const content = node.childForFieldName("content") || perlFindChildByType(node, "string_content");
265
+ return [content ? content.text : unquote(node.text)];
266
+ }
267
+ if (node.type === "interpolated_string_literal") {
268
+ const values2 = [];
269
+ for (let i = 0; i < node.namedChildCount; i++) {
270
+ const child = node.namedChild(i);
271
+ if (!child) continue;
272
+ if (child.type === "string_content") {
273
+ for (const w of splitWords(child.text)) values2.push(w);
274
+ }
275
+ }
276
+ return values2;
277
+ }
278
+ if (node.type === "subroutine_declaration_statement" || node.type === "anonymous_subroutine_expression" || node.type === "block" || node.type === "block_statement") return [];
279
+ if (node.type === "conditional_expression") return [];
280
+ if (node.type === "function_call_expression" || node.type === "method_call_expression" || node.type === "func0op_call_expression" || node.type === "func1op_call_expression" || node.type === "coderef_call_expression" || node.type === "ambiguous_function_call_expression") return [];
281
+ if (node.type === "binary_expression" || node.type === "lowprec_logical_expression" || node.type === "logical_not_expression") {
282
+ const op = node.childForFieldName("operator") ?? node.children.find((c) => !c.isNamed);
283
+ const t = op ? op.type : "";
284
+ if (t === "and" || t === "or" || t === "||" || t === "&&" || t === "//") return [];
285
+ }
286
+ const values = [];
287
+ for (let i = 0; i < node.namedChildCount; i++) {
288
+ const child = node.namedChild(i);
289
+ if (child) values.push(...perlWordValues(child));
290
+ }
291
+ return values;
292
+ }
293
+ function unquote(text) {
294
+ if (text.length >= 2) {
295
+ const first = text[0];
296
+ const last = text[text.length - 1];
297
+ if (first === "'" && last === "'" || first === '"' && last === '"') return text.slice(1, -1);
298
+ }
299
+ return text;
300
+ }
301
+ function isPerlPragma(module) {
302
+ return PERL_PRAGMAS.has(module);
303
+ }
304
+ var PERL_PRAGMAS = /* @__PURE__ */ new Set([
305
+ "strict",
306
+ "warnings",
307
+ "integer",
308
+ "feature",
309
+ "lib",
310
+ "utf8",
311
+ "bytes",
312
+ "constant",
313
+ "vars",
314
+ "parent",
315
+ "base",
316
+ "overload",
317
+ "bigint",
318
+ "bignum",
319
+ "bigrat",
320
+ "open",
321
+ "charnames",
322
+ "autodie",
323
+ "re",
324
+ "attributes",
325
+ "sigtrap",
326
+ "sort",
327
+ "subs",
328
+ "threads",
329
+ "fields",
330
+ "if",
331
+ "less",
332
+ "locale",
333
+ "mro",
334
+ "ops",
335
+ "version",
336
+ "vmsish",
337
+ "arybase",
338
+ "deprecated",
339
+ "experimental",
340
+ "diagnostics",
341
+ "sigtrap",
342
+ "class",
343
+ "builtin"
344
+ ]);
345
+ var PERL_BUILTIN_REF_TYPES = /* @__PURE__ */ new Set(["ARRAY", "HASH", "SCALAR", "CODE", "REF", "GLOB", "LVALUE", "REGEXP", "IO", "VSTRING", "UNKNOWN"]);
346
+ function splitWords(text) {
347
+ const result = [];
348
+ let current = "";
349
+ for (const char of text) {
350
+ if (char === " " || char === " " || char === "\n" || char === ",") {
351
+ if (current) result.push(current);
352
+ current = "";
353
+ } else current += char;
354
+ }
355
+ if (current) result.push(current);
356
+ return result;
357
+ }
358
+ function isArgumentSpecification(statement) {
359
+ const WRAPPERS = /* @__PURE__ */ new Set(["list_expression", "array_expression", "hash_expression", "anonymous_array_expression", "anonymous_hash_expression"]);
360
+ const hasFatArrow = (n) => {
361
+ for (let i = 0; i < n.childCount; i++) {
362
+ const child = n.child(i);
363
+ if (!child) continue;
364
+ if (!child.isNamed && child.type === "=>") return true;
365
+ }
366
+ return false;
367
+ };
368
+ const nodes = [statement, ...Array.from({ length: statement.childCount }, (_, i) => statement.child(i)).filter(Boolean)];
369
+ for (const n of nodes) {
370
+ if (!n) continue;
371
+ if (hasFatArrow(n)) return true;
372
+ for (let i = 0; i < n.childCount; i++) {
373
+ const child = n.child(i);
374
+ if (child && WRAPPERS.has(child.type) && hasFatArrow(child)) return true;
375
+ }
376
+ }
377
+ return false;
378
+ }
379
+ function perlArgumentNodes(node) {
380
+ const module = node.childForFieldName("module");
381
+ const fromField = [];
382
+ for (let i = 0; i < node.childCount; i++) {
383
+ const child = node.child(i);
384
+ if (child && node.fieldNameForChild(i) === "arguments" && perlWordValues(child).length > 0) fromField.push(child);
385
+ }
386
+ if (fromField.length) return fromField;
387
+ const list = perlFindChildByType(node, "list_expression");
388
+ if (list) return list.namedChildren.filter((c) => !!c && perlWordValues(c).length > 0);
389
+ const result = [];
390
+ for (let i = 0; i < node.namedChildCount; i++) {
391
+ const child = node.namedChild(i);
392
+ if (!child || child === module) continue;
393
+ if (child.type === "string_literal" || child.type === "interpolated_string_literal" || child.type === "quoted_word_list" || child.type === "autoquoted_bareword") {
394
+ if (perlWordValues(child).length > 0) result.push(child);
395
+ }
396
+ }
397
+ return result;
398
+ }
399
+ function hasArgumentSyntax(node) {
400
+ if (node.childrenForFieldName("arguments").length > 0) return true;
401
+ for (let i = 0; i < node.childCount; i++) {
402
+ const t = node.child(i)?.type;
403
+ if (t === "(" || t === ")") return true;
404
+ }
405
+ return false;
406
+ }
407
+ var RealPerlAnalyzer = class {
408
+ analyze(rootNode, content, _filePath, context) {
409
+ const symbols = [];
410
+ const relationships = [];
411
+ const imports = [];
412
+ const exports = [];
413
+ const errors = [];
414
+ const frameworks = /* @__PURE__ */ new Set(["Perl"]);
415
+ const patterns = /* @__PURE__ */ new Set();
416
+ const symbolKeys = /* @__PURE__ */ new Set();
417
+ const symbolsByKey = /* @__PURE__ */ new Map();
418
+ const relationshipKeys = /* @__PURE__ */ new Set();
419
+ const packageBases = /* @__PURE__ */ new Map();
420
+ const packageBaseOrigin = /* @__PURE__ */ new Map();
421
+ const packageBaseLine = /* @__PURE__ */ new Map();
422
+ const packageMethods = /* @__PURE__ */ new Map();
423
+ const deferredIsa = [];
424
+ const indirectObjectClassNodes = /* @__PURE__ */ new Set();
425
+ const nodeKey = (n) => `${n.startIndex}:${n.endIndex}:${n.type}`;
426
+ const versionRequirements = [];
427
+ if (!rootNode) return this.result(symbols, relationships, imports, exports, errors, content, frameworks, patterns);
428
+ this.collectErrors(rootNode, errors);
429
+ const addSymbol = (name, kind, node, state, extra = {}, ownerOverride, nameNode) => {
430
+ if (!name) return void 0;
431
+ const owner = ownerOverride ?? state.ownerName;
432
+ const key = `${kind}${owner || ""}${name}`;
433
+ if (symbolKeys.has(key)) return symbolsByKey.get(key);
434
+ const nameLineSource = nameNode ?? node.childForFieldName("name") ?? this.findChildByType(node, "bareword");
435
+ const nameLine = name === node.text?.trim() ? node.startPosition.row + 1 : nameLineSource ? nameLineSource.startPosition.row + 1 : node.startPosition.row + 1;
436
+ const symbol = {
437
+ name,
438
+ kind,
439
+ range: this.nodeToRange(node),
440
+ scope: owner || state.packageName || void 0,
441
+ ownerName: owner,
442
+ ownerKind: owner ? state.ownerKind : void 0,
443
+ visibility: state.visibility,
444
+ metadata: { perlSpecific: true, nameLine, ...extra }
445
+ };
446
+ symbolKeys.add(key);
447
+ symbolsByKey.set(key, symbol);
448
+ symbols.push(symbol);
449
+ return symbol;
450
+ };
451
+ const nameLineOf = (symbol) => {
452
+ const recorded = symbol.metadata?.nameLine;
453
+ if (typeof recorded === "number" && recorded > 0) return recorded;
454
+ const start = symbol.range?.startLine;
455
+ return typeof start === "number" && start > 0 ? start : void 0;
456
+ };
457
+ const addRelationship = (source, target, type, metadata2 = {}, confidence = 0.9, lineNode, explicitLine) => {
458
+ if (!source || !target) return;
459
+ const key = `${type}${source}${target}`;
460
+ if (relationshipKeys.has(key)) return;
461
+ relationshipKeys.add(key);
462
+ const line = explicitLine ?? (lineNode ? lineNode.startPosition.row + 1 : void 0);
463
+ relationships.push({
464
+ source,
465
+ target,
466
+ type,
467
+ confidence,
468
+ ...typeof line === "number" && line > 0 ? { line_number: line } : {},
469
+ metadata: { perlSpecific: true, ...metadata2 }
470
+ });
471
+ };
472
+ const addReferenceName = (rawName, nameNode, state, reasonCode) => {
473
+ const leaf = this.leafName(rawName);
474
+ const source = state.functionName || state.ownerName || state.packageName || "global";
475
+ if (!leaf || leaf === source) return;
476
+ const qualifier = rawName.includes("::") ? rawName.slice(0, rawName.lastIndexOf("::")) : void 0;
477
+ addRelationship(source, leaf, ASTEngine.RelationshipType.References, { reasonCode, ...qualifier ? { qualifiedAs: rawName, qualifier } : {} }, 0.9, nameNode);
478
+ };
479
+ const addBase = (owner, parents, reasonCode, nameNode, parentLines) => {
480
+ const existing = packageBases.get(owner) || [];
481
+ for (const parent of parents) {
482
+ if (!parent) continue;
483
+ if (!existing.includes(parent)) {
484
+ existing.push(parent);
485
+ if (!packageBaseOrigin.has(owner)) packageBaseOrigin.set(owner, reasonCode);
486
+ }
487
+ const key = `${owner}${parent}`;
488
+ if (packageBaseLine.has(key)) continue;
489
+ const perParent = parentLines?.get(parent);
490
+ if (typeof perParent === "number") packageBaseLine.set(key, perParent);
491
+ else if (nameNode) packageBaseLine.set(key, nameNode.startPosition.row + 1);
492
+ }
493
+ packageBases.set(owner, existing);
494
+ };
495
+ const qualifiedClassNames = /* @__PURE__ */ new Set();
496
+ const addCall = (target, state, calleeNode, dynamicReceiver, callNode, builtinProbe) => {
497
+ const named = target.startsWith("&") ? target.slice(1) : target;
498
+ const probe = builtinProbe ?? named;
499
+ if (!target || PERL_BUILTINS.has(probe) || probe === "use" || probe === "no") return;
500
+ if (target.startsWith("&") && !hasArgumentSyntax(callNode ?? calleeNode)) {
501
+ const source = state.functionName || state.ownerName || state.packageName || "global";
502
+ const leaf = this.leafName(named);
503
+ if (leaf && leaf !== source) {
504
+ const qualifier = named.includes("::") ? named.slice(0, named.lastIndexOf("::")) : void 0;
505
+ addRelationship(source, leaf, ASTEngine.RelationshipType.Uses, { reasonCode: REASON.codeReference, ...qualifier ? { qualifiedAs: named, qualifier } : {} }, 0.9, calleeNode);
506
+ }
507
+ return;
508
+ }
509
+ addRelationship(
510
+ state.functionName || state.ownerName || state.packageName || "global",
511
+ this.leafName(named),
512
+ ASTEngine.RelationshipType.Calls,
513
+ { reasonCode: dynamicReceiver ? REASON.dynamicReceiver : REASON.nameOnlyCallee },
514
+ 0.9,
515
+ calleeNode
516
+ );
517
+ };
518
+ const walk = (node, state) => {
519
+ switch (node.type) {
520
+ case "package_statement": {
521
+ const nameNode = node.childForFieldName("name") ?? void 0;
522
+ const name = nameNode?.text || "";
523
+ if (!name) break;
524
+ const kind = ASTEngine.SymbolKind.Class;
525
+ addSymbol(name, kind, nameNode ?? node, { ...state, packageName: name }, { isPackage: true });
526
+ patterns.add("Packages");
527
+ const block = node.childForFieldName("body") || this.findChildByType(node, "block");
528
+ if (block) {
529
+ walkChildren(block, {
530
+ ...state,
531
+ packageName: name,
532
+ ownerName: name,
533
+ ownerKind: kind,
534
+ insideFunction: false,
535
+ bodyKind: "package"
536
+ });
537
+ } else {
538
+ state.packageName = name;
539
+ }
540
+ return;
541
+ }
542
+ case "class_statement":
543
+ case "role_statement": {
544
+ const nameNode = node.childForFieldName("name") ?? void 0;
545
+ const name = nameNode?.text || "";
546
+ if (!name) break;
547
+ const isRole = node.type === "role_statement";
548
+ const kind = isRole ? ASTEngine.SymbolKind.Trait : ASTEngine.SymbolKind.Class;
549
+ addSymbol(name, kind, nameNode ?? node, { ...state, packageName: name }, isRole ? { isPerlRole: true } : { isPerlClass: true });
550
+ patterns.add(isRole ? "Roles" : "Classes");
551
+ frameworks.add("Perl 5.38 core OO");
552
+ const block = node.childForFieldName("body") || this.findChildByType(node, "block");
553
+ if (block) {
554
+ walkChildren(block, {
555
+ ...state,
556
+ packageName: name,
557
+ ownerName: name,
558
+ ownerKind: kind,
559
+ insideFunction: false,
560
+ bodyKind: isRole ? "role" : "class"
561
+ });
562
+ }
563
+ return;
564
+ }
565
+ case "subroutine_declaration_statement":
566
+ case "method_declaration_statement": {
567
+ if (state.insideFunction) {
568
+ const nestedBody = node.childForFieldName("body") || this.findChildByType(node, "block");
569
+ if (nestedBody) walkChildren(nestedBody, { ...state, insideFunction: true });
570
+ return;
571
+ }
572
+ const nameNode = node.childForFieldName("name") || this.findChildByType(node, "bareword");
573
+ const rawName = nameNode?.text || "";
574
+ if (!rawName) break;
575
+ const isConstructor = rawName === "new";
576
+ const name = isConstructor ? "constructor" : rawName;
577
+ const owner = state.packageName || state.ownerName;
578
+ const kind = isConstructor ? ASTEngine.SymbolKind.Constructor : owner ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function;
579
+ const symbol = addSymbol(name, kind, node, {
580
+ ...state,
581
+ ownerName: owner,
582
+ ownerKind: owner ? ASTEngine.SymbolKind.Class : void 0
583
+ }, {
584
+ originalName: rawName,
585
+ isMethod: !!owner,
586
+ isConstructor
587
+ }, owner);
588
+ if (symbol) {
589
+ symbol.parameters = this.extractParameters(node);
590
+ symbol.ownerKind = owner ? ASTEngine.SymbolKind.Class : void 0;
591
+ const decorators = this.extractAttributes(node);
592
+ if (decorators.length) symbol.decorators = decorators;
593
+ }
594
+ if (owner && symbol) {
595
+ let methodSet = packageMethods.get(owner);
596
+ if (!methodSet) {
597
+ methodSet = /* @__PURE__ */ new Set();
598
+ packageMethods.set(owner, methodSet);
599
+ }
600
+ methodSet.add(rawName);
601
+ }
602
+ const body = node.childForFieldName("body") || this.findChildByType(node, "block");
603
+ if (body) walkChildren(body, { ...state, ownerName: owner, ownerKind: owner ? ASTEngine.SymbolKind.Class : void 0, functionName: name, insideFunction: true });
604
+ return;
605
+ }
606
+ case "variable_declaration": {
607
+ const declaration = this.declarationKind(node);
608
+ patterns.add("Variable Declarations");
609
+ const inClassBody = state.bodyKind === "class" || state.bodyKind === "role";
610
+ const isPackageGlobal = declaration === "our" && !state.insideFunction;
611
+ const isClassField = declaration === "field" && inClassBody && !state.insideFunction;
612
+ if (!isPackageGlobal && !isClassField) break;
613
+ for (const variable of this.variableChildren(node)) {
614
+ const varNameNode = this.findChildByType(variable, "varname");
615
+ const rawName = varNameNode?.text || "";
616
+ const name = this.stripSigil(rawName);
617
+ if (!name || name === "_") continue;
618
+ if (isClassField) {
619
+ addSymbol(name, ASTEngine.SymbolKind.Property, variable, state, { isPerlField: true, perlDeclType: declaration, sigil: variable.type }, state.packageName || state.ownerName);
620
+ continue;
621
+ }
622
+ const kind = name === "ISA" ? ASTEngine.SymbolKind.Variable : this.isAllCapsName(name) ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Variable;
623
+ addSymbol(name, kind, variable, state, { perlDeclType: declaration, sigil: variable.type, ...name === "ISA" ? { isInheritanceVariable: true } : {} }, state.packageName || void 0);
624
+ }
625
+ break;
626
+ }
627
+ case "refgen_expression": {
628
+ const fnNode = this.findChildByType(node, "function");
629
+ if (!fnNode) break;
630
+ const varname = this.findChildByType(fnNode, "varname");
631
+ if (!varname) break;
632
+ if (fnNode.namedChildCount !== 1) break;
633
+ const name = varname.text;
634
+ if (!name || !this.isWrittenCalleeName(name)) break;
635
+ const leaf = this.leafName(name);
636
+ const source = state.functionName || state.ownerName || state.packageName || "global";
637
+ if (leaf === source) break;
638
+ const qualifier = name.includes("::") ? name.slice(0, name.lastIndexOf("::")) : void 0;
639
+ addRelationship(source, leaf, ASTEngine.RelationshipType.Uses, { reasonCode: REASON.codeReference, ...qualifier ? { qualifiedAs: name, qualifier } : {} }, 0.9, varname);
640
+ break;
641
+ }
642
+ case "scalar":
643
+ case "array":
644
+ case "hash": {
645
+ const varname = this.findChildByType(node, "varname");
646
+ const qualified = varname?.text || "";
647
+ if (!qualified || !qualified.includes("::")) break;
648
+ if (!this.isWrittenCalleeName(qualified)) break;
649
+ const readSource = state.functionName || state.ownerName || state.packageName || "global";
650
+ addRelationship(readSource, qualified, ASTEngine.RelationshipType.Uses, { reasonCode: REASON.qualifiedVariableRead }, 0.9, varname);
651
+ break;
652
+ }
653
+ case "equality_expression": {
654
+ const kids = [];
655
+ for (let i = 0; i < node.namedChildCount; i++) {
656
+ const c = node.namedChild(i);
657
+ if (c) kids.push(c);
658
+ }
659
+ const isRefCall = (n) => n.type === "func1op_call_expression" && String(n.child(0)?.text ?? "") === "ref";
660
+ const refKid = kids.find(isRefCall);
661
+ const litKid = kids.find((k) => k.type === "string_literal" || k.type === "interpolated_string_literal");
662
+ if (refKid && litKid) {
663
+ const word = this.wordValues(litKid)[0];
664
+ if (word && this.isWrittenCalleeName(word) && !PERL_BUILTIN_REF_TYPES.has(word)) addReferenceName(word, litKid, state, REASON.refClassComparison);
665
+ }
666
+ break;
667
+ }
668
+ case "bareword": {
669
+ const text = String(node.text ?? "");
670
+ if (!text.includes("::") || text.endsWith("::")) break;
671
+ const parent = node.parent;
672
+ const parentType = parent ? String(parent.type) : "";
673
+ if (parentType === "hash_element_expression" || parentType === "slice_container_variable") break;
674
+ if (parent) {
675
+ let nextIsFatComma = false;
676
+ for (let i = 0; i < parent.childCount; i++) {
677
+ const child = parent.child(i);
678
+ if (!child || child.id !== node.id) continue;
679
+ const next = parent.child(i + 1);
680
+ if (next && !next.isNamed && next.type === "=>") nextIsFatComma = true;
681
+ break;
682
+ }
683
+ if (nextIsFatComma) break;
684
+ }
685
+ const VALUE_PARENT = /* @__PURE__ */ new Set(["assignment_expression", "return_expression", "list_expression", "expression_statement", "keyval_expression"]);
686
+ if (!VALUE_PARENT.has(parentType)) break;
687
+ let ancestor = parent;
688
+ let insideDirective = false;
689
+ for (let depth = 0; ancestor && depth < 4; depth++) {
690
+ if (ancestor.type === "use_statement" || ancestor.type === "require_expression" || ancestor.type === "require_version_expression") {
691
+ insideDirective = true;
692
+ break;
693
+ }
694
+ ancestor = ancestor.parent;
695
+ }
696
+ if (insideDirective) break;
697
+ const source = state.functionName || state.ownerName || state.packageName || "global";
698
+ addRelationship(source, text, ASTEngine.RelationshipType.Uses, { reasonCode: REASON.qualifiedNameAccess }, 0.9, node);
699
+ break;
700
+ }
701
+ case "use_statement": {
702
+ if (node.child(0)?.type === "no") break;
703
+ const moduleNode = node.childForFieldName("module") ?? void 0;
704
+ const module = moduleNode?.text || "";
705
+ if (!module) break;
706
+ if (module === "constant") {
707
+ for (const constant of this.constantDeclarationNames(node)) {
708
+ addSymbol(constant.name, ASTEngine.SymbolKind.Constant, constant.node, state, { isPerlConstant: true }, state.packageName || void 0);
709
+ }
710
+ patterns.add("Constants");
711
+ frameworks.add("Compile-time Constants");
712
+ break;
713
+ }
714
+ const args = this.argumentValues(node);
715
+ imports.push({
716
+ module,
717
+ items: args.length > 0 && !isArgumentSpecification(node) ? args : ["*"],
718
+ isDefault: true,
719
+ range: this.nodeToRange(node),
720
+ metadata: { perlSpecific: true, type: "use" }
721
+ });
722
+ patterns.add("Module Includes");
723
+ addRelationship(state.packageName || "global", module, ASTEngine.RelationshipType.Imports, {
724
+ reasonCode: REASON.useModuleLoad,
725
+ perlSpecific: true,
726
+ relationshipType: "use_module",
727
+ isImport: true,
728
+ fileLevel: true,
729
+ isPragma: isPerlPragma(module),
730
+ items: imports[imports.length - 1]?.items ?? []
731
+ }, 1, node);
732
+ if (module === "Moose" || module === "Moo" || module === "Mojo::Base") frameworks.add(module === "Mojo::Base" ? "Mojolicious" : "Moose/Moo OOP");
733
+ if (module === "parent" || module === "base") {
734
+ const parents = args.filter((arg) => !this.isOption(arg));
735
+ const parentLines = /* @__PURE__ */ new Map();
736
+ for (const argNode of this.argumentNodes(node)) {
737
+ for (const value of this.wordValues(argNode)) {
738
+ if (this.isOption(value)) continue;
739
+ const line = this.lineOfNameWithin(argNode, value);
740
+ if (line !== void 0) parentLines.set(value, line);
741
+ }
742
+ }
743
+ if (state.packageName) addBase(state.packageName, parents, REASON.useParentDeclaration, moduleNode, parentLines);
744
+ } else if (module === "role") {
745
+ for (const argNode of this.argumentNodes(node)) {
746
+ for (const value of this.wordValues(argNode)) {
747
+ if (this.isOption(value)) continue;
748
+ const roleName = this.leafName(value);
749
+ const roleLine = this.lineOfNameWithin(argNode, value);
750
+ addRelationship(
751
+ state.packageName || "global",
752
+ roleName,
753
+ ASTEngine.RelationshipType.Implements,
754
+ { reasonCode: REASON.useRoleComposition },
755
+ 0.9,
756
+ void 0,
757
+ roleLine
758
+ );
759
+ addRelationship(
760
+ state.packageName || "global",
761
+ roleName,
762
+ ASTEngine.RelationshipType.MixesIn,
763
+ { reasonCode: REASON.useRoleComposition, compositionSite: "role-composition" },
764
+ 0.9,
765
+ void 0,
766
+ roleLine
767
+ );
768
+ }
769
+ }
770
+ if (state.packageName) frameworks.add("Moose/Moo OOP");
771
+ }
772
+ if (module === "Mojo::Base" && state.packageName) {
773
+ const named = args.find((arg) => arg && !this.isOption(arg));
774
+ addBase(state.packageName, [named ?? module], REASON.mojoBaseDeclaration, moduleNode);
775
+ }
776
+ const useArgList = this.findChildByType(node, "list_expression");
777
+ if (useArgList) walk(useArgList, state);
778
+ return;
779
+ }
780
+ case "use_version_statement": {
781
+ const versionNode = node.childForFieldName("version");
782
+ const version = versionNode?.text || "";
783
+ if (version) {
784
+ versionRequirements.push(version);
785
+ patterns.add("Version Requirements");
786
+ }
787
+ return;
788
+ }
789
+ case "require_expression": {
790
+ const moduleNode = this.findChildByType(node, "bareword") || this.findChildByType(node, "package");
791
+ const pathNode = this.findChildByType(node, "string_literal");
792
+ const module = pathNode ? this.wordValues(pathNode)[0] ?? "" : moduleNode?.text ?? "";
793
+ if (!module) break;
794
+ imports.push({
795
+ module,
796
+ items: [],
797
+ isDefault: false,
798
+ range: this.nodeToRange(node),
799
+ metadata: { perlSpecific: true, type: "require" }
800
+ });
801
+ addRelationship(state.packageName || "global", module, ASTEngine.RelationshipType.Imports, {
802
+ reasonCode: REASON.runtimeRequire,
803
+ perlSpecific: true,
804
+ relationshipType: pathNode ? "require_file" : "require_module",
805
+ ...pathNode ? { specifier: "path" } : {},
806
+ isImport: true,
807
+ fileLevel: true,
808
+ isPragma: false,
809
+ items: []
810
+ }, 0.9, node);
811
+ patterns.add("Module Includes");
812
+ return;
813
+ }
814
+ case "require_version_expression": {
815
+ const versionNode = node.childForFieldName("version") || this.findChildByType(node, "number");
816
+ const version = versionNode?.text || "";
817
+ if (version) {
818
+ versionRequirements.push(version);
819
+ patterns.add("Version Requirements");
820
+ }
821
+ return;
822
+ }
823
+ case "assignment_expression": {
824
+ const lefts = this.fieldNodes(node, "left");
825
+ const rights = this.fieldNodes(node, "right");
826
+ const leftVar = lefts.flatMap((l) => this.variableChildren(l).map((variable) => this.findChildByType(variable, "varname")).find(Boolean)).find(Boolean);
827
+ const leftName = leftVar?.text || "";
828
+ const right = rights.find((r) => r.isNamed && r.namedChildCount > 0) ?? rights[0];
829
+ if (leftName === "ISA" && right && state.packageName) deferredIsa.push({ owner: state.packageName, right });
830
+ if (right && (leftName === "EXPORT" || leftName === "EXPORT_OK")) {
831
+ for (const exported of this.wordValues(right)) {
832
+ exports.push({ name: exported, kind: ASTEngine.SymbolKind.Function, range: this.nodeToRange(node), metadata: { perlSpecific: true, exportArray: leftName } });
833
+ addRelationship(state.packageName || "global", exported, ASTEngine.RelationshipType.Exports, { reasonCode: REASON.exportedArrayDeclaration, perlSpecific: true, exportArray: leftName }, 1, node, this.lineOfNameWithin(right, exported));
834
+ }
835
+ patterns.add("Exports");
836
+ }
837
+ if (leftName === "EXPORT_TAGS" && right) {
838
+ for (const tag of this.fatCommaKeys(right)) {
839
+ exports.push({ name: tag.text, kind: ASTEngine.SymbolKind.Function, range: this.nodeToRange(node), metadata: { perlSpecific: true, exportTag: "EXPORT_TAGS" } });
840
+ addRelationship(state.packageName || "global", tag.text, ASTEngine.RelationshipType.Exports, { reasonCode: REASON.exporterSurface, perlSpecific: true, exportTag: "EXPORT_TAGS" }, 1, node, tag.startPosition.row + 1);
841
+ }
842
+ patterns.add("Exports");
843
+ }
844
+ break;
845
+ }
846
+ case "ambiguous_function_call_expression":
847
+ case "function_call_expression": {
848
+ const functionNode = node.childForFieldName("function") || this.findChildByType(node, "function");
849
+ const target = functionNode?.text || "";
850
+ if (!this.isWrittenCalleeName(target)) break;
851
+ if (target === "bless") {
852
+ const els = this.compositionElements(node);
853
+ let cls;
854
+ let clsNode;
855
+ for (const el of els) {
856
+ if (el.type !== "string_literal" && el.type !== "interpolated_string_literal") continue;
857
+ const word = this.wordValues(el)[0];
858
+ if (word && this.isWrittenCalleeName(word)) {
859
+ cls = word;
860
+ clsNode = el;
861
+ }
862
+ }
863
+ if (cls && clsNode) addReferenceName(cls, clsNode, state, REASON.blessClassLiteral);
864
+ break;
865
+ }
866
+ if (target === "UNIVERSAL::isa") {
867
+ const els = this.compositionElements(node);
868
+ let cls;
869
+ let clsNode;
870
+ for (const el of els) {
871
+ if (el.type !== "string_literal" && el.type !== "interpolated_string_literal") continue;
872
+ const word = this.wordValues(el)[0];
873
+ if (word && this.isWrittenCalleeName(word)) {
874
+ cls = word;
875
+ clsNode = el;
876
+ }
877
+ }
878
+ if (cls && clsNode) addReferenceName(cls, clsNode, state, REASON.universalIsaLiteral);
879
+ }
880
+ if (target === "push") {
881
+ const elements = this.compositionElements(node);
882
+ const targetEl = elements[0];
883
+ const arrayVar = targetEl && targetEl.type === "array" ? this.findChildByType(targetEl, "varname")?.text ?? "" : "";
884
+ const arrayName = this.leafName(arrayVar);
885
+ const owner = arrayVar.includes("::") ? arrayVar.slice(0, arrayVar.lastIndexOf("::")) : state.packageName || "global";
886
+ const NAMED_ELEMENT = /* @__PURE__ */ new Set(["quoted_word_list", "string_literal", "interpolated_string_literal", "autoquoted_bareword", "bareword"]);
887
+ if (arrayName === "EXPORT" || arrayName === "EXPORT_OK") {
888
+ for (const el of elements.slice(1)) {
889
+ if (!NAMED_ELEMENT.has(el.type)) continue;
890
+ for (const value of this.wordValues(el)) {
891
+ if (!value || this.isOption(value)) continue;
892
+ exports.push({ name: value, kind: ASTEngine.SymbolKind.Function, range: this.nodeToRange(node), metadata: { perlSpecific: true, exportArray: arrayName } });
893
+ addRelationship(owner, value, ASTEngine.RelationshipType.Exports, { reasonCode: REASON.exportedArrayPush, perlSpecific: true, exportArray: arrayName }, 1, node, this.lineOfNameWithin(el, value));
894
+ }
895
+ }
896
+ patterns.add("Exports");
897
+ } else if (arrayName === "ISA") {
898
+ const bases = [];
899
+ const lines = /* @__PURE__ */ new Map();
900
+ for (const el of elements.slice(1)) {
901
+ if (!NAMED_ELEMENT.has(el.type)) continue;
902
+ for (const value of this.wordValues(el)) {
903
+ if (!value || this.isOption(value)) continue;
904
+ bases.push(value);
905
+ const line = this.lineOfNameWithin(el, value);
906
+ if (line !== void 0) lines.set(value, line);
907
+ }
908
+ }
909
+ if (bases.length) addBase(owner, bases, REASON.isaArrayPush, targetEl, lines);
910
+ }
911
+ break;
912
+ }
913
+ if (target === "has") {
914
+ const attrs = this.attributeNames(node);
915
+ for (const attr of attrs) {
916
+ if (!attr.name || !state.packageName) continue;
917
+ if (symbolKeys.has(`${ASTEngine.SymbolKind.Property}${state.packageName}${attr.name}`)) continue;
918
+ addSymbol(attr.name, ASTEngine.SymbolKind.Property, attr.node, { ...state, ownerName: state.packageName, ownerKind: ASTEngine.SymbolKind.Class }, { isMooseProperty: true }, state.packageName);
919
+ }
920
+ frameworks.add("Moose/Moo OOP");
921
+ patterns.add("Declarative Properties");
922
+ } else if (target === "with" || target === "extends") {
923
+ const isWith = target === "with";
924
+ for (const arg of this.compositionElements(node)) {
925
+ if (arg.type === "anonymous_hash_expression" || arg.type === "anonymous_array_expression") continue;
926
+ for (const value of this.wordValues(arg)) {
927
+ if (!value || this.isOption(value)) continue;
928
+ if (isWith) {
929
+ const roleName = this.leafName(value);
930
+ const roleLine = this.lineOfNameWithin(arg, value);
931
+ addRelationship(
932
+ state.packageName || "global",
933
+ roleName,
934
+ ASTEngine.RelationshipType.Implements,
935
+ { reasonCode: REASON.mooRoleComposition, mooseKeyword: target },
936
+ 0.9,
937
+ arg,
938
+ // `arg` is the right NODE but the wrong LINE for a `qw` list that
939
+ // wraps: the list starts on the `with` line and the role names
940
+ // are on the lines after it. Prefer the name's own line and keep
941
+ // the node as the fallback, so a shape the offset search cannot
942
+ // resolve keeps its previous (correct) line.
943
+ roleLine
944
+ );
945
+ addRelationship(
946
+ state.packageName || "global",
947
+ roleName,
948
+ ASTEngine.RelationshipType.MixesIn,
949
+ { reasonCode: REASON.mooRoleComposition, mooseKeyword: target, compositionSite: "role-composition" },
950
+ 0.9,
951
+ arg,
952
+ roleLine
953
+ );
954
+ continue;
955
+ }
956
+ addBase(state.packageName || "global", [value], REASON.mooExtendsKeyword, arg, /* @__PURE__ */ new Map([[value, this.lineOfNameWithin(arg, value) ?? arg.startPosition.row + 1]]));
957
+ }
958
+ }
959
+ frameworks.add("Moose/Moo OOP");
960
+ } else if (target === "new") {
961
+ const indirect = this.indirectObjectClass(node);
962
+ const inner = indirect?.carrier;
963
+ if (inner && indirect?.name) {
964
+ indirectObjectClassNodes.add(nodeKey(inner));
965
+ if (indirect.name.includes("::")) qualifiedClassNames.add(indirect.name);
966
+ addRelationship(state.functionName || state.packageName || "global", `${this.leafName(indirect.name)}.constructor`, ASTEngine.RelationshipType.Instantiates, { isConstructor: true, reasonCode: REASON.indirectObjectInvocation }, 0.9, node);
967
+ break;
968
+ }
969
+ const requestedType = this.firstValue(node);
970
+ const instanceTarget = requestedType && !requestedType.includes("$") ? `${this.leafName(requestedType)}.constructor` : "constructor";
971
+ if (requestedType && requestedType.includes("::")) qualifiedClassNames.add(requestedType);
972
+ addRelationship(state.functionName || state.packageName || "global", instanceTarget, ASTEngine.RelationshipType.Instantiates, { isConstructor: true, reasonCode: REASON.concreteInvocant }, 0.9, node);
973
+ } else {
974
+ if (indirectObjectClassNodes.has(nodeKey(node))) break;
975
+ addCall(target, state, functionNode ?? node, false, node);
976
+ }
977
+ break;
978
+ }
979
+ case "method_call_expression": {
980
+ const methodNode = this.findChildByType(node, "method");
981
+ const method = methodNode?.text || "";
982
+ if (!this.isWrittenCalleeName(method)) {
983
+ break;
984
+ }
985
+ const invocant = this.fieldNodes(node, "invocant").find((c) => c.isNamed) ?? this.firstNamedChild(node);
986
+ if (method === "new") {
987
+ const target = invocant ? this.invocantName(invocant) : "";
988
+ if (target && PERL_MAGIC_TOKENS.has(target)) {
989
+ const owner = state.packageName;
990
+ if (owner && owner !== "main") {
991
+ if (owner.includes("::")) qualifiedClassNames.add(owner);
992
+ addRelationship(state.functionName || owner, `${this.leafName(owner)}.constructor`, ASTEngine.RelationshipType.Instantiates, { isConstructor: true, reasonCode: REASON.magicTokenInvocant }, 0.9, invocant ?? node);
993
+ }
994
+ } else if (invocant && this.namesAClass(invocant) && target && !target.includes("$") && !target.includes("->")) {
995
+ if (target.includes("::")) qualifiedClassNames.add(target);
996
+ addRelationship(state.functionName || state.packageName || "global", `${this.leafName(target)}.constructor`, ASTEngine.RelationshipType.Instantiates, { isConstructor: true, reasonCode: REASON.concreteInvocant }, 0.9, invocant ?? node);
997
+ }
998
+ } else {
999
+ const recvTarget = invocant ? this.invocantName(invocant) : "";
1000
+ const provableClass = invocant != null && this.namesAClass(invocant) && (invocant.type === "package" || recvTarget.includes("::")) && !recvTarget.includes("$") && !recvTarget.includes("->");
1001
+ if (provableClass) {
1002
+ if (recvTarget.includes("::")) qualifiedClassNames.add(recvTarget);
1003
+ addCall(`${this.leafName(recvTarget)}.${method}`, state, methodNode ?? node, false, node, method);
1004
+ } else {
1005
+ addCall(method, state, methodNode ?? node, true, node, method);
1006
+ }
1007
+ }
1008
+ if (method === "isa" || method === "DOES" || method === "can") {
1009
+ const args = this.argumentNodes(node);
1010
+ const named = this.fieldNodes(node, "arguments").filter((a) => a.isNamed);
1011
+ const first = named[0] ?? args[0];
1012
+ const queried = this.literalNameIn(first);
1013
+ if (queried && this.isWrittenCalleeName(queried) && !PERL_MAGIC_TOKENS.has(queried)) {
1014
+ const leaf = this.leafName(queried);
1015
+ const source = state.functionName || state.ownerName || state.packageName || "global";
1016
+ const selfByMethod = leaf === source;
1017
+ const selfByPackage = state.packageName !== "" && queried === state.packageName;
1018
+ if (!selfByMethod && !selfByPackage) {
1019
+ const nameNode = this.queriedNameNode(first) ?? first;
1020
+ addRelationship(
1021
+ source,
1022
+ leaf,
1023
+ ASTEngine.RelationshipType.References,
1024
+ // The written package rides in `qualifiedAs` for the same
1025
+ // reason the other reference sites record it: the target is the
1026
+ // LEAF (one class, one node) and the qualification the source
1027
+ // wrote is not lost, it is metadata a reader can ask for.
1028
+ { reasonCode: REASON.typeQueryLiteral, query: method, ...queried.includes("::") ? { qualifiedAs: queried } : {} },
1029
+ 0.9,
1030
+ nameNode
1031
+ );
1032
+ }
1033
+ }
1034
+ }
1035
+ break;
1036
+ }
1037
+ }
1038
+ if (node.type !== "source_file") {
1039
+ const childState = node.type === "block" || node.type === "block_statement" ? { ...state } : state;
1040
+ for (let i = 0; i < node.namedChildCount; i++) {
1041
+ const child = node.namedChild(i);
1042
+ if (child) walk(child, childState);
1043
+ }
1044
+ }
1045
+ };
1046
+ const walkChildren = (container, state) => {
1047
+ for (let i = 0; i < container.namedChildCount; i++) {
1048
+ const child = container.namedChild(i);
1049
+ if (child) walk(child, state);
1050
+ }
1051
+ };
1052
+ const rootState = { packageName: "", insideFunction: false, visibility: "public", bodyKind: "file" };
1053
+ if (rootNode.type === "source_file") walkChildren(rootNode, rootState);
1054
+ else walk(rootNode, rootState);
1055
+ for (const item of deferredIsa) {
1056
+ const parents = this.wordValues(item.right);
1057
+ if (parents.length) addBase(item.owner, parents, REASON.isaArray, item.right);
1058
+ }
1059
+ for (const [owner, bases] of packageBases) {
1060
+ const origin = packageBaseOrigin.get(owner) ?? REASON.useParentDeclaration;
1061
+ for (const base of bases) {
1062
+ if (base === owner) continue;
1063
+ addRelationship(
1064
+ owner,
1065
+ this.leafName(base),
1066
+ ASTEngine.RelationshipType.Extends,
1067
+ { via: "Perl inheritance", reasonCode: origin },
1068
+ 0.9,
1069
+ void 0,
1070
+ packageBaseLine.get(`${owner}${base}`)
1071
+ );
1072
+ }
1073
+ }
1074
+ for (const symbol of symbols) {
1075
+ if (symbol.kind !== ASTEngine.SymbolKind.Method && symbol.kind !== ASTEngine.SymbolKind.Constructor || !symbol.ownerName) continue;
1076
+ const base = packageBases.get(symbol.ownerName)?.[0];
1077
+ if (!base) continue;
1078
+ const baseLeaf = this.leafName(base);
1079
+ if (base === symbol.ownerName) continue;
1080
+ const method = String(symbol.metadata?.originalName || symbol.name);
1081
+ const candidateNames = [method, symbol.name];
1082
+ const localMethods = packageMethods.get(base) || packageMethods.get(baseLeaf);
1083
+ const companionMethods = context?.overrideCompanion?.get(base) || context?.overrideCompanion?.get(baseLeaf);
1084
+ const localDeclares = candidateNames.some((candidate) => localMethods?.has(candidate));
1085
+ const companionDeclares = candidateNames.some((candidate) => companionMethods?.has(candidate));
1086
+ if (localDeclares || companionDeclares) {
1087
+ addRelationship(`${symbol.ownerName}.${method}`, method, ASTEngine.RelationshipType.Overrides, { isOverride: true, reasonCode: REASON.baseDeclaresMethod, overriddenOwner: baseLeaf }, 0.95, void 0, nameLineOf(symbol));
1088
+ } else if (!context?.overrideCompanion?.has(base) && !context?.overrideCompanion?.has(baseLeaf)) {
1089
+ if (base.includes("::")) qualifiedClassNames.add(base);
1090
+ addRelationship(`${symbol.ownerName}.${method}`, `${baseLeaf}.${method}`, ASTEngine.RelationshipType.Overrides, { isOverride: true, reasonCode: REASON.firstParentGuess }, 0.5, void 0, nameLineOf(symbol));
1091
+ }
1092
+ }
1093
+ for (const symbol of symbols) {
1094
+ if (symbol.ownerName && symbol.ownerName !== symbol.name) addRelationship(symbol.ownerName, symbol.name, ASTEngine.RelationshipType.Contains, {}, 1, void 0, nameLineOf(symbol));
1095
+ }
1096
+ const metadata = {
1097
+ complexity: symbols.length + relationships.length,
1098
+ linesOfCode: this.lineCount(content),
1099
+ cyclomaticComplexity: this.countBranches(rootNode) + 1,
1100
+ framework: [...frameworks][0],
1101
+ detectedFrameworks: [...frameworks],
1102
+ patterns: [...patterns],
1103
+ frameworkPatterns: [...patterns].map((pattern) => ({ framework: "Perl", patterns: [{ type: "usage", detected: true, description: pattern }] })),
1104
+ architecturalInsights: [],
1105
+ architecturalScore: Math.max(0, 100 - errors.length * 5),
1106
+ codeQuality: errors.length === 0 ? "Excellent" : "Fair",
1107
+ hasTypeInference: false,
1108
+ hasFrameworkIntelligence: true,
1109
+ analysisVersion: "3.1.0-perl-ast",
1110
+ ...versionRequirements.length ? { perlVersion: versionRequirements[0], perlVersions: versionRequirements } : {}
1111
+ };
1112
+ this.classifyEvidenceTier(relationships);
1113
+ this.publishStatedCauses(symbols, relationships, qualifiedClassNames);
1114
+ this.publishExternalDependencies(symbols, relationships);
1115
+ return { symbols, relationships, imports, exports, errors, metadata };
1116
+ }
1117
+ /**
1118
+ * Perl's own confidences decide the floors, not a copied constant: the
1119
+ * source-declared edges (`extends`/`implements`/`instantiates` from `use
1120
+ * parent`, `with`, `use role`, a class invocant) are all 0.9, `contains` is
1121
+ * 1.0, `calls` is 0.9, and `overrides` is 0.5 because it is guessed from a
1122
+ * first-parent chain. So the guessed kind lands on `inferred` on its own,
1123
+ * with no special case: a 0.5 edge can never clear a 0.7 structural floor.
1124
+ */
1125
+ classifyEvidenceTier(relationships) {
1126
+ for (const r of relationships) {
1127
+ const type = r.type ?? r.relationshipType ?? "";
1128
+ const md = r.metadata ?? {};
1129
+ r.metadata = md;
1130
+ if (type === "contains") {
1131
+ md.evidenceClass = "structural";
1132
+ continue;
1133
+ }
1134
+ if (type === "imports" || type === "exports") {
1135
+ md.evidenceClass = "import_based";
1136
+ continue;
1137
+ }
1138
+ if (type === "instantiates" || type === "extends" || type === "implements") {
1139
+ md.evidenceClass = (r.confidence ?? 0) >= 0.9 ? "structural" : "inferred";
1140
+ continue;
1141
+ }
1142
+ if (type === "calls") {
1143
+ md.evidenceClass = (r.confidence ?? 0) >= 0.85 ? "usage_contextual" : "inferred";
1144
+ continue;
1145
+ }
1146
+ if (type === "uses" || type === "references") {
1147
+ md.evidenceClass = (r.confidence ?? 0) >= 0.7 ? "usage_contextual" : "inferred";
1148
+ continue;
1149
+ }
1150
+ if (type === "overrides") {
1151
+ md.evidenceClass = "inferred";
1152
+ continue;
1153
+ }
1154
+ md.evidenceClass = "inferred";
1155
+ }
1156
+ }
1157
+ result(symbols, relationships, imports, exports, errors, content, frameworks, patterns) {
1158
+ return {
1159
+ symbols,
1160
+ relationships,
1161
+ imports,
1162
+ exports,
1163
+ errors,
1164
+ metadata: {
1165
+ complexity: symbols.length + relationships.length,
1166
+ linesOfCode: this.lineCount(content),
1167
+ cyclomaticComplexity: 1,
1168
+ framework: [...frameworks][0],
1169
+ detectedFrameworks: [...frameworks],
1170
+ patterns: [...patterns],
1171
+ frameworkPatterns: [],
1172
+ architecturalInsights: [],
1173
+ architecturalScore: Math.max(0, 100 - errors.length * 5),
1174
+ codeQuality: errors.length === 0 ? "Excellent" : "Fair",
1175
+ hasTypeInference: false,
1176
+ hasFrameworkIntelligence: true,
1177
+ analysisVersion: "3.1.0-perl-ast"
1178
+ }
1179
+ };
1180
+ }
1181
+ collectErrors(root, errors) {
1182
+ const stack = [root];
1183
+ while (stack.length > 0) {
1184
+ const node = stack.pop();
1185
+ if (node.type === "ERROR" || node.isMissing) {
1186
+ errors.push({ message: `Syntax ${node.type} detected.`, range: this.nodeToRange(node), severity: "warning" });
1187
+ }
1188
+ for (let i = 0; i < node.namedChildCount; i++) {
1189
+ const child = node.namedChild(i);
1190
+ if (child) stack.push(child);
1191
+ }
1192
+ }
1193
+ }
1194
+ /**
1195
+ * ALL children carrying `name` as their field.
1196
+ *
1197
+ * The pinned grammar gives one field name to SEVERAL children whenever a
1198
+ * construct is parenthesised: `our @EXPORT_OK = (a => 1, b => 2)` puts `(`,
1199
+ * the `list_expression` and `)` ALL under the `right` field, so
1200
+ * `childForFieldName("right")` returns the EMPTY `(` marker. Measured over
1201
+ * mojo+Moose+DBI+plack: `assignment_expression.right` 1,593 sites,
1202
+ * `ambiguous_function_call_expression.arguments` 1,058,
1203
+ * `method_call_expression.invocant` 168, `function_call_expression.arguments`
1204
+ * 3, `method_call_expression.arguments` 36 — every one of them lost by the
1205
+ * single-child read. `variable_declaration` additionally spells the paren form
1206
+ * `variables` (plural) against the bare form `variable`, 14,272 sites.
1207
+ */
1208
+ fieldNodes(node, name) {
1209
+ const out = [];
1210
+ for (let i = 0; i < node.childCount; i++) {
1211
+ const child = node.child(i);
1212
+ if (child && node.fieldNameForChild(i) === name) out.push(child);
1213
+ }
1214
+ return out;
1215
+ }
1216
+ /**
1217
+ * The declared variables of a `variable_declaration`. The bare form
1218
+ * (`our $VERSION`) spells the field `variable`; the parenthesised form
1219
+ * (`our ($x, $y) = ...`) spells it `variables`, so BOTH are read.
1220
+ */
1221
+ variableChildren(node) {
1222
+ const result = [];
1223
+ for (const child of [...this.fieldNodes(node, "variable"), ...this.fieldNodes(node, "variables")]) {
1224
+ if (child.type === "scalar" || child.type === "array" || child.type === "hash") result.push(child);
1225
+ }
1226
+ return result;
1227
+ }
1228
+ /**
1229
+ * The declaration scope, read from the grammar's own scope node.
1230
+ *
1231
+ * `variable_declaration` declares NO `scope` field: `our`/`my`/`state`/`field`
1232
+ * are CHILD NODES of the declaration. The pre-review implementation matched
1233
+ * `node.text.startsWith("our ")` — source text doing a syntax job, which
1234
+ * mis-reads any declaration whose keyword is followed by a newline or whose
1235
+ * text begins with something else.
1236
+ *
1237
+ * Those scope tokens are UNNAMED (`node.isNamed === false`, no field), so the
1238
+ * scan walks the RAW child list. A named-child scan silently finds nothing
1239
+ * and every `our` global in the corpus would vanish — the 2,379 constants
1240
+ * measured on the pre-review corpus all came from the text prefix.
1241
+ */
1242
+ declarationKind(node) {
1243
+ for (let i = 0; i < node.childCount; i++) {
1244
+ const child = node.child(i);
1245
+ if (!child) continue;
1246
+ if (child.type === "our" || child.type === "my" || child.type === "state" || child.type === "field") return child.type;
1247
+ }
1248
+ return "unknown";
1249
+ }
1250
+ /**
1251
+ * The argument nodes of a call or `use`.
1252
+ *
1253
+ * Two grammar generations have to be read here, and both are live:
1254
+ * - 1.2.1 (the WIRED build): the `arguments` field is MULTI-CHILD — a
1255
+ * parenthesised argument list spreads one field across `(`, the payload
1256
+ * and `)`, so `childForFieldName` returns the empty bracket marker.
1257
+ * Measured on 5,875 corpus files: 39,897 such sites over 41 field pairs.
1258
+ * - 2.0.0: the paren is a `parenthesized_expression` NODE, and on a
1259
+ * `use_statement` it carries NO field at all — so a field-driven read sees
1260
+ * nothing and the whole import list disappears.
1261
+ *
1262
+ * Reading every child that yields words, and descending through the paren
1263
+ * wrapper, is correct under BOTH: measured over the same 5,875 files, the
1264
+ * analyzer's emissions are then identical under a 1.2.1 build and a 2.0.0
1265
+ * build on 5,875 of 5,875 files, so a grammar bump needs no analyzer work.
1266
+ */
1267
+ argumentNodes(node) {
1268
+ return perlArgumentNodes(node);
1269
+ }
1270
+ /**
1271
+ * The line a NAME is written on INSIDE a node, when the node's own start line
1272
+ * is not it. `with qw/\n MooseRoleOne\n MooseRoleTwo\n/` is ONE
1273
+ * `quoted_word_list` node starting on the `with` line, so stamping the edge from
1274
+ * that node puts `implements` two lines above the role it names.
1275
+ *
1276
+ * Scoped to the ARGUMENT node, never the whole statement: for
1277
+ * `use Moose::Role 'My::Role'`, searching the statement for `Role` finds the
1278
+ * one inside `Moose::Role` and returns the module's line — exactly the bug
1279
+ * this avoids. Returns undefined when the name is not in the node's text, so
1280
+ * callers fall back to the node's own line rather than inventing one.
1281
+ */
1282
+ lineOfNameWithin(node, name) {
1283
+ if (!name) return void 0;
1284
+ const text = String(node.text ?? "");
1285
+ let from = 0;
1286
+ for (; ; ) {
1287
+ const idx = text.indexOf(name, from);
1288
+ if (idx < 0) return void 0;
1289
+ const before = idx > 0 ? text[idx - 1] : "";
1290
+ const after = text[idx + name.length] ?? "";
1291
+ if (!isPerlIdentChar(before) && !isPerlIdentChar(after)) {
1292
+ let line = node.startPosition.row;
1293
+ for (let i = 0; i < idx; i++) if (text.charCodeAt(i) === 10) line++;
1294
+ return line + 1;
1295
+ }
1296
+ from = idx + 1;
1297
+ }
1298
+ }
1299
+ /**
1300
+ * Is this callee text a NAME the source actually writes?
1301
+ *
1302
+ * A target that is not written cannot be verified — the M59 line oracle reads
1303
+ * every claimed target back against the file's own text, and one that is not
1304
+ * there is a FABRICATED claim rather than a weak one. Same reasoning that
1305
+ * removed the phantom `DSN` and `use_module` classes, applied to the method
1306
+ * side, and found by that oracle:
1307
+ *
1308
+ * Moo/Object.pm:56 calls -> DemolishAll->new
1309
+ * Moo/Role.pm:224 calls -> ${\( $tc->can('coercion')||sub { ... } )}
1310
+ *
1311
+ * Both are symbolic deref — `$self->${\( EXPR \)}`, perl's "call the method
1312
+ * whose name this expression computes". The grammar puts the whole expression
1313
+ * in the `method` field, so the lane published arbitrary source text as a
1314
+ * callee name. A POSITIVE allowlist again, not a "does this look wrong" test:
1315
+ * a negative test only enumerates the dynamic cases somebody already hit, and
1316
+ * this pair has been wrong that way three times.
1317
+ *
1318
+ * Rejected: a sigil (`->$name`), braces, `$`, `->`, whitespace. For a rejected
1319
+ * one NO edge is emitted — and the children are still walked, so the real calls
1320
+ * INSIDE the expression survive: `Method::Generate::DemolishAll->new` inside a
1321
+ * deref becomes its own `instantiates` edge. 19 such edges left a 14-file
1322
+ * draw.
1323
+ */
1324
+ isWrittenCalleeName(text) {
1325
+ const t = String(text ?? "").trim();
1326
+ if (!t) return false;
1327
+ for (let i2 = 0; i2 < t.length; i2++) {
1328
+ const c = t.charAt(i2);
1329
+ if (c === " " || c === " " || c === "\n" || c === "\r") return false;
1330
+ if (c === "$" || c === "{" || c === "}") return false;
1331
+ }
1332
+ if (t.includes("->")) return false;
1333
+ let i = 0;
1334
+ if (t.charAt(i) === "&") i++;
1335
+ if (t.charAt(i) === ":" && t.charAt(i + 1) === ":") i += 2;
1336
+ let segments = 0;
1337
+ for (; ; ) {
1338
+ if (segments > 0) {
1339
+ if (t.charAt(i) !== ":" || t.charAt(i + 1) !== ":") break;
1340
+ i += 2;
1341
+ }
1342
+ if (!isPerlIdentStart(t.charAt(i))) return false;
1343
+ i++;
1344
+ while (isPerlIdentChar(t.charAt(i))) i++;
1345
+ segments++;
1346
+ if (i >= t.length) return true;
1347
+ }
1348
+ return false;
1349
+ }
1350
+ /**
1351
+ * The ELEMENTS of a role-composition argument list — the shapes `with` and
1352
+ * `extends` are written in, as separate elements rather than as one blob.
1353
+ *
1354
+ * The call's `arguments` field holds ONE `list_expression` for every
1355
+ * multi-argument form, so a reader that treated that field as a single argument
1356
+ * read the first word of the WHOLE list and dropped the rest. Expanding the list
1357
+ * into its named children restores the element boundary: in
1358
+ * `with A, { -excludes => [...] }, B` the elements are A, the modifier hash
1359
+ * and B, which is exactly the information the role reader needs.
1360
+ */
1361
+ compositionElements(node) {
1362
+ const out = [];
1363
+ for (const arg of this.argumentNodes(node)) {
1364
+ if (arg.type === "list_expression") {
1365
+ for (let i = 0; i < arg.namedChildCount; i++) {
1366
+ const child = arg.namedChild(i);
1367
+ if (child) out.push(child);
1368
+ }
1369
+ } else out.push(arg);
1370
+ }
1371
+ return out;
1372
+ }
1373
+ /**
1374
+ * The names a `has` declaration declares — ONE per element of its name list.
1375
+ *
1376
+ * TWO facts live here and each was a measured defect. (1) A QUOTED name is ONE
1377
+ * name whatever it contains: `has 'has spaces' => (is => 'ro')` is a legal
1378
+ * attribute whose name has a space in it, and the old reader ran the name
1379
+ * through `wordValues`, whose whitespace splitter exists for `qw(a b)` (a real
1380
+ * LIST) and truncated this to `has` — publishing a member named `has`, which is
1381
+ * a real perl function, while the declared member got no symbol at all. (2) A
1382
+ * `qw`/array list declares SEVERAL attributes: `has [qw(handlers helpers)]` is
1383
+ * two members, and reading only the first dropped the rest.
1384
+ *
1385
+ * The splitter is therefore applied to the shapes that ARE lists
1386
+ * (`quoted_word_list`, each element of an array), and never to a string
1387
+ * literal's own content.
1388
+ */
1389
+ attributeNames(node) {
1390
+ const args = this.argumentNodes(node);
1391
+ const first = args[0];
1392
+ if (!first) return [];
1393
+ const head = first.type === "list_expression" && first.namedChildCount > 0 ? first.namedChild(0) : first;
1394
+ if (!head) return [];
1395
+ return this.namesOfDeclarator(head);
1396
+ }
1397
+ namesOfDeclarator(head) {
1398
+ if (head.type === "string_literal") {
1399
+ const content = head.childForFieldName("content") || this.findChildByType(head, "string_content");
1400
+ const name = content ? content.text : unquote(head.text);
1401
+ return name ? [{ name, node: content ?? head }] : [];
1402
+ }
1403
+ if (head.type === "quoted_word_list") {
1404
+ const content = head.childForFieldName("content") || this.findChildByType(head, "string_content");
1405
+ if (!content) return [];
1406
+ return splitWords(content.text).filter(Boolean).map((name) => ({ name, node: content }));
1407
+ }
1408
+ if (head.type === "anonymous_array_expression") {
1409
+ const out = [];
1410
+ for (let i = 0; i < head.namedChildCount; i++) {
1411
+ const child = head.namedChild(i);
1412
+ if (child) out.push(...this.namesOfDeclarator(child));
1413
+ }
1414
+ return out;
1415
+ }
1416
+ if (head.type === "interpolated_string_literal") {
1417
+ const pieces = [];
1418
+ let interpolates = false;
1419
+ for (let i = 0; i < head.namedChildCount; i++) {
1420
+ const child = head.namedChild(i);
1421
+ if (!child) continue;
1422
+ if (child.type === "string_content") pieces.push(...splitWords(child.text));
1423
+ else if (child.type === "scalar" || child.type === "array" || child.type === "hash") interpolates = true;
1424
+ }
1425
+ if (interpolates) return pieces.filter(Boolean).map((name) => ({ name, node: head }));
1426
+ const content = head.childForFieldName("content") || this.findChildByType(head, "string_content");
1427
+ const verbatim = content ? content.text : "";
1428
+ return verbatim ? [{ name: verbatim, node: content ?? head }] : [];
1429
+ }
1430
+ return this.wordValues(head).filter(Boolean).map((name) => ({ name, node: head }));
1431
+ }
1432
+ argumentValues(node) {
1433
+ const module = node.childForFieldName("module");
1434
+ if (module && module.type !== "package") return this.wordValues(module);
1435
+ const values = [];
1436
+ for (const arg of this.argumentNodes(node)) values.push(...this.wordValues(arg));
1437
+ return values;
1438
+ }
1439
+ /**
1440
+ * The fat-comma KEYS of a list: the named child immediately followed by a
1441
+ * `=>` token. Read from the child list, so `A => 1, B => 2` yields A and B
1442
+ * and never the values.
1443
+ */
1444
+ fatCommaKeys(node) {
1445
+ let list;
1446
+ if (node.type === "list_expression") list = node;
1447
+ else {
1448
+ list = this.findChildByType(node, "list_expression");
1449
+ if (!list) {
1450
+ const hash = this.findChildByType(node, "anonymous_hash_expression");
1451
+ if (hash) list = this.findChildByType(hash, "list_expression");
1452
+ }
1453
+ }
1454
+ if (!list) return [];
1455
+ const keys = [];
1456
+ let pending = null;
1457
+ for (let i = 0; i < list.childCount; i++) {
1458
+ const child = list.child(i);
1459
+ if (!child) continue;
1460
+ if (child.type === "=>") {
1461
+ if (pending) keys.push(pending);
1462
+ pending = null;
1463
+ continue;
1464
+ }
1465
+ pending = child.isNamed ? child : pending;
1466
+ }
1467
+ return keys;
1468
+ }
1469
+ /** Names declared by `use constant ...` — the fat-comma keys. */
1470
+ constantDeclarationNames(node) {
1471
+ const result = [];
1472
+ for (const key of this.fatCommaKeys(node)) {
1473
+ const name = this.wordValues(key)[0];
1474
+ if (name) result.push({ name, node: key });
1475
+ }
1476
+ return result;
1477
+ }
1478
+ wordValues(node) {
1479
+ return perlWordValues(node);
1480
+ }
1481
+ firstValue(node) {
1482
+ const values = this.argumentValues(node);
1483
+ return values[0];
1484
+ }
1485
+ /**
1486
+ * M14 #863 REPAYMENT — the dead `childForFieldName("parameters")`.
1487
+ *
1488
+ * The pinned tree-sitter-perl grammar declares NO `parameters` field, so that
1489
+ * lookup returned undefined for every sub in every file and the lane shipped
1490
+ * 0 parameters across 29,640 symbols on 5,866 corpus files. A signature is the
1491
+ * `signature` NODE TYPE, whose children are `mandatory_parameter`,
1492
+ * `optional_parameter` and `slurpy_parameter`; each wraps the
1493
+ * scalar/array/hash node that carries the `varname`.
1494
+ *
1495
+ * The first version of this fix said FIELD here and reached for
1496
+ * `childForFieldName("signature")`, which is dead for the same reason the
1497
+ * original was — `signature` is not among the grammar's 44 fields either. The
1498
+ * working `findChildByType` beside it is what has been producing every
1499
+ * parameter all along, and the dead-literal audit is what finally said so. The
1500
+ * wording is corrected here because the wrong word is what produced the bug.
1501
+ */
1502
+ extractParameters(node) {
1503
+ const signature = this.findChildByType(node, "signature");
1504
+ if (!signature) return [];
1505
+ const result = [];
1506
+ for (let i = 0; i < signature.namedChildCount; i++) {
1507
+ const param = signature.namedChild(i);
1508
+ if (!param) continue;
1509
+ const kind = param.type;
1510
+ if (kind !== "mandatory_parameter" && kind !== "optional_parameter" && kind !== "slurpy_parameter") continue;
1511
+ const carrier = this.findChildByType(param, "scalar") || this.findChildByType(param, "array") || this.findChildByType(param, "hash");
1512
+ if (!carrier) continue;
1513
+ const varname = this.findChildByType(carrier, "varname");
1514
+ if (!varname) continue;
1515
+ result.push({ name: this.stripSigil(varname.text), isOptional: kind === "optional_parameter" });
1516
+ }
1517
+ return result;
1518
+ }
1519
+ /** `sub f :lvalue :method` — the grammar's `attributes` field. */
1520
+ extractAttributes(node) {
1521
+ const attrlist = node.childForFieldName("attributes") || this.findChildByType(node, "attrlist");
1522
+ if (!attrlist) return [];
1523
+ const result = [];
1524
+ for (let i = 0; i < attrlist.namedChildCount; i++) {
1525
+ const attribute = attrlist.namedChild(i);
1526
+ if (!attribute || attribute.type !== "attribute") continue;
1527
+ const nameNode = attribute.childForFieldName("name") || this.findChildByType(attribute, "attribute_name");
1528
+ const name = nameNode?.text || "";
1529
+ if (name) result.push(name);
1530
+ }
1531
+ return result;
1532
+ }
1533
+ /**
1534
+ * Does this receiver node NAME a class, as opposed to being a runtime value?
1535
+ *
1536
+ * The allowlist is the set of node types whose entire content is a name. A
1537
+ * `function_call_expression` is deliberately absent: its `function` field is
1538
+ * the name of a FUNCTION, and calling it yields a value, so
1539
+ * `use_module('X')->new` constructs nothing knowable. `ambiguous_function_
1540
+ * call_expression` IS present because that is the indirect-object spelling,
1541
+ * where the node's `function` field is the class.
1542
+ */
1543
+ /**
1544
+ * The CLASS half of an indirect-object call: `new Class @args` is exactly
1545
+ * `Class->new(@args)`, and the grammar spells the class as the `function`
1546
+ * FIELD of a nested `ambiguous_function_call_expression`.
1547
+ *
1548
+ * Read STRUCTURALLY, from the node's named children, rather than through
1549
+ * `argumentNodes` — that helper keeps an argument only when it contributes at
1550
+ * least one WORD, and this nested call contributes none whenever its own
1551
+ * arguments are a plain scalar or a list of literals. Measured: it returned
1552
+ * `[]` for `new Win32::ODBC $dbname` and `new Foo 1, 2` but DID return the
1553
+ * call for `new Win32::ODBC $x->{DSN}` — kept only because of a hash KEY, which
1554
+ * is the same accident that published the phantom class `DSN`. A structural
1555
+ * read is right for all three, and for the third shape (`new Foo;`, where the
1556
+ * class is a direct `bareword` child and there is no nested call at all) the
1557
+ * caller's existing path already reads the bareword correctly.
1558
+ */
1559
+ indirectObjectClass(node) {
1560
+ for (let i = 0; i < node.namedChildCount; i++) {
1561
+ const child = node.namedChild(i);
1562
+ if (!child || child.type !== "ambiguous_function_call_expression") continue;
1563
+ const fn = child.childForFieldName("function") || this.findChildByType(child, "function");
1564
+ if (fn?.text) return { name: fn.text, carrier: child };
1565
+ }
1566
+ return void 0;
1567
+ }
1568
+ namesAClass(node) {
1569
+ switch (node.type) {
1570
+ case "bareword":
1571
+ case "autoquoted_bareword":
1572
+ case "package":
1573
+ case "ambiguous_function_call_expression":
1574
+ return true;
1575
+ case "string_literal":
1576
+ case "interpolated_string_literal":
1577
+ return !node.text.includes("$");
1578
+ default:
1579
+ return false;
1580
+ }
1581
+ }
1582
+ invocantName(node) {
1583
+ if (node.type === "scalar" || node.type === "array" || node.type === "hash") {
1584
+ return node.text;
1585
+ }
1586
+ if (node.type === "function_call_expression" || node.type === "ambiguous_function_call_expression") {
1587
+ const fn = node.childForFieldName("function") || this.findChildByType(node, "function");
1588
+ return fn?.text || "";
1589
+ }
1590
+ return node.text;
1591
+ }
1592
+ isOption(value) {
1593
+ return value.length > 0 && value[0] === "-";
1594
+ }
1595
+ /**
1596
+ * The NAME an argument node writes, and only when it writes one literally.
1597
+ *
1598
+ * `'Class::Name'` and `Class::Name` are names. `$class`, `@classes` and
1599
+ * `"Pkg::$sub"` are not: the file states no name, so an edge built on one
1600
+ * would name a symbol the source never mentions. The interpolation test is
1601
+ * character comparison on the literal's own text, never a pattern -- the
1602
+ * grammar exposes the unquoted body under `content`, so there is nothing to
1603
+ * unquote here and nothing to strip.
1604
+ *
1605
+ * Returns undefined for every other node type, which is the refusal the
1606
+ * measured counts depend on: 170 `isa($var)` and 5 `DOES($var)` sites in
1607
+ * 5,127 files resolve to nothing rather than to a guess.
1608
+ */
1609
+ literalNameIn(node) {
1610
+ if (!node) return void 0;
1611
+ if (node.type !== "string_literal" && node.type !== "bareword" && node.type !== "package" && node.type !== "autoquoted_bareword") return void 0;
1612
+ if (node.text.includes("$")) return void 0;
1613
+ const content = node.childForFieldName("content") ?? this.findChildByType(node, "string_content");
1614
+ const text = String(content?.text ?? node.text ?? "").trim();
1615
+ return text.length ? text : void 0;
1616
+ }
1617
+ /**
1618
+ * The node a queried name is WRITTEN on, so the edge's `line_number` is the
1619
+ * line that names it and not the line the call starts on (M59.A7: the line
1620
+ * comes from the node that NAMES the edge). `literalNameIn` already decided
1621
+ * the name exists; this only locates it.
1622
+ */
1623
+ queriedNameNode(node) {
1624
+ if (!node) return void 0;
1625
+ return node.childForFieldName("content") ?? this.findChildByType(node, "string_content") ?? this.findChildByType(node, "varname") ?? node;
1626
+ }
1627
+ stripSigil(name) {
1628
+ if (!name) return name;
1629
+ const first = name[0];
1630
+ return first === "$" || first === "@" || first === "%" || first === "#" ? name.slice(1) : name;
1631
+ }
1632
+ leafName(name) {
1633
+ let trimmed = name;
1634
+ while (trimmed.length >= 2 && trimmed.charAt(trimmed.length - 1) === ":" && trimmed.charAt(trimmed.length - 2) === ":") trimmed = trimmed.slice(0, -2);
1635
+ const colon = trimmed.lastIndexOf("::");
1636
+ return colon >= 0 ? trimmed.slice(colon + 2) : trimmed;
1637
+ }
1638
+ /**
1639
+ * THE BASE A `leaf.member` TARGET NAMES — the single-dot, identifier-shaped owner the declared
1640
+ * cause channel is defined over, or null when the target cannot carry that claim at all.
1641
+ *
1642
+ * This is not a style choice, it is the channel's own precondition. The arbiter takes the owner
1643
+ * as everything before the LAST dot (`incoming-rule.ts` `targetOwner`) and accepts a stated base
1644
+ * only when it is IDENTIFIER-SHAPED and IS that owner
1645
+ * (`classifyEdge`: `const shaped = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(claim.base)` then
1646
+ * `const consistent = shaped && owner === claim.base`). A target carrying two dots, an empty
1647
+ * member, or an owner with `::` in it therefore cannot be given this claim: writing one there
1648
+ * reads `causeRefuted`, which is `causeFAIL` — a clause-4 defect, not a stated cause.
1649
+ *
1650
+ * The character test is the lane's own `isPerlIdentStart`/`isPerlIdentChar` rather than a regex
1651
+ * literal, because this file's contract forbids regex literals (proved by
1652
+ * `script/m14/tmp/perl-zero-regex.mts`), and reusing them keeps the predicate from drifting away
1653
+ * from the rest of the lane.
1654
+ */
1655
+ statedOwnerOf(target) {
1656
+ const dot = target.lastIndexOf(".");
1657
+ if (dot <= 0 || dot === target.length - 1) return null;
1658
+ if (target.indexOf(".") !== dot) return null;
1659
+ const owner = target.slice(0, dot);
1660
+ if (!isPerlIdentStart(owner[0])) return null;
1661
+ for (let i = 1; i < owner.length; i++) if (!isPerlIdentChar(owner[i])) return null;
1662
+ return owner;
1663
+ }
1664
+ /**
1665
+ * PUBLISH THE REASON THE LANE ALREADY COMPUTES, ON THE KEY THE GATE READS — the fleet's finding
1666
+ * (a), measured on this lane rather than assumed: every perl edge below confidence 1.0 carries a
1667
+ * machine-readable `metadata.reasonCode` (M14 HONESTY-CONTRACT §4.1 — the 1,664 `contains` edges
1668
+ * are @1.0 and carry none), and the arbiter's bucket-3 column reads
1669
+ * `metadata.baseBeyondRunScope` ONLY (`incoming-rule.ts` `CAUSE_CLAIM_KEY`). So the gate printed
1670
+ * `causeOK 0`, `clause 2 ok — no edge carries a declared cause (residual 6998)`, and clause 2
1671
+ * passed VACUOUSLY over a 4,110-edge unresolved residual. See
1672
+ * `validation/m14/verification/PERL-FIX.md` for the bucketed residual and the measurements.
1673
+ *
1674
+ * WHAT THE CLAIM MEANS, and it is a claim about the RUN, not about the project: "the base this
1675
+ * edge names is beyond THIS RUN's reach" (`incoming-rule.ts`, `CAUSE_CLAIM_KEY`). Production
1676
+ * parses a perl file with NO project index and NO override companion
1677
+ * (`parallel-indexer.ts` `PROJECT_INDEX_EXTENSIONS` names only `.sol`), so a class another file
1678
+ * declares is genuinely out of this run's sight — which is exactly what the lane's own reason
1679
+ * codes already say (`first-parent-guess`: "the base does not declare it anywhere visible";
1680
+ * `name-only-callee`: "whether it binds to a sub in this file, an imported sub, or a
1681
+ * module-level sub is not decided at this edge"). Dart is the reference implementation of this
1682
+ * mechanism for the index-less case (`real-dart-analyzer.ts:2089-2091`).
1683
+ *
1684
+ * THE GUARD, and it is the difference between a stated cause and a laundering vector: the claim
1685
+ * is attached ONLY where the class the target names is NOT published by the file being analyzed.
1686
+ * When this file declares the class, the run DID reach it, and "beyond this run's reach" would be
1687
+ * FALSE — so `IOLoop.start` inside `package IOLoop` keeps its reason code and states nothing.
1688
+ * The arbiter cannot check this half (property 4 of the channel — a second implementation — is
1689
+ * explicitly not claimed to exist), so the analyzer must not state it.
1690
+ *
1691
+ * It can never move a resolution number. `classifyEdge` reads a cause ONLY inside its
1692
+ * `decls === 0` branch, so a claimed edge is already unresolved; and it is REFUTED outright if
1693
+ * the edge also carries an external claim — the lane publishes none, which is why the conflict
1694
+ * branch cannot fire here.
1695
+ */
1696
+ publishStatedCauses(symbols, relationships, qualifiedClassNames) {
1697
+ const declaredHere = /* @__PURE__ */ new Set();
1698
+ for (const symbol of symbols) {
1699
+ const kind = symbol.kind;
1700
+ if (kind !== ASTEngine.SymbolKind.Class && kind !== ASTEngine.SymbolKind.Trait) continue;
1701
+ const name = String(symbol.name ?? "");
1702
+ if (name) declaredHere.add(this.leafName(name));
1703
+ }
1704
+ const heldQualified = /* @__PURE__ */ new Set();
1705
+ for (const qualified of qualifiedClassNames) heldQualified.add(this.leafName(qualified));
1706
+ for (const relationship of relationships) {
1707
+ const type = relationship.type;
1708
+ if (type !== ASTEngine.RelationshipType.Calls && type !== ASTEngine.RelationshipType.Overrides && type !== ASTEngine.RelationshipType.Instantiates) continue;
1709
+ const metadata = relationship.metadata;
1710
+ if (!metadata || metadata.baseBeyondRunScope !== void 0) continue;
1711
+ const owner = this.statedOwnerOf(String(relationship.target ?? ""));
1712
+ if (owner === null || declaredHere.has(owner)) continue;
1713
+ if (heldQualified.has(owner)) continue;
1714
+ metadata.baseBeyondRunScope = owner;
1715
+ }
1716
+ }
1717
+ /**
1718
+ * PERL-1 — A DEPENDENCY THE FILE DOES NOT DECLARE IS A CLAIM, NOT SILENCE.
1719
+ *
1720
+ * The lane published no externality reading at all: its whole-population arm read **10,096 edges ·
1721
+ * 4,605 resolved · 9,435 ambiguous/unresolved · 0 verified · 0 refuted**, i.e. 93 % of the graph was
1722
+ * wall. The two largest unresolved labels are the two dependencies Perl names without any syntax of
1723
+ * its own:
1724
+ *
1725
+ * `use strict` / `use warnings` / `use Test::More` an IMPORT of a module or pragma
1726
+ * `is(…)` / `plan(…)` / `ok(…)` / `run_tests(…)` a BARE function call
1727
+ *
1728
+ * MEASURED: `imports` 1,654 unresolved (strict 328 · Test::More 267 · warnings 228 ·
1729
+ * Test::Nginx::Socket::Lua 120) and `calls` 2,966 unresolved (is 206 · plan 175 · run_tests 124 ·
1730
+ * repeat_each 122 · blocks 120 · ok 118). None of those names is declared by a corpus file that does
1731
+ * not already declare it, so every row was a dependency published with no reading at all.
1732
+ *
1733
+ * THE CLAIM IS SAFE BY CONSTRUCTION, exactly as on the powershell and haskell lanes: the arbiter
1734
+ * consults a claim only where the name model has NOTHING (`decls === 0`), so a name the project
1735
+ * declares keeps its resolved/ambiguous answer and this claim cannot refute it. The guard is the
1736
+ * file's own declaration table, so a local sub or a package this file declares keeps no claim.
1737
+ */
1738
+ publishExternalDependencies(symbols, relationships) {
1739
+ const declared = /* @__PURE__ */ new Set();
1740
+ for (const s of symbols) if (s.name) declared.add(s.name);
1741
+ for (const rel of relationships) {
1742
+ const type = rel.type;
1743
+ if (type !== ASTEngine.RelationshipType.Imports && type !== ASTEngine.RelationshipType.Calls && type !== ASTEngine.RelationshipType.Instantiates && type !== ASTEngine.RelationshipType.Extends && type !== ASTEngine.RelationshipType.Uses) continue;
1744
+ const md = rel.metadata;
1745
+ if (!md || md.externalTarget === true) continue;
1746
+ if (type === ASTEngine.RelationshipType.Calls && md.baseBeyondRunScope !== void 0) continue;
1747
+ const target = String(rel.target ?? "");
1748
+ if (target.length === 0) continue;
1749
+ if (!this.isPerlName(target)) continue;
1750
+ if (declared.has(target) || declared.has(this.leafName(target))) continue;
1751
+ if (target.indexOf("::") >= 0) {
1752
+ md.externalTarget = true;
1753
+ md.externalReason = "module-namespace";
1754
+ md.externalSpecifier = target;
1755
+ continue;
1756
+ }
1757
+ md.externalTarget = true;
1758
+ md.externalVia = type === ASTEngine.RelationshipType.Imports ? "use" : "call";
1759
+ }
1760
+ }
1761
+ /** A Perl module/function spelling: `strict`, `Test::More`, `is`, `run_tests`. */
1762
+ isPerlName(name) {
1763
+ const code = name.charCodeAt(0);
1764
+ const firstOk = code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
1765
+ if (!firstOk) return false;
1766
+ for (let i = 1; i < name.length; i++) {
1767
+ const c = name.charCodeAt(i);
1768
+ const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95 || c === 58;
1769
+ if (!ok) return false;
1770
+ }
1771
+ return !name.endsWith(":");
1772
+ }
1773
+ isAllCapsName(name) {
1774
+ let hasLetter = false;
1775
+ for (const char of name) {
1776
+ if (char >= "a" && char <= "z") return false;
1777
+ if (char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_") hasLetter = true;
1778
+ }
1779
+ return hasLetter;
1780
+ }
1781
+ findChildByType(node, type) {
1782
+ for (let i = 0; i < node.namedChildCount; i++) {
1783
+ const child = node.namedChild(i);
1784
+ if (child?.type === type) return child;
1785
+ }
1786
+ return void 0;
1787
+ }
1788
+ firstNamedChild(node) {
1789
+ return node.namedChildCount > 0 ? node.namedChild(0) || void 0 : void 0;
1790
+ }
1791
+ /**
1792
+ * Cyclomatic decision points, counted from the pinned grammar's OWN node
1793
+ * types (probed: script/m14/tmp/perl-branch-shapes.ts).
1794
+ *
1795
+ * The pre-review set named `if_statement`, `unless_statement`,
1796
+ * `while_statement`, `until_statement`, `foreach_statement` and
1797
+ * `ternary_expression` — SIX literals the grammar does not declare, so only
1798
+ * `for_statement` and `conditional_expression` ever fired and
1799
+ * cyclomaticComplexity was 1 for almost every real file. This grammar spells
1800
+ * them `conditional_statement` (if/unless), `loop_statement` (while/until/
1801
+ * bare foreach), `for_statement`, `cstyle_for_statement`,
1802
+ * `conditional_expression` (ternary), `postfix_conditional_expression`
1803
+ * (statement modifier) and `try_statement`; `elsif` and `catch` are further
1804
+ * decisions in their own chains.
1805
+ */
1806
+ countBranches(root) {
1807
+ const branchTypes = /* @__PURE__ */ new Set([
1808
+ "conditional_statement",
1809
+ "elsif",
1810
+ "loop_statement",
1811
+ "for_statement",
1812
+ "cstyle_for_statement",
1813
+ "conditional_expression",
1814
+ "postfix_conditional_expression",
1815
+ "try_statement",
1816
+ "catch",
1817
+ "when",
1818
+ "given"
1819
+ ]);
1820
+ const logicalTypes = /* @__PURE__ */ new Set(["lowprec_logical_expression", "logical_not_expression"]);
1821
+ let count = 0;
1822
+ const stack = [root];
1823
+ while (stack.length > 0) {
1824
+ const node = stack.pop();
1825
+ if (branchTypes.has(node.type)) count++;
1826
+ else if (logicalTypes.has(node.type)) {
1827
+ for (let i = 0; i < node.childCount; i++) {
1828
+ const child = node.child(i);
1829
+ if (child && (child.type === "&&" || child.type === "||" || child.type === "and" || child.type === "or")) {
1830
+ count++;
1831
+ break;
1832
+ }
1833
+ }
1834
+ }
1835
+ for (let i = 0; i < node.namedChildCount; i++) {
1836
+ const child = node.namedChild(i);
1837
+ if (child) stack.push(child);
1838
+ }
1839
+ }
1840
+ return count;
1841
+ }
1842
+ lineCount(content) {
1843
+ let lines = 1;
1844
+ for (const char of content) if (char === "\n") lines++;
1845
+ return lines;
1846
+ }
1847
+ nodeToRange(node) {
1848
+ return {
1849
+ startLine: node.startPosition.row + 1,
1850
+ startColumn: node.startPosition.column + 1,
1851
+ endLine: node.endPosition.row + 1,
1852
+ endColumn: node.endPosition.column + 1,
1853
+ startIndex: node.startIndex,
1854
+ endIndex: node.endIndex
1855
+ };
1856
+ }
1857
+ };
1858
+ export {
1859
+ PERL_BUILTINS,
1860
+ PERL_REASON_CODES,
1861
+ RealPerlAnalyzer,
1862
+ hasArgumentSyntax,
1863
+ perlArgumentNodes,
1864
+ perlWordValues
1865
+ };