@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,2532 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RealTypeScriptAnalyzer
|
|
3
|
+
} from "./chunk-XJKJYIPP.js";
|
|
4
|
+
import {
|
|
5
|
+
RealJavaScriptAnalyzer
|
|
6
|
+
} from "./chunk-FVVQTSIM.js";
|
|
7
|
+
import "./chunk-R2GW4F6R.js";
|
|
8
|
+
import {
|
|
9
|
+
ASTEngine
|
|
10
|
+
} from "./chunk-V44PGPA6.js";
|
|
11
|
+
import "./chunk-BQ3OVQ3P.js";
|
|
12
|
+
import "./chunk-SK6YVHFZ.js";
|
|
13
|
+
import "./chunk-2BSKD7O3.js";
|
|
14
|
+
import "./chunk-KAET47ZL.js";
|
|
15
|
+
import "./chunk-KCDECRJY.js";
|
|
16
|
+
import "./chunk-MLKGABMK.js";
|
|
17
|
+
|
|
18
|
+
// src/ast/real-svelte-analyzer.ts
|
|
19
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
function shiftMetadataLineMirrors(md, lineOffset) {
|
|
22
|
+
if (!md || lineOffset === 0) return;
|
|
23
|
+
for (const [k, v] of Object.entries(md)) {
|
|
24
|
+
if (!keyCarriesALine(k)) continue;
|
|
25
|
+
if (typeof v === "number") md[k] = v + lineOffset;
|
|
26
|
+
else if (Array.isArray(v) && v.some((x) => typeof x === "number")) {
|
|
27
|
+
md[k] = v.map((x) => typeof x === "number" ? x + lineOffset : x);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
var PROJECT_PUBLISHED_PACKAGES = /* @__PURE__ */ new Map();
|
|
32
|
+
function projectPublishedPackages(projectRoot) {
|
|
33
|
+
const cached = PROJECT_PUBLISHED_PACKAGES.get(projectRoot);
|
|
34
|
+
if (cached) return cached;
|
|
35
|
+
const names = /* @__PURE__ */ new Set();
|
|
36
|
+
const read = (p) => {
|
|
37
|
+
try {
|
|
38
|
+
const j = JSON.parse(readFileSync(p, "utf8"));
|
|
39
|
+
if (typeof j?.name === "string" && j.name.trim() !== "") names.add(j.name.trim());
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
read(path.join(projectRoot, "package.json"));
|
|
44
|
+
for (const dir of ["packages", "apps"]) {
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = readdirSync(path.join(projectRoot, dir));
|
|
48
|
+
} catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const e of entries) read(path.join(projectRoot, dir, e, "package.json"));
|
|
52
|
+
}
|
|
53
|
+
PROJECT_PUBLISHED_PACKAGES.set(projectRoot, names);
|
|
54
|
+
return names;
|
|
55
|
+
}
|
|
56
|
+
var HTML_BUILTIN_TAGS = /* @__PURE__ */ new Set([
|
|
57
|
+
"a",
|
|
58
|
+
"abbr",
|
|
59
|
+
"address",
|
|
60
|
+
"area",
|
|
61
|
+
"article",
|
|
62
|
+
"aside",
|
|
63
|
+
"audio",
|
|
64
|
+
"b",
|
|
65
|
+
"base",
|
|
66
|
+
"bdi",
|
|
67
|
+
"bdo",
|
|
68
|
+
"blockquote",
|
|
69
|
+
"body",
|
|
70
|
+
"br",
|
|
71
|
+
"button",
|
|
72
|
+
"canvas",
|
|
73
|
+
"caption",
|
|
74
|
+
"cite",
|
|
75
|
+
"code",
|
|
76
|
+
"col",
|
|
77
|
+
"colgroup",
|
|
78
|
+
"data",
|
|
79
|
+
"datalist",
|
|
80
|
+
"dd",
|
|
81
|
+
"del",
|
|
82
|
+
"details",
|
|
83
|
+
"dfn",
|
|
84
|
+
"dialog",
|
|
85
|
+
"div",
|
|
86
|
+
"dl",
|
|
87
|
+
"dt",
|
|
88
|
+
"em",
|
|
89
|
+
"embed",
|
|
90
|
+
"fieldset",
|
|
91
|
+
"figcaption",
|
|
92
|
+
"figure",
|
|
93
|
+
"footer",
|
|
94
|
+
"form",
|
|
95
|
+
"h1",
|
|
96
|
+
"h2",
|
|
97
|
+
"h3",
|
|
98
|
+
"h4",
|
|
99
|
+
"h5",
|
|
100
|
+
"h6",
|
|
101
|
+
"head",
|
|
102
|
+
"header",
|
|
103
|
+
"hgroup",
|
|
104
|
+
"hr",
|
|
105
|
+
"html",
|
|
106
|
+
"i",
|
|
107
|
+
"iframe",
|
|
108
|
+
"img",
|
|
109
|
+
"input",
|
|
110
|
+
"ins",
|
|
111
|
+
"kbd",
|
|
112
|
+
"label",
|
|
113
|
+
"legend",
|
|
114
|
+
"li",
|
|
115
|
+
"link",
|
|
116
|
+
"main",
|
|
117
|
+
"map",
|
|
118
|
+
"mark",
|
|
119
|
+
"menu",
|
|
120
|
+
"meta",
|
|
121
|
+
"meter",
|
|
122
|
+
"nav",
|
|
123
|
+
"noscript",
|
|
124
|
+
"object",
|
|
125
|
+
"ol",
|
|
126
|
+
"optgroup",
|
|
127
|
+
"option",
|
|
128
|
+
"output",
|
|
129
|
+
"p",
|
|
130
|
+
"picture",
|
|
131
|
+
"pre",
|
|
132
|
+
"progress",
|
|
133
|
+
"q",
|
|
134
|
+
"rp",
|
|
135
|
+
"rt",
|
|
136
|
+
"ruby",
|
|
137
|
+
"s",
|
|
138
|
+
"samp",
|
|
139
|
+
"script",
|
|
140
|
+
"search",
|
|
141
|
+
"section",
|
|
142
|
+
"select",
|
|
143
|
+
"slot",
|
|
144
|
+
"small",
|
|
145
|
+
"source",
|
|
146
|
+
"span",
|
|
147
|
+
"strong",
|
|
148
|
+
"style",
|
|
149
|
+
"sub",
|
|
150
|
+
"summary",
|
|
151
|
+
"sup",
|
|
152
|
+
"table",
|
|
153
|
+
"tbody",
|
|
154
|
+
"td",
|
|
155
|
+
"template",
|
|
156
|
+
"textarea",
|
|
157
|
+
"tfoot",
|
|
158
|
+
"th",
|
|
159
|
+
"thead",
|
|
160
|
+
"time",
|
|
161
|
+
"title",
|
|
162
|
+
"tr",
|
|
163
|
+
"track",
|
|
164
|
+
"u",
|
|
165
|
+
"ul",
|
|
166
|
+
"var",
|
|
167
|
+
"video",
|
|
168
|
+
"wbr",
|
|
169
|
+
// SVG
|
|
170
|
+
"svg",
|
|
171
|
+
"path",
|
|
172
|
+
"g",
|
|
173
|
+
"rect",
|
|
174
|
+
"circle",
|
|
175
|
+
"ellipse",
|
|
176
|
+
"line",
|
|
177
|
+
"polyline",
|
|
178
|
+
"polygon",
|
|
179
|
+
"text",
|
|
180
|
+
"tspan",
|
|
181
|
+
"use",
|
|
182
|
+
"defs",
|
|
183
|
+
"linearGradient",
|
|
184
|
+
"radialGradient",
|
|
185
|
+
"stop",
|
|
186
|
+
"mask",
|
|
187
|
+
"pattern",
|
|
188
|
+
"marker",
|
|
189
|
+
"view",
|
|
190
|
+
"symbol",
|
|
191
|
+
"clipPath",
|
|
192
|
+
// SVG — the FILTER module and the rest of the element vocabulary (F30, 2026-10-12).
|
|
193
|
+
//
|
|
194
|
+
// Kept in LOCKSTEP with the vue lane's list: script/m14/verify-cross-language-consistency.ts
|
|
195
|
+
// extracts both sets and both against the oracle's ORACLE_HTML_TAGS, and fails if they
|
|
196
|
+
// differ, because whether a tag is markup or a component is a fact about the tag, not
|
|
197
|
+
// about the language it is written in. NO DOUBLE QUOTES OR CLOSING-BRACKET-PAREN PAIR in
|
|
198
|
+
// this comment: the parity gate slices this literal to the first such pair and treats
|
|
199
|
+
// every quoted string in the slice as a tag.
|
|
200
|
+
//
|
|
201
|
+
// Measured on THIS lane's corpora before assuming, and the number is zero: 0 of the
|
|
202
|
+
// 6,436 .svelte files on disk (svelte 4,561 + shadcn-svelte 1,878) contain a filter
|
|
203
|
+
// primitive or a `<filter>` element at all. So this lane inherits the vocabulary from
|
|
204
|
+
// the parity rule rather than from a defect anyone can see here today — the live
|
|
205
|
+
// measurement is the vue lane's, and this lane's obligation is that it USES the set,
|
|
206
|
+
// which is what svelte/svg-filter-builtin-tags pins and
|
|
207
|
+
// svelte/reemit-svg-filter-tags regresses.
|
|
208
|
+
// The defect it fixes was measured on the vue corpus (<filter>
|
|
209
|
+
// and every filter PRIMITIVE published `instantiates` edges to targets no file declares:
|
|
210
|
+
// 377 edges, 6 primitives); this lane has the same gap by construction, and the svelte
|
|
211
|
+
// fixture set carries the twin check through the parity gate rather than a second corpus
|
|
212
|
+
// measurement it has no draw for. An unknown namespace still fails toward VISIBILITY (see
|
|
213
|
+
// BUILTIN_NAMESPACES); an element the SVG spec DEFINES is markup.
|
|
214
|
+
"filter",
|
|
215
|
+
"feBlend",
|
|
216
|
+
"feColorMatrix",
|
|
217
|
+
"feComponentTransfer",
|
|
218
|
+
"feComposite",
|
|
219
|
+
"feConvolveMatrix",
|
|
220
|
+
"feDiffuseLighting",
|
|
221
|
+
"feDisplacementMap",
|
|
222
|
+
"feDistantLight",
|
|
223
|
+
"feDropShadow",
|
|
224
|
+
"feFlood",
|
|
225
|
+
"feFuncA",
|
|
226
|
+
"feFuncB",
|
|
227
|
+
"feFuncG",
|
|
228
|
+
"feFuncR",
|
|
229
|
+
"feGaussianBlur",
|
|
230
|
+
"feImage",
|
|
231
|
+
"feMerge",
|
|
232
|
+
"feMergeNode",
|
|
233
|
+
"feMorphology",
|
|
234
|
+
"feOffset",
|
|
235
|
+
"fePointLight",
|
|
236
|
+
"feSpecularLighting",
|
|
237
|
+
"feSpotLight",
|
|
238
|
+
"feTile",
|
|
239
|
+
"feTurbulence",
|
|
240
|
+
// the rest of SVG 2's element list, plus the SVG 1.1 elements browsers still parse —
|
|
241
|
+
// the SPEC's vocabulary, counted separately from the measured block above.
|
|
242
|
+
"switch",
|
|
243
|
+
"foreignObject",
|
|
244
|
+
"desc",
|
|
245
|
+
"metadata",
|
|
246
|
+
"textPath",
|
|
247
|
+
"tref",
|
|
248
|
+
"mpath",
|
|
249
|
+
"animate",
|
|
250
|
+
"animateMotion",
|
|
251
|
+
"animateTransform",
|
|
252
|
+
"set",
|
|
253
|
+
"discard",
|
|
254
|
+
"solidColor",
|
|
255
|
+
"mesh",
|
|
256
|
+
"meshgradient",
|
|
257
|
+
"meshpatch",
|
|
258
|
+
"meshrow",
|
|
259
|
+
"hatch",
|
|
260
|
+
"hatchpath",
|
|
261
|
+
"altGlyph",
|
|
262
|
+
"altGlyphDef",
|
|
263
|
+
"altGlyphItem",
|
|
264
|
+
"glyphRef",
|
|
265
|
+
"animateColor",
|
|
266
|
+
"cursor",
|
|
267
|
+
"color-profile",
|
|
268
|
+
"font",
|
|
269
|
+
"font-face",
|
|
270
|
+
"font-face-format",
|
|
271
|
+
"font-face-name",
|
|
272
|
+
"font-face-src",
|
|
273
|
+
"font-face-uri",
|
|
274
|
+
"glyph",
|
|
275
|
+
"hkern",
|
|
276
|
+
"missing-glyph",
|
|
277
|
+
"vkern"
|
|
278
|
+
]);
|
|
279
|
+
var SVELTE_BUILTIN_TAGS = /* @__PURE__ */ new Set([
|
|
280
|
+
"slot",
|
|
281
|
+
"svelte:component",
|
|
282
|
+
"svelte:self",
|
|
283
|
+
"svelte:element",
|
|
284
|
+
"svelte:window",
|
|
285
|
+
"svelte:document",
|
|
286
|
+
"svelte:body",
|
|
287
|
+
"svelte:head",
|
|
288
|
+
"svelte:options",
|
|
289
|
+
"svelte:fragment",
|
|
290
|
+
"svelte:boundary"
|
|
291
|
+
]);
|
|
292
|
+
var SVELTE_MEMBER_READ_DIRECTIVES = /* @__PURE__ */ new Set(["bind", "class", "style"]);
|
|
293
|
+
var SVELTE_INVOKED_DIRECTIVES = /* @__PURE__ */ new Set(["use", "transition", "in", "out", "animate"]);
|
|
294
|
+
var SVELTE_DECLARING_DIRECTIVES = /* @__PURE__ */ new Set(["let"]);
|
|
295
|
+
var SVELTE_RUNES = /* @__PURE__ */ new Set([
|
|
296
|
+
"$state",
|
|
297
|
+
"$state.raw",
|
|
298
|
+
"$state.snapshot",
|
|
299
|
+
"$state.eager",
|
|
300
|
+
"$derived",
|
|
301
|
+
"$derived.by",
|
|
302
|
+
"$effect",
|
|
303
|
+
"$effect.pre",
|
|
304
|
+
"$effect.tracking",
|
|
305
|
+
"$effect.root",
|
|
306
|
+
"$effect.untracked",
|
|
307
|
+
"$effect.pending",
|
|
308
|
+
"$props",
|
|
309
|
+
"$props.id",
|
|
310
|
+
"$props.is_safe",
|
|
311
|
+
"$bindable",
|
|
312
|
+
"$host",
|
|
313
|
+
"$inspect",
|
|
314
|
+
"$inspect.trace",
|
|
315
|
+
"$render",
|
|
316
|
+
"$props"
|
|
317
|
+
]);
|
|
318
|
+
var NON_CALLABLE_LITERALS = /* @__PURE__ */ new Set(["null", "undefined", "true", "false", "NaN", "Infinity", "this"]);
|
|
319
|
+
function isIdentifier(text) {
|
|
320
|
+
if (text.length === 0) return false;
|
|
321
|
+
const first = text[0];
|
|
322
|
+
if (!(first >= "a" && first <= "z" || first >= "A" && first <= "Z" || first === "_" || first === "$")) return false;
|
|
323
|
+
for (let i = 1; i < text.length; i++) {
|
|
324
|
+
const c = text[i];
|
|
325
|
+
if (!(c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "$")) return false;
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
function containsWhitespace(text) {
|
|
330
|
+
for (let i = 0; i < text.length; i++) {
|
|
331
|
+
const c = text.charCodeAt(i);
|
|
332
|
+
if (c === 9 || c === 10 || c === 11 || c === 12 || c === 13 || c === 32 || c === 160 || c === 5760) return true;
|
|
333
|
+
if (c >= 8192 && c <= 8202) return true;
|
|
334
|
+
if (c === 8232 || c === 8233 || c === 8239 || c === 8287 || c === 12288 || c === 65279) return true;
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
function isUriSchemeSpecifier(spec) {
|
|
339
|
+
const colon = spec.indexOf(":");
|
|
340
|
+
if (colon <= 0) return false;
|
|
341
|
+
for (let i = 0; i < colon; i++) {
|
|
342
|
+
const c = spec.charCodeAt(i);
|
|
343
|
+
const letter = c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
344
|
+
if (i === 0) {
|
|
345
|
+
if (!letter) return false;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const digit = c >= 48 && c <= 57;
|
|
349
|
+
if (!letter && !digit && c !== 43 && c !== 46 && c !== 45) return false;
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
function isDottedIdentifierChain(text) {
|
|
354
|
+
let start = 0;
|
|
355
|
+
while (true) {
|
|
356
|
+
const dot = text.indexOf(".", start);
|
|
357
|
+
const end = dot < 0 ? text.length : dot;
|
|
358
|
+
if (!isIdentifier(text.slice(start, end))) return false;
|
|
359
|
+
if (dot < 0) return true;
|
|
360
|
+
start = dot + 1;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function splitAwaitHead(text) {
|
|
364
|
+
const isSpace = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
365
|
+
let i = 0;
|
|
366
|
+
while (i < text.length) {
|
|
367
|
+
if (!isSpace(text[i])) {
|
|
368
|
+
i++;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const wsStart = i;
|
|
372
|
+
while (i < text.length && isSpace(text[i])) i++;
|
|
373
|
+
const kwStart = i;
|
|
374
|
+
let kwLen = 0;
|
|
375
|
+
if (text.startsWith("then", i)) kwLen = 4;
|
|
376
|
+
else if (text.startsWith("as", i)) kwLen = 2;
|
|
377
|
+
if (kwLen > 0 && i + kwLen < text.length && isSpace(text[i + kwLen])) {
|
|
378
|
+
let after = i + kwLen;
|
|
379
|
+
while (after < text.length && isSpace(text[after])) after++;
|
|
380
|
+
return { head: text.slice(0, wsStart), matchLength: after };
|
|
381
|
+
}
|
|
382
|
+
i = kwStart + 1;
|
|
383
|
+
}
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
function keyCarriesALine(key) {
|
|
387
|
+
return key.toLowerCase().indexOf("line") >= 0;
|
|
388
|
+
}
|
|
389
|
+
function isOnEventAttrName(name) {
|
|
390
|
+
if (name.length < 3 || name[0] !== "o" || name[1] !== "n") return false;
|
|
391
|
+
for (let i = 2; i < name.length; i++) {
|
|
392
|
+
const c = name[i];
|
|
393
|
+
if (c < "a" || c > "z") return false;
|
|
394
|
+
}
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
var RUNE_LEAF_SEGMENTS = /* @__PURE__ */ new Set([
|
|
398
|
+
"state",
|
|
399
|
+
"raw",
|
|
400
|
+
"snapshot",
|
|
401
|
+
"derived",
|
|
402
|
+
"by",
|
|
403
|
+
"effect",
|
|
404
|
+
"pre",
|
|
405
|
+
"tracking",
|
|
406
|
+
"root",
|
|
407
|
+
"untracked",
|
|
408
|
+
"props",
|
|
409
|
+
"id",
|
|
410
|
+
"is_safe",
|
|
411
|
+
"bindable",
|
|
412
|
+
"host",
|
|
413
|
+
"inspect",
|
|
414
|
+
"trace",
|
|
415
|
+
"render"
|
|
416
|
+
]);
|
|
417
|
+
var SVELTE_INVOKING_RUNES = /* @__PURE__ */ new Set([
|
|
418
|
+
"$effect",
|
|
419
|
+
"$effect.pre",
|
|
420
|
+
"$effect.root",
|
|
421
|
+
"$effect.untracked",
|
|
422
|
+
"$derived.by",
|
|
423
|
+
"$host"
|
|
424
|
+
]);
|
|
425
|
+
function isInvokingRune(calleeExpression) {
|
|
426
|
+
return SVELTE_INVOKING_RUNES.has(calleeExpression);
|
|
427
|
+
}
|
|
428
|
+
function isRuneCallee(calleeExpression) {
|
|
429
|
+
if (!calleeExpression.startsWith("$")) return false;
|
|
430
|
+
if (SVELTE_RUNES.has(calleeExpression)) return true;
|
|
431
|
+
const leaf = calleeExpression.split(".").pop() ?? "";
|
|
432
|
+
return RUNE_LEAF_SEGMENTS.has(leaf);
|
|
433
|
+
}
|
|
434
|
+
var DELEGATE_FILE_ACTOR = "(file)";
|
|
435
|
+
var RealSvelteAnalyzer = class _RealSvelteAnalyzer {
|
|
436
|
+
analyze(rootNode, content, filePath, context) {
|
|
437
|
+
const symbols = [];
|
|
438
|
+
const relationships = [];
|
|
439
|
+
const imports = [];
|
|
440
|
+
const exports = [];
|
|
441
|
+
const errors = [];
|
|
442
|
+
const patterns = {};
|
|
443
|
+
const moduleName = path.basename(filePath, path.extname(filePath));
|
|
444
|
+
patterns["Svelte SFC"] = true;
|
|
445
|
+
if (rootNode.hasError) {
|
|
446
|
+
const errStack = [rootNode];
|
|
447
|
+
while (errStack.length > 0) {
|
|
448
|
+
const n = errStack.pop();
|
|
449
|
+
if (n.type === "ERROR" || n.isMissing) {
|
|
450
|
+
errors.push({
|
|
451
|
+
message: `Syntax ${n.type} detected. The Brain Engine is bypassing this broken node.`,
|
|
452
|
+
range: this.nodeToRange(n),
|
|
453
|
+
severity: "warning"
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
for (let i = n.childCount - 1; i >= 0; i--) {
|
|
457
|
+
const child = n.child(i);
|
|
458
|
+
if (child) errStack.push(child);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
symbols.push({
|
|
463
|
+
name: moduleName,
|
|
464
|
+
kind: ASTEngine.SymbolKind.Class,
|
|
465
|
+
range: this.nodeToRange(rootNode),
|
|
466
|
+
scope: "",
|
|
467
|
+
visibility: "public",
|
|
468
|
+
metadata: { isSvelteComponent: true, isSfc: true }
|
|
469
|
+
});
|
|
470
|
+
const seenUsages = /* @__PURE__ */ new Set();
|
|
471
|
+
let templateDecisions = 0;
|
|
472
|
+
const templateExprNames = /* @__PURE__ */ new Map();
|
|
473
|
+
const templateReadSites = /* @__PURE__ */ new Map();
|
|
474
|
+
const directiveCalls = [];
|
|
475
|
+
const seenDirectiveCalls = /* @__PURE__ */ new Set();
|
|
476
|
+
const carrierReadKeys = /* @__PURE__ */ new Set();
|
|
477
|
+
const thisTargets = /* @__PURE__ */ new Map();
|
|
478
|
+
const thisTargetSites = /* @__PURE__ */ new Map();
|
|
479
|
+
const dottedTags = /* @__PURE__ */ new Map();
|
|
480
|
+
const templateStack = [
|
|
481
|
+
{ node: rootNode, scope: { shadowed: /* @__PURE__ */ new Set() } }
|
|
482
|
+
];
|
|
483
|
+
while (templateStack.length > 0) {
|
|
484
|
+
const { node: n, scope } = templateStack.pop();
|
|
485
|
+
const t = n.type;
|
|
486
|
+
if (t === "script_element" || t === "style_element") continue;
|
|
487
|
+
const shadowed = scope.shadowed;
|
|
488
|
+
const opensScope = t === "each_statement" || t === "await_statement" || t === "if_statement" || t === "else_if_block" || t === "key_statement" || t === "snippet_statement";
|
|
489
|
+
const childScope = opensScope ? { shadowed: new Set(shadowed) } : scope;
|
|
490
|
+
const childShadowed = childScope.shadowed;
|
|
491
|
+
if (t === "snippet_statement") {
|
|
492
|
+
const nm = this.findDescendant(n, (c) => c.type === "snippet_name");
|
|
493
|
+
if (nm && !seenUsages.has("snippet:" + nm.text)) {
|
|
494
|
+
seenUsages.add("snippet:" + nm.text);
|
|
495
|
+
symbols.push({
|
|
496
|
+
name: nm.text,
|
|
497
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
498
|
+
range: this.nodeToRange(n),
|
|
499
|
+
scope: moduleName,
|
|
500
|
+
ownerName: moduleName,
|
|
501
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
502
|
+
visibility: "public",
|
|
503
|
+
metadata: { isSnippet: true }
|
|
504
|
+
});
|
|
505
|
+
this.collectSnippetParams(n, templateExprNames, childShadowed, templateReadSites);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (t === "render_tag") {
|
|
509
|
+
const text = n.text.replace("{@render", "").trim();
|
|
510
|
+
let end = 0;
|
|
511
|
+
while (end < text.length && isIdentifier(text.slice(0, end + 1))) end++;
|
|
512
|
+
let targetName = text.slice(0, end);
|
|
513
|
+
const afterLead = text.slice(end).trimStart();
|
|
514
|
+
if (targetName && (afterLead.startsWith(".") || afterLead.startsWith("?."))) {
|
|
515
|
+
const leaf = this.dottedRenderLeaf(text);
|
|
516
|
+
if (leaf) targetName = leaf;
|
|
517
|
+
}
|
|
518
|
+
const target = targetName ? { text: targetName } : void 0;
|
|
519
|
+
if (target && isIdentifier(target.text)) {
|
|
520
|
+
relationships.push({
|
|
521
|
+
source: moduleName,
|
|
522
|
+
target: target.text,
|
|
523
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
524
|
+
confidence: 0.9,
|
|
525
|
+
// M59 (#825): the `{@render ...}` tag the name is written in.
|
|
526
|
+
line_number: n.startPosition.row + 1,
|
|
527
|
+
metadata: { context: "Svelte {@render} tag" }
|
|
528
|
+
});
|
|
529
|
+
const open = text.indexOf("(");
|
|
530
|
+
if (open >= 0) {
|
|
531
|
+
const args = text.slice(open + 1, text.endsWith(")") ? text.length - 1 : text.length);
|
|
532
|
+
if (args.trim()) {
|
|
533
|
+
this.collectIdentifiersFromText(
|
|
534
|
+
args,
|
|
535
|
+
templateExprNames,
|
|
536
|
+
childShadowed,
|
|
537
|
+
n.startPosition.row + 1 + _RealSvelteAnalyzer.linesBefore(text, open + 1),
|
|
538
|
+
templateReadSites
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (t === "if_statement" || t === "else_if_block" || t === "each_statement" || t === "await_statement" || t === "key_statement") {
|
|
545
|
+
templateDecisions++;
|
|
546
|
+
}
|
|
547
|
+
if (t === "each_statement") {
|
|
548
|
+
this.collectEachExpression(n, templateExprNames, childShadowed, templateReadSites);
|
|
549
|
+
}
|
|
550
|
+
if (t === "await_statement") {
|
|
551
|
+
this.collectAwaitExpression(n, templateExprNames, childShadowed, templateReadSites);
|
|
552
|
+
}
|
|
553
|
+
if (t === "if_statement" || t === "else_if_block" || t === "key_statement") {
|
|
554
|
+
this.collectConditionExpression(n, templateExprNames, childShadowed, templateReadSites);
|
|
555
|
+
}
|
|
556
|
+
if (t === "expression") {
|
|
557
|
+
this.collectExpressionIdentifiers(n, templateExprNames, childShadowed, templateReadSites);
|
|
558
|
+
}
|
|
559
|
+
if (t === "html_tag" || t === "const_tag" || t === "debug_tag") {
|
|
560
|
+
this.collectTagExpressionIdentifiers(n, templateExprNames, childShadowed, templateReadSites);
|
|
561
|
+
if (t === "const_tag") {
|
|
562
|
+
const body = this.expressionText(n);
|
|
563
|
+
const eq = this.topLevelEq(body);
|
|
564
|
+
if (eq >= 0) this.collectShadowNames(body.slice(0, eq), childShadowed);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (t === "element" || t === "self_closing_tag") {
|
|
568
|
+
const tag = this.elementTagName(n);
|
|
569
|
+
if (tag && tag.startsWith("svelte:") && !SVELTE_BUILTIN_TAGS.has(tag)) {
|
|
570
|
+
} else if (tag && tag.includes(".") && !seenUsages.has(tag)) {
|
|
571
|
+
seenUsages.add(tag);
|
|
572
|
+
const tagNode = this.elementTagNameNode(n);
|
|
573
|
+
const nsSegment = tag.slice(0, tag.indexOf("."));
|
|
574
|
+
dottedTags.set(tag, {
|
|
575
|
+
line: tagNode ? tagNode.startPosition.row + 1 : 0,
|
|
576
|
+
templateLocal: shadowed.has(nsSegment)
|
|
577
|
+
});
|
|
578
|
+
} else if (tag && !HTML_BUILTIN_TAGS.has(tag) && !SVELTE_BUILTIN_TAGS.has(tag) && !seenUsages.has(tag)) {
|
|
579
|
+
seenUsages.add(tag);
|
|
580
|
+
const tagNode = this.elementTagNameNode(n);
|
|
581
|
+
relationships.push({
|
|
582
|
+
source: moduleName,
|
|
583
|
+
target: tag,
|
|
584
|
+
// RENDERS, NOT INSTANTIATES (2026-10-23, extended vocabulary). A component tag creates
|
|
585
|
+
// an element the HOST RENDERER mounts; the source contains no `new`, so an
|
|
586
|
+
// `instantiates` edge claimed an allocation site the file does not have (types.ts'
|
|
587
|
+
// Renders contract). `instantiates` keeps its OWN sites: the JS/TS delegate's script
|
|
588
|
+
// `new` rows, copied by the seam below — the two relations stay distinct.
|
|
589
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
590
|
+
confidence: 0.95,
|
|
591
|
+
// M59 (#825): the `<Child/>` tag as written.
|
|
592
|
+
...tagNode ? { line_number: tagNode.startPosition.row + 1 } : {},
|
|
593
|
+
metadata: { isComponentUsage: true }
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
if (tag === "svelte:component" || tag === "svelte:element") {
|
|
597
|
+
const thisTag = this.findChildByType(n, "start_tag") || this.findChildByType(n, "self_closing_tag");
|
|
598
|
+
if (thisTag) {
|
|
599
|
+
for (let i = 0; i < thisTag.namedChildCount; i++) {
|
|
600
|
+
const attr = thisTag.namedChild(i);
|
|
601
|
+
if (!attr || attr.type !== "attribute" || this.attributeName(attr) !== "this") continue;
|
|
602
|
+
const valueNode = this.findChildByType(attr, "expression");
|
|
603
|
+
if (!valueNode) continue;
|
|
604
|
+
let vt = valueNode.text.trim();
|
|
605
|
+
if (vt.startsWith("{") && vt.endsWith("}")) vt = vt.slice(1, -1).trim();
|
|
606
|
+
if (vt && isIdentifier(vt)) {
|
|
607
|
+
const siteLine = valueNode.startPosition.row + 1;
|
|
608
|
+
thisTargets.set(vt, siteLine);
|
|
609
|
+
const prior = thisTargetSites.get(vt);
|
|
610
|
+
if (prior) prior.push(siteLine);
|
|
611
|
+
else thisTargetSites.set(vt, [siteLine]);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const startTag2 = this.findChildByType(n, "start_tag") || this.findChildByType(n, "self_closing_tag");
|
|
617
|
+
if (startTag2) {
|
|
618
|
+
for (let i = 0; i < startTag2.namedChildCount; i++) {
|
|
619
|
+
const attr = startTag2.namedChild(i);
|
|
620
|
+
if (!attr || attr.type !== "attribute") continue;
|
|
621
|
+
const attrName = this.attributeName(attr);
|
|
622
|
+
if (!attrName || attrName.startsWith("on:")) continue;
|
|
623
|
+
if (this.findChildByType(attr, "expression")) continue;
|
|
624
|
+
if (attrName.startsWith("{") && attrName.endsWith("}")) {
|
|
625
|
+
const inner = attrName.slice(1, -1).trim();
|
|
626
|
+
const expr = inner.startsWith("...") ? inner.slice(3) : inner;
|
|
627
|
+
this.collectIdentifiersFromText(expr, templateExprNames, childShadowed, attr.startPosition.row + 1, templateReadSites);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
for (let i = 0; startTag2 && i < startTag2.namedChildCount; i++) {
|
|
632
|
+
const attr = startTag2.namedChild(i);
|
|
633
|
+
if (!attr || attr.type !== "attribute") continue;
|
|
634
|
+
const attrName = this.attributeName(attr);
|
|
635
|
+
if (!attrName) continue;
|
|
636
|
+
const colon = attrName.indexOf(":");
|
|
637
|
+
if (colon <= 0) continue;
|
|
638
|
+
const prefix = attrName.slice(0, colon);
|
|
639
|
+
const rest = attrName.slice(colon + 1);
|
|
640
|
+
if (!rest) continue;
|
|
641
|
+
if (attrName.startsWith("on:")) continue;
|
|
642
|
+
if (SVELTE_DECLARING_DIRECTIVES.has(prefix)) continue;
|
|
643
|
+
const exprNode = this.findChildByType(attr, "expression");
|
|
644
|
+
if (SVELTE_MEMBER_READ_DIRECTIVES.has(prefix)) {
|
|
645
|
+
const text = exprNode ? this.expressionText(exprNode) : "";
|
|
646
|
+
if (!text) continue;
|
|
647
|
+
const base = exprNode ? exprNode.startPosition.row + 1 : attr.startPosition.row + 1;
|
|
648
|
+
const before = new Set(templateExprNames.keys());
|
|
649
|
+
this.collectIdentifiersFromText(text, templateExprNames, childShadowed, base, templateReadSites);
|
|
650
|
+
for (const nm of templateExprNames.keys()) if (!before.has(nm)) carrierReadKeys.add(`${nm}@${templateExprNames.get(nm)}`);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (SVELTE_INVOKED_DIRECTIVES.has(prefix)) {
|
|
654
|
+
if (rest === moduleName) continue;
|
|
655
|
+
const leaf = rest.includes(".") ? rest.slice(rest.lastIndexOf(".") + 1) : rest;
|
|
656
|
+
if (!leaf) continue;
|
|
657
|
+
if (NON_CALLABLE_LITERALS.has(leaf)) continue;
|
|
658
|
+
seenDirectiveCalls.add(`${leaf}@${attr.startPosition.row + 1}`);
|
|
659
|
+
directiveCalls.push({
|
|
660
|
+
leaf,
|
|
661
|
+
line: attr.startPosition.row + 1,
|
|
662
|
+
context: `Svelte ${prefix} directive ${attrName}`
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const startTag = this.findChildByType(n, "start_tag") || this.findChildByType(n, "self_closing_tag");
|
|
667
|
+
if (startTag) {
|
|
668
|
+
for (let i = 0; i < startTag.namedChildCount; i++) {
|
|
669
|
+
const attr = startTag.namedChild(i);
|
|
670
|
+
if (!attr || attr.type !== "attribute") continue;
|
|
671
|
+
const attrName = this.attributeName(attr);
|
|
672
|
+
if (!attrName) continue;
|
|
673
|
+
const isOnDirective = attrName.startsWith("on:");
|
|
674
|
+
const isOnclickAttr = !isOnDirective && isOnEventAttrName(attrName) && attrName.length > 4 && !!tag && tag[0] === tag[0].toLowerCase() && !tag.includes(".") && (!tag.includes(":") || tag.startsWith("svelte:"));
|
|
675
|
+
if (!isOnDirective && !isOnclickAttr) continue;
|
|
676
|
+
const handlerSite = this.onHandlerCall(attr);
|
|
677
|
+
const handler = handlerSite?.name;
|
|
678
|
+
if (handler && handler !== moduleName) {
|
|
679
|
+
relationships.push({
|
|
680
|
+
source: moduleName,
|
|
681
|
+
target: handler,
|
|
682
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
683
|
+
confidence: 0.85,
|
|
684
|
+
...typeof handlerSite.line === "number" ? { line_number: handlerSite.line } : {},
|
|
685
|
+
metadata: {
|
|
686
|
+
context: isOnDirective ? `Svelte event directive ${attrName}` : `Svelte event attribute ${attrName}`
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
for (let i = 0; i < startTag.childCount; i++) {
|
|
692
|
+
const directive = startTag.child(i);
|
|
693
|
+
if (!directive || directive.type !== "ERROR") continue;
|
|
694
|
+
const kids = [];
|
|
695
|
+
for (let j = 0; j < directive.childCount; j++) {
|
|
696
|
+
const c = directive.child(j);
|
|
697
|
+
if (c) kids.push(c);
|
|
698
|
+
}
|
|
699
|
+
const attachIdx = kids.findIndex((k) => k.type === "ERROR" && k.text === "attach");
|
|
700
|
+
if (attachIdx < 0) continue;
|
|
701
|
+
const nameTok = kids[attachIdx + 1];
|
|
702
|
+
if (!nameTok || !isIdentifier(nameTok.text)) continue;
|
|
703
|
+
const afterText = (kids[attachIdx + 2]?.text ?? "}").trim();
|
|
704
|
+
if (afterText !== "}" && afterText !== "(") continue;
|
|
705
|
+
const attachHandler = nameTok.text;
|
|
706
|
+
if (attachHandler === moduleName) continue;
|
|
707
|
+
relationships.push({
|
|
708
|
+
source: moduleName,
|
|
709
|
+
target: attachHandler,
|
|
710
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
711
|
+
confidence: 0.8,
|
|
712
|
+
line_number: nameTok.startPosition.row + 1,
|
|
713
|
+
metadata: { context: "Svelte attach directive" }
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
719
|
+
const child = n.namedChild(i);
|
|
720
|
+
if (child) templateStack.push({ node: child, scope: childScope });
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const scriptTrees = context?.sfcScriptTrees ?? [];
|
|
724
|
+
const scriptLines = context?.sfcScriptLines ?? [];
|
|
725
|
+
const scriptCols = context?.sfcScriptCols ?? [];
|
|
726
|
+
const scriptStartIdx = context?.sfcScriptStartIndex ?? [];
|
|
727
|
+
let scriptDecisions = 0;
|
|
728
|
+
for (let bi = 0; bi < scriptTrees.length; bi++) {
|
|
729
|
+
const scriptTree = scriptTrees[bi];
|
|
730
|
+
const scriptStartLine = scriptLines[bi];
|
|
731
|
+
const scriptStartColumn = scriptCols[bi];
|
|
732
|
+
const scriptBlockIndex = scriptStartIdx[bi] ?? 0;
|
|
733
|
+
if (!scriptTree || scriptStartLine === void 0) continue;
|
|
734
|
+
const scriptContent = scriptTree.rootNode?.text ?? "";
|
|
735
|
+
const lineOffset = scriptStartLine - 1;
|
|
736
|
+
const colOffset = scriptStartColumn ?? 0;
|
|
737
|
+
const isTs = context?.sfcScriptLangs?.[bi] === "ts" || context?.sfcScriptLangs?.[bi] === "tsx";
|
|
738
|
+
let sub;
|
|
739
|
+
try {
|
|
740
|
+
sub = isTs ? new RealTypeScriptAnalyzer().analyze(scriptTree.rootNode, scriptContent, filePath) : new RealJavaScriptAnalyzer().analyze(scriptTree.rootNode, scriptContent, filePath, context);
|
|
741
|
+
} catch (e) {
|
|
742
|
+
sub = { symbols: [], relationships: [], imports: [], exports: [], errors: [], metadata: {} };
|
|
743
|
+
errors.push({
|
|
744
|
+
message: `script sub-parse failed: ${e?.message?.slice(0, 120)}`,
|
|
745
|
+
range: { startLine: scriptStartLine, startColumn: 0, endLine: scriptStartLine, endColumn: 0 },
|
|
746
|
+
severity: "warning"
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
this.extractSvelteDeclarations(scriptTree.rootNode, moduleName, symbols, lineOffset, colOffset, scriptBlockIndex);
|
|
750
|
+
this.extractRuneInvocations(scriptTree.rootNode, moduleName, relationships, lineOffset);
|
|
751
|
+
const isModuleBlock = this.isModuleContextBlock(rootNode, bi);
|
|
752
|
+
const runePropNames = /* @__PURE__ */ new Set();
|
|
753
|
+
this.collectPropsDestructuredNames(scriptTree.rootNode, runePropNames);
|
|
754
|
+
const runePropNamesLowered = new Set(scriptContent.length > 0 ? [...runePropNames] : []);
|
|
755
|
+
const reactiveTargetNames = /* @__PURE__ */ new Set();
|
|
756
|
+
this.collectReactiveTargetNames(scriptTree.rootNode, reactiveTargetNames);
|
|
757
|
+
for (const s of sub.symbols ?? []) {
|
|
758
|
+
if (runePropNamesLowered.has(s.name ?? "")) continue;
|
|
759
|
+
if (reactiveTargetNames.has(s.name ?? "")) continue;
|
|
760
|
+
const firstLine = s.range.startLine === 1;
|
|
761
|
+
s.range.startLine += lineOffset;
|
|
762
|
+
s.range.endLine += lineOffset;
|
|
763
|
+
if (firstLine && colOffset) {
|
|
764
|
+
s.range.startColumn += colOffset;
|
|
765
|
+
if (s.range.endLine === s.range.startLine) s.range.endColumn += colOffset;
|
|
766
|
+
}
|
|
767
|
+
if (scriptBlockIndex && s.range.startIndex !== void 0) s.range.startIndex += scriptBlockIndex;
|
|
768
|
+
if (scriptBlockIndex && s.range.endIndex !== void 0) s.range.endIndex += scriptBlockIndex;
|
|
769
|
+
shiftMetadataLineMirrors(s.metadata, lineOffset);
|
|
770
|
+
if (!isModuleBlock && this.isExported(sub.exports ?? [], s.name)) {
|
|
771
|
+
const wasVariable = s.kind === ASTEngine.SymbolKind.Variable;
|
|
772
|
+
if (wasVariable) {
|
|
773
|
+
s.kind = ASTEngine.SymbolKind.Property;
|
|
774
|
+
s.metadata = { ...s.metadata ?? {}, isSvelteProp: true };
|
|
775
|
+
}
|
|
776
|
+
s.ownerName = moduleName;
|
|
777
|
+
s.ownerKind = ASTEngine.SymbolKind.Class;
|
|
778
|
+
s.scope = moduleName;
|
|
779
|
+
}
|
|
780
|
+
symbols.push(s);
|
|
781
|
+
}
|
|
782
|
+
const moduleExportNames = new Set(
|
|
783
|
+
isModuleBlock ? (sub.exports ?? []).map((e) => e.name) : []
|
|
784
|
+
);
|
|
785
|
+
const delegateBases = /* @__PURE__ */ new Map();
|
|
786
|
+
for (const rb of sub.relationships ?? []) {
|
|
787
|
+
if (rb?.type !== ASTEngine.RelationshipType.Extends) continue;
|
|
788
|
+
if (typeof rb.source === "string" && rb.source !== "" && typeof rb.target === "string" && rb.target !== "") {
|
|
789
|
+
delegateBases.set(rb.source, rb.target);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
for (const r of sub.relationships ?? []) {
|
|
793
|
+
const tgt = r.target ?? "";
|
|
794
|
+
if (SVELTE_RUNES.has(tgt)) continue;
|
|
795
|
+
if (r.type === ASTEngine.RelationshipType.Contains && isModuleBlock && moduleExportNames.has(tgt)) continue;
|
|
796
|
+
const callee = r.metadata?.calleeExpression ?? "";
|
|
797
|
+
if (isRuneCallee(callee) || r.source === "(script)" && SVELTE_RUNES.has(tgt)) continue;
|
|
798
|
+
if (typeof r.line_number === "number") r.line_number += lineOffset;
|
|
799
|
+
shiftMetadataLineMirrors(r.metadata, lineOffset);
|
|
800
|
+
if (r.source === DELEGATE_FILE_ACTOR) r.source = moduleName;
|
|
801
|
+
if (r.type === ASTEngine.RelationshipType.Imports) {
|
|
802
|
+
const mod = r.metadata?.module;
|
|
803
|
+
if (typeof mod === "string" && mod !== "" && typeof r.target === "string" && r.target !== "" && !r.target.includes(":") && !r.target.includes("/")) {
|
|
804
|
+
r.target = `${mod}:${r.target}`;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (r.type === ASTEngine.RelationshipType.Overrides) {
|
|
808
|
+
if (typeof r.source === "string" && typeof r.target === "string" && r.target !== "" && !r.target.includes(".")) {
|
|
809
|
+
const owner = r.source.slice(0, r.source.lastIndexOf("."));
|
|
810
|
+
const base = owner === "" ? void 0 : delegateBases.get(owner);
|
|
811
|
+
if (base) r.target = `${base}.${r.target}`;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
r.metadata = { ...r.metadata ?? {}, sfcDelegated: true, sfcLineOffset: lineOffset };
|
|
815
|
+
relationships.push(r);
|
|
816
|
+
}
|
|
817
|
+
for (const i of sub.imports ?? []) {
|
|
818
|
+
if (i.range) {
|
|
819
|
+
const firstLine = i.range.startLine === 1;
|
|
820
|
+
i.range.startLine += lineOffset;
|
|
821
|
+
i.range.endLine += lineOffset;
|
|
822
|
+
if (firstLine && colOffset) {
|
|
823
|
+
i.range.startColumn += colOffset;
|
|
824
|
+
if (i.range.endLine === i.range.startLine) i.range.endColumn += colOffset;
|
|
825
|
+
}
|
|
826
|
+
if (scriptBlockIndex && i.range.startIndex !== void 0) i.range.startIndex += scriptBlockIndex;
|
|
827
|
+
if (scriptBlockIndex && i.range.endIndex !== void 0) i.range.endIndex += scriptBlockIndex;
|
|
828
|
+
}
|
|
829
|
+
imports.push(i);
|
|
830
|
+
}
|
|
831
|
+
if (isModuleBlock) {
|
|
832
|
+
for (const e of sub.exports ?? []) {
|
|
833
|
+
const r = e.range;
|
|
834
|
+
if (r) {
|
|
835
|
+
const firstLine = r.startLine === 1;
|
|
836
|
+
r.startLine += lineOffset;
|
|
837
|
+
r.endLine += lineOffset;
|
|
838
|
+
if (firstLine && colOffset) {
|
|
839
|
+
r.startColumn += colOffset;
|
|
840
|
+
if (r.endLine === r.startLine) r.endColumn += colOffset;
|
|
841
|
+
}
|
|
842
|
+
if (scriptBlockIndex && r.startIndex !== void 0) r.startIndex += scriptBlockIndex;
|
|
843
|
+
if (scriptBlockIndex && r.endIndex !== void 0) r.endIndex += scriptBlockIndex;
|
|
844
|
+
}
|
|
845
|
+
exports.push(e);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
for (const er of sub.errors ?? []) {
|
|
849
|
+
const r = er.range;
|
|
850
|
+
if (r) {
|
|
851
|
+
const firstLine = r.startLine === 1;
|
|
852
|
+
r.startLine += lineOffset;
|
|
853
|
+
r.endLine += lineOffset;
|
|
854
|
+
if (firstLine && typeof r.startColumn === "number") {
|
|
855
|
+
r.startColumn += colOffset;
|
|
856
|
+
r.endColumn += colOffset;
|
|
857
|
+
}
|
|
858
|
+
if (typeof r.startIndex === "number") {
|
|
859
|
+
r.startIndex += scriptBlockIndex;
|
|
860
|
+
r.endIndex += scriptBlockIndex;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
errors.push(er);
|
|
864
|
+
}
|
|
865
|
+
scriptDecisions += Math.max(0, (sub.metadata?.cyclomaticComplexity ?? 1) - 1);
|
|
866
|
+
}
|
|
867
|
+
if (templateExprNames.size > 0) {
|
|
868
|
+
const memberNames = new Set(symbols.map((s) => s.name));
|
|
869
|
+
const seenUses = /* @__PURE__ */ new Set();
|
|
870
|
+
for (const [name, line] of templateExprNames) {
|
|
871
|
+
let resolved = name;
|
|
872
|
+
if (!memberNames.has(name) && name.startsWith("$") && !name.startsWith("$$") && memberNames.has(name.slice(1))) {
|
|
873
|
+
resolved = name.slice(1);
|
|
874
|
+
}
|
|
875
|
+
if (!memberNames.has(resolved)) continue;
|
|
876
|
+
const key = `${moduleName}:${resolved}`;
|
|
877
|
+
if (seenUses.has(key)) continue;
|
|
878
|
+
seenUses.add(key);
|
|
879
|
+
const sites = templateReadSites.get(name) ?? (resolved === name ? void 0 : templateReadSites.get(resolved));
|
|
880
|
+
relationships.push({
|
|
881
|
+
source: moduleName,
|
|
882
|
+
target: resolved,
|
|
883
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
884
|
+
confidence: 0.95,
|
|
885
|
+
// M59 (#825): the line the identifier is WRITTEN on, recorded where
|
|
886
|
+
// the text was scanned. `0` can only appear if a caller passed no
|
|
887
|
+
// base line for a fragment it could not locate — the oracle counts
|
|
888
|
+
// that as a wrong line rather than a silent pass. This is the LAST site;
|
|
889
|
+
// the whole set is in `templateReadSites` below.
|
|
890
|
+
...line > 0 ? { line_number: line } : {},
|
|
891
|
+
metadata: {
|
|
892
|
+
isTemplateExpression: true,
|
|
893
|
+
// D12: present only for a carrier-sourced read. Absent means the read came
|
|
894
|
+
// from a block construct, a store subscription or a tag expression, so a
|
|
895
|
+
// consumer can tell a two-way binding from an ordinary template read.
|
|
896
|
+
...carrierReadKeys.has(`${resolved}@${line}`) ? { carrierDirective: true } : {},
|
|
897
|
+
// Present only when the member is read on MORE THAN ONE site, so a
|
|
898
|
+
// single-read edge carries no extra key. `templateReadSitesCollapsed`
|
|
899
|
+
// is the machine-readable statement that `line_number` above is one
|
|
900
|
+
// of several and not the only place to look.
|
|
901
|
+
...sites && sites.length > 1 ? { templateReadSites: [...sites], templateReadSitesCollapsed: sites.length } : {}
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (dottedTags.size > 0) {
|
|
907
|
+
const nsBindings = /* @__PURE__ */ new Map();
|
|
908
|
+
for (const imp of imports) {
|
|
909
|
+
for (const b of this.importLocalBindings(imp)) nsBindings.set(b.name, b.module);
|
|
910
|
+
}
|
|
911
|
+
const declaredNames = new Set(symbols.map((s) => s.name));
|
|
912
|
+
for (const [tag, site] of dottedTags) {
|
|
913
|
+
const dot = tag.indexOf(".");
|
|
914
|
+
const ns = tag.slice(0, dot);
|
|
915
|
+
const module = nsBindings.get(ns);
|
|
916
|
+
if (module === void 0 && !site.templateLocal && !declaredNames.has(ns)) continue;
|
|
917
|
+
relationships.push({
|
|
918
|
+
source: moduleName,
|
|
919
|
+
target: tag,
|
|
920
|
+
// RENDERS: the dotted tag is a component render, exactly like the plain tag above.
|
|
921
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
922
|
+
confidence: 0.9,
|
|
923
|
+
...site.line > 0 ? { line_number: site.line } : {},
|
|
924
|
+
metadata: {
|
|
925
|
+
isComponentUsage: true,
|
|
926
|
+
resolution: "namespace-member",
|
|
927
|
+
// The MODULE is deliberately NOT copied onto the edge. It is already in
|
|
928
|
+
// this same result's `imports` records, keyed by `namespaceBinding`, and
|
|
929
|
+
// repeating a module specifier on every edge cost 45 bytes × 7,802 corpus
|
|
930
|
+
// edges: measured +903 KiB (+16.6%) on shadcn-svelte's serialized
|
|
931
|
+
// relationship payload before this was removed, for information the
|
|
932
|
+
// consumer already had. `namespaceBinding` is the key into it.
|
|
933
|
+
...module !== void 0 ? { namespaceBinding: ns } : {},
|
|
934
|
+
...module === void 0 && site.templateLocal ? { namespaceLocal: true } : {},
|
|
935
|
+
...module === void 0 && !site.templateLocal ? { namespaceSymbol: true } : {}
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (thisTargets.size > 0) {
|
|
941
|
+
const memberNames = new Set(symbols.map((s) => s.name));
|
|
942
|
+
const importBindings = /* @__PURE__ */ new Set();
|
|
943
|
+
for (const imp of imports) {
|
|
944
|
+
for (const b of this.importLocalBindings(imp)) importBindings.add(b.name);
|
|
945
|
+
}
|
|
946
|
+
for (const [name, line] of thisTargets) {
|
|
947
|
+
if (memberNames.has(name) || !importBindings.has(name)) continue;
|
|
948
|
+
const sites = thisTargetSites.get(name);
|
|
949
|
+
relationships.push({
|
|
950
|
+
source: moduleName,
|
|
951
|
+
target: name,
|
|
952
|
+
// RENDERS: a `<svelte:component this={X}/>` mounts X — a component render, not a `new`.
|
|
953
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
954
|
+
confidence: 0.95,
|
|
955
|
+
// M59 (#825): the `this={X}` value's line. F22: the LAST site, kept
|
|
956
|
+
// deliberately — see the site map's declaration.
|
|
957
|
+
...line > 0 ? { line_number: line } : {},
|
|
958
|
+
metadata: {
|
|
959
|
+
isComponentUsage: true,
|
|
960
|
+
// F22: the full site set, sorted ascending so two runs agree.
|
|
961
|
+
//
|
|
962
|
+
// EMITTED FOR A SINGLE SITE TOO, and that is a PIN rather than a
|
|
963
|
+
// preference: `svelte/dynamic-target-sites` requires
|
|
964
|
+
// `dynamicTargetLines: [30]` for its one-site `Panel`, while
|
|
965
|
+
// `svelte/multi-site-read-record` requires the read map to be ABSENT
|
|
966
|
+
// (`templateReadSites: null`) for its one-site reads. The two
|
|
967
|
+
// conventions therefore DIVERGE, and the divergence is recorded here
|
|
968
|
+
// rather than smoothed over: unifying them is a payload change on one
|
|
969
|
+
// fixture or the other, not a fix, and it belongs to whichever round
|
|
970
|
+
// decides which of the two shapes a consumer should read.
|
|
971
|
+
...sites && sites.length > 0 ? { dynamicTargetLines: [...sites].sort((a, b) => a - b) } : {}
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
for (const s of symbols) {
|
|
977
|
+
if (s.ownerName) {
|
|
978
|
+
relationships.push({
|
|
979
|
+
source: s.ownerName,
|
|
980
|
+
target: s.name,
|
|
981
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
982
|
+
confidence: 1,
|
|
983
|
+
// M59 (#825): the MEMBER's declaration line (already in SFC
|
|
984
|
+
// coordinates) — the edge names the member.
|
|
985
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
986
|
+
metadata: {}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const architecturalScore = Math.min(100, Math.max(0, 100 - errors.length * 5));
|
|
991
|
+
const detectedPatterns = Object.keys(patterns);
|
|
992
|
+
const frameworkPatterns = detectedPatterns.map((p) => ({
|
|
993
|
+
framework: "Svelte",
|
|
994
|
+
patterns: [{ type: "usage", detected: true, description: p }]
|
|
995
|
+
}));
|
|
996
|
+
for (const dc of directiveCalls) {
|
|
997
|
+
relationships.push({
|
|
998
|
+
source: moduleName,
|
|
999
|
+
target: dc.leaf,
|
|
1000
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
1001
|
+
confidence: 0.85,
|
|
1002
|
+
line_number: dc.line,
|
|
1003
|
+
metadata: { context: dc.context }
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
const relSeen = /* @__PURE__ */ new Map();
|
|
1007
|
+
const uniqueRelationships = [];
|
|
1008
|
+
for (const r of relationships) {
|
|
1009
|
+
const key = `${String(r.type)}|${String(r.source)}|${String(r.target)}`;
|
|
1010
|
+
const prior = relSeen.get(key);
|
|
1011
|
+
if (!prior) {
|
|
1012
|
+
relSeen.set(key, r);
|
|
1013
|
+
uniqueRelationships.push(r);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
_RealSvelteAnalyzer.recordDroppedTemplateRead(prior, r);
|
|
1017
|
+
}
|
|
1018
|
+
relationships.length = 0;
|
|
1019
|
+
relationships.push(...uniqueRelationships);
|
|
1020
|
+
this.publishResolutionClaims(relationships, imports, symbols, filePath, context?.projectRoot);
|
|
1021
|
+
this.classifyEvidenceTier(relationships);
|
|
1022
|
+
const cyclomaticComplexity = 1 + templateDecisions + scriptDecisions;
|
|
1023
|
+
return {
|
|
1024
|
+
language: "svelte",
|
|
1025
|
+
symbols,
|
|
1026
|
+
relationships,
|
|
1027
|
+
imports,
|
|
1028
|
+
exports,
|
|
1029
|
+
errors,
|
|
1030
|
+
metadata: {
|
|
1031
|
+
// Real cyclomatic complexity: script decisions + template decision
|
|
1032
|
+
// blocks. Was the synthetic symbols+relationships heuristic with a
|
|
1033
|
+
// hardcoded cyclomatic of 1 (fourth analyzer with this disease).
|
|
1034
|
+
complexity: cyclomaticComplexity,
|
|
1035
|
+
linesOfCode: content.split("\n").length,
|
|
1036
|
+
cyclomaticComplexity,
|
|
1037
|
+
framework: "Svelte",
|
|
1038
|
+
patterns: detectedPatterns,
|
|
1039
|
+
detectedFrameworks: ["Svelte"],
|
|
1040
|
+
frameworkPatterns,
|
|
1041
|
+
architecturalInsights: [],
|
|
1042
|
+
architecturalScore,
|
|
1043
|
+
hasFrameworkIntelligence: true,
|
|
1044
|
+
hasTypeInference: false
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
// ============================================================================
|
|
1049
|
+
// SVELTE-SPECIFIC DECLARATION EXTRACTION
|
|
1050
|
+
// ============================================================================
|
|
1051
|
+
/**
|
|
1052
|
+
* S2 (#419): the binding names declared by a $props() destructure
|
|
1053
|
+
* (`let { a, b, c = 5 } = $props()`). These are component-owned Properties
|
|
1054
|
+
* emitted by extractSvelteDeclarations; the JS sub-analyzer's parallel
|
|
1055
|
+
* destructured Variables are dropped to avoid double declaration.
|
|
1056
|
+
*/
|
|
1057
|
+
collectPropsDestructuredNames(root, out) {
|
|
1058
|
+
const stack = [root];
|
|
1059
|
+
while (stack.length > 0) {
|
|
1060
|
+
const n = stack.pop();
|
|
1061
|
+
if (n.type === "call_expression") {
|
|
1062
|
+
const callee = n.childForFieldName?.("function") ?? n.namedChild(0);
|
|
1063
|
+
if (callee && (callee.text === "$props" || callee.text === "$props()")) {
|
|
1064
|
+
const declarator = this.findAncestor(n, (p) => p.type === "variable_declarator");
|
|
1065
|
+
if (declarator) {
|
|
1066
|
+
const pattern = declarator.childForFieldName?.("name") ?? this.findChildByType(declarator, "object_pattern");
|
|
1067
|
+
const names = pattern ? this.objectPatternNames(pattern) : [];
|
|
1068
|
+
names.forEach((nm) => out.add(nm));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1073
|
+
const c = n.namedChild(i);
|
|
1074
|
+
if (c) stack.push(c);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
objectPatternNames(pattern) {
|
|
1079
|
+
return this.objectPatternPairs(pattern).map((p) => p.name);
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* M59 (#825) follow-up, measured on a `$props()` rename destructure: the
|
|
1083
|
+
* pair VALUE binds, not the key (`let { renamed: local }` binds `local`).
|
|
1084
|
+
* The old key-side naming emitted a phantom Property `renamed` AND defeated
|
|
1085
|
+
* the merge dedupe (the JS delegate's correct `local` survived alongside
|
|
1086
|
+
* it — one binding, two symbols). Each pair also carries its node so the
|
|
1087
|
+
* symbol ranges at its own NAME line, not the whole declarator.
|
|
1088
|
+
*/
|
|
1089
|
+
objectPatternPairs(pattern) {
|
|
1090
|
+
const out = [];
|
|
1091
|
+
const take = (n) => {
|
|
1092
|
+
if (n.type === "identifier" || n.type === "shorthand_property_identifier" || n.type === "shorthand_property_identifier_pattern") {
|
|
1093
|
+
out.push({ name: n.text, node: n });
|
|
1094
|
+
} else if (n.type === "pair_pattern") {
|
|
1095
|
+
const value = n.childForFieldName?.("value");
|
|
1096
|
+
if (value) take(value);
|
|
1097
|
+
} else if (n.type === "rest_pattern") {
|
|
1098
|
+
const inner = n.namedChild(0);
|
|
1099
|
+
if (inner) take(inner);
|
|
1100
|
+
} else if (n.type === "assignment_pattern") {
|
|
1101
|
+
const left = n.childForFieldName?.("left") ?? n.namedChild(0);
|
|
1102
|
+
if (left) take(left);
|
|
1103
|
+
} else if (n.type === "object_assignment_pattern") {
|
|
1104
|
+
const left = n.childForFieldName?.("left") ?? n.childForFieldName?.("pattern") ?? n.namedChild(0);
|
|
1105
|
+
if (left) take(left);
|
|
1106
|
+
} else if (
|
|
1107
|
+
// `object_assignment_pattern_deprecated` was a dead disjunct: measured
|
|
1108
|
+
// 2026-10-08, the pinned svelte grammar declares no assignment-pattern
|
|
1109
|
+
// type at all, and the pinned typescript/javascript grammars declare
|
|
1110
|
+
// `object_assignment_pattern` and `assignment_pattern` with no deprecated
|
|
1111
|
+
// spelling. A real parse of `let { a = 1, ...rest } = obj` builds
|
|
1112
|
+
// `object_pattern` -> `object_assignment_pattern`, which is the shape the
|
|
1113
|
+
// arm ABOVE already handles; this container branch is for the patterns
|
|
1114
|
+
// whose children are all bindings.
|
|
1115
|
+
n.type === "object_pattern" || n.type === "array_pattern"
|
|
1116
|
+
) {
|
|
1117
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1118
|
+
const c = n.namedChild(i);
|
|
1119
|
+
if (c) take(c);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
take(pattern);
|
|
1124
|
+
return out;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* S6 (#419): collect the target names of every `$:` reactive declaration
|
|
1128
|
+
* (`$: doubled = expr` → `doubled`). Used at the merge to drop the JS/TS
|
|
1129
|
+
* sub-analyzer's PLACE COPY of such a variable, so the component-owned
|
|
1130
|
+
* reactive symbol emitted by extractSvelteDeclarations is the ONLY one —
|
|
1131
|
+
* the same dedupe S2 applies to $props() bindings.
|
|
1132
|
+
*/
|
|
1133
|
+
collectReactiveTargetNames(root, out) {
|
|
1134
|
+
const stack = [root];
|
|
1135
|
+
while (stack.length > 0) {
|
|
1136
|
+
const n = stack.pop();
|
|
1137
|
+
if (n.type === "labeled_statement") {
|
|
1138
|
+
const label = n.namedChild(0);
|
|
1139
|
+
if (label && label.text === "$") {
|
|
1140
|
+
const assignment = this.findDescendant(n, (c) => c.type === "assignment_expression");
|
|
1141
|
+
const left = assignment?.childForFieldName?.("left") ?? assignment?.namedChild(0);
|
|
1142
|
+
if (left && left.type === "identifier") out.add(left.text);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1146
|
+
const c = n.namedChild(i);
|
|
1147
|
+
if (c) stack.push(c);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
/** D7 (RULED 2026-09-27): `calls` edges for named functions passed to an
|
|
1152
|
+
* INVOKING rune. See SVELTE_INVOKING_RUNES for why the set is the invoking
|
|
1153
|
+
* half of the rune surface and not all of it.
|
|
1154
|
+
*
|
|
1155
|
+
* Only a BARE IDENTIFIER argument produces an edge. An arrow function is not a
|
|
1156
|
+
* name and its body is already walked; a call argument (`$effect(doThing())`)
|
|
1157
|
+
* is a use, not a call of `doThing`; a member expression (`$effect(obj.m)`)
|
|
1158
|
+
* names a method the analyzer does not resolve here. Emitting for those would
|
|
1159
|
+
* be the fabrication class this lane already fixed twice.
|
|
1160
|
+
*/
|
|
1161
|
+
extractRuneInvocations(root, componentName, relationships, lineOffset) {
|
|
1162
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1163
|
+
const stack = [root];
|
|
1164
|
+
while (stack.length > 0) {
|
|
1165
|
+
const n = stack.pop();
|
|
1166
|
+
if (n.type === "call_expression") {
|
|
1167
|
+
const callee = n.childForFieldName?.("function") ?? n.namedChild(0);
|
|
1168
|
+
const calleeText = callee?.text ?? "";
|
|
1169
|
+
if (isInvokingRune(calleeText)) {
|
|
1170
|
+
const argsField = n.childForFieldName?.("arguments");
|
|
1171
|
+
const argList = [];
|
|
1172
|
+
if (argsField) {
|
|
1173
|
+
for (let i = 0; i < argsField.namedChildCount; i++) {
|
|
1174
|
+
const a = argsField.namedChild(i);
|
|
1175
|
+
if (a) argList.push(a);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const line = this.nodeToRange(n).startLine + lineOffset;
|
|
1179
|
+
for (const arg of argList) {
|
|
1180
|
+
if (arg.type !== "identifier") continue;
|
|
1181
|
+
const name = arg.text;
|
|
1182
|
+
if (name === componentName || isRuneCallee(name)) continue;
|
|
1183
|
+
const key = `${calleeText}|${name}|${line}`;
|
|
1184
|
+
if (seen.has(key)) continue;
|
|
1185
|
+
seen.add(key);
|
|
1186
|
+
relationships.push({
|
|
1187
|
+
source: componentName,
|
|
1188
|
+
target: name,
|
|
1189
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
1190
|
+
confidence: 0.85,
|
|
1191
|
+
line_number: line,
|
|
1192
|
+
metadata: { context: `Svelte rune ${calleeText}`, runeCallee: calleeText, evidenceClass: "structural" }
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1198
|
+
const c = n.namedChild(i);
|
|
1199
|
+
if (c) stack.push(c);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
extractSvelteDeclarations(root, componentName, symbols, lineOffset, colOffset, indexOffset = 0) {
|
|
1204
|
+
const stack = [root];
|
|
1205
|
+
const reactiveTargets = /* @__PURE__ */ new Set();
|
|
1206
|
+
while (stack.length > 0) {
|
|
1207
|
+
const n = stack.pop();
|
|
1208
|
+
if (n.type === "call_expression") {
|
|
1209
|
+
const callee = n.childForFieldName?.("function") ?? n.namedChild(0);
|
|
1210
|
+
if (callee && (callee.text === "$props" || callee.text === "$props()")) {
|
|
1211
|
+
const declarator = this.findAncestor(n, (p) => p.type === "variable_declarator");
|
|
1212
|
+
if (declarator) {
|
|
1213
|
+
const pattern = declarator.childForFieldName?.("name") ?? this.findChildByType(declarator, "object_pattern");
|
|
1214
|
+
const pairs = pattern && pattern.type === "object_pattern" ? this.objectPatternPairs(pattern) : [];
|
|
1215
|
+
for (const { name, node: nameNode } of pairs) {
|
|
1216
|
+
symbols.push({
|
|
1217
|
+
name,
|
|
1218
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1219
|
+
range: this.offsetRange(this.nodeToRange(nameNode), lineOffset, colOffset, indexOffset),
|
|
1220
|
+
scope: componentName,
|
|
1221
|
+
ownerName: componentName,
|
|
1222
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1223
|
+
visibility: "public",
|
|
1224
|
+
metadata: { isSvelteProp: true, fromRunes: true }
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (n.type === "labeled_statement") {
|
|
1231
|
+
const label = n.namedChild(0);
|
|
1232
|
+
if (label && label.text === "$") {
|
|
1233
|
+
const assignment = this.findDescendant(n, (c) => c.type === "assignment_expression");
|
|
1234
|
+
if (assignment) {
|
|
1235
|
+
const left = assignment.childForFieldName?.("left") ?? assignment.namedChild(0);
|
|
1236
|
+
if (left && left.type === "identifier") {
|
|
1237
|
+
if (reactiveTargets.has(left.text)) continue;
|
|
1238
|
+
reactiveTargets.add(left.text);
|
|
1239
|
+
symbols.push({
|
|
1240
|
+
name: left.text,
|
|
1241
|
+
kind: ASTEngine.SymbolKind.Variable,
|
|
1242
|
+
range: this.offsetRange(this.nodeToRange(assignment), lineOffset, colOffset, indexOffset),
|
|
1243
|
+
scope: componentName,
|
|
1244
|
+
ownerName: componentName,
|
|
1245
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1246
|
+
visibility: "public",
|
|
1247
|
+
metadata: { isReactiveDeclaration: true }
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1254
|
+
const c = n.namedChild(i);
|
|
1255
|
+
if (c) stack.push(c);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Is the bi-th <script> block a MODULE block? Module-block exports are
|
|
1261
|
+
* module-level API - they must NOT become component props.
|
|
1262
|
+
*
|
|
1263
|
+
* Svelte spells this two ways and BOTH are real: `context="module"` and the
|
|
1264
|
+
* bare `module` attribute (the Svelte 3/4 shorthand, still what the corpus
|
|
1265
|
+
* writes - `sv-addons/registry/template/src/lib/registry/ui/button/button.svelte`
|
|
1266
|
+
* opens `<script lang="ts" module>`). The old predicate compared the whole
|
|
1267
|
+
* start-tag TEXT for the literal `context="module"`, so every bare-`module`
|
|
1268
|
+
* block was read as an INSTANCE block: its exports became component props
|
|
1269
|
+
* (ownerName = the component, plus a `contains` edge) and the `exports[]`
|
|
1270
|
+
* channel stayed empty. Measured on the 200-file sample: exports[] 0 rows,
|
|
1271
|
+
* and the 22 `exports` edges all arrived from the JS delegate's own
|
|
1272
|
+
* `export const` handling rather than from the module API they are.
|
|
1273
|
+
*
|
|
1274
|
+
* Both spellings are now read STRUCTURALLY, off the attribute nodes: an
|
|
1275
|
+
* `attribute` whose `attribute_name` is `context` with value `module`, or one
|
|
1276
|
+
* whose `attribute_name` is `module` with no value. No pattern literal, and a
|
|
1277
|
+
* single-quoted `context='module'` is now found for the same reason the
|
|
1278
|
+
* double-quoted one always was: by construction, not by spelling.
|
|
1279
|
+
*/
|
|
1280
|
+
isModuleContextBlock(rootNode, index) {
|
|
1281
|
+
const blocks = [];
|
|
1282
|
+
const walk = (n) => {
|
|
1283
|
+
if (n.type === "script_element") {
|
|
1284
|
+
blocks.push(n);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1288
|
+
const c = n.namedChild(i);
|
|
1289
|
+
if (c) walk(c);
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
walk(rootNode);
|
|
1293
|
+
const block = blocks[index];
|
|
1294
|
+
if (!block) return false;
|
|
1295
|
+
const startTag = this.findChildByType(block, "start_tag");
|
|
1296
|
+
if (!startTag) return false;
|
|
1297
|
+
for (let i = 0; i < startTag.namedChildCount; i++) {
|
|
1298
|
+
const attr = startTag.namedChild(i);
|
|
1299
|
+
if (!attr || attr.type !== "attribute") continue;
|
|
1300
|
+
const name = this.attributeName(attr);
|
|
1301
|
+
if (name === "module") return true;
|
|
1302
|
+
if (name === "context" && this.attributeValue(attr) === "module") return true;
|
|
1303
|
+
}
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
/** The `attribute_value` text of an attribute, however it is quoted. */
|
|
1307
|
+
attributeValue(attr) {
|
|
1308
|
+
const value = this.findChildByType(attr, "attribute_value");
|
|
1309
|
+
if (value) return value.text;
|
|
1310
|
+
const quoted = this.findChildByType(attr, "quoted_attribute_value");
|
|
1311
|
+
if (!quoted) return "";
|
|
1312
|
+
const inner = this.findChildByType(quoted, "attribute_value");
|
|
1313
|
+
return inner ? inner.text : "";
|
|
1314
|
+
}
|
|
1315
|
+
isExported(exportInfos, name) {
|
|
1316
|
+
for (const e of exportInfos) {
|
|
1317
|
+
if (e.name === name && !e.isDefault) return true;
|
|
1318
|
+
}
|
|
1319
|
+
return false;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* ONTOLOGY-WORKSTREAM SVELTE3 — the LOCAL BINDING an `import` introduces, which
|
|
1323
|
+
* is the only name Svelte's template can legitimately write.
|
|
1324
|
+
*
|
|
1325
|
+
* The JS/TS sub-analyzer does not write bare binding names in `items`; it writes
|
|
1326
|
+
* the item AS SPELLED. Measured on real analyzer-local records:
|
|
1327
|
+
*
|
|
1328
|
+
* import * as Card from "./card" -> items ["* as Card"], alias "Card"
|
|
1329
|
+
* import { alpha as beta } from "./b" -> items ["alpha as beta"], alias undefined
|
|
1330
|
+
* import Foo from "./foo" -> items ["Foo"], alias undefined
|
|
1331
|
+
* import { qux } from "./qux" -> items ["qux"], alias undefined
|
|
1332
|
+
*
|
|
1333
|
+
* So a consumer that adds `items` to a set of binding names holds `"* as Card"`
|
|
1334
|
+
* and `"alpha as beta"` and never `Card` or `beta`. Two rules were reading that
|
|
1335
|
+
* set and both were silent on the forms above; this is the one derivation they
|
|
1336
|
+
* share now.
|
|
1337
|
+
*
|
|
1338
|
+
* Pure String operations — the `as` split is `indexOf`/`slice`, never a regex
|
|
1339
|
+
* (the lane is regex-free by contract).
|
|
1340
|
+
*/
|
|
1341
|
+
importLocalBindings(imp) {
|
|
1342
|
+
const module = String(imp?.module ?? "");
|
|
1343
|
+
const out = [];
|
|
1344
|
+
if (imp?.isSideEffectImport === true) return out;
|
|
1345
|
+
const items = Array.isArray(imp?.items) ? imp.items : [];
|
|
1346
|
+
const alias = typeof imp?.alias === "string" ? String(imp.alias).trim() : "";
|
|
1347
|
+
if (alias) {
|
|
1348
|
+
out.push({ name: alias, isNamespace: items.some((i) => String(i).trim().startsWith("*")), module });
|
|
1349
|
+
return out;
|
|
1350
|
+
}
|
|
1351
|
+
for (const raw of items) {
|
|
1352
|
+
let item = String(raw).trim();
|
|
1353
|
+
if (!item) continue;
|
|
1354
|
+
const isNamespace = item.startsWith("*");
|
|
1355
|
+
if (isNamespace) {
|
|
1356
|
+
item = item.startsWith("* as ") ? item.slice(4).trim() : "";
|
|
1357
|
+
if (!item) continue;
|
|
1358
|
+
}
|
|
1359
|
+
const asIdx = item.indexOf(" as ");
|
|
1360
|
+
if (asIdx >= 0) item = item.slice(asIdx + 4).trim();
|
|
1361
|
+
if (!item) continue;
|
|
1362
|
+
out.push({ name: item, isNamespace, module });
|
|
1363
|
+
}
|
|
1364
|
+
return out;
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* IS THIS MODULE SPECIFIER A PACKAGE THE PROJECT DOES NOT PUBLISH?
|
|
1368
|
+
*
|
|
1369
|
+
* The NEGATIVE cases are the whole rule, and each one is a shape that would otherwise be
|
|
1370
|
+
* claimed wrongly:
|
|
1371
|
+
*
|
|
1372
|
+
* * `.` / `..` / `/` — a FILE, not a package. That question belongs to the file claim
|
|
1373
|
+
* (`importProjectFile`) and is answered separately below.
|
|
1374
|
+
* * `$lib`, `$app`, `$env` — a framework ALIAS into the project's own tree. In this
|
|
1375
|
+
* corpus the app root is not the project root (`shadcn-svelte/docs/src/lib`), so the
|
|
1376
|
+
* analyzer cannot even spell the file it names; calling it external would be a false
|
|
1377
|
+
* statement that the admitted file list happens to confirm, which is the laundering
|
|
1378
|
+
* shape and is refused here rather than discovered later.
|
|
1379
|
+
* * a URI scheme (`virtual:`, `http:`, `data:`) — not a package at all.
|
|
1380
|
+
* * a bare name the project PUBLISHES (`projectPublishedPackages`) — the monorepo
|
|
1381
|
+
* self-reference, measured above at 24 import records in the `svelte` checkout.
|
|
1382
|
+
*
|
|
1383
|
+
* A single-segment specifier IS eligible, and that is deliberately wider than the shape
|
|
1384
|
+
* rule `sourcePathClaim`'s own docstring states for FILE targets (`"dependency"`,
|
|
1385
|
+
* `"SubProjectB"`): a module specifier with no separator and no extension cannot name a
|
|
1386
|
+
* relative file in ESM, and the thing that stops a self-reference is the published-name
|
|
1387
|
+
* table rather than the shape. Measured after the change: 0 of the resulting claims was
|
|
1388
|
+
* refuted by the project's own file list.
|
|
1389
|
+
*/
|
|
1390
|
+
isExternalPackageSpecifier(spec, published) {
|
|
1391
|
+
if (spec === "" || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("$")) return false;
|
|
1392
|
+
if (spec.includes("\\") || containsWhitespace(spec)) return false;
|
|
1393
|
+
if (isUriSchemeSpecifier(spec)) return false;
|
|
1394
|
+
const segs = spec.split("/").filter((s) => s !== "");
|
|
1395
|
+
if (segs.length === 0) return false;
|
|
1396
|
+
const root = spec.startsWith("@") ? segs.slice(0, 2).join("/") : segs[0];
|
|
1397
|
+
if (root === "" || root === "@") return false;
|
|
1398
|
+
return !published.has(root);
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* PUBLISH THE CLAIM THE LANE ALREADY HOLDS — the module half of an import edge, and the
|
|
1402
|
+
* PROVENANCE of an imported binding.
|
|
1403
|
+
*
|
|
1404
|
+
* THE DEFECT IS NOT A MISSING COMPUTATION. Three of this lane's four largest unresolved
|
|
1405
|
+
* shapes are edges where the answer is already computed and thrown away one line later:
|
|
1406
|
+
*
|
|
1407
|
+
* imports (616 of 616 unresolved) the edge publishes `<specifier>:<item>` and
|
|
1408
|
+
* `metadata.module` carries the specifier, but NO claim key is set — so a dependency
|
|
1409
|
+
* the project HAS (142 relative `.svelte` specifiers in this draw) and a dependency the
|
|
1410
|
+
* project CANNOT have (bare npm packages) both read "no answer". `externalClaimOf`
|
|
1411
|
+
* reads `importProjectFile` and `sourcePathClaim`; neither was ever set here.
|
|
1412
|
+
* instantiates / calls / uses / references a target whose ROOT binding is an import
|
|
1413
|
+
* from a package the project does not publish (`TrendingUpIcon` from
|
|
1414
|
+
* `@lucide/svelte/icons/minus`, `AccordionPrimitive.Root` from `bits-ui`) names a
|
|
1415
|
+
* declaration outside the population — which is precisely what `isExternal` claims,
|
|
1416
|
+
* and what the checkout's own package names can PROVE from provenance instead of
|
|
1417
|
+
* guessing from the name.
|
|
1418
|
+
*
|
|
1419
|
+
* WHAT IS DELIBERATELY NOT DONE, and this is the trap refused in CODE rather than in
|
|
1420
|
+
* prose: **no target is ever rebased.** `<Card.Root/>` is not rewritten to `Root` or to
|
|
1421
|
+
* the module's file stem `card`; `$lib/utils.js:cn` is not rewritten to `cn`. That rewrite
|
|
1422
|
+
* moves edges out of `unresolved` and into `collRes` — a tie-break the indexer measures
|
|
1423
|
+
* 37,318 of 49,729 WRONG — and the names it would land on in this corpus (`Root`,
|
|
1424
|
+
* `Header`, `Title`, `Trigger`, `Button`, `card`) are exactly the registry's recurring
|
|
1425
|
+
* ones. A claim is not a resolution: `classifyEdge` reads an external marker or a cause
|
|
1426
|
+
* ONLY where the target names nothing (`decls === 0`), so no edge here can become
|
|
1427
|
+
* `resolved` and the ambiguity columns cannot move.
|
|
1428
|
+
*
|
|
1429
|
+
* AND THE `$lib` / RELATIVE NON-FILE HALF IS LEFT ALONE ON PURPOSE. 251 `$lib` import
|
|
1430
|
+
* edges and the 214 component/read edges bound to `$lib` or relative modules name
|
|
1431
|
+
* declarations that ARE in the project (the module resolves under `src/lib`; the component
|
|
1432
|
+
* is `button.svelte`'s own `button` symbol). No honest claim exists for them, and the
|
|
1433
|
+
* module-namespace member (`<Card.Root/>`, 419 edges — this lane's top cause) has no
|
|
1434
|
+
* name-keyed identity at all. Both are ruled in `validation/m14/verification/SVELTE-FIX.md`
|
|
1435
|
+
* §2 with the file-level evidence.
|
|
1436
|
+
*/
|
|
1437
|
+
publishResolutionClaims(relationships, imports, symbols, filePath, projectRoot) {
|
|
1438
|
+
if (!projectRoot) return;
|
|
1439
|
+
const published = projectPublishedPackages(projectRoot);
|
|
1440
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
1441
|
+
for (const imp of imports) {
|
|
1442
|
+
for (const b of this.importLocalBindings(imp)) if (!bindings.has(b.name)) bindings.set(b.name, b.module);
|
|
1443
|
+
}
|
|
1444
|
+
const declaredHere = new Set(symbols.map((s) => String(s.name ?? "")));
|
|
1445
|
+
for (const r of relationships) {
|
|
1446
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
1447
|
+
const tgt = String(r.target ?? "");
|
|
1448
|
+
if (String(r.type) === ASTEngine.RelationshipType.Imports) {
|
|
1449
|
+
const spec2 = typeof md.module === "string" ? md.module : "";
|
|
1450
|
+
if (spec2 === "") continue;
|
|
1451
|
+
if (spec2.startsWith("./") || spec2.startsWith("../")) {
|
|
1452
|
+
if (!spec2.endsWith(".svelte")) continue;
|
|
1453
|
+
const abs = path.resolve(path.dirname(filePath), spec2);
|
|
1454
|
+
let isFile = false;
|
|
1455
|
+
try {
|
|
1456
|
+
isFile = statSync(abs).isFile();
|
|
1457
|
+
} catch {
|
|
1458
|
+
isFile = false;
|
|
1459
|
+
}
|
|
1460
|
+
if (!isFile) continue;
|
|
1461
|
+
const rel = path.relative(projectRoot, abs).split(path.sep).join("/");
|
|
1462
|
+
if (rel === "" || rel.startsWith("..")) continue;
|
|
1463
|
+
md.importProjectFile = rel;
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (this.isExternalPackageSpecifier(spec2, published)) md.sourcePathClaim = [spec2];
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
if (!isDottedIdentifierChain(tgt)) continue;
|
|
1470
|
+
const root = tgt.includes(".") ? tgt.slice(0, tgt.indexOf(".")) : tgt;
|
|
1471
|
+
if (declaredHere.has(root)) continue;
|
|
1472
|
+
const spec = bindings.get(root);
|
|
1473
|
+
if (spec === void 0) continue;
|
|
1474
|
+
if (!this.isExternalPackageSpecifier(spec, published)) continue;
|
|
1475
|
+
md.isExternal = true;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
// ============================================================================
|
|
1479
|
+
// S1 TEMPLATE EXPRESSION EXTRACTION
|
|
1480
|
+
// ============================================================================
|
|
1481
|
+
/** Extract the opaque expression text from a node (svelte_raw_text child). */
|
|
1482
|
+
/** The `svelte_raw_text` node the expression body lives in — the node that
|
|
1483
|
+
* gives the expression a LINE (M59 #825). `expressionText` returns only the
|
|
1484
|
+
* string, which is exactly why every template read in this analyzer claimed
|
|
1485
|
+
* no line at all. */
|
|
1486
|
+
expressionTextNode(node) {
|
|
1487
|
+
const stack = [node];
|
|
1488
|
+
while (stack.length > 0) {
|
|
1489
|
+
const m = stack.pop();
|
|
1490
|
+
if (m.type === "svelte_raw_text") return m;
|
|
1491
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1492
|
+
const c = m.namedChild(i);
|
|
1493
|
+
if (c) stack.push(c);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
expressionText(node) {
|
|
1499
|
+
return this.expressionTextNode(node)?.text ?? "";
|
|
1500
|
+
}
|
|
1501
|
+
/** Every `svelte_raw_text` node under `root`, in document order — text AND
|
|
1502
|
+
* position from the same node, so a fragment's line cannot drift from the
|
|
1503
|
+
* fragment the name was read out of. */
|
|
1504
|
+
rawTextNodes(root) {
|
|
1505
|
+
const out = [];
|
|
1506
|
+
const stack = [root];
|
|
1507
|
+
while (stack.length > 0) {
|
|
1508
|
+
const m = stack.pop();
|
|
1509
|
+
if (m.type === "svelte_raw_text") out.push(m);
|
|
1510
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1511
|
+
const c = m.namedChild(i);
|
|
1512
|
+
if (c) stack.push(c);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return out.sort((a, b) => a.startIndex - b.startIndex);
|
|
1516
|
+
}
|
|
1517
|
+
/** Line breaks before `end` — how far into a fragment a match sits. */
|
|
1518
|
+
static linesBefore(text, end) {
|
|
1519
|
+
let n = 0;
|
|
1520
|
+
for (let i = 0; i < end && i < text.length; i++) if (text[i] === "\n") n++;
|
|
1521
|
+
return n;
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Shadow-binding sites only: the names are DEFINITIONS, so no line is ever
|
|
1525
|
+
* claimed for them — but they MUST land in `shadowed`.
|
|
1526
|
+
*
|
|
1527
|
+
* Careful: the pre-#825 call sites did this by passing the shadow set as the
|
|
1528
|
+
* collector's `out` (`collectIdentifiersFromText(vars, shadowed, new Set())`),
|
|
1529
|
+
* which is where the bindings were registered. Now that `out` is a Map, that
|
|
1530
|
+
* shape silently registers nothing — measured: `{#each rows as row}` and
|
|
1531
|
+
* `{#await p then value}` stopped shadowing, so `row`/`item`/`total`/`value`/
|
|
1532
|
+
* `d` all came back as template READS (two existing fixtures caught it:
|
|
1533
|
+
* svelte/each-shadow-collision and svelte/await-inline-shadow). So the names
|
|
1534
|
+
* are collected into a scratch Map and then transferred, preserving the
|
|
1535
|
+
* original semantics exactly.
|
|
1536
|
+
*/
|
|
1537
|
+
collectShadowNames(text, shadowed) {
|
|
1538
|
+
const names = /* @__PURE__ */ new Map();
|
|
1539
|
+
this.collectIdentifiersFromText(text, names, /* @__PURE__ */ new Set());
|
|
1540
|
+
for (const n of names.keys()) shadowed.add(n);
|
|
1541
|
+
}
|
|
1542
|
+
collectIdentifiersFromText(text, out, shadowed, baseLine = 0, sites) {
|
|
1543
|
+
const isIdentStart = (ch) => ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_" || ch === "$";
|
|
1544
|
+
const isIdentPart = (ch) => isIdentStart(ch) || ch >= "0" && ch <= "9";
|
|
1545
|
+
const isSpace = (ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r";
|
|
1546
|
+
const stack = [];
|
|
1547
|
+
const brackets = [];
|
|
1548
|
+
const ternary = [];
|
|
1549
|
+
const isObjectKey = (end) => {
|
|
1550
|
+
let j = end;
|
|
1551
|
+
while (j < text.length && isSpace(text[j])) j++;
|
|
1552
|
+
if (text[j] !== ":") return false;
|
|
1553
|
+
const top = brackets.length > 0 ? brackets[brackets.length - 1] : "";
|
|
1554
|
+
if (top !== "{") return false;
|
|
1555
|
+
const t = ternary.length > 0 ? ternary[ternary.length - 1] : false;
|
|
1556
|
+
return !t;
|
|
1557
|
+
};
|
|
1558
|
+
const emit = (word, start) => {
|
|
1559
|
+
let k = start - 1;
|
|
1560
|
+
while (k >= 0 && isSpace(text[k])) k--;
|
|
1561
|
+
const prev = k >= 0 ? text[k] : "";
|
|
1562
|
+
const prev2 = k - 1 >= 0 ? text[k - 1] : "";
|
|
1563
|
+
if (prev === "." && prev2 !== ".") return;
|
|
1564
|
+
if (prev2 === "?" && prev === ".") return;
|
|
1565
|
+
if (prev === "#") return;
|
|
1566
|
+
if (shadowed.has(word)) return;
|
|
1567
|
+
if (isObjectKey(start + word.length)) return;
|
|
1568
|
+
const site = baseLine + _RealSvelteAnalyzer.linesBefore(text, start);
|
|
1569
|
+
out.set(word, site);
|
|
1570
|
+
if (sites) {
|
|
1571
|
+
const arr = sites.get(word);
|
|
1572
|
+
if (arr) {
|
|
1573
|
+
if (arr[arr.length - 1] !== site) arr.push(site);
|
|
1574
|
+
} else sites.set(word, [site]);
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
let i = 0;
|
|
1578
|
+
while (i < text.length) {
|
|
1579
|
+
const ch = text[i];
|
|
1580
|
+
const top = stack.length > 0 ? stack[stack.length - 1] : "";
|
|
1581
|
+
if (top === "'" || top === '"') {
|
|
1582
|
+
if (ch === "\\") {
|
|
1583
|
+
i += 2;
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
if (ch === top) stack.pop();
|
|
1587
|
+
i++;
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
if (top === "`") {
|
|
1591
|
+
if (ch === "\\") {
|
|
1592
|
+
i += 2;
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (ch === "$" && text[i + 1] === "{") {
|
|
1596
|
+
stack.push("${");
|
|
1597
|
+
brackets.push("{");
|
|
1598
|
+
ternary.push(false);
|
|
1599
|
+
i += 2;
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
if (ch === "`") {
|
|
1603
|
+
stack.pop();
|
|
1604
|
+
brackets.pop();
|
|
1605
|
+
ternary.pop();
|
|
1606
|
+
i++;
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
i++;
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
1613
|
+
stack.push(ch);
|
|
1614
|
+
i++;
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
if (ch === "{") {
|
|
1618
|
+
stack.push(ch);
|
|
1619
|
+
brackets.push("{");
|
|
1620
|
+
ternary.push(false);
|
|
1621
|
+
i++;
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
if (ch === "(" || ch === "[") {
|
|
1625
|
+
stack.push(ch);
|
|
1626
|
+
brackets.push(ch);
|
|
1627
|
+
ternary.push(false);
|
|
1628
|
+
i++;
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
if (ch === "}") {
|
|
1632
|
+
if (top === "{" || top === "${") {
|
|
1633
|
+
stack.pop();
|
|
1634
|
+
brackets.pop();
|
|
1635
|
+
ternary.pop();
|
|
1636
|
+
} else if (top === "(") {
|
|
1637
|
+
stack.pop();
|
|
1638
|
+
brackets.pop();
|
|
1639
|
+
ternary.pop();
|
|
1640
|
+
}
|
|
1641
|
+
i++;
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (ch === ")") {
|
|
1645
|
+
if (top === "(") {
|
|
1646
|
+
stack.pop();
|
|
1647
|
+
brackets.pop();
|
|
1648
|
+
ternary.pop();
|
|
1649
|
+
}
|
|
1650
|
+
i++;
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
if (ch === "]") {
|
|
1654
|
+
if (top === "[") {
|
|
1655
|
+
stack.pop();
|
|
1656
|
+
brackets.pop();
|
|
1657
|
+
ternary.pop();
|
|
1658
|
+
}
|
|
1659
|
+
i++;
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
if (ch === "?") {
|
|
1663
|
+
if (text[i + 1] === "?" || text[i + 1] === ".") {
|
|
1664
|
+
i += 2;
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (ternary.length > 0) ternary[ternary.length - 1] = true;
|
|
1668
|
+
i++;
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
if (ch === ":") {
|
|
1672
|
+
if (ternary.length > 0) ternary[ternary.length - 1] = false;
|
|
1673
|
+
i++;
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
if (ch === "/" && (text[i + 1] === "/" || text[i + 1] === "*")) {
|
|
1677
|
+
if (text[i + 1] === "/") {
|
|
1678
|
+
const nl = text.indexOf("\n", i + 2);
|
|
1679
|
+
i = nl >= 0 ? nl + 1 : text.length;
|
|
1680
|
+
} else {
|
|
1681
|
+
const end = text.indexOf("*/", i + 2);
|
|
1682
|
+
i = end >= 0 ? end + 2 : text.length;
|
|
1683
|
+
}
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
if (!isIdentStart(ch)) {
|
|
1687
|
+
i++;
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
let j = i + 1;
|
|
1691
|
+
while (j < text.length && isIdentPart(text[j])) j++;
|
|
1692
|
+
emit(text.slice(i, j), i);
|
|
1693
|
+
i = j;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
/** {#each items as item, i} — collection is a use; loop vars shadow. */
|
|
1697
|
+
collectEachExpression(node, out, shadowed, sites) {
|
|
1698
|
+
const start = this.findDescendant(node, (c) => c.type === "each_start");
|
|
1699
|
+
if (!start) return;
|
|
1700
|
+
const rawTexts = this.rawTextNodes(start);
|
|
1701
|
+
const collection = rawTexts[0];
|
|
1702
|
+
if (collection) this.collectIdentifiersFromText(collection.text, out, shadowed, collection.startPosition.row + 1, sites);
|
|
1703
|
+
const vars = rawTexts[1];
|
|
1704
|
+
const varsText = vars?.text ?? "";
|
|
1705
|
+
const varsBase = (vars?.startPosition.row ?? 0) + 1;
|
|
1706
|
+
const keyClause = _RealSvelteAnalyzer.patternKeySplit(varsText);
|
|
1707
|
+
if (keyClause) {
|
|
1708
|
+
this.collectBindingPattern(keyClause.pattern, out, shadowed, varsBase, sites);
|
|
1709
|
+
this.collectIdentifiersFromText(
|
|
1710
|
+
keyClause.keyText,
|
|
1711
|
+
out,
|
|
1712
|
+
shadowed,
|
|
1713
|
+
varsBase + _RealSvelteAnalyzer.linesBefore(varsText, keyClause.keyAt),
|
|
1714
|
+
sites
|
|
1715
|
+
);
|
|
1716
|
+
} else {
|
|
1717
|
+
this.collectBindingPattern(varsText, out, shadowed, varsBase, sites);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
/** {#await promise then value} {:catch err} — promise is a use; vars shadow. */
|
|
1721
|
+
collectAwaitExpression(node, out, shadowed, sites) {
|
|
1722
|
+
const start = this.findDescendant(node, (c) => c.type === "await_start");
|
|
1723
|
+
const thenBlk = this.findDescendant(node, (c) => c.type === "then_start");
|
|
1724
|
+
const catchBlk = this.findDescendant(node, (c) => c.type === "catch_start");
|
|
1725
|
+
if (start) {
|
|
1726
|
+
const rawNodes = this.rawTextNodes(start);
|
|
1727
|
+
const rawTexts = rawNodes.map((r) => r.text);
|
|
1728
|
+
const text = rawTexts.join(" ");
|
|
1729
|
+
const firstLine = rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0;
|
|
1730
|
+
const m = splitAwaitHead(text);
|
|
1731
|
+
const promiseExpr = m ? m.head : text;
|
|
1732
|
+
this.collectIdentifiersFromText(promiseExpr, out, shadowed, firstLine);
|
|
1733
|
+
if (m) {
|
|
1734
|
+
this.collectBindingPattern(
|
|
1735
|
+
text.slice(m.matchLength),
|
|
1736
|
+
out,
|
|
1737
|
+
shadowed,
|
|
1738
|
+
firstLine + _RealSvelteAnalyzer.linesBefore(text, m.matchLength),
|
|
1739
|
+
sites
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
for (const blk of [thenBlk, catchBlk]) {
|
|
1744
|
+
if (!blk) continue;
|
|
1745
|
+
const rawNodes = this.rawTextNodes(blk);
|
|
1746
|
+
this.collectBindingPattern(
|
|
1747
|
+
rawNodes.map((r) => r.text).join(" "),
|
|
1748
|
+
out,
|
|
1749
|
+
shadowed,
|
|
1750
|
+
rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0,
|
|
1751
|
+
sites
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/** D5: {#if cond} / {:else if cond} / {#key exp} — the condition is a
|
|
1756
|
+
* svelte_raw_text child of if_start/else_if_start/key_start (no
|
|
1757
|
+
* `expression` wrapper, unlike mustache bodies). Every identifier is a
|
|
1758
|
+
* member read; these heads bind nothing, so no shadow registration. */
|
|
1759
|
+
collectConditionExpression(node, out, shadowed, sites) {
|
|
1760
|
+
const start = this.findDescendant(node, (c) => c.type === "if_start" || c.type === "else_if_start" || c.type === "key_start");
|
|
1761
|
+
if (!start) return;
|
|
1762
|
+
const rawNodes = this.rawTextNodes(start);
|
|
1763
|
+
const baseLine = rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0;
|
|
1764
|
+
this.collectIdentifiersFromText(rawNodes.map((r) => r.text).join(" "), out, shadowed, baseLine, sites);
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* {#snippet row(label, count)} heads — params are template-local.
|
|
1768
|
+
*
|
|
1769
|
+
* F28: `{#snippet counter(c = count)}` binds `c` and READS `count`, and the
|
|
1770
|
+
* grammar hands the parameter list over WITHOUT its parens or the snippet name
|
|
1771
|
+
* (measured: `snippet_start` raw text is `c = count` for exactly that head), so the
|
|
1772
|
+
* pattern scanner takes the fragment as-is. A parameter annotation is a type and
|
|
1773
|
+
* the bare `{#snippet typed(c: number = 4)}` spelling is the fixture's shape for
|
|
1774
|
+
* it: `number` is neither a read nor a binding.
|
|
1775
|
+
*/
|
|
1776
|
+
collectSnippetParams(node, out, shadowed, sites) {
|
|
1777
|
+
const start = this.findDescendant(node, (c) => c.type === "snippet_start");
|
|
1778
|
+
if (!start) return;
|
|
1779
|
+
const rawNodes = this.rawTextNodes(start);
|
|
1780
|
+
this.collectBindingPattern(
|
|
1781
|
+
rawNodes.map((r) => r.text).join(" "),
|
|
1782
|
+
out,
|
|
1783
|
+
shadowed,
|
|
1784
|
+
rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0,
|
|
1785
|
+
sites
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
/** A template expression body ({count}, label={label}, {@html raw}). */
|
|
1789
|
+
collectExpressionIdentifiers(node, out, shadowed, sites) {
|
|
1790
|
+
const textNode = this.expressionTextNode(node);
|
|
1791
|
+
const text = textNode?.text ?? "";
|
|
1792
|
+
const decl = _RealSvelteAnalyzer.declarationTagSplit(text);
|
|
1793
|
+
if (decl) {
|
|
1794
|
+
this.collectBindingPattern(decl.head, out, shadowed, textNode ? textNode.startPosition.row + 1 : 0, sites);
|
|
1795
|
+
this.collectIdentifiersFromText(
|
|
1796
|
+
decl.tail,
|
|
1797
|
+
out,
|
|
1798
|
+
shadowed,
|
|
1799
|
+
(textNode ? textNode.startPosition.row + 1 : 0) + _RealSvelteAnalyzer.linesBefore(text, decl.eqAt + 1),
|
|
1800
|
+
sites
|
|
1801
|
+
);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
const scope = this.eventCarrierName(node.parent) ? this.withArrowParams(shadowed, text) : shadowed;
|
|
1805
|
+
this.collectIdentifiersFromText(text, out, scope, textNode ? textNode.startPosition.row + 1 : 0, sites);
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* M60 SVELTE-D6 / F26: split a Svelte 5.36 declaration tag `{let x = …}` /
|
|
1809
|
+
* `{const x = …}` into its binding head and its initialiser. Returns null for
|
|
1810
|
+
* an ordinary mustache, which is the overwhelming majority. A leading
|
|
1811
|
+
* `let`/`const` is REQUIRED and so is the word boundary after it: without the
|
|
1812
|
+
* boundary `{letter = 5}` — an assignment to a member named `letter` — parses as
|
|
1813
|
+
* a `let` tag binding `ter`, a fabricated shadow rather than a real one.
|
|
1814
|
+
*/
|
|
1815
|
+
static declarationTagSplit(text) {
|
|
1816
|
+
const ws = (i2) => {
|
|
1817
|
+
while (i2 < text.length && (text[i2] === " " || text[i2] === " " || text[i2] === "\n" || text[i2] === "\r")) i2++;
|
|
1818
|
+
return i2;
|
|
1819
|
+
};
|
|
1820
|
+
const i = ws(0);
|
|
1821
|
+
const kw = text.startsWith("let", i) ? "let" : text.startsWith("const", i) ? "const" : null;
|
|
1822
|
+
if (!kw) return null;
|
|
1823
|
+
const after = i + kw.length;
|
|
1824
|
+
const next = text[after];
|
|
1825
|
+
const boundary = next === "{" || next === "[" || next === " " || next === " " || next === "\n" || next === "\r";
|
|
1826
|
+
if (!boundary) return null;
|
|
1827
|
+
const body = ws(after);
|
|
1828
|
+
let depth = 0;
|
|
1829
|
+
for (let j = body; j < text.length; j++) {
|
|
1830
|
+
const ch = text[j];
|
|
1831
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
1832
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
1833
|
+
else if (ch === "=" && depth === 0) {
|
|
1834
|
+
if (text[j + 1] === "=" || text[j - 1] === "=" || text[j - 1] === "!" || text[j - 1] === "<" || text[j - 1] === ">") continue;
|
|
1835
|
+
return { head: text.slice(body, j), tail: text.slice(j + 1), eqAt: j };
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
return null;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* F28 (2026-10-12): A BINDING PATTERN HAS READ POSITIONS AND BINDING POSITIONS.
|
|
1842
|
+
*
|
|
1843
|
+
* Every template site that writes a PATTERN — `{#each rows as …}`, `{#await p
|
|
1844
|
+
* then …}`, `{:catch …}`, `{#snippet name(…)}`, `{let …}` / `{@const …}` heads —
|
|
1845
|
+
* used to hand its text to the flat word collector, which did two wrong things at
|
|
1846
|
+
* once:
|
|
1847
|
+
*
|
|
1848
|
+
* a LOST READ `{#snippet counter(c = count)}` binds `c` and READS `count`;
|
|
1849
|
+
* a FALSE SHADOW `{#each xs as { a: b }}` binds `b` alone — `a` is a PROPERTY
|
|
1850
|
+
* name, so registering it silenced a member named `a` for the
|
|
1851
|
+
* whole block.
|
|
1852
|
+
*
|
|
1853
|
+
* Measured before this fix over the 6,439-file corpus (svelte 4,561 + shadcn-svelte
|
|
1854
|
+
* 1,878 git-tracked .svelte files) with the grammar-derived oracle
|
|
1855
|
+
* (`script/m14/tmp/svelte-uses-oracle.ts`, which re-parses each template fragment
|
|
1856
|
+
* with the JAVASCRIPT grammar and so decides the split from
|
|
1857
|
+
* `assignment_pattern.right` and `computed_property_name` rather than from a
|
|
1858
|
+
* character walk): 35 template reads the payload does not publish, 27 of them this
|
|
1859
|
+
* class, over 81 sites carrying a computed key or a default value.
|
|
1860
|
+
*
|
|
1861
|
+
* The split, and nothing wider than the split:
|
|
1862
|
+
* COMPUTED KEY `{ [k]: v }` the inside of `[ ]` is an EXPRESSION (a read)
|
|
1863
|
+
* DEFAULT VALUE `x = expr` `expr` is an EXPRESSION (a read) and `x` binds
|
|
1864
|
+
* PROPERTY NAME `{ a: b }` `a` is neither a read nor a binding
|
|
1865
|
+
* VALUE `{ a: b }` `b` binds, recursively (nested patterns too)
|
|
1866
|
+
* SHORTHAND `{ a }` `a` binds
|
|
1867
|
+
* REST `...props` `props` binds
|
|
1868
|
+
* ANNOTATION `c: number = 4` `number` is neither: it is a type
|
|
1869
|
+
* BOUND EARLIER `{ idx, [idx - 1]: last }` `idx` is a template-local by the
|
|
1870
|
+
* time the computed key reads it, so it publishes NOTHING
|
|
1871
|
+
*
|
|
1872
|
+
* The last rule is why the walk is ONE LEFT-TO-RIGHT PASS that mutates the shadow
|
|
1873
|
+
* set as it binds: a reader that collects all reads before all bindings publishes
|
|
1874
|
+
* a false edge on the first entry of every `{ name, [name …] }` pattern, and that
|
|
1875
|
+
* is the one row the fixture pins as forbidden precisely because it would silently
|
|
1876
|
+
* ADD edges rather than lose them.
|
|
1877
|
+
*
|
|
1878
|
+
* Pure-String scanning: this family's regex census is 0 and stays 0.
|
|
1879
|
+
*/
|
|
1880
|
+
collectBindingPattern(text, out, shadowed, baseLine = 0, sites) {
|
|
1881
|
+
if (!text) return;
|
|
1882
|
+
const isSpace = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
1883
|
+
const isIdentStart = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$";
|
|
1884
|
+
const isIdentPart = (c) => isIdentStart(c) || c >= "0" && c <= "9";
|
|
1885
|
+
const skipWs = (i, e2) => {
|
|
1886
|
+
while (i < e2 && isSpace(text[i])) i++;
|
|
1887
|
+
return i;
|
|
1888
|
+
};
|
|
1889
|
+
const identAt = (i) => {
|
|
1890
|
+
if (i >= text.length || !isIdentStart(text[i])) return null;
|
|
1891
|
+
let j = i + 1;
|
|
1892
|
+
while (j < text.length && isIdentPart(text[j])) j++;
|
|
1893
|
+
return { name: text.slice(i, j), end: j };
|
|
1894
|
+
};
|
|
1895
|
+
const read = (s2, e2) => {
|
|
1896
|
+
if (s2 >= e2) return;
|
|
1897
|
+
this.collectIdentifiersFromText(
|
|
1898
|
+
text.slice(s2, e2),
|
|
1899
|
+
out,
|
|
1900
|
+
shadowed,
|
|
1901
|
+
baseLine + _RealSvelteAnalyzer.linesBefore(text, s2),
|
|
1902
|
+
sites
|
|
1903
|
+
);
|
|
1904
|
+
};
|
|
1905
|
+
const bind = (name) => {
|
|
1906
|
+
if (isIdentifier(name)) shadowed.add(name);
|
|
1907
|
+
};
|
|
1908
|
+
const atTopLevel = (i, e2, ch) => {
|
|
1909
|
+
let d = 0;
|
|
1910
|
+
let j = i;
|
|
1911
|
+
while (j < e2) {
|
|
1912
|
+
const c = text[j];
|
|
1913
|
+
if (c === "'" || c === '"' || c === "`") {
|
|
1914
|
+
j = _RealSvelteAnalyzer.skipQuoted(text, j);
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
if (c === "(" || c === "[" || c === "{") {
|
|
1918
|
+
d++;
|
|
1919
|
+
j++;
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
if (c === ")" || c === "]" || c === "}") {
|
|
1923
|
+
d--;
|
|
1924
|
+
j++;
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if (c === ch && d === 0) {
|
|
1928
|
+
const prev = j > 0 ? text[j - 1] : "";
|
|
1929
|
+
const next = text[j + 1] ?? "";
|
|
1930
|
+
if (ch === "=" && (next === "=" || prev === "=" || prev === "!" || prev === "<" || prev === ">")) {
|
|
1931
|
+
j++;
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
return j;
|
|
1935
|
+
}
|
|
1936
|
+
j++;
|
|
1937
|
+
}
|
|
1938
|
+
return -1;
|
|
1939
|
+
};
|
|
1940
|
+
const scanValue = (s0, e2) => {
|
|
1941
|
+
const s2 = skipWs(s0, e2);
|
|
1942
|
+
if (s2 >= e2) return;
|
|
1943
|
+
if (text.startsWith("...", s2)) {
|
|
1944
|
+
const w2 = identAt(skipWs(s2 + 3, e2));
|
|
1945
|
+
if (w2) bind(w2.name);
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
const ch = text[s2];
|
|
1949
|
+
if (ch === "{" || ch === "[") {
|
|
1950
|
+
const close = _RealSvelteAnalyzer.matchingBracket(text, s2);
|
|
1951
|
+
const end = close < 0 || close > e2 ? e2 : close;
|
|
1952
|
+
if (ch === "{") scanObject(s2 + 1, end);
|
|
1953
|
+
else scanArray(s2 + 1, end);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const w = identAt(s2);
|
|
1957
|
+
if (!w) {
|
|
1958
|
+
read(s2, e2);
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
let k = skipWs(w.end, e2);
|
|
1962
|
+
if (k < e2 && text[k] === "?" && text[k + 1] === ":") k = skipWs(k + 1, e2);
|
|
1963
|
+
if (k < e2 && text[k] === ":") {
|
|
1964
|
+
bind(w.name);
|
|
1965
|
+
const eq = atTopLevel(k + 1, e2, "=");
|
|
1966
|
+
if (eq >= 0) read(eq + 1, e2);
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
if (k < e2 && text[k] === "=") {
|
|
1970
|
+
bind(w.name);
|
|
1971
|
+
read(k + 1, e2);
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
bind(w.name);
|
|
1975
|
+
};
|
|
1976
|
+
const scanObject = (s2, e2) => {
|
|
1977
|
+
for (const [a, b] of _RealSvelteAnalyzer.splitTopLevel(text, s2, e2)) {
|
|
1978
|
+
const i = skipWs(a, b);
|
|
1979
|
+
if (i >= b) continue;
|
|
1980
|
+
if (text.startsWith("...", i)) {
|
|
1981
|
+
const w2 = identAt(skipWs(i + 3, b));
|
|
1982
|
+
if (w2) bind(w2.name);
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
const ch = text[i];
|
|
1986
|
+
if (ch === "[") {
|
|
1987
|
+
const close = _RealSvelteAnalyzer.matchingBracket(text, i);
|
|
1988
|
+
const end = close < 0 || close > b ? b : close;
|
|
1989
|
+
read(i + 1, end);
|
|
1990
|
+
const k2 = skipWs(end + 1, b);
|
|
1991
|
+
if (k2 < b && text[k2] === ":") scanValue(k2 + 1, b);
|
|
1992
|
+
else if (k2 < b && text[k2] === "=") read(k2 + 1, b);
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
1996
|
+
const k2 = skipWs(_RealSvelteAnalyzer.skipQuoted(text, i), b);
|
|
1997
|
+
if (k2 < b && text[k2] === ":") scanValue(k2 + 1, b);
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
const w = identAt(i);
|
|
2001
|
+
if (!w) {
|
|
2002
|
+
read(i, b);
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
2005
|
+
const k = skipWs(w.end, b);
|
|
2006
|
+
if (k < b && text[k] === ":") {
|
|
2007
|
+
scanValue(k + 1, b);
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
if (k < b && text[k] === "=") {
|
|
2011
|
+
bind(w.name);
|
|
2012
|
+
read(k + 1, b);
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
bind(w.name);
|
|
2016
|
+
}
|
|
2017
|
+
};
|
|
2018
|
+
const scanArray = (s2, e2) => {
|
|
2019
|
+
for (const [a, b] of _RealSvelteAnalyzer.splitTopLevel(text, s2, e2)) scanValue(a, b);
|
|
2020
|
+
};
|
|
2021
|
+
let s = skipWs(0, text.length);
|
|
2022
|
+
let e = text.length;
|
|
2023
|
+
if (s < e && text[s] === "(") {
|
|
2024
|
+
const close = _RealSvelteAnalyzer.matchingBracket(text, s);
|
|
2025
|
+
if (close >= 0 && skipWs(close + 1, e) >= e) {
|
|
2026
|
+
s = s + 1;
|
|
2027
|
+
e = close;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
for (const [a, b] of _RealSvelteAnalyzer.splitTopLevel(text, s, e)) scanValue(a, b);
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* `i` is at a quote: the index just past its closing quote (or the end of the
|
|
2034
|
+
* text when it never closes). A TEMPLATE literal's `${ … }` re-enters code, so its
|
|
2035
|
+
* braces are tracked — otherwise a `,` inside an interpolation would split a
|
|
2036
|
+
* parameter list. Structural scanning only; the READ side is the ordinary
|
|
2037
|
+
* character collector, which has its own (identical) quote machine.
|
|
2038
|
+
*/
|
|
2039
|
+
static skipQuoted(text, i) {
|
|
2040
|
+
const q = text[i];
|
|
2041
|
+
let j = i + 1;
|
|
2042
|
+
let depth = 0;
|
|
2043
|
+
while (j < text.length) {
|
|
2044
|
+
const c = text[j];
|
|
2045
|
+
if (c === "\\") {
|
|
2046
|
+
j += 2;
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
if (q === "`") {
|
|
2050
|
+
if (c === "$" && text[j + 1] === "{") {
|
|
2051
|
+
depth++;
|
|
2052
|
+
j += 2;
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
if (c === "}" && depth > 0) {
|
|
2056
|
+
depth--;
|
|
2057
|
+
j++;
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (c === q && depth === 0) return j + 1;
|
|
2062
|
+
j++;
|
|
2063
|
+
}
|
|
2064
|
+
return text.length;
|
|
2065
|
+
}
|
|
2066
|
+
/** Index of the bracket matching the opener at `open`, or -1 when unbalanced. */
|
|
2067
|
+
static matchingBracket(text, open) {
|
|
2068
|
+
const closerOf = (c) => c === "(" ? ")" : c === "[" ? "]" : c === "{" ? "}" : "";
|
|
2069
|
+
const stack = [];
|
|
2070
|
+
let i = open;
|
|
2071
|
+
while (i < text.length) {
|
|
2072
|
+
const ch = text[i];
|
|
2073
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
2074
|
+
i = _RealSvelteAnalyzer.skipQuoted(text, i);
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
const expect = closerOf(ch);
|
|
2078
|
+
if (expect) {
|
|
2079
|
+
stack.push(expect);
|
|
2080
|
+
i++;
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
2084
|
+
if (stack.length === 0 || stack[stack.length - 1] !== ch) return -1;
|
|
2085
|
+
stack.pop();
|
|
2086
|
+
if (stack.length === 0) return i;
|
|
2087
|
+
}
|
|
2088
|
+
i++;
|
|
2089
|
+
}
|
|
2090
|
+
return -1;
|
|
2091
|
+
}
|
|
2092
|
+
/** `[s, e)` split on TOP-LEVEL commas: quotes, brackets and `${…}` are skipped whole. */
|
|
2093
|
+
static splitTopLevel(text, s, e) {
|
|
2094
|
+
const segs = [];
|
|
2095
|
+
let start = s;
|
|
2096
|
+
let i = s;
|
|
2097
|
+
while (i < e) {
|
|
2098
|
+
const ch = text[i];
|
|
2099
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
2100
|
+
i = _RealSvelteAnalyzer.skipQuoted(text, i);
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
2104
|
+
const close = _RealSvelteAnalyzer.matchingBracket(text, i);
|
|
2105
|
+
i = close < 0 || close >= e ? e : close + 1;
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2108
|
+
if (ch === ",") {
|
|
2109
|
+
segs.push([start, i]);
|
|
2110
|
+
start = i + 1;
|
|
2111
|
+
i++;
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
i++;
|
|
2115
|
+
}
|
|
2116
|
+
segs.push([start, e]);
|
|
2117
|
+
return segs;
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* F25/F28: split a loop binding list's trailing KEY CLAUSE off its pattern —
|
|
2121
|
+
* `row (keyOf(row))` gives the pattern `row ` and the key `keyOf(row)`.
|
|
2122
|
+
*
|
|
2123
|
+
* The grammar keeps the key INSIDE the same fragment as the bindings
|
|
2124
|
+
* (`{#each rows as row}` gives `row`, `{#each rows as row (row.id)}` gives
|
|
2125
|
+
* `row (row.id)`), so the two halves have to be separated by hand. The test is
|
|
2126
|
+
* STRUCTURAL rather than `indexOf("(")`: the key clause is the parenthesised group
|
|
2127
|
+
* that ends the list, which keeps a `(` inside a pattern's own default
|
|
2128
|
+
* (`{ [k]: v = f() }`) from being mistaken for it.
|
|
2129
|
+
*/
|
|
2130
|
+
static patternKeySplit(text) {
|
|
2131
|
+
let e = text.length;
|
|
2132
|
+
while (e > 0 && (text[e - 1] === " " || text[e - 1] === " " || text[e - 1] === "\n" || text[e - 1] === "\r")) e--;
|
|
2133
|
+
if (e === 0 || text[e - 1] !== ")") return null;
|
|
2134
|
+
let depth = 0;
|
|
2135
|
+
let openAt = -1;
|
|
2136
|
+
for (let i = 0; i < e; i++) {
|
|
2137
|
+
const ch = text[i];
|
|
2138
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
2139
|
+
i = _RealSvelteAnalyzer.skipQuoted(text, i) - 1;
|
|
2140
|
+
continue;
|
|
2141
|
+
}
|
|
2142
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
2143
|
+
if (depth === 0 && ch === "(") openAt = i;
|
|
2144
|
+
depth++;
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
2148
|
+
depth--;
|
|
2149
|
+
if (depth === 0) {
|
|
2150
|
+
if (i === e - 1 && openAt >= 0) {
|
|
2151
|
+
return { pattern: text.slice(0, openAt), keyText: text.slice(openAt + 1, e - 1), keyAt: openAt + 1 };
|
|
2152
|
+
}
|
|
2153
|
+
openAt = -1;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* F29: record the TEMPLATE read a dedupe drop would otherwise lose.
|
|
2161
|
+
*
|
|
2162
|
+
* Called only from the graph dedupe, for a row that is being DROPPED in favour of a
|
|
2163
|
+
* row already published for the same `type|source|target`. It does nothing unless
|
|
2164
|
+
* the dropped row is a template read and the survivor is not one — the case the
|
|
2165
|
+
* fixture measures — and it writes onto the SURVIVOR:
|
|
2166
|
+
*
|
|
2167
|
+
* `isTemplateRead: true` "this row stands for a template read too"
|
|
2168
|
+
* `templateReadSites: [ … ]` WHICH sites, merged with any already recorded
|
|
2169
|
+
*
|
|
2170
|
+
* The site list is written even for a SINGLE site, unlike the read map's
|
|
2171
|
+
* `templateReadSites` convention (present only when a member is read on more than
|
|
2172
|
+
* one line). The two are not inconsistent, and the difference is what
|
|
2173
|
+
* `line_number` means on each: on a template-read row `line_number` IS a site, so a
|
|
2174
|
+
* one-element list adds nothing; here `line_number` is the SCRIPT's line, so for a
|
|
2175
|
+
* single dropped site the list is the only place that site exists. The divergence
|
|
2176
|
+
* is recorded in `archive/docs-pre-consolidation/analyzers-BRANCH-INVENTORY-2026-10-07.md` rather than smoothed
|
|
2177
|
+
* over. Returns whether anything was recorded, so a caller could assert the empty
|
|
2178
|
+
* case.
|
|
2179
|
+
*/
|
|
2180
|
+
static recordDroppedTemplateRead(survivor, dropped) {
|
|
2181
|
+
if (dropped.metadata?.isTemplateExpression !== true) return false;
|
|
2182
|
+
if (survivor.metadata?.isTemplateExpression === true) return false;
|
|
2183
|
+
const sites = new Set(survivor.metadata?.templateReadSites ?? []);
|
|
2184
|
+
for (const s of dropped.metadata?.templateReadSites ?? []) {
|
|
2185
|
+
if (typeof s === "number") sites.add(s);
|
|
2186
|
+
}
|
|
2187
|
+
if (typeof dropped.line_number === "number") sites.add(dropped.line_number);
|
|
2188
|
+
if (sites.size === 0) return false;
|
|
2189
|
+
survivor.metadata = {
|
|
2190
|
+
...survivor.metadata ?? {},
|
|
2191
|
+
isTemplateRead: true,
|
|
2192
|
+
templateReadSites: [...sites].sort((a, b) => a - b)
|
|
2193
|
+
};
|
|
2194
|
+
return true;
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* F23: is this attribute one of the two EVENT CARRIERS — the two spellings that
|
|
2198
|
+
* make their value a function Svelte calls, and therefore the only two whose
|
|
2199
|
+
* arrow parameters bind?
|
|
2200
|
+
*
|
|
2201
|
+
* `eventCarrierName` is deliberately narrower than "an attribute whose name
|
|
2202
|
+
* starts with on": `onclick` is an event only on an INTRINSIC tag (on a
|
|
2203
|
+
* component it is a prop, and the parent attribute node does not carry the tag
|
|
2204
|
+
* this walk already resolved), so the caller's own `on:`/`onclick` decision is
|
|
2205
|
+
* what this reads. A bare `on:click={save}` has no arrow at all and
|
|
2206
|
+
* `arrowParams` returns nothing, which is correct: a bare identifier is a call
|
|
2207
|
+
* target and is handled by `onHandlerCall`.
|
|
2208
|
+
*/
|
|
2209
|
+
eventCarrierName(parent) {
|
|
2210
|
+
if (!parent || parent.type !== "attribute") return null;
|
|
2211
|
+
const name = this.attributeName(parent);
|
|
2212
|
+
if (!name) return null;
|
|
2213
|
+
if (name.startsWith("on:")) return name;
|
|
2214
|
+
return isOnEventAttrName(name) && name.length > 4 ? name : null;
|
|
2215
|
+
}
|
|
2216
|
+
/** `shadowed` widened by the parameters written at the head of `text`. */
|
|
2217
|
+
withArrowParams(shadowed, text) {
|
|
2218
|
+
const params = _RealSvelteAnalyzer.arrowParams(text);
|
|
2219
|
+
if (params.length === 0) return shadowed;
|
|
2220
|
+
const out = new Set(shadowed);
|
|
2221
|
+
for (const p of params) out.add(p);
|
|
2222
|
+
return out;
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* F23: the bound names of an arrow function's parameters, read off the text
|
|
2226
|
+
* before the FIRST top-level `=>`. Pure-String scanning, no regex — this
|
|
2227
|
+
* family's regex census is 0 and stays 0.
|
|
2228
|
+
*
|
|
2229
|
+
* `(evt) =>` gives `evt`; `() =>` gives nothing; `async ({ data }) =>` gives
|
|
2230
|
+
* `data`. Inside a DESTRUCTURING pattern an identifier immediately after a `:`
|
|
2231
|
+
* is the rename KEY (`{ a: local }` binds `local`, not `a`) and one inside
|
|
2232
|
+
* `[ ]` is a COMPUTED key, which is a read rather than a binding — both are
|
|
2233
|
+
* skipped rather than over-shadowed, because over-shadowing silently drops a
|
|
2234
|
+
* true read instead of publishing a false one.
|
|
2235
|
+
*/
|
|
2236
|
+
static arrowParams(text) {
|
|
2237
|
+
const out = [];
|
|
2238
|
+
let depth = 0;
|
|
2239
|
+
let arrow = -1;
|
|
2240
|
+
for (let i = 0; i < text.length; i++) {
|
|
2241
|
+
const c = text[i];
|
|
2242
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
2243
|
+
else if (c === ")" || c === "]" || c === "}") depth--;
|
|
2244
|
+
else if (depth === 0 && c === "=" && text[i + 1] === ">") {
|
|
2245
|
+
arrow = i;
|
|
2246
|
+
break;
|
|
2247
|
+
} else if (c === ";") break;
|
|
2248
|
+
}
|
|
2249
|
+
if (arrow < 0) return out;
|
|
2250
|
+
let head = text.slice(0, arrow).trim();
|
|
2251
|
+
if (head.startsWith("async")) head = head.slice(5).trim();
|
|
2252
|
+
if (head.startsWith("(") && head.endsWith(")")) head = head.slice(1, -1);
|
|
2253
|
+
else if (!isIdentifier(head)) return out;
|
|
2254
|
+
const isWordChar = (ch) => ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
|
|
2255
|
+
const binding = (raw) => {
|
|
2256
|
+
let t = raw.trim();
|
|
2257
|
+
if (!t) return;
|
|
2258
|
+
if (t.startsWith("...")) t = t.slice(3).trim();
|
|
2259
|
+
if (!t) return;
|
|
2260
|
+
const destructured = t.indexOf("{") >= 0 || t.indexOf("[") >= 0;
|
|
2261
|
+
if (!destructured) {
|
|
2262
|
+
let end = t.length;
|
|
2263
|
+
for (let i2 = 0; i2 < t.length; i2++) {
|
|
2264
|
+
if (t[i2] === "=" || t[i2] === ":") {
|
|
2265
|
+
end = i2;
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const w = t.slice(0, end).trim();
|
|
2270
|
+
if (isIdentifier(w)) out.push(w);
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
let bracket = 0;
|
|
2274
|
+
let i = 0;
|
|
2275
|
+
while (i < t.length) {
|
|
2276
|
+
const c = t[i];
|
|
2277
|
+
if (c === "[") {
|
|
2278
|
+
bracket++;
|
|
2279
|
+
i++;
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
2282
|
+
if (c === "]") {
|
|
2283
|
+
if (bracket > 0) bracket--;
|
|
2284
|
+
i++;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
if (bracket > 0 || !isWordChar(c) || c >= "0" && c <= "9") {
|
|
2288
|
+
i++;
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
let j = i;
|
|
2292
|
+
while (j < t.length && isWordChar(t[j])) j++;
|
|
2293
|
+
const w = t.slice(i, j);
|
|
2294
|
+
let k = i - 1;
|
|
2295
|
+
while (k >= 0 && (t[k] === " " || t[k] === " ")) k--;
|
|
2296
|
+
if (!(t[k] === ":")) out.push(w);
|
|
2297
|
+
i = j;
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
let d = 0;
|
|
2301
|
+
let token = "";
|
|
2302
|
+
for (const ch of head) {
|
|
2303
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
2304
|
+
d++;
|
|
2305
|
+
token += ch;
|
|
2306
|
+
continue;
|
|
2307
|
+
}
|
|
2308
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
2309
|
+
d--;
|
|
2310
|
+
if (d < 0) break;
|
|
2311
|
+
token += ch;
|
|
2312
|
+
if (d === 0) binding(token);
|
|
2313
|
+
token = "";
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
if (d === 0 && ch === ",") {
|
|
2317
|
+
binding(token);
|
|
2318
|
+
token = "";
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
token += ch;
|
|
2322
|
+
}
|
|
2323
|
+
binding(token);
|
|
2324
|
+
return [...new Set(out)];
|
|
2325
|
+
}
|
|
2326
|
+
/** D4: last all-identifier segment of a dotted render target
|
|
2327
|
+
* (`ns.row(it)` -> row; `a . b . c()` -> c). Returns undefined unless
|
|
2328
|
+
* every segment is a bare identifier — the caller keeps its fallback. */
|
|
2329
|
+
dottedRenderLeaf(text) {
|
|
2330
|
+
let body = text.trim();
|
|
2331
|
+
if (body.endsWith("}")) body = body.slice(0, -1);
|
|
2332
|
+
const paren = body.indexOf("(");
|
|
2333
|
+
if (paren >= 0) body = body.slice(0, paren);
|
|
2334
|
+
const parts = body.split(".").map((p) => {
|
|
2335
|
+
let q = p.trim();
|
|
2336
|
+
if (q.endsWith("?")) q = q.slice(0, -1).trim();
|
|
2337
|
+
return q;
|
|
2338
|
+
});
|
|
2339
|
+
if (parts.length < 2 || !parts.every((p) => isIdentifier(p))) return void 0;
|
|
2340
|
+
return parts[parts.length - 1];
|
|
2341
|
+
}
|
|
2342
|
+
/** {@html raw} / {@const total = expr} / {@debug expr} — body is a direct
|
|
2343
|
+
* svelte_raw_text child of the tag (no `expression` wrapper). */
|
|
2344
|
+
collectTagExpressionIdentifiers(node, out, shadowed, sites) {
|
|
2345
|
+
const textNode = this.expressionTextNode(node);
|
|
2346
|
+
const baseLine = textNode ? textNode.startPosition.row + 1 : 0;
|
|
2347
|
+
let text = textNode?.text ?? "";
|
|
2348
|
+
let base = baseLine;
|
|
2349
|
+
if (node.type === "const_tag") {
|
|
2350
|
+
const eq = this.topLevelEq(text);
|
|
2351
|
+
if (eq >= 0) {
|
|
2352
|
+
this.collectBindingPattern(text.slice(0, eq), out, shadowed, baseLine, sites);
|
|
2353
|
+
base = baseLine + _RealSvelteAnalyzer.linesBefore(text, eq + 1);
|
|
2354
|
+
text = text.slice(eq + 1);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
this.collectIdentifiersFromText(text, out, shadowed, base, sites);
|
|
2358
|
+
}
|
|
2359
|
+
/** Index of the first top-level `=` (not inside () / [] / {}) in `text`.*/
|
|
2360
|
+
topLevelEq(text) {
|
|
2361
|
+
let depth = 0;
|
|
2362
|
+
for (let i = 0; i < text.length; i++) {
|
|
2363
|
+
const c = text[i];
|
|
2364
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
2365
|
+
else if (c === ")" || c === "]" || c === "}") depth--;
|
|
2366
|
+
else if (c === "=" && depth === 0) return i;
|
|
2367
|
+
}
|
|
2368
|
+
return -1;
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* M59 (#825) follow-up, measured on shadcn-svelte forms: the handler's
|
|
2372
|
+
* NAME and its LINE travel together. The old text-only leaf plus the
|
|
2373
|
+
* expression node's line split them across lines for multiline arrow
|
|
2374
|
+
* bodies. Same three rules as before (first call target, bare identifier,
|
|
2375
|
+
* dotted callable leaf), each now carrying its offset into the raw text.
|
|
2376
|
+
*/
|
|
2377
|
+
onHandlerCall(attr) {
|
|
2378
|
+
const value = this.findChildByType(attr, "expression");
|
|
2379
|
+
if (!value) return void 0;
|
|
2380
|
+
let text = "";
|
|
2381
|
+
let baseLine = 0;
|
|
2382
|
+
const stack = [value];
|
|
2383
|
+
while (stack.length > 0) {
|
|
2384
|
+
const m = stack.pop();
|
|
2385
|
+
if (m.type === "svelte_raw_text") {
|
|
2386
|
+
text = m.text;
|
|
2387
|
+
baseLine = m.startPosition.row + 1;
|
|
2388
|
+
break;
|
|
2389
|
+
}
|
|
2390
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
2391
|
+
const c = m.namedChild(i);
|
|
2392
|
+
if (c) stack.push(c);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
if (!text) return void 0;
|
|
2396
|
+
const at = (offset) => baseLine + _RealSvelteAnalyzer.linesBefore(text, offset);
|
|
2397
|
+
for (let i = 0; i < text.length; i++) {
|
|
2398
|
+
const ch = text[i];
|
|
2399
|
+
if (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_" || ch === "$") {
|
|
2400
|
+
let end = i;
|
|
2401
|
+
while (end < text.length) {
|
|
2402
|
+
const c = text[end];
|
|
2403
|
+
if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "$") end++;
|
|
2404
|
+
else break;
|
|
2405
|
+
}
|
|
2406
|
+
const ident = text.slice(i, end);
|
|
2407
|
+
if (end < text.length && text[end] === "(") {
|
|
2408
|
+
let back = i - 1;
|
|
2409
|
+
while (back >= 0 && (text[back] === " " || text[back] === " " || text[back] === "\n")) back--;
|
|
2410
|
+
if (back < 0 || text[back] !== ".") return { name: ident, line: at(i) };
|
|
2411
|
+
}
|
|
2412
|
+
i = end;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
if (isIdentifier(text) && !NON_CALLABLE_LITERALS.has(text)) return { name: text, line: at(0) };
|
|
2416
|
+
const parts = text.split(".");
|
|
2417
|
+
if (parts.length > 1 && parts.every((p) => isIdentifier(p))) {
|
|
2418
|
+
const leaf = parts[parts.length - 1];
|
|
2419
|
+
if (NON_CALLABLE_LITERALS.has(leaf)) return void 0;
|
|
2420
|
+
return { name: leaf, line: at(text.lastIndexOf(leaf)) };
|
|
2421
|
+
}
|
|
2422
|
+
return void 0;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* #419-D - evidence-tier classification for EVERY emitted relationship.
|
|
2426
|
+
* Same rules as the Vue/JVM family. Template RENDERS are
|
|
2427
|
+
* usage_contextual (component usage by tag name); the delegate's script `new`
|
|
2428
|
+
* `instantiates` rows keep their structural tier.
|
|
2429
|
+
*/
|
|
2430
|
+
classifyEvidenceTier(relationships) {
|
|
2431
|
+
for (const r of relationships) {
|
|
2432
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
2433
|
+
if (md.evidenceClass) continue;
|
|
2434
|
+
const t = String(r.type ?? r.relationshipType ?? "");
|
|
2435
|
+
const conf = typeof r.confidence === "number" ? r.confidence : 1;
|
|
2436
|
+
if (t === "contains" || t === "exports") {
|
|
2437
|
+
md.evidenceClass = "structural";
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (t === "references") {
|
|
2441
|
+
md.evidenceClass = md.isImport ? "import_based" : "reference_qualified";
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
if (t === "extends" || t === "implements" || t === "overrides") {
|
|
2445
|
+
md.evidenceClass = conf >= 1 ? "structural" : conf >= 0.85 ? "usage_contextual" : "inferred";
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2448
|
+
if (t === "instantiates") {
|
|
2449
|
+
md.evidenceClass = md.isComponentUsage ? "usage_contextual" : "structural";
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
md.evidenceClass = "usage_contextual";
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
findAncestor(node, predicate) {
|
|
2456
|
+
let current = node.parent;
|
|
2457
|
+
while (current) {
|
|
2458
|
+
if (predicate(current)) return current;
|
|
2459
|
+
current = current.parent;
|
|
2460
|
+
}
|
|
2461
|
+
return null;
|
|
2462
|
+
}
|
|
2463
|
+
findDescendant(node, predicate) {
|
|
2464
|
+
const stack = [node];
|
|
2465
|
+
while (stack.length > 0) {
|
|
2466
|
+
const n = stack.pop();
|
|
2467
|
+
if (!(n.startIndex === node.startIndex && n.endIndex === node.endIndex) && predicate(n)) return n;
|
|
2468
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2469
|
+
const c = n.namedChild(i);
|
|
2470
|
+
if (c) stack.push(c);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
return null;
|
|
2474
|
+
}
|
|
2475
|
+
elementTagName(el) {
|
|
2476
|
+
return this.elementTagNameNode(el)?.text;
|
|
2477
|
+
}
|
|
2478
|
+
/** The `tag_name` node itself — the node that SPELLS the tag (M59 #825: the
|
|
2479
|
+
* Instantiates edge for a component usage is about that name, so its line
|
|
2480
|
+
* must come from the tag, not from the element's opening bracket). */
|
|
2481
|
+
elementTagNameNode(el) {
|
|
2482
|
+
const startTag = this.findChildByType(el, "start_tag") || this.findChildByType(el, "self_closing_tag");
|
|
2483
|
+
if (startTag) {
|
|
2484
|
+
const tag = this.findChildByType(startTag, "tag_name");
|
|
2485
|
+
if (tag) return tag;
|
|
2486
|
+
}
|
|
2487
|
+
return this.findChildByType(el, "tag_name");
|
|
2488
|
+
}
|
|
2489
|
+
attributeName(attr) {
|
|
2490
|
+
const name = this.findChildByType(attr, "attribute_name");
|
|
2491
|
+
return name ? name.text : "";
|
|
2492
|
+
}
|
|
2493
|
+
offsetRange(range, lineOffset, colOffset, indexOffset = 0) {
|
|
2494
|
+
const out = { ...range };
|
|
2495
|
+
const firstLine = out.startLine === 1;
|
|
2496
|
+
out.startLine += lineOffset;
|
|
2497
|
+
out.endLine += lineOffset;
|
|
2498
|
+
if (firstLine && colOffset) {
|
|
2499
|
+
out.startColumn += colOffset;
|
|
2500
|
+
if (out.endLine === out.startLine) out.endColumn += colOffset;
|
|
2501
|
+
}
|
|
2502
|
+
if (indexOffset && out.startIndex !== void 0) out.startIndex += indexOffset;
|
|
2503
|
+
if (indexOffset && out.endIndex !== void 0) out.endIndex += indexOffset;
|
|
2504
|
+
return out;
|
|
2505
|
+
}
|
|
2506
|
+
findChildByType(node, type) {
|
|
2507
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2508
|
+
const c = node.namedChild(i);
|
|
2509
|
+
if (c && c.type === type) return c;
|
|
2510
|
+
}
|
|
2511
|
+
return null;
|
|
2512
|
+
}
|
|
2513
|
+
nodeToRange(node) {
|
|
2514
|
+
return {
|
|
2515
|
+
startLine: node.startPosition.row + 1,
|
|
2516
|
+
endLine: node.endPosition.row + 1,
|
|
2517
|
+
// Columns are 1-based everywhere else in the codebase (+1); this
|
|
2518
|
+
// analyzer silently emitted 0-based columns.
|
|
2519
|
+
startColumn: node.startPosition.column + 1,
|
|
2520
|
+
endColumn: node.endPosition.column + 1,
|
|
2521
|
+
startIndex: node.startIndex,
|
|
2522
|
+
endIndex: node.endIndex
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
export {
|
|
2527
|
+
RealSvelteAnalyzer,
|
|
2528
|
+
containsWhitespace,
|
|
2529
|
+
isDottedIdentifierChain,
|
|
2530
|
+
isUriSchemeSpecifier,
|
|
2531
|
+
splitAwaitHead
|
|
2532
|
+
};
|