@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,2680 @@
|
|
|
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-vue-analyzer.ts
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
function shiftMetadataLineMirrors(md, lineOffset) {
|
|
21
|
+
if (!md || lineOffset === 0) return;
|
|
22
|
+
for (const [k, v] of Object.entries(md)) {
|
|
23
|
+
if (!k.toLowerCase().includes("line")) continue;
|
|
24
|
+
if (typeof v === "number") md[k] = v + lineOffset;
|
|
25
|
+
else if (Array.isArray(v) && v.some((x) => typeof x === "number")) {
|
|
26
|
+
md[k] = v.map((x) => typeof x === "number" ? x + lineOffset : x);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
var HTML_BUILTIN_TAGS = /* @__PURE__ */ new Set([
|
|
31
|
+
"a",
|
|
32
|
+
"abbr",
|
|
33
|
+
"address",
|
|
34
|
+
"area",
|
|
35
|
+
"article",
|
|
36
|
+
"aside",
|
|
37
|
+
"audio",
|
|
38
|
+
"b",
|
|
39
|
+
"base",
|
|
40
|
+
"bdi",
|
|
41
|
+
"bdo",
|
|
42
|
+
"blockquote",
|
|
43
|
+
"body",
|
|
44
|
+
"br",
|
|
45
|
+
"button",
|
|
46
|
+
"canvas",
|
|
47
|
+
"caption",
|
|
48
|
+
"cite",
|
|
49
|
+
"code",
|
|
50
|
+
"col",
|
|
51
|
+
"colgroup",
|
|
52
|
+
"data",
|
|
53
|
+
"datalist",
|
|
54
|
+
"dd",
|
|
55
|
+
"del",
|
|
56
|
+
"details",
|
|
57
|
+
"dfn",
|
|
58
|
+
"dialog",
|
|
59
|
+
"div",
|
|
60
|
+
"dl",
|
|
61
|
+
"dt",
|
|
62
|
+
"em",
|
|
63
|
+
"embed",
|
|
64
|
+
"fieldset",
|
|
65
|
+
"figcaption",
|
|
66
|
+
"figure",
|
|
67
|
+
"footer",
|
|
68
|
+
"form",
|
|
69
|
+
"h1",
|
|
70
|
+
"h2",
|
|
71
|
+
"h3",
|
|
72
|
+
"h4",
|
|
73
|
+
"h5",
|
|
74
|
+
"h6",
|
|
75
|
+
"head",
|
|
76
|
+
"header",
|
|
77
|
+
"hgroup",
|
|
78
|
+
"hr",
|
|
79
|
+
"html",
|
|
80
|
+
"i",
|
|
81
|
+
"iframe",
|
|
82
|
+
"img",
|
|
83
|
+
"input",
|
|
84
|
+
"ins",
|
|
85
|
+
"kbd",
|
|
86
|
+
"label",
|
|
87
|
+
"legend",
|
|
88
|
+
"li",
|
|
89
|
+
"link",
|
|
90
|
+
"main",
|
|
91
|
+
"map",
|
|
92
|
+
"mark",
|
|
93
|
+
"menu",
|
|
94
|
+
"meta",
|
|
95
|
+
"meter",
|
|
96
|
+
"nav",
|
|
97
|
+
"noscript",
|
|
98
|
+
"object",
|
|
99
|
+
"ol",
|
|
100
|
+
"optgroup",
|
|
101
|
+
"option",
|
|
102
|
+
"output",
|
|
103
|
+
"p",
|
|
104
|
+
"picture",
|
|
105
|
+
"pre",
|
|
106
|
+
"progress",
|
|
107
|
+
"q",
|
|
108
|
+
"rp",
|
|
109
|
+
"rt",
|
|
110
|
+
"ruby",
|
|
111
|
+
"s",
|
|
112
|
+
"samp",
|
|
113
|
+
"script",
|
|
114
|
+
"search",
|
|
115
|
+
"section",
|
|
116
|
+
"select",
|
|
117
|
+
"slot",
|
|
118
|
+
"small",
|
|
119
|
+
"source",
|
|
120
|
+
"span",
|
|
121
|
+
"strong",
|
|
122
|
+
"style",
|
|
123
|
+
"sub",
|
|
124
|
+
"summary",
|
|
125
|
+
"sup",
|
|
126
|
+
"table",
|
|
127
|
+
"tbody",
|
|
128
|
+
"td",
|
|
129
|
+
"template",
|
|
130
|
+
"textarea",
|
|
131
|
+
"tfoot",
|
|
132
|
+
"th",
|
|
133
|
+
"thead",
|
|
134
|
+
"time",
|
|
135
|
+
"title",
|
|
136
|
+
"tr",
|
|
137
|
+
"track",
|
|
138
|
+
"u",
|
|
139
|
+
"ul",
|
|
140
|
+
"var",
|
|
141
|
+
"video",
|
|
142
|
+
"wbr",
|
|
143
|
+
// SVG
|
|
144
|
+
"svg",
|
|
145
|
+
"path",
|
|
146
|
+
"g",
|
|
147
|
+
"rect",
|
|
148
|
+
"circle",
|
|
149
|
+
"ellipse",
|
|
150
|
+
"line",
|
|
151
|
+
"polyline",
|
|
152
|
+
"polygon",
|
|
153
|
+
"text",
|
|
154
|
+
"tspan",
|
|
155
|
+
"use",
|
|
156
|
+
"defs",
|
|
157
|
+
"linearGradient",
|
|
158
|
+
"radialGradient",
|
|
159
|
+
"stop",
|
|
160
|
+
"mask",
|
|
161
|
+
"pattern",
|
|
162
|
+
"marker",
|
|
163
|
+
"view",
|
|
164
|
+
"symbol",
|
|
165
|
+
"clipPath",
|
|
166
|
+
// SVG — the FILTER module and the rest of the element vocabulary (F30, 2026-10-12).
|
|
167
|
+
//
|
|
168
|
+
// The list above covered the SVG elements a chart OR a decorative icon uses and
|
|
169
|
+
// stopped there, so `<filter>` and every filter PRIMITIVE fell through the builtin
|
|
170
|
+
// filter and were published as component usages: `<feGaussianBlur/>` emitted
|
|
171
|
+
// `instantiates -> feGaussianBlur`, a target no file in any corpus declares. Measured
|
|
172
|
+
// through production `parseFile` over the lane's 1,162-file draw (element-plus,
|
|
173
|
+
// vuejs, vue-element-admin, vue-core): 377 such edges from just six primitives —
|
|
174
|
+
// feGaussianBlur 64, feFlood 63, feColorMatrix 63, feOffset 63, feBlend 63,
|
|
175
|
+
// feComposite 61 — every one of them a dangling endpoint of exactly the class F23/F26
|
|
176
|
+
// removed for reads. An unknown namespace still fails toward VISIBILITY (see
|
|
177
|
+
// BUILTIN_NAMESPACES below); an element the SVG spec DEFINES is markup, and the rule
|
|
178
|
+
// this list encodes is that a real element is not a component — not that a lowercase
|
|
179
|
+
// name is not a component.
|
|
180
|
+
//
|
|
181
|
+
// NO DOUBLE QUOTES AND NO CLOSING-BRACKET-PAREN PAIR IN THIS COMMENT OR THE TWO
|
|
182
|
+
// BELOW, and that is a constraint, not a style preference:
|
|
183
|
+
// verify-cross-language-consistency.ts extracts this literal by slicing from the
|
|
184
|
+
// `new Set` open anchor to the FIRST closing-bracket-paren pair after it, then matching
|
|
185
|
+
// every quoted string in that slice. So a quoted phrase in a comment here becomes a
|
|
186
|
+
// PHANTOM TAG, and that pair ends the slice early — silently dropping the very tags the
|
|
187
|
+
// fix adds. Measured twice, not assumed: the first draft of this comment quoted the
|
|
188
|
+
// rule and the gate reported extra tags, and the second draft wrote the pair out and
|
|
189
|
+
// the gate reported every new SVG tag as missing from this list only.
|
|
190
|
+
"filter",
|
|
191
|
+
"feBlend",
|
|
192
|
+
"feColorMatrix",
|
|
193
|
+
"feComponentTransfer",
|
|
194
|
+
"feComposite",
|
|
195
|
+
"feConvolveMatrix",
|
|
196
|
+
"feDiffuseLighting",
|
|
197
|
+
"feDisplacementMap",
|
|
198
|
+
"feDistantLight",
|
|
199
|
+
"feDropShadow",
|
|
200
|
+
"feFlood",
|
|
201
|
+
"feFuncA",
|
|
202
|
+
"feFuncB",
|
|
203
|
+
"feFuncG",
|
|
204
|
+
"feFuncR",
|
|
205
|
+
"feGaussianBlur",
|
|
206
|
+
"feImage",
|
|
207
|
+
"feMerge",
|
|
208
|
+
"feMergeNode",
|
|
209
|
+
"feMorphology",
|
|
210
|
+
"feOffset",
|
|
211
|
+
"fePointLight",
|
|
212
|
+
"feSpecularLighting",
|
|
213
|
+
"feSpotLight",
|
|
214
|
+
"feTile",
|
|
215
|
+
"feTurbulence",
|
|
216
|
+
// the rest of SVG 2's element list, plus the SVG 1.1 elements browsers still parse.
|
|
217
|
+
// Counted separately from the block above on purpose: the block above is measured on
|
|
218
|
+
// the corpus, this one is the SPEC's vocabulary, and the difference is recorded rather
|
|
219
|
+
// than blurred (same honesty rule the svelte lane's read map uses for its site lists).
|
|
220
|
+
"switch",
|
|
221
|
+
"foreignObject",
|
|
222
|
+
"desc",
|
|
223
|
+
"metadata",
|
|
224
|
+
"textPath",
|
|
225
|
+
"tref",
|
|
226
|
+
"mpath",
|
|
227
|
+
"animate",
|
|
228
|
+
"animateMotion",
|
|
229
|
+
"animateTransform",
|
|
230
|
+
"set",
|
|
231
|
+
"discard",
|
|
232
|
+
"solidColor",
|
|
233
|
+
"mesh",
|
|
234
|
+
"meshgradient",
|
|
235
|
+
"meshpatch",
|
|
236
|
+
"meshrow",
|
|
237
|
+
"hatch",
|
|
238
|
+
"hatchpath",
|
|
239
|
+
"altGlyph",
|
|
240
|
+
"altGlyphDef",
|
|
241
|
+
"altGlyphItem",
|
|
242
|
+
"glyphRef",
|
|
243
|
+
"animateColor",
|
|
244
|
+
"cursor",
|
|
245
|
+
"color-profile",
|
|
246
|
+
"font",
|
|
247
|
+
"font-face",
|
|
248
|
+
"font-face-format",
|
|
249
|
+
"font-face-name",
|
|
250
|
+
"font-face-src",
|
|
251
|
+
"font-face-uri",
|
|
252
|
+
"glyph",
|
|
253
|
+
"hkern",
|
|
254
|
+
"missing-glyph",
|
|
255
|
+
"vkern"
|
|
256
|
+
]);
|
|
257
|
+
var VUE_BUILTIN_TAGS = /* @__PURE__ */ new Set([
|
|
258
|
+
"component",
|
|
259
|
+
"transition",
|
|
260
|
+
"transition-group",
|
|
261
|
+
"keep-alive",
|
|
262
|
+
"teleport",
|
|
263
|
+
"suspense",
|
|
264
|
+
"fragment"
|
|
265
|
+
]);
|
|
266
|
+
var BUILTIN_NAMESPACES = /* @__PURE__ */ new Set([
|
|
267
|
+
"svg",
|
|
268
|
+
"math",
|
|
269
|
+
"mathml",
|
|
270
|
+
"xlink",
|
|
271
|
+
"html",
|
|
272
|
+
"xhtml"
|
|
273
|
+
]);
|
|
274
|
+
var VUE_COMPILER_MACROS = /* @__PURE__ */ new Set([
|
|
275
|
+
"defineProps",
|
|
276
|
+
"defineEmits",
|
|
277
|
+
"defineExpose",
|
|
278
|
+
"withDefaults",
|
|
279
|
+
"defineModel",
|
|
280
|
+
"defineSlots",
|
|
281
|
+
"defineOptions"
|
|
282
|
+
]);
|
|
283
|
+
function vueRegistryName(tag) {
|
|
284
|
+
const candidates = [tag];
|
|
285
|
+
const kebab = tag.includes("-");
|
|
286
|
+
let camel = false;
|
|
287
|
+
if (!kebab) {
|
|
288
|
+
for (let ci = 1; ci < tag.length; ci++) {
|
|
289
|
+
const cc = tag.charCodeAt(ci);
|
|
290
|
+
if (cc >= 65 && cc <= 90) {
|
|
291
|
+
camel = true;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (!kebab && !camel) return { target: tag, candidates, normalized: false };
|
|
297
|
+
const pascal = kebab ? tag.split("-").filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") : tag.charAt(0).toUpperCase() + tag.slice(1);
|
|
298
|
+
if (pascal.length === 0 || pascal === tag) return { target: tag, candidates, normalized: false };
|
|
299
|
+
const camelized = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
300
|
+
if (camelized !== tag) candidates.push(camelized);
|
|
301
|
+
candidates.push(pascal);
|
|
302
|
+
return { target: pascal, candidates, normalized: true };
|
|
303
|
+
}
|
|
304
|
+
function peelTypeWrappers(node) {
|
|
305
|
+
let cur = node;
|
|
306
|
+
for (let i = 0; i < 8; i++) {
|
|
307
|
+
const t = cur.type;
|
|
308
|
+
if (t !== "as_expression" && t !== "satisfies_expression" && t !== "parenthesized_expression" && t !== "non_null_expression") return cur;
|
|
309
|
+
const kids = cur.namedChildren;
|
|
310
|
+
const first = kids[0];
|
|
311
|
+
if (!first) return cur;
|
|
312
|
+
cur = first;
|
|
313
|
+
}
|
|
314
|
+
return cur;
|
|
315
|
+
}
|
|
316
|
+
function isVueModuleSpecifier(specifier) {
|
|
317
|
+
return specifier === "vue" || specifier.startsWith("vue/") || specifier.startsWith("@vue/");
|
|
318
|
+
}
|
|
319
|
+
function unquote(text) {
|
|
320
|
+
if (text.length >= 2) {
|
|
321
|
+
const first = text[0];
|
|
322
|
+
if ((first === '"' || first === "'") && text[text.length - 1] === first) {
|
|
323
|
+
return text.slice(1, -1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return text;
|
|
327
|
+
}
|
|
328
|
+
var DELEGATE_FILE_ACTOR = "(file)";
|
|
329
|
+
var RealVueAnalyzer = class _RealVueAnalyzer {
|
|
330
|
+
analyze(rootNode, content, filePath, context) {
|
|
331
|
+
const symbols = [];
|
|
332
|
+
const relationships = [];
|
|
333
|
+
const imports = [];
|
|
334
|
+
const exports = [];
|
|
335
|
+
const errors = [];
|
|
336
|
+
const patterns = {};
|
|
337
|
+
const moduleName = path.basename(filePath, path.extname(filePath));
|
|
338
|
+
patterns["Vue SFC"] = true;
|
|
339
|
+
if (rootNode.hasError) {
|
|
340
|
+
const errStack = [rootNode];
|
|
341
|
+
while (errStack.length > 0) {
|
|
342
|
+
const n = errStack.pop();
|
|
343
|
+
if (n.type === "ERROR" || n.isMissing) {
|
|
344
|
+
errors.push({
|
|
345
|
+
message: `Syntax ${n.type} detected. The Brain Engine is bypassing this broken node.`,
|
|
346
|
+
range: this.nodeToRange(n),
|
|
347
|
+
severity: "warning"
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
351
|
+
const child = n.namedChild(i);
|
|
352
|
+
if (child) errStack.push(child);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
symbols.push({
|
|
357
|
+
name: moduleName,
|
|
358
|
+
kind: ASTEngine.SymbolKind.Class,
|
|
359
|
+
range: this.nodeToRange(rootNode),
|
|
360
|
+
scope: "",
|
|
361
|
+
visibility: "public",
|
|
362
|
+
metadata: { isVueComponent: true, isSfc: true }
|
|
363
|
+
});
|
|
364
|
+
const templateStack = [rootNode];
|
|
365
|
+
const usageRows = /* @__PURE__ */ new Map();
|
|
366
|
+
const usageSpellings = /* @__PURE__ */ new Map();
|
|
367
|
+
let templateDecisions = 0;
|
|
368
|
+
const templateExprNames = /* @__PURE__ */ new Map();
|
|
369
|
+
const shadowStack = [{ node: rootNode, shadowed: /* @__PURE__ */ new Set() }];
|
|
370
|
+
while (shadowStack.length > 0) {
|
|
371
|
+
const { node: n, shadowed } = shadowStack.pop();
|
|
372
|
+
const t = n.type;
|
|
373
|
+
if (t === "script_element" || t === "style_element") continue;
|
|
374
|
+
const childShadowed = new Set(shadowed);
|
|
375
|
+
if (t === "element" || t === "template_element") {
|
|
376
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
377
|
+
const c = n.namedChild(i);
|
|
378
|
+
if (!c || c.type !== "start_tag") continue;
|
|
379
|
+
this.collectNodeShadowBindings(c, childShadowed);
|
|
380
|
+
}
|
|
381
|
+
const rawTag = this.elementTagSpelling(n);
|
|
382
|
+
const dotIdx = rawTag?.indexOf(".") ?? -1;
|
|
383
|
+
const colonIdx = rawTag?.indexOf(":") ?? -1;
|
|
384
|
+
const tag = dotIdx > 0 ? rawTag.slice(0, dotIdx) : rawTag;
|
|
385
|
+
const member = dotIdx > 0 ? rawTag.slice(dotIdx + 1) : void 0;
|
|
386
|
+
const nsPrefix = colonIdx > 0 ? rawTag.slice(0, colonIdx) : void 0;
|
|
387
|
+
const isBuiltinNamespace = nsPrefix !== void 0 && BUILTIN_NAMESPACES.has(nsPrefix);
|
|
388
|
+
if (tag && !isBuiltinNamespace && !HTML_BUILTIN_TAGS.has(tag) && !VUE_BUILTIN_TAGS.has(tag)) {
|
|
389
|
+
const registry = vueRegistryName(tag);
|
|
390
|
+
const spellings = usageSpellings.get(registry.target);
|
|
391
|
+
if (spellings) {
|
|
392
|
+
if (!spellings.includes(tag)) {
|
|
393
|
+
spellings.push(tag);
|
|
394
|
+
const row = usageRows.get(registry.target);
|
|
395
|
+
row.metadata = { ...row.metadata, tagSpellingsWritten: [...spellings].sort() };
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
usageSpellings.set(registry.target, [tag]);
|
|
399
|
+
const tagNode = this.elementTagNameNode(n);
|
|
400
|
+
const row = {
|
|
401
|
+
source: moduleName,
|
|
402
|
+
target: registry.target,
|
|
403
|
+
// RENDERS, NOT INSTANTIATES (2026-10-24, extended vocabulary; the svelte
|
|
404
|
+
// precedent, Lead-ruled). A template component tag mounts an element the
|
|
405
|
+
// HOST RENDERER creates — the source contains no `new`, so an
|
|
406
|
+
// `instantiates` edge claimed an allocation site the file does not have
|
|
407
|
+
// (types.ts' Renders contract: "NOT `instantiates`: nothing is `new`ed —
|
|
408
|
+
// the host renderer constructs the element"). `instantiates` keeps its
|
|
409
|
+
// OWN rows: the JS/TS delegate's script `new` rows, copied by the seam
|
|
410
|
+
// below — the two relations stay distinct, exactly as svelte's migration
|
|
411
|
+
// kept 191 delegate `new` rows while moving 11,512 template rows.
|
|
412
|
+
// Measured before the relabel over the whole 1,155-file admitted
|
|
413
|
+
// population: 2,235 template rows (isComponentUsage:true) and 16
|
|
414
|
+
// delegate `new` rows = the 2,251 `instantiates` the last whole-
|
|
415
|
+
// population run published — so the relabel moves 2,235 rows and no
|
|
416
|
+
// edge is gained or lost.
|
|
417
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
418
|
+
confidence: 0.95,
|
|
419
|
+
// (#825): the `<Child/>` tag as written in the template.
|
|
420
|
+
...tagNode ? { line_number: tagNode.startPosition.row + 1 } : {},
|
|
421
|
+
metadata: {
|
|
422
|
+
// F9(b): the leaf of a dotted tag — see the comment at the filter.
|
|
423
|
+
// Merged HERE rather than spread above, because this literal's own
|
|
424
|
+
// `metadata` key comes later and a spread placed before it is
|
|
425
|
+
// silently overwritten (which is exactly what the first attempt did).
|
|
426
|
+
...member ? { member, componentPath: rawTag } : {},
|
|
427
|
+
// F31: the target is the registry name (see `vueRegistryName`). When it
|
|
428
|
+
// is not the spelling the template wrote, the edge says so and carries
|
|
429
|
+
// the written spelling plus Vue's lookup order, so nothing is lost and a
|
|
430
|
+
// reader can tell a normalized target from a literal one.
|
|
431
|
+
...registry.normalized ? {
|
|
432
|
+
// The family's reason code for "why this target string is what it
|
|
433
|
+
// is" (`svelte` writes `resolution: namespace-member` for its own
|
|
434
|
+
// non-literal target), so a reader has one key to look for.
|
|
435
|
+
resolution: "registry-name",
|
|
436
|
+
tagSpellingsWritten: [tag],
|
|
437
|
+
tagAsWritten: tag,
|
|
438
|
+
componentNameCandidates: registry.candidates,
|
|
439
|
+
componentNameNormalized: true
|
|
440
|
+
} : {},
|
|
441
|
+
isComponentUsage: true
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
usageRows.set(registry.target, row);
|
|
445
|
+
relationships.push(row);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (t === "directive_attribute") {
|
|
450
|
+
const dname = this.findChildByType(n, "directive_name")?.text;
|
|
451
|
+
if (dname === "v-if" || dname === "v-else-if" || dname === "v-for" || dname === "v-show") templateDecisions++;
|
|
452
|
+
const value = this.findChildByType(n, "quoted_attribute_value");
|
|
453
|
+
const attrVal = value ? this.findChildByType(value, "attribute_value") : null;
|
|
454
|
+
if (attrVal) {
|
|
455
|
+
if (dname === "v-for") {
|
|
456
|
+
this.collectVForUsage(attrVal.text, templateExprNames, childShadowed, attrVal.startPosition.row + 1);
|
|
457
|
+
} else if (dname === "#" || dname === "v-slot") {
|
|
458
|
+
} else {
|
|
459
|
+
this.collectIdentifiers(attrVal.text, templateExprNames, childShadowed, attrVal.startPosition.row + 1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (t === "interpolation") {
|
|
464
|
+
const raw = this.findChildByType(n, "raw_text");
|
|
465
|
+
if (raw) {
|
|
466
|
+
this.collectIdentifiers(raw.text, templateExprNames, childShadowed, raw.startPosition.row + 1);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
470
|
+
const child = n.namedChild(i);
|
|
471
|
+
if (child) shadowStack.push({ node: child, shadowed: childShadowed });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const scriptTreeCount = context?.sfcScriptTrees?.length ?? 0;
|
|
475
|
+
const pushedComponentNames = /* @__PURE__ */ new Set([moduleName]);
|
|
476
|
+
const pendingContracts = [];
|
|
477
|
+
const pendingInjections = [];
|
|
478
|
+
const pendingProvided = [];
|
|
479
|
+
let scriptCyclomaticSum = 0;
|
|
480
|
+
let dispatchedToTypeScript = false;
|
|
481
|
+
const MEMBER_TARGET = /* @__PURE__ */ new Set([
|
|
482
|
+
ASTEngine.RelationshipType.Contains,
|
|
483
|
+
ASTEngine.RelationshipType.Provides
|
|
484
|
+
]);
|
|
485
|
+
let componentIdentity = moduleName;
|
|
486
|
+
let declaredNameUnread = false;
|
|
487
|
+
let declaredNameFromLocalConst;
|
|
488
|
+
let declaredNameUnreadSetup = false;
|
|
489
|
+
let declaredNameFromOptions;
|
|
490
|
+
let optionsScriptIndex = 0;
|
|
491
|
+
for (let bi = 0; bi < scriptTreeCount; bi++) {
|
|
492
|
+
const scriptTree = context.sfcScriptTrees[bi];
|
|
493
|
+
const scriptStartLine = context.sfcScriptLines[bi];
|
|
494
|
+
const scriptStartColumn = context.sfcScriptCols[bi] ?? 0;
|
|
495
|
+
const scriptStartIndex = context?.sfcScriptStartIndex?.[bi] ?? 0;
|
|
496
|
+
const isTs = context?.sfcScriptLangs?.[bi] === "ts" || context?.sfcScriptLangs?.[bi] === "tsx";
|
|
497
|
+
if (isTs) dispatchedToTypeScript = true;
|
|
498
|
+
{
|
|
499
|
+
const scriptContent = scriptTree.rootNode?.text ?? "";
|
|
500
|
+
const lineOffset = scriptStartLine - 1;
|
|
501
|
+
const colOffset = scriptStartColumn;
|
|
502
|
+
const defaultExport = this.findExportDefault(scriptTree.rootNode);
|
|
503
|
+
const defaultObject = defaultExport ? this.findDescendantObject(defaultExport) : void 0;
|
|
504
|
+
if (defaultObject) patterns["Options API"] = true;
|
|
505
|
+
else patterns["Composition API"] = true;
|
|
506
|
+
let sub;
|
|
507
|
+
try {
|
|
508
|
+
sub = isTs ? new RealTypeScriptAnalyzer().analyze(scriptTree.rootNode, scriptContent, filePath) : new RealJavaScriptAnalyzer().analyze(scriptTree.rootNode, scriptContent, filePath, context);
|
|
509
|
+
} catch (e) {
|
|
510
|
+
sub = { symbols: [], relationships: [], imports: [], exports: [], errors: [], metadata: {} };
|
|
511
|
+
errors.push({
|
|
512
|
+
message: `script sub-parse failed: ${e?.message?.slice(0, 120)}`,
|
|
513
|
+
range: { startLine: scriptStartLine, startColumn: 0, endLine: scriptStartLine, endColumn: 0 },
|
|
514
|
+
severity: "warning"
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
scriptCyclomaticSum += Math.max(0, (sub.metadata?.cyclomaticComplexity ?? 1) - 1);
|
|
518
|
+
const defaultPayloads = /* @__PURE__ */ new Map();
|
|
519
|
+
if (defaultObject) {
|
|
520
|
+
for (const s of sub.symbols ?? []) {
|
|
521
|
+
if ((s.range.startIndex ?? 0) >= defaultObject.startIndex && (s.range.endIndex ?? 0) <= defaultObject.endIndex && (s.kind === ASTEngine.SymbolKind.Method || s.kind === ASTEngine.SymbolKind.Function) && !defaultPayloads.has(s.name)) {
|
|
522
|
+
defaultPayloads.set(s.name, s);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (defaultObject) {
|
|
527
|
+
let componentName = moduleName;
|
|
528
|
+
const namePair = defaultObject.namedChildren.find(
|
|
529
|
+
(c) => c.type === "pair" && c.childForFieldName("key")?.text === "name"
|
|
530
|
+
);
|
|
531
|
+
let declaredNameUnreadable = false;
|
|
532
|
+
if (namePair) {
|
|
533
|
+
const v = namePair.childForFieldName("value");
|
|
534
|
+
const declared = this.declaredNameValue(v, scriptTree.rootNode);
|
|
535
|
+
if (declared) {
|
|
536
|
+
componentName = declared.name;
|
|
537
|
+
declaredNameFromLocalConst = declared.constName;
|
|
538
|
+
} else if (v) declaredNameUnreadable = true;
|
|
539
|
+
}
|
|
540
|
+
declaredNameUnread = declaredNameUnread || declaredNameUnreadable;
|
|
541
|
+
if (componentName !== moduleName) {
|
|
542
|
+
const sfcSymbol = symbols.find((sy) => sy.kind === ASTEngine.SymbolKind.Class && sy.name === moduleName);
|
|
543
|
+
if (sfcSymbol) {
|
|
544
|
+
sfcSymbol.name = componentName;
|
|
545
|
+
sfcSymbol.metadata = {
|
|
546
|
+
...sfcSymbol.metadata ?? {},
|
|
547
|
+
moduleName,
|
|
548
|
+
isOptionsComponent: true,
|
|
549
|
+
...declaredNameUnreadable ? { declaredNameUnreadable: true } : {}
|
|
550
|
+
};
|
|
551
|
+
componentIdentity = componentName;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
pushedComponentNames.add(componentName);
|
|
555
|
+
this.extractOptionsApi(defaultObject, componentName, symbols, patterns, lineOffset, colOffset, defaultPayloads, scriptStartIndex, scriptTree.rootNode, relationships);
|
|
556
|
+
}
|
|
557
|
+
const defaultStart = defaultObject ? defaultObject.startIndex : -1;
|
|
558
|
+
const defaultEnd = defaultObject ? defaultObject.endIndex : -1;
|
|
559
|
+
for (const s of sub.symbols ?? []) {
|
|
560
|
+
if (defaultStart !== -1 && (s.range.startIndex ?? 0) >= defaultStart && (s.range.endIndex ?? 0) <= defaultEnd) continue;
|
|
561
|
+
s.range.startLine += lineOffset;
|
|
562
|
+
s.range.endLine += lineOffset;
|
|
563
|
+
if (s.range.startLine === 1 + lineOffset) s.range.startColumn += colOffset;
|
|
564
|
+
if (scriptStartIndex && s.range.startIndex !== void 0) s.range.startIndex += scriptStartIndex;
|
|
565
|
+
if (scriptStartIndex && s.range.endIndex !== void 0) s.range.endIndex += scriptStartIndex;
|
|
566
|
+
shiftMetadataLineMirrors(s.metadata, lineOffset);
|
|
567
|
+
symbols.push(s);
|
|
568
|
+
}
|
|
569
|
+
const delegateBases = /* @__PURE__ */ new Map();
|
|
570
|
+
for (const rb of sub.relationships ?? []) {
|
|
571
|
+
if (rb?.type !== ASTEngine.RelationshipType.Extends) continue;
|
|
572
|
+
if (typeof rb.source === "string" && rb.source !== "" && typeof rb.target === "string" && rb.target !== "") {
|
|
573
|
+
delegateBases.set(rb.source, rb.target);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (const r of sub.relationships ?? []) {
|
|
577
|
+
const tgt = r.target ?? "";
|
|
578
|
+
if (VUE_COMPILER_MACROS.has(tgt)) continue;
|
|
579
|
+
if (String(r.type ?? r.relationshipType) === ASTEngine.RelationshipType.Exports && tgt === "default") {
|
|
580
|
+
if (r.metadata?.isDefault !== true) continue;
|
|
581
|
+
}
|
|
582
|
+
if (typeof r.line_number === "number") r.line_number += lineOffset;
|
|
583
|
+
shiftMetadataLineMirrors(r.metadata, lineOffset);
|
|
584
|
+
if (r.source === DELEGATE_FILE_ACTOR) r.source = moduleName;
|
|
585
|
+
if (r.type === ASTEngine.RelationshipType.Imports) {
|
|
586
|
+
const mod = r.metadata?.module;
|
|
587
|
+
if (typeof mod === "string" && mod !== "" && typeof r.target === "string" && r.target !== "" && !r.target.includes(":") && !r.target.includes("/")) {
|
|
588
|
+
r.target = `${mod}:${r.target}`;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (r.type === ASTEngine.RelationshipType.Overrides) {
|
|
592
|
+
if (typeof r.source === "string" && typeof r.target === "string" && r.target !== "" && !r.target.includes(".")) {
|
|
593
|
+
const owner = r.source.slice(0, r.source.lastIndexOf("."));
|
|
594
|
+
const base = owner === "" ? void 0 : delegateBases.get(owner);
|
|
595
|
+
if (base) r.target = `${base}.${r.target}`;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
r.metadata = { ...r.metadata ?? {}, sfcDelegated: true, sfcLineOffset: lineOffset };
|
|
599
|
+
relationships.push(r);
|
|
600
|
+
}
|
|
601
|
+
const namedDefaultDeclaration = defaultExport ? (defaultExport.namedChildren ?? []).some(
|
|
602
|
+
(c) => c.type === "class_declaration" || c.type === "abstract_class_declaration" || c.type === "function_declaration" || c.type === "generator_function_declaration"
|
|
603
|
+
) : false;
|
|
604
|
+
if (isTs && defaultExport && !namedDefaultDeclaration) {
|
|
605
|
+
const alreadyPublished = relationships.some(
|
|
606
|
+
(x) => String(x.type ?? x.relationshipType) === ASTEngine.RelationshipType.Exports && String(x.target ?? "") === "default" && x.metadata?.isDefault === true
|
|
607
|
+
);
|
|
608
|
+
if (!alreadyPublished) {
|
|
609
|
+
relationships.push({
|
|
610
|
+
source: moduleName,
|
|
611
|
+
target: "default",
|
|
612
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
613
|
+
confidence: 1,
|
|
614
|
+
line_number: defaultExport.startPosition.row + 1 + lineOffset,
|
|
615
|
+
metadata: { isDefault: true, sfcDefaultExport: true }
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
for (const i of sub.imports ?? []) {
|
|
620
|
+
if (i.range) {
|
|
621
|
+
const shifted = this.shiftScriptRange(i.range, lineOffset, colOffset, scriptStartIndex);
|
|
622
|
+
i.range.startLine = shifted.startLine;
|
|
623
|
+
i.range.endLine = shifted.endLine;
|
|
624
|
+
i.range.startColumn = shifted.startColumn;
|
|
625
|
+
i.range.endColumn = shifted.endColumn;
|
|
626
|
+
if (shifted.startIndex !== void 0) i.range.startIndex = shifted.startIndex;
|
|
627
|
+
if (shifted.endIndex !== void 0) i.range.endIndex = shifted.endIndex;
|
|
628
|
+
}
|
|
629
|
+
imports.push(i);
|
|
630
|
+
}
|
|
631
|
+
for (const e of sub.exports ?? []) {
|
|
632
|
+
if (e.range) {
|
|
633
|
+
const shifted = this.shiftScriptRange(e.range, lineOffset, colOffset, scriptStartIndex);
|
|
634
|
+
e.range.startLine = shifted.startLine;
|
|
635
|
+
e.range.endLine = shifted.endLine;
|
|
636
|
+
e.range.startColumn = shifted.startColumn;
|
|
637
|
+
e.range.endColumn = shifted.endColumn;
|
|
638
|
+
if (shifted.startIndex !== void 0) e.range.startIndex = shifted.startIndex;
|
|
639
|
+
if (shifted.endIndex !== void 0) e.range.endIndex = shifted.endIndex;
|
|
640
|
+
}
|
|
641
|
+
exports.push(e);
|
|
642
|
+
}
|
|
643
|
+
for (const er of sub.errors ?? []) {
|
|
644
|
+
if (er.range) {
|
|
645
|
+
const shifted = this.shiftScriptRange(er.range, lineOffset, colOffset, scriptStartIndex);
|
|
646
|
+
er.range.startLine = shifted.startLine;
|
|
647
|
+
er.range.endLine = shifted.endLine;
|
|
648
|
+
er.range.startColumn = shifted.startColumn;
|
|
649
|
+
er.range.endColumn = shifted.endColumn;
|
|
650
|
+
if (shifted.startIndex !== void 0) er.range.startIndex = shifted.startIndex;
|
|
651
|
+
if (shifted.endIndex !== void 0) er.range.endIndex = shifted.endIndex;
|
|
652
|
+
}
|
|
653
|
+
errors.push(er);
|
|
654
|
+
}
|
|
655
|
+
{
|
|
656
|
+
const macroStack = [scriptTree.rootNode];
|
|
657
|
+
const seenMacroNames = /* @__PURE__ */ new Set();
|
|
658
|
+
while (macroStack.length > 0) {
|
|
659
|
+
const n = macroStack.pop();
|
|
660
|
+
if (n.type === "call_expression") {
|
|
661
|
+
const fn = n.childForFieldName("function");
|
|
662
|
+
if (fn && (fn.text === "defineProps" || fn.text === "defineEmits")) {
|
|
663
|
+
const args = n.childForFieldName("arguments");
|
|
664
|
+
const firstArg = args?.namedChild(0);
|
|
665
|
+
const which = fn.text === "defineProps" ? "props" : "emits";
|
|
666
|
+
const defaultsArg = which === "props" ? this.withDefaultsObject(n) : null;
|
|
667
|
+
const propDefaults = this.defaultsEntries(defaultsArg);
|
|
668
|
+
const defaultsExpression = defaultsArg && defaultsArg.type !== "object" ? defaultsArg.text : void 0;
|
|
669
|
+
const typeArgsNode = this.findChildByType(n, "type_arguments");
|
|
670
|
+
for (const part of this.contractTypeShape(typeArgsNode).participants) {
|
|
671
|
+
const partKey = `${which}:${part.name}`;
|
|
672
|
+
if (seenMacroNames.has(partKey)) continue;
|
|
673
|
+
seenMacroNames.add(partKey);
|
|
674
|
+
pendingContracts.push({
|
|
675
|
+
name: part.name,
|
|
676
|
+
marker: which === "props" ? "isPropContract" : "isEmitContract",
|
|
677
|
+
resolved: this.hasTopLevelDeclaration(part.name, n),
|
|
678
|
+
line: this.offsetRange(this.nodeToRange(part.node), lineOffset, colOffset, scriptStartIndex).startLine,
|
|
679
|
+
lineOffset,
|
|
680
|
+
// F3, and a consequence of moving the participant push UP here:
|
|
681
|
+
// this row is now the FIRST to claim `seenMacroNames`, so it is
|
|
682
|
+
// also the row the F12 `macroContractName` branch skips. Without
|
|
683
|
+
// the defaults carried along, adding F14 silently stripped every
|
|
684
|
+
// withDefaults default off every NAMED contract — the fixture
|
|
685
|
+
// caught it, which is the only reason it is worth recording.
|
|
686
|
+
...propDefaults.size > 0 ? { defaults: propDefaults } : {},
|
|
687
|
+
...defaultsExpression ? { defaultsExpression } : {}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
const declRefs = this.propDeclarationEntries(firstArg ?? void 0);
|
|
691
|
+
for (const d of declRefs) {
|
|
692
|
+
const contractKey = `${which}:${d.name}`;
|
|
693
|
+
if (seenMacroNames.has(contractKey)) continue;
|
|
694
|
+
seenMacroNames.add(contractKey);
|
|
695
|
+
pendingContracts.push({
|
|
696
|
+
name: d.name,
|
|
697
|
+
marker: which === "props" ? "isPropContract" : "isEmitContract",
|
|
698
|
+
// Same certainty as the macro hoist: the name is written here,
|
|
699
|
+
// but this lane has not read the declaration, so the row is the
|
|
700
|
+
// weaker of the two — matching `defineProps(binding)`'s shape
|
|
701
|
+
// rather than claiming more resolution than it did.
|
|
702
|
+
resolved: false,
|
|
703
|
+
line: this.offsetRange(this.nodeToRange(d.node), lineOffset, colOffset, scriptStartIndex).startLine,
|
|
704
|
+
lineOffset
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
let fromBinding = false;
|
|
708
|
+
let inlineGeneric = false;
|
|
709
|
+
let entries = this.propEntries(firstArg ?? void 0);
|
|
710
|
+
if (entries.length === 0) {
|
|
711
|
+
entries = this.genericMacroEntries(n, which);
|
|
712
|
+
if (entries.length > 0) inlineGeneric = true;
|
|
713
|
+
}
|
|
714
|
+
if (entries.length === 0) {
|
|
715
|
+
entries = this.boundMacroEntries(firstArg ?? void 0, scriptTree.rootNode, which);
|
|
716
|
+
if (entries.length > 0) fromBinding = true;
|
|
717
|
+
}
|
|
718
|
+
if (entries.length === 0) {
|
|
719
|
+
const declared = this.macroContractName(n, firstArg ?? void 0, which);
|
|
720
|
+
if (declared) {
|
|
721
|
+
const key = `${which}:${declared.name}`;
|
|
722
|
+
if (!seenMacroNames.has(key)) {
|
|
723
|
+
seenMacroNames.add(key);
|
|
724
|
+
const macroKey = which === "props" ? "isPropContract" : "isEmitContract";
|
|
725
|
+
pendingContracts.push({
|
|
726
|
+
name: declared.name,
|
|
727
|
+
marker: macroKey,
|
|
728
|
+
module: declared.module,
|
|
729
|
+
resolved: declared.resolved,
|
|
730
|
+
line: this.offsetRange(this.nodeToRange(declared.node), lineOffset, colOffset, scriptStartIndex).startLine,
|
|
731
|
+
lineOffset,
|
|
732
|
+
...propDefaults.size > 0 ? { defaults: propDefaults } : {},
|
|
733
|
+
...defaultsExpression ? { defaultsExpression } : {}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const kind = fn.text === "defineProps" ? ASTEngine.SymbolKind.Property : ASTEngine.SymbolKind.Event;
|
|
740
|
+
const meta = fn.text === "defineProps" ? "isProp" : "isEmit";
|
|
741
|
+
for (const { name: pn, node: pnNode } of entries) {
|
|
742
|
+
const dedupe = `${meta}:${pn}`;
|
|
743
|
+
if (seenMacroNames.has(dedupe)) continue;
|
|
744
|
+
seenMacroNames.add(dedupe);
|
|
745
|
+
symbols.push({
|
|
746
|
+
name: pn,
|
|
747
|
+
kind,
|
|
748
|
+
range: this.offsetRange(this.nodeToRange(pnNode), lineOffset, colOffset, scriptStartIndex),
|
|
749
|
+
// F8: a contract symbol belongs to the ONE component. `name:`
|
|
750
|
+
// is read from a `name:` option in the FIRST block, so by the
|
|
751
|
+
// time a `<script setup>` block's macros run, `componentIdentity`
|
|
752
|
+
// already holds the declared name — and a script-setup prop
|
|
753
|
+
// owned by the FILE STEM while an Options prop is owned by the
|
|
754
|
+
// component is exactly the split identity F8 removed.
|
|
755
|
+
scope: componentIdentity,
|
|
756
|
+
ownerName: componentIdentity,
|
|
757
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
758
|
+
visibility: "public",
|
|
759
|
+
metadata: {
|
|
760
|
+
[meta]: true,
|
|
761
|
+
isScriptSetup: true,
|
|
762
|
+
// Which shape the names came from: "inline" (a literal
|
|
763
|
+
// argument), "generic" (an inline type literal) or
|
|
764
|
+
// "binding" (a same-file declaration resolved by name).
|
|
765
|
+
contractSource: fromBinding ? "binding" : inlineGeneric ? "generic" : "inline",
|
|
766
|
+
// F3: the `withDefaults` default for this prop, as WRITTEN
|
|
767
|
+
// SOURCE. A same-file binding is included, which is why this
|
|
768
|
+
// is keyed off the map rather than off the shape: a binding's
|
|
769
|
+
// members have no type annotation, and the default is the only
|
|
770
|
+
// thing that says the prop is genuinely optional-by-default.
|
|
771
|
+
...propDefaults.has(pn) ? { defaultValue: propDefaults.get(pn), hasDefault: true } : {}
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
778
|
+
const c = n.namedChild(i);
|
|
779
|
+
if (c) macroStack.push(c);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
{
|
|
783
|
+
const pjStack = [scriptTree.rootNode];
|
|
784
|
+
while (pjStack.length > 0) {
|
|
785
|
+
const n = pjStack.pop();
|
|
786
|
+
if (n.type === "call_expression") {
|
|
787
|
+
const callee = n.childForFieldName("function")?.text;
|
|
788
|
+
if (callee === "provide" || callee === "inject") {
|
|
789
|
+
const vueContract = this.contractNameIsVueBinding(callee, n);
|
|
790
|
+
const argsNode = n.childForFieldName("arguments");
|
|
791
|
+
const keyNode = argsNode?.namedChild(0);
|
|
792
|
+
if (vueContract && keyNode && (keyNode.type === "identifier" || keyNode.type === "string")) {
|
|
793
|
+
const keyName = keyNode.type === "string" ? unquote(keyNode.text) : keyNode.text;
|
|
794
|
+
if (keyName) {
|
|
795
|
+
pendingInjections.push({
|
|
796
|
+
key: keyName,
|
|
797
|
+
marker: callee === "provide" ? "isProvideContract" : "isInjectContract",
|
|
798
|
+
line: this.offsetRange(this.nodeToRange(keyNode), lineOffset, colOffset, scriptStartIndex).startLine,
|
|
799
|
+
lineOffset
|
|
800
|
+
});
|
|
801
|
+
if (callee === "provide") {
|
|
802
|
+
const value = this.providedValueObject(argsNode?.namedChild(1) ?? null);
|
|
803
|
+
if (value) {
|
|
804
|
+
for (let i = 0; i < value.namedChildCount; i++) {
|
|
805
|
+
const c = value.namedChild(i);
|
|
806
|
+
if (!c || c.type !== "pair" && c.type !== "method_definition" && c.type !== "shorthand_property_identifier") continue;
|
|
807
|
+
const shorthand = c.type === "shorthand_property_identifier";
|
|
808
|
+
const k = shorthand ? c.text : c.type === "pair" ? c.childForFieldName("key")?.text : c.childForFieldName("name")?.text;
|
|
809
|
+
if (k && !k.startsWith("[")) {
|
|
810
|
+
pendingProvided.push({
|
|
811
|
+
name: unquote(k),
|
|
812
|
+
owner: moduleName,
|
|
813
|
+
line: n.startPosition.row + 1 + scriptStartLine,
|
|
814
|
+
column: n.startPosition.column + 1 + (context.sfcScriptCols[bi] ?? 0)
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
825
|
+
const c = n.namedChild(i);
|
|
826
|
+
if (c) pjStack.push(c);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
for (const [bi, scriptTree] of (context?.sfcScriptTrees ?? []).entries()) {
|
|
834
|
+
const optsCall = this.findCallNamed(scriptTree.rootNode, "defineOptions");
|
|
835
|
+
if (!optsCall) continue;
|
|
836
|
+
const optsObj = this.firstObjectArgument(optsCall);
|
|
837
|
+
if (!optsObj) continue;
|
|
838
|
+
const stack = [optsObj];
|
|
839
|
+
while (stack.length > 0 && declaredNameFromOptions === void 0) {
|
|
840
|
+
const n = stack.pop();
|
|
841
|
+
const keyNode = this.findChildByType(n, "property_identifier");
|
|
842
|
+
if (n.type === "pair" && keyNode && keyNode.text === "name") {
|
|
843
|
+
const value = n.childForFieldName("value") ?? this.findChildByType(n, "string");
|
|
844
|
+
const declared = this.declaredNameValue(value, scriptTree.rootNode);
|
|
845
|
+
if (declared) {
|
|
846
|
+
declaredNameFromOptions = declared.name;
|
|
847
|
+
declaredNameFromLocalConst = declared.constName;
|
|
848
|
+
} else if (value) declaredNameUnreadSetup = true;
|
|
849
|
+
}
|
|
850
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
851
|
+
const c = n.namedChild(i);
|
|
852
|
+
if (c) stack.push(c);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (declaredNameFromOptions) {
|
|
856
|
+
optionsScriptIndex = bi;
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (declaredNameFromOptions && declaredNameFromOptions !== componentIdentity) {
|
|
861
|
+
const cls = symbols.find(
|
|
862
|
+
(sy) => sy.kind === ASTEngine.SymbolKind.Class && sy.name === componentIdentity && sy.metadata?.isVueComponent === true
|
|
863
|
+
);
|
|
864
|
+
if (cls) {
|
|
865
|
+
cls.metadata = {
|
|
866
|
+
...cls.metadata ?? {},
|
|
867
|
+
moduleName: componentIdentity,
|
|
868
|
+
isScriptSetupComponent: true
|
|
869
|
+
};
|
|
870
|
+
const col = context.sfcScriptCols?.[optionsScriptIndex] ?? 0;
|
|
871
|
+
cls.range = { ...cls.range, startColumn: cls.range.startColumn + col };
|
|
872
|
+
const oldIdentity = cls.name;
|
|
873
|
+
for (const sym of symbols) {
|
|
874
|
+
if (sym.ownerName === oldIdentity || sym.ownerName === moduleName) sym.ownerName = declaredNameFromOptions;
|
|
875
|
+
if (sym.scope === oldIdentity || sym.scope === moduleName) sym.scope = declaredNameFromOptions;
|
|
876
|
+
}
|
|
877
|
+
for (const e of relationships) {
|
|
878
|
+
if (e.source === oldIdentity || e.source === moduleName) e.source = declaredNameFromOptions;
|
|
879
|
+
if (e.target === oldIdentity || e.target === moduleName) e.target = declaredNameFromOptions;
|
|
880
|
+
}
|
|
881
|
+
cls.name = declaredNameFromOptions;
|
|
882
|
+
componentIdentity = declaredNameFromOptions;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
{
|
|
886
|
+
const cls = symbols.find(
|
|
887
|
+
(sy) => sy.kind === ASTEngine.SymbolKind.Class && sy.metadata?.isVueComponent === true
|
|
888
|
+
);
|
|
889
|
+
if (cls) {
|
|
890
|
+
if (cls.name === moduleName) cls.metadata = { ...cls.metadata ?? {}, synthesized: true, identityFromFileStem: true };
|
|
891
|
+
if (declaredNameUnread || declaredNameUnreadSetup) cls.metadata = { ...cls.metadata ?? {}, declaredNameUnreadable: true };
|
|
892
|
+
if (declaredNameFromLocalConst) {
|
|
893
|
+
cls.metadata = { ...cls.metadata ?? {}, nameFromLocalConst: declaredNameFromLocalConst };
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
for (const c of pendingInjections) {
|
|
898
|
+
const meta = { [c.marker]: true, sfcDelegated: true, sfcLineOffset: c.lineOffset };
|
|
899
|
+
const existing = relationships.find(
|
|
900
|
+
(x) => x.source === moduleName && x.target === c.key && String(x.type ?? x.relationshipType) === String(ASTEngine.RelationshipType.References)
|
|
901
|
+
);
|
|
902
|
+
if (existing) {
|
|
903
|
+
existing.metadata = { ...existing.metadata ?? {}, ...meta };
|
|
904
|
+
} else {
|
|
905
|
+
relationships.push({
|
|
906
|
+
source: moduleName,
|
|
907
|
+
target: c.key,
|
|
908
|
+
type: ASTEngine.RelationshipType.References,
|
|
909
|
+
confidence: 0.9,
|
|
910
|
+
line_number: c.line,
|
|
911
|
+
metadata: meta
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
for (const c of pendingContracts) {
|
|
916
|
+
const contractMeta = {
|
|
917
|
+
[c.marker]: true,
|
|
918
|
+
...c.module ? { contractModule: c.module } : {},
|
|
919
|
+
...c.resolved ? {} : { unresolved: true },
|
|
920
|
+
// F3: the defaults map, plus its size so a consumer can answer "does this
|
|
921
|
+
// contract declare defaults" without walking the keys.
|
|
922
|
+
...c.defaults && c.defaults.size > 0 ? { defaults: Object.fromEntries(c.defaults), defaultKeys: c.defaults.size } : {},
|
|
923
|
+
// F3: an indirect defaults expression, kept as text. Deliberately NOT
|
|
924
|
+
// `defaultKeys: 0` — zero keys would claim the component declares no
|
|
925
|
+
// defaults, which is the opposite of the truth.
|
|
926
|
+
...c.defaultsExpression ? { defaultsExpression: c.defaultsExpression } : {}
|
|
927
|
+
};
|
|
928
|
+
const sameEdge = (x) => x.target === c.name && String(x.type ?? x.relationshipType) === String(ASTEngine.RelationshipType.References) && x.metadata?.sfcDelegated === true && (x.line_number ?? x.line) === c.line;
|
|
929
|
+
const existing = relationships.find(sameEdge);
|
|
930
|
+
if (existing) {
|
|
931
|
+
existing.metadata = { ...existing.metadata ?? {}, ...contractMeta };
|
|
932
|
+
if (existing.metadata?.sfcDelegated === true) existing.source = componentIdentity;
|
|
933
|
+
} else {
|
|
934
|
+
relationships.push({
|
|
935
|
+
source: componentIdentity,
|
|
936
|
+
target: c.name,
|
|
937
|
+
type: ASTEngine.RelationshipType.References,
|
|
938
|
+
confidence: c.resolved ? 1 : 0.8,
|
|
939
|
+
line_number: c.line,
|
|
940
|
+
metadata: { ...contractMeta, sfcDelegated: true, sfcLineOffset: c.lineOffset }
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (templateExprNames.size > 0) {
|
|
945
|
+
const memberNames = new Set(symbols.map((s) => s.name));
|
|
946
|
+
const seenUses = /* @__PURE__ */ new Set();
|
|
947
|
+
for (const [name, line] of templateExprNames) {
|
|
948
|
+
if (!memberNames.has(name)) continue;
|
|
949
|
+
const key = `${moduleName}:${name}`;
|
|
950
|
+
if (seenUses.has(key)) continue;
|
|
951
|
+
seenUses.add(key);
|
|
952
|
+
relationships.push({
|
|
953
|
+
source: moduleName,
|
|
954
|
+
target: name,
|
|
955
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
956
|
+
confidence: 0.95,
|
|
957
|
+
// (#825): the line the identifier is WRITTEN on (the fragment's
|
|
958
|
+
// base plus its offset inside it).
|
|
959
|
+
line_number: line,
|
|
960
|
+
metadata: { isTemplateExpression: true }
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (componentIdentity !== moduleName) {
|
|
965
|
+
for (const rel of relationships) {
|
|
966
|
+
if (rel.source === moduleName) rel.source = componentIdentity;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
for (const p of pendingProvided) {
|
|
970
|
+
const sym = symbols.find((sy) => sy.name === p.name && sy.ownerName === void 0);
|
|
971
|
+
if (sym) {
|
|
972
|
+
sym.ownerName = p.owner;
|
|
973
|
+
sym.ownerKind = ASTEngine.SymbolKind.Class;
|
|
974
|
+
sym.scope = p.owner;
|
|
975
|
+
sym.metadata = { ...sym.metadata ?? {}, isProvided: true };
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (symbols.some((sy) => sy.name === p.name && sy.ownerName === p.owner)) continue;
|
|
979
|
+
symbols.push({
|
|
980
|
+
name: p.name,
|
|
981
|
+
kind: ASTEngine.SymbolKind.Variable,
|
|
982
|
+
ownerName: p.owner,
|
|
983
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
984
|
+
scope: p.owner,
|
|
985
|
+
line_number: p.line,
|
|
986
|
+
range: { startLine: p.line, startColumn: p.column, endLine: p.line, endColumn: p.column + p.name.length },
|
|
987
|
+
metadata: { isProvided: true, synthesized: true }
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
const existingContains = /* @__PURE__ */ new Set();
|
|
991
|
+
for (const r of relationships) {
|
|
992
|
+
if (r.type === ASTEngine.RelationshipType.Contains && r.source && r.target) {
|
|
993
|
+
existingContains.add(`${r.source}::${r.target}`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
for (const s of symbols) {
|
|
997
|
+
if (s.ownerName) {
|
|
998
|
+
const key = `${s.ownerName}::${s.name}`;
|
|
999
|
+
if (existingContains.has(key)) continue;
|
|
1000
|
+
existingContains.add(key);
|
|
1001
|
+
relationships.push({
|
|
1002
|
+
source: s.ownerName,
|
|
1003
|
+
target: s.name,
|
|
1004
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
1005
|
+
confidence: 1,
|
|
1006
|
+
// (#825): the MEMBER's declaration line (its symbol range, already
|
|
1007
|
+
// shifted to SFC coordinates) — the edge names the member.
|
|
1008
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
1009
|
+
metadata: {}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const architecturalScore = Math.min(100, Math.max(0, 100 - errors.length * 5));
|
|
1014
|
+
const detectedPatterns = Object.keys(patterns);
|
|
1015
|
+
const frameworkPatterns = detectedPatterns.map((p) => ({
|
|
1016
|
+
framework: "Vue",
|
|
1017
|
+
patterns: [{ type: "usage", detected: true, description: p }]
|
|
1018
|
+
}));
|
|
1019
|
+
this.classifyEvidenceTier(relationships);
|
|
1020
|
+
if (componentIdentity !== moduleName) {
|
|
1021
|
+
for (const sym of symbols) {
|
|
1022
|
+
if (sym.ownerName === moduleName) sym.ownerName = componentIdentity;
|
|
1023
|
+
if (sym.scope === moduleName) sym.scope = componentIdentity;
|
|
1024
|
+
}
|
|
1025
|
+
for (const e of relationships) {
|
|
1026
|
+
if (e.source === moduleName) e.source = componentIdentity;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
language: "vue",
|
|
1031
|
+
symbols,
|
|
1032
|
+
relationships,
|
|
1033
|
+
imports,
|
|
1034
|
+
exports,
|
|
1035
|
+
errors,
|
|
1036
|
+
metadata: {
|
|
1037
|
+
// Real cyclomatic complexity: script-block decisions (per JS/TS
|
|
1038
|
+
// contract) plus template decision directives (v-if/v-for/v-show).
|
|
1039
|
+
// Was hardcoded 1 / the synthetic symbols+relationships heuristic.
|
|
1040
|
+
complexity: 1 + scriptCyclomaticSum + templateDecisions,
|
|
1041
|
+
linesOfCode: content.split("\n").length,
|
|
1042
|
+
cyclomaticComplexity: 1 + scriptCyclomaticSum + templateDecisions,
|
|
1043
|
+
framework: "Vue",
|
|
1044
|
+
patterns: detectedPatterns,
|
|
1045
|
+
detectedFrameworks: ["Vue"],
|
|
1046
|
+
frameworkPatterns,
|
|
1047
|
+
architecturalInsights: [],
|
|
1048
|
+
architecturalScore,
|
|
1049
|
+
codeQuality: errors.length === 0 ? "Excellent" : "Fair",
|
|
1050
|
+
// F7: true iff a <script> block was parsed with a TypeScript grammar
|
|
1051
|
+
// (lang="ts" or lang="tsx", #903's three-way dispatch). A plain block,
|
|
1052
|
+
// or an SFC with no script, is the JavaScript analyzer and stays false.
|
|
1053
|
+
hasTypeInference: dispatchedToTypeScript,
|
|
1054
|
+
hasFrameworkIntelligence: true,
|
|
1055
|
+
analysisVersion: "5.0.0-m12.12-family-contract"
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
// ============================================================================
|
|
1060
|
+
// OPTIONS API EXTRACTION
|
|
1061
|
+
// ============================================================================
|
|
1062
|
+
/**
|
|
1063
|
+
* Which shape the LAST contract resolution came from. A field rather than a
|
|
1064
|
+
* return value because the two call sites (the Options options and the
|
|
1065
|
+
* script-setup macros) each consume the entries in their own loop; the marker
|
|
1066
|
+
* is what lets a fixture pin the shape and a mutant revert one of them
|
|
1067
|
+
* without touching the others.
|
|
1068
|
+
*/
|
|
1069
|
+
lastContractSource = "none";
|
|
1070
|
+
extractOptionsApi(objectNode, componentName, symbols, patterns, lineOffset, colOffset, payloads, indexOffset, scriptRoot, relationships) {
|
|
1071
|
+
const memberName = (n) => {
|
|
1072
|
+
const key = n.childForFieldName("key");
|
|
1073
|
+
if (key) return key.text;
|
|
1074
|
+
const name = n.childForFieldName("name");
|
|
1075
|
+
if (name) return name.text;
|
|
1076
|
+
return this.findChildByType(n, "property_identifier")?.text;
|
|
1077
|
+
};
|
|
1078
|
+
const mergeBases = [];
|
|
1079
|
+
for (let i = 0; i < objectNode.namedChildCount; i++) {
|
|
1080
|
+
const child = objectNode.namedChild(i);
|
|
1081
|
+
if (!child) continue;
|
|
1082
|
+
if (child.type !== "pair" && child.type !== "method_definition") continue;
|
|
1083
|
+
const key = memberName(child);
|
|
1084
|
+
if (!key) continue;
|
|
1085
|
+
const value = child.type === "pair" ? child.childForFieldName("value") : void 0;
|
|
1086
|
+
if (key === "methods" && value) {
|
|
1087
|
+
patterns["Options API"] = true;
|
|
1088
|
+
this.emitObjectMembers(value, componentName, symbols, ASTEngine.SymbolKind.Method, "method", lineOffset, colOffset, payloads, indexOffset);
|
|
1089
|
+
} else if (key === "computed" && value) {
|
|
1090
|
+
patterns["Options API"] = true;
|
|
1091
|
+
this.emitObjectMembers(value, componentName, symbols, ASTEngine.SymbolKind.Property, "computed", lineOffset, colOffset, payloads, indexOffset);
|
|
1092
|
+
} else if (key === "data") {
|
|
1093
|
+
symbols.push({
|
|
1094
|
+
name: "data",
|
|
1095
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
1096
|
+
range: this.offsetRange(this.nodeToRange(child), lineOffset, colOffset, indexOffset),
|
|
1097
|
+
scope: componentName,
|
|
1098
|
+
ownerName: componentName,
|
|
1099
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1100
|
+
visibility: "public",
|
|
1101
|
+
metadata: { isOptionsData: true }
|
|
1102
|
+
});
|
|
1103
|
+
const dataValue = child.type === "pair" ? child.childForFieldName("value") : void 0;
|
|
1104
|
+
if (dataValue && dataValue.type === "object") {
|
|
1105
|
+
this.emitDataKeys(dataValue, componentName, symbols, lineOffset, colOffset, indexOffset);
|
|
1106
|
+
} else if (child.type === "method_definition") {
|
|
1107
|
+
const returned = this.findReturnedObject(child);
|
|
1108
|
+
if (returned) this.emitDataKeys(returned, componentName, symbols, lineOffset, colOffset, indexOffset);
|
|
1109
|
+
} else if (dataValue && (dataValue.type === "arrow_function" || dataValue.type === "function_expression" || dataValue.type === "function")) {
|
|
1110
|
+
const returned = this.findReturnedObject(dataValue);
|
|
1111
|
+
if (returned) this.emitDataKeys(returned, componentName, symbols, lineOffset, colOffset, indexOffset);
|
|
1112
|
+
}
|
|
1113
|
+
} else if (key === "props") {
|
|
1114
|
+
for (const { name: pn, node: pnode } of this.resolveContractEntries(value ?? child, scriptRoot, "props", relationships, componentName)) {
|
|
1115
|
+
symbols.push({
|
|
1116
|
+
name: pn,
|
|
1117
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1118
|
+
// (#825): the prop KEY's own node — the line and column a
|
|
1119
|
+
// navigation to this prop must land on.
|
|
1120
|
+
range: this.offsetRange(this.nodeToRange(pnode), lineOffset, colOffset, indexOffset),
|
|
1121
|
+
scope: componentName,
|
|
1122
|
+
ownerName: componentName,
|
|
1123
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1124
|
+
visibility: "public",
|
|
1125
|
+
metadata: { isProp: true, contractSource: this.lastContractSource }
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
} else if (key === "emits") {
|
|
1129
|
+
for (const { name: en, node: enode } of this.resolveContractEntries(value ?? child, scriptRoot, "emits", relationships, componentName)) {
|
|
1130
|
+
symbols.push({
|
|
1131
|
+
name: en,
|
|
1132
|
+
kind: ASTEngine.SymbolKind.Event,
|
|
1133
|
+
range: this.offsetRange(this.nodeToRange(enode), lineOffset, colOffset, indexOffset),
|
|
1134
|
+
scope: componentName,
|
|
1135
|
+
ownerName: componentName,
|
|
1136
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1137
|
+
visibility: "public",
|
|
1138
|
+
metadata: { isEmit: true, contractSource: this.lastContractSource }
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
} else if (key === "extends" || key === "mixins") {
|
|
1142
|
+
if (!value || !relationships) continue;
|
|
1143
|
+
const isMixin = key === "mixins";
|
|
1144
|
+
for (const base of this.identifiersOf(value)) {
|
|
1145
|
+
relationships.push({
|
|
1146
|
+
source: componentName,
|
|
1147
|
+
target: base.name,
|
|
1148
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
1149
|
+
// A named component the analyzer cannot see is the Vue
|
|
1150
|
+
// documentation's own guarantee — the option is resolved by Vue at
|
|
1151
|
+
// runtime — so this is a declaration of the relationship, not a
|
|
1152
|
+
// guess. It is not `structural` in the closure sense (nothing in
|
|
1153
|
+
// this file states the base's members), which is why the
|
|
1154
|
+
// confidence is below 1.
|
|
1155
|
+
confidence: 0.9,
|
|
1156
|
+
line_number: this.offsetRange(this.nodeToRange(base.node), lineOffset, colOffset, indexOffset).startLine,
|
|
1157
|
+
metadata: { isMixin, isOptionsApi: true }
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
for (const base of this.identifiersOf(value)) {
|
|
1161
|
+
mergeBases.push({
|
|
1162
|
+
name: base.name,
|
|
1163
|
+
isMixin,
|
|
1164
|
+
node: base.node,
|
|
1165
|
+
optionsObject: this.sameTreeOptionsObject(base.node, value, scriptRoot)
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
if (value.type === "object") {
|
|
1169
|
+
for (let i2 = 0; i2 < value.namedChildCount; i2++) {
|
|
1170
|
+
const sub = value.namedChild(i2);
|
|
1171
|
+
if (!sub || sub.type !== "pair" && sub.type !== "method_definition") continue;
|
|
1172
|
+
const subKey = sub.type === "pair" ? sub.childForFieldName("key")?.text : sub.childForFieldName("name")?.text;
|
|
1173
|
+
const subValue = sub.type === "pair" ? sub.childForFieldName("value") : void 0;
|
|
1174
|
+
if (!subKey) continue;
|
|
1175
|
+
if ((subKey === "methods" || subKey === "computed") && subValue) {
|
|
1176
|
+
this.emitObjectMembers(
|
|
1177
|
+
subValue,
|
|
1178
|
+
componentName,
|
|
1179
|
+
symbols,
|
|
1180
|
+
subKey === "methods" ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Property,
|
|
1181
|
+
subKey === "methods" ? "method" : "computed",
|
|
1182
|
+
lineOffset,
|
|
1183
|
+
colOffset,
|
|
1184
|
+
payloads,
|
|
1185
|
+
indexOffset
|
|
1186
|
+
);
|
|
1187
|
+
} else if (subKey === "props" && subValue) {
|
|
1188
|
+
for (const { name: pn, node: pnode } of this.propEntries(subValue)) {
|
|
1189
|
+
symbols.push({
|
|
1190
|
+
name: pn,
|
|
1191
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1192
|
+
range: this.offsetRange(this.nodeToRange(pnode), lineOffset, colOffset, indexOffset),
|
|
1193
|
+
scope: componentName,
|
|
1194
|
+
ownerName: componentName,
|
|
1195
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1196
|
+
visibility: "public",
|
|
1197
|
+
metadata: { isProp: true, inheritedFrom: "extends" }
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
} else if (key === "watch") {
|
|
1204
|
+
if (value) {
|
|
1205
|
+
patterns["Options API"] = true;
|
|
1206
|
+
this.emitObjectMembers(value, componentName, symbols, ASTEngine.SymbolKind.Method, "isWatcher", lineOffset, colOffset, payloads, indexOffset);
|
|
1207
|
+
}
|
|
1208
|
+
} else if (key === "setup") {
|
|
1209
|
+
patterns["Options API"] = true;
|
|
1210
|
+
symbols.push({
|
|
1211
|
+
name: key,
|
|
1212
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
1213
|
+
range: this.offsetRange(this.nodeToRange(child), lineOffset, colOffset, indexOffset),
|
|
1214
|
+
scope: componentName,
|
|
1215
|
+
ownerName: componentName,
|
|
1216
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1217
|
+
visibility: "public",
|
|
1218
|
+
metadata: { isSetup: true }
|
|
1219
|
+
});
|
|
1220
|
+
} else if (this.isLifecycleHook(key)) {
|
|
1221
|
+
symbols.push({
|
|
1222
|
+
name: key,
|
|
1223
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
1224
|
+
range: this.offsetRange(this.nodeToRange(child), lineOffset, colOffset, indexOffset),
|
|
1225
|
+
scope: componentName,
|
|
1226
|
+
ownerName: componentName,
|
|
1227
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1228
|
+
visibility: "public",
|
|
1229
|
+
metadata: { isLifecycleHook: true }
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (mergeBases.length > 0 && relationships) {
|
|
1234
|
+
const MEMBER_KINDS = /* @__PURE__ */ new Set([
|
|
1235
|
+
ASTEngine.SymbolKind.Method,
|
|
1236
|
+
ASTEngine.SymbolKind.Property,
|
|
1237
|
+
ASTEngine.SymbolKind.Event
|
|
1238
|
+
]);
|
|
1239
|
+
const own = /* @__PURE__ */ new Map();
|
|
1240
|
+
for (const s of symbols) {
|
|
1241
|
+
if (s.ownerName !== componentName) continue;
|
|
1242
|
+
if (!MEMBER_KINDS.has(s.kind)) continue;
|
|
1243
|
+
own.set(s.name, s);
|
|
1244
|
+
}
|
|
1245
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
1246
|
+
for (const base of mergeBases) {
|
|
1247
|
+
const baseMembers = base.optionsObject ? this.optionsMemberNames(base.optionsObject) : void 0;
|
|
1248
|
+
if (!baseMembers || baseMembers.size === 0) continue;
|
|
1249
|
+
for (const [name, sym] of own) {
|
|
1250
|
+
if (!baseMembers.has(name)) continue;
|
|
1251
|
+
const edgeKey = `${componentName}.${name}->${base.name}.${name}`;
|
|
1252
|
+
if (emitted.has(edgeKey)) continue;
|
|
1253
|
+
emitted.add(edgeKey);
|
|
1254
|
+
relationships.push({
|
|
1255
|
+
source: `${componentName}.${name}`,
|
|
1256
|
+
target: `${base.name}.${name}`,
|
|
1257
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
1258
|
+
confidence: 0.95,
|
|
1259
|
+
...sym.range?.startLine ? { line_number: this.offsetRange(sym.range, lineOffset, colOffset, indexOffset).startLine } : {},
|
|
1260
|
+
metadata: {
|
|
1261
|
+
baseOption: base.name,
|
|
1262
|
+
isMixin: base.isMixin,
|
|
1263
|
+
isOptionsApi: true,
|
|
1264
|
+
// Honesty contract : a sub-1.0 confidence carries a
|
|
1265
|
+
// machine-readable reason, not just a number.
|
|
1266
|
+
reasonCode: "verified-in-file"
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* ONTOLOGY : the options OBJECT a `mixins`/`extends` base names,
|
|
1275
|
+
* when this parse tree can read one, and `undefined` when it cannot.
|
|
1276
|
+
*
|
|
1277
|
+
* Two accepted shapes, both resolved WITHOUT leaving the tree:
|
|
1278
|
+
*
|
|
1279
|
+
* INLINE `mixins: [{ methods: { … } }]` / `extends: { … }` — the object
|
|
1280
|
+
* is the element (or the value) itself.
|
|
1281
|
+
* SAME-FILE `const shared = { … }` in the same `<script>` block, named by
|
|
1282
|
+
* the mixins array. Resolved by walking `variable_declarator`s
|
|
1283
|
+
* for the wanted name, which is exactly what `boundMacroEntries`
|
|
1284
|
+
* does for `defineProps(binding)` — the same rule, for the same
|
|
1285
|
+
* reason: a same-tree declaration is the only form whose keys are
|
|
1286
|
+
* published by nothing else, so reading them here cannot double
|
|
1287
|
+
* count another producer's symbols.
|
|
1288
|
+
*
|
|
1289
|
+
* Anything else returns `undefined` and the caller emits nothing. That is the
|
|
1290
|
+
* honest answer for an imported base: the analyzer has not read the base, so it
|
|
1291
|
+
* does not know what the base declares, and an `overrides` row needs that.
|
|
1292
|
+
*/
|
|
1293
|
+
sameTreeOptionsObject(baseNode, value, scriptRoot) {
|
|
1294
|
+
if (baseNode.type === "object") return baseNode;
|
|
1295
|
+
if (value.type === "object" && baseNode.startIndex === value.startIndex) return value;
|
|
1296
|
+
if (baseNode.type !== "identifier" || !scriptRoot) return void 0;
|
|
1297
|
+
const wanted = baseNode.text;
|
|
1298
|
+
const stack = [scriptRoot];
|
|
1299
|
+
while (stack.length > 0) {
|
|
1300
|
+
const n = stack.pop();
|
|
1301
|
+
if (n.type === "variable_declarator") {
|
|
1302
|
+
const nameField = n.childForFieldName("name");
|
|
1303
|
+
const valueField = n.childForFieldName("value");
|
|
1304
|
+
if (nameField?.type === "identifier" && nameField.text === wanted && valueField) {
|
|
1305
|
+
const peeled = peelTypeWrappers(valueField);
|
|
1306
|
+
if (peeled.type === "object") return peeled;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1310
|
+
const c = n.namedChild(i);
|
|
1311
|
+
if (c) stack.push(c);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return void 0;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* ONTOLOGY : the member names an options
|
|
1318
|
+
* object declares — restricted to the options Vue's OWN MERGE TABLE REPLACES,
|
|
1319
|
+
* and to nothing else.
|
|
1320
|
+
*
|
|
1321
|
+
* WHY THE RESTRICTION IS THE WHOLE FIX. The first version of this read every
|
|
1322
|
+
* `pair` child of `MEMBER_OPTIONS` plus every `method_definition` child, on
|
|
1323
|
+
* the stated premise that "Vue's own merge rule is that a component's own
|
|
1324
|
+
* value for an option WINS over the base's, so a member declared here that the
|
|
1325
|
+
* base also declares is an override of that member". That premise is FALSE, and
|
|
1326
|
+
* Vue's own source says so in one table.
|
|
1327
|
+
*
|
|
1328
|
+
* vuejs@4ab865a, packages/runtime-core/src/componentOptions.ts:1005-1035,
|
|
1329
|
+
* `internalOptionMergeStrats`:
|
|
1330
|
+
*
|
|
1331
|
+
* methods, computed, components, directives -> mergeObjectOptions
|
|
1332
|
+
* `extend(Object.create(null), to, from)` — the CHILD'S value for a
|
|
1333
|
+
* same-named key wins outright. OVERRIDE
|
|
1334
|
+
* props, emits -> mergeEmitsOrPropsOptions
|
|
1335
|
+
* `extend({}, normalize(to), normalize(from ?? {}))` — same. OVERRIDE
|
|
1336
|
+
* inject -> mergeInject -> mergeObjectOptions OVERRIDE
|
|
1337
|
+
* data -> mergeDataFn
|
|
1338
|
+
* BOTH factories run and the results are `extend`ed, so the child's
|
|
1339
|
+
* wins PER RETURNED KEY while the `data` option itself survives as
|
|
1340
|
+
* two. Not a member override — but the colliding DATA KEYS are real.
|
|
1341
|
+
* beforeCreate … serverPrefetch (14 hooks) -> mergeAsArray
|
|
1342
|
+
* `[...new Set([].concat(to, from))]` — BOTH HOOKS RUN. NOT one.
|
|
1343
|
+
* Confirmed independently at apiLifecycle.ts:26-27:
|
|
1344
|
+
* `const hooks = target[type] || (target[type] = []); hooks.push(...)`.
|
|
1345
|
+
* watch -> mergeWatchOptions
|
|
1346
|
+
* per key the two are CONCATENATED into an array — both run. NOT one.
|
|
1347
|
+
* provide -> mergeDataFn, same as data. NOT one.
|
|
1348
|
+
*
|
|
1349
|
+
* So of the option kinds a real merge can collide on, four classes are a
|
|
1350
|
+
* replacement and four are a merge. The first version could not tell them
|
|
1351
|
+
* apart, and it emitted a row for all of them.
|
|
1352
|
+
*
|
|
1353
|
+
* WHAT THAT COST, MEASURED. Over the 1,151 admitted `.vue` files of
|
|
1354
|
+
* element-plus + vue-element-admin + vuejs there are 9 `extends`/`mixins`
|
|
1355
|
+
* heritage edges and 21 name collisions across them — and every one of the 21
|
|
1356
|
+
* is in the merge half: `mounted`, `beforeDestroy`, `activated`, `deactivated`
|
|
1357
|
+
* and the `data` factory. A cross-file resolution built on the old rule would
|
|
1358
|
+
* have emitted 21 fabricated `overrides` rows and 0 true ones
|
|
1359
|
+
* (`script/m14/vue-overrides-crossfile-probe.mts`; evidence in
|
|
1360
|
+
* `validation/m14/results/archive/vue-verify8-overrides-crossfile.json`). Under this
|
|
1361
|
+
* rule the same corpus supports 0, which is the correct answer and not a gap.
|
|
1362
|
+
*
|
|
1363
|
+
* A fabricated edge is the most expensive mistake available in this programme
|
|
1364
|
+
* (ONTOLOGY-WORKSTREAM, "The fabrication rule"): it counts as `unexplained`
|
|
1365
|
+
* and it poisons bb_graph, bb_impact and bb_migrate. Removing 21 false rows is
|
|
1366
|
+
* worth more than adding them.
|
|
1367
|
+
*
|
|
1368
|
+
* WHAT IS DELIBERATELY NOT IN THE SET, each exclusion a decision:
|
|
1369
|
+
* - lifecycle hooks, `data`, `setup`, `watch`: merged, per the table above.
|
|
1370
|
+
* - `components` / `directives`: merged per key like `methods`, so they COULD
|
|
1371
|
+
* collide — but this analyzer publishes no symbol for an asset
|
|
1372
|
+
* registration, so a collision could never be joined to an own member and
|
|
1373
|
+
* the rule could never fire. Adding it would be a branch that cannot fire,
|
|
1374
|
+
* which is the dead-code class this workstream exists to remove.
|
|
1375
|
+
*/
|
|
1376
|
+
optionsMemberNames(optionsObject) {
|
|
1377
|
+
const out = /* @__PURE__ */ new Set();
|
|
1378
|
+
const MEMBER_OPTIONS = /* @__PURE__ */ new Set(["methods", "computed", "props", "emits", "inject"]);
|
|
1379
|
+
for (let i = 0; i < optionsObject.namedChildCount; i++) {
|
|
1380
|
+
const child = optionsObject.namedChild(i);
|
|
1381
|
+
if (!child) continue;
|
|
1382
|
+
if (child.type === "method_definition") continue;
|
|
1383
|
+
if (child.type !== "pair") continue;
|
|
1384
|
+
const key = child.childForFieldName("key")?.text;
|
|
1385
|
+
if (!key || key.startsWith("[")) continue;
|
|
1386
|
+
const k = child.childForFieldName("key").type === "string" ? unquote(key) : key;
|
|
1387
|
+
if (!MEMBER_OPTIONS.has(key)) continue;
|
|
1388
|
+
const v = child.childForFieldName("value");
|
|
1389
|
+
if (!v) {
|
|
1390
|
+
out.add(k);
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
if (v.type === "object") {
|
|
1394
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
1395
|
+
const m = v.namedChild(j);
|
|
1396
|
+
if (!m || m.type !== "pair" && m.type !== "method_definition" && m.type !== "shorthand_property_identifier") continue;
|
|
1397
|
+
const mk = m.type === "pair" ? m.childForFieldName("key")?.text : m.type === "method_definition" ? m.childForFieldName("name")?.text : m.text;
|
|
1398
|
+
if (mk && !mk.startsWith("[")) out.add(unquote(mk));
|
|
1399
|
+
}
|
|
1400
|
+
} else if (v.type === "array") {
|
|
1401
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
1402
|
+
const m = v.namedChild(j);
|
|
1403
|
+
if (m?.type === "string") out.add(unquote(m.text));
|
|
1404
|
+
}
|
|
1405
|
+
} else {
|
|
1406
|
+
out.add(k);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
for (const dk of this.optionsDataKeys(optionsObject)) out.add(dk);
|
|
1410
|
+
return out;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* The keys a `data` option RETURNS — the one collision class `data` really does
|
|
1414
|
+
* replace, because `mergeDataFn` runs both factories and `extend`s the two
|
|
1415
|
+
* results with the child's last.
|
|
1416
|
+
*
|
|
1417
|
+
* Two shapes, both read off the options object and neither leaving the tree:
|
|
1418
|
+
* the object form (`data: { n: 0 }`) and the factory form
|
|
1419
|
+
* (`data() { return { n: 0 } }`), which is `findReturnedObject`'s job. An arrow
|
|
1420
|
+
* factory written as a `pair` value (`data: () => ({ … })`) goes through the
|
|
1421
|
+
* same call, because `findReturnedObject` handles an expression body.
|
|
1422
|
+
*/
|
|
1423
|
+
optionsDataKeys(optionsObject) {
|
|
1424
|
+
const out = /* @__PURE__ */ new Set();
|
|
1425
|
+
const addFrom = (o) => {
|
|
1426
|
+
if (!o || o.type !== "object") return;
|
|
1427
|
+
for (let i = 0; i < o.namedChildCount; i++) {
|
|
1428
|
+
const p = o.namedChild(i);
|
|
1429
|
+
if (p?.type !== "pair") continue;
|
|
1430
|
+
const raw = p.childForFieldName("key")?.text;
|
|
1431
|
+
if (!raw || raw.startsWith("[")) continue;
|
|
1432
|
+
out.add(unquote(raw));
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
for (let i = 0; i < optionsObject.namedChildCount; i++) {
|
|
1436
|
+
const child = optionsObject.namedChild(i);
|
|
1437
|
+
if (!child) continue;
|
|
1438
|
+
if (child.type === "method_definition") {
|
|
1439
|
+
if (child.childForFieldName("name")?.text !== "data") continue;
|
|
1440
|
+
addFrom(this.findReturnedObject(child));
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (child.type !== "pair" || child.childForFieldName("key")?.text !== "data") continue;
|
|
1444
|
+
const v = child.childForFieldName("value");
|
|
1445
|
+
if (!v) continue;
|
|
1446
|
+
if (v.type === "object") {
|
|
1447
|
+
addFrom(v);
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
if (v.type === "arrow_function" || v.type === "function_expression" || v.type === "function") {
|
|
1451
|
+
addFrom(this.findReturnedObject(v));
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return out;
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* H1 (review wave): emit one Property symbol per key of a data-state
|
|
1458
|
+
* object (pair ranges, so hover lands on the key). String keys unquoted.
|
|
1459
|
+
*/
|
|
1460
|
+
emitDataKeys(objectNode, componentName, symbols, lineOffset, colOffset, indexOffset) {
|
|
1461
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1462
|
+
for (let i = 0; i < objectNode.namedChildCount; i++) {
|
|
1463
|
+
const child = objectNode.namedChild(i);
|
|
1464
|
+
if (!child || child.type !== "pair") continue;
|
|
1465
|
+
const keyNode = child.childForFieldName("key");
|
|
1466
|
+
if (!keyNode) continue;
|
|
1467
|
+
if (keyNode.text.startsWith("[")) continue;
|
|
1468
|
+
const name = keyNode.type === "string" ? unquote(keyNode.text) : keyNode.text;
|
|
1469
|
+
if (!name) continue;
|
|
1470
|
+
const prevIdx = seen.get(name);
|
|
1471
|
+
const sym = {
|
|
1472
|
+
name,
|
|
1473
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1474
|
+
range: this.offsetRange(this.nodeToRange(child), lineOffset, colOffset, indexOffset),
|
|
1475
|
+
scope: componentName,
|
|
1476
|
+
ownerName: componentName,
|
|
1477
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1478
|
+
visibility: "public",
|
|
1479
|
+
metadata: { isDataKey: true }
|
|
1480
|
+
};
|
|
1481
|
+
if (prevIdx !== void 0) {
|
|
1482
|
+
symbols[prevIdx] = sym;
|
|
1483
|
+
} else {
|
|
1484
|
+
seen.set(name, symbols.length);
|
|
1485
|
+
symbols.push(sym);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* H1 (review wave): first `return {...}` object literal under a data()
|
|
1491
|
+
* factory body. Conditional/multiple returns: first wins (documented
|
|
1492
|
+
* approximation — branches are not evaluated). D2 (deeper review):
|
|
1493
|
+
* expression-bodied arrows (`() => ({...})`) carry the object as the
|
|
1494
|
+
* direct body — an object reached with only parenthesized wrappers
|
|
1495
|
+
* between it and an arrow/function start is the body's value, not a
|
|
1496
|
+
* nested construction.
|
|
1497
|
+
*/
|
|
1498
|
+
findReturnedObject(methodNode) {
|
|
1499
|
+
const isStart = (p) => !!p && p.startIndex === methodNode.startIndex && p.endIndex === methodNode.endIndex;
|
|
1500
|
+
const stack = [methodNode];
|
|
1501
|
+
while (stack.length > 0) {
|
|
1502
|
+
const n = stack.pop();
|
|
1503
|
+
if (n.type === "return_statement") {
|
|
1504
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1505
|
+
const c = n.namedChild(i);
|
|
1506
|
+
if (c && c.type === "object") return c;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (n.type === "object" && !isStart(n)) {
|
|
1510
|
+
let ok = true;
|
|
1511
|
+
let p = n.parent;
|
|
1512
|
+
while (p && !isStart(p)) {
|
|
1513
|
+
if (p.type !== "parenthesized_expression" && p.type !== "arrow_function" && p.type !== "function_expression" && p.type !== "function" && p.type !== "method_definition") {
|
|
1514
|
+
ok = false;
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
p = p.parent;
|
|
1518
|
+
}
|
|
1519
|
+
if (ok && p) return n;
|
|
1520
|
+
}
|
|
1521
|
+
if (!isStart(n) && (n.type === "function_expression" || n.type === "arrow_function" || n.type === "function_declaration" || n.type === "method_definition")) continue;
|
|
1522
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1523
|
+
const c = n.namedChild(i);
|
|
1524
|
+
if (c) stack.push(c);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return void 0;
|
|
1528
|
+
}
|
|
1529
|
+
emitObjectMembers(valueNode, componentName, symbols, kind, meta, lineOffset, colOffset, payloads, indexOffset) {
|
|
1530
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1531
|
+
for (let i = 0; i < valueNode.namedChildCount; i++) {
|
|
1532
|
+
const child = valueNode.namedChild(i);
|
|
1533
|
+
if (!child) continue;
|
|
1534
|
+
let name;
|
|
1535
|
+
if (child.type === "method_definition" || child.type === "pair") {
|
|
1536
|
+
const raw = child.childForFieldName("name")?.text || child.childForFieldName("key")?.text;
|
|
1537
|
+
if (!raw) continue;
|
|
1538
|
+
if (raw.startsWith("[")) continue;
|
|
1539
|
+
name = unquote(raw);
|
|
1540
|
+
} else if (child.type === "property_identifier") {
|
|
1541
|
+
name = child.text;
|
|
1542
|
+
}
|
|
1543
|
+
if (!name) continue;
|
|
1544
|
+
const prevIdx = seen.get(name);
|
|
1545
|
+
const payload = payloads?.get(name);
|
|
1546
|
+
const sym = {
|
|
1547
|
+
name,
|
|
1548
|
+
kind,
|
|
1549
|
+
range: this.offsetRange(this.nodeToRange(child), lineOffset, colOffset, indexOffset),
|
|
1550
|
+
scope: componentName,
|
|
1551
|
+
ownerName: componentName,
|
|
1552
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1553
|
+
visibility: "public",
|
|
1554
|
+
parameters: payload?.parameters,
|
|
1555
|
+
returnType: payload?.returnType,
|
|
1556
|
+
complexity: payload?.complexity,
|
|
1557
|
+
qualified_calls: payload?.qualified_calls,
|
|
1558
|
+
metadata: { [meta]: true }
|
|
1559
|
+
};
|
|
1560
|
+
if (prevIdx !== void 0) {
|
|
1561
|
+
symbols[prevIdx] = sym;
|
|
1562
|
+
} else {
|
|
1563
|
+
seen.set(name, symbols.length);
|
|
1564
|
+
symbols.push(sym);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* F12: a contract OPTION's entries, in the shape order —
|
|
1570
|
+
* inline literal, then a same-file BINDING, then the declaration EDGE.
|
|
1571
|
+
*
|
|
1572
|
+
* Shared by the Options `props:`/`emits:` options and (for the binding half)
|
|
1573
|
+
* the script-setup macros, because the two differ only in spelling: Vue
|
|
1574
|
+
* hoists the declaration to a `const` in both styles, and both styles lost
|
|
1575
|
+
* the whole contract when the name was not followed.
|
|
1576
|
+
*/
|
|
1577
|
+
resolveContractEntries(value, scriptRoot, which, relationships, componentName) {
|
|
1578
|
+
const inline = this.propEntries(value ?? void 0);
|
|
1579
|
+
if (inline.length > 0) {
|
|
1580
|
+
this.lastContractSource = "inline";
|
|
1581
|
+
return inline;
|
|
1582
|
+
}
|
|
1583
|
+
if (!value || !scriptRoot) {
|
|
1584
|
+
this.lastContractSource = "none";
|
|
1585
|
+
return [];
|
|
1586
|
+
}
|
|
1587
|
+
const bound = this.boundMacroEntries(value, scriptRoot, which);
|
|
1588
|
+
if (bound.length > 0) {
|
|
1589
|
+
this.lastContractSource = "binding";
|
|
1590
|
+
return bound;
|
|
1591
|
+
}
|
|
1592
|
+
this.lastContractSource = "none";
|
|
1593
|
+
if (!relationships) return [];
|
|
1594
|
+
const declared = this.macroContractNameFromNode(value, which);
|
|
1595
|
+
if (!declared) return [];
|
|
1596
|
+
const marker = which === "props" ? "isPropContract" : "isEmitContract";
|
|
1597
|
+
relationships.push({
|
|
1598
|
+
source: componentName,
|
|
1599
|
+
target: declared.name,
|
|
1600
|
+
type: ASTEngine.RelationshipType.References,
|
|
1601
|
+
confidence: declared.resolved ? 1 : 0.8,
|
|
1602
|
+
...declared.node.startPosition ? { line_number: declared.node.startPosition.row + 1 } : {},
|
|
1603
|
+
metadata: {
|
|
1604
|
+
[marker]: true,
|
|
1605
|
+
isOptionsApi: true,
|
|
1606
|
+
...declared.module ? { contractModule: declared.module } : {},
|
|
1607
|
+
...declared.resolved ? {} : { unresolved: true }
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
return [];
|
|
1611
|
+
}
|
|
1612
|
+
/** The declaration a bare identifier argument names, without a call node. */
|
|
1613
|
+
macroContractNameFromNode(arg, _which) {
|
|
1614
|
+
if (arg.type !== "identifier") return void 0;
|
|
1615
|
+
const root = this.scriptRootOf(arg);
|
|
1616
|
+
return {
|
|
1617
|
+
name: arg.text,
|
|
1618
|
+
node: arg,
|
|
1619
|
+
module: root ? this.importModuleFor(arg.text, arg) : void 0,
|
|
1620
|
+
resolved: root ? this.hasTopLevelDeclaration(arg.text, arg) : false
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* The identifiers a heritage option names, in the three shapes Vue accepts:
|
|
1625
|
+
* a bare name (`extends: Base`), an array of them (`extends: [A, B]`), and a
|
|
1626
|
+
* string name — which Vue also honours for a globally-registered component.
|
|
1627
|
+
*
|
|
1628
|
+
* Only names are taken. An inline OBJECT (`extends: { methods: … }`) is a
|
|
1629
|
+
* merge rather than a reference, and its members are extracted by the caller;
|
|
1630
|
+
* naming the literal's own keys as base classes would fabricate an inheritance
|
|
1631
|
+
* to a property.
|
|
1632
|
+
*/
|
|
1633
|
+
identifiersOf(node) {
|
|
1634
|
+
const out = [];
|
|
1635
|
+
if (node.type === "identifier") {
|
|
1636
|
+
out.push({ name: node.text, node });
|
|
1637
|
+
return out;
|
|
1638
|
+
}
|
|
1639
|
+
if (node.type === "string") {
|
|
1640
|
+
const v = unquote(node.text);
|
|
1641
|
+
if (v) out.push({ name: v, node });
|
|
1642
|
+
return out;
|
|
1643
|
+
}
|
|
1644
|
+
if (node.type === "array") {
|
|
1645
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1646
|
+
const c = node.namedChild(i);
|
|
1647
|
+
if (!c) continue;
|
|
1648
|
+
if (c.type === "identifier") out.push({ name: c.text, node: c });
|
|
1649
|
+
else if (c.type === "string") {
|
|
1650
|
+
const v = unquote(c.text);
|
|
1651
|
+
if (v) out.push({ name: v, node: c });
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return out;
|
|
1655
|
+
}
|
|
1656
|
+
return out;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* F12: a same-file BINDING as the macro's argument —
|
|
1660
|
+
* `defineProps(boundProps)` / `defineEmits(boundEmits)`.
|
|
1661
|
+
*
|
|
1662
|
+
* The name is an `identifier`; this resolves it to its `variable_declarator`
|
|
1663
|
+
* in the SAME script tree and reads the literal it holds, which is the only
|
|
1664
|
+
* form whose keys are published by nothing else — so component-owned symbols
|
|
1665
|
+
* are safe here and match what the inline runtime form already emits. A
|
|
1666
|
+
* binding that does not resolve (imported from a sibling module) returns
|
|
1667
|
+
* nothing and the caller emits the contract edge instead; this walk never
|
|
1668
|
+
* leaves the tree, so it costs no I/O and cannot depend on the filesystem.
|
|
1669
|
+
*/
|
|
1670
|
+
boundMacroEntries(arg, scriptRoot, which) {
|
|
1671
|
+
if (!arg) return [];
|
|
1672
|
+
const nameNode = arg.type === "identifier" ? arg : this.findChildByType(arg, "identifier");
|
|
1673
|
+
if (!nameNode) return [];
|
|
1674
|
+
const wanted = nameNode.text;
|
|
1675
|
+
const stack = [scriptRoot];
|
|
1676
|
+
while (stack.length > 0) {
|
|
1677
|
+
const n = stack.pop();
|
|
1678
|
+
if (n.type === "variable_declarator") {
|
|
1679
|
+
const nameField = n.childForFieldName("name");
|
|
1680
|
+
const valueField = n.childForFieldName("value");
|
|
1681
|
+
if (nameField?.type === "identifier" && nameField.text === wanted && valueField) {
|
|
1682
|
+
return this.propEntries(peelTypeWrappers(valueField));
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1686
|
+
const c = n.namedChild(i);
|
|
1687
|
+
if (c) stack.push(c);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
void which;
|
|
1691
|
+
return [];
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* F12: the DECLARATION a macro names, when it is not an inline literal —
|
|
1695
|
+
* `defineProps<AffixProps>()` (a `type_identifier` in `type_arguments`) or
|
|
1696
|
+
* `defineProps(affixProps)` (a `binding` identifier). Returns the name plus
|
|
1697
|
+
* the node that writes it, and whether the declaration is reachable in THIS
|
|
1698
|
+
* tree (`resolved`) — which selects the edge's confidence, since a
|
|
1699
|
+
* same-file declaration is a certainty and an imported one is the import's
|
|
1700
|
+
* word for it.
|
|
1701
|
+
*/
|
|
1702
|
+
macroContractName(callNode, arg, _which) {
|
|
1703
|
+
if (arg) {
|
|
1704
|
+
let inner = arg;
|
|
1705
|
+
for (let i = 0; i < 8 && inner; i++) {
|
|
1706
|
+
if (inner.type === "identifier") break;
|
|
1707
|
+
if (inner.type === "as_expression" || inner.type === "satisfies_expression" || inner.type === "parenthesized_expression" || inner.type === "non_null_expression") {
|
|
1708
|
+
const kids = inner.namedChildren;
|
|
1709
|
+
inner = kids.length > 0 ? kids[0] ?? null : null;
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
break;
|
|
1713
|
+
}
|
|
1714
|
+
if (inner?.type === "identifier") {
|
|
1715
|
+
const resolved = this.hasTopLevelDeclaration(inner.text, callNode);
|
|
1716
|
+
return { name: inner.text, node: inner, module: this.importModuleFor(inner.text, callNode), resolved };
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return void 0;
|
|
1720
|
+
}
|
|
1721
|
+
/** Is `name` declared at this tree's top level (an interface or a type alias)? */
|
|
1722
|
+
hasTopLevelDeclaration(name, callNode) {
|
|
1723
|
+
const root = callNode.parent ? this.scriptRootOf(callNode) : null;
|
|
1724
|
+
const scope = root ?? callNode;
|
|
1725
|
+
const stack = [];
|
|
1726
|
+
for (let i = 0; i < scope.namedChildCount; i++) {
|
|
1727
|
+
const c = scope.namedChild(i);
|
|
1728
|
+
if (c) stack.push(c);
|
|
1729
|
+
}
|
|
1730
|
+
while (stack.length > 0) {
|
|
1731
|
+
const n = stack.pop();
|
|
1732
|
+
if ((n.type === "interface_declaration" || n.type === "type_alias_declaration") && n.childForFieldName("name")?.text === name) {
|
|
1733
|
+
return true;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return false;
|
|
1737
|
+
}
|
|
1738
|
+
/** The `program` node this call sits in, by climbing parents (web-tree-sitter
|
|
1739
|
+
* hands back fresh wrappers, so identity is never compared by reference). */
|
|
1740
|
+
/** The first call expression in `root` whose callee is `name` (F16). */
|
|
1741
|
+
findCallNamed(root, name) {
|
|
1742
|
+
const stack = [root];
|
|
1743
|
+
while (stack.length > 0) {
|
|
1744
|
+
const n = stack.pop();
|
|
1745
|
+
if (n.type === "call_expression") {
|
|
1746
|
+
const fn = n.childForFieldName("function");
|
|
1747
|
+
if (fn?.text === name) return n;
|
|
1748
|
+
}
|
|
1749
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1750
|
+
const c = n.namedChild(i);
|
|
1751
|
+
if (c) stack.push(c);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* F32 . The component name a `name:` option declares, resolved one
|
|
1758
|
+
* step further than a string literal.
|
|
1759
|
+
*
|
|
1760
|
+
* WHY. F17 ruled that a non-literal `name:` keeps the file stem and
|
|
1761
|
+
* records `declaredNameUnreadable`, because the value is "an IMPORTED IDENTIFIER,
|
|
1762
|
+
* whose value lives in another module". That ruling is right about an import and
|
|
1763
|
+
* wrong about the shape this corpus is full of — element-plus declares almost
|
|
1764
|
+
* every component's name through a LOCAL constant:
|
|
1765
|
+
*
|
|
1766
|
+
* const COMPONENT_NAME = 'ElInput' // input.vue
|
|
1767
|
+
* defineOptions({ name: COMPONENT_NAME })
|
|
1768
|
+
*
|
|
1769
|
+
* Measured on the 1,162-file draw before this: 724 unresolved component-usage
|
|
1770
|
+
* `instantiates` edges, of which **253 (34.9%) named a component whose own name is
|
|
1771
|
+
* assigned as a string literal in the DECLARING file** — `ElInput` 59 edges,
|
|
1772
|
+
* `ElSelect` 42, `ElSwitch` 33, `ElForm` 20, 25 distinct names — while the lane
|
|
1773
|
+
* published the stem (`input`, `select`) and no reader could bind the tag.
|
|
1774
|
+
*
|
|
1775
|
+
* WHAT IT READS, and the ORDER is the contract: an IMPORTED identifier is refused
|
|
1776
|
+
* FIRST (F17's case — the value really is in another module, and this lane does not
|
|
1777
|
+
* do cross-file resolution), then a TOP-LEVEL `const`/`let`/`var NAME = '<literal>'`
|
|
1778
|
+
* in the SAME program is read. Top-level only, for the same reason
|
|
1779
|
+
* `importModuleFor` and `hasTopLevelDeclaration` are: a declarator nested inside a
|
|
1780
|
+
* function body is not a binding the options object can see, so `program`'s direct
|
|
1781
|
+
* children are the complete search space.
|
|
1782
|
+
*
|
|
1783
|
+
* A `name:` that is neither a literal nor a readable local const still returns
|
|
1784
|
+
* undefined, so the caller keeps F17's stem fallback and its marker.
|
|
1785
|
+
*/
|
|
1786
|
+
declaredNameValue(valueNode, scriptRoot) {
|
|
1787
|
+
if (!valueNode) return void 0;
|
|
1788
|
+
if (valueNode.type === "string") return { name: unquote(valueNode.text) };
|
|
1789
|
+
if (valueNode.type !== "identifier") return void 0;
|
|
1790
|
+
const name = valueNode.text;
|
|
1791
|
+
if (this.importModuleFor(name, valueNode) !== void 0) return void 0;
|
|
1792
|
+
for (let i = 0; i < scriptRoot.namedChildCount; i++) {
|
|
1793
|
+
const stmt = scriptRoot.namedChild(i);
|
|
1794
|
+
if (!stmt) continue;
|
|
1795
|
+
if (stmt.type !== "lexical_declaration" && stmt.type !== "variable_declaration") continue;
|
|
1796
|
+
for (let j = 0; j < stmt.namedChildCount; j++) {
|
|
1797
|
+
const d = stmt.namedChild(j);
|
|
1798
|
+
if (!d || d.type !== "variable_declarator") continue;
|
|
1799
|
+
if (d.childForFieldName("name")?.text !== name) continue;
|
|
1800
|
+
const value = d.childForFieldName("value");
|
|
1801
|
+
if (value?.type === "string") return { name: unquote(value.text), constName: name };
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return void 0;
|
|
1805
|
+
}
|
|
1806
|
+
/** The first object-typed argument of a call, if any (F16). */
|
|
1807
|
+
firstObjectArgument(call) {
|
|
1808
|
+
const args = call.childForFieldName("arguments");
|
|
1809
|
+
if (!args) return null;
|
|
1810
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
1811
|
+
const c = args.namedChild(i);
|
|
1812
|
+
if (c?.type === "object") return c;
|
|
1813
|
+
}
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1816
|
+
scriptRootOf(node) {
|
|
1817
|
+
let cur = node.parent;
|
|
1818
|
+
let last = node;
|
|
1819
|
+
while (cur) {
|
|
1820
|
+
if (cur.type === "program") return cur;
|
|
1821
|
+
last = cur;
|
|
1822
|
+
cur = cur.parent;
|
|
1823
|
+
}
|
|
1824
|
+
return last.parent ? null : last;
|
|
1825
|
+
}
|
|
1826
|
+
/** The module specifier an identifier was imported from, if any. */
|
|
1827
|
+
importModuleFor(name, callNode) {
|
|
1828
|
+
const root = this.scriptRootOf(callNode);
|
|
1829
|
+
if (!root) return void 0;
|
|
1830
|
+
const stack = [];
|
|
1831
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
1832
|
+
const c = root.namedChild(i);
|
|
1833
|
+
if (c) stack.push(c);
|
|
1834
|
+
}
|
|
1835
|
+
while (stack.length > 0) {
|
|
1836
|
+
const n = stack.pop();
|
|
1837
|
+
if (n.type === "import_statement") {
|
|
1838
|
+
const src = this.findChildByType(n, "string");
|
|
1839
|
+
const clause = n.namedChildren.find((c) => c.type === "import_clause");
|
|
1840
|
+
for (const c of clause?.namedChildren ?? []) {
|
|
1841
|
+
if (!c) continue;
|
|
1842
|
+
if (c.type === "named_imports") {
|
|
1843
|
+
for (const sp of c.namedChildren) {
|
|
1844
|
+
if (!sp) continue;
|
|
1845
|
+
const alias = sp.childForFieldName("alias")?.text;
|
|
1846
|
+
const imp = sp.childForFieldName("name")?.text;
|
|
1847
|
+
if ((alias ?? imp) === name) return src ? unquote(src.text) : void 0;
|
|
1848
|
+
}
|
|
1849
|
+
} else if (c.type === "identifier" && c.text === name) {
|
|
1850
|
+
return src ? unquote(src.text) : void 0;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
return void 0;
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* F21 . Does `name` at this call site still denote Vue's
|
|
1859
|
+
* `provide`/`inject`? Ordered narrowest-first, and the ORDER is the contract:
|
|
1860
|
+
* a decorator is a syntactic position, so it decides before any name lookup; a
|
|
1861
|
+
* bound name is decided by its module; only an UNBOUND name reaches the local
|
|
1862
|
+
* declaration test, where a top-level declaration of the same name is the
|
|
1863
|
+
* shadow. A nested declaration deliberately does NOT count: it does not shadow
|
|
1864
|
+
* a call in a sibling scope, and treating it as one would suppress genuine
|
|
1865
|
+
* contract edges in real components — the failure mode a blanket name filter
|
|
1866
|
+
* has anyway.
|
|
1867
|
+
*/
|
|
1868
|
+
contractNameIsVueBinding(name, callNode) {
|
|
1869
|
+
if (this.isInsideDecorator(callNode)) return false;
|
|
1870
|
+
const module = this.importModuleFor(name, callNode);
|
|
1871
|
+
if (module !== void 0) return isVueModuleSpecifier(module);
|
|
1872
|
+
return !this.hasProgramBinding(name, callNode);
|
|
1873
|
+
}
|
|
1874
|
+
/** Is this call written inside a decorator (`@inject("db")`)? */
|
|
1875
|
+
isInsideDecorator(node) {
|
|
1876
|
+
let cur = node.parent;
|
|
1877
|
+
while (cur) {
|
|
1878
|
+
if (cur.type === "decorator") return true;
|
|
1879
|
+
cur = cur.parent;
|
|
1880
|
+
}
|
|
1881
|
+
return false;
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Is `name` declared as a VALUE binding among this program's top-level
|
|
1885
|
+
* statements (`function inject`, `class inject`, `const/let/var inject`, and a
|
|
1886
|
+
* destructuring declaration that binds it)? Short-circuits on the same
|
|
1887
|
+
* reasoning as `importModuleFor`: only top-level statements can shadow a
|
|
1888
|
+
* program-wide global, and `program`'s direct children are their whole search
|
|
1889
|
+
* space.
|
|
1890
|
+
*/
|
|
1891
|
+
hasProgramBinding(name, callNode) {
|
|
1892
|
+
const root = this.scriptRootOf(callNode);
|
|
1893
|
+
if (!root) return false;
|
|
1894
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
1895
|
+
const n = root.namedChild(i);
|
|
1896
|
+
if (!n) continue;
|
|
1897
|
+
if (n.type === "function_declaration" || n.type === "generator_function_declaration" || n.type === "class_declaration") {
|
|
1898
|
+
if (n.childForFieldName("name")?.text === name) return true;
|
|
1899
|
+
} else if (n.type === "lexical_declaration" || n.type === "variable_declaration") {
|
|
1900
|
+
for (let j = 0; j < n.namedChildCount; j++) {
|
|
1901
|
+
const d = n.namedChild(j);
|
|
1902
|
+
if (d?.type !== "variable_declarator") continue;
|
|
1903
|
+
const bound = d.childForFieldName("name");
|
|
1904
|
+
if (!bound) continue;
|
|
1905
|
+
if (bound.type === "identifier" ? bound.text === name : this.patternBindsName(bound, name)) return true;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
return false;
|
|
1910
|
+
}
|
|
1911
|
+
/** Does a destructuring PATTERN bind `name`? (an object/array pattern's own
|
|
1912
|
+
* identifier leaves, including a shorthand `{ inject }`.) */
|
|
1913
|
+
patternBindsName(pattern, name) {
|
|
1914
|
+
const stack = [pattern];
|
|
1915
|
+
while (stack.length > 0) {
|
|
1916
|
+
const n = stack.pop();
|
|
1917
|
+
if ((n.type === "identifier" || n.type === "shorthand_property_identifier_pattern") && n.text === name) {
|
|
1918
|
+
return true;
|
|
1919
|
+
}
|
|
1920
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1921
|
+
const c = n.namedChild(i);
|
|
1922
|
+
if (c) stack.push(c);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
return false;
|
|
1926
|
+
}
|
|
1927
|
+
propNames(node) {
|
|
1928
|
+
return this.propEntries(node).map((e) => e.name);
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* The prop/emit names WITH the node that writes each one (#825).
|
|
1932
|
+
*
|
|
1933
|
+
* The string-only walk was enough while nothing asked WHERE a prop is
|
|
1934
|
+
* declared, and the caller then stamped every prop with the `props: { … }`
|
|
1935
|
+
* PAIR's range — one line for all of them. The oracle made that visible the
|
|
1936
|
+
* moment `contains` edges carried lines: Account.vue claimed `user` at line 17
|
|
1937
|
+
* (`props: {`) while `user` is written at 18 (the same for UserCard.vue:60 and
|
|
1938
|
+
* TabPane.vue:70's `type`). A prop's declaration line is the line its own key
|
|
1939
|
+
* is written on, which only this walk can say.
|
|
1940
|
+
*/
|
|
1941
|
+
/**
|
|
1942
|
+
* F3 (vue master review 2026-09-30): the DEFAULTS object of a
|
|
1943
|
+
* `withDefaults(defineProps<T>(), { … })` call.
|
|
1944
|
+
*
|
|
1945
|
+
* The macro handler visits the inner `defineProps` call, so the defaults sit
|
|
1946
|
+
* one frame UP the parent chain — that frame is what was never looked at, and
|
|
1947
|
+
* it is the whole reason 98 corpus files lost their declared defaults. Climbs
|
|
1948
|
+
* from the macro call to the nearest enclosing `withDefaults` and returns its
|
|
1949
|
+
* SECOND argument, or null when there is no wrapper (the plain `defineProps`
|
|
1950
|
+
* form, which legitimately has no defaults). The hop bound guards against a
|
|
1951
|
+
* pathological tree; `scriptRootOf` above climbs the same way without one.
|
|
1952
|
+
*/
|
|
1953
|
+
withDefaultsObject(macroCall) {
|
|
1954
|
+
let n = macroCall.parent;
|
|
1955
|
+
let hops = 0;
|
|
1956
|
+
while (n && hops < 8) {
|
|
1957
|
+
if (n.type === "call_expression") {
|
|
1958
|
+
const fn = n.childForFieldName("function");
|
|
1959
|
+
if (fn && fn.text === "withDefaults") return n.childForFieldName("arguments")?.namedChild(1) ?? null;
|
|
1960
|
+
}
|
|
1961
|
+
n = n.parent;
|
|
1962
|
+
hops++;
|
|
1963
|
+
}
|
|
1964
|
+
return null;
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* F3: key → raw default-value TEXT of a `withDefaults` defaults object.
|
|
1968
|
+
*
|
|
1969
|
+
* The value is kept as WRITTEN SOURCE, not evaluated: a default may be any
|
|
1970
|
+
* expression (`0`, `'hi'`, `() => []`, `defaultSize`) and this lane has no
|
|
1971
|
+
* evaluator, so pretending otherwise would be a worse lie than storing the
|
|
1972
|
+
* text. A BRACKET-LED computed key is skipped under the same F6 ruling — its
|
|
1973
|
+
* "default" is a variable reference, not a static default.
|
|
1974
|
+
*/
|
|
1975
|
+
defaultsEntries(objects) {
|
|
1976
|
+
const out = /* @__PURE__ */ new Map();
|
|
1977
|
+
if (!objects || objects.type !== "object") return out;
|
|
1978
|
+
for (let i = 0; i < objects.namedChildCount; i++) {
|
|
1979
|
+
const c = objects.namedChild(i);
|
|
1980
|
+
if (!c || c.type !== "pair") continue;
|
|
1981
|
+
const keyNode = c.childForFieldName("key");
|
|
1982
|
+
const raw = keyNode?.text;
|
|
1983
|
+
if (!raw || raw.startsWith("[")) continue;
|
|
1984
|
+
const value = c.childForFieldName("value");
|
|
1985
|
+
if (!value) continue;
|
|
1986
|
+
const k = keyNode.type === "string" ? unquote(raw) : raw;
|
|
1987
|
+
if (k) out.set(k, value.text);
|
|
1988
|
+
}
|
|
1989
|
+
return out;
|
|
1990
|
+
}
|
|
1991
|
+
propEntries(node) {
|
|
1992
|
+
if (!node) return [];
|
|
1993
|
+
const out = [];
|
|
1994
|
+
if (node.type === "array") {
|
|
1995
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1996
|
+
const c = node.namedChild(i);
|
|
1997
|
+
if (c && c.type === "string") {
|
|
1998
|
+
const v = unquote(c.text);
|
|
1999
|
+
if (v) out.push({ name: v, node: c });
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
} else if (node.type === "object") {
|
|
2003
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2004
|
+
const c = node.namedChild(i);
|
|
2005
|
+
if (c && c.type === "pair") {
|
|
2006
|
+
const keyNode = c.childForFieldName("key");
|
|
2007
|
+
const key = keyNode?.text;
|
|
2008
|
+
if (key && !key.startsWith("[")) out.push({ name: key, node: keyNode ?? c });
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return out;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* F13 (full 829-file corpus draw, 2026-09-30): the IDENTIFIER elements of a
|
|
2016
|
+
* `defineProps([ … ])` / `defineEmits([ … ])` array.
|
|
2017
|
+
*
|
|
2018
|
+
* `propEntries` accepts string elements only, which is right for its callers —
|
|
2019
|
+
* an identifier is not an event NAME and must not become a symbol (that would
|
|
2020
|
+
* be the F6 phantom class again: `CHANGE_EVENT` is a declaration, and its
|
|
2021
|
+
* value is the event). But dropping it was worse than either: measured, 13
|
|
2022
|
+
* corpus files lost a member of their event set outright, and
|
|
2023
|
+
* time-select.vue's `defineEmits([CHANGE_EVENT, 'blur', 'focus', 'clear',
|
|
2024
|
+
* UPDATE_MODEL_EVENT])` published three events where the component emits five.
|
|
2025
|
+
*
|
|
2026
|
+
* Those constants are imported, so resolving their VALUES needs cross-file
|
|
2027
|
+
* constant folding this lane does not do. The DECLARATION is nameable right
|
|
2028
|
+
* here, and F12 already established the rule for exactly this situation in
|
|
2029
|
+
* `defineProps(binding)`: a named declaration becomes a contract EDGE, never a
|
|
2030
|
+
* copy of its members. This is a separate accessor rather than a flag on
|
|
2031
|
+
* `propEntries` so that the Options-API path, which uses the same helper for
|
|
2032
|
+
* `emits: [ … ]`, keeps ignoring identifiers rather than inheriting a
|
|
2033
|
+
* declaration edge it never asked for.
|
|
2034
|
+
*/
|
|
2035
|
+
propDeclarationEntries(node) {
|
|
2036
|
+
if (!node || node.type !== "array") return [];
|
|
2037
|
+
const out = [];
|
|
2038
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2039
|
+
const c = node.namedChild(i);
|
|
2040
|
+
if (c && c.type === "identifier") out.push({ name: c.text, node: c });
|
|
2041
|
+
}
|
|
2042
|
+
return out;
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* V1 (#419): names from the TYPE-LITERAL generic form of the script-setup
|
|
2046
|
+
* compiler macros — defineProps<{ id: string }>() and
|
|
2047
|
+
* defineEmits<{ (e: 'save'): void }>() — where the public contract lives in
|
|
2048
|
+
* the type_arguments node, not a runtime argument. Probe-verified against
|
|
2049
|
+
* tree-sitter-typescript: props = property_identifier under object_type;
|
|
2050
|
+
* emits = the first string literal of each call_signature's formal
|
|
2051
|
+
* parameters.
|
|
2052
|
+
*/
|
|
2053
|
+
genericMacroNames(callNode, which) {
|
|
2054
|
+
return this.genericMacroEntries(callNode, which).map((e) => e.name);
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* (#825) follow-up: genericMacroNames carrying the name node, so macro
|
|
2058
|
+
* symbols range at their own key lines (same contract as the runtime form).
|
|
2059
|
+
*/
|
|
2060
|
+
/**
|
|
2061
|
+
* F14 (full 829-file corpus draw, 2026-09-30): the shape of a macro's TYPE
|
|
2062
|
+
* ARGUMENT, at the level of the contract, with one rule — descend through
|
|
2063
|
+
* intersection / union constituents and through type wrappers, read a generic's
|
|
2064
|
+
* OWN name, and STOP at an object literal.
|
|
2065
|
+
*
|
|
2066
|
+
* The old code had no shape at all: `findChildByType(typeArgs, "object_type")`
|
|
2067
|
+
* reads DIRECT children only, so `defineProps<{ … } & Pick<X, 'a'>>()` — an
|
|
2068
|
+
* intersection — matched nothing and published NO props (measured:
|
|
2069
|
+
* cascader-panel/src/menu.vue, prop symbols `[]`), while a separate
|
|
2070
|
+
* blind-descendant search for the first `type_identifier` reached INTO the
|
|
2071
|
+
* object literal's property signatures and named a prop's TYPE as the
|
|
2072
|
+
* contract. Both halves were visible only at the 829-file draw, and in
|
|
2073
|
+
* opposite directions: node.vue's plain literal `defineProps<{ node: NodeShape;
|
|
2074
|
+
* menuId?: string }>()` should name nothing, yet the oracle derived
|
|
2075
|
+
* `NodeShape` for it — a MISSING that no sampled run had ever seen.
|
|
2076
|
+
*
|
|
2077
|
+
* `CascaderNode[]` inside a property signature is still inside one, so the
|
|
2078
|
+
* array case that made the old pre-order rule look reasonable is covered by
|
|
2079
|
+
* the same stop.
|
|
2080
|
+
*/
|
|
2081
|
+
contractTypeShape(typeArgs) {
|
|
2082
|
+
const objectTypes = [];
|
|
2083
|
+
const participants = [];
|
|
2084
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
2085
|
+
const addParticipant = (n) => {
|
|
2086
|
+
if (!n || !n.text) return;
|
|
2087
|
+
if (seenParticipants.has(n.text)) return;
|
|
2088
|
+
seenParticipants.add(n.text);
|
|
2089
|
+
participants.push({ name: n.text, node: n });
|
|
2090
|
+
};
|
|
2091
|
+
const walk = (node, depth) => {
|
|
2092
|
+
if (!node || depth > 8) return;
|
|
2093
|
+
switch (node.type) {
|
|
2094
|
+
case "intersection_type":
|
|
2095
|
+
case "union_type":
|
|
2096
|
+
for (let i = 0; i < node.namedChildCount; i++) walk(node.namedChild(i), depth + 1);
|
|
2097
|
+
return;
|
|
2098
|
+
case "parenthesized_type":
|
|
2099
|
+
case "as_expression":
|
|
2100
|
+
case "satisfies_expression":
|
|
2101
|
+
case "non_null_expression": {
|
|
2102
|
+
const kids = node.namedChildren;
|
|
2103
|
+
walk(kids.length ? kids[0] : null, depth + 1);
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
case "object_type":
|
|
2107
|
+
objectTypes.push(node);
|
|
2108
|
+
return;
|
|
2109
|
+
case "generic_type": {
|
|
2110
|
+
const kids = node.namedChildren;
|
|
2111
|
+
const nameNode = kids.length > 0 ? kids[0] : null;
|
|
2112
|
+
if (nameNode && (nameNode.type === "type_identifier" || nameNode.type === "nested_type_identifier")) {
|
|
2113
|
+
addParticipant(nameNode);
|
|
2114
|
+
}
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
case "type_identifier":
|
|
2118
|
+
addParticipant(node);
|
|
2119
|
+
return;
|
|
2120
|
+
default:
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
if (typeArgs) for (let i = 0; i < typeArgs.namedChildCount; i++) walk(typeArgs.namedChild(i), 0);
|
|
2125
|
+
return { objectTypes, participants };
|
|
2126
|
+
}
|
|
2127
|
+
genericMacroEntries(callNode, which) {
|
|
2128
|
+
const out = [];
|
|
2129
|
+
const typeArgs = this.findChildByType(callNode, "type_arguments");
|
|
2130
|
+
if (!typeArgs) return out;
|
|
2131
|
+
const objectTypes = this.contractTypeShape(typeArgs).objectTypes;
|
|
2132
|
+
for (const objectType of objectTypes) for (let i = 0; i < objectType.namedChildCount; i++) {
|
|
2133
|
+
const member = objectType.namedChild(i);
|
|
2134
|
+
if (!member) continue;
|
|
2135
|
+
if (which === "props") {
|
|
2136
|
+
if (member.type === "property_signature") {
|
|
2137
|
+
const pid = this.findChildByType(member, "property_identifier");
|
|
2138
|
+
if (pid?.text) out.push({ name: pid.text, node: pid });
|
|
2139
|
+
} else if (member.type === "index_signature") {
|
|
2140
|
+
}
|
|
2141
|
+
} else {
|
|
2142
|
+
if (member.type === "call_signature") {
|
|
2143
|
+
const fp = this.findChildByType(member, "formal_parameters");
|
|
2144
|
+
if (!fp) continue;
|
|
2145
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2146
|
+
for (let j = 0; j < fp.namedChildCount; j++) {
|
|
2147
|
+
const p = fp.namedChild(j);
|
|
2148
|
+
if (!p) continue;
|
|
2149
|
+
this.collectUnionStringNodes(p, seen, out);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
return out;
|
|
2155
|
+
}
|
|
2156
|
+
/** collectUnionStrings carrying the literal node (generic-emits key lines). */
|
|
2157
|
+
collectUnionStringNodes(root, seen, out) {
|
|
2158
|
+
const stack = [root];
|
|
2159
|
+
while (stack.length > 0) {
|
|
2160
|
+
const n = stack.pop();
|
|
2161
|
+
if (n.type === "string") {
|
|
2162
|
+
const v = unquote(n.text);
|
|
2163
|
+
if (v && !seen.has(v)) {
|
|
2164
|
+
seen.add(v);
|
|
2165
|
+
out.push({ name: v, node: n });
|
|
2166
|
+
}
|
|
2167
|
+
continue;
|
|
2168
|
+
}
|
|
2169
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2170
|
+
const c = n.namedChild(i);
|
|
2171
|
+
if (c) stack.push(c);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
findDescendantType(root, type) {
|
|
2176
|
+
const stack = [root];
|
|
2177
|
+
while (stack.length > 0) {
|
|
2178
|
+
const n = stack.pop();
|
|
2179
|
+
if (n.type === type) return n;
|
|
2180
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2181
|
+
const c = n.namedChild(i);
|
|
2182
|
+
if (c) stack.push(c);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* V1b (#419): collect every string-literal event name under a parameter's
|
|
2189
|
+
* type — the type_annotation may be a literal_type ('save') or a union_type
|
|
2190
|
+
* ('save' | 'close'); both declare event names.
|
|
2191
|
+
*/
|
|
2192
|
+
collectUnionStrings(root, out) {
|
|
2193
|
+
const stack = [root];
|
|
2194
|
+
while (stack.length > 0) {
|
|
2195
|
+
const n = stack.pop();
|
|
2196
|
+
if (n.type === "string") {
|
|
2197
|
+
const v = unquote(n.text);
|
|
2198
|
+
if (v) out.add(v);
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2202
|
+
const c = n.namedChild(i);
|
|
2203
|
+
if (c) stack.push(c);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* V2 (#419) + M1 (review wave) + D1 (deeper review): extract simple
|
|
2209
|
+
* identifier tokens from a template expression's raw text ({{ count }},
|
|
2210
|
+
* :label="label", @click="onSave", v-model="count", v-if="show"). The vue
|
|
2211
|
+
* grammar keeps these as opaque text — no child AST — so tokenization is
|
|
2212
|
+
* by character walk (no regex). Only chain ROOTS are uses: in `item.id`,
|
|
2213
|
+
* `id` is a read of the loop variable, never of a same-named member —
|
|
2214
|
+
* emitting it fabricated member edges (family receiver-only rule). An
|
|
2215
|
+
* identifier preceded (modulo whitespace) by `.`, `?.` or `#` is skipped.
|
|
2216
|
+
* String/template-literal CONTENTS are not identifiers either:
|
|
2217
|
+
* `@click="go('save')"` must not read 'save' as a member. Single/double
|
|
2218
|
+
* quotes skip to their closer (backslash escapes honored); backticks
|
|
2219
|
+
* additionally re-enter code inside ${...} (nesting tracked).
|
|
2220
|
+
*
|
|
2221
|
+
* V4 (#419): identifiers bound by a template-local construct (v-for loop
|
|
2222
|
+
* var, slot prop) in the current scope are skipped — they shadow any
|
|
2223
|
+
* same-named member.
|
|
2224
|
+
*/
|
|
2225
|
+
/** Counts the line breaks before `end` — how many lines into the fragment a
|
|
2226
|
+
* match sits. (#825): a template expression's identifier has a REAL line
|
|
2227
|
+
* (the line of the text fragment it is written in, plus its own offset within
|
|
2228
|
+
* that fragment when the fragment wraps), and the collector is the only place
|
|
2229
|
+
* that still knows it — a `Set<string>` throws that away, which is why vue's
|
|
2230
|
+
* template `uses` edges carried no line at all.
|
|
2231
|
+
*/
|
|
2232
|
+
static linesBefore(text, end) {
|
|
2233
|
+
let n = 0;
|
|
2234
|
+
for (let i = 0; i < end && i < text.length; i++) if (text[i] === "\n") n++;
|
|
2235
|
+
return n;
|
|
2236
|
+
}
|
|
2237
|
+
collectIdentifiers(text, out, shadowed, baseLine) {
|
|
2238
|
+
const isIdentStart = (ch) => ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_" || ch === "$";
|
|
2239
|
+
const isIdentPart = (ch) => isIdentStart(ch) || ch >= "0" && ch <= "9";
|
|
2240
|
+
const isSpace = (ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r";
|
|
2241
|
+
const stack = [];
|
|
2242
|
+
let braceDepth = 0;
|
|
2243
|
+
const pendingTernary = /* @__PURE__ */ new Set();
|
|
2244
|
+
const isKeyPosition = (start) => {
|
|
2245
|
+
let k = start;
|
|
2246
|
+
while (k < text.length && isIdentPart(text[k])) k++;
|
|
2247
|
+
while (k < text.length && isSpace(text[k])) k++;
|
|
2248
|
+
if (text[k] !== ":") return false;
|
|
2249
|
+
if (text[k + 1] === ":") return false;
|
|
2250
|
+
if (pendingTernary.has(braceDepth)) {
|
|
2251
|
+
pendingTernary.delete(braceDepth);
|
|
2252
|
+
return false;
|
|
2253
|
+
}
|
|
2254
|
+
return true;
|
|
2255
|
+
};
|
|
2256
|
+
const emit = (word, start) => {
|
|
2257
|
+
let k = start - 1;
|
|
2258
|
+
while (k >= 0 && isSpace(text[k])) k--;
|
|
2259
|
+
const prev = k >= 0 ? text[k] : "";
|
|
2260
|
+
const prev2 = k - 1 >= 0 ? text[k - 1] : "";
|
|
2261
|
+
const chained = prev === "." || prev2 === "?" && prev === "." || prev === "#";
|
|
2262
|
+
if (!chained && !shadowed?.has(word)) out.set(word, baseLine + _RealVueAnalyzer.linesBefore(text, start));
|
|
2263
|
+
};
|
|
2264
|
+
let i = 0;
|
|
2265
|
+
while (i < text.length) {
|
|
2266
|
+
const ch = text[i];
|
|
2267
|
+
const top = stack.length > 0 ? stack[stack.length - 1] : "";
|
|
2268
|
+
if (top === "'" || top === '"') {
|
|
2269
|
+
if (ch === "\\") {
|
|
2270
|
+
i += 2;
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
if (ch === top) stack.pop();
|
|
2274
|
+
i++;
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2277
|
+
if (top === "`") {
|
|
2278
|
+
if (ch === "\\") {
|
|
2279
|
+
i += 2;
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
2282
|
+
if (ch === "$" && text[i + 1] === "{") {
|
|
2283
|
+
stack.push("${");
|
|
2284
|
+
braceDepth++;
|
|
2285
|
+
pendingTernary.delete(braceDepth);
|
|
2286
|
+
i += 2;
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
if (ch === "`") {
|
|
2290
|
+
stack.pop();
|
|
2291
|
+
i++;
|
|
2292
|
+
continue;
|
|
2293
|
+
}
|
|
2294
|
+
i++;
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
2298
|
+
stack.push(ch);
|
|
2299
|
+
i++;
|
|
2300
|
+
continue;
|
|
2301
|
+
}
|
|
2302
|
+
if (ch === "{") {
|
|
2303
|
+
stack.push(ch);
|
|
2304
|
+
braceDepth++;
|
|
2305
|
+
pendingTernary.delete(braceDepth);
|
|
2306
|
+
i++;
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
if (ch === "}") {
|
|
2310
|
+
if (top === "{" || top === "${") {
|
|
2311
|
+
stack.pop();
|
|
2312
|
+
braceDepth--;
|
|
2313
|
+
pendingTernary.delete(braceDepth);
|
|
2314
|
+
}
|
|
2315
|
+
i++;
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
if (ch === "?") {
|
|
2319
|
+
if (text[i + 1] !== "." && text[i + 1] !== "?") pendingTernary.add(braceDepth);
|
|
2320
|
+
i++;
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
if (ch === ":") {
|
|
2324
|
+
if (pendingTernary.has(braceDepth)) pendingTernary.delete(braceDepth);
|
|
2325
|
+
i++;
|
|
2326
|
+
continue;
|
|
2327
|
+
}
|
|
2328
|
+
if (!isIdentStart(ch)) {
|
|
2329
|
+
i++;
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
let j = i + 1;
|
|
2333
|
+
while (j < text.length && isIdentPart(text[j])) j++;
|
|
2334
|
+
if (!isKeyPosition(i)) emit(text.slice(i, j), i);
|
|
2335
|
+
i = j;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* V4 (#419): template-local bindings declared on an element's start tag.
|
|
2340
|
+
* (a) v-for="item in items" / v-for="(item, i) in items" — the loop
|
|
2341
|
+
* variable(s) are bound locally;
|
|
2342
|
+
* (b) slot props #default="{ row }" / v-slot="{ row, col }" — destructured
|
|
2343
|
+
* names are bound locally.
|
|
2344
|
+
*/
|
|
2345
|
+
collectNodeShadowBindings(startTag, shadowed) {
|
|
2346
|
+
for (let i = 0; i < startTag.namedChildCount; i++) {
|
|
2347
|
+
const c = startTag.namedChild(i);
|
|
2348
|
+
if (!c || c.type !== "directive_attribute") continue;
|
|
2349
|
+
const dname = this.findChildByType(c, "directive_name")?.text;
|
|
2350
|
+
const value = this.findChildByType(c, "quoted_attribute_value");
|
|
2351
|
+
const attrVal = value ? this.findChildByType(value, "attribute_value") : null;
|
|
2352
|
+
if (!attrVal) continue;
|
|
2353
|
+
if (dname === "v-for") {
|
|
2354
|
+
this.collectVForVars(attrVal.text, shadowed);
|
|
2355
|
+
} else if (dname === "#" || dname === "v-slot") {
|
|
2356
|
+
this.collectIdentifierNames(attrVal.text, shadowed, attrVal.startPosition.row);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* V4 (#419): the loop-variable part of v-for="item in items" (before
|
|
2362
|
+
* in/of) is bound locally; only the collection part is a member use.
|
|
2363
|
+
*/
|
|
2364
|
+
collectVForUsage(text, out, shadowed, baseLine) {
|
|
2365
|
+
const m = this.parseVForDirective(text);
|
|
2366
|
+
const collection = m && m.collectionStart >= 0 ? m.collection : text;
|
|
2367
|
+
const start = m && m.collectionStart >= 0 ? m.collectionStart : 0;
|
|
2368
|
+
this.collectIdentifiers(collection, out, shadowed, baseLine + _RealVueAnalyzer.linesBefore(text, start));
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* V4 (#419): the loop variables themselves (item, or item + i in the
|
|
2372
|
+
* parenthesized form) are bound locally.
|
|
2373
|
+
*/
|
|
2374
|
+
collectVForVars(text, shadowed) {
|
|
2375
|
+
const m = this.parseVForDirective(text);
|
|
2376
|
+
const vars = m ? m.aliases : text;
|
|
2377
|
+
this.collectIdentifierNames(vars, shadowed, 0);
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* The `v-for` directive grammar, with NO regular expression.
|
|
2381
|
+
*
|
|
2382
|
+
* v-for="item in items" v-for="(row, col) in grid"
|
|
2383
|
+
* v-for="item of items" v-for="(value, key) of map"
|
|
2384
|
+
*
|
|
2385
|
+
* This replaces `/^\s*\(?([^)]*?)\)?\s+(?:in|of)\s+(.+)$/` and its tail-less
|
|
2386
|
+
* sibling, and reproduces their behaviour rather than an idealised version of
|
|
2387
|
+
* it, because three of the old behaviours are load-bearing:
|
|
2388
|
+
*
|
|
2389
|
+
* 1. `([^)]*?)` is LAZY, so the alias head is the SHORTEST run without a `)`
|
|
2390
|
+
* for which the rest of the pattern matches. A greedy scan would make
|
|
2391
|
+
* `item in items` bind `item in items`.
|
|
2392
|
+
* 2. `\)?` is optional, so the `)` belongs to the KEYWORD MATCH, not the head.
|
|
2393
|
+
* `(row, col) in grid` must bind `row` and `col` — and must stop at the
|
|
2394
|
+
* first `)`, because the head cannot contain one.
|
|
2395
|
+
* 3. `(.+)$` has no `s` flag, so `.` does NOT match a newline: a collection that
|
|
2396
|
+
* spans lines fails the pattern, and the caller falls back to treating the
|
|
2397
|
+
* WHOLE directive as the collection. That fallback is preserved, including
|
|
2398
|
+
* its consequence that such a file loses its shadowing (the tail-less
|
|
2399
|
+
* sibling still matches, so `collectVForVars` is unaffected).
|
|
2400
|
+
*
|
|
2401
|
+
* `collectionStart` is the byte offset of the collection within `text`, which
|
|
2402
|
+
* is what `collectVForUsage` needs to count the line breaks before it. It is
|
|
2403
|
+
* `-1` when the tail-less form matched but the collection form did not.
|
|
2404
|
+
*/
|
|
2405
|
+
parseVForDirective(text) {
|
|
2406
|
+
const n = text.length;
|
|
2407
|
+
const isWs = (ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
2408
|
+
let i = 0;
|
|
2409
|
+
while (i < n && isWs(text[i])) i++;
|
|
2410
|
+
if (i < n && text[i] === "(") i++;
|
|
2411
|
+
const aliasStart = i;
|
|
2412
|
+
const keywordAt = (e) => {
|
|
2413
|
+
let j = e;
|
|
2414
|
+
if (j < n && text[j] === ")") j++;
|
|
2415
|
+
const ws1 = j;
|
|
2416
|
+
while (j < n && isWs(text[j])) j++;
|
|
2417
|
+
if (j === ws1) return null;
|
|
2418
|
+
const two = text.slice(j, j + 2);
|
|
2419
|
+
if (two !== "in" && two !== "of") return null;
|
|
2420
|
+
j += 2;
|
|
2421
|
+
const ws2 = j;
|
|
2422
|
+
while (j < n && isWs(text[j])) j++;
|
|
2423
|
+
if (j === ws2) return null;
|
|
2424
|
+
return { wsStart: ws2, wsEnd: j };
|
|
2425
|
+
};
|
|
2426
|
+
const limit = (() => {
|
|
2427
|
+
const close = text.indexOf(")", aliasStart);
|
|
2428
|
+
return close < 0 ? n : close;
|
|
2429
|
+
})();
|
|
2430
|
+
let aliases = "";
|
|
2431
|
+
let collectionStart = -1;
|
|
2432
|
+
for (let e = aliasStart; e <= limit; e++) {
|
|
2433
|
+
const kw = keywordAt(e);
|
|
2434
|
+
if (!kw) continue;
|
|
2435
|
+
let chosen = -1;
|
|
2436
|
+
for (let q = kw.wsEnd; q > kw.wsStart; q--) {
|
|
2437
|
+
const tail = text.slice(q);
|
|
2438
|
+
if (tail.length === 0) continue;
|
|
2439
|
+
if (tail.includes("\n")) continue;
|
|
2440
|
+
chosen = q;
|
|
2441
|
+
break;
|
|
2442
|
+
}
|
|
2443
|
+
if (chosen < 0) continue;
|
|
2444
|
+
aliases = text.slice(aliasStart, e);
|
|
2445
|
+
collectionStart = chosen;
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
if (collectionStart < 0) {
|
|
2449
|
+
for (let e = aliasStart; e <= limit; e++) {
|
|
2450
|
+
if (!keywordAt(e)) continue;
|
|
2451
|
+
aliases = text.slice(aliasStart, e);
|
|
2452
|
+
break;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (aliases === "") return null;
|
|
2456
|
+
return {
|
|
2457
|
+
aliases,
|
|
2458
|
+
collection: collectionStart >= 0 ? text.slice(collectionStart) : text,
|
|
2459
|
+
collectionStart
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Tokenize identifiers out of template expression TEXT and SHADOW them, using
|
|
2464
|
+
* the same stack machine `collectIdentifiers` uses for member reads. The lane
|
|
2465
|
+
* previously ran a regex over this input in two places while the stack machine
|
|
2466
|
+
* sat one method away; both now share one tokenizer.
|
|
2467
|
+
*/
|
|
2468
|
+
collectIdentifierNames(text, into, baseLine) {
|
|
2469
|
+
const scratch = /* @__PURE__ */ new Map();
|
|
2470
|
+
this.collectIdentifiers(text, scratch, void 0, baseLine);
|
|
2471
|
+
for (const k of scratch.keys()) into.add(k);
|
|
2472
|
+
}
|
|
2473
|
+
isLifecycleHook(key) {
|
|
2474
|
+
return key === "beforeCreate" || key === "created" || key === "beforeMount" || key === "mounted" || key === "beforeUpdate" || key === "updated" || key === "beforeUnmount" || key === "unmounted" || key === "activated" || key === "deactivated" || key === "errorCaptured" || key === "renderTracked" || key === "renderTriggered" || key === "beforeDestroy" || key === "destroyed" || // M3 (review wave): the Vue 3 SSR hook was missing, so
|
|
2475
|
+
// serverPrefetch() methods were invisible.
|
|
2476
|
+
key === "serverPrefetch";
|
|
2477
|
+
}
|
|
2478
|
+
// ============================================================================
|
|
2479
|
+
// HELPERS
|
|
2480
|
+
// ============================================================================
|
|
2481
|
+
/**
|
|
2482
|
+
* M14 #299 - evidence-tier classification for EVERY emitted relationship.
|
|
2483
|
+
* Same rules as the JVM family. Vue template component tags are
|
|
2484
|
+
* usage_contextual (component usage by tag name, not construction).
|
|
2485
|
+
*
|
|
2486
|
+
* THE 0.7 FLOOR IS APPLIED HERE, AND THIS IS THE ONLY LAYER THAT CAN APPLY IT
|
|
2487
|
+
* (F20, re-measured 2026-10-25 on the way to closing the fixture). The contract
|
|
2488
|
+
* in src/lsp/evidence_tier.ts is two rules that interact: rule 1 says a STORED
|
|
2489
|
+
* legend-tier evidenceClass WINS and is "never re-derived, never re-upgraded",
|
|
2490
|
+
* and rule 2 says an edge below the 0.7 floor is a guess and MUST read
|
|
2491
|
+
* `inferred`. Because rule 1 short-circuits the derivation, a confident class
|
|
2492
|
+
* STORED on a heuristic row is final — nobody downstream can correct it. The
|
|
2493
|
+
* lane measured the consequence over 1,151 corpus files: 1,628 rows sit at
|
|
2494
|
+
* confidence 0.6 (1,616 `uses` field-read/bare-read rows and 12 unresolved
|
|
2495
|
+
* dynamic-import/`require` rows) and every one was stored as `usage_contextual`
|
|
2496
|
+
* or `import_based`. So the floor is applied at the TOP of this pass, before
|
|
2497
|
+
* the stored-class skip and before any per-label arm: a sub-floor row stores
|
|
2498
|
+
* `inferred` whoever produced it (a delegate's `import_based` at 0.6 is the
|
|
2499
|
+
* same claim about a row the analyzer cannot see inside), and a row at or
|
|
2500
|
+
* above the floor keeps the arm's own classification. The confident template
|
|
2501
|
+
* read at 0.95 stays `usage_contextual` — the fixture pins it beside the
|
|
2502
|
+
* downgraded rows so a mutant that downgraded EVERYTHING would be caught.
|
|
2503
|
+
*/
|
|
2504
|
+
classifyEvidenceTier(relationships) {
|
|
2505
|
+
for (const r of relationships) {
|
|
2506
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
2507
|
+
const conf = typeof r.confidence === "number" ? r.confidence : 1;
|
|
2508
|
+
if (conf < 0.7) {
|
|
2509
|
+
md.evidenceClass = "inferred";
|
|
2510
|
+
continue;
|
|
2511
|
+
}
|
|
2512
|
+
if (md.evidenceClass) continue;
|
|
2513
|
+
const t = String(r.type ?? r.relationshipType ?? "");
|
|
2514
|
+
if (t === "contains" || t === "exports") {
|
|
2515
|
+
md.evidenceClass = "structural";
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
if (t === "references") {
|
|
2519
|
+
md.evidenceClass = md.isImport ? "import_based" : "reference_qualified";
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
if (t === "extends" || t === "implements" || t === "overrides") {
|
|
2523
|
+
md.evidenceClass = conf >= 1 ? "structural" : conf >= 0.85 ? "usage_contextual" : "inferred";
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
if (t === "instantiates") {
|
|
2527
|
+
md.evidenceClass = md.isComponentUsage ? "usage_contextual" : "structural";
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
if (t === "renders") {
|
|
2531
|
+
md.evidenceClass = "usage_contextual";
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
md.evidenceClass = "usage_contextual";
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
findDescendantObject(root) {
|
|
2538
|
+
const stack = [root];
|
|
2539
|
+
while (stack.length > 0) {
|
|
2540
|
+
const n = stack.pop();
|
|
2541
|
+
if (n.type === "object") return n;
|
|
2542
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2543
|
+
const c = n.namedChild(i);
|
|
2544
|
+
if (c) stack.push(c);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return void 0;
|
|
2548
|
+
}
|
|
2549
|
+
findExportDefault(root) {
|
|
2550
|
+
const stack = [root];
|
|
2551
|
+
while (stack.length > 0) {
|
|
2552
|
+
const n = stack.pop();
|
|
2553
|
+
if (n.type === "export_statement" && this.isDefaultExportStatement(n)) return n;
|
|
2554
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2555
|
+
const c = n.namedChild(i);
|
|
2556
|
+
if (c) stack.push(c);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return null;
|
|
2560
|
+
}
|
|
2561
|
+
isDefaultExportStatement(n) {
|
|
2562
|
+
let seenExport = false;
|
|
2563
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
2564
|
+
const c = n.child(i);
|
|
2565
|
+
if (!c || c.type === "comment" || c.type === "decorator") continue;
|
|
2566
|
+
if (!seenExport) {
|
|
2567
|
+
if (!c.isNamed && c.text === "export") seenExport = true;
|
|
2568
|
+
else if (c.isNamed) return false;
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
if (!c.isNamed && c.text === "default") return true;
|
|
2572
|
+
if (c.isNamed) return false;
|
|
2573
|
+
}
|
|
2574
|
+
return false;
|
|
2575
|
+
}
|
|
2576
|
+
elementTagName(element) {
|
|
2577
|
+
return this.elementTagNameNode(element)?.text;
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* F9(b): the tag as WRITTEN, dotted segments and namespace included.
|
|
2581
|
+
*
|
|
2582
|
+
* `tag_name` spells only the FIRST segment: for `<Form.Input />` it is `Form`,
|
|
2583
|
+
* so reading it alone is what silently dropped the sub-component. The spelling
|
|
2584
|
+
* lives in the enclosing `start_tag` / `self_closing_tag` text, up to the first
|
|
2585
|
+
* delimiter. Written with indexOf rather than a regex on purpose — this file's
|
|
2586
|
+
* regex census is a claim the docs now publish, and adding an 7th literal to
|
|
2587
|
+
* recover a string the grammar already hands us in two pieces is the wrong
|
|
2588
|
+
* trade.
|
|
2589
|
+
*/
|
|
2590
|
+
elementTagSpelling(element) {
|
|
2591
|
+
const tagNode = this.elementTagNameNode(element);
|
|
2592
|
+
const holder = this.findChildByType(element, "start_tag") ?? this.findChildByType(element, "self_closing_tag");
|
|
2593
|
+
let text = holder?.text ?? tagNode?.text;
|
|
2594
|
+
if (!text) return void 0;
|
|
2595
|
+
if (text.charCodeAt(0) === 60) text = text.slice(1);
|
|
2596
|
+
let end = text.length;
|
|
2597
|
+
for (const delim of [" ", " ", "\n", "\r", ">", "/"]) {
|
|
2598
|
+
const i = text.indexOf(delim);
|
|
2599
|
+
if (i >= 0 && i < end) end = i;
|
|
2600
|
+
}
|
|
2601
|
+
return end > 0 ? text.slice(0, end) : void 0;
|
|
2602
|
+
}
|
|
2603
|
+
/** The `tag_name` node itself — the node that SPELLS the tag, for its line. */
|
|
2604
|
+
elementTagNameNode(element) {
|
|
2605
|
+
const tagNode = this.findChildByType(element, "start_tag") || this.findChildByType(element, "self_closing_tag");
|
|
2606
|
+
if (!tagNode) return null;
|
|
2607
|
+
return this.findChildByType(tagNode, "tag_name");
|
|
2608
|
+
}
|
|
2609
|
+
offsetRange(range, lineOffset, colOffset, indexOffset = 0) {
|
|
2610
|
+
const out = { ...range };
|
|
2611
|
+
out.startLine += lineOffset;
|
|
2612
|
+
out.endLine += lineOffset;
|
|
2613
|
+
if (range.startLine === 1) out.startColumn += colOffset;
|
|
2614
|
+
if (indexOffset && out.startIndex !== void 0) out.startIndex += indexOffset;
|
|
2615
|
+
if (indexOffset && out.endIndex !== void 0) out.endIndex += indexOffset;
|
|
2616
|
+
return out;
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* H2 (review wave): full script→SFC shift — lines always, columns when on
|
|
2620
|
+
* the script's first line (endColumn only when the range never leaves
|
|
2621
|
+
* it). Errors, exports, and imports were pushed raw, pointing at script
|
|
2622
|
+
* coordinates while symbols pointed at SFC ones.
|
|
2623
|
+
*/
|
|
2624
|
+
shiftScriptRange(range, lineOffset, colOffset, indexOffset = 0) {
|
|
2625
|
+
const out = { ...range };
|
|
2626
|
+
const firstLine = out.startLine === 1;
|
|
2627
|
+
out.startLine += lineOffset;
|
|
2628
|
+
out.endLine += lineOffset;
|
|
2629
|
+
if (firstLine && colOffset) {
|
|
2630
|
+
out.startColumn += colOffset;
|
|
2631
|
+
if (out.endLine === out.startLine) out.endColumn += colOffset;
|
|
2632
|
+
}
|
|
2633
|
+
if (indexOffset && out.startIndex !== void 0) out.startIndex += indexOffset;
|
|
2634
|
+
if (indexOffset && out.endIndex !== void 0) out.endIndex += indexOffset;
|
|
2635
|
+
return out;
|
|
2636
|
+
}
|
|
2637
|
+
/**
|
|
2638
|
+
* The object literal a `provide(key, …)` publishes, unwrapping ONE call layer.
|
|
2639
|
+
*
|
|
2640
|
+
* `provide(k, reactive({ a, b }))` publishes `a` and `b`, but the direct
|
|
2641
|
+
* argument is a call, not an object, so reading only `args[1]` as an object
|
|
2642
|
+
* found nothing. `reactive` / `readonly` / `markRaw` / `computed` all take the
|
|
2643
|
+
* object as their first argument, which is why one level is enough;
|
|
2644
|
+
* `form.vue`'s `provide(formContextKey, reactive({ ...toRefs(props), emit,
|
|
2645
|
+
* resetFields, clearValidate, validateField }))` lost every one of those keys
|
|
2646
|
+
* this way. One layer only — deeper is a shape this lane should not guess at.
|
|
2647
|
+
*/
|
|
2648
|
+
providedValueObject(value) {
|
|
2649
|
+
if (!value) return null;
|
|
2650
|
+
if (value.type === "object") return value;
|
|
2651
|
+
if (value.type !== "call_expression") return null;
|
|
2652
|
+
const args = this.findChildByType(value, "arguments");
|
|
2653
|
+
if (!args) return null;
|
|
2654
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
2655
|
+
const c = args.namedChild(i);
|
|
2656
|
+
if (c && c.type === "object") return c;
|
|
2657
|
+
}
|
|
2658
|
+
return null;
|
|
2659
|
+
}
|
|
2660
|
+
findChildByType(node, type) {
|
|
2661
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2662
|
+
const child = node.namedChild(i);
|
|
2663
|
+
if (child && child.type === type) return child;
|
|
2664
|
+
}
|
|
2665
|
+
return null;
|
|
2666
|
+
}
|
|
2667
|
+
nodeToRange(node) {
|
|
2668
|
+
return {
|
|
2669
|
+
startLine: node.startPosition.row + 1,
|
|
2670
|
+
startColumn: node.startPosition.column + 1,
|
|
2671
|
+
endLine: node.endPosition.row + 1,
|
|
2672
|
+
endColumn: node.endPosition.column + 1,
|
|
2673
|
+
startIndex: node.startIndex,
|
|
2674
|
+
endIndex: node.endIndex
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
export {
|
|
2679
|
+
RealVueAnalyzer
|
|
2680
|
+
};
|