@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,1442 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FILE_CLAIM_MAX_BYTES,
|
|
3
|
+
fileClaimForTarget
|
|
4
|
+
} from "./chunk-EWMWK7WN.js";
|
|
5
|
+
import {
|
|
6
|
+
ASTEngine
|
|
7
|
+
} from "./chunk-V44PGPA6.js";
|
|
8
|
+
import "./chunk-BQ3OVQ3P.js";
|
|
9
|
+
import "./chunk-SK6YVHFZ.js";
|
|
10
|
+
import "./chunk-2BSKD7O3.js";
|
|
11
|
+
import "./chunk-KAET47ZL.js";
|
|
12
|
+
import "./chunk-KCDECRJY.js";
|
|
13
|
+
import "./chunk-MLKGABMK.js";
|
|
14
|
+
|
|
15
|
+
// src/ast/real-graphql-analyzer.ts
|
|
16
|
+
var EXTENSION_TO_DEFINITION = {
|
|
17
|
+
ObjectTypeExtension: "ObjectTypeDefinition",
|
|
18
|
+
InterfaceTypeExtension: "InterfaceTypeDefinition",
|
|
19
|
+
UnionTypeExtension: "UnionTypeDefinition",
|
|
20
|
+
EnumTypeExtension: "EnumTypeDefinition",
|
|
21
|
+
ScalarTypeExtension: "ScalarTypeDefinition",
|
|
22
|
+
InputObjectTypeExtension: "InputObjectTypeDefinition"
|
|
23
|
+
};
|
|
24
|
+
var SPEC_BUILTIN_SCALARS = ["String", "Int", "Float", "Boolean", "ID"];
|
|
25
|
+
var SPEC_BUILTIN_SCALAR_SET = new Set(SPEC_BUILTIN_SCALARS);
|
|
26
|
+
function lastPathSegment(p) {
|
|
27
|
+
let cut = -1;
|
|
28
|
+
for (let i = 0; i < p.length; i++) {
|
|
29
|
+
const c = p.charAt(i);
|
|
30
|
+
if (c === "/" || c === "\\") cut = i;
|
|
31
|
+
}
|
|
32
|
+
return cut >= 0 ? p.slice(cut + 1) : p;
|
|
33
|
+
}
|
|
34
|
+
function unnamedOperationIdentity(filePath, ordinal) {
|
|
35
|
+
const base = lastPathSegment(filePath ?? "");
|
|
36
|
+
const dot = base.lastIndexOf(".");
|
|
37
|
+
const stem = dot > 0 ? base.slice(0, dot) : base;
|
|
38
|
+
return `${stem || "document"}::operation_${ordinal}`;
|
|
39
|
+
}
|
|
40
|
+
var HASH_IMPORT_KEYWORD = "import";
|
|
41
|
+
var isQuote = (code) => code === 34 || code === 39 || code === 96;
|
|
42
|
+
var isInlineSpace = (code) => code === 32 || code === 9;
|
|
43
|
+
var isWordByte = (code) => code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 95;
|
|
44
|
+
function parseHashImport(text) {
|
|
45
|
+
let i = 0;
|
|
46
|
+
while (i < text.length && isInlineSpace(text.charCodeAt(i))) i++;
|
|
47
|
+
if (text.charCodeAt(i) !== 35) return null;
|
|
48
|
+
i++;
|
|
49
|
+
while (i < text.length && isInlineSpace(text.charCodeAt(i))) i++;
|
|
50
|
+
for (let k = 0; k < HASH_IMPORT_KEYWORD.length; k++) {
|
|
51
|
+
if (text.charCodeAt(i + k) !== HASH_IMPORT_KEYWORD.charCodeAt(k)) return null;
|
|
52
|
+
}
|
|
53
|
+
i += HASH_IMPORT_KEYWORD.length;
|
|
54
|
+
if (i < text.length && isWordByte(text.charCodeAt(i))) return null;
|
|
55
|
+
const skipSpace = () => {
|
|
56
|
+
while (i < text.length && isInlineSpace(text.charCodeAt(i))) i++;
|
|
57
|
+
};
|
|
58
|
+
const quoted = () => {
|
|
59
|
+
skipSpace();
|
|
60
|
+
const q = text.charCodeAt(i);
|
|
61
|
+
if (!isQuote(q)) return null;
|
|
62
|
+
i++;
|
|
63
|
+
const start = i;
|
|
64
|
+
while (i < text.length && text.charCodeAt(i) !== q) i++;
|
|
65
|
+
if (i >= text.length) return null;
|
|
66
|
+
const value = text.slice(start, i);
|
|
67
|
+
i++;
|
|
68
|
+
return value.length > 0 ? value : null;
|
|
69
|
+
};
|
|
70
|
+
const word = () => {
|
|
71
|
+
skipSpace();
|
|
72
|
+
const s = i;
|
|
73
|
+
while (i < text.length && !isInlineSpace(text.charCodeAt(i)) && !isQuote(text.charCodeAt(i)) && text.charCodeAt(i) !== 44) i++;
|
|
74
|
+
return text.slice(s, i);
|
|
75
|
+
};
|
|
76
|
+
const first = quoted();
|
|
77
|
+
if (first !== null) {
|
|
78
|
+
const after = word();
|
|
79
|
+
if (after.length === 0) return { target: first, form: "bare", bindings: [] };
|
|
80
|
+
return after === "as" ? { target: first, form: "bare", bindings: [word()] } : null;
|
|
81
|
+
}
|
|
82
|
+
const bindings = [];
|
|
83
|
+
for (; ; ) {
|
|
84
|
+
const w = word();
|
|
85
|
+
if (w.length === 0) return null;
|
|
86
|
+
bindings.push(w);
|
|
87
|
+
if (text.charCodeAt(i) === 44) {
|
|
88
|
+
i++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (word() !== "from") return null;
|
|
94
|
+
const target = quoted();
|
|
95
|
+
if (target === null) return null;
|
|
96
|
+
return { target, form: "from_binding", bindings };
|
|
97
|
+
}
|
|
98
|
+
var DIRECTIVE_NODE = /* @__PURE__ */ new WeakMap();
|
|
99
|
+
var RealGraphqlAnalyzer = class _RealGraphqlAnalyzer {
|
|
100
|
+
/**
|
|
101
|
+
* `node.type` memoised per node id, for the duration of one `analyze` call.
|
|
102
|
+
*
|
|
103
|
+
* `type` is a marshalling getter — every read crosses into WASM and copies a
|
|
104
|
+
* string out — and the analyzer reads it far more often than there are nodes:
|
|
105
|
+
* measured on the 20,368-line GitHub public schema, **103,531 reads of 31,491
|
|
106
|
+
* distinct nodes, so 69.6% were redundant.** `node.id`, by contrast, is a plain
|
|
107
|
+
* own property set in the binding's constructor, so keying a `Map` on it costs
|
|
108
|
+
* nothing and turns every read after the first into a hash lookup.
|
|
109
|
+
*
|
|
110
|
+
* It is an instance field only because the readers are instance methods, and it
|
|
111
|
+
* is REASSIGNED at the top of every `analyze` call, which is what makes it safe:
|
|
112
|
+
* a `Node` id is only unique within one tree, and the binding documents that a
|
|
113
|
+
* node reused from an older tree keeps its id, so a cache that outlived a call
|
|
114
|
+
* could answer with the previous tree's type. Reassigning makes that impossible
|
|
115
|
+
* rather than merely unlikely.
|
|
116
|
+
*/
|
|
117
|
+
typeCache = /* @__PURE__ */ new Map();
|
|
118
|
+
/** Counts the operations this document leaves unnamed, so each can be published under
|
|
119
|
+
* the file's identity (see `unnamedOperationIdentity`). Reset per `analyze` call: a
|
|
120
|
+
* pooled worker reuses one analyzer instance across files, and a counter that
|
|
121
|
+
* survived the call would number the second file's operations from where the first
|
|
122
|
+
* file stopped. */
|
|
123
|
+
unnamedOperationOrdinal = 0;
|
|
124
|
+
/** `node.type`, read through the per-call memo. */
|
|
125
|
+
typeOf(node) {
|
|
126
|
+
const id = node.id;
|
|
127
|
+
if (typeof id !== "number") return node.type;
|
|
128
|
+
const hit = this.typeCache.get(id);
|
|
129
|
+
if (hit !== void 0) return hit;
|
|
130
|
+
const t = node.type;
|
|
131
|
+
this.typeCache.set(id, t);
|
|
132
|
+
return t;
|
|
133
|
+
}
|
|
134
|
+
analyze(rootNode, content, filePath, context) {
|
|
135
|
+
this.typeCache = /* @__PURE__ */ new Map();
|
|
136
|
+
this.unnamedOperationOrdinal = 0;
|
|
137
|
+
const symbols = [];
|
|
138
|
+
const relationships = [];
|
|
139
|
+
const imports = [];
|
|
140
|
+
const exports = [];
|
|
141
|
+
const errors = [];
|
|
142
|
+
const relKeys = /* @__PURE__ */ new Set();
|
|
143
|
+
const addRelationship = (source, target, type, line, role, metadata = {}) => {
|
|
144
|
+
if (source === target && role === "field_selection") return;
|
|
145
|
+
if (!source || !target) return;
|
|
146
|
+
const key = `${source}\0${type}\0${target}\0${role}\0${line}`;
|
|
147
|
+
if (relKeys.has(key)) return;
|
|
148
|
+
relKeys.add(key);
|
|
149
|
+
relationships.push({
|
|
150
|
+
source,
|
|
151
|
+
target,
|
|
152
|
+
type,
|
|
153
|
+
confidence: 1,
|
|
154
|
+
line_number: line,
|
|
155
|
+
// Every edge this analyzer emits is read off a NAMED AST NODE — an
|
|
156
|
+
// `ImplementsInterfaces` clause, a `NamedType`, a field's own `Name`, a
|
|
157
|
+
// `Directives` child — so `structural` is the class for most of them and is
|
|
158
|
+
// stated rather than left absent. 38 sibling lanes publish this key and this
|
|
159
|
+
// one published none, which made "how much of this graph is guesswork" a
|
|
160
|
+
// question with no machine-readable answer on this lane: the honesty contract
|
|
161
|
+
// §4.3 requires the heuristic ratio to be a QUERYABLE number, and an absent
|
|
162
|
+
// key is not queryable. `confidence` was already 1 everywhere, so nothing
|
|
163
|
+
// here is newly a heuristic — the key says so explicitly.
|
|
164
|
+
//
|
|
165
|
+
// THE ONE TIER THAT IS NOT STRUCTURAL: `imports`. A `#import "file.graphql"`
|
|
166
|
+
// hash-import's TARGET is a FILE PATH read out of a comment string, not a name
|
|
167
|
+
// read off a named AST node — it is the lane's file-dependency claim (the same
|
|
168
|
+
// ladder toml's imports row carries), so it is `import_based`. Section D of the
|
|
169
|
+
// completeness inventory requires two DISTINCT tiers pinned by fixtures, and
|
|
170
|
+
// this is the lane's honest second one rather than a manufactured value.
|
|
171
|
+
metadata: {
|
|
172
|
+
...metadata,
|
|
173
|
+
relationshipRole: role,
|
|
174
|
+
evidenceClass: type === ASTEngine.RelationshipType.Imports ? "import_based" : "structural"
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
const addDeclaredType = (source, target, line, role, metadata = {}) => {
|
|
179
|
+
addRelationship(source, target, ASTEngine.RelationshipType.References, line, role, {
|
|
180
|
+
...metadata,
|
|
181
|
+
relationshipType: "declared_type",
|
|
182
|
+
// THE CLAIM — attached HERE and nowhere else, because this closure is the ONE place every
|
|
183
|
+
// declared-type position passes through (directive argument, field return, field argument,
|
|
184
|
+
// input field, variable) and the only place where `target` is guaranteed to be a TYPE NAME
|
|
185
|
+
// rather than a member spelling. A union member list, an export row and a field selection are
|
|
186
|
+
// NOT type clauses, so the 48 edges of those shapes whose target happens to spell one of the
|
|
187
|
+
// five are left alone and stay `unresolved` — a measured boundary, not an oversight.
|
|
188
|
+
//
|
|
189
|
+
// The reason travels WITH the claim (`languageNamespaceBasis`) so the disposition can be read
|
|
190
|
+
// off the edge; it is not a key any claim channel reads, so it cannot misfire.
|
|
191
|
+
...SPEC_BUILTIN_SCALAR_SET.has(target) ? {
|
|
192
|
+
languageNamespace: [...SPEC_BUILTIN_SCALARS],
|
|
193
|
+
languageNamespaceBasis: "graphql-spec-builtin-scalar"
|
|
194
|
+
} : {}
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
const directiveCache = /* @__PURE__ */ new WeakMap();
|
|
198
|
+
const directivesOf = (node) => {
|
|
199
|
+
const cached = directiveCache.get(node);
|
|
200
|
+
if (cached !== void 0) return cached;
|
|
201
|
+
const list = this.scanDirectiveApplications(node);
|
|
202
|
+
directiveCache.set(node, list);
|
|
203
|
+
return list;
|
|
204
|
+
};
|
|
205
|
+
const declaredHere = /* @__PURE__ */ new Set();
|
|
206
|
+
const kindOfNameHere = /* @__PURE__ */ new Map();
|
|
207
|
+
for (const decl of this.topLevelDeclarations(rootNode)) {
|
|
208
|
+
if (!decl.name) continue;
|
|
209
|
+
declaredHere.add(`${decl.symbolKind}\0${decl.name}`);
|
|
210
|
+
const kinds = kindOfNameHere.get(decl.name) ?? /* @__PURE__ */ new Set();
|
|
211
|
+
kinds.add(decl.symbolKind);
|
|
212
|
+
kindOfNameHere.set(decl.name, kinds);
|
|
213
|
+
}
|
|
214
|
+
const supertypeKindOf = (supertype) => {
|
|
215
|
+
const kinds = kindOfNameHere.get(supertype);
|
|
216
|
+
if (!kinds || kinds.size !== 1) return void 0;
|
|
217
|
+
const only = [...kinds][0];
|
|
218
|
+
return only === ASTEngine.SymbolKind.Interface || only === ASTEngine.SymbolKind.Class || only === ASTEngine.SymbolKind.Type ? only : void 0;
|
|
219
|
+
};
|
|
220
|
+
const emitHeritage = (owner, supertype, line, inExtension) => {
|
|
221
|
+
const supertypeKind = supertypeKindOf(supertype);
|
|
222
|
+
const relType = supertypeKind === ASTEngine.SymbolKind.Class || supertypeKind === ASTEngine.SymbolKind.Type ? ASTEngine.RelationshipType.Extends : ASTEngine.RelationshipType.Implements;
|
|
223
|
+
heritageEdges.push({ owner, iface: supertype, ifaceKind: supertypeKind });
|
|
224
|
+
addRelationship(owner, supertype, relType, line, relType === ASTEngine.RelationshipType.Extends ? "extends" : "implements", {
|
|
225
|
+
...relType === ASTEngine.RelationshipType.Extends ? { implementsClause: true } : {},
|
|
226
|
+
...inExtension ? { extension: true } : {}
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
const heritageEdges = [];
|
|
230
|
+
const memberNameNodes = /* @__PURE__ */ new Map();
|
|
231
|
+
const extendedHere = /* @__PURE__ */ new Set();
|
|
232
|
+
for (const ext of this.topLevelExtensions(rootNode)) {
|
|
233
|
+
extendedHere.add(`${ext.symbolKind}\0${ext.name}`);
|
|
234
|
+
}
|
|
235
|
+
const TYPE_KIND = {
|
|
236
|
+
ObjectTypeDefinition: ASTEngine.SymbolKind.Class,
|
|
237
|
+
InterfaceTypeDefinition: ASTEngine.SymbolKind.Interface,
|
|
238
|
+
UnionTypeDefinition: ASTEngine.SymbolKind.Type,
|
|
239
|
+
EnumTypeDefinition: ASTEngine.SymbolKind.Enum,
|
|
240
|
+
ScalarTypeDefinition: ASTEngine.SymbolKind.Type,
|
|
241
|
+
InputObjectTypeDefinition: ASTEngine.SymbolKind.Class
|
|
242
|
+
};
|
|
243
|
+
const extensionSymbols = [];
|
|
244
|
+
const rootOperationNames = /* @__PURE__ */ new Set();
|
|
245
|
+
const hashImports = [];
|
|
246
|
+
const stack = [
|
|
247
|
+
{ node: rootNode, owner: {}, sel: { path: [] }, inExtension: false }
|
|
248
|
+
];
|
|
249
|
+
const pushChildren = (node, owner, sel, inExtension, skip = () => false) => {
|
|
250
|
+
const kids = node.namedChildren;
|
|
251
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
252
|
+
const child = kids[i];
|
|
253
|
+
if (child && !skip(child)) stack.push({ node: child, owner, sel, inExtension });
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
if (this.isAnyOf(rootNode, "Document", "source_file", "SourceFile")) {
|
|
257
|
+
}
|
|
258
|
+
while (stack.length > 0) {
|
|
259
|
+
const { node, owner, sel, inExtension } = stack.pop();
|
|
260
|
+
const t = node.type;
|
|
261
|
+
if (t === "SchemaDefinition" || t === "SchemaExtension") {
|
|
262
|
+
if (!symbols.some((s) => s.name === "schema" && s.kind === ASTEngine.SymbolKind.Namespace)) {
|
|
263
|
+
symbols.push({
|
|
264
|
+
name: "schema",
|
|
265
|
+
kind: ASTEngine.SymbolKind.Namespace,
|
|
266
|
+
range: this.nodeToRange(node),
|
|
267
|
+
scope: "",
|
|
268
|
+
visibility: "public",
|
|
269
|
+
docstring: this.descriptionOf(node),
|
|
270
|
+
metadata: {
|
|
271
|
+
graphqlType: t === "SchemaDefinition" ? "schema" : "schema_extension",
|
|
272
|
+
topLevel: true,
|
|
273
|
+
...t === "SchemaExtension" ? { inExtension: true } : {}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
for (const opDef of node.namedChildren) {
|
|
278
|
+
if (opDef?.type !== "OperationTypeDefinition") continue;
|
|
279
|
+
const opTypeNode = this.firstChildOfType(opDef, "OperationType");
|
|
280
|
+
const opType = opTypeNode?.text ?? "operation";
|
|
281
|
+
const named = this.firstChildOfType(opDef, "NamedType");
|
|
282
|
+
const rootType = named ? this.nameOf(named) : void 0;
|
|
283
|
+
const line = this.lineOf(named ?? opDef);
|
|
284
|
+
if (rootOperationNames.has(opType)) continue;
|
|
285
|
+
rootOperationNames.add(opType);
|
|
286
|
+
symbols.push({
|
|
287
|
+
name: opType,
|
|
288
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
289
|
+
range: this.nodeToRange(opDef),
|
|
290
|
+
scope: "schema",
|
|
291
|
+
visibility: "public",
|
|
292
|
+
returnType: rootType,
|
|
293
|
+
ownerName: "schema",
|
|
294
|
+
ownerKind: ASTEngine.SymbolKind.Namespace,
|
|
295
|
+
metadata: {
|
|
296
|
+
graphqlRole: "root_operation_type",
|
|
297
|
+
...t === "SchemaExtension" ? { inExtension: true } : {}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
addRelationship("schema", `schema.${opType}`, ASTEngine.RelationshipType.Contains, line, "contains", {
|
|
301
|
+
...t === "SchemaExtension" ? { extension: true } : {}
|
|
302
|
+
});
|
|
303
|
+
if (rootType) {
|
|
304
|
+
addRelationship(`schema.${opType}`, rootType, ASTEngine.RelationshipType.Uses, line, "root_operation_type", {
|
|
305
|
+
...t === "SchemaExtension" ? { extension: true } : {}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.emitDirectiveApplications(node, directivesOf(node), "schema", sel, addRelationship, t === "SchemaExtension");
|
|
310
|
+
pushChildren(node, { name: "schema", kind: ASTEngine.SymbolKind.Namespace }, sel, inExtension, (c) => this.typeOf(c) === "OperationTypeDefinition");
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (TYPE_KIND[t]) {
|
|
314
|
+
const name = this.nameOf(node);
|
|
315
|
+
if (name) {
|
|
316
|
+
const kind = TYPE_KIND[t];
|
|
317
|
+
this.emitTypeSymbol(node, name, kind, t, symbols, relationships, sel, inExtension, directivesOf(node));
|
|
318
|
+
if (t === "ScalarTypeDefinition" && extendedHere.has(`${kind}\0${name}`)) {
|
|
319
|
+
const last = symbols[symbols.length - 1];
|
|
320
|
+
last.metadata = { ...last.metadata, extended: true };
|
|
321
|
+
}
|
|
322
|
+
const implNode = this.firstChildOfType(node, "ImplementsInterfaces");
|
|
323
|
+
if (implNode) {
|
|
324
|
+
for (const { name: iface, node: ifaceNode } of this.collectNamedTypes(implNode)) {
|
|
325
|
+
emitHeritage(name, iface, this.lineOf(ifaceNode), inExtension);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (t === "UnionTypeDefinition") {
|
|
329
|
+
const membersNode = this.firstChildOfType(node, "UnionMemberTypes");
|
|
330
|
+
if (membersNode) {
|
|
331
|
+
for (const { name: member, node: memberNode } of this.collectNamedTypes(membersNode)) {
|
|
332
|
+
addRelationship(name, member, ASTEngine.RelationshipType.Uses, this.lineOf(memberNode), "union_member", {
|
|
333
|
+
...inExtension ? { extension: true } : {}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
this.emitDirectiveApplications(node, directivesOf(node), name, sel, addRelationship, inExtension);
|
|
339
|
+
pushChildren(node, { name, kind }, sel, inExtension);
|
|
340
|
+
}
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (EXTENSION_TO_DEFINITION[t]) {
|
|
344
|
+
if (t === "ScalarTypeExtension") {
|
|
345
|
+
this.emitDirectiveApplications(node, directivesOf(node), this.nameOf(node) ?? "", sel, addRelationship, true);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const definitionType = EXTENSION_TO_DEFINITION[t];
|
|
349
|
+
const name = this.nameOf(node);
|
|
350
|
+
if (name) {
|
|
351
|
+
const kind = TYPE_KIND[definitionType];
|
|
352
|
+
const kindKey = `${kind}\0${name}`;
|
|
353
|
+
if (!declaredHere.has(kindKey)) {
|
|
354
|
+
const index = symbols.length;
|
|
355
|
+
symbols.push({
|
|
356
|
+
name,
|
|
357
|
+
kind,
|
|
358
|
+
range: this.nodeToRange(node),
|
|
359
|
+
scope: "",
|
|
360
|
+
visibility: "public",
|
|
361
|
+
docstring: this.descriptionOf(node),
|
|
362
|
+
metadata: { graphqlType: t, partial: true, inExtension: true, topLevel: true }
|
|
363
|
+
});
|
|
364
|
+
extensionSymbols.push({ index, name, kindKey, typeNode: t });
|
|
365
|
+
}
|
|
366
|
+
const implNode = this.firstChildOfType(node, "ImplementsInterfaces");
|
|
367
|
+
if (implNode) {
|
|
368
|
+
for (const { name: iface, node: ifaceNode } of this.collectNamedTypes(implNode)) {
|
|
369
|
+
emitHeritage(name, iface, this.lineOf(ifaceNode), true);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (definitionType === "UnionTypeDefinition") {
|
|
373
|
+
const membersNode = this.firstChildOfType(node, "UnionMemberTypes");
|
|
374
|
+
if (membersNode) {
|
|
375
|
+
for (const { name: member, node: memberNode } of this.collectNamedTypes(membersNode)) {
|
|
376
|
+
addRelationship(name, member, ASTEngine.RelationshipType.Uses, this.lineOf(memberNode), "union_member", { extension: true });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
this.emitDirectiveApplications(node, directivesOf(node), name, sel, addRelationship, true);
|
|
381
|
+
pushChildren(node, { name, kind }, sel, true);
|
|
382
|
+
}
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (t === "EnumValueDefinition") {
|
|
386
|
+
const enumValue = this.firstChildOfType(node, "EnumValue");
|
|
387
|
+
const valueName = enumValue ? this.nameOf(enumValue) : void 0;
|
|
388
|
+
if (valueName && owner.name) {
|
|
389
|
+
symbols.push({
|
|
390
|
+
name: valueName,
|
|
391
|
+
kind: ASTEngine.SymbolKind.EnumMember,
|
|
392
|
+
range: this.nodeToRange(node),
|
|
393
|
+
scope: owner.name,
|
|
394
|
+
ownerName: owner.name,
|
|
395
|
+
ownerKind: owner.kind,
|
|
396
|
+
visibility: "public",
|
|
397
|
+
docstring: this.descriptionOf(node),
|
|
398
|
+
metadata: { graphqlRole: "enum_value", ...inExtension ? { inExtension: true } : {} }
|
|
399
|
+
});
|
|
400
|
+
addRelationship(owner.name, `${owner.name}.${valueName}`, ASTEngine.RelationshipType.Contains, this.lineOf(enumValue ?? node), "contains", {
|
|
401
|
+
...inExtension ? { extension: true } : {}
|
|
402
|
+
});
|
|
403
|
+
this.emitDirectiveApplications(node, directivesOf(node), `${owner.name}.${valueName}`, { root: void 0 }, addRelationship, inExtension);
|
|
404
|
+
}
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (t === "DirectiveDefinition") {
|
|
408
|
+
const name = this.nameOf(node);
|
|
409
|
+
if (name) {
|
|
410
|
+
const argsNode = this.firstChildOfType(node, "ArgumentsDefinition");
|
|
411
|
+
const repeatable = this.hasChild(node, (n) => this.typeOf(n) === "repeatable");
|
|
412
|
+
const locations = this.firstChildOfType(node, "DirectiveLocations");
|
|
413
|
+
const locationNames = locations ? this.collectDirectiveLocations(locations).map((l) => l.name) : void 0;
|
|
414
|
+
symbols.push({
|
|
415
|
+
name: `@${name}`,
|
|
416
|
+
kind: ASTEngine.SymbolKind.Function,
|
|
417
|
+
range: this.nodeToRange(node),
|
|
418
|
+
scope: "",
|
|
419
|
+
visibility: "public",
|
|
420
|
+
parameters: argsNode ? this.extractArguments(argsNode) : [],
|
|
421
|
+
docstring: this.descriptionOf(node),
|
|
422
|
+
metadata: {
|
|
423
|
+
graphqlRole: "directive_definition",
|
|
424
|
+
topLevel: true,
|
|
425
|
+
directiveName: name,
|
|
426
|
+
...repeatable ? { repeatable: true } : {},
|
|
427
|
+
...locationNames ? { locations: locationNames } : {},
|
|
428
|
+
...this.directiveMetadata(directivesOf(node))
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
if (argsNode) {
|
|
432
|
+
for (const arg of this.argumentPairs(argsNode)) {
|
|
433
|
+
if (!arg.type || !arg.typeNode) continue;
|
|
434
|
+
addDeclaredType(`@${name}`, arg.type, this.lineOf(arg.typeNode), "directive_argument_type", { argument: arg.name });
|
|
435
|
+
if (arg.name) this.emitEnumValueReferences(`@${name}`, arg.defaultNode, arg.type, addRelationship, { declaration: "directive_argument_default", argument: arg.name });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (t === "FieldDefinition") {
|
|
442
|
+
const fieldName = this.nameOf(node);
|
|
443
|
+
if (fieldName && owner.name) {
|
|
444
|
+
const typeNode = this.firstChildOfType(node, "Type");
|
|
445
|
+
const returnType = typeNode ? this.namedTypeName(typeNode) : void 0;
|
|
446
|
+
const argsNode = this.firstChildOfType(node, "ArgumentsDefinition");
|
|
447
|
+
const ownerSymbol = `${owner.name}.${fieldName}`;
|
|
448
|
+
symbols.push({
|
|
449
|
+
name: fieldName,
|
|
450
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
451
|
+
range: this.nodeToRange(node),
|
|
452
|
+
scope: owner.name,
|
|
453
|
+
visibility: "public",
|
|
454
|
+
returnType,
|
|
455
|
+
ownerName: owner.name,
|
|
456
|
+
ownerKind: owner.kind,
|
|
457
|
+
parameters: argsNode ? this.extractArguments(argsNode) : [],
|
|
458
|
+
docstring: this.descriptionOf(node),
|
|
459
|
+
metadata: {
|
|
460
|
+
graphqlRole: "field",
|
|
461
|
+
// `rawType` is the type EXPRESSION's source text and is emitted only
|
|
462
|
+
// when there is one: a recovered `Type` node with a MISSING `Name` has
|
|
463
|
+
// the empty text, and publishing `rawType: ""` is a fact about nothing.
|
|
464
|
+
...typeNode && typeNode.text ? { rawType: typeNode.text } : {},
|
|
465
|
+
...returnType ? { nullable: !this.isNonNullType(typeNode) } : {},
|
|
466
|
+
...inExtension ? { inExtension: true } : {},
|
|
467
|
+
...this.directiveMetadata(directivesOf(node))
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
const fieldNameNode = this.firstChildOfType(node, "Name");
|
|
471
|
+
addRelationship(owner.name, ownerSymbol, ASTEngine.RelationshipType.Contains, this.lineOf(fieldNameNode ?? node), "contains", {
|
|
472
|
+
...inExtension ? { extension: true } : {}
|
|
473
|
+
});
|
|
474
|
+
if (fieldNameNode) {
|
|
475
|
+
const bag = memberNameNodes.get(owner.name) ?? /* @__PURE__ */ new Map();
|
|
476
|
+
if (!bag.has(fieldName)) bag.set(fieldName, fieldNameNode);
|
|
477
|
+
memberNameNodes.set(owner.name, bag);
|
|
478
|
+
}
|
|
479
|
+
if (returnType) {
|
|
480
|
+
addDeclaredType(ownerSymbol, returnType, this.lineOf(this.namedTypeNode(typeNode) ?? typeNode), "field_return_type", {
|
|
481
|
+
...inExtension ? { extension: true } : {}
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
if (argsNode) {
|
|
485
|
+
for (const arg of this.argumentPairs(argsNode)) {
|
|
486
|
+
if (!arg.type || !arg.typeNode) continue;
|
|
487
|
+
addDeclaredType(ownerSymbol, arg.type, this.lineOf(arg.typeNode), "field_argument_type", {
|
|
488
|
+
argument: arg.name,
|
|
489
|
+
...inExtension ? { extension: true } : {}
|
|
490
|
+
});
|
|
491
|
+
if (arg.name) this.emitEnumValueReferences(ownerSymbol, arg.defaultNode, arg.type, addRelationship, { declaration: "argument_default", argument: arg.name });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
this.emitDirectiveApplications(node, directivesOf(node), ownerSymbol, sel, addRelationship, inExtension);
|
|
495
|
+
}
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (t === "InputValueDefinition" && node.parent?.type === "InputFieldsDefinition") {
|
|
499
|
+
const fieldName = this.nameOf(node);
|
|
500
|
+
if (fieldName && owner.name) {
|
|
501
|
+
const typeNode = this.firstChildOfType(node, "Type");
|
|
502
|
+
const fieldType = typeNode ? this.namedTypeName(typeNode) : void 0;
|
|
503
|
+
const ownerSymbol = `${owner.name}.${fieldName}`;
|
|
504
|
+
symbols.push({
|
|
505
|
+
name: fieldName,
|
|
506
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
507
|
+
range: this.nodeToRange(node),
|
|
508
|
+
scope: owner.name,
|
|
509
|
+
visibility: "public",
|
|
510
|
+
returnType: fieldType,
|
|
511
|
+
ownerName: owner.name,
|
|
512
|
+
ownerKind: owner.kind,
|
|
513
|
+
docstring: this.descriptionOf(node),
|
|
514
|
+
metadata: {
|
|
515
|
+
graphqlRole: "input_field",
|
|
516
|
+
...typeNode && typeNode.text ? { rawType: typeNode.text } : {},
|
|
517
|
+
...fieldType ? { nullable: !this.isNonNullType(typeNode) } : {},
|
|
518
|
+
// Called ONCE. The conditional form below ran it twice per input
|
|
519
|
+
// field — a `firstChildOfType` walk each time — to test a value it
|
|
520
|
+
// then computed again, and this is the per-node hot path.
|
|
521
|
+
...(() => {
|
|
522
|
+
const dv = this.defaultValueOf(node);
|
|
523
|
+
return dv !== void 0 ? { defaultValue: dv } : {};
|
|
524
|
+
})(),
|
|
525
|
+
...inExtension ? { inExtension: true } : {},
|
|
526
|
+
...this.directiveMetadata(directivesOf(node))
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
addRelationship(owner.name, ownerSymbol, ASTEngine.RelationshipType.Contains, this.lineOf(this.firstChildOfType(node, "Name") ?? node), "contains", {
|
|
530
|
+
...inExtension ? { extension: true } : {}
|
|
531
|
+
});
|
|
532
|
+
this.emitEnumValueReferences(ownerSymbol, this.firstChildOfType(node, "DefaultValue"), fieldType, addRelationship, { declaration: "input_field_default" });
|
|
533
|
+
if (fieldType) {
|
|
534
|
+
addDeclaredType(ownerSymbol, fieldType, this.lineOf(this.namedTypeNode(typeNode) ?? typeNode), "input_field_type", {
|
|
535
|
+
...inExtension ? { extension: true } : {}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
this.emitDirectiveApplications(node, directivesOf(node), ownerSymbol, sel, addRelationship, inExtension);
|
|
539
|
+
}
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (t === "OperationDefinition") {
|
|
543
|
+
const opTypeNode = this.firstChildOfType(node, "OperationType");
|
|
544
|
+
const opType = opTypeNode?.text ?? "query";
|
|
545
|
+
const nameNode = this.firstChildOfType(node, "Name");
|
|
546
|
+
const opName = nameNode?.text ?? unnamedOperationIdentity(filePath, this.unnamedOperationOrdinal++);
|
|
547
|
+
symbols.push({
|
|
548
|
+
name: opName,
|
|
549
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
550
|
+
range: this.nodeToRange(node),
|
|
551
|
+
scope: "",
|
|
552
|
+
visibility: "public",
|
|
553
|
+
metadata: {
|
|
554
|
+
graphqlRole: "operation",
|
|
555
|
+
graphqlOperationType: opType,
|
|
556
|
+
...nameNode ? {} : { anonymous: true, shorthand: true }
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
const varsNode = this.firstChildOfType(node, "VariableDefinitions");
|
|
560
|
+
if (varsNode) {
|
|
561
|
+
for (const varDef of varsNode.namedChildren) {
|
|
562
|
+
if (varDef?.type !== "VariableDefinition") continue;
|
|
563
|
+
const varNode = this.firstChildOfType(varDef, "Variable");
|
|
564
|
+
const varName = varNode ? this.nameOf(varNode) : void 0;
|
|
565
|
+
const typeNode = this.firstChildOfType(varDef, "Type");
|
|
566
|
+
const varType = typeNode ? this.namedTypeName(typeNode) : void 0;
|
|
567
|
+
if (!varName) continue;
|
|
568
|
+
if (varType) {
|
|
569
|
+
addDeclaredType(`$${varName}`, varType, this.lineOf(this.namedTypeNode(typeNode) ?? typeNode), "variable_type", {
|
|
570
|
+
operation: opName
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
this.emitEnumValueReferences(`$${varName}`, this.firstChildOfType(varDef, "DefaultValue"), varType, addRelationship, { declaration: "variable_default" });
|
|
574
|
+
symbols.push({
|
|
575
|
+
name: `$${varName}`,
|
|
576
|
+
kind: ASTEngine.SymbolKind.Variable,
|
|
577
|
+
range: this.nodeToRange(varDef),
|
|
578
|
+
scope: opName,
|
|
579
|
+
returnType: varType,
|
|
580
|
+
ownerName: opName,
|
|
581
|
+
ownerKind: ASTEngine.SymbolKind.Method,
|
|
582
|
+
metadata: {
|
|
583
|
+
graphqlRole: "variable",
|
|
584
|
+
...typeNode && typeNode.text ? { rawType: typeNode.text } : {},
|
|
585
|
+
...varType ? { nullable: !this.isNonNullType(typeNode) } : {},
|
|
586
|
+
...this.defaultValueOf(varDef) !== void 0 ? { defaultValue: this.defaultValueOf(varDef) } : {},
|
|
587
|
+
...this.directiveMetadata(directivesOf(varDef))
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
this.emitDirectiveApplications(node, directivesOf(node), opName, sel, addRelationship, false);
|
|
593
|
+
if (varsNode) {
|
|
594
|
+
this.emitDirectiveApplications(varsNode, directivesOf(varsNode), opName, sel, addRelationship, false);
|
|
595
|
+
}
|
|
596
|
+
pushChildren(node, { name: opName, kind: ASTEngine.SymbolKind.Method }, { root: opName, path: [] }, inExtension, (c) => this.typeOf(c) === "VariableDefinition");
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (t === "FragmentDefinition") {
|
|
600
|
+
const fragNode = this.firstChildOfType(node, "FragmentName");
|
|
601
|
+
const fragName = fragNode ? this.nameOf(fragNode) : void 0;
|
|
602
|
+
if (fragName) {
|
|
603
|
+
symbols.push({
|
|
604
|
+
name: fragName,
|
|
605
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
606
|
+
range: this.nodeToRange(node),
|
|
607
|
+
scope: "",
|
|
608
|
+
visibility: "public",
|
|
609
|
+
metadata: { graphqlRole: "fragment", ...this.directiveMetadata(directivesOf(node)) }
|
|
610
|
+
});
|
|
611
|
+
const typeCondition = this.firstChildOfType(node, "TypeCondition");
|
|
612
|
+
const onType = typeCondition ? this.namedTypeName(typeCondition) : void 0;
|
|
613
|
+
if (onType && typeCondition) {
|
|
614
|
+
addRelationship(fragName, onType, ASTEngine.RelationshipType.Uses, this.lineOf(this.namedTypeNode(typeCondition) ?? typeCondition), "fragment_type_condition");
|
|
615
|
+
}
|
|
616
|
+
this.emitDirectiveApplications(node, directivesOf(node), fragName, sel, addRelationship, false);
|
|
617
|
+
pushChildren(node, { name: fragName, kind: ASTEngine.SymbolKind.Method }, { root: fragName, enclosingType: onType, path: [] }, inExtension);
|
|
618
|
+
}
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (t === "VariableDefinition") continue;
|
|
622
|
+
if (t === "Field") {
|
|
623
|
+
const nameNode = this.firstChildOfType(node, "Name");
|
|
624
|
+
const fieldName = nameNode?.text;
|
|
625
|
+
const aliasNode = this.firstChildOfType(node, "Alias");
|
|
626
|
+
const alias = aliasNode ? this.nameOf(aliasNode) : void 0;
|
|
627
|
+
if (fieldName && sel.root) {
|
|
628
|
+
const qualified = sel.enclosingType ? `${sel.enclosingType}.${fieldName}` : fieldName;
|
|
629
|
+
const path = [...sel.path, fieldName].join(".");
|
|
630
|
+
const argsNode = this.firstChildOfType(node, "Arguments");
|
|
631
|
+
if (argsNode && sel.root) {
|
|
632
|
+
for (const arg of this.childrenOfType(argsNode, "Argument")) {
|
|
633
|
+
this.emitEnumValueReferences(sel.root, this.firstChildOfType(arg, "Value"), void 0, addRelationship, { declaration: "field_argument" });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
addRelationship(sel.root, qualified, ASTEngine.RelationshipType.Uses, this.lineOf(nameNode), "field_selection", {
|
|
637
|
+
...alias ? { alias } : {},
|
|
638
|
+
...argsNode ? { arguments: this.argumentNames(argsNode) } : {},
|
|
639
|
+
...sel.enclosingType ? { enclosingType: sel.enclosingType } : {},
|
|
640
|
+
...path ? { selectionPath: path } : {}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
this.emitDirectiveApplications(node, directivesOf(node), sel.root ?? "", sel, addRelationship, false);
|
|
644
|
+
const childPath = [...sel.path, fieldName ?? "?"];
|
|
645
|
+
pushChildren(node, { name: fieldName, kind: ASTEngine.SymbolKind.Property }, { root: sel.root, enclosingType: void 0, path: childPath }, inExtension, (c) => this.typeOf(c) === "Name" || this.typeOf(c) === "Alias");
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (t === "FragmentSpread") {
|
|
649
|
+
const spreadNode = this.firstChildOfType(node, "FragmentName");
|
|
650
|
+
const spreadName = spreadNode ? this.nameOf(spreadNode) : void 0;
|
|
651
|
+
if (spreadName && sel.root) {
|
|
652
|
+
addRelationship(sel.root, spreadName, ASTEngine.RelationshipType.Uses, this.lineOf(spreadNode), "fragment_spread", {
|
|
653
|
+
...[...sel.path].length ? { selectionPath: sel.path.join(".") } : {}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
this.emitDirectiveApplications(node, directivesOf(node), sel.root ?? "", sel, addRelationship, false);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (t === "InlineFragment") {
|
|
660
|
+
const typeCondition = this.firstChildOfType(node, "TypeCondition");
|
|
661
|
+
const onType = typeCondition ? this.namedTypeName(typeCondition) : void 0;
|
|
662
|
+
if (onType && sel.root && typeCondition) {
|
|
663
|
+
addRelationship(sel.root, onType, ASTEngine.RelationshipType.Uses, this.lineOf(this.namedTypeNode(typeCondition) ?? typeCondition), "inline_fragment_type_condition", {
|
|
664
|
+
...[...sel.path].length ? { selectionPath: sel.path.join(".") } : {}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
this.emitDirectiveApplications(node, directivesOf(node), sel.root ?? "", sel, addRelationship, false);
|
|
668
|
+
pushChildren(node, { name: onType, kind: ASTEngine.SymbolKind.Type }, { root: sel.root, enclosingType: onType ?? sel.enclosingType, path: [...sel.path] }, inExtension, (c) => this.typeOf(c) === "TypeCondition");
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (t === "Variable") {
|
|
672
|
+
const valueParent = node.parent;
|
|
673
|
+
if (valueParent?.type === "Value" && sel.root) {
|
|
674
|
+
const varName = this.nameOf(node);
|
|
675
|
+
if (varName) {
|
|
676
|
+
addRelationship(sel.root, `$${varName}`, ASTEngine.RelationshipType.Uses, this.lineOf(node), "variable_reference", {
|
|
677
|
+
...[...sel.path].length ? { selectionPath: sel.path.join(".") } : {}
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (t === "comment") {
|
|
684
|
+
const parsed = parseHashImport(node.text);
|
|
685
|
+
if (parsed) hashImports.push({ ...parsed, line: node.startPosition.row + 1 });
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
pushChildren(node, owner, sel, inExtension);
|
|
689
|
+
}
|
|
690
|
+
for (const ext of extensionSymbols) {
|
|
691
|
+
if (!declaredHere.has(ext.kindKey)) continue;
|
|
692
|
+
const sym = symbols[ext.index];
|
|
693
|
+
symbols.splice(ext.index, 1);
|
|
694
|
+
for (let i = extensionSymbols.length - 1; i >= 0; i--) {
|
|
695
|
+
if (extensionSymbols[i].index > ext.index) extensionSymbols[i].index--;
|
|
696
|
+
}
|
|
697
|
+
if (sym) sym.metadata = { ...sym.metadata, droppedExtensionSymbol: true };
|
|
698
|
+
}
|
|
699
|
+
for (const h of heritageEdges) {
|
|
700
|
+
if (h.owner === h.iface) continue;
|
|
701
|
+
const mine = memberNameNodes.get(h.owner);
|
|
702
|
+
const theirs = memberNameNodes.get(h.iface);
|
|
703
|
+
if (!mine || !theirs) continue;
|
|
704
|
+
for (const [member, nameNode] of mine) {
|
|
705
|
+
if (!theirs.has(member)) continue;
|
|
706
|
+
addRelationship(`${h.owner}.${member}`, `${h.iface}.${member}`, ASTEngine.RelationshipType.Override, this.lineOf(nameNode), "interface_member_supplied", {
|
|
707
|
+
implementedType: h.owner,
|
|
708
|
+
supertype: h.iface,
|
|
709
|
+
supertypeKind: h.ifaceKind ?? "undeclared_in_this_document",
|
|
710
|
+
member
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (hashImports.length > 0) {
|
|
715
|
+
const importer = lastPathSegment(filePath) || filePath;
|
|
716
|
+
for (const hi of hashImports) {
|
|
717
|
+
addRelationship(importer, hi.target, ASTEngine.RelationshipType.Imports, hi.line, "imports", {
|
|
718
|
+
isGraphqlImport: true,
|
|
719
|
+
importSource: importer,
|
|
720
|
+
importForm: hi.form,
|
|
721
|
+
// `fileLevel` IS THE FLAG THAT MAKES THE SOURCE ABOVE JOINABLE, and this edge was missing
|
|
722
|
+
// it while this analyzer's own `exports` edges already carried it. The M60/#910 contract
|
|
723
|
+
// (`parallel-indexer.ts:4391`) reads it as:
|
|
724
|
+
//
|
|
725
|
+
// if (!sourceSymbol && rel.metadata?.fileLevel === true) { … sourceExternal = relativePath }
|
|
726
|
+
//
|
|
727
|
+
// — a module-level edge whose source did not resolve to a symbol is rewritten to the
|
|
728
|
+
// IMPORTING FILE's project-relative path. Without the flag `source_external` keeps this
|
|
729
|
+
// analyzer's own spelling, a bare basename (`schema.graphql`), which no declaration can
|
|
730
|
+
// ever answer: the row existed, the dependency existed, and the only thing missing was who
|
|
731
|
+
// declared it — verbatim the defect that clause exists to fix, and the reason no surface
|
|
732
|
+
// could answer "what does this file include".
|
|
733
|
+
//
|
|
734
|
+
// NOTHING ABOVE IS DISCARDED: the store keeps this spelling in `metadata.scopeName`, and
|
|
735
|
+
// the fixture's assertion that the source IS the importing file's own name
|
|
736
|
+
// (`ontology-file-imports.expectation.json`) is about the analyzer's OUTPUT, which is
|
|
737
|
+
// unchanged. Metadata-only, so no target-side count can move — the residual probe's
|
|
738
|
+
// reproduction of the gate's TABLE 1, in the same run, is the independent check of that.
|
|
739
|
+
fileLevel: true,
|
|
740
|
+
// The bound names are real GraphQL identifiers in this document's scope; `[]`
|
|
741
|
+
// on a bare directive means "the document as a whole", which is not the same
|
|
742
|
+
// as "no names" and is distinguished by `importForm` rather than by omission.
|
|
743
|
+
...hi.bindings.length > 0 ? { importBindings: hi.bindings } : {}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
for (const s of symbols) {
|
|
748
|
+
if (s.metadata?.droppedExtensionSymbol) continue;
|
|
749
|
+
if (s.metadata?.topLevel !== true) continue;
|
|
750
|
+
exports.push({ name: s.name, kind: s.kind, range: s.range, visibility: "public" });
|
|
751
|
+
addRelationship("(file)", s.name, ASTEngine.RelationshipType.Exports, s.range.startLine, "exports", {
|
|
752
|
+
fileLevel: true,
|
|
753
|
+
...s.metadata?.graphqlType ? { graphqlType: s.metadata.graphqlType } : {},
|
|
754
|
+
...s.metadata?.graphqlCategory ? { graphqlCategory: s.metadata.graphqlCategory } : {}
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
const claimRoot = typeof context?.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
|
|
758
|
+
if (claimRoot !== null) {
|
|
759
|
+
for (const r of relationships) {
|
|
760
|
+
if (r.type !== ASTEngine.RelationshipType.Imports) continue;
|
|
761
|
+
const md = r.metadata;
|
|
762
|
+
if (!md || typeof md.importProjectFile === "string") continue;
|
|
763
|
+
const claim = fileClaimForTarget({
|
|
764
|
+
projectRoot: claimRoot,
|
|
765
|
+
filePath,
|
|
766
|
+
written: String(r.target ?? ""),
|
|
767
|
+
admittedExtensions: ["graphql", "gql"],
|
|
768
|
+
// THE POPULATION'S SIZE CAP, passed because this lane HAS over-cap files (4 for graphql,
|
|
769
|
+
// 2 for scss on the gate's own population line). A claim about one of them would be
|
|
770
|
+
// REFUTED, and nothing in the row would say why.
|
|
771
|
+
maxFileSizeBytes: FILE_CLAIM_MAX_BYTES
|
|
772
|
+
});
|
|
773
|
+
if (claim === null) continue;
|
|
774
|
+
md.importProjectFile = claim;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
language: "graphql",
|
|
779
|
+
symbols,
|
|
780
|
+
relationships,
|
|
781
|
+
imports,
|
|
782
|
+
exports,
|
|
783
|
+
errors,
|
|
784
|
+
metadata: {
|
|
785
|
+
complexity: symbols.length + relationships.length,
|
|
786
|
+
linesOfCode: content.split("\n").length,
|
|
787
|
+
patterns: [],
|
|
788
|
+
framework: "GraphQL",
|
|
789
|
+
hasFrameworkIntelligence: true,
|
|
790
|
+
hasTypeInference: true,
|
|
791
|
+
documentKind: this.documentKind(rootNode)
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
// ---------------------------------------------------------------- helpers
|
|
796
|
+
documentKind(rootNode) {
|
|
797
|
+
let hasTypeSystem = false;
|
|
798
|
+
let hasExecutable = false;
|
|
799
|
+
for (const def of this.topLevelDefinitions(rootNode)) {
|
|
800
|
+
const inner = this.innermostNamed(def);
|
|
801
|
+
if (!inner) continue;
|
|
802
|
+
if (this.typeOf(inner) === "OperationDefinition" || this.typeOf(inner) === "FragmentDefinition") hasExecutable = true;
|
|
803
|
+
else hasTypeSystem = true;
|
|
804
|
+
}
|
|
805
|
+
if (hasTypeSystem && hasExecutable) return "mixed";
|
|
806
|
+
if (hasTypeSystem) return "type_system";
|
|
807
|
+
if (hasExecutable) return "executable";
|
|
808
|
+
return "empty";
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* The document's top-level declarations: for each `Definition`, the innermost
|
|
812
|
+
* named child, which is the actual construct. Cheap (one level of the tree)
|
|
813
|
+
* and it is the only place a type can be DECLARED, so it is exactly the set
|
|
814
|
+
* an extension has to be compared against.
|
|
815
|
+
*/
|
|
816
|
+
topLevelDeclarations(rootNode) {
|
|
817
|
+
const out = [];
|
|
818
|
+
for (const def of this.topLevelDefinitions(rootNode)) {
|
|
819
|
+
const inner = this.innermostNamed(def);
|
|
820
|
+
if (!inner) continue;
|
|
821
|
+
const typeNode = inner.type;
|
|
822
|
+
if (typeNode === "SchemaDefinition") {
|
|
823
|
+
out.push({ name: "schema", symbolKind: ASTEngine.SymbolKind.Namespace, typeNode });
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (typeNode === "DirectiveDefinition") {
|
|
827
|
+
const n2 = this.nameOf(inner);
|
|
828
|
+
if (n2) out.push({ name: `@${n2}`, symbolKind: ASTEngine.SymbolKind.Function, typeNode });
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (typeNode.endsWith("Extension")) continue;
|
|
832
|
+
const kind = this.definitionKindOf(typeNode);
|
|
833
|
+
if (!kind) continue;
|
|
834
|
+
const n = this.nameOf(inner);
|
|
835
|
+
if (n) out.push({ name: n, symbolKind: kind, typeNode });
|
|
836
|
+
}
|
|
837
|
+
return out;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* The top-level type EXTENSIONS of a document, with the kind of the definition
|
|
841
|
+
* each one extends. Computed in the pre-pass, beside `topLevelDeclarations`,
|
|
842
|
+
* because both answers come from the same one-level walk and computing either
|
|
843
|
+
* on demand meant re-walking every definition per question.
|
|
844
|
+
*/
|
|
845
|
+
topLevelExtensions(rootNode) {
|
|
846
|
+
const out = [];
|
|
847
|
+
for (const def of this.topLevelDefinitions(rootNode)) {
|
|
848
|
+
const inner = this.innermostNamed(def);
|
|
849
|
+
if (!inner || !inner.type.endsWith("Extension")) continue;
|
|
850
|
+
if (this.typeOf(inner) === "SchemaExtension") {
|
|
851
|
+
out.push({ name: "schema", symbolKind: ASTEngine.SymbolKind.Namespace });
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
const definitionType = EXTENSION_TO_DEFINITION[inner.type];
|
|
855
|
+
const kind = definitionType ? this.definitionKindOf(definitionType) : void 0;
|
|
856
|
+
const n = this.nameOf(inner);
|
|
857
|
+
if (kind && n) out.push({ name: n, symbolKind: kind });
|
|
858
|
+
}
|
|
859
|
+
return out;
|
|
860
|
+
}
|
|
861
|
+
definitionKindOf(typeNode) {
|
|
862
|
+
switch (typeNode) {
|
|
863
|
+
case "ObjectTypeDefinition":
|
|
864
|
+
case "InputObjectTypeDefinition":
|
|
865
|
+
return ASTEngine.SymbolKind.Class;
|
|
866
|
+
case "InterfaceTypeDefinition":
|
|
867
|
+
return ASTEngine.SymbolKind.Interface;
|
|
868
|
+
case "UnionTypeDefinition":
|
|
869
|
+
case "ScalarTypeDefinition":
|
|
870
|
+
return ASTEngine.SymbolKind.Type;
|
|
871
|
+
case "EnumTypeDefinition":
|
|
872
|
+
return ASTEngine.SymbolKind.Enum;
|
|
873
|
+
default:
|
|
874
|
+
return void 0;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* The rule that classifies a top-level definition, reached by unwrapping the
|
|
879
|
+
* grammar's pass-through rules.
|
|
880
|
+
*
|
|
881
|
+
* The WRAPPER SET IS EXPLICIT. "Descend while there is exactly one named
|
|
882
|
+
* child" is not the same rule and is wrong: a shorthand operation is
|
|
883
|
+
* `Definition > ExecutableDefinition > OperationDefinition > SelectionSet >
|
|
884
|
+
* Selection > Field > Name`, and an unconditional descent returns `Name` — so
|
|
885
|
+
* `{ ping }` was classified as a type-system definition and every file's
|
|
886
|
+
* `documentKind` was wrong. A `schema` block hit the same descent
|
|
887
|
+
* (`… > SchemaDefinition > OperationTypeDefinition > NamedType > Name`).
|
|
888
|
+
*/
|
|
889
|
+
static DEFINITION_WRAPPERS = /* @__PURE__ */ new Set([
|
|
890
|
+
"Document",
|
|
891
|
+
"Definition",
|
|
892
|
+
"ExecutableDefinition",
|
|
893
|
+
"TypeSystemDefinition",
|
|
894
|
+
"TypeSystemExtension",
|
|
895
|
+
"TypeDefinition",
|
|
896
|
+
"TypeExtension"
|
|
897
|
+
]);
|
|
898
|
+
innermostNamed(node) {
|
|
899
|
+
let cur = node;
|
|
900
|
+
for (; ; ) {
|
|
901
|
+
if (!_RealGraphqlAnalyzer.DEFINITION_WRAPPERS.has(this.typeOf(cur))) return cur;
|
|
902
|
+
const next = this.onlyNamedChild(cur);
|
|
903
|
+
if (!next) return cur;
|
|
904
|
+
cur = next;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
onlyNamedChild(node) {
|
|
908
|
+
if (!node) return null;
|
|
909
|
+
const kids = node.namedChildren;
|
|
910
|
+
if (kids.length !== 1) return null;
|
|
911
|
+
return kids[0];
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* The document's `Definition` children. The tree is
|
|
915
|
+
* `source_file > Document > Definition`, so the root is NOT the document —
|
|
916
|
+
* reading `Definition` straight off `source_file` finds nothing, which is how
|
|
917
|
+
* the extension de-duplication silently became a no-op and every
|
|
918
|
+
* `extend type X` in a file that also declared `X` published a SECOND `class
|
|
919
|
+
* X` (a duplicate identity in the index).
|
|
920
|
+
*/
|
|
921
|
+
topLevelDefinitions(rootNode) {
|
|
922
|
+
if (this.typeOf(rootNode) === "Definition") return [rootNode];
|
|
923
|
+
if (this.typeOf(rootNode) === "Document") return this.childrenOfType(rootNode, "Definition");
|
|
924
|
+
const document = this.firstChildOfType(rootNode, "Document");
|
|
925
|
+
if (document) return this.childrenOfType(document, "Definition");
|
|
926
|
+
return this.childrenOfType(rootNode, "Definition");
|
|
927
|
+
}
|
|
928
|
+
childrenOfType(node, type) {
|
|
929
|
+
const out = [];
|
|
930
|
+
for (const c of node.namedChildren) {
|
|
931
|
+
if (c && this.typeOf(c) === type) out.push(c);
|
|
932
|
+
}
|
|
933
|
+
return out;
|
|
934
|
+
}
|
|
935
|
+
emitTypeSymbol(node, name, kind, typeNode, symbols, _relationships, _sel, _inExtension, directives) {
|
|
936
|
+
symbols.push({
|
|
937
|
+
name,
|
|
938
|
+
kind,
|
|
939
|
+
range: this.nodeToRange(node),
|
|
940
|
+
scope: "",
|
|
941
|
+
visibility: "public",
|
|
942
|
+
docstring: this.descriptionOf(node),
|
|
943
|
+
metadata: {
|
|
944
|
+
// `graphqlType` is the GRAMMAR's node type, on every branch of this
|
|
945
|
+
// analyzer, so a consumer can match on it; `graphqlCategory` is the
|
|
946
|
+
// specification's word for the same thing. They used to be one field
|
|
947
|
+
// with two possible vocabularies, which silently emptied `exports`.
|
|
948
|
+
graphqlType: typeNode,
|
|
949
|
+
topLevel: true,
|
|
950
|
+
graphqlCategory: typeNode === "UnionTypeDefinition" ? "union" : typeNode === "ScalarTypeDefinition" ? "scalar" : typeNode === "EnumTypeDefinition" ? "enum" : typeNode === "InputObjectTypeDefinition" ? "input" : typeNode === "InterfaceTypeDefinition" ? "interface" : "object",
|
|
951
|
+
...this.directiveMetadata(directives)
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* A directive applied to a declaration is metadata ON that declaration, and —
|
|
957
|
+
* when the directive is defined in this file — a real reference to the
|
|
958
|
+
* `@directive` function symbol. Federation schemas (`@key(fields: "id")`,
|
|
959
|
+
* `@external`, `@requires`) carry their meaning almost entirely in these
|
|
960
|
+
* applications, so dropping them drops the schema's structure.
|
|
961
|
+
*/
|
|
962
|
+
emitDirectiveApplications(_node, applications, owner, sel, addRelationship, inExtension) {
|
|
963
|
+
if (!owner || !applications) return;
|
|
964
|
+
for (const app of applications) {
|
|
965
|
+
addRelationship(owner, `@${app.name}`, ASTEngine.RelationshipType.Uses, app.line, "directive_application", {
|
|
966
|
+
...app.arguments ? { arguments: app.arguments } : {},
|
|
967
|
+
...sel.root && sel.root !== owner ? { within: sel.root } : {},
|
|
968
|
+
...inExtension ? { extension: true } : {}
|
|
969
|
+
});
|
|
970
|
+
const dnode = DIRECTIVE_NODE.get(app);
|
|
971
|
+
const dargs = dnode ? this.firstChildOfType(dnode, "Arguments") : null;
|
|
972
|
+
if (dargs) {
|
|
973
|
+
for (const arg of this.childrenOfType(dargs, "Argument")) {
|
|
974
|
+
this.emitEnumValueReferences(owner, this.firstChildOfType(arg, "Value"), void 0, addRelationship, {
|
|
975
|
+
declaration: "directive_argument",
|
|
976
|
+
directive: `@${app.name}`
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* The `Directives` scan itself: the `Directives` child of a definition, as
|
|
984
|
+
* structured metadata. A directive argument is a CONSTANT in GraphQL (the
|
|
985
|
+
* specification forbids variables there), so the `Value` node's own text is the
|
|
986
|
+
* literal's exact source spelling — no unquoting, no guessing, no regular
|
|
987
|
+
* expression. Called ONCE per node; the walk memoises it in a WeakMap, because
|
|
988
|
+
* this scan is the analyzer's single most expensive operation.
|
|
989
|
+
*/
|
|
990
|
+
scanDirectiveApplications(node) {
|
|
991
|
+
const directives = this.firstChildOfType(node, "Directives");
|
|
992
|
+
if (!directives) return null;
|
|
993
|
+
const list = [];
|
|
994
|
+
for (const d of this.childrenOfType(directives, "Directive")) {
|
|
995
|
+
const nameNode = this.firstChildOfType(d, "Name");
|
|
996
|
+
if (!nameNode) continue;
|
|
997
|
+
const argsNode = this.firstChildOfType(d, "Arguments");
|
|
998
|
+
const args = {};
|
|
999
|
+
if (argsNode) {
|
|
1000
|
+
for (const a of this.childrenOfType(argsNode, "Argument")) {
|
|
1001
|
+
const argName = this.nameOf(a);
|
|
1002
|
+
const value = this.firstChildOfType(a, "Value");
|
|
1003
|
+
if (argName && value) args[argName] = value.text;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
list.push({ name: nameNode.text, ...Object.keys(args).length ? { arguments: args } : {}, line: this.lineOf(nameNode) });
|
|
1007
|
+
DIRECTIVE_NODE.set(list[list.length - 1], d);
|
|
1008
|
+
}
|
|
1009
|
+
return list.length ? list : null;
|
|
1010
|
+
}
|
|
1011
|
+
/** The memoised scan's result, as symbol metadata; `{}` when there is none. */
|
|
1012
|
+
directiveMetadata(applications) {
|
|
1013
|
+
return applications ? { graphqlDirectives: applications } : _RealGraphqlAnalyzer.NO_DIRECTIVE_METADATA;
|
|
1014
|
+
}
|
|
1015
|
+
/** Frozen so a future in-place write fails loudly instead of corrupting every symbol. */
|
|
1016
|
+
static NO_DIRECTIVE_METADATA = Object.freeze({});
|
|
1017
|
+
/**
|
|
1018
|
+
* `on A | B | C` — the same left-recursion shape as `implements`, so the
|
|
1019
|
+
* locations are collected from the whole subtree, not from direct children.
|
|
1020
|
+
*/
|
|
1021
|
+
collectDirectiveLocations(node) {
|
|
1022
|
+
const out = [];
|
|
1023
|
+
const stack = [node];
|
|
1024
|
+
while (stack.length) {
|
|
1025
|
+
const n = stack.pop();
|
|
1026
|
+
if (this.typeOf(n) === "DirectiveLocation") {
|
|
1027
|
+
const inner = this.findChild(n, (c) => this.typeOf(c) === "ExecutableDirectiveLocation" || this.typeOf(c) === "TypeSystemDirectiveLocation");
|
|
1028
|
+
const leaf = inner ? inner.namedChild(0) ?? inner : n;
|
|
1029
|
+
out.push({ name: leaf.text, node: leaf });
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
const kids = n.namedChildren;
|
|
1033
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
1034
|
+
const c = kids[i];
|
|
1035
|
+
if (c) stack.push(c);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return out;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Every `NamedType` in a subtree, in source order and deduplicated.
|
|
1042
|
+
*
|
|
1043
|
+
* `ImplementsInterfaces` and `UnionMemberTypes` are left-recursive, so
|
|
1044
|
+
* `implements A & B & C` produces `I > I > I` in a tree-sitter build that
|
|
1045
|
+
* keeps the nesting, and a flat `I > [implements, A, &, B, &, C]` in one that
|
|
1046
|
+
* inlines the auxiliary repeat. Reading only DIRECT children — which is what
|
|
1047
|
+
* this analyzer used to do — therefore loses every implemented interface but
|
|
1048
|
+
* the last, and every union member but the last, on any grammar that nests.
|
|
1049
|
+
* Walking the subtree is correct for both shapes.
|
|
1050
|
+
*/
|
|
1051
|
+
collectNamedTypes(node) {
|
|
1052
|
+
const out = [];
|
|
1053
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1054
|
+
const stack = [node];
|
|
1055
|
+
while (stack.length) {
|
|
1056
|
+
const n = stack.pop();
|
|
1057
|
+
if (this.typeOf(n) === "NamedType") {
|
|
1058
|
+
const name = this.nameOf(n);
|
|
1059
|
+
if (name && !seen.has(name)) {
|
|
1060
|
+
seen.add(name);
|
|
1061
|
+
out.push({ name, node: n });
|
|
1062
|
+
}
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
const kids = n.namedChildren;
|
|
1066
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
1067
|
+
const c = kids[i];
|
|
1068
|
+
if (c) stack.push(c);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return out;
|
|
1072
|
+
}
|
|
1073
|
+
namedTypeNode(typeNode) {
|
|
1074
|
+
return this.findDescendant(typeNode, (n) => this.typeOf(n) === "NamedType");
|
|
1075
|
+
}
|
|
1076
|
+
/** Resolve a `Type`/`TypeCondition` node to its base named type name. */
|
|
1077
|
+
namedTypeName(typeNode) {
|
|
1078
|
+
const named = this.namedTypeNode(typeNode);
|
|
1079
|
+
return named ? this.nameOf(named) : void 0;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Non-null-ness read off the grammar, not off the text. `typeNode.text`
|
|
1083
|
+
* containing `!` was the previous implementation; a type NAME may legally
|
|
1084
|
+
* contain `!` nowhere, but a future grammar could wrap the marker, and the
|
|
1085
|
+
* `NonNullType` node is the fact.
|
|
1086
|
+
*/
|
|
1087
|
+
isNonNullType(typeNode) {
|
|
1088
|
+
const first = typeNode.namedChild(0);
|
|
1089
|
+
return first?.type === "NonNullType";
|
|
1090
|
+
}
|
|
1091
|
+
/** The `Value` of a `DefaultValue` child, as its exact source spelling. */
|
|
1092
|
+
defaultValueOf(node) {
|
|
1093
|
+
const dv = this.firstChildOfType(node, "DefaultValue");
|
|
1094
|
+
if (!dv) return void 0;
|
|
1095
|
+
const value = this.firstChildOfType(dv, "Value");
|
|
1096
|
+
return value?.text;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Every `EnumValue` in a VALUE position — a reference — with its line.
|
|
1100
|
+
*
|
|
1101
|
+
* The grammar puts an `EnumValue` node in two different places, and conflating
|
|
1102
|
+
* them is the whole bug: `EnumValueDefinition > EnumValue` is a DECLARATION
|
|
1103
|
+
* (already a symbol, with a `contains` edge from its enum), while
|
|
1104
|
+
* `Value > EnumValue` is a REFERENCE to one. Only the second is a relationship,
|
|
1105
|
+
* and the test is the parent, never the node type alone. Values nest
|
|
1106
|
+
* (`ListValue > Value`, `ObjectField > Value`), so this descends rather than
|
|
1107
|
+
* looking at direct children.
|
|
1108
|
+
*/
|
|
1109
|
+
enumValueReferences(valueNode) {
|
|
1110
|
+
const out = [];
|
|
1111
|
+
if (!valueNode) return out;
|
|
1112
|
+
const stack = [valueNode];
|
|
1113
|
+
while (stack.length) {
|
|
1114
|
+
const n = stack.pop();
|
|
1115
|
+
if (this.typeOf(n) === "EnumValue" && n.parent?.type === "Value") {
|
|
1116
|
+
const name = this.nameOf(n);
|
|
1117
|
+
if (name) out.push({ name, line: this.lineOf(n) });
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
const kids = n.namedChildren;
|
|
1121
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
1122
|
+
const c = kids[i];
|
|
1123
|
+
if (c) stack.push(c);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return out;
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Emit `uses` edges for the enum values a value position names.
|
|
1130
|
+
*
|
|
1131
|
+
* `enumType` is supplied ONLY where the document itself states which enum the
|
|
1132
|
+
* value belongs to — an `InputValueDefinition` or `VariableDefinition` carries
|
|
1133
|
+
* its own `Type` sibling, so `color: Color = RED` proves `RED` is `Color.RED`.
|
|
1134
|
+
* An executable field argument does not: `item(filter: { color: RED })` names
|
|
1135
|
+
* the INPUT OBJECT, and reaching `Color` would need that object's declaration,
|
|
1136
|
+
* which is in another file. So there the target stays the bare member name and
|
|
1137
|
+
* the store's own unique-name strategy decides it — the same rule the analyzer
|
|
1138
|
+
* already applies to a field selection.
|
|
1139
|
+
*/
|
|
1140
|
+
emitEnumValueReferences(source, valueNode, enumType, addRelationship, metadata = {}) {
|
|
1141
|
+
if (!source) return;
|
|
1142
|
+
for (const ref of this.enumValueReferences(valueNode)) {
|
|
1143
|
+
const target = enumType ? `${enumType}.${ref.name}` : ref.name;
|
|
1144
|
+
addRelationship(source, target, ASTEngine.RelationshipType.Uses, ref.line, "enum_value_reference", { ...metadata, ...enumType ? { enumType } : {} });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
argumentNames(argsNode) {
|
|
1148
|
+
return this.childrenOfType(argsNode, "Argument").map((a) => this.nameOf(a)).filter((n) => !!n);
|
|
1149
|
+
}
|
|
1150
|
+
argumentPairs(argsNode) {
|
|
1151
|
+
const pairs = [];
|
|
1152
|
+
for (const arg of this.childrenOfType(argsNode, "InputValueDefinition")) {
|
|
1153
|
+
const typeNode = this.firstChildOfType(arg, "Type");
|
|
1154
|
+
pairs.push({
|
|
1155
|
+
name: this.nameOf(arg),
|
|
1156
|
+
type: typeNode ? this.namedTypeName(typeNode) : void 0,
|
|
1157
|
+
typeNode: typeNode ?? void 0,
|
|
1158
|
+
defaultNode: this.firstChildOfType(arg, "DefaultValue") ?? void 0
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
return pairs;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* An argument's `isOptional` is GraphQL's own rule, not a guess from the
|
|
1165
|
+
* `!`: a NON-NULL argument is required; a NULLABLE argument is optional only
|
|
1166
|
+
* when it has a default. `f(x: Int)` is required, `f(x: Int = 1)` is not.
|
|
1167
|
+
*/
|
|
1168
|
+
extractArguments(argsNode) {
|
|
1169
|
+
const params = [];
|
|
1170
|
+
for (const arg of this.childrenOfType(argsNode, "InputValueDefinition")) {
|
|
1171
|
+
const name = this.nameOf(arg);
|
|
1172
|
+
if (!name) continue;
|
|
1173
|
+
const typeNode = this.firstChildOfType(arg, "Type");
|
|
1174
|
+
const defaultValue = this.defaultValueOf(arg);
|
|
1175
|
+
params.push({
|
|
1176
|
+
name,
|
|
1177
|
+
type: typeNode ? this.namedTypeName(typeNode) : void 0,
|
|
1178
|
+
defaultValue,
|
|
1179
|
+
// GraphQL's own rule, from §3.6.1: an argument is OPTIONAL only when it
|
|
1180
|
+
// is NULLABLE **and** has a default. `f(a: Int)` is required even
|
|
1181
|
+
// though `Int` is nullable, because a caller must pass something;
|
|
1182
|
+
// `f(a: Int = 1)` is not. The previous implementation asked only
|
|
1183
|
+
// `!typeNode.text.includes("!")`, so it reported both nullable shapes
|
|
1184
|
+
// as optional and inverted the one distinction GraphQL makes.
|
|
1185
|
+
isOptional: typeNode ? !this.isNonNullType(typeNode) && defaultValue !== void 0 : true
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
return params;
|
|
1189
|
+
}
|
|
1190
|
+
// ------------------------------------------------------------- descriptions
|
|
1191
|
+
/**
|
|
1192
|
+
* The description's VALUE, per specification §2.9.4.
|
|
1193
|
+
*
|
|
1194
|
+
* A `"…"` string is unquoted and its `EscapedCharacter` sequences are resolved
|
|
1195
|
+
* by an explicit character scan — `\" \\ \/ \b \f \n \r \t \uXXXX` and
|
|
1196
|
+
* `\\u{…}` — because the previous implementation returned the raw inner text
|
|
1197
|
+
* with the backslashes still in it, so `"a \"quote\""` was published as
|
|
1198
|
+
* `a \"quote\"`.
|
|
1199
|
+
*
|
|
1200
|
+
* A `"""…"""` block string is dedented as the specification defines: split on
|
|
1201
|
+
* line terminators, take the common indent of every line after the first that
|
|
1202
|
+
* has non-whitespace content, strip it, and drop leading and trailing blank
|
|
1203
|
+
* lines. Then `\"""` becomes `"""`.
|
|
1204
|
+
*/
|
|
1205
|
+
descriptionOf(node) {
|
|
1206
|
+
const desc = this.firstChildOfType(node, "Description");
|
|
1207
|
+
if (!desc) return void 0;
|
|
1208
|
+
const sv = this.firstChildOfType(desc, "StringValue");
|
|
1209
|
+
if (!sv) return void 0;
|
|
1210
|
+
const raw = sv.text;
|
|
1211
|
+
if (raw.length >= 6 && raw.startsWith('"""') && raw.endsWith('"""')) {
|
|
1212
|
+
return this.blockStringValue(raw.slice(3, raw.length - 3));
|
|
1213
|
+
}
|
|
1214
|
+
if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"')) {
|
|
1215
|
+
return this.unescapeStringValue(raw.slice(1, raw.length - 1));
|
|
1216
|
+
}
|
|
1217
|
+
return raw;
|
|
1218
|
+
}
|
|
1219
|
+
/** §2.9.4 BlockStringValue: the common-indent algorithm, then the escape. */
|
|
1220
|
+
blockStringValue(inner) {
|
|
1221
|
+
const lines = inner.split("\n");
|
|
1222
|
+
let common;
|
|
1223
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1224
|
+
const line = lines[i];
|
|
1225
|
+
let indent = 0;
|
|
1226
|
+
while (indent < line.length && (line[indent] === " " || line[indent] === " ")) indent++;
|
|
1227
|
+
if (indent === line.length) continue;
|
|
1228
|
+
common = common === void 0 ? indent : Math.min(common, indent);
|
|
1229
|
+
}
|
|
1230
|
+
if (common !== void 0 && common > 0) {
|
|
1231
|
+
for (let i = 1; i < lines.length; i++) lines[i] = lines[i].slice(common);
|
|
1232
|
+
}
|
|
1233
|
+
while (lines.length > 0 && lines[0].trim() === "") lines.shift();
|
|
1234
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
|
|
1235
|
+
return lines.join("\n").replaceAll('\\"""', '"""');
|
|
1236
|
+
}
|
|
1237
|
+
/** §2.9.4 EscapedCharacter, resolved by scanning — no regular expression. */
|
|
1238
|
+
unescapeStringValue(inner) {
|
|
1239
|
+
let out = "";
|
|
1240
|
+
for (let i = 0; i < inner.length; i++) {
|
|
1241
|
+
const ch = inner[i];
|
|
1242
|
+
if (ch !== "\\") {
|
|
1243
|
+
out += ch;
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
const next = inner[++i];
|
|
1247
|
+
switch (next) {
|
|
1248
|
+
case "b":
|
|
1249
|
+
out += "\b";
|
|
1250
|
+
break;
|
|
1251
|
+
case "f":
|
|
1252
|
+
out += "\f";
|
|
1253
|
+
break;
|
|
1254
|
+
case "n":
|
|
1255
|
+
out += "\n";
|
|
1256
|
+
break;
|
|
1257
|
+
case "r":
|
|
1258
|
+
out += "\r";
|
|
1259
|
+
break;
|
|
1260
|
+
case "t":
|
|
1261
|
+
out += " ";
|
|
1262
|
+
break;
|
|
1263
|
+
case "u": {
|
|
1264
|
+
if (inner[i + 1] === "{") {
|
|
1265
|
+
const close = inner.indexOf("}", i + 2);
|
|
1266
|
+
if (close > 0) {
|
|
1267
|
+
const code2 = Number.parseInt(inner.slice(i + 2, close), 16);
|
|
1268
|
+
if (Number.isFinite(code2) && code2 >= 0 && code2 <= 1114111) {
|
|
1269
|
+
out += String.fromCodePoint(code2);
|
|
1270
|
+
i = close;
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
out += "\\u";
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
const hex = inner.slice(i + 1, i + 5);
|
|
1278
|
+
const code = Number.parseInt(hex, 16);
|
|
1279
|
+
if (hex.length === 4 && Number.isFinite(code)) {
|
|
1280
|
+
out += String.fromCharCode(code);
|
|
1281
|
+
i += 4;
|
|
1282
|
+
} else {
|
|
1283
|
+
out += "\\u";
|
|
1284
|
+
}
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
case void 0:
|
|
1288
|
+
out += "\\";
|
|
1289
|
+
break;
|
|
1290
|
+
default:
|
|
1291
|
+
out += next;
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return out;
|
|
1296
|
+
}
|
|
1297
|
+
// -------------------------------------------------------------------- ranges
|
|
1298
|
+
/**
|
|
1299
|
+
* 1-based line and column, byte offsets, and a leading `Description` skipped.
|
|
1300
|
+
*
|
|
1301
|
+
* The `Description` is a child of the definition node in this grammar, so a
|
|
1302
|
+
* naive `nodeToRange` reports the DOCSTRING's line for every described type,
|
|
1303
|
+
* field, argument, enum value and input field. On the GitHub public schema
|
|
1304
|
+
* that is 3,131 of 3,332 symbols — 94 % of the file's symbols pointing at a
|
|
1305
|
+
* line that does not contain the name they are named for. The M59/#861 rule
|
|
1306
|
+
* (a decorated declaration's line belongs to the name, not to its decorator)
|
|
1307
|
+
* is the same defect with a different decorator, and scala
|
|
1308
|
+
* (`declaration-lines-825`), typescript and javascript already implement it.
|
|
1309
|
+
*/
|
|
1310
|
+
nodeToRange(node) {
|
|
1311
|
+
let anchor = node;
|
|
1312
|
+
const firstChild = node.child(0);
|
|
1313
|
+
if (firstChild && this.typeOf(firstChild) === "Description") {
|
|
1314
|
+
const afterDescription = node.child(1);
|
|
1315
|
+
if (afterDescription) anchor = afterDescription;
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
startLine: anchor.startPosition.row + 1,
|
|
1319
|
+
startColumn: anchor.startPosition.column + 1,
|
|
1320
|
+
endLine: node.endPosition.row + 1,
|
|
1321
|
+
endColumn: node.endPosition.column + 1,
|
|
1322
|
+
startIndex: anchor.startIndex,
|
|
1323
|
+
endIndex: node.endIndex
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
lineOf(node) {
|
|
1327
|
+
return node ? node.startPosition.row + 1 : 0;
|
|
1328
|
+
}
|
|
1329
|
+
// ------------------------------------------------------------ node plumbing
|
|
1330
|
+
/** The definition's own name: its direct `Name` child, or a wrapper's. */
|
|
1331
|
+
nameOf(node) {
|
|
1332
|
+
return this.firstChildOfType(node, "Name")?.text;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Is there a child matching `predicate`, ANONYMOUS children included?
|
|
1336
|
+
*
|
|
1337
|
+
* `findChild` walks `namedChild`, and `repeatable` is an anonymous keyword
|
|
1338
|
+
* token in this grammar, so `directive @a repeatable on FIELD` reported
|
|
1339
|
+
* `repeatable: false` — the specification's June-2018 feature was invisible
|
|
1340
|
+
* on every directive that used it.
|
|
1341
|
+
*/
|
|
1342
|
+
hasChild(node, predicate) {
|
|
1343
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1344
|
+
const child = node.child(i);
|
|
1345
|
+
if (child && predicate(child)) return true;
|
|
1346
|
+
}
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
isAnyOf(node, ...types) {
|
|
1350
|
+
return types.includes(this.typeOf(node));
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* A node's NAMED children, in ONE wasm call.
|
|
1354
|
+
*
|
|
1355
|
+
* web-tree-sitter documents `namedChild(index)` as *"fairly fast, but its cost
|
|
1356
|
+
* is technically log(n), so if you might be iterating over a long list of
|
|
1357
|
+
* children, you should use Node#namedChildren instead"*, and that getter
|
|
1358
|
+
* materialises the whole list in a single call and CACHES it on the node. A
|
|
1359
|
+
* `namedChildCount` + `namedChild(i)` loop is therefore the O(n log n)
|
|
1360
|
+
* anti-pattern, and it was the one this analyzer used in nine places.
|
|
1361
|
+
*
|
|
1362
|
+
* The CPU profile of the 20,368-line GitHub public schema said so before this
|
|
1363
|
+
* change: the tree-sitter accessor layer — `setValue`, `getValue`,
|
|
1364
|
+
* `ts_node_named_child_wasm`, `ts_node_named_child_count_wasm`,
|
|
1365
|
+
* `get namedChildCount` — was over 11% of self time, and `pushChildren`, which
|
|
1366
|
+
* runs once per node, was its largest caller. The binding's own `children` /
|
|
1367
|
+
* `namedChildren` getters also replace a per-child `unmarshalNode` with one
|
|
1368
|
+
* bulk transfer, so this is fewer wasm round-trips as well as less arithmetic.
|
|
1369
|
+
*
|
|
1370
|
+
* The returned array is the BINDING'S OWN CACHE. It is read, never written.
|
|
1371
|
+
*/
|
|
1372
|
+
namedChildrenOf(node) {
|
|
1373
|
+
return node.namedChildren;
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* The first NAMED child of `type`, or null — with no closure and no per-index
|
|
1377
|
+
* wasm call.
|
|
1378
|
+
*
|
|
1379
|
+
* `findChild(node, n => this.typeOf(n) === "X")` is the idiomatic spelling and it was
|
|
1380
|
+
* used 40 times here, so it allocated 40 closures PER SYMBOL and reached `.type`
|
|
1381
|
+
* through a call frame for every child it examined. The comparison is a string
|
|
1382
|
+
* equality; the closure buys nothing. This is the same shape the SQL analyzer
|
|
1383
|
+
* uses for its own direct-child reads, and it is the shape the profile asked
|
|
1384
|
+
* for: `analyze` self time was 6.1% and the tree-sitter accessor layer was
|
|
1385
|
+
* another ~9%, and both are dominated by the NUMBER of property reads rather
|
|
1386
|
+
* than by any algorithm of ours.
|
|
1387
|
+
*/
|
|
1388
|
+
firstChildOfType(node, type) {
|
|
1389
|
+
if (!node) return null;
|
|
1390
|
+
for (const child of node.namedChildren) {
|
|
1391
|
+
if (child !== null && this.typeOf(child) === type) return child;
|
|
1392
|
+
}
|
|
1393
|
+
return null;
|
|
1394
|
+
}
|
|
1395
|
+
findChild(node, predicate) {
|
|
1396
|
+
for (const child of node.namedChildren) {
|
|
1397
|
+
if (child && predicate(child)) return child;
|
|
1398
|
+
}
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* The first descendant satisfying `predicate`, in pre-order.
|
|
1403
|
+
*
|
|
1404
|
+
* ITERATIVE, and the recursion was a real defect rather than a style choice. This
|
|
1405
|
+
* walks to the depth of the subtree when the predicate does not match early, and
|
|
1406
|
+
* `[[[…[Int]]]]` is exactly that case: the `NamedType` sits at the BOTTOM, so the
|
|
1407
|
+
* recursion went as deep as the nesting. Measured, list nesting **2,749 deep
|
|
1408
|
+
* threw `RangeError: Maximum call stack size exceeded`**, and because the engine's
|
|
1409
|
+
* guard turns any throw into a minimal result, that file yielded ZERO symbols and
|
|
1410
|
+
* one opaque error — silently losing a whole type definition. The threshold is a
|
|
1411
|
+
* function of stack-frame size, so it is a platform figure, not a guarantee.
|
|
1412
|
+
*
|
|
1413
|
+
* `A & B & C` was never affected, which is why the bug survived the corpus: the
|
|
1414
|
+
* left-recursive rules put a `NamedType` at depth 1, so the predicate matched
|
|
1415
|
+
* immediately and the recursion never went deep. 5,000 clauses still work.
|
|
1416
|
+
*
|
|
1417
|
+
* Children are pushed in reverse so they POP in order, which keeps this a
|
|
1418
|
+
* pre-order DFS with first-match-wins — the same order, and the same result, as
|
|
1419
|
+
* the recursion it replaces.
|
|
1420
|
+
*/
|
|
1421
|
+
findDescendant(node, predicate) {
|
|
1422
|
+
const stack = [];
|
|
1423
|
+
const rootKids = node.namedChildren;
|
|
1424
|
+
for (let i = rootKids.length - 1; i >= 0; i--) {
|
|
1425
|
+
const c = rootKids[i];
|
|
1426
|
+
if (c) stack.push(c);
|
|
1427
|
+
}
|
|
1428
|
+
while (stack.length) {
|
|
1429
|
+
const n = stack.pop();
|
|
1430
|
+
if (predicate(n)) return n;
|
|
1431
|
+
const kids = n.namedChildren;
|
|
1432
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
1433
|
+
const c = kids[i];
|
|
1434
|
+
if (c) stack.push(c);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
export {
|
|
1441
|
+
RealGraphqlAnalyzer
|
|
1442
|
+
};
|