@backbencher/brain 0.0.0-stage → 0.1.1
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-Q5APTJQL.js +17 -0
- package/dist/brain-relationship-enrichment-MYR2AM4F.js +558 -0
- package/dist/brain.sql-EIM4WS4D.js +29 -0
- package/dist/chunk-22V37YTK.js +1758 -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-2WXYNLBA.js +29 -0
- package/dist/chunk-3CTFIJJB.js +17 -0
- package/dist/chunk-3ZTOHHSW.js +2104 -0
- package/dist/chunk-4H22TLOT.js +543 -0
- package/dist/chunk-5BVAH26D.js +340 -0
- package/dist/chunk-62DHLYW6.js +3427 -0
- package/dist/chunk-7AN7U5IZ.js +371 -0
- package/dist/chunk-7DRPNVIN.js +782 -0
- package/dist/chunk-7MBYT777.js +174 -0
- package/dist/chunk-AHDWGM7Q.js +236 -0
- package/dist/chunk-B5GEAAYF.js +818 -0
- package/dist/chunk-BEIZZ73B.js +1479 -0
- package/dist/chunk-BQ3OVQ3P.js +125 -0
- package/dist/chunk-D2Q4Y47A.js +53 -0
- package/dist/chunk-DIYAGQJL.js +34 -0
- package/dist/chunk-DWKXZ6IF.js +336 -0
- package/dist/chunk-DYM4FQEE.js +90 -0
- package/dist/chunk-ENW4BTBA.js +930 -0
- package/dist/chunk-EWMWK7WN.js +75 -0
- package/dist/chunk-EY5ZZUUA.js +626 -0
- package/dist/chunk-GBI7V7KY.js +69 -0
- package/dist/chunk-GEFFO6O2.js +152 -0
- package/dist/chunk-HJEWO5EV.js +247 -0
- package/dist/chunk-HQSPJDHI.js +613 -0
- package/dist/chunk-KAET47ZL.js +125 -0
- package/dist/chunk-KCDECRJY.js +440 -0
- package/dist/chunk-L5TDYKYK.js +76 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-NO3U3FJQ.js +201 -0
- package/dist/chunk-OWISBAOF.js +61 -0
- package/dist/chunk-QKTRECYS.js +1119 -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-RM3PY6TG.js +376 -0
- package/dist/chunk-ROMM6KMC.js +433 -0
- package/dist/chunk-SGGCSHAX.js +12050 -0
- package/dist/chunk-SK6YVHFZ.js +211 -0
- package/dist/chunk-SM7RFQRO.js +214 -0
- package/dist/chunk-UGR4QIGS.js +67 -0
- package/dist/chunk-UQNQZXHY.js +711 -0
- package/dist/chunk-UUNHHX4M.js +13 -0
- package/dist/chunk-VL52PL2T.js +71 -0
- package/dist/chunk-WIG36E67.js +196 -0
- package/dist/chunk-XHK3WWGB.js +7436 -0
- package/dist/chunk-XTQM2IP3.js +2863 -0
- package/dist/chunk-ZE2LG76D.js +1296 -0
- package/dist/chunk-ZETTGPAW.js +6950 -0
- package/dist/cli/index.js +37 -0
- package/dist/config-HZAAYDP3.js +10 -0
- package/dist/content-search-7XIP555Q.js +593 -0
- package/dist/database-76RP44JG.js +9 -0
- package/dist/doctor-ANGETYMN.js +395 -0
- package/dist/edit-engine-CG72LDZD.js +51 -0
- package/dist/engine-BKEOGN7N.js +11 -0
- package/dist/engine-VIW6NSKO.js +18 -0
- package/dist/fingerprint-OBLIMZLP.js +33 -0
- package/dist/framework-intelligence-3LORTPOX.js +860 -0
- package/dist/freshness-3K6VU5OT.js +174 -0
- package/dist/graph-engine-HBSQNPTB.js +21 -0
- package/dist/graph_request-Q6NKB6N2.js +350 -0
- package/dist/impact_request-LYP672WE.js +68 -0
- package/dist/index-space-IUUN5WYV.js +15 -0
- package/dist/index-status-7OVGYSGW.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-FK2XKZXY.js +88 -0
- package/dist/pool-worker.js +1153 -0
- package/dist/project-health-6XPENVAA.js +94 -0
- package/dist/project-index-5SDPHITE.js +57 -0
- package/dist/project-registry-ZJ3DVUGO.js +355 -0
- package/dist/real-bash-analyzer-EJAU5LW5.js +2201 -0
- package/dist/real-c-analyzer-HBD3FNNH.js +5942 -0
- package/dist/real-clean-analyzer-4EEZLPV5.js +3470 -0
- package/dist/real-clojure-analyzer-Q55EBOO2.js +2818 -0
- package/dist/real-cpp-analyzer-IVVOD25R.js +4101 -0
- package/dist/real-csharp-analyzer-ZBNLNJZJ.js +3298 -0
- package/dist/real-css-analyzer-6XRQLUN7.js +15 -0
- package/dist/real-dart-analyzer-OLR72EFW.js +3944 -0
- package/dist/real-elixir-analyzer-MQWGAH2I.js +2192 -0
- package/dist/real-erlang-analyzer-7BVPQEHO.js +1731 -0
- package/dist/real-fsharp-analyzer-C2UPFNOA.js +2390 -0
- package/dist/real-go-analyzer-UKGI43YS.js +4126 -0
- package/dist/real-graphql-analyzer-VE2EEFII.js +1442 -0
- package/dist/real-groovy-analyzer-W26VACFW.js +2285 -0
- package/dist/real-haskell-analyzer-5CVVAJRX.js +2533 -0
- package/dist/real-hcl-analyzer-5ZT4U43N.js +725 -0
- package/dist/real-html-analyzer-JWNX3C6I.js +1759 -0
- package/dist/real-java-analyzer-GU42VBGQ.js +4677 -0
- package/dist/real-javascript-analyzer-QA4QOJ6W.js +14 -0
- package/dist/real-json-analyzer-MPA6PRXL.js +1640 -0
- package/dist/real-julia-analyzer-22CWYQMN.js +2085 -0
- package/dist/real-kotlin-analyzer-QDZJLXXU.js +3956 -0
- package/dist/real-lua-analyzer-G7NHLYKS.js +1957 -0
- package/dist/real-makefile-analyzer-OCY3GT5N.js +920 -0
- package/dist/real-markdown-analyzer-TEJK2JXJ.js +1215 -0
- package/dist/real-matlab-analyzer-Q5SWLN7Z.js +480 -0
- package/dist/real-nim-analyzer-YLXSK563.js +3603 -0
- package/dist/real-objective-c-analyzer-D23ACC2R.js +1518 -0
- package/dist/real-perl-analyzer-WWTRHYDT.js +1865 -0
- package/dist/real-php-analyzer-JNKJHTCL.js +9368 -0
- package/dist/real-powershell-analyzer-XRSNQERV.js +2915 -0
- package/dist/real-protobuf-analyzer-3OKUCZ7T.js +867 -0
- package/dist/real-python-analyzer-VXAFWI72.js +4880 -0
- package/dist/real-r-analyzer-ZGAIDVI4.js +2596 -0
- package/dist/real-ruby-analyzer-X2KTLVBH.js +2429 -0
- package/dist/real-rust-analyzer-OFFHPL2A.js +3359 -0
- package/dist/real-scala-analyzer-TSTW27J6.js +3293 -0
- package/dist/real-scss-analyzer-AVWYPSOQ.js +1515 -0
- package/dist/real-solidity-analyzer-UCI2KWUB.js +2608 -0
- package/dist/real-sql-analyzer-F77ZMQXR.js +3840 -0
- package/dist/real-svelte-analyzer-SF7NRUO5.js +2532 -0
- package/dist/real-swift-analyzer-MN5QT3QN.js +2311 -0
- package/dist/real-toml-analyzer-66L7BDCQ.js +929 -0
- package/dist/real-typescript-analyzer-6KEMVDWQ.js +14 -0
- package/dist/real-vb-analyzer-VICVPOP7.js +3145 -0
- package/dist/real-vba-analyzer-HXLTTHKM.js +466 -0
- package/dist/real-vue-analyzer-QXR56CFV.js +2680 -0
- package/dist/real-xml-analyzer-YL465ZJW.js +2405 -0
- package/dist/real-yaml-analyzer-SUBCBNSI.js +3528 -0
- package/dist/real-zig-analyzer-HIQLZMGY.js +2430 -0
- package/dist/regex-search-NJKHMZNO.js +239 -0
- package/dist/rules-ACHPB7QU.js +152 -0
- package/dist/scoped-path-MJYWJZQX.js +11 -0
- package/dist/setup-show-NT5ZHYNE.js +15 -0
- package/dist/setup-wizard-G5YYR5T6.js +24 -0
- package/dist/symbol-search-GZJIM2J2.js +16 -0
- package/dist/taint-KXUHHPIE.js +202 -0
- package/dist/taint-analyzer-N7YLZU3N.js +163 -0
- package/dist/tool-handlers-OJDC35DI.js +72 -0
- package/dist/tool-registry-BPEJNSZG.js +17 -0
- package/dist/tool-validation-Q57YMMBX.js +32 -0
- package/dist/type-inference-QMT2RMLV.js +13 -0
- package/dist/vector-binary-LMY4QGAN.js +19 -0
- package/dist/version-ABP2TUL2.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,2430 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FILE_CLAIM_MAX_BYTES,
|
|
3
|
+
fileClaimForTarget
|
|
4
|
+
} from "./chunk-EWMWK7WN.js";
|
|
5
|
+
import {
|
|
6
|
+
ASTEngine
|
|
7
|
+
} from "./chunk-3ZTOHHSW.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-zig-analyzer.ts
|
|
16
|
+
var ZIG_ADMITTED_EXTENSIONS = ["zig"];
|
|
17
|
+
var ZIG_TYPE_RHS = /* @__PURE__ */ new Set([
|
|
18
|
+
"builtin_type",
|
|
19
|
+
"pointer_type",
|
|
20
|
+
"slice_type",
|
|
21
|
+
"array_type",
|
|
22
|
+
"nullable_type",
|
|
23
|
+
"error_union_type",
|
|
24
|
+
"anyframe_type",
|
|
25
|
+
"opaque_declaration",
|
|
26
|
+
"function_signature"
|
|
27
|
+
]);
|
|
28
|
+
var ANONYMOUS_CONTAINER_NODES = /* @__PURE__ */ new Set([
|
|
29
|
+
"struct_declaration",
|
|
30
|
+
"union_declaration",
|
|
31
|
+
"enum_declaration",
|
|
32
|
+
"error_set_declaration",
|
|
33
|
+
"opaque_declaration"
|
|
34
|
+
]);
|
|
35
|
+
var ENUM_MEMBER_NAME_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
36
|
+
"builtin_type",
|
|
37
|
+
"boolean",
|
|
38
|
+
"null",
|
|
39
|
+
"undefined"
|
|
40
|
+
]);
|
|
41
|
+
var ZIG_VALUE_RHS = /* @__PURE__ */ new Set([
|
|
42
|
+
"integer",
|
|
43
|
+
"float",
|
|
44
|
+
"string",
|
|
45
|
+
"multiline_string",
|
|
46
|
+
"boolean",
|
|
47
|
+
"character",
|
|
48
|
+
"binary_expression",
|
|
49
|
+
"unary_expression",
|
|
50
|
+
"null_coercion_expression",
|
|
51
|
+
"index_expression",
|
|
52
|
+
"catch_expression",
|
|
53
|
+
"block",
|
|
54
|
+
"initializer_list",
|
|
55
|
+
"range_expression",
|
|
56
|
+
"assignment_expression",
|
|
57
|
+
"error_type",
|
|
58
|
+
"anonymous_struct_initializer",
|
|
59
|
+
"dereference_expression"
|
|
60
|
+
]);
|
|
61
|
+
var ZIG_FILE_SCOPE_NAMES = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
function zigFileScopeNames(root) {
|
|
63
|
+
const cached = ZIG_FILE_SCOPE_NAMES.get(root);
|
|
64
|
+
if (cached) return cached;
|
|
65
|
+
const BARRIERS = /* @__PURE__ */ new Set([
|
|
66
|
+
"function_declaration",
|
|
67
|
+
"test_declaration",
|
|
68
|
+
"block",
|
|
69
|
+
"if_statement",
|
|
70
|
+
"if_expression",
|
|
71
|
+
"for_statement",
|
|
72
|
+
"for_expression",
|
|
73
|
+
"while_statement",
|
|
74
|
+
"while_expression",
|
|
75
|
+
"switch_statement",
|
|
76
|
+
"switch_expression",
|
|
77
|
+
"catch_expression",
|
|
78
|
+
"defer_statement",
|
|
79
|
+
"expression_statement",
|
|
80
|
+
"comptime_statement",
|
|
81
|
+
"errdefer_statement"
|
|
82
|
+
]);
|
|
83
|
+
const out = /* @__PURE__ */ new Set();
|
|
84
|
+
const stack = [root];
|
|
85
|
+
let guard = 0;
|
|
86
|
+
while (stack.length > 0 && guard++ < 4e5) {
|
|
87
|
+
const n = stack.pop();
|
|
88
|
+
if (n.type === "variable_declaration" || n.type === "function_declaration" || n.type === "container_field") {
|
|
89
|
+
const c0 = n.namedChildCount > 0 ? n.namedChild(0) : null;
|
|
90
|
+
if (c0 && c0.type === "identifier") {
|
|
91
|
+
let q = n.parent;
|
|
92
|
+
let fileScope = true;
|
|
93
|
+
let hops = 0;
|
|
94
|
+
while (q && hops++ < 40) {
|
|
95
|
+
if (BARRIERS.has(q.type)) {
|
|
96
|
+
fileScope = false;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
q = q.parent;
|
|
100
|
+
}
|
|
101
|
+
if (fileScope) out.add(stripZigQuoting(c0.text));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
105
|
+
const c = n.namedChild(i);
|
|
106
|
+
if (c) stack.push(c);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
ZIG_FILE_SCOPE_NAMES.set(root, out);
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
var ZIG_IMPORT_ALIASES = /* @__PURE__ */ new WeakMap();
|
|
113
|
+
function zigImportAliases(root) {
|
|
114
|
+
const cached = ZIG_IMPORT_ALIASES.get(root);
|
|
115
|
+
if (cached) return cached;
|
|
116
|
+
const imports = /* @__PURE__ */ new Map();
|
|
117
|
+
const shadowed = /* @__PURE__ */ new Set();
|
|
118
|
+
const stack = [root];
|
|
119
|
+
let guard = 0;
|
|
120
|
+
while (stack.length > 0 && guard++ < 4e5) {
|
|
121
|
+
const n = stack.pop();
|
|
122
|
+
if (n.type === "variable_declaration") {
|
|
123
|
+
const kids = zigNamedChildren(n);
|
|
124
|
+
const nameNode = kids.length > 0 && kids[0].type === "identifier" ? kids[0] : null;
|
|
125
|
+
const rhs = kids.length > 0 ? kids[kids.length - 1] : null;
|
|
126
|
+
const bound = nameNode ? stripZigQuoting(nameNode.text) : "";
|
|
127
|
+
if (bound) {
|
|
128
|
+
const lit = zigImportModuleLiteral(rhs);
|
|
129
|
+
if (lit) imports.set(bound, { module: lit.module, external: zigModuleIsExternal(lit.module) });
|
|
130
|
+
else shadowed.add(bound);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
134
|
+
const c = n.namedChild(i);
|
|
135
|
+
if (c) stack.push(c);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const s of shadowed) imports.delete(s);
|
|
139
|
+
ZIG_IMPORT_ALIASES.set(root, imports);
|
|
140
|
+
return imports;
|
|
141
|
+
}
|
|
142
|
+
function zigModuleIsExternal(modulePath) {
|
|
143
|
+
if (modulePath.startsWith(".") || modulePath.startsWith("/")) return false;
|
|
144
|
+
if (modulePath.includes("/") || modulePath.includes("\\")) return false;
|
|
145
|
+
if (modulePath.endsWith(".zig") || modulePath.endsWith(".zon")) return false;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function zigImportModuleLiteral(rhs) {
|
|
149
|
+
if (!rhs) return null;
|
|
150
|
+
const host = rhs.type === "call_expression" || rhs.type === "builtin_function" ? rhs : null;
|
|
151
|
+
if (!host) return null;
|
|
152
|
+
let isImport = false;
|
|
153
|
+
for (let i = 0; i < host.namedChildCount; i++) {
|
|
154
|
+
const c = host.namedChild(i);
|
|
155
|
+
if (c && (c.type === "builtin_identifier" || c.type === "identifier")) {
|
|
156
|
+
const t = String(c.text ?? "");
|
|
157
|
+
if (t === "@import" || t === "@cImport" || t === "import") {
|
|
158
|
+
isImport = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!isImport) return null;
|
|
164
|
+
const firstString = (h) => {
|
|
165
|
+
if (!h) return null;
|
|
166
|
+
for (let i = 0; i < h.namedChildCount; i++) {
|
|
167
|
+
const c = h.namedChild(i);
|
|
168
|
+
if (c && (c.type === "string" || c.type === "string_content")) return c;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
};
|
|
172
|
+
let args = null;
|
|
173
|
+
for (let i = 0; i < host.namedChildCount; i++) {
|
|
174
|
+
const c = host.namedChild(i);
|
|
175
|
+
if (c && c.type === "arguments") {
|
|
176
|
+
args = c;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
let strNode = firstString(host) ?? firstString(args);
|
|
181
|
+
if (strNode && strNode.type === "string") {
|
|
182
|
+
for (let i = 0; i < strNode.namedChildCount; i++) {
|
|
183
|
+
const sc = strNode.namedChild(i);
|
|
184
|
+
if (sc && sc.type === "string_content") {
|
|
185
|
+
strNode = sc;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!strNode) return null;
|
|
191
|
+
let out = String(strNode.text ?? "");
|
|
192
|
+
if (out.startsWith('"') && out.endsWith('"') || out.startsWith("'") && out.endsWith("'")) out = out.substring(1, out.length - 1);
|
|
193
|
+
return out.length > 0 ? { module: out, node: strNode } : null;
|
|
194
|
+
}
|
|
195
|
+
function zigNamedChildren(n) {
|
|
196
|
+
const out = [];
|
|
197
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
198
|
+
const c = n.namedChild(i);
|
|
199
|
+
if (c) out.push(c);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
function zigModuleSymbol(specifier) {
|
|
204
|
+
const slash = specifier.lastIndexOf("/");
|
|
205
|
+
const base = slash >= 0 ? specifier.slice(slash + 1) : specifier;
|
|
206
|
+
const dot = base.lastIndexOf(".");
|
|
207
|
+
const stem = dot > 0 ? base.slice(0, dot) : base;
|
|
208
|
+
return stem.trim();
|
|
209
|
+
}
|
|
210
|
+
function zigImportModuleInSubtree(node) {
|
|
211
|
+
let found = null;
|
|
212
|
+
const walk = (n) => {
|
|
213
|
+
if (found !== null) return;
|
|
214
|
+
if (n.type === "builtin_function") {
|
|
215
|
+
const lit = zigImportModuleLiteral(n);
|
|
216
|
+
if (lit) {
|
|
217
|
+
found = lit.module;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
222
|
+
const c = n.namedChild(i);
|
|
223
|
+
if (c) walk(c);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
walk(node);
|
|
227
|
+
return found;
|
|
228
|
+
}
|
|
229
|
+
function zigMergedMemberLeaf(expr) {
|
|
230
|
+
if (expr.type === "identifier") return stripZigQuoting(String(expr.text ?? ""));
|
|
231
|
+
if (expr.type === "field_expression") {
|
|
232
|
+
const last = expr.namedChildCount > 0 ? expr.namedChild(expr.namedChildCount - 1) : null;
|
|
233
|
+
return last ? zigMergedMemberLeaf(last) : null;
|
|
234
|
+
}
|
|
235
|
+
if (expr.type === "parenthesized_expression") {
|
|
236
|
+
for (let i = expr.namedChildCount - 1; i >= 0; i--) {
|
|
237
|
+
const c = expr.namedChild(i);
|
|
238
|
+
const leaf = c ? zigMergedMemberLeaf(c) : null;
|
|
239
|
+
if (leaf) return leaf;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
function zigUsingNamespaceMergedUnit(node) {
|
|
245
|
+
let arg = null;
|
|
246
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
247
|
+
const c = node.namedChild(i);
|
|
248
|
+
if (c && c.type !== "comment") {
|
|
249
|
+
arg = c;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (!arg) return null;
|
|
254
|
+
if (arg.type === "builtin_function") {
|
|
255
|
+
let builtin = "";
|
|
256
|
+
for (let i = 0; i < arg.namedChildCount; i++) {
|
|
257
|
+
const c = arg.namedChild(i);
|
|
258
|
+
if (c && c.type === "builtin_identifier") {
|
|
259
|
+
builtin = String(c.text ?? "");
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (builtin !== "@import") return null;
|
|
264
|
+
const lit = zigImportModuleLiteral(arg);
|
|
265
|
+
if (!lit) return null;
|
|
266
|
+
const target2 = zigModuleSymbol(lit.module);
|
|
267
|
+
return target2 ? { target: target2, form: "import", module: lit.module } : null;
|
|
268
|
+
}
|
|
269
|
+
if (arg.type === "identifier") {
|
|
270
|
+
const target2 = stripZigQuoting(String(arg.text ?? ""));
|
|
271
|
+
return target2 ? { target: target2, form: "identifier" } : null;
|
|
272
|
+
}
|
|
273
|
+
const target = zigMergedMemberLeaf(arg);
|
|
274
|
+
if (!target) return null;
|
|
275
|
+
const module = zigImportModuleInSubtree(arg);
|
|
276
|
+
return { target, form: module ? "import_member" : "identifier", ...module ? { module } : {} };
|
|
277
|
+
}
|
|
278
|
+
var ZIG_DECLARED_TYPE_BUILTINS = /* @__PURE__ */ new Set([
|
|
279
|
+
"void",
|
|
280
|
+
"bool",
|
|
281
|
+
"f16",
|
|
282
|
+
"f32",
|
|
283
|
+
"f64",
|
|
284
|
+
"f80",
|
|
285
|
+
"f128",
|
|
286
|
+
"c_longdouble",
|
|
287
|
+
"i8",
|
|
288
|
+
"i16",
|
|
289
|
+
"i32",
|
|
290
|
+
"i64",
|
|
291
|
+
"i128",
|
|
292
|
+
"isize",
|
|
293
|
+
"u8",
|
|
294
|
+
"u16",
|
|
295
|
+
"u32",
|
|
296
|
+
"u64",
|
|
297
|
+
"u128",
|
|
298
|
+
"usize",
|
|
299
|
+
"anytype",
|
|
300
|
+
"comptime_int",
|
|
301
|
+
"comptime_float",
|
|
302
|
+
"noreturn",
|
|
303
|
+
"type",
|
|
304
|
+
"anyerror",
|
|
305
|
+
"c_char",
|
|
306
|
+
"c_short",
|
|
307
|
+
"c_int",
|
|
308
|
+
"c_long",
|
|
309
|
+
"c_longlong",
|
|
310
|
+
"c_uint",
|
|
311
|
+
"c_ulong",
|
|
312
|
+
"c_ulonglong",
|
|
313
|
+
"c_void",
|
|
314
|
+
"error",
|
|
315
|
+
"test"
|
|
316
|
+
]);
|
|
317
|
+
function stripZigQuoting(text) {
|
|
318
|
+
if (!text.includes('@"')) return text;
|
|
319
|
+
let out = "";
|
|
320
|
+
let i = 0;
|
|
321
|
+
while (i < text.length) {
|
|
322
|
+
if (text.charCodeAt(i) === 64 && text[i + 1] === '"') {
|
|
323
|
+
const close = text.indexOf('"', i + 2);
|
|
324
|
+
if (close === -1) {
|
|
325
|
+
out += text.slice(i);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
out += text.slice(i + 2, close);
|
|
329
|
+
i = close + 1;
|
|
330
|
+
} else {
|
|
331
|
+
out += text[i];
|
|
332
|
+
i++;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
function ownerPathOf(container) {
|
|
338
|
+
if (!container) return void 0;
|
|
339
|
+
return container.zigOwnerPath ?? container.name;
|
|
340
|
+
}
|
|
341
|
+
function publishedSpelling(s) {
|
|
342
|
+
const name = String(s.name ?? "");
|
|
343
|
+
const owner = s.ownerName;
|
|
344
|
+
return owner ? `${owner}.${name}` : name;
|
|
345
|
+
}
|
|
346
|
+
var RealZigAnalyzer = class {
|
|
347
|
+
analyze(rootNode, content, filePath, context) {
|
|
348
|
+
const symbols = [];
|
|
349
|
+
const relationships = [];
|
|
350
|
+
const imports = [];
|
|
351
|
+
const exports = [];
|
|
352
|
+
const errors = [];
|
|
353
|
+
const projectRoot = typeof context?.projectRoot === "string" ? context.projectRoot : void 0;
|
|
354
|
+
const walkStack = [
|
|
355
|
+
{ node: rootNode, container: null, inFunction: false, anonContainer: false }
|
|
356
|
+
];
|
|
357
|
+
const fileContainerName = this.zigThisBindingName(rootNode);
|
|
358
|
+
while (walkStack.length > 0) {
|
|
359
|
+
const item = walkStack.pop();
|
|
360
|
+
const { node, container, inFunction, anonContainer } = item;
|
|
361
|
+
const type = node.type;
|
|
362
|
+
const nodeText = this.nodeText(node);
|
|
363
|
+
if (type === "variable_declaration") {
|
|
364
|
+
if (inFunction || anonContainer) {
|
|
365
|
+
const lchildren = this.getAllNamedChildren(node);
|
|
366
|
+
const lrhs = lchildren[lchildren.length - 1];
|
|
367
|
+
const lrhsType = lrhs?.type || "";
|
|
368
|
+
const lbuiltin = lrhsType === "call_expression" || lrhsType === "builtin_function" ? this.anyChild(lrhs, (n) => n.type === "builtin_identifier") : null;
|
|
369
|
+
if (lbuiltin && (lbuiltin.text === "@import" || lbuiltin.text === "@cImport")) {
|
|
370
|
+
const largs = this.anyChild(lrhs, (n) => n.type === "arguments");
|
|
371
|
+
const lstr = largs ? this.anyChild(largs, (n) => n.type === "string" || n.type === "string_content") : null;
|
|
372
|
+
if (lstr) {
|
|
373
|
+
let ltarget = lstr.text;
|
|
374
|
+
if (ltarget.startsWith('"') && ltarget.endsWith('"') || ltarget.startsWith("'") && ltarget.endsWith("'")) {
|
|
375
|
+
ltarget = ltarget.substring(1, ltarget.length - 1);
|
|
376
|
+
}
|
|
377
|
+
imports.push({
|
|
378
|
+
module: ltarget,
|
|
379
|
+
isDefaultImport: false,
|
|
380
|
+
range: this.nodeRange(node),
|
|
381
|
+
metadata: { zigImport: true, builtin: lbuiltin.text }
|
|
382
|
+
});
|
|
383
|
+
relationships.push({
|
|
384
|
+
source: "(global)",
|
|
385
|
+
target: ltarget,
|
|
386
|
+
type: ASTEngine.RelationshipType.References,
|
|
387
|
+
confidence: 1,
|
|
388
|
+
// M59.A7 (#825): an import dependency occurs where its TARGET is
|
|
389
|
+
// written — the path literal, not the declaration statement.
|
|
390
|
+
...this.nodeLine(lstr) ? { line_number: this.nodeLine(lstr) } : {},
|
|
391
|
+
metadata: { relationshipType: "zig_import", isImport: true }
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const localKids = this.getAllNamedChildren(node);
|
|
396
|
+
const localRhs = localKids[localKids.length - 1];
|
|
397
|
+
this.pushChildren(
|
|
398
|
+
node,
|
|
399
|
+
walkStack,
|
|
400
|
+
container,
|
|
401
|
+
inFunction,
|
|
402
|
+
anonContainer || !!localRhs && ANONYMOUS_CONTAINER_NODES.has(localRhs.type)
|
|
403
|
+
);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const identNode = this.anyChild(node, (n) => n.type === "identifier");
|
|
407
|
+
const varName = identNode ? identNode.text : "";
|
|
408
|
+
if (!varName || varName.length > 80) {
|
|
409
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (varName === "_") {
|
|
413
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const isExported = this.hasToken(node, "pub") || this.hasToken(node, "export");
|
|
417
|
+
const children = this.getAllNamedChildren(node);
|
|
418
|
+
const rhs = children[children.length - 1];
|
|
419
|
+
const rhsType = rhs?.type || "";
|
|
420
|
+
const rhsText = rhs ? this.nodeText(rhs) : "";
|
|
421
|
+
const builtin = rhsType === "call_expression" || rhsType === "builtin_function" ? this.anyChild(rhs, (n) => n.type === "builtin_identifier") : null;
|
|
422
|
+
if (builtin && (builtin.text === "@import" || builtin.text === "@cImport")) {
|
|
423
|
+
const argsNode = this.anyChild(rhs, (n) => n.type === "arguments");
|
|
424
|
+
const strNode = argsNode ? this.anyChild(argsNode, (n) => n.type === "string" || n.type === "string_content") : null;
|
|
425
|
+
if (strNode) {
|
|
426
|
+
let importTarget = strNode.text;
|
|
427
|
+
if (importTarget.startsWith('"') && importTarget.endsWith('"') || importTarget.startsWith("'") && importTarget.endsWith("'")) {
|
|
428
|
+
importTarget = importTarget.substring(1, importTarget.length - 1);
|
|
429
|
+
}
|
|
430
|
+
if (!importTarget) {
|
|
431
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
imports.push({
|
|
435
|
+
module: importTarget,
|
|
436
|
+
isDefaultImport: false,
|
|
437
|
+
range: this.nodeRange(node),
|
|
438
|
+
metadata: { zigImport: true, builtin: builtin.text }
|
|
439
|
+
});
|
|
440
|
+
relationships.push({
|
|
441
|
+
source: "(global)",
|
|
442
|
+
target: importTarget,
|
|
443
|
+
type: ASTEngine.RelationshipType.References,
|
|
444
|
+
confidence: 1,
|
|
445
|
+
// M59.A7 (#825): the line of the module path as written.
|
|
446
|
+
...this.nodeLine(strNode) ? { line_number: this.nodeLine(strNode) } : {},
|
|
447
|
+
metadata: { relationshipType: "zig_import", isImport: true }
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (inFunction) {
|
|
451
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const isVar = this.hasToken(node, "var");
|
|
456
|
+
let kind = isVar ? ASTEngine.SymbolKind.Variable : ASTEngine.SymbolKind.Constant;
|
|
457
|
+
const isContainer = rhsType === "struct_declaration" || rhsType === "enum_declaration" || rhsType === "union_declaration" || rhsType === "error_set_declaration" || rhsType === "opaque_declaration";
|
|
458
|
+
if (rhsType === "struct_declaration") kind = ASTEngine.SymbolKind.Struct;
|
|
459
|
+
else if (rhsType === "enum_declaration") kind = ASTEngine.SymbolKind.Enum;
|
|
460
|
+
else if (rhsType === "union_declaration") kind = ASTEngine.SymbolKind.Union;
|
|
461
|
+
else if (rhsType === "error_set_declaration") kind = ASTEngine.SymbolKind.Enum;
|
|
462
|
+
else if (rhsType === "opaque_declaration") kind = ASTEngine.SymbolKind.Type;
|
|
463
|
+
else if (ZIG_TYPE_RHS.has(rhsType)) {
|
|
464
|
+
kind = ASTEngine.SymbolKind.Type;
|
|
465
|
+
} else if (ZIG_VALUE_RHS.has(rhsType) || rhsType === "labeled_type_expression" && this.isLabeledBlockValue(rhs)) {
|
|
466
|
+
if (!isVar) kind = ASTEngine.SymbolKind.Constant;
|
|
467
|
+
} else if (rhsType === "struct_initializer" && !this.isTypeValuedInitializer(rhs)) {
|
|
468
|
+
if (!isVar) kind = ASTEngine.SymbolKind.Constant;
|
|
469
|
+
} else if (varName[0] === varName[0].toUpperCase() && varName[0] !== varName[0].toLowerCase()) {
|
|
470
|
+
kind = ASTEngine.SymbolKind.Type;
|
|
471
|
+
}
|
|
472
|
+
const sym = {
|
|
473
|
+
name: varName,
|
|
474
|
+
kind,
|
|
475
|
+
visibility: isExported ? "public" : "private",
|
|
476
|
+
range: this.nodeRange(node),
|
|
477
|
+
scope: ownerPathOf(container),
|
|
478
|
+
ownerName: ownerPathOf(container),
|
|
479
|
+
ownerKind: container?.kind,
|
|
480
|
+
metadata: {
|
|
481
|
+
zigDeclaration: kind,
|
|
482
|
+
...rhsType === "error_set_declaration" ? { zigErrorSet: true } : {},
|
|
483
|
+
// Z35: a NAMESPACE IMPORT BINDING is a distinct fact from any other
|
|
484
|
+
// const, and a consumer asking "which names in this file are modules"
|
|
485
|
+
// cannot ask without it. It is also what makes the regression
|
|
486
|
+
// reconstructible by a MARKER rather than by a list of names.
|
|
487
|
+
...this.isZigImportBinding(rhs) ? { zigImportBinding: true } : {}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
symbols.push(sym);
|
|
491
|
+
if (isExported && !container) exports.push({ name: varName, kind, range: this.nodeRange(node) });
|
|
492
|
+
if (isContainer) {
|
|
493
|
+
const own = stripZigQuoting(varName);
|
|
494
|
+
const parentPath = ownerPathOf(container);
|
|
495
|
+
sym.zigOwnerPath = parentPath ? `${parentPath}.${own}` : own;
|
|
496
|
+
}
|
|
497
|
+
const newContainer = isContainer ? sym : container;
|
|
498
|
+
this.pushChildren(node, walkStack, newContainer, inFunction, anonContainer);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (type === "test_declaration" || type === "comptime_declaration") {
|
|
502
|
+
this.pushChildren(node, walkStack, container, true, anonContainer);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (type === "using_namespace_declaration") {
|
|
506
|
+
{
|
|
507
|
+
const merged = zigUsingNamespaceMergedUnit(node);
|
|
508
|
+
const mixinSource = !inFunction && !anonContainer ? container ? container.name : "(global)" : null;
|
|
509
|
+
if (merged && mixinSource) {
|
|
510
|
+
relationships.push({
|
|
511
|
+
source: mixinSource,
|
|
512
|
+
target: merged.target,
|
|
513
|
+
type: ASTEngine.RelationshipType.MixesIn,
|
|
514
|
+
confidence: 1,
|
|
515
|
+
...this.nodeLine(node) ? { line_number: this.nodeLine(node) } : {},
|
|
516
|
+
metadata: {
|
|
517
|
+
relationshipType: "zig_usingnamespace",
|
|
518
|
+
isMixin: true,
|
|
519
|
+
mixinForm: merged.form,
|
|
520
|
+
...merged.module ? { module: merged.module } : {},
|
|
521
|
+
...mixinSource === "(global)" ? { fileLevel: true } : {}
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const bf = this.anyChild(node, (n) => n.type === "builtin_function");
|
|
527
|
+
const bi = bf ? this.anyChild(bf, (n) => n.type === "builtin_identifier") : null;
|
|
528
|
+
if (bf && bi && (bi.text === "@import" || bi.text === "@cImport")) {
|
|
529
|
+
const argsNode = this.anyChild(bf, (n) => n.type === "arguments");
|
|
530
|
+
const strNode = argsNode ? this.anyChild(argsNode, (n) => n.type === "string" || n.type === "string_content") : null;
|
|
531
|
+
if (strNode) {
|
|
532
|
+
let importTarget = strNode.text;
|
|
533
|
+
if (importTarget.startsWith('"') && importTarget.endsWith('"') || importTarget.startsWith("'") && importTarget.endsWith("'")) {
|
|
534
|
+
importTarget = importTarget.substring(1, importTarget.length - 1);
|
|
535
|
+
}
|
|
536
|
+
if (!importTarget) {
|
|
537
|
+
relationships.push({
|
|
538
|
+
source: "(global)",
|
|
539
|
+
target: "@import",
|
|
540
|
+
type: ASTEngine.RelationshipType.References,
|
|
541
|
+
confidence: 1,
|
|
542
|
+
metadata: { relationshipType: "zig_import", isImport: true, emptyImportPath: true }
|
|
543
|
+
});
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
imports.push({
|
|
547
|
+
module: importTarget,
|
|
548
|
+
isDefaultImport: false,
|
|
549
|
+
range: this.nodeRange(node),
|
|
550
|
+
metadata: { zigImport: true, builtin: bi.text, usingnamespace: true }
|
|
551
|
+
});
|
|
552
|
+
relationships.push({
|
|
553
|
+
source: "(global)",
|
|
554
|
+
target: importTarget,
|
|
555
|
+
type: ASTEngine.RelationshipType.References,
|
|
556
|
+
confidence: 1,
|
|
557
|
+
// M59.A7 (#825): the module path as written.
|
|
558
|
+
...this.nodeLine(strNode) ? { line_number: this.nodeLine(strNode) } : {},
|
|
559
|
+
metadata: { relationshipType: "zig_import", isImport: true }
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (type === "function_signature") {
|
|
567
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (type === "function_declaration") {
|
|
571
|
+
const firstNamed = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
572
|
+
const identNode = firstNamed && firstNamed.type === "identifier" ? firstNamed : this.anyChild(node, (n) => n.type === "identifier");
|
|
573
|
+
const fnName = identNode ? identNode.text : "";
|
|
574
|
+
const isExported = this.hasToken(node, "pub") || this.hasToken(node, "export");
|
|
575
|
+
const isInline = this.hasToken(node, "inline");
|
|
576
|
+
const isAnonymousContainerMember = anonContainer || !container && inFunction;
|
|
577
|
+
if (fnName && fnName.length < 100 && !isAnonymousContainerMember) {
|
|
578
|
+
const params = [];
|
|
579
|
+
const paramsNode = this.anyChild(node, (n) => n.type === "parameters");
|
|
580
|
+
if (paramsNode) {
|
|
581
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
582
|
+
const child = paramsNode.namedChild(i);
|
|
583
|
+
if (!child || child.type !== "parameter") continue;
|
|
584
|
+
const pNameNode = this.namedFieldOf(child, "name") ?? this.anyChild(child, (n) => n.type === "identifier");
|
|
585
|
+
const pTypeNode = this.typeFieldOf(child);
|
|
586
|
+
if (pNameNode) {
|
|
587
|
+
params.push({
|
|
588
|
+
name: pNameNode.text,
|
|
589
|
+
type: pTypeNode ? this.nodeText(pTypeNode) : void 0
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const isMethod = container !== null;
|
|
595
|
+
const sym = {
|
|
596
|
+
name: fnName,
|
|
597
|
+
kind: isMethod ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function,
|
|
598
|
+
visibility: isExported ? "public" : "private",
|
|
599
|
+
range: this.nodeRange(node),
|
|
600
|
+
scope: ownerPathOf(container),
|
|
601
|
+
parameters: params,
|
|
602
|
+
ownerName: ownerPathOf(container),
|
|
603
|
+
ownerKind: container?.kind,
|
|
604
|
+
// Per-symbol cyclomatic complexity (family standard — go/c/rust
|
|
605
|
+
// emit symbol.complexity; zig did NOT, so completeness section C
|
|
606
|
+
// was impossible). Added zig wave 2026-08-29.
|
|
607
|
+
complexity: this.zigBodyComplexity(node),
|
|
608
|
+
metadata: {
|
|
609
|
+
zigFunction: true,
|
|
610
|
+
inline: isInline,
|
|
611
|
+
paramCount: params.length,
|
|
612
|
+
// #612 store-phase: DECLARATION vs DEFINITION. Measured against
|
|
613
|
+
// this grammar (tree dump, 2026-09-25): `extern fn run_engine()
|
|
614
|
+
// c_int;` and `pub fn call_it() c_int { … }` BOTH parse as
|
|
615
|
+
// `function_declaration`, and the grammar hands the difference
|
|
616
|
+
// over as a FIELD — 35,692 of the corpus's 37,129 functions carry
|
|
617
|
+
// a `body` child under the `body` field name, the rest declare
|
|
618
|
+
// nothing. So the discriminator is read off the field, with the
|
|
619
|
+
// node-type test kept as the fallback for a grammar variant that
|
|
620
|
+
// does not name it. Without the flag an `extern` declaration
|
|
621
|
+
// competed on equal footing with the definition implementing it in
|
|
622
|
+
// another file.
|
|
623
|
+
...this.functionBody(node) === null ? { isDeclaration: true } : {}
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
symbols.push(sym);
|
|
627
|
+
if (isExported && !isMethod) exports.push({ name: fnName, kind: ASTEngine.SymbolKind.Function, range: this.nodeRange(node) });
|
|
628
|
+
}
|
|
629
|
+
this.pushChildren(node, walkStack, container, true, anonContainer);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (ANONYMOUS_CONTAINER_NODES.has(type)) {
|
|
633
|
+
const parent = node.parent;
|
|
634
|
+
const isDeclRhs = !!parent && parent.type === "variable_declaration" && parent.namedChildCount > 0 && parent.namedChild(parent.namedChildCount - 1)?.id === node.id;
|
|
635
|
+
if (!isDeclRhs) {
|
|
636
|
+
this.pushChildren(node, walkStack, container, inFunction, true);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (type === "container_field") {
|
|
641
|
+
if (!container && !anonContainer && node.parent?.type === "source_file") {
|
|
642
|
+
this.zigFileLevelField(node, fileContainerName, symbols);
|
|
643
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (!container || anonContainer) {
|
|
647
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const nameField = node.childForFieldName?.("name");
|
|
651
|
+
const isEnumMember = container?.kind === ASTEngine.SymbolKind.Enum;
|
|
652
|
+
const enumPrimitiveName = isEnumMember && nameField && ENUM_MEMBER_NAME_NODE_TYPES.has(nameField.type) ? nameField : null;
|
|
653
|
+
const nameNode = nameField && nameField.type === "identifier" ? nameField : enumPrimitiveName ?? this.anyChild(node, (n) => n.type === "identifier");
|
|
654
|
+
const fieldName = nameNode ? nameNode.text : "";
|
|
655
|
+
if (fieldName && fieldName !== "_" && fieldName.length < 200) {
|
|
656
|
+
const typeField = this.typeFieldOf(node);
|
|
657
|
+
symbols.push({
|
|
658
|
+
name: fieldName,
|
|
659
|
+
kind: isEnumMember ? ASTEngine.SymbolKind.EnumMember : ASTEngine.SymbolKind.Property,
|
|
660
|
+
visibility: "public",
|
|
661
|
+
range: this.nodeRange(node),
|
|
662
|
+
scope: ownerPathOf(container),
|
|
663
|
+
ownerName: ownerPathOf(container),
|
|
664
|
+
ownerKind: container?.kind,
|
|
665
|
+
returnType: typeField ? this.nodeText(typeField) : void 0,
|
|
666
|
+
metadata: { zigField: true, ...isEnumMember ? { zigEnumMember: true } : {} }
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
this.pushChildren(node, walkStack, container, inFunction, anonContainer);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (type === "identifier" && container && container.metadata?.zigErrorSet && node.parent?.type === "error_set_declaration") {
|
|
673
|
+
const identText = node.text;
|
|
674
|
+
if (identText && identText.length < 50) {
|
|
675
|
+
symbols.push({
|
|
676
|
+
name: identText,
|
|
677
|
+
kind: ASTEngine.SymbolKind.EnumMember,
|
|
678
|
+
visibility: "public",
|
|
679
|
+
range: this.nodeRange(node),
|
|
680
|
+
scope: ownerPathOf(container),
|
|
681
|
+
ownerName: ownerPathOf(container),
|
|
682
|
+
ownerKind: container.kind,
|
|
683
|
+
metadata: { zigErrorSetMember: true }
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (node.type === "ERROR" || node.isMissing) {
|
|
689
|
+
const snippet = nodeText.substring(0, 50);
|
|
690
|
+
errors.push({
|
|
691
|
+
message: `Zig syntax error: ${snippet}`,
|
|
692
|
+
range: {
|
|
693
|
+
// Z2 (zig review): 1-based columns like every other range in
|
|
694
|
+
// this file (nodeRange +1s) — the raw 0-based columns leaked
|
|
695
|
+
// through here (svelte-wave class).
|
|
696
|
+
startLine: node.startPosition.row + 1,
|
|
697
|
+
startColumn: node.startPosition.column + 1,
|
|
698
|
+
endLine: node.endPosition.row + 1,
|
|
699
|
+
endColumn: node.endPosition.column + 1,
|
|
700
|
+
startIndex: node.startIndex,
|
|
701
|
+
endIndex: node.endIndex
|
|
702
|
+
},
|
|
703
|
+
severity: "error"
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
this.pushChildren(node, walkStack, container, inFunction || type === "block", anonContainer);
|
|
707
|
+
}
|
|
708
|
+
for (const s of symbols) {
|
|
709
|
+
if (s.ownerName) {
|
|
710
|
+
relationships.push({
|
|
711
|
+
source: s.ownerName,
|
|
712
|
+
// M60 cause A — the target is the member's own PUBLISHED SPELLING, which
|
|
713
|
+
// is what Z-p22 above already established for the SOURCE and this edge
|
|
714
|
+
// never followed on the target half.
|
|
715
|
+
//
|
|
716
|
+
// The projection spells a symbol `scope ? scope + "." + name : name`
|
|
717
|
+
// (pool-worker.ts:997) and `scope` is this symbol's `ownerName`
|
|
718
|
+
// (see `mkSymbol`'s `scope: ownerName ?? undefined`). So the string this
|
|
719
|
+
// edge targets IS the member's published `qualified_name`, and the store
|
|
720
|
+
// resolves it on its FIRST strategy: `target_external = qualified_name`
|
|
721
|
+
// is an exact JOIN (parallel-indexer.ts:4483-4495, `_res_qname` PRIMARY
|
|
722
|
+
// KEY), which binds one declaration by construction.
|
|
723
|
+
//
|
|
724
|
+
// A BARE member name reaches only strategy 2, which fires
|
|
725
|
+
// `_res_name.cnt = 1` — the name must be unique across the WHOLE project
|
|
726
|
+
// (parallel-indexer.ts:4506). No container member name ever is: the
|
|
727
|
+
// fleet's own corpus declares `init`, `deinit`, `assert` and `release`
|
|
728
|
+
// in dozens of files each. So the bare target was not merely imprecise,
|
|
729
|
+
// it was the one spelling guaranteed to fail the only strategy that
|
|
730
|
+
// could have carried it.
|
|
731
|
+
//
|
|
732
|
+
// MEASURED, 600-file stride sample, production `parseFile`, incoming-gate
|
|
733
|
+
// (which counts ambiguity by DECLARATION and indexes name + qualifiedName
|
|
734
|
+
// aliases): `contains` 13,690 edges — 8,993 resolved, 4,697 AMBIGUOUS,
|
|
735
|
+
// 0 unresolved. That 4,697 was the largest single ambiguous bucket in the
|
|
736
|
+
// lane, 24.6% of the 19,084 total. Priced over the FULL population first
|
|
737
|
+
// (3,834 files, 80,876 contains edges, `contains-qualify-control.ts`):
|
|
738
|
+
// FIX 31,800 / RISK 5 / LOST 797 / NEUTRAL 48,274 — i.e. +31,800 edges
|
|
739
|
+
// that resolve to exactly one declaration against 5 that stop doing so.
|
|
740
|
+
//
|
|
741
|
+
// The name SURFACE is untouched: `symbols.push({ name: s.name })` still
|
|
742
|
+
// publishes the bare member, so hover, navigation and every expectation
|
|
743
|
+
// file that reads `name` are unaffected. Only the edge endpoint changes.
|
|
744
|
+
// `source` still carries the owner, so containment survives independently
|
|
745
|
+
// of the target — the two halves are redundant by design, and this makes
|
|
746
|
+
// them agree.
|
|
747
|
+
target: publishedSpelling(s),
|
|
748
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
749
|
+
confidence: 1,
|
|
750
|
+
// M59.A7 (#825): a containment edge has no call site, but it has a
|
|
751
|
+
// navigable one — the member's own declaration line. Omitted, never
|
|
752
|
+
// written as 0, when the symbol carries no range (#820).
|
|
753
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
754
|
+
// Z44: a reason code, so every class in this analyzer answers the same
|
|
755
|
+
// question — "why does this edge exist?" — the same way. `contains` is
|
|
756
|
+
// built from the SYMBOL TABLE at the end of the walk rather than at the
|
|
757
|
+
// member's site, which is why it carried an EMPTY metadata object and
|
|
758
|
+
// was invisible to any consumer that reads the reason. It is the
|
|
759
|
+
// largest class in the corpus (261 of the 300-file draw) and it was the
|
|
760
|
+
// one that said nothing. `zigField`/`zigEnumMember` are already on the
|
|
761
|
+
// SYMBOL; here the reason is that the edge is ownership.
|
|
762
|
+
metadata: { relationshipType: "zig_ownership" }
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
this.analyzeZigCodeRelationships(rootNode, relationships, symbols);
|
|
767
|
+
this.collectZigImportSites(rootNode, imports, relationships, projectRoot, filePath);
|
|
768
|
+
this.analyzeZigInstantiation(rootNode, relationships);
|
|
769
|
+
this.emitZigExportEdges(relationships, exports);
|
|
770
|
+
this.analyzeZigBareIdentifierUses(rootNode, relationships, symbols);
|
|
771
|
+
{
|
|
772
|
+
const bestUses = /* @__PURE__ */ new Map();
|
|
773
|
+
const order = [];
|
|
774
|
+
for (const r of relationships) {
|
|
775
|
+
if (r.type !== ASTEngine.RelationshipType.Uses) {
|
|
776
|
+
order.push(r);
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
const key = `${r.source}|${r.target}`;
|
|
780
|
+
const prior = bestUses.get(key);
|
|
781
|
+
if (!prior) {
|
|
782
|
+
bestUses.set(key, r);
|
|
783
|
+
order.push(r);
|
|
784
|
+
} else if ((r.confidence ?? 0) > (prior.confidence ?? 0)) {
|
|
785
|
+
order[order.indexOf(prior)] = r;
|
|
786
|
+
bestUses.set(key, r);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
relationships.length = 0;
|
|
790
|
+
relationships.push(...order);
|
|
791
|
+
}
|
|
792
|
+
this.extractZigDeclaredTypeRefs(rootNode, relationships);
|
|
793
|
+
this.classifyEvidenceTier(relationships);
|
|
794
|
+
this.annotateTargetScope(symbols, relationships);
|
|
795
|
+
this.canonicalizePublishedNames(symbols, relationships, exports);
|
|
796
|
+
const fileComplexity = this.zigBodyComplexity(rootNode);
|
|
797
|
+
const zigDeclaredHere = /* @__PURE__ */ new Set();
|
|
798
|
+
for (const sym of symbols) {
|
|
799
|
+
if (sym.name) zigDeclaredHere.add(sym.name);
|
|
800
|
+
const q = sym.qualifiedName;
|
|
801
|
+
if (typeof q === "string" && q.length > 0) zigDeclaredHere.add(q);
|
|
802
|
+
}
|
|
803
|
+
let zigNamespaceClaims = 0;
|
|
804
|
+
for (const rel of relationships) {
|
|
805
|
+
if (rel.type !== ASTEngine.RelationshipType.Uses) continue;
|
|
806
|
+
const tgt = String(rel.target ?? "");
|
|
807
|
+
if (!tgt.startsWith("std.")) continue;
|
|
808
|
+
if (zigDeclaredHere.has(tgt)) continue;
|
|
809
|
+
const md = rel.metadata;
|
|
810
|
+
if (!md || typeof md !== "object") continue;
|
|
811
|
+
if (md.zigExternalRef === true || md.isExternal === true) continue;
|
|
812
|
+
md.languageNamespace = ["std"];
|
|
813
|
+
zigNamespaceClaims++;
|
|
814
|
+
}
|
|
815
|
+
return {
|
|
816
|
+
language: "zig",
|
|
817
|
+
symbols,
|
|
818
|
+
relationships,
|
|
819
|
+
imports,
|
|
820
|
+
exports,
|
|
821
|
+
errors,
|
|
822
|
+
metadata: {
|
|
823
|
+
namespaceClaims: zigNamespaceClaims,
|
|
824
|
+
complexity: fileComplexity,
|
|
825
|
+
cyclomaticComplexity: fileComplexity,
|
|
826
|
+
linesOfCode: content.split("\n").length,
|
|
827
|
+
patterns: [],
|
|
828
|
+
framework: "Zig",
|
|
829
|
+
hasFrameworkIntelligence: true,
|
|
830
|
+
hasTypeInference: true
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Calls + Uses (family contract, zig wave 2026-08-29).
|
|
836
|
+
* Walks the tree tracking the enclosing function/method name; a
|
|
837
|
+
* call_expression whose callee is an identifier or a field_expression emits
|
|
838
|
+
* a `calls` edge to the callee LEAF (Rust/Go contract — shared callee
|
|
839
|
+
* `self.repository.add` -> `add`); a field_expression that is NOT a call
|
|
840
|
+
* callee emits a `uses` edge to its field leaf. Builtin calls (@import,
|
|
841
|
+
* @intCast, ...) are compile-time intrinsics, NOT user symbols — skipped.
|
|
842
|
+
*/
|
|
843
|
+
/**
|
|
844
|
+
* Z59 — does this analyzer publish a SYMBOL for `fnNode`, so that naming it as
|
|
845
|
+
* an edge SOURCE resolves?
|
|
846
|
+
*
|
|
847
|
+
* ## The defect this answers
|
|
848
|
+
*
|
|
849
|
+
* A `function_declaration` can be a MEMBER of a container the analyzer
|
|
850
|
+
* deliberately publishes nothing for: a container written inside a function or
|
|
851
|
+
* test body, a container with no name (`fn GenericStack(…) type { return
|
|
852
|
+
* struct { fn push(…) } }`), or a container nested in an anonymous one. That is
|
|
853
|
+
* the Z22 / C-negative contract and it is correct — such a type has no
|
|
854
|
+
* file-scope identity.
|
|
855
|
+
*
|
|
856
|
+
* The three edge passes each took the nearest `function_declaration` /
|
|
857
|
+
* `test_declaration` identifier as the source name WITHOUT asking whether a
|
|
858
|
+
* symbol was published for it. So 5.05% of `calls` + `uses` + `instantiates`
|
|
859
|
+
* edges over the full corpus (11,332 of 224,599) carried a source naming a
|
|
860
|
+
* declaration the index does not hold: a caller that resolves to nothing.
|
|
861
|
+
* Measured by `validation/m14/fixtures/zig/dangling-source-census.ts`, whose
|
|
862
|
+
* residual class is 0, so this is the whole population and not a sample of it.
|
|
863
|
+
*
|
|
864
|
+
* ## The rule
|
|
865
|
+
*
|
|
866
|
+
* A function is published when it has NO container ancestor (a top-level `fn`),
|
|
867
|
+
* or when its nearest container ancestor is bound to an identifier at FILE
|
|
868
|
+
* SCOPE. Named is not sufficient: `const S = struct { … }` inside a test body is
|
|
869
|
+
* a named LOCAL container and is not published either.
|
|
870
|
+
*
|
|
871
|
+
* `test_declaration` is answered separately by the callers: a named test is a
|
|
872
|
+
* scope with a name (Z39) and NO symbol is published for one, so that class is
|
|
873
|
+
* still dangling and is declared as a limitation rather than silently reverted.
|
|
874
|
+
*/
|
|
875
|
+
zigFunctionScopeIsPublished(fnNode) {
|
|
876
|
+
let cur = fnNode.parent;
|
|
877
|
+
while (cur) {
|
|
878
|
+
if (ANONYMOUS_CONTAINER_NODES.has(cur.type)) {
|
|
879
|
+
const binding = cur.parent;
|
|
880
|
+
if (!binding || binding.type !== "variable_declaration") return false;
|
|
881
|
+
let named = false;
|
|
882
|
+
for (let i = 0; i < binding.namedChildCount; i++) {
|
|
883
|
+
const c = binding.namedChild(i);
|
|
884
|
+
if (c && c.type === "identifier") {
|
|
885
|
+
named = true;
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (!named) return false;
|
|
890
|
+
let up = binding.parent;
|
|
891
|
+
while (up) {
|
|
892
|
+
if (up.type === "function_declaration" || up.type === "test_declaration" || up.type === "comptime_declaration") return false;
|
|
893
|
+
if (ANONYMOUS_CONTAINER_NODES.has(up.type)) return false;
|
|
894
|
+
up = up.parent;
|
|
895
|
+
}
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
cur = cur.parent;
|
|
899
|
+
}
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
902
|
+
analyzeZigCodeRelationships(rootNode, relationships, symbols) {
|
|
903
|
+
const emittedCalls = /* @__PURE__ */ new Set();
|
|
904
|
+
const emittedUses = /* @__PURE__ */ new Set();
|
|
905
|
+
const importAliases = zigImportAliases(rootNode);
|
|
906
|
+
const stack = [{ node: rootNode, fn: null, inFn: false }];
|
|
907
|
+
while (stack.length > 0) {
|
|
908
|
+
const { node: n, fn, inFn } = stack.pop();
|
|
909
|
+
let nextFn = fn;
|
|
910
|
+
let nextInFn = inFn;
|
|
911
|
+
if (n.type === "function_declaration" || n.type === "test_declaration") {
|
|
912
|
+
const nameNode = this.anyChild(n, (c) => c.type === "identifier");
|
|
913
|
+
if (nameNode) {
|
|
914
|
+
nextInFn = true;
|
|
915
|
+
if (n.type === "test_declaration" || this.zigFunctionScopeIsPublished(n)) {
|
|
916
|
+
nextFn = nameNode.text;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (n.type === "call_expression") {
|
|
921
|
+
const leafNode = this.zigCalleeLeafNode(n);
|
|
922
|
+
const leaf = leafNode?.text;
|
|
923
|
+
if (leaf) {
|
|
924
|
+
const source = nextInFn && nextFn ? nextFn : "(global)";
|
|
925
|
+
const calleeExpr = n.namedChildCount > 0 ? n.namedChild(0) : null;
|
|
926
|
+
const ext = this.zigExternalReceiverPath(
|
|
927
|
+
calleeExpr?.type === "field_expression" ? calleeExpr : void 0,
|
|
928
|
+
importAliases
|
|
929
|
+
);
|
|
930
|
+
const target = ext ? ext.target : leaf;
|
|
931
|
+
const k = `${source}->${target}`;
|
|
932
|
+
if (!emittedCalls.has(k)) {
|
|
933
|
+
emittedCalls.add(k);
|
|
934
|
+
relationships.push({
|
|
935
|
+
source,
|
|
936
|
+
target,
|
|
937
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
938
|
+
confidence: 0.85,
|
|
939
|
+
// M59.A7 (#825): the callee LEAF as written — the name-bearing
|
|
940
|
+
// node, not the call expression (which a receiver can precede).
|
|
941
|
+
...this.nodeLine(leafNode) ? { line_number: this.nodeLine(leafNode) } : {},
|
|
942
|
+
metadata: {
|
|
943
|
+
relationshipType: "zig_function_call",
|
|
944
|
+
callExpression: n.text.slice(0, 60),
|
|
945
|
+
...this.zigExternalMetadata(ext)
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (n.type === "field_expression" && !this.isCallCallee(n)) {
|
|
952
|
+
const fieldNode = this.zigFieldLeafNode(n);
|
|
953
|
+
const field = fieldNode?.text;
|
|
954
|
+
const notBuiltinAccess = this.anyChild(n, (c) => c.type === "identifier") !== null;
|
|
955
|
+
const isAssignLhs = this.isWriteLhs(n);
|
|
956
|
+
const isEnumLiteral = n.namedChildCount < 2;
|
|
957
|
+
const p = n.parent;
|
|
958
|
+
const isTypePosition = (() => {
|
|
959
|
+
const VALUE_BOUNDARY = /* @__PURE__ */ new Set([
|
|
960
|
+
"call_expression",
|
|
961
|
+
"block",
|
|
962
|
+
"block_expression",
|
|
963
|
+
"arguments",
|
|
964
|
+
"assignment_expression",
|
|
965
|
+
"assignment_statement",
|
|
966
|
+
"compound_assignment_statement",
|
|
967
|
+
"struct_initializer",
|
|
968
|
+
"anonymous_struct_initializer",
|
|
969
|
+
"initializer_list",
|
|
970
|
+
"composite_literal",
|
|
971
|
+
"variable_declaration",
|
|
972
|
+
"return_expression",
|
|
973
|
+
"if_expression",
|
|
974
|
+
"switch_expression",
|
|
975
|
+
"while_expression",
|
|
976
|
+
"for_expression"
|
|
977
|
+
]);
|
|
978
|
+
let cur = p;
|
|
979
|
+
let arrivedFrom = n;
|
|
980
|
+
let hops = 0;
|
|
981
|
+
while (cur && hops < 8) {
|
|
982
|
+
if (cur.type === "parameter" || cur.type === "container_field" || cur.type === "function_declaration" || cur.type === "function_signature") return true;
|
|
983
|
+
if (cur.type === "variable_declaration") {
|
|
984
|
+
return this.zigArrivedAtTypeAnnotation(cur, arrivedFrom);
|
|
985
|
+
}
|
|
986
|
+
if (VALUE_BOUNDARY.has(cur.type)) return false;
|
|
987
|
+
arrivedFrom = cur;
|
|
988
|
+
cur = cur.parent;
|
|
989
|
+
hops++;
|
|
990
|
+
}
|
|
991
|
+
return false;
|
|
992
|
+
})();
|
|
993
|
+
if (field && notBuiltinAccess && !isAssignLhs && !isEnumLiteral && !isTypePosition) {
|
|
994
|
+
const source = nextInFn && nextFn ? nextFn : "(global)";
|
|
995
|
+
const ext = this.zigExternalReceiverPath(n, importAliases);
|
|
996
|
+
const target = ext ? ext.target : field;
|
|
997
|
+
const k = `${source}->${target}`;
|
|
998
|
+
if (!emittedUses.has(k)) {
|
|
999
|
+
emittedUses.add(k);
|
|
1000
|
+
relationships.push({
|
|
1001
|
+
source,
|
|
1002
|
+
target,
|
|
1003
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
1004
|
+
confidence: 0.75,
|
|
1005
|
+
// M59.A7 (#825): the field LEAF as written.
|
|
1006
|
+
...this.nodeLine(fieldNode) ? { line_number: this.nodeLine(fieldNode) } : {},
|
|
1007
|
+
metadata: {
|
|
1008
|
+
relationshipType: "zig_field_access",
|
|
1009
|
+
fieldAccessExpression: n.text.slice(0, 60),
|
|
1010
|
+
...this.zigExternalMetadata(ext)
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
const reflectedName = this.zigReflectionFieldName(n);
|
|
1017
|
+
if (reflectedName) {
|
|
1018
|
+
const source = nextInFn && nextFn ? nextFn : "(global)";
|
|
1019
|
+
const k = `${source}->${reflectedName.text}`;
|
|
1020
|
+
if (!emittedUses.has(k)) {
|
|
1021
|
+
emittedUses.add(k);
|
|
1022
|
+
relationships.push({
|
|
1023
|
+
source,
|
|
1024
|
+
target: reflectedName.text,
|
|
1025
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
1026
|
+
confidence: 0.75,
|
|
1027
|
+
// M59.A7 (#825): the line of the NAME, matching `zig_field_access`.
|
|
1028
|
+
...this.nodeLine(reflectedName) ? { line_number: this.nodeLine(reflectedName) } : {},
|
|
1029
|
+
metadata: {
|
|
1030
|
+
relationshipType: "zig_field_reflection",
|
|
1031
|
+
reflectedMember: reflectedName.text
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1037
|
+
const c = n.namedChild(i);
|
|
1038
|
+
if (c) stack.push({ node: c, fn: nextFn, inFn: nextInFn });
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Z3: bare-identifier value reads (`return MAX`, `g(D)`, `m[k]` keys,
|
|
1044
|
+
* return operands). Every bare `identifier` naming a file-visible VALUE
|
|
1045
|
+
* (file-scope const/var/fn/enum-member) is a Uses edge, with:
|
|
1046
|
+
* - a lexical scope stack: function params bind at entry; block locals
|
|
1047
|
+
* (variable_declaration names), payload captures (`|x|`), for/if/while
|
|
1048
|
+
* payloads bind in walk order (reads before any binding still resolve
|
|
1049
|
+
* outward — C-family flow discipline);
|
|
1050
|
+
* - declaration positions never read (first-identifier rule for
|
|
1051
|
+
* variable_declaration; entity names; composite-literal keys are
|
|
1052
|
+
* field_identifier-typed and auto-skip; labels/package/import paths/
|
|
1053
|
+
* blank `_` excluded);
|
|
1054
|
+
* - call callees (span-matched; the calls branch owns them), member
|
|
1055
|
+
* leaves (field path owns them; objects stay readable), qualified
|
|
1056
|
+
* non-leaves, assignment LHS writes, test/comptime-exempt contexts all
|
|
1057
|
+
* excluded;
|
|
1058
|
+
* - resolvability required: unknown names (std/dot-imported/external,
|
|
1059
|
+
* macro-template `$x`) stay silent — no junk edges.
|
|
1060
|
+
*/
|
|
1061
|
+
/**
|
|
1062
|
+
* M60 (#924, zig half): canonicalize the PUBLISHED name surface.
|
|
1063
|
+
*
|
|
1064
|
+
* Zig builds its symbols inline (no factory), so the strip lands here, at the
|
|
1065
|
+
* publish boundary — after every analysis pass, so nothing internal ever sees a
|
|
1066
|
+
* canonical spelling mid-walk (the bare-use inventory and the shadowing frames
|
|
1067
|
+
* are keyed on the SOURCE's own spellings and stay consistent with the tree),
|
|
1068
|
+
* and before the result leaves the analyzer.
|
|
1069
|
+
*/
|
|
1070
|
+
/** Z35: is this right-hand side a module import? The two shapes the import
|
|
1071
|
+
* branches recognise, in one place so three sites cannot disagree. */
|
|
1072
|
+
isZigImportBinding(rhs) {
|
|
1073
|
+
if (!rhs) return false;
|
|
1074
|
+
const host = rhs.type === "call_expression" || rhs.type === "builtin_function" ? rhs : null;
|
|
1075
|
+
if (!host) return false;
|
|
1076
|
+
for (let i = 0; i < host.namedChildCount; i++) {
|
|
1077
|
+
const c = host.namedChild(i);
|
|
1078
|
+
if (c && (c.type === "builtin_identifier" || c.type === "identifier")) {
|
|
1079
|
+
const t = String(c.text ?? "");
|
|
1080
|
+
return t === "@import" || t === "@cImport" || t === "import";
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Z30: every `@import("path")` / `@cImport(…)` SITE, whether or not the import
|
|
1087
|
+
* is the whole right-hand side.
|
|
1088
|
+
*
|
|
1089
|
+
* The three shape branches each ask "is the RHS exactly an @import?", so
|
|
1090
|
+
* `const v = @import("build.zig.zon").version;` (a .zon manifest read),
|
|
1091
|
+
* `const c = @import("c.zig").c;` (a module's C bindings) and
|
|
1092
|
+
* `try @import("apple_sdk").addPaths(b, lib);` (a call ON a module, not even a
|
|
1093
|
+
* declaration) fell through all of them: no fact, no edge, no symbol — 460 of
|
|
1094
|
+
* 1,918 sites, 24% of the class. It is a SITE PASS rather than a fourth shape
|
|
1095
|
+
* test, which is the lesson of this wave written down: a rule that enumerates
|
|
1096
|
+
* the shapes it has seen keeps losing the one it has not.
|
|
1097
|
+
*
|
|
1098
|
+
* Measured shape: `builtin_function[builtin_identifier, arguments]`, the path in
|
|
1099
|
+
* `arguments[string[string_content]]`. `@cImport({ @cInclude(…) })` nests its
|
|
1100
|
+
* include strings and has no module path of its own, so it records the fact and
|
|
1101
|
+
* publishes no edge — the same asymmetry the shape branches have.
|
|
1102
|
+
*
|
|
1103
|
+
* Dedupe key `(line, target)` against what the branches already pushed, so a
|
|
1104
|
+
* site they handled is not recorded twice.
|
|
1105
|
+
*/
|
|
1106
|
+
collectZigImportSites(rootNode, imports, relationships, projectRoot, filePath) {
|
|
1107
|
+
const stack = [rootNode];
|
|
1108
|
+
const seenFacts = /* @__PURE__ */ new Set();
|
|
1109
|
+
for (const f of imports) seenFacts.add(`${f.range?.startLine ?? 0}|${f.module ?? ""}`);
|
|
1110
|
+
const refEdgesAt = /* @__PURE__ */ new Map();
|
|
1111
|
+
const importEdgesAt = /* @__PURE__ */ new Map();
|
|
1112
|
+
for (const r of relationships) {
|
|
1113
|
+
if (r.metadata?.isImport && typeof r.line_number === "number") {
|
|
1114
|
+
const k = `${r.line_number}|${r.target}`;
|
|
1115
|
+
if (r.type === ASTEngine.RelationshipType.Imports) importEdgesAt.set(k, r);
|
|
1116
|
+
else refEdgesAt.set(k, r);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
const attachFileClaim = (edge, claim) => {
|
|
1120
|
+
if (claim === null) return;
|
|
1121
|
+
const md = edge.metadata ?? (edge.metadata = {});
|
|
1122
|
+
if (md.importProjectFile === void 0) md.importProjectFile = claim;
|
|
1123
|
+
};
|
|
1124
|
+
while (stack.length > 0) {
|
|
1125
|
+
const n = stack.pop();
|
|
1126
|
+
if (n.type === "builtin_function" || n.type === "call_expression") {
|
|
1127
|
+
let id = null;
|
|
1128
|
+
if (n.type === "builtin_function") {
|
|
1129
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1130
|
+
const c = n.namedChild(i);
|
|
1131
|
+
if (c && (c.type === "builtin_identifier" || c.type === "identifier")) {
|
|
1132
|
+
id = c;
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
} else {
|
|
1137
|
+
id = n.namedChildCount > 0 ? n.namedChild(0) : null;
|
|
1138
|
+
}
|
|
1139
|
+
const text = String(id?.text ?? "");
|
|
1140
|
+
if (text === "@import" || text === "@cImport" || text === "import") {
|
|
1141
|
+
const lit = zigImportModuleLiteral(n);
|
|
1142
|
+
if (lit) {
|
|
1143
|
+
const target = lit.module;
|
|
1144
|
+
const line = this.nodeLine(lit.node) ?? 0;
|
|
1145
|
+
const isUsingNamespace = n.parent?.type === "using_namespace_declaration";
|
|
1146
|
+
if (target && line > 0) {
|
|
1147
|
+
const k = `${line}|${target}`;
|
|
1148
|
+
const fileClaim = projectRoot === void 0 ? null : fileClaimForTarget({
|
|
1149
|
+
projectRoot,
|
|
1150
|
+
filePath,
|
|
1151
|
+
written: target,
|
|
1152
|
+
admittedExtensions: ZIG_ADMITTED_EXTENSIONS,
|
|
1153
|
+
// THE SIZE CLAUSE IS THE GUARD'S NOW (`ba04c6b4c`), not this lane's: a file over
|
|
1154
|
+
// the indexer's own limit is never admitted, so a claim naming it cannot verify
|
|
1155
|
+
// while a miss still REFUTES. Measured before it existed: adopting the claim
|
|
1156
|
+
// without it took whole-population `extFAIL` 0 -> 17, all of them oversized
|
|
1157
|
+
// `.zig` files, and all 15 of the corpus's `@import("….zig")` cap cases.
|
|
1158
|
+
maxFileSizeBytes: FILE_CLAIM_MAX_BYTES
|
|
1159
|
+
});
|
|
1160
|
+
if (!seenFacts.has(k)) {
|
|
1161
|
+
seenFacts.add(k);
|
|
1162
|
+
imports.push({
|
|
1163
|
+
module: target,
|
|
1164
|
+
isDefaultImport: false,
|
|
1165
|
+
range: this.nodeRange(n),
|
|
1166
|
+
metadata: { zigImport: true, builtin: text, ...isUsingNamespace ? { usingnamespace: true } : {} }
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
const existingImport = importEdgesAt.get(k);
|
|
1170
|
+
if (existingImport) {
|
|
1171
|
+
attachFileClaim(existingImport, fileClaim);
|
|
1172
|
+
} else {
|
|
1173
|
+
const edge = {
|
|
1174
|
+
source: "(global)",
|
|
1175
|
+
target,
|
|
1176
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
1177
|
+
confidence: 0.9,
|
|
1178
|
+
line_number: line,
|
|
1179
|
+
metadata: {
|
|
1180
|
+
relationshipType: "zig_import",
|
|
1181
|
+
isImport: true,
|
|
1182
|
+
zigImportDep: true,
|
|
1183
|
+
// THE FILE CLAIM, ONCE PER EDGE AND ON BOTH LABELS. See the block comment
|
|
1184
|
+
// above `collectZigImportSites`' call site for why it is on both.
|
|
1185
|
+
...fileClaim ? { importProjectFile: fileClaim } : {}
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
relationships.push(edge);
|
|
1189
|
+
importEdgesAt.set(k, edge);
|
|
1190
|
+
}
|
|
1191
|
+
const existingRef = refEdgesAt.get(k);
|
|
1192
|
+
if (existingRef) {
|
|
1193
|
+
attachFileClaim(existingRef, fileClaim);
|
|
1194
|
+
} else {
|
|
1195
|
+
const edge = {
|
|
1196
|
+
source: "(global)",
|
|
1197
|
+
target,
|
|
1198
|
+
type: ASTEngine.RelationshipType.References,
|
|
1199
|
+
confidence: 1,
|
|
1200
|
+
line_number: line,
|
|
1201
|
+
metadata: {
|
|
1202
|
+
relationshipType: "zig_import",
|
|
1203
|
+
isImport: true,
|
|
1204
|
+
...fileClaim ? { importProjectFile: fileClaim } : {}
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
relationships.push(edge);
|
|
1208
|
+
refEdgesAt.set(k, edge);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1215
|
+
const c = n.namedChild(i);
|
|
1216
|
+
if (c) stack.push(c);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Z32: `instantiates` for the two statically derivable classes — a call to an
|
|
1222
|
+
* in-file `fn … type`, and a construction of an in-file type (`TypeName{ … }`,
|
|
1223
|
+
* bare or QUALIFIED: `Color.C{ .r = 1 }`). The inventory is the tree's own
|
|
1224
|
+
* declarations, so the class needs no inference. `.{ … }` is an
|
|
1225
|
+
* anonymous_struct_initializer with no name and instantiates nothing (Z21).
|
|
1226
|
+
*/
|
|
1227
|
+
analyzeZigInstantiation(rootNode, relationships) {
|
|
1228
|
+
const typeNames = /* @__PURE__ */ new Set();
|
|
1229
|
+
const typeFns = /* @__PURE__ */ new Set();
|
|
1230
|
+
const inventory = (n) => {
|
|
1231
|
+
if (n.type === "function_declaration" || n.type === "function_signature") {
|
|
1232
|
+
const t = n.childForFieldName?.("type");
|
|
1233
|
+
if (t && (t.type === "builtin_type" || t.type === "identifier") && String(t.text) === "type") {
|
|
1234
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1235
|
+
const c = n.namedChild(i);
|
|
1236
|
+
if (c && c.type === "identifier") {
|
|
1237
|
+
typeFns.add(String(c.text));
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (n.type === "struct_declaration" || n.type === "enum_declaration" || n.type === "union_declaration" || n.type === "opaque_declaration") {
|
|
1244
|
+
const p = n.parent;
|
|
1245
|
+
const p0 = p?.namedChild(0);
|
|
1246
|
+
if (p && p.type === "variable_declaration" && p.namedChildCount > 0 && p0?.type === "identifier") {
|
|
1247
|
+
typeNames.add(String(p0.text));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1251
|
+
const c = n.namedChild(i);
|
|
1252
|
+
if (c) inventory(c);
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
inventory(rootNode);
|
|
1256
|
+
if (typeNames.size === 0 && typeFns.size === 0) return;
|
|
1257
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1258
|
+
const enclosingName = (n) => {
|
|
1259
|
+
let cur = n.parent;
|
|
1260
|
+
while (cur) {
|
|
1261
|
+
if (cur.type === "function_declaration" || cur.type === "test_declaration") {
|
|
1262
|
+
for (let i = 0; i < cur.namedChildCount; i++) {
|
|
1263
|
+
const c = cur.namedChild(i);
|
|
1264
|
+
if (c && c.type === "identifier") {
|
|
1265
|
+
if (cur.type === "test_declaration" || this.zigFunctionScopeIsPublished(cur)) {
|
|
1266
|
+
return String(c.text);
|
|
1267
|
+
}
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
cur = cur.parent;
|
|
1273
|
+
}
|
|
1274
|
+
return "(global)";
|
|
1275
|
+
};
|
|
1276
|
+
const push = (source, target, line, md) => {
|
|
1277
|
+
if (!target || line <= 0) return;
|
|
1278
|
+
const k = `${source}|${target}|${line}`;
|
|
1279
|
+
if (seen.has(k)) return;
|
|
1280
|
+
seen.add(k);
|
|
1281
|
+
relationships.push({
|
|
1282
|
+
source,
|
|
1283
|
+
target,
|
|
1284
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
1285
|
+
confidence: 1,
|
|
1286
|
+
line_number: line,
|
|
1287
|
+
metadata: { relationshipType: "zig_instantiation", zigSpecific: true, isExternalTarget: false, ...md }
|
|
1288
|
+
});
|
|
1289
|
+
};
|
|
1290
|
+
const sites = (n) => {
|
|
1291
|
+
if (n.type === "call_expression") {
|
|
1292
|
+
const callee = n.namedChildCount > 0 ? n.namedChild(0) : null;
|
|
1293
|
+
if (callee && callee.type === "identifier" && typeFns.has(String(callee.text))) {
|
|
1294
|
+
push(enclosingName(n), String(callee.text), this.nodeLine(callee) ?? 0, { isGenericTypeApplication: true });
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (n.type === "struct_initializer") {
|
|
1298
|
+
const first = n.namedChildCount > 0 ? n.namedChild(0) : null;
|
|
1299
|
+
if (first) {
|
|
1300
|
+
const raw = String(first.text ?? "");
|
|
1301
|
+
const nm = raw.includes(".") ? raw.slice(raw.lastIndexOf(".") + 1) : raw;
|
|
1302
|
+
if (first.type !== "call_expression" && typeNames.has(nm)) {
|
|
1303
|
+
push(enclosingName(n), nm, this.nodeLine(first) ?? 0, { isConstructorApplication: true });
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1308
|
+
const c = n.namedChild(i);
|
|
1309
|
+
if (c) sites(c);
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
sites(rootNode);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Z33: one `exports` relationship per exported declaration, GENERATED FROM the
|
|
1316
|
+
* export list so the array and the edges cannot drift — which is the failure the
|
|
1317
|
+
* `imports` twins already paid for once, when they were added to three shapes and
|
|
1318
|
+
* not the fourth and only a bidirectional gate caught it.
|
|
1319
|
+
*
|
|
1320
|
+
* Source is `(file)`, not `(global)`: the exporting entity is the compilation
|
|
1321
|
+
* unit, and `(global)` is this lane's marker for the EXPRESSION scope that reads
|
|
1322
|
+
* and imports are attributed to. Keeping them apart lets a consumer group an API
|
|
1323
|
+
* surface without guessing which convention a row used. The line is omitted —
|
|
1324
|
+
* never written as 0 — when a declaration carries none (#820).
|
|
1325
|
+
*/
|
|
1326
|
+
emitZigExportEdges(relationships, exports) {
|
|
1327
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1328
|
+
for (const e of exports) {
|
|
1329
|
+
const name = String(e.name ?? "");
|
|
1330
|
+
if (!name) continue;
|
|
1331
|
+
const line = e.range?.startLine ?? 0;
|
|
1332
|
+
const k = `${name}|${line}`;
|
|
1333
|
+
if (seen.has(k)) continue;
|
|
1334
|
+
seen.add(k);
|
|
1335
|
+
relationships.push({
|
|
1336
|
+
source: "(file)",
|
|
1337
|
+
target: name,
|
|
1338
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
1339
|
+
confidence: 1,
|
|
1340
|
+
...line > 0 ? { line_number: line } : {},
|
|
1341
|
+
metadata: { relationshipType: "zig_export", zigExport: true, exportKind: String(e.kind ?? "") }
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
canonicalizePublishedNames(symbols, relationships, exports) {
|
|
1346
|
+
for (const s of symbols) {
|
|
1347
|
+
if (typeof s.name === "string") s.name = stripZigQuoting(s.name);
|
|
1348
|
+
if (typeof s.ownerName === "string") s.ownerName = stripZigQuoting(s.ownerName);
|
|
1349
|
+
if (typeof s.id === "string") s.id = stripZigQuoting(s.id);
|
|
1350
|
+
if (typeof s.returnType === "string") s.returnType = stripZigQuoting(s.returnType);
|
|
1351
|
+
for (const p of s.parameters ?? []) {
|
|
1352
|
+
if (typeof p?.type === "string") p.type = stripZigQuoting(p.type);
|
|
1353
|
+
if (typeof p?.name === "string") p.name = stripZigQuoting(p.name);
|
|
1354
|
+
}
|
|
1355
|
+
for (const key of ["genericParameters", "decorators", "comments"]) {
|
|
1356
|
+
const arr = s[key];
|
|
1357
|
+
if (Array.isArray(arr)) s[key] = arr.map((v) => typeof v === "string" ? stripZigQuoting(v) : v);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
for (const r of relationships) {
|
|
1361
|
+
if (typeof r.source === "string") r.source = stripZigQuoting(r.source);
|
|
1362
|
+
if (typeof r.target === "string") r.target = stripZigQuoting(r.target);
|
|
1363
|
+
}
|
|
1364
|
+
for (const e of exports) {
|
|
1365
|
+
if (typeof e.name === "string") e.name = stripZigQuoting(e.name);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
analyzeZigBareIdentifierUses(rootNode, relationships, symbols) {
|
|
1369
|
+
const fileValues = /* @__PURE__ */ new Set();
|
|
1370
|
+
const enumMemberNames = /* @__PURE__ */ new Set();
|
|
1371
|
+
for (const s of symbols) {
|
|
1372
|
+
if (s.kind === ASTEngine.SymbolKind.EnumMember) enumMemberNames.add(s.name);
|
|
1373
|
+
if (!s.ownerName && (s.kind === ASTEngine.SymbolKind.Constant || s.kind === ASTEngine.SymbolKind.Variable || s.kind === ASTEngine.SymbolKind.Function || s.kind === ASTEngine.SymbolKind.EnumMember)) {
|
|
1374
|
+
fileValues.add(s.name);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
1378
|
+
const scopeStack = [];
|
|
1379
|
+
const isShadowed = (name) => {
|
|
1380
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) {
|
|
1381
|
+
if (scopeStack[i].names.has(name)) return true;
|
|
1382
|
+
}
|
|
1383
|
+
return false;
|
|
1384
|
+
};
|
|
1385
|
+
const bindParamName = (node, frame) => {
|
|
1386
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1387
|
+
const c = node.namedChild(i);
|
|
1388
|
+
if (c && c.type === "identifier") {
|
|
1389
|
+
if (c.text !== "_") frame.names.add(c.text);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const any = this.anyChild(node, (c) => c.type === "identifier");
|
|
1394
|
+
if (any && any.text !== "_") frame.names.add(any.text);
|
|
1395
|
+
};
|
|
1396
|
+
const bindIdentifiersUnder = (node, frame) => {
|
|
1397
|
+
const walk = [node];
|
|
1398
|
+
while (walk.length > 0) {
|
|
1399
|
+
const m = walk.pop();
|
|
1400
|
+
if (m !== node && (m.type === "call_expression" || m.type === "block" || m.type === "block_expression")) {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
if (m.type === "identifier" && m.text !== "_") frame.names.add(m.text);
|
|
1404
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1405
|
+
const c = m.namedChild(i);
|
|
1406
|
+
if (c) walk.push(c);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
const isDefiningOccurrence = (node) => {
|
|
1411
|
+
const p = node.parent;
|
|
1412
|
+
if (!p) return false;
|
|
1413
|
+
if (p.type === "variable_declaration" || p.type === "assignment_expression") {
|
|
1414
|
+
if (!this.isPlainAssignment(p)) return false;
|
|
1415
|
+
const lhs = p.namedChildCount > 0 ? p.namedChild(0) : null;
|
|
1416
|
+
return !!lhs && lhs.id === node.id;
|
|
1417
|
+
}
|
|
1418
|
+
if (p.type === "error_set_declaration" && node.type === "identifier") return true;
|
|
1419
|
+
if (p.type === "function_declaration" || p.type === "test_declaration" || p.type === "container_field" || p.type === "error_set_declaration") {
|
|
1420
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
1421
|
+
const c = p.namedChild(i);
|
|
1422
|
+
if (c && c.type === "identifier") {
|
|
1423
|
+
return c.startIndex === node.startIndex && c.endIndex === node.endIndex;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
return false;
|
|
1428
|
+
};
|
|
1429
|
+
const isCallCallee = (node) => {
|
|
1430
|
+
let top = node;
|
|
1431
|
+
let p = node.parent;
|
|
1432
|
+
while (p && p.type !== "call_expression" && p.type !== "block" && p.type !== "function_declaration") {
|
|
1433
|
+
top = p;
|
|
1434
|
+
p = p.parent;
|
|
1435
|
+
}
|
|
1436
|
+
if (!p || p.type !== "call_expression") return false;
|
|
1437
|
+
const first = p.namedChildCount > 0 ? p.namedChild(0) : null;
|
|
1438
|
+
return !!first && top.startIndex >= first.startIndex && top.endIndex <= first.endIndex;
|
|
1439
|
+
};
|
|
1440
|
+
const stack = [{ node: rootNode, exit: false, pushed: false }];
|
|
1441
|
+
while (stack.length > 0) {
|
|
1442
|
+
const { node, exit, pushed } = stack.pop();
|
|
1443
|
+
if (exit) {
|
|
1444
|
+
if (pushed) scopeStack.pop();
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const t = node.type;
|
|
1448
|
+
let pushedScope = false;
|
|
1449
|
+
if (t === "function_declaration" || t === "test_declaration") {
|
|
1450
|
+
const frame = { names: /* @__PURE__ */ new Set(), fnName: null };
|
|
1451
|
+
const params = [node];
|
|
1452
|
+
while (params.length > 0) {
|
|
1453
|
+
const m = params.pop();
|
|
1454
|
+
if (m.type !== "function_declaration" || m === node) {
|
|
1455
|
+
if (m.type === "parameter") bindParamName(m, frame);
|
|
1456
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1457
|
+
const c = m.namedChild(i);
|
|
1458
|
+
if (c && c.type !== "block" && c.type !== "block_expression" && c.type !== "function_declaration") params.push(c);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
const nm = this.anyChild(node, (c) => c.type === "identifier");
|
|
1463
|
+
frame.fnName = nm && (node.type === "test_declaration" || this.zigFunctionScopeIsPublished(node)) ? nm.text : null;
|
|
1464
|
+
scopeStack.push(frame);
|
|
1465
|
+
pushedScope = true;
|
|
1466
|
+
} else if (t === "block" || t === "block_expression") {
|
|
1467
|
+
scopeStack.push({ names: /* @__PURE__ */ new Set(), fnName: null });
|
|
1468
|
+
pushedScope = true;
|
|
1469
|
+
} else if (t === "switch_case") {
|
|
1470
|
+
scopeStack.push({ names: /* @__PURE__ */ new Set(), fnName: null });
|
|
1471
|
+
pushedScope = true;
|
|
1472
|
+
} else if (t === "payload") {
|
|
1473
|
+
const top = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
|
|
1474
|
+
if (top) bindIdentifiersUnder(node, top);
|
|
1475
|
+
} else if (t === "variable_declaration") {
|
|
1476
|
+
const top = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
|
|
1477
|
+
if (top && (this.hasToken(node, "var") || this.hasToken(node, "const"))) {
|
|
1478
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1479
|
+
const c = node.namedChild(i);
|
|
1480
|
+
if (!c) continue;
|
|
1481
|
+
if (c.type === "identifier") {
|
|
1482
|
+
if (c.text !== "_") top.names.add(c.text);
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
break;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
} else if (t === "identifier") {
|
|
1489
|
+
const name = node.text;
|
|
1490
|
+
const p = node.parent;
|
|
1491
|
+
let inError = false;
|
|
1492
|
+
if (p && p.type === "error_type" && p.namedChildCount > 0) {
|
|
1493
|
+
const last = p.namedChild(p.namedChildCount - 1);
|
|
1494
|
+
if (last && last.startIndex === node.startIndex && last.endIndex === node.endIndex) {
|
|
1495
|
+
let q = p.parent;
|
|
1496
|
+
let typePos = false;
|
|
1497
|
+
while (q) {
|
|
1498
|
+
if (q.type === "parameter" || q.type === "container_field") {
|
|
1499
|
+
typePos = true;
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
if (q.type === "variable_declaration" || q.type === "arguments" || q.type === "return_expression" || q.type === "call_expression" || q.type === "function_declaration" || q.type === "block" || q.type === "block_expression") break;
|
|
1503
|
+
q = q.parent;
|
|
1504
|
+
}
|
|
1505
|
+
if (!typePos) inError = true;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (name && name !== "_" && !name.startsWith("$") && !isDefiningOccurrence(node) && !isCallCallee(node) && !(p && p.type === "field_expression") && // F6 audit: the former `label_name` / `continue_label` disjuncts are
|
|
1509
|
+
// rust vocabulary — tree-sitter-zig labels are `block_label` /
|
|
1510
|
+
// `break_label` (both live here) and a `continue :outer` label is an
|
|
1511
|
+
// anonymous token with no identifier child to suppress.
|
|
1512
|
+
!(p && p.type === "labeled_statement" || p && p.type === "block_label" || p && p.type === "break_label") && !isShadowed(name) && (fileValues.has(name) || inError && enumMemberNames.has(name))) {
|
|
1513
|
+
let fnName = null;
|
|
1514
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) {
|
|
1515
|
+
if (scopeStack[i].fnName) {
|
|
1516
|
+
fnName = scopeStack[i].fnName;
|
|
1517
|
+
break;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
const src = fnName ?? "(global)";
|
|
1521
|
+
const key = `${src}::uses::${name}`;
|
|
1522
|
+
if (!emitted.has(key)) {
|
|
1523
|
+
emitted.add(key);
|
|
1524
|
+
relationships.push({
|
|
1525
|
+
source: src,
|
|
1526
|
+
target: name,
|
|
1527
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
1528
|
+
confidence: 1,
|
|
1529
|
+
// M59.A7 (#825): the read occurs on the identifier's own line.
|
|
1530
|
+
...this.nodeLine(node) ? { line_number: this.nodeLine(node) } : {},
|
|
1531
|
+
metadata: { relationshipType: "identifier_usage", nodeType: node.type }
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
stack.push({ node, exit: true, pushed: pushedScope });
|
|
1537
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
1538
|
+
const child = node.namedChild(i);
|
|
1539
|
+
if (child) stack.push({ node: child, exit: false, pushed: false });
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
// F6 audit — REMOVED: `hasZigQualifiedAncestor`. Its only `true` return was
|
|
1544
|
+
// `cur.type === "qualified_type"`, and tree-sitter-zig has no such node (a
|
|
1545
|
+
// qualified type `std.mem.Allocator` is a nested `field_expression`,
|
|
1546
|
+
// probe-verified), so the function returned false for every input and the
|
|
1547
|
+
// `!this.hasZigQualifiedAncestor(node)` guard it fed was a no-op. Its
|
|
1548
|
+
// boundary names were equally dead (`field_declaration`, `composite_literal`,
|
|
1549
|
+
// `assignment_statement` are rust/go names; zig uses `container_field`,
|
|
1550
|
+
// `struct_initializer`, `variable_declaration`). The intended suppression of
|
|
1551
|
+
// type-position reads is already done by the direct `field_expression`
|
|
1552
|
+
// parent check at the call site; re-pointing this at `field_expression`
|
|
1553
|
+
// would have dropped genuine receiver reads instead.
|
|
1554
|
+
/** Builtin @-prefixed calls and generic type applications are not user
|
|
1555
|
+
* call targets. Returns the leaf name for a real user call site, else
|
|
1556
|
+
* undefined. */
|
|
1557
|
+
zigCalleeLeafNode(call) {
|
|
1558
|
+
const callee = call.namedChildCount > 0 ? call.namedChild(0) : null;
|
|
1559
|
+
if (!callee) return void 0;
|
|
1560
|
+
if (callee.type === "identifier") {
|
|
1561
|
+
if (callee.text === "usingnamespace") return void 0;
|
|
1562
|
+
return callee;
|
|
1563
|
+
}
|
|
1564
|
+
if (callee.type === "field_expression") {
|
|
1565
|
+
return this.zigFieldLeafNode(callee);
|
|
1566
|
+
}
|
|
1567
|
+
return void 0;
|
|
1568
|
+
}
|
|
1569
|
+
// Z17 (deep review) — REMOVED as dead: `zigCalleeLeaf` and `zigFieldLeaf`,
|
|
1570
|
+
// the string-returning wrappers around the two `*Node` accessors below. Every
|
|
1571
|
+
// call site uses the NODE form, because every emitted edge needs the site line
|
|
1572
|
+
// (M59.A7 #825); the string forms had no caller. `zigTypeLeaf` (the same
|
|
1573
|
+
// wrapper for the type leaf) went the same way, and `nameLine` — a helper for
|
|
1574
|
+
// the C/C++ lanes' attribute-line-base rule, never called here, so the zig lane
|
|
1575
|
+
// has no `#920` line rule to claim. Dead code in a review wave is a claim
|
|
1576
|
+
// about a contract that is not implemented, and four of them were sitting in
|
|
1577
|
+
// the file.
|
|
1578
|
+
/** The name-bearing field LEAF of a field_expression (`std.math.abs` ->
|
|
1579
|
+
* `abs`, `list.items` -> `items`) — the node whose text is the edge's target
|
|
1580
|
+
* and whose line is the edge's site (M59.A7 #825). */
|
|
1581
|
+
zigFieldLeafNode(expr) {
|
|
1582
|
+
const last = expr.namedChildCount > 0 ? expr.namedChild(expr.namedChildCount - 1) : null;
|
|
1583
|
+
if (last && last.type === "identifier") return last;
|
|
1584
|
+
return void 0;
|
|
1585
|
+
}
|
|
1586
|
+
/** True when this field_expression is the direct callee of a call_expression
|
|
1587
|
+
* (list.append in `list.append()`) — then the calls edge is the fact, not
|
|
1588
|
+
* a uses edge. Also skips builtin access `std.foo`. */
|
|
1589
|
+
isCallCallee(expr) {
|
|
1590
|
+
const p = expr.parent;
|
|
1591
|
+
return !!p && p.type === "call_expression";
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Z66 — the dotted path a read's receiver spells, when the receiver is an
|
|
1595
|
+
* EXTERNAL `@import` alias, else null. The path is written out of the tree,
|
|
1596
|
+
* segment by segment, so it is a parse fact and not a guess.
|
|
1597
|
+
*
|
|
1598
|
+
* `std.mem.Allocator` reaches the edge layer as the leaf `Allocator` alone; this
|
|
1599
|
+
* walks up the `field_expression` chain to the root identifier and, if that root
|
|
1600
|
+
* is a name this file binds by importing a BARE module path, returns
|
|
1601
|
+
* `std.mem.Allocator`. A chain that breaks anywhere — a call result receiver
|
|
1602
|
+
* (`std.io.getStdOut().writer()`), an anonymous enum literal (`.bad`, one named
|
|
1603
|
+
* child and no base), a computed base — returns null and the edge stays as it
|
|
1604
|
+
* was, because a path this analyzer cannot spell out of the tree is a path it
|
|
1605
|
+
* must not publish.
|
|
1606
|
+
*
|
|
1607
|
+
* The name is emitted in place of the leaf and the receiver is recorded in
|
|
1608
|
+
* metadata, so the edge says which module it names (`zigImportAlias`,
|
|
1609
|
+
* `zigImportModule`) and a consumer can EXCLUDE it from its own unresolved count
|
|
1610
|
+
* with a declared reason instead of inferring one. That is the whole of the
|
|
1611
|
+
* `imports`-module-path defect's cost: `diagnose-incoming` reads 404 / 1,024 /
|
|
1612
|
+
* 2,585 unresolved targets for zig with 0 module-wildcard shaped, because the
|
|
1613
|
+
* target was the leaf, so nothing about the edge said `std`.
|
|
1614
|
+
*/
|
|
1615
|
+
zigExternalReceiverPath(expr, aliases) {
|
|
1616
|
+
if (!expr || expr.type !== "field_expression" || aliases.size === 0) return null;
|
|
1617
|
+
if (expr.namedChildCount < 2) return null;
|
|
1618
|
+
const segments = [];
|
|
1619
|
+
let node = expr;
|
|
1620
|
+
let root = "";
|
|
1621
|
+
let depth = 0;
|
|
1622
|
+
while (depth++ < 64) {
|
|
1623
|
+
const fieldNode = node.namedChild(node.namedChildCount - 1);
|
|
1624
|
+
if (!fieldNode || fieldNode.type !== "identifier") return null;
|
|
1625
|
+
segments.push(stripZigQuoting(String(fieldNode.text ?? "")));
|
|
1626
|
+
const base = node.namedChild(0);
|
|
1627
|
+
if (!base) return null;
|
|
1628
|
+
if (base.type === "identifier") {
|
|
1629
|
+
root = stripZigQuoting(String(base.text ?? ""));
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
if (base.type !== "field_expression") return null;
|
|
1633
|
+
node = base;
|
|
1634
|
+
}
|
|
1635
|
+
if (root === "") return null;
|
|
1636
|
+
const hit = aliases.get(root);
|
|
1637
|
+
if (!hit || !hit.external) return null;
|
|
1638
|
+
segments.reverse();
|
|
1639
|
+
return { target: `${root}.${segments.join(".")}`, alias: root, module: hit.module };
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* THE EXTERNAL CLAIM'S METADATA, and the ONE place the `zigExternalRef` KEY is decided.
|
|
1643
|
+
* Three emission sites (calls, field reads, declared-type references) all call this,
|
|
1644
|
+
* because three copies of "may I claim this" is three rules.
|
|
1645
|
+
*
|
|
1646
|
+
* WHY THE CLAIM IS NOT ALWAYS ADMISSIBLE, measured rather than argued. The incoming
|
|
1647
|
+
* arbiter reads `zigExternalRef` as an OWNER claim — "the target names a declaration
|
|
1648
|
+
* whose OWNER the project does not publish" — and verifies it by looking the owner up
|
|
1649
|
+
* (`script/m14/incoming-rule.ts`, `verifyExternalClaim`). For a target the receiver walk
|
|
1650
|
+
* produced, the owner is everything before the LAST dot, and when the receiver path is
|
|
1651
|
+
* exactly ONE segment long, that owner IS the alias:
|
|
1652
|
+
*
|
|
1653
|
+
* `std.mem` owner `std` -> the file's own `const std = @import("std")`
|
|
1654
|
+
* `std.mem.Allocator` owner `std.mem` -> no declaration carries that name
|
|
1655
|
+
*
|
|
1656
|
+
* Z35 publishes that import binding as a `constant` named for the alias, so the owner of
|
|
1657
|
+
* a one-segment path is ALWAYS a published declaration and the claim is ALWAYS refuted.
|
|
1658
|
+
* MEASURED on the gate's own 60-file stride sample (565 marked edges, via
|
|
1659
|
+
* `validation/m14/fixtures/zig/external-claim-audit.ts`), cross-tabulated by target depth
|
|
1660
|
+
* because `owner === alias` is exactly the one-dot case:
|
|
1661
|
+
*
|
|
1662
|
+
* 1 dot: extOK 1 extFAIL 259
|
|
1663
|
+
* 2 dots: extOK 223 extFAIL 0
|
|
1664
|
+
* 3 dots: extOK 60 extFAIL 0
|
|
1665
|
+
* 4 dots: extOK 21 extFAIL 0
|
|
1666
|
+
* 5 dots: extOK 1 extFAIL 0
|
|
1667
|
+
*
|
|
1668
|
+
* and all 259 were answered by a `constant` named for the alias, in the edge's own file.
|
|
1669
|
+
* The key was asserting a claim the project can check and does refute — this lane's own
|
|
1670
|
+
* defect, not a marker-spelling accident — and the fix is to stop asserting it where it
|
|
1671
|
+
* cannot survive the lookup.
|
|
1672
|
+
*
|
|
1673
|
+
* WHAT IS NOT LOST. The QUALIFICATION stays: the target is still `std.mem`, not the bare
|
|
1674
|
+
* leaf `mem`, which is the Z66 defect fix and is untouched by this. The receiver is still
|
|
1675
|
+
* identified on the edge — `zigImportAlias`, `zigImportModule` and the post-pass's
|
|
1676
|
+
* `targetScope: "file-external"` are all still written — so a consumer can still exclude
|
|
1677
|
+
* the edge by a rule instead of by a guess. What is dropped is one key whose meaning the
|
|
1678
|
+
* family defines as "the owner is not published", which is false here.
|
|
1679
|
+
*
|
|
1680
|
+
* THE RESIDUAL RISK, stated rather than hidden: a project that declares a symbol literally
|
|
1681
|
+
* named `std.mem` (a quoted identifier, `@"std.mem"`) would refute a two-segment claim too.
|
|
1682
|
+
* It is not zero, and it is what the arbiter's refutation is FOR — a deep-path claim that
|
|
1683
|
+
* ever reads refuted is a defect this lane can see.
|
|
1684
|
+
*/
|
|
1685
|
+
zigExternalMetadata(ext) {
|
|
1686
|
+
if (!ext) return {};
|
|
1687
|
+
const receiver = { zigImportAlias: ext.alias, zigImportModule: ext.module };
|
|
1688
|
+
const dot = ext.target.lastIndexOf(".");
|
|
1689
|
+
if (dot <= 0 || ext.target.slice(0, dot) === ext.alias) return receiver;
|
|
1690
|
+
return { zigExternalRef: true, ...receiver };
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Did `from` reach `decl` (a `variable_declaration`) THROUGH its type annotation?
|
|
1694
|
+
*
|
|
1695
|
+
* True for `var x: c.Foo = ...` and `var x: ?c.Bar = null`, false for the two
|
|
1696
|
+
* positions that are value reads: the initializer (`var x = v.baz`) and the
|
|
1697
|
+
* assignment LHS that also parses as a declaration. The test is an ANCESTRY test
|
|
1698
|
+
* rather than an identity test because the grammar's `type` field is the whole
|
|
1699
|
+
* wrapped path — `?c.Bar` is one `field_expression` — so an inner segment of the
|
|
1700
|
+
* chain reaches the declaration from a DESCENDANT of that node.
|
|
1701
|
+
*
|
|
1702
|
+
* It reads the same `type` field the declared-type collector reads, so the pass
|
|
1703
|
+
* that suppresses the duplicate `uses` and the pass that carries the fact as
|
|
1704
|
+
* `references (declared_type)` are keyed off one node by construction. `var d =
|
|
1705
|
+
* v.baz` has no `type` field at all, which is why the initializer is excluded
|
|
1706
|
+
* here and not by a name test.
|
|
1707
|
+
*/
|
|
1708
|
+
zigArrivedAtTypeAnnotation(decl, from) {
|
|
1709
|
+
const annotation = this.typeFieldOf(decl);
|
|
1710
|
+
if (!annotation || !from) return false;
|
|
1711
|
+
let cur = from;
|
|
1712
|
+
let hops = 0;
|
|
1713
|
+
while (cur && hops++ < 8) {
|
|
1714
|
+
if (cur.id === annotation.id) return true;
|
|
1715
|
+
const parent = cur.parent;
|
|
1716
|
+
if (!parent || parent.id === decl.id) return false;
|
|
1717
|
+
cur = parent;
|
|
1718
|
+
}
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* The field_expression a leaf NAME belongs to, when the leaf is the LAST named
|
|
1723
|
+
* child of one — i.e. when the leaf really is the final segment of that access
|
|
1724
|
+
* rather than an identifier found somewhere under it. The declared-type pass reads
|
|
1725
|
+
* leaves by a breadth-first walk (Z19), so for an unnamed fn-TYPE parameter the leaf
|
|
1726
|
+
* can be a BASE segment; qualifying from there would name a path the annotation
|
|
1727
|
+
* does not end at, so that case is refused instead.
|
|
1728
|
+
*/
|
|
1729
|
+
zigTrailingFieldExpr(leaf) {
|
|
1730
|
+
if (!leaf) return void 0;
|
|
1731
|
+
const parent = leaf.parent;
|
|
1732
|
+
if (!parent || parent.type !== "field_expression") return void 0;
|
|
1733
|
+
if (parent.namedChild(parent.namedChildCount - 1)?.id !== leaf.id) return void 0;
|
|
1734
|
+
return parent;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Declared-type reference graph (family standard — go/c/rust M14). Collects
|
|
1738
|
+
* the leaf names of parameter types, return types, and struct/union/enum/
|
|
1739
|
+
* error-set field types, emitting `references` edges. Builtins and the bare
|
|
1740
|
+
* `void`/`!void` wrappers are excluded; generic type applications on a var
|
|
1741
|
+
* RHS (std.StringHashMap(usize)) are NOT declared-type deps.
|
|
1742
|
+
*/
|
|
1743
|
+
extractZigDeclaredTypeRefs(rootNode, relationships) {
|
|
1744
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
1745
|
+
const importAliases = zigImportAliases(rootNode);
|
|
1746
|
+
const push = (leaf, site) => {
|
|
1747
|
+
if (!leaf) return;
|
|
1748
|
+
const head = leaf;
|
|
1749
|
+
if (!head || ZIG_DECLARED_TYPE_BUILTINS.has(head) && !zigFileScopeNames(rootNode).has(head)) return;
|
|
1750
|
+
const c0 = head.charCodeAt(0);
|
|
1751
|
+
if (c0 >= 48 && c0 <= 57) return;
|
|
1752
|
+
const ext = this.zigExternalReceiverPath(this.zigTrailingFieldExpr(site), importAliases);
|
|
1753
|
+
const target = ext ? ext.target : head;
|
|
1754
|
+
if (!emitted.has(target)) {
|
|
1755
|
+
emitted.add(target);
|
|
1756
|
+
relationships.push({
|
|
1757
|
+
source: "(declared-type)",
|
|
1758
|
+
target,
|
|
1759
|
+
type: ASTEngine.RelationshipType.References,
|
|
1760
|
+
confidence: 0.9,
|
|
1761
|
+
// M59.A7 (#825): the type as written at this site.
|
|
1762
|
+
...this.nodeLine(site) ? { line_number: this.nodeLine(site) } : {},
|
|
1763
|
+
metadata: {
|
|
1764
|
+
relationshipType: "declared_type",
|
|
1765
|
+
...this.zigExternalMetadata(ext)
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
const stack = [rootNode];
|
|
1771
|
+
while (stack.length > 0) {
|
|
1772
|
+
const n = stack.pop();
|
|
1773
|
+
if (n.type === "function_declaration" || n.type === "function_signature") {
|
|
1774
|
+
const paramsNode = this.anyChild(n, (c) => c.type === "parameters");
|
|
1775
|
+
if (paramsNode) {
|
|
1776
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
1777
|
+
const param = paramsNode.namedChild(i);
|
|
1778
|
+
if (param?.type !== "parameter") continue;
|
|
1779
|
+
const tNode = this.zigTypeLeafNode(param);
|
|
1780
|
+
if (tNode) push(tNode.text, tNode);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
const retNode = this.typeFieldOf(n) ?? this.zigFallbackReturnType(n);
|
|
1784
|
+
const retLeafNode = retNode ? this.leafOfTypeNode(retNode) : void 0;
|
|
1785
|
+
if (retLeafNode) push(retLeafNode.text, retLeafNode);
|
|
1786
|
+
}
|
|
1787
|
+
if (n.type === "container_field") {
|
|
1788
|
+
const tNode = this.zigTypeLeafNode(n);
|
|
1789
|
+
if (tNode) push(tNode.text, tNode);
|
|
1790
|
+
}
|
|
1791
|
+
if (n.type === "variable_declaration") {
|
|
1792
|
+
const tField = this.typeFieldOf(n);
|
|
1793
|
+
if (tField) {
|
|
1794
|
+
const leaf = this.leafOfTypeNode(tField);
|
|
1795
|
+
if (leaf) push(leaf.text, leaf);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1799
|
+
const c = n.namedChild(i);
|
|
1800
|
+
if (c) stack.push(c);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* The history this replaced, kept because the field-based version is only
|
|
1806
|
+
* better if the field is really there (deep-review fixes, 2026-08-29):
|
|
1807
|
+
* (1) builtin_type nodes were dropped because they shadowed the real leaf —
|
|
1808
|
+
* `map: *std.StringHashMap(usize)` returned `usize`. Z13 keeps that
|
|
1809
|
+
* exclusion (it is a value filter, in `push`) and fixes the cause: the
|
|
1810
|
+
* leaf is read from the `type` FIELD, so a generic ARGUMENT can no
|
|
1811
|
+
* longer be the answer because it is no longer in scope.
|
|
1812
|
+
* (2) the first identifier was dropped as the declared name. Z13 drops the
|
|
1813
|
+
* whole guess — the field is the name's sibling, not its predecessor.
|
|
1814
|
+
* (3) enum members yielded undefined, and still do: a `container_field`
|
|
1815
|
+
* with no `type` field IS the enum member, which is now the test
|
|
1816
|
+
* instead of a consequence.
|
|
1817
|
+
*/
|
|
1818
|
+
/**
|
|
1819
|
+
* True when a `struct_initializer` initializes an array/slice OF TYPES —
|
|
1820
|
+
* `pub const Implements = [_]type{ gtk.Scrollable };` — so the declaration is
|
|
1821
|
+
* a type, not a value. Measured on the pinned corpora: every one of the 32
|
|
1822
|
+
* upper-case `struct_initializer` declarations is of this form, while the
|
|
1823
|
+
* 467 lower-case ones are ordinary values (`[_]u8{ 1, 2, 3 }`, `Point{}`), and
|
|
1824
|
+
* the element type is a direct child (`array_type` / `slice_type` carrying the
|
|
1825
|
+
* `type`, `comptime_int` or `comptime_float` builtin).
|
|
1826
|
+
*/
|
|
1827
|
+
isTypeValuedInitializer(init) {
|
|
1828
|
+
for (let i = 0; i < init.namedChildCount; i++) {
|
|
1829
|
+
const c = init.namedChild(i);
|
|
1830
|
+
if (!c) continue;
|
|
1831
|
+
if (c.type !== "array_type" && c.type !== "slice_type") continue;
|
|
1832
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
1833
|
+
const e = c.namedChild(j);
|
|
1834
|
+
if (e && e.type === "builtin_type" && (e.text === "type" || e.text === "comptime_int" || e.text === "comptime_float")) return true;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
return false;
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* A `labeled_type_expression` that is really a labeled BLOCK — `const V =
|
|
1841
|
+
* cvalue: { … break :cvalue v; }` — yields a VALUE, whatever the name's case.
|
|
1842
|
+
*
|
|
1843
|
+
* The grammar has ONE node type for both Zig forms (3,900 occurrences in the
|
|
1844
|
+
* pinned corpora): `L: { … break :L v; }` is a labeled block and
|
|
1845
|
+
* `L: T { … return v; }` is a labeled type, and the two differ in the block
|
|
1846
|
+
* they carry — a block leaves by `break :label`, a type by `return`. Measured
|
|
1847
|
+
* over 400 corpus files: 9 of 9 labeled_type_expression right-hand sides
|
|
1848
|
+
* break and none returns, and the six with an upper-case name
|
|
1849
|
+
* (`pub const CValue = cvalue: { … }`, `pub const Key = key: { … }`) were
|
|
1850
|
+
* published as `Type` for what are comptime-computed struct values.
|
|
1851
|
+
*/
|
|
1852
|
+
isLabeledBlockValue(rhs) {
|
|
1853
|
+
if (rhs.type !== "labeled_type_expression") return false;
|
|
1854
|
+
let hasBreak = false;
|
|
1855
|
+
let hasReturn = false;
|
|
1856
|
+
const stack = [rhs];
|
|
1857
|
+
let guard = 0;
|
|
1858
|
+
while (stack.length > 0 && guard++ < 500) {
|
|
1859
|
+
const n = stack.pop();
|
|
1860
|
+
if (n.type === "break_label") hasBreak = true;
|
|
1861
|
+
if (n.type === "return_expression") hasReturn = true;
|
|
1862
|
+
if (hasReturn) return false;
|
|
1863
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1864
|
+
const c = n.namedChild(i);
|
|
1865
|
+
if (c) stack.push(c);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return hasBreak;
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* The identifier NODE that names a declaration's type, so a declared-type
|
|
1872
|
+
* reference can carry the line the type is written on (M59.A7 #825).
|
|
1873
|
+
*
|
|
1874
|
+
* Z13 (deep review, 2026-09-28) — this used to walk EVERY `identifier` under
|
|
1875
|
+
* the declaration node, drop the first (assumed to be the declared name) and
|
|
1876
|
+
* take the last. The grammar does not require that guess: it hands the type
|
|
1877
|
+
* over as a FIELD (`parameter.type` 78,895, `container_field.type` 49,424,
|
|
1878
|
+
* `variable_declaration.type` 43,949, `function_declaration.type` 37,129 in
|
|
1879
|
+
* the pinned corpora), and no TYPE node carries a field of that name, so
|
|
1880
|
+
* preferring it can never pick the wrong side of an `!` or a `.*`.
|
|
1881
|
+
*
|
|
1882
|
+
* The walk was wrong wherever a declaration has a DEFAULT VALUE: the value
|
|
1883
|
+
* contributes its own identifiers, and the LAST one won.
|
|
1884
|
+
* `size: Size = .byte` published `byte` — an enum member — and never `Size`
|
|
1885
|
+
* `mode: Mode = .a` published `a`
|
|
1886
|
+
* `normal = @sizeOf(u8)` published `u8`
|
|
1887
|
+
* Measured over the three corpora, the sites where the emitted target changes
|
|
1888
|
+
* are dominated by exactly these shapes (`script/m14/tmp/zigw-decltype.ts`).
|
|
1889
|
+
*
|
|
1890
|
+
* With no `type` field the node is an ENUM MEMBER (`container_field` is the
|
|
1891
|
+
* same node type for `a: u8` and for `north` / `normal = 0`; only the former
|
|
1892
|
+
* has a type), and a member's value is not a type — so that case yields
|
|
1893
|
+
* `undefined` rather than falling back to the walk. The walk survives only
|
|
1894
|
+
* where nothing else exists: an already-extracted type node.
|
|
1895
|
+
*/
|
|
1896
|
+
zigTypeLeafNode(node) {
|
|
1897
|
+
const typeField = this.typeFieldOf(node);
|
|
1898
|
+
if (typeField) return this.leafOfTypeNode(typeField);
|
|
1899
|
+
if (node.type === "container_field") return void 0;
|
|
1900
|
+
if (node.type === "variable_declaration") return void 0;
|
|
1901
|
+
const nameField = this.namedFieldOf(node, "name");
|
|
1902
|
+
if (!nameField) return this.leafOfTypeNode(node);
|
|
1903
|
+
const stack = [];
|
|
1904
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
1905
|
+
const c = node.namedChild(i);
|
|
1906
|
+
if (c && c.id !== nameField.id) stack.push(c);
|
|
1907
|
+
}
|
|
1908
|
+
while (stack.length > 0) {
|
|
1909
|
+
const n = stack.pop();
|
|
1910
|
+
if (n.type === "identifier") return n;
|
|
1911
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1912
|
+
const c = n.namedChild(i);
|
|
1913
|
+
if (c) stack.push(c);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return void 0;
|
|
1917
|
+
}
|
|
1918
|
+
/** The `name` FIELD of a node, or null. `function_declaration` carries one
|
|
1919
|
+
* 37,126 times in the corpus, `container_field` 104,090, `parameter` 77,594
|
|
1920
|
+
* (the 1,346 that do not are fn-TYPE parameters, which is what the
|
|
1921
|
+
* name/no-name split above turns on). */
|
|
1922
|
+
namedFieldOf(node, field) {
|
|
1923
|
+
if (typeof node.childForFieldName !== "function") return null;
|
|
1924
|
+
return node.childForFieldName(field) ?? null;
|
|
1925
|
+
}
|
|
1926
|
+
/** The name a TYPE NODE carries, or `undefined` when it carries none.
|
|
1927
|
+
*
|
|
1928
|
+
* - a container literal in a type position is ANONYMOUS (`fn f(s: struct {
|
|
1929
|
+
* FLAG: u8 }) void`): it has fields, not a name, and the last identifier
|
|
1930
|
+
* under it is a FIELD name — the shape that published
|
|
1931
|
+
* `references (declared-type) -> FLAG` for a type that names nothing;
|
|
1932
|
+
* - `!T` carries its dependency in the `ok` FIELD (21,353 in the corpus).
|
|
1933
|
+
* The walk took the LAST identifier under the whole node, which is a
|
|
1934
|
+
* member of the ERROR set: `fn f() error{A}!u32` published `-> A`, a
|
|
1935
|
+
* phantom, and never `u32` (correctly, as a builtin);
|
|
1936
|
+
* - an `error_type` is an unnamed set, for the same reason;
|
|
1937
|
+
* - a `labeled_type_expression` (`const V = blk: { … break :blk v; }`) has
|
|
1938
|
+
* no statically knowable name at all: its label and its value are both
|
|
1939
|
+
* identifiers in the tree, and the last one is the VALUE, not a type.
|
|
1940
|
+
*/
|
|
1941
|
+
leafOfTypeNode(t) {
|
|
1942
|
+
if (t.type === "identifier") return t;
|
|
1943
|
+
switch (t.type) {
|
|
1944
|
+
case "struct_declaration":
|
|
1945
|
+
case "union_declaration":
|
|
1946
|
+
case "enum_declaration":
|
|
1947
|
+
case "error_set_declaration":
|
|
1948
|
+
case "error_type":
|
|
1949
|
+
case "labeled_type_expression":
|
|
1950
|
+
return void 0;
|
|
1951
|
+
case "error_union_type": {
|
|
1952
|
+
const ok = this.namedFieldOf(t, "ok");
|
|
1953
|
+
return ok ? this.leafOfTypeNode(ok) : void 0;
|
|
1954
|
+
}
|
|
1955
|
+
default:
|
|
1956
|
+
return this.leafIdentifierUnder(t);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
/** The `type` FIELD of a node, or null when the grammar does not name one.
|
|
1960
|
+
* `childForFieldName` is guarded because a web-tree-sitter build without
|
|
1961
|
+
* field access would otherwise throw on the first declaration. */
|
|
1962
|
+
typeFieldOf(node) {
|
|
1963
|
+
if (typeof node.childForFieldName !== "function") return null;
|
|
1964
|
+
return node.childForFieldName("type") ?? null;
|
|
1965
|
+
}
|
|
1966
|
+
/** The LAST `identifier` at or under `node` — the leaf of a qualified type
|
|
1967
|
+
* (`std.mem.Allocator` → `Allocator`, `MyList(u32)` → `MyList`), the same
|
|
1968
|
+
* rule the old walk applied, now scoped to the type. Guarded at 200 nodes,
|
|
1969
|
+
* which the corpus census shows is far above the deepest real type
|
|
1970
|
+
* (`parameter.type` trees are a handful of nodes). */
|
|
1971
|
+
leafIdentifierUnder(node) {
|
|
1972
|
+
if (node.type === "identifier") return node;
|
|
1973
|
+
let found;
|
|
1974
|
+
let seen = 0;
|
|
1975
|
+
const stack = [node];
|
|
1976
|
+
while (stack.length > 0 && seen++ < 200) {
|
|
1977
|
+
const n = stack.pop();
|
|
1978
|
+
if (n.type === "identifier") found = n;
|
|
1979
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1980
|
+
const c = n.namedChild(i);
|
|
1981
|
+
if (c) stack.push(c);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
return found;
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* The return-type node of a function, WITHOUT the `type` field — the
|
|
1988
|
+
* fallback for a grammar variant that does not name it. Zig's return type is
|
|
1989
|
+
* the last child that is not the body and not the parameter list; a
|
|
1990
|
+
* `callconv(.C)` between them is not the return type, so the node-type list
|
|
1991
|
+
* that used to sit here is replaced by position.
|
|
1992
|
+
*/
|
|
1993
|
+
zigFallbackReturnType(fn) {
|
|
1994
|
+
const kids = this.getAllNamedChildren(fn);
|
|
1995
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
1996
|
+
const c = kids[i];
|
|
1997
|
+
if (c.type === "block" || c.type === "parameters" || c.type === "block_expression") continue;
|
|
1998
|
+
if (i === 0 && c.type === "identifier") continue;
|
|
1999
|
+
if (c.type === "calling_convention") continue;
|
|
2000
|
+
return c;
|
|
2001
|
+
}
|
|
2002
|
+
return void 0;
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* True when `node` is the left-hand side of a WRITE. Zig assignments parse as
|
|
2006
|
+
* `variable_declaration` with the LHS as the first named child, and the
|
|
2007
|
+
* OPERATOR is the discriminator: a plain `=` writes without reading, while
|
|
2008
|
+
* `+=`, `|=`, `*%=` and the rest read their left side as well (Z15).
|
|
2009
|
+
*/
|
|
2010
|
+
isWriteLhs(node) {
|
|
2011
|
+
const p = node.parent;
|
|
2012
|
+
if (!p) return false;
|
|
2013
|
+
if (p.type !== "variable_declaration" && p.type !== "assignment_expression") return false;
|
|
2014
|
+
if (p.namedChildCount > 0 && p.namedChild(0)?.id !== node.id) return false;
|
|
2015
|
+
return this.isPlainAssignment(p);
|
|
2016
|
+
}
|
|
2017
|
+
/** A plain `=` assignment: the operator is the anonymous token `=`, which is
|
|
2018
|
+
* a node type of its own, distinct from `+=`/`-=`/`|=`/`<<=` (140 distinct
|
|
2019
|
+
* anonymous token types in the corpus census). */
|
|
2020
|
+
isPlainAssignment(node) {
|
|
2021
|
+
return this.hasToken(node, "=");
|
|
2022
|
+
}
|
|
2023
|
+
/** A function's body node, read off the grammar's `body` field, falling back
|
|
2024
|
+
* to a `block` child for a grammar variant that does not name it. */
|
|
2025
|
+
functionBody(fn) {
|
|
2026
|
+
if (typeof fn.childForFieldName === "function") {
|
|
2027
|
+
const b = fn.childForFieldName("body");
|
|
2028
|
+
if (b) return b;
|
|
2029
|
+
if (fn.namedChildCount > 0) {
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
return null;
|
|
2033
|
+
}
|
|
2034
|
+
return this.anyChild(fn, (c) => c.type === "block");
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* M59.A7 (#825): the 1-based line of a syntax node, or `undefined` when the
|
|
2038
|
+
* node is absent. Never 0 — a missing site is OMITTED from the edge (the
|
|
2039
|
+
* #820 rule), so a reader can tell "no line" from "line 1".
|
|
2040
|
+
*/
|
|
2041
|
+
nodeLine(node) {
|
|
2042
|
+
if (!node || !node.startPosition || typeof node.startPosition.row !== "number") return void 0;
|
|
2043
|
+
return node.startPosition.row + 1;
|
|
2044
|
+
}
|
|
2045
|
+
/** Per-symbol cyclomatic complexity of a function/method body (family
|
|
2046
|
+
* standard — go/c/rust all emit symbol.complexity; zig did NOT).
|
|
2047
|
+
*
|
|
2048
|
+
* Deep-review fixes (2026-08-29, final zig pass):
|
|
2049
|
+
* (1) Expression forms if_expression / for_expression / while_expression
|
|
2050
|
+
* are Zig's DOMINANT forms (`const a = if (c) 1 else 2;`) and were
|
|
2051
|
+
* never counted — only the statement forms were. All six now count.
|
|
2052
|
+
* (2) `else` in expression forms is an ANONYMOUS token inside the
|
|
2053
|
+
* if_expression (probe-verified), not an else_clause — both are
|
|
2054
|
+
* counted now (else_clause node OR hasToken "else").
|
|
2055
|
+
* (3) binary_expression used a substring trap: `t.indexOf("and")`
|
|
2056
|
+
* falsely matched identifiers (`x + standard + order` -> +2). The
|
|
2057
|
+
* operator is an anonymous child token (`and`/`or`/`orelse`,
|
|
2058
|
+
* probe-verified) — we now match the token type exactly. */
|
|
2059
|
+
zigBodyComplexity(node) {
|
|
2060
|
+
let c = 1;
|
|
2061
|
+
const stack = [node];
|
|
2062
|
+
while (stack.length > 0) {
|
|
2063
|
+
const n = stack.pop();
|
|
2064
|
+
if (n.type === "if_statement" || n.type === "if_expression" || n.type === "for_statement" || n.type === "for_expression" || n.type === "while_statement" || n.type === "while_expression" || n.type === "switch_expression" || n.type === "else_clause" || n.type === "catch_expression") {
|
|
2065
|
+
c++;
|
|
2066
|
+
}
|
|
2067
|
+
if ((n.type === "if_expression" || n.type === "for_expression" || n.type === "while_expression") && this.hasToken(n, "else")) {
|
|
2068
|
+
c++;
|
|
2069
|
+
}
|
|
2070
|
+
if (n.type === "binary_expression") {
|
|
2071
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
2072
|
+
const op = n.child(i);
|
|
2073
|
+
if (op && !op.isNamed && (op.type === "and" || op.type === "or" || op.type === "orelse")) {
|
|
2074
|
+
c++;
|
|
2075
|
+
break;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2080
|
+
const c2 = n.namedChild(i);
|
|
2081
|
+
if (c2) stack.push(c2);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
return c;
|
|
2085
|
+
}
|
|
2086
|
+
/** #419-D evidence-tier classification for every emitted relationship
|
|
2087
|
+
* (family rules — rust/c/cpp/go all classify; zig did NOT). */
|
|
2088
|
+
classifyEvidenceTier(relationships) {
|
|
2089
|
+
for (const r of relationships) {
|
|
2090
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
2091
|
+
if (md.evidenceClass) continue;
|
|
2092
|
+
const t = String(r.type ?? "");
|
|
2093
|
+
const conf = typeof r.confidence === "number" ? r.confidence : 1;
|
|
2094
|
+
if (t === "contains") {
|
|
2095
|
+
md.evidenceClass = "structural";
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
if (t === "references") {
|
|
2099
|
+
md.evidenceClass = md.relationshipType === "declared_type" ? "reference_qualified" : "import_based";
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
if (t === "instantiates") {
|
|
2103
|
+
md.evidenceClass = conf >= 0.9 && md.isExternalTarget === false ? "structural" : "inferred";
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (t === "imports" || t === "exports") {
|
|
2107
|
+
md.evidenceClass = "import_based";
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2110
|
+
if (t === "mixes_in") {
|
|
2111
|
+
md.evidenceClass = "structural";
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
md.evidenceClass = "usage_contextual";
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Z65 — TARGET SCOPE: state, per edge, whether the target name is a symbol THIS
|
|
2119
|
+
* file publishes, and with which kind.
|
|
2120
|
+
*
|
|
2121
|
+
* WHY. A zig relationship carries six keys and both endpoints are NAME STRINGS.
|
|
2122
|
+
* `src/lsp/impact.ts` obtains inbound edges by scanning outgoing edges and
|
|
2123
|
+
* MATCHING TARGETS — no inbound edge is stored — so an edge's name is all a
|
|
2124
|
+
* reverse walk has, and a name two declarations share is attributed to one of
|
|
2125
|
+
* them by a rule nobody chose. Measured over the whole 3,834-file zig corpus
|
|
2126
|
+
* (draw `sha256:2f3545f9…`, production `parseFile`, 392,550 edges):
|
|
2127
|
+
* **265,887 edges (67.73%) name a target 2+ published symbols share**, and of
|
|
2128
|
+
* those **151,004 (56.79%) have a candidate in the edge's OWN file** — a tie a
|
|
2129
|
+
* per-file rule breaks soundly, because within one file the analyzer knows which
|
|
2130
|
+
* declaration it published.
|
|
2131
|
+
*
|
|
2132
|
+
* WHAT IT ADDS. `metadata.targetScope`, plus `targetKind` when the file
|
|
2133
|
+
* publishes exactly one kind under that name:
|
|
2134
|
+
* `file-local` the file publishes a symbol with this exact name. A
|
|
2135
|
+
* consumer resolves the edge to THAT symbol and needs no
|
|
2136
|
+
* cross-file guess.
|
|
2137
|
+
* `file-external` the file publishes none. The edge names something outside
|
|
2138
|
+
* it — a `std.*` declaration, a builtin, an import path — and
|
|
2139
|
+
* no per-file rule can say which. Stated, not guessed.
|
|
2140
|
+
* `scope-marker` the endpoint is one of the literal scope markers
|
|
2141
|
+
* (`(global)`, `(file)`, `(declared-type)`), which is a fact
|
|
2142
|
+
* about the edge's provenance and not a symbol name.
|
|
2143
|
+
*
|
|
2144
|
+
* It adds NO edge, removes NO edge, and changes NO count — it is a label on
|
|
2145
|
+
* edges that already exist, derived from this analyzer's own output rather than
|
|
2146
|
+
* from a guess about the rest of the repository. A consumer that ignores it
|
|
2147
|
+
* behaves exactly as before, which is why it cannot regress a budgeted lane.
|
|
2148
|
+
*
|
|
2149
|
+
* THE LIMIT, stated because it is the part that cannot be fixed here. This says
|
|
2150
|
+
* whether the target is IN this file, not whether it is the declaration the
|
|
2151
|
+
* author meant: `const Self = @This();` is a file-local name, and 240 edges
|
|
2152
|
+
* resolve against it, while `keepSymbol`'s UNIVERSAL denylist
|
|
2153
|
+
* (`src/ast/pool-worker.ts:449`, shared, §8) refuses to publish ANY symbol
|
|
2154
|
+
* named `Self` — so those edges are labelled `file-external` even though the
|
|
2155
|
+
* name is right there in the file. The denylist is the defect, and it is not
|
|
2156
|
+
* this lane's file.
|
|
2157
|
+
*/
|
|
2158
|
+
annotateTargetScope(symbols, relationships) {
|
|
2159
|
+
const kindsByName = /* @__PURE__ */ new Map();
|
|
2160
|
+
for (const s of symbols) {
|
|
2161
|
+
const n = String(s.name ?? "");
|
|
2162
|
+
if (!n) continue;
|
|
2163
|
+
const addKey = (k) => {
|
|
2164
|
+
const set = kindsByName.get(k) ?? /* @__PURE__ */ new Set();
|
|
2165
|
+
set.add(String(s.kind ?? ""));
|
|
2166
|
+
kindsByName.set(k, set);
|
|
2167
|
+
};
|
|
2168
|
+
addKey(n);
|
|
2169
|
+
const qualified = publishedSpelling(s);
|
|
2170
|
+
if (qualified !== n) addKey(qualified);
|
|
2171
|
+
}
|
|
2172
|
+
const MARKERS = /* @__PURE__ */ new Set(["(global)", "(file)", "(declared-type)", "(import)", "(local)"]);
|
|
2173
|
+
for (const r of relationships) {
|
|
2174
|
+
const md = r.metadata ?? (r.metadata = {});
|
|
2175
|
+
if (md.targetScope) continue;
|
|
2176
|
+
const t = String(r.target ?? "");
|
|
2177
|
+
if (MARKERS.has(t)) {
|
|
2178
|
+
md.targetScope = "scope-marker";
|
|
2179
|
+
continue;
|
|
2180
|
+
}
|
|
2181
|
+
const kinds = kindsByName.get(t);
|
|
2182
|
+
if (!kinds) {
|
|
2183
|
+
md.targetScope = "file-external";
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
md.targetScope = "file-local";
|
|
2187
|
+
if (kinds.size === 1) md.targetKind = [...kinds][0];
|
|
2188
|
+
else md.targetKindCount = kinds.size;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
/** The `const X = @This();` binding of a file, or null when the file does not
|
|
2192
|
+
* name its own container. Read off node types only: a direct
|
|
2193
|
+
* `variable_declaration` child of the root whose value is a `builtin_function`
|
|
2194
|
+
* carrying the `builtin_identifier` `@This`.
|
|
2195
|
+
*
|
|
2196
|
+
* The NAME is `namedChild(0)` when that is an `identifier`, NOT
|
|
2197
|
+
* `childForFieldName("name")`: this grammar declares no `name` field on
|
|
2198
|
+
* `variable_declaration` — the walk reads a variable declaration's name the
|
|
2199
|
+
* same way, and a probe that used the field lookup reported 0 bindings on a
|
|
2200
|
+
* source that carries one. `container_field` DOES declare `name`, which is
|
|
2201
|
+
* why `zigFileLevelField` can use the field.
|
|
2202
|
+
*
|
|
2203
|
+
* Null is a real answer and not a fallback: 10 of the 411 corpus files that
|
|
2204
|
+
* declare file-level fields never bind `@This()`, and for those the owner of
|
|
2205
|
+
* those fields is not nameable from the file. */
|
|
2206
|
+
zigThisBindingName(root) {
|
|
2207
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
2208
|
+
const decl = root.namedChild(i);
|
|
2209
|
+
if (!decl || decl.type !== "variable_declaration") continue;
|
|
2210
|
+
for (let j = 0; j < decl.namedChildCount; j++) {
|
|
2211
|
+
const value = decl.namedChild(j);
|
|
2212
|
+
if (!value || value.type !== "builtin_function") continue;
|
|
2213
|
+
const isThis = this.anyChild(value, (n) => n.type === "builtin_identifier" && n.text === "@This");
|
|
2214
|
+
if (!isThis) continue;
|
|
2215
|
+
const name = decl.namedChildCount > 0 ? decl.namedChild(0) : null;
|
|
2216
|
+
if (name && name.type === "identifier") return name.text;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
/** Publish one `container_field` whose direct parent is the file's root — a
|
|
2222
|
+
* member of the struct the FILE is, per `zigThisBindingName`.
|
|
2223
|
+
*
|
|
2224
|
+
* The kind is decided by the field's own `type` FIELD, which is this
|
|
2225
|
+
* analyzer's discriminator between a struct field and an enum member
|
|
2226
|
+
* (`:2180`): the grammar uses one node type for `a: u8` and for `north`, and
|
|
2227
|
+
* only the former carries a type. A named container gets that answer from
|
|
2228
|
+
* its kind instead; a file-level container has no published kind, because
|
|
2229
|
+
* `const X = @This()` publishes `X` as a `Type` whether the body is a struct
|
|
2230
|
+
* or an enum, so the field's own field is the only evidence there is.
|
|
2231
|
+
*
|
|
2232
|
+
* The owner is set ONLY when the file names it. The 10 corpus files that
|
|
2233
|
+
* declare file-level fields without a `@This()` binding publish the field
|
|
2234
|
+
* with no owner: the declaration is real and its reads then resolve, while
|
|
2235
|
+
* no `contains` edge is emitted for it, because that edge would need a
|
|
2236
|
+
* source symbol and asserting one would be inventing the container's name.
|
|
2237
|
+
*
|
|
2238
|
+
* The name guards are the SAME three the named-container branch applies, and
|
|
2239
|
+
* they are load-bearing here rather than defensive: the corpus's three
|
|
2240
|
+
* untyped file-level fields are all in `zig/test/cases/compile_errors/`
|
|
2241
|
+
* (`file_level_tuple.zig` — a file-level tuple, which Zig rejects), and their
|
|
2242
|
+
* text holds a TYPE where a name belongs (`u32`, `comptime u8 = 123`). A
|
|
2243
|
+
* symbol named `u32` is a fabrication, so they are refused exactly as an
|
|
2244
|
+
* unnamed `extern struct` member is. */
|
|
2245
|
+
zigFileLevelField(node, ownerName, symbols) {
|
|
2246
|
+
const nameField = this.namedFieldOf(node, "name");
|
|
2247
|
+
const nameNode = nameField && nameField.type === "identifier" ? nameField : this.anyChild(node, (n) => n.type === "identifier");
|
|
2248
|
+
const fieldName = nameNode ? nameNode.text : "";
|
|
2249
|
+
if (!fieldName || fieldName === "_" || fieldName.length >= 200) return;
|
|
2250
|
+
const typeField = this.typeFieldOf(node);
|
|
2251
|
+
symbols.push({
|
|
2252
|
+
name: fieldName,
|
|
2253
|
+
kind: typeField ? ASTEngine.SymbolKind.Property : ASTEngine.SymbolKind.EnumMember,
|
|
2254
|
+
visibility: "public",
|
|
2255
|
+
range: this.nodeRange(node),
|
|
2256
|
+
scope: ownerName ?? void 0,
|
|
2257
|
+
ownerName: ownerName ?? void 0,
|
|
2258
|
+
ownerKind: ownerName ? ASTEngine.SymbolKind.Type : void 0,
|
|
2259
|
+
returnType: typeField ? this.nodeText(typeField) : void 0,
|
|
2260
|
+
metadata: {
|
|
2261
|
+
zigField: true,
|
|
2262
|
+
zigFileLevelField: true,
|
|
2263
|
+
...typeField ? {} : { zigEnumMember: true }
|
|
2264
|
+
}
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
pushChildren(node, walkStack, container, inFunction, anonContainer) {
|
|
2268
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
2269
|
+
const child = node.namedChild(i);
|
|
2270
|
+
if (child) walkStack.push({ node: child, container, inFunction, anonContainer });
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
// ─── PURE HELPER FUNCTIONS ───
|
|
2274
|
+
nodeRange(node) {
|
|
2275
|
+
return {
|
|
2276
|
+
startLine: node.startPosition.row + 1,
|
|
2277
|
+
startColumn: node.startPosition.column + 1,
|
|
2278
|
+
endLine: node.endPosition.row + 1,
|
|
2279
|
+
endColumn: node.endPosition.column + 1,
|
|
2280
|
+
startIndex: node.startIndex,
|
|
2281
|
+
endIndex: node.endIndex
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
nodeText(node) {
|
|
2285
|
+
if (!node) return "";
|
|
2286
|
+
return node.text;
|
|
2287
|
+
}
|
|
2288
|
+
anyChild(node, predicate) {
|
|
2289
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2290
|
+
const child = node.namedChild(i);
|
|
2291
|
+
if (child && predicate(child)) return child;
|
|
2292
|
+
}
|
|
2293
|
+
return null;
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Z24 (pass 24): the DECLARATION NAME `@field` reads, when it is spelled as a string.
|
|
2297
|
+
*
|
|
2298
|
+
* ## The construct
|
|
2299
|
+
*
|
|
2300
|
+
* Zig can read a container's member by name-as-text rather than by identifier:
|
|
2301
|
+
*
|
|
2302
|
+
* @field(p, "x") // the field `x` of whatever `p` holds
|
|
2303
|
+
* @field(Point, "z") // the field `z` of the type `Point`
|
|
2304
|
+
*
|
|
2305
|
+
* Measured over the whole zig corpus (`validation/m14/fixtures/zig/reflection-builtin-probe.mts`,
|
|
2306
|
+
* 8 shards, 3,848/3,848 files, 0 parse traps): **779 `@field` and 210 `@hasDecl` call
|
|
2307
|
+
* sites over 242 files**, of which **206 name a declaration as a string**. Before this
|
|
2308
|
+
* pass the analyzer read none of them — `reflection-firing.mts` drove the production
|
|
2309
|
+
* `parseFile` over all 70 files carrying such a site and found **0** edges attributed to
|
|
2310
|
+
* a reflection reason code. The 52 sites whose line carried an edge naming the same
|
|
2311
|
+
* declaration decompose, by the analyzer's own reason codes, into `zig_field_access` 39,
|
|
2312
|
+
* `zig_function_call` 10, `zig_export` 3 and `identifier_usage` 1 — all different
|
|
2313
|
+
* constructs that share a line, since a relationship carries `line_number` and no range.
|
|
2314
|
+
*
|
|
2315
|
+
* Measured AFTER this pass, same instrument, same 70 files: **18 of the 22 named `@field`
|
|
2316
|
+
* sites are represented by a `zig_field_reflection` edge**, and 7 of those 18 have the edge
|
|
2317
|
+
* on their OWN line. The gap between 18 and 7 is Z52's post-pass, which collapses one `uses`
|
|
2318
|
+
* edge per (source, target) keeping the higher confidence: `zig/test/behavior/member_func.zig:77,78`
|
|
2319
|
+
* name `one` and `two`, the reflection edge for each is emitted, and the dot-access edge the
|
|
2320
|
+
* same file already published at lines 39 and 62 wins the tie at the same 0.75. The
|
|
2321
|
+
* site-level number alone would have read 7 and filed a working derivation as 11 misses.
|
|
2322
|
+
*
|
|
2323
|
+
* ## Why `@field` and NOT `@hasDecl`
|
|
2324
|
+
*
|
|
2325
|
+
* `@field(S, "a")` READS `S.a`: the corpus compiles, so `S` has that member and the code
|
|
2326
|
+
* consumes its value. That is exactly the claim the `zig_field_access` pass already makes
|
|
2327
|
+
* for `S.a` written with dots, so it is emitted as `uses` at the same confidence, with a
|
|
2328
|
+
* distinct reason code so the two derivations stay auditable apart.
|
|
2329
|
+
*
|
|
2330
|
+
* `@hasDecl(S, "a")` is an EXISTENCE TEST. Its whole purpose is to ask whether `a` is
|
|
2331
|
+
* there, and the surrounding code handles both answers (`if (@hasDecl(T, "opt")) … else
|
|
2332
|
+
* …`). A hard `uses`/`references` edge would assert a dependency the source deliberately
|
|
2333
|
+
* does not take, which is the fabrication this workstream exists to prevent, so it is NOT
|
|
2334
|
+
* emitted. Its measured population is published instead: 184 named sites, and 128 of the
|
|
2335
|
+
* 184 name a declaration this corpus does not publish at all (a foreign container, a type
|
|
2336
|
+
* parameter, a translate-c struct), so even a permissive reading would have produced 128
|
|
2337
|
+
* edges the graph cannot walk to.
|
|
2338
|
+
*
|
|
2339
|
+
* ## Why the name must be identifier-shaped
|
|
2340
|
+
*
|
|
2341
|
+
* `@field(anyerror, "Expected closing ]")` is a legal Zig read of an error-set member, and
|
|
2342
|
+
* Z18 already recorded what publishing such a text as a bare target does: it produced
|
|
2343
|
+
* targets like `@"ziglib"` for names that were legitimate. The same guard applies — a
|
|
2344
|
+
* `string_content` carrying a character outside `[A-Za-z0-9_]` is an error name, not a
|
|
2345
|
+
* member this file declares, and publishing it would add an edge whose target is junk.
|
|
2346
|
+
* Measured: **2 of the 22 named `@field` sites** carry a non-identifier-shaped name
|
|
2347
|
+
* (`zig/lib/std/fmt.zig:1550,1557`, both `"Expected closing ]"`) and **both are declined**.
|
|
2348
|
+
*
|
|
2349
|
+
* ## WHAT THE SHAPE GUARD DOES NOT DO, because a comment that claimed otherwise was wrong
|
|
2350
|
+
*
|
|
2351
|
+
* An earlier revision of this header read "3 of the 22 resolve to nothing and all 3 are
|
|
2352
|
+
* declined, which is what the arity measurement independently found". The two numbers are
|
|
2353
|
+
* 2 and 3 and they are not the same set. The third zero-resolving site is `NotFound` at
|
|
2354
|
+
* `zig/test/behavior/error.zig:897`, `@field(anyerror, "NotFound")`, and its name IS
|
|
2355
|
+
* identifier-shaped, so it is EMITTED — with `targetScope: file-external`, because nothing in
|
|
2356
|
+
* that 3,000-line file publishes `NotFound`. So the guard is not an error-set detector: it
|
|
2357
|
+
* declines error names containing punctuation and lets identifier-shaped error names
|
|
2358
|
+
* through. That is the intended behaviour and it is stated rather than papered over:
|
|
2359
|
+
*
|
|
2360
|
+
* - `error.NotFound` IS a legitimate declaration name in Zig, so the edge states a TRUE
|
|
2361
|
+
* dependency — on a name this particular file's corpus does not publish.
|
|
2362
|
+
* - `anyerror` is a `builtin_type` node, not an `identifier`, so its member namespace is
|
|
2363
|
+
* the inferred error set of the whole PROGRAM: unbounded, and bindable by no per-file
|
|
2364
|
+
* rule.
|
|
2365
|
+
* - Suppressing it instead would be inconsistent with the sibling pass, which publishes
|
|
2366
|
+
* `@typeInfo(u8).Int` → `Int` under the same `file-external` label, and `Int` equally
|
|
2367
|
+
* resolves to nothing in its own file. Dropping one unresolvable read and keeping the
|
|
2368
|
+
* other would be a fit to a single test line, not a rule.
|
|
2369
|
+
*
|
|
2370
|
+
* So the honest count is: of the 7 `zig_field_reflection` edges the corpus produces, **6
|
|
2371
|
+
* carry a target published in their own file and exactly 1 does not**
|
|
2372
|
+
* (`zig/test/behavior/error.zig:897`), and it says so on the edge.
|
|
2373
|
+
*
|
|
2374
|
+
* No regex: the shape test is a Set of node types and the character test is a
|
|
2375
|
+
* character-code range, both the file's existing zero-regex discipline.
|
|
2376
|
+
*/
|
|
2377
|
+
zigReflectionFieldName(node) {
|
|
2378
|
+
if (node.type !== "builtin_function") return null;
|
|
2379
|
+
const bid = this.anyChild(node, (c) => c.type === "builtin_identifier");
|
|
2380
|
+
if (!bid || bid.text !== "@field") return null;
|
|
2381
|
+
const args = this.anyChild(node, (c) => c.type === "arguments");
|
|
2382
|
+
if (!args) return null;
|
|
2383
|
+
let stringArg = null;
|
|
2384
|
+
let seen = 0;
|
|
2385
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
2386
|
+
const a = args.namedChild(i);
|
|
2387
|
+
if (!a) continue;
|
|
2388
|
+
seen++;
|
|
2389
|
+
if (seen === 2 && a.type === "string") stringArg = a;
|
|
2390
|
+
}
|
|
2391
|
+
if (!stringArg) return null;
|
|
2392
|
+
const content = this.anyChild(stringArg, (c) => c.type === "string_content");
|
|
2393
|
+
if (!content) return null;
|
|
2394
|
+
const text = content.text;
|
|
2395
|
+
if (!text) return null;
|
|
2396
|
+
const isStart = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
|
|
2397
|
+
const isRest = (c) => isStart(c) || c >= 48 && c <= 57;
|
|
2398
|
+
if (!isStart(text.charCodeAt(0))) return null;
|
|
2399
|
+
for (let i = 1; i < text.length; i++) {
|
|
2400
|
+
if (!isRest(text.charCodeAt(i))) return null;
|
|
2401
|
+
}
|
|
2402
|
+
return content;
|
|
2403
|
+
}
|
|
2404
|
+
// The `pub`/`export`/`inline` modifiers are anonymous token children
|
|
2405
|
+
// (`variable_declaration [pub:"pub"] [const:"const"]`) — pure AST, never
|
|
2406
|
+
// source-text scanning.
|
|
2407
|
+
hasToken(node, token) {
|
|
2408
|
+
const count = node.childCount;
|
|
2409
|
+
for (let i = 0; i < count; i++) {
|
|
2410
|
+
const c = node.child(i);
|
|
2411
|
+
if (c && !c.isNamed && c.type === token) return true;
|
|
2412
|
+
}
|
|
2413
|
+
return false;
|
|
2414
|
+
}
|
|
2415
|
+
getAllNamedChildren(node) {
|
|
2416
|
+
const children = [];
|
|
2417
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2418
|
+
const child = node.namedChild(i);
|
|
2419
|
+
if (child) children.push(child);
|
|
2420
|
+
}
|
|
2421
|
+
return children;
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
export {
|
|
2425
|
+
RealZigAnalyzer,
|
|
2426
|
+
ZIG_DECLARED_TYPE_BUILTINS,
|
|
2427
|
+
stripZigQuoting,
|
|
2428
|
+
zigFileScopeNames,
|
|
2429
|
+
zigImportAliases
|
|
2430
|
+
};
|