@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.
- package/CHANGELOG.md +298 -0
- package/EMBEDDINGS.md +355 -0
- package/LICENSE +21 -0
- package/README.md +415 -2
- package/bin/bb +13 -0
- package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
- package/dist/brain-index-health-KY7K552E.js +17 -0
- package/dist/brain-relationship-enrichment-QH7OOHUF.js +558 -0
- package/dist/brain.sql-EIM4WS4D.js +29 -0
- package/dist/chunk-2BSKD7O3.js +40 -0
- package/dist/chunk-2DE27DVC.js +1314 -0
- package/dist/chunk-2RRBMNBQ.js +322 -0
- package/dist/chunk-2RTN2DFU.js +711 -0
- package/dist/chunk-2WXYNLBA.js +29 -0
- package/dist/chunk-3CTFIJJB.js +17 -0
- package/dist/chunk-3LFTSMIV.js +174 -0
- package/dist/chunk-47HKX4AJ.js +930 -0
- package/dist/chunk-4H22TLOT.js +543 -0
- package/dist/chunk-5SD45FLC.js +818 -0
- package/dist/chunk-66A5ZFYA.js +1758 -0
- package/dist/chunk-6AIRWAO2.js +247 -0
- package/dist/chunk-6WNGD3NN.js +196 -0
- package/dist/chunk-7AN7U5IZ.js +371 -0
- package/dist/chunk-BHB7BYYI.js +626 -0
- package/dist/chunk-BQ3OVQ3P.js +125 -0
- package/dist/chunk-CW4XXSNN.js +1479 -0
- package/dist/chunk-D2Q4Y47A.js +53 -0
- package/dist/chunk-DIYAGQJL.js +34 -0
- package/dist/chunk-DYM4FQEE.js +90 -0
- package/dist/chunk-EWMWK7WN.js +75 -0
- package/dist/chunk-FVVQTSIM.js +3427 -0
- package/dist/chunk-FWSL4VG6.js +1290 -0
- package/dist/chunk-GBI7V7KY.js +69 -0
- package/dist/chunk-GEFFO6O2.js +152 -0
- package/dist/chunk-HOWAQXJO.js +236 -0
- package/dist/chunk-HQSPJDHI.js +613 -0
- package/dist/chunk-IFZNR5UJ.js +2863 -0
- package/dist/chunk-J4PMBXQQ.js +336 -0
- package/dist/chunk-KAET47ZL.js +125 -0
- package/dist/chunk-KCDECRJY.js +440 -0
- package/dist/chunk-KLK3PBTO.js +1119 -0
- package/dist/chunk-L5TDYKYK.js +76 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-OWISBAOF.js +61 -0
- package/dist/chunk-PJTNGD3Z.js +6924 -0
- package/dist/chunk-QLK6ED4Q.js +235 -0
- package/dist/chunk-R2GW4F6R.js +19 -0
- package/dist/chunk-RCMTFSEF.js +87 -0
- package/dist/chunk-RH7KC5HU.js +12050 -0
- package/dist/chunk-RM3PY6TG.js +376 -0
- package/dist/chunk-ROMM6KMC.js +433 -0
- package/dist/chunk-SK6YVHFZ.js +211 -0
- package/dist/chunk-SM7RFQRO.js +214 -0
- package/dist/chunk-UFE77L67.js +782 -0
- package/dist/chunk-UGR4QIGS.js +67 -0
- package/dist/chunk-UUNHHX4M.js +13 -0
- package/dist/chunk-V44PGPA6.js +2104 -0
- package/dist/chunk-VL52PL2T.js +71 -0
- package/dist/chunk-VURYFIHW.js +201 -0
- package/dist/chunk-XJKJYIPP.js +7436 -0
- package/dist/chunk-YI6FF2ZR.js +340 -0
- package/dist/cli/index.js +37 -0
- package/dist/config-HZAAYDP3.js +10 -0
- package/dist/content-search-642F7E5V.js +593 -0
- package/dist/database-TCTZDLE5.js +9 -0
- package/dist/doctor-GXCKSJOX.js +395 -0
- package/dist/edit-engine-KM7MPFEC.js +51 -0
- package/dist/engine-42TZFNIZ.js +11 -0
- package/dist/engine-LO6L4JLQ.js +18 -0
- package/dist/fingerprint-XDXFRWO6.js +33 -0
- package/dist/framework-intelligence-DWFCKV44.js +860 -0
- package/dist/freshness-3K6VU5OT.js +174 -0
- package/dist/graph-engine-PL5YA7BA.js +21 -0
- package/dist/graph_request-MM4REP6H.js +350 -0
- package/dist/impact_request-WDE47IZ5.js +68 -0
- package/dist/index-space-IUUN5WYV.js +15 -0
- package/dist/index-status-6BD7UBHR.js +34 -0
- package/dist/index.js +159 -0
- package/dist/instance-FO6QZRLH.js +19 -0
- package/dist/language-detection-DFCAU5DZ.js +11 -0
- package/dist/lsp/server.js +4301 -0
- package/dist/mcp/server.js +132 -0
- package/dist/onboard-DIQZV2NW.js +161 -0
- package/dist/override-companion-pass-XR7MHTP7.js +117 -0
- package/dist/parallel-indexer-4PW3AFXP.js +88 -0
- package/dist/pool-worker.js +1153 -0
- package/dist/project-health-ZS5TS2Z6.js +94 -0
- package/dist/project-index-5SDPHITE.js +57 -0
- package/dist/project-registry-DGZBIZS7.js +355 -0
- package/dist/real-bash-analyzer-SXSV67E2.js +2201 -0
- package/dist/real-c-analyzer-SY7FTZ6W.js +5942 -0
- package/dist/real-clean-analyzer-IGDHHLZK.js +3470 -0
- package/dist/real-clojure-analyzer-4OH7IWLB.js +2818 -0
- package/dist/real-cpp-analyzer-7LTDRJRJ.js +4101 -0
- package/dist/real-csharp-analyzer-KICU3TLH.js +3298 -0
- package/dist/real-css-analyzer-HPU277Q6.js +15 -0
- package/dist/real-dart-analyzer-7LZ5LCMW.js +3944 -0
- package/dist/real-elixir-analyzer-BE3MEVZO.js +2192 -0
- package/dist/real-erlang-analyzer-BZMAKYBK.js +1731 -0
- package/dist/real-fsharp-analyzer-5QLTOV3E.js +2390 -0
- package/dist/real-go-analyzer-POD7MZQR.js +4126 -0
- package/dist/real-graphql-analyzer-MAQRZ6QW.js +1442 -0
- package/dist/real-groovy-analyzer-MEWSUPMJ.js +2285 -0
- package/dist/real-haskell-analyzer-I5L2GR3Y.js +2533 -0
- package/dist/real-hcl-analyzer-DBOPXSWJ.js +725 -0
- package/dist/real-html-analyzer-O63XZLZD.js +1759 -0
- package/dist/real-java-analyzer-CHCW3EKA.js +4677 -0
- package/dist/real-javascript-analyzer-QAAJQYZE.js +14 -0
- package/dist/real-json-analyzer-UGBR7YQN.js +1640 -0
- package/dist/real-julia-analyzer-35Q4GHOZ.js +2085 -0
- package/dist/real-kotlin-analyzer-GRAYCWW2.js +3956 -0
- package/dist/real-lua-analyzer-5BAPDZDF.js +1957 -0
- package/dist/real-makefile-analyzer-YJKAHNAS.js +920 -0
- package/dist/real-markdown-analyzer-UKUQZO4F.js +1215 -0
- package/dist/real-matlab-analyzer-FVIKQZKG.js +480 -0
- package/dist/real-nim-analyzer-M7FAIABG.js +3603 -0
- package/dist/real-objective-c-analyzer-XMYQIGQI.js +1518 -0
- package/dist/real-perl-analyzer-ZLY6M2DZ.js +1865 -0
- package/dist/real-php-analyzer-KI2ZAM7X.js +9368 -0
- package/dist/real-powershell-analyzer-B3T5ZP6U.js +2915 -0
- package/dist/real-protobuf-analyzer-23OZ75XV.js +867 -0
- package/dist/real-python-analyzer-I4TIUB6D.js +4880 -0
- package/dist/real-r-analyzer-FJZJXXS6.js +2596 -0
- package/dist/real-ruby-analyzer-M3UFIVTQ.js +2429 -0
- package/dist/real-rust-analyzer-LVSONPAN.js +3359 -0
- package/dist/real-scala-analyzer-CWPI33Q2.js +3293 -0
- package/dist/real-scss-analyzer-TGRJQ7TH.js +1515 -0
- package/dist/real-solidity-analyzer-4P2VBVWA.js +2608 -0
- package/dist/real-sql-analyzer-J7DYZOZR.js +3840 -0
- package/dist/real-svelte-analyzer-NHEN2SX5.js +2532 -0
- package/dist/real-swift-analyzer-2NSYULWI.js +2311 -0
- package/dist/real-toml-analyzer-L3C66L7Q.js +929 -0
- package/dist/real-typescript-analyzer-FTC6ETRQ.js +14 -0
- package/dist/real-vb-analyzer-ORY7LAQS.js +3145 -0
- package/dist/real-vba-analyzer-JH2TTUFI.js +466 -0
- package/dist/real-vue-analyzer-OO4FUGBV.js +2680 -0
- package/dist/real-xml-analyzer-6CQVR477.js +2405 -0
- package/dist/real-yaml-analyzer-J6MQG3YX.js +3528 -0
- package/dist/real-zig-analyzer-EFKCPJNU.js +2430 -0
- package/dist/regex-search-OPTLKUN5.js +239 -0
- package/dist/rules-I453JTRC.js +152 -0
- package/dist/scoped-path-MJYWJZQX.js +11 -0
- package/dist/setup-show-Y5GB5KXA.js +15 -0
- package/dist/setup-wizard-G5YYR5T6.js +24 -0
- package/dist/symbol-search-KB4MU4SI.js +16 -0
- package/dist/taint-YOTN5X55.js +202 -0
- package/dist/taint-analyzer-N7YLZU3N.js +163 -0
- package/dist/tool-handlers-G4NAGJIH.js +72 -0
- package/dist/tool-registry-BPEJNSZG.js +17 -0
- package/dist/tool-validation-RF675A3C.js +32 -0
- package/dist/type-inference-4Y7WTMFE.js +13 -0
- package/dist/vector-binary-LMY4QGAN.js +19 -0
- package/dist/version-LXGNFO37.js +9 -0
- package/dist/wasm-NMWO3WXY.js +16 -0
- package/docs/EMBEDDINGS.md +132 -0
- package/docs/README.md +178 -0
- package/docs/data/m10-baseline.json +602 -0
- package/docs/data/m10-benchmark-baseline.json +57 -0
- package/docs/data/m10-benchmark-last-run.json +127 -0
- package/docs/data/m10-final-audit.json +215 -0
- package/docs/data/m12-final-audit.json +138 -0
- package/docs/data/m12-surface-matrix.json +50 -0
- package/docs/data/public-tools.json +1902 -0
- package/docs/indexing/DEAD-ASSETS.md +88 -0
- package/docs/indexing/INDEXER-REVIEW.md +634 -0
- package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
- package/docs/indexing/README.md +427 -0
- package/docs/indexing/discovery-matrix.md +41 -0
- package/docs/indexing/embeddings.md +325 -0
- package/docs/indexing/environment-variables.md +77 -0
- package/docs/indexing/performance.md +323 -0
- package/docs/indexing/resume.md +345 -0
- package/docs/indexing/troubleshooting.md +391 -0
- package/docs/man/bb.1 +190 -0
- package/docs/tools/bb_analyze.md +147 -0
- package/docs/tools/bb_annotate.md +110 -0
- package/docs/tools/bb_annotations.md +106 -0
- package/docs/tools/bb_ask.md +101 -0
- package/docs/tools/bb_cross_language.md +103 -0
- package/docs/tools/bb_doc.md +169 -0
- package/docs/tools/bb_edit.md +185 -0
- package/docs/tools/bb_graph.md +169 -0
- package/docs/tools/bb_health.md +156 -0
- package/docs/tools/bb_impact.md +117 -0
- package/docs/tools/bb_index.md +108 -0
- package/docs/tools/bb_migrate.md +135 -0
- package/docs/tools/bb_patterns.md +140 -0
- package/docs/tools/bb_search.md +150 -0
- package/docs/tools/bb_security.md +100 -0
- package/docs/tools/bb_visualize.md +184 -0
- package/grammars/README.md +57 -0
- package/grammars/THIRD-PARTY-NOTICES.md +50 -0
- package/grammars/tree-sitter-c/LICENSE +21 -0
- package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
- package/grammars/tree-sitter-cpp/LICENSE +21 -0
- package/grammars/tree-sitter-csharp/LICENSE +21 -0
- package/grammars/tree-sitter-css/LICENSE +21 -0
- package/grammars/tree-sitter-css/README.md +21 -0
- package/grammars/tree-sitter-dart/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/README.md +18 -0
- package/grammars/tree-sitter-elixir/LICENSE +176 -0
- package/grammars/tree-sitter-elixir/README.md +11 -0
- package/grammars/tree-sitter-fsharp/LICENSE +21 -0
- package/grammars/tree-sitter-fsharp/README.md +45 -0
- package/grammars/tree-sitter-graphql/LICENSE +21 -0
- package/grammars/tree-sitter-graphql/README.md +106 -0
- package/grammars/tree-sitter-groovy/LICENSE +21 -0
- package/grammars/tree-sitter-groovy/README.md +4 -0
- package/grammars/tree-sitter-haskell/LICENSE +21 -0
- package/grammars/tree-sitter-haskell/README.md +394 -0
- package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
- package/grammars/tree-sitter-hcl/LICENSE +201 -0
- package/grammars/tree-sitter-hcl/README.md +39 -0
- package/grammars/tree-sitter-html/LICENSE +21 -0
- package/grammars/tree-sitter-html/README.md +21 -0
- package/grammars/tree-sitter-julia/LICENSE +21 -0
- package/grammars/tree-sitter-julia/README.md +24 -0
- package/grammars/tree-sitter-kotlin/LICENSE +9 -0
- package/grammars/tree-sitter-lua/LICENSE.md +21 -0
- package/grammars/tree-sitter-lua/README.md +29 -0
- package/grammars/tree-sitter-make/LICENSE +21 -0
- package/grammars/tree-sitter-matlab/LICENSE +19 -0
- package/grammars/tree-sitter-matlab/README.md +99 -0
- package/grammars/tree-sitter-nim/LICENSE +373 -0
- package/grammars/tree-sitter-nim/README.md +46 -0
- package/grammars/tree-sitter-objc/LICENSE +21 -0
- package/grammars/tree-sitter-objc/README.md +20 -0
- package/grammars/tree-sitter-perl/LICENSE +7 -0
- package/grammars/tree-sitter-perl/README.md +100 -0
- package/grammars/tree-sitter-powershell/LICENSE +21 -0
- package/grammars/tree-sitter-powershell/README.md +7 -0
- package/grammars/tree-sitter-proto/LICENSE +21 -0
- package/grammars/tree-sitter-proto/README.md +105 -0
- package/grammars/tree-sitter-r/LICENSE +9 -0
- package/grammars/tree-sitter-r/README.md +66 -0
- package/grammars/tree-sitter-ruby/LICENSE +22 -0
- package/grammars/tree-sitter-scss/LICENSE +21 -0
- package/grammars/tree-sitter-scss/README.md +17 -0
- package/grammars/tree-sitter-solidity/LICENSE +7 -0
- package/grammars/tree-sitter-sql/LICENSE +21 -0
- package/grammars/tree-sitter-typescript/LICENSE +21 -0
- package/grammars/tree-sitter-vba/LICENSE +21 -0
- package/grammars/tree-sitter-vba/README.md +738 -0
- package/grammars/tree-sitter-vbnet/LICENSE +21 -0
- package/grammars/tree-sitter-vbnet/README.md +322 -0
- package/grammars/tree-sitter-xml/LICENSE +19 -0
- package/grammars/tree-sitter-yaml/LICENSE +20 -0
- package/grammars/tree-sitter-yaml/README.md +21 -0
- package/package.json +184 -4
- package/wasm/dart.wasm +0 -0
- package/wasm/tree-sitter-bash.wasm +0 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-clean.wasm +0 -0
- package/wasm/tree-sitter-clojure.wasm +0 -0
- package/wasm/tree-sitter-core_schema.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-css.wasm +0 -0
- package/wasm/tree-sitter-dart.wasm +0 -0
- package/wasm/tree-sitter-elisp.wasm +0 -0
- package/wasm/tree-sitter-elixir.wasm +0 -0
- package/wasm/tree-sitter-elm.wasm +0 -0
- package/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/wasm/tree-sitter-erlang.wasm +0 -0
- package/wasm/tree-sitter-fsharp.wasm +0 -0
- package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-graphql.wasm +0 -0
- package/wasm/tree-sitter-groovy.wasm +0 -0
- package/wasm/tree-sitter-haskell.wasm +0 -0
- package/wasm/tree-sitter-hcl.wasm +0 -0
- package/wasm/tree-sitter-html.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-json.wasm +0 -0
- package/wasm/tree-sitter-json_schema.wasm +0 -0
- package/wasm/tree-sitter-julia.wasm +0 -0
- package/wasm/tree-sitter-kotlin.wasm +0 -0
- package/wasm/tree-sitter-lua.wasm +0 -0
- package/wasm/tree-sitter-make.wasm +0 -0
- package/wasm/tree-sitter-markdown.wasm +0 -0
- package/wasm/tree-sitter-markdown_inline.wasm +0 -0
- package/wasm/tree-sitter-matlab.wasm +0 -0
- package/wasm/tree-sitter-nim.wasm +0 -0
- package/wasm/tree-sitter-objc.wasm +0 -0
- package/wasm/tree-sitter-ocaml.wasm +0 -0
- package/wasm/tree-sitter-perl.wasm +0 -0
- package/wasm/tree-sitter-php.wasm +0 -0
- package/wasm/tree-sitter-php_only.wasm +0 -0
- package/wasm/tree-sitter-powershell.wasm +0 -0
- package/wasm/tree-sitter-protobuf.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ql.wasm +0 -0
- package/wasm/tree-sitter-r.wasm +0 -0
- package/wasm/tree-sitter-rescript.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-scala.wasm +0 -0
- package/wasm/tree-sitter-scss.wasm +0 -0
- package/wasm/tree-sitter-solidity.wasm +0 -0
- package/wasm/tree-sitter-sql.wasm +0 -0
- package/wasm/tree-sitter-svelte.wasm +0 -0
- package/wasm/tree-sitter-swift.wasm +0 -0
- package/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/wasm/tree-sitter-terraform.wasm +0 -0
- package/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/wasm/tree-sitter-toml.wasm +0 -0
- package/wasm/tree-sitter-tsx.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter-vba.wasm +0 -0
- package/wasm/tree-sitter-vbnet.wasm +0 -0
- package/wasm/tree-sitter-vue.wasm +0 -0
- package/wasm/tree-sitter-xml.wasm +0 -0
- package/wasm/tree-sitter-yaml.wasm +0 -0
- package/wasm/tree-sitter-zig.wasm +0 -0
- package/wasm/tree-sitter.wasm +0 -0
- package/wasm/tsx.wasm +0 -0
- 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
|
+
};
|