@backbencher/brain 0.0.0-stage → 0.1.0

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-KY7K552E.js +17 -0
  8. package/dist/brain-relationship-enrichment-QH7OOHUF.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-2BSKD7O3.js +40 -0
  11. package/dist/chunk-2DE27DVC.js +1314 -0
  12. package/dist/chunk-2RRBMNBQ.js +322 -0
  13. package/dist/chunk-2RTN2DFU.js +711 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3LFTSMIV.js +174 -0
  17. package/dist/chunk-47HKX4AJ.js +930 -0
  18. package/dist/chunk-4H22TLOT.js +543 -0
  19. package/dist/chunk-5SD45FLC.js +818 -0
  20. package/dist/chunk-66A5ZFYA.js +1758 -0
  21. package/dist/chunk-6AIRWAO2.js +247 -0
  22. package/dist/chunk-6WNGD3NN.js +196 -0
  23. package/dist/chunk-7AN7U5IZ.js +371 -0
  24. package/dist/chunk-BHB7BYYI.js +626 -0
  25. package/dist/chunk-BQ3OVQ3P.js +125 -0
  26. package/dist/chunk-CW4XXSNN.js +1479 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DYM4FQEE.js +90 -0
  30. package/dist/chunk-EWMWK7WN.js +75 -0
  31. package/dist/chunk-FVVQTSIM.js +3427 -0
  32. package/dist/chunk-FWSL4VG6.js +1290 -0
  33. package/dist/chunk-GBI7V7KY.js +69 -0
  34. package/dist/chunk-GEFFO6O2.js +152 -0
  35. package/dist/chunk-HOWAQXJO.js +236 -0
  36. package/dist/chunk-HQSPJDHI.js +613 -0
  37. package/dist/chunk-IFZNR5UJ.js +2863 -0
  38. package/dist/chunk-J4PMBXQQ.js +336 -0
  39. package/dist/chunk-KAET47ZL.js +125 -0
  40. package/dist/chunk-KCDECRJY.js +440 -0
  41. package/dist/chunk-KLK3PBTO.js +1119 -0
  42. package/dist/chunk-L5TDYKYK.js +76 -0
  43. package/dist/chunk-MLKGABMK.js +9 -0
  44. package/dist/chunk-OWISBAOF.js +61 -0
  45. package/dist/chunk-PJTNGD3Z.js +6924 -0
  46. package/dist/chunk-QLK6ED4Q.js +235 -0
  47. package/dist/chunk-R2GW4F6R.js +19 -0
  48. package/dist/chunk-RCMTFSEF.js +87 -0
  49. package/dist/chunk-RH7KC5HU.js +12050 -0
  50. package/dist/chunk-RM3PY6TG.js +376 -0
  51. package/dist/chunk-ROMM6KMC.js +433 -0
  52. package/dist/chunk-SK6YVHFZ.js +211 -0
  53. package/dist/chunk-SM7RFQRO.js +214 -0
  54. package/dist/chunk-UFE77L67.js +782 -0
  55. package/dist/chunk-UGR4QIGS.js +67 -0
  56. package/dist/chunk-UUNHHX4M.js +13 -0
  57. package/dist/chunk-V44PGPA6.js +2104 -0
  58. package/dist/chunk-VL52PL2T.js +71 -0
  59. package/dist/chunk-VURYFIHW.js +201 -0
  60. package/dist/chunk-XJKJYIPP.js +7436 -0
  61. package/dist/chunk-YI6FF2ZR.js +340 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-642F7E5V.js +593 -0
  65. package/dist/database-TCTZDLE5.js +9 -0
  66. package/dist/doctor-GXCKSJOX.js +395 -0
  67. package/dist/edit-engine-KM7MPFEC.js +51 -0
  68. package/dist/engine-42TZFNIZ.js +11 -0
  69. package/dist/engine-LO6L4JLQ.js +18 -0
  70. package/dist/fingerprint-XDXFRWO6.js +33 -0
  71. package/dist/framework-intelligence-DWFCKV44.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-PL5YA7BA.js +21 -0
  74. package/dist/graph_request-MM4REP6H.js +350 -0
  75. package/dist/impact_request-WDE47IZ5.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-6BD7UBHR.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-4PW3AFXP.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-ZS5TS2Z6.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-DGZBIZS7.js +355 -0
  90. package/dist/real-bash-analyzer-SXSV67E2.js +2201 -0
  91. package/dist/real-c-analyzer-SY7FTZ6W.js +5942 -0
  92. package/dist/real-clean-analyzer-IGDHHLZK.js +3470 -0
  93. package/dist/real-clojure-analyzer-4OH7IWLB.js +2818 -0
  94. package/dist/real-cpp-analyzer-7LTDRJRJ.js +4101 -0
  95. package/dist/real-csharp-analyzer-KICU3TLH.js +3298 -0
  96. package/dist/real-css-analyzer-HPU277Q6.js +15 -0
  97. package/dist/real-dart-analyzer-7LZ5LCMW.js +3944 -0
  98. package/dist/real-elixir-analyzer-BE3MEVZO.js +2192 -0
  99. package/dist/real-erlang-analyzer-BZMAKYBK.js +1731 -0
  100. package/dist/real-fsharp-analyzer-5QLTOV3E.js +2390 -0
  101. package/dist/real-go-analyzer-POD7MZQR.js +4126 -0
  102. package/dist/real-graphql-analyzer-MAQRZ6QW.js +1442 -0
  103. package/dist/real-groovy-analyzer-MEWSUPMJ.js +2285 -0
  104. package/dist/real-haskell-analyzer-I5L2GR3Y.js +2533 -0
  105. package/dist/real-hcl-analyzer-DBOPXSWJ.js +725 -0
  106. package/dist/real-html-analyzer-O63XZLZD.js +1759 -0
  107. package/dist/real-java-analyzer-CHCW3EKA.js +4677 -0
  108. package/dist/real-javascript-analyzer-QAAJQYZE.js +14 -0
  109. package/dist/real-json-analyzer-UGBR7YQN.js +1640 -0
  110. package/dist/real-julia-analyzer-35Q4GHOZ.js +2085 -0
  111. package/dist/real-kotlin-analyzer-GRAYCWW2.js +3956 -0
  112. package/dist/real-lua-analyzer-5BAPDZDF.js +1957 -0
  113. package/dist/real-makefile-analyzer-YJKAHNAS.js +920 -0
  114. package/dist/real-markdown-analyzer-UKUQZO4F.js +1215 -0
  115. package/dist/real-matlab-analyzer-FVIKQZKG.js +480 -0
  116. package/dist/real-nim-analyzer-M7FAIABG.js +3603 -0
  117. package/dist/real-objective-c-analyzer-XMYQIGQI.js +1518 -0
  118. package/dist/real-perl-analyzer-ZLY6M2DZ.js +1865 -0
  119. package/dist/real-php-analyzer-KI2ZAM7X.js +9368 -0
  120. package/dist/real-powershell-analyzer-B3T5ZP6U.js +2915 -0
  121. package/dist/real-protobuf-analyzer-23OZ75XV.js +867 -0
  122. package/dist/real-python-analyzer-I4TIUB6D.js +4880 -0
  123. package/dist/real-r-analyzer-FJZJXXS6.js +2596 -0
  124. package/dist/real-ruby-analyzer-M3UFIVTQ.js +2429 -0
  125. package/dist/real-rust-analyzer-LVSONPAN.js +3359 -0
  126. package/dist/real-scala-analyzer-CWPI33Q2.js +3293 -0
  127. package/dist/real-scss-analyzer-TGRJQ7TH.js +1515 -0
  128. package/dist/real-solidity-analyzer-4P2VBVWA.js +2608 -0
  129. package/dist/real-sql-analyzer-J7DYZOZR.js +3840 -0
  130. package/dist/real-svelte-analyzer-NHEN2SX5.js +2532 -0
  131. package/dist/real-swift-analyzer-2NSYULWI.js +2311 -0
  132. package/dist/real-toml-analyzer-L3C66L7Q.js +929 -0
  133. package/dist/real-typescript-analyzer-FTC6ETRQ.js +14 -0
  134. package/dist/real-vb-analyzer-ORY7LAQS.js +3145 -0
  135. package/dist/real-vba-analyzer-JH2TTUFI.js +466 -0
  136. package/dist/real-vue-analyzer-OO4FUGBV.js +2680 -0
  137. package/dist/real-xml-analyzer-6CQVR477.js +2405 -0
  138. package/dist/real-yaml-analyzer-J6MQG3YX.js +3528 -0
  139. package/dist/real-zig-analyzer-EFKCPJNU.js +2430 -0
  140. package/dist/regex-search-OPTLKUN5.js +239 -0
  141. package/dist/rules-I453JTRC.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-Y5GB5KXA.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-KB4MU4SI.js +16 -0
  146. package/dist/taint-YOTN5X55.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-G4NAGJIH.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-RF675A3C.js +32 -0
  151. package/dist/type-inference-4Y7WTMFE.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-LXGNFO37.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,2533 @@
1
+ import {
2
+ ASTEngine
3
+ } from "./chunk-V44PGPA6.js";
4
+ import "./chunk-BQ3OVQ3P.js";
5
+ import "./chunk-SK6YVHFZ.js";
6
+ import "./chunk-2BSKD7O3.js";
7
+ import {
8
+ BINDING_SEP
9
+ } from "./chunk-KAET47ZL.js";
10
+ import "./chunk-KCDECRJY.js";
11
+ import "./chunk-MLKGABMK.js";
12
+
13
+ // src/ast/real-haskell-analyzer.ts
14
+ var HASKELL_DECLARATION_KEYWORDS = /* @__PURE__ */ new Set([
15
+ "type",
16
+ "data",
17
+ "newtype",
18
+ "class",
19
+ "instance",
20
+ "family",
21
+ "pattern",
22
+ "proc",
23
+ "default",
24
+ "module"
25
+ ]);
26
+ var HASKELL_TYPE_BEARING_NODES = /* @__PURE__ */ new Set([
27
+ "type",
28
+ "signature",
29
+ "type_synomym",
30
+ "type_synonym",
31
+ "data_type",
32
+ "newtype",
33
+ "type_patterns",
34
+ "type_family",
35
+ "type_instance",
36
+ "deriving",
37
+ "deriving_instance",
38
+ "class",
39
+ "instance",
40
+ "strict_field",
41
+ "field_pattern",
42
+ "forall",
43
+ "quantified_variables",
44
+ "promoted",
45
+ "type_params",
46
+ "constraint",
47
+ "context",
48
+ "deriving_strategy",
49
+ "via",
50
+ "kind_signature",
51
+ "arity",
52
+ "kind",
53
+ "children"
54
+ ]);
55
+ var RealHaskellAnalyzer = class {
56
+ analyze(rootNode, content, filePath) {
57
+ const ctx = {
58
+ symbols: [],
59
+ relationships: [],
60
+ imports: [],
61
+ exports: [],
62
+ errors: [],
63
+ patterns: {},
64
+ frameworks: {},
65
+ typeSignatures: {},
66
+ functionSymbolIndex: {},
67
+ declaredFunctions: /* @__PURE__ */ new Set(),
68
+ declaredTypes: /* @__PURE__ */ new Set(),
69
+ declaredConstructors: /* @__PURE__ */ new Set(),
70
+ importAliases: /* @__PURE__ */ new Map(),
71
+ annPragmas: 0,
72
+ classMethods: /* @__PURE__ */ new Map(),
73
+ localScopes: { callableScoped: /* @__PURE__ */ new Map(), blockLocals: /* @__PURE__ */ new Map(), frameLocals: /* @__PURE__ */ new Map() },
74
+ // A file with no `module` header is module `Main` — the language rule.
75
+ // Defaulted HERE, before the walk, because the import edge is pushed during
76
+ // it and read `currentModule || ""`; defaulting in the post-loop was too
77
+ // late and still produced `imports -> Distribution.Simple` with no source.
78
+ currentModule: "Main",
79
+ currentFunction: void 0,
80
+ seenRels: /* @__PURE__ */ new Set()
81
+ };
82
+ const scopeStack = [];
83
+ const stack = [{ node: rootNode }];
84
+ let cyclomaticComplexity = 1;
85
+ this.collectDeclaredFunctions(rootNode, ctx.declaredFunctions);
86
+ this.collectDeclaredTypes(rootNode, ctx.declaredTypes);
87
+ this.collectDeclaredConstructors(rootNode, ctx.declaredConstructors);
88
+ this.collectClassMethods(rootNode, ctx.classMethods);
89
+ this.collectLocalScopes(rootNode, ctx.localScopes);
90
+ while (stack.length > 0) {
91
+ const { node, isExit, restoreFn } = stack.pop();
92
+ if (isExit) {
93
+ if (restoreFn !== void 0) ctx.currentFunction = restoreFn;
94
+ else if (node.type === "function" || node.type === "bind" || node.type === "class" || node.type === "instance" || node.type === "data" || node.type === "newtype") {
95
+ ctx.currentFunction = void 0;
96
+ }
97
+ if (scopeStack.length > 0 && scopeStack[scopeStack.length - 1].node === node) scopeStack.pop();
98
+ continue;
99
+ }
100
+ if (node.hasError || node.isMissing) {
101
+ if (node.type === "ERROR" || node.type === "MISSING") {
102
+ ctx.errors.push({
103
+ message: `Syntax ${node.type} detected.`,
104
+ range: this.nodeToRange(node),
105
+ severity: "warning"
106
+ });
107
+ }
108
+ }
109
+ let pushedScope = false;
110
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
111
+ if (node.type === "case" || node.type === "conditional" || node.type === "guards") {
112
+ cyclomaticComplexity++;
113
+ }
114
+ if (node.type === "do") ctx.patterns["Monads"] = true;
115
+ if (node.type === "list_comprehension") ctx.patterns["List Comprehensions"] = true;
116
+ if (node.type === "case" || node.type === "patterns") ctx.patterns["Pattern Matching"] = true;
117
+ if (node.type === "header") {
118
+ pushedScope = this.processModule(node, ctx, scopeStack);
119
+ } else if (node.type === "import") {
120
+ this.processImport(node, ctx);
121
+ } else if (node.type === "signature") {
122
+ const p = node.parent?.type;
123
+ const isDeclSig = p === "declarations" || p === "local_binds" || p === "class" || p === "instance" || p === "class_declarations" || p === "instance_declarations";
124
+ if (isDeclSig) {
125
+ this.processSignature(node, ctx, currentScope, currentScope?.kind === ASTEngine.SymbolKind.Namespace);
126
+ continue;
127
+ }
128
+ const expr = node.namedChildCount > 0 ? node.namedChild(0) : null;
129
+ if (expr) stack.push({ node: expr });
130
+ continue;
131
+ } else if (node.type === "function" || node.type === "bind") {
132
+ const hasArrowBind = this.hasChildOfType(node, "<-");
133
+ const localScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
134
+ const insideLocalBlock = localScope?.kind === ASTEngine.SymbolKind.Namespace;
135
+ if (hasArrowBind) {
136
+ stack.push({ node, isExit: true, restoreFn: ctx.currentFunction });
137
+ } else if (!insideLocalBlock) {
138
+ pushedScope = this.processFunction(node, ctx, currentScope, scopeStack);
139
+ if (!pushedScope && !(node.type === "function" && (node.childForFieldName("parameter") || node.childForFieldName("result")))) {
140
+ stack.push({ node, isExit: true, restoreFn: ctx.currentFunction });
141
+ }
142
+ } else if (node.type === "function") {
143
+ stack.push({ node, isExit: true, restoreFn: ctx.currentFunction });
144
+ const head = node.namedChildCount > 0 ? node.namedChild(0) : null;
145
+ let localName;
146
+ if (head?.type === "variable") {
147
+ localName = head.text;
148
+ } else if (head && (head.type === "infix" || head.type === "prefix_id")) {
149
+ const op = this.findNamedChildByType(head, ["operator"])?.text;
150
+ if (op) {
151
+ localName = op;
152
+ } else {
153
+ const infixId = this.findNamedChildByType(head, ["infix_id"]);
154
+ const tv = infixId ? this.findNamedChildByType(infixId, ["variable"]) : void 0;
155
+ if (tv?.text) localName = tv.text;
156
+ }
157
+ }
158
+ const scopedLocal = ctx.localScopes.callableScoped.get(node.id) ?? localName;
159
+ ctx.currentFunction = scopedLocal;
160
+ scopeStack.push({
161
+ node,
162
+ name: scopedLocal ?? "local",
163
+ kind: ASTEngine.SymbolKind.Namespace,
164
+ locals: ctx.localScopes.frameLocals.get(node.id),
165
+ isFrame: true
166
+ });
167
+ } else {
168
+ stack.push({ node, isExit: true, restoreFn: ctx.currentFunction });
169
+ const head = node.namedChildCount > 0 ? node.namedChild(0) : null;
170
+ const scopedLocal = ctx.localScopes.callableScoped.get(node.id) ?? (head?.type === "variable" ? head.text : void 0);
171
+ if (scopedLocal !== void 0) {
172
+ ctx.currentFunction = scopedLocal;
173
+ scopeStack.push({
174
+ node,
175
+ name: scopedLocal,
176
+ kind: ASTEngine.SymbolKind.Namespace,
177
+ locals: ctx.localScopes.frameLocals.get(node.id),
178
+ isFrame: true
179
+ });
180
+ }
181
+ }
182
+ } else if (node.type === "let" || node.type === "local_binds" || node.type === "list_comprehension") {
183
+ const blockLocals = ctx.localScopes.blockLocals.get(node.id);
184
+ if (blockLocals && this.isForwardVisibleLocalBlock(node)) {
185
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
186
+ if (scopeStack[i].isFrame) {
187
+ const fm = scopeStack[i].locals ?? /* @__PURE__ */ new Map();
188
+ for (const [k, v] of blockLocals) fm.set(k, v);
189
+ scopeStack[i].locals = fm;
190
+ break;
191
+ }
192
+ }
193
+ }
194
+ scopeStack.push({ node, name: "local", kind: ASTEngine.SymbolKind.Namespace, locals: blockLocals });
195
+ pushedScope = true;
196
+ } else if (node.type === "class") {
197
+ pushedScope = this.processClass(node, ctx, scopeStack);
198
+ } else if (node.type === "instance") {
199
+ pushedScope = this.processInstance(node, ctx, scopeStack);
200
+ } else if (node.type === "data_type" || node.type === "newtype") {
201
+ pushedScope = this.processData(node, ctx, scopeStack);
202
+ } else if (node.type === "type_synomym") {
203
+ pushedScope = this.processTypeSynonym(node, ctx, scopeStack);
204
+ } else if (node.type === "pattern_synonym") {
205
+ pushedScope = this.processPatternSynonym(node, ctx);
206
+ } else if (node.type === "foreign_import") {
207
+ pushedScope = this.processForeignImport(node, ctx, scopeStack);
208
+ } else if (node.type === "deriving_instance") {
209
+ const grab = (nn) => {
210
+ let cls;
211
+ let clsNode = null;
212
+ let target;
213
+ for (let i = 0; i < nn.namedChildCount; i++) {
214
+ const c = nn.namedChild(i);
215
+ if (!c) continue;
216
+ if (c.type === "name" || c.type === "qualified") {
217
+ cls = c.text;
218
+ clsNode = c;
219
+ } else if (c.type === "apply") {
220
+ const h = c.namedChildCount > 0 ? c.namedChild(0) : null;
221
+ if (h?.text) {
222
+ cls = h.text;
223
+ clsNode = h;
224
+ }
225
+ } else if (c.type === "type_patterns") {
226
+ const t2 = this.findNamedChildByType(c, ["name", "constructor"]);
227
+ target = t2?.text;
228
+ } else if (c.type === "parens") grab(c);
229
+ }
230
+ if (cls && target) {
231
+ this.pushRel(
232
+ ctx,
233
+ target,
234
+ cls,
235
+ ASTEngine.RelationshipType.Implements,
236
+ 1,
237
+ {
238
+ derived: true,
239
+ isStandaloneDeriving: true
240
+ },
241
+ this.lineOf(clsNode)
242
+ );
243
+ }
244
+ };
245
+ grab(node);
246
+ } else if (node.type === "record") {
247
+ const ctor = this.findNamedChildByType(node, ["constructor"]);
248
+ if (ctor?.text && ctx.currentFunction && !this.isDataDeclarationPosition(node) && !this.isPatternPosition(node)) {
249
+ this.pushRel(
250
+ ctx,
251
+ ctx.currentFunction,
252
+ ctor.text,
253
+ ASTEngine.RelationshipType.Instantiates,
254
+ 0.9,
255
+ {
256
+ haskellSpecific: true,
257
+ isRecordConstruction: true
258
+ },
259
+ this.lineOf(ctor)
260
+ );
261
+ }
262
+ } else if (node.type === "apply") {
263
+ this.extractApplyCall(node, ctx, scopeStack);
264
+ } else if (node.type === "infix") {
265
+ this.processInfix(node, ctx, scopeStack);
266
+ } else if (node.type === "prefix_id") {
267
+ this.emitOperatorApplication(node, ctx, scopeStack);
268
+ } else if (node.type === "top_splice") {
269
+ this.decorateFromSplice(node, ctx);
270
+ } else if (node.type === "pragma") {
271
+ this.countPragmas(node, ctx);
272
+ } else if (node.type === "cpp") {
273
+ this.processCppDirective(node, ctx);
274
+ }
275
+ if (pushedScope) stack.push({ node, isExit: true });
276
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
277
+ const child = node.namedChild(i);
278
+ if (child) stack.push({ node: child });
279
+ }
280
+ }
281
+ const frameworks = [];
282
+ for (const f in ctx.frameworks) frameworks.push(f);
283
+ const patterns = [];
284
+ for (const p in ctx.patterns) patterns.push(p);
285
+ const frameworkPatterns = [];
286
+ for (let i = 0; i < frameworks.length; i++) {
287
+ frameworkPatterns.push({
288
+ framework: frameworks[i],
289
+ patterns: [{ type: "usage", detected: true, description: `${frameworks[i]} usage detected` }]
290
+ });
291
+ }
292
+ if (!ctx.symbols.some((s) => s.kind === ASTEngine.SymbolKind.Module)) {
293
+ ctx.symbols.push({
294
+ name: "Main",
295
+ kind: ASTEngine.SymbolKind.Module,
296
+ range: this.nodeToRange(rootNode),
297
+ scope: "",
298
+ ownerName: "",
299
+ visibility: "public",
300
+ metadata: { haskellSpecific: true, isImplicitMain: true }
301
+ });
302
+ for (const s of ctx.symbols) {
303
+ if (s.kind !== ASTEngine.SymbolKind.Module && !s.ownerName && !s.scope) s.ownerName = "Main";
304
+ }
305
+ }
306
+ const declaredHere = /* @__PURE__ */ new Set();
307
+ for (const s of ctx.symbols) {
308
+ if (s.ownerName && s.ownerName !== ctx.currentModule) continue;
309
+ if (s.kind === ASTEngine.SymbolKind.Class || s.kind === ASTEngine.SymbolKind.Type || // `TypeAlias` is a legacy ALIAS for `type` -- both members carry the value
310
+ // "type" (see SymbolKind in engine.ts) -- so comparing it here tested the
311
+ // same value twice. TypeScript collapses same-valued enum members, which is
312
+ // what made this comparison an error rather than a redundancy.
313
+ s.kind === ASTEngine.SymbolKind.Interface || s.kind === ASTEngine.SymbolKind.Enum) declaredHere.add(s.name);
314
+ }
315
+ for (const s of ctx.symbols) {
316
+ const _owner = s.ownerName || (s.scope && s.scope !== ctx.currentModule ? s.scope : void 0);
317
+ if (_owner && _owner !== ctx.currentModule && s.kind === ASTEngine.SymbolKind.Method && !declaredHere.has(_owner)) continue;
318
+ if (_owner && _owner !== s.name && s.kind !== ASTEngine.SymbolKind.Instance) {
319
+ this.pushRel(ctx, _owner, s.name, ASTEngine.RelationshipType.Contains, 1, {}, s.range?.startLine);
320
+ }
321
+ }
322
+ if (ctx.exports.length === 0) {
323
+ for (const s of ctx.symbols) {
324
+ if (s.ownerName === ctx.currentModule || !s.ownerName && !s.scope) {
325
+ ctx.exports.push({ name: s.name, kind: s.kind, range: s.range, visibility: "public" });
326
+ }
327
+ }
328
+ }
329
+ const localIdentities = /* @__PURE__ */ new Set();
330
+ for (const block of ctx.localScopes.blockLocals.values()) {
331
+ for (const identity of block.values()) localIdentities.add(identity);
332
+ }
333
+ if (localIdentities.size > 0) {
334
+ for (const rel of ctx.relationships) {
335
+ const target = rel.target ?? "";
336
+ if (!localIdentities.has(target)) continue;
337
+ rel.target = `${rel.source ?? ""}${BINDING_SEP}${target}`;
338
+ rel.metadata = { ...rel.metadata ?? {}, localBinding: true };
339
+ }
340
+ }
341
+ this.classifyEvidenceTier(ctx.relationships);
342
+ return {
343
+ language: "haskell",
344
+ symbols: ctx.symbols,
345
+ relationships: ctx.relationships,
346
+ imports: ctx.imports,
347
+ exports: ctx.exports,
348
+ errors: ctx.errors,
349
+ metadata: {
350
+ complexity: cyclomaticComplexity,
351
+ linesOfCode: content.split("\n").length,
352
+ cyclomaticComplexity,
353
+ framework: frameworks[0],
354
+ patterns,
355
+ detectedFrameworks: frameworks,
356
+ frameworkPatterns,
357
+ architecturalScore: Math.min(100, Math.max(0, 100 - ctx.errors.length * 5)),
358
+ codeQuality: ctx.errors.length === 0 ? "Excellent" : "Fair",
359
+ hasTypeInference: true,
360
+ hasFrameworkIntelligence: true,
361
+ analysisVersion: "10.1.0-haskell-pure-ast",
362
+ // The passive-metadata pragma class, disclosed as a count. `{-# ANN #-}`
363
+ // attaches a value to a target and applies no callable, so it emits no
364
+ // relationship; the number is here so the omission is a stated class and
365
+ // not a silence a reader has to interpret.
366
+ ...ctx.annPragmas > 0 ? { annPragmaSites: ctx.annPragmas } : {}
367
+ }
368
+ };
369
+ }
370
+ // ==========================================================================
371
+ // Relationship emission (dedupe by source|type|target identity).
372
+ // ==========================================================================
373
+ // M59.A7 / formula §8 (dimension 8) — the site line, the clean/erlang
374
+ // contract verbatim:
375
+ // * the line is part of the DEDUPE KEY: a function called from three lines
376
+ // is three edges at three lines, not one edge at whichever line the walk
377
+ // saw first (the store keys its rows the same way, parallel-indexer.ts);
378
+ // * the anchor is the NAME-BEARING node — the callee head for a call, the
379
+ // constructed name for an `instantiates`, the class name for
380
+ // `implements`/`extends`, the imported module name for `references`, and
381
+ // the child's OWN naming line for `contains` (#921: it is what resolves
382
+ // the intended target among same-named siblings);
383
+ // * a line that is not a positive integer is recorded as ABSENT, never as 0
384
+ // (M59.O2: the contract is "the site line or NULL" — a fabricated 0 reads
385
+ // as "line 0" to every reader downstream).
386
+ pushRel(ctx, source, target, type, confidence, metadata = {}, line) {
387
+ const hasLine = typeof line === "number" && Number.isInteger(line) && line > 0;
388
+ const key = `${source}|${type}|${target}|${hasLine ? line : ""}`;
389
+ if (ctx.seenRels.has(key)) return;
390
+ ctx.seenRels.add(key);
391
+ ctx.relationships.push({
392
+ source,
393
+ target,
394
+ type,
395
+ confidence,
396
+ // HSA1 — the claim rides every row this writer publishes, so a new emission site cannot
397
+ // silently drop it (see `bareExternalClaim`).
398
+ metadata: { ...metadata, ...this.bareExternalClaim(ctx, type, target) },
399
+ ...hasLine ? { line_number: line } : {}
400
+ });
401
+ }
402
+ /**
403
+ * HSA1 — A DEPENDENCY THE FILE DOES NOT DECLARE IS A CLAIM, NOT SILENCE.
404
+ *
405
+ * The lane's wall after its declaration wave was 60,898 ambiguous/unresolved of 193,418 edges, and
406
+ * the two largest classes are the two dependencies Haskell spells as a BARE CAPITALISED NAME:
407
+ *
408
+ * `instance Show Foo where …` the class `Show` lives in `base`, not in this project
409
+ * `Just x`, `Left e`, `Right v` the constructors are the language's own
410
+ *
411
+ * MEASURED over the whole admitted population (3,702 files / 6 repos): `implements` 11,563
412
+ * unresolved, led by `Show` 2,459 / `Eq` 1,899 / `Generic` 1,404 / `Ord` 703 / `ToJSON` 662 /
413
+ * `FromJSON` 641 / `NFData` 530 / `Hashable` 327; and `instantiates` 10,978 unresolved, led by
414
+ * `Just` 3,657 / `Right` 1,017 / `Left` 617. None of those names can be declared by a corpus file
415
+ * that does not already declare it, so every row was a dependency published with NO reading at all.
416
+ *
417
+ * THE CLAIM IS THE FLEET'S `externalTarget`, and it is SAFE BY CONSTRUCTION rather than by care: the
418
+ * arbiter consults a claim only where the name model has NOTHING (`decls === 0`), so a name the
419
+ * project really does declare keeps its resolved/ambiguous answer and can never be refuted by this
420
+ * row. A REFUTATION requires `decls > 0` on an UNRESOLVED row, which this claim cannot reach.
421
+ *
422
+ * THREE REFUSALS, each because the spelling is not a bare declaration name:
423
+ * · a QUALIFIED target (`Csv.ToNamedRecord`, `T.unpack`) — a module path, and the arbiter's own
424
+ * `importPath` claim is the one that reads it; a second claim would be a different question.
425
+ * · a target that is not capitalised — Haskell type and data constructors are upper-case, so a
426
+ * lower-case name here is a parse artifact, not a type.
427
+ * · a name THIS FILE declares — the row is a local dependency and must resolve, not claim.
428
+ */
429
+ bareExternalClaim(ctx, type, target) {
430
+ if (type === ASTEngine.RelationshipType.Exports && target.length > 0) {
431
+ if (target === ctx.currentModule) return {};
432
+ if (ctx.declaredTypes.has(target) || ctx.declaredFunctions.has(target)) return {};
433
+ const first2 = target.charCodeAt(0);
434
+ const firstOk = first2 >= 65 && first2 <= 90 || first2 >= 97 && first2 <= 122 || first2 === 95;
435
+ if (!firstOk) return {};
436
+ for (let i = 1; i < target.length; i++) {
437
+ const c = target.charCodeAt(i);
438
+ const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95 || c === 39;
439
+ if (!ok) return {};
440
+ }
441
+ return { externalTarget: true, externalVia: "re-export" };
442
+ }
443
+ if (type === ASTEngine.RelationshipType.Calls) {
444
+ const dot = target.indexOf(".");
445
+ if (dot > 0) {
446
+ const mod = ctx.importAliases.get(target.slice(0, dot));
447
+ if (mod !== void 0 && mod.length > 0) {
448
+ return { externalTarget: true, externalReason: "module-namespace", externalSpecifier: mod };
449
+ }
450
+ return {};
451
+ }
452
+ return {};
453
+ }
454
+ if (type === ASTEngine.RelationshipType.Imports && target.length > 0) {
455
+ if (ctx.declaredTypes.has(target) || ctx.declaredFunctions.has(target)) return {};
456
+ return { externalTarget: true, externalReason: "module-namespace", externalSpecifier: target };
457
+ }
458
+ if (type === ASTEngine.RelationshipType.Implements) {
459
+ const dotI = target.indexOf(".");
460
+ if (dotI > 0) {
461
+ const mod = ctx.importAliases.get(target.slice(0, dotI));
462
+ if (mod !== void 0 && mod.length > 0) {
463
+ return { externalTarget: true, externalReason: "module-namespace", externalSpecifier: mod };
464
+ }
465
+ return {};
466
+ }
467
+ }
468
+ if (type === ASTEngine.RelationshipType.Uses) {
469
+ if (target.length === 0 || target.indexOf(".") >= 0) return {};
470
+ const firstU = target.charCodeAt(0);
471
+ if (!(firstU >= 65 && firstU <= 90)) return {};
472
+ for (let i = 1; i < target.length; i++) {
473
+ const c = target.charCodeAt(i);
474
+ const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95 || c === 39;
475
+ if (!ok) return {};
476
+ }
477
+ if (ctx.declaredTypes.has(target) || ctx.declaredFunctions.has(target)) return {};
478
+ return { externalTarget: true, externalVia: "type" };
479
+ }
480
+ if (type === ASTEngine.RelationshipType.Instantiates) {
481
+ const dot = target.indexOf(".");
482
+ if (dot > 0) {
483
+ const mod = ctx.importAliases.get(target.slice(0, dot));
484
+ if (mod !== void 0 && mod.length > 0) {
485
+ return { externalTarget: true, externalReason: "module-namespace", externalSpecifier: mod };
486
+ }
487
+ return {};
488
+ }
489
+ }
490
+ if (type === ASTEngine.RelationshipType.Extends) {
491
+ if (target.length === 0 || target.indexOf(".") >= 0) return {};
492
+ const firstE = target.charCodeAt(0);
493
+ if (!(firstE >= 65 && firstE <= 90)) return {};
494
+ for (let i = 1; i < target.length; i++) {
495
+ const c = target.charCodeAt(i);
496
+ const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95 || c === 39;
497
+ if (!ok) return {};
498
+ }
499
+ if (ctx.declaredTypes.has(target)) return {};
500
+ return { externalTarget: true, externalVia: "superclass" };
501
+ }
502
+ if (type !== ASTEngine.RelationshipType.Implements && type !== ASTEngine.RelationshipType.Instantiates) return {};
503
+ if (!target || target.length === 0) return {};
504
+ for (let i = 0; i < target.length; i++) {
505
+ const c = target.charCodeAt(i);
506
+ if (c === 46 || c === 58) return {};
507
+ }
508
+ const first = target.charCodeAt(0);
509
+ if (!(first >= 65 && first <= 90)) return {};
510
+ if (ctx.declaredTypes.has(target) || ctx.declaredFunctions.has(target) || ctx.declaredConstructors.has(target)) return {};
511
+ return {
512
+ externalTarget: true,
513
+ externalVia: type === ASTEngine.RelationshipType.Implements ? "instance-class" : "constructor"
514
+ };
515
+ }
516
+ /** 1-based line of a node, or undefined — never 0 (M59.O2). */
517
+ lineOf(node) {
518
+ if (!node) return void 0;
519
+ const line = node.startPosition.row + 1;
520
+ return line > 0 ? line : void 0;
521
+ }
522
+ // ==========================================================================
523
+ // Template Haskell: `decorates` from a declaration-position splice.
524
+ //
525
+ // WHY THIS IS `decorates` AND NOT A RULING. Template Haskell runs code at
526
+ // COMPILE TIME. `$(makeLenses ''Widget)` applies the function `makeLenses` to
527
+ // the named declaration `Widget` and the function GENERATES accessor
528
+ // declarations for it; the declaration therefore CARRIES the decorator in
529
+ // exactly the sense the vocabulary means (`decorates`: a decorator is applied
530
+ // to a declaration). The measured population has 420 declaration-position
531
+ // splices, 177 of which name a declaration this file declares.
532
+ //
533
+ // THE DISCRIMINATOR IS ON THE DECORATED DECLARATION, NEVER ON THE HEAD.
534
+ // TH decorators are LIBRARY functions — `makeLenses` is imported from
535
+ // `Control.Lens.TH`, `deriveJSON` from `Data.Aeson.TH` — exactly as Python's
536
+ // `@dataclass` is imported from `dataclasses`. Requiring the HEAD to be
537
+ // declared in the file would exclude the construct and publish nothing; the
538
+ // test that identifies the construct is the `th_quoted_name` argument naming a
539
+ // declaration of THIS file. Measured near misses that must therefore stay
540
+ // silent: a splice quoting an IMPORTED declaration (33 sites — the quoted
541
+ // name is declared in another module, so this file cannot source an edge from
542
+ // it), and a declaration-position splice with no callable head (77 sites —
543
+ // `$(do ... [d| ... |])` generators and C-preprocessor macro lines such as
544
+ // `INSTANCE_CLUMP_1(...)`, which the grammar parses as a bare top-level
545
+ // expression splice).
546
+ //
547
+ // THE EDGE, per the lane's brief: source is the ENCLOSING MODULE SCOPE and the
548
+ // target is the DECORATED DECLARATION's own symbol. The decorator name is
549
+ // often external and unresolvable, so it is carried in
550
+ // `metadata.decoratorName` rather than emitted as a dangling endpoint — an
551
+ // edge to a name nothing declares would be a claim the index cannot answer.
552
+ //
553
+ // `{-# ANN #-}` IS NOT THIS CONSTRUCT. It attaches a value to a target
554
+ // (`{-# ANN getEnvironment ("HLINT: ..." :: String) #-}`) and applies no
555
+ // callable, so it is the passive-metadata class and publishes no edge; its
556
+ // sites are counted in `metadata.annPragmaSites` so the class is disclosed
557
+ // rather than silently dropped.
558
+ // ==========================================================================
559
+ decorateFromSplice(node, ctx) {
560
+ ctx.patterns["Template Haskell"] = true;
561
+ let expr = node.namedChildCount > 0 ? node.namedChild(0) : null;
562
+ for (let guard = 0; guard < 6 && expr; guard++) {
563
+ if (expr.type === "splice" || expr.type === "parens") {
564
+ expr = expr.namedChildCount > 0 ? expr.namedChild(0) : null;
565
+ continue;
566
+ }
567
+ break;
568
+ }
569
+ if (!expr) return;
570
+ let head = expr;
571
+ let decoratorName;
572
+ let decoratorQualified = false;
573
+ for (let guard = 0; guard < 12 && head; guard++) {
574
+ if (head.type === "variable" || head.type === "name") {
575
+ decoratorName = head.text;
576
+ break;
577
+ }
578
+ if (head.type === "qualified") {
579
+ const mod = this.findNamedChildByType(head, ["module"]);
580
+ const v = this.findNamedChildByType(head, ["variable", "constructor"]);
581
+ const modText = this.stripTrailingDot(mod?.text ?? "");
582
+ decoratorName = modText && v?.text ? `${modText}.${v.text}` : head.text;
583
+ decoratorQualified = true;
584
+ break;
585
+ }
586
+ if (head.type === "apply") {
587
+ head = head.namedChildCount > 0 ? head.namedChild(0) : null;
588
+ continue;
589
+ }
590
+ break;
591
+ }
592
+ if (!decoratorName) return;
593
+ const quotes = this.thQuotedNames(expr);
594
+ let decorated;
595
+ for (const q of quotes) {
596
+ if (ctx.declaredTypes.has(q.name) || ctx.declaredFunctions.has(q.name)) {
597
+ decorated = q;
598
+ break;
599
+ }
600
+ }
601
+ if (!decorated) return;
602
+ const scope = ctx.currentModule || "Main";
603
+ this.pushRel(
604
+ ctx,
605
+ scope,
606
+ decorated.name,
607
+ ASTEngine.RelationshipType.Decorates,
608
+ 1,
609
+ {
610
+ decoratorName,
611
+ isQualifiedDecorator: decoratorQualified,
612
+ thQuotedForm: decorated.raw,
613
+ isTemplateHaskell: true,
614
+ haskellSpecific: true
615
+ },
616
+ this.lineOf(node)
617
+ );
618
+ }
619
+ /**
620
+ * Every `th_quoted_name` under a subtree, with the quotes stripped.
621
+ *
622
+ * The grammar puts the quoted name in a `name` child (`''Foo` -> `''` token +
623
+ * `name` "Foo"); the raw text is kept so the edge's metadata can say which
624
+ * quote form was used. A quote with no readable name is skipped rather than
625
+ * published with its ticks — a name that is not a name is not an endpoint.
626
+ */
627
+ thQuotedNames(node) {
628
+ const out = [];
629
+ const visit = (n) => {
630
+ if (n.type === "th_quoted_name") {
631
+ const raw = n.text ?? "";
632
+ const nameNode = this.findNamedChildByType(n, ["name", "variable", "constructor"]);
633
+ const name = (nameNode?.text ?? "").trim();
634
+ if (name.length > 0) out.push({ name, raw });
635
+ return;
636
+ }
637
+ for (let i = 0; i < n.namedChildCount; i++) {
638
+ const c = n.namedChild(i);
639
+ if (c) visit(c);
640
+ }
641
+ };
642
+ visit(node);
643
+ return out;
644
+ }
645
+ /**
646
+ * Count the passive-metadata pragma family (`{-# ANN ... #-}`).
647
+ *
648
+ * The pragma node's body is OPAQUE to the grammar (the node carries no named
649
+ * children — measured over the whole population), so the keyword is read from
650
+ * the text between `{-#` and the first whitespace, with no regular
651
+ * expression: the analyzer's no-regex criterion forbids a pattern literal and
652
+ * a keyword test does not need one.
653
+ */
654
+ countPragmas(node, ctx) {
655
+ const text = (node.text ?? "").trim();
656
+ if (!text.startsWith("{-#")) return;
657
+ let i = 3;
658
+ while (i < text.length && (text[i] === " " || text[i] === " ")) i++;
659
+ const keyword = text.slice(i, i + 3).toUpperCase();
660
+ if (keyword === "ANN") ctx.annPragmas++;
661
+ }
662
+ // ==========================================================================
663
+ // `part_of` FROM A CPP `#include` — THE R3 EMISSION ON AN EXISTING NODE TYPE.
664
+ //
665
+ // WHY THIS IS `part_of` AND NOT AN `imports`. Haskell has no textual include
666
+ // of its own: `import Module` is a MODULE LOAD — it creates binding scope for
667
+ // the module's exports and can be resolved as `Module.item`, which is the
668
+ // OPPOSITE of a fragment. The ONE textual-inclusion construct a Haskell file
669
+ // can carry is the C preprocessor's `#include`, available under
670
+ // `{-# LANGUAGE CPP #-}`: the named file's text is spliced into this module
671
+ // BEFORE parsing, it shares this module's single scope, and it has no module
672
+ // identity at all. That is the contract's `part_of` verbatim ("a part shares
673
+ // the library's single scope, imports nothing, and cannot be resolved as a
674
+ // separate module"), and the fleet's implemented `part_of` set is unanimously
675
+ // textual file inclusion (cpp `#include`, fsharp `#load`, php include/require,
676
+ // erlang `-include`, typescript reference directives).
677
+ //
678
+ // RUNG WALKED: R1 and R2 are UNNECESSARY — `cpp` is already a NAMED node type
679
+ // of the SERVED grammar (253 named of 552 ids, digest 37a6b07b1a83; measured
680
+ // in validation/m14/measurements/haskell-grammar-nodes.json), so R3 is live
681
+ // and no grammar build or patch is needed.
682
+ //
683
+ // THE TARGET IS THE PART'S MODULE SYMBOL (its file STEM), NOT A PATH, and the
684
+ // written path is kept in `metadata.partPath`. The arbiter resolves TARGET
685
+ // STRINGS AGAINST SYMBOL NAMES and has no space for a path — the cpp lane
686
+ // measured that emitting paths gave 0 resolved of 4,612 rows — while the
687
+ // contract permits either ("the part's file OR ITS MODULE SYMBOL"). The stem
688
+ // is symbol-shaped, so it can bind when it names something.
689
+ //
690
+ // MEASURED over the whole admitted population (3,702 files): FOUR real
691
+ // directive sites, every one a quoted `#include "MachDeps.h"` (three Cabal
692
+ // Cmm test-suite files, one hasura-engine module), 0 non-literal. The SEVEN
693
+ // other `#include` strings in the corpus are NOT directives and emit nothing:
694
+ // a string-built line `["#include \"" ++ hdr ++ "\""]`, the `-#include` ghc
695
+ // FLAG as a string, a Haddock comment mentioning the flag, and a JSON
696
+ // quasiquote carrying the text. All four are pinned as negatives by the
697
+ // fixture. `MachDeps.h` is a GHC SYSTEM header, so its stem resolves to
698
+ // nothing in the admitted set — the system-header case the cpp lane handles,
699
+ // and it reads unresolved rather than wrong.
700
+ // ==========================================================================
701
+ processCppDirective(node, ctx) {
702
+ const include = this.readCppInclude(node.text ?? "");
703
+ if (!include) return;
704
+ ctx.patterns["CPP"] = true;
705
+ const scope = ctx.currentModule || "Main";
706
+ if (include.form === "quoted") {
707
+ this.pushRel(
708
+ ctx,
709
+ scope,
710
+ include.stem,
711
+ ASTEngine.RelationshipType.PartOf,
712
+ 1,
713
+ {
714
+ // MANDATORY on every part_of row (src/ast/types.ts): "library" because
715
+ // THIS file is the library/unit and the directive names its part.
716
+ partRole: "library",
717
+ // The written path is EVIDENCE; the stem is the target. Keeping both
718
+ // is what makes the difference between them checkable.
719
+ partPath: include.specifier,
720
+ partSymbol: include.stem,
721
+ isQuotedInclude: true,
722
+ isCppDirective: true,
723
+ relationshipType: "part_of_directive",
724
+ haskellSpecific: true
725
+ },
726
+ this.lineOf(node)
727
+ );
728
+ } else {
729
+ this.pushRel(
730
+ ctx,
731
+ scope,
732
+ include.specifier,
733
+ ASTEngine.RelationshipType.Imports,
734
+ 1,
735
+ {
736
+ isSystemInclude: true,
737
+ isCppDirective: true,
738
+ relationshipType: "include_dependency",
739
+ haskellSpecific: true
740
+ },
741
+ this.lineOf(node)
742
+ );
743
+ }
744
+ }
745
+ /**
746
+ * Read a `#include` directive's specifier from a `cpp` node's text.
747
+ *
748
+ * Returns undefined unless the directive's keyword is EXACTLY `include` and it
749
+ * names a quoted or angle specifier. `#if`, `#else`, `#endif` and a shebang
750
+ * share the `cpp` node type and name no file, so every one of them falls out
751
+ * here. PURE STRING SCAN — the analyzer's no-regex contract forbids a pattern
752
+ * literal, and a keyword test does not need one.
753
+ */
754
+ readCppInclude(text) {
755
+ const isSpace = (c) => c === " " || c === " " || c === "\n" || c === "\r";
756
+ let i = 0;
757
+ while (i < text.length && isSpace(text[i])) i++;
758
+ if (i >= text.length || text[i] !== "#") return void 0;
759
+ i++;
760
+ while (i < text.length && (text[i] === " " || text[i] === " ")) i++;
761
+ const keyword = "include";
762
+ if (i + keyword.length > text.length) return void 0;
763
+ if (text.slice(i, i + keyword.length).toLowerCase() !== keyword) return void 0;
764
+ let j = i + keyword.length;
765
+ if (j < text.length && !(text[j] === " " || text[j] === " " || text[j] === '"' || text[j] === "<")) return void 0;
766
+ while (j < text.length && (text[j] === " " || text[j] === " ")) j++;
767
+ if (j >= text.length) return void 0;
768
+ const open = text[j];
769
+ if (open === '"') {
770
+ const end = text.indexOf('"', j + 1);
771
+ if (end < 0) return void 0;
772
+ const specifier = text.slice(j + 1, end).trim();
773
+ if (specifier.length === 0) return void 0;
774
+ return { form: "quoted", specifier, stem: this.cppIncludeStem(specifier) };
775
+ }
776
+ if (open === "<") {
777
+ const end = text.indexOf(">", j + 1);
778
+ if (end < 0) return void 0;
779
+ const specifier = text.slice(j + 1, end).trim();
780
+ if (specifier.length === 0) return void 0;
781
+ return { form: "angle", specifier, stem: this.cppIncludeStem(specifier) };
782
+ }
783
+ return void 0;
784
+ }
785
+ /** A specifier's module stem: no directories, no extension (`util/Service.h` -> `Service`). */
786
+ cppIncludeStem(specifier) {
787
+ const slash = specifier.lastIndexOf("/");
788
+ let base = slash >= 0 ? specifier.slice(slash + 1) : specifier;
789
+ const backslash = base.lastIndexOf("\\");
790
+ if (backslash >= 0) base = base.slice(backslash + 1);
791
+ const dot = base.lastIndexOf(".");
792
+ return (dot > 0 ? base.slice(0, dot) : base).trim();
793
+ }
794
+ // ==========================================================================
795
+ // Pre-pass: declared TYPE names at module scope (forward-reference resolution
796
+ // for TH quotes). A `data`/`newtype`/`type`/`class`/`pattern` declaration is
797
+ // the thing a Template Haskell decorator is applied to, and the splice may
798
+ // appear before or after it in source order.
799
+ // ==========================================================================
800
+ collectDeclaredTypes(node, out) {
801
+ const t = node.type;
802
+ if (t === "data_type" || t === "newtype" || t === "type_synomym" || t === "class" || t === "pattern_synonym") {
803
+ const nameNode = this.findNamedChildByType(node, ["name"]);
804
+ if (nameNode?.text) out.add(nameNode.text);
805
+ }
806
+ for (let i = 0; i < node.namedChildCount; i++) {
807
+ const child = node.namedChild(i);
808
+ if (!child) continue;
809
+ if (child.type === "local_binds" || child.type === "let" || child.type === "let_in") continue;
810
+ this.collectDeclaredTypes(child, out);
811
+ }
812
+ }
813
+ /**
814
+ * HSA1 — the DATA CONSTRUCTORS this module declares, for the external-claim guard only.
815
+ *
816
+ * The walk is deliberately generous: every `constructor` under a `data_type`/`newtype` is
817
+ * collected, field types included (`data T = C Int` also collects `Int`). Over-collecting can only
818
+ * WITHHOLD a claim, and a withheld claim leaves the row where it was — the safe direction. Missing
819
+ * one would publish a false statement about the module's own declaration, which is what the fixture
820
+ * caught.
821
+ */
822
+ collectDeclaredConstructors(node, out) {
823
+ if (node.type === "data_type" || node.type === "newtype") {
824
+ const grab = (n) => {
825
+ if (n.type === "constructor" && n.text) out.add(n.text);
826
+ for (let i = 0; i < n.namedChildCount; i++) {
827
+ const c = n.namedChild(i);
828
+ if (c) grab(c);
829
+ }
830
+ };
831
+ grab(node);
832
+ return;
833
+ }
834
+ for (let i = 0; i < node.namedChildCount; i++) {
835
+ const child = node.namedChild(i);
836
+ if (!child) continue;
837
+ if (child.type === "local_binds" || child.type === "let" || child.type === "let_in") continue;
838
+ this.collectDeclaredConstructors(child, out);
839
+ }
840
+ }
841
+ // ==========================================================================
842
+ // Pre-pass: declared function/method names (forward-reference resolution).
843
+ // ==========================================================================
844
+ // class -> its method names. This is what makes `overrides` derivable at all:
845
+ // an instance declares a method name, and the only way to know that name
846
+ // OVERRIDES rather than merely exists is to compare it against the class
847
+ // body's method set. A single walk cannot do that — the instance may be
848
+ // visited before the class, and in the payloads/cabal corpus it frequently is
849
+ // — so the class side is gathered first and the instance side is compared
850
+ // against it when the instance is reached.
851
+ collectClassMethods(node, out) {
852
+ if (node.type === "class") {
853
+ const clsName = this.findNamedChildByType(node, ["name"])?.text ?? "";
854
+ if (clsName) {
855
+ let set = out.get(clsName);
856
+ if (!set) {
857
+ set = /* @__PURE__ */ new Set();
858
+ out.set(clsName, set);
859
+ }
860
+ const decls = this.findNamedChildByType(node, ["class_declarations"]);
861
+ const groups = decls ? [decls] : [];
862
+ for (const g of groups) {
863
+ for (const sig of this.collectNamedChildrenByType(g, ["signature"])) {
864
+ const v = this.findNamedChildByType(sig, ["variable"]);
865
+ if (v?.text) set.add(v.text);
866
+ }
867
+ for (const fn of this.collectNamedChildrenByType(g, ["function", "bind"])) {
868
+ const first = fn.namedChildCount > 0 ? fn.namedChild(0) : null;
869
+ if (first?.type === "variable" && first.text) set.add(first.text);
870
+ }
871
+ }
872
+ }
873
+ }
874
+ for (let i = 0; i < node.namedChildCount; i++) {
875
+ const c = node.namedChild(i);
876
+ if (c) this.collectClassMethods(c, out);
877
+ }
878
+ }
879
+ collectDeclaredFunctions(node, out) {
880
+ if (node.type === "function" && (node.childForFieldName("parameter") || node.childForFieldName("result"))) {
881
+ return;
882
+ }
883
+ if (node.type === "function" || node.type === "bind") {
884
+ if (!(node.type === "bind" && this.hasChildOfType(node, "<-"))) {
885
+ const nameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
886
+ if (nameNode && nameNode.type === "variable") {
887
+ out.add(nameNode.text);
888
+ } else if (nameNode && (nameNode.type === "infix" || nameNode.type === "prefix_id")) {
889
+ const op = this.findNamedChildByType(nameNode, ["operator"]);
890
+ const tv = op ? void 0 : (() => {
891
+ const infixId = this.findNamedChildByType(nameNode, ["infix_id"]);
892
+ return infixId ? this.findNamedChildByType(infixId, ["variable"]) : void 0;
893
+ })();
894
+ const callable = op?.text ?? tv?.text;
895
+ if (callable) out.add(callable);
896
+ }
897
+ }
898
+ }
899
+ if (node.type === "signature") {
900
+ if (node.parent?.type === "parens") return;
901
+ const first = node.namedChildCount > 0 ? node.namedChild(0) : null;
902
+ if (first?.type !== "variable") return;
903
+ out.add(first.text);
904
+ return;
905
+ }
906
+ for (let i = 0; i < node.namedChildCount; i++) {
907
+ const child = node.namedChild(i);
908
+ if (!child) continue;
909
+ if (child.type === "local_binds" || child.type === "let" || child.type === "let_in") continue;
910
+ this.collectDeclaredFunctions(child, out);
911
+ }
912
+ }
913
+ // ==========================================================================
914
+ // Pre-pass: lexical local identity (M60 functional lane, 2026-09-26).
915
+ // A callable declared inside a where/let block is a real callable but NOT
916
+ // a module symbol (HSD6/HSD19 contract, unchanged). Its identity carries
917
+ // its lexical owner chain: `outer.local` (nested: `outer.inner.local`), so
918
+ // it can never be confused with a module-level callable of the same name.
919
+ // ==========================================================================
920
+ collectLocalScopes(root, out) {
921
+ const stack = [
922
+ { n: root, frameId: -1, frameScoped: void 0 }
923
+ ];
924
+ while (stack.length > 0) {
925
+ const { n, frameId, frameScoped } = stack.pop();
926
+ for (let i = 0; i < n.namedChildCount; i++) {
927
+ const child = n.namedChild(i);
928
+ if (!child) continue;
929
+ const t = child.type;
930
+ if (t === "local_binds" || t === "let" || t === "let_in") {
931
+ const binders = this.localBlockBinders(child);
932
+ if (binders.length > 0) {
933
+ const map = /* @__PURE__ */ new Map();
934
+ for (const b of binders) {
935
+ const nm2 = this.callableHeadName(b);
936
+ if (!nm2) continue;
937
+ const scoped = frameScoped !== void 0 ? `${frameScoped}.${nm2}` : nm2;
938
+ map.set(nm2, scoped);
939
+ out.callableScoped.set(b.id, scoped);
940
+ }
941
+ if (map.size > 0) {
942
+ out.blockLocals.set(child.id, map);
943
+ if (t === "local_binds" && frameId >= 0) {
944
+ const fm = out.frameLocals.get(frameId) ?? /* @__PURE__ */ new Map();
945
+ for (const [k, v] of map) fm.set(k, v);
946
+ out.frameLocals.set(frameId, fm);
947
+ }
948
+ }
949
+ }
950
+ for (let j = 0; j < child.namedChildCount; j++) {
951
+ const c = child.namedChild(j);
952
+ if (!c) continue;
953
+ const scoped = out.callableScoped.get(c.id);
954
+ if (scoped !== void 0 && this.callableHeadName(c) !== void 0) {
955
+ stack.push({ n: c, frameId: c.id, frameScoped: scoped });
956
+ } else {
957
+ stack.push({ n: c, frameId, frameScoped });
958
+ }
959
+ }
960
+ continue;
961
+ }
962
+ const nm = this.callableHeadName(child);
963
+ if (nm !== void 0) {
964
+ const scoped = frameScoped !== void 0 ? `${frameScoped}.${nm}` : nm;
965
+ out.callableScoped.set(child.id, scoped);
966
+ stack.push({ n: child, frameId: child.id, frameScoped: scoped });
967
+ } else {
968
+ stack.push({ n: child, frameId, frameScoped });
969
+ }
970
+ }
971
+ }
972
+ }
973
+ /** Direct callable children of a where/let block (the local binders). */
974
+ localBlockBinders(block) {
975
+ const out = [];
976
+ for (let i = 0; i < block.namedChildCount; i++) {
977
+ const c = block.namedChild(i);
978
+ if (!c || c.type !== "function" && c.type !== "bind") continue;
979
+ if (this.callableHeadName(c) === void 0) continue;
980
+ out.push(c);
981
+ }
982
+ return out;
983
+ }
984
+ /**
985
+ * The declared name of a callable node, or undefined when the node is not a
986
+ * callable at all (type-position function, do-bind, pattern-headed bind).
987
+ * Mirrors collectDeclaredFunctions' naming rules exactly — one ruler for
988
+ * module-level and local callables.
989
+ */
990
+ callableHeadName(node) {
991
+ if (node.type !== "function" && node.type !== "bind") return void 0;
992
+ if (node.type === "function" && (node.childForFieldName("parameter") || node.childForFieldName("result"))) {
993
+ return void 0;
994
+ }
995
+ if (node.type === "bind" && this.hasChildOfType(node, "<-")) return void 0;
996
+ const head = node.namedChildCount > 0 ? node.namedChild(0) : null;
997
+ if (!head) return void 0;
998
+ if (head.type === "variable") return head.text;
999
+ if (head.type === "infix" || head.type === "prefix_id") {
1000
+ const op = this.findNamedChildByType(head, ["operator"]);
1001
+ if (op?.text) return op.text;
1002
+ const infixId = this.findNamedChildByType(head, ["infix_id"]);
1003
+ const tv = infixId ? this.findNamedChildByType(infixId, ["variable"]) : void 0;
1004
+ return tv?.text;
1005
+ }
1006
+ return void 0;
1007
+ }
1008
+ /**
1009
+ * A do-block `let` (`do { let g = ...; ... g x }`): its binders stay visible
1010
+ * for the REST of the do block — subsequent statements are siblings, not
1011
+ * descendants, so the block scope alone cannot carry them. Folded into the
1012
+ * nearest callable frame as the walk reaches the block (source order).
1013
+ */
1014
+ isForwardVisibleLocalBlock(node) {
1015
+ if (node.type !== "local_binds") return false;
1016
+ const p = node.parent;
1017
+ return p?.type === "let" && p.parent?.type === "do";
1018
+ }
1019
+ /**
1020
+ * Resolve a bare variable head to its callable target: the innermost
1021
+ * visible local wins (Haskell scoping — a where-binder shadows the module
1022
+ * binding for its whole RHS), else the module-level declared set, else no
1023
+ * edge (imported/Prelude/parameter names produce none).
1024
+ */
1025
+ resolveCallableTarget(name, scopeStack, ctx) {
1026
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
1027
+ const locals = scopeStack[i].locals;
1028
+ if (locals !== void 0) {
1029
+ const hit = locals.get(name);
1030
+ if (hit !== void 0) return hit;
1031
+ }
1032
+ }
1033
+ return ctx.declaredFunctions.has(name) ? name : void 0;
1034
+ }
1035
+ processModule(node, ctx, scopeStack) {
1036
+ let moduleName = "";
1037
+ const moduleNode = this.findNamedChildByType(node, ["module"]);
1038
+ if (moduleNode) {
1039
+ moduleName = this.assembleModuleId(moduleNode);
1040
+ } else {
1041
+ const mIdx = this.findChildIndexByText(node, "module");
1042
+ if (mIdx !== -1 && node.childCount > mIdx + 1) {
1043
+ moduleName = node.child(mIdx + 1)?.text || "";
1044
+ }
1045
+ }
1046
+ if (!moduleName) return false;
1047
+ ctx.currentModule = moduleName;
1048
+ const exportsList = this.findNamedChildByType(node, ["exports"]);
1049
+ if (exportsList) {
1050
+ for (let i = 0; i < exportsList.namedChildCount; i++) {
1051
+ const expNode = exportsList.namedChild(i);
1052
+ if (!expNode) continue;
1053
+ let exportName;
1054
+ let isReExport = false;
1055
+ let anchor = expNode;
1056
+ if (expNode.type === "export") {
1057
+ const read = this.readExportName(expNode, exportsList, i);
1058
+ exportName = read.name;
1059
+ if (read.anchor) anchor = read.anchor;
1060
+ } else if (expNode.type === "module_export") {
1061
+ const mod = this.findNamedChildByType(expNode, ["module", "module_id"]);
1062
+ exportName = mod ? this.assembleModuleId(mod) : void 0;
1063
+ isReExport = exportName !== void 0;
1064
+ } else {
1065
+ continue;
1066
+ }
1067
+ if (exportName && exportName !== ",") {
1068
+ ctx.exports.push({ name: exportName, isDefault: false, range: this.nodeToRange(expNode) });
1069
+ this.pushRel(
1070
+ ctx,
1071
+ moduleName,
1072
+ exportName,
1073
+ ASTEngine.RelationshipType.Exports,
1074
+ 1,
1075
+ { evidenceClass: "structural", ...isReExport ? { isReExport: true } : {} },
1076
+ this.lineOf(anchor)
1077
+ );
1078
+ }
1079
+ }
1080
+ }
1081
+ ctx.symbols.push({
1082
+ name: moduleName,
1083
+ kind: ASTEngine.SymbolKind.Module,
1084
+ range: this.nodeToRange(node),
1085
+ scope: "",
1086
+ visibility: "public",
1087
+ metadata: { haskellSpecific: true }
1088
+ });
1089
+ scopeStack.push({ node, name: moduleName, kind: ASTEngine.SymbolKind.Module });
1090
+ return true;
1091
+ }
1092
+ /**
1093
+ * THE NAME an `export` node exports, or undefined when it exports none.
1094
+ *
1095
+ * WHY THIS IS NOT `expNode.text`. The export list states a name with a
1096
+ * DECLARATION KEYWORD in front of it whenever the entry narrows what is
1097
+ * exported, and the raw text is the whole phrase:
1098
+ *
1099
+ * export -> namespace("type") + name("T") text "type T"
1100
+ * export -> namespace("pattern") + name("P") text "pattern P"
1101
+ * export -> name("U") + children("(..)") text "U(..)"
1102
+ * export -> variable("data") + ERROR("D") text "data" (a mis-parse)
1103
+ *
1104
+ * Measured over the whole corpus before this method existed (analyzer pass,
1105
+ * `script/m14/tmp/hs-ontology-analyse.ts`): 392 of 54,830 `exports` edges
1106
+ * carried a target that was a keyword or a keyword phrase — 100 `type`,
1107
+ * 32 `data`, 8 `pattern`, and the rest `pattern <Ctor>` / `type <Ty>`.
1108
+ * `exports Data.Type.Bool -> type` names the Haskell keyword: it resolves to
1109
+ * nothing, and `T(A, B)`-style narrowing means the thing the module actually
1110
+ * exports is `T`, not `T(A, B)`.
1111
+ *
1112
+ * Tree shapes, counted on the pinned grammar over all 14,066 admitted files
1113
+ * (`script/m14/tmp/hs-export-ffi-probe.ts`): 31,892 `export` nodes —
1114
+ * 6,697 with a `name`/`constructor` child, 24,515 a bare `variable`, 43 a bare
1115
+ * declaration keyword whose real name the grammar pushed into a SIBLING `ERROR`
1116
+ * (37 of the 43 recoverable that way), and 0 unclassified.
1117
+ *
1118
+ * The 6 unrecoverable ones emit NOTHING. Under standing rule R7 the honest
1119
+ * state for "I cannot read this name" is absence, not a keyword standing in for
1120
+ * a name — a literal in a name-bearing field becomes an edge endpoint that does
1121
+ * not exist in the source, and `bb_graph` will happily count and display it.
1122
+ */
1123
+ readExportName(expNode, exportsList, index) {
1124
+ const named = this.findNamedChildByType(expNode, ["name", "constructor"]);
1125
+ if (named?.text) return { name: named.text, anchor: named };
1126
+ const opId = this.findNamedChildByType(expNode, ["prefix_id", "infix_id"]);
1127
+ if (opId) {
1128
+ const op = this.findNamedChildByType(opId, ["operator", "constructor_operator"]);
1129
+ if (op?.text) return { name: op.text, anchor: op };
1130
+ const innerVar = this.findNamedChildByType(opId, ["variable"]);
1131
+ if (innerVar?.text) return { name: innerVar.text, anchor: innerVar };
1132
+ }
1133
+ const variable = this.findNamedChildByType(expNode, ["variable"]);
1134
+ if (variable?.text) {
1135
+ if (!HASKELL_DECLARATION_KEYWORDS.has(variable.text)) return { name: variable.text, anchor: variable };
1136
+ for (let j = index + 1; j < exportsList.namedChildCount; j++) {
1137
+ const nx = exportsList.namedChild(j);
1138
+ if (!nx) continue;
1139
+ if (nx.type === "export" || nx.type === "module_export") break;
1140
+ if (nx.type === "ERROR") {
1141
+ const rec = this.findNamedChildByType(nx, ["name", "constructor"]);
1142
+ if (rec?.text) return { name: rec.text, anchor: rec };
1143
+ }
1144
+ }
1145
+ return { name: void 0 };
1146
+ }
1147
+ const text = expNode.text.trim();
1148
+ if (!text) return { name: void 0 };
1149
+ const paren = text.indexOf("(");
1150
+ return { name: paren >= 0 ? text.slice(0, paren).trim() : text, anchor: expNode };
1151
+ }
1152
+ // module_id children compose dotted names: Data → List → "Data.List"
1153
+ assembleModuleId(node) {
1154
+ let out = "";
1155
+ for (let i = 0; i < node.namedChildCount; i++) {
1156
+ const c = node.namedChild(i);
1157
+ if (!c) continue;
1158
+ const leaf = c.type === "module_id" ? this.assembleModuleId(c) : c.text;
1159
+ if (out) out += ".";
1160
+ out += leaf;
1161
+ }
1162
+ return out || node.text;
1163
+ }
1164
+ processImport(node, ctx) {
1165
+ let isQualified = false;
1166
+ const items = [];
1167
+ const modulesToImport = [];
1168
+ let currentIsHiding = this.findChildIndexByText(node, "hiding") !== -1;
1169
+ for (let i = 0; i < node.childCount; i++) {
1170
+ const child = node.child(i);
1171
+ if (!child) continue;
1172
+ if (child.type === "qualified") {
1173
+ isQualified = true;
1174
+ } else if (child.type === "module" || child.type === "module_id") {
1175
+ const name = this.assembleModuleId(child);
1176
+ if (i > 0 && node.child(i - 1)?.text.trim() === "as") {
1177
+ const lastMod = modulesToImport[modulesToImport.length - 1];
1178
+ if (lastMod) lastMod.alias = name;
1179
+ } else {
1180
+ modulesToImport.push({ name, isHiding: currentIsHiding, node: child });
1181
+ }
1182
+ } else if (child.type === "hiding") {
1183
+ currentIsHiding = true;
1184
+ } else if (child.type === "import_list") {
1185
+ for (let j = 0; j < child.namedChildCount; j++) {
1186
+ const item = child.namedChild(j);
1187
+ if (item && item.type !== "hiding") {
1188
+ const itemText = this.stripOuterParens(item.text.trim());
1189
+ if (itemText && itemText !== "hiding" && itemText !== ",") {
1190
+ const paren = itemText.indexOf("(");
1191
+ items.push(paren >= 0 ? itemText.slice(0, paren).trim() : itemText);
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+ for (const mod of modulesToImport) {
1198
+ if (!mod.name) continue;
1199
+ const importItems = [];
1200
+ if (items.length > 0) {
1201
+ for (const item of items) importItems.push(item);
1202
+ } else {
1203
+ importItems.push("*");
1204
+ }
1205
+ if (mod.alias && mod.alias.length > 0) ctx.importAliases.set(mod.alias, mod.name);
1206
+ const own = mod.name.indexOf(".") < 0 ? mod.name : mod.name.slice(mod.name.lastIndexOf(".") + 1);
1207
+ if (own.length > 0 && !ctx.importAliases.has(own)) ctx.importAliases.set(own, mod.name);
1208
+ ctx.imports.push({
1209
+ module: mod.name,
1210
+ source: mod.name,
1211
+ items: importItems,
1212
+ isTypeOnly: false,
1213
+ range: this.nodeToRange(node),
1214
+ metadata: { isQualified, alias: mod.alias, isHiding: mod.isHiding }
1215
+ });
1216
+ this.pushRel(
1217
+ ctx,
1218
+ ctx.currentModule || "",
1219
+ mod.name,
1220
+ ASTEngine.RelationshipType.Imports,
1221
+ 0.9,
1222
+ {
1223
+ isHaskellImport: true
1224
+ },
1225
+ this.lineOf(mod.node)
1226
+ );
1227
+ if (mod.name.indexOf("Scotty") !== -1) ctx.frameworks["Scotty"] = true;
1228
+ if (mod.name.indexOf("Servant") !== -1) ctx.frameworks["Servant"] = true;
1229
+ if (mod.name.indexOf("QuickCheck") !== -1) ctx.frameworks["QuickCheck"] = true;
1230
+ if (mod.name.indexOf("Database.Persist") !== -1) ctx.frameworks["Persistent"] = true;
1231
+ if (mod.name.indexOf("Yesod") !== -1) ctx.frameworks["Yesod"] = true;
1232
+ }
1233
+ }
1234
+ processSignature(node, ctx, currentScope, inLocalBlock = false) {
1235
+ const nameNode = this.findNamedChildByType(node, ["variable"]);
1236
+ if (nameNode && !inLocalBlock) {
1237
+ const idx = node.text.indexOf("::");
1238
+ if (idx !== -1) {
1239
+ ctx.typeSignatures[nameNode.text] = node.text.slice(idx + 2).trim();
1240
+ }
1241
+ }
1242
+ if (nameNode && currentScope && (currentScope.kind === ASTEngine.SymbolKind.Interface || currentScope.kind === ASTEngine.SymbolKind.Class)) {
1243
+ const methodKey = `${currentScope.name}::${nameNode.text}`;
1244
+ const existingIdx = ctx.functionSymbolIndex[methodKey];
1245
+ if (existingIdx === void 0) {
1246
+ ctx.functionSymbolIndex[methodKey] = ctx.symbols.length;
1247
+ ctx.symbols.push({
1248
+ name: nameNode.text,
1249
+ kind: ASTEngine.SymbolKind.Method,
1250
+ range: this.nodeToRange(node),
1251
+ scope: currentScope.name || "",
1252
+ ownerName: currentScope.name,
1253
+ ownerKind: currentScope.kind,
1254
+ visibility: "public",
1255
+ metadata: { haskellSpecific: true, isInterfaceMethod: true }
1256
+ });
1257
+ }
1258
+ }
1259
+ }
1260
+ processFunction(node, ctx, currentScope, scopeStack) {
1261
+ if (node.type === "function" && (node.childForFieldName("parameter") || node.childForFieldName("result"))) {
1262
+ return false;
1263
+ }
1264
+ let name = "";
1265
+ const nameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
1266
+ if (nameNode && nameNode.type === "variable") {
1267
+ name = nameNode.text;
1268
+ } else if (nameNode && (nameNode.type === "infix" || nameNode.type === "prefix_id")) {
1269
+ const op = this.findNamedChildByType(nameNode, ["operator"]);
1270
+ if (op?.text) {
1271
+ name = op.text;
1272
+ } else {
1273
+ const infixId = this.findNamedChildByType(nameNode, ["infix_id"]);
1274
+ const tv = infixId ? this.findNamedChildByType(infixId, ["variable"]) : void 0;
1275
+ if (tv?.text) name = tv.text;
1276
+ }
1277
+ }
1278
+ if (!name || name === "=" || name === "|" || node.parent?.type === "signature" || node.parent?.type === "function") {
1279
+ return false;
1280
+ }
1281
+ const functionKey = `${currentScope?.name || ""}::${name}`;
1282
+ const existingIndex = ctx.functionSymbolIndex[functionKey];
1283
+ const existing = existingIndex !== void 0 ? ctx.symbols[existingIndex] : void 0;
1284
+ if (existing) {
1285
+ const newRange = this.nodeToRange(node);
1286
+ existing.range.endLine = Math.max(existing.range.endLine, newRange.endLine);
1287
+ if (existing.range.endIndex !== void 0 && newRange.endIndex !== void 0) {
1288
+ existing.range.endIndex = Math.max(existing.range.endIndex, newRange.endIndex);
1289
+ } else if (newRange.endIndex !== void 0) {
1290
+ existing.range.endIndex = newRange.endIndex;
1291
+ }
1292
+ if (existing.complexity !== void 0) {
1293
+ existing.complexity = Math.max(existing.complexity, this.calculateNodeComplexity(node));
1294
+ }
1295
+ ctx.currentFunction = name;
1296
+ scopeStack.push({
1297
+ node,
1298
+ name,
1299
+ kind: existing.kind,
1300
+ locals: ctx.localScopes.frameLocals.get(node.id),
1301
+ isFrame: true
1302
+ });
1303
+ return true;
1304
+ }
1305
+ const typeSignature = ctx.typeSignatures[name];
1306
+ let isHigherOrder = false;
1307
+ let returnType = void 0;
1308
+ if (typeSignature && typeof typeSignature === "string") {
1309
+ isHigherOrder = this.hasArrowInParens(typeSignature);
1310
+ const parts = this.splitOnArrows(typeSignature);
1311
+ if (parts.length > 0) returnType = parts[parts.length - 1].trim();
1312
+ }
1313
+ if (isHigherOrder) ctx.patterns["Higher-Order Functions"] = true;
1314
+ const kind = currentScope && (currentScope.kind === ASTEngine.SymbolKind.Interface || currentScope.kind === ASTEngine.SymbolKind.Class) ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function;
1315
+ ctx.symbols.push({
1316
+ name,
1317
+ kind,
1318
+ range: this.nodeToRange(node),
1319
+ scope: currentScope?.name || "",
1320
+ ownerName: currentScope?.name || ctx.currentModule,
1321
+ ownerKind: currentScope?.kind,
1322
+ visibility: "public",
1323
+ returnType,
1324
+ complexity: this.calculateNodeComplexity(node),
1325
+ metadata: { typeSignature, isHigherOrder, haskellSpecific: true }
1326
+ });
1327
+ ctx.functionSymbolIndex[functionKey] = ctx.symbols.length - 1;
1328
+ ctx.currentFunction = name;
1329
+ scopeStack.push({ node, name, kind, locals: ctx.localScopes.frameLocals.get(node.id), isFrame: true });
1330
+ return true;
1331
+ }
1332
+ // A type synonym is a first-class declaration — `type Result = [Int64]` names
1333
+ // a type that other code can annotate with, so omitting it left every use of
1334
+ // the name with nothing to resolve to. The grammar's node is spelled
1335
+ // `type_synomym` (the upstream typo is in the node NAME, not this comment), and
1336
+ // its shape is `name` + right-hand side, so the name is the first `name` child.
1337
+ processTypeSynonym(node, ctx, scopeStack) {
1338
+ ctx.patterns["Type Synonyms"] = true;
1339
+ const nameNode = this.findNamedChildByType(node, ["name"]);
1340
+ const name = nameNode?.text ?? "";
1341
+ if (!name) return false;
1342
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1343
+ ctx.symbols.push({
1344
+ name,
1345
+ kind: ASTEngine.SymbolKind.Type,
1346
+ range: this.nodeToRange(node),
1347
+ scope: currentScope?.name || "",
1348
+ ownerName: currentScope?.name || ctx.currentModule,
1349
+ visibility: "public",
1350
+ metadata: { haskellSpecific: true, isTypeSynonym: true }
1351
+ });
1352
+ if (currentScope) {
1353
+ this.pushRel(
1354
+ ctx,
1355
+ currentScope.name,
1356
+ name,
1357
+ ASTEngine.RelationshipType.Contains,
1358
+ 1,
1359
+ { evidenceClass: "structural" },
1360
+ this.lineOf(node)
1361
+ );
1362
+ }
1363
+ if (nameNode) {
1364
+ for (const leaf of this.collectTypeNameLeafNodes(node, nameNode)) {
1365
+ if (leaf.text.trim() === name) continue;
1366
+ this.pushRel(
1367
+ ctx,
1368
+ name,
1369
+ leaf.text.trim(),
1370
+ ASTEngine.RelationshipType.Uses,
1371
+ 1,
1372
+ { evidenceClass: "declared_type" },
1373
+ this.lineOf(leaf)
1374
+ );
1375
+ }
1376
+ }
1377
+ return true;
1378
+ }
1379
+ // Type-name leaves reachable in a subtree, skipping the subtree rooted at
1380
+ // `skip`, returned as NODES so a caller can cite each one's own line.
1381
+ // Haskell type expressions nest arbitrarily (`Map.Map ByteString ByteString`
1382
+ // is apply → qualified(name) + name), so this walks rather than reading one
1383
+ // level, and it takes `name` and `constructor` only: a `variable` in a type
1384
+ // position is a type PARAMETER (`f a`), not a type, and publishing `a` as a
1385
+ // use of some type named `a` is exactly the fabrication the declared-type
1386
+ // contract forbids.
1387
+ collectTypeNameLeafNodes(node, skip, out = []) {
1388
+ for (let i = 0; i < node.namedChildCount; i++) {
1389
+ const c = node.namedChild(i);
1390
+ if (!c || skip !== null && c.id === skip.id) continue;
1391
+ if (c.type === "name" || c.type === "constructor") {
1392
+ if (c.text.trim() && !out.some((o) => o.text === c.text)) out.push(c);
1393
+ continue;
1394
+ }
1395
+ this.collectTypeNameLeafNodes(c, null, out);
1396
+ }
1397
+ return out;
1398
+ }
1399
+ // A pattern synonym is a first-class top-level DECLARATION — `pattern Head a b =
1400
+ // expr` names a constructor-shaped value the module defines, so omitting it left
1401
+ // every use of the name with nothing to resolve to, exactly as omitting a type
1402
+ // synonym did. It is not exotic: GHC's own sources carry 1,094 of them across
1403
+ // 261 files (`script/m14/tmp/hs-pattern-synonyms.ts`, all 14,038 admitted files),
1404
+ // and 94 export entries name one, so those `exports` edges had a target nothing
1405
+ // declared. Found by the `haskell/exports-and-ffi` fixture, which the M14 runner
1406
+ // refused with `export(s) [":|"] do not resolve to a top-level symbol`.
1407
+ //
1408
+ // MEASURED shape distribution over those 1,094 nodes: 449 carry a readable name
1409
+ // (421 a `signature` child, 28 a bare `equation`), and 645 are VIEW PATTERNS
1410
+ // (`pattern P x <- Q x where P x = Q x`) whose name the previous reader could not
1411
+ // reach at all. The name is ALWAYS the head of the equation's left-hand side, so
1412
+ // one rule covers every shape:
1413
+ //
1414
+ // pattern_synonym -> pattern + signature -> prefix_id -> constructor_operator (`:|`)
1415
+ // pattern_synonym -> pattern + signature -> constructor (`Zero`)
1416
+ // pattern_synonym -> pattern + equation -> apply(apply(constructor, x), y) (`OptMResult`)
1417
+ // pattern_synonym -> pattern + equation -> apply -> constructor (a view pattern)
1418
+ // pattern_synonym -> pattern + equation -> infix -> constructor_operator (`:|`)
1419
+ //
1420
+ // The symbol kind is `constructor`, not `function`: a pattern synonym is built
1421
+ // and matched like a data constructor (`instantiates` already targets it through
1422
+ // the `apply`-head rule), and calling it a function would put a name the module
1423
+ // never defines into the FP-family `calls` set.
1424
+ processPatternSynonym(node, ctx) {
1425
+ ctx.patterns["Pattern Synonyms"] = true;
1426
+ const sig = this.findNamedChildByType(node, ["signature"]);
1427
+ const eq = this.findNamedChildByType(node, ["equation"]);
1428
+ let name;
1429
+ let anchor;
1430
+ if (sig) {
1431
+ const head = this.findNamedChildByType(sig, ["prefix_id", "infix_id", "constructor", "constructor_operator", "variable"]);
1432
+ if (head) {
1433
+ name = head.type === "prefix_id" || head.type === "infix_id" ? this.findNamedChildByType(head, ["constructor_operator", "operator", "constructor", "variable"])?.text : head.text;
1434
+ anchor = head;
1435
+ }
1436
+ }
1437
+ if (!name && eq) {
1438
+ for (let i = 0; i < eq.namedChildCount; i++) {
1439
+ const c = eq.namedChild(i);
1440
+ if (!c) continue;
1441
+ if (c.type === "constructor" && c.text) {
1442
+ name = c.text;
1443
+ anchor = c;
1444
+ break;
1445
+ }
1446
+ if (c.type === "infix") {
1447
+ const op = this.findNamedChildByType(c, ["constructor_operator", "operator"]);
1448
+ if (op?.text) {
1449
+ name = op.text;
1450
+ anchor = op;
1451
+ break;
1452
+ }
1453
+ continue;
1454
+ }
1455
+ if (c.type === "apply") {
1456
+ const head = this.haskellAppliedHeadName(c);
1457
+ if (head) {
1458
+ name = head;
1459
+ anchor = this.findAppliedHeadNode(c) ?? c;
1460
+ break;
1461
+ }
1462
+ }
1463
+ }
1464
+ }
1465
+ if (!name || !anchor) return false;
1466
+ return this.publishPatternSynonym(name, node, anchor, ctx);
1467
+ }
1468
+ publishPatternSynonym(name, declNode, anchor, ctx) {
1469
+ if (!name || name === " ") return false;
1470
+ const key = `${ctx.currentModule ?? ""}::${name}`;
1471
+ const existingIdx = ctx.functionSymbolIndex[key];
1472
+ if (existingIdx !== void 0) {
1473
+ const existing = ctx.symbols[existingIdx];
1474
+ if (existing) {
1475
+ const declRange = this.nodeToRange(declNode);
1476
+ existing.range.endLine = Math.max(existing.range.endLine, declRange.endLine);
1477
+ if (existing.range.endIndex !== void 0 && declRange.endIndex !== void 0) {
1478
+ existing.range.endIndex = Math.max(existing.range.endIndex, declRange.endIndex);
1479
+ }
1480
+ }
1481
+ return true;
1482
+ }
1483
+ const range = this.nodeToRange(declNode);
1484
+ range.startLine = anchor.startPosition.row + 1;
1485
+ range.startColumn = anchor.startPosition.column + 1;
1486
+ ctx.symbols.push({
1487
+ name,
1488
+ kind: ASTEngine.SymbolKind.Constructor,
1489
+ range,
1490
+ scope: ctx.currentModule ?? "",
1491
+ ownerName: ctx.currentModule,
1492
+ ownerKind: ASTEngine.SymbolKind.Type,
1493
+ visibility: "public",
1494
+ metadata: { haskellSpecific: true, isPatternSynonym: true }
1495
+ });
1496
+ ctx.functionSymbolIndex[key] = ctx.symbols.length - 1;
1497
+ return true;
1498
+ }
1499
+ /** The node carrying the head name of an applied type/expression spine. */
1500
+ findAppliedHeadNode(node) {
1501
+ let cur = node;
1502
+ let guard = 0;
1503
+ while (cur && guard++ < 64) {
1504
+ if (cur.type === "apply" || cur.type === "parens" || cur.type === "strict") {
1505
+ cur = cur.namedChildCount > 0 ? cur.namedChild(0) : null;
1506
+ continue;
1507
+ }
1508
+ break;
1509
+ }
1510
+ return cur;
1511
+ }
1512
+ // FFI declarations. `foreign import capi "libpq-fe.h PQresultStatus"`
1513
+ // parses as foreign_import → calling_convention, entity (the C symbol),
1514
+ // signature → variable (the HASKELL name) → function (its type). Nothing in
1515
+ // the tree above the module carried the Haskell-side binding, so a file whose
1516
+ // entire content is FFI published one symbol: the module. postgrest's
1517
+ // `Hasql/LibPq14/Ffi.hs` is exactly that file — 6 declarations, 0 published.
1518
+ // The Haskell name is `signature → variable`; the C entity is kept as metadata
1519
+ // and as a `references` edge, because that string is how a reader finds the
1520
+ // symbol in the C headers and nothing else in the graph points at it.
1521
+ processForeignImport(node, ctx, scopeStack) {
1522
+ ctx.patterns["Foreign Imports"] = true;
1523
+ const sig = this.findNamedChildByType(node, ["signature"]);
1524
+ const varNode = sig ? this.findNamedChildByType(sig, ["variable"]) : null;
1525
+ const name = varNode?.text ?? "";
1526
+ if (!name) return false;
1527
+ const convention = this.findNamedChildByType(node, ["calling_convention"])?.text ?? "";
1528
+ const entity = this.foreignEntityName(this.readForeignEntity(node) ?? "", convention);
1529
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1530
+ ctx.symbols.push({
1531
+ name,
1532
+ kind: ASTEngine.SymbolKind.Function,
1533
+ range: this.nodeToRange(node),
1534
+ scope: currentScope?.name || "",
1535
+ ownerName: currentScope?.name || ctx.currentModule,
1536
+ visibility: "public",
1537
+ metadata: {
1538
+ haskellSpecific: true,
1539
+ isForeignImport: true,
1540
+ ...entity ? { foreignEntity: entity } : {},
1541
+ ...convention ? { callingConvention: convention } : {}
1542
+ }
1543
+ });
1544
+ const owner = currentScope?.name || ctx.currentModule;
1545
+ if (owner) {
1546
+ this.pushRel(
1547
+ ctx,
1548
+ owner,
1549
+ name,
1550
+ ASTEngine.RelationshipType.Contains,
1551
+ 1,
1552
+ // The entity travels with the edge as well as the symbol: the cited line
1553
+ // is the `foreign import` line, which spells the C name
1554
+ // (`WaitForSingleObject`) while the Haskell binding adds an underscore
1555
+ // (`waitForSingleObject_`). A consumer — or a gate — checking that the
1556
+ // target name appears at the cited line has to be able to find the
1557
+ // spelling that IS there.
1558
+ { evidenceClass: "structural", isForeignImport: true, ...entity ? { foreignEntity: entity } : {} },
1559
+ this.lineOf(node)
1560
+ );
1561
+ if (entity) {
1562
+ this.pushRel(
1563
+ ctx,
1564
+ name,
1565
+ entity,
1566
+ ASTEngine.RelationshipType.References,
1567
+ 0.9,
1568
+ { evidenceClass: "foreign_symbol", isForeignEntity: true },
1569
+ this.lineOf(node)
1570
+ );
1571
+ }
1572
+ }
1573
+ return true;
1574
+ }
1575
+ processData(node, ctx, scopeStack) {
1576
+ ctx.patterns["Algebraic Data Types"] = true;
1577
+ let name = "";
1578
+ const nameNode = this.findNamedChildByType(node, ["name"]);
1579
+ if (nameNode) name = nameNode.text;
1580
+ else {
1581
+ const dataIdx = this.findChildIndexByText(node, ["data", "newtype"]);
1582
+ if (dataIdx !== -1 && node.childCount > dataIdx + 1) {
1583
+ name = node.child(dataIdx + 1)?.text || "";
1584
+ }
1585
+ }
1586
+ if (!name) return false;
1587
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1588
+ const typeRange = this.nodeToRange(node);
1589
+ if (nameNode) {
1590
+ typeRange.startLine = nameNode.startPosition.row + 1;
1591
+ typeRange.startColumn = nameNode.startPosition.column;
1592
+ }
1593
+ ctx.symbols.push({
1594
+ name,
1595
+ kind: ASTEngine.SymbolKind.Type,
1596
+ range: typeRange,
1597
+ scope: currentScope?.name || "",
1598
+ ownerName: currentScope?.name || ctx.currentModule,
1599
+ visibility: "public",
1600
+ metadata: { haskellSpecific: true, isData: node.type === "data_type" }
1601
+ });
1602
+ const ctorNodes = [];
1603
+ for (const group of ["data_constructors", "gadt_constructors"]) {
1604
+ const g = this.findNamedChildByType(node, [group]);
1605
+ if (!g) continue;
1606
+ for (const c of this.collectNamedChildrenByType(g, ["data_constructor", "gadt_constructor"])) {
1607
+ if (!ctorNodes.includes(c)) ctorNodes.push(c);
1608
+ }
1609
+ }
1610
+ for (const c of this.collectNamedChildrenByType(node, ["newtype_constructor", "data_constructor", "gadt_constructor"])) {
1611
+ if (!ctorNodes.includes(c)) ctorNodes.push(c);
1612
+ }
1613
+ for (const cNode of ctorNodes) {
1614
+ const cName = this.extractConstructorName(cNode);
1615
+ if (cName) {
1616
+ ctx.symbols.push({
1617
+ name: cName,
1618
+ kind: ASTEngine.SymbolKind.Constructor,
1619
+ range: this.nodeToRange(cNode),
1620
+ scope: name,
1621
+ ownerName: name,
1622
+ ownerKind: ASTEngine.SymbolKind.Type,
1623
+ visibility: "public",
1624
+ metadata: { isDataConstructor: true }
1625
+ });
1626
+ }
1627
+ const recordNode = this.findNamedChildByType(cNode, ["record"]);
1628
+ if (recordNode) {
1629
+ const fields = this.collectFields(recordNode);
1630
+ for (const field of fields) {
1631
+ const fNameNode = this.findNamedChildByType(field, ["field_name"]);
1632
+ const varNode = fNameNode ? fNameNode.namedChild(0) : null;
1633
+ if (varNode?.text) {
1634
+ ctx.symbols.push({
1635
+ name: varNode.text,
1636
+ kind: ASTEngine.SymbolKind.Property,
1637
+ range: this.nodeToRange(field),
1638
+ scope: name,
1639
+ ownerName: name,
1640
+ ownerKind: ASTEngine.SymbolKind.Type,
1641
+ visibility: "public",
1642
+ metadata: { haskellSpecific: true, isRecordField: true }
1643
+ });
1644
+ }
1645
+ }
1646
+ }
1647
+ }
1648
+ const deriving = this.findNamedChildByType(node, ["deriving", "deriving_strategy"]);
1649
+ if (deriving) {
1650
+ const derived = [];
1651
+ const derivedNodes = [];
1652
+ const grab = (nn) => {
1653
+ for (let i = 0; i < nn.namedChildCount; i++) {
1654
+ const c = nn.namedChild(i);
1655
+ if (!c) continue;
1656
+ if (c.type === "name") {
1657
+ derived.push(c.text);
1658
+ derivedNodes.push(c);
1659
+ } else if (c.type === "qualified") {
1660
+ derived.push(c.text);
1661
+ derivedNodes.push(c);
1662
+ } else if (c.type === "apply") {
1663
+ const h = c.namedChildCount > 0 ? c.namedChild(0) : null;
1664
+ if (h?.text) {
1665
+ derived.push(h.text);
1666
+ derivedNodes.push(h);
1667
+ }
1668
+ } else if (c.type === "parens") grab(c);
1669
+ }
1670
+ };
1671
+ const tuple = this.findNamedChildByType(deriving, ["tuple"]);
1672
+ if (tuple) grab(tuple);
1673
+ grab(deriving);
1674
+ for (let i = 0; i < derived.length; i++) {
1675
+ this.pushRel(
1676
+ ctx,
1677
+ name,
1678
+ derived[i],
1679
+ ASTEngine.RelationshipType.Implements,
1680
+ 1,
1681
+ { derived: true },
1682
+ this.lineOf(derivedNodes[i])
1683
+ );
1684
+ }
1685
+ }
1686
+ scopeStack.push({ node, name, kind: ASTEngine.SymbolKind.Type });
1687
+ return true;
1688
+ }
1689
+ // data_constructor → prefix → constructor "Node" | record → constructor "Rect"
1690
+ extractConstructorName(node) {
1691
+ const prefix = this.findNamedChildByType(node, ["prefix"]);
1692
+ if (prefix) {
1693
+ const con = this.findNamedChildByType(prefix, ["constructor", "name"]);
1694
+ if (con?.text) return con.text;
1695
+ }
1696
+ const record = this.findNamedChildByType(node, ["record"]);
1697
+ if (record) {
1698
+ const con = this.findNamedChildByType(record, ["constructor"]);
1699
+ if (con?.text) return con.text;
1700
+ }
1701
+ const direct = this.findNamedChildByType(node, ["constructor", "name"]);
1702
+ if (direct?.text) return direct.text;
1703
+ return void 0;
1704
+ }
1705
+ processClass(node, ctx, scopeStack) {
1706
+ ctx.patterns["Type Classes"] = true;
1707
+ let name = "";
1708
+ const nameNode = this.findNamedChildByType(node, ["name"]);
1709
+ if (nameNode) name = nameNode.text;
1710
+ else {
1711
+ const classIdx = this.findChildIndexByText(node, "class");
1712
+ if (classIdx !== -1 && node.childCount > classIdx + 1) {
1713
+ name = node.child(classIdx + 1)?.text || "";
1714
+ }
1715
+ }
1716
+ if (!name) return false;
1717
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1718
+ const context = this.findNamedChildByType(node, ["context"]);
1719
+ if (context) {
1720
+ const superNames = /* @__PURE__ */ new Set();
1721
+ const superNodes = /* @__PURE__ */ new Map();
1722
+ const stack = [context];
1723
+ while (stack.length > 0) {
1724
+ const x = stack.pop();
1725
+ if (x.type === "apply") {
1726
+ let head = x;
1727
+ while (head && head.type === "apply") head = head.namedChildren[0] ?? null;
1728
+ if (head && (head.type === "name" || head.type === "constructor" || head.type === "qualified")) {
1729
+ superNames.add(head.text);
1730
+ if (!superNodes.has(head.text)) superNodes.set(head.text, head);
1731
+ }
1732
+ }
1733
+ for (const c of x.namedChildren) if (c) stack.push(c);
1734
+ }
1735
+ for (const sn of superNames) {
1736
+ if (sn === name) continue;
1737
+ this.pushRel(
1738
+ ctx,
1739
+ name,
1740
+ sn,
1741
+ ASTEngine.RelationshipType.Extends,
1742
+ 1,
1743
+ { isSuperclass: true },
1744
+ this.lineOf(superNodes.get(sn))
1745
+ );
1746
+ }
1747
+ }
1748
+ const classRange = this.nodeToRange(node);
1749
+ if (nameNode) {
1750
+ classRange.startLine = nameNode.startPosition.row + 1;
1751
+ classRange.startColumn = nameNode.startPosition.column;
1752
+ }
1753
+ ctx.symbols.push({
1754
+ name,
1755
+ kind: ASTEngine.SymbolKind.Interface,
1756
+ range: classRange,
1757
+ scope: currentScope?.name || "",
1758
+ ownerName: currentScope?.name || ctx.currentModule,
1759
+ visibility: "public",
1760
+ metadata: { haskellSpecific: true }
1761
+ });
1762
+ scopeStack.push({ node, name, kind: ASTEngine.SymbolKind.Interface });
1763
+ return true;
1764
+ }
1765
+ /**
1766
+ * M60 (#924, haskell E half): the TYPE NAME at the head of an applied type.
1767
+ *
1768
+ * An instance declaration such as instance GArbitrary (M1 i c f) declares an
1769
+ * implementation FOR the type M1 i c f, whose name is its head type constructor
1770
+ * M1; the published symbol was the whole application (M1 i c), i.e. a payload
1771
+ * where a name belongs — measured as 2 distinct strings over 6 occurrences on the
1772
+ * census's own haskell samples (script/m14/tmp/c924-name-surface-probe.ts). Walks
1773
+ * the APPLICATION SPINE (apply / parens / strict) to the leftmost leaf and returns
1774
+ * its text verbatim (a qualified head keeps its dots). The class head already
1775
+ * takes this same left-spine descent; only the instance TARGET did not.
1776
+ */
1777
+ haskellAppliedHeadName(node) {
1778
+ let cur = node;
1779
+ let guard = 0;
1780
+ while (cur && guard++ < 64) {
1781
+ if (cur.type === "apply" || cur.type === "parens" || cur.type === "strict") {
1782
+ cur = cur.namedChildCount > 0 ? cur.namedChild(0) : null;
1783
+ continue;
1784
+ }
1785
+ break;
1786
+ }
1787
+ return cur?.text || void 0;
1788
+ }
1789
+ processInstance(node, ctx, scopeStack) {
1790
+ let className = "";
1791
+ let targetType = "";
1792
+ let targetNode = null;
1793
+ const classNode = this.findNamedChildByType(node, ["name"]);
1794
+ const tpNode = this.findNamedChildByType(node, ["type_patterns"]);
1795
+ if (classNode?.text) className = classNode.text;
1796
+ if (tpNode) {
1797
+ const direct = this.findNamedChildByType(tpNode, ["name", "constructor"]);
1798
+ if (direct?.text) {
1799
+ targetType = direct.text;
1800
+ targetNode = direct;
1801
+ } else {
1802
+ const list = this.findNamedChildByType(tpNode, ["list"]);
1803
+ if (list) {
1804
+ targetType = "[]";
1805
+ } else {
1806
+ const parens = this.findNamedChildByType(tpNode, ["parens"]);
1807
+ if (parens) {
1808
+ const ap = this.findNamedChildByType(parens, ["apply"]);
1809
+ const first = ap?.namedChildCount ? ap.namedChild(0) : null;
1810
+ if (first) {
1811
+ targetType = this.haskellAppliedHeadName(first) ?? first.text;
1812
+ const head = first.namedChildren.find((c) => c.type === "name" || c.type === "constructor");
1813
+ targetNode = head ?? first;
1814
+ }
1815
+ } else {
1816
+ const firstPattern = tpNode.namedChildCount > 0 ? tpNode.namedChild(0) : null;
1817
+ if (firstPattern?.type === "qualified" && firstPattern.text) {
1818
+ targetType = firstPattern.text;
1819
+ targetNode = firstPattern;
1820
+ }
1821
+ }
1822
+ }
1823
+ }
1824
+ }
1825
+ if (!className || !targetType) {
1826
+ const named = [];
1827
+ for (let i = 0; i < node.namedChildCount; i++) {
1828
+ const c = node.namedChild(i);
1829
+ if (c && (c.type === "name" || c.type === "constructor")) {
1830
+ named.push(c.text);
1831
+ }
1832
+ }
1833
+ if (!className && named.length >= 1) className = named[0];
1834
+ if (!targetType && named.length >= 2) targetType = named[1];
1835
+ }
1836
+ const name = targetType || className;
1837
+ if (!name || name === " ") return false;
1838
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1839
+ const instRange = this.nodeToRange(node);
1840
+ if (targetNode) {
1841
+ instRange.startLine = targetNode.startPosition.row + 1;
1842
+ instRange.startColumn = targetNode.startPosition.column;
1843
+ }
1844
+ ctx.symbols.push({
1845
+ name,
1846
+ // An INSTANCE, not a class. It is named after the type it instantiates,
1847
+ // and `contains` is derived from symbol ownership, so publishing it as
1848
+ // a Class asserted this module DECLARES that type. The scope pushed for
1849
+ // the body below stays Class on purpose -- method resolution reads it.
1850
+ kind: ASTEngine.SymbolKind.Instance,
1851
+ range: instRange,
1852
+ scope: currentScope?.name || "",
1853
+ ownerName: currentScope?.name || ctx.currentModule,
1854
+ visibility: "public",
1855
+ metadata: { haskellSpecific: true, className, targetType }
1856
+ });
1857
+ if (className && targetType) {
1858
+ this.pushRel(
1859
+ ctx,
1860
+ targetType,
1861
+ className,
1862
+ ASTEngine.RelationshipType.Implements,
1863
+ 1,
1864
+ { isInstance: true },
1865
+ this.lineOf(classNode)
1866
+ );
1867
+ }
1868
+ const classM = ctx.classMethods.get(className);
1869
+ if (classM && classM.size > 0 && targetType) {
1870
+ const decls = this.findNamedChildByType(node, ["instance_declarations"]);
1871
+ const bodies = [];
1872
+ if (decls) {
1873
+ for (const d of this.collectNamedChildrenByType(decls, ["function", "bind"])) bodies.push(d);
1874
+ }
1875
+ for (const d of this.collectNamedChildrenByType(node, ["function", "bind"])) {
1876
+ if (!bodies.includes(d)) bodies.push(d);
1877
+ }
1878
+ for (const b of bodies) {
1879
+ const mName = b.namedChildCount > 0 ? b.namedChild(0)?.text : void 0;
1880
+ if (!mName || !classM.has(mName)) continue;
1881
+ this.pushRel(
1882
+ ctx,
1883
+ `${targetType}.${mName}`,
1884
+ `${className}.${mName}`,
1885
+ ASTEngine.RelationshipType.Override,
1886
+ 1,
1887
+ { isInstanceMethod: true, evidenceClass: "structural" },
1888
+ this.lineOf(b)
1889
+ );
1890
+ }
1891
+ }
1892
+ scopeStack.push({ node, name: className || name, kind: ASTEngine.SymbolKind.Class });
1893
+ return true;
1894
+ }
1895
+ // ==========================================================================
1896
+ // Call extraction — apply nodes in body position.
1897
+ // ==========================================================================
1898
+ extractApplyCall(node, ctx, scopeStack) {
1899
+ const head = node.namedChildCount > 0 ? node.namedChild(0) : null;
1900
+ if (!head) return;
1901
+ const fn = ctx.currentFunction;
1902
+ if (!fn) return;
1903
+ if (head.type === "constructor" || head.type === "prefix") {
1904
+ if (this.isPatternPosition(node)) return;
1905
+ this.pushRel(
1906
+ ctx,
1907
+ fn,
1908
+ head.text,
1909
+ ASTEngine.RelationshipType.Instantiates,
1910
+ 0.9,
1911
+ {
1912
+ haskellSpecific: true,
1913
+ isConstructorApplication: true
1914
+ },
1915
+ this.lineOf(head)
1916
+ );
1917
+ return;
1918
+ }
1919
+ if (head.type === "qualified") {
1920
+ const mod = this.findNamedChildByType(head, ["module"]);
1921
+ const modText = mod ? this.stripTrailingDot(mod.text) : "";
1922
+ const ctor = this.findNamedChildByType(head, ["constructor"]);
1923
+ if (modText && ctor?.text) {
1924
+ if (!this.isPatternPosition(node)) {
1925
+ this.pushRel(
1926
+ ctx,
1927
+ fn,
1928
+ `${modText}.${ctor.text}`,
1929
+ ASTEngine.RelationshipType.Instantiates,
1930
+ 0.9,
1931
+ {
1932
+ haskellSpecific: true,
1933
+ isQualifiedConstructorApplication: true
1934
+ },
1935
+ this.lineOf(head)
1936
+ );
1937
+ }
1938
+ return;
1939
+ }
1940
+ const func = this.findNamedChildByType(head, ["variable"]);
1941
+ if (modText && func?.text) {
1942
+ this.pushRel(
1943
+ ctx,
1944
+ fn,
1945
+ `${modText}.${func.text}`,
1946
+ ASTEngine.RelationshipType.Calls,
1947
+ 0.9,
1948
+ {
1949
+ haskellSpecific: true,
1950
+ isQualifiedCall: true
1951
+ },
1952
+ this.lineOf(head)
1953
+ );
1954
+ }
1955
+ return;
1956
+ }
1957
+ if (head.type === "variable") {
1958
+ const target = this.resolveCallableTarget(head.text, scopeStack, ctx);
1959
+ if (target !== void 0) {
1960
+ this.pushRel(
1961
+ ctx,
1962
+ fn,
1963
+ target,
1964
+ ASTEngine.RelationshipType.Calls,
1965
+ 0.9,
1966
+ {
1967
+ haskellSpecific: true,
1968
+ context: "local function call"
1969
+ },
1970
+ this.lineOf(head)
1971
+ );
1972
+ }
1973
+ }
1974
+ }
1975
+ isPatternPosition(node) {
1976
+ let cur = node.parent;
1977
+ let prev = node;
1978
+ while (cur) {
1979
+ if (cur.type === "patterns") return true;
1980
+ if (cur.type === "alternative") return true;
1981
+ if (cur.type === "pattern_guard") {
1982
+ if (prev && prev.id === cur.namedChild(0)?.id) return true;
1983
+ } else if (cur.type === "match" || cur.type === "do" || cur.type === "class" || cur.type === "instance") {
1984
+ return false;
1985
+ } else if (cur.type === "function" || cur.type === "bind") {
1986
+ if (prev.type === "match" || prev.type === "patterns") return false;
1987
+ if (prev.id === cur.namedChild(0)?.id) return true;
1988
+ return false;
1989
+ }
1990
+ prev = cur;
1991
+ cur = cur.parent;
1992
+ }
1993
+ return false;
1994
+ }
1995
+ // =========================================================================
1996
+ // Infix handling: backtick calls, $ application, and pattern marking.
1997
+ // HSD1/HSD2 (probe 2026-09-03): infix_id is a backtick call (`x `elem2` xs`);
1998
+ // operator "$" is function application — Haskell's most common idiom. Both
1999
+ // were previously dropped entirely.
2000
+ // =========================================================================
2001
+ processInfix(node, ctx, scopeStack) {
2002
+ const opNode = this.findNamedChildByType(node, ["operator"]);
2003
+ const infixId = this.findNamedChildByType(node, ["infix_id"]);
2004
+ if (infixId) {
2005
+ const tickedCtor = this.findNamedChildByType(infixId, ["constructor"]);
2006
+ if (tickedCtor?.text) {
2007
+ const fnC = ctx.currentFunction;
2008
+ if (fnC && !this.isPatternPosition(node) && !this.isTypePosition(node)) {
2009
+ this.pushRel(
2010
+ ctx,
2011
+ fnC,
2012
+ tickedCtor.text,
2013
+ ASTEngine.RelationshipType.Instantiates,
2014
+ 0.9,
2015
+ {
2016
+ haskellSpecific: true,
2017
+ isTickedConstructorApplication: true
2018
+ },
2019
+ this.lineOf(tickedCtor)
2020
+ );
2021
+ }
2022
+ return;
2023
+ }
2024
+ if (this.isPatternPosition(node)) return;
2025
+ const fn = ctx.currentFunction;
2026
+ const target = this.findNamedChildByType(infixId, ["variable"]);
2027
+ const resolved = target?.text !== void 0 ? this.resolveCallableTarget(target.text, scopeStack, ctx) : void 0;
2028
+ if (fn && resolved !== void 0) {
2029
+ this.pushRel(
2030
+ ctx,
2031
+ fn,
2032
+ resolved,
2033
+ ASTEngine.RelationshipType.Calls,
2034
+ 0.9,
2035
+ {
2036
+ haskellSpecific: true,
2037
+ context: "backtick infix call"
2038
+ },
2039
+ this.lineOf(target)
2040
+ );
2041
+ }
2042
+ } else if (opNode?.text === "$") {
2043
+ const fn = ctx.currentFunction;
2044
+ if (fn) {
2045
+ for (let i = 0; i < node.namedChildCount; i++) {
2046
+ const c = node.namedChild(i);
2047
+ if (!c || c.type === "operator") continue;
2048
+ const resolved = c.type === "variable" ? this.resolveCallableTarget(c.text, scopeStack, ctx) : void 0;
2049
+ if (resolved !== void 0) {
2050
+ this.pushRel(
2051
+ ctx,
2052
+ fn,
2053
+ resolved,
2054
+ ASTEngine.RelationshipType.Calls,
2055
+ 0.9,
2056
+ {
2057
+ haskellSpecific: true,
2058
+ context: "$ application"
2059
+ },
2060
+ this.lineOf(c)
2061
+ );
2062
+ } else if (c.type === "qualified") {
2063
+ const mod = this.findNamedChildByType(c, ["module"]);
2064
+ const func = this.findNamedChildByType(c, ["variable"]);
2065
+ const modText = mod ? this.stripTrailingDot(mod.text) : "";
2066
+ if (modText && func?.text) {
2067
+ this.pushRel(
2068
+ ctx,
2069
+ fn,
2070
+ `${modText}.${func.text}`,
2071
+ ASTEngine.RelationshipType.Calls,
2072
+ 0.9,
2073
+ {
2074
+ haskellSpecific: true,
2075
+ isQualifiedCall: true,
2076
+ context: "$ application"
2077
+ },
2078
+ this.lineOf(c)
2079
+ );
2080
+ }
2081
+ }
2082
+ break;
2083
+ }
2084
+ }
2085
+ } else {
2086
+ this.emitOperatorApplication(node, ctx, scopeStack);
2087
+ }
2088
+ const op = opNode?.text ?? "";
2089
+ if (op === ">>=" || op === ">>" || op === "<-") ctx.patterns["Monads"] = true;
2090
+ if (op === "<$>" || op === "<*>") ctx.patterns["Functors and Applicatives"] = true;
2091
+ if (op === "|>") ctx.patterns["Pipe Operator"] = true;
2092
+ }
2093
+ // ==========================================================================
2094
+ // VERIFY_HS9 — operator and infix-constructor APPLICATION (G1 + G2).
2095
+ //
2096
+ // What was missing, measured (script/m14/tmp/hs9-opshape-probe.ts, 60-file
2097
+ // GHC draw, all counts AST-side):
2098
+ //
2099
+ // EXP/infix:operator 3,140 of which 86 name an operator THIS
2100
+ // file declares as a callable
2101
+ // EXP/infix:constructor_operator 111 an infix DATA-CONSTRUCTOR
2102
+ // application (`a :+: b`) — a
2103
+ // construction, emitted as nothing
2104
+ // EXP/infix_id:constructor 9 a ticked constructor (`x `Cons` y`)
2105
+ // EXP/prefix_id:operator 22 `(<+>) a b`, a prefix application
2106
+ // EXP/prefix_id:constructor_operator 3 `(:+:) a b`
2107
+ // TYP/infix:operator 29 TYPE-level operator — must emit
2108
+ // nothing, and the node type alone
2109
+ // cannot tell it from an expression
2110
+ // PAT/infix:constructor_operator 98 pattern — must emit nothing
2111
+ //
2112
+ // WHY THE POSITION TEST IS NOT OPTIONAL: `infix` is ONE node type for THREE
2113
+ // syntactic positions in the pinned grammar. _exp_infix (grammar/exp.js:353),
2114
+ // _pat_infix (grammar/pat.js:99) and _type_infix (grammar/type.js:116) are all
2115
+ // aliased to `infix`, and all three use the field names left_operand /
2116
+ // operator / right_operand. A type-level operator is a bare `operator` node,
2117
+ // exactly like an expression's — the 29 TYP rows above are sites that an
2118
+ // operator-only test would have turned into 29 fabricated calls.
2119
+ //
2120
+ // WHY NAME RESOLUTION IS RIGHT AND NOT A GUESS: Haskell resolves an
2121
+ // unqualified name lexically, and a module's own top-level declaration — or
2122
+ // class method — SHADOWS the implicit Prelude import. So in a module that
2123
+ // declares `==`, `x == y` IS that module's `==`; the alternative would be an
2124
+ // ambiguous-occurrence error. This is the same rule resolveCallableTarget
2125
+ // already applies to plain variables, extended to operators.
2126
+ //
2127
+ // WHY SECTIONS ARE NOT EMITTED: `(x +)` is a PARTIAL application — it builds
2128
+ // a function value, it performs no call. `(<+>) a b` is a full application
2129
+ // and is emitted. The line is full-application vs partial, and it is stated
2130
+ // here rather than left to a reader of the code.
2131
+ // ==========================================================================
2132
+ emitOperatorApplication(node, ctx, scopeStack) {
2133
+ const fn = ctx.currentFunction;
2134
+ if (!fn) return;
2135
+ if (this.isPatternPosition(node) || this.isTypePosition(node)) return;
2136
+ const opField = node.childForFieldName("operator");
2137
+ const head = opField ?? (node.namedChildCount > 0 ? node.namedChild(0) : null);
2138
+ if (!head) return;
2139
+ if (head.type === "constructor_operator") {
2140
+ this.pushRel(
2141
+ ctx,
2142
+ fn,
2143
+ head.text,
2144
+ ASTEngine.RelationshipType.Instantiates,
2145
+ 0.9,
2146
+ {
2147
+ haskellSpecific: true,
2148
+ isInfixConstructorApplication: true
2149
+ },
2150
+ this.lineOf(head)
2151
+ );
2152
+ return;
2153
+ }
2154
+ if (head.type !== "operator") return;
2155
+ if (head.text === "$") return;
2156
+ const resolved = this.resolveCallableTarget(head.text, scopeStack, ctx);
2157
+ if (resolved === void 0) return;
2158
+ this.pushRel(
2159
+ ctx,
2160
+ fn,
2161
+ resolved,
2162
+ ASTEngine.RelationshipType.Calls,
2163
+ 0.9,
2164
+ {
2165
+ haskellSpecific: true,
2166
+ context: "operator application"
2167
+ },
2168
+ this.lineOf(head)
2169
+ );
2170
+ }
2171
+ /**
2172
+ * VERIFY_HS9: is this node part of a TYPE rather than an expression?
2173
+ *
2174
+ * Needed because `infix` covers expression, pattern AND type position in the
2175
+ * pinned grammar (see emitOperatorApplication). The list is the grammar's own
2176
+ * type-bearing vocabulary; the loop stops at an expression-bearing node so a
2177
+ * `type` buried inside a body does not capture expression operators above it.
2178
+ * Measured 29 type-level `infix:operator` sites on a 60-file draw, so this
2179
+ * test is load-bearing rather than defensive.
2180
+ */
2181
+ isTypePosition(node) {
2182
+ let cur = node.parent;
2183
+ let guard = 0;
2184
+ while (cur && guard++ < 256) {
2185
+ if (HASKELL_TYPE_BEARING_NODES.has(cur.type)) return true;
2186
+ if (cur.type === "match" || cur.type === "do" || cur.type === "bind" || cur.type === "let_in" || cur.type === "lambda" || cur.type === "record" || cur.type === "guard") {
2187
+ return false;
2188
+ }
2189
+ cur = cur.parent;
2190
+ }
2191
+ return false;
2192
+ }
2193
+ /**
2194
+ * Per-symbol cyclomatic complexity (family contract: python #270 / ruby
2195
+ * #269 / php #268). Haskell decision points per the analyzer's own
2196
+ * module-level counter: case, conditional (if), guards.
2197
+ */
2198
+ calculateNodeComplexity(node) {
2199
+ if (!node) return 1;
2200
+ let complexity = 1;
2201
+ const stack = [node];
2202
+ let guard = 0;
2203
+ while (stack.length > 0 && guard++ < 2e5) {
2204
+ const n = stack.pop();
2205
+ if (n.type === "case" || n.type === "conditional" || n.type === "guards") complexity++;
2206
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
2207
+ const child = n.namedChild(i);
2208
+ if (child) stack.push(child);
2209
+ }
2210
+ }
2211
+ return complexity;
2212
+ }
2213
+ /**
2214
+ * Evidence-tier classification — same rules as the python/ruby/php family
2215
+ * (M14 section D requires >=2 distinct evidenceClass values).
2216
+ */
2217
+ classifyEvidenceTier(relationships) {
2218
+ for (const r of relationships) {
2219
+ const md = r.metadata ??= {};
2220
+ if (md.evidenceClass) continue;
2221
+ const t = r.type;
2222
+ const conf = r.confidence ?? 0;
2223
+ if (t === "contains" || t === "exports") {
2224
+ md.evidenceClass = "structural";
2225
+ continue;
2226
+ }
2227
+ if (t === "imports") {
2228
+ md.evidenceClass = md.isHaskellImport ? "import_based" : "structural";
2229
+ continue;
2230
+ }
2231
+ if (t === "references") {
2232
+ md.evidenceClass = md.isHaskellImport ? "import_based" : "reference_qualified";
2233
+ continue;
2234
+ }
2235
+ if (t === "extends" || t === "implements") {
2236
+ md.evidenceClass = "structural";
2237
+ continue;
2238
+ }
2239
+ if (t === "overrides") {
2240
+ md.evidenceClass = md.isInstanceMethod ? "structural" : md.evidenceClass ?? "structural";
2241
+ continue;
2242
+ }
2243
+ if (t === "decorates") {
2244
+ md.evidenceClass = "structural";
2245
+ continue;
2246
+ }
2247
+ if (t === "part_of") {
2248
+ md.evidenceClass = "structural";
2249
+ continue;
2250
+ }
2251
+ if (t === "instantiates") {
2252
+ md.evidenceClass = md.isRecordConstruction || md.isConstructorApplication || md.isQualifiedConstructorApplication || md.isInfixConstructorApplication || md.isTickedConstructorApplication ? "usage_contextual" : "structural";
2253
+ continue;
2254
+ }
2255
+ md.evidenceClass = conf >= 0.85 ? "usage_contextual" : "inferred";
2256
+ }
2257
+ }
2258
+ // Data-DECLARATION records live inside data_constructors; body-position
2259
+ // records sit under a match/do/bind. Walk up: crossing a match/bind/do
2260
+ // before any data_constructor means body position.
2261
+ isDataDeclarationPosition(node) {
2262
+ let cur = node.parent;
2263
+ while (cur) {
2264
+ if (cur.type === "match" || cur.type === "do" || cur.type === "bind" || cur.type === "let_in") return false;
2265
+ if (cur.type === "data_constructor" || cur.type === "newtype_constructor") return true;
2266
+ cur = cur.parent;
2267
+ }
2268
+ return false;
2269
+ }
2270
+ // ==========================================================================
2271
+ // Pure-string helpers (no regex).
2272
+ // ==========================================================================
2273
+ stripOuterParens(text) {
2274
+ let start = 0;
2275
+ let end = text.length;
2276
+ while (start < end && text[start] === "(") start++;
2277
+ while (end > start && text[end - 1] === ")") end--;
2278
+ return text.slice(start, end);
2279
+ }
2280
+ stripTrailingDot(text) {
2281
+ return text.length > 0 && text[text.length - 1] === "." ? text.slice(0, -1) : text;
2282
+ }
2283
+ /**
2284
+ * The C entity a `foreign_import` binds, or undefined when the declaration
2285
+ * names none.
2286
+ *
2287
+ * The Haskell 2010 FFI grammar is `foreign import [cc] [safety] import
2288
+ * [stringliteral] (entity | variadic)`: the string literal is the OPTIONAL
2289
+ * HEADER and the entity is the name that follows it. The pinned grammar models
2290
+ * both as `entity` children, so `foreign import capi "libwidget.h"
2291
+ * "widget_alloc" c_widgetAlloc` carries TWO of them — the header first — and
2292
+ * taking the FIRST match publishes the header as the symbol:
2293
+ * `references c_widgetAlloc -> "libwidget.h"`, a file name where a function
2294
+ * name belongs.
2295
+ *
2296
+ * MEASURED, and it corrected the hypothesis: over all 14,038 admitted `.hs`
2297
+ * files there are 1,153 `foreign_import` nodes and the shape distribution is
2298
+ * 1,040 with ONE quoted `entity` (which IS the entity — GHC writes
2299
+ * `foreign import ccall unsafe "rts_isDebugged" c_rtsIsDebugged`), 113 with NO
2300
+ * `entity` node at all, exactly 1 whose entity sits inside a sibling `ERROR`,
2301
+ * and ZERO with two `entity` children
2302
+ * (`script/m14/tmp/hs-ffi-shapes.ts`). So on THIS corpus the header case never
2303
+ * fires and the only live FFI defect was the quotes. The LAST-entity rule is
2304
+ * kept because it is strictly more correct on a shape the grammar does produce
2305
+ * elsewhere, and the count above is what says its reach here is 0 — a fix whose
2306
+ * corpus measurement is 0 is still worth having, and its 0 is recorded rather
2307
+ * than implied.
2308
+ */
2309
+ readForeignEntity(node) {
2310
+ let found = null;
2311
+ for (let i = 0; i < node.namedChildCount; i++) {
2312
+ const c = node.namedChild(i);
2313
+ if (c?.type === "entity") found = c;
2314
+ }
2315
+ return found?.text;
2316
+ }
2317
+ /**
2318
+ * Drop ONE surrounding pair of double quotes.
2319
+ *
2320
+ * The FFI `entity` is a Haskell string literal, so the grammar's node text is
2321
+ * `"name"` and the quotes are part of the node's text rather than of the name.
2322
+ * Only the exact pair is removed, and only when both ends have one: a C
2323
+ * identifier cannot contain `"`, and a bare word that happens to be an entity
2324
+ * (the `foreign export`/`foreign import` variants whose entity is not a literal)
2325
+ * is left exactly as it was. No regex — the lane's string contract is char
2326
+ * scans over values already extracted from AST nodes.
2327
+ */
2328
+ stripQuotes(text) {
2329
+ if (text.length >= 2 && text[0] === '"' && text[text.length - 1] === '"') return text.slice(1, -1);
2330
+ return text;
2331
+ }
2332
+ /**
2333
+ * THE C SYMBOL an FFI `entity` literal binds, not the whole literal.
2334
+ *
2335
+ * MEASURED DEFECT (2026-10-03, `script/m14/tmp/hs-verify-ffi-instance.ts`, tree
2336
+ * pass over a stride-40 draw of the GHC corpus — 351 files, 23 `foreign_import`
2337
+ * nodes): 7 of those 23 entity literals (30.4 %) carry a HEADER AND THE C NAME
2338
+ * IN ONE STRING, separated by a space, and the analyzer published the header as
2339
+ * the symbol:
2340
+ *
2341
+ * references log1pDoubleOld -> "math.h log1p" <- a FILE NAME
2342
+ * references functionInC -> "T15933.h function_in_c"
2343
+ *
2344
+ * This is the Haskell 2010 Report's own grammar, F.3.2.1:
2345
+ *
2346
+ * impent → 'static' [chname] [&] [cid] | string
2347
+ *
2348
+ * and for the string form the header and the entity share one literal, so
2349
+ * `foreign import ccall "T15933.h function_in_c"` binds the C symbol
2350
+ * `function_in_c`. GHC writes both shapes in this very corpus:
2351
+ * `foreign import ccall unsafe "RTSkeepAlive" c_keepCAFsForGHCi` (name only)
2352
+ * and the two above (header + name). The name-only shape is unaffected, which
2353
+ * is why the defect sat unread: every spot check drew the common form.
2354
+ *
2355
+ * WHY THE PRIOR MEASUREMENT MISSED IT. `readForeignEntity`'s own comment
2356
+ * records a tree census of 1,153 `foreign_import` nodes with "ZERO with two
2357
+ * `entity` children", and concluded the header case never fires. That count is
2358
+ * of NODES, and it is correct — this grammar really does model the header and
2359
+ * the entity as ONE `entity` node whose text is the whole literal. The header
2360
+ * is inside the string, not beside it, so no node census can see it; only the
2361
+ * literal's CONTENT distinguishes the two shapes. A fix whose measured reach
2362
+ * was recorded as 0 was recorded from the wrong quantity.
2363
+ *
2364
+ * RULE, IN TWO PARTS, BOTH CONDITIONED ON THE CALLING CONVENTION.
2365
+ *
2366
+ * (a) `javascript` NAMES NO ENTITY, so it yields "". `foreign import
2367
+ * javascript "(($1, $2) => { return $1($2); })"` binds a JAVASCRIPT BODY.
2368
+ * Measured over a stride-12 draw (1,170 files, 62 `foreign_import` nodes,
2369
+ * `script/m14/tmp/hs-verify-ffi-conv.ts`): 13 of 13 `javascript` nodes name
2370
+ * JS code and not one names an entity. Before this branch the analyzer
2371
+ * published those bodies verbatim, and 8 of them SHIPPED — they contain
2372
+ * none of the characters `pool-worker.filterNoiseRelationships` bans, so
2373
+ * the filter did not catch them:
2374
+ *
2375
+ * references js_print -> "console.log($1)"
2376
+ * references js_req_to_str -> "$1.text($2)"
2377
+ * references js_server_create -> "$1.createServer($2)"
2378
+ * references js_fetch_post -> "r.text();"
2379
+ *
2380
+ * That is the R7 payload-as-name shape: a code fragment standing where a
2381
+ * symbol belongs, counted by bb_graph and displayed by bb_impact. Under R7
2382
+ * the honest state for "no entity is named here" is ABSENCE. The declaration
2383
+ * still publishes its Haskell symbol and its `contains` edge; only the
2384
+ * `references` edge — which asserts an external symbol exists — is withheld.
2385
+ *
2386
+ * (b) For every C convention (`ccall`, `capi`, `stdcall`, `prim`, …) the C
2387
+ * entity is the LAST whitespace-separated token of the unquoted literal,
2388
+ * less a leading `&`. A C identifier contains neither a space nor a `&`
2389
+ * sigil, and the Report puts the header and the `static` keyword before the
2390
+ * entity, separated by a space. Measured in the same draw, the C
2391
+ * conventions are where the header and the `&` actually live:
2392
+ *
2393
+ * capi HEADER_PLUS_NAME x6 "math.h log1p"
2394
+ * ccall HEADER_PLUS_NAME x2 "test_error_throwing.h equivalent_to_id"
2395
+ * ccall AMPERSAND x1 "stdio.h &putchar"
2396
+ *
2397
+ * against 37 name-only C nodes and 13 javascript ones. The `&` is Report
2398
+ * F.3.2.1's `impent → 'static' [chname] [&] [cid]`: it means "pass the
2399
+ * address of this entity" and is not part of the entity's spelling, so
2400
+ * `foreign import ccall unsafe "&ghc_unique_counter64" ghc_unique_counter64`
2401
+ * (compiler/GHC/Types/Unique/Supply.hs:280) names the C symbol
2402
+ * `ghc_unique_counter64`.
2403
+ *
2404
+ * A corpus-wide count of the two prefix defects together, over the 1,356 quoted
2405
+ * FFI literals GHC writes: **391 header+name and 42 `&`-prefixed = 433 of
2406
+ * 1,356 (31.9 %)**, with 923 clean. That count is taken over the literals; the
2407
+ * convention split is taken over the tree, because only the tree knows which
2408
+ * convention a literal belongs to and the two must not be conflated — 10 of the
2409
+ * 391 text-level "header" hits are JavaScript bodies, not headers.
2410
+ */
2411
+ foreignEntityName(literal, convention) {
2412
+ if (convention === "javascript") return "";
2413
+ const unquoted = this.stripQuotes(literal).trim();
2414
+ if (!unquoted) return "";
2415
+ let start = 0;
2416
+ for (let i = 0; i < unquoted.length; i++) {
2417
+ if (unquoted[i] === " " || unquoted[i] === " " || unquoted[i] === "\n" || unquoted[i] === "\r") {
2418
+ start = i + 1;
2419
+ }
2420
+ }
2421
+ let name = unquoted.slice(start);
2422
+ if (name[0] === "&") name = name.slice(1);
2423
+ return name;
2424
+ }
2425
+ hasArrowInParens(text) {
2426
+ let depth = 0;
2427
+ for (let i = 0; i < text.length - 1; i++) {
2428
+ const ch = text[i];
2429
+ if (ch === "(") depth++;
2430
+ else if (ch === ")") depth = Math.max(0, depth - 1);
2431
+ else if (ch === "-" && text[i + 1] === ">" && depth > 0) return true;
2432
+ }
2433
+ return false;
2434
+ }
2435
+ splitOnArrows(text) {
2436
+ const parts = [];
2437
+ let cur = "";
2438
+ for (let i = 0; i < text.length; i++) {
2439
+ if (text[i] === "-" && text[i + 1] === ">") {
2440
+ parts.push(cur);
2441
+ cur = "";
2442
+ i++;
2443
+ } else {
2444
+ cur += text[i];
2445
+ }
2446
+ }
2447
+ parts.push(cur);
2448
+ return parts;
2449
+ }
2450
+ nodeToRange(node) {
2451
+ return {
2452
+ startLine: node.startPosition.row + 1,
2453
+ startColumn: node.startPosition.column + 1,
2454
+ endLine: node.endPosition.row + 1,
2455
+ endColumn: node.endPosition.column + 1,
2456
+ startIndex: node.startIndex,
2457
+ endIndex: node.endIndex
2458
+ };
2459
+ }
2460
+ findNamedChildByType(node, types, extraPredicate) {
2461
+ for (let i = 0; i < node.namedChildCount; i++) {
2462
+ const child = node.namedChild(i);
2463
+ if (!child) continue;
2464
+ let found = false;
2465
+ for (let j = 0; j < types.length; j++) {
2466
+ if (types[j] === child.type) {
2467
+ found = true;
2468
+ break;
2469
+ }
2470
+ }
2471
+ if (found || extraPredicate && extraPredicate(child.type)) return child;
2472
+ }
2473
+ return null;
2474
+ }
2475
+ collectFields(recordNode) {
2476
+ const fields = [];
2477
+ const stack = [recordNode];
2478
+ while (stack.length > 0) {
2479
+ const n = stack.pop();
2480
+ for (let i = 0; i < n.namedChildCount; i++) {
2481
+ const child = n.namedChild(i);
2482
+ if (!child) continue;
2483
+ if (child.type === "field") fields.push(child);
2484
+ else stack.push(child);
2485
+ }
2486
+ }
2487
+ return fields;
2488
+ }
2489
+ collectNamedChildrenByType(node, types) {
2490
+ const matches = [];
2491
+ for (let i = 0; i < node.namedChildCount; i++) {
2492
+ const child = node.namedChild(i);
2493
+ if (child) {
2494
+ let found = false;
2495
+ for (let j = 0; j < types.length; j++) {
2496
+ if (types[j] === child.type) {
2497
+ found = true;
2498
+ break;
2499
+ }
2500
+ }
2501
+ if (found) matches.push(child);
2502
+ }
2503
+ }
2504
+ return matches;
2505
+ }
2506
+ hasChildOfType(node, type) {
2507
+ for (let i = 0; i < node.childCount; i++) {
2508
+ const child = node.child(i);
2509
+ if (child && child.type === type) return true;
2510
+ }
2511
+ return false;
2512
+ }
2513
+ findChildIndexByText(node, needle) {
2514
+ const needles = [];
2515
+ if (Array.isArray(needle)) {
2516
+ for (let i = 0; i < needle.length; i++) needles.push(needle[i]);
2517
+ } else {
2518
+ needles.push(needle);
2519
+ }
2520
+ for (let i = 0; i < node.childCount; i++) {
2521
+ const child = node.child(i);
2522
+ if (child) {
2523
+ for (let j = 0; j < needles.length; j++) {
2524
+ if (needles[j] === child.text) return i;
2525
+ }
2526
+ }
2527
+ }
2528
+ return -1;
2529
+ }
2530
+ };
2531
+ export {
2532
+ RealHaskellAnalyzer
2533
+ };