@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,2405 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fileClaimForTarget
|
|
3
|
+
} from "./chunk-EWMWK7WN.js";
|
|
4
|
+
import {
|
|
5
|
+
ASTEngine
|
|
6
|
+
} from "./chunk-V44PGPA6.js";
|
|
7
|
+
import "./chunk-BQ3OVQ3P.js";
|
|
8
|
+
import "./chunk-SK6YVHFZ.js";
|
|
9
|
+
import "./chunk-2BSKD7O3.js";
|
|
10
|
+
import "./chunk-KAET47ZL.js";
|
|
11
|
+
import "./chunk-KCDECRJY.js";
|
|
12
|
+
import "./chunk-MLKGABMK.js";
|
|
13
|
+
|
|
14
|
+
// src/ast/real-xml-analyzer.ts
|
|
15
|
+
import { statSync } from "fs";
|
|
16
|
+
import * as path from "path";
|
|
17
|
+
var RealXmlAnalyzer = class _RealXmlAnalyzer {
|
|
18
|
+
/**
|
|
19
|
+
* Every node type this analyzer acts on, and why. The F8 completeness gate
|
|
20
|
+
* compares this against the analyzer's dispatch sites AND against the node
|
|
21
|
+
* types observed in a real corpus, so a type that appears in the corpus, is
|
|
22
|
+
* not declared here, and is not a pass-through literal is a red gate.
|
|
23
|
+
*/
|
|
24
|
+
static DECLARED = {
|
|
25
|
+
document: "walk root; the root element is reached through the walk",
|
|
26
|
+
prolog: "container: XMLDecl, doctypedecl and the misc PIs/comments under it are dispatched from here",
|
|
27
|
+
element: "the document's structural unit: a symbol plus a `contains` edge from its parent element",
|
|
28
|
+
STag: "start tag: carries the element Name and the Attribute list",
|
|
29
|
+
EmptyElemTag: "empty tag: carries the element Name and the Attribute list",
|
|
30
|
+
Name: "the name-bearing leaf for tags, attributes, PIs, DTD declarations and notations",
|
|
31
|
+
Attribute: "a property of its element, and the source of `references` / `imports` edges for its value",
|
|
32
|
+
doctypedecl: "a Type symbol naming the DTD, plus its ExternalID import",
|
|
33
|
+
ExternalID: "a file reference on a doctypedecl, entity or notation \u2014 ONLY when it carries a SystemLiteral",
|
|
34
|
+
SystemLiteral: "the file path inside a SYSTEM ExternalID",
|
|
35
|
+
PubidLiteral: "the public identifier inside a PUBLIC ExternalID; a catalogue name, not a path",
|
|
36
|
+
elementdecl: "a Type symbol for a DTD element declaration; its contentspec is the content model",
|
|
37
|
+
AttlistDecl: "the element whose attributes are declared here",
|
|
38
|
+
AttDef: "a Property symbol plus a `contains` edge from the element it is declared for",
|
|
39
|
+
GEDecl: "a general entity declaration: a Type symbol owned by the DTD, plus its ExternalID import",
|
|
40
|
+
PEDecl: "a parameter entity declaration: a Type symbol owned by the DTD, plus its ExternalID import",
|
|
41
|
+
NotationDecl: "a notation declaration: a Type symbol owned by the DTD, plus its ExternalID import",
|
|
42
|
+
PublicID: "a PUBLIC identifier on a NotationDecl; it names NO file, so it publishes no import",
|
|
43
|
+
NDataDecl: "the notation an unparsed entity points at: a `contains` edge",
|
|
44
|
+
contentspec: "the content model of an element declaration; each Name leaf becomes a `contains` edge",
|
|
45
|
+
NotationType: "the notations an attribute may carry: each Name leaf becomes a `contains` edge",
|
|
46
|
+
PI: "a processing instruction: a Module symbol owned by its enclosing element",
|
|
47
|
+
PITarget: "the name-bearing leaf of a PI",
|
|
48
|
+
StyleSheetPI: "a file reference: the PI spelling the `xml-stylesheet` external token produces",
|
|
49
|
+
XmlModelPI: "a file reference: the PI spelling the `xml-model` external token produces",
|
|
50
|
+
PseudoAtt: "a pseudo-attribute of a StyleSheetPI / XmlModelPI",
|
|
51
|
+
PseudoAttValue: "the value of a pseudo-attribute; `href` is a file path"
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Node types the walk deliberately walks THROUGH without acting on them. The
|
|
55
|
+
* F8 gate requires every observed named type to be in `DECLARED` or here; the
|
|
56
|
+
* value is the reason, so a pass-through is a decision on record and not a
|
|
57
|
+
* shrug.
|
|
58
|
+
*/
|
|
59
|
+
static PASSTHROUGH_LITERALS = {
|
|
60
|
+
XMLDecl: "`xml` is reserved; version and encoding are not declarations",
|
|
61
|
+
Comment: "not a declaration",
|
|
62
|
+
CharData: "text, not a declaration",
|
|
63
|
+
CData: "text, not a declaration",
|
|
64
|
+
CDSect: "wrapper around CData",
|
|
65
|
+
CDStart: "wrapper for `<![CDATA[`",
|
|
66
|
+
CharRef: "a numeric character reference, not a name",
|
|
67
|
+
EntityRef: "a USE; the declaration is GEDecl / PEDecl",
|
|
68
|
+
PEReference: "inside an internal subset a PEReference is a DeclSep \u2014 a separator, not a use",
|
|
69
|
+
content: "an element's child sequence: walked through so each child element is reached; nothing on it is a name",
|
|
70
|
+
conditionalSect: "an INCLUDE/IGNORE section inside an internal subset; walked through so the declarations it guards are reached",
|
|
71
|
+
VersionNum: "the version pseudo-attribute literal inside an XMLDecl, not a name",
|
|
72
|
+
EncName: "the encoding pseudo-attribute literal inside an XMLDecl, not a name",
|
|
73
|
+
URI: "the aliased body of a SystemLiteral; the edge target is the enclosing literal, and the LITERAL carries the edge line",
|
|
74
|
+
_intSubsetItem: "hidden rule; the repeat that carries a subset's declarations and conditional sections",
|
|
75
|
+
ETag: "the repaired grammar rejects a mismatched end name, so the name adds nothing",
|
|
76
|
+
AttValue: "read by the Attribute branch",
|
|
77
|
+
Mixed: "`(#PCDATA|a|b)*` \u2014 the names are read from the Name leaves, not the wrapper",
|
|
78
|
+
children: "the element content model; its Name leaves are the declarations that matter",
|
|
79
|
+
Nmtoken: "an enumeration member is a token, not a name",
|
|
80
|
+
Enumeration: "wrapper over the Nmtokens of a DTD attribute type",
|
|
81
|
+
StringType: "the literal CDATA, not a name",
|
|
82
|
+
TokenizedType: "the literal ID / IDREF / \u2026 token, not a name",
|
|
83
|
+
DefaultDecl: "the literal #REQUIRED / #IMPLIED / #FIXED value, not a name",
|
|
84
|
+
EntityValue: "an entity's replacement text, not a name"
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Namespaces whose members are specification built-ins rather than project
|
|
88
|
+
* symbols. A `type="xsd:string"` reference is real and is emitted, but it is
|
|
89
|
+
* marked `resolvable: "builtin"` and never published as a phantom symbol —
|
|
90
|
+
* recording a `string` symbol that no document in the corpus declares is the
|
|
91
|
+
* M60.R10 "a value where a name belongs" class of lie.
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* WHERE A DECLARATION'S NAME ACTUALLY LIVES, per vocabulary.
|
|
95
|
+
*
|
|
96
|
+
* In a schema language the DECLARED NAME of a construct is the VALUE of its
|
|
97
|
+
* `name` attribute, not its tag name: `<xs:complexType name="RootType">`
|
|
98
|
+
* declares the type `RootType`. An analyzer that publishes the tag name files
|
|
99
|
+
* every type in a project under one symbol — `xsd:element` alone appears 5,914
|
|
100
|
+
* times in this corpus — and makes the search a developer actually types,
|
|
101
|
+
* `bb_search("RootType")`, return nothing at all.
|
|
102
|
+
*
|
|
103
|
+
* The rule is gated on the element's EFFECTIVE NAMESPACE and on a DECLARED set
|
|
104
|
+
* of local names, and both halves are load-bearing:
|
|
105
|
+
*
|
|
106
|
+
* · tag-only gating is wrong, and measurably so. Outside a declaration
|
|
107
|
+
* vocabulary 29,196 elements carry a `name` attribute in this corpus and
|
|
108
|
+
* nearly all of them are DATA: `<property name="wsdl.dir">`,
|
|
109
|
+
* `<target name="client">`, a POM's `<name>` display label. Renaming those
|
|
110
|
+
* would publish a data value as a graph symbol.
|
|
111
|
+
* · `rng:ref name="…"` is a REFERENCE, not a declaration, so it is
|
|
112
|
+
* deliberately absent from the RelaxNG set; and because the value carries
|
|
113
|
+
* no prefix the QName pass cannot see it either. That gap is DECLARED here
|
|
114
|
+
* rather than left silently open.
|
|
115
|
+
*
|
|
116
|
+
* XSLT's local bindings (`xsl:variable`, `xsl:param`, `xsl:with-param`) are
|
|
117
|
+
* included: they are named, they are scoped to the enclosing template, and the
|
|
118
|
+
* walk already carries that owner.
|
|
119
|
+
*/
|
|
120
|
+
static DECLARATION_VOCABULARIES = {
|
|
121
|
+
"http://www.w3.org/2001/XMLSchema": /* @__PURE__ */ new Set([
|
|
122
|
+
"element",
|
|
123
|
+
"attribute",
|
|
124
|
+
"complexType",
|
|
125
|
+
"simpleType",
|
|
126
|
+
"group",
|
|
127
|
+
"attributeGroup",
|
|
128
|
+
"notation",
|
|
129
|
+
"key",
|
|
130
|
+
"keyref",
|
|
131
|
+
"unique"
|
|
132
|
+
]),
|
|
133
|
+
"http://relaxng.org/ns/structure/1.0": /* @__PURE__ */ new Set([
|
|
134
|
+
"grammar",
|
|
135
|
+
"define",
|
|
136
|
+
"element",
|
|
137
|
+
"attribute",
|
|
138
|
+
"group",
|
|
139
|
+
"data",
|
|
140
|
+
"value",
|
|
141
|
+
"text",
|
|
142
|
+
"list",
|
|
143
|
+
"div",
|
|
144
|
+
"except",
|
|
145
|
+
"name",
|
|
146
|
+
"externalRef"
|
|
147
|
+
]),
|
|
148
|
+
"http://www.w3.org/1999/XSL/Transform": /* @__PURE__ */ new Set([
|
|
149
|
+
"template",
|
|
150
|
+
"attribute-set",
|
|
151
|
+
"key",
|
|
152
|
+
"decimal-format",
|
|
153
|
+
"character-map",
|
|
154
|
+
"output",
|
|
155
|
+
"strip-space",
|
|
156
|
+
"preserve-space",
|
|
157
|
+
"variable",
|
|
158
|
+
"param",
|
|
159
|
+
"with-param",
|
|
160
|
+
"function"
|
|
161
|
+
]),
|
|
162
|
+
"http://schemas.xmlsoap.org/wsdl/": /* @__PURE__ */ new Set([
|
|
163
|
+
"definitions",
|
|
164
|
+
"binding",
|
|
165
|
+
"portType",
|
|
166
|
+
"message",
|
|
167
|
+
"service",
|
|
168
|
+
"port",
|
|
169
|
+
"operation"
|
|
170
|
+
])
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* ATTRIBUTE NAMES whose unprefixed value names a declaration.
|
|
174
|
+
*
|
|
175
|
+
* A PREFIX is the primary signal and needs no table: a value written `tns:X`
|
|
176
|
+
* names a declaration, and the census over 3,000 corpus files puts 14,479 such
|
|
177
|
+
* occurrences on real naming attributes (`type` 7,770, `ref` 1,903, `base`
|
|
178
|
+
* 1,608, Spring's `serviceName` 1,526 and `endpointName` 1,516, …). A prefixed
|
|
179
|
+
* value is a reference wherever it appears.
|
|
180
|
+
*
|
|
181
|
+
* An UNPREFIXED value is not, and the first version of this rule treated it as
|
|
182
|
+
* one, which is the M60.R10 class in reverse: the census counted 9,391 bare
|
|
183
|
+
* `key="…"` values that are property names, 4,453 `use="optional|required"`,
|
|
184
|
+
* 1,404 `maxOccurs="unbounded"`, 14,023 `value="…"` and 2,157 `w:val="Normal"`,
|
|
185
|
+
* and the analyzer published references to names like `unbounded` and `Normal`
|
|
186
|
+
* that nothing declares as a type. So an unprefixed value is a reference only
|
|
187
|
+
* on an attribute in this set, and the set is the DECLARATION (this is the
|
|
188
|
+
* M60.R10 target-spelling register, per-vocabulary, with the census above as
|
|
189
|
+
* its evidence).
|
|
190
|
+
*/
|
|
191
|
+
static REFERENCE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
192
|
+
// XSD 1.0 §4.2 / §3.4.3 / §3.4.4 — the four attributes whose value is a type
|
|
193
|
+
// or element reference by definition.
|
|
194
|
+
"type",
|
|
195
|
+
"base",
|
|
196
|
+
"ref",
|
|
197
|
+
"itemType",
|
|
198
|
+
"memberTypes",
|
|
199
|
+
"substitutionGroup",
|
|
200
|
+
"refer",
|
|
201
|
+
// WSDL 1.1 §3.5 / §3.6, and the SOAP binding's own reference attributes.
|
|
202
|
+
"portType",
|
|
203
|
+
"binding",
|
|
204
|
+
// RelaxNG: a `name` on a `ref` names another pattern, not this one. The
|
|
205
|
+
// DECLARING uses of `name` (`rng:element`, `rng:attribute`) are handled by
|
|
206
|
+
// the declared-name rule and must not also become references, which is why
|
|
207
|
+
// `name` is NOT in this set and `rng:ref` is named explicitly below.
|
|
208
|
+
"pattern"
|
|
209
|
+
]);
|
|
210
|
+
/**
|
|
211
|
+
* XSLT and RelaxNG name ONE construct by an attribute the generic set cannot
|
|
212
|
+
* carry, because the attribute is `name` and `name` is a DECLARATION on every
|
|
213
|
+
* sibling construct. Declared explicitly for that reason, and each is pinned by
|
|
214
|
+
* a fixture with its sibling case pinned the other way.
|
|
215
|
+
*
|
|
216
|
+
* `call-template` WAS here and was removed, and the removal is the M60
|
|
217
|
+
* ontology finding rather than a tidiness change. XSLT 1.0 §6 Named Templates
|
|
218
|
+
* is unambiguous about the construct:
|
|
219
|
+
*
|
|
220
|
+
* "An xsl:call-template element INVOKES A TEMPLATE BY NAME; it has a
|
|
221
|
+
* required name attribute that identifies the template to be invoked."
|
|
222
|
+
*
|
|
223
|
+
* and a named template is the only procedure the XML family has: it takes
|
|
224
|
+
* parameters (§11.6), is invoked by name, and returns a result tree fragment.
|
|
225
|
+
* Labelling that edge `references` says the document MENTIONS a name, when the
|
|
226
|
+
* document says it CALLS one — a `calls` edge existed in the ontology and the
|
|
227
|
+
* vocabulary was withholding it. So `call-template` emits `calls` (see
|
|
228
|
+
* `XSLT_CALL_ELEMENTS`), and `apply-templates` stays a `references` edge
|
|
229
|
+
* because XSLT 1.0 §5.4 gives `xsl:apply-templates` NO `name` attribute at
|
|
230
|
+
* all — §6 says so outright: "the name attribute on an xsl:template element
|
|
231
|
+
* does not affect whether the template is invoked by an xsl:apply-templates
|
|
232
|
+
* element". A dispatch by pattern and mode is not a call.
|
|
233
|
+
*/
|
|
234
|
+
static NAMED_REFERENCE_ELEMENTS = {
|
|
235
|
+
"http://www.w3.org/1999/XSL/Transform": /* @__PURE__ */ new Set(["apply-templates"]),
|
|
236
|
+
"http://relaxng.org/ns/structure/1.0": /* @__PURE__ */ new Set(["ref"])
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* The XSLT instructions whose `name` attribute names a template that is
|
|
240
|
+
* INVOKED, which is a `calls` edge and not a `references` one.
|
|
241
|
+
*
|
|
242
|
+
* `xsl:call-template` is the whole list, and deliberately so. `xsl:apply-imports`
|
|
243
|
+
* (§5.6) invokes the rule a stylesheet overrides, but it names it by nothing —
|
|
244
|
+
* it takes no `name` — so there is no edge to write. `xsl:apply-templates`
|
|
245
|
+
* dispatches by pattern and mode and, in XSLT 1.0, carries no `name`.
|
|
246
|
+
*/
|
|
247
|
+
static XSLT_CALL_ELEMENTS = /* @__PURE__ */ new Set(["call-template"]);
|
|
248
|
+
/**
|
|
249
|
+
* XSLT names a template at its definition and calls it by that name, so `name`
|
|
250
|
+
* on these two is a REFERENCE to a declaration elsewhere in the same document.
|
|
251
|
+
* The QName pass cannot see it: the value carries no prefix, so it is not a
|
|
252
|
+
* QName. Declared explicitly, for the same reason.
|
|
253
|
+
*/
|
|
254
|
+
static XSLT_NAMED_TEMPLATE_REFERENCES = /* @__PURE__ */ new Set([
|
|
255
|
+
"call-template",
|
|
256
|
+
"apply-templates"
|
|
257
|
+
]);
|
|
258
|
+
/**
|
|
259
|
+
* The M14 Matrix 1d evidence tier for each edge CLASS, assigned where the edge
|
|
260
|
+
* is written so a tier cannot drift from the rule that produced it.
|
|
261
|
+
*
|
|
262
|
+
* structural the parent/child or declaration/member relation is READ
|
|
263
|
+
* off the tree — the fact needs nothing outside the file
|
|
264
|
+
* import_based the target is a FILE, taken from a path the document
|
|
265
|
+
* itself writes
|
|
266
|
+
* reference_qualified the target is a NAME reached through a declared
|
|
267
|
+
* namespace prefix, or an XSLT named template reached by
|
|
268
|
+
* its declared name
|
|
269
|
+
* usage_contextual a containment the document states through an attribute
|
|
270
|
+
* rather than through nesting — the DTD content model and
|
|
271
|
+
* the NOTATION attribute type, which are the document
|
|
272
|
+
* saying what an element MAY contain
|
|
273
|
+
* inferred the edge is a reading the analyzer supplies and the
|
|
274
|
+
* document does not state outright
|
|
275
|
+
*
|
|
276
|
+
* XML emitted no tier at all before this, so the lane's Matrix 1d cell was
|
|
277
|
+
* silent; the completeness gate's section D now pins four of the five.
|
|
278
|
+
*/
|
|
279
|
+
static EVIDENCE_TIER = {
|
|
280
|
+
xml_element: "structural",
|
|
281
|
+
xml_attribute: "structural",
|
|
282
|
+
xml_namespace: "structural",
|
|
283
|
+
dtd_element_declaration: "structural",
|
|
284
|
+
dtd_attribute_declaration: "structural",
|
|
285
|
+
dtd_content_model: "usage_contextual",
|
|
286
|
+
dtd_notation_reference: "usage_contextual",
|
|
287
|
+
// NOT `structural`, and the containment ledger is what moved it. XML 1.0 §4.5
|
|
288
|
+
// writes the clause as "NDATA NotationName", naming a notation that the DTD
|
|
289
|
+
// declares ELSEWHERE — and necessarily EARLIER, so the notation's declaration is
|
|
290
|
+
// never inside the entity's. Measured over the full 11,464-file admitted
|
|
291
|
+
// population: 2 edges, `libxslt/tests/general/bug-21-.xml` (`<!ENTITY test SYSTEM
|
|
292
|
+
// "test.jpg" NDATA JPEG>` with `<!NOTATION JPEG …>` at 96-130 and the entity at
|
|
293
|
+
// 131-174) and `libxslt/tests/general/bug-52.xml`, and in both the target's span lies
|
|
294
|
+
// entirely BEFORE the source's. A `structural` tier is a claim that nesting was
|
|
295
|
+
// observed, and no nesting was; `usage_contextual` already defines this exact shape
|
|
296
|
+
// one row up — "a containment the document states through an attribute rather than
|
|
297
|
+
// through nesting" — and the DTD content model is its precedent. Two edges is not
|
|
298
|
+
// worth a new relationship type, and relabelling them `references` would be worse:
|
|
299
|
+
// `references` resolution has four classes and this construct is not one of them.
|
|
300
|
+
dtd_ndata_notation: "usage_contextual",
|
|
301
|
+
dtd_entity_declaration: "structural",
|
|
302
|
+
dtd_parameter_entity_declaration: "structural",
|
|
303
|
+
dtd_notation_declaration: "structural",
|
|
304
|
+
processing_instruction: "structural",
|
|
305
|
+
pseudo_attribute: "structural",
|
|
306
|
+
doctype_external_id: "import_based",
|
|
307
|
+
entity_external_id: "import_based",
|
|
308
|
+
parameter_entity_external_id: "import_based",
|
|
309
|
+
notation_external_id: "import_based",
|
|
310
|
+
processing_instruction_href: "import_based",
|
|
311
|
+
schema_location: "import_based",
|
|
312
|
+
attribute_qname_reference: "reference_qualified",
|
|
313
|
+
// An XSLT named template INVOKED by name. `reference_qualified` because the
|
|
314
|
+
// document names its target — that is what makes the call resolvable — and
|
|
315
|
+
// not `structural`, because the tree says nothing about which template an
|
|
316
|
+
// instruction invokes until the name is read.
|
|
317
|
+
xslt_named_template_call: "reference_qualified",
|
|
318
|
+
// XSD type derivation, `xs:extension`/`xs:restriction` `base=`. `structural`:
|
|
319
|
+
// the vocabulary element IS the declaration of the relation, so the fact needs
|
|
320
|
+
// nothing outside the file, exactly as a parent/child edge does.
|
|
321
|
+
xsd_derivation_base: "structural",
|
|
322
|
+
// XSLT 1.0 §5.5 competing template rules. `structural`, not `inferred`: both
|
|
323
|
+
// declarations and the three attributes that make them compete are read straight
|
|
324
|
+
// off the tree, and the only thing the spec contributes is WHICH of two colliding
|
|
325
|
+
// declarations wins — a direction, recorded in `metadata.derivation`, not a guess
|
|
326
|
+
// about whether the relation exists. Leaving this role unmapped let `addRelationship`
|
|
327
|
+
// label every one of these edges `inferred`, which is the tier the ontology census
|
|
328
|
+
// counts as heuristic: a deterministic parse fact filed as a guess, and the census
|
|
329
|
+
// row `tier inferred = 0` would have become false without anything changing in the
|
|
330
|
+
// analyzer's behaviour.
|
|
331
|
+
xslt_competing_template_rule: "structural",
|
|
332
|
+
// An entity use is reached through a NAME in the document's own DTD, not
|
|
333
|
+
// through a declared namespace prefix — but it is a reference to a
|
|
334
|
+
// declaration all the same, and `usage_contextual` would say the document is
|
|
335
|
+
// only *using* the entity, which understates it: `&logo;` in content means the
|
|
336
|
+
// element's value IS that entity's replacement text. `reference_qualified` is
|
|
337
|
+
// the same claim an XSLT named-template call makes.
|
|
338
|
+
entity_reference: "reference_qualified",
|
|
339
|
+
xslt_named_template_reference: "reference_qualified",
|
|
340
|
+
// An XInclude directive. `import_based` is the tier for a FILE inclusion
|
|
341
|
+
// directive (the same tier cpp's `#include`, fsharp's `#load` and erlang's
|
|
342
|
+
// `-include` carry): the clause names another FILE, and the fact
|
|
343
|
+
// needs nothing outside the file to be read — but it is not a symbol
|
|
344
|
+
// reference, because the target is a file, not a declaration.
|
|
345
|
+
xinclude_part: "import_based",
|
|
346
|
+
// XSLT's `<xsl:include href>` — the SAME inclusion contract in the stylesheet
|
|
347
|
+
// vocabulary (XSLT 1.0 §2.6.1: the included stylesheet's content is treated as
|
|
348
|
+
// if it were written at the include's position), so the same tier.
|
|
349
|
+
xslt_include_part: "import_based",
|
|
350
|
+
// XSD definition composition: `<xs:group ref="G"/>` / `<xs:attributeGroup ref="G"/>`
|
|
351
|
+
// inside a type composes G's definitions into THIS type with no base and no
|
|
352
|
+
// conformance declared. `reference_qualified` because the document names the
|
|
353
|
+
// composed definition — the same reading a QName reference gets — and the
|
|
354
|
+
// relation the edge asserts (composition into this type) is decided by the
|
|
355
|
+
// vocabulary construct, not inferred.
|
|
356
|
+
xsd_definition_composition: "reference_qualified",
|
|
357
|
+
// XSLT's `<xsl:import href>` — a MODULE import with import precedence
|
|
358
|
+
// (XSLT 1.0 §2.6.2), not a textual splice: the imported definitions are in
|
|
359
|
+
// scope but may be overridden by the importing stylesheet's own. That is the
|
|
360
|
+
// `imports` relation, and it shares `imports`' tier.
|
|
361
|
+
xslt_stylesheet_import: "import_based"
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* THE XINCLUDE NAMESPACE — the W3C XML Inclusions vocabulary (XInclude 1.0,
|
|
365
|
+
* https://www.w3.org/TR/xinclude/).
|
|
366
|
+
*
|
|
367
|
+
* This is the only XML construct that says "splice the named document's content
|
|
368
|
+
* INTO this one at this point". It is identified by NAMESPACE, never by the
|
|
369
|
+
* written prefix: `xi` is a convention, not syntax, and a document may bind the
|
|
370
|
+
* vocabulary to any prefix or to the default namespace. The analyzer already
|
|
371
|
+
* resolves the element's effective namespace from the document's own `xmlns`
|
|
372
|
+
* declarations (`rememberNamespaces`), so the test is on that URI.
|
|
373
|
+
*/
|
|
374
|
+
static XINCLUDE_NAMESPACE = "http://www.w3.org/2001/XInclude";
|
|
375
|
+
static BUILTIN_NAMESPACE_URIS = /* @__PURE__ */ new Set([
|
|
376
|
+
"http://www.w3.org/2001/XMLSchema",
|
|
377
|
+
"http://www.w3.org/XML/1998/namespace"
|
|
378
|
+
]);
|
|
379
|
+
/**
|
|
380
|
+
* The five entities XML declares in EVERY document (XML 1.0 §4.6, "Entity
|
|
381
|
+
* declarations"), whether or not a DTD mentions them.
|
|
382
|
+
*
|
|
383
|
+
* The same shape of knowledge as `BUILTIN_NAMESPACE_URIS` above and the
|
|
384
|
+
* spec-bound prefixes below it, and needed for the same reason: the grammar
|
|
385
|
+
* reports each of them as an `EntityRef`, and a pass with only "declared or
|
|
386
|
+
* not" answers has to call all of them undefined. A document containing
|
|
387
|
+
* `<` is well-formed in every conforming parser, so `unresolved` on it is
|
|
388
|
+
* not a cautious reading — it is a false claim about the document.
|
|
389
|
+
*
|
|
390
|
+
* Deliberately exactly five. ` `, `—` and the rest of the HTML
|
|
391
|
+
* entity set are NOT predefined; an XML document that uses one without
|
|
392
|
+
* declaring it really does reference something undefined, and that stays
|
|
393
|
+
* `unresolved`.
|
|
394
|
+
*/
|
|
395
|
+
static PREDEFINED_ENTITIES = /* @__PURE__ */ new Set([
|
|
396
|
+
"lt",
|
|
397
|
+
"gt",
|
|
398
|
+
"amp",
|
|
399
|
+
"apos",
|
|
400
|
+
"quot"
|
|
401
|
+
]);
|
|
402
|
+
/**
|
|
403
|
+
* The two prefixes the Namespaces specification binds in EVERY document,
|
|
404
|
+
* whether or not the document says so (XML Namespaces 1.0 §4, and the
|
|
405
|
+
* Namespaces in XML 1.1 §2.2 which makes it a namespace name rather than a
|
|
406
|
+
* reserved prefix). Neither may be declared to anything else: re-binding `xml`
|
|
407
|
+
* is an error, and `xmlns` is bound to the XMLNS namespace.
|
|
408
|
+
*
|
|
409
|
+
* This table exists because a well-formed document CANNOT supply them, so a
|
|
410
|
+
* resolver that only reads declarations can never resolve them. See M60 X8.
|
|
411
|
+
*/
|
|
412
|
+
/**
|
|
413
|
+
* The XML Schema built-in DERIVED types, from Part 2's own list. Written out
|
|
414
|
+
* rather than derived, because the analyzer and the corpus oracle each keep
|
|
415
|
+
* their own copy ON PURPOSE: two implementations that share a constant cannot
|
|
416
|
+
* disagree, and a constant that cannot be disagreed with cannot find a defect.
|
|
417
|
+
*/
|
|
418
|
+
static XSD_BUILTIN_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
419
|
+
"anyType",
|
|
420
|
+
"anySimpleType",
|
|
421
|
+
"string",
|
|
422
|
+
"boolean",
|
|
423
|
+
"decimal",
|
|
424
|
+
"float",
|
|
425
|
+
"double",
|
|
426
|
+
"duration",
|
|
427
|
+
"dateTime",
|
|
428
|
+
"time",
|
|
429
|
+
"date",
|
|
430
|
+
"gYearMonth",
|
|
431
|
+
"gYear",
|
|
432
|
+
"gMonthDay",
|
|
433
|
+
"gDay",
|
|
434
|
+
"gMonth",
|
|
435
|
+
"hexBinary",
|
|
436
|
+
"base64Binary",
|
|
437
|
+
"anyURI",
|
|
438
|
+
"QName",
|
|
439
|
+
"NOTATION",
|
|
440
|
+
"normalizedString",
|
|
441
|
+
"token",
|
|
442
|
+
"language",
|
|
443
|
+
"NMTOKEN",
|
|
444
|
+
"NMTOKENS",
|
|
445
|
+
"Name",
|
|
446
|
+
"NCName",
|
|
447
|
+
"ID",
|
|
448
|
+
"IDREF",
|
|
449
|
+
"IDREFS",
|
|
450
|
+
"ENTITY",
|
|
451
|
+
"ENTITIES",
|
|
452
|
+
"integer",
|
|
453
|
+
"nonPositiveInteger",
|
|
454
|
+
"negativeInteger",
|
|
455
|
+
"long",
|
|
456
|
+
"int",
|
|
457
|
+
"short",
|
|
458
|
+
"byte",
|
|
459
|
+
"nonNegativeInteger",
|
|
460
|
+
"unsignedLong",
|
|
461
|
+
"unsignedInt",
|
|
462
|
+
"unsignedShort",
|
|
463
|
+
"unsignedByte",
|
|
464
|
+
"positiveInteger",
|
|
465
|
+
"dateTimeStamp",
|
|
466
|
+
"dayTimeDuration",
|
|
467
|
+
"yearMonthDuration",
|
|
468
|
+
"lang",
|
|
469
|
+
"space",
|
|
470
|
+
"base",
|
|
471
|
+
"id"
|
|
472
|
+
]);
|
|
473
|
+
static IMPLICIT_PREFIX_BINDINGS = {
|
|
474
|
+
xml: "http://www.w3.org/XML/1998/namespace",
|
|
475
|
+
xmlns: "http://www.w3.org/2000/xmlns/"
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* THE LANE'S OWN FILE VOCABULARY, as ONE set with two readers — the two halves of the imports
|
|
479
|
+
* file claim must not be able to disagree about what a file of this lane is.
|
|
480
|
+
*
|
|
481
|
+
* The router maps exactly these seven extensions to language `xml`
|
|
482
|
+
* (`src/ast/language-detection.ts:197`, the M60 XML lane comment above it), the incoming gate's
|
|
483
|
+
* lane definition admits exactly these (`script/m14/incoming-gate.ts` LANES_B). The POSITIVE half
|
|
484
|
+
* of the claim passes them to `fileClaimForTarget` as the admitted set; the NEGATIVE half requires
|
|
485
|
+
* a bare target to end in one of them before it will call the target a file. A second spelling of
|
|
486
|
+
* the list in the same file would be a second rule, and the copy that drifted would be the one
|
|
487
|
+
* that mattered.
|
|
488
|
+
*/
|
|
489
|
+
static IMPORT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
490
|
+
"xml",
|
|
491
|
+
"xsd",
|
|
492
|
+
"dtd",
|
|
493
|
+
"svg",
|
|
494
|
+
"xsl",
|
|
495
|
+
"xslt",
|
|
496
|
+
"rng"
|
|
497
|
+
]);
|
|
498
|
+
/**
|
|
499
|
+
* Transient per-attribute context, set by the element branch before its
|
|
500
|
+
* attributes run. `bareNameReference` — the element is one that NAMES another
|
|
501
|
+
* declaration rather than being one (XSLT `call-template`, RelaxNG `ref`).
|
|
502
|
+
* `bareNameDeclarationElement` — the element DECLARES through `name`
|
|
503
|
+
* (RelaxNG `element`, `attribute`), so its `name` is a declaration and must not
|
|
504
|
+
* also become a reference to itself.
|
|
505
|
+
*/
|
|
506
|
+
bareNameReference = false;
|
|
507
|
+
bareNameDeclarationElement = false;
|
|
508
|
+
/**
|
|
509
|
+
* The project's placeholder for an endpoint that could not be determined. Named
|
|
510
|
+
* here rather than inlined so the refusal that uses it and the census that checks
|
|
511
|
+
* it are reading the same string.
|
|
512
|
+
*/
|
|
513
|
+
static PLACEHOLDER_LITERAL = "unknown";
|
|
514
|
+
/** `xsl:call-template`: its `name` attribute names a template it INVOKES. */
|
|
515
|
+
xsltCallElement = false;
|
|
516
|
+
/** An XSD `extension`/`restriction`: its `base` attribute names a SUPERtype. */
|
|
517
|
+
xsdDerivationElement = false;
|
|
518
|
+
derivationKind = "extension";
|
|
519
|
+
analyze(rootNode, content, filePath, context) {
|
|
520
|
+
this.bareNameReference = false;
|
|
521
|
+
this.bareNameDeclarationElement = false;
|
|
522
|
+
this.xsltCallElement = false;
|
|
523
|
+
this.xsdDerivationElement = false;
|
|
524
|
+
const symbols = [];
|
|
525
|
+
const relationships = [];
|
|
526
|
+
const imports = [];
|
|
527
|
+
const errors = [];
|
|
528
|
+
const seenRelationships = /* @__PURE__ */ new Map();
|
|
529
|
+
const publishedNames = /* @__PURE__ */ new Set();
|
|
530
|
+
const namespaceBindings = /* @__PURE__ */ new Map();
|
|
531
|
+
const bindingScopes = [];
|
|
532
|
+
const declaredNames = /* @__PURE__ */ new Set();
|
|
533
|
+
const importedNamespaces = /* @__PURE__ */ new Map();
|
|
534
|
+
const referenceCandidates = [];
|
|
535
|
+
const callCandidates = [];
|
|
536
|
+
const derivationCandidates = [];
|
|
537
|
+
const compositionCandidates = [];
|
|
538
|
+
const declaredTemplateNames = /* @__PURE__ */ new Set();
|
|
539
|
+
const competingTemplates = [];
|
|
540
|
+
const entityReferenceCandidates = [];
|
|
541
|
+
const parameterEntityReferences = [];
|
|
542
|
+
const shadowCandidates = [];
|
|
543
|
+
let errorNodes = 0;
|
|
544
|
+
let missingNodes = 0;
|
|
545
|
+
let documentElementName;
|
|
546
|
+
const publish = (symbol, identity) => {
|
|
547
|
+
symbols.push(symbol);
|
|
548
|
+
if (identity) publishedNames.add(identity);
|
|
549
|
+
return symbol;
|
|
550
|
+
};
|
|
551
|
+
const addRelationship = (source, target, type, metadata, targetNode) => {
|
|
552
|
+
if (!source || !target) return;
|
|
553
|
+
if (type === ASTEngine.RelationshipType.References) {
|
|
554
|
+
if (source === target) return;
|
|
555
|
+
}
|
|
556
|
+
const key = `${source}\0${type}\0${target}`;
|
|
557
|
+
const line = targetNode ? targetNode.startPosition.row + 1 : void 0;
|
|
558
|
+
const role = metadata.role;
|
|
559
|
+
const existing = seenRelationships.get(key);
|
|
560
|
+
if (existing) {
|
|
561
|
+
existing.metadata.occurrences = (existing.metadata.occurrences ?? 1) + 1;
|
|
562
|
+
if (role && role !== existing.metadata.role) {
|
|
563
|
+
const also = existing.metadata.alsoRoles ?? [];
|
|
564
|
+
if (!also.includes(role)) also.push(role);
|
|
565
|
+
existing.metadata.alsoRoles = also;
|
|
566
|
+
const tiers = existing.metadata.alsoRoleTiers ?? [];
|
|
567
|
+
if (!tiers.some((t) => t.role === role)) {
|
|
568
|
+
tiers.push({ role, tier: _RealXmlAnalyzer.EVIDENCE_TIER[role] ?? "inferred" });
|
|
569
|
+
}
|
|
570
|
+
existing.metadata.alsoRoleTiers = tiers;
|
|
571
|
+
}
|
|
572
|
+
const lines = existing.metadata.lines ?? (existing.line_number !== void 0 ? [existing.line_number] : []);
|
|
573
|
+
if (line !== void 0) {
|
|
574
|
+
if (!lines.includes(line)) lines.push(line);
|
|
575
|
+
if (existing.line_number === void 0 || line < existing.line_number) {
|
|
576
|
+
existing.line_number = line;
|
|
577
|
+
existing.metadata.line = line;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
lines.sort((a, b) => a - b);
|
|
581
|
+
existing.metadata.lines = lines;
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const relationship = {
|
|
585
|
+
source,
|
|
586
|
+
target,
|
|
587
|
+
type,
|
|
588
|
+
confidence: 1,
|
|
589
|
+
line_number: line,
|
|
590
|
+
// M14 Matrix 1d: the tier is a property of the RULE that produced the
|
|
591
|
+
// edge, so it is stamped from a table keyed by that rule's role and an
|
|
592
|
+
// unrecognised role is `inferred` — which is the honest default, because
|
|
593
|
+
// a tier nobody can name is a reading the analyzer supplies rather than
|
|
594
|
+
// one the document states.
|
|
595
|
+
metadata: { ...metadata, occurrences: 1, evidenceClass: role && _RealXmlAnalyzer.EVIDENCE_TIER[role] || "inferred" }
|
|
596
|
+
};
|
|
597
|
+
seenRelationships.set(key, relationship);
|
|
598
|
+
relationships.push(relationship);
|
|
599
|
+
};
|
|
600
|
+
const ensureTargetSymbol = (name, ownerName, kind, node, metadata) => {
|
|
601
|
+
if (!name || !node) return name;
|
|
602
|
+
if (declaredNames.has(name)) return name;
|
|
603
|
+
const identity = ownerName ? `${ownerName}\0${name}` : name;
|
|
604
|
+
if (publishedNames.has(identity)) return name;
|
|
605
|
+
publish(
|
|
606
|
+
{
|
|
607
|
+
name,
|
|
608
|
+
kind,
|
|
609
|
+
range: this.nodeToRange(node),
|
|
610
|
+
visibility: "public",
|
|
611
|
+
ownerName,
|
|
612
|
+
ownerKind: ownerName ? ASTEngine.SymbolKind.Type : void 0,
|
|
613
|
+
qualifiedName: ownerName ? `${ownerName}.${name}` : name,
|
|
614
|
+
metadata: { ...metadata, nodeType: node.type }
|
|
615
|
+
},
|
|
616
|
+
identity
|
|
617
|
+
);
|
|
618
|
+
declaredNames.add(name);
|
|
619
|
+
return name;
|
|
620
|
+
};
|
|
621
|
+
const work = [{ node: rootNode }];
|
|
622
|
+
while (work.length > 0) {
|
|
623
|
+
const { node, ownerName, dtdName, bindDepth, nearestNamedDeclaration } = work.pop();
|
|
624
|
+
while (bindingScopes.length > (bindDepth ?? 0)) {
|
|
625
|
+
const scope = bindingScopes.pop();
|
|
626
|
+
namespaceBindings.clear();
|
|
627
|
+
for (const [k, v] of scope.outer) namespaceBindings.set(k, v);
|
|
628
|
+
}
|
|
629
|
+
if (node.isMissing) {
|
|
630
|
+
missingNodes++;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (node.isError) errorNodes++;
|
|
634
|
+
if (node.type === "element") {
|
|
635
|
+
const tagNode = this.findDirectChild(node, "STag") || this.findDirectChild(node, "EmptyElemTag");
|
|
636
|
+
const nameNode = tagNode ? this.findDirectChild(tagNode, "Name") : null;
|
|
637
|
+
const name = nameNode?.text.trim();
|
|
638
|
+
if (name && tagNode) {
|
|
639
|
+
bindingScopes.push({ outer: new Map(namespaceBindings) });
|
|
640
|
+
this.rememberNamespaces(tagNode, namespaceBindings);
|
|
641
|
+
const colon = name.indexOf(":");
|
|
642
|
+
const prefix = colon >= 0 ? name.slice(0, colon) : "";
|
|
643
|
+
const localName = colon >= 0 ? name.slice(colon + 1) : name;
|
|
644
|
+
const effectiveNamespace = namespaceBindings.get(prefix) ?? (colon < 0 ? namespaceBindings.get("") : void 0);
|
|
645
|
+
const declaration = this.declaredNameOf(effectiveNamespace, localName, tagNode);
|
|
646
|
+
const declaredName = declaration?.name;
|
|
647
|
+
const symbolName = declaredName ?? name;
|
|
648
|
+
if (prefix.length > 0 && namespaceBindings.has(prefix)) {
|
|
649
|
+
addRelationship(symbolName, prefix, ASTEngine.RelationshipType.Uses, {
|
|
650
|
+
role: "xml_namespace_prefix_use",
|
|
651
|
+
namespaceUri: namespaceBindings.get(prefix),
|
|
652
|
+
resolvable: "in-document"
|
|
653
|
+
}, nameNode);
|
|
654
|
+
}
|
|
655
|
+
const isNamedVocabularyDeclaration = effectiveNamespace !== void 0 && _RealXmlAnalyzer.DECLARATION_VOCABULARIES[effectiveNamespace]?.has(localName) === true && declaredName !== void 0;
|
|
656
|
+
const namedDeclarationForChildren = isNamedVocabularyDeclaration ? symbolName : nearestNamedDeclaration;
|
|
657
|
+
const elementSymbol = {
|
|
658
|
+
name: symbolName,
|
|
659
|
+
kind: ASTEngine.SymbolKind.Class,
|
|
660
|
+
// M19 (master review, 2026-09-29): a VOCABULARY DECLARATION is written
|
|
661
|
+
// on its `name=` attribute, not on the tag that opens it, and the two
|
|
662
|
+
// are routinely on DIFFERENT LINES. Measured over the 2,500-file
|
|
663
|
+
// blast radius: 4 of 19,852 declarations reported the tag's line, so
|
|
664
|
+
// a search for the symbol landed on `<xsl:with-param` rather than on
|
|
665
|
+
// `name="content"` a line below. The symbol's range therefore starts
|
|
666
|
+
// at the ATTRIBUTE, while `elementRange` below keeps the structural
|
|
667
|
+
// span so a reader can still find the element itself. Before this the
|
|
668
|
+
// two readings shared one field and one of them had to be wrong.
|
|
669
|
+
//
|
|
670
|
+
// M60 (containment ledger, 2026-10-04): the ANCHOR is right about the
|
|
671
|
+
// line and wrong about the SPAN, and the span is what `contains` is made
|
|
672
|
+
// of. Anchoring the same-line case to the `name=` Attribute put the
|
|
673
|
+
// declaration's range INSIDE its own element, so
|
|
674
|
+
//
|
|
675
|
+
// <define name="boolean"> symbol `boolean` @ name="boolean"
|
|
676
|
+
// <choice> symbol `choice` @ <choice>…</choice>
|
|
677
|
+
//
|
|
678
|
+
// produced the edge `boolean -contains-> choice` — which is TRUE — with a
|
|
679
|
+
// target that is not inside the source, and the attribute symbol `name`
|
|
680
|
+
// occupied the identical byte range as the declaration itself.
|
|
681
|
+
// Measured over 300 corpus files: 194 contains edges unverifiable by span
|
|
682
|
+
// nesting because two names shared one range, and 280 more whose source
|
|
683
|
+
// span did not enclose a child the source really does contain. Both are
|
|
684
|
+
// invisible to a resolution rate, which is 100 % on this type.
|
|
685
|
+
//
|
|
686
|
+
// So the all-on-one-line case anchors to the ELEMENT, and the reported LINE
|
|
687
|
+
// cannot move, because that is exactly the condition under which the
|
|
688
|
+
// element's row and the attribute's row are the same row. The other two
|
|
689
|
+
// shapes keep the Attribute / AttValue anchor and M19 exactly as measured —
|
|
690
|
+
// at the cost of leaving their containment unverifiable, which is 4 of
|
|
691
|
+
// 19,852 declarations and is declared rather than rounded away.
|
|
692
|
+
range: declaration ? this.nodeToRange(declaration.allOnOneLine ? node : declaration.nameNode) : this.nodeToRange(node),
|
|
693
|
+
visibility: "public",
|
|
694
|
+
ownerName,
|
|
695
|
+
ownerKind: ownerName ? ASTEngine.SymbolKind.Class : void 0,
|
|
696
|
+
qualifiedName: ownerName ? `${ownerName}.${symbolName}` : symbolName,
|
|
697
|
+
metadata: {
|
|
698
|
+
nodeType: "element",
|
|
699
|
+
isXmlElement: true,
|
|
700
|
+
tagName: name,
|
|
701
|
+
isSchemaConstruct: this.isSchemaConstruct(name),
|
|
702
|
+
// The DECLARED name, when the vocabulary puts it in the `name`
|
|
703
|
+
// attribute. `tagName` stays the tag, so the structural reading is
|
|
704
|
+
// carried rather than overwritten.
|
|
705
|
+
...declaration ? { isVocabDeclaration: true, declaredName: declaration.name, declarationKind: localName, declaredByAttribute: declaration.attribute, namespaceUri: effectiveNamespace, elementRange: this.nodeToRange(node) } : {}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
publish(elementSymbol, ownerName ? `${ownerName}\0${symbolName}` : symbolName);
|
|
709
|
+
declaredNames.add(symbolName);
|
|
710
|
+
if (documentElementName === void 0 && ownerName === void 0) documentElementName = symbolName;
|
|
711
|
+
const isXIncludeElement = effectiveNamespace === _RealXmlAnalyzer.XINCLUDE_NAMESPACE && localName === "include";
|
|
712
|
+
const isXsltIncludeElement = effectiveNamespace === "http://www.w3.org/1999/XSL/Transform" && localName === "include";
|
|
713
|
+
const isXsltImportElement = effectiveNamespace === "http://www.w3.org/1999/XSL/Transform" && localName === "import";
|
|
714
|
+
if (isXIncludeElement || isXsltIncludeElement) {
|
|
715
|
+
const hrefAttr = this.findDirectAttribute(tagNode, "href");
|
|
716
|
+
const hrefPath = (this.attributeValueOf(tagNode, "href") ?? "").trim();
|
|
717
|
+
if (hrefAttr && hrefPath.length > 0 && !this.isRemoteUri(hrefPath)) {
|
|
718
|
+
const partSymbol = _RealXmlAnalyzer.includedModuleStem(hrefPath);
|
|
719
|
+
if (partSymbol.length > 0) {
|
|
720
|
+
addRelationship(documentElementName ?? symbolName, partSymbol, ASTEngine.RelationshipType.PartOf, {
|
|
721
|
+
// The VOCABULARY rides on the role, so a consumer can tell the two
|
|
722
|
+
// inclusion channels apart without re-deriving namespace bindings.
|
|
723
|
+
role: isXIncludeElement ? "xinclude_part" : "xslt_include_part",
|
|
724
|
+
// Mandatory, per `src/ast/types.ts` — see the block above.
|
|
725
|
+
partRole: "library",
|
|
726
|
+
// THE PATH IS EVIDENCE, THE STEM IS THE TARGET. Keeping both is
|
|
727
|
+
// what makes the difference checkable instead of inferred.
|
|
728
|
+
partPath: hrefPath,
|
|
729
|
+
partSymbol,
|
|
730
|
+
// XInclude alone can spell a TEXT inclusion; XSLT's include is
|
|
731
|
+
// always a parsed stylesheet, so the field is present only where
|
|
732
|
+
// the vocabulary gives it a value.
|
|
733
|
+
...isXIncludeElement ? { parseMode: (this.attributeValueOf(tagNode, "parse") ?? "") === "text" ? "text" : "xml" } : {},
|
|
734
|
+
// The written element, so the construct is identified on the row
|
|
735
|
+
// without the consumer having to know the namespace URI.
|
|
736
|
+
elementName: name,
|
|
737
|
+
isFileInclusion: true,
|
|
738
|
+
// A part is NOT an import (the contract keeps them distinct): the
|
|
739
|
+
// included content does not create an import binding, it becomes
|
|
740
|
+
// this document's content.
|
|
741
|
+
isImport: false
|
|
742
|
+
}, this.findDirectChild(hrefAttr, "AttValue"));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
} else if (isXsltImportElement) {
|
|
746
|
+
const hrefAttr = this.findDirectAttribute(tagNode, "href");
|
|
747
|
+
const hrefPath = (this.attributeValueOf(tagNode, "href") ?? "").trim();
|
|
748
|
+
if (hrefAttr && hrefPath.length > 0 && !this.isRemoteUri(hrefPath)) {
|
|
749
|
+
addRelationship(documentElementName ?? symbolName, hrefPath, ASTEngine.RelationshipType.Imports, {
|
|
750
|
+
role: "xslt_stylesheet_import",
|
|
751
|
+
attributeName: "href",
|
|
752
|
+
// A module import, and the row says so rather than leaving a consumer
|
|
753
|
+
// to infer it from the element name.
|
|
754
|
+
importKind: "stylesheet",
|
|
755
|
+
resolvable: this.importTargetClass(hrefPath)
|
|
756
|
+
}, this.findDirectChild(hrefAttr, "AttValue"));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (declaration && effectiveNamespace === "http://www.w3.org/1999/XSL/Transform" && localName === "template") {
|
|
760
|
+
declaredTemplateNames.add(symbolName);
|
|
761
|
+
}
|
|
762
|
+
if (effectiveNamespace === "http://www.w3.org/1999/XSL/Transform" && localName === "template") {
|
|
763
|
+
const matchAttr = this.findDirectAttribute(tagNode, "match");
|
|
764
|
+
if (matchAttr) {
|
|
765
|
+
const matchValue = this.attributeValueOf(tagNode, "match") ?? "";
|
|
766
|
+
const modeValue = this.attributeValueOf(tagNode, "mode") ?? "";
|
|
767
|
+
const priorityValue = this.attributeValueOf(tagNode, "priority") ?? "";
|
|
768
|
+
competingTemplates.push({
|
|
769
|
+
key: `${matchValue}|${modeValue}|${priorityValue}`,
|
|
770
|
+
declaredName: declaration ? symbolName : "",
|
|
771
|
+
match: matchValue,
|
|
772
|
+
mode: modeValue,
|
|
773
|
+
priority: priorityValue,
|
|
774
|
+
node,
|
|
775
|
+
anchorNode: matchAttr,
|
|
776
|
+
ownerName
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (ownerName) {
|
|
781
|
+
addRelationship(ownerName, symbolName, ASTEngine.RelationshipType.Contains, {
|
|
782
|
+
role: "xml_element",
|
|
783
|
+
...declaration ? { tagName: name, declarationKind: localName } : {}
|
|
784
|
+
// M60 requires the edge's line to be the line the TARGET NAME is
|
|
785
|
+
// written on, and `addRelationship` already takes that node for
|
|
786
|
+
// exactly this reason (M59.A7). This call site was passing the
|
|
787
|
+
// ELEMENT, so a multi-line declaration pointed one line too high.
|
|
788
|
+
}, declaration ? declaration.nameNode : node);
|
|
789
|
+
}
|
|
790
|
+
for (let i = 0; i < tagNode.namedChildCount; i++) {
|
|
791
|
+
const child = tagNode.namedChild(i);
|
|
792
|
+
if (!child || child.type !== "Attribute") continue;
|
|
793
|
+
this.extractAttribute(
|
|
794
|
+
child,
|
|
795
|
+
symbolName,
|
|
796
|
+
symbols,
|
|
797
|
+
addRelationship,
|
|
798
|
+
publishedNames,
|
|
799
|
+
declaredNames,
|
|
800
|
+
namespaceBindings,
|
|
801
|
+
referenceCandidates,
|
|
802
|
+
ensureTargetSymbol,
|
|
803
|
+
localName,
|
|
804
|
+
effectiveNamespace,
|
|
805
|
+
callCandidates,
|
|
806
|
+
derivationCandidates,
|
|
807
|
+
compositionCandidates,
|
|
808
|
+
ownerName,
|
|
809
|
+
nearestNamedDeclaration,
|
|
810
|
+
isNamedVocabularyDeclaration ? symbolName : void 0
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
814
|
+
const child = node.namedChild(i);
|
|
815
|
+
if (child && child !== tagNode) work.push({ node: child, ownerName: symbolName, dtdName, bindDepth: bindingScopes.length, nearestNamedDeclaration: namedDeclarationForChildren });
|
|
816
|
+
}
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (node.type === "doctypedecl") {
|
|
821
|
+
const nameNode = this.findDirectChild(node, "Name");
|
|
822
|
+
const positionedName = this.isPositionedDeclarationName(node, nameNode) ? nameNode.text.trim() : void 0;
|
|
823
|
+
const currentDtdName = positionedName || dtdName;
|
|
824
|
+
if (positionedName) {
|
|
825
|
+
publish(
|
|
826
|
+
{
|
|
827
|
+
name: positionedName,
|
|
828
|
+
kind: ASTEngine.SymbolKind.Type,
|
|
829
|
+
range: this.nodeToRange(node),
|
|
830
|
+
visibility: "public",
|
|
831
|
+
metadata: { nodeType: node.type, isDoctype: true, dtdName: positionedName }
|
|
832
|
+
},
|
|
833
|
+
positionedName
|
|
834
|
+
);
|
|
835
|
+
declaredNames.add(positionedName);
|
|
836
|
+
}
|
|
837
|
+
const externalId = this.findDirectChild(node, "ExternalID");
|
|
838
|
+
if (externalId && currentDtdName) {
|
|
839
|
+
this.extractExternalId(externalId, currentDtdName, "doctype_external_id", addRelationship, imports);
|
|
840
|
+
}
|
|
841
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
842
|
+
const child = node.namedChild(i);
|
|
843
|
+
if (child && child !== nameNode && child !== externalId) work.push({ node: child, ownerName, dtdName: currentDtdName });
|
|
844
|
+
}
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (node.type === "elementdecl") {
|
|
848
|
+
const nameNode = this.findDirectChild(node, "Name");
|
|
849
|
+
const name = nameNode?.text.trim();
|
|
850
|
+
if (name) {
|
|
851
|
+
publish(
|
|
852
|
+
{
|
|
853
|
+
name,
|
|
854
|
+
kind: ASTEngine.SymbolKind.Type,
|
|
855
|
+
range: this.nodeToRange(node),
|
|
856
|
+
visibility: "public",
|
|
857
|
+
ownerName: dtdName,
|
|
858
|
+
ownerKind: dtdName ? ASTEngine.SymbolKind.Type : void 0,
|
|
859
|
+
metadata: { nodeType: node.type, isDtdElementDeclaration: true, dtdName }
|
|
860
|
+
},
|
|
861
|
+
dtdName ? `${dtdName}\0${name}` : name
|
|
862
|
+
);
|
|
863
|
+
declaredNames.add(name);
|
|
864
|
+
if (dtdName) addRelationship(dtdName, name, ASTEngine.RelationshipType.Contains, { role: "dtd_element_declaration" }, nameNode);
|
|
865
|
+
const contentSpec = this.findDirectChild(node, "contentspec");
|
|
866
|
+
if (contentSpec) {
|
|
867
|
+
for (const member of this.descendantsOfType(contentSpec, "Name")) {
|
|
868
|
+
const memberName = member.text.trim();
|
|
869
|
+
if (!memberName) continue;
|
|
870
|
+
shadowCandidates.push({
|
|
871
|
+
name: memberName,
|
|
872
|
+
ownerName: dtdName,
|
|
873
|
+
node: member,
|
|
874
|
+
metadata: { isDtdContentModelMember: true, isDoctype: true, dtdName },
|
|
875
|
+
edge: { source: name, role: "dtd_content_model", dtdName }
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (node.type === "AttlistDecl") {
|
|
882
|
+
const elementName = this.findDirectChild(node, "Name")?.text.trim();
|
|
883
|
+
if (elementName) {
|
|
884
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
885
|
+
const attDef = node.namedChild(i);
|
|
886
|
+
if (!attDef || attDef.type !== "AttDef") continue;
|
|
887
|
+
const attrNameNode = this.findDirectChild(attDef, "Name");
|
|
888
|
+
const attrName = attrNameNode?.text.trim();
|
|
889
|
+
if (!attrName) continue;
|
|
890
|
+
const defaultDecl = this.findDirectChild(attDef, "DefaultDecl");
|
|
891
|
+
const attrType = attDef.namedChildCount > 0 ? this.findFirstNamedChild(attDef, /* @__PURE__ */ new Set(["StringType", "TokenizedType", "Enumeration", "NotationType"])) : null;
|
|
892
|
+
publish(
|
|
893
|
+
{
|
|
894
|
+
name: attrName,
|
|
895
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
896
|
+
range: this.nodeToRange(attDef),
|
|
897
|
+
visibility: "public",
|
|
898
|
+
ownerName: elementName,
|
|
899
|
+
ownerKind: ASTEngine.SymbolKind.Type,
|
|
900
|
+
metadata: {
|
|
901
|
+
nodeType: "AttDef",
|
|
902
|
+
isDtdAttributeDeclaration: true,
|
|
903
|
+
dtdElement: elementName,
|
|
904
|
+
attributeType: attrType?.text,
|
|
905
|
+
defaultDeclaration: defaultDecl?.text
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
`${elementName}\0${attrName}`
|
|
909
|
+
);
|
|
910
|
+
addRelationship(elementName, attrName, ASTEngine.RelationshipType.Contains, { role: "dtd_attribute_declaration" }, attrNameNode);
|
|
911
|
+
if (defaultDecl) {
|
|
912
|
+
const stack = [defaultDecl];
|
|
913
|
+
while (stack.length > 0) {
|
|
914
|
+
const n = stack.pop();
|
|
915
|
+
if (n.type === "EntityRef") {
|
|
916
|
+
const text = n.text.trim();
|
|
917
|
+
if (text.length > 2 && text.startsWith("&") && text.endsWith(";")) {
|
|
918
|
+
const ref = text.slice(1, -1);
|
|
919
|
+
if (ref.length > 0) {
|
|
920
|
+
entityReferenceCandidates.push({ ownerName: attrName, name: ref, node: n, textNode: n });
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
for (let i2 = n.namedChildCount - 1; i2 >= 0; i2--) {
|
|
926
|
+
const c = n.namedChild(i2);
|
|
927
|
+
if (c) stack.push(c);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
for (const notation of this.descendantsOfType(attDef, "NotationType")) {
|
|
932
|
+
for (const notationName of this.descendantsOfType(notation, "Name")) {
|
|
933
|
+
const target = notationName.text.trim();
|
|
934
|
+
if (!target) continue;
|
|
935
|
+
shadowCandidates.push({
|
|
936
|
+
name: target,
|
|
937
|
+
ownerName: dtdName,
|
|
938
|
+
node: notationName,
|
|
939
|
+
metadata: { isDtdNotation: true, dtdName },
|
|
940
|
+
edge: { source: elementName, role: "dtd_notation_reference", dtdName }
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (node.type === "GEDecl" || node.type === "PEDecl" || node.type === "NotationDecl") {
|
|
948
|
+
const nameNode = this.findDirectChild(node, "Name");
|
|
949
|
+
const name = nameNode?.text.trim();
|
|
950
|
+
const isParameter = node.type === "PEDecl";
|
|
951
|
+
const isNotation = node.type === "NotationDecl";
|
|
952
|
+
if (name) {
|
|
953
|
+
{
|
|
954
|
+
publish(
|
|
955
|
+
{
|
|
956
|
+
name,
|
|
957
|
+
kind: ASTEngine.SymbolKind.Type,
|
|
958
|
+
range: this.nodeToRange(node),
|
|
959
|
+
visibility: "public",
|
|
960
|
+
ownerName: dtdName,
|
|
961
|
+
ownerKind: dtdName ? ASTEngine.SymbolKind.Type : void 0,
|
|
962
|
+
metadata: {
|
|
963
|
+
nodeType: node.type,
|
|
964
|
+
isDtdEntityDeclaration: true,
|
|
965
|
+
isDtdParameterEntity: isParameter,
|
|
966
|
+
isDtdNotationDeclaration: isNotation,
|
|
967
|
+
dtdName
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
dtdName ? `${dtdName}\0${name}` : name
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
declaredNames.add(name);
|
|
974
|
+
if (dtdName) {
|
|
975
|
+
addRelationship(dtdName, name, ASTEngine.RelationshipType.Contains, {
|
|
976
|
+
role: isParameter ? "dtd_parameter_entity_declaration" : isNotation ? "dtd_notation_declaration" : "dtd_entity_declaration",
|
|
977
|
+
dtdName
|
|
978
|
+
}, nameNode);
|
|
979
|
+
}
|
|
980
|
+
const externalId = this.findDirectChild(node, "ExternalID");
|
|
981
|
+
if (externalId) {
|
|
982
|
+
this.extractExternalId(
|
|
983
|
+
externalId,
|
|
984
|
+
name,
|
|
985
|
+
isParameter ? "parameter_entity_external_id" : isNotation ? "notation_external_id" : "entity_external_id",
|
|
986
|
+
addRelationship,
|
|
987
|
+
imports
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
const ndata = this.findDirectChild(node, "NDataDecl");
|
|
991
|
+
const ndataName = ndata ? this.findDirectChild(ndata, "Name") : null;
|
|
992
|
+
if (ndataName) {
|
|
993
|
+
const target = ndataName.text.trim();
|
|
994
|
+
if (target) {
|
|
995
|
+
shadowCandidates.push({
|
|
996
|
+
name: target,
|
|
997
|
+
ownerName: dtdName,
|
|
998
|
+
node: ndataName,
|
|
999
|
+
metadata: { isDtdNotation: true, dtdName },
|
|
1000
|
+
edge: { source: name, role: "dtd_ndata_notation", dtdName }
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
{
|
|
1005
|
+
const value = this.findDirectChild(node, "EntityValue");
|
|
1006
|
+
if (value) {
|
|
1007
|
+
const stack = [value];
|
|
1008
|
+
while (stack.length > 0) {
|
|
1009
|
+
const n = stack.pop();
|
|
1010
|
+
if (n.type === "EntityRef") {
|
|
1011
|
+
const text = n.text.trim();
|
|
1012
|
+
if (text.length > 2 && text.startsWith("&") && text.endsWith(";")) {
|
|
1013
|
+
const ref = text.slice(1, -1);
|
|
1014
|
+
if (ref.length > 0) {
|
|
1015
|
+
entityReferenceCandidates.push({ ownerName: name, name: ref, node: n, textNode: n });
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1021
|
+
const c = n.namedChild(i);
|
|
1022
|
+
if (c) stack.push(c);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (node.type === "PI") {
|
|
1030
|
+
const targetNode = this.findDirectChild(node, "PITarget");
|
|
1031
|
+
const target = targetNode?.text.trim();
|
|
1032
|
+
if (target && target.toLowerCase() !== "xml") {
|
|
1033
|
+
publish(
|
|
1034
|
+
{
|
|
1035
|
+
name: target,
|
|
1036
|
+
kind: ASTEngine.SymbolKind.Module,
|
|
1037
|
+
range: this.nodeToRange(node),
|
|
1038
|
+
visibility: "public",
|
|
1039
|
+
ownerName,
|
|
1040
|
+
ownerKind: ownerName ? ASTEngine.SymbolKind.Class : void 0,
|
|
1041
|
+
metadata: { nodeType: node.type, isProcessingInstruction: true, target }
|
|
1042
|
+
},
|
|
1043
|
+
ownerName ? `${ownerName}\0${target}` : target
|
|
1044
|
+
);
|
|
1045
|
+
if (ownerName) addRelationship(ownerName, target, ASTEngine.RelationshipType.Contains, { role: "processing_instruction" }, targetNode);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (node.type === "StyleSheetPI" || node.type === "XmlModelPI") {
|
|
1049
|
+
const label = node.type === "StyleSheetPI" ? "xml-stylesheet" : "xml-model";
|
|
1050
|
+
publish(
|
|
1051
|
+
{
|
|
1052
|
+
name: label,
|
|
1053
|
+
kind: ASTEngine.SymbolKind.Module,
|
|
1054
|
+
range: this.nodeToRange(node),
|
|
1055
|
+
visibility: "public",
|
|
1056
|
+
metadata: { nodeType: node.type, isProcessingInstruction: true, target: label, pseudoInstruction: true }
|
|
1057
|
+
},
|
|
1058
|
+
label
|
|
1059
|
+
);
|
|
1060
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1061
|
+
const pseudo = node.namedChild(i);
|
|
1062
|
+
if (!pseudo || pseudo.type !== "PseudoAtt") continue;
|
|
1063
|
+
const pseudoNameNode = this.findDirectChild(pseudo, "Name");
|
|
1064
|
+
const valueNode = this.findDirectChild(pseudo, "PseudoAttValue");
|
|
1065
|
+
const attributeName = pseudoNameNode?.text.trim();
|
|
1066
|
+
if (!attributeName) continue;
|
|
1067
|
+
const value = valueNode ? this.unquote(valueNode.text).trim() : void 0;
|
|
1068
|
+
if (attributeName === "href" && value) {
|
|
1069
|
+
const targetClass = this.importTargetClass(value);
|
|
1070
|
+
addRelationship(label, value, ASTEngine.RelationshipType.Imports, {
|
|
1071
|
+
role: "processing_instruction_href",
|
|
1072
|
+
attributeName,
|
|
1073
|
+
resolvable: targetClass,
|
|
1074
|
+
...targetClass === "external" ? { isExternalTarget: true } : {}
|
|
1075
|
+
}, valueNode);
|
|
1076
|
+
imports.push({
|
|
1077
|
+
module: value,
|
|
1078
|
+
path: value,
|
|
1079
|
+
importType: "xml-stylesheet",
|
|
1080
|
+
range: this.nodeToRange(valueNode ?? pseudo),
|
|
1081
|
+
metadata: { instruction: label, attributeName }
|
|
1082
|
+
});
|
|
1083
|
+
} else {
|
|
1084
|
+
publish(
|
|
1085
|
+
{
|
|
1086
|
+
name: attributeName,
|
|
1087
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1088
|
+
range: this.nodeToRange(pseudo),
|
|
1089
|
+
visibility: "public",
|
|
1090
|
+
ownerName: label,
|
|
1091
|
+
ownerKind: ASTEngine.SymbolKind.Module,
|
|
1092
|
+
metadata: { nodeType: pseudo.type, isPseudoAttribute: true, value }
|
|
1093
|
+
},
|
|
1094
|
+
`${label}\0${attributeName}`
|
|
1095
|
+
);
|
|
1096
|
+
addRelationship(label, attributeName, ASTEngine.RelationshipType.Contains, { role: "pseudo_attribute" }, pseudoNameNode);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (node.type === "EntityRef" && ownerName) {
|
|
1101
|
+
const text = node.text.trim();
|
|
1102
|
+
if (text.length > 2 && text.startsWith("&") && text.endsWith(";")) {
|
|
1103
|
+
const name = text.slice(1, -1);
|
|
1104
|
+
if (name.length > 0) entityReferenceCandidates.push({ ownerName: nearestNamedDeclaration ?? ownerName, name, node, textNode: node });
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (node.type === "PEReference") {
|
|
1108
|
+
const text = node.text.trim();
|
|
1109
|
+
if (text.length > 1 && text.startsWith("%")) {
|
|
1110
|
+
const semi = text.indexOf(";");
|
|
1111
|
+
if (semi > 1) {
|
|
1112
|
+
const ref = text.slice(1, semi).trim();
|
|
1113
|
+
const isValueForm = ref.length === 0 || _RealXmlAnalyzer.containsEcmaWhitespace(ref);
|
|
1114
|
+
const inEntityValue = this.withinEntityValue(node);
|
|
1115
|
+
if (!isValueForm && ref.length > 0) {
|
|
1116
|
+
const ownerForRef = inEntityValue ? this.enclosingDeclaredName(node) ?? dtdName ?? "" : dtdName ?? "";
|
|
1117
|
+
if (ownerForRef.length > 0) {
|
|
1118
|
+
parameterEntityReferences.push({
|
|
1119
|
+
ownerName: ownerForRef,
|
|
1120
|
+
name: ref,
|
|
1121
|
+
node,
|
|
1122
|
+
textNode: node,
|
|
1123
|
+
recovery: !!this.withinErrorRegion(node)
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const childDepth = bindDepth ?? 0;
|
|
1131
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
1132
|
+
const child = node.namedChild(i);
|
|
1133
|
+
if (child) work.push({ node: child, ownerName, dtdName, bindDepth: childDepth, nearestNamedDeclaration });
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
for (const candidate of shadowCandidates) {
|
|
1137
|
+
ensureTargetSymbol(candidate.name, candidate.ownerName, ASTEngine.SymbolKind.Type, candidate.node, candidate.metadata);
|
|
1138
|
+
addRelationship(candidate.edge.source, candidate.name, ASTEngine.RelationshipType.Contains, {
|
|
1139
|
+
role: candidate.edge.role,
|
|
1140
|
+
dtdName: candidate.edge.dtdName
|
|
1141
|
+
}, candidate.node);
|
|
1142
|
+
}
|
|
1143
|
+
{
|
|
1144
|
+
const IMPORT_ELEMENTS = /* @__PURE__ */ new Set(["import", "include", "import-schema"]);
|
|
1145
|
+
const stack = [rootNode];
|
|
1146
|
+
while (stack.length) {
|
|
1147
|
+
const n = stack.pop();
|
|
1148
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
1149
|
+
const c = n.child(i);
|
|
1150
|
+
if (c) stack.push(c);
|
|
1151
|
+
}
|
|
1152
|
+
if (n.type !== "element") continue;
|
|
1153
|
+
const tag = n.children.find((c) => c && (c.type === "STag" || c.type === "EmptyElemTag"));
|
|
1154
|
+
if (!tag) continue;
|
|
1155
|
+
const local = this.findDirectChild(tag, "Name")?.text.trim().split(":").pop() ?? "";
|
|
1156
|
+
if (!IMPORT_ELEMENTS.has(local)) continue;
|
|
1157
|
+
let nsUri;
|
|
1158
|
+
let path2;
|
|
1159
|
+
for (let i = 0; i < tag.namedChildCount; i++) {
|
|
1160
|
+
const attr = tag.namedChild(i);
|
|
1161
|
+
if (attr?.type !== "Attribute") continue;
|
|
1162
|
+
const an = this.findDirectChild(attr, "Name")?.text.trim();
|
|
1163
|
+
const av = this.findDirectChild(attr, "AttValue");
|
|
1164
|
+
const value = av ? this.unquote(av.text).trim() : void 0;
|
|
1165
|
+
if (!an || value === void 0) continue;
|
|
1166
|
+
if (an === "namespace") nsUri = value;
|
|
1167
|
+
else if (an === "schemaLocation") {
|
|
1168
|
+
const parts = _RealXmlAnalyzer.splitOnWhitespace(value);
|
|
1169
|
+
path2 = parts.length % 2 === 0 ? parts[parts.length - 1] : parts[0];
|
|
1170
|
+
} else if (an === "href") path2 = value;
|
|
1171
|
+
}
|
|
1172
|
+
if (nsUri) importedNamespaces.set(nsUri, { path: path2, element: local });
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
for (const candidate of referenceCandidates) {
|
|
1176
|
+
if (candidate.bareNameReference) {
|
|
1177
|
+
if (!declaredNames.has(candidate.value)) continue;
|
|
1178
|
+
addRelationship(candidate.ownerName, candidate.value, ASTEngine.RelationshipType.References, {
|
|
1179
|
+
role: "xslt_named_template_reference",
|
|
1180
|
+
attributeName: candidate.attributeName,
|
|
1181
|
+
resolvable: "in-document"
|
|
1182
|
+
}, candidate.valueNode);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
const qname = this.asQName(candidate.value);
|
|
1186
|
+
if (!qname) continue;
|
|
1187
|
+
const resolvableInDocument = declaredNames.has(qname.local);
|
|
1188
|
+
const isBuiltin = candidate.binding !== void 0 && _RealXmlAnalyzer.BUILTIN_NAMESPACE_URIS.has(candidate.binding);
|
|
1189
|
+
const imported = !resolvableInDocument && !isBuiltin && candidate.binding !== void 0 ? importedNamespaces.get(candidate.binding) : void 0;
|
|
1190
|
+
const ownerIsVocabulary = candidate.ownerNs !== void 0 && _RealXmlAnalyzer.DECLARATION_VOCABULARIES[candidate.ownerNs] !== void 0;
|
|
1191
|
+
const isBareValue = qname.prefix.length === 0;
|
|
1192
|
+
const unresolvedBareReference = !resolvableInDocument && !isBuiltin && !imported && isBareValue && ownerIsVocabulary;
|
|
1193
|
+
if (!resolvableInDocument && !isBuiltin && !imported && !unresolvedBareReference) continue;
|
|
1194
|
+
const ambiguousWithBuiltin = unresolvedBareReference && _RealXmlAnalyzer.XSD_BUILTIN_TYPE_NAMES.has(qname.local);
|
|
1195
|
+
if (resolvableInDocument) {
|
|
1196
|
+
ensureTargetSymbol(qname.local, void 0, ASTEngine.SymbolKind.Type, candidate.nameNode, { isReferencedDeclaration: true });
|
|
1197
|
+
}
|
|
1198
|
+
const claimsExternalTarget = isBuiltin || !resolvableInDocument && !unresolvedBareReference;
|
|
1199
|
+
addRelationship(candidate.ownerName, qname.local, ASTEngine.RelationshipType.References, {
|
|
1200
|
+
role: "attribute_qname_reference",
|
|
1201
|
+
attributeName: candidate.attributeName,
|
|
1202
|
+
qname: candidate.value,
|
|
1203
|
+
prefix: qname.prefix,
|
|
1204
|
+
namespaceUri: candidate.binding,
|
|
1205
|
+
resolvable: isBuiltin ? "builtin" : resolvableInDocument ? "in-document" : unresolvedBareReference ? "unresolved" : "external",
|
|
1206
|
+
...claimsExternalTarget ? { isExternalTarget: true } : {},
|
|
1207
|
+
// X13: the name is also a built-in, and this file cannot say whether the
|
|
1208
|
+
// document means the built-in or a same-named type an `xs:include` brought
|
|
1209
|
+
// in. Recorded so a consumer can see the ambiguity instead of inheriting
|
|
1210
|
+
// whichever reading the graph happened to take.
|
|
1211
|
+
...ambiguousWithBuiltin ? { ambiguousWithBuiltin: true } : {},
|
|
1212
|
+
// where the name actually lives, so a consumer can follow it rather than
|
|
1213
|
+
// having to guess from the namespace alone
|
|
1214
|
+
...imported ? { importedFrom: imported.path, importedBy: imported.element } : {}
|
|
1215
|
+
}, candidate.valueNode);
|
|
1216
|
+
}
|
|
1217
|
+
for (const cand of callCandidates) {
|
|
1218
|
+
if (!declaredTemplateNames.has(cand.templateName)) continue;
|
|
1219
|
+
addRelationship(cand.ownerName, cand.templateName, ASTEngine.RelationshipType.Calls, {
|
|
1220
|
+
role: "xslt_named_template_call",
|
|
1221
|
+
attributeName: "name",
|
|
1222
|
+
templateName: cand.templateName,
|
|
1223
|
+
// A name resolved against the stylesheet's own declarations, which is what
|
|
1224
|
+
// makes this edge different from a mention. XSLT 2.0 §6.1.3 adds
|
|
1225
|
+
// `xsl:use-when`/`use-unless` to conditional dispatch, and neither can change
|
|
1226
|
+
// WHICH template is invoked.
|
|
1227
|
+
resolvable: "in-document"
|
|
1228
|
+
}, cand.textNode);
|
|
1229
|
+
}
|
|
1230
|
+
{
|
|
1231
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1232
|
+
for (const t of competingTemplates) {
|
|
1233
|
+
const arr = byKey.get(t.key) ?? [];
|
|
1234
|
+
arr.push(t);
|
|
1235
|
+
byKey.set(t.key, arr);
|
|
1236
|
+
}
|
|
1237
|
+
for (const group of byKey.values()) {
|
|
1238
|
+
if (group.length < 2) continue;
|
|
1239
|
+
const declared = group.map((t) => t.declaredName);
|
|
1240
|
+
const useDeclaredNames = declared.every((d) => d.length > 0) && new Set(declared).size === declared.length;
|
|
1241
|
+
const identityOf = (t, index) => {
|
|
1242
|
+
if (useDeclaredNames) return t.declaredName;
|
|
1243
|
+
const derived = `xsl:template[match=${t.match}|mode=${t.mode}|priority=${t.priority}]#${index}`;
|
|
1244
|
+
ensureTargetSymbol(derived, t.ownerName, ASTEngine.SymbolKind.Class, t.anchorNode, {
|
|
1245
|
+
nodeType: "element",
|
|
1246
|
+
tagName: "xsl:template",
|
|
1247
|
+
isXmlElement: true,
|
|
1248
|
+
// Marked, so a consumer can tell a derived identity from a declared one
|
|
1249
|
+
// instead of inferring it from the punctuation.
|
|
1250
|
+
derivedTemplateIdentity: true,
|
|
1251
|
+
// The `name` attribute is carried here because a colliding pair usually
|
|
1252
|
+
// shares one, or has none: dropping it would lose the only name the
|
|
1253
|
+
// document actually wrote.
|
|
1254
|
+
...t.declaredName.length > 0 ? { declaredName: t.declaredName } : {},
|
|
1255
|
+
templateMatch: t.match,
|
|
1256
|
+
templateMode: t.mode,
|
|
1257
|
+
templatePriority: t.priority,
|
|
1258
|
+
templateOccurrenceInGroup: index
|
|
1259
|
+
});
|
|
1260
|
+
return derived;
|
|
1261
|
+
};
|
|
1262
|
+
const identities = group.map(identityOf);
|
|
1263
|
+
for (let i = 1; i < group.length; i++) {
|
|
1264
|
+
const winner = group[i];
|
|
1265
|
+
const source = identities[i];
|
|
1266
|
+
const target = identities[i - 1];
|
|
1267
|
+
if (source.length === 0 || target.length === 0 || source === target) continue;
|
|
1268
|
+
addRelationship(source, target, ASTEngine.RelationshipType.Overrides, {
|
|
1269
|
+
role: "xslt_competing_template_rule",
|
|
1270
|
+
// The three attributes §5.5 compares, carried so a consumer can re-derive
|
|
1271
|
+
// the collision instead of trusting the edge.
|
|
1272
|
+
templateMatch: winner.match,
|
|
1273
|
+
templateMode: winner.mode,
|
|
1274
|
+
templatePriority: winner.priority,
|
|
1275
|
+
derivation: "xslt-5.5-later-template-replaces-earlier",
|
|
1276
|
+
resolvable: "in-document",
|
|
1277
|
+
...winner.declaredName.length > 0 ? { declaredName: winner.declaredName } : {}
|
|
1278
|
+
}, winner.anchorNode);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
for (const cand of derivationCandidates) {
|
|
1283
|
+
const qname = this.asQName(cand.value);
|
|
1284
|
+
if (!qname) continue;
|
|
1285
|
+
const resolvableInDocument = declaredNames.has(qname.local);
|
|
1286
|
+
const isBuiltin = cand.binding !== void 0 && _RealXmlAnalyzer.BUILTIN_NAMESPACE_URIS.has(cand.binding);
|
|
1287
|
+
const imported = !resolvableInDocument && !isBuiltin && cand.binding !== void 0 ? importedNamespaces.get(cand.binding) : void 0;
|
|
1288
|
+
const ownerIsVocabulary = cand.ownerNs !== void 0 && _RealXmlAnalyzer.DECLARATION_VOCABULARIES[cand.ownerNs] !== void 0;
|
|
1289
|
+
const isBareValue = qname.prefix.length === 0;
|
|
1290
|
+
const unresolvedBare = !resolvableInDocument && !isBuiltin && !imported && isBareValue && ownerIsVocabulary;
|
|
1291
|
+
if (!resolvableInDocument && !isBuiltin && !imported && !unresolvedBare) continue;
|
|
1292
|
+
if (resolvableInDocument) {
|
|
1293
|
+
ensureTargetSymbol(qname.local, void 0, ASTEngine.SymbolKind.Type, cand.nameNode, { isReferencedDeclaration: true });
|
|
1294
|
+
}
|
|
1295
|
+
const claimsExternalTarget = isBuiltin || !resolvableInDocument && !unresolvedBare;
|
|
1296
|
+
addRelationship(cand.ownerName, qname.local, ASTEngine.RelationshipType.Extends, {
|
|
1297
|
+
role: "xsd_derivation_base",
|
|
1298
|
+
attributeName: "base",
|
|
1299
|
+
qname: cand.value,
|
|
1300
|
+
prefix: qname.prefix,
|
|
1301
|
+
namespaceUri: cand.binding,
|
|
1302
|
+
// WHICH derivation, because XSD has two and they narrow in opposite
|
|
1303
|
+
// directions: `extension` keeps everything the base admits and adds to it,
|
|
1304
|
+
// `restriction` admits a subset. The edge is the same subtype relation in
|
|
1305
|
+
// both cases and this is what tells a consumer which way.
|
|
1306
|
+
derivation: cand.kind,
|
|
1307
|
+
resolvable: isBuiltin ? "builtin" : resolvableInDocument ? "in-document" : unresolvedBare ? "unresolved" : "external",
|
|
1308
|
+
...claimsExternalTarget ? { isExternalTarget: true } : {},
|
|
1309
|
+
...unresolvedBare && _RealXmlAnalyzer.XSD_BUILTIN_TYPE_NAMES.has(qname.local) ? { ambiguousWithBuiltin: true } : {},
|
|
1310
|
+
...imported ? { importedFrom: imported.path, importedBy: imported.element } : {}
|
|
1311
|
+
}, cand.valueNode);
|
|
1312
|
+
}
|
|
1313
|
+
for (const cand of compositionCandidates) {
|
|
1314
|
+
const qname = this.asQName(cand.value);
|
|
1315
|
+
if (!qname) continue;
|
|
1316
|
+
const resolvableInDocument = declaredNames.has(qname.local);
|
|
1317
|
+
const isBuiltin = cand.binding !== void 0 && _RealXmlAnalyzer.BUILTIN_NAMESPACE_URIS.has(cand.binding);
|
|
1318
|
+
const imported = !resolvableInDocument && !isBuiltin && cand.binding !== void 0 ? importedNamespaces.get(cand.binding) : void 0;
|
|
1319
|
+
if (!resolvableInDocument && !isBuiltin && !imported) continue;
|
|
1320
|
+
if (resolvableInDocument) {
|
|
1321
|
+
ensureTargetSymbol(qname.local, void 0, ASTEngine.SymbolKind.Type, cand.nameNode, { isReferencedDeclaration: true });
|
|
1322
|
+
}
|
|
1323
|
+
addRelationship(cand.ownerName, qname.local, ASTEngine.RelationshipType.MixesIn, {
|
|
1324
|
+
role: "xsd_definition_composition",
|
|
1325
|
+
attributeName: "ref",
|
|
1326
|
+
qname: cand.value,
|
|
1327
|
+
prefix: qname.prefix,
|
|
1328
|
+
namespaceUri: cand.binding,
|
|
1329
|
+
// WHICH carrier, because the two constructs compose different things: a group
|
|
1330
|
+
// composes a content model, an attributeGroup composes attribute declarations.
|
|
1331
|
+
// A consumer filtering on the kind does not have to re-derive it from the tag.
|
|
1332
|
+
composedKind: cand.kind,
|
|
1333
|
+
resolvable: isBuiltin ? "builtin" : resolvableInDocument ? "in-document" : "external",
|
|
1334
|
+
...isBuiltin || !resolvableInDocument ? { isExternalTarget: true } : {},
|
|
1335
|
+
...imported ? { importedFrom: imported.path, importedBy: imported.element } : {}
|
|
1336
|
+
}, cand.valueNode);
|
|
1337
|
+
}
|
|
1338
|
+
for (const cand of entityReferenceCandidates) {
|
|
1339
|
+
const declared = declaredNames.has(cand.name);
|
|
1340
|
+
const builtin = _RealXmlAnalyzer.PREDEFINED_ENTITIES.has(cand.name);
|
|
1341
|
+
addRelationship(cand.ownerName, cand.name, ASTEngine.RelationshipType.References, {
|
|
1342
|
+
role: "entity_reference",
|
|
1343
|
+
entityName: cand.name,
|
|
1344
|
+
resolvable: declared ? "in-document" : builtin ? "builtin" : "unresolved",
|
|
1345
|
+
...declared || builtin ? {} : { danglingEntityReference: true },
|
|
1346
|
+
// `builtin` IS the external claim here: the five predefined entities are not declarations
|
|
1347
|
+
// of this population (or of any document), and a bare identifier is verified by
|
|
1348
|
+
// `verifyExternalClaim` exactly when the key space does not declare it. `unresolved`
|
|
1349
|
+
// (a dangling entity reference) is deliberately NOT marked — the analyzer has not
|
|
1350
|
+
// measured that it is outside the population, only that this file does not declare it.
|
|
1351
|
+
...builtin && !declared ? { isExternalTarget: true } : {}
|
|
1352
|
+
}, cand.textNode);
|
|
1353
|
+
}
|
|
1354
|
+
for (const cand of parameterEntityReferences) {
|
|
1355
|
+
const declared = declaredNames.has(cand.name);
|
|
1356
|
+
addRelationship(cand.ownerName, cand.name, ASTEngine.RelationshipType.References, {
|
|
1357
|
+
role: "parameter_entity_reference",
|
|
1358
|
+
entityName: cand.name,
|
|
1359
|
+
isParameterEntity: true,
|
|
1360
|
+
resolvable: declared ? "in-document" : "unresolved",
|
|
1361
|
+
// Kept and LABELLED rather than dropped, which is the recovery-labelling
|
|
1362
|
+
// contract `xml/recovery-labelling` already pins for symbols and edges out
|
|
1363
|
+
// of a damaged file. libxml2's `test/errors/*` is a corpus of deliberately
|
|
1364
|
+
// malformed documents; a parameter reference recovered out of one is a claim
|
|
1365
|
+
// the bytes make that no processor would honour, and saying so is more
|
|
1366
|
+
// useful than silence.
|
|
1367
|
+
...cand.recovery ? { recovery: true, reasonCode: "parameter-entity-reference-in-damaged-region" } : {},
|
|
1368
|
+
...declared ? {} : { danglingEntityReference: true }
|
|
1369
|
+
}, cand.textNode);
|
|
1370
|
+
}
|
|
1371
|
+
const hasSyntaxErrors = errorNodes > 0 || missingNodes > 0;
|
|
1372
|
+
if (hasSyntaxErrors) {
|
|
1373
|
+
for (const symbol of symbols) symbol.metadata.syntaxErrors = true;
|
|
1374
|
+
for (const relationship of relationships) relationship.metadata.syntaxErrors = true;
|
|
1375
|
+
}
|
|
1376
|
+
{
|
|
1377
|
+
const isPlaceholder = (name) => name === _RealXmlAnalyzer.PLACEHOLDER_LITERAL;
|
|
1378
|
+
const keptSymbols = symbols.filter((s) => !isPlaceholder(String(s.name ?? "")));
|
|
1379
|
+
const keptRelationships = relationships.filter((x) => !isPlaceholder(String(x.source ?? "")) && !isPlaceholder(String(x.target ?? "")));
|
|
1380
|
+
const droppedSymbols = symbols.length - keptSymbols.length;
|
|
1381
|
+
const droppedRelationships = relationships.length - keptRelationships.length;
|
|
1382
|
+
if (droppedSymbols > 0 || droppedRelationships > 0) {
|
|
1383
|
+
this.qualifySameFileTier(keptSymbols, keptRelationships);
|
|
1384
|
+
this.attachFileClaims(keptRelationships, filePath, context);
|
|
1385
|
+
return {
|
|
1386
|
+
language: "xml",
|
|
1387
|
+
symbols: keptSymbols,
|
|
1388
|
+
relationships: keptRelationships,
|
|
1389
|
+
imports,
|
|
1390
|
+
exports: [],
|
|
1391
|
+
errors,
|
|
1392
|
+
metadata: {
|
|
1393
|
+
complexity: keptSymbols.length + keptRelationships.length,
|
|
1394
|
+
linesOfCode: content.split("\n").length,
|
|
1395
|
+
cyclomaticComplexity: 1,
|
|
1396
|
+
patterns: [],
|
|
1397
|
+
framework: "XML",
|
|
1398
|
+
errorNodes,
|
|
1399
|
+
missingNodes,
|
|
1400
|
+
syntaxErrors: hasSyntaxErrors,
|
|
1401
|
+
placeholderLiteralRefusals: droppedSymbols + droppedRelationships
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
this.qualifySameFileTier(symbols, relationships);
|
|
1407
|
+
this.attachFileClaims(relationships, filePath, context);
|
|
1408
|
+
return {
|
|
1409
|
+
language: "xml",
|
|
1410
|
+
symbols,
|
|
1411
|
+
relationships,
|
|
1412
|
+
imports,
|
|
1413
|
+
exports: [],
|
|
1414
|
+
errors,
|
|
1415
|
+
metadata: {
|
|
1416
|
+
complexity: symbols.length + relationships.length,
|
|
1417
|
+
linesOfCode: content.split("\n").length,
|
|
1418
|
+
cyclomaticComplexity: 1,
|
|
1419
|
+
patterns: [],
|
|
1420
|
+
framework: "XML",
|
|
1421
|
+
errorNodes,
|
|
1422
|
+
missingNodes,
|
|
1423
|
+
syntaxErrors: hasSyntaxErrors
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* `<!DOCTYPE a PUBLIC "-//X//DTD A//EN" "a.dtd">` spans lines: `PUBLIC` and the
|
|
1429
|
+
* public id are on one, the SYSTEM path on another. The edge target is the
|
|
1430
|
+
* path, so the edge's line is the LITERAL's line (M59.A7 / #825) — passing the
|
|
1431
|
+
* `ExternalID` node instead points the edge at the `SYSTEM` keyword's line,
|
|
1432
|
+
* which is the line of no name in the edge.
|
|
1433
|
+
*
|
|
1434
|
+
* A `SystemLiteral` is REQUIRED and a `PubidLiteral` is not a substitute. The two are
|
|
1435
|
+
* different things and only one of them is a file: `SYSTEM "graphic.gif"` names a path
|
|
1436
|
+
* a parser opens, `PUBLIC "GIF"` names a symbolic identifier a parser matches against a
|
|
1437
|
+
* catalogue. This read was `systemLiteral || pubidLiteral`, so the second stood in for
|
|
1438
|
+
* the first and a public identifier became an edge target — the fabricated
|
|
1439
|
+
* `GIF -imports-> GIF` in `libxslt/tests/general/bug-52.xml`, at `confidence: 1`, on a
|
|
1440
|
+
* tier table that maps `notation_external_id` to `import_based`.
|
|
1441
|
+
*
|
|
1442
|
+
* The invariant lives HERE rather than only at the one caller that broke it, because
|
|
1443
|
+
* this is the single point where the target string is created: any future caller that
|
|
1444
|
+
* hands this a `PublicID` now gets nothing instead of an edge, which is the correct
|
|
1445
|
+
* outcome for a node that carries no file. Measured by
|
|
1446
|
+
* `validation/m14/fixtures/xml/public-id-census.mts` — 298 system-literal nodes and 298
|
|
1447
|
+
* `externalIdKind: "system"` edges agree exactly, so nothing legitimate is lost.
|
|
1448
|
+
*/
|
|
1449
|
+
extractExternalId(externalId, owner, role, addRelationship, imports) {
|
|
1450
|
+
const literal = this.findDirectChild(externalId, "SystemLiteral");
|
|
1451
|
+
if (!literal) return;
|
|
1452
|
+
const path2 = this.unquote(literal.text);
|
|
1453
|
+
if (!path2) return;
|
|
1454
|
+
imports.push({
|
|
1455
|
+
module: path2,
|
|
1456
|
+
path: path2,
|
|
1457
|
+
importType: "xml-doctype",
|
|
1458
|
+
range: this.nodeToRange(literal),
|
|
1459
|
+
metadata: { isDtdExternalId: true, role, systemId: true }
|
|
1460
|
+
});
|
|
1461
|
+
const targetClass = this.importTargetClass(path2);
|
|
1462
|
+
addRelationship(owner, path2, ASTEngine.RelationshipType.Imports, {
|
|
1463
|
+
role,
|
|
1464
|
+
externalIdKind: "system",
|
|
1465
|
+
resolvable: targetClass,
|
|
1466
|
+
// A DOCTYPE whose SYSTEM identifier is a URI names a DTD outside this project — the largest
|
|
1467
|
+
// single group of this lane's unresolved edges is exactly this shape
|
|
1468
|
+
// (`http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd`). Path-like, so the owner check
|
|
1469
|
+
// returns externalUnverified and can never refute; a relative `file` path is left unmarked.
|
|
1470
|
+
...targetClass === "external" ? { isExternalTarget: true } : {}
|
|
1471
|
+
}, literal);
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* A correctly-parsed declaration's name is its FIRST named child. If it is
|
|
1475
|
+
* not, the node is a recovery shape and any `Name` beneath it may be a
|
|
1476
|
+
* fragment of a construct the parser did not understand — publishing it would
|
|
1477
|
+
* put a name in the graph that is not a declaration. The pre-repair grammar
|
|
1478
|
+
* produced exactly this for a conditional section: the DTD's name came out as
|
|
1479
|
+
* the literal text `INCLUDE`.
|
|
1480
|
+
*/
|
|
1481
|
+
isPositionedDeclarationName(node, nameNode) {
|
|
1482
|
+
if (!nameNode) return false;
|
|
1483
|
+
const firstNamed = node.namedChild(0);
|
|
1484
|
+
return Boolean(firstNamed) && firstNamed.id === nameNode.id;
|
|
1485
|
+
}
|
|
1486
|
+
/** Reads the `xmlns` / `xmlns:prefix` attributes of a tag into the binding map. */
|
|
1487
|
+
rememberNamespaces(tagNode, bindings) {
|
|
1488
|
+
for (let i = 0; i < tagNode.namedChildCount; i++) {
|
|
1489
|
+
const child = tagNode.namedChild(i);
|
|
1490
|
+
if (!child || child.type !== "Attribute") continue;
|
|
1491
|
+
const attributeName = this.findDirectChild(child, "Name")?.text.trim();
|
|
1492
|
+
if (attributeName !== "xmlns" && !attributeName?.startsWith("xmlns:")) continue;
|
|
1493
|
+
const valueNode = this.findDirectChild(child, "AttValue");
|
|
1494
|
+
if (!valueNode) continue;
|
|
1495
|
+
const prefix = attributeName === "xmlns" ? "" : attributeName.slice(6);
|
|
1496
|
+
if (attributeName !== "xmlns" && prefix.length === 0) continue;
|
|
1497
|
+
bindings.set(prefix, this.unquote(valueNode.text));
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
extractAttribute(node, ownerName, symbols, addRelationship, publishedNames, declaredNames, namespaceBindings, referenceCandidates, ensureTargetSymbol, ownerLocalName, ownerNamespace, callCandidates, derivationCandidates, compositionCandidates, elementOwnerName, nearestNamedDeclaration, selfNamedDeclaration) {
|
|
1501
|
+
const ownerNs = ownerNamespace;
|
|
1502
|
+
const namedRefs = ownerNamespace ? _RealXmlAnalyzer.NAMED_REFERENCE_ELEMENTS[ownerNamespace] : void 0;
|
|
1503
|
+
this.bareNameReference = namedRefs ? namedRefs.has(ownerLocalName) : false;
|
|
1504
|
+
this.xsltCallElement = ownerNamespace === "http://www.w3.org/1999/XSL/Transform" && _RealXmlAnalyzer.XSLT_CALL_ELEMENTS.has(ownerLocalName);
|
|
1505
|
+
this.xsdDerivationElement = ownerNamespace === "http://www.w3.org/2001/XMLSchema" && (ownerLocalName === "extension" || ownerLocalName === "restriction");
|
|
1506
|
+
this.derivationKind = ownerLocalName === "extension" ? "extension" : "restriction";
|
|
1507
|
+
this.bareNameDeclarationElement = ownerNamespace === "http://relaxng.org/ns/structure/1.0" && (ownerLocalName === "element" || ownerLocalName === "attribute" || ownerLocalName === "define");
|
|
1508
|
+
const nameNode = this.findDirectChild(node, "Name");
|
|
1509
|
+
const valueNode = this.findDirectChild(node, "AttValue");
|
|
1510
|
+
const attributeName = nameNode?.text.trim();
|
|
1511
|
+
if (!attributeName) return;
|
|
1512
|
+
const value = valueNode ? this.unquote(valueNode.text) : void 0;
|
|
1513
|
+
const isNamespace = attributeName === "xmlns" || attributeName.startsWith("xmlns:");
|
|
1514
|
+
const namespacePrefix = attributeName === "xmlns" ? "default" : attributeName.slice("xmlns:".length);
|
|
1515
|
+
if (!attributeName) return;
|
|
1516
|
+
if (isNamespace && namespacePrefix.length === 0) return;
|
|
1517
|
+
const colon = isNamespace ? -1 : attributeName.indexOf(":");
|
|
1518
|
+
const attributePrefix = colon > 0 ? attributeName.slice(0, colon) : void 0;
|
|
1519
|
+
const attributeLocalName = colon > 0 ? attributeName.slice(colon + 1) : attributeName;
|
|
1520
|
+
if (colon > 0 && attributeLocalName.length === 0) return;
|
|
1521
|
+
const name = isNamespace ? namespacePrefix : attributeLocalName;
|
|
1522
|
+
const kind = isNamespace ? ASTEngine.SymbolKind.Namespace : ASTEngine.SymbolKind.Property;
|
|
1523
|
+
if (attributePrefix && namespaceBindings.has(attributePrefix)) {
|
|
1524
|
+
addRelationship(ownerName, attributePrefix, ASTEngine.RelationshipType.Uses, {
|
|
1525
|
+
role: "xml_namespace_prefix_use",
|
|
1526
|
+
namespaceUri: namespaceBindings.get(attributePrefix),
|
|
1527
|
+
resolvable: "in-document"
|
|
1528
|
+
}, nameNode);
|
|
1529
|
+
}
|
|
1530
|
+
symbols.push({
|
|
1531
|
+
name,
|
|
1532
|
+
kind,
|
|
1533
|
+
range: this.nodeToRange(node),
|
|
1534
|
+
visibility: "public",
|
|
1535
|
+
ownerName,
|
|
1536
|
+
ownerKind: ASTEngine.SymbolKind.Class,
|
|
1537
|
+
// M60: an attribute is a declaration OWNED BY the element it is written on,
|
|
1538
|
+
// and `pool-worker.ts:930` defaults `qualifiedName` to the bare name when the
|
|
1539
|
+
// analyzer leaves it unset. So `svg/@fill` and `path/@fill` were both
|
|
1540
|
+
// published under the single key `fill` and the store could not tell two
|
|
1541
|
+
// declarations of two elements apart (`parallel-indexer.ts:3038` writes
|
|
1542
|
+
// `symbol.qualifiedName || symbol.name`). Measured on the 600-file draw
|
|
1543
|
+
// `099e7a868f7b73ee`: 83,454 `property` symbols and 1,002 `namespace` symbols
|
|
1544
|
+
// carried a qualified name equal to their bare name.
|
|
1545
|
+
//
|
|
1546
|
+
// The same expression already used for elements at `:790` and `:920`, so this
|
|
1547
|
+
// is one convention in one analyzer rather than a second identity scheme.
|
|
1548
|
+
//
|
|
1549
|
+
// WHAT IT DOES NOT FIX, stated rather than implied: two instances of the SAME
|
|
1550
|
+
// tag (`<path fill>` twice) still publish `path.fill` twice, because a
|
|
1551
|
+
// qualified name is unique within a scope and the distinguishing information
|
|
1552
|
+
// — ancestry and position — is not in the published name. Measured residual
|
|
1553
|
+
// for this edit: 2,195 of 14,081 ambiguous edges repaired, 11,886 left
|
|
1554
|
+
// (`validation/m14/measurements/xml/qname-population-after-d1.json`).
|
|
1555
|
+
qualifiedName: ownerName ? `${ownerName}.${name}` : name,
|
|
1556
|
+
metadata: {
|
|
1557
|
+
nodeType: node.type,
|
|
1558
|
+
isXmlAttribute: !isNamespace,
|
|
1559
|
+
isXmlNamespace: isNamespace,
|
|
1560
|
+
// The name AS WRITTEN, prefix included. The symbol's `name` is the local
|
|
1561
|
+
// part, so this is the only place the prefix survives in the analyzer's
|
|
1562
|
+
// output — see the comment on `attributeLocalName` above.
|
|
1563
|
+
attributeName,
|
|
1564
|
+
attributeLocalName,
|
|
1565
|
+
value,
|
|
1566
|
+
namespacePrefix: isNamespace ? namespacePrefix : attributePrefix,
|
|
1567
|
+
namespaceUri: isNamespace ? value : void 0
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
if (!publishedNames.has(`${ownerName}\0${name}`)) publishedNames.add(`${ownerName}\0${name}`);
|
|
1571
|
+
addRelationship(ownerName, name, ASTEngine.RelationshipType.Contains, {
|
|
1572
|
+
role: isNamespace ? "xml_namespace" : "xml_attribute",
|
|
1573
|
+
value,
|
|
1574
|
+
// M59 (#825): the name as WRITTEN. For `xmlns="…"` the edge's target is the
|
|
1575
|
+
// synthesised element name `default` (the default namespace has no written
|
|
1576
|
+
// prefix), which appears nowhere in the source — so a source check on the
|
|
1577
|
+
// target is unfixable by construction. Carrying `xmlns` (or `xmlns:pfx`)
|
|
1578
|
+
// lets the claim be checked against what is actually on the line.
|
|
1579
|
+
attributeName
|
|
1580
|
+
}, nameNode);
|
|
1581
|
+
if (isNamespace || value === void 0) return;
|
|
1582
|
+
if (attributeName === "schemaLocation") {
|
|
1583
|
+
const parts = _RealXmlAnalyzer.splitOnWhitespace(value);
|
|
1584
|
+
const paired = parts.length % 2 === 0 && parts.length >= 2;
|
|
1585
|
+
let emitted = 0;
|
|
1586
|
+
for (let i = paired ? 1 : 0; i < parts.length; i += paired ? 2 : 1) {
|
|
1587
|
+
const target = parts[i];
|
|
1588
|
+
if (!target || this.isRemoteUri(target)) continue;
|
|
1589
|
+
addRelationship(ownerName, target, ASTEngine.RelationshipType.Imports, {
|
|
1590
|
+
role: "schema_location",
|
|
1591
|
+
attributeName,
|
|
1592
|
+
namespace: paired ? parts[i - 1] : void 0,
|
|
1593
|
+
resolvable: this.importTargetClass(target)
|
|
1594
|
+
}, valueNode);
|
|
1595
|
+
emitted++;
|
|
1596
|
+
}
|
|
1597
|
+
if (emitted > 0) {
|
|
1598
|
+
const last = symbols[symbols.length - 1];
|
|
1599
|
+
if (last && last.name === attributeName && last.ownerName === ownerName) {
|
|
1600
|
+
last.metadata.schemaLocationTargets = parts.filter((_x, i) => paired ? i % 2 === 1 : true);
|
|
1601
|
+
}
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (this.bareNameReference) {
|
|
1606
|
+
if (!nameNode || !valueNode) return;
|
|
1607
|
+
referenceCandidates.push({
|
|
1608
|
+
ownerName,
|
|
1609
|
+
attributeName,
|
|
1610
|
+
value,
|
|
1611
|
+
nameNode,
|
|
1612
|
+
valueNode,
|
|
1613
|
+
binding: void 0,
|
|
1614
|
+
ownerNs,
|
|
1615
|
+
bareNameReference: true
|
|
1616
|
+
});
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (this.xsltCallElement && attributeName === "name" && valueNode) {
|
|
1620
|
+
const colon2 = value.indexOf(":");
|
|
1621
|
+
const local = colon2 >= 0 ? value.slice(colon2 + 1) : value;
|
|
1622
|
+
if (local.length > 0 && local.length <= 256 && nearestNamedDeclaration) {
|
|
1623
|
+
callCandidates.push({ ownerName: nearestNamedDeclaration, templateName: local, valueNode: node, textNode: valueNode });
|
|
1624
|
+
}
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const collapsed = _RealXmlAnalyzer.collapseWhitespace(value);
|
|
1628
|
+
const qname = this.asQName(collapsed);
|
|
1629
|
+
if (!qname || !nameNode || !valueNode) return;
|
|
1630
|
+
if (!qname.prefix && !_RealXmlAnalyzer.REFERENCE_ATTRIBUTES.has(attributeName)) return;
|
|
1631
|
+
if (!qname.prefix && this.bareNameDeclarationElement) return;
|
|
1632
|
+
const declared = namespaceBindings.get(qname.prefix);
|
|
1633
|
+
const binding = declared ?? _RealXmlAnalyzer.IMPLICIT_PREFIX_BINDINGS[qname.prefix];
|
|
1634
|
+
if (this.xsdDerivationElement && attributeName === "base") {
|
|
1635
|
+
derivationCandidates.push({
|
|
1636
|
+
// `nearestNamedDeclaration`, NOT `ownerName`. `ownerName` is the derivation
|
|
1637
|
+
// ELEMENT's own symbol, which for `<xs:restriction>` is the tag text — measured at
|
|
1638
|
+
// 438 of 438 corpus edges naming a vocabulary constructor instead of a declared
|
|
1639
|
+
// type. See the NEAREST NAMED SCHEMA DECLARATION block in the walk above.
|
|
1640
|
+
ownerName: nearestNamedDeclaration ?? "",
|
|
1641
|
+
value: collapsed,
|
|
1642
|
+
binding,
|
|
1643
|
+
ownerNs,
|
|
1644
|
+
kind: this.derivationKind,
|
|
1645
|
+
valueNode,
|
|
1646
|
+
nameNode
|
|
1647
|
+
});
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (attributeName === "ref" && ownerNs === "http://www.w3.org/2001/XMLSchema" && (ownerLocalName === "group" || ownerLocalName === "attributeGroup")) {
|
|
1651
|
+
compositionCandidates.push({
|
|
1652
|
+
// Same rule as derivation: the composed-INTO type is the nearest named
|
|
1653
|
+
// declaration above the carrier — the carrier itself is `xs:group`, a vocabulary
|
|
1654
|
+
// tag no document declares. `selfNamedDeclaration` is undefined here in practice
|
|
1655
|
+
// (a composing group/attributeGroup has no `name=` of its own), and the nearest
|
|
1656
|
+
// named ancestor is the type or nesting group the definitions become part of.
|
|
1657
|
+
ownerName: selfNamedDeclaration ?? nearestNamedDeclaration ?? ownerName,
|
|
1658
|
+
value: collapsed,
|
|
1659
|
+
binding,
|
|
1660
|
+
kind: ownerLocalName,
|
|
1661
|
+
valueNode,
|
|
1662
|
+
nameNode
|
|
1663
|
+
});
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
referenceCandidates.push({
|
|
1667
|
+
// THE SOURCE OF A REFERENCE EDGE. This is the third edge type to need it, and the
|
|
1668
|
+
// defect is the same one the other two had: `ownerName` is the element's OWN symbol,
|
|
1669
|
+
// which is its declared name when the vocabulary writes one on `name=` and its TAG
|
|
1670
|
+
// TEXT when it does not. A tag text is not an entity a document declares.
|
|
1671
|
+
//
|
|
1672
|
+
// From FSharp.Data/docs/data/po.xsd, verbatim:
|
|
1673
|
+
//
|
|
1674
|
+
// "xs:element" -> "comment" (role=attribute_qname_reference)
|
|
1675
|
+
//
|
|
1676
|
+
// `<xs:element ref="comment" minOccurs="0"/>` sits inside
|
|
1677
|
+
// `<xs:complexType name="PurchaseOrderType">`. The reference is made BY that type's
|
|
1678
|
+
// content model, so the source is `PurchaseOrderType`; the analyzer published
|
|
1679
|
+
// `xs:element`, which is the schema vocabulary's word for a type CONSTRUCTOR and a
|
|
1680
|
+
// symbol no document declares. Measured at 1,066 of 10,411 `references` edges on the
|
|
1681
|
+
// gate's own sample — see `docs/analyzers/xml.md`.
|
|
1682
|
+
//
|
|
1683
|
+
// The rule, and it is deliberately two lines rather than one:
|
|
1684
|
+
//
|
|
1685
|
+
// self declared the carrier HAS a name of its own, so the reference belongs
|
|
1686
|
+
// to that declaration — `<xs:element name="e" type="T"/>` is
|
|
1687
|
+
// `e -> T`, and this branch leaves those edges exactly as they
|
|
1688
|
+
// were. It is also why `nearestNamedDeclaration` must not be
|
|
1689
|
+
// consulted here: for that element the enclosing scope is not
|
|
1690
|
+
// the declarer.
|
|
1691
|
+
// nearest, else tag the carrier has NO name, so the declarer is the nearest
|
|
1692
|
+
// named ancestor, and the tag is kept when there is none. That
|
|
1693
|
+
// fallback is the difference between this fix and the
|
|
1694
|
+
// `extends` fix, which has no fallback at all and refuses
|
|
1695
|
+
// instead: `references` is emitted for carriers in EVERY
|
|
1696
|
+
// vocabulary — Spring, SVG, WSDL, DocBook and application XML —
|
|
1697
|
+
// and most of them have no named schema ancestor, so refusing
|
|
1698
|
+
// would delete real edges rather than re-base them. It cannot
|
|
1699
|
+
// fabricate, because it is the value that was there before.
|
|
1700
|
+
ownerName: selfNamedDeclaration ?? nearestNamedDeclaration ?? ownerName,
|
|
1701
|
+
attributeName,
|
|
1702
|
+
// The COLLAPSED value, not the raw one. The deferred pass re-parses
|
|
1703
|
+
// `candidate.value` with `asQName`, so storing the raw text meant the
|
|
1704
|
+
// facet was applied once for the push-site check and then thrown away —
|
|
1705
|
+
// the candidate passed the collapsed test here and failed the raw one
|
|
1706
|
+
// there, and the edge was refused exactly as before. One transformation,
|
|
1707
|
+
// at the point the value enters the queue, and both readers see the same
|
|
1708
|
+
// string. It also makes `metadata.qname` the reading the spec defines
|
|
1709
|
+
// rather than the bytes a human typed.
|
|
1710
|
+
value: collapsed,
|
|
1711
|
+
nameNode,
|
|
1712
|
+
valueNode,
|
|
1713
|
+
binding,
|
|
1714
|
+
ownerNs
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* A QName is `NCName` or `NCName ':' NCName` with NOTHING else in the value.
|
|
1719
|
+
* Written as a character scan rather than a regular expression: the lane's rule
|
|
1720
|
+
* is that no regular expression does syntax work, and this is a syntax
|
|
1721
|
+
* decision about an attribute value.
|
|
1722
|
+
*/
|
|
1723
|
+
asQName(value) {
|
|
1724
|
+
if (!value || value.length > 256) return null;
|
|
1725
|
+
let colon = -1;
|
|
1726
|
+
for (let i = 0; i < value.length; i++) {
|
|
1727
|
+
if (value[i] === ":") {
|
|
1728
|
+
if (colon >= 0) return null;
|
|
1729
|
+
colon = i;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
const prefix = colon < 0 ? "" : value.slice(0, colon);
|
|
1733
|
+
const local = colon < 0 ? value : value.slice(colon + 1);
|
|
1734
|
+
if (!this.isNcName(local)) return null;
|
|
1735
|
+
if (colon >= 0 && !this.isNcName(prefix)) return null;
|
|
1736
|
+
return { prefix, local };
|
|
1737
|
+
}
|
|
1738
|
+
/** XML `NCName` — the character set the grammar's own `Name` rule accepts. */
|
|
1739
|
+
isNcName(text) {
|
|
1740
|
+
if (text.length === 0) return false;
|
|
1741
|
+
for (let i = 0; i < text.length; i++) {
|
|
1742
|
+
const c = text[i];
|
|
1743
|
+
const alpha = c >= "A" && c <= "Z" || c >= "a" && c <= "z";
|
|
1744
|
+
const ok = i === 0 ? alpha || c === "_" : alpha || c >= "0" && c <= "9" || c === "_" || c === "." || c === "-" || c === ":";
|
|
1745
|
+
if (!ok) return false;
|
|
1746
|
+
}
|
|
1747
|
+
return true;
|
|
1748
|
+
}
|
|
1749
|
+
/** A URI with a scheme or an authority is not a repository-relative file path. */
|
|
1750
|
+
isRemoteUri(target) {
|
|
1751
|
+
for (let i = 0; i < target.length; i++) {
|
|
1752
|
+
const c = target[i];
|
|
1753
|
+
if (c === "/" || c === "\\" || c === ".") return false;
|
|
1754
|
+
if (c === ":") return true;
|
|
1755
|
+
}
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* WHAT KIND OF THING an `imports` target is, so the edge carries a class a consumer
|
|
1760
|
+
* can count instead of reading as a dangling name.
|
|
1761
|
+
*
|
|
1762
|
+
* `imports` is the only relationship type this analyzer emits whose target is not a
|
|
1763
|
+
* symbol, and until this existed the type reported 0 % resolution with nothing on the
|
|
1764
|
+
* edge to say why — which is the state the round brief's cause C calls "silently
|
|
1765
|
+
* unresolved". Measured on the census draw `3bd539410d1a9189` by
|
|
1766
|
+
* `validation/m14/fixtures/xml/imports-target-classes.mts`: 263 edges, 84 distinct
|
|
1767
|
+
* targets, **0 of 263 carrying any class**. The same probe splits them 153 path-shaped,
|
|
1768
|
+
* 107 scheme-bearing, 3 in-document fragments.
|
|
1769
|
+
*
|
|
1770
|
+
* Three classes, and each is a claim this function can actually keep:
|
|
1771
|
+
*
|
|
1772
|
+
* `in-document` — the target starts with `#`, so it names a location in THIS file.
|
|
1773
|
+
* The one `imports` shape whose target can resolve to a symbol here.
|
|
1774
|
+
* `external` — `isRemoteUri`, the SAME test `schemaLocation` already applies, so
|
|
1775
|
+
* this file does not grow a second notion of "remote" that disagrees with the
|
|
1776
|
+
* first. Outside the corpus by construction.
|
|
1777
|
+
* `file` — a path. **Not a claim that the file exists.** The analyzer is a
|
|
1778
|
+
* per-file pure unit and does no filesystem access, so existence is a corpus
|
|
1779
|
+
* question it cannot answer; the probe answers it for one draw and the honest
|
|
1780
|
+
* split of the 153 is 130 admitting as xml, 21 present nowhere, 2 present but not
|
|
1781
|
+
* admitted. A consumer that needs that split must ask the corpus, not the edge.
|
|
1782
|
+
*
|
|
1783
|
+
* `file` is a new member of the vocabulary `references` and `extends` already use
|
|
1784
|
+
* (`in-document` / `builtin` / `external` / `unresolved`); nothing outside this file
|
|
1785
|
+
* switches on it, which was checked rather than assumed.
|
|
1786
|
+
*/
|
|
1787
|
+
importTargetClass(target) {
|
|
1788
|
+
if (target.startsWith("#")) return "in-document";
|
|
1789
|
+
return this.isRemoteUri(target) ? "external" : "file";
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* WHICH ATTRIBUTE carries a declaration's name, where `name` is not it.
|
|
1793
|
+
*
|
|
1794
|
+
* XSLT 1.0 §7.1 gives `xsl:output` no `name` at all: the output method IS its
|
|
1795
|
+
* name, written `method="html"`. That is the only such case measured across
|
|
1796
|
+
* the four vocabularies, and it is a table rather than a branch so that the
|
|
1797
|
+
* next one is a DECLARATION in the same place as the first, and so the
|
|
1798
|
+
* completeness gate can see that the attribute vocabulary is not silently
|
|
1799
|
+
* assumed to be `{name}`.
|
|
1800
|
+
*/
|
|
1801
|
+
/**
|
|
1802
|
+
* Declared EMPTY, and that emptiness is the finding.
|
|
1803
|
+
*
|
|
1804
|
+
* This used to read `{ output: "method" }`, on the theory that XSLT
|
|
1805
|
+
* `xsl:output` names something. It does not: `method` names the output METHOD
|
|
1806
|
+
* (`xml`, `html`, `text`) and nothing else. So `<xsl:output method="xml"/>`
|
|
1807
|
+
* was published as a symbol called **`xml`**, marked `isVocabDeclaration`,
|
|
1808
|
+
* with `method`/`encoding`/`indent` hung off it as its attributes — and the
|
|
1809
|
+
* element's real name, `xsl:output`, appeared nowhere in the graph.
|
|
1810
|
+
*
|
|
1811
|
+
* That is the M60.R10 defect class in MIRROR IMAGE. R10 is publishing a VALUE
|
|
1812
|
+
* where a NAME belongs, into a `contains` target; this was taking a value
|
|
1813
|
+
* where a NAME belongs and calling the element IT. The comment on
|
|
1814
|
+
* REFERENCE_ATTRIBUTES above spends two paragraphs refusing to publish
|
|
1815
|
+
* `unbounded` and `Normal` as names, and then the declared-name table handed
|
|
1816
|
+
* `xml` to the graph in the very next screen. The corpus paid for it in
|
|
1817
|
+
* bug-128.xml and bug-92.xml, where `contains xsl:stylesheet -> xsl:output`
|
|
1818
|
+
* was missing and the element had been renamed to its own configuration.
|
|
1819
|
+
*
|
|
1820
|
+
* XSLT 2.0 does give `xsl:output` a `name`, for stylesheets that declare
|
|
1821
|
+
* several outputs — and `name` is already this function's default, so the
|
|
1822
|
+
* correct table is the empty one. `xsl:output` in XSLT 1.0, which is what
|
|
1823
|
+
* these files are, has no name at all and is correctly published under its
|
|
1824
|
+
* written name: it is an instruction, not a declaration.
|
|
1825
|
+
*/
|
|
1826
|
+
static DECLARATION_NAME_ATTRIBUTE = {};
|
|
1827
|
+
/**
|
|
1828
|
+
* The DECLARED NAME of an element, when its vocabulary puts the name in an
|
|
1829
|
+
* attribute — and ONLY then.
|
|
1830
|
+
*
|
|
1831
|
+
* Returns null for every other element, which is the common case: a document's
|
|
1832
|
+
* elements are named by their tag, and an attribute that happens to be spelled
|
|
1833
|
+
* `name` is data. An ANONYMOUS inline declaration (`<xs:complexType>` with no
|
|
1834
|
+
* `name` inside an `<xs:element>`) is also null, and correctly so: an inline
|
|
1835
|
+
* type has no name, and publishing the tag for it is the honest reading.
|
|
1836
|
+
*
|
|
1837
|
+
* `nameNode` is the node the NAME ITSELF is written on, which is the AttValue
|
|
1838
|
+
* when the value is on a different line from the attribute keyword and the
|
|
1839
|
+
* whole Attribute otherwise. It is not the same node in both cases, and
|
|
1840
|
+
* picking the Attribute unconditionally was the whole of M19:
|
|
1841
|
+
*
|
|
1842
|
+
* <xsd:element name=
|
|
1843
|
+
* "InvalidModificationFault"
|
|
1844
|
+
*
|
|
1845
|
+
* (cxf/wsn/wsdl/rp-2.xsd) — the Attribute starts on 233, where the line reads
|
|
1846
|
+
* `<xsd:element name=` and carries no part of the name; the AttValue is on
|
|
1847
|
+
* 234. Anchoring to the Attribute reported 233, so a search for the name
|
|
1848
|
+
* landed one line above the string that spells it.
|
|
1849
|
+
*
|
|
1850
|
+
* `allOnOneLine` says which of the shapes this declaration is, because the shapes
|
|
1851
|
+
* want DIFFERENT spans even where two of them agree on the line. See the call site:
|
|
1852
|
+
* when all three share a line the SYMBOL anchors to the whole element, and the
|
|
1853
|
+
* difference is containment rather than cosmetics.
|
|
1854
|
+
*/
|
|
1855
|
+
declaredNameOf(namespaceUri, localName, tagNode) {
|
|
1856
|
+
if (!namespaceUri) return null;
|
|
1857
|
+
const declaring = _RealXmlAnalyzer.DECLARATION_VOCABULARIES[namespaceUri];
|
|
1858
|
+
if (!declaring || !declaring.has(localName)) return null;
|
|
1859
|
+
const attribute = _RealXmlAnalyzer.DECLARATION_NAME_ATTRIBUTE[localName] ?? "name";
|
|
1860
|
+
for (let i = 0; i < tagNode.namedChildCount; i++) {
|
|
1861
|
+
const node = tagNode.namedChild(i);
|
|
1862
|
+
if (node?.type !== "Attribute") continue;
|
|
1863
|
+
const attrNameNode = this.findDirectChild(node, "Name");
|
|
1864
|
+
if (attrNameNode?.text.trim() !== attribute) continue;
|
|
1865
|
+
const valueNode = this.findDirectChild(node, "AttValue");
|
|
1866
|
+
if (!valueNode) return null;
|
|
1867
|
+
const value = this.unquote(valueNode.text).trim();
|
|
1868
|
+
if (value.length === 0 || value.length > 256) return null;
|
|
1869
|
+
const anchor = valueNode.startPosition.row !== node.startPosition.row ? valueNode : node;
|
|
1870
|
+
return {
|
|
1871
|
+
name: value,
|
|
1872
|
+
attribute,
|
|
1873
|
+
nameNode: anchor,
|
|
1874
|
+
// The switch the call site needs is "are the TAG, the ATTRIBUTE and the VALUE
|
|
1875
|
+
// all on ONE line". Either half alone is wrong, and the M19 fixture
|
|
1876
|
+
// `xml/xsd-declared-name-lines` fails on each of them in turn:
|
|
1877
|
+
//
|
|
1878
|
+
// tag 27 / attribute 28 / value 28 `<xsd:attribute` + `name="X"` on 28
|
|
1879
|
+
// — attribute and value share a line, the tag does not. Testing the
|
|
1880
|
+
// value's row alone anchors to the element and reports 27.
|
|
1881
|
+
// tag 23 / attribute 23 / value 24 `<xsd:element name=` + `"X"` on 24
|
|
1882
|
+
// — tag and attribute share a line, the value does not. Testing the
|
|
1883
|
+
// attribute's row alone anchors to the element and reports 23.
|
|
1884
|
+
//
|
|
1885
|
+
// All three on one line is the only shape where the element's row and the
|
|
1886
|
+
// attribute's row are the same row, which is the whole reason the element
|
|
1887
|
+
// anchor is line-safe.
|
|
1888
|
+
allOnOneLine: node.startPosition.row === tagNode.startPosition.row && valueNode.startPosition.row === node.startPosition.row
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
return null;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Splits on runs of whitespace as a CHAR WALK, never a regex.
|
|
1895
|
+
*
|
|
1896
|
+
* `schemaLocation` is the one attribute whose value is a token list rather than
|
|
1897
|
+
* a single name, and tokenising it is the only place this analyzer needs to cut
|
|
1898
|
+
* a value into words. `\s` was doing that job and it was the analyzer's last
|
|
1899
|
+
* regex — which made this doc's "zero regex usages" claim false and left the
|
|
1900
|
+
* machine census carrying that line as `needs_review` with an empty
|
|
1901
|
+
* justification. The census and the doc disagreed and the census was right.
|
|
1902
|
+
*
|
|
1903
|
+
* The character set is spelled out rather than borrowed from `\s` so the
|
|
1904
|
+
* behaviour is pinned to a stated set — space, tab, LF, CR, the XML `S`
|
|
1905
|
+
* production — instead of to a regex dialect.
|
|
1906
|
+
*/
|
|
1907
|
+
/**
|
|
1908
|
+
* XML Schema's `collapse` whiteSpace facet, as a char walk.
|
|
1909
|
+
*
|
|
1910
|
+
* Every run of space/tab/LF/CR becomes ONE space, and the ends are trimmed.
|
|
1911
|
+
* This is the transformation the `collapse` facet defines, and it is NOT the
|
|
1912
|
+
* same as trimming: `"a b"` collapses to `"a b"` rather than `"ab"`. Applied to
|
|
1913
|
+
* a QName-typed attribute value before the value is read as a name — see the
|
|
1914
|
+
* call site in the reference pass for why that matters and why an interior
|
|
1915
|
+
* space must still be refused afterwards.
|
|
1916
|
+
*/
|
|
1917
|
+
static collapseWhitespace(text) {
|
|
1918
|
+
const out = [];
|
|
1919
|
+
let pendingSpace = false;
|
|
1920
|
+
let sawAny = false;
|
|
1921
|
+
for (let i = 0; i < text.length; i++) {
|
|
1922
|
+
const c = text.charCodeAt(i);
|
|
1923
|
+
if (c === 32 || c === 9 || c === 10 || c === 13) {
|
|
1924
|
+
if (sawAny) pendingSpace = true;
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if (pendingSpace) {
|
|
1928
|
+
out.push(" ");
|
|
1929
|
+
pendingSpace = false;
|
|
1930
|
+
}
|
|
1931
|
+
out.push(text[i]);
|
|
1932
|
+
sawAny = true;
|
|
1933
|
+
}
|
|
1934
|
+
return out.join("");
|
|
1935
|
+
}
|
|
1936
|
+
static splitOnWhitespace(text) {
|
|
1937
|
+
const parts = [];
|
|
1938
|
+
let start = -1;
|
|
1939
|
+
for (let i = 0; i <= text.length; i++) {
|
|
1940
|
+
const c = i < text.length ? text.charCodeAt(i) : 32;
|
|
1941
|
+
if (c === 32 || c === 9 || c === 10 || c === 13) {
|
|
1942
|
+
if (start >= 0) {
|
|
1943
|
+
parts.push(text.slice(start, i));
|
|
1944
|
+
start = -1;
|
|
1945
|
+
}
|
|
1946
|
+
} else if (start < 0) {
|
|
1947
|
+
start = i;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
return parts;
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Does `text` contain any code point JavaScript's `\s` matches?
|
|
1954
|
+
*
|
|
1955
|
+
* This is the analyzer's LAST regular expression, deleted. It stood at the
|
|
1956
|
+
* `PEReference` branch as `/\s/.test(ref)` — the predicate separating a parameter
|
|
1957
|
+
* entity REFERENCE (`%name;`) from the VALUE form (`% name '...'`) — and the doc
|
|
1958
|
+
* already claimed zero regex usages while the machine census carried that line as
|
|
1959
|
+
* unremoved. The census and the doc disagreed and the census was right.
|
|
1960
|
+
*
|
|
1961
|
+
* ## Why the class is spelled out and NOT narrowed to XML's own `S`
|
|
1962
|
+
*
|
|
1963
|
+
* The obvious replacement is {@link splitOnWhitespace}, since XML 1.0 §2.3 `S` is
|
|
1964
|
+
* `[ \t\r\n]` and that is the only whitespace a document may use BETWEEN tokens.
|
|
1965
|
+
* Narrowing to it is a behaviour change, and it was measured rather than assumed:
|
|
1966
|
+
* `validation/m14/fixtures/xml/pereference-shape-probe.mts` runs the deleted
|
|
1967
|
+
* predicate (`\s`), the narrowed one (`S`) and a `Name`-child lookup over every
|
|
1968
|
+
* `PEReference` node obtainable from the corpus, the lane's fixtures and eight
|
|
1969
|
+
* synthetic shapes.
|
|
1970
|
+
*
|
|
1971
|
+
* - corpus: 0 nodes. The branch is not exercised by the fixture corpus
|
|
1972
|
+
* at all, so no corpus number can decide this. The 22 admitted files whose
|
|
1973
|
+
* text contains `<!ENTITY %` are bare `.dtd` fragments, which this grammar
|
|
1974
|
+
* cannot root (no `document`), and the one admitted `.xml` — libxslt's
|
|
1975
|
+
* `tests/xmlspec/REC-xml-20001006.xml` — carries its `<!ENTITY %` inside
|
|
1976
|
+
* `CDSect`/`CDATA`, i.e. documentation ABOUT parameter entities.
|
|
1977
|
+
* - fixtures: 4 nodes, `\s` and `S` agree on 4 of 4.
|
|
1978
|
+
* - synthetic: 7 nodes, `\s` and `S` agree on 6 of 7. The one disagreement is
|
|
1979
|
+
* `%p<U+00A0>e;`, where `\s` drops it and `S` publishes a symbol named
|
|
1980
|
+
* `p<U+00A0>e` — a name no XML parser would accept, since `Name` is
|
|
1981
|
+
* `[a-zA-Z_][a-zA-Z0-9_:.·-]*`. On that single node the wider class is the
|
|
1982
|
+
* better answer, so this keeps it.
|
|
1983
|
+
*
|
|
1984
|
+
* ## Why the code points are listed rather than inherited
|
|
1985
|
+
*
|
|
1986
|
+
* The set is ECMAScript's `WhiteSpace` and `LineTerminator` productions, written
|
|
1987
|
+
* out so the behaviour is pinned to a stated list rather than to a dialect. It is
|
|
1988
|
+
* 25 code points and `isEcmaWhitespace` is total over them, which is what makes
|
|
1989
|
+
* this a replacement rather than an approximation: for any string, the answer is
|
|
1990
|
+
* identical to the deleted expression's, because both ask the same per-character
|
|
1991
|
+
* question and a character class is a per-character question.
|
|
1992
|
+
*
|
|
1993
|
+
* A narrower class WOULD be defensible here on XML grounds, and is recorded as a
|
|
1994
|
+
* limitation rather than shipped, because shipping it would change one measured
|
|
1995
|
+
* verdict on a shape the corpus cannot produce, which is the trade this lane has
|
|
1996
|
+
* no evidence to make.
|
|
1997
|
+
*/
|
|
1998
|
+
static containsEcmaWhitespace(text) {
|
|
1999
|
+
for (let i = 0; i < text.length; i++) {
|
|
2000
|
+
if (_RealXmlAnalyzer.isEcmaWhitespace(text.charCodeAt(i))) return true;
|
|
2001
|
+
}
|
|
2002
|
+
return false;
|
|
2003
|
+
}
|
|
2004
|
+
/** ECMAScript `\s`: `WhiteSpace` + `LineTerminator`. 25 code points, total. */
|
|
2005
|
+
static isEcmaWhitespace(c) {
|
|
2006
|
+
if (c === 32 || c === 9 || c === 10 || c === 13) return true;
|
|
2007
|
+
if (c === 11 || c === 12) return true;
|
|
2008
|
+
if (c === 160) return true;
|
|
2009
|
+
if (c === 5760) return true;
|
|
2010
|
+
if (c >= 8192 && c <= 8202) return true;
|
|
2011
|
+
if (c === 8232 || c === 8233) return true;
|
|
2012
|
+
if (c === 8239 || c === 8287) return true;
|
|
2013
|
+
if (c === 12288) return true;
|
|
2014
|
+
if (c === 65279) return true;
|
|
2015
|
+
return false;
|
|
2016
|
+
}
|
|
2017
|
+
isSchemaConstruct(name) {
|
|
2018
|
+
const separator = name.lastIndexOf(":");
|
|
2019
|
+
const localName = separator >= 0 ? name.slice(separator + 1) : name;
|
|
2020
|
+
switch (localName) {
|
|
2021
|
+
case "schema":
|
|
2022
|
+
case "element":
|
|
2023
|
+
case "attribute":
|
|
2024
|
+
case "complexType":
|
|
2025
|
+
case "simpleType":
|
|
2026
|
+
case "sequence":
|
|
2027
|
+
case "choice":
|
|
2028
|
+
case "all":
|
|
2029
|
+
case "group":
|
|
2030
|
+
case "attributeGroup":
|
|
2031
|
+
case "notation":
|
|
2032
|
+
case "import":
|
|
2033
|
+
case "include":
|
|
2034
|
+
case "redefine":
|
|
2035
|
+
case "annotation":
|
|
2036
|
+
case "documentation":
|
|
2037
|
+
case "appinfo":
|
|
2038
|
+
case "any":
|
|
2039
|
+
case "anyAttribute":
|
|
2040
|
+
return true;
|
|
2041
|
+
default:
|
|
2042
|
+
return false;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* The DECLARATION a node sits inside, for a reference inside replacement text.
|
|
2047
|
+
*
|
|
2048
|
+
* `<!ENTITY % shared "… %other; …">` makes `shared` depend on `other`, and the
|
|
2049
|
+
* owner of that use is `shared` — not the DOCTYPE, and not an element. Same
|
|
2050
|
+
* reasoning as the `GEDecl` EntityValue case, reached by walking up to the
|
|
2051
|
+
* nearest declaration rather than by descending into it, because the reference
|
|
2052
|
+
* may be nested inside arbitrary markup in the replacement text.
|
|
2053
|
+
*/
|
|
2054
|
+
enclosingDeclaredName(node) {
|
|
2055
|
+
let cur = node.parent;
|
|
2056
|
+
let depth = 0;
|
|
2057
|
+
while (cur && depth < 12) {
|
|
2058
|
+
if (cur.type === "GEDecl" || cur.type === "PEDecl" || cur.type === "NotationDecl") {
|
|
2059
|
+
const name = this.findDirectChild(cur, "Name")?.text.trim();
|
|
2060
|
+
return name && name.length > 0 ? name : null;
|
|
2061
|
+
}
|
|
2062
|
+
cur = cur.parent;
|
|
2063
|
+
depth++;
|
|
2064
|
+
}
|
|
2065
|
+
return null;
|
|
2066
|
+
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Is this node inside a region the PARSER could not read?
|
|
2069
|
+
*
|
|
2070
|
+
* Walks up rather than asking the file: `errorNodes` is a whole-file count and
|
|
2071
|
+
* would label every edge in a partly-damaged document, which is the opposite of
|
|
2072
|
+
* what `recovery` is for. libxml2's `test/errors/*` is a corpus of
|
|
2073
|
+
* deliberately malformed documents whose only purpose is to be malformed, and
|
|
2074
|
+
* their parameter references are assertions about bytes no processor would accept.
|
|
2075
|
+
*/
|
|
2076
|
+
/**
|
|
2077
|
+
* Is this node inside an entity's replacement text?
|
|
2078
|
+
*
|
|
2079
|
+
* Walks UP, deliberately: the question is about a node's POSITION, and asking
|
|
2080
|
+
* the node itself for an `EntityValue` child cannot answer it. See the call site
|
|
2081
|
+
* for what the first version got wrong.
|
|
2082
|
+
*/
|
|
2083
|
+
withinEntityValue(node) {
|
|
2084
|
+
let cur = node.parent;
|
|
2085
|
+
let depth = 0;
|
|
2086
|
+
while (cur && depth < 12) {
|
|
2087
|
+
if (cur.type === "EntityValue") return true;
|
|
2088
|
+
if (cur.type === "GEDecl" || cur.type === "PEDecl" || cur.type === "NotationDecl") return false;
|
|
2089
|
+
cur = cur.parent;
|
|
2090
|
+
depth++;
|
|
2091
|
+
}
|
|
2092
|
+
return false;
|
|
2093
|
+
}
|
|
2094
|
+
withinErrorRegion(node) {
|
|
2095
|
+
let cur = node;
|
|
2096
|
+
let depth = 0;
|
|
2097
|
+
while (cur && depth < 24) {
|
|
2098
|
+
if (cur.type === "ERROR") return true;
|
|
2099
|
+
if (cur.isMissing) return true;
|
|
2100
|
+
cur = cur.parent;
|
|
2101
|
+
depth++;
|
|
2102
|
+
}
|
|
2103
|
+
return false;
|
|
2104
|
+
}
|
|
2105
|
+
findDirectChild(node, type) {
|
|
2106
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2107
|
+
const child = node.namedChild(i);
|
|
2108
|
+
if (child?.type === type) return child;
|
|
2109
|
+
}
|
|
2110
|
+
return null;
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* THE INCLUDED DOCUMENT'S MODULE STEM — the `part_of` target, for BOTH inclusion
|
|
2114
|
+
* vocabularies this analyzer reads: XInclude's `<xi:include href>` and XSLT's
|
|
2115
|
+
* `<xsl:include href>`.
|
|
2116
|
+
*
|
|
2117
|
+
* `part_of`'s contract allows "the part's file OR its module symbol", and this
|
|
2118
|
+
* analyzer emits the SYMBOL because that is the only half the arbiter can bind: a
|
|
2119
|
+
* PATH target resolved 0 of 4,612 `part_of` edges (100% unresolved) in cpp's
|
|
2120
|
+
* measurement, because targets are resolved against SYMBOL NAMES. The path is kept
|
|
2121
|
+
* on the row as `metadata.partPath`; this is the symbol-shaped spelling of the same
|
|
2122
|
+
* fact.
|
|
2123
|
+
*
|
|
2124
|
+
* An href is a URI reference (RFC 3986): a `#fragment` selects a part of the
|
|
2125
|
+
* target, and a `?query` is not part of a file name, so both are cut BEFORE the
|
|
2126
|
+
* basename is taken — otherwise `intro.xml#sec2` would yield `xml#sec2`. The
|
|
2127
|
+
* directory segments are dropped and the extension is dropped, so
|
|
2128
|
+
* `sections/intro.xml` and `../shared/intro.xml` both name the module `intro`,
|
|
2129
|
+
* which is what a consumer sees. A dot at position 0 (`.hidden`) is not an
|
|
2130
|
+
* extension separator, so that name is kept whole.
|
|
2131
|
+
*
|
|
2132
|
+
* PURE CHARACTER SCAN, never a pattern: the analyzer's no-regex rule is a
|
|
2133
|
+
* committed gate, and this is string arithmetic on a path, not a shape search.
|
|
2134
|
+
*/
|
|
2135
|
+
static includedModuleStem(href) {
|
|
2136
|
+
let end = href.length;
|
|
2137
|
+
const hash = href.indexOf("#");
|
|
2138
|
+
if (hash >= 0 && hash < end) end = hash;
|
|
2139
|
+
const query = href.indexOf("?");
|
|
2140
|
+
if (query >= 0 && query < end) end = query;
|
|
2141
|
+
const pathOnly = href.slice(0, end);
|
|
2142
|
+
const slash = pathOnly.lastIndexOf("/");
|
|
2143
|
+
const base = slash >= 0 ? pathOnly.slice(slash + 1) : pathOnly;
|
|
2144
|
+
const dot = base.lastIndexOf(".");
|
|
2145
|
+
const stem = dot > 0 ? base.slice(0, dot) : base;
|
|
2146
|
+
return stem.trim();
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* A named `Attribute` child of a tag node, by attribute name.
|
|
2150
|
+
*
|
|
2151
|
+
* Read off the TREE rather than off the tag's text: an attribute name is a `Name`
|
|
2152
|
+
* child inside an `Attribute` node, and three sibling attributes on one tag differ
|
|
2153
|
+
* only in which of their own `Name` children says `match`. Splitting the tag's text
|
|
2154
|
+
* would have to re-tokenise, and would match an attribute NAME that appears inside
|
|
2155
|
+
* another attribute's VALUE.
|
|
2156
|
+
*/
|
|
2157
|
+
findDirectAttribute(tagNode, attrName) {
|
|
2158
|
+
for (let i = 0; i < tagNode.namedChildCount; i++) {
|
|
2159
|
+
const attr = tagNode.namedChild(i);
|
|
2160
|
+
if (attr?.type !== "Attribute") continue;
|
|
2161
|
+
const nameNode = this.findDirectChild(attr, "Name");
|
|
2162
|
+
const text = nameNode?.text.trim();
|
|
2163
|
+
if (text === attrName) return attr;
|
|
2164
|
+
}
|
|
2165
|
+
return null;
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* An attribute's value, unquoted and with the XML Schema `collapse` facet applied.
|
|
2169
|
+
*
|
|
2170
|
+
* `unquote` rather than a pattern, for the reason the rest of this analyzer spells
|
|
2171
|
+
* out: an `AttValue` is `"…"` or `'…'` in the tree, and only the quote characters
|
|
2172
|
+
* need removing. Returns `null` for an attribute written with no value, which is a
|
|
2173
|
+
* different thing from an empty value and must not collapse to `""`.
|
|
2174
|
+
*/
|
|
2175
|
+
attributeValueOf(tagNode, attrName) {
|
|
2176
|
+
const attr = this.findDirectAttribute(tagNode, attrName);
|
|
2177
|
+
if (!attr) return null;
|
|
2178
|
+
const valueNode = this.findDirectChild(attr, "AttValue");
|
|
2179
|
+
if (!valueNode) return null;
|
|
2180
|
+
return _RealXmlAnalyzer.collapseWhitespace(this.unquote(valueNode.text.trim()));
|
|
2181
|
+
}
|
|
2182
|
+
findFirstNamedChild(node, types) {
|
|
2183
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2184
|
+
const child = node.namedChild(i);
|
|
2185
|
+
if (child && types.has(child.type)) return child;
|
|
2186
|
+
}
|
|
2187
|
+
return null;
|
|
2188
|
+
}
|
|
2189
|
+
descendantsOfType(node, type) {
|
|
2190
|
+
const out = [];
|
|
2191
|
+
const stack = [node];
|
|
2192
|
+
while (stack.length > 0) {
|
|
2193
|
+
const current = stack.pop();
|
|
2194
|
+
for (let i = 0; i < current.namedChildCount; i++) {
|
|
2195
|
+
const child = current.namedChild(i);
|
|
2196
|
+
if (!child) continue;
|
|
2197
|
+
if (child.type === type) out.push(child);
|
|
2198
|
+
stack.push(child);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
return out;
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* THE SAME-FILE TIER — a BOUNDED fix for 366 edges of this lane's ~9,131 residual, and not a fix for
|
|
2205
|
+
* the lane. Sized from a POPULATION-scope run of the lane's own `ambiguity-split.mts`
|
|
2206
|
+
* (`validation/m14/fixtures/xml/measurements/ambiguity-split-population.txt`): the same-file bucket (B1)
|
|
2207
|
+
* totals 1,798 across every type — references 189 · extends 173 · calls 4 · overrides 0, plus 1,432
|
|
2208
|
+
* `contains` which production ALREADY binds through `containsLike` and which is therefore not part of
|
|
2209
|
+
* the residual. So the tier reaches 366, and the commit that lands it says exactly that, because the
|
|
2210
|
+
* next reader would otherwise inherit a 600-file draw's 279 as the lane's shape — which is how the
|
|
2211
|
+
* assignment that produced this file's measurement was written in the first place.
|
|
2212
|
+
*
|
|
2213
|
+
* THE RULE, and why it cannot be wrong: the lane ALREADY computes a two-part identity
|
|
2214
|
+
* (`qualifiedName = ownerName + "." + name`, `:780` and `:934`) and then hands the graph the BARE name,
|
|
2215
|
+
* because `addRelationship` takes two strings. `src/ast/parallel-indexer.ts:3435-3437` already looks up
|
|
2216
|
+
* `name === target` and then `qualifiedName === target`. So emitting the qualified form is readable to
|
|
2217
|
+
* the store WITHOUT changing the store. An edge is qualified only when the file declares its target
|
|
2218
|
+
* under EXACTLY ONE owner — `set.size === 1` — so there is no choice to make and no preference to
|
|
2219
|
+
* invent, which is the difference between a tier and a guess. Zero owners leaves it bare (nothing to
|
|
2220
|
+
* name) and two or more leaves it bare (still ambiguous, and the tier does not pretend otherwise).
|
|
2221
|
+
*
|
|
2222
|
+
* The BARE name is preserved as `metadata.bareTarget`, so nothing is lost and a consumer that matched
|
|
2223
|
+
* the old spelling can still see it.
|
|
2224
|
+
*/
|
|
2225
|
+
/**
|
|
2226
|
+
* THE FILE CLAIM — the ~466-edge lever, taken through infrastructure that ALREADY EXISTS.
|
|
2227
|
+
*
|
|
2228
|
+
* The lane's own note above the DOCTYPE emission says a file claim "REFUTES when no candidate is an
|
|
2229
|
+
* admitted file, and `extFAIL` must stay 0" — and that is true of an UNGUARDED claim. It is not true of
|
|
2230
|
+
* `fileClaimForTarget` (`src/ast/file-claim.ts:137`), whose whole contract is the opposite: it returns
|
|
2231
|
+
* the project-relative path ONLY when a candidate is CONFIRMED, and null otherwise, "because an
|
|
2232
|
+
* unconfirmed claim is what REFUTES". Two lanes already use it in production — `real-clean-analyzer.ts`
|
|
2233
|
+
* and `real-graphql-analyzer.ts` — and `resolveImportTargets` (`src/ast/import-resolution.ts:382`) is
|
|
2234
|
+
* wired into the indexer at `parallel-indexer.ts:5015` and `:5561`. So this is a USE of a channel rather
|
|
2235
|
+
* than a file model to build.
|
|
2236
|
+
*
|
|
2237
|
+
* WHAT IT DOES TO THE COLUMNS: the arbiter's `projectFile` branch is the one claim it can CHECK against
|
|
2238
|
+
* the project's own file list (`verifyFileClaim`), so a confirmed claim moves an edge out of
|
|
2239
|
+
* `unresolved` and into `extOK` — a claim the project VERIFIES, which is what clause 1 asks for and is
|
|
2240
|
+
* a RESOLUTION in the owner's standard rather than a ruling. An edge whose path cannot be confirmed is
|
|
2241
|
+
* left exactly as it was: no claim, no refutation, extFAIL stays zero.
|
|
2242
|
+
*
|
|
2243
|
+
* ── THE NEGATIVE HALF OF THE SAME CLAIM, WHICH WAS THE UNSTATED HOLE ────────────────────────────
|
|
2244
|
+
*
|
|
2245
|
+
* The file claim above can only ever say "the file IS here". When the file is not here the edge said
|
|
2246
|
+
* NOTHING checkable, and the incoming gate counted it as `unresolved` — this lane's largest unresolved
|
|
2247
|
+
* class: **60 of its 72 whole-population unresolved edges (83.3 %), all of them `imports`** (39
|
|
2248
|
+
* `schemaLocation` paths + 21 external-ID `SYSTEM` paths; measured at HEAD `fb93f6f18`, recorded in
|
|
2249
|
+
* `validation/m14/verification/XML-FIX.md` §2.2). The analyzer already computes the reason —
|
|
2250
|
+
* `importTargetClass` returns `resolvable: "file"` for exactly this shape (`:2780`) — and then
|
|
2251
|
+
* publishes it on a key the gate does not read. That is the fleet-wide cause (a) of
|
|
2252
|
+
* `RESIDUAL-CLASSIFICATION-41-LANES.md:20-34`, present on this lane.
|
|
2253
|
+
*
|
|
2254
|
+
* So the negative half is published as `sourcePathClaim`, the claim kind the arbiter ALREADY verifies
|
|
2255
|
+
* (`SOURCE_PATH_CLAIM_KEY`, `script/m14/incoming-rule.ts:321-337`, `classifyEdge`'s inverted-polarity
|
|
2256
|
+
* branch): *nothing the project ADMITS resolves to this written source string*. No new channel, no new
|
|
2257
|
+
* marker spelling, no claim the project cannot check.
|
|
2258
|
+
*
|
|
2259
|
+
* WHY THIS CAN NEVER REFUTE, which is the property `extFAIL` 0 depends on: the gate's verdict for this
|
|
2260
|
+
* claim is a pure function of `facts.files` (the admitted-file list), and `facts.files` is a SUBSET of
|
|
2261
|
+
* the files that exist on disk. Before attaching, every spelling the gate's `sourcePathCandidates`
|
|
2262
|
+
* tests — plus separator-normalised variants of them — is checked against the filesystem under the
|
|
2263
|
+
* project root (`isFileAbsentFromProject`). If ANY candidate exists as a regular file, the claim is NOT
|
|
2264
|
+
* attached and the edge is left exactly as it was. So "claim attached" implies "no candidate can be in
|
|
2265
|
+
* `facts.files`" implies "the gate cannot refute it" — by construction, not by measurement luck.
|
|
2266
|
+
*
|
|
2267
|
+
* AND THE SHAPE GUARD IS NOT THE BARE-NAME LAUNDERING HOLE THE CLAIM'S OWN RULE WARNS ABOUT: the claim
|
|
2268
|
+
* is attached only to an `imports` target the LANE classified `resolvable: "file"`, and only when it is
|
|
2269
|
+
* spelled as a path (a separator) or ends in one of THIS LANE's seven admitted file extensions.
|
|
2270
|
+
* `imports` targets are created from `SystemLiteral`/`schemaLocation` attribute text — a file reference
|
|
2271
|
+
* by construction, never a declaration name — and a target ending in `.dtd`/`.xsd`/… cannot be a
|
|
2272
|
+
* published symbol in this lane's key space in any ordinary document. What is refused is the bare word
|
|
2273
|
+
* with no file spelling (`SYSTEM "foo"`), which is exactly where the "could also name a declaration"
|
|
2274
|
+
* risk the claim's contract names actually lives.
|
|
2275
|
+
*/
|
|
2276
|
+
attachFileClaims(relationships, filePath, context) {
|
|
2277
|
+
const projectRoot = typeof context?.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
|
|
2278
|
+
if (projectRoot === null) return;
|
|
2279
|
+
for (const rel of relationships) {
|
|
2280
|
+
if (String(rel.type ?? "") !== ASTEngine.RelationshipType.Imports) continue;
|
|
2281
|
+
const md = rel.metadata ?? {};
|
|
2282
|
+
if (typeof md.importProjectFile === "string") continue;
|
|
2283
|
+
const written = String(rel.target ?? "");
|
|
2284
|
+
if (written.length === 0) continue;
|
|
2285
|
+
const claim = fileClaimForTarget({
|
|
2286
|
+
projectRoot,
|
|
2287
|
+
filePath,
|
|
2288
|
+
written,
|
|
2289
|
+
admittedExtensions: [..._RealXmlAnalyzer.IMPORT_FILE_EXTENSIONS]
|
|
2290
|
+
});
|
|
2291
|
+
if (claim !== null) {
|
|
2292
|
+
md.importProjectFile = claim;
|
|
2293
|
+
rel.metadata = md;
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
if (md.resolvable !== "file") continue;
|
|
2297
|
+
if (!this.isPathShapedImportTarget(written)) continue;
|
|
2298
|
+
if (!this.isFileAbsentFromProject(projectRoot, written)) continue;
|
|
2299
|
+
md.sourcePathClaim = [written];
|
|
2300
|
+
rel.metadata = md;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* IS THIS `imports` TARGET SPELLED AS A FILE? — the guard for the negative file claim.
|
|
2305
|
+
*
|
|
2306
|
+
* A target with a path separator is a path. A target with no separator is a FILE only when it
|
|
2307
|
+
* carries a file EXTENSION this lane admits — the vocabulary comes from `IMPORT_FILE_EXTENSIONS`,
|
|
2308
|
+
* the same set the positive file claim is checked against, so the two halves of the claim cannot
|
|
2309
|
+
* disagree about what a file of this lane is. A bare word with no file spelling (`SYSTEM "foo"`)
|
|
2310
|
+
* is refused, because that is the one shape that could also be a declaration name.
|
|
2311
|
+
*/
|
|
2312
|
+
isPathShapedImportTarget(written) {
|
|
2313
|
+
if (written.includes("/") || written.includes("\\")) return true;
|
|
2314
|
+
const dot = written.lastIndexOf(".");
|
|
2315
|
+
if (dot <= 0 || dot === written.length - 1) return false;
|
|
2316
|
+
return _RealXmlAnalyzer.IMPORT_FILE_EXTENSIONS.has(written.slice(dot + 1).toLowerCase());
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* NO SPELLING OF THIS TARGET IS A FILE UNDER THE PROJECT ROOT — the proof that the negative file
|
|
2320
|
+
* claim cannot be refuted by the arbiter.
|
|
2321
|
+
*
|
|
2322
|
+
* The candidate arithmetic MIRRORS the gate's `sourcePathCandidates` (`incoming-rule.ts:344-353`:
|
|
2323
|
+
* the written string, `./`-stripped, `../`-stripped, the dot-segment-joined path, the basename)
|
|
2324
|
+
* and is a SUPERSET of it (separator normalisation is added), so a spelling the gate would test can
|
|
2325
|
+
* never be missing here. A superset is the safe direction: a candidate this guard sees but the gate
|
|
2326
|
+
* does not can only make the guard refuse a claim (the edge stays unresolved, never refuted).
|
|
2327
|
+
*
|
|
2328
|
+
* It is a REFUSAL, never a resolution: it returns true only when nothing was found, and its only
|
|
2329
|
+
* effect is to suppress a claim.
|
|
2330
|
+
*
|
|
2331
|
+
* PURE CHARACTER SCANS, no patterns. Each of the three normalisations below is a shape test on a
|
|
2332
|
+
* FIXED PREFIX or a single character, which is what a pattern would be doing too — only a pattern
|
|
2333
|
+
* reaches back into the source text to do it, and this analyzer's no-regex rule is a committed
|
|
2334
|
+
* gate (script/m14/no-regex-gate.sh). The three replacements are behaviour-identical to the
|
|
2335
|
+
* patterns they replace, stated here so the equivalence is checkable: every backslash becomes a
|
|
2336
|
+
* slash; a leading `./` is dropped; a leading `../` is dropped (and `./../x` therefore becomes
|
|
2337
|
+
* `../x`, exactly as `.replace(/^\.\//, "")` did).
|
|
2338
|
+
*/
|
|
2339
|
+
isFileAbsentFromProject(projectRoot, written) {
|
|
2340
|
+
const trimmed = written.trim();
|
|
2341
|
+
let normalised = "";
|
|
2342
|
+
for (const ch of trimmed) normalised += ch === "\\" ? "/" : ch;
|
|
2343
|
+
const segments = normalised.split("/").filter((s) => s.length > 0 && s !== "." && s !== "..");
|
|
2344
|
+
const candidates = /* @__PURE__ */ new Set([
|
|
2345
|
+
trimmed,
|
|
2346
|
+
normalised,
|
|
2347
|
+
normalised.startsWith("./") ? normalised.slice(2) : normalised,
|
|
2348
|
+
normalised.startsWith("../") ? normalised.slice(3) : normalised,
|
|
2349
|
+
segments.join("/"),
|
|
2350
|
+
segments.length > 0 ? segments[segments.length - 1] : ""
|
|
2351
|
+
]);
|
|
2352
|
+
for (const candidate of candidates) {
|
|
2353
|
+
if (candidate.length === 0 || candidate.startsWith("/")) continue;
|
|
2354
|
+
if (candidate === ".." || candidate.startsWith("../")) continue;
|
|
2355
|
+
try {
|
|
2356
|
+
if (statSync(path.join(projectRoot, candidate)).isFile()) return false;
|
|
2357
|
+
} catch {
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return true;
|
|
2361
|
+
}
|
|
2362
|
+
qualifySameFileTier(symbols, relationships) {
|
|
2363
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2364
|
+
for (const sym of symbols) {
|
|
2365
|
+
const n = String(sym.name ?? "");
|
|
2366
|
+
const q = String(sym.qualifiedName ?? "");
|
|
2367
|
+
if (n.length === 0 || q.length === 0 || q === n) continue;
|
|
2368
|
+
let set = byName.get(n);
|
|
2369
|
+
if (!set) byName.set(n, set = /* @__PURE__ */ new Set());
|
|
2370
|
+
set.add(q);
|
|
2371
|
+
}
|
|
2372
|
+
const TIER_TYPES = /* @__PURE__ */ new Set(["references", "extends", "calls", "uses", "overrides"]);
|
|
2373
|
+
for (const rel of relationships) {
|
|
2374
|
+
if (!TIER_TYPES.has(String(rel.type ?? ""))) continue;
|
|
2375
|
+
const target = String(rel.target ?? "");
|
|
2376
|
+
const qs = byName.get(target);
|
|
2377
|
+
if (qs === void 0 || qs.size !== 1) continue;
|
|
2378
|
+
const md = rel.metadata ?? {};
|
|
2379
|
+
md.bareTarget = target;
|
|
2380
|
+
rel.metadata = md;
|
|
2381
|
+
rel.target = [...qs][0];
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
unquote(text) {
|
|
2385
|
+
if (text.length >= 2) {
|
|
2386
|
+
const first = text[0];
|
|
2387
|
+
const last = text[text.length - 1];
|
|
2388
|
+
if ((first === '"' || first === "'") && first === last) return text.slice(1, -1);
|
|
2389
|
+
}
|
|
2390
|
+
return text;
|
|
2391
|
+
}
|
|
2392
|
+
nodeToRange(node) {
|
|
2393
|
+
return {
|
|
2394
|
+
startLine: node.startPosition.row + 1,
|
|
2395
|
+
startColumn: node.startPosition.column + 1,
|
|
2396
|
+
endLine: node.endPosition.row + 1,
|
|
2397
|
+
endColumn: node.endPosition.column + 1,
|
|
2398
|
+
startIndex: node.startIndex,
|
|
2399
|
+
endIndex: node.endIndex
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
export {
|
|
2404
|
+
RealXmlAnalyzer
|
|
2405
|
+
};
|