@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,2390 @@
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-fsharp-analyzer.ts
14
+ import * as path from "path";
15
+ import { existsSync } from "fs";
16
+ var FSHARP_PROJECT_ROOT_CACHE = /* @__PURE__ */ new Map();
17
+ function fsharpRepoRelPath(projectRoot, absPath) {
18
+ const rel = path.relative(path.resolve(projectRoot), path.resolve(absPath));
19
+ if (rel.length === 0 || path.isAbsolute(rel)) return null;
20
+ if (rel === ".." || rel.startsWith(`..${path.sep}`)) return null;
21
+ return rel.split(path.sep).join("/");
22
+ }
23
+ function fsharpProjectRootOf(filePath) {
24
+ const start = path.dirname(path.resolve(filePath));
25
+ const seen = [];
26
+ let dir = start;
27
+ for (let i = 0; i < 64; i++) {
28
+ const cached = FSHARP_PROJECT_ROOT_CACHE.get(dir);
29
+ if (cached !== void 0) {
30
+ for (const d of seen) FSHARP_PROJECT_ROOT_CACHE.set(d, cached);
31
+ return cached;
32
+ }
33
+ seen.push(dir);
34
+ if (existsSync(path.join(dir, ".git"))) {
35
+ if (FSHARP_PROJECT_ROOT_CACHE.size > 2e4) FSHARP_PROJECT_ROOT_CACHE.clear();
36
+ for (const d of seen) FSHARP_PROJECT_ROOT_CACHE.set(d, dir);
37
+ return dir;
38
+ }
39
+ const parent = path.dirname(dir);
40
+ if (parent === dir) break;
41
+ dir = parent;
42
+ }
43
+ if (FSHARP_PROJECT_ROOT_CACHE.size > 2e4) FSHARP_PROJECT_ROOT_CACHE.clear();
44
+ for (const d of seen) FSHARP_PROJECT_ROOT_CACHE.set(d, null);
45
+ return null;
46
+ }
47
+ var FSHARP_CONSOLE_BUILTINS = /* @__PURE__ */ new Set([
48
+ "printfn",
49
+ "printf",
50
+ "eprintfn",
51
+ "eprintf",
52
+ "fprintfn",
53
+ "fprintf",
54
+ "Console.WriteLine",
55
+ "Console.Write",
56
+ "Console.Error.WriteLine",
57
+ "Console.Error.Write"
58
+ ]);
59
+ var OBJECT_LEAF_METHODS = /* @__PURE__ */ new Set(["ToString", "GetHashCode", "Equals", "GetType", "ReferenceEquals"]);
60
+ var PIPELINE_CALLEE_SIDE = /* @__PURE__ */ new Map([
61
+ ["|>", "right"],
62
+ ["|>>", "right"],
63
+ [">>=", "right"],
64
+ ["<|", "left"],
65
+ ["<<", "left"]
66
+ ]);
67
+ var PIPELINE_NAMEY = /* @__PURE__ */ new Set(["long_identifier", "identifier", "long_identifier_or_op", "dot_expression"]);
68
+ var isCallableOwnerNode = (n) => n.type === "member_defn" || n.type === "function_or_value_defn" || n.type === "additional_constr_defn";
69
+ var TYPE_CLAUSE_TYPES = /* @__PURE__ */ new Set([
70
+ "type_abbrev_defn",
71
+ "anon_type_defn",
72
+ "record_type_defn",
73
+ "union_type_defn",
74
+ "interface_type_defn",
75
+ "enum_type_defn",
76
+ "delegate_type_defn",
77
+ "type_declaration"
78
+ ]);
79
+ var TYPE_WRAPPER_NODES = /* @__PURE__ */ new Set([
80
+ "simple_type",
81
+ "postfix_type",
82
+ "paren_type",
83
+ "generic_type",
84
+ "function_type",
85
+ "compound_type",
86
+ "list_type",
87
+ "atomic_type",
88
+ "nullable_type",
89
+ "struct_type",
90
+ "static_type",
91
+ "byref_type",
92
+ "flexible_type",
93
+ "types"
94
+ ]);
95
+ var UNRESOLVED_TYPE = "unresolved-type-name";
96
+ var UNRESOLVED_NAMESPACE = "unresolved-namespace";
97
+ var UNRESOLVED_BASE = "unresolved-base-name";
98
+ var UNRESOLVED_INTERFACE = "unresolved-interface-name";
99
+ var TYPE_POSITION_KINDS = /* @__PURE__ */ new Set([
100
+ ASTEngine.SymbolKind.Class,
101
+ ASTEngine.SymbolKind.Interface,
102
+ ASTEngine.SymbolKind.Enum,
103
+ ASTEngine.SymbolKind.Type,
104
+ ASTEngine.SymbolKind.Struct,
105
+ ASTEngine.SymbolKind.Delegate
106
+ ]);
107
+ var RealFsharpAnalyzer = class {
108
+ analyze(rootNode, content, filePath, context) {
109
+ const symbols = [];
110
+ const relationships = [];
111
+ const imports = [];
112
+ const exports = [];
113
+ const errors = [];
114
+ const publishedNames = /* @__PURE__ */ new Set();
115
+ const fqnOfName = /* @__PURE__ */ new Map();
116
+ const qualifyScope = (n) => n === void 0 ? void 0 : fqnOfName.get(n) ?? n;
117
+ const publishedQnames = /* @__PURE__ */ new Set();
118
+ const typeFqnOfName = /* @__PURE__ */ new Map();
119
+ const publishedTypeNames = /* @__PURE__ */ new Set();
120
+ const isIdentStart = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_";
121
+ const isIdentPart = (c) => isIdentStart(c) || c >= "0" && c <= "9" || c === "'";
122
+ const isSpaceChar = (c) => {
123
+ if (c === " " || c === " " || c === "\n" || c === "\v" || c === "\f" || c === "\r") return true;
124
+ if (c === "\xA0" || c === "\uFEFF" || c === "\u1680" || c === "\u2028" || c === "\u2029" || c === "\u202F" || c === "\u205F" || c === "\u3000") return true;
125
+ const cp = c.codePointAt(0) ?? 0;
126
+ return cp >= 8192 && cp <= 8202;
127
+ };
128
+ const isName = (s) => {
129
+ if (s.length === 0) return false;
130
+ if (s.startsWith("`")) {
131
+ const q = s.length >= 4 && s.startsWith("``") ? 2 : 1;
132
+ const close = q === 2 ? "``" : "`";
133
+ if (s.length < q * 2 || !s.endsWith(close)) return false;
134
+ return !s.slice(q, s.length - q).includes("`");
135
+ }
136
+ if (s.startsWith("(")) {
137
+ if (s.length < 3 || !s.endsWith(")")) return false;
138
+ for (let i = 1; i < s.length - 1; i++) {
139
+ const c = s[i];
140
+ if (c === "(" || c === ")" || isSpaceChar(c)) return false;
141
+ }
142
+ return true;
143
+ }
144
+ if (!isIdentStart(s[0])) return false;
145
+ for (let i = 1; i < s.length; i++) if (!isIdentPart(s[i])) return false;
146
+ return true;
147
+ };
148
+ const localNames = /* @__PURE__ */ new Set();
149
+ const openedScopes = /* @__PURE__ */ new Set();
150
+ const moduleLike = (seg) => !!seg && seg.length > 0 && seg[0] >= "A" && seg[0] <= "Z";
151
+ const targetSpelling = (written, enclosingType, position = "type") => {
152
+ const parts = written.split(".");
153
+ if (!parts.every(isName)) {
154
+ const last = [...parts].reverse().find(isName);
155
+ if (last) {
156
+ const prefix = written.slice(0, written.lastIndexOf(last));
157
+ const cut = prefix.endsWith(".") ? prefix.slice(0, -1) : prefix;
158
+ return { target: last, resolution: "call-chain-receiver", ...cut ? { qualifier: cut } : {} };
159
+ }
160
+ return { target: written, resolution: "not-a-name" };
161
+ }
162
+ const leaf = parts[parts.length - 1] || written;
163
+ if (parts.length === 1) {
164
+ if (localNames.has(written)) return { target: written, resolution: "local-binding" };
165
+ const fqn = fqnOfName.get(written);
166
+ return fqn ? { target: fqn, resolution: "in-file-declaration" } : { target: written, resolution: "bare-external" };
167
+ }
168
+ const head = parts[0];
169
+ const qualifier = parts.slice(0, -1).join(".");
170
+ if (head === "this") {
171
+ return enclosingType ? { target: `${enclosingType}.${leaf}`, resolution: "self-member" } : { target: leaf, resolution: "self-member-unknown-type", qualifier };
172
+ }
173
+ if (head === "base") {
174
+ const b = enclosingType ? baseTypes.get(enclosingType) : void 0;
175
+ const bLeaf = b ? b.split(".").pop() : void 0;
176
+ return bLeaf && fqnOfName.has(bLeaf) ? { target: `${bLeaf}.${leaf}`, resolution: "base-member" } : { target: leaf, resolution: "base-member-external", qualifier };
177
+ }
178
+ if (publishedQnames.has(written)) return { target: written, resolution: "in-file-qualified", qualifier };
179
+ const penult = parts[parts.length - 2];
180
+ if (position === "member") {
181
+ if (parts.length > 2 && moduleLike(penult)) {
182
+ const two = `${penult}.${leaf}`;
183
+ if (publishedQnames.has(two)) return { target: two, resolution: "in-file-qualified", qualifier };
184
+ return { target: two, resolution: "module-member", qualifier };
185
+ }
186
+ if (parts.length === 2 && moduleLike(head)) {
187
+ return {
188
+ target: written,
189
+ resolution: openedScopes.has(head) ? "opened-module" : "external-type-or-module",
190
+ qualifier
191
+ };
192
+ }
193
+ return { target: leaf, resolution: "receiver-value", qualifier };
194
+ }
195
+ if (openedScopes.has(head)) return { target: leaf, resolution: "opened-module", qualifier };
196
+ return { target: leaf, resolution: moduleLike(head) ? "external-type-or-module" : "receiver-value", qualifier };
197
+ };
198
+ const bindingSpelled = (owner, spelled) => spelled.resolution === "local-binding" && owner ? { ...spelled, target: `${owner}${BINDING_SEP}${spelled.target}`, localBinding: true } : spelled;
199
+ const publish = (s) => {
200
+ if (s?.name) publishedNames.add(s.name);
201
+ const owner = String(s?.ownerName ?? "");
202
+ if (owner.length > 0 && s?.name) {
203
+ const metadata = s.metadata ?? {};
204
+ if (metadata.fqn === void 0) metadata.fqn = `${owner}.${s.name}`;
205
+ s.metadata = metadata;
206
+ }
207
+ if (s?.name && !fqnOfName.has(s.name)) {
208
+ const metadata = s.metadata ?? {};
209
+ const fqn = typeof metadata.fqn === "string" ? metadata.fqn : s.name;
210
+ fqnOfName.set(s.name, fqn);
211
+ publishedQnames.add(fqn);
212
+ }
213
+ if (s?.name && TYPE_POSITION_KINDS.has(String(s.kind ?? ""))) {
214
+ publishedTypeNames.add(s.name);
215
+ if (!typeFqnOfName.has(s.name)) {
216
+ const metadata = s.metadata ?? {};
217
+ const fqn = typeof metadata.fqn === "string" ? metadata.fqn : s.name;
218
+ typeFqnOfName.set(s.name, fqn);
219
+ }
220
+ }
221
+ symbols.push(s);
222
+ };
223
+ const seenRels = /* @__PURE__ */ new Set();
224
+ const pushRel = (source, target, type, confidence, metadata = {}, line) => {
225
+ const hasLine = typeof line === "number" && Number.isInteger(line) && line > 0;
226
+ const key = `${source}|${type}|${target}|${hasLine ? line : ""}`;
227
+ if (seenRels.has(key)) return;
228
+ seenRels.add(key);
229
+ relationships.push({
230
+ source,
231
+ target,
232
+ type,
233
+ confidence,
234
+ metadata,
235
+ ...hasLine ? { line_number: line } : {}
236
+ });
237
+ };
238
+ const lineOf = (node) => {
239
+ if (!node) return void 0;
240
+ const line = node.startPosition.row + 1;
241
+ return line > 0 ? line : void 0;
242
+ };
243
+ const abstractMemberNames = /* @__PURE__ */ new Set();
244
+ const baseTypes = /* @__PURE__ */ new Map();
245
+ const typeNamesDeclaredInFile = this.collectDeclaredTypeNames(rootNode);
246
+ const fileScope = this.collectFileScope(rootNode);
247
+ const fileIdentity = this.fsharpFileIdentity(filePath, context);
248
+ const fileScopeName = fileScope ?? fileIdentity.stem;
249
+ const typeExtensionsPublished = /* @__PURE__ */ new Set();
250
+ const moduleNames = /* @__PURE__ */ new Set();
251
+ const namespaceNames = /* @__PURE__ */ new Set();
252
+ const walkStack = [{ node: rootNode, scopePath: [], callOwner: void 0, callOwnerQualified: void 0, bodyDepth: 0 }];
253
+ while (walkStack.length > 0) {
254
+ const { node, scopePath, callOwner, callOwnerQualified, bodyDepth, asTypeUnit, unitAttributes } = walkStack.pop();
255
+ const type = node.type;
256
+ if (type === "ERROR") {
257
+ errors.push({
258
+ message: "parse recovery region: the tree here is the parser's reconstruction, not the source",
259
+ range: { startLine: node.startPosition.row + 1, startColumn: node.startPosition.column, endLine: node.endPosition.row + 1, endColumn: node.endPosition.column },
260
+ severity: "warning"
261
+ });
262
+ continue;
263
+ }
264
+ if (node.isMissing) {
265
+ errors.push({
266
+ message: `missing ${node.type} \u2014 the parser inserted this token`,
267
+ range: { startLine: node.startPosition.row + 1, startColumn: node.startPosition.column, endLine: node.endPosition.row + 1, endColumn: node.endPosition.column },
268
+ severity: "warning"
269
+ });
270
+ continue;
271
+ }
272
+ let currentScope = [...scopePath];
273
+ let childCallOwner = callOwner;
274
+ let childCallOwnerQualified = callOwnerQualified;
275
+ let childBodyDepth = bodyDepth;
276
+ if (type === "namespace" || type === "named_module" || type === "module_defn") {
277
+ const nameNode = this.findChild(node, (n) => n.type === "identifier" || n.type === "long_identifier");
278
+ if (nameNode && nameNode.text) {
279
+ const name = nameNode.text;
280
+ publish({
281
+ name,
282
+ kind: type === "namespace" ? ASTEngine.SymbolKind.Namespace : ASTEngine.SymbolKind.Module,
283
+ range: this.nodeToRange(node),
284
+ ownerName: currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0,
285
+ metadata: { nodeType: type }
286
+ });
287
+ if (type === "namespace") namespaceNames.add(name);
288
+ else moduleNames.add(name);
289
+ currentScope.push(name);
290
+ }
291
+ } else if (type === "type_definition" || asTypeUnit) {
292
+ const isExtensionBody = !!this.findChild(node, (n) => n.type === "type_extension");
293
+ const isForwardDeclaration = !!this.findChild(node, (n) => n.type === "type_declaration");
294
+ if (!asTypeUnit) {
295
+ const clauseNodes = this.filterChildren(
296
+ node,
297
+ (n) => TYPE_CLAUSE_TYPES.has(n.type) && !!this.findChild(n, (c) => c.type === "type_name")
298
+ );
299
+ if (clauseNodes.length >= 2) {
300
+ const keyOf = (n) => `${n.startIndex}:${n.endIndex}`;
301
+ const clauseKeys = new Set(clauseNodes.map(keyOf));
302
+ const attrsOfClause = /* @__PURE__ */ new Map();
303
+ const pending = [];
304
+ for (let i = 0; i < node.namedChildCount; i++) {
305
+ const c = node.namedChild(i);
306
+ if (!c) continue;
307
+ if (c.type === "attributes") pending.push(c);
308
+ else if (clauseKeys.has(keyOf(c))) attrsOfClause.set(keyOf(c), pending.splice(0, pending.length));
309
+ }
310
+ const firstKey = keyOf(clauseNodes[0]);
311
+ if (pending.length > 0) {
312
+ attrsOfClause.set(firstKey, [...attrsOfClause.get(firstKey) ?? [], ...pending]);
313
+ }
314
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
315
+ const child = node.namedChild(i);
316
+ if (!child || child.type === "attributes") continue;
317
+ const key = keyOf(child);
318
+ const isClause = clauseKeys.has(key);
319
+ walkStack.push({
320
+ node: child,
321
+ scopePath: currentScope,
322
+ callOwner: childCallOwner,
323
+ callOwnerQualified: childCallOwnerQualified,
324
+ bodyDepth: childBodyDepth,
325
+ ...isClause ? { asTypeUnit: true, unitAttributes: attrsOfClause.get(key) } : {}
326
+ });
327
+ }
328
+ continue;
329
+ }
330
+ }
331
+ const declarationAttributes = asTypeUnit ? [...unitAttributes ?? [], ...this.filterChildren(node, (n) => n.type === "attributes")] : this.filterChildren(node, (n) => n.type === "attributes");
332
+ const typeNameNode = this.findDescendant(node, (n) => n.type === "type_name");
333
+ const typeInfo = typeNameNode ? this.typeInfoOf(typeNameNode) : null;
334
+ const name = typeInfo?.name ?? null;
335
+ const accessModifier = this.accessModifierOf(node, typeNameNode ?? null);
336
+ if (name) {
337
+ const anonNode = node.type === "anon_type_defn" ? node : this.findChild(node, (n) => n.type === "anon_type_defn");
338
+ const hasAnonTypeDefn = !!anonNode;
339
+ const hasRecordTypeDefn = node.type === "record_type_defn" || this.findDescendants(node, (n) => n.type === "record_type_defn").length > 0;
340
+ const hasUnionTypeDefn = node.type === "union_type_defn" || this.findDescendants(node, (n) => n.type === "union_type_defn").length > 0;
341
+ const hasInterfaceTypeDefn = node.type === "interface_type_defn" || this.findDescendants(node, (n) => n.type === "interface_type_defn").length > 0;
342
+ const hasEnumTypeDefn = node.type === "enum_type_defn" || this.findDescendants(node, (n) => n.type === "enum_type_defn").length > 0;
343
+ const hasDelegateTypeDefn = node.type === "delegate_type_defn" || this.findDescendants(node, (n) => n.type === "delegate_type_defn").length > 0;
344
+ const hasTypeAbbrevDefn = node.type === "type_abbrev_defn" || this.findDescendants(node, (n) => n.type === "type_abbrev_defn").length > 0;
345
+ let kind = ASTEngine.SymbolKind.Class;
346
+ let isInterface = false;
347
+ const isMeasure = declarationAttributes.some(
348
+ (attrs) => this.findDescendants(attrs, (n) => n.type === "attribute", 3).some((attr) => {
349
+ const nm = this.findDescendant(attr, (n) => n.type === "long_identifier", 3) ?? this.findDescendant(attr, (n) => n.type === "identifier", 3);
350
+ return nm?.text === "Measure" || nm?.text === "MeasureAttribute";
351
+ })
352
+ );
353
+ if (isMeasure) kind = ASTEngine.SymbolKind.Type;
354
+ if (hasRecordTypeDefn) {
355
+ kind = ASTEngine.SymbolKind.Class;
356
+ } else if (hasUnionTypeDefn) {
357
+ kind = ASTEngine.SymbolKind.Enum;
358
+ } else if (hasInterfaceTypeDefn) {
359
+ kind = ASTEngine.SymbolKind.Interface;
360
+ isInterface = true;
361
+ } else if (hasEnumTypeDefn) {
362
+ kind = ASTEngine.SymbolKind.Enum;
363
+ } else if (hasDelegateTypeDefn) kind = ASTEngine.SymbolKind.Type;
364
+ else if (hasTypeAbbrevDefn) kind = ASTEngine.SymbolKind.Type;
365
+ else if (hasAnonTypeDefn) {
366
+ if (anonNode) {
367
+ const memberSigs = this.findDescendants(anonNode, (n) => n.type === "member_signature");
368
+ const hasMemberSig = memberSigs.length > 0;
369
+ const hasInherits = this.findDescendants(anonNode, (n) => n.type === "class_inherits_decl").length > 0;
370
+ const hasInterfaceImpl = this.findDescendants(anonNode, (n) => n.type === "interface_implementation").length > 0;
371
+ const hasConcreteMembers = this.findDescendants(
372
+ anonNode,
373
+ (n) => n.type === "member_defn" && !this.findDescendant(n, (child) => child.type === "member_signature")
374
+ ).length > 0;
375
+ if (hasMemberSig && !hasInherits && !hasInterfaceImpl && !hasConcreteMembers) {
376
+ kind = ASTEngine.SymbolKind.Interface;
377
+ isInterface = true;
378
+ }
379
+ }
380
+ }
381
+ const isExtensionOnly = isExtensionBody && !typeNamesDeclaredInFile.has(name) && !typeExtensionsPublished.has(name);
382
+ if (!isExtensionBody || isExtensionOnly) {
383
+ publish({
384
+ name,
385
+ kind,
386
+ range: this.nodeToRange(node),
387
+ ownerName: currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0,
388
+ genericParameters: typeInfo?.genericParameters,
389
+ metadata: {
390
+ nodeType: type,
391
+ ...isExtensionBody ? { isTypeExtension: true } : {},
392
+ ...isForwardDeclaration ? { isForwardDeclaration: true } : {},
393
+ ...accessModifier ? { accessModifier } : {}
394
+ }
395
+ });
396
+ }
397
+ if (isExtensionBody) typeExtensionsPublished.add(name);
398
+ currentScope.push(name);
399
+ const inherits = this.findDescendants(node, (n) => n.type === "class_inherits_decl", 8);
400
+ if (inherits.length > 0) {
401
+ const firstInherit = inherits[0];
402
+ const baseType = this.findDescendant(firstInherit, (n) => n.type === "long_identifier" || n.type === "identifier");
403
+ if (baseType?.text) baseTypes.set(name, baseType.text);
404
+ }
405
+ if (hasRecordTypeDefn) {
406
+ const fields = this.findDescendants(node, (n) => n.type === "record_field");
407
+ for (const field of fields) {
408
+ const fieldName = this.findChild(field, (n) => n.type === "identifier");
409
+ if (!fieldName?.text) continue;
410
+ publish({
411
+ name: fieldName.text,
412
+ kind: ASTEngine.SymbolKind.Property,
413
+ range: this.nodeToRange(field),
414
+ ownerName: name,
415
+ ownerKind: kind,
416
+ metadata: { nodeType: "record_field", isRecordField: true }
417
+ });
418
+ }
419
+ }
420
+ if (kind === ASTEngine.SymbolKind.Enum) {
421
+ const unionDefns = node.type === "union_type_defn" ? [node] : this.findDescendants(node, (n) => n.type === "union_type_defn");
422
+ for (const unionDefn of unionDefns) {
423
+ const cases = this.findDescendants(unionDefn, (n) => n.type === "union_type_case");
424
+ for (const c of cases) {
425
+ const caseNameNode = this.findDescendant(c, (n) => n.type === "identifier");
426
+ if (caseNameNode && caseNameNode.text) {
427
+ publish({
428
+ name: caseNameNode.text,
429
+ kind: ASTEngine.SymbolKind.EnumMember,
430
+ range: this.nodeToRange(c),
431
+ ownerName: name,
432
+ metadata: { nodeType: "union_type_case" }
433
+ });
434
+ }
435
+ }
436
+ }
437
+ }
438
+ const enumDefns = node.type === "enum_type_defn" ? [node] : this.findDescendants(node, (n) => n.type === "enum_type_defn");
439
+ for (const enumDefn of enumDefns) {
440
+ const cases = this.findDescendants(enumDefn, (n) => n.type === "enum_type_case");
441
+ for (const c of cases) {
442
+ const caseNameNode = this.findDescendant(c, (n) => n.type === "identifier");
443
+ if (caseNameNode && caseNameNode.text) {
444
+ publish({
445
+ name: caseNameNode.text,
446
+ kind: ASTEngine.SymbolKind.EnumMember,
447
+ range: this.nodeToRange(c),
448
+ ownerName: name,
449
+ metadata: { nodeType: "enum_type_case" }
450
+ });
451
+ }
452
+ }
453
+ }
454
+ const ifaceImpls = this.findDescendants(node, (n) => n.type === "interface_implementation", 8);
455
+ for (const impl of ifaceImpls) {
456
+ const ifaceType = this.findDescendant(impl, (n) => n.type === "long_identifier" || n.type === "identifier");
457
+ const writtenIface = ifaceType ? ifaceType.text : null;
458
+ if (writtenIface) {
459
+ const leaf = writtenIface.includes(".") ? writtenIface.split(".").pop() : writtenIface;
460
+ pushRel(
461
+ qualifyScope(name) ?? name,
462
+ leaf,
463
+ ASTEngine.RelationshipType.Implements,
464
+ 1,
465
+ {
466
+ // FS-P7-1: the reason, and the spelling that was discarded to get here.
467
+ // `qualifiedType` below IS that discarded spelling (R10's record, kept
468
+ // because two fixtures pin it), so this site carries the REASON only and
469
+ // the census reads `targetWritten ?? qualifiedType`.
470
+ nodeType: "interface_implementation",
471
+ targetResolution: UNRESOLVED_TYPE,
472
+ ...leaf !== writtenIface ? { qualifiedType: writtenIface } : {}
473
+ },
474
+ lineOf(ifaceType)
475
+ );
476
+ }
477
+ }
478
+ for (const inh of inherits) {
479
+ const baseType = this.findDescendant(inh, (n) => n.type === "long_identifier" || n.type === "identifier");
480
+ const writtenName = baseType ? baseType.text : null;
481
+ if (writtenName) {
482
+ const leaf = writtenName.includes(".") ? writtenName.split(".").pop() : writtenName;
483
+ pushRel(
484
+ qualifyScope(name) ?? name,
485
+ leaf,
486
+ ASTEngine.RelationshipType.Extends,
487
+ 1,
488
+ {
489
+ nodeType: "class_inherits_decl",
490
+ targetResolution: UNRESOLVED_TYPE,
491
+ ...leaf !== writtenName ? { qualifiedType: writtenName } : {}
492
+ },
493
+ lineOf(baseType)
494
+ );
495
+ }
496
+ }
497
+ }
498
+ } else if (type === "value_declaration" || type === "function_or_value_defn" || type === "member_defn") {
499
+ if (bodyDepth > 0 && (type === "value_declaration" || type === "function_or_value_defn")) {
500
+ const localName = this.declaredBindingName(node);
501
+ if (localName) localNames.add(String(localName));
502
+ childCallOwner = callOwner;
503
+ childCallOwnerQualified = callOwnerQualified;
504
+ childBodyDepth = bodyDepth;
505
+ } else {
506
+ let nameNode = this.findChild(
507
+ node,
508
+ (n) => !!n.text && (n.type === "identifier" || n.type === "long_identifier" || n.type === "identifier_pattern")
509
+ );
510
+ if (!nameNode) {
511
+ const leftNode = this.findChild(
512
+ node,
513
+ (n) => n.type === "function_declaration_left" || n.type === "value_declaration_left"
514
+ );
515
+ if (leftNode) {
516
+ nameNode = this.findChild(leftNode, (n) => n.type === "identifier" || n.type === "identifier_pattern");
517
+ if (nameNode?.type === "identifier_pattern") {
518
+ nameNode = this.patternNameNode(nameNode) ?? nameNode;
519
+ }
520
+ }
521
+ }
522
+ if (!nameNode) {
523
+ const mopNode = this.findChild(node, (n) => n.type === "method_or_prop_defn");
524
+ if (mopNode) {
525
+ const propNode = this.findChild(mopNode, (n) => n.type === "property_or_ident");
526
+ if (propNode) {
527
+ const idents = this.filterChildren(propNode, (n) => n.type === "identifier");
528
+ nameNode = idents.length >= 2 ? idents[idents.length - 1] : idents[0];
529
+ }
530
+ }
531
+ }
532
+ if (nameNode && nameNode.text) {
533
+ const name = nameNode.text;
534
+ const isFunc = !!this.findChild(
535
+ node,
536
+ (n) => n.type === "function_declaration_left" || n.type === "argument_patterns" || n.type === "argument_spec" || n.type === "arguments_spec"
537
+ );
538
+ const isMember = type === "member_defn";
539
+ const hasMethodOrProp = !!this.findChild(node, (n) => n.type === "method_or_prop_defn");
540
+ const owner = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
541
+ const isAbstractDefaultImpl = isMember && !!owner && abstractMemberNames.has(`${owner}:${name}`);
542
+ if (!isAbstractDefaultImpl) {
543
+ let kind;
544
+ let isProperty = false;
545
+ if (isMember && hasMethodOrProp) {
546
+ const mopNode = this.findChild(node, (n) => n.type === "method_or_prop_defn");
547
+ if (mopNode) {
548
+ const argsNode = mopNode.childForFieldName?.("args") ?? null;
549
+ if (argsNode) {
550
+ isProperty = false;
551
+ } else {
552
+ let afterIdent = false;
553
+ for (let i = 0; i < mopNode.namedChildCount; i++) {
554
+ const c = mopNode.namedChild(i);
555
+ if (!c) continue;
556
+ if (c.type === "property_or_ident") {
557
+ afterIdent = true;
558
+ continue;
559
+ }
560
+ if (afterIdent) {
561
+ if (c.type === "paren_pattern" || c.type === "argument_patterns" || c.type === "identifier_pattern" || c.type === "typed_pattern" || c.type === "wildcard_pattern" || c.type === "list_pattern" || c.type === "optional_pattern" || c.type === "const" && c.text === "()") {
562
+ isProperty = false;
563
+ break;
564
+ }
565
+ isProperty = true;
566
+ break;
567
+ }
568
+ }
569
+ }
570
+ }
571
+ kind = isProperty ? ASTEngine.SymbolKind.Property : ASTEngine.SymbolKind.Method;
572
+ } else if (isMember) {
573
+ kind = ASTEngine.SymbolKind.Method;
574
+ } else {
575
+ kind = isFunc ? ASTEngine.SymbolKind.Function : ASTEngine.SymbolKind.Variable;
576
+ }
577
+ const isAsync = !!this.findDescendant(
578
+ node,
579
+ (n) => n.type === "ce_expression" || n.type === "short_comp_expression",
580
+ 4
581
+ );
582
+ const memberAccess = this.accessModifierOf(node, null);
583
+ publish({
584
+ name,
585
+ kind,
586
+ range: this.nodeToRange(node),
587
+ ownerName: owner,
588
+ isAsync,
589
+ metadata: { nodeType: type, ...memberAccess ? { accessModifier: memberAccess } : {} }
590
+ });
591
+ const extraLefts = [];
592
+ for (let i = 0; i < node.namedChildCount; i++) {
593
+ const c = node.namedChild(i);
594
+ if (c && (c.type === "function_declaration_left" || c.type === "value_declaration_left")) {
595
+ extraLefts.push(c);
596
+ }
597
+ }
598
+ for (let k = 1; k < extraLefts.length; k++) {
599
+ const andName = this.nameOfDeclarationLeft(extraLefts[k]);
600
+ if (andName && andName !== name) {
601
+ publish({
602
+ name: andName,
603
+ kind,
604
+ range: this.nodeToRange(node),
605
+ ownerName: owner,
606
+ isAsync,
607
+ metadata: { nodeType: type, isMutualRecursionClause: true }
608
+ });
609
+ }
610
+ }
611
+ if (isMember && this.firstTokenIs(node, "override")) {
612
+ const qualifiedSource = owner ? `${owner}.${name}` : name;
613
+ const implementedInterface = this.enclosingInterfaceImplementationLeaf(node);
614
+ if (implementedInterface) {
615
+ pushRel(
616
+ qualifiedSource,
617
+ `${implementedInterface}.${name}`,
618
+ ASTEngine.RelationshipType.Overrides,
619
+ 1,
620
+ {
621
+ nodeType: type,
622
+ isOverride: true,
623
+ relationshipType: "interface_member_implementation",
624
+ isInterfaceMemberImplementation: true,
625
+ targetResolution: UNRESOLVED_INTERFACE
626
+ },
627
+ lineOf(node)
628
+ );
629
+ } else {
630
+ const baseName = owner ? baseTypes.get(owner) : void 0;
631
+ if (baseName) {
632
+ pushRel(
633
+ qualifiedSource,
634
+ `${baseName}.${name}`,
635
+ ASTEngine.RelationshipType.Overrides,
636
+ 1,
637
+ {
638
+ nodeType: type,
639
+ isOverride: true,
640
+ targetResolution: UNRESOLVED_BASE,
641
+ // The base is stored AS WRITTEN (`baseTypes` takes `baseType.text`), so
642
+ // `inherit System.Exception()` spells the target
643
+ // `System.Exception.ToString` — three segments whose store tail
644
+ // (everything after the FIRST dot) is `Exception.ToString`, which no
645
+ // symbol is named. The leaf form `Exception.ToString` can reach
646
+ // strategy 3's tail (`ToString`) instead.
647
+ //
648
+ // PRICED AND DECLINED, FS-P7-2's method applied to this site
649
+ // (`.tmp-probe/fs-p7-base.py`, arm `rows-pass10-meta.ndjson`): the
650
+ // tempting reading is that 542 `base-member-external` rows are all
651
+ // three-segment spellings, and they are not — only 6 carry a DOTTED
652
+ // written base, because `inherit Base()` with a bare name already
653
+ // spells the leaf. Respelling those 6 moves **+3 determinate / −0**
654
+ // and converts 3 UNATTACHED edges into ARBITRARY ones
655
+ // (`none -> ambiguous`: the store now picks MIN(id) among several
656
+ // symbols named `Invoke`). An arbitrary attachment is not a gain
657
+ // under this lane's accounting — it is the analyzer saying nothing
658
+ // while the store says something — so the as-written base stands and
659
+ // the number lives here rather than in a TODO.
660
+ ...baseName.includes(".") ? { targetWritten: baseName } : {}
661
+ },
662
+ lineOf(node)
663
+ );
664
+ }
665
+ }
666
+ }
667
+ if (kind === ASTEngine.SymbolKind.Property && !this.hasExplicitSetter(node)) {
668
+ childCallOwner = void 0;
669
+ childCallOwnerQualified = void 0;
670
+ } else {
671
+ childCallOwner = name;
672
+ childCallOwnerQualified = owner ? `${owner}.${name}` : name;
673
+ }
674
+ childBodyDepth = bodyDepth + 1;
675
+ } else {
676
+ childCallOwner = name;
677
+ childCallOwnerQualified = owner ? `${owner}.${name}` : name;
678
+ childBodyDepth = bodyDepth + 1;
679
+ }
680
+ }
681
+ }
682
+ } else if (type === "additional_constr_defn") {
683
+ const nameNode = this.findDescendant(node, (n) => n.type === "identifier" || n.type === "long_identifier");
684
+ if (nameNode && nameNode.text) {
685
+ const owner = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
686
+ publish({
687
+ name: "constructor",
688
+ kind: ASTEngine.SymbolKind.Constructor,
689
+ range: this.nodeToRange(node),
690
+ ownerName: owner,
691
+ metadata: { nodeType: "additional_constr_defn" }
692
+ });
693
+ childCallOwner = "constructor";
694
+ childCallOwnerQualified = owner ? `${owner}.constructor` : "constructor";
695
+ childBodyDepth = bodyDepth + 1;
696
+ }
697
+ } else if (type === "member_signature") {
698
+ const nameNode = this.findChild(node, (n) => n.type === "identifier" || n.type === "property_or_ident");
699
+ if (nameNode && nameNode.text) {
700
+ const owner = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
701
+ publish({
702
+ name: nameNode.text,
703
+ kind: ASTEngine.SymbolKind.Method,
704
+ range: this.nodeToRange(node),
705
+ ownerName: owner,
706
+ metadata: { nodeType: "member_signature" }
707
+ });
708
+ if (owner) abstractMemberNames.add(`${owner}:${nameNode.text}`);
709
+ }
710
+ } else if (type === "value_declaration" && bodyDepth > 0) {
711
+ childCallOwner = callOwner;
712
+ childCallOwnerQualified = callOwnerQualified;
713
+ childBodyDepth = bodyDepth;
714
+ } else if (type === "object_expression") {
715
+ const iface = this.objectExpressionInterface(node);
716
+ if (iface) {
717
+ const { written, leaf } = iface;
718
+ const ownerName = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
719
+ publish({
720
+ name: "new " + written,
721
+ kind: ASTEngine.SymbolKind.Class,
722
+ range: this.nodeToRange(node),
723
+ ownerName,
724
+ metadata: { nodeType: "object_expression" }
725
+ });
726
+ currentScope.push("new " + written);
727
+ if (ownerName) {
728
+ pushRel(
729
+ qualifyScope(ownerName) ?? ownerName,
730
+ leaf,
731
+ ASTEngine.RelationshipType.Implements,
732
+ 1,
733
+ { nodeType: "object_expression", targetResolution: UNRESOLVED_TYPE, ...leaf !== written ? { qualifiedType: written } : {} },
734
+ lineOf(node)
735
+ );
736
+ }
737
+ }
738
+ } else if (type === "active_pattern") {
739
+ publish({
740
+ name: node.text,
741
+ kind: ASTEngine.SymbolKind.Function,
742
+ range: this.nodeToRange(node),
743
+ ownerName: currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0,
744
+ metadata: { nodeType: "active_pattern" }
745
+ });
746
+ } else if (type === "exception_definition") {
747
+ const nameNode = this.findDescendant(node, (n) => n.type === "identifier");
748
+ if (nameNode && nameNode.text) {
749
+ publish({
750
+ name: nameNode.text,
751
+ kind: ASTEngine.SymbolKind.Class,
752
+ range: this.nodeToRange(node),
753
+ ownerName: currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0,
754
+ metadata: { nodeType: "exception_definition" }
755
+ });
756
+ }
757
+ } else if (type === "module_val_declaration") {
758
+ const nameNode = this.findChild(node, (n) => n.type === "identifier");
759
+ if (nameNode?.text) {
760
+ publish({
761
+ name: nameNode.text,
762
+ kind: ASTEngine.SymbolKind.Function,
763
+ range: this.nodeToRange(node),
764
+ ownerName: currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0,
765
+ metadata: { nodeType: "module_val_declaration", isDeclaration: true }
766
+ });
767
+ }
768
+ } else if (type === "type_extension") {
769
+ const typeNameNode = this.findChild(node, (n) => n.type === "type_name");
770
+ const extName = typeNameNode ? this.typeInfoOf(typeNameNode)?.name ?? null : null;
771
+ if (extName) currentScope.push(extName);
772
+ } else if (type === "attribute") {
773
+ const attrNameNode = this.findDescendant(node, (n) => n.type === "identifier" || n.type === "long_identifier");
774
+ if (attrNameNode && attrNameNode.text) {
775
+ const attrText = attrNameNode.text;
776
+ if (attrText.includes("TypeProvider") || attrText.includes("TypeProviderAssembly")) {
777
+ publish({
778
+ name: attrText,
779
+ kind: ASTEngine.SymbolKind.Class,
780
+ range: this.nodeToRange(node),
781
+ ownerName: currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0,
782
+ metadata: { nodeType: "attribute", isTypeProvider: true }
783
+ });
784
+ }
785
+ }
786
+ const attrTarget = node.childForFieldName("target");
787
+ let attrTypeNode = null;
788
+ for (let i = 0; i < node.childCount; i++) {
789
+ const c = node.child(i);
790
+ if (!c || !c.isNamed) continue;
791
+ if (node.fieldNameForChild(i) === "target") continue;
792
+ const named = this.typeNameNodeOf(c);
793
+ if (named) {
794
+ attrTypeNode = named;
795
+ break;
796
+ }
797
+ }
798
+ const attrName = attrTypeNode ? this.dependencyLeafName(attrTypeNode) : null;
799
+ const attrWritten = attrTypeNode ? String(attrTypeNode.text) : null;
800
+ if (attrName) {
801
+ const attrOwner = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
802
+ const spelledAttr = targetSpelling(attrWritten ?? attrName, attrOwner, "type");
803
+ pushRel(
804
+ qualifyScope(attrOwner) || qualifyScope(fileScope) || "Global",
805
+ spelledAttr.target,
806
+ ASTEngine.RelationshipType.Uses,
807
+ 1,
808
+ {
809
+ nodeType: type,
810
+ relationshipType: "attribute_application",
811
+ isAttributeUsage: true,
812
+ targetResolution: spelledAttr.resolution,
813
+ ...spelledAttr.qualifier ? { targetQualifier: spelledAttr.qualifier } : {},
814
+ ...attrTarget ? { attributeTarget: attrTarget.text } : {}
815
+ },
816
+ attrTypeNode ? lineOf(attrTypeNode) : lineOf(node)
817
+ );
818
+ }
819
+ } else if (type === "module_abbrev") {
820
+ const aliasNode = this.filterChildren(node, (n) => n.type === "identifier")[0];
821
+ const pathNode = this.filterChildren(node, (n) => n.type === "long_identifier").pop();
822
+ if (pathNode && pathNode.text) {
823
+ const alias = aliasNode?.text;
824
+ imports.push({
825
+ module: pathNode.text,
826
+ items: [],
827
+ isDefault: true,
828
+ alias,
829
+ range: this.nodeToRange(node)
830
+ });
831
+ const abbrevOwner = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
832
+ pushRel(
833
+ qualifyScope(abbrevOwner) || qualifyScope(fileScope) || "Global",
834
+ pathNode.text,
835
+ ASTEngine.RelationshipType.Imports,
836
+ 1,
837
+ { nodeType: type, relationshipType: "module_alias", targetResolution: "module-alias-path", ...alias ? { moduleAlias: alias } : {} },
838
+ lineOf(pathNode)
839
+ );
840
+ }
841
+ } else if (type === "fsi_directive_decl") {
842
+ const resourceKeywords = /* @__PURE__ */ new Set(["#r", "#I"]);
843
+ const partKeywords = /* @__PURE__ */ new Set(["#load"]);
844
+ let directiveKeyword = "";
845
+ for (let i = 0; i < node.childCount; i++) {
846
+ const c = node.child(i);
847
+ if (c && !c.isNamed) {
848
+ directiveKeyword = c.text;
849
+ break;
850
+ }
851
+ }
852
+ const specifiers = this.filterChildren(
853
+ node,
854
+ (n) => n.type === "string" || n.type === "verbatim_string"
855
+ );
856
+ const isPart = partKeywords.has(directiveKeyword);
857
+ if (specifiers.length > 0 && (resourceKeywords.has(directiveKeyword) || isPart)) {
858
+ const dirOwner = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
859
+ const source = qualifyScope(dirOwner) || qualifyScope(fileScope) || "Global";
860
+ const partSource = qualifyScope(dirOwner) || qualifyScope(fileScope) || fileScopeName;
861
+ const named = isPart ? specifiers : specifiers.slice(0, 1);
862
+ for (const specifier of named) {
863
+ const specifierValue = this.stringLiteralValue(specifier);
864
+ if (!specifierValue) continue;
865
+ imports.push({
866
+ module: specifierValue,
867
+ items: [],
868
+ isDefault: false,
869
+ isExternal: true,
870
+ path: specifierValue,
871
+ range: this.nodeToRange(node),
872
+ metadata: { fsharpSpecific: true, directive: directiveKeyword }
873
+ });
874
+ if (isPart) {
875
+ const partTarget = this.fsharpPartTargetPath(filePath, context, specifierValue);
876
+ if (partTarget) {
877
+ pushRel(
878
+ partSource,
879
+ partTarget,
880
+ ASTEngine.RelationshipType.PartOf,
881
+ 1,
882
+ {
883
+ nodeType: type,
884
+ relationshipType: "part_directive",
885
+ directive: directiveKeyword,
886
+ partRole: "library",
887
+ targetResolution: "project-relative-path"
888
+ },
889
+ lineOf(specifier)
890
+ );
891
+ }
892
+ } else {
893
+ pushRel(
894
+ source,
895
+ specifierValue,
896
+ ASTEngine.RelationshipType.Imports,
897
+ 1,
898
+ { nodeType: type, relationshipType: "resource_directive", directive: directiveKeyword, targetResolution: "resource-path" },
899
+ lineOf(specifier)
900
+ );
901
+ }
902
+ }
903
+ }
904
+ } else if (type === "import_decl") {
905
+ const targetNode = this.findChild(node, (n) => n.type === "long_identifier" || n.type === "identifier");
906
+ if (targetNode && targetNode.text) {
907
+ imports.push({
908
+ module: targetNode.text,
909
+ items: [],
910
+ isDefault: true,
911
+ range: this.nodeToRange(node)
912
+ });
913
+ const owner = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
914
+ const openedHead = String(targetNode.text).split(".")[0];
915
+ if (openedHead) openedScopes.add(openedHead);
916
+ pushRel(
917
+ qualifyScope(owner) || qualifyScope(fileScope) || "Global",
918
+ targetNode.text,
919
+ ASTEngine.RelationshipType.References,
920
+ 0.9,
921
+ { nodeType: type, isImport: true, targetResolution: UNRESOLVED_NAMESPACE },
922
+ lineOf(node)
923
+ );
924
+ pushRel(
925
+ qualifyScope(owner) || qualifyScope(fileScope) || "Global",
926
+ targetNode.text,
927
+ ASTEngine.RelationshipType.Imports,
928
+ 1,
929
+ { nodeType: type, relationshipType: "open_directive", targetResolution: UNRESOLVED_NAMESPACE, ...this.nodeHasToken(node, "type") ? { opensTypes: true } : {} },
930
+ lineOf(node)
931
+ );
932
+ }
933
+ } else if (type === "infix_expression") {
934
+ let opText = "";
935
+ let opIndex = -1;
936
+ for (let i = 0; i < node.childCount; i++) {
937
+ const c = node.child(i);
938
+ if (c && c.type === "infix_op") {
939
+ opText = c.text;
940
+ opIndex = i;
941
+ break;
942
+ }
943
+ }
944
+ const side = PIPELINE_CALLEE_SIDE.get(opText);
945
+ if (side && callOwner && opIndex >= 0) {
946
+ const operandNode = node.child(side === "right" ? opIndex + 1 : opIndex - 1);
947
+ const calleeNode = this.pipelineCalleeName(operandNode);
948
+ const calleeText = calleeNode?.text ?? "";
949
+ const leaf = calleeText.split(".").pop() || calleeText;
950
+ if (calleeText.length > 1 && !this.isFSharpBuiltin(calleeText, leaf)) {
951
+ const callSource = callOwnerQualified ?? callOwner;
952
+ const spelled = bindingSpelled(callSource, targetSpelling(calleeText, currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0, "member"));
953
+ pushRel(
954
+ callSource,
955
+ spelled.target,
956
+ ASTEngine.RelationshipType.Calls,
957
+ 0.8,
958
+ {
959
+ nodeType: type,
960
+ isPipelineCall: true,
961
+ pipelineOperator: opText,
962
+ targetResolution: spelled.resolution,
963
+ ...spelled.qualifier ? { targetQualifier: spelled.qualifier } : {},
964
+ ...spelled.localBinding ? { localBinding: true } : {}
965
+ },
966
+ lineOf(calleeNode ?? node)
967
+ );
968
+ }
969
+ }
970
+ } else if (type === "application_expression") {
971
+ let fnName = "";
972
+ const fnNode = node.namedChild(0);
973
+ if (fnNode) {
974
+ if (fnNode.type === "long_identifier" || fnNode.type === "identifier" || fnNode.type === "long_identifier_or_op") {
975
+ fnName = fnNode.text;
976
+ } else if (fnNode.type === "infix_expression") {
977
+ const rhs = fnNode.namedChild(fnNode.namedChildCount - 1);
978
+ if (rhs && (rhs.type === "long_identifier" || rhs.type === "identifier" || rhs.type === "long_identifier_or_op")) {
979
+ fnName = rhs.text;
980
+ }
981
+ } else if (fnNode.type === "dot_expression") {
982
+ fnName = fnNode.text;
983
+ }
984
+ }
985
+ if (fnName && callOwner) {
986
+ const leaf = fnName.split(".").pop() || fnName;
987
+ if (!this.isFSharpBuiltin(fnName, leaf)) {
988
+ const enclosingType = currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0;
989
+ if (callOwner === "constructor" && leaf === enclosingType) {
990
+ pushRel(
991
+ callOwnerQualified ?? callOwner,
992
+ leaf,
993
+ ASTEngine.RelationshipType.Instantiates,
994
+ 0.95,
995
+ { nodeType: type, isConstructorChain: true },
996
+ lineOf(node)
997
+ );
998
+ } else {
999
+ const callSource = callOwnerQualified ?? callOwner;
1000
+ const spelled = bindingSpelled(callSource, targetSpelling(fnName, enclosingType, "member"));
1001
+ pushRel(
1002
+ callSource,
1003
+ spelled.target,
1004
+ ASTEngine.RelationshipType.Calls,
1005
+ 0.8,
1006
+ {
1007
+ nodeType: type,
1008
+ targetResolution: spelled.resolution,
1009
+ ...spelled.qualifier ? { targetQualifier: spelled.qualifier } : {},
1010
+ ...spelled.localBinding ? { localBinding: true } : {}
1011
+ },
1012
+ lineOf(node)
1013
+ );
1014
+ }
1015
+ }
1016
+ }
1017
+ } else if (type === "prefixed_expression") {
1018
+ if (this.firstTokenIs(node, "new") && callOwner) {
1019
+ const operand = node.namedChild(0);
1020
+ if (operand && operand.type === "application_expression") {
1021
+ const calleeNode = operand.namedChild(0);
1022
+ const calleeText = calleeNode ? calleeNode.text : "";
1023
+ const leaf = calleeText.split(".").pop() || calleeText;
1024
+ if (calleeText && calleeText.length > 1 && !this.isFSharpBuiltin(calleeText, leaf)) {
1025
+ const spelled = targetSpelling(calleeText, currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0, "type");
1026
+ pushRel(
1027
+ callOwnerQualified ?? callOwner,
1028
+ spelled.target,
1029
+ ASTEngine.RelationshipType.Instantiates,
1030
+ 0.95,
1031
+ {
1032
+ nodeType: type,
1033
+ isNewConstruction: true,
1034
+ targetResolution: spelled.resolution,
1035
+ ...spelled.qualifier ? { targetQualifier: spelled.qualifier } : {}
1036
+ },
1037
+ lineOf(node)
1038
+ );
1039
+ for (let i = operand.namedChildCount - 1; i >= 0; i--) {
1040
+ const argChild = operand.namedChild(i);
1041
+ if (argChild) {
1042
+ walkStack.push({ node: argChild, scopePath: currentScope, callOwner: childCallOwner, callOwnerQualified: childCallOwnerQualified, bodyDepth: childBodyDepth });
1043
+ }
1044
+ }
1045
+ continue;
1046
+ }
1047
+ }
1048
+ }
1049
+ } else if (type === "compiler_directive_decl") {
1050
+ const nameNode = this.findDescendant(node, (n) => n.type === "identifier" || n.type === "long_identifier");
1051
+ if (nameNode && nameNode.text) {
1052
+ publish({
1053
+ name: nameNode.text,
1054
+ kind: ASTEngine.SymbolKind.Macro,
1055
+ range: this.nodeToRange(node),
1056
+ ownerName: currentScope.length > 0 ? currentScope[currentScope.length - 1] : void 0,
1057
+ metadata: { nodeType: type }
1058
+ });
1059
+ }
1060
+ }
1061
+ if (asTypeUnit) {
1062
+ for (const attrs of unitAttributes ?? []) {
1063
+ walkStack.push({ node: attrs, scopePath: currentScope, callOwner: childCallOwner, callOwnerQualified: childCallOwnerQualified, bodyDepth: childBodyDepth });
1064
+ }
1065
+ }
1066
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
1067
+ const child = node.namedChild(i);
1068
+ if (child) {
1069
+ walkStack.push({ node: child, scopePath: currentScope, callOwner: childCallOwner, callOwnerQualified: childCallOwnerQualified, bodyDepth: childBodyDepth });
1070
+ }
1071
+ }
1072
+ }
1073
+ const parentNameCounts = /* @__PURE__ */ new Map();
1074
+ const ownerlessUnitNames = /* @__PURE__ */ new Set();
1075
+ for (const s of symbols) {
1076
+ if (!s.name) continue;
1077
+ if (!s.ownerName) ownerlessUnitNames.add(s.name);
1078
+ else parentNameCounts.set(String(s.ownerName), (parentNameCounts.get(String(s.ownerName)) ?? 0) + 1);
1079
+ }
1080
+ const fqnByName = /* @__PURE__ */ new Map();
1081
+ for (const s of symbols) {
1082
+ if (!s.name) continue;
1083
+ const fqn = typeof s.metadata?.fqn === "string" ? s.metadata.fqn : null;
1084
+ const prev = fqnByName.get(s.name);
1085
+ if (prev === void 0) fqnByName.set(s.name, fqn);
1086
+ else if (prev !== fqn) fqnByName.set(s.name, null);
1087
+ }
1088
+ for (const s of symbols) {
1089
+ const _owner = s.ownerName || s.scope;
1090
+ if (_owner && _owner !== s.name) {
1091
+ const childFqn = typeof s.metadata?.fqn === "string" ? s.metadata.fqn : s.name;
1092
+ const parentFqn = fqnByName.get(String(_owner));
1093
+ pushRel(
1094
+ parentFqn || String(_owner),
1095
+ childFqn,
1096
+ ASTEngine.RelationshipType.Contains,
1097
+ 1,
1098
+ {
1099
+ nodeType: s.metadata?.nodeType || "symbol",
1100
+ isContained: true,
1101
+ // FS-P7-1. Both ends of a containment edge get a reason, because both ends
1102
+ // have one and only the target's was ever written down.
1103
+ //
1104
+ // The TARGET is always an identity this file minted, so it needs no class
1105
+ // beyond that fact: `contains` cannot spell its child any other way — the
1106
+ // child IS the symbol, and re-spelling it would attach the edge to a
1107
+ // different symbol. What the class does is make the residual auditable: a
1108
+ // target that does not resolve is a target the CORPUS duplicates (the `.fs`
1109
+ // and `.fsi` halves of one declaration, or two files declaring the same
1110
+ // identity), never an analyzer guess.
1111
+ targetResolution: "published-identity",
1112
+ // The SOURCE is the parent's identity when the file has exactly one symbol of
1113
+ // that name, and the bare owner NAME when it has two with different
1114
+ // identities (`fqnByName` nulls the ambiguous case) — a real loss of one end,
1115
+ // invisible until it is named.
1116
+ //
1117
+ // THREE classes, not two, because a top-level unit is not an ambiguity: a
1118
+ // module with no owner IS its own name, and calling that ambiguous (the first
1119
+ // draft did) mislabelled every module-scoped containment in the corpus.
1120
+ sourceResolution: parentFqn ? "in-file-declaration" : ownerlessUnitNames.has(String(_owner)) ? "ownerless-unit" : (parentNameCounts.get(String(_owner)) ?? 0) > 1 ? "ambiguous-owner-name" : "in-file-declaration"
1121
+ },
1122
+ s.range?.startLine
1123
+ );
1124
+ }
1125
+ }
1126
+ this.extractDeclaredTypeRefs(rootNode, pushRel, publishedNames, fqnOfName, typeFqnOfName);
1127
+ const exportedNames = /* @__PURE__ */ new Set();
1128
+ const declaredUnits = new Set(
1129
+ symbols.filter((s) => !s.ownerName && s.metadata?.isForwardDeclaration !== true).map((s) => s.name)
1130
+ );
1131
+ for (const s of symbols) {
1132
+ if (s.kind === ASTEngine.SymbolKind.Variable) continue;
1133
+ if (s.kind === ASTEngine.SymbolKind.EnumMember) continue;
1134
+ const isForwardDecl = s.metadata?.isForwardDeclaration === true;
1135
+ if (isForwardDecl && declaredUnits.has(s.name)) continue;
1136
+ const visibility = this.visibilityOf(s);
1137
+ if (visibility === "private" || visibility === "internal") continue;
1138
+ const isTypeMember = !!s.ownerName && !moduleNames.has(s.ownerName) && !namespaceNames.has(s.ownerName);
1139
+ if (isTypeMember) continue;
1140
+ if (!s.ownerName && (s.kind === ASTEngine.SymbolKind.Property || s.kind === ASTEngine.SymbolKind.Method)) continue;
1141
+ if (exportedNames.has(s.name)) continue;
1142
+ exportedNames.add(s.name);
1143
+ exports.push({ name: s.name, kind: s.kind, range: s.range, visibility });
1144
+ const exportTarget = typeof s.metadata?.fqn === "string" ? s.metadata.fqn : s.name;
1145
+ const exportOwner = s.ownerName || s.scope;
1146
+ const exportParent = exportOwner ? fqnByName.get(String(exportOwner)) || String(exportOwner) : null;
1147
+ pushRel(
1148
+ exportParent || "Global",
1149
+ exportTarget,
1150
+ ASTEngine.RelationshipType.Exports,
1151
+ 1,
1152
+ {
1153
+ nodeType: s.metadata?.nodeType || "symbol",
1154
+ // WHICH rule of the FS-D8 surface published this name, so a consumer can tell an
1155
+ // export from a containment row without re-deriving the filter.
1156
+ exportForm: "module-surface",
1157
+ declaredKind: s.kind,
1158
+ visibility,
1159
+ publishedBy: exportOwner ? "enclosing-scope" : "file-scope",
1160
+ // The SAME three-valued source class `contains` writes (FS-P7-1), because the
1161
+ // lookup and its two failure modes are the same lookup: a top-level unit is not
1162
+ // an ambiguity, an owner the file published twice is, and a source that fell
1163
+ // back to the bare owner name says so rather than reading as a declaration.
1164
+ sourceResolution: exportParent ? "in-file-declaration" : ownerlessUnitNames.has(String(exportOwner)) ? "ownerless-unit" : (parentNameCounts.get(String(exportOwner)) ?? 0) > 1 ? "ambiguous-owner-name" : "in-file-declaration",
1165
+ targetResolution: "published-identity"
1166
+ },
1167
+ s.range?.startLine
1168
+ );
1169
+ }
1170
+ for (const rel of relationships) {
1171
+ const md = rel.metadata;
1172
+ if (!md) continue;
1173
+ const marker = md.targetResolution;
1174
+ if (marker !== UNRESOLVED_TYPE && marker !== UNRESOLVED_NAMESPACE && marker !== UNRESOLVED_BASE && marker !== UNRESOLVED_INTERFACE) continue;
1175
+ const target = String(rel.target ?? "");
1176
+ const leaf = target.split(".").pop() ?? "";
1177
+ if (marker === UNRESOLVED_TYPE) {
1178
+ const inFile = publishedTypeNames.has(leaf) || publishedQnames.has(target);
1179
+ md.targetResolution = inFile ? "in-file-declaration" : "external-type";
1180
+ continue;
1181
+ }
1182
+ if (marker === UNRESOLVED_NAMESPACE) {
1183
+ md.targetResolution = publishedQnames.has(target) ? "in-file-declaration" : "external-namespace";
1184
+ continue;
1185
+ }
1186
+ const writtenBase = typeof md.targetWritten === "string" && md.targetWritten ? String(md.targetWritten).split(".").pop() ?? "" : target.split(".").slice(0, -1).join(".");
1187
+ const baseInFile = publishedTypeNames.has(writtenBase);
1188
+ md.targetResolution = marker === UNRESOLVED_INTERFACE ? baseInFile ? "interface-member" : "interface-member-external" : baseInFile ? "base-member" : "base-member-external";
1189
+ }
1190
+ const projectIndex = context?.projectIndex;
1191
+ if (projectIndex && relationships.length > 0) {
1192
+ const projectTypes = projectIndex.typeKinds;
1193
+ const projectNames = projectIndex.symbolNames;
1194
+ const projectMemberMap = projectIndex.typeMembers;
1195
+ const projectBaseMap = projectIndex.typeBases;
1196
+ const knowsType = (n) => n.length > 0 && (projectTypes?.has(n) ?? false);
1197
+ const knowsName = (n) => n.length > 0 && ((projectNames?.has(n) ?? false) || knowsType(n));
1198
+ const memberClosure = (t, seen = /* @__PURE__ */ new Set()) => {
1199
+ const direct = projectMemberMap?.get(t);
1200
+ if (direct === void 0) return /* @__PURE__ */ new Set();
1201
+ if (seen.has(t)) return /* @__PURE__ */ new Set();
1202
+ seen.add(t);
1203
+ const out = new Set(direct);
1204
+ for (const b of projectBaseMap?.get(t) ?? []) {
1205
+ for (const m of memberClosure(b, seen)) out.add(m);
1206
+ }
1207
+ return out;
1208
+ };
1209
+ for (const rel of relationships) {
1210
+ const md = rel.metadata;
1211
+ if (!md) continue;
1212
+ const cls = String(md.targetResolution ?? "");
1213
+ const target = String(rel.target ?? "");
1214
+ const leaf = target.split(".").pop() ?? "";
1215
+ const writtenOwner = (() => {
1216
+ const q = typeof md.targetQualifier === "string" ? md.targetQualifier : "";
1217
+ const w = typeof md.targetWritten === "string" ? md.targetWritten : "";
1218
+ const qt = typeof md.qualifiedType === "string" ? md.qualifiedType : "";
1219
+ const own = target.includes(".") ? target.slice(0, target.lastIndexOf(".")) : "";
1220
+ return q || w || qt || own;
1221
+ })();
1222
+ const ownerHead = writtenOwner.split(".")[0] ?? "";
1223
+ const ownerLeaf = writtenOwner.split(".").pop() ?? "";
1224
+ let next = "";
1225
+ if (cls === "bare-external") {
1226
+ if (knowsName(leaf)) next = "in-project-name";
1227
+ } else if (cls === "external-type-or-module") {
1228
+ if (knowsType(writtenOwner) || knowsName(writtenOwner)) next = "in-project-type-or-module";
1229
+ } else if (cls === "external-namespace") {
1230
+ if (knowsName(target)) next = "in-project-namespace";
1231
+ } else if (cls === "external-type") {
1232
+ if (knowsName(leaf)) next = "in-project-type";
1233
+ } else if (cls === "base-member-external" || cls === "interface-member-external") {
1234
+ const baseLeaf = ownerLeaf;
1235
+ if (knowsType(baseLeaf)) {
1236
+ next = cls === "interface-member-external" ? "interface-member-in-project" : "base-member-in-project";
1237
+ if (projectMemberMap?.has(baseLeaf)) {
1238
+ const members = memberClosure(baseLeaf);
1239
+ if (members.size > 0 && !members.has(leaf)) md.baseInProjectLacksMember = true;
1240
+ }
1241
+ }
1242
+ }
1243
+ if (next !== "") {
1244
+ md.targetResolution = next;
1245
+ md.projectIndexRefined = true;
1246
+ }
1247
+ }
1248
+ }
1249
+ const CAUSE_BASE_CLASSES = /* @__PURE__ */ new Set([
1250
+ "external-type-or-module",
1251
+ // the owner is module-like and NOT opened here (:537, :548)
1252
+ "external-namespace",
1253
+ // the namespace path is not an identity this file minted (:2428)
1254
+ "module-member",
1255
+ // `penult.leaf`, the two-segment form unpublished here (:532)
1256
+ "base-member-external",
1257
+ // the base type is not in this file (:520, :2439)
1258
+ "interface-member-external"
1259
+ // ditto, for the interface form (:2439)
1260
+ ]);
1261
+ const isPlainIdentifier = (t) => {
1262
+ if (t.length === 0) return false;
1263
+ const head = t.charCodeAt(0);
1264
+ const headOk = head >= 65 && head <= 90 || head >= 97 && head <= 122 || head === 95 || head === 36;
1265
+ if (!headOk) return false;
1266
+ for (let i = 1; i < t.length; i++) {
1267
+ const c = t.charCodeAt(i);
1268
+ const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95 || c === 36;
1269
+ if (!ok) return false;
1270
+ }
1271
+ return true;
1272
+ };
1273
+ const CAUSE_BASE_SHAPE = { test: isPlainIdentifier };
1274
+ const EXTERNAL_CLAIM_KEYS = [
1275
+ "zigExternalRef",
1276
+ "nimStdlibModule",
1277
+ "isExternal",
1278
+ "isExternalTarget",
1279
+ "external",
1280
+ "externalRef",
1281
+ "isStdlib",
1282
+ "stdlibModule",
1283
+ "moduleNamingEdges",
1284
+ "markAsExternal",
1285
+ "isExternalBase",
1286
+ "importOutsideProject"
1287
+ ];
1288
+ for (const rel of relationships) {
1289
+ const md = rel.metadata;
1290
+ if (!md) continue;
1291
+ if (!CAUSE_BASE_CLASSES.has(String(md.targetResolution ?? ""))) continue;
1292
+ if (md.projectIndexRefined === true) continue;
1293
+ if (EXTERNAL_CLAIM_KEYS.some((k) => md[k] === true || Array.isArray(md[k]) && md[k].length > 0)) continue;
1294
+ const target = String(rel.target ?? "");
1295
+ const dot = target.lastIndexOf(".");
1296
+ if (dot <= 0) continue;
1297
+ const base = target.slice(0, dot);
1298
+ if (!CAUSE_BASE_SHAPE.test(base)) continue;
1299
+ if (publishedNames.has(base) || publishedQnames.has(base) || publishedTypeNames.has(base) || moduleNames.has(base) || namespaceNames.has(base) || openedScopes.has(base) || localNames.has(base)) continue;
1300
+ md.baseBeyondRunScope = base;
1301
+ }
1302
+ if (fileIdentity.relPath !== null) {
1303
+ publish({
1304
+ name: fileIdentity.stem,
1305
+ kind: ASTEngine.SymbolKind.Module,
1306
+ qualifiedName: fileIdentity.relPath,
1307
+ range: { startLine: 1, startColumn: 0, endLine: 1, endColumn: 0 },
1308
+ metadata: { nodeType: "file_scope", fsharpFileScope: true }
1309
+ });
1310
+ }
1311
+ this.classifyEvidenceTier(relationships);
1312
+ return {
1313
+ language: "fsharp",
1314
+ symbols,
1315
+ relationships,
1316
+ imports,
1317
+ exports,
1318
+ errors,
1319
+ metadata: {
1320
+ complexity: symbols.length,
1321
+ linesOfCode: content.split("\n").length,
1322
+ patterns: [],
1323
+ framework: "F#",
1324
+ hasFrameworkIntelligence: true,
1325
+ hasTypeInference: true
1326
+ }
1327
+ };
1328
+ }
1329
+ /**
1330
+ * #419-D — THE EVIDENCE TIER OF EVERY RELATIONSHIP, AS A PROPERTY OF ITS RULE.
1331
+ *
1332
+ * The tier answers WHY the target spelling can be trusted, which is a different question
1333
+ * from whether this run resolved it. The vocabulary and the mapping follow the fleet's two
1334
+ * shipped tables (`real-c-analyzer.ts:classifyEvidenceTier`, `real-bash-analyzer.ts`'s
1335
+ * `evidenceClassFor`) so the same edge class carries the same tier in every lane:
1336
+ *
1337
+ * structural the parse tree STATES the relation — a declaration body, a heritage
1338
+ * clause, a `new` construction: contains / exports / extends /
1339
+ * implements / instantiates.
1340
+ * import_based the source writes a module/file dependency: `imports` (open
1341
+ * directives, #r resources) and every `references` row that is an
1342
+ * `open` rather than a written type.
1343
+ * reference_qualified the target is a WRITTEN TYPE matched as a name (`references` with
1344
+ * `relationshipType: declared_type`), or an override whose base member
1345
+ * is qualified `Owner.member` (`overrides`).
1346
+ * usage_contextual the target is inferred from how the source USES the name — a call's
1347
+ * callee, a read: calls / uses.
1348
+ *
1349
+ * No tier is `inferred`: this analyzer refuses the shapes it cannot classify at the emit site
1350
+ * (FS-D11's type queries, FS-A4's dead branches, the withdrawn external claims) rather than
1351
+ * publishing an unclassified edge, so the fallback is `usage_contextual` — a true statement
1352
+ * about how the name was reached, never a guess about where it resolves.
1353
+ */
1354
+ classifyEvidenceTier(relationships) {
1355
+ for (const r of relationships) {
1356
+ const md = r.metadata ?? (r.metadata = {});
1357
+ if (md.evidenceClass) continue;
1358
+ const t = String(r.type ?? "");
1359
+ if (t === "contains" || t === "exports" || t === "extends" || t === "implements" || t === "instantiates") {
1360
+ md.evidenceClass = "structural";
1361
+ continue;
1362
+ }
1363
+ if (t === "imports") {
1364
+ md.evidenceClass = "import_based";
1365
+ continue;
1366
+ }
1367
+ if (t === "part_of") {
1368
+ md.evidenceClass = "import_based";
1369
+ continue;
1370
+ }
1371
+ if (t === "references") {
1372
+ md.evidenceClass = md.relationshipType === "declared_type" ? "reference_qualified" : "import_based";
1373
+ continue;
1374
+ }
1375
+ if (t === "overrides") {
1376
+ md.evidenceClass = "reference_qualified";
1377
+ continue;
1378
+ }
1379
+ md.evidenceClass = "usage_contextual";
1380
+ }
1381
+ }
1382
+ /**
1383
+ * FS-D11 — is this written type a TYPE QUERY rather than a declared type?
1384
+ *
1385
+ * `typeof<T>` and `unbox<T>` are EXPRESSIONS that carry a type argument:
1386
+ * `typeof<TypeInThisAssembly>.Assembly.Location` depends on a RUNTIME VALUE, and
1387
+ * publishing `references loc -> TypeInThisAssembly` as a declared-type dependency
1388
+ * says the opposite — that the declaration names the type in its signature.
1389
+ *
1390
+ * The discriminator walks up through the two shapes the grammar spells the query with,
1391
+ * because it spells it differently depending on which one it is:
1392
+ * * `typed_expression` whose `base` is a `long_identifier_or_op` reading `typeof` /
1393
+ * `unbox` — the corpus's own shape (measured by reading the tree: `typeof<Foo>` is
1394
+ * `typed_expression > long_identifier_or_op(typeof) + types > simple_type`);
1395
+ * * a `static_type` wrapper, the other spelling.
1396
+ *
1397
+ * A first draft tested only the DIRECT parent for the `long_identifier_or_op`, which
1398
+ * measured nothing, because the type's parent is the `typed_expression` and the
1399
+ * `long_identifier_or_op` is ITS base. The differential's UNEXPLAINED row is what
1400
+ * forced the second reading.
1401
+ */
1402
+ /**
1403
+ * FS-D13 — does a member declare an explicit SETTER?
1404
+ *
1405
+ * F#'s `member _.P with get, set` is ONE member with two `property_accessor` bodies,
1406
+ * and the setter's body executes. The property-getter contract ("a computed property
1407
+ * emits no calls", inherited from the C# family) is about an EXPRESSION-bodied
1408
+ * property, which has no second body to run code in — applying it to the setter
1409
+ * deleted the calls in real code.
1410
+ *
1411
+ * Read from the `property_accessor` node's own keyword token, never from the member's
1412
+ * text: `with get () = … and set (x) = …` and `with get, set` are the same shape.
1413
+ */
1414
+ hasExplicitSetter(memberDefn) {
1415
+ const mop = this.findChild(memberDefn, (n) => n.type === "method_or_prop_defn");
1416
+ if (!mop) return false;
1417
+ for (const acc of this.findDescendants(mop, (n) => n.type === "property_accessor", 6)) {
1418
+ for (let i = 0; i < acc.childCount; i++) {
1419
+ const c = acc.child(i);
1420
+ if (c && !c.isNamed && c.type === "set") return true;
1421
+ }
1422
+ }
1423
+ return false;
1424
+ }
1425
+ isTypeQueryPosition(t) {
1426
+ let cur = t;
1427
+ for (let hop = 0; cur && hop < 3; hop++) {
1428
+ if (cur.type === "static_type") return true;
1429
+ if (cur.type === "typed_expression") {
1430
+ for (let i = 0; i < cur.namedChildCount; i++) {
1431
+ const c = cur.namedChild(i);
1432
+ if (c && c.type === "long_identifier_or_op" && (c.text === "typeof" || c.text === "unbox")) return true;
1433
+ }
1434
+ return false;
1435
+ }
1436
+ const parent = cur.parent;
1437
+ if (!parent) return false;
1438
+ if (parent.type === "long_identifier_or_op" && (parent.text === "typeof" || parent.text === "unbox")) return true;
1439
+ if (parent.type === "static_type" || parent.type === "typed_expression") {
1440
+ cur = parent;
1441
+ continue;
1442
+ }
1443
+ return false;
1444
+ }
1445
+ return false;
1446
+ }
1447
+ /** The member's declared name: `method_or_prop_defn -> property_or_ident`, LAST identifier. */
1448
+ /**
1449
+ * The interface an OBJECT EXPRESSION constructs, as `{ written, leaf }`.
1450
+ *
1451
+ * FS-D14. The object-expression branch used to take the first `long_identifier`
1452
+ * descendant of the node and call that the interface name, which is right exactly
1453
+ * when the name is unqualified. A qualified name is NESTED in this grammar —
1454
+ *
1455
+ * object_expression
1456
+ * new
1457
+ * typed_expression "System.Collections.Generic.IEqualityComparer<InferedType>"
1458
+ * dot_expression "System.Collections.Generic.IEqualityComparer"
1459
+ * long_identifier_or_op "System.Collections"
1460
+ * long_identifier "System" <-- what the old code found
1461
+ * . identifier "Collections"
1462
+ * . long_identifier_or_op "Generic.IEqualityComparer"
1463
+ * types "InferedType"
1464
+ *
1465
+ * — so "first `long_identifier`" is the FIRST SEGMENT, and the published name was
1466
+ * `new System` with `implements -> System`, a target that resolves to nothing.
1467
+ *
1468
+ * The differential found it twice and in the most useful way available: the lane's
1469
+ * truth side read the SECOND segment. `{ new System.Collections.Generic.IEqualityComparer
1470
+ * with … }` gave the analyzer `System` and the truth `Collections`, which cannot both
1471
+ * be the interface and makes it obvious that the name is somewhere else entirely
1472
+ * rather than that one of them is off by a rule. Two implementations independently
1473
+ * wrong in the same place is the cheapest kind of evidence there is.
1474
+ *
1475
+ * The written name is therefore read from the `dot_expression` that carries it — a
1476
+ * node whose own text IS the full dotted path — and the leaf from the LAST identifier
1477
+ * inside it, so R10's spelling rule applies to the same leaf a consumer would join.
1478
+ * The tree this is built on is printed by script/m14/tmp/fs030-object-expr-name.ts.
1479
+ */
1480
+ objectExpressionInterface(node) {
1481
+ const first = node.namedChild(0);
1482
+ const isNameish = (n) => !!n && (n.type === "dot_expression" || n.type === "typed_expression" || n.type === "long_identifier_or_op" || n.type === "long_identifier");
1483
+ const carrier = isNameish(first) ? first : (
1484
+ // Kept for shapes where the name is not the first child, so this is a narrowing of
1485
+ // where the name may be READ rather than a claim that it is never elsewhere.
1486
+ this.findDescendant(node, (n) => n.type === "dot_expression", 4) ?? this.findDescendant(
1487
+ node,
1488
+ (n) => n.type === "typed_expression" || n.type === "long_identifier_or_op" || n.type === "long_identifier",
1489
+ 4
1490
+ )
1491
+ );
1492
+ if (!carrier) return void 0;
1493
+ const written = carrier.text.split("<")[0].trim();
1494
+ if (!written) return void 0;
1495
+ const nameish = carrier.type === "typed_expression" ? carrier.namedChild(0) ?? carrier : carrier;
1496
+ const idents = this.findDescendants(nameish, (n) => n.type === "identifier", 8);
1497
+ const leaf = idents.length ? idents[idents.length - 1].text : written;
1498
+ return { written, leaf: leaf || written };
1499
+ }
1500
+ /**
1501
+ * The interface a member IMPLEMENTS, when the member is written inside
1502
+ * `interface I with …` — the LEAF of the interface's written name, or undefined.
1503
+ *
1504
+ * FS-A2. The walk to the nearest `interface_implementation` ancestor is unbounded on
1505
+ * purpose: `findAncestor` cannot distinguish "no interface above this" from "further than
1506
+ * N levels up", and treating both as the first is how the `overrides` rule ended up naming
1507
+ * a base type for a member that implements an interface (see the call site).
1508
+ *
1509
+ * The name is read the SAME WAY the `implements` branch reads it — the first
1510
+ * `long_identifier`/`identifier` descendant of the implementation node, then the leaf of a
1511
+ * dotted path, with R10's spelling rule (`System.IDisposable` declares `IDisposable`). Two
1512
+ * implementations of one rule is the point: a member-level edge and a type-level edge about
1513
+ * the same `interface I with` clause that spell the interface differently would be
1514
+ * indistinguishable downstream from two different interfaces.
1515
+ *
1516
+ * THE OBJECT-EXPRESSION ARM, and it is not a rare shape: `{ new IFoo with override x.M() }`
1517
+ * has NO `interface_implementation` node at all — the grammar puts the interface directly on
1518
+ * the `object_expression` as its own `long_identifier_or_op` child. So a walk that stops at
1519
+ * `interface_implementation` answers "no interface" for every mock in the corpus, and the
1520
+ * corpus is full of them: vsintegration/tests/Salsa/VsMocks.fs alone carries 6 such members
1521
+ * on 2 object expressions. The interface is therefore read a second way, through the SAME
1522
+ * `objectExpressionInterface` the `implements` branch and the symbol publication use, so the
1523
+ * three edges about one object expression cannot spell its interface differently.
1524
+ */
1525
+ enclosingInterfaceImplementationLeaf(node) {
1526
+ let cur = node.parent;
1527
+ while (cur) {
1528
+ if (cur.type === "interface_implementation") {
1529
+ const written = this.findDescendant(cur, (n) => n.type === "long_identifier" || n.type === "identifier", 3);
1530
+ if (!written?.text) return void 0;
1531
+ return written.text.includes(".") ? written.text.split(".").pop() : written.text;
1532
+ }
1533
+ if (cur.type === "object_expression") return this.objectExpressionInterface(cur)?.leaf;
1534
+ cur = cur.parent;
1535
+ }
1536
+ return void 0;
1537
+ }
1538
+ memberNameOf(decl) {
1539
+ const mop = this.findChild(decl, (n) => n.type === "method_or_prop_defn");
1540
+ const prop = mop ? this.findChild(mop, (n) => n.type === "property_or_ident") : void 0;
1541
+ const idents = prop ? this.filterChildren(prop, (n) => n.type === "identifier") : [];
1542
+ return idents.length >= 2 ? idents[idents.length - 1]?.text : idents[0]?.text;
1543
+ }
1544
+ /**
1545
+ * The name a `function_or_value_defn` / `value_declaration` DECLARES, read
1546
+ * structurally and never from an `identifier_pattern`'s text (FS-D6).
1547
+ */
1548
+ /**
1549
+ * The name a `*_declaration_left` node declares.
1550
+ *
1551
+ * The binding's own left-hand side spans a parameter list and a return annotation, so its
1552
+ * `identifier_pattern` text is the WHOLE left side (FS-D6) and cannot be used. The name is
1553
+ * the first `long_identifier` inside the pattern, in document order.
1554
+ */
1555
+ /**
1556
+ * The binding a type annotation belongs to, when the declaration node holds several.
1557
+ *
1558
+ * `let rec f … and g …` is ONE node with two left-hand sides, and each owns its own
1559
+ * signature. The name is read from the left whose SPAN contains the annotation, so the
1560
+ * answer is a fact about position rather than about order — and this is the same correction
1561
+ * the truth oracle needed for a dotted name and for a `let … and …` group, and the analyzer
1562
+ * needed twice.
1563
+ */
1564
+ declaredNameAt(decl, t) {
1565
+ let best;
1566
+ for (let i = 0; i < decl.namedChildCount; i++) {
1567
+ const c = decl.namedChild(i);
1568
+ if (!c) continue;
1569
+ if (c.type !== "function_declaration_left" && c.type !== "value_declaration_left") continue;
1570
+ if (c.startIndex <= t.startIndex && t.endIndex <= c.endIndex) best = c;
1571
+ }
1572
+ return best ? this.nameOfDeclarationLeft(best) : void 0;
1573
+ }
1574
+ nameOfDeclarationLeft(left) {
1575
+ const pat = this.findChild(left, (n) => n.type === "identifier_pattern");
1576
+ const carrier = (pat ? this.findChild(pat, (n) => n.type === "long_identifier_or_op") ?? this.findChild(pat, (n) => n.type === "long_identifier") : void 0) ?? left;
1577
+ const idents = this.findDescendants(carrier, (n) => n.type === "identifier", 3).filter((n) => n.startIndex < (pat?.endIndex ?? left.endIndex)).sort((a, b) => a.startIndex - b.startIndex);
1578
+ if (idents.length) return idents[0].text;
1579
+ const direct = this.findChild(left, (n) => n.type === "identifier");
1580
+ return direct?.text;
1581
+ }
1582
+ declaredBindingName(decl) {
1583
+ let left;
1584
+ for (let i = 0; i < decl.namedChildCount; i++) {
1585
+ const c = decl.namedChild(i);
1586
+ if (c && (c.type === "function_declaration_left" || c.type === "value_declaration_left")) {
1587
+ left = c;
1588
+ break;
1589
+ }
1590
+ }
1591
+ if (!left) {
1592
+ const direct = this.findChild(decl, (n) => n.type === "identifier" || n.type === "long_identifier");
1593
+ if (direct?.text) return direct.text.includes(".") ? direct.text.split(".").pop() : direct.text;
1594
+ return void 0;
1595
+ }
1596
+ const pat = this.findChild(left, (n) => n.type === "identifier_pattern");
1597
+ const leaf = pat ? this.patternNameNode(pat) : this.findChild(left, (n) => n.type === "identifier");
1598
+ if (leaf?.text) return leaf.text;
1599
+ if (pat) {
1600
+ const names = this.filterChildren(pat, (n) => n.type === "long_identifier" || n.type === "long_identifier_or_op");
1601
+ const isBareName = names.length === 1 && pat.namedChildCount === 1 && !this.findDescendant(pat, (n) => n.type === "argument_patterns", 2);
1602
+ if (isBareName) {
1603
+ const text = names[0].text;
1604
+ if (text) return text.includes(".") ? text.split(".").pop() : text;
1605
+ }
1606
+ }
1607
+ return void 0;
1608
+ }
1609
+ /**
1610
+ * The declared NAME inside an `identifier_pattern` — the FIRST
1611
+ * `long_identifier_or_op -> long_identifier -> identifier` chain.
1612
+ *
1613
+ * Not `pattern.text`: an `identifier_pattern` spans the entire left-hand side,
1614
+ * parameters and return annotation included, so its text is a signature, not a name.
1615
+ * See FS-D6 at the call site.
1616
+ */
1617
+ patternNameNode(pattern) {
1618
+ const holder = pattern.type === "identifier_pattern" ? pattern : this.findChild(pattern, (n) => n.type === "identifier_pattern");
1619
+ const scope = holder ?? pattern;
1620
+ return this.findDescendant(
1621
+ scope,
1622
+ (n) => n.type === "identifier" && !!n.parent && (n.parent.type === "long_identifier" || n.parent.type === "long_identifier_or_op"),
1623
+ 3
1624
+ );
1625
+ }
1626
+ /**
1627
+ * The nearest ANCESTOR matching the predicate, bounded by `maxDepth` hops.
1628
+ *
1629
+ * Distinct from `findDescendant` on purpose: the declared-type graph asks "which
1630
+ * declaration am I inside?", which is a question about the tree ABOVE the node, and
1631
+ * reusing the descendant search for it would mean passing the whole root — an O(n)
1632
+ * scan per type node, i.e. O(n²) over a file. Bounded hops keep the walk linear (the
1633
+ * lane's own hot-path rule: no per-symbol O(n) scans).
1634
+ */
1635
+ /**
1636
+ * The OUTERMOST callable above `n` — the member a local belongs to — or undefined if
1637
+ * there is none, which means `n` is itself top-level and is its own owner.
1638
+ *
1639
+ * This replaces `findAncestor(decl, isCallable, 8)`, and the bound was the defect. A
1640
+ * depth-limited search cannot distinguish "there is no callable above this" from "the
1641
+ * callable is further than 8 levels up", and the code treated both as the first. It then
1642
+ * fell through to publishing the LOCAL's own name, and a local is not a symbol — the lane's
1643
+ * contract, pinned by the `Negative.fs` fixture, says so — so the edge it published had no
1644
+ * other end:
1645
+ *
1646
+ * references insert -> ConsoleKeyInfo // `let insert(key: ConsoleKeyInfo) =` in console.fs
1647
+ * references select -> Type // `let select (methInfo:MethodInfo) =` in the same file
1648
+ *
1649
+ * Neither `insert` nor `select` is a symbol; the truth oracle independently derived both
1650
+ * facts against `ReadLine`, the method that contains them, and the differential reported
1651
+ * 125 such rows. `insert` sits inside nested `for`/`do` bindings in a method body, which is
1652
+ * more than 8 levels up.
1653
+ *
1654
+ * Walking to the root and keeping the LAST callable found is both unbounded and simpler to
1655
+ * state: a local inside a local inside a member is owned by the member, because a namespace
1656
+ * and a module are not callables and so never become the answer. A decl with no callable
1657
+ * anywhere above it really is top-level, and its own name is then correct — which is the
1658
+ * `annotated : Bar` case in the fixture, and the reason the fallback is kept rather than
1659
+ * removed.
1660
+ *
1661
+ * Cost is O(depth) per annotation and the walk terminates at the root, so the bound that
1662
+ * caused this cannot come back.
1663
+ */
1664
+ /**
1665
+ * Owner lookups, memoised PER DECLARATION rather than per annotation.
1666
+ *
1667
+ * Both of the root walks below are O(depth) and both are asked the same question about the
1668
+ * same `decl` once for every type node the declaration carries — so a member with four
1669
+ * annotations walked to the root four times to learn the same thing. The pass is therefore
1670
+ * O(annotations · depth) over what is really O(declarations · depth).
1671
+ *
1672
+ * MEASURED on CheckExpressions.fs (702 KB), analyze-only with the parse excluded and the
1673
+ * median of 7: 1411 ms before this change, 1510 ms after the walks were introduced — about
1674
+ * 7% of the analyzer's own time, from two lines of correct-looking code. It is easy to miss
1675
+ * because the end-to-end dump barely moves: parse is 8.6 s on that file, so a 100 ms
1676
+ * regression is under 1% of the number a corpus run reports. That is the same shape as the
1677
+ * O(n^2) the M12 envelope found on the production path, found there by scaling rather than by
1678
+ * reading — so it was found here by measuring the thing the walks sit inside, not the file.
1679
+ *
1680
+ * Keyed by node in a WeakMap, so it needs no reset between runs and cannot outlive its tree.
1681
+ */
1682
+ ownerMemo = /* @__PURE__ */ new WeakMap();
1683
+ outermostCallableAbove(n) {
1684
+ const hit = this.ownerMemo.get(n);
1685
+ if (hit && "callable" in hit) return hit.callable ?? void 0;
1686
+ let found;
1687
+ let cur = n.parent;
1688
+ while (cur) {
1689
+ if (isCallableOwnerNode(cur)) found = cur;
1690
+ cur = cur.parent;
1691
+ }
1692
+ this.ownerMemo.set(n, { ...hit ?? {}, callable: found ?? null });
1693
+ return found;
1694
+ }
1695
+ /**
1696
+ * The type declaration whose body contains `n`, walking to the root.
1697
+ *
1698
+ * The OUTERMOST one, which for a mutual group is the node that holds every clause in the
1699
+ * group — `type A = … and B = …` is one `type_definition` with both bodies inside it. The
1700
+ * name of the type that actually owns a declaration is recovered separately, by taking the
1701
+ * last `type_name` before the declaration; see the class-field arm in `resolveSource`.
1702
+ */
1703
+ outermostTypeDefinitionAbove(n) {
1704
+ const hit = this.ownerMemo.get(n);
1705
+ if (hit && "typeDef" in hit) return hit.typeDef ?? void 0;
1706
+ let found;
1707
+ let cur = n.parent;
1708
+ while (cur) {
1709
+ if (cur.type === "type_definition") found = cur;
1710
+ cur = cur.parent;
1711
+ }
1712
+ this.ownerMemo.set(n, { ...hit ?? {}, typeDef: found ?? null });
1713
+ return found;
1714
+ }
1715
+ findAncestor(node, predicate, maxDepth = 12) {
1716
+ let cur = node.parent;
1717
+ for (let i = 0; cur && i < maxDepth; i++) {
1718
+ if (predicate(cur)) return cur;
1719
+ cur = cur.parent;
1720
+ }
1721
+ return void 0;
1722
+ }
1723
+ /**
1724
+ * The ACCESS MODIFIER written on a declaration, from the grammar's own
1725
+ * `access_modifier` node — never from the declaration's text.
1726
+ *
1727
+ * Where the modifier sits differs by construct, which is why a declaration node and
1728
+ * (for a type) its `type_name` node are both offered:
1729
+ * * a TYPE — `type internal Tables<'Token>` puts it INSIDE the `type_name`, as its
1730
+ * first named child; that node's own text is literally `internal Tables<'Token>`,
1731
+ * which is how the pre-FS-D6 name read published a name with an access modifier
1732
+ * inside it;
1733
+ * * a VALUE or a MEMBER — `let internal f = …`, `member private this.Reset()` — puts
1734
+ * it on the declaration itself;
1735
+ * * an omitted modifier is `public` in F# and the grammar then emits no node, so
1736
+ * the answer is `undefined`, which the caller treats as public. `undefined` is
1737
+ * deliberately NOT "public": a caller can tell "written public" from "written
1738
+ * nothing" without a second pass.
1739
+ */
1740
+ accessModifierOf(decl, typeNameNode) {
1741
+ const read = (n) => {
1742
+ if (!n) return void 0;
1743
+ for (let i = 0; i < n.namedChildCount; i++) {
1744
+ const c = n.namedChild(i);
1745
+ if (c && c.type === "access_modifier") return c.text;
1746
+ }
1747
+ return void 0;
1748
+ };
1749
+ const fromTypeName = read(typeNameNode);
1750
+ if (fromTypeName) return fromTypeName;
1751
+ const fromDecl = read(decl);
1752
+ if (fromDecl) return fromDecl;
1753
+ for (let i = 0; i < decl.namedChildCount; i++) {
1754
+ const c = decl.namedChild(i);
1755
+ if (!c) continue;
1756
+ if (c.type === "attributes" || c.type === "type_attributes") {
1757
+ const inner = read(c);
1758
+ if (inner) return inner;
1759
+ }
1760
+ }
1761
+ return void 0;
1762
+ }
1763
+ /** The access modifier a symbol was minted with, defaulting to F#'s implicit public. */
1764
+ visibilityOf(symbol) {
1765
+ const recorded = symbol.metadata?.accessModifier;
1766
+ if (recorded === "internal" || recorded === "private" || recorded === "public") return recorded;
1767
+ return "public";
1768
+ }
1769
+ /**
1770
+ * Every type name this file DECLARES with a real `type X = …` body — i.e. NOT by
1771
+ * `type X with …`. Collected before the walk so the publication rule is
1772
+ * order-independent.
1773
+ */
1774
+ /**
1775
+ * The module or namespace a FILE declares, or undefined.
1776
+ *
1777
+ * FS-D17. An `open` is a SIBLING of the file's `module` declaration, not a child, so a
1778
+ * scope stack maintained by walking down the tree is empty when the walk reaches one.
1779
+ * The analyzer's behaviour was then inconsistent in a way the corpus made obvious:
1780
+ *
1781
+ * FscMain.fs `module … CommandLineMain` (line 3), `open System` (line 5)
1782
+ * -> the import was sourced to the module, because the walk had
1783
+ * already entered the module node by the time it got there
1784
+ * AssemblyCheck.fs `open System` (line 3), `module AssemblyCheck =` (line 10)
1785
+ * -> the import was sourced to `Global`, because the walk had not
1786
+ * reached the module yet
1787
+ *
1788
+ * The file's scope is the same in both cases — in F# the whole file is inside
1789
+ * `AssemblyCheck` — so one of the two answers was wrong and the rule that produced them
1790
+ * was "wherever the walk happened to be", which is not a rule about the program. The
1791
+ * fix is a PRE-PASS, so the answer does not depend on the order of two sibling
1792
+ * declarations.
1793
+ *
1794
+ * A module is preferred over a namespace because a file that declares both nests the
1795
+ * module inside the namespace, and a module name is the closer scope of the two.
1796
+ *
1797
+ * Measured: 128 UNEXPLAINED open-path rows per 120 corpus files before the rule, and
1798
+ * the two example files above disagreeing with each other rather than with the truth.
1799
+ *
1800
+ * ONE top-level declaration, and only one. The first version of this pre-pass took the
1801
+ * FIRST module it found, which is right for AssemblyCheck.fs and wrong for
1802
+ * `docs/fcs/project.fsx`, and the two are the same shape: an `open` above the `module`
1803
+ * keyword. In a single-module file that scope is the file's, wherever the `open` sits. In
1804
+ * a SCRIPT with several modules it is not — `project.fsx` opens `System` at line 35 and
1805
+ * declares `module Inputs` at line 48, and code above the first module in a script is at
1806
+ * the SCRIPT's scope, not inside `Inputs`. "The first module" made 128 rows disagree in
1807
+ * the other direction.
1808
+ *
1809
+ * So the distinction is the count, and it is structural rather than a file extension: a
1810
+ * file with more than one top-level module/namespace is a script, and the walk's own scope
1811
+ * — which is correct inside a module and correctly empty above the first one — already
1812
+ * answers it.
1813
+ */
1814
+ collectFileScope(root) {
1815
+ const topLevel = [];
1816
+ let topLevelStartIndex;
1817
+ const stack = [root];
1818
+ while (stack.length > 0) {
1819
+ const n = stack.pop();
1820
+ if (n.type === "module_defn" || n.type === "named_module" || n.type === "namespace") {
1821
+ const nm = this.findChild(n, (c) => c.type === "type_name" || c.type === "long_identifier" || c.type === "identifier");
1822
+ if (nm?.text) {
1823
+ topLevel.push(nm.text.trim());
1824
+ if (topLevelStartIndex === void 0) topLevelStartIndex = n.startIndex;
1825
+ }
1826
+ continue;
1827
+ }
1828
+ for (let i = 0; i < n.namedChildCount; i++) {
1829
+ const c = n.namedChild(i);
1830
+ if (c) stack.push(c);
1831
+ }
1832
+ }
1833
+ if (topLevel.length !== 1) return void 0;
1834
+ const moduleStart = topLevelStartIndex;
1835
+ if (moduleStart === void 0) return void 0;
1836
+ const codeWalker = [root];
1837
+ while (codeWalker.length > 0) {
1838
+ const n = codeWalker.pop();
1839
+ if ((n.type === "function_or_value_defn" || n.type === "value_declaration" || n.type === "sequential_expression") && n.startIndex < moduleStart) {
1840
+ return void 0;
1841
+ }
1842
+ for (let i = 0; i < n.namedChildCount; i++) {
1843
+ const c = n.namedChild(i);
1844
+ if (c) codeWalker.push(c);
1845
+ }
1846
+ }
1847
+ return topLevel[0];
1848
+ }
1849
+ collectDeclaredTypeNames(root) {
1850
+ const names = /* @__PURE__ */ new Set();
1851
+ const stack = [root];
1852
+ const readName = (typeNameNode) => {
1853
+ const li = this.findChild(typeNameNode, (c) => c.type === "long_identifier");
1854
+ const id = li ? this.findDescendant(li, (c) => c.type === "identifier", 3) : this.findChild(typeNameNode, (c) => c.type === "identifier");
1855
+ return id?.text;
1856
+ };
1857
+ while (stack.length > 0) {
1858
+ const n = stack.pop();
1859
+ if (n.type === "type_definition" && !this.findChild(n, (c) => c.type === "type_extension")) {
1860
+ const clauses = this.filterChildren(n, (c) => TYPE_CLAUSE_TYPES.has(c.type));
1861
+ const typeNameNodes = clauses.length > 0 ? clauses.map((c) => this.findChild(c, (x) => x.type === "type_name")).filter((x) => !!x) : [this.findDescendant(n, (c) => c.type === "type_name", 3)].filter((x) => !!x);
1862
+ for (const typeNameNode of typeNameNodes) {
1863
+ const name = readName(typeNameNode);
1864
+ if (name) names.add(name);
1865
+ }
1866
+ continue;
1867
+ }
1868
+ for (let i = 0; i < n.namedChildCount; i++) {
1869
+ const c = n.namedChild(i);
1870
+ if (c) stack.push(c);
1871
+ }
1872
+ }
1873
+ return names;
1874
+ }
1875
+ /**
1876
+ * FS-D4 — the R9 DECLARED-TYPE reference graph: a declaration's WRITTEN type is a
1877
+ * dependency, published as a `references` edge marked
1878
+ * `metadata.relationshipType === "declared_type"`.
1879
+ *
1880
+ * WHY THIS LANE NEEDED IT. R9 is a family standard — rust's own R13 header calls it
1881
+ * one ("mirrors go/c/zig") and thirteen lanes implement it.
1882
+ * `script/m60/declared-type-refs-census.ts` measured fsharp **absent** with the
1883
+ * control present: the snippet parses, the holder is walked, and the lane emits no
1884
+ * type reference at all. A capability that is absent emits nothing for a per-edge
1885
+ * oracle to adjudicate, which is why it survived — the same failure mode R9 was
1886
+ * created to find.
1887
+ *
1888
+ * THE POSITIONS. A written type in F# is a `simple_type` / `postfix_type` /
1889
+ * `generic_type` / … wrapper whose NAME is a `long_identifier` (dotted) or a bare
1890
+ * `identifier`; the position is decided by the wrapper's PARENT, and the source
1891
+ * attribution follows the family rule measured off java/kotlin/python and pinned by
1892
+ * csharp's `declared-type-graph` fixture: a MEMBER's written type belongs to the
1893
+ * enclosing TYPE, a parameter's / return's / local's to the enclosing CALLABLE.
1894
+ *
1895
+ * record field -> the record type (a field is a property OF the type)
1896
+ * class-body `let` -> the class (F#'s private field — a member)
1897
+ * member parameter -> the member
1898
+ * member return type -> the member
1899
+ * DU case payload -> the union type
1900
+ * module let / local -> the binding or the enclosing function
1901
+ * type abbreviation -> the abbreviation
1902
+ * class inherit -> REFUSED: already an `extends` edge
1903
+ * interface impl -> REFUSED: already an `implements` edge
1904
+ * the ambiguous `type X = SomeType` (which the grammar reads as a one-case union)
1905
+ * -> REFUSED: the RHS and a DU case are the same text, and
1906
+ * script/m14/tmp/fs020 measured 114 such sites per 400 files
1907
+ * that single-file analysis cannot separate. See that probe
1908
+ * for the ruling.
1909
+ *
1910
+ * WHAT MUST NOT BECOME A NAME, each measured (script/m14/tmp/fs016):
1911
+ * a bound type PARAMETER (`'T`, `^T`) — inside a declaration's own `type_arguments`
1912
+ * it is a BINDING, not a dependency (rust's R13 rule; csharp's `_genericBound`
1913
+ * control);
1914
+ * an UNDERSCORE (`_`, the anonymous type of `Map<string,_>`) — not a name at all;
1915
+ * an F# PREDEFINED type (`int`, `string`, `unit`, …) — the analogue of go's
1916
+ * BUILTINS set and csharp's `int`/`string` control.
1917
+ * Deliberately NOT used: the capitalisation of a name. F# lets a type be named
1918
+ * anything, so this lane does not guess a type from its spelling.
1919
+ */
1920
+ extractDeclaredTypeRefs(rootNode, pushRel, publishedNames, fqnOfName, typeFqnOfName) {
1921
+ const PREDEFINED = /* @__PURE__ */ new Set([
1922
+ "int",
1923
+ "uint",
1924
+ "int8",
1925
+ "uint8",
1926
+ "int16",
1927
+ "uint16",
1928
+ "int32",
1929
+ "uint32",
1930
+ "int64",
1931
+ "uint64",
1932
+ "nativeint",
1933
+ "unativeint",
1934
+ "float",
1935
+ "double",
1936
+ "float32",
1937
+ "single",
1938
+ "decimal",
1939
+ "bool",
1940
+ "char",
1941
+ "string",
1942
+ "obj",
1943
+ "unit",
1944
+ "void",
1945
+ "exn",
1946
+ "list",
1947
+ "array",
1948
+ "seq",
1949
+ "option",
1950
+ "ref",
1951
+ "byte",
1952
+ "sbyte",
1953
+ "intptr",
1954
+ "uintptr",
1955
+ "bigint"
1956
+ ]);
1957
+ const nameNodeOf = (t) => this.typeNameNodeOf(t);
1958
+ const isTypeBinderNode = (node) => {
1959
+ for (let i = 0; i < node.childCount; i++) {
1960
+ const c = node.child(i);
1961
+ if (c && c.type === "'") return true;
1962
+ }
1963
+ return false;
1964
+ };
1965
+ const isIdentifierNameNode = (node, leaf) => {
1966
+ if (node.type === "identifier") return node.text === leaf;
1967
+ if (node.type === "long_identifier" || node.type === "long_identifier_or_op") {
1968
+ const last = this.filterChildren(node, (n) => n.type === "identifier").pop();
1969
+ return last?.text === leaf;
1970
+ }
1971
+ const inner = this.findDescendant(node, (n) => n.type === "identifier", 3);
1972
+ return inner?.text === leaf;
1973
+ };
1974
+ const publishedName = (node) => {
1975
+ const text = (node.text ?? "").trim();
1976
+ if (!text) return null;
1977
+ if (text === "_") return null;
1978
+ const leaf = text.includes(".") ? text.split(".").pop() : text;
1979
+ if (!leaf || leaf === "_") return null;
1980
+ if (isTypeBinderNode(node)) return null;
1981
+ if (PREDEFINED.has(leaf)) return null;
1982
+ if (!isIdentifierNameNode(node, leaf)) return null;
1983
+ return leaf;
1984
+ };
1985
+ const callablePosition = (t, decl) => {
1986
+ const inArgs = this.findAncestor(
1987
+ t,
1988
+ (n) => n.type === "paren_pattern" || n.type === "argument_patterns" || n.type === "argument_spec" || n.type === "arguments_spec" || n.type === "curried_spec",
1989
+ 3
1990
+ );
1991
+ if (inArgs) return "parameter";
1992
+ if (decl.type === "value_declaration") return "local_annotation";
1993
+ return "return_type";
1994
+ };
1995
+ const resolveSource = (t) => {
1996
+ const parent = t.parent;
1997
+ if (!parent) return null;
1998
+ if (parent.type === "class_inherits_decl" || parent.type === "interface_implementation") {
1999
+ return null;
2000
+ }
2001
+ if (parent.type === "type_attribute" || parent.type === "type_arguments" || parent.type === "type_argument_defn") {
2002
+ return null;
2003
+ }
2004
+ if (parent.type === "record_field") {
2005
+ const rt = this.findAncestor(parent, (n) => n.type === "record_type_defn", 6);
2006
+ if (!rt) return null;
2007
+ const tn = this.findChild(rt, (n) => n.type === "type_name");
2008
+ const name2 = tn ? this.typeInfoOf(tn)?.name ?? null : null;
2009
+ return name2 ? { source: name2, declaredOn: "record_field" } : null;
2010
+ }
2011
+ if (parent.type === "union_type_field") {
2012
+ const ud = this.findAncestor(parent, (n) => n.type === "union_type_defn", 4);
2013
+ if (!ud) return null;
2014
+ const tn = this.findDescendant(ud, (n) => n.type === "type_name", 2);
2015
+ const name2 = tn ? this.typeInfoOf(tn)?.name ?? null : null;
2016
+ return name2 ? { source: name2, declaredOn: "union_case_payload" } : null;
2017
+ }
2018
+ if (parent.type === "type_abbrev_defn") {
2019
+ const tn = this.findChild(parent, (n) => n.type === "type_name");
2020
+ const name2 = tn ? this.typeInfoOf(tn)?.name ?? null : null;
2021
+ return name2 ? { source: name2, declaredOn: "type_abbreviation" } : null;
2022
+ }
2023
+ const isDecl = (n) => n.type === "member_defn" || n.type === "function_or_value_defn" || n.type === "value_declaration" || n.type === "additional_constr_defn";
2024
+ const inLambda = this.findAncestor(
2025
+ parent,
2026
+ (n) => n.type === "fun_expression",
2027
+ 6
2028
+ );
2029
+ if (inLambda) return null;
2030
+ const decl = isDecl(parent) ? parent : this.findAncestor(parent, isDecl, 14);
2031
+ if (!decl) return null;
2032
+ const pos = callablePosition(t, decl);
2033
+ if (decl.type === "member_defn") {
2034
+ const mop = this.findChild(decl, (n) => n.type === "method_or_prop_defn");
2035
+ const prop = mop ? this.findChild(mop, (n) => n.type === "property_or_ident") : void 0;
2036
+ const idents = prop ? this.filterChildren(prop, (n) => n.type === "identifier") : [];
2037
+ const name2 = idents.length >= 2 ? idents[idents.length - 1]?.text : idents[0]?.text;
2038
+ return name2 ? { source: name2, declaredOn: pos } : null;
2039
+ }
2040
+ if (decl.type === "additional_constr_defn") return { source: "constructor", declaredOn: "parameter" };
2041
+ const inTypeBody = this.findAncestor(decl, (n) => n.type === "type_extension_elements", 3) !== void 0;
2042
+ const enclosingCallable = this.outermostCallableAbove(decl);
2043
+ if (inTypeBody && enclosingCallable === void 0) {
2044
+ const td = this.outermostTypeDefinitionAbove(decl);
2045
+ if (!td) return null;
2046
+ const fieldStart = decl.startIndex;
2047
+ const names = this.findDescendants(td, (n) => n.type === "type_name", 4).filter((n) => n.startIndex < fieldStart).sort((a, b) => a.startIndex - b.startIndex);
2048
+ const tn = names.length ? names[names.length - 1] : void 0;
2049
+ const tName = tn ? this.typeInfoOf(tn)?.name ?? null : null;
2050
+ if (tName) return { source: tName, declaredOn: "class_field" };
2051
+ }
2052
+ if (enclosingCallable) {
2053
+ if (enclosingCallable.type === "additional_constr_defn") return { source: "constructor", declaredOn: "local_annotation" };
2054
+ const cName = enclosingCallable.type === "member_defn" ? this.memberNameOf(enclosingCallable) : this.declaredBindingName(enclosingCallable);
2055
+ if (cName) return { source: cName, declaredOn: "local_annotation" };
2056
+ }
2057
+ const name = this.declaredBindingName(decl);
2058
+ if (name && publishedNames.has(name)) return { source: name, declaredOn: pos };
2059
+ return null;
2060
+ };
2061
+ const emitted = /* @__PURE__ */ new Set();
2062
+ const stack = [rootNode];
2063
+ while (stack.length > 0) {
2064
+ const n = stack.pop();
2065
+ if (n.type === "ERROR" || n.isMissing) continue;
2066
+ if (TYPE_WRAPPER_NODES.has(n.type)) {
2067
+ const nestedInType = n.parent != null && TYPE_WRAPPER_NODES.has(n.parent.type);
2068
+ if (!this.isTypeQueryPosition(n) && !nestedInType) {
2069
+ const nameNode = nameNodeOf(n);
2070
+ const writtenLeaf = nameNode ? publishedName(nameNode) : null;
2071
+ const target = writtenLeaf && typeFqnOfName.has(writtenLeaf) ? typeFqnOfName.get(writtenLeaf) : writtenLeaf;
2072
+ const src = resolveSource(n);
2073
+ const key = `${src?.source}|${target}|${n.startIndex}`;
2074
+ if (target && src && !emitted.has(key)) {
2075
+ emitted.add(key);
2076
+ const dottedAncestor = (() => {
2077
+ let cur = nameNode;
2078
+ for (let guard = 0; guard < 6 && cur; guard++) {
2079
+ if (cur.type === "long_identifier") return cur.text;
2080
+ cur = cur.parent;
2081
+ }
2082
+ return "";
2083
+ })();
2084
+ const writtenType = dottedAncestor || (nameNode?.text ?? "").trim();
2085
+ pushRel(
2086
+ fqnOfName.get(src.source) ?? src.source,
2087
+ target,
2088
+ ASTEngine.RelationshipType.References,
2089
+ 1,
2090
+ {
2091
+ nodeType: n.type,
2092
+ relationshipType: "declared_type",
2093
+ declaredOn: src.declaredOn,
2094
+ declaredType: target,
2095
+ // FS-P7-1: the same two facts every other target carries — the reason,
2096
+ // and the spelling this one is a reduction of. `publishedName` takes the
2097
+ // LAST segment of a dotted type, so `System.IO.Stream` becomes `Stream`
2098
+ // and the path the source wrote is otherwise unrecoverable from the
2099
+ // result. Whether that reduction is right is a price, not an opinion.
2100
+ targetResolution: UNRESOLVED_TYPE,
2101
+ ...writtenType.includes(".") && writtenType !== target ? { targetWritten: writtenType } : {}
2102
+ },
2103
+ nameNode ? nameNode.startPosition.row + 1 : n.startPosition.row + 1
2104
+ );
2105
+ }
2106
+ }
2107
+ }
2108
+ for (let i = 0; i < n.namedChildCount; i++) {
2109
+ const c = n.namedChild(i);
2110
+ if (c) stack.push(c);
2111
+ }
2112
+ }
2113
+ }
2114
+ /**
2115
+ * Pure-AST name + declared generic-parameter extraction for a type_name
2116
+ * node. The clean name is long_identifier -> identifier (generics live in a
2117
+ * sibling type_arguments node, never inside the identifier).
2118
+ */
2119
+ typeInfoOf(node) {
2120
+ const liNode = this.findChild(node, (n) => n.type === "long_identifier");
2121
+ const idNode = liNode ? this.findDescendant(liNode, (n) => n.type === "identifier") : this.findChild(node, (n) => n.type === "identifier");
2122
+ if (!idNode?.text) return null;
2123
+ const genericParameters = [];
2124
+ const taNode = this.findChild(node, (n) => n.type === "type_arguments");
2125
+ if (taNode) {
2126
+ for (let i = 0; i < taNode.namedChildCount; i++) {
2127
+ const c = taNode.namedChild(i);
2128
+ if (c?.type !== "type_argument_defn") continue;
2129
+ const argNode = this.findChild(c, (n) => n.type === "type_argument");
2130
+ if (argNode?.text) genericParameters.push(argNode.text);
2131
+ }
2132
+ }
2133
+ return {
2134
+ name: idNode.text,
2135
+ genericParameters: genericParameters.length > 0 ? genericParameters : void 0
2136
+ };
2137
+ }
2138
+ isFSharpBuiltin(call, leaf) {
2139
+ if (FSHARP_CONSOLE_BUILTINS.has(call)) return true;
2140
+ if (OBJECT_LEAF_METHODS.has(leaf)) return true;
2141
+ if (call === "Console.WriteLine" || call.startsWith("Console.")) return true;
2142
+ return false;
2143
+ }
2144
+ /**
2145
+ * Does this node CARRY an unnamed token equal to `text`, anywhere in its children?
2146
+ *
2147
+ * Distinct from `firstTokenIs`, which asks about the FIRST non-trivia token and is
2148
+ * used for a declaration's keyword (`override`, `new`). `open type X` needs the
2149
+ * opposite question — the token is not first, it sits between the keyword and the
2150
+ * path — so it asks for the token's presence rather than its position.
2151
+ */
2152
+ nodeHasToken(node, text) {
2153
+ for (let i = 0; i < node.childCount; i++) {
2154
+ const c = node.child(i);
2155
+ if (c && !c.isNamed && c.type === text) return true;
2156
+ }
2157
+ return false;
2158
+ }
2159
+ /**
2160
+ * The name-bearing node inside a F# type wrapper, or null.
2161
+ *
2162
+ * WRAPPERS NEST (`postfix_type` holds a `simple_type` for its base and a
2163
+ * `long_identifier` for the postfix), so the rule is: descend to the first type-ish
2164
+ * child, then take the LAST `long_identifier`/`identifier` at that level — the name
2165
+ * the wrapper writes LAST, which is the type the whole expression denotes (`int option`
2166
+ * is `option`, `Map<string,int>` is `Map`). This is the extraction the declared-type
2167
+ * pass has always used; it is a METHOD now so the attribute rule reads a wrapper the
2168
+ * same way instead of carrying a second copy of the descent that could drift from it.
2169
+ * A bound type parameter (`type_argument`) yields null: `'T` is a binder, not a name.
2170
+ */
2171
+ typeNameNodeOf(t) {
2172
+ let cur = t;
2173
+ for (let guard = 0; guard < 8 && cur; guard++) {
2174
+ if (cur.type === "type_argument") return null;
2175
+ if (cur.type === "long_identifier") {
2176
+ let leaf = null;
2177
+ for (let i = 0; i < cur.namedChildCount; i++) {
2178
+ const c = cur.namedChild(i);
2179
+ if (c && (c.type === "identifier" || c.type === "long_identifier")) leaf = c;
2180
+ }
2181
+ return leaf ?? cur;
2182
+ }
2183
+ if (cur.type === "identifier") return cur;
2184
+ let next = null;
2185
+ for (let i = 0; i < cur.namedChildCount; i++) {
2186
+ const c = cur.namedChild(i);
2187
+ if (c && (TYPE_WRAPPER_NODES.has(c.type) || c.type === "type_argument" || c.type === "type_arguments" || c.type === "long_identifier" || c.type === "identifier")) {
2188
+ next = c;
2189
+ break;
2190
+ }
2191
+ }
2192
+ if (!next) return null;
2193
+ cur = next;
2194
+ }
2195
+ return null;
2196
+ }
2197
+ /**
2198
+ * The dependency LEAF a name-bearing node denotes: the last segment of a dotted path.
2199
+ *
2200
+ * `System.Obsolete` is `Obsolete`, which is the spelling a consumer binds by — the
2201
+ * index stores one symbol per name, and `System.Obsolete` binds to nothing while
2202
+ * `Obsolete` binds to the attribute class.
2203
+ *
2204
+ * "Is this a name?" is asked of the NODE and never of the string. This lane's doc
2205
+ * carries a regex inventory whose first two rows are exactly this decision — the
2206
+ * patterns `/^['^]/` and `/^[A-Za-z_][A-Za-z0-9_']*$/` — and both were replaced for
2207
+ * the same reason this method has no pattern: the grammar already makes the
2208
+ * distinction as a node type. `typeNameNodeOf` returns an `identifier` or a
2209
+ * `long_identifier`, so a non-`identifier` node here is not a name, and `_` is
2210
+ * refused because the grammar writes it as an `identifier` with that one spelling
2211
+ * and it names no declaration.
2212
+ */
2213
+ dependencyLeafName(node) {
2214
+ if (node.type !== "identifier" && node.type !== "long_identifier") return null;
2215
+ const raw = (node.text ?? "").trim();
2216
+ if (!raw || raw === "_") return null;
2217
+ for (let i = 0; i < node.childCount; i++) {
2218
+ const c = node.child(i);
2219
+ if (c && (c.type === "'" || c.type === "^")) return null;
2220
+ }
2221
+ const leaf = raw.includes(".") ? raw.split(".").pop() : raw;
2222
+ return leaf && leaf !== "_" ? leaf : null;
2223
+ }
2224
+ /**
2225
+ * The VALUE inside an F# string-literal node, with the delimiters removed.
2226
+ *
2227
+ * The grammar gives the content no node: `(fsi_directive_decl (string))` — the
2228
+ * `string` node's two children are the two `"` tokens and `namedChildCount` is 0.
2229
+ * So the delimiters are read off the TREE (their `endIndex`/`startIndex` are the
2230
+ * exact byte bounds) and the content is the span between them. Taking `node.text`
2231
+ * instead put the quotes IN the dependency: `imports Fixture.ImportAndUse ->
2232
+ * "nuget: FSharp.Core"`, a target that binds to nothing, which is the shape of a
2233
+ * fabricated edge even though the fact underneath it is real.
2234
+ *
2235
+ * `verbatim_string` (`@"C:\dir\file.fsx"`) has three delimiter tokens: the opening
2236
+ * `@"`, the escape `\`, and the closing `"`. The span between the FIRST and the LAST
2237
+ * is the content. Its one disclosed limit: F# escapes a quote inside a verbatim
2238
+ * string by doubling it, and this does not un-double it, so a specifier containing
2239
+ * `""` keeps both characters. No corpus specifier does (measured over the whole
2240
+ * corpus's 193 `fsi_directive_decl` nodes), and a wrong-but-visible path is better
2241
+ * than a silently truncated one.
2242
+ */
2243
+ /**
2244
+ * THE FILE A `#load` NAMES, AS A PROJECT-RELATIVE PATH — or null when this checkout cannot
2245
+ * place it, in which case NO `part_of` ROW IS PUBLISHED.
2246
+ *
2247
+ * THE RULES, IN THE ORDER THEY APPLY:
2248
+ *
2249
+ * 1. A SPECIFIER WITH A SCHEME (`nuget:`, `http:`) names something that is not a path in
2250
+ * this checkout. Refused.
2251
+ * 2. A RELATIVE SPECIFIER IS RESOLVED AGAINST THE INCLUDING FILE'S OWN DIRECTORY — F#'s
2252
+ * `#load` is relative to the file that writes it, not to the project root.
2253
+ * 3. THE RESULT IS EXPRESSED RELATIVE TO THE PROJECT ROOT, so the spelling does not depend
2254
+ * on where the checkout lives (the same reason the yaml lane's document identity is
2255
+ * project-relative — src/ast/engine.ts:2405-2429).
2256
+ * 4. A TARGET THAT ESCAPES THE PROJECT (`..`) IS REFUSED: it is not a file of this project,
2257
+ * and a `part_of` row whose target no project key can answer is exactly the shape this
2258
+ * label exists to stop publishing (types.ts:187-190).
2259
+ * 5. WITHOUT A PROJECT ROOT THERE IS NO PROJECT-RELATIVE PATH TO PUBLISH, and the row is
2260
+ * REFUSED. Fail-closed: a `part_of` edge is a fact about two files of one project, and a
2261
+ * caller that cannot say which project the file is in cannot make that fact. The root
2262
+ * comes from `context.projectRoot` (production always supplies it — pool-worker.ts:2041)
2263
+ * and, failing that, from the nearest `.git` above the file (the dart sibling's rule).
2264
+ *
2265
+ * NO SOURCE TEXT IS READ: this is a pure path computation over the two strings the caller
2266
+ * already has, so the resolution cannot differ between two runs of the same file.
2267
+ */
2268
+ /**
2269
+ * FS-P1. THE FILE'S IDENTITY — the path a `part_of` target is spelled in, and the name
2270
+ * the file-scoped symbol carries.
2271
+ *
2272
+ * The path is computed against the SAME root the `part_of` target is computed against
2273
+ * (`context.projectRoot`, the repository root in production — pool-worker.ts:2041), so
2274
+ * the two spellings agree by construction rather than by coincidence.
2275
+ */
2276
+ fsharpFileIdentity(filePath, context) {
2277
+ const base = path.basename(filePath);
2278
+ const dot = base.lastIndexOf(".");
2279
+ const stem = dot > 0 ? base.slice(0, dot) : base;
2280
+ const contextRoot = context?.projectRoot;
2281
+ const projectRoot = typeof contextRoot === "string" && contextRoot.length > 0 ? contextRoot : fsharpProjectRootOf(filePath);
2282
+ if (projectRoot === null) return { relPath: null, stem };
2283
+ return { relPath: fsharpRepoRelPath(projectRoot, filePath), stem };
2284
+ }
2285
+ fsharpPartTargetPath(filePath, context, specifier) {
2286
+ if (specifier.length === 0) return null;
2287
+ if (specifier.indexOf(":") >= 0) return null;
2288
+ const contextRoot = context?.projectRoot;
2289
+ const projectRoot = typeof contextRoot === "string" && contextRoot.length > 0 ? contextRoot : fsharpProjectRootOf(filePath);
2290
+ if (projectRoot === null) return null;
2291
+ const abs = path.resolve(path.dirname(path.resolve(filePath)), specifier);
2292
+ const rel = path.relative(path.resolve(projectRoot), abs);
2293
+ if (rel.length === 0 || path.isAbsolute(rel)) return null;
2294
+ if (rel === ".." || rel.startsWith(`..${path.sep}`)) return null;
2295
+ return rel.split(path.sep).join("/");
2296
+ }
2297
+ stringLiteralValue(node) {
2298
+ if (node.type !== "string" && node.type !== "verbatim_string") return null;
2299
+ if (node.childCount === 0) return null;
2300
+ const first = node.child(0);
2301
+ const last = node.child(node.childCount - 1);
2302
+ if (!first || !last) return null;
2303
+ const from = first.endIndex - node.startIndex;
2304
+ const to = last.startIndex - node.startIndex;
2305
+ if (!(to > from)) return null;
2306
+ const value = node.text.slice(from, to);
2307
+ return value.length > 0 ? value : null;
2308
+ }
2309
+ firstTokenIs(node, text) {
2310
+ for (let i = 0; i < node.childCount; i++) {
2311
+ const c = node.child(i);
2312
+ if (!c) continue;
2313
+ if (c.type === text) return true;
2314
+ if (c.isNamed) break;
2315
+ }
2316
+ return false;
2317
+ }
2318
+ findChild(node, predicate) {
2319
+ for (let i = 0; i < node.namedChildCount; i++) {
2320
+ const child = node.namedChild(i);
2321
+ if (child && predicate(child)) return child;
2322
+ }
2323
+ return void 0;
2324
+ }
2325
+ filterChildren(node, predicate) {
2326
+ const results = [];
2327
+ for (let i = 0; i < node.namedChildCount; i++) {
2328
+ const child = node.namedChild(i);
2329
+ if (child && predicate(child)) results.push(child);
2330
+ }
2331
+ return results;
2332
+ }
2333
+ /**
2334
+ * The callee NAME inside a pipeline operand (FS-D30).
2335
+ *
2336
+ * An operand is either name-like already (the `xs |> ignore` shape) or an application whose callee
2337
+ * is its FIRST name-like child (the `xs |> Array.exists(fun …)` shape), which is what puts an
2338
+ * `application_expression` on the callee side and defeated the old direct-children filter. One
2339
+ * level of descent is enough for both observed shapes and deliberately not more: descending until
2340
+ * something name-like turns up would read an ARGUMENT on the shapes where the callee is absent.
2341
+ */
2342
+ pipelineCalleeName(operand) {
2343
+ if (!operand) return void 0;
2344
+ if (PIPELINE_NAMEY.has(operand.type)) return operand;
2345
+ if (operand.type === "application_expression" || operand.type === "prefixed_expression") {
2346
+ for (let i = 0; i < operand.namedChildCount; i++) {
2347
+ const c = operand.namedChild(i);
2348
+ if (c && PIPELINE_NAMEY.has(c.type)) return c;
2349
+ }
2350
+ }
2351
+ return void 0;
2352
+ }
2353
+ findDescendants(node, predicate, maxDepth = 5) {
2354
+ const results = [];
2355
+ if (maxDepth <= 0) return results;
2356
+ for (let i = 0; i < node.namedChildCount; i++) {
2357
+ const child = node.namedChild(i);
2358
+ if (child) {
2359
+ if (predicate(child)) results.push(child);
2360
+ results.push(...this.findDescendants(child, predicate, maxDepth - 1));
2361
+ }
2362
+ }
2363
+ return results;
2364
+ }
2365
+ findDescendant(node, predicate, maxDepth = 5) {
2366
+ if (maxDepth <= 0) return void 0;
2367
+ for (let i = 0; i < node.namedChildCount; i++) {
2368
+ const child = node.namedChild(i);
2369
+ if (child) {
2370
+ if (predicate(child)) return child;
2371
+ const found = this.findDescendant(child, predicate, maxDepth - 1);
2372
+ if (found) return found;
2373
+ }
2374
+ }
2375
+ return void 0;
2376
+ }
2377
+ nodeToRange(node) {
2378
+ return {
2379
+ startLine: node.startPosition.row + 1,
2380
+ startColumn: node.startPosition.column + 1,
2381
+ endLine: node.endPosition.row + 1,
2382
+ endColumn: node.endPosition.column + 1,
2383
+ startIndex: node.startIndex,
2384
+ endIndex: node.endIndex
2385
+ };
2386
+ }
2387
+ };
2388
+ export {
2389
+ RealFsharpAnalyzer
2390
+ };