@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,3427 @@
|
|
|
1
|
+
import {
|
|
2
|
+
treeContainsNodeType
|
|
3
|
+
} from "./chunk-R2GW4F6R.js";
|
|
4
|
+
import {
|
|
5
|
+
ASTEngine
|
|
6
|
+
} from "./chunk-V44PGPA6.js";
|
|
7
|
+
import {
|
|
8
|
+
BINDING_SEP
|
|
9
|
+
} from "./chunk-KAET47ZL.js";
|
|
10
|
+
|
|
11
|
+
// src/ast/real-javascript-analyzer.ts
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
|
|
14
|
+
// src/ast/js-builtin-prototypes.ts
|
|
15
|
+
var JS_BUILTIN_CHAINS = {
|
|
16
|
+
Map: ["Map", "Object"],
|
|
17
|
+
Set: ["Set", "Object"],
|
|
18
|
+
WeakMap: ["WeakMap", "Object"],
|
|
19
|
+
WeakSet: ["WeakSet", "Object"],
|
|
20
|
+
Promise: ["Promise", "Object"],
|
|
21
|
+
Array: ["Array", "Object"],
|
|
22
|
+
String: ["String", "Object"],
|
|
23
|
+
Number: ["Number", "Object"],
|
|
24
|
+
Boolean: ["Boolean", "Object"],
|
|
25
|
+
Error: ["Error", "Object"],
|
|
26
|
+
TypeError: ["TypeError", "Error", "Object"],
|
|
27
|
+
RangeError: ["RangeError", "Error", "Object"],
|
|
28
|
+
Date: ["Date", "Object"],
|
|
29
|
+
RegExp: ["RegExp", "Object"],
|
|
30
|
+
JSON: ["JSON", "Object"],
|
|
31
|
+
Reflect: ["Reflect", "Object"],
|
|
32
|
+
URL: ["URL", "Object"],
|
|
33
|
+
URLSearchParams: ["URLSearchParams", "Object"],
|
|
34
|
+
Object: ["Object"],
|
|
35
|
+
HTMLElement: ["HTMLElement", "Element", "Node", "EventTarget", "Object"],
|
|
36
|
+
Element: ["Element", "Node", "EventTarget", "Object"],
|
|
37
|
+
Node: ["Node", "EventTarget", "Object"],
|
|
38
|
+
EventTarget: ["EventTarget", "Object"]
|
|
39
|
+
};
|
|
40
|
+
var MEMBERS = {
|
|
41
|
+
Map: ["clear", "delete", "entries", "forEach", "get", "has", "keys", "set", "values", "size", "[Symbol.iterator]"],
|
|
42
|
+
Set: ["add", "clear", "delete", "entries", "forEach", "has", "keys", "size", "values", "[Symbol.iterator]"],
|
|
43
|
+
WeakMap: ["delete", "get", "has", "set"],
|
|
44
|
+
WeakSet: ["add", "delete", "has"],
|
|
45
|
+
Promise: ["all", "allSettled", "any", "catch", "finally", "race", "reject", "resolve", "then"],
|
|
46
|
+
Array: ["at", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flat", "flatMap", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "length", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toString", "unshift", "values", "[Symbol.iterator]"],
|
|
47
|
+
String: ["at", "charAt", "charCodeAt", "codePointAt", "concat", "endsWith", "includes", "indexOf", "lastIndexOf", "length", "localeCompare", "match", "normalize", "padEnd", "padStart", "repeat", "replace", "replaceAll", "search", "slice", "split", "startsWith", "substring", "toLowerCase", "toString", "toUpperCase", "trim", "trimEnd", "trimStart"],
|
|
48
|
+
Number: ["toExponential", "toFixed", "toPrecision", "toString", "valueOf"],
|
|
49
|
+
Boolean: ["toString", "valueOf"],
|
|
50
|
+
Error: ["cause", "message", "name", "stack", "toString"],
|
|
51
|
+
Date: ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds", "getTime", "getTimezoneOffset", "getUTCDate", "getUTCDay", "getUTCFullYear", "getUTCHours", "setDate", "setFullYear", "setHours", "setMinutes", "setMonth", "setSeconds", "setTime", "toISOString", "toJSON", "toLocaleDateString", "toLocaleString", "toLocaleTimeString", "toString", "toUTCString", "valueOf"],
|
|
52
|
+
RegExp: ["exec", "flags", "global", "lastIndex", "source", "test", "toString"],
|
|
53
|
+
JSON: ["parse", "stringify"],
|
|
54
|
+
Reflect: ["apply", "construct", "defineProperty", "deleteProperty", "get", "getOwnPropertyDescriptor", "getPrototypeOf", "getOwnPropertyDescriptor", "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf"],
|
|
55
|
+
URL: ["hash", "href", "host", "hostname", "origin", "password", "pathname", "port", "protocol", "search", "searchParams", "toJSON", "toString", "username"],
|
|
56
|
+
URLSearchParams: ["append", "delete", "entries", "forEach", "get", "getAll", "has", "keys", "set", "size", "sort", "toString", "values", "[Symbol.iterator]"],
|
|
57
|
+
Object: ["assign", "create", "defineProperty", "entries", "freeze", "fromEntries", "getOwnPropertyDescriptor", "getOwnPropertyNames", "getPrototypeOf", "hasOwn", "hasOwnProperty", "is", "isFrozen", "isSealed", "keys", "preventExtensions", "propertyIsEnumerable", "seal", "setPrototypeOf", "toString", "valueOf"],
|
|
58
|
+
HTMLElement: ["attachShadow", "blur", "click", "closest", "focus", "getAttribute", "getBoundingClientRect", "getClientRects", "getElementsByClassName", "getElementsByTagName", "hidden", "id", "innerHTML", "innerText", "matches", "remove", "removeAttribute", "scrollIntoView", "setAttribute", "shadowRoot", "style", "tagName", "textContent", "title"],
|
|
59
|
+
Element: ["closest", "getAttribute", "getAttributeNames", "hasAttribute", "matches", "removeAttribute", "setAttribute", "tagName"],
|
|
60
|
+
Node: ["appendChild", "childNodes", "cloneNode", "contains", "insertBefore", "nodeName", "nodeType", "parentNode", "removeChild", "replaceChild", "textContent"],
|
|
61
|
+
EventTarget: ["addEventListener", "removeEventListener", "dispatchEvent"]
|
|
62
|
+
};
|
|
63
|
+
var MEMBER_SETS = Object.fromEntries(
|
|
64
|
+
Object.entries(MEMBERS).map(([k, v]) => [k, new Set(v)])
|
|
65
|
+
);
|
|
66
|
+
function isJsBuiltin(base) {
|
|
67
|
+
return Object.prototype.hasOwnProperty.call(JS_BUILTIN_CHAINS, base);
|
|
68
|
+
}
|
|
69
|
+
function jsBuiltinHasMember(base, member) {
|
|
70
|
+
const chain = JS_BUILTIN_CHAINS[base];
|
|
71
|
+
if (!chain) return false;
|
|
72
|
+
for (const link of chain) {
|
|
73
|
+
const set = MEMBER_SETS[link];
|
|
74
|
+
if (set && set.has(member)) return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
var HOST_GLOBALS = {
|
|
79
|
+
// WHATWG HTML §7.3 — the Window interface and its members.
|
|
80
|
+
window: "WHATWG HTML \xA77.3 Window",
|
|
81
|
+
document: "WHATWG HTML \xA77.3 Window",
|
|
82
|
+
console: "WHATWG HTML \xA77.3 Window.console",
|
|
83
|
+
location: "WHATWG HTML \xA77.3 Window.location",
|
|
84
|
+
navigator: "WHATWG HTML \xA77.3 Window.navigator",
|
|
85
|
+
history: "WHATWG HTML \xA77.3 Window.history",
|
|
86
|
+
screen: "WHATWG HTML \xA77.3 Window.screen",
|
|
87
|
+
localStorage: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.localStorage",
|
|
88
|
+
sessionStorage: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.sessionStorage",
|
|
89
|
+
fetch: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.fetch",
|
|
90
|
+
setTimeout: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.setTimeout",
|
|
91
|
+
clearTimeout: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.clearTimeout",
|
|
92
|
+
setInterval: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.setInterval",
|
|
93
|
+
clearInterval: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.clearInterval",
|
|
94
|
+
requestAnimationFrame: "WHATWG HTML \xA77.3 Window.requestAnimationFrame",
|
|
95
|
+
cancelAnimationFrame: "WHATWG HTML \xA77.3 Window.cancelAnimationFrame",
|
|
96
|
+
alert: "WHATWG HTML \xA77.3 Window.alert",
|
|
97
|
+
confirm: "WHATWG HTML \xA77.3 Window.confirm",
|
|
98
|
+
prompt: "WHATWG HTML \xA77.3 Window.prompt",
|
|
99
|
+
atob: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.atob",
|
|
100
|
+
btoa: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.btoa",
|
|
101
|
+
queueMicrotask: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.queueMicrotask",
|
|
102
|
+
structuredClone: "WHATWG HTML \xA77.3 WindowOrWorkerGlobalScope.structuredClone",
|
|
103
|
+
performance: "WHATWG HTML \xA77.3 Window.performance",
|
|
104
|
+
customElements: "WHATWG HTML \xA77.3 Window.customElements",
|
|
105
|
+
// ECMA-262 §19–§26 — the global object's own properties.
|
|
106
|
+
globalThis: "ECMA-262 \xA719.1 globalThis",
|
|
107
|
+
JSON: "ECMA-262 \xA725.5 JSON",
|
|
108
|
+
Math: "ECMA-262 \xA721.3 Math",
|
|
109
|
+
Object: "ECMA-262 \xA720.1 Object",
|
|
110
|
+
Array: "ECMA-262 \xA723.1 Array",
|
|
111
|
+
String: "ECMA-262 \xA722.1 String",
|
|
112
|
+
Number: "ECMA-262 \xA721.1 Number",
|
|
113
|
+
Boolean: "ECMA-262 \xA720.3 Boolean",
|
|
114
|
+
Date: "ECMA-262 \xA721.4 Date",
|
|
115
|
+
RegExp: "ECMA-262 \xA722.2 RegExp",
|
|
116
|
+
Promise: "ECMA-262 \xA727.2 Promise",
|
|
117
|
+
Map: "ECMA-262 \xA724.1 Map",
|
|
118
|
+
Set: "ECMA-262 \xA724.2 Set",
|
|
119
|
+
WeakMap: "ECMA-262 \xA724.3 WeakMap",
|
|
120
|
+
WeakSet: "ECMA-262 \xA724.4 WeakSet",
|
|
121
|
+
Symbol: "ECMA-262 \xA720.4 Symbol",
|
|
122
|
+
Proxy: "ECMA-262 \xA728.2 Proxy",
|
|
123
|
+
Reflect: "ECMA-262 \xA728.1 Reflect",
|
|
124
|
+
Error: "ECMA-262 \xA720.5 Error",
|
|
125
|
+
TypeError: "ECMA-262 \xA720.5.6 TypeError",
|
|
126
|
+
RangeError: "ECMA-262 \xA720.5.5 RangeError",
|
|
127
|
+
parseInt: "ECMA-262 \xA719.2.5 parseInt",
|
|
128
|
+
parseFloat: "ECMA-262 \xA719.2.4 parseFloat",
|
|
129
|
+
isNaN: "ECMA-262 \xA719.2.3 isNaN",
|
|
130
|
+
isFinite: "ECMA-262 \xA719.2.2 isFinite",
|
|
131
|
+
encodeURIComponent: "ECMA-262 \xA719.2.6 encodeURIComponent",
|
|
132
|
+
decodeURIComponent: "ECMA-262 \xA719.2.7 decodeURIComponent",
|
|
133
|
+
encodeURI: "ECMA-262 \xA719.2.8 encodeURI",
|
|
134
|
+
decodeURI: "ECMA-262 \xA719.2.9 decodeURI",
|
|
135
|
+
NaN: "ECMA-262 \xA76.1.6 NaN",
|
|
136
|
+
Infinity: "ECMA-262 \xA76.1.6 Infinity",
|
|
137
|
+
undefined: "ECMA-262 \xA76.1.1 undefined"
|
|
138
|
+
};
|
|
139
|
+
function hostGlobalCitation(name) {
|
|
140
|
+
return HOST_GLOBALS[name] ?? null;
|
|
141
|
+
}
|
|
142
|
+
function memberChainClaim(receiverRoot, _member) {
|
|
143
|
+
if (!receiverRoot) return null;
|
|
144
|
+
const root = receiverRoot.trim();
|
|
145
|
+
if (!root) return null;
|
|
146
|
+
const cite = hostGlobalCitation(root);
|
|
147
|
+
return cite ? `${cite} (member of a host global; receiver root \`${root}\`)` : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/ast/js-runtime-globals.ts
|
|
151
|
+
var RUNTIME_GLOBALS = {
|
|
152
|
+
// ── Node.js host surface ──────────────────────────────────────────────────────────────────────
|
|
153
|
+
process: "Node.js API \xA7process (https://nodejs.org/api/process.html) \u2014 a global",
|
|
154
|
+
Buffer: "Node.js API \xA7Buffer (https://nodejs.org/api/buffer.html) \u2014 a global class",
|
|
155
|
+
global: "Node.js API \xA7global (https://nodejs.org/api/globals.html#global)",
|
|
156
|
+
setImmediate: "Node.js API \xA7setImmediate (https://nodejs.org/api/timers.html#setimmediatecallback-args)",
|
|
157
|
+
clearImmediate: "Node.js API \xA7clearImmediate (https://nodejs.org/api/timers.html#clearimmediateimmediate)",
|
|
158
|
+
require: "Node.js API \xA7require (https://nodejs.org/api/modules.html#requireid) \u2014 the CommonJS wrapper local, available bare",
|
|
159
|
+
module: "Node.js API \xA7module (https://nodejs.org/api/modules.html#the-module-object) \u2014 the CommonJS wrapper local, available bare",
|
|
160
|
+
exports: "Node.js API \xA7exports (https://nodejs.org/api/modules.html#exports) \u2014 the CommonJS wrapper local, available bare",
|
|
161
|
+
__dirname: "Node.js API \xA7__dirname (https://nodejs.org/api/modules.html#__dirname)",
|
|
162
|
+
__filename: "Node.js API \xA7__filename (https://nodejs.org/api/modules.html#__filename)",
|
|
163
|
+
// ── Test-runner globals (Jest-canonical spellings; Vitest mirrors them by design) ────────────
|
|
164
|
+
describe: "Jest Globals \xA7describe (https://jestjs.io/docs/api) \u2014 also a Mocha BDD global (https://mochajs.org/) and mirrored by Vitest (https://vitest.dev/api/)",
|
|
165
|
+
context: "Mocha BDD \xA7context (https://mochajs.org/) \u2014 an alias of describe",
|
|
166
|
+
it: "Jest Globals \xA7test, of which `it` is the alias (https://jestjs.io/docs/api) \u2014 also a Mocha BDD global and mirrored by Vitest",
|
|
167
|
+
test: "Jest Globals \xA7test (https://jestjs.io/docs/api) \u2014 also a Mocha BDD global and mirrored by Vitest",
|
|
168
|
+
specify: "Mocha BDD \xA7specify (https://mochajs.org/) \u2014 an alias of it",
|
|
169
|
+
expect: "Jest API \xA7expect (https://jestjs.io/docs/expect) \u2014 mirrored by Vitest (https://vitest.dev/api/)",
|
|
170
|
+
beforeAll: "Jest Globals \xA7beforeAll (https://jestjs.io/docs/api) \u2014 mirrored by Vitest",
|
|
171
|
+
afterAll: "Jest Globals \xA7afterAll (https://jestjs.io/docs/api) \u2014 mirrored by Vitest",
|
|
172
|
+
beforeEach: "Jest Globals \xA7beforeEach (https://jestjs.io/docs/api) \u2014 also a Mocha BDD global; mirrored by Vitest",
|
|
173
|
+
afterEach: "Jest Globals \xA7afterEach (https://jestjs.io/docs/api) \u2014 also a Mocha BDD global; mirrored by Vitest",
|
|
174
|
+
before: "Mocha BDD \xA7before (https://mochajs.org/) \u2014 the TDD/BDD hook",
|
|
175
|
+
after: "Mocha BDD \xA7after (https://mochajs.org/) \u2014 the TDD/BDD hook",
|
|
176
|
+
jest: "Jest Object (https://jestjs.io/docs/jest-object) \u2014 a global",
|
|
177
|
+
vi: "Vitest API \xA7vi (https://vitest.dev/api/) \u2014 a global",
|
|
178
|
+
QUnit: "QUnit API (https://qunitjs.com/api/) \u2014 QUnit exposes its API through the QUnit global"
|
|
179
|
+
};
|
|
180
|
+
function runtimeGlobalCitation(name) {
|
|
181
|
+
return RUNTIME_GLOBALS[name] ?? null;
|
|
182
|
+
}
|
|
183
|
+
function runtimeGlobalReason(name) {
|
|
184
|
+
if (!isRuntimeGlobal(name)) return null;
|
|
185
|
+
return NODE_RUNTIME_NAMES.has(name) ? "node-runtime-global" : "test-runner-global";
|
|
186
|
+
}
|
|
187
|
+
var NODE_RUNTIME_NAMES = /* @__PURE__ */ new Set([
|
|
188
|
+
"process",
|
|
189
|
+
"Buffer",
|
|
190
|
+
"global",
|
|
191
|
+
"setImmediate",
|
|
192
|
+
"clearImmediate",
|
|
193
|
+
"require",
|
|
194
|
+
"module",
|
|
195
|
+
"exports",
|
|
196
|
+
"__dirname",
|
|
197
|
+
"__filename"
|
|
198
|
+
]);
|
|
199
|
+
function isRuntimeGlobal(name) {
|
|
200
|
+
return Object.prototype.hasOwnProperty.call(RUNTIME_GLOBALS, name);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/ast/real-javascript-analyzer.ts
|
|
204
|
+
var JS_CONSOLE_BUILTINS = /* @__PURE__ */ new Set([
|
|
205
|
+
"console.log",
|
|
206
|
+
"console.warn",
|
|
207
|
+
"console.error",
|
|
208
|
+
"console.info",
|
|
209
|
+
"console.debug",
|
|
210
|
+
"console.trace",
|
|
211
|
+
"console.table",
|
|
212
|
+
"console.group",
|
|
213
|
+
"console.groupEnd",
|
|
214
|
+
"console.groupCollapsed",
|
|
215
|
+
"console.time",
|
|
216
|
+
"console.timeEnd",
|
|
217
|
+
"console.assert",
|
|
218
|
+
"console.count",
|
|
219
|
+
"console.countReset",
|
|
220
|
+
"console.dir",
|
|
221
|
+
"console.dirxml",
|
|
222
|
+
"console.clear",
|
|
223
|
+
"console.profile",
|
|
224
|
+
"console.profileEnd"
|
|
225
|
+
]);
|
|
226
|
+
var JS_GLOBAL_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
227
|
+
"Promise",
|
|
228
|
+
"Date",
|
|
229
|
+
"Map",
|
|
230
|
+
"Set",
|
|
231
|
+
"WeakMap",
|
|
232
|
+
"WeakSet",
|
|
233
|
+
"Symbol",
|
|
234
|
+
"Proxy",
|
|
235
|
+
"Error",
|
|
236
|
+
"TypeError",
|
|
237
|
+
"RangeError",
|
|
238
|
+
"SyntaxError",
|
|
239
|
+
"URIError",
|
|
240
|
+
"EvalError",
|
|
241
|
+
"AggregateError",
|
|
242
|
+
"RegExp",
|
|
243
|
+
"Array",
|
|
244
|
+
"Object",
|
|
245
|
+
"Function",
|
|
246
|
+
"Boolean",
|
|
247
|
+
"Number",
|
|
248
|
+
"String",
|
|
249
|
+
"BigInt",
|
|
250
|
+
"ArrayBuffer",
|
|
251
|
+
"SharedArrayBuffer",
|
|
252
|
+
"DataView",
|
|
253
|
+
"Int8Array",
|
|
254
|
+
"Uint8Array",
|
|
255
|
+
"Uint8ClampedArray",
|
|
256
|
+
"Int16Array",
|
|
257
|
+
"Uint16Array",
|
|
258
|
+
"Int32Array",
|
|
259
|
+
"Uint32Array",
|
|
260
|
+
"Float32Array",
|
|
261
|
+
"Float64Array",
|
|
262
|
+
"BigInt64Array",
|
|
263
|
+
"BigUint64Array",
|
|
264
|
+
"WeakRef",
|
|
265
|
+
"FinalizationRegistry",
|
|
266
|
+
"AbortController",
|
|
267
|
+
"FormData",
|
|
268
|
+
"Blob",
|
|
269
|
+
"File",
|
|
270
|
+
"URL",
|
|
271
|
+
"URLSearchParams",
|
|
272
|
+
"Headers",
|
|
273
|
+
"Request",
|
|
274
|
+
"Response",
|
|
275
|
+
"Intl",
|
|
276
|
+
"Image",
|
|
277
|
+
"Worker",
|
|
278
|
+
"SharedWorker",
|
|
279
|
+
"CustomEvent",
|
|
280
|
+
"Event",
|
|
281
|
+
"MouseEvent",
|
|
282
|
+
"KeyboardEvent",
|
|
283
|
+
"ResizeObserver",
|
|
284
|
+
"MutationObserver",
|
|
285
|
+
"IntersectionObserver",
|
|
286
|
+
"PerformanceObserver",
|
|
287
|
+
"FileReader",
|
|
288
|
+
"XMLHttpRequest",
|
|
289
|
+
"WebSocket",
|
|
290
|
+
"TextEncoder",
|
|
291
|
+
"TextDecoder",
|
|
292
|
+
"DOMParser",
|
|
293
|
+
"Notification",
|
|
294
|
+
"BroadcastChannel",
|
|
295
|
+
"MessageChannel",
|
|
296
|
+
"FocusEvent",
|
|
297
|
+
"InputEvent",
|
|
298
|
+
"PointerEvent",
|
|
299
|
+
"TouchEvent",
|
|
300
|
+
"DragEvent",
|
|
301
|
+
"ErrorEvent",
|
|
302
|
+
"UIEvent",
|
|
303
|
+
"StorageEvent",
|
|
304
|
+
"PromiseRejectionEvent",
|
|
305
|
+
"HashChangeEvent",
|
|
306
|
+
"PageTransitionEvent",
|
|
307
|
+
"PopStateEvent",
|
|
308
|
+
"ProgressEvent",
|
|
309
|
+
"SpeechSynthesisEvent",
|
|
310
|
+
"AnimationEvent",
|
|
311
|
+
"TransitionEvent",
|
|
312
|
+
"ClipboardEvent",
|
|
313
|
+
"WheelEvent",
|
|
314
|
+
"CompositionEvent",
|
|
315
|
+
"BeforeUnloadEvent"
|
|
316
|
+
]);
|
|
317
|
+
var JS_OBJECT_LEAF_METHODS = /* @__PURE__ */ new Set([
|
|
318
|
+
"toString",
|
|
319
|
+
"valueOf",
|
|
320
|
+
"hasOwnProperty",
|
|
321
|
+
"isPrototypeOf",
|
|
322
|
+
"propertyIsEnumerable",
|
|
323
|
+
"toLocaleString",
|
|
324
|
+
"__defineGetter__",
|
|
325
|
+
"__defineSetter__"
|
|
326
|
+
]);
|
|
327
|
+
function unquote(text) {
|
|
328
|
+
if (text.length >= 2) {
|
|
329
|
+
const first = text[0];
|
|
330
|
+
if ((first === '"' || first === "'" || first === "`") && text[text.length - 1] === first) {
|
|
331
|
+
return text.slice(1, -1);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return text;
|
|
335
|
+
}
|
|
336
|
+
function stringContent(node) {
|
|
337
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
338
|
+
const c = node.namedChild(i);
|
|
339
|
+
if (c && c.type === "string_fragment") return c.text;
|
|
340
|
+
}
|
|
341
|
+
return unquote(node.text);
|
|
342
|
+
}
|
|
343
|
+
function stripCommentMarkers(text) {
|
|
344
|
+
if (text.startsWith("//")) return text.slice(2).trimStart();
|
|
345
|
+
if (text.startsWith("/*")) {
|
|
346
|
+
const end = text.endsWith("*/") ? text.length - 2 : text.length;
|
|
347
|
+
return text.slice(2, end).trim();
|
|
348
|
+
}
|
|
349
|
+
return text.trim();
|
|
350
|
+
}
|
|
351
|
+
function writtenSegment(prop, lhs) {
|
|
352
|
+
if (prop) return prop;
|
|
353
|
+
if (lhs.type === "subscript_expression") return lhs.childForFieldName("index");
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
function segmentName(node) {
|
|
357
|
+
return node.type === "string" ? stringContent(node) : node.text;
|
|
358
|
+
}
|
|
359
|
+
function isPlainJsIdentifier(text) {
|
|
360
|
+
if (text.length === 0) return false;
|
|
361
|
+
for (let i = 0; i < text.length; i++) {
|
|
362
|
+
const c = text.charCodeAt(i);
|
|
363
|
+
const isLetter = c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
364
|
+
const isDigit = c >= 48 && c <= 57;
|
|
365
|
+
const isStart = isLetter || c === 95 || c === 36;
|
|
366
|
+
if (i === 0) {
|
|
367
|
+
if (!isStart) return false;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (!isStart && !isDigit) return false;
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
function jsIdentifierPrefixEnd(text) {
|
|
375
|
+
let end = 0;
|
|
376
|
+
for (let i = 0; i < text.length; i++) {
|
|
377
|
+
const c = text.charCodeAt(i);
|
|
378
|
+
const isLetter = c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
379
|
+
const isDigit = c >= 48 && c <= 57;
|
|
380
|
+
const isStart = isLetter || c === 95 || c === 36;
|
|
381
|
+
if (end === 0 ? !isStart : !(isStart || isDigit)) break;
|
|
382
|
+
end = i + 1;
|
|
383
|
+
}
|
|
384
|
+
return end;
|
|
385
|
+
}
|
|
386
|
+
var JS_MODULE_LOCALITY_CACHE = /* @__PURE__ */ new WeakMap();
|
|
387
|
+
function jsProjectModuleLocalityOf(index) {
|
|
388
|
+
const cached = JS_MODULE_LOCALITY_CACHE.get(index);
|
|
389
|
+
if (cached) return cached;
|
|
390
|
+
const modules = index.modules ?? /* @__PURE__ */ new Set();
|
|
391
|
+
const paths = [];
|
|
392
|
+
const stems = /* @__PURE__ */ new Set();
|
|
393
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
394
|
+
const allSegments = /* @__PURE__ */ new Set();
|
|
395
|
+
for (const raw of modules) {
|
|
396
|
+
const m = String(raw).split("\\").join("/");
|
|
397
|
+
if (m.length === 0) continue;
|
|
398
|
+
paths.push(m);
|
|
399
|
+
const slash = m.lastIndexOf("/");
|
|
400
|
+
const base = slash >= 0 ? m.slice(slash + 1) : m;
|
|
401
|
+
const dot = base.lastIndexOf(".");
|
|
402
|
+
if (dot > 0) {
|
|
403
|
+
const stem = base.slice(0, dot);
|
|
404
|
+
if (stem.length > 0) stems.add(stem);
|
|
405
|
+
}
|
|
406
|
+
if (slash > 0) {
|
|
407
|
+
const dir = m.slice(0, slash);
|
|
408
|
+
const d = dir.lastIndexOf("/");
|
|
409
|
+
dirs.add(d >= 0 ? dir.slice(d + 1) : dir);
|
|
410
|
+
}
|
|
411
|
+
for (const seg of m.split("/")) if (seg.length > 0) allSegments.add(seg);
|
|
412
|
+
}
|
|
413
|
+
const JS_EXTS = [".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
414
|
+
const tailNamesIndexedFile = (spec) => {
|
|
415
|
+
for (const p of paths) {
|
|
416
|
+
if (p.endsWith(`/${spec}`) || p === spec) return true;
|
|
417
|
+
for (const ext of JS_EXTS) {
|
|
418
|
+
if (p.endsWith(`/${spec}${ext}`)) return true;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return false;
|
|
422
|
+
};
|
|
423
|
+
const stripQueryAndHash = (text) => {
|
|
424
|
+
for (let i = 0; i < text.length; i++) {
|
|
425
|
+
const ch = text[i];
|
|
426
|
+
if (ch === "?" || ch === "#") return text.slice(0, i);
|
|
427
|
+
}
|
|
428
|
+
return text;
|
|
429
|
+
};
|
|
430
|
+
const predicate = (spec) => {
|
|
431
|
+
const clean = stripQueryAndHash(spec.trim());
|
|
432
|
+
if (clean.length === 0) return "unknown";
|
|
433
|
+
const segments = clean.split("/");
|
|
434
|
+
const root = segments[0];
|
|
435
|
+
const rootOf = root.startsWith("@") && segments.length >= 2 ? `${root}/${segments[1]}` : root;
|
|
436
|
+
const rootOfOwn = root.startsWith("@") ? root : rootOf;
|
|
437
|
+
if (stems.has(rootOf) || dirs.has(rootOf) || dirs.has(rootOfOwn) || stems.has(rootOfOwn) || allSegments.has(rootOf) || allSegments.has(rootOfOwn)) return "project-local";
|
|
438
|
+
if (segments.length > 1 && tailNamesIndexedFile(clean)) return "project-local";
|
|
439
|
+
return "third-party";
|
|
440
|
+
};
|
|
441
|
+
JS_MODULE_LOCALITY_CACHE.set(index, predicate);
|
|
442
|
+
return predicate;
|
|
443
|
+
}
|
|
444
|
+
var NAME_NODE_TYPES = /* @__PURE__ */ new Set(["identifier", "property_identifier", "private_property_identifier"]);
|
|
445
|
+
function isNameNode(node) {
|
|
446
|
+
return !!node && NAME_NODE_TYPES.has(String(node.type ?? ""));
|
|
447
|
+
}
|
|
448
|
+
function hasBraceEquals(text) {
|
|
449
|
+
for (let i = 0; i < text.length; i++) {
|
|
450
|
+
const ch = text[i];
|
|
451
|
+
if (ch === "{" || ch === "}" || ch === ";" || ch === "=") return true;
|
|
452
|
+
}
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
function jsDecoratorName(decorator) {
|
|
456
|
+
const inner = decorator.namedChild(0);
|
|
457
|
+
const nameText = inner && (inner.type === "call_expression" || inner.type === "member_expression" || inner.type === "identifier") ? inner.type === "call_expression" ? inner.childForFieldName("function")?.text ?? inner.text.split("(")[0] : inner.text.split("(")[0] : decorator.text;
|
|
458
|
+
return stripLeadingAt(nameText) || stripLeadingAt(decorator.text);
|
|
459
|
+
}
|
|
460
|
+
function stripLeadingAt(text) {
|
|
461
|
+
return text.startsWith("@") ? text.slice(1) : text;
|
|
462
|
+
}
|
|
463
|
+
function stripWhitespace(text) {
|
|
464
|
+
let out = "";
|
|
465
|
+
for (let i = 0; i < text.length; i++) {
|
|
466
|
+
const ch = text[i];
|
|
467
|
+
if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") out += ch;
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
471
|
+
function isNodeInDeadSpans(spans, n) {
|
|
472
|
+
if (spans.length === 0) return false;
|
|
473
|
+
let p = n;
|
|
474
|
+
while (p) {
|
|
475
|
+
for (const d of spans) {
|
|
476
|
+
if (d.s <= p.startIndex && p.endIndex <= d.e) return true;
|
|
477
|
+
}
|
|
478
|
+
p = p.parent;
|
|
479
|
+
}
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
function scopeOwnerName(scopes) {
|
|
483
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
484
|
+
if (scopes[i].kind === "class" /* Class */) return scopes[i].name;
|
|
485
|
+
}
|
|
486
|
+
return void 0;
|
|
487
|
+
}
|
|
488
|
+
function bindingNameAbove(value) {
|
|
489
|
+
let q = value.parent;
|
|
490
|
+
while (q) {
|
|
491
|
+
if (q.type === "variable_declarator") {
|
|
492
|
+
const nm = q.childForFieldName("name");
|
|
493
|
+
return nm && nm.type === "identifier" ? nm.text : void 0;
|
|
494
|
+
}
|
|
495
|
+
if (q.type === "assignment_expression") {
|
|
496
|
+
const l = q.childForFieldName("left");
|
|
497
|
+
return l && l.type === "identifier" ? l.text : void 0;
|
|
498
|
+
}
|
|
499
|
+
if (q.type === "pair" || q.type === "object" || q.type === "return_statement" || q.type === "expression_statement" || q.type === "export_statement") {
|
|
500
|
+
q = q.parent;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
return void 0;
|
|
504
|
+
}
|
|
505
|
+
return void 0;
|
|
506
|
+
}
|
|
507
|
+
function containerOwnerName(n) {
|
|
508
|
+
let p = n.parent;
|
|
509
|
+
while (p) {
|
|
510
|
+
if (p.type === "class_declaration") return p.childForFieldName("name")?.text;
|
|
511
|
+
if (p.type === "class") {
|
|
512
|
+
return bindingNameAbove(p);
|
|
513
|
+
}
|
|
514
|
+
if (p.type === "object") {
|
|
515
|
+
return bindingNameAbove(p);
|
|
516
|
+
}
|
|
517
|
+
if (p.type === "function_declaration" || p.type === "function_expression" || p.type === "arrow_function" || p.type === "generator_function_declaration" || p.type === "method_definition" || p.type === "function") return void 0;
|
|
518
|
+
p = p.parent;
|
|
519
|
+
}
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
function isExternalSpecifier(specifier) {
|
|
523
|
+
if (specifier.length === 0) return false;
|
|
524
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) return false;
|
|
525
|
+
const low = specifier.toLowerCase();
|
|
526
|
+
if (low.startsWith("workspace:") || low.startsWith("file:") || low.startsWith("link:")) return false;
|
|
527
|
+
if (specifier.length >= 3) {
|
|
528
|
+
const c0 = specifier.charCodeAt(0);
|
|
529
|
+
const isLetter = c0 >= 65 && c0 <= 90 || c0 >= 97 && c0 <= 122;
|
|
530
|
+
const s1 = specifier[1];
|
|
531
|
+
const s2 = specifier[2];
|
|
532
|
+
if (isLetter && s1 === ":" && (s2 === "\\" || s2 === "/")) return false;
|
|
533
|
+
}
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
function scopeString(scopes) {
|
|
537
|
+
const parts = [];
|
|
538
|
+
for (let i = 1; i < scopes.length; i++) {
|
|
539
|
+
const s = scopes[i];
|
|
540
|
+
if (s.kind !== "global" /* Global */) parts.push(s.name);
|
|
541
|
+
}
|
|
542
|
+
return parts.join(".");
|
|
543
|
+
}
|
|
544
|
+
var FILE_LEVEL_SOURCE = "(file)";
|
|
545
|
+
var RealJavaScriptAnalyzer = class {
|
|
546
|
+
/**
|
|
547
|
+
* Whether the file currently being analyzed contains any `decorator` node.
|
|
548
|
+
* Set once per `analyze` call; see the pre-pass at its assignment for the
|
|
549
|
+
* measurement that made it worth having.
|
|
550
|
+
*/
|
|
551
|
+
hasAnyDecoratorNode = false;
|
|
552
|
+
/**
|
|
553
|
+
* The positional lexical scope model for the file currently being analyzed (built once per
|
|
554
|
+
* `analyze`, before the relationship extraction passes, so every emission site can resolve its
|
|
555
|
+
* own node position exactly). Null outside an `analyze` call.
|
|
556
|
+
*/
|
|
557
|
+
bindingModel = null;
|
|
558
|
+
analyze(rootNode, content, filePath, context) {
|
|
559
|
+
const symbols = [];
|
|
560
|
+
const relationships = [];
|
|
561
|
+
const imports = [];
|
|
562
|
+
const exports = [];
|
|
563
|
+
const errors = [];
|
|
564
|
+
const frameworks = { JavaScript: true };
|
|
565
|
+
const patterns = {};
|
|
566
|
+
const scopes = [{ name: "__global__", kind: "global" /* Global */ }];
|
|
567
|
+
this.hasAnyDecoratorNode = treeContainsNodeType(rootNode, "decorator");
|
|
568
|
+
const moduleName = path.basename(filePath, path.extname(filePath));
|
|
569
|
+
if (rootNode.hasError) {
|
|
570
|
+
const errStack = [rootNode];
|
|
571
|
+
while (errStack.length > 0) {
|
|
572
|
+
const n = errStack.pop();
|
|
573
|
+
if (n.type === "ERROR" || n.isMissing) {
|
|
574
|
+
errors.push({
|
|
575
|
+
message: `Syntax ${n.type} detected. The Brain Engine is bypassing this broken node.`,
|
|
576
|
+
range: this.nodeToRange(n),
|
|
577
|
+
severity: "warning"
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
581
|
+
const child = n.namedChild(i);
|
|
582
|
+
if (child) errStack.push(child);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const stack = [{ node: rootNode, scopes, inClassBody: false, functionDepth: 0 }];
|
|
587
|
+
let cyclomaticComplexity = 1;
|
|
588
|
+
const seenMethodIdx = /* @__PURE__ */ new Map();
|
|
589
|
+
const deadSpans = [];
|
|
590
|
+
const b5Zombies = [];
|
|
591
|
+
const relSpans = [];
|
|
592
|
+
while (stack.length > 0) {
|
|
593
|
+
const { node, scopes: frameScopes, inClassBody, functionDepth } = stack.pop();
|
|
594
|
+
const scopeStack = [...frameScopes];
|
|
595
|
+
let nextInClassBody = inClassBody;
|
|
596
|
+
let nextFunctionDepth = functionDepth;
|
|
597
|
+
if (node.type === "if_statement" || node.type === "for_statement" || node.type === "for_in_statement" || node.type === "while_statement" || node.type === "do_statement" || node.type === "catch_clause" || node.type === "switch_case" || node.type === "ternary_expression") {
|
|
598
|
+
cyclomaticComplexity++;
|
|
599
|
+
} else if (node.type === "binary_expression") {
|
|
600
|
+
const op = node.childForFieldName("operator")?.text;
|
|
601
|
+
if (op === "&&" || op === "||" || op === "??") cyclomaticComplexity++;
|
|
602
|
+
}
|
|
603
|
+
const relMark = relationships.length;
|
|
604
|
+
switch (node.type) {
|
|
605
|
+
case "class_declaration": {
|
|
606
|
+
const name = node.childForFieldName("name")?.text;
|
|
607
|
+
if (name) {
|
|
608
|
+
const comments = this.collectComments(node);
|
|
609
|
+
const dec = this.collectClassDecoratorDetails(node);
|
|
610
|
+
const decorators = dec.names;
|
|
611
|
+
symbols.push({
|
|
612
|
+
name,
|
|
613
|
+
kind: ASTEngine.SymbolKind.Class,
|
|
614
|
+
range: this.nodeToRange(node),
|
|
615
|
+
scope: scopeString(scopeStack),
|
|
616
|
+
visibility: "public",
|
|
617
|
+
decorators: decorators.length > 0 ? decorators : void 0,
|
|
618
|
+
comments,
|
|
619
|
+
metadata: {
|
|
620
|
+
isClass: true,
|
|
621
|
+
// M49 #868: parallel to `decorators`, so a decorator's
|
|
622
|
+
// reference edge can name the line the decorator is written
|
|
623
|
+
// on (the M59 #860 contract the typescript twin already has).
|
|
624
|
+
...dec.lines.length > 0 ? { decoratorLines: dec.lines } : {}
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
scopeStack.push({ name, kind: "class" /* Class */ });
|
|
628
|
+
nextInClassBody = true;
|
|
629
|
+
const heritage = this.findChildByType(node, "class_heritage");
|
|
630
|
+
if (heritage) {
|
|
631
|
+
const base = this.findChildByType(heritage, "identifier") ?? this.findChildByType(heritage, "member_expression");
|
|
632
|
+
if (base) {
|
|
633
|
+
const boundBase = base.type === "identifier" ? this.localBindingTarget(base, base.text, name) : null;
|
|
634
|
+
const importedBase = boundBase ? null : base.type === "identifier" ? this.importedBindingFor(base, base.text) : null;
|
|
635
|
+
relationships.push({
|
|
636
|
+
source: name,
|
|
637
|
+
target: boundBase ?? base.text,
|
|
638
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
639
|
+
confidence: 1,
|
|
640
|
+
...this.lineField(base),
|
|
641
|
+
metadata: boundBase ? { localBinding: true } : importedBase ? { importedBinding: importedBase } : {}
|
|
642
|
+
});
|
|
643
|
+
} else {
|
|
644
|
+
const rows = this.callShapedHeritageRows(heritage, name);
|
|
645
|
+
if (rows.base) {
|
|
646
|
+
relationships.push({
|
|
647
|
+
source: name,
|
|
648
|
+
target: rows.base.target,
|
|
649
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
650
|
+
confidence: 1,
|
|
651
|
+
...this.lineField(rows.base.node),
|
|
652
|
+
metadata: rows.base.metadata
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
for (const mix of rows.mixins) {
|
|
656
|
+
relationships.push({
|
|
657
|
+
source: name,
|
|
658
|
+
target: mix.target,
|
|
659
|
+
type: ASTEngine.RelationshipType.MixesIn,
|
|
660
|
+
confidence: 1,
|
|
661
|
+
...this.lineField(mix.node),
|
|
662
|
+
metadata: { ...mix.metadata, isMixin: true }
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
case "class": {
|
|
671
|
+
const ownName = node.childForFieldName("name")?.text ?? bindingNameAbove(node);
|
|
672
|
+
const heritage = this.findChildByType(node, "class_heritage");
|
|
673
|
+
if (heritage && ownName) {
|
|
674
|
+
const base = this.findChildByType(heritage, "identifier") ?? this.findChildByType(heritage, "member_expression");
|
|
675
|
+
if (base) {
|
|
676
|
+
const boundBaseExpr = base.type === "identifier" ? this.localBindingTarget(base, base.text, ownName) : null;
|
|
677
|
+
const importedBaseExpr = boundBaseExpr ? null : base.type === "identifier" ? this.importedBindingFor(base, base.text) : null;
|
|
678
|
+
relationships.push({
|
|
679
|
+
source: ownName,
|
|
680
|
+
target: boundBaseExpr ?? base.text,
|
|
681
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
682
|
+
confidence: 1,
|
|
683
|
+
...this.lineField(base),
|
|
684
|
+
metadata: boundBaseExpr ? { localBinding: true } : importedBaseExpr ? { importedBinding: importedBaseExpr } : {}
|
|
685
|
+
});
|
|
686
|
+
} else {
|
|
687
|
+
const rows = this.callShapedHeritageRows(heritage, ownName);
|
|
688
|
+
if (rows.base) {
|
|
689
|
+
relationships.push({
|
|
690
|
+
source: ownName,
|
|
691
|
+
target: rows.base.target,
|
|
692
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
693
|
+
confidence: 1,
|
|
694
|
+
...this.lineField(rows.base.node),
|
|
695
|
+
metadata: rows.base.metadata
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
for (const mix of rows.mixins) {
|
|
699
|
+
relationships.push({
|
|
700
|
+
source: ownName,
|
|
701
|
+
target: mix.target,
|
|
702
|
+
type: ASTEngine.RelationshipType.MixesIn,
|
|
703
|
+
confidence: 1,
|
|
704
|
+
...this.lineField(mix.node),
|
|
705
|
+
metadata: { ...mix.metadata, isMixin: true }
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
case "method_definition": {
|
|
713
|
+
const name = node.childForFieldName("name")?.text;
|
|
714
|
+
if (name) {
|
|
715
|
+
const inObjectLiteral = node.parent?.type === "object";
|
|
716
|
+
const classOwner = inObjectLiteral ? void 0 : scopeOwnerName(scopeStack);
|
|
717
|
+
const owner = classOwner ?? containerOwnerName(node);
|
|
718
|
+
const isConstructor = name === "constructor";
|
|
719
|
+
const isAccessor = this.hasToken(node, "get") || this.hasToken(node, "set");
|
|
720
|
+
const kindSlot = isAccessor ? this.hasToken(node, "get") ? ":get" : ":set" : "";
|
|
721
|
+
const parentType = node.parent?.type;
|
|
722
|
+
const containerKey = parentType === "object" ? `obj@${node.parent.startIndex}` : parentType === "class_body" ? `cls@${node.parent.startIndex}` : owner;
|
|
723
|
+
const dedupKey = `${containerKey ?? "module"}:${name}${kindSlot}`;
|
|
724
|
+
const kind = isConstructor ? ASTEngine.SymbolKind.Constructor : isAccessor ? ASTEngine.SymbolKind.Property : ASTEngine.SymbolKind.Method;
|
|
725
|
+
const isStatic = this.hasToken(node, "static");
|
|
726
|
+
const isAsync = this.hasToken(node, "async");
|
|
727
|
+
const isGenerator = this.hasToken(node, "*");
|
|
728
|
+
const isPrivateMethod = name.startsWith("#");
|
|
729
|
+
let seenIdx = seenMethodIdx.get(dedupKey);
|
|
730
|
+
let seenKey = seenIdx === void 0 ? void 0 : dedupKey;
|
|
731
|
+
if (seenIdx === void 0) {
|
|
732
|
+
if (kindSlot === "") {
|
|
733
|
+
for (const opp of [":get", ":set"]) {
|
|
734
|
+
const k2 = dedupKey + opp;
|
|
735
|
+
const i2 = seenMethodIdx.get(k2);
|
|
736
|
+
if (i2 !== void 0) {
|
|
737
|
+
seenIdx = i2;
|
|
738
|
+
seenKey = k2;
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
const k2 = dedupKey.slice(0, dedupKey.length - kindSlot.length);
|
|
744
|
+
const i2 = seenMethodIdx.get(k2);
|
|
745
|
+
if (i2 !== void 0) {
|
|
746
|
+
seenIdx = i2;
|
|
747
|
+
seenKey = k2;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
{
|
|
752
|
+
const params = this.extractParameters(node.childForFieldName("parameters"));
|
|
753
|
+
const comments = this.collectComments(node);
|
|
754
|
+
const dec = this.collectOwnDecoratorDetails(node);
|
|
755
|
+
const decorators = dec.names;
|
|
756
|
+
var methodSymbol = {
|
|
757
|
+
// #813: the declared spelling IS the member's name (every edge
|
|
758
|
+
// that reads it carries the `#`; see the TS site's note).
|
|
759
|
+
name,
|
|
760
|
+
kind,
|
|
761
|
+
range: this.nodeToRange(node),
|
|
762
|
+
scope: scopeString(scopeStack),
|
|
763
|
+
ownerName: owner,
|
|
764
|
+
ownerKind: classOwner ? ASTEngine.SymbolKind.Class : void 0,
|
|
765
|
+
// '#' members are class-private regardless of other tokens.
|
|
766
|
+
visibility: isPrivateMethod ? "private" : "public",
|
|
767
|
+
isStatic,
|
|
768
|
+
parameters: params,
|
|
769
|
+
decorators: decorators.length > 0 ? decorators : void 0,
|
|
770
|
+
comments,
|
|
771
|
+
complexity: this.methodComplexity(node),
|
|
772
|
+
metadata: {
|
|
773
|
+
isAccessor,
|
|
774
|
+
isAsync,
|
|
775
|
+
isGenerator,
|
|
776
|
+
...kindSlot ? { accessorKind: kindSlot === ":get" ? "get" : "set" } : {},
|
|
777
|
+
// M49 #868: the decorator's own line, parallel to
|
|
778
|
+
// `decorators` (same lockstep as the typescript twin).
|
|
779
|
+
...dec.lines.length > 0 ? { decoratorLines: dec.lines } : {}
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
if (seenIdx === void 0) {
|
|
784
|
+
seenMethodIdx.set(dedupKey, symbols.length);
|
|
785
|
+
symbols.push(methodSymbol);
|
|
786
|
+
} else {
|
|
787
|
+
const oldRange = symbols[seenIdx].range;
|
|
788
|
+
if (oldRange && oldRange.startIndex !== void 0 && oldRange.endIndex !== void 0) deadSpans.push({ s: oldRange.startIndex, e: oldRange.endIndex });
|
|
789
|
+
if (kindSlot === "") {
|
|
790
|
+
const otherKey = seenKey === dedupKey + ":get" ? dedupKey + ":set" : seenKey === dedupKey + ":set" ? dedupKey + ":get" : void 0;
|
|
791
|
+
const otherIdx = otherKey !== void 0 ? seenMethodIdx.get(otherKey) : void 0;
|
|
792
|
+
if (otherIdx !== void 0 && otherIdx !== seenIdx) {
|
|
793
|
+
const rj = symbols[otherIdx]?.range;
|
|
794
|
+
if (rj && rj.startIndex !== void 0 && rj.endIndex !== void 0) deadSpans.push({ s: rj.startIndex, e: rj.endIndex });
|
|
795
|
+
b5Zombies.push(otherIdx);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
symbols[seenIdx] = methodSymbol;
|
|
799
|
+
if (seenKey) seenMethodIdx.delete(seenKey);
|
|
800
|
+
seenMethodIdx.set(dedupKey, seenIdx);
|
|
801
|
+
}
|
|
802
|
+
scopeStack.push({ name, kind: "function" /* Function */ });
|
|
803
|
+
nextFunctionDepth = functionDepth + 1;
|
|
804
|
+
nextInClassBody = false;
|
|
805
|
+
}
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
case "field_definition": {
|
|
809
|
+
const prop = node.childForFieldName("property");
|
|
810
|
+
if (prop) {
|
|
811
|
+
const name = prop.text;
|
|
812
|
+
const classOwner = scopeOwnerName(scopeStack);
|
|
813
|
+
const owner = classOwner ?? containerOwnerName(node);
|
|
814
|
+
const dec = this.collectOwnDecoratorDetails(node);
|
|
815
|
+
symbols.push({
|
|
816
|
+
name,
|
|
817
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
818
|
+
range: this.nodeToRange(node),
|
|
819
|
+
scope: scopeString(scopeStack),
|
|
820
|
+
ownerName: owner,
|
|
821
|
+
ownerKind: classOwner ? ASTEngine.SymbolKind.Class : void 0,
|
|
822
|
+
// J9 (review wave, TS parity): `#x` fields are class-private —
|
|
823
|
+
// the old hardcoded "public" contradicted metadata.isPrivate
|
|
824
|
+
// on the same symbol.
|
|
825
|
+
visibility: prop.type === "private_property_identifier" ? "private" : "public",
|
|
826
|
+
decorators: dec.names.length > 0 ? dec.names : void 0,
|
|
827
|
+
metadata: {
|
|
828
|
+
isPrivate: prop.type === "private_property_identifier",
|
|
829
|
+
...dec.lines.length > 0 ? { decoratorLines: dec.lines } : {}
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
case "function_declaration":
|
|
836
|
+
case "generator_function_declaration": {
|
|
837
|
+
const name = node.childForFieldName("name")?.text;
|
|
838
|
+
if (name) {
|
|
839
|
+
const comments = this.collectComments(node);
|
|
840
|
+
const params = this.extractParameters(node.childForFieldName("parameters"));
|
|
841
|
+
const isAsync = this.hasToken(node, "async");
|
|
842
|
+
symbols.push({
|
|
843
|
+
name,
|
|
844
|
+
kind: ASTEngine.SymbolKind.Function,
|
|
845
|
+
range: this.nodeToRange(node),
|
|
846
|
+
scope: scopeString(scopeStack),
|
|
847
|
+
visibility: "public",
|
|
848
|
+
parameters: params,
|
|
849
|
+
comments,
|
|
850
|
+
complexity: this.methodComplexity(node),
|
|
851
|
+
metadata: {
|
|
852
|
+
isGenerator: node.type === "generator_function_declaration",
|
|
853
|
+
isAsync
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
scopeStack.push({ name, kind: "function" /* Function */ });
|
|
857
|
+
nextFunctionDepth = functionDepth + 1;
|
|
858
|
+
}
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
case "variable_declarator": {
|
|
862
|
+
const nameNode = node.childForFieldName("name");
|
|
863
|
+
const valueNode = node.childForFieldName("value");
|
|
864
|
+
const name = nameNode && nameNode.type === "identifier" ? nameNode.text : void 0;
|
|
865
|
+
const isFunctionValue = !!valueNode && (valueNode.type === "arrow_function" || valueNode.type === "function_expression" || valueNode.type === "generator_function");
|
|
866
|
+
const isRequireBinding = !!valueNode && valueNode.type === "call_expression" && valueNode.childForFieldName("function")?.text === "require";
|
|
867
|
+
if (name && isRequireBinding) {
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
if (name && functionDepth === 0 && !inClassBody) {
|
|
871
|
+
if (isFunctionValue) {
|
|
872
|
+
const comments = this.collectComments(node);
|
|
873
|
+
const params = this.extractParameters(valueNode.childForFieldName("parameters"));
|
|
874
|
+
const isAsync = this.hasToken(valueNode, "async");
|
|
875
|
+
symbols.push({
|
|
876
|
+
name,
|
|
877
|
+
kind: ASTEngine.SymbolKind.Function,
|
|
878
|
+
range: this.nodeToRange(node),
|
|
879
|
+
scope: scopeString(scopeStack),
|
|
880
|
+
visibility: "public",
|
|
881
|
+
parameters: params,
|
|
882
|
+
comments,
|
|
883
|
+
complexity: this.methodComplexity(valueNode),
|
|
884
|
+
metadata: { isArrowFunction: valueNode.type === "arrow_function", isAsync }
|
|
885
|
+
});
|
|
886
|
+
scopeStack.push({ name, kind: "function" /* Function */ });
|
|
887
|
+
nextFunctionDepth = functionDepth + 1;
|
|
888
|
+
} else {
|
|
889
|
+
const isConst = this.isConstDeclarator(node);
|
|
890
|
+
symbols.push({
|
|
891
|
+
name,
|
|
892
|
+
kind: isConst ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Variable,
|
|
893
|
+
range: this.nodeToRange(node),
|
|
894
|
+
scope: scopeString(scopeStack),
|
|
895
|
+
visibility: "public",
|
|
896
|
+
metadata: { isConst }
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
} else if (nameNode && (nameNode.type === "object_pattern" || nameNode.type === "array_pattern") && functionDepth === 0 && !inClassBody) {
|
|
900
|
+
const isConst = this.isConstDeclarator(node);
|
|
901
|
+
const pairs = this.collectPatternPairs(nameNode);
|
|
902
|
+
for (const bound of pairs) {
|
|
903
|
+
symbols.push({
|
|
904
|
+
name: bound.name,
|
|
905
|
+
kind: isConst ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Variable,
|
|
906
|
+
// M59 (#825): each binding ranges at its own NAME, not the
|
|
907
|
+
// whole declarator (a wrapped binding list stamped `const {`
|
|
908
|
+
// for every member — measured on element-plus drawer.vue).
|
|
909
|
+
range: this.nodeToRange(bound.node),
|
|
910
|
+
scope: scopeString(scopeStack),
|
|
911
|
+
visibility: "public",
|
|
912
|
+
metadata: { isConst, isDestructured: true }
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
} else if (isFunctionValue) {
|
|
916
|
+
scopeStack.push({ name: name ?? "anonymous", kind: "function" /* Function */ });
|
|
917
|
+
nextFunctionDepth = functionDepth + 1;
|
|
918
|
+
}
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
case "function_expression":
|
|
922
|
+
case "arrow_function": {
|
|
923
|
+
const parent = node.parent;
|
|
924
|
+
if (parent && parent.type !== "variable_declarator") {
|
|
925
|
+
const nm = `anonymous_function_${node.startIndex}`;
|
|
926
|
+
scopeStack.push({ name: nm, kind: "function" /* Function */ });
|
|
927
|
+
nextFunctionDepth = functionDepth + 1;
|
|
928
|
+
}
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
case "assignment_expression": {
|
|
932
|
+
const left = node.childForFieldName("left");
|
|
933
|
+
const right = node.childForFieldName("right");
|
|
934
|
+
if (left && right) {
|
|
935
|
+
if (left.type === "identifier" && left.text === "exports") {
|
|
936
|
+
patterns["CommonJS Exports"] = true;
|
|
937
|
+
this.collectModuleExports(right, exports, node, false, "exports");
|
|
938
|
+
}
|
|
939
|
+
if (left.type === "member_expression" || left.type === "subscript_expression") {
|
|
940
|
+
const obj = left.childForFieldName("object");
|
|
941
|
+
const prop = left.childForFieldName("property");
|
|
942
|
+
const objText = obj ? obj.text : "";
|
|
943
|
+
const propText = prop ? prop.text : "";
|
|
944
|
+
if (objText === "module" && propText === "exports") {
|
|
945
|
+
patterns["CommonJS Exports"] = true;
|
|
946
|
+
this.collectModuleExports(right, exports, node, true, "module-exports");
|
|
947
|
+
} else if (obj && writtenSegment(prop, left)) {
|
|
948
|
+
const surface = this.cjsSurfacePath(obj);
|
|
949
|
+
if (surface) {
|
|
950
|
+
patterns["CommonJS Exports"] = true;
|
|
951
|
+
const seg = writtenSegment(prop, left);
|
|
952
|
+
const exportName = [...surface, segmentName(seg)].join(".");
|
|
953
|
+
exports.push({
|
|
954
|
+
name: exportName,
|
|
955
|
+
isDefault: false,
|
|
956
|
+
range: this.nodeToRange(seg),
|
|
957
|
+
metadata: { viaCommonJs: true, cjsForm: "deep-member-assignment" }
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
const isFunctionRight = right.type === "function_expression" || right.type === "arrow_function" || right.type === "generator_function";
|
|
962
|
+
if (isFunctionRight && obj && obj.type === "member_expression") {
|
|
963
|
+
const protoObj = obj.childForFieldName("object");
|
|
964
|
+
const protoProp = obj.childForFieldName("property");
|
|
965
|
+
if (protoProp && protoProp.text === "prototype" && protoObj && propText) {
|
|
966
|
+
patterns["Prototype Inheritance"] = true;
|
|
967
|
+
const className = protoObj.text;
|
|
968
|
+
const methodName = propText;
|
|
969
|
+
const dedupKey = `${className}:${methodName}`;
|
|
970
|
+
{
|
|
971
|
+
const params = this.extractParameters(right.childForFieldName("parameters"));
|
|
972
|
+
var protoSymbol = {
|
|
973
|
+
name: methodName,
|
|
974
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
975
|
+
range: this.nodeToRange(right),
|
|
976
|
+
scope: className,
|
|
977
|
+
ownerName: className,
|
|
978
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
979
|
+
visibility: "public",
|
|
980
|
+
parameters: params,
|
|
981
|
+
complexity: this.methodComplexity(right),
|
|
982
|
+
metadata: { isPrototypeMethod: true, prototypeOwner: className }
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
const protoIdx = seenMethodIdx.get(dedupKey);
|
|
986
|
+
if (protoIdx === void 0) {
|
|
987
|
+
seenMethodIdx.set(dedupKey, symbols.length);
|
|
988
|
+
symbols.push(protoSymbol);
|
|
989
|
+
} else {
|
|
990
|
+
const oldRange = symbols[protoIdx].range;
|
|
991
|
+
if (oldRange && oldRange.startIndex !== void 0 && oldRange.endIndex !== void 0) deadSpans.push({ s: oldRange.startIndex, e: oldRange.endIndex });
|
|
992
|
+
symbols[protoIdx] = protoSymbol;
|
|
993
|
+
}
|
|
994
|
+
scopeStack.push({ name: methodName, kind: "function" /* Function */ });
|
|
995
|
+
nextFunctionDepth = functionDepth + 1;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
case "call_expression": {
|
|
1003
|
+
const fn = node.childForFieldName("function");
|
|
1004
|
+
const isImportCall = !!fn && (fn.type === "import" || fn.type === "identifier" && fn.text === "import");
|
|
1005
|
+
if (fn && (fn.type === "identifier" && fn.text === "require" || isImportCall)) {
|
|
1006
|
+
const args = node.childForFieldName("arguments");
|
|
1007
|
+
if (args && args.namedChildCount > 0) {
|
|
1008
|
+
const arg = args.namedChild(0);
|
|
1009
|
+
if (arg && (arg.type === "string" || arg.type === "template_string")) {
|
|
1010
|
+
const reqPath = stringContent(arg);
|
|
1011
|
+
const isDynamic = isImportCall;
|
|
1012
|
+
let requireIsDefault = true;
|
|
1013
|
+
if (!isDynamic) {
|
|
1014
|
+
let vp = node.parent;
|
|
1015
|
+
while (vp && vp.type !== "variable_declarator" && vp.type !== "lexical_declaration" && vp.type !== "variable_declaration" && vp.type !== "export_statement" && vp.type !== "expression_statement" && vp.type !== "program") {
|
|
1016
|
+
vp = vp.parent;
|
|
1017
|
+
}
|
|
1018
|
+
if (vp && vp.type === "variable_declarator") {
|
|
1019
|
+
const bindName = vp.childForFieldName("name");
|
|
1020
|
+
requireIsDefault = !!bindName && bindName.type === "identifier";
|
|
1021
|
+
} else {
|
|
1022
|
+
requireIsDefault = false;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
imports.push({
|
|
1026
|
+
module: reqPath,
|
|
1027
|
+
items: ["*"],
|
|
1028
|
+
isDefault: requireIsDefault,
|
|
1029
|
+
range: this.nodeToRange(node),
|
|
1030
|
+
metadata: { type: isDynamic ? "dynamic-import" : "require" }
|
|
1031
|
+
});
|
|
1032
|
+
patterns[isDynamic ? "Dynamic Imports" : "CommonJS Imports"] = true;
|
|
1033
|
+
relationships.push({
|
|
1034
|
+
source: moduleName,
|
|
1035
|
+
target: `${reqPath}:*`,
|
|
1036
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
1037
|
+
// JD-J12 (M60, 2026-09-27): a `*` target names NO SYMBOL.
|
|
1038
|
+
//
|
|
1039
|
+
// `require("m")` and `import("m")` are module-level: the
|
|
1040
|
+
// source declares that the module is loaded, not which member
|
|
1041
|
+
// is taken from it. Publishing `<module>:*` at 1.0 states a
|
|
1042
|
+
// certainty the file does not contain — and this is the ONLY
|
|
1043
|
+
// shape a CJS require ever takes, so for an ES5 corpus it is
|
|
1044
|
+
// the whole import class.
|
|
1045
|
+
//
|
|
1046
|
+
// Measured on the two javascript corpora at 300 files: 310
|
|
1047
|
+
// edges carry a `*` leaf and **every one is at confidence
|
|
1048
|
+
// 1.0** — 233 of 1,606 in svelte, and 77 of 77 in lodash
|
|
1049
|
+
// (whose entire import surface is `require`). No named-leaf
|
|
1050
|
+
// import edge exists there at all.
|
|
1051
|
+
//
|
|
1052
|
+
// This is the same defect shape as java's inferred JPA table
|
|
1053
|
+
// name (`mapped_table`, fixed earlier in this review): an
|
|
1054
|
+
// inference published at maximum confidence. The edge is KEPT
|
|
1055
|
+
// — the module dependency is real and worth recording — but it
|
|
1056
|
+
// is marked and demoted below the 0.7 fabrication threshold,
|
|
1057
|
+
// so it is declared suspicion rather than a counted
|
|
1058
|
+
// relationship, and a consumer can filter on `unresolved`.
|
|
1059
|
+
confidence: 0.6,
|
|
1060
|
+
// The module path argument is the name this edge is about.
|
|
1061
|
+
...this.lineField(arg),
|
|
1062
|
+
metadata: {
|
|
1063
|
+
// THE EVIDENCE KEY for the arbiter's import facts: the module specifier as written.
|
|
1064
|
+
// The ES-import rows already carried `module`; the require/import() rows did not, and
|
|
1065
|
+
// the first arbiter smoke measured the cost exactly: SIX importedBinding claims were
|
|
1066
|
+
// REFUTED because their evidence edge had no `module` to key file->specifier->bindings
|
|
1067
|
+
// with (every one of the six was a require() binding: fs, path, an absolute pg path,
|
|
1068
|
+
// ../utils/logger, ../../services/...). The claim's `specifier` and this field must be
|
|
1069
|
+
// the SAME spelling — both come from stringContent() on the written argument.
|
|
1070
|
+
module: reqPath,
|
|
1071
|
+
isRequire: !isDynamic,
|
|
1072
|
+
isDynamicImport: isDynamic,
|
|
1073
|
+
unresolved: true,
|
|
1074
|
+
// THE EVIDENCE the imported-binding claim is verified against: the local names this
|
|
1075
|
+
// module load binds IN THIS FILE (a destructured `const { a } = require("m")` binds a,
|
|
1076
|
+
// a bare `const fs = require("m")` binds fs). Dynamic specifiers are refused earlier,
|
|
1077
|
+
// so they reach no claim — the contract's never-claim (c).
|
|
1078
|
+
importedBindings: (() => {
|
|
1079
|
+
let owner = node.parent;
|
|
1080
|
+
while (owner && owner.type !== "variable_declarator" && owner.type !== "lexical_declaration" && owner.type !== "variable_declaration" && owner.type !== "export_statement" && owner.type !== "expression_statement" && owner.type !== "program") {
|
|
1081
|
+
owner = owner.parent;
|
|
1082
|
+
}
|
|
1083
|
+
const bindName = owner && owner.type === "variable_declarator" ? owner.childForFieldName("name") : null;
|
|
1084
|
+
return bindName ? this.collectPatternPairs(bindName).map((pair) => pair.name) : [];
|
|
1085
|
+
})()
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
break;
|
|
1092
|
+
}
|
|
1093
|
+
case "import_statement": {
|
|
1094
|
+
patterns["ES6 Imports"] = true;
|
|
1095
|
+
const srcNode = node.childForFieldName("source");
|
|
1096
|
+
const srcPath = srcNode ? stringContent(srcNode) : "";
|
|
1097
|
+
const items = [];
|
|
1098
|
+
const clause = this.findChildByType(node, "import_clause");
|
|
1099
|
+
let hasDefaultImport = false;
|
|
1100
|
+
if (clause) {
|
|
1101
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
1102
|
+
const c = clause.namedChild(i);
|
|
1103
|
+
if (!c) continue;
|
|
1104
|
+
if (c.type === "identifier") {
|
|
1105
|
+
items.push({ text: c.text, node: c, declared: "default", kind: "default" });
|
|
1106
|
+
hasDefaultImport = true;
|
|
1107
|
+
} else if (c.type === "named_imports") {
|
|
1108
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
1109
|
+
const spec = c.namedChild(j);
|
|
1110
|
+
if (spec && spec.type === "import_specifier") {
|
|
1111
|
+
const n = this.findChildByType(spec, "identifier");
|
|
1112
|
+
const alias = spec.childForFieldName("alias");
|
|
1113
|
+
const item = alias ?? n;
|
|
1114
|
+
if (item) {
|
|
1115
|
+
items.push({ text: item.text, node: item, declared: n?.text ?? item.text, kind: "named" });
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
} else if (c.type === "namespace_import") {
|
|
1120
|
+
const nsName = c.childForFieldName("name") ?? c.namedChild(0);
|
|
1121
|
+
items.push({ text: "*", node: nsName ?? c, declared: "*", kind: "namespace" });
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (srcPath) {
|
|
1126
|
+
imports.push({
|
|
1127
|
+
module: srcPath,
|
|
1128
|
+
items: items.map((i) => i.text),
|
|
1129
|
+
isDefault: hasDefaultImport,
|
|
1130
|
+
range: this.nodeToRange(node),
|
|
1131
|
+
metadata: { type: "import" }
|
|
1132
|
+
});
|
|
1133
|
+
for (const item of items) {
|
|
1134
|
+
const isNamespaceItem = item.text === "*";
|
|
1135
|
+
const jsItemName = item.text.split(" as ")[0];
|
|
1136
|
+
const bareLocalItem = item.kind === "named" && !isNamespaceItem && jsItemName !== "" && (srcPath.startsWith(".") || srcPath.startsWith("/"));
|
|
1137
|
+
const localNamedTarget = item.kind === "named" ? item.declared || jsItemName : jsItemName;
|
|
1138
|
+
relationships.push({
|
|
1139
|
+
source: moduleName,
|
|
1140
|
+
// FOR A LOCAL MODULE the target is the symbol the edge refers to;
|
|
1141
|
+
// for an external one it stays `<specifier>:<local binding>`,
|
|
1142
|
+
// which is what the importing fixtures pin.
|
|
1143
|
+
target: bareLocalItem ? localNamedTarget : `${srcPath}:${isNamespaceItem ? "*" : jsItemName}`,
|
|
1144
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
1145
|
+
confidence: isNamespaceItem ? 0.6 : 1,
|
|
1146
|
+
...this.lineField(item.node),
|
|
1147
|
+
// JD2b, additive: what the MODULE declares, so the resolver can
|
|
1148
|
+
// reach the right symbol for an aliased or default import. The
|
|
1149
|
+
// edge target above is untouched.
|
|
1150
|
+
metadata: {
|
|
1151
|
+
// THE MODULE, NOW THAT IT IS NO LONGER GLUED INTO THE TARGET. TypeScript's site
|
|
1152
|
+
// already carried `module`; this one did not, so a bare item name here would have
|
|
1153
|
+
// LOST the module half rather than moved it — and an item name with no module is
|
|
1154
|
+
// unresolvable by construction (which module's `readJson`?).
|
|
1155
|
+
module: srcPath,
|
|
1156
|
+
declaredName: item.declared,
|
|
1157
|
+
declaredKind: item.kind,
|
|
1158
|
+
// THE LOCAL NAME THIS IMPORT BINDS — the evidence for the imported-binding claim.
|
|
1159
|
+
// Default/named: the item's local spelling (alias when present); namespace: the
|
|
1160
|
+
// local `ns` identifier the clause writes (the item's text is `*`).
|
|
1161
|
+
importedBindings: isNamespaceItem ? String(item.node?.text ?? "") ? [String(item.node.text)] : [] : item.text ? [item.text] : [],
|
|
1162
|
+
...isNamespaceItem ? { unresolved: true } : {}
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
case "export_statement": {
|
|
1170
|
+
patterns["ES6 Exports"] = true;
|
|
1171
|
+
this.collectExportStatement(node, exports);
|
|
1172
|
+
const exportSource = node.childForFieldName("source");
|
|
1173
|
+
if (exportSource) {
|
|
1174
|
+
const modPath = stringContent(exportSource);
|
|
1175
|
+
const namedSpecs = [];
|
|
1176
|
+
const clauseNames = [];
|
|
1177
|
+
const clause2 = this.findChildByType(node, "export_clause");
|
|
1178
|
+
if (clause2) {
|
|
1179
|
+
for (let i = 0; i < clause2.namedChildCount; i++) {
|
|
1180
|
+
const spec = clause2.namedChild(i);
|
|
1181
|
+
if (spec && spec.type === "export_specifier") {
|
|
1182
|
+
const nameNode = spec.childForFieldName("name") ?? spec.namedChild(0);
|
|
1183
|
+
const alias = spec.childForFieldName("alias");
|
|
1184
|
+
const member = nameNode ? nameNode.type === "string" ? stringContent(nameNode) : String(nameNode.text ?? "") : "";
|
|
1185
|
+
if (nameNode && member) {
|
|
1186
|
+
namedSpecs.push({ member, exportedAs: alias ? String(alias.text) : member, node: nameNode });
|
|
1187
|
+
clauseNames.push(alias ? String(alias.text) : member);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
const starOnly = !clause2 && this.hasToken(node, "*");
|
|
1193
|
+
const namespaceExport = this.findChildByType(node, "namespace_export");
|
|
1194
|
+
const namespaceAlias = namespaceExport ? String(namespaceExport.childForFieldName("name") ?? namespaceExport.namedChild(0)?.text ?? "") : "";
|
|
1195
|
+
imports.push({
|
|
1196
|
+
module: modPath,
|
|
1197
|
+
items: starOnly ? ["*"] : clauseNames,
|
|
1198
|
+
isDefault: false,
|
|
1199
|
+
range: this.nodeToRange(node),
|
|
1200
|
+
metadata: { reExport: true }
|
|
1201
|
+
});
|
|
1202
|
+
if (namedSpecs.length) {
|
|
1203
|
+
for (const spec of namedSpecs) {
|
|
1204
|
+
relationships.push({
|
|
1205
|
+
source: FILE_LEVEL_SOURCE,
|
|
1206
|
+
// The MEMBER in the source module — not the alias this module
|
|
1207
|
+
// exports it under, which is what an `exports` edge is about.
|
|
1208
|
+
target: `${modPath}:${spec.member}`,
|
|
1209
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
1210
|
+
// 1.0 for the same reason a named import keeps 1.0: the member
|
|
1211
|
+
// name is WRITTEN in this file, and ESM fixes its meaning (the
|
|
1212
|
+
// source module must export `member`), so this is a stated
|
|
1213
|
+
// dependency and not an inference. `export *` / `export * as ns`
|
|
1214
|
+
// keep 0.6 and `unresolved` — they write no member name.
|
|
1215
|
+
confidence: 1,
|
|
1216
|
+
// The specifier's OWN line: a clause is a list, and the name the
|
|
1217
|
+
// edge is about is written on its own line.
|
|
1218
|
+
...this.lineField(spec.node),
|
|
1219
|
+
metadata: {
|
|
1220
|
+
isReExport: true,
|
|
1221
|
+
declaredName: spec.member,
|
|
1222
|
+
declaredKind: "named",
|
|
1223
|
+
// A re-export binds NO local name in this file (never-claim (d): a different
|
|
1224
|
+
// surface). The key is published on EVERY imports edge so the arbiter never has
|
|
1225
|
+
// to distinguish "no bindings" from "this row forgot the evidence".
|
|
1226
|
+
importedBindings: [],
|
|
1227
|
+
...spec.exportedAs !== spec.member ? { exportedAs: spec.exportedAs } : {}
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
} else {
|
|
1232
|
+
relationships.push({
|
|
1233
|
+
source: FILE_LEVEL_SOURCE,
|
|
1234
|
+
target: `${modPath}:*`,
|
|
1235
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
1236
|
+
// JD-J12 (M60, 2026-09-27): a clause-less re-export (`export *
|
|
1237
|
+
// from "m"`) names the MODULE and no member of it — `*` again. A
|
|
1238
|
+
// module path is not a symbol, so 1.0 overstated it the same way
|
|
1239
|
+
// it overstated `require` and the namespace import. Kept, marked,
|
|
1240
|
+
// and demoted: the re-export dependency is real.
|
|
1241
|
+
confidence: 0.6,
|
|
1242
|
+
// A re-export with no clause names the MODULE, so the written
|
|
1243
|
+
// name is the module-path argument itself.
|
|
1244
|
+
...this.lineField(exportSource),
|
|
1245
|
+
// `starForm` is the discriminator, and it is the one piece of
|
|
1246
|
+
// evidence a resolver needs to tell a REDISTRIBUTION from a
|
|
1247
|
+
// namespace binding: only `all` may be followed for a name this
|
|
1248
|
+
// file does not publish.
|
|
1249
|
+
metadata: {
|
|
1250
|
+
isReExport: true,
|
|
1251
|
+
unresolved: true,
|
|
1252
|
+
starForm: namespaceAlias ? "namespace" : "all",
|
|
1253
|
+
// A clause-less re-export binds NO local name (never-claim (d)). Published on every
|
|
1254
|
+
// imports edge so the arbiter never has to distinguish "no bindings" from "missing
|
|
1255
|
+
// evidence".
|
|
1256
|
+
importedBindings: [],
|
|
1257
|
+
...namespaceAlias ? { namespaceName: namespaceAlias } : {}
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
patterns["ES6 ReExports"] = true;
|
|
1262
|
+
}
|
|
1263
|
+
break;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
for (let i = relMark; i < relationships.length; i++) {
|
|
1267
|
+
relSpans.push({ idx: i, s: node.startIndex, e: node.endIndex });
|
|
1268
|
+
}
|
|
1269
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
1270
|
+
const child = node.namedChild(i);
|
|
1271
|
+
if (child) {
|
|
1272
|
+
stack.push({ node: child, scopes: scopeStack, inClassBody: nextInClassBody, functionDepth: nextFunctionDepth });
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
{
|
|
1277
|
+
const deadIdx = new Set(b5Zombies);
|
|
1278
|
+
if (deadSpans.length > 0) {
|
|
1279
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
1280
|
+
const r = symbols[i].range;
|
|
1281
|
+
if (!r || r.startIndex === void 0 || r.endIndex === void 0) continue;
|
|
1282
|
+
for (const d of deadSpans) {
|
|
1283
|
+
if (d.s <= r.startIndex && r.endIndex <= d.e && (d.s !== r.startIndex || r.endIndex !== d.e)) {
|
|
1284
|
+
deadIdx.add(i);
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (deadIdx.size > 0) {
|
|
1291
|
+
for (let i = symbols.length - 1; i >= 0; i--) if (deadIdx.has(i)) symbols.splice(i, 1);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (deadSpans.length > 0 && relSpans.length > 0) {
|
|
1295
|
+
const drop = /* @__PURE__ */ new Set();
|
|
1296
|
+
for (const r of relSpans) {
|
|
1297
|
+
for (const d of deadSpans) {
|
|
1298
|
+
if (d.s <= r.s && r.e <= d.e) {
|
|
1299
|
+
drop.add(r.idx);
|
|
1300
|
+
break;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (drop.size > 0) {
|
|
1305
|
+
for (let i = relationships.length - 1; i >= 0; i--) if (drop.has(i)) relationships.splice(i, 1);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
{
|
|
1309
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1310
|
+
const qcOwnerOf = (n) => {
|
|
1311
|
+
let p = n.parent;
|
|
1312
|
+
while (p) {
|
|
1313
|
+
if (p.type === "class_declaration") return p.childForFieldName("name")?.text;
|
|
1314
|
+
if (p.type === "variable_declarator" || p.type === "object" || p.type === "pair" || p.type === "function_declaration" || p.type === "function_expression" || p.type === "arrow_function") return void 0;
|
|
1315
|
+
p = p.parent;
|
|
1316
|
+
}
|
|
1317
|
+
return void 0;
|
|
1318
|
+
};
|
|
1319
|
+
const qcResolve = (n, current) => {
|
|
1320
|
+
const fnName = n.childForFieldName?.("name")?.text;
|
|
1321
|
+
if ((n.type === "function_declaration" || n.type === "generator_function_declaration" || n.type === "method_definition") && fnName) {
|
|
1322
|
+
const owner = n.type === "method_definition" ? qcOwnerOf(n) : void 0;
|
|
1323
|
+
return owner ? `${owner}::${fnName}` : fnName;
|
|
1324
|
+
}
|
|
1325
|
+
if (n.type === "variable_declarator") {
|
|
1326
|
+
const nameNode = n.childForFieldName("name");
|
|
1327
|
+
const valueNode = n.childForFieldName("value");
|
|
1328
|
+
if (nameNode && valueNode && (valueNode.type === "arrow_function" || valueNode.type === "function_expression" || valueNode.type === "generator_function")) {
|
|
1329
|
+
return nameNode.text;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
if (n.type === "assignment_expression") {
|
|
1333
|
+
const left = n.childForFieldName("left");
|
|
1334
|
+
const right = n.childForFieldName("right");
|
|
1335
|
+
if (left && right && (right.type === "function_expression" || right.type === "arrow_function") && left.type === "member_expression") {
|
|
1336
|
+
const obj = left.childForFieldName("object");
|
|
1337
|
+
const prop = left.childForFieldName("property");
|
|
1338
|
+
if (obj && obj.type === "member_expression" && prop) {
|
|
1339
|
+
const protoProp = obj.childForFieldName("property");
|
|
1340
|
+
if (protoProp && protoProp.text === "prototype") {
|
|
1341
|
+
const owner = obj.childForFieldName("object")?.text;
|
|
1342
|
+
return owner ? `${owner}::${prop.text}` : prop.text;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return current;
|
|
1348
|
+
};
|
|
1349
|
+
const qcWalk = (n, current) => {
|
|
1350
|
+
if (isNodeInDeadSpans(deadSpans, n)) return;
|
|
1351
|
+
current = qcResolve(n, current);
|
|
1352
|
+
if (n.type === "call_expression") {
|
|
1353
|
+
const fn = n.childForFieldName("function");
|
|
1354
|
+
const isImportCallee = !!fn && (fn.type === "import" || fn.type === "identifier" && fn.text === "import");
|
|
1355
|
+
if (fn && !(fn.type === "identifier" && fn.text === "require") && !isImportCallee) {
|
|
1356
|
+
const parts = [];
|
|
1357
|
+
let cur = fn;
|
|
1358
|
+
while (cur) {
|
|
1359
|
+
if (cur.type === "member_expression") {
|
|
1360
|
+
const prop2 = cur.childForFieldName("property");
|
|
1361
|
+
parts.unshift(prop2 ? prop2.text : cur.namedChild(cur.namedChildCount - 1)?.text ?? "");
|
|
1362
|
+
cur = cur.childForFieldName("object");
|
|
1363
|
+
} else if (cur.type === "call_expression") {
|
|
1364
|
+
cur = cur.namedChild(0);
|
|
1365
|
+
} else {
|
|
1366
|
+
parts.unshift(cur.text);
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
const path2 = parts.filter(Boolean).join(".");
|
|
1371
|
+
if (current && path2) {
|
|
1372
|
+
const list = bySource.get(current) ?? [];
|
|
1373
|
+
list.push(path2);
|
|
1374
|
+
bySource.set(current, list);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1379
|
+
const c = n.namedChild(i);
|
|
1380
|
+
if (c) qcWalk(c, current);
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
qcWalk(rootNode, null);
|
|
1384
|
+
for (const sym of symbols) {
|
|
1385
|
+
const key = sym.ownerName && sym.ownerKind === ASTEngine.SymbolKind.Class ? `${sym.ownerName}::${sym.name}` : sym.name;
|
|
1386
|
+
const list = bySource.get(key);
|
|
1387
|
+
if (list && list.length > 0) sym.qualified_calls = [...new Set(list)];
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
this.bindingModel = this.collectBindingModel(rootNode);
|
|
1391
|
+
this.addContainmentRelationships(symbols, relationships);
|
|
1392
|
+
this.extractOverrideRelationships(symbols, relationships, context?.overrideCompanion);
|
|
1393
|
+
const receiverTypes = this.collectLocalReceiverTypes(rootNode);
|
|
1394
|
+
this.extractCallRelationships(rootNode, symbols, relationships, moduleName, receiverTypes, deadSpans);
|
|
1395
|
+
if (deadSpans.length > 0 && exports.length > 0) {
|
|
1396
|
+
for (let i = exports.length - 1; i >= 0; i--) {
|
|
1397
|
+
const r = exports[i].range;
|
|
1398
|
+
if (r && r.startIndex !== void 0 && r.endIndex !== void 0) {
|
|
1399
|
+
for (const d of deadSpans) {
|
|
1400
|
+
if (d.s <= r.startIndex && r.endIndex <= d.e) {
|
|
1401
|
+
exports.splice(i, 1);
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
this.extractExportRelationships(
|
|
1409
|
+
exports,
|
|
1410
|
+
relationships,
|
|
1411
|
+
FILE_LEVEL_SOURCE,
|
|
1412
|
+
new Set(symbols.map((sym) => String(sym?.name ?? "")).filter((n) => n.length > 0))
|
|
1413
|
+
);
|
|
1414
|
+
this.extractDecoratorRelationships(symbols, relationships);
|
|
1415
|
+
const architecturalScore = Math.min(100, Math.max(0, 100 - errors.length * 5));
|
|
1416
|
+
const detectedPatterns = Object.keys(patterns);
|
|
1417
|
+
const detectedFrameworks = Object.keys(frameworks);
|
|
1418
|
+
if (this.detectsJsx(rootNode)) {
|
|
1419
|
+
detectedFrameworks.push("React");
|
|
1420
|
+
detectedPatterns.push("JSX Components");
|
|
1421
|
+
}
|
|
1422
|
+
const frameworkPatterns = detectedPatterns.map((p) => ({
|
|
1423
|
+
framework: "JavaScript",
|
|
1424
|
+
patterns: [{ type: "usage", detected: true, description: p }]
|
|
1425
|
+
}));
|
|
1426
|
+
this.collectFieldAccessUses(rootNode, relationships, moduleName, receiverTypes, deadSpans);
|
|
1427
|
+
this.extractInstanceofRefs(rootNode, relationships, moduleName);
|
|
1428
|
+
for (const r of relationships) {
|
|
1429
|
+
if (r.source !== FILE_LEVEL_SOURCE) continue;
|
|
1430
|
+
r.metadata = { ...r.metadata ?? {}, fileLevel: true, scopeName: moduleName };
|
|
1431
|
+
}
|
|
1432
|
+
for (let i = relationships.length - 1; i >= 0; i--) {
|
|
1433
|
+
const r = relationships[i];
|
|
1434
|
+
if (!r.source || !r.target) relationships.splice(i, 1);
|
|
1435
|
+
}
|
|
1436
|
+
this.markExternalClaims(rootNode, relationships, symbols, context);
|
|
1437
|
+
this.classifyEvidenceTier(relationships);
|
|
1438
|
+
return {
|
|
1439
|
+
language: "javascript",
|
|
1440
|
+
symbols,
|
|
1441
|
+
relationships,
|
|
1442
|
+
imports,
|
|
1443
|
+
exports,
|
|
1444
|
+
errors,
|
|
1445
|
+
metadata: {
|
|
1446
|
+
// Real cyclomatic complexity — was the synthetic heuristic
|
|
1447
|
+
// symbols.length + relationships.length, which is not a complexity
|
|
1448
|
+
// metric at all (same defect previously fixed on groovy/scala).
|
|
1449
|
+
complexity: cyclomaticComplexity,
|
|
1450
|
+
linesOfCode: content.split("\n").length,
|
|
1451
|
+
cyclomaticComplexity,
|
|
1452
|
+
framework: detectedFrameworks.length > 1 ? detectedFrameworks[1] : detectedFrameworks[0],
|
|
1453
|
+
patterns: detectedPatterns,
|
|
1454
|
+
detectedFrameworks,
|
|
1455
|
+
frameworkPatterns,
|
|
1456
|
+
architecturalInsights: [],
|
|
1457
|
+
architecturalScore,
|
|
1458
|
+
codeQuality: errors.length === 0 ? "Excellent" : "Fair",
|
|
1459
|
+
hasTypeInference: false,
|
|
1460
|
+
hasFrameworkIntelligence: true,
|
|
1461
|
+
analysisVersion: "3.1.0-m12.10-family-contract"
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
// ============================================================================
|
|
1466
|
+
// RELATIONSHIP PASSES
|
|
1467
|
+
// ============================================================================
|
|
1468
|
+
addContainmentRelationships(symbols, relationships) {
|
|
1469
|
+
for (const symbol of symbols) {
|
|
1470
|
+
if (symbol.ownerName) {
|
|
1471
|
+
relationships.push({
|
|
1472
|
+
source: symbol.ownerName,
|
|
1473
|
+
target: symbol.name,
|
|
1474
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
1475
|
+
confidence: 1,
|
|
1476
|
+
// The MEMBER's declaration line — the edge names the member.
|
|
1477
|
+
...symbol.range?.startLine ? { line_number: symbol.range.startLine } : {},
|
|
1478
|
+
metadata: {}
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
extractOverrideRelationships(symbols, relationships, overrideCompanion) {
|
|
1484
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1485
|
+
for (const s of symbols) {
|
|
1486
|
+
const list = byName.get(s.name) ?? [];
|
|
1487
|
+
list.push(s);
|
|
1488
|
+
byName.set(s.name, list);
|
|
1489
|
+
}
|
|
1490
|
+
const baseByClass = /* @__PURE__ */ new Map();
|
|
1491
|
+
for (const r of relationships) {
|
|
1492
|
+
if (r.type === ASTEngine.RelationshipType.Extends && r.source && r.target) {
|
|
1493
|
+
baseByClass.set(r.source, r.target);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
const overrideSeen = /* @__PURE__ */ new Set();
|
|
1497
|
+
for (const s of symbols) {
|
|
1498
|
+
const isAccessorMember = s.kind === ASTEngine.SymbolKind.Property && s.metadata?.isAccessor === true;
|
|
1499
|
+
if ((s.kind === ASTEngine.SymbolKind.Method || s.kind === ASTEngine.SymbolKind.Constructor || isAccessorMember) && s.ownerName) {
|
|
1500
|
+
if (s.isStatic) continue;
|
|
1501
|
+
const base = baseByClass.get(s.ownerName);
|
|
1502
|
+
if (!base) continue;
|
|
1503
|
+
const isCtor = s.kind === ASTEngine.SymbolKind.Constructor || s.name === "constructor";
|
|
1504
|
+
const inFileDeclares = !isCtor && (byName.get(s.name)?.some((bm) => bm.ownerName === base && (bm.kind === ASTEngine.SymbolKind.Method || bm.kind === ASTEngine.SymbolKind.Property && bm.metadata?.isAccessor === true)) ?? false);
|
|
1505
|
+
if (inFileDeclares || !isCtor && overrideCompanion?.get(base)?.has(s.name)) {
|
|
1506
|
+
const ovKey = `${s.ownerName}.${s.name}->${base}.${s.name}`;
|
|
1507
|
+
if (overrideSeen.has(ovKey)) continue;
|
|
1508
|
+
overrideSeen.add(ovKey);
|
|
1509
|
+
relationships.push({
|
|
1510
|
+
source: `${s.ownerName}.${s.name}`,
|
|
1511
|
+
// THE TARGET IS THE MEMBER NAME, NOT `Owner.member`: the resolver binds against
|
|
1512
|
+
// DECLARATIONS, and a declaration is named by its leaf. Measured: 100% of this lane's
|
|
1513
|
+
// overrides targets are dotted, 100% have their leaf declared in the edge's own file, and
|
|
1514
|
+
// 100% classify unresolved — a naming problem, not a mechanism problem. `localEdgeName`
|
|
1515
|
+
// derives the leaf AFTER the paren deletion `localEdgeLabel` performs, so a dotted
|
|
1516
|
+
// parameter list cannot be cut into a non-name (see its doc).
|
|
1517
|
+
target: s.name,
|
|
1518
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
1519
|
+
confidence: 0.95,
|
|
1520
|
+
// The OVERRIDING member's own line, not the class's.
|
|
1521
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
1522
|
+
metadata: { reasonCode: "verified-in-file" }
|
|
1523
|
+
});
|
|
1524
|
+
} else if (!isCtor && !overrideCompanion?.has(base)) {
|
|
1525
|
+
const baseInFile = symbols.some((s2) => s2.name === base && s2.kind === ASTEngine.SymbolKind.Class);
|
|
1526
|
+
if (baseInFile) continue;
|
|
1527
|
+
if (s.name.startsWith("#")) continue;
|
|
1528
|
+
if (isJsBuiltin(base) && !jsBuiltinHasMember(base, s.name)) continue;
|
|
1529
|
+
const guessKey = `${s.ownerName}.${s.name}->${base}.${s.name}`;
|
|
1530
|
+
if (overrideSeen.has(guessKey)) continue;
|
|
1531
|
+
overrideSeen.add(guessKey);
|
|
1532
|
+
relationships.push({
|
|
1533
|
+
source: `${s.ownerName}.${s.name}`,
|
|
1534
|
+
// THE TARGET IS THE MEMBER NAME, NOT `Owner.member`: the resolver binds against
|
|
1535
|
+
// DECLARATIONS, and a declaration is named by its leaf. Measured: 100% of this lane's
|
|
1536
|
+
// overrides targets are dotted, 100% have their leaf declared in the edge's own file, and
|
|
1537
|
+
// 100% classify unresolved — a naming problem, not a mechanism problem. `localEdgeName`
|
|
1538
|
+
// derives the leaf AFTER the paren deletion `localEdgeLabel` performs, so a dotted
|
|
1539
|
+
// parameter list cannot be cut into a non-name (see its doc).
|
|
1540
|
+
target: s.name,
|
|
1541
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
1542
|
+
confidence: 0.5,
|
|
1543
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
1544
|
+
metadata: { reasonCode: "first-parent-guess" }
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* RECEIVER TYPES for a member call on a LOCAL variable — the JavaScript twin of
|
|
1552
|
+
* the TypeScript pass (`collectLocalReceiverTypes` there, where the three-file
|
|
1553
|
+
* measurement and the reasoning live).
|
|
1554
|
+
*
|
|
1555
|
+
* JavaScript states a receiver's type in exactly one syntactic form: an
|
|
1556
|
+
* initialiser. `const c = new Foo()` is the whole rule here; there are no type
|
|
1557
|
+
* annotations to read, which is why this map is smaller than the TS one rather
|
|
1558
|
+
* than narrower by choice. A name declared with two different constructors in
|
|
1559
|
+
* one file is dropped: shadowing makes the file-level answer a guess, and a
|
|
1560
|
+
* wrong owner rewrites a binding that is currently right.
|
|
1561
|
+
*/
|
|
1562
|
+
collectLocalReceiverTypes(root) {
|
|
1563
|
+
const types = /* @__PURE__ */ new Map();
|
|
1564
|
+
const conflicted = /* @__PURE__ */ new Set();
|
|
1565
|
+
const record = (name, type, evidence) => {
|
|
1566
|
+
if (name.length === 0 || type.length === 0) return;
|
|
1567
|
+
const previous = types.get(name);
|
|
1568
|
+
if (previous === void 0) {
|
|
1569
|
+
if (!conflicted.has(name)) types.set(name, { type, evidence });
|
|
1570
|
+
} else if (previous.type !== type) {
|
|
1571
|
+
types.delete(name);
|
|
1572
|
+
conflicted.add(name);
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
const newCtorLeaf = (value) => {
|
|
1576
|
+
if (!value || value.type !== "new_expression") return null;
|
|
1577
|
+
const ctor = value.childForFieldName("constructor") ?? value.namedChild(0);
|
|
1578
|
+
if (ctor?.type === "identifier") return ctor.text;
|
|
1579
|
+
if (ctor?.type === "member_expression") return ctor.childForFieldName("property")?.text ?? null;
|
|
1580
|
+
return null;
|
|
1581
|
+
};
|
|
1582
|
+
const precedingDoc = (node) => {
|
|
1583
|
+
let stmt = node;
|
|
1584
|
+
while (stmt.parent && stmt.parent.type !== "program" && stmt.parent.type !== "statement_block" && stmt.parent.type !== "class_body" && stmt.parent.type !== "switch_body") {
|
|
1585
|
+
stmt = stmt.parent;
|
|
1586
|
+
}
|
|
1587
|
+
const prev = stmt.previousSibling;
|
|
1588
|
+
if (!prev || prev.type !== "comment") return null;
|
|
1589
|
+
const text = prev.text;
|
|
1590
|
+
return text.startsWith("/**") ? text : null;
|
|
1591
|
+
};
|
|
1592
|
+
const jsdocTypeOf = (node) => {
|
|
1593
|
+
const text = precedingDoc(node);
|
|
1594
|
+
if (text === null) return null;
|
|
1595
|
+
const at = text.indexOf("@type");
|
|
1596
|
+
if (at < 0) return null;
|
|
1597
|
+
const open = text.indexOf("{", at + 5);
|
|
1598
|
+
if (open < 0) return null;
|
|
1599
|
+
const close = text.indexOf("}", open + 1);
|
|
1600
|
+
if (close < 0) return null;
|
|
1601
|
+
const inner = text.slice(open + 1, close).trim();
|
|
1602
|
+
return isPlainJsIdentifier(inner) ? inner : null;
|
|
1603
|
+
};
|
|
1604
|
+
const jsdocParamsOf = (node) => {
|
|
1605
|
+
const out = /* @__PURE__ */ new Map();
|
|
1606
|
+
const text = precedingDoc(node);
|
|
1607
|
+
if (text === null) return out;
|
|
1608
|
+
let from = 0;
|
|
1609
|
+
for (; ; ) {
|
|
1610
|
+
const at = text.indexOf("@param", from);
|
|
1611
|
+
if (at < 0) break;
|
|
1612
|
+
from = at + 6;
|
|
1613
|
+
const open = text.indexOf("{", from);
|
|
1614
|
+
if (open < 0) break;
|
|
1615
|
+
const close = text.indexOf("}", open + 1);
|
|
1616
|
+
if (close < 0) break;
|
|
1617
|
+
const type = text.slice(open + 1, close).trim();
|
|
1618
|
+
let i = close + 1;
|
|
1619
|
+
while (i < text.length && (text[i] === " " || text[i] === " ")) i++;
|
|
1620
|
+
let end = i;
|
|
1621
|
+
while (end < text.length) {
|
|
1622
|
+
const ch = text[end];
|
|
1623
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "-" || ch === "*" || ch === "/") break;
|
|
1624
|
+
end++;
|
|
1625
|
+
}
|
|
1626
|
+
const name = text.slice(i, end);
|
|
1627
|
+
if (isPlainJsIdentifier(type) && isPlainJsIdentifier(name)) out.set(name, type);
|
|
1628
|
+
from = close + 1;
|
|
1629
|
+
}
|
|
1630
|
+
return out;
|
|
1631
|
+
};
|
|
1632
|
+
const paramScopes = [];
|
|
1633
|
+
{
|
|
1634
|
+
const st = [root];
|
|
1635
|
+
while (st.length > 0) {
|
|
1636
|
+
const n = st.pop();
|
|
1637
|
+
if (n.type === "function_declaration" || n.type === "function_expression" || n.type === "generator_function_declaration" || n.type === "generator_function" || n.type === "arrow_function" || n.type === "method_definition") {
|
|
1638
|
+
const types2 = jsdocParamsOf(n);
|
|
1639
|
+
if (types2.size > 0) {
|
|
1640
|
+
paramScopes.push({ startLine: n.startPosition.row + 1, endLine: n.endPosition.row + 1, types: types2 });
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1644
|
+
const c = n.namedChild(i);
|
|
1645
|
+
if (c) st.push(c);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
const jsdocParamTypeAt = (line, name) => {
|
|
1650
|
+
let best = null;
|
|
1651
|
+
for (const scope of paramScopes) {
|
|
1652
|
+
if (line < scope.startLine || line > scope.endLine) continue;
|
|
1653
|
+
if (!scope.types.has(name)) continue;
|
|
1654
|
+
if (best === null || scope.endLine - scope.startLine < best.endLine - best.startLine) best = scope;
|
|
1655
|
+
}
|
|
1656
|
+
return best === null ? null : best.types.get(name) ?? null;
|
|
1657
|
+
};
|
|
1658
|
+
const stack = [root];
|
|
1659
|
+
while (stack.length) {
|
|
1660
|
+
const n = stack.pop();
|
|
1661
|
+
if (n.type === "variable_declarator") {
|
|
1662
|
+
const nameNode = n.childForFieldName("name");
|
|
1663
|
+
const value = n.childForFieldName("value");
|
|
1664
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
1665
|
+
const ctor = newCtorLeaf(value);
|
|
1666
|
+
if (ctor !== null) record(nameNode.text, ctor, "new");
|
|
1667
|
+
else {
|
|
1668
|
+
const docType = jsdocTypeOf(n);
|
|
1669
|
+
if (docType !== null) record(nameNode.text, docType, "jsdoc");
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
} else if (n.type === "field_definition") {
|
|
1673
|
+
const prop = n.childForFieldName("property");
|
|
1674
|
+
const value = n.childForFieldName("value");
|
|
1675
|
+
const ctor = newCtorLeaf(value);
|
|
1676
|
+
if (prop?.type === "property_identifier" && ctor !== null) {
|
|
1677
|
+
record(`this.${prop.text}`, ctor, "assignment");
|
|
1678
|
+
}
|
|
1679
|
+
} else if (n.type === "assignment_expression") {
|
|
1680
|
+
const left = n.childForFieldName("left");
|
|
1681
|
+
const right = n.childForFieldName("right");
|
|
1682
|
+
const ctor = newCtorLeaf(right);
|
|
1683
|
+
const line = n.startPosition.row + 1;
|
|
1684
|
+
if (left && ctor !== null) {
|
|
1685
|
+
if (left.type === "identifier") record(left.text, ctor, "assignment");
|
|
1686
|
+
else if (left.type === "member_expression") {
|
|
1687
|
+
const obj = left.childForFieldName("object");
|
|
1688
|
+
const prop = left.childForFieldName("property");
|
|
1689
|
+
if (obj?.type === "this" && prop?.type === "property_identifier") {
|
|
1690
|
+
record(`this.${prop.text}`, ctor, "assignment");
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
} else if (left && right && left.type === "member_expression" && right.type === "identifier") {
|
|
1694
|
+
const obj = left.childForFieldName("object");
|
|
1695
|
+
const prop = left.childForFieldName("property");
|
|
1696
|
+
if (obj?.type === "this" && prop?.type === "property_identifier") {
|
|
1697
|
+
const paramType = jsdocParamTypeAt(line, right.text);
|
|
1698
|
+
if (paramType !== null) record(`this.${prop.text}`, paramType, "jsdoc");
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1703
|
+
const child = n.namedChild(i);
|
|
1704
|
+
if (child) stack.push(child);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return types;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* THE CLASS `this` IS BOUND TO AT A CALL SITE — `null` when the call's `this` is not a
|
|
1711
|
+
* class instance (a plain function's dynamic `this`, or no class at all).
|
|
1712
|
+
*
|
|
1713
|
+
* PURE SYNTAX, no inference: an arrow function does not rebind `this`, so it is stepped
|
|
1714
|
+
* through; a `method_definition`'s own class is found by the same walk (the class ancestor
|
|
1715
|
+
* is ABOVE it), and a nested `function` expression/declaration rebinds `this`, so the walk
|
|
1716
|
+
* stops there and answers null. A static method or static block answers the CLASS, which is
|
|
1717
|
+
* what a class-like owner symbol is for.
|
|
1718
|
+
*/
|
|
1719
|
+
enclosingThisClass(n) {
|
|
1720
|
+
let p = n.parent;
|
|
1721
|
+
while (p) {
|
|
1722
|
+
if (p.type === "class_declaration") return p.childForFieldName("name")?.text ?? null;
|
|
1723
|
+
if (p.type === "function_declaration" || p.type === "function_expression" || p.type === "generator_function_declaration" || p.type === "generator_function") return null;
|
|
1724
|
+
p = p.parent;
|
|
1725
|
+
}
|
|
1726
|
+
return null;
|
|
1727
|
+
}
|
|
1728
|
+
extractCallRelationships(node, symbols, relationships, moduleName, receiverTypes, deadSpans = []) {
|
|
1729
|
+
const resolveCurrent = (n, current) => {
|
|
1730
|
+
const fnName = n.childForFieldName?.("name")?.text;
|
|
1731
|
+
if ((n.type === "function_declaration" || n.type === "generator_function_declaration" || n.type === "method_definition" || n.type === "function_expression") && fnName) {
|
|
1732
|
+
return fnName;
|
|
1733
|
+
}
|
|
1734
|
+
if (n.type === "variable_declarator") {
|
|
1735
|
+
const nameNode = n.childForFieldName("name");
|
|
1736
|
+
const valueNode = n.childForFieldName("value");
|
|
1737
|
+
if (nameNode && nameNode.type === "identifier" && valueNode && (valueNode.type === "arrow_function" || valueNode.type === "function_expression")) {
|
|
1738
|
+
return nameNode.text;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
if (n.type === "class_declaration") {
|
|
1742
|
+
const nm = n.childForFieldName("name")?.text;
|
|
1743
|
+
if (nm) return nm;
|
|
1744
|
+
}
|
|
1745
|
+
if (n.type === "assignment_expression") {
|
|
1746
|
+
const left = n.childForFieldName("left");
|
|
1747
|
+
const right = n.childForFieldName("right");
|
|
1748
|
+
if (left && right && (right.type === "function_expression" || right.type === "arrow_function") && left.type === "member_expression") {
|
|
1749
|
+
const obj = left.childForFieldName("object");
|
|
1750
|
+
const prop = left.childForFieldName("property");
|
|
1751
|
+
if (obj && obj.type === "member_expression") {
|
|
1752
|
+
const protoProp = obj.childForFieldName("property");
|
|
1753
|
+
if (protoProp && protoProp.text === "prototype" && prop) {
|
|
1754
|
+
return prop.text;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return current;
|
|
1760
|
+
};
|
|
1761
|
+
const processNode = (n, current) => {
|
|
1762
|
+
if (isNodeInDeadSpans(deadSpans, n)) return;
|
|
1763
|
+
if (this.inDecoratorPosition(n)) return;
|
|
1764
|
+
current = resolveCurrent(n, current);
|
|
1765
|
+
const callSource = current || moduleName;
|
|
1766
|
+
if (n.type === "call_expression") {
|
|
1767
|
+
const fn = n.childForFieldName("function");
|
|
1768
|
+
const isImportCallee = !!fn && (fn.type === "import" || fn.type === "identifier" && fn.text === "import");
|
|
1769
|
+
if (fn && !(fn.type === "identifier" && fn.text === "require" || isImportCallee)) {
|
|
1770
|
+
let targetName = fn.text;
|
|
1771
|
+
let fullCallee = fn.text;
|
|
1772
|
+
let skip = false;
|
|
1773
|
+
let nameNode = fn;
|
|
1774
|
+
if (fn.type === "member_expression") {
|
|
1775
|
+
const prop = fn.childForFieldName("property");
|
|
1776
|
+
if (prop) {
|
|
1777
|
+
targetName = prop.text;
|
|
1778
|
+
nameNode = prop;
|
|
1779
|
+
}
|
|
1780
|
+
} else if (fn.type === "subscript_expression") {
|
|
1781
|
+
skip = true;
|
|
1782
|
+
}
|
|
1783
|
+
if (skip) {
|
|
1784
|
+
} else if (targetName.length > 100 || targetName.includes("\n") || targetName.includes("`") || targetName.includes("${") || hasBraceEquals(targetName) || !isNameNode(nameNode)) {
|
|
1785
|
+
} else if (this.isJSBuiltinCall(targetName, fullCallee)) {
|
|
1786
|
+
} else {
|
|
1787
|
+
const boundCallee = fn.type === "identifier" ? this.localBindingTarget(fn, targetName, callSource) : null;
|
|
1788
|
+
const importedCallee = boundCallee ? null : fn.type === "identifier" ? this.importedBindingFor(fn, targetName) : null;
|
|
1789
|
+
let receiverImport = null;
|
|
1790
|
+
if (!boundCallee && !importedCallee && fn.type === "member_expression") {
|
|
1791
|
+
let recvRoot = fn.childForFieldName("object");
|
|
1792
|
+
while (recvRoot && recvRoot.type === "member_expression") recvRoot = recvRoot.childForFieldName("object");
|
|
1793
|
+
if (recvRoot?.type === "identifier") {
|
|
1794
|
+
const candidate = this.importedBindingFor(recvRoot, recvRoot.text);
|
|
1795
|
+
if (candidate && isExternalSpecifier(candidate.specifier)) receiverImport = candidate;
|
|
1796
|
+
}
|
|
1797
|
+
if (receiverImport && receiverImport.binding === targetName) receiverImport = null;
|
|
1798
|
+
}
|
|
1799
|
+
relationships.push({
|
|
1800
|
+
source: callSource,
|
|
1801
|
+
target: boundCallee ?? targetName,
|
|
1802
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
1803
|
+
confidence: 0.9,
|
|
1804
|
+
// The callee LEAF — for `a.b.c()` the property node, so a chain's
|
|
1805
|
+
// call does not take the chain's opening line.
|
|
1806
|
+
...this.lineField(fn.type === "member_expression" ? fn.childForFieldName("property") ?? fn : fn),
|
|
1807
|
+
metadata: {
|
|
1808
|
+
calleeExpression: fullCallee,
|
|
1809
|
+
...boundCallee ? { localBinding: true } : {},
|
|
1810
|
+
...importedCallee ? { importedBinding: importedCallee } : {},
|
|
1811
|
+
...receiverImport ? {
|
|
1812
|
+
receiverImport,
|
|
1813
|
+
receiverImportSpecifier: receiverImport.specifier,
|
|
1814
|
+
receiverImportBinding: receiverImport.binding
|
|
1815
|
+
} : {},
|
|
1816
|
+
// The receiver's stated type, on the store's EXISTING channel: `metadata.owner`
|
|
1817
|
+
// is what `member-owner-resolution.ts` (claimOwnerCarryingTargets +
|
|
1818
|
+
// resolveMemberTargetsByOwner) reads on BOTH production resolution paths — it
|
|
1819
|
+
// re-resolves the row inside that class, which is what stops `c.run()` binding
|
|
1820
|
+
// to another file's same-named method. `ownerEvidence` says WHICH syntactic
|
|
1821
|
+
// proof produced the owner ({new, assignment, this, jsdoc, class-static,
|
|
1822
|
+
// this-alias}) so coverage is a
|
|
1823
|
+
// countable number per evidence class rather than one asserted figure. NO new
|
|
1824
|
+
// channel is introduced: a wrong owner is worse than none, because the store
|
|
1825
|
+
// withdraws the name binding for an owner-carrying row and then declines.
|
|
1826
|
+
...fn.type === "member_expression" ? (() => {
|
|
1827
|
+
const object = fn.childForFieldName("object");
|
|
1828
|
+
if (!object) return {};
|
|
1829
|
+
let recv;
|
|
1830
|
+
if (object.type === "identifier") {
|
|
1831
|
+
recv = receiverTypes.get(object.text) ?? this.staticClassReceiver(object, object.text) ?? this.thisAliasOwnerAt(object, object.text) ?? void 0;
|
|
1832
|
+
} else if (object.type === "this") {
|
|
1833
|
+
const cls = this.enclosingThisClass(fn);
|
|
1834
|
+
if (cls) recv = { type: cls, evidence: "this" };
|
|
1835
|
+
} else if (object.type === "member_expression") {
|
|
1836
|
+
const inner = object.childForFieldName("object");
|
|
1837
|
+
const prop = object.childForFieldName("property");
|
|
1838
|
+
if (inner?.type === "this" && prop) recv = receiverTypes.get(`this.${prop.text}`);
|
|
1839
|
+
}
|
|
1840
|
+
return recv ? { owner: recv.type, ownerEvidence: recv.evidence } : {};
|
|
1841
|
+
})() : {}
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
} else if (n.type === "new_expression") {
|
|
1847
|
+
const ctor = n.childForFieldName("constructor");
|
|
1848
|
+
if (ctor) {
|
|
1849
|
+
let ctorName = "";
|
|
1850
|
+
if (ctor.type === "identifier") ctorName = ctor.text;
|
|
1851
|
+
else if (ctor.type === "member_expression") {
|
|
1852
|
+
const prop = ctor.childForFieldName("property");
|
|
1853
|
+
ctorName = prop ? prop.text : ctor.text;
|
|
1854
|
+
}
|
|
1855
|
+
if (ctorName && !JS_GLOBAL_CONSTRUCTORS.has(ctorName)) {
|
|
1856
|
+
const boundCtor = ctor.type === "identifier" ? this.localBindingTarget(ctor, ctorName, callSource) : null;
|
|
1857
|
+
const importedCtor = boundCtor ? null : ctor.type === "identifier" ? this.importedBindingFor(ctor, ctorName) : null;
|
|
1858
|
+
relationships.push({
|
|
1859
|
+
source: callSource,
|
|
1860
|
+
target: boundCtor ?? ctorName,
|
|
1861
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
1862
|
+
confidence: 0.95,
|
|
1863
|
+
// The CLASS NAME node: for `new ns.Thing()` the property leaf,
|
|
1864
|
+
// since the target is the leaf (TSD3 leaf identity).
|
|
1865
|
+
...this.lineField(ctor.type === "member_expression" ? ctor.childForFieldName("property") ?? ctor : ctor),
|
|
1866
|
+
metadata: boundCtor ? { localBinding: true } : importedCtor ? { importedBinding: importedCtor } : {}
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
} else if (n.type === "jsx_element" || n.type === "jsx_self_closing_element") {
|
|
1871
|
+
const opening = n.type === "jsx_self_closing_element" ? n : n.namedChild(0);
|
|
1872
|
+
if (opening && (opening.type === "jsx_self_closing_element" || opening.type === "jsx_opening_element")) {
|
|
1873
|
+
const identifier = opening.namedChild(0);
|
|
1874
|
+
if (identifier && identifier.type === "identifier") {
|
|
1875
|
+
const char = identifier.text[0];
|
|
1876
|
+
if (char && char === char.toUpperCase() && char !== char.toLowerCase()) {
|
|
1877
|
+
const boundJsx = this.localBindingTarget(identifier, identifier.text, callSource);
|
|
1878
|
+
const importedJsx = boundJsx ? null : this.importedBindingFor(identifier, identifier.text);
|
|
1879
|
+
relationships.push({
|
|
1880
|
+
source: callSource,
|
|
1881
|
+
target: boundJsx ?? identifier.text,
|
|
1882
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
1883
|
+
confidence: 0.9,
|
|
1884
|
+
...this.lineField(identifier),
|
|
1885
|
+
metadata: {
|
|
1886
|
+
jsxElement: identifier.text,
|
|
1887
|
+
...boundJsx ? { localBinding: true } : {},
|
|
1888
|
+
...importedJsx ? { importedBinding: importedJsx } : {}
|
|
1889
|
+
}
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1896
|
+
const child = n.namedChild(i);
|
|
1897
|
+
if (child) processNode(child, current);
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
processNode(node, null);
|
|
1901
|
+
}
|
|
1902
|
+
extractExportRelationships(exports, relationships, moduleActor, declaredNames) {
|
|
1903
|
+
const CJS_PROPERTY_CLAIM_FORMS = /* @__PURE__ */ new Set([
|
|
1904
|
+
"module-exports-object",
|
|
1905
|
+
"exports-object",
|
|
1906
|
+
"deep-member-assignment"
|
|
1907
|
+
]);
|
|
1908
|
+
for (const item of exports) {
|
|
1909
|
+
const isCjs = item.metadata?.viaCommonJs === true;
|
|
1910
|
+
const localName = typeof item.metadata?.localName === "string" ? String(item.metadata.localName) : "";
|
|
1911
|
+
const defaultForm = isCjs ? "" : item.metadata?.viaDefaultDeclaration === true ? "named-declaration" : item.metadata?.viaAliasDefault === true ? "export-alias" : item.metadata?.viaDefaultExpression === true ? "expression" : item.metadata?.viaAnonymousDefaultExpression === true ? "anonymous" : "anonymous";
|
|
1912
|
+
const claimMarkers = {};
|
|
1913
|
+
if (item.isDefault && !isCjs) {
|
|
1914
|
+
claimMarkers.defaultExportForm = defaultForm;
|
|
1915
|
+
if (localName) claimMarkers.defaultExportLocalName = localName;
|
|
1916
|
+
claimMarkers.defaultExport = { form: defaultForm, ...localName ? { localName } : {} };
|
|
1917
|
+
} else if (isCjs && !item.isDefault && !declaredNames.has(item.name) && CJS_PROPERTY_CLAIM_FORMS.has(String(item.metadata?.cjsForm ?? ""))) {
|
|
1918
|
+
const cjsForm = String(item.metadata?.cjsForm ?? "module-exports-unknown");
|
|
1919
|
+
claimMarkers.moduleExportsProperty = item.name;
|
|
1920
|
+
claimMarkers.moduleExportsForm = cjsForm;
|
|
1921
|
+
claimMarkers.moduleExports = { property: item.name, form: cjsForm };
|
|
1922
|
+
}
|
|
1923
|
+
relationships.push({
|
|
1924
|
+
source: moduleActor,
|
|
1925
|
+
target: item.name,
|
|
1926
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
1927
|
+
confidence: 1,
|
|
1928
|
+
// The exported NAME's own line, from the export's range when the
|
|
1929
|
+
// collector recorded one. Absent rather than invented when it did not.
|
|
1930
|
+
...item.range?.startLine ? { line_number: item.range.startLine } : {},
|
|
1931
|
+
// THE RECORD'S OWN metadata is carried through, not replaced by
|
|
1932
|
+
// `{isDefault}`. JSD53's ruling: JD-J14 decided a NAMED default
|
|
1933
|
+
// declaration is exported under `default` (ECMAScript) and kept the
|
|
1934
|
+
// declaration's own name in `metadata.localName` "so nothing is lost"
|
|
1935
|
+
// — and this line then dropped it, so the loss was total. The row's
|
|
1936
|
+
// target `default` matches no symbol (it is in the symbol denylist),
|
|
1937
|
+
// so `import Def from "./m"` could not resolve for any module with a
|
|
1938
|
+
// named default export, and `script/m40/verify-import-resolution.ts`
|
|
1939
|
+
// JD2.4 has been red since. `loadImportInputs` reads exactly this
|
|
1940
|
+
// `localName` (src/ast/import-resolution.ts) to recover the
|
|
1941
|
+
// declaration in the row's own file. Additive: `isDefault` stays, and
|
|
1942
|
+
// the engine's own evidenceClass/fileLevel are added later.
|
|
1943
|
+
metadata: { isDefault: item.isDefault, ...item.metadata ?? {}, ...claimMarkers }
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
// ============================================================================
|
|
1948
|
+
// HELPERS
|
|
1949
|
+
// ============================================================================
|
|
1950
|
+
/**
|
|
1951
|
+
* The CJS public-surface path BENEATH the base, read structurally.
|
|
1952
|
+
*
|
|
1953
|
+
* `module.exports.a.b = 1` -> ["a","b"]; `exports.g.x = 1` -> ["g","x"];
|
|
1954
|
+
* `exports.tag = 1` -> [] (an empty array, which is truthy — the caller
|
|
1955
|
+
* relies on that to tell "CJS surface" from "not CJS surface").
|
|
1956
|
+
*
|
|
1957
|
+
* Returns null when the chain is not a module surface (`svc.limit = 1`, whose
|
|
1958
|
+
* obj is a plain identifier) or when a segment is not statically nameable
|
|
1959
|
+
* (`module[expr].a = 1`, `(freeModule.exports = lodash)._ = 1`). An unnameable
|
|
1960
|
+
* segment yields NO export rather than a fabricated one — the defect class the
|
|
1961
|
+
* regex policy exists to prevent, measured on minified lodash where the text
|
|
1962
|
+
* form emitted the literal export name "(ie.exports=we)._".
|
|
1963
|
+
*/
|
|
1964
|
+
cjsSurfacePath(obj) {
|
|
1965
|
+
const segs = [];
|
|
1966
|
+
let cur = obj;
|
|
1967
|
+
while (cur && (cur.type === "member_expression" || cur.type === "subscript_expression")) {
|
|
1968
|
+
if (cur.type === "subscript_expression") {
|
|
1969
|
+
const idx = cur.childForFieldName("index");
|
|
1970
|
+
if (!idx) return null;
|
|
1971
|
+
if (idx.type !== "string" && idx.type !== "number") return null;
|
|
1972
|
+
segs.unshift(stringContent(idx));
|
|
1973
|
+
} else {
|
|
1974
|
+
const prop = cur.childForFieldName("property");
|
|
1975
|
+
if (!prop) return null;
|
|
1976
|
+
segs.unshift(prop.text);
|
|
1977
|
+
}
|
|
1978
|
+
cur = cur.childForFieldName("object");
|
|
1979
|
+
}
|
|
1980
|
+
if (!cur) return null;
|
|
1981
|
+
if (cur.type === "member_expression" || cur.type === "subscript_expression") return null;
|
|
1982
|
+
if (cur.type !== "identifier") return null;
|
|
1983
|
+
if (cur.text === "module") return segs[0] === "exports" ? segs.slice(1) : null;
|
|
1984
|
+
if (cur.text === "exports") return segs;
|
|
1985
|
+
return null;
|
|
1986
|
+
}
|
|
1987
|
+
isJSBuiltinCall(targetName, fullCallee) {
|
|
1988
|
+
if (targetName === "super") return true;
|
|
1989
|
+
if (JS_CONSOLE_BUILTINS.has(fullCallee || "")) return true;
|
|
1990
|
+
const trimmed = stripWhitespace(fullCallee || "");
|
|
1991
|
+
if (JS_CONSOLE_BUILTINS.has(trimmed)) return true;
|
|
1992
|
+
if (JS_OBJECT_LEAF_METHODS.has(targetName)) return true;
|
|
1993
|
+
return false;
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* M49 #868: the JS twin of the typescript analyzer's `decoratorLines`. The
|
|
1997
|
+
* name list is what the store reads on the symbol; the PARALLEL line list
|
|
1998
|
+
* lets the decorator's reference edge name the line the decorator is
|
|
1999
|
+
* written on. A name already recorded keeps its FIRST position, so the two
|
|
2000
|
+
* lists stay in lockstep by construction.
|
|
2001
|
+
*/
|
|
2002
|
+
collectOwnDecoratorDetails(node) {
|
|
2003
|
+
const names = [];
|
|
2004
|
+
const lines = [];
|
|
2005
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2006
|
+
const c = node.namedChild(i);
|
|
2007
|
+
if (!c || c.type !== "decorator") continue;
|
|
2008
|
+
const name = jsDecoratorName(c);
|
|
2009
|
+
if (name.length === 0 || names.includes(name)) continue;
|
|
2010
|
+
names.push(name);
|
|
2011
|
+
lines.push(c.startPosition.row + 1);
|
|
2012
|
+
}
|
|
2013
|
+
return { names, lines };
|
|
2014
|
+
}
|
|
2015
|
+
/**
|
|
2016
|
+
* M49 #868 — is this node written in DECORATOR position?
|
|
2017
|
+
*
|
|
2018
|
+
* A decorator is METADATA on the symbol it decorates, not a call or a read
|
|
2019
|
+
* made by whoever lexically encloses it. The family already rules this way
|
|
2020
|
+
* (typescript: `inDecoratorPosition`, real-typescript-analyzer.ts; the JVM
|
|
2021
|
+
* analyzers attach annotations as metadata and emit no edge; the python
|
|
2022
|
+
* oracle documents PYD1). javascript was the exception: a class-body
|
|
2023
|
+
* `@inject("db")` is syntactically a call expression, so the walk attributed
|
|
2024
|
+
* it to the enclosing CLASS — `calls Service -> inject` beside the
|
|
2025
|
+
* member-scoped edge that should carry it — and the bare-read pass turned
|
|
2026
|
+
* every decorator name into a `uses` edge from the decorated symbol.
|
|
2027
|
+
*
|
|
2028
|
+
* The walk stops at the enclosing statement/declaration boundary rather than
|
|
2029
|
+
* walking to the root: a decorator on a METHOD cannot make the method's own
|
|
2030
|
+
* body a decorator position (its decorators are siblings, never ancestors of
|
|
2031
|
+
* the body).
|
|
2032
|
+
*/
|
|
2033
|
+
inDecoratorPosition(n) {
|
|
2034
|
+
if (!this.hasAnyDecoratorNode) return false;
|
|
2035
|
+
let p = n.parent;
|
|
2036
|
+
while (p) {
|
|
2037
|
+
if (p.type === "decorator") return true;
|
|
2038
|
+
if (p.type === "class_declaration" || p.type === "class" || p.type === "function_declaration" || p.type === "generator_function_declaration" || p.type === "method_definition" || p.type === "statement_block" || p.type === "program") {
|
|
2039
|
+
return false;
|
|
2040
|
+
}
|
|
2041
|
+
p = p.parent;
|
|
2042
|
+
}
|
|
2043
|
+
return false;
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* M49 #868 — CLASS decorators, which the JS grammar places in TWO spots:
|
|
2047
|
+
* own children of `class_declaration`, or own children of the WRAPPING
|
|
2048
|
+
* `export_statement` when the class is exported (probe:
|
|
2049
|
+
* `export_statement children=[decorator@2, export@3, class_declaration@3]`).
|
|
2050
|
+
* The own-child reader alone therefore dropped every decorator on an
|
|
2051
|
+
* exported class — `@sealed export class A {}` produced no decorators on `A`
|
|
2052
|
+
* and no reference edge at all, while the typescript twin's
|
|
2053
|
+
* `collectDecoratorsForClass` already checks the export_statement.
|
|
2054
|
+
*/
|
|
2055
|
+
collectClassDecoratorDetails(node) {
|
|
2056
|
+
const own = this.collectOwnDecoratorDetails(node);
|
|
2057
|
+
const wrapping = node.parent?.type === "export_statement" ? this.collectOwnDecoratorDetails(node.parent) : { names: [], lines: [] };
|
|
2058
|
+
if (wrapping.names.length === 0) return own;
|
|
2059
|
+
const names = [...own.names];
|
|
2060
|
+
const lines = [...own.lines];
|
|
2061
|
+
for (let i = 0; i < wrapping.names.length; i++) {
|
|
2062
|
+
if (names.includes(wrapping.names[i])) continue;
|
|
2063
|
+
names.push(wrapping.names[i]);
|
|
2064
|
+
lines.push(wrapping.lines[i]);
|
|
2065
|
+
}
|
|
2066
|
+
return { names, lines };
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* M49 #868: a decorator is metadata on the symbol it decorates, so its name
|
|
2070
|
+
* is reachable as a REFERENCE from that symbol (the typescript twin's
|
|
2071
|
+
* `extractDecoratorRelationships`, and the shape the M59 #860 fixture pins
|
|
2072
|
+
* there). Emitted for every decorated symbol, with the decorator's OWN line:
|
|
2073
|
+
* a decorator whose line was never recorded stays line-less rather than
|
|
2074
|
+
* borrowing the declaration's line (never invent a line, #823).
|
|
2075
|
+
*/
|
|
2076
|
+
extractDecoratorRelationships(symbols, relationships) {
|
|
2077
|
+
for (const symbol of symbols) {
|
|
2078
|
+
if (!symbol.decorators || symbol.decorators.length === 0) continue;
|
|
2079
|
+
const lines = symbol.metadata?.decoratorLines;
|
|
2080
|
+
for (let i = 0; i < symbol.decorators.length; i++) {
|
|
2081
|
+
const decorator = symbol.decorators[i];
|
|
2082
|
+
const line = lines?.[i];
|
|
2083
|
+
relationships.push({
|
|
2084
|
+
source: symbol.name,
|
|
2085
|
+
target: decorator,
|
|
2086
|
+
type: ASTEngine.RelationshipType.Decorates,
|
|
2087
|
+
confidence: 1,
|
|
2088
|
+
...typeof line === "number" ? { line_number: line } : {},
|
|
2089
|
+
metadata: {
|
|
2090
|
+
isDecorator: true,
|
|
2091
|
+
decoratorLine: typeof line === "number" ? line : null
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
hasToken(node, token) {
|
|
2098
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2099
|
+
const child = node.child(i);
|
|
2100
|
+
if (!child || child.isNamed) continue;
|
|
2101
|
+
if (child.type === token) return true;
|
|
2102
|
+
}
|
|
2103
|
+
return false;
|
|
2104
|
+
}
|
|
2105
|
+
isConstDeclarator(node) {
|
|
2106
|
+
const parent = node.parent;
|
|
2107
|
+
if (parent && (parent.type === "lexical_declaration" || parent.type === "variable_declaration")) {
|
|
2108
|
+
const kind = parent.childForFieldName("kind");
|
|
2109
|
+
if (kind) return kind.text === "const";
|
|
2110
|
+
}
|
|
2111
|
+
return false;
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* M59 (#825) follow-up: the destructuring walk carries the binding NODE so each
|
|
2115
|
+
* destructured symbol ranges at its own NAME
|
|
2116
|
+
* instead of the whole declarator (`const {` for a wrapped binding list).
|
|
2117
|
+
* Names and nodes stay in lockstep by construction (single walk).
|
|
2118
|
+
*/
|
|
2119
|
+
collectPatternPairs(pattern) {
|
|
2120
|
+
const out = [];
|
|
2121
|
+
const walk = (n) => {
|
|
2122
|
+
switch (n.type) {
|
|
2123
|
+
case "shorthand_property_identifier":
|
|
2124
|
+
case "shorthand_property_identifier_pattern":
|
|
2125
|
+
case "identifier":
|
|
2126
|
+
if (n.text) out.push({ name: n.text, node: n });
|
|
2127
|
+
break;
|
|
2128
|
+
case "pair_pattern": {
|
|
2129
|
+
const value = n.childForFieldName("value");
|
|
2130
|
+
if (value) walk(value);
|
|
2131
|
+
break;
|
|
2132
|
+
}
|
|
2133
|
+
case "assignment_pattern":
|
|
2134
|
+
case "object_assignment_pattern": {
|
|
2135
|
+
const left = n.childForFieldName("left");
|
|
2136
|
+
if (left) walk(left);
|
|
2137
|
+
break;
|
|
2138
|
+
}
|
|
2139
|
+
case "rest_pattern": {
|
|
2140
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2141
|
+
const c = n.namedChild(i);
|
|
2142
|
+
if (c) walk(c);
|
|
2143
|
+
}
|
|
2144
|
+
break;
|
|
2145
|
+
}
|
|
2146
|
+
default:
|
|
2147
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2148
|
+
const c = n.namedChild(i);
|
|
2149
|
+
if (c) walk(c);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
walk(pattern);
|
|
2154
|
+
return out;
|
|
2155
|
+
}
|
|
2156
|
+
extractParameters(paramsNode) {
|
|
2157
|
+
if (!paramsNode) return [];
|
|
2158
|
+
const out = [];
|
|
2159
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
2160
|
+
const p = paramsNode.namedChild(i);
|
|
2161
|
+
if (!p) continue;
|
|
2162
|
+
if (p.type === "identifier") {
|
|
2163
|
+
out.push({ name: p.text });
|
|
2164
|
+
} else if (p.type === "assignment_pattern") {
|
|
2165
|
+
const left = p.childForFieldName("left");
|
|
2166
|
+
if (left && left.type === "identifier") out.push({ name: left.text });
|
|
2167
|
+
else if (left && (left.type === "object_pattern" || left.type === "array_pattern")) {
|
|
2168
|
+
out.push({ name: p.text.slice(0, 60) });
|
|
2169
|
+
}
|
|
2170
|
+
} else if (p.type === "object_pattern" || p.type === "array_pattern") {
|
|
2171
|
+
out.push({ name: p.text.slice(0, 60) });
|
|
2172
|
+
} else if (p.type === "rest_pattern") {
|
|
2173
|
+
const inner = this.findChildByType(p, "identifier");
|
|
2174
|
+
out.push({ name: inner ? inner.text : "..." });
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
return out;
|
|
2178
|
+
}
|
|
2179
|
+
methodComplexity(node) {
|
|
2180
|
+
let c = 1;
|
|
2181
|
+
const stack = [node];
|
|
2182
|
+
while (stack.length) {
|
|
2183
|
+
const n = stack.pop();
|
|
2184
|
+
if (n.type === "if_statement" || n.type === "for_statement" || n.type === "for_in_statement" || n.type === "do_statement" || n.type === "while_statement" || n.type === "catch_clause" || n.type === "switch_case" || n.type === "ternary_expression") {
|
|
2185
|
+
c++;
|
|
2186
|
+
} else if (n.type === "binary_expression") {
|
|
2187
|
+
const op = n.childForFieldName("operator")?.text;
|
|
2188
|
+
if (op === "&&" || op === "||" || op === "??") c++;
|
|
2189
|
+
}
|
|
2190
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2191
|
+
const child = n.namedChild(i);
|
|
2192
|
+
if (child) stack.push(child);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return c;
|
|
2196
|
+
}
|
|
2197
|
+
collectComments(node) {
|
|
2198
|
+
const comments = [];
|
|
2199
|
+
const scanNode = node.parent && node.parent.type === "export_statement" ? node.parent : node;
|
|
2200
|
+
let sibling = scanNode.previousSibling;
|
|
2201
|
+
let anchorRow = scanNode.startPosition.row;
|
|
2202
|
+
while (sibling) {
|
|
2203
|
+
if (sibling.type === "comment") {
|
|
2204
|
+
if (sibling.endPosition.row + 1 < anchorRow) break;
|
|
2205
|
+
comments.unshift(stripCommentMarkers(sibling.text));
|
|
2206
|
+
anchorRow = sibling.startPosition.row;
|
|
2207
|
+
sibling = sibling.previousSibling;
|
|
2208
|
+
} else {
|
|
2209
|
+
break;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return comments.length > 0 ? comments : void 0;
|
|
2213
|
+
}
|
|
2214
|
+
collectModuleExports(right, exports, node, isDefault, surface) {
|
|
2215
|
+
const form = (kind) => `${surface}-${kind}`;
|
|
2216
|
+
if (right.type === "object") {
|
|
2217
|
+
for (let i = 0; i < right.namedChildCount; i++) {
|
|
2218
|
+
const c = right.namedChild(i);
|
|
2219
|
+
if (!c) continue;
|
|
2220
|
+
if (c.type === "shorthand_property_identifier" || c.type === "identifier") {
|
|
2221
|
+
exports.push({ name: c.text, isDefault: false, range: this.nodeToRange(c), metadata: { viaCommonJs: true, cjsForm: form("object") } });
|
|
2222
|
+
} else if (c.type === "pair") {
|
|
2223
|
+
const key = c.childForFieldName("key");
|
|
2224
|
+
if (key && (key.type === "property_identifier" || key.type === "identifier")) {
|
|
2225
|
+
exports.push({ name: key.text, isDefault: false, range: this.nodeToRange(key), metadata: { viaCommonJs: true, cjsForm: form("object") } });
|
|
2226
|
+
}
|
|
2227
|
+
} else if (c.type === "method_definition") {
|
|
2228
|
+
const nm = c.childForFieldName("name");
|
|
2229
|
+
if (nm) exports.push({ name: nm.text, isDefault: false, range: this.nodeToRange(nm), metadata: { viaCommonJs: true, cjsForm: form("object") } });
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
} else if (right.type === "identifier") {
|
|
2233
|
+
exports.push({ name: right.text, isDefault, range: this.nodeToRange(right), metadata: { viaCommonJs: true, cjsForm: form("binding") } });
|
|
2234
|
+
} else if (right.type === "function_expression" || right.type === "arrow_function" || right.type === "class_declaration" || right.type === "class") {
|
|
2235
|
+
const nm = right.type === "class_declaration" || right.type === "class" ? right.childForFieldName("name")?.text : void 0;
|
|
2236
|
+
exports.push({ name: nm ?? "default", isDefault, range: this.nodeToRange(right), metadata: { viaCommonJs: true, cjsForm: form("expression") } });
|
|
2237
|
+
}
|
|
2238
|
+
{
|
|
2239
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2240
|
+
for (let i = exports.length - 1; i >= 0; i--) {
|
|
2241
|
+
if (seen.has(exports[i].name)) exports.splice(i, 1);
|
|
2242
|
+
else seen.add(exports[i].name);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
collectExportStatement(node, exports) {
|
|
2247
|
+
let isDefault = false;
|
|
2248
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2249
|
+
const t = node.child(i);
|
|
2250
|
+
if (t && !t.isNamed && t.text === "default") {
|
|
2251
|
+
isDefault = true;
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
let foundNamed = false;
|
|
2256
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2257
|
+
const child = node.namedChild(i);
|
|
2258
|
+
if (!child) continue;
|
|
2259
|
+
if (child.type === "identifier" && !isDefault) {
|
|
2260
|
+
foundNamed = true;
|
|
2261
|
+
exports.push({ name: child.text, isDefault, range: this.nodeToRange(child) });
|
|
2262
|
+
} else if (child.type === "export_clause") {
|
|
2263
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
2264
|
+
const spec = child.namedChild(j);
|
|
2265
|
+
if (spec && spec.type === "export_specifier") {
|
|
2266
|
+
const exportName = spec.childForFieldName("alias")?.text ?? spec.childForFieldName("name")?.text ?? this.findChildByType(spec, "identifier")?.text;
|
|
2267
|
+
const aliasNode = spec.childForFieldName("alias");
|
|
2268
|
+
const isAsDefault = !!aliasNode && aliasNode.text === "default";
|
|
2269
|
+
if (exportName) {
|
|
2270
|
+
foundNamed = true;
|
|
2271
|
+
exports.push({
|
|
2272
|
+
name: isAsDefault ? "default" : exportName,
|
|
2273
|
+
isDefault: isAsDefault,
|
|
2274
|
+
range: this.nodeToRange(spec),
|
|
2275
|
+
metadata: isAsDefault ? { viaAliasDefault: true, localName: spec.childForFieldName("name")?.text } : void 0
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
} else if (child.type === "function_declaration" || child.type === "generator_function_declaration" || child.type === "class_declaration" || child.type === "function_expression" || child.type === "arrow_function" || child.type === "class") {
|
|
2281
|
+
const n = child.childForFieldName("name");
|
|
2282
|
+
foundNamed = true;
|
|
2283
|
+
exports.push({
|
|
2284
|
+
name: isDefault ? "default" : n?.text ?? "default",
|
|
2285
|
+
isDefault,
|
|
2286
|
+
range: this.nodeToRange(child),
|
|
2287
|
+
...isDefault && n?.text ? { metadata: { viaDefaultDeclaration: true, localName: n.text } } : {}
|
|
2288
|
+
});
|
|
2289
|
+
} else if (child.type === "lexical_declaration" || child.type === "variable_declaration") {
|
|
2290
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
2291
|
+
const decl = child.namedChild(j);
|
|
2292
|
+
if (decl && decl.type === "variable_declarator") {
|
|
2293
|
+
const nm = decl.childForFieldName("name");
|
|
2294
|
+
if (nm && nm.type === "identifier") {
|
|
2295
|
+
foundNamed = true;
|
|
2296
|
+
exports.push({ name: nm.text, isDefault, range: this.nodeToRange(nm) });
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
if (isDefault && !foundNamed) {
|
|
2303
|
+
const valueNode = node.childForFieldName("value");
|
|
2304
|
+
const isIdentifierValue = valueNode?.type === "identifier";
|
|
2305
|
+
exports.push({
|
|
2306
|
+
name: "default",
|
|
2307
|
+
isDefault: true,
|
|
2308
|
+
range: this.nodeToRange(node),
|
|
2309
|
+
// EVIDENCE for the exports claim: the statement form, and — when the defaulted expression IS
|
|
2310
|
+
// a plain identifier — the local binding it names (`export default foo`). An anonymous
|
|
2311
|
+
// expression (literal, object, call, anonymous function) states no local name and says so
|
|
2312
|
+
// rather than inventing one.
|
|
2313
|
+
metadata: isIdentifierValue ? { viaDefaultExpression: true, localName: valueNode.text } : { viaAnonymousDefaultExpression: true }
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
{
|
|
2317
|
+
let sawDefault = false;
|
|
2318
|
+
for (let i = exports.length - 1; i >= 0; i--) {
|
|
2319
|
+
if (exports[i].name !== "default") continue;
|
|
2320
|
+
if (sawDefault) exports.splice(i, 1);
|
|
2321
|
+
else sawDefault = true;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
{
|
|
2325
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
2326
|
+
for (let i = exports.length - 1; i >= 0; i--) {
|
|
2327
|
+
if (seenNames.has(exports[i].name)) exports.splice(i, 1);
|
|
2328
|
+
else seenNames.add(exports[i].name);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
findChildByType(node, type) {
|
|
2333
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2334
|
+
const child = node.namedChild(i);
|
|
2335
|
+
if (child && child.type === type) return child;
|
|
2336
|
+
}
|
|
2337
|
+
return null;
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* M14 / R3 — THE CALL-SHAPED CLASS HERITAGE. A class's supertype may be a CALL, and there are
|
|
2341
|
+
* TWO shapes of it, and they put the base and the mixin in OPPOSITE positions:
|
|
2342
|
+
*
|
|
2343
|
+
* (A) `class X extends Base.extend(M1, M2)` — a MEMBER call. The RECEIVER names the BASE and
|
|
2344
|
+
* the ARGUMENTS name the MIXINS composed into X's supertype chain. This is the Ember /
|
|
2345
|
+
* Backbone class-extension idiom, and it is the shape the corpus overwhelmingly has.
|
|
2346
|
+
* (B) `class X extends MixinFn(Base)` — a plain FUNCTION call. The CALLEE names the MIXIN and
|
|
2347
|
+
* the ARGUMENT names the BASE. This is the TypeScript/Redwood mixin-factory idiom, where
|
|
2348
|
+
* `MixinFn(Base)` evaluates to a class that extends Base.
|
|
2349
|
+
*
|
|
2350
|
+
* WHY THE SHAPE, NOT A NAME, DECIDES: C2 forbids a naming convention as the discriminator.
|
|
2351
|
+
* `Route.extend(M)` and `_.extend(obj, props)` are the same member-call shape, and only the
|
|
2352
|
+
* callee's SHAPE (member vs plain) tells the two apart structurally. The discriminator here is
|
|
2353
|
+
* the node shape of the heritage's own expression — `member_expression` versus `identifier` —
|
|
2354
|
+
* and the role of each operand follows from that shape. It is not "the method is called extend".
|
|
2355
|
+
*
|
|
2356
|
+
* WHY IT MATTERS: `findChildByType` scans the DIRECT named children of the node it is given, and
|
|
2357
|
+
* a call-shaped heritage's single direct named child is the `call_expression` — so the base's
|
|
2358
|
+
* `identifier` and `member_expression` are GRANDCHILDREN of it. Before this, every call-shaped
|
|
2359
|
+
* heritage published NO edge at all: neither the base nor the mixin. Measured over the admitted
|
|
2360
|
+
* population by `script/m14/javascript-heritage-census.ts`: 76 call-shaped clauses, of which the
|
|
2361
|
+
* analyzer's own lookup pair found 0.
|
|
2362
|
+
*
|
|
2363
|
+
* WHAT IT DELIBERATELY DOES NOT DO: it does not look inside the call for a base, so it never
|
|
2364
|
+
* claims a base that the call computes rather than names — the 11 arguments that are themselves
|
|
2365
|
+
* calls (`SortableFactory([...])`) contribute a mixin FACTORY, and this publishes the factory's
|
|
2366
|
+
* own name only when the argument names something.
|
|
2367
|
+
*/
|
|
2368
|
+
callShapedHeritage(heritage) {
|
|
2369
|
+
const call = this.findChildByType(heritage, "call_expression");
|
|
2370
|
+
if (!call) return null;
|
|
2371
|
+
const callee = call.childForFieldName("function");
|
|
2372
|
+
if (!callee) return null;
|
|
2373
|
+
const args = this.findChildByType(call, "arguments");
|
|
2374
|
+
const argNodes = [];
|
|
2375
|
+
if (args) {
|
|
2376
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
2377
|
+
const a = args.namedChild(i);
|
|
2378
|
+
if (a) argNodes.push(a);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
if (callee.type === "member_expression") {
|
|
2382
|
+
const receiver = callee.childForFieldName("object");
|
|
2383
|
+
return { base: receiver ?? null, mixins: argNodes, shape: "member" };
|
|
2384
|
+
}
|
|
2385
|
+
if (callee.type === "identifier") {
|
|
2386
|
+
return { base: argNodes[0] ?? null, mixins: [callee], shape: "function" };
|
|
2387
|
+
}
|
|
2388
|
+
return null;
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* M14 / R3 — the relationship rows a call-shaped heritage publishes. The base becomes an
|
|
2392
|
+
* `extends` edge and each mixin becomes a `mixes_in` edge, with the same binding resolution the
|
|
2393
|
+
* identifier form already uses, so the rows are resolvable on exactly the same terms.
|
|
2394
|
+
*/
|
|
2395
|
+
callShapedHeritageRows(heritage, source) {
|
|
2396
|
+
const shape = this.callShapedHeritage(heritage);
|
|
2397
|
+
const out = { base: null, mixins: [] };
|
|
2398
|
+
if (!shape) return out;
|
|
2399
|
+
const resolve = (node) => {
|
|
2400
|
+
if (!node) return null;
|
|
2401
|
+
if (node.type === "identifier") {
|
|
2402
|
+
const bound = this.localBindingTarget(node, node.text, source);
|
|
2403
|
+
const imported = bound ? null : this.importedBindingFor(node, node.text);
|
|
2404
|
+
return {
|
|
2405
|
+
target: bound ?? node.text,
|
|
2406
|
+
node,
|
|
2407
|
+
metadata: bound ? { localBinding: true } : imported ? { importedBinding: imported } : {}
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
if (node.type === "member_expression") {
|
|
2411
|
+
return { target: node.text, node, metadata: {} };
|
|
2412
|
+
}
|
|
2413
|
+
return null;
|
|
2414
|
+
};
|
|
2415
|
+
const base = resolve(shape.base);
|
|
2416
|
+
if (base) out.base = base;
|
|
2417
|
+
for (const m of shape.mixins) {
|
|
2418
|
+
const r = resolve(m);
|
|
2419
|
+
if (r) out.mixins.push(r);
|
|
2420
|
+
}
|
|
2421
|
+
return out;
|
|
2422
|
+
}
|
|
2423
|
+
detectsJsx(rootNode) {
|
|
2424
|
+
const stack = [rootNode];
|
|
2425
|
+
while (stack.length > 0) {
|
|
2426
|
+
const node = stack.pop();
|
|
2427
|
+
if (node.type === "jsx_element" || node.type === "jsx_self_closing_element") return true;
|
|
2428
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2429
|
+
const child = node.namedChild(i);
|
|
2430
|
+
if (child) stack.push(child);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
return false;
|
|
2434
|
+
}
|
|
2435
|
+
/**
|
|
2436
|
+
* Field-access uses: member_expression nodes emit root-anchored Uses edges
|
|
2437
|
+
* @0.6 with isFieldAccess marker - parity with java/groovy/kotlin
|
|
2438
|
+
* collectors. Nested member_expressions mean each chain segment appears as
|
|
2439
|
+
* a target (anti-skip guarantee); optional chains (?.) are
|
|
2440
|
+
* member_expression nodes with optional_chain tokens in this grammar.
|
|
2441
|
+
*/
|
|
2442
|
+
/**
|
|
2443
|
+
* M14 — instanceof type references. `x instanceof T` is JavaScript's only
|
|
2444
|
+
* unambiguous named-type usage; emitted as References from the enclosing
|
|
2445
|
+
* function/class. The right operand lexes as identifier (expression
|
|
2446
|
+
* position), so no type_identifier filtering applies.
|
|
2447
|
+
*/
|
|
2448
|
+
extractInstanceofRefs(rootNode, relationships, moduleName) {
|
|
2449
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
2450
|
+
const resolveSource = (start) => {
|
|
2451
|
+
let p = start.parent;
|
|
2452
|
+
while (p) {
|
|
2453
|
+
if (p.type === "class_declaration" || p.type === "function_declaration" || p.type === "generator_function_declaration" || p.type === "method_definition" || // M14 #419 J6: top-level const r = x instanceof T attributes the
|
|
2454
|
+
// reference to r (the enclosing declaration), never the global
|
|
2455
|
+
// placeholder (TS same-wave fix).
|
|
2456
|
+
p.type === "variable_declarator") {
|
|
2457
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
2458
|
+
const c = p.namedChild(i);
|
|
2459
|
+
if (!c) continue;
|
|
2460
|
+
if ((c.type === "identifier" || c.type === "property_identifier") && c.text) return c.text;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
p = p.parent;
|
|
2464
|
+
}
|
|
2465
|
+
return moduleName;
|
|
2466
|
+
};
|
|
2467
|
+
const walk = (n) => {
|
|
2468
|
+
if (n.type === "binary_expression") {
|
|
2469
|
+
let isInstanceof = false;
|
|
2470
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
2471
|
+
const c = n.child(i);
|
|
2472
|
+
if (c && !c.isNamed && c.text === "instanceof") {
|
|
2473
|
+
isInstanceof = true;
|
|
2474
|
+
break;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
if (isInstanceof && n.namedChildCount >= 2) {
|
|
2478
|
+
const right = n.namedChild(n.namedChildCount - 1);
|
|
2479
|
+
if (right) {
|
|
2480
|
+
const source = resolveSource(n);
|
|
2481
|
+
const key = `${source}|${right.text}`;
|
|
2482
|
+
if (!emitted.has(key)) {
|
|
2483
|
+
emitted.add(key);
|
|
2484
|
+
const boundRef = right.type === "identifier" ? this.localBindingTarget(right, right.text, source) : null;
|
|
2485
|
+
const importedRef = boundRef ? null : right.type === "identifier" ? this.importedBindingFor(right, right.text) : null;
|
|
2486
|
+
relationships.push({
|
|
2487
|
+
source,
|
|
2488
|
+
target: boundRef ?? right.text,
|
|
2489
|
+
type: ASTEngine.RelationshipType.References,
|
|
2490
|
+
confidence: 0.95,
|
|
2491
|
+
...this.lineField(right),
|
|
2492
|
+
metadata: {
|
|
2493
|
+
relationshipType: "instanceof_check",
|
|
2494
|
+
...boundRef ? { localBinding: true } : {},
|
|
2495
|
+
...importedRef ? { importedBinding: importedRef } : {}
|
|
2496
|
+
}
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2503
|
+
const c = n.namedChild(i);
|
|
2504
|
+
if (c) walk(c);
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
walk(rootNode);
|
|
2508
|
+
}
|
|
2509
|
+
collectFieldAccessUses(rootNode, relationships, moduleName, receiverTypes, deadSpans = []) {
|
|
2510
|
+
const stack = [
|
|
2511
|
+
{ node: rootNode, owner: null, method: null, module: moduleName }
|
|
2512
|
+
];
|
|
2513
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2514
|
+
while (stack.length > 0) {
|
|
2515
|
+
const item = stack.pop();
|
|
2516
|
+
const node = item.node;
|
|
2517
|
+
const owner = item.owner;
|
|
2518
|
+
const method = item.method;
|
|
2519
|
+
if (this.inDecoratorPosition(node)) continue;
|
|
2520
|
+
if (deadSpans.length > 0 && isNodeInDeadSpans(deadSpans, node)) continue;
|
|
2521
|
+
if (node.type === "class_declaration" || node.type === "class") {
|
|
2522
|
+
const nameNode = node.childForFieldName?.("name") ?? node.namedChildren.find((c) => c.type === "identifier");
|
|
2523
|
+
const nextOwner2 = nameNode ? nameNode.text : owner;
|
|
2524
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
2525
|
+
const child = node.namedChild(i);
|
|
2526
|
+
if (child) stack.push({ node: child, owner: nextOwner2, method: null, module: moduleName });
|
|
2527
|
+
}
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
let nextMethod = method;
|
|
2531
|
+
let nextOwner = owner;
|
|
2532
|
+
if (node.type === "function_declaration" || node.type === "generator_function_declaration" || node.type === "method_definition") {
|
|
2533
|
+
const nameNode = node.childForFieldName?.("name");
|
|
2534
|
+
if (nameNode) nextMethod = nameNode.text;
|
|
2535
|
+
} else if (node.type === "variable_declarator") {
|
|
2536
|
+
const v = node.childForFieldName("value");
|
|
2537
|
+
const nm = node.childForFieldName("name")?.text;
|
|
2538
|
+
if (v && nm && (v.type === "arrow_function" || v.type === "function_expression")) nextMethod = nm;
|
|
2539
|
+
} else if (node.type === "arrow_function" || node.type === "function_expression") {
|
|
2540
|
+
const parent = node.parent;
|
|
2541
|
+
if (parent?.type === "assignment_expression") {
|
|
2542
|
+
const left = parent.childForFieldName("left");
|
|
2543
|
+
const obj = left?.childForFieldName("object");
|
|
2544
|
+
const prop = left?.childForFieldName("property");
|
|
2545
|
+
if (obj?.type === "member_expression" && obj.childForFieldName("property")?.text === "prototype" && prop) {
|
|
2546
|
+
nextMethod = prop.text;
|
|
2547
|
+
nextOwner = obj.childForFieldName("object")?.text ?? owner;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
if (node.type === "member_expression") {
|
|
2552
|
+
const isCallCallee = node.parent?.type === "call_expression" && node.parent.childForFieldName("function")?.id === node.id;
|
|
2553
|
+
if (isCallCallee) {
|
|
2554
|
+
let root = node.childForFieldName("object");
|
|
2555
|
+
while (root && root.type === "member_expression") root = root.childForFieldName("object");
|
|
2556
|
+
const head = root && root.type === "identifier" ? root.text : "";
|
|
2557
|
+
if (head && head !== "this" && head !== "super") {
|
|
2558
|
+
const recvSource = method || owner || moduleName;
|
|
2559
|
+
const recvScopeId = this.localBindingScopeId(root, head);
|
|
2560
|
+
const recvBaseKey = `${recvSource}.#${head}`;
|
|
2561
|
+
const recvKey = recvScopeId ? `${recvBaseKey}|${recvScopeId}` : recvBaseKey;
|
|
2562
|
+
if (!seen.has(recvKey)) {
|
|
2563
|
+
seen.add(recvKey);
|
|
2564
|
+
seen.add(recvBaseKey);
|
|
2565
|
+
const boundRecv = this.localBindingTarget(root, head, recvSource);
|
|
2566
|
+
const importedRecv = boundRecv ? null : this.importedBindingFor(root, head);
|
|
2567
|
+
relationships.push({
|
|
2568
|
+
source: recvSource,
|
|
2569
|
+
target: boundRecv ?? head,
|
|
2570
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2571
|
+
confidence: 0.6,
|
|
2572
|
+
...this.lineField(root),
|
|
2573
|
+
metadata: {
|
|
2574
|
+
isFieldAccess: true,
|
|
2575
|
+
bareRead: true,
|
|
2576
|
+
callReceiver: true,
|
|
2577
|
+
...boundRecv ? { localBinding: true } : {},
|
|
2578
|
+
...recvScopeId ? { localBindingScope: recvScopeId } : {},
|
|
2579
|
+
...importedRecv ? { importedBinding: importedRecv } : {}
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
if (!isCallCallee) {
|
|
2586
|
+
const parts = [];
|
|
2587
|
+
let cur = node;
|
|
2588
|
+
while (cur && cur.type === "member_expression") {
|
|
2589
|
+
const prop = cur.childForFieldName("property");
|
|
2590
|
+
if (prop) parts.unshift({ seg: prop.text, node: prop });
|
|
2591
|
+
cur = cur.childForFieldName("object");
|
|
2592
|
+
}
|
|
2593
|
+
const head = cur ? cur.text : "";
|
|
2594
|
+
if (head && parts.length >= 1) {
|
|
2595
|
+
const pType = node.parent?.type;
|
|
2596
|
+
let isWrite = false;
|
|
2597
|
+
if (pType === "assignment_expression" || pType === "augmented_assignment_expression") {
|
|
2598
|
+
const lhs = node.parent.childForFieldName("left") ?? node.parent.namedChild(0);
|
|
2599
|
+
isWrite = !!lhs && lhs.startIndex === node.startIndex && lhs.endIndex === node.endIndex;
|
|
2600
|
+
}
|
|
2601
|
+
if (!isWrite) {
|
|
2602
|
+
if (head && head !== "this" && head !== "super" && cur?.type === "identifier") {
|
|
2603
|
+
const rootSource = method || owner || moduleName;
|
|
2604
|
+
const rootScopeId = this.localBindingScopeId(cur, head);
|
|
2605
|
+
const rootBaseKey = `${rootSource}.#${head}`;
|
|
2606
|
+
const rootKey = rootScopeId ? `${rootBaseKey}|${rootScopeId}` : rootBaseKey;
|
|
2607
|
+
if (!seen.has(rootKey)) {
|
|
2608
|
+
seen.add(rootKey);
|
|
2609
|
+
seen.add(rootBaseKey);
|
|
2610
|
+
const boundRoot = cur?.type === "identifier" ? this.localBindingTarget(cur, head, rootSource) : null;
|
|
2611
|
+
const importedRoot = boundRoot ? null : cur?.type === "identifier" ? this.importedBindingFor(cur, head) : null;
|
|
2612
|
+
relationships.push({
|
|
2613
|
+
source: rootSource,
|
|
2614
|
+
target: boundRoot ?? head,
|
|
2615
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2616
|
+
confidence: 0.6,
|
|
2617
|
+
// The root identifier's own line (the chain may open on a
|
|
2618
|
+
// line the segments do not share).
|
|
2619
|
+
...this.lineField(cur),
|
|
2620
|
+
metadata: {
|
|
2621
|
+
isFieldAccess: true,
|
|
2622
|
+
chainRoot: true,
|
|
2623
|
+
...boundRoot ? { localBinding: true } : {},
|
|
2624
|
+
...rootScopeId ? { localBindingScope: rootScopeId } : {},
|
|
2625
|
+
...importedRoot ? { importedBinding: importedRoot } : {}
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
for (const { seg, node: segNode } of parts) {
|
|
2631
|
+
const source = method || owner || moduleName;
|
|
2632
|
+
const key = seg.startsWith("#") ? `${source}.$private${seg}` : `${source}.${seg}`;
|
|
2633
|
+
if (!seen.has(key)) {
|
|
2634
|
+
seen.add(key);
|
|
2635
|
+
const recvOwner = (() => {
|
|
2636
|
+
if (cur?.type === "identifier" && head) {
|
|
2637
|
+
return receiverTypes.get(head) ?? this.staticClassReceiver(cur, head) ?? this.thisAliasOwnerAt(cur, head) ?? void 0;
|
|
2638
|
+
}
|
|
2639
|
+
if (cur?.type === "this") {
|
|
2640
|
+
const cls = this.enclosingThisClass(node);
|
|
2641
|
+
return cls ? { type: cls, evidence: "this" } : void 0;
|
|
2642
|
+
}
|
|
2643
|
+
return void 0;
|
|
2644
|
+
})();
|
|
2645
|
+
relationships.push({
|
|
2646
|
+
source,
|
|
2647
|
+
target: seg,
|
|
2648
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2649
|
+
confidence: 0.6,
|
|
2650
|
+
...this.lineField(segNode),
|
|
2651
|
+
// `receiverRoot` is the chain's FIRST identifier when it IS an identifier
|
|
2652
|
+
// (`location.hash.substring` -> `location`), and empty for a call-rooted chain
|
|
2653
|
+
// (`$(el).removeClass` -> ""). It is published so the terminal claim pass can ask
|
|
2654
|
+
// memberChainClaim the question the rule is defined on — the RECEIVER ROOT — without
|
|
2655
|
+
// re-deriving the chain from text. Additive; consumers that do not read it are unaffected.
|
|
2656
|
+
metadata: {
|
|
2657
|
+
isFieldAccess: true,
|
|
2658
|
+
...cur?.type === "identifier" && head ? { receiverRoot: head } : {},
|
|
2659
|
+
...recvOwner ? { owner: recvOwner.type, ownerEvidence: recvOwner.evidence } : {}
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
if (node.type === "identifier") {
|
|
2669
|
+
const txt = node.text;
|
|
2670
|
+
if (txt !== "this" && txt !== "super" && txt !== "undefined" && txt !== "arguments") {
|
|
2671
|
+
const p7 = node.parent;
|
|
2672
|
+
const calleeFn = p7?.type === "call_expression" ? p7.childForFieldName("function") : null;
|
|
2673
|
+
const isCallee = !!calleeFn && calleeFn.id === node.id;
|
|
2674
|
+
let isWrite2 = false;
|
|
2675
|
+
if (p7?.type === "assignment_expression" || p7?.type === "augmented_assignment_expression") {
|
|
2676
|
+
const lhs2 = p7.childForFieldName("left") ?? p7.namedChild(0);
|
|
2677
|
+
isWrite2 = !!lhs2 && lhs2.id === node.id;
|
|
2678
|
+
}
|
|
2679
|
+
const isDeclName = p7?.type === "variable_declarator" && p7.childForFieldName("name")?.id === node.id || p7?.type === "function_declaration" || p7?.type === "function_expression" || p7?.type === "generator_function_declaration" || p7?.type === "generator_function" || p7?.type === "method_definition" || p7?.type === "class_declaration" || p7?.type === "class";
|
|
2680
|
+
const isPropKey = (p7?.type === "pair" || p7?.type === "pair_pattern") && p7.childForFieldName("key")?.id === node.id;
|
|
2681
|
+
const isMemberPart = p7?.type === "member_expression";
|
|
2682
|
+
const isPropName = p7?.type === "property_identifier";
|
|
2683
|
+
const isJsxName = p7?.type.startsWith("jsx_") ?? false;
|
|
2684
|
+
const isLabel = p7?.type === "labeled_statement";
|
|
2685
|
+
const isShorthandProp = p7?.type === "shorthand_property_identifier";
|
|
2686
|
+
const isImportBinding = p7?.type === "import_clause" || p7?.type === "import_specifier" || p7?.type === "namespace_import";
|
|
2687
|
+
const isParamBinding = p7?.type === "formal_parameters" || p7?.type === "arrow_function" || p7?.type === "catch_clause";
|
|
2688
|
+
const isPatternBinding = p7?.type === "rest_pattern" || p7?.type === "array_pattern" || p7?.type === "pair_pattern" || // `a = 1` in a pattern: the LEFT is the binding, the RIGHT is a
|
|
2689
|
+
// genuine read (`function f(a = b)` must still read b).
|
|
2690
|
+
p7?.type === "assignment_pattern" && (p7.childForFieldName("left") ?? p7.namedChild(0))?.id === node.id;
|
|
2691
|
+
const isExportSpecifier = p7?.type === "export_specifier";
|
|
2692
|
+
const isBindingSite = isImportBinding || isParamBinding || isPatternBinding || isExportSpecifier;
|
|
2693
|
+
if (!isCallee && !isWrite2 && !isDeclName && !isPropKey && !isMemberPart && !isPropName && !isLabel && !isShorthandProp && !isJsxName && !isBindingSite) {
|
|
2694
|
+
const bSource = method || owner || moduleName;
|
|
2695
|
+
const bScopeId = this.localBindingScopeId(node, txt);
|
|
2696
|
+
const bBaseKey = `${bSource}.#${txt}`;
|
|
2697
|
+
const bKey = bScopeId ? `${bBaseKey}|${bScopeId}` : bBaseKey;
|
|
2698
|
+
if (!seen.has(bKey)) {
|
|
2699
|
+
seen.add(bKey);
|
|
2700
|
+
seen.add(bBaseKey);
|
|
2701
|
+
const boundRead = this.localBindingTarget(node, txt, bSource);
|
|
2702
|
+
const importedRead = boundRead ? null : this.importedBindingFor(node, txt);
|
|
2703
|
+
relationships.push({
|
|
2704
|
+
source: bSource,
|
|
2705
|
+
target: boundRead ?? txt,
|
|
2706
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2707
|
+
confidence: 0.6,
|
|
2708
|
+
...this.lineField(node),
|
|
2709
|
+
metadata: {
|
|
2710
|
+
isFieldAccess: true,
|
|
2711
|
+
bareRead: true,
|
|
2712
|
+
...boundRead ? { localBinding: true } : {},
|
|
2713
|
+
...bScopeId ? { localBindingScope: bScopeId } : {},
|
|
2714
|
+
...importedRead ? { importedBinding: importedRead } : {}
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
if (node.type === "template_substitution") {
|
|
2722
|
+
const inCalleeOrWritePosition = (n) => {
|
|
2723
|
+
let cur = n;
|
|
2724
|
+
while (cur) {
|
|
2725
|
+
const p = cur.parent;
|
|
2726
|
+
if (!p || p.type === "template_substitution") break;
|
|
2727
|
+
if (p.type === "call_expression") {
|
|
2728
|
+
const fn = p.childForFieldName("function");
|
|
2729
|
+
if (fn && fn.startIndex <= n.startIndex && n.endIndex <= fn.endIndex) return true;
|
|
2730
|
+
}
|
|
2731
|
+
if (p.type === "assignment_expression" || p.type === "augmented_assignment_expression") {
|
|
2732
|
+
const left = p.childForFieldName("left") ?? p.namedChild(0);
|
|
2733
|
+
if (left && left.startIndex <= n.startIndex && n.endIndex <= left.endIndex) return true;
|
|
2734
|
+
}
|
|
2735
|
+
cur = p;
|
|
2736
|
+
}
|
|
2737
|
+
return false;
|
|
2738
|
+
};
|
|
2739
|
+
const sub = (n) => {
|
|
2740
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2741
|
+
const c = n.namedChild(i);
|
|
2742
|
+
if (!c) continue;
|
|
2743
|
+
if (c.type === "identifier") {
|
|
2744
|
+
if (inCalleeOrWritePosition(c)) continue;
|
|
2745
|
+
const src2 = method || owner || moduleName;
|
|
2746
|
+
const key = `${src2}.${c.text}`;
|
|
2747
|
+
if (!seen.has(key)) {
|
|
2748
|
+
seen.add(key);
|
|
2749
|
+
seen.add(`${src2}.#${c.text}`);
|
|
2750
|
+
const boundTmpl = this.localBindingTarget(c, c.text, src2);
|
|
2751
|
+
const importedTmpl = boundTmpl ? null : this.importedBindingFor(c, c.text);
|
|
2752
|
+
relationships.push({
|
|
2753
|
+
source: src2,
|
|
2754
|
+
target: boundTmpl ?? c.text,
|
|
2755
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2756
|
+
confidence: 0.85,
|
|
2757
|
+
...this.lineField(c),
|
|
2758
|
+
metadata: {
|
|
2759
|
+
isTemplateInterpolation: true,
|
|
2760
|
+
...boundTmpl ? { localBinding: true } : {},
|
|
2761
|
+
...importedTmpl ? { importedBinding: importedTmpl } : {}
|
|
2762
|
+
}
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
} else if (c.type !== "identifier") {
|
|
2766
|
+
sub(c);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
sub(node);
|
|
2771
|
+
}
|
|
2772
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
2773
|
+
const child = node.namedChild(i);
|
|
2774
|
+
if (child) stack.push({ node: child, owner: nextOwner, method: nextMethod, module: moduleName });
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
/**
|
|
2779
|
+
* BUILD THE POSITIONAL LEXICAL SCOPE MODEL — one walk of the tree, ranges in node offsets.
|
|
2780
|
+
*
|
|
2781
|
+
* THE RULES ARE THE LANGUAGE'S, not an approximation:
|
|
2782
|
+
* · scopes: program, every function form (declaration / expression / generator / arrow /
|
|
2783
|
+
* method / constructor / static block), blocks (`statement_block`, `switch_body`), loop
|
|
2784
|
+
* headers (`for`/`for_in`/`for_of`), catch clauses;
|
|
2785
|
+
* · `var` HOISTS to the nearest function-or-program scope; `let`/`const` bind in the current
|
|
2786
|
+
* block; parameters bind in the function scope; a catch parameter binds in its catch scope;
|
|
2787
|
+
* a function or class DECLARATION binds in its enclosing scope;
|
|
2788
|
+
* · a later same-scope redeclaration keeps one name (a Set), and an inner binding shadows an
|
|
2789
|
+
* outer one because resolution walks the scope chain from the innermost containing scope;
|
|
2790
|
+
* · import bindings and `require(...)` / `import(...)` declarators are recorded in
|
|
2791
|
+
* `moduleBindings` and are NEVER local-binding claims — they are the imported-binding seam;
|
|
2792
|
+
* · destructured/rest/default parameter and declarator patterns bind their VALUE sides
|
|
2793
|
+
* (`collectPatternPairs`, the same reader the symbol walk uses).
|
|
2794
|
+
*/
|
|
2795
|
+
collectBindingModel(rootNode) {
|
|
2796
|
+
const scopes = [];
|
|
2797
|
+
const allNames = /* @__PURE__ */ new Set();
|
|
2798
|
+
const moduleBindings = /* @__PURE__ */ new Set();
|
|
2799
|
+
const moduleBindingSpecs = /* @__PURE__ */ new Map();
|
|
2800
|
+
const publishedClasses = /* @__PURE__ */ new Set();
|
|
2801
|
+
const program = {
|
|
2802
|
+
kind: "program",
|
|
2803
|
+
startIndex: rootNode.startIndex,
|
|
2804
|
+
endIndex: rootNode.endIndex,
|
|
2805
|
+
published: true,
|
|
2806
|
+
parent: null,
|
|
2807
|
+
names: /* @__PURE__ */ new Set(),
|
|
2808
|
+
decls: /* @__PURE__ */ new Map()
|
|
2809
|
+
};
|
|
2810
|
+
scopes.push(program);
|
|
2811
|
+
const addName = (scope, name, declNode) => {
|
|
2812
|
+
if (!name) return;
|
|
2813
|
+
scope.names.add(name);
|
|
2814
|
+
if (!scope.decls.has(name)) scope.decls.set(name, declNode?.startIndex ?? scope.startIndex);
|
|
2815
|
+
allNames.add(name);
|
|
2816
|
+
};
|
|
2817
|
+
const newScope = (node, kind, parent, published) => {
|
|
2818
|
+
const scope = { kind, startIndex: node.startIndex, endIndex: node.endIndex, published, parent, names: /* @__PURE__ */ new Set(), decls: /* @__PURE__ */ new Map() };
|
|
2819
|
+
scopes.push(scope);
|
|
2820
|
+
return scope;
|
|
2821
|
+
};
|
|
2822
|
+
const hoistTarget = (scope) => {
|
|
2823
|
+
let cur = scope;
|
|
2824
|
+
while (cur.kind !== "function" && cur.kind !== "program" && cur.parent) cur = cur.parent;
|
|
2825
|
+
return cur;
|
|
2826
|
+
};
|
|
2827
|
+
const addParams = (fnNode, fnScope) => {
|
|
2828
|
+
const paramsNode = fnNode.childForFieldName("parameters");
|
|
2829
|
+
if (paramsNode) {
|
|
2830
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
2831
|
+
const param = paramsNode.namedChild(i);
|
|
2832
|
+
if (!param) continue;
|
|
2833
|
+
for (const pair of this.collectPatternPairs(param)) addName(fnScope, pair.name, pair.node);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
const single = fnNode.childForFieldName("parameter");
|
|
2837
|
+
if (single) for (const pair of this.collectPatternPairs(single)) addName(fnScope, pair.name, pair.node);
|
|
2838
|
+
};
|
|
2839
|
+
const addImportBindings = (importNode) => {
|
|
2840
|
+
const clause = this.findChildByType(importNode, "import_clause");
|
|
2841
|
+
if (!clause) return;
|
|
2842
|
+
const sourceNode = importNode.childForFieldName("source");
|
|
2843
|
+
const specifier = sourceNode ? stringContent(sourceNode) : "";
|
|
2844
|
+
const add = (name, declNode) => {
|
|
2845
|
+
if (!name) return;
|
|
2846
|
+
addName(program, name, declNode);
|
|
2847
|
+
moduleBindings.add(name);
|
|
2848
|
+
if (specifier) moduleBindingSpecs.set(name, specifier);
|
|
2849
|
+
};
|
|
2850
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
2851
|
+
const c = clause.namedChild(i);
|
|
2852
|
+
if (!c) continue;
|
|
2853
|
+
if (c.type === "identifier") add(c.text, c);
|
|
2854
|
+
else if (c.type === "namespace_import") {
|
|
2855
|
+
const nm = c.childForFieldName("name") ?? c.namedChild(0);
|
|
2856
|
+
if (nm) add(nm.text, nm);
|
|
2857
|
+
} else if (c.type === "named_imports") {
|
|
2858
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
2859
|
+
const spec = c.namedChild(j);
|
|
2860
|
+
if (!spec || spec.type !== "import_specifier") continue;
|
|
2861
|
+
const alias = spec.childForFieldName("alias");
|
|
2862
|
+
const nm = spec.childForFieldName("name") ?? spec.namedChild(0);
|
|
2863
|
+
const local = alias ?? nm;
|
|
2864
|
+
if (local) add(local.text, local);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
const isModuleLoad = (value) => {
|
|
2870
|
+
if (!value || value.type !== "call_expression") return false;
|
|
2871
|
+
const fn = value.childForFieldName("function");
|
|
2872
|
+
if (!fn) return false;
|
|
2873
|
+
return fn.type === "import" || fn.type === "identifier" && (fn.text === "require" || fn.text === "import");
|
|
2874
|
+
};
|
|
2875
|
+
const moduleLoadSpecifier = (value) => {
|
|
2876
|
+
if (!value || value.type !== "call_expression") return "";
|
|
2877
|
+
const args = value.childForFieldName("arguments");
|
|
2878
|
+
const arg = args?.namedChild(0);
|
|
2879
|
+
if (!arg) return "";
|
|
2880
|
+
if (arg.type !== "string" && arg.type !== "template_string") return "";
|
|
2881
|
+
return stringContent(arg);
|
|
2882
|
+
};
|
|
2883
|
+
const walk = (n, scope) => {
|
|
2884
|
+
const t = n.type;
|
|
2885
|
+
if (t === "function_declaration" || t === "generator_function_declaration") {
|
|
2886
|
+
const nm = n.childForFieldName("name");
|
|
2887
|
+
if (nm) addName(scope, nm.text, nm);
|
|
2888
|
+
const fnScope = newScope(n, "function", scope, false);
|
|
2889
|
+
addParams(n, fnScope);
|
|
2890
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2891
|
+
const c = n.namedChild(i);
|
|
2892
|
+
if (c) walk(c, fnScope);
|
|
2893
|
+
}
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
if (t === "function_expression" || t === "generator_function") {
|
|
2897
|
+
const fnScope = newScope(n, "function", scope, false);
|
|
2898
|
+
const nm = n.childForFieldName("name");
|
|
2899
|
+
if (nm) addName(fnScope, nm.text, nm);
|
|
2900
|
+
addParams(n, fnScope);
|
|
2901
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2902
|
+
const c = n.namedChild(i);
|
|
2903
|
+
if (c) walk(c, fnScope);
|
|
2904
|
+
}
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
if (t === "arrow_function" || t === "method_definition" || t === "class_static_block") {
|
|
2908
|
+
const fnScope = newScope(n, "function", scope, false);
|
|
2909
|
+
if (t !== "class_static_block") addParams(n, fnScope);
|
|
2910
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2911
|
+
const c = n.namedChild(i);
|
|
2912
|
+
if (c) walk(c, fnScope);
|
|
2913
|
+
}
|
|
2914
|
+
return;
|
|
2915
|
+
}
|
|
2916
|
+
if (t === "import_statement") {
|
|
2917
|
+
addImportBindings(n);
|
|
2918
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2919
|
+
const c = n.namedChild(i);
|
|
2920
|
+
if (c) walk(c, scope);
|
|
2921
|
+
}
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
if (t === "variable_declarator") {
|
|
2925
|
+
const nameNode = n.childForFieldName("name");
|
|
2926
|
+
const value = n.childForFieldName("value");
|
|
2927
|
+
const parent = n.parent;
|
|
2928
|
+
const kindNode = parent?.childForFieldName?.("kind");
|
|
2929
|
+
const isVar = parent?.type === "variable_declaration" || kindNode !== void 0 && kindNode !== null && kindNode.text === "var";
|
|
2930
|
+
const target = isVar ? hoistTarget(scope) : scope;
|
|
2931
|
+
const moduleBound = isModuleLoad(value);
|
|
2932
|
+
const moduleSpec = moduleBound ? moduleLoadSpecifier(value) : "";
|
|
2933
|
+
if (nameNode) {
|
|
2934
|
+
for (const pair of this.collectPatternPairs(nameNode)) {
|
|
2935
|
+
addName(target, pair.name, pair.node);
|
|
2936
|
+
if (moduleBound) {
|
|
2937
|
+
moduleBindings.add(pair.name);
|
|
2938
|
+
if (moduleSpec) moduleBindingSpecs.set(pair.name, moduleSpec);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2943
|
+
const c = n.namedChild(i);
|
|
2944
|
+
if (c) walk(c, scope);
|
|
2945
|
+
}
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
if (t === "catch_clause") {
|
|
2949
|
+
const catchScope = newScope(n, "catch", scope, false);
|
|
2950
|
+
const param = n.childForFieldName("parameter");
|
|
2951
|
+
if (param) for (const pair of this.collectPatternPairs(param)) addName(catchScope, pair.name, pair.node);
|
|
2952
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2953
|
+
const c = n.namedChild(i);
|
|
2954
|
+
if (c) walk(c, catchScope);
|
|
2955
|
+
}
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
if (t === "class_declaration") {
|
|
2959
|
+
const nm = n.childForFieldName("name");
|
|
2960
|
+
if (nm) {
|
|
2961
|
+
addName(scope, nm.text, nm);
|
|
2962
|
+
if (scope.published) publishedClasses.add(nm.text);
|
|
2963
|
+
}
|
|
2964
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2965
|
+
const c = n.namedChild(i);
|
|
2966
|
+
if (c) walk(c, scope);
|
|
2967
|
+
}
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
if (t === "statement_block" || t === "switch_body") {
|
|
2971
|
+
const blockScope = newScope(n, "block", scope, false);
|
|
2972
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2973
|
+
const c = n.namedChild(i);
|
|
2974
|
+
if (c) walk(c, blockScope);
|
|
2975
|
+
}
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
if (t === "for_statement" || t === "for_in_statement") {
|
|
2979
|
+
const loopScope = newScope(n, "block", scope, false);
|
|
2980
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2981
|
+
const c = n.namedChild(i);
|
|
2982
|
+
if (c) walk(c, loopScope);
|
|
2983
|
+
}
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2987
|
+
const c = n.namedChild(i);
|
|
2988
|
+
if (c) walk(c, scope);
|
|
2989
|
+
}
|
|
2990
|
+
};
|
|
2991
|
+
walk(rootNode, program);
|
|
2992
|
+
const declScopeCounts = /* @__PURE__ */ new Map();
|
|
2993
|
+
for (const s of scopes) {
|
|
2994
|
+
for (const n of s.names) declScopeCounts.set(n, (declScopeCounts.get(n) ?? 0) + 1);
|
|
2995
|
+
}
|
|
2996
|
+
const multiScopeNames = /* @__PURE__ */ new Set();
|
|
2997
|
+
for (const [n, count] of declScopeCounts) if (count > 1) multiScopeNames.add(n);
|
|
2998
|
+
return { scopes, allNames, moduleBindings, moduleBindingSpecs, publishedClasses, multiScopeNames };
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* THE LOCAL-BINDING SPELLING for the site, or `null` when the name at this node is not a
|
|
3002
|
+
* claimable local binding. Returns `${source}${BINDING_SEP}${name}` exactly as the family
|
|
3003
|
+
* contract specifies (php/dart/java/fsharp/haskell): the arbiter verifies the SPELLING (a
|
|
3004
|
+
* declaration name can never contain the separator), the row lands in the gate's `bindOK`
|
|
3005
|
+
* column, and it is never merged into `resolved`.
|
|
3006
|
+
*
|
|
3007
|
+
* REFUSALS, each one a false statement the claim must not make:
|
|
3008
|
+
* · the file loads the name from a module (`import` / `require` / dynamic `import()`) — that is
|
|
3009
|
+
* the imported-binding seam class, not a local binding;
|
|
3010
|
+
* · the name resolves to the PROGRAM scope — module-scope declarations are PUBLISHED SYMBOLS,
|
|
3011
|
+
* so the edge can resolve normally and a binding claim would deny a real declaration;
|
|
3012
|
+
* · no lexical binding answers the name at this node's position (the read is of something the
|
|
3013
|
+
* file does not bind — host global, import through another route, or a genuine gap);
|
|
3014
|
+
* · the name would be the empty string or is one of the keywords the language never binds.
|
|
3015
|
+
*/
|
|
3016
|
+
localBindingTarget(node, name, source) {
|
|
3017
|
+
const model = this.bindingModel;
|
|
3018
|
+
if (!model || !node || !name || !source) return null;
|
|
3019
|
+
if (!this.isLocalBindingName(node, name)) return null;
|
|
3020
|
+
return `${source}${BINDING_SEP}${name}`;
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* STATIC CLASS RECEIVER — `Service.create()` where the receiver identifier is a PROGRAM-scope
|
|
3024
|
+
* class DECLARATION in this file. That class's members ARE published symbols, so the owner channel
|
|
3025
|
+
* can resolve the member inside it; the proof is positional (the identifier must still resolve to
|
|
3026
|
+
* the program scope at THIS site — a parameter or local shadowing the class name is not the class,
|
|
3027
|
+
* and gets nothing). Evidence class `class-static`.
|
|
3028
|
+
*/
|
|
3029
|
+
staticClassReceiver(node, name) {
|
|
3030
|
+
const model = this.bindingModel;
|
|
3031
|
+
if (!model || !node || !name) return null;
|
|
3032
|
+
if (!model.publishedClasses.has(name)) return null;
|
|
3033
|
+
return this.resolveBindingKind(model, node.startIndex, name) === "published" ? { type: name, evidence: "class-static" } : null;
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* THIS-ALIAS RECEIVER — `const self = this` in the SAME function body as the site, with `this`
|
|
3037
|
+
* provably the enclosing class DECLARATION's instance. Resolved POSITIONALLY, never through the
|
|
3038
|
+
* file map: a file map leaked an alias across sibling functions in the fixture (a plain
|
|
3039
|
+
* function's `const self = this` is dynamic `this`, yet it inherited the class recorded by
|
|
3040
|
+
* another function's alias of the same name) — the leak produced a wrong owner, which is worse
|
|
3041
|
+
* than none. Refusals: a plain function/generator is dynamic `this` (nothing); an arrow is
|
|
3042
|
+
* lexical `this` but the alias would have to be declared in an enclosing body, so this helper
|
|
3043
|
+
* under-claims rather than reach across (nothing); a class expression is not a published symbol.
|
|
3044
|
+
*/
|
|
3045
|
+
thisAliasOwnerAt(node, name) {
|
|
3046
|
+
if (!node || !name) return null;
|
|
3047
|
+
const isFunctionForm = (t) => t === "function_declaration" || t === "function_expression" || t === "generator_function_declaration" || t === "generator_function" || t === "arrow_function" || t === "method_definition" || t === "class_static_block";
|
|
3048
|
+
let fn = null;
|
|
3049
|
+
for (let p = node; p; p = p.parent) {
|
|
3050
|
+
if (isFunctionForm(p.type)) {
|
|
3051
|
+
fn = p;
|
|
3052
|
+
break;
|
|
3053
|
+
}
|
|
3054
|
+
if (p.type === "class_declaration" || p.type === "class") return null;
|
|
3055
|
+
}
|
|
3056
|
+
if (!fn) return null;
|
|
3057
|
+
if (fn.type !== "method_definition" && fn.type !== "class_static_block") return null;
|
|
3058
|
+
let cls = null;
|
|
3059
|
+
for (let p = fn.parent; p; p = p.parent) {
|
|
3060
|
+
if (p.type === "class_declaration") {
|
|
3061
|
+
cls = p.childForFieldName("name")?.text ?? null;
|
|
3062
|
+
break;
|
|
3063
|
+
}
|
|
3064
|
+
if (p.type === "class") return null;
|
|
3065
|
+
if (p.type === "function_declaration" || p.type === "function_expression" || p.type === "generator_function_declaration" || p.type === "generator_function") return null;
|
|
3066
|
+
}
|
|
3067
|
+
if (!cls) return null;
|
|
3068
|
+
const stack = [fn];
|
|
3069
|
+
while (stack.length > 0) {
|
|
3070
|
+
const n = stack.pop();
|
|
3071
|
+
if (n !== fn && isFunctionForm(n.type)) continue;
|
|
3072
|
+
if (n.type === "variable_declarator") {
|
|
3073
|
+
const id = n.childForFieldName("name");
|
|
3074
|
+
const value = n.childForFieldName("value");
|
|
3075
|
+
if (id && id.type === "identifier" && id.text === name && value && value.type === "this") {
|
|
3076
|
+
return { type: cls, evidence: "this-alias" };
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
3080
|
+
const c = n.namedChild(i);
|
|
3081
|
+
if (c) stack.push(c);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
return null;
|
|
3085
|
+
}
|
|
3086
|
+
/** innermost scope containing `index`, then the scope chain — "published" | "lexical" | null. */
|
|
3087
|
+
resolveBindingKind(model, index, name) {
|
|
3088
|
+
const scope = this.resolveBindingScope(model, index, name);
|
|
3089
|
+
if (!scope) return null;
|
|
3090
|
+
return scope.published ? "published" : "lexical";
|
|
3091
|
+
}
|
|
3092
|
+
/**
|
|
3093
|
+
* R2 (charter §21.41) — THE SCOPE THAT DECLARES `name` AT `index`: the innermost scope that
|
|
3094
|
+
* contains the position, then the scope chain, exactly the resolution `resolveBindingKind` uses
|
|
3095
|
+
* — but it returns the SCOPE, not the kind, because the scope is what carries the declaration's
|
|
3096
|
+
* identity.
|
|
3097
|
+
*/
|
|
3098
|
+
resolveBindingScope(model, index, name) {
|
|
3099
|
+
let best = null;
|
|
3100
|
+
for (const scope of model.scopes) {
|
|
3101
|
+
if (index < scope.startIndex || index > scope.endIndex) continue;
|
|
3102
|
+
if (best === null || scope.endIndex - scope.startIndex < best.endIndex - best.startIndex) best = scope;
|
|
3103
|
+
}
|
|
3104
|
+
let cur = best;
|
|
3105
|
+
while (cur) {
|
|
3106
|
+
if (cur.names.has(name)) return cur;
|
|
3107
|
+
cur = cur.parent;
|
|
3108
|
+
}
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* R2 (charter §21.41) — THE DECLARATION'S SCOPE IDENTITY for a read of `name` at `node`, or null.
|
|
3113
|
+
*
|
|
3114
|
+
* The identity is the innermost scope that DECLARES the name at this position, plus that
|
|
3115
|
+
* declaration's own offset — the dart lane's shape. Two reads of ONE declaration share it; two
|
|
3116
|
+
* reads of TWO declarations do not. It joins the DEDUP KEY only, and only for a row whose
|
|
3117
|
+
* identity is the occurrence (a local-binding row), so the payload-pollution dedup stays byte for
|
|
3118
|
+
* byte for every other row.
|
|
3119
|
+
*
|
|
3120
|
+
* `null` in three cases, and every one of them keeps the old key:
|
|
3121
|
+
* · the name is declared in only ONE scope in the file — the base key already separates it;
|
|
3122
|
+
* · the read resolves to the PROGRAM scope — a published symbol, not a local-binding row, and
|
|
3123
|
+
* a claim there would deny a real declaration;
|
|
3124
|
+
* · the name is not bound at this site at all.
|
|
3125
|
+
*/
|
|
3126
|
+
localBindingScopeId(node, name) {
|
|
3127
|
+
const model = this.bindingModel;
|
|
3128
|
+
if (!model || !node || !name) return null;
|
|
3129
|
+
if (!model.multiScopeNames.has(name)) return null;
|
|
3130
|
+
if (!this.isLocalBindingName(node, name)) return null;
|
|
3131
|
+
const scope = this.resolveBindingScope(model, node.startIndex, name);
|
|
3132
|
+
if (!scope || scope.published) return null;
|
|
3133
|
+
return `${scope.startIndex}:${scope.decls.get(name) ?? scope.startIndex}`;
|
|
3134
|
+
}
|
|
3135
|
+
/**
|
|
3136
|
+
* IS THIS READ A LOCAL-BINDING ROW? — the ONE test both the TARGET's spelling and the DEDUP KEY's
|
|
3137
|
+
* scope identity must agree on.
|
|
3138
|
+
*
|
|
3139
|
+
* The refusals, each a false statement the row must not make:
|
|
3140
|
+
* · `this` / `super` / `arguments` / `undefined` are not names a declaration can bind;
|
|
3141
|
+
* · a name the file binds FROM A MODULE is the imported-binding class, never a local binding
|
|
3142
|
+
* (its claim is evidence-verified and its target stays bare);
|
|
3143
|
+
* · a name that resolves to the PROGRAM scope is a published symbol, not a local binding — a
|
|
3144
|
+
* claim there would deny a real declaration.
|
|
3145
|
+
*/
|
|
3146
|
+
isLocalBindingName(node, name) {
|
|
3147
|
+
const model = this.bindingModel;
|
|
3148
|
+
if (!model || !node || !name) return false;
|
|
3149
|
+
if (name === "this" || name === "super" || name === "arguments" || name === "undefined") return false;
|
|
3150
|
+
if (model.moduleBindings.has(name)) return false;
|
|
3151
|
+
return this.resolveBindingKind(model, node.startIndex, name) === "lexical";
|
|
3152
|
+
}
|
|
3153
|
+
/**
|
|
3154
|
+
* IMPORTED-BINDING CLAIM — the EVIDENCE-VERIFIED claim kind (Lead contract, 2026-10-18). Returns
|
|
3155
|
+
* `{ specifier, binding }` when the name IS the imported binding at THIS site, so the caller can
|
|
3156
|
+
* publish `metadata.importedBinding` and keep the target spelling BARE (no BINDING_SEP — the
|
|
3157
|
+
* arbiter verifies this claim against the run's own import rows, not a spelling).
|
|
3158
|
+
*
|
|
3159
|
+
* REFUSALS, each a false statement the claim must not make:
|
|
3160
|
+
* · the name has no import evidence in this file (no literal specifier: a name the PROJECT
|
|
3161
|
+
* declares, a name with no import at all, or a DYNAMIC `require(expr)` — never-claim (a)/(c));
|
|
3162
|
+
* · a LOCAL binding shadows the import at this site (the positional scope check): the target is
|
|
3163
|
+
* the shadow, not the import;
|
|
3164
|
+
* · the site is a namespace MEMBER access — the caller only asks this for a target that IS the
|
|
3165
|
+
* binding (bare callee / bare read / chain root), never a member segment (never-claim (b));
|
|
3166
|
+
* · re-export rows never reach here (never-claim (d): different surface).
|
|
3167
|
+
*/
|
|
3168
|
+
importedBindingFor(node, name) {
|
|
3169
|
+
const model = this.bindingModel;
|
|
3170
|
+
if (!model || !node || !name) return null;
|
|
3171
|
+
const specifier = model.moduleBindingSpecs.get(name);
|
|
3172
|
+
if (specifier === void 0) return null;
|
|
3173
|
+
if (this.resolveBindingKind(model, node.startIndex, name) !== "published") return null;
|
|
3174
|
+
return { specifier, binding: name };
|
|
3175
|
+
}
|
|
3176
|
+
/**
|
|
3177
|
+
* JS EXTERNAL CLAIMS — mark the edges whose target is a platform name the FILE cannot declare,
|
|
3178
|
+
* using the CLOSED CITED vocabulary in `js-builtin-prototypes.ts` and nothing else.
|
|
3179
|
+
*
|
|
3180
|
+
* WHY THIS PASS EXISTS. The lane's unresolved `calls`/`uses` mass is dominated by names no
|
|
3181
|
+
* repository can declare: `document.getElementById`, `window.addEventListener`,
|
|
3182
|
+
* `location.hash.substring`, `JSON.stringify`, `setTimeout`. Before this pass the analyzer
|
|
3183
|
+
* published those rows with NO claim at all, so they read exactly like a name the project
|
|
3184
|
+
* failed to bind. The claim fixes the READ, not the binding — it never picks a declaration,
|
|
3185
|
+
* so it cannot raise `arbitraryTwin`, `bindFAIL` or `extFAIL` for a DOTLESS target (the
|
|
3186
|
+
* arbiter looks a dotless target up by its own spelling and can only reach a refutation on a
|
|
3187
|
+
* target that names a declaration — which is why this pass NEVER rewrites a target, and why
|
|
3188
|
+
* it refuses every non-identifier target; a dotted target is looked up by its OWNER, which a
|
|
3189
|
+
* project may well publish).
|
|
3190
|
+
*
|
|
3191
|
+
* THE TWO PROOFS, both readable from the syntax with NO type inference:
|
|
3192
|
+
*
|
|
3193
|
+
* bare name is a cited host global `setTimeout(...)` -> claim, cites ECMA-262 §19.2
|
|
3194
|
+
* receiver ROOT is a cited host global `document.getElementById` -> claim, cites WHATWG HTML §7.3
|
|
3195
|
+
* receiver root is a LOCAL `x.substring(...)` -> NO CLAIM (x may be a project object)
|
|
3196
|
+
* name/receiver root the FILE DECLARES `class Map {}; Map.x()` -> NO CLAIM (the declaration outranks the table)
|
|
3197
|
+
*
|
|
3198
|
+
* The vocabulary is `HOST_GLOBALS` (57 cited entries) + `memberChainClaim` in
|
|
3199
|
+
* `js-builtin-prototypes.ts` — the same module the differential's oracle imports, so the two
|
|
3200
|
+
* sides cannot drift into two copies of one rule. A name NOT in that list gets NO claim: a
|
|
3201
|
+
* heuristic ("looks like a DOM name") would be the closed-list lie this fleet keeps catching.
|
|
3202
|
+
*
|
|
3203
|
+
* THE IMPORT CLASS is claimed too, and it is a DIFFERENT claim kind on purpose: an `imports`
|
|
3204
|
+
* row spells `<module>:<item>`, so the claim is about the MODULE (a namespace, not a
|
|
3205
|
+
* declaration). The arbiter's `moduleNamespace` claim verifies the module ROOT against the
|
|
3206
|
+
* project's own file/directory names and FAILS CLOSED when the root is project-owned, so a
|
|
3207
|
+
* workspace package that names a real project directory cannot be laundered. A relative or
|
|
3208
|
+
* absolute specifier is NEVER claimed: it names a FILE in this project, and a file claim that
|
|
3209
|
+
* misses is REFUTED against the lane — this per-file analyzer is not given the project's file
|
|
3210
|
+
* list, so those rows stay unclaimed rather than risk a false external statement.
|
|
3211
|
+
*
|
|
3212
|
+
* WHAT IT DELIBERATELY DOES NOT CLAIM, and the counts are the next measurement: Node runtime
|
|
3213
|
+
* globals (`process`, `Buffer`, `module`, `require`), test-runner globals (`describe`, `it`,
|
|
3214
|
+
* `test`, `expect`, `assert`) and imported bindings from external packages are OUTSIDE the
|
|
3215
|
+
* cited list. They stay unresolved until each gets its own cited vocabulary; they are named in
|
|
3216
|
+
* `validation/m14/verification/JAVASCRIPT-RESOLUTION-CLASSIFICATION.md` with their measured
|
|
3217
|
+
* counts rather than smuggled into this table.
|
|
3218
|
+
*
|
|
3219
|
+
* The claim's own words are published twice: `externalReason` (the machine bucket the arbiter
|
|
3220
|
+
* reads) and `externalClaimReason` (the full citation a human audits). `externalReceiver`
|
|
3221
|
+
* names the receiver root when the proof is the MEMBER rule, so a reader can re-run the rule
|
|
3222
|
+
* from the edge itself.
|
|
3223
|
+
*/
|
|
3224
|
+
markExternalClaims(rootNode, relationships, symbols, context) {
|
|
3225
|
+
const declaredHere = /* @__PURE__ */ new Set();
|
|
3226
|
+
for (const s of symbols) if (s.name) declaredHere.add(String(s.name));
|
|
3227
|
+
const bound = this.bindingModel ? this.bindingModel.allNames : this.collectBoundNames(rootNode);
|
|
3228
|
+
let marked = 0;
|
|
3229
|
+
for (const r of relationships) {
|
|
3230
|
+
const label = String(r.type ?? "");
|
|
3231
|
+
const target = String(r.target ?? "");
|
|
3232
|
+
if (target.length === 0) continue;
|
|
3233
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
3234
|
+
if (md.isExternalTarget === true) continue;
|
|
3235
|
+
if (label !== "imports" && !isPlainJsIdentifier(target)) continue;
|
|
3236
|
+
if (declaredHere.has(target)) continue;
|
|
3237
|
+
let reason = null;
|
|
3238
|
+
let citation = null;
|
|
3239
|
+
let receiver = null;
|
|
3240
|
+
if (label === "calls") {
|
|
3241
|
+
const callee = String(md.calleeExpression ?? "");
|
|
3242
|
+
if (callee.length > 0 && callee === target) {
|
|
3243
|
+
if (!bound.has(target)) {
|
|
3244
|
+
citation = hostGlobalCitation(target);
|
|
3245
|
+
if (citation !== null) reason = "host-global-bare";
|
|
3246
|
+
else {
|
|
3247
|
+
citation = runtimeGlobalCitation(target);
|
|
3248
|
+
if (citation !== null) reason = runtimeGlobalReason(target);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
} else if (callee.length > 0 && callee.endsWith(`.${target}`)) {
|
|
3252
|
+
const recvChain = callee.slice(0, callee.length - target.length - 1);
|
|
3253
|
+
const rootEnd = jsIdentifierPrefixEnd(recvChain);
|
|
3254
|
+
const root = recvChain.slice(0, rootEnd);
|
|
3255
|
+
if (root.length > 0 && isPlainJsIdentifier(root) && !bound.has(root) && !declaredHere.has(root)) {
|
|
3256
|
+
citation = memberChainClaim(root, target);
|
|
3257
|
+
if (citation !== null) {
|
|
3258
|
+
reason = "host-global-member";
|
|
3259
|
+
receiver = root;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
} else if (label === "uses") {
|
|
3264
|
+
const isBareRead = md.bareRead === true || md.chainRoot === true || md.callReceiver === true || md.isTemplateInterpolation === true;
|
|
3265
|
+
if (isBareRead) {
|
|
3266
|
+
if (!bound.has(target)) {
|
|
3267
|
+
citation = hostGlobalCitation(target);
|
|
3268
|
+
if (citation !== null) reason = "host-global-bare";
|
|
3269
|
+
else {
|
|
3270
|
+
citation = runtimeGlobalCitation(target);
|
|
3271
|
+
if (citation !== null) reason = runtimeGlobalReason(target);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
} else if (md.isFieldAccess === true) {
|
|
3275
|
+
const root = String(md.receiverRoot ?? "");
|
|
3276
|
+
if (root.length > 0 && isPlainJsIdentifier(root) && !bound.has(root) && !declaredHere.has(root)) {
|
|
3277
|
+
citation = memberChainClaim(root, target);
|
|
3278
|
+
if (citation !== null) {
|
|
3279
|
+
reason = "host-global-member";
|
|
3280
|
+
receiver = root;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
} else if (label === "imports") {
|
|
3285
|
+
const at = target.lastIndexOf(":");
|
|
3286
|
+
if (at > 0) {
|
|
3287
|
+
const spec = target.slice(0, at);
|
|
3288
|
+
const item = target.slice(at + 1);
|
|
3289
|
+
const isRelative = spec.startsWith(".") || spec.startsWith("/");
|
|
3290
|
+
const isScheme = spec.includes("://");
|
|
3291
|
+
const isPathish = spec.includes("\\") || spec.length === 0 || item.length === 0;
|
|
3292
|
+
const index = context?.projectIndex;
|
|
3293
|
+
if (!isRelative && !isScheme && !isPathish && index !== void 0 && index.complete === true) {
|
|
3294
|
+
const verdict = jsProjectModuleLocalityOf(index)(spec);
|
|
3295
|
+
if (verdict === "third-party") {
|
|
3296
|
+
reason = "module-namespace";
|
|
3297
|
+
citation = `import specifier \`${spec}\` names no module of this project (complete project index; ${item === "*" ? "namespace import" : `item ${item}`})`;
|
|
3298
|
+
receiver = spec;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
} else if (label === "extends" || label === "references" || label === "instantiates") {
|
|
3303
|
+
if (!bound.has(target) && label !== "instantiates") {
|
|
3304
|
+
citation = hostGlobalCitation(target);
|
|
3305
|
+
if (citation !== null) reason = label === "extends" ? "host-global-base" : "host-global-type";
|
|
3306
|
+
}
|
|
3307
|
+
if (citation === null && !bound.has(target)) {
|
|
3308
|
+
citation = runtimeGlobalCitation(target);
|
|
3309
|
+
if (citation !== null) reason = runtimeGlobalReason(target);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
if (reason === null || citation === null) continue;
|
|
3313
|
+
md.isExternalTarget = true;
|
|
3314
|
+
md.externalReason = reason;
|
|
3315
|
+
md.externalClaimReason = citation;
|
|
3316
|
+
if (receiver !== null) md.externalReceiver = receiver;
|
|
3317
|
+
if (reason === "module-namespace" && receiver !== null) md.externalSpecifier = receiver;
|
|
3318
|
+
marked++;
|
|
3319
|
+
}
|
|
3320
|
+
return marked;
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* EVERY NAME THIS FILE BINDS — the negative control of the claim pass, and deliberately
|
|
3324
|
+
* over-collecting: a name collected here that is not really a binding can only REFUSE a
|
|
3325
|
+
* claim, never make one, so the conservative direction is the safe direction.
|
|
3326
|
+
*
|
|
3327
|
+
* The positions are the grammar's own definition positions, matched by the same node-type
|
|
3328
|
+
* tests the bare-read walk applies (`isImportBinding` / `isParamBinding` / `isPatternBinding`
|
|
3329
|
+
* / declaration names), read from the IDENTIFIER's parent rather than re-walking each
|
|
3330
|
+
* construct — one pass, no shape re-derived.
|
|
3331
|
+
*/
|
|
3332
|
+
collectBoundNames(rootNode) {
|
|
3333
|
+
const out = /* @__PURE__ */ new Set();
|
|
3334
|
+
const stack = [rootNode];
|
|
3335
|
+
while (stack.length > 0) {
|
|
3336
|
+
const n = stack.pop();
|
|
3337
|
+
if (n.type === "identifier") {
|
|
3338
|
+
const p = n.parent;
|
|
3339
|
+
if (p) {
|
|
3340
|
+
const isDeclName = p.type === "variable_declarator" && p.childForFieldName("name")?.id === n.id || p.type === "function_declaration" && p.childForFieldName("name")?.id === n.id || p.type === "generator_function_declaration" && p.childForFieldName("name")?.id === n.id || p.type === "class_declaration" && p.childForFieldName("name")?.id === n.id || p.type === "function_expression" && p.childForFieldName("name")?.id === n.id || p.type === "method_definition" && p.childForFieldName("name")?.id === n.id;
|
|
3341
|
+
const isBindingPosition = p.type === "formal_parameters" || p.type === "import_clause" || p.type === "import_specifier" || p.type === "namespace_import" || p.type === "catch_clause" || p.type === "object_pattern" || p.type === "array_pattern" || p.type === "rest_pattern" || p.type === "shorthand_property_identifier_pattern" || p.type === "pair_pattern" || p.type === "assignment_pattern" && (p.childForFieldName("left") ?? p.namedChild(0))?.id === n.id || p.type === "arrow_function" && p.childForFieldName("parameter")?.id === n.id;
|
|
3342
|
+
if (isDeclName || isBindingPosition) out.add(n.text);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
3346
|
+
const c = n.namedChild(i);
|
|
3347
|
+
if (c) stack.push(c);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
return out;
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* M14 #299 - evidence-tier classification for EVERY emitted relationship.
|
|
3354
|
+
* Same rules as the JVM/TS family. JS calls are usage_contextual,
|
|
3355
|
+
* field_access uses are usage_contextual, imports are import_based.
|
|
3356
|
+
*/
|
|
3357
|
+
classifyEvidenceTier(relationships) {
|
|
3358
|
+
for (const r of relationships) {
|
|
3359
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
3360
|
+
if (md.evidenceClass) continue;
|
|
3361
|
+
const t = String(r.type ?? "");
|
|
3362
|
+
const conf = typeof r.confidence === "number" ? r.confidence : 1;
|
|
3363
|
+
if (t === "contains" || t === "exports") {
|
|
3364
|
+
md.evidenceClass = "structural";
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
if (t === "imports") {
|
|
3368
|
+
md.evidenceClass = "import_based";
|
|
3369
|
+
continue;
|
|
3370
|
+
}
|
|
3371
|
+
if (t === "references") {
|
|
3372
|
+
md.evidenceClass = "reference_qualified";
|
|
3373
|
+
continue;
|
|
3374
|
+
}
|
|
3375
|
+
if (t === "extends" || t === "implements" || t === "overrides") {
|
|
3376
|
+
md.evidenceClass = conf >= 1 ? "structural" : conf >= 0.85 ? "usage_contextual" : "inferred";
|
|
3377
|
+
continue;
|
|
3378
|
+
}
|
|
3379
|
+
if (t === "instantiates") {
|
|
3380
|
+
md.evidenceClass = conf >= 0.9 ? "structural" : "usage_contextual";
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
md.evidenceClass = "usage_contextual";
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
/**
|
|
3387
|
+
* M59 (#825): the `line_number` for a relationship, taken from the node that
|
|
3388
|
+
* SPELLS the name the edge is about.
|
|
3389
|
+
*
|
|
3390
|
+
* javascript is the largest silent lane in the product: measured by the M59
|
|
3391
|
+
* oracle before this round, **0 of 5,818 relationships on a 40-file sample
|
|
3392
|
+
* carried a line** (vue 0/3,940 and svelte 0/526, both of which delegate their
|
|
3393
|
+
* script block to this analyzer — so one defect blanked three lanes). The
|
|
3394
|
+
* contract is inherited, not re-invented: **the line belongs to the name, never
|
|
3395
|
+
* to the construct that contains it.** A `call_expression` starts at its
|
|
3396
|
+
* RECEIVER (`a.b.c()` opens on `a`, not on `c`), a `new_expression` on its
|
|
3397
|
+
* `new` token, an `import_statement` on `import` even when its named specifiers
|
|
3398
|
+
* sit three lines below, and a class body's children open at their decorators.
|
|
3399
|
+
* Callers therefore pass the NAME node and this returns nothing when there is
|
|
3400
|
+
* none — never `0`, because `0` reads as "unknown" and unknown must be absent.
|
|
3401
|
+
*/
|
|
3402
|
+
lineField(nameNode, fallback) {
|
|
3403
|
+
const n = nameNode ?? fallback;
|
|
3404
|
+
return n ? { line_number: n.startPosition.row + 1 } : {};
|
|
3405
|
+
}
|
|
3406
|
+
nodeToRange(node) {
|
|
3407
|
+
let startSource = node;
|
|
3408
|
+
{
|
|
3409
|
+
let i = 0;
|
|
3410
|
+
while (i < node.childCount && node.child(i)?.type === "decorator") i++;
|
|
3411
|
+
const first = i < node.childCount ? node.child(i) : null;
|
|
3412
|
+
if (i > 0 && first) startSource = first;
|
|
3413
|
+
}
|
|
3414
|
+
return {
|
|
3415
|
+
startLine: startSource.startPosition.row + 1,
|
|
3416
|
+
startColumn: startSource.startPosition.column + 1,
|
|
3417
|
+
endLine: node.endPosition.row + 1,
|
|
3418
|
+
endColumn: node.endPosition.column + 1,
|
|
3419
|
+
startIndex: startSource.startIndex,
|
|
3420
|
+
endIndex: node.endIndex
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
};
|
|
3424
|
+
|
|
3425
|
+
export {
|
|
3426
|
+
RealJavaScriptAnalyzer
|
|
3427
|
+
};
|