@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,2311 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findDeclaringParent
|
|
3
|
+
} from "./chunk-OWISBAOF.js";
|
|
4
|
+
import {
|
|
5
|
+
ASTEngine
|
|
6
|
+
} from "./chunk-3ZTOHHSW.js";
|
|
7
|
+
import "./chunk-BQ3OVQ3P.js";
|
|
8
|
+
import "./chunk-SK6YVHFZ.js";
|
|
9
|
+
import "./chunk-2BSKD7O3.js";
|
|
10
|
+
import "./chunk-KAET47ZL.js";
|
|
11
|
+
import "./chunk-KCDECRJY.js";
|
|
12
|
+
import "./chunk-MLKGABMK.js";
|
|
13
|
+
|
|
14
|
+
// src/ast/real-swift-analyzer.ts
|
|
15
|
+
var ENUM_RAW_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
16
|
+
"String",
|
|
17
|
+
"Character",
|
|
18
|
+
"Int",
|
|
19
|
+
"Int8",
|
|
20
|
+
"Int16",
|
|
21
|
+
"Int32",
|
|
22
|
+
"Int64",
|
|
23
|
+
"UInt",
|
|
24
|
+
"UInt8",
|
|
25
|
+
"UInt16",
|
|
26
|
+
"UInt32",
|
|
27
|
+
"UInt64",
|
|
28
|
+
"Float",
|
|
29
|
+
"Float32",
|
|
30
|
+
"Float64",
|
|
31
|
+
"Double"
|
|
32
|
+
]);
|
|
33
|
+
var MEMBER_OWNER_KINDS = /* @__PURE__ */ new Set([
|
|
34
|
+
ASTEngine.SymbolKind.Class,
|
|
35
|
+
ASTEngine.SymbolKind.Struct,
|
|
36
|
+
ASTEngine.SymbolKind.Interface,
|
|
37
|
+
ASTEngine.SymbolKind.Enum,
|
|
38
|
+
ASTEngine.SymbolKind.Actor,
|
|
39
|
+
ASTEngine.SymbolKind.Extension
|
|
40
|
+
]);
|
|
41
|
+
var LOCAL_SCOPE_BODIES = /* @__PURE__ */ new Set([
|
|
42
|
+
"function_body",
|
|
43
|
+
"computed_property",
|
|
44
|
+
"computed_getter",
|
|
45
|
+
"computed_setter",
|
|
46
|
+
"willset_didset_block",
|
|
47
|
+
"willset_clause",
|
|
48
|
+
"didset_clause",
|
|
49
|
+
"lambda_literal"
|
|
50
|
+
]);
|
|
51
|
+
var SCOPE_RESET_DECLARATIONS = /* @__PURE__ */ new Set([
|
|
52
|
+
"class_declaration",
|
|
53
|
+
"protocol_declaration",
|
|
54
|
+
"function_declaration",
|
|
55
|
+
"protocol_function_declaration",
|
|
56
|
+
"init_declaration"
|
|
57
|
+
]);
|
|
58
|
+
var BUILTIN_BARE_CALLS = /* @__PURE__ */ new Set([
|
|
59
|
+
"print",
|
|
60
|
+
"debugPrint",
|
|
61
|
+
"assert",
|
|
62
|
+
"precondition",
|
|
63
|
+
"fatalError",
|
|
64
|
+
"String",
|
|
65
|
+
"Int",
|
|
66
|
+
"Double",
|
|
67
|
+
"Float",
|
|
68
|
+
"Bool",
|
|
69
|
+
"Array",
|
|
70
|
+
"Dictionary",
|
|
71
|
+
"Set",
|
|
72
|
+
"Optional",
|
|
73
|
+
"Any",
|
|
74
|
+
"Void",
|
|
75
|
+
"Character",
|
|
76
|
+
"Data",
|
|
77
|
+
"Date",
|
|
78
|
+
// Fixed-width stdlib numerics (SWIFTD16): trivial value construction, same
|
|
79
|
+
// class as Int/Double — filtered for consistency (Int8-64/UInt8-64 were
|
|
80
|
+
// previously missed while Int was filtered; corpus: UInt8 244 / Int64 72 /
|
|
81
|
+
// UInt16 47 bare constructions per swift-nio 200-file census).
|
|
82
|
+
"UInt",
|
|
83
|
+
"UInt8",
|
|
84
|
+
"UInt16",
|
|
85
|
+
"UInt32",
|
|
86
|
+
"UInt64",
|
|
87
|
+
"Int8",
|
|
88
|
+
"Int16",
|
|
89
|
+
"Int32",
|
|
90
|
+
"Int64",
|
|
91
|
+
// `defer { }` parses as call_expression(simple_identifier "defer", lambda_literal)
|
|
92
|
+
// in the pinned grammar — it is a control keyword, never a real function call.
|
|
93
|
+
"defer",
|
|
94
|
+
// SW-E (ultimate review): `type(of:)` is a stdlib free function like
|
|
95
|
+
// print/assert — a bare `type` call is the metatype query, never user
|
|
96
|
+
// code. Member reads (`x.type` is invalid Swift) are unaffected.
|
|
97
|
+
"type"
|
|
98
|
+
]);
|
|
99
|
+
var SWIFT_FRAMEWORK_ORDER = ["SwiftUI", "Combine", "UIKit", "Foundation"];
|
|
100
|
+
var SWIFT_FRAMEWORK_PATTERNS = {
|
|
101
|
+
SwiftUI: ["declarative_ui"],
|
|
102
|
+
Combine: ["reactive_streams"],
|
|
103
|
+
UIKit: ["ui_framework"],
|
|
104
|
+
Foundation: ["base_library"]
|
|
105
|
+
};
|
|
106
|
+
var RealSwiftAnalyzer = class {
|
|
107
|
+
analyze(rootNode, content, filePath, context) {
|
|
108
|
+
const symbols = [];
|
|
109
|
+
const imports = [];
|
|
110
|
+
const relationships = [];
|
|
111
|
+
const exports = [];
|
|
112
|
+
const patternsObj = {};
|
|
113
|
+
const frameworksObj = {};
|
|
114
|
+
const metadata = {
|
|
115
|
+
linesOfCode: 0,
|
|
116
|
+
performanceScore: 100,
|
|
117
|
+
architecturalInsights: [],
|
|
118
|
+
detectedFrameworks: [],
|
|
119
|
+
patterns: []
|
|
120
|
+
};
|
|
121
|
+
if (!rootNode) return { symbols, imports, relationships, exports, metadata, errors: [] };
|
|
122
|
+
if (rootNode.hasError) {
|
|
123
|
+
const errStack = [rootNode];
|
|
124
|
+
while (errStack.length > 0) {
|
|
125
|
+
const n = errStack.pop();
|
|
126
|
+
if (n.type === "ERROR" || n.isMissing) {
|
|
127
|
+
;
|
|
128
|
+
metadata.errors = metadata.errors || [];
|
|
129
|
+
metadata.errors.push({
|
|
130
|
+
message: `Syntax ${n.type} detected. The Brain Engine is bypassing this broken node.`,
|
|
131
|
+
range: {
|
|
132
|
+
startLine: n.startPosition.row + 1,
|
|
133
|
+
startColumn: n.startPosition.column + 1,
|
|
134
|
+
endLine: n.endPosition.row + 1,
|
|
135
|
+
endColumn: n.endPosition.column + 1,
|
|
136
|
+
startIndex: n.startIndex,
|
|
137
|
+
endIndex: n.endIndex
|
|
138
|
+
},
|
|
139
|
+
severity: "warning"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
143
|
+
const child = n.namedChild(i);
|
|
144
|
+
if (child) errStack.push(child);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
let loc = 1;
|
|
149
|
+
for (let i = 0; i < content.length; i++) {
|
|
150
|
+
if (content[i] === "\n") loc++;
|
|
151
|
+
}
|
|
152
|
+
metadata.linesOfCode = loc;
|
|
153
|
+
const overrideTasks = [];
|
|
154
|
+
const classHierarchy = /* @__PURE__ */ new Map();
|
|
155
|
+
const symbolMap = /* @__PURE__ */ new Map();
|
|
156
|
+
const inFileKinds = /* @__PURE__ */ new Map();
|
|
157
|
+
this.collectDeclaredKinds(rootNode, inFileKinds);
|
|
158
|
+
const cross = this.crossFileTables(context, classHierarchy);
|
|
159
|
+
const emittedCalls = /* @__PURE__ */ new Set();
|
|
160
|
+
const callSiteCounts = /* @__PURE__ */ new Map();
|
|
161
|
+
const callSiteLines = /* @__PURE__ */ new Map();
|
|
162
|
+
const emittedUses = /* @__PURE__ */ new Set();
|
|
163
|
+
const emittedContains = /* @__PURE__ */ new Set();
|
|
164
|
+
const enumCtorCandidates = [];
|
|
165
|
+
const superByClass = /* @__PURE__ */ new Map();
|
|
166
|
+
const reExports = [];
|
|
167
|
+
const moduleSource = this.moduleLeafName(filePath);
|
|
168
|
+
const emittedExportNames = /* @__PURE__ */ new Set();
|
|
169
|
+
const stack = [
|
|
170
|
+
{ node: rootNode, parentSymbol: null, typeName: null, localScope: false }
|
|
171
|
+
];
|
|
172
|
+
while (stack.length > 0) {
|
|
173
|
+
const popped = stack.pop();
|
|
174
|
+
if (!popped) continue;
|
|
175
|
+
const { node: curr, parentSymbol, localScope } = popped;
|
|
176
|
+
let newParentSymbol = parentSymbol;
|
|
177
|
+
let newTypeName = popped.typeName;
|
|
178
|
+
const nominalRecovery = curr.type === "ERROR" && this.isNominalRecovery(curr);
|
|
179
|
+
const childLocalScope = SCOPE_RESET_DECLARATIONS.has(curr.type) || nominalRecovery ? false : LOCAL_SCOPE_BODIES.has(curr.type) ? true : localScope;
|
|
180
|
+
if (curr.type === "import_declaration") {
|
|
181
|
+
this.handleImport(curr, imports, frameworksObj, reExports);
|
|
182
|
+
} else if (curr.type === "protocol_declaration") {
|
|
183
|
+
newParentSymbol = this.handleProtocol(curr, symbols, relationships, parentSymbol, patternsObj, classHierarchy, localScope);
|
|
184
|
+
if (newParentSymbol) newTypeName = newParentSymbol.name;
|
|
185
|
+
} else if (curr.type === "class_declaration") {
|
|
186
|
+
newParentSymbol = this.handleDeclaration(curr, symbols, relationships, parentSymbol, patternsObj, classHierarchy, inFileKinds, cross.kinds, superByClass, localScope);
|
|
187
|
+
if (newParentSymbol) newTypeName = newParentSymbol.name;
|
|
188
|
+
} else if (nominalRecovery) {
|
|
189
|
+
newParentSymbol = this.handleDeclaration(curr, symbols, relationships, parentSymbol, patternsObj, classHierarchy, inFileKinds, cross.kinds, superByClass, localScope);
|
|
190
|
+
if (newParentSymbol) newTypeName = newParentSymbol.name;
|
|
191
|
+
} else if (curr.type === "function_declaration" || curr.type === "protocol_function_declaration") {
|
|
192
|
+
newParentSymbol = this.handleFunction(curr, symbols, relationships, parentSymbol, overrideTasks, context?.overrideCompanion, localScope);
|
|
193
|
+
} else if (curr.type === "init_declaration") {
|
|
194
|
+
const vis = this.getVisibility(curr);
|
|
195
|
+
const meta = {};
|
|
196
|
+
const funcAttrs = this.extractAttributes(curr);
|
|
197
|
+
if (funcAttrs.length > 0) meta.annotations = funcAttrs;
|
|
198
|
+
const sym = this.createSymbol("constructor", ASTEngine.SymbolKind.Constructor, curr, parentSymbol, symbols, meta, vis, this.getDeclaredVisibility(curr), localScope);
|
|
199
|
+
this.collectFunctionMeta(curr, meta, sym);
|
|
200
|
+
newParentSymbol = sym;
|
|
201
|
+
} else if (curr.type === "deinit_declaration" || curr.type === "subscript_declaration") {
|
|
202
|
+
} else if (curr.type === "property_declaration" || curr.type === "protocol_property_declaration") {
|
|
203
|
+
this.handleVariable(curr, symbols, parentSymbol, newTypeName, frameworksObj, patternsObj, localScope);
|
|
204
|
+
} else if (curr.type === "enum_entry") {
|
|
205
|
+
if (!localScope && parentSymbol && parentSymbol.kind === ASTEngine.SymbolKind.Enum) {
|
|
206
|
+
let caseNode = null;
|
|
207
|
+
for (let i = 0; i < curr.namedChildCount; i++) {
|
|
208
|
+
const c = curr.namedChild(i);
|
|
209
|
+
if (!c || c.type === "modifiers" || c.type === "attribute") continue;
|
|
210
|
+
caseNode = c;
|
|
211
|
+
if (c.type === "simple_identifier" || c.type === "identifier") break;
|
|
212
|
+
}
|
|
213
|
+
const caseName = caseNode ? caseNode.text : curr.text;
|
|
214
|
+
if (caseName && caseName !== "case") {
|
|
215
|
+
this.createSymbol(caseName, ASTEngine.SymbolKind.EnumMember, caseNode ?? curr, parentSymbol, symbols, { enumCase: true });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else if (curr.type === "typealias_declaration") {
|
|
219
|
+
const nameNode = this.findChildByType(curr, "type_identifier") || this.firstNamedChild(curr);
|
|
220
|
+
if (nameNode && (nameNode.type === "type_identifier" || nameNode.type === "simple_identifier" || nameNode.type === "identifier")) {
|
|
221
|
+
this.createSymbol(nameNode.text, ASTEngine.SymbolKind.Type, curr, parentSymbol, symbols, { isTypeAlias: true }, this.getVisibility(curr), this.getDeclaredVisibility(curr), localScope);
|
|
222
|
+
}
|
|
223
|
+
} else if (curr.type === "associatedtype_declaration") {
|
|
224
|
+
const nameNode = this.findChildByType(curr, "type_identifier") || this.firstNamedChild(curr);
|
|
225
|
+
if (nameNode && (nameNode.type === "type_identifier" || nameNode.type === "simple_identifier" || nameNode.type === "identifier")) {
|
|
226
|
+
this.createSymbol(nameNode.text, ASTEngine.SymbolKind.Type, curr, parentSymbol, symbols, { isAssociatedType: true }, this.getVisibility(curr), this.getDeclaredVisibility(curr), localScope);
|
|
227
|
+
}
|
|
228
|
+
} else if (curr.type === "call_expression") {
|
|
229
|
+
this.handleCall(curr, relationships, parentSymbol, emittedCalls, enumCtorCandidates, superByClass, callSiteCounts, callSiteLines);
|
|
230
|
+
} else if (curr.type === "navigation_expression") {
|
|
231
|
+
this.handleSelfUse(curr, relationships, newTypeName, parentSymbol, emittedUses);
|
|
232
|
+
this.handleMemberUses(curr, relationships, parentSymbol, newTypeName, emittedUses);
|
|
233
|
+
} else if (curr.type === "key_path_expression") {
|
|
234
|
+
this.handleKeyPathUses(curr, relationships, parentSymbol, newTypeName, emittedUses);
|
|
235
|
+
}
|
|
236
|
+
if (parentSymbol === null && !localScope && this.isExportingDeclaration(curr)) {
|
|
237
|
+
this.emitDeclarationExport(curr, moduleSource, relationships, exports, emittedExportNames);
|
|
238
|
+
}
|
|
239
|
+
if (curr.type === "type_constraints") {
|
|
240
|
+
patternsObj["Generics with Where Clauses"] = true;
|
|
241
|
+
} else if (curr.type === "willset_clause" || curr.type === "didset_clause" || curr.type === "willset_didset_block") {
|
|
242
|
+
patternsObj["Property Observers (willSet/didSet)"] = true;
|
|
243
|
+
} else if (curr.type === "call_expression" && this.isDeferCall(curr)) {
|
|
244
|
+
patternsObj["Defer (Cleanup)"] = true;
|
|
245
|
+
} else if (curr.type === "do_statement") {
|
|
246
|
+
patternsObj["Do-Catch Blocks"] = true;
|
|
247
|
+
} else if (curr.type === "getter_specifier" || curr.type === "setter_specifier") {
|
|
248
|
+
patternsObj["Computed Properties"] = true;
|
|
249
|
+
} else if (curr.type === "availability_condition") {
|
|
250
|
+
patternsObj["Platform Availability Checks"] = true;
|
|
251
|
+
} else if (curr.type === "macro_declaration") {
|
|
252
|
+
patternsObj["Swift Macros"] = true;
|
|
253
|
+
} else if (curr.type === "lambda_literal") {
|
|
254
|
+
patternsObj["Closures"] = true;
|
|
255
|
+
}
|
|
256
|
+
if (curr.type === "try_operator" || curr.type === "catch_block" || curr.type === "control_transfer_statement" && this.hasChildType(curr, "throw_keyword")) {
|
|
257
|
+
patternsObj["Error Handling"] = true;
|
|
258
|
+
}
|
|
259
|
+
if (curr.type === "await_expression" || curr.type === "async") {
|
|
260
|
+
patternsObj["Async/Await"] = true;
|
|
261
|
+
}
|
|
262
|
+
if (curr.type === "optional_type" || curr.type === "guard_statement" || curr.type === "navigation_expression" && this.hasAnonToken(curr, "?") || (curr.type === "if_statement" || curr.type === "while_statement") && this.hasChildType(curr, "value_binding_pattern")) {
|
|
263
|
+
patternsObj["Optional Handling"] = true;
|
|
264
|
+
}
|
|
265
|
+
if (curr.type === "type_parameters") {
|
|
266
|
+
patternsObj["Generics"] = true;
|
|
267
|
+
}
|
|
268
|
+
if (curr.type === "if_statement" || curr.type === "for_statement" || curr.type === "while_statement" || curr.type === "repeat_while_statement" || curr.type === "switch_statement") {
|
|
269
|
+
if (!metadata.cyclomaticComplexity) metadata.cyclomaticComplexity = 1;
|
|
270
|
+
metadata.cyclomaticComplexity++;
|
|
271
|
+
}
|
|
272
|
+
if (curr.type === "modifiers") {
|
|
273
|
+
for (let i = 0; i < curr.namedChildCount; i++) {
|
|
274
|
+
const mod = curr.namedChild(i);
|
|
275
|
+
if (!mod) continue;
|
|
276
|
+
if (mod.type === "ownership_modifier")
|
|
277
|
+
patternsObj["ARC Memory Management"] = true;
|
|
278
|
+
if (mod.type === "property_behavior_modifier" || mod.text === "lazy")
|
|
279
|
+
patternsObj["Lazy Initialization"] = true;
|
|
280
|
+
if (mod.type === "inheritance_modifier" || mod.text === "final")
|
|
281
|
+
patternsObj["Final/Sealed"] = true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (let i = curr.childCount - 1; i >= 0; i--) {
|
|
285
|
+
const child = curr.child(i);
|
|
286
|
+
if (child) stack.push({ node: child, parentSymbol: newParentSymbol, typeName: newTypeName, localScope: childLocalScope });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
this.handleBareUses(rootNode, relationships, symbols, emittedUses);
|
|
290
|
+
if (enumCtorCandidates.length > 0) {
|
|
291
|
+
const enumCases = /* @__PURE__ */ new Set();
|
|
292
|
+
for (const s of symbols) {
|
|
293
|
+
if (s.kind === ASTEngine.SymbolKind.EnumMember && s.ownerName) {
|
|
294
|
+
enumCases.add(`${s.ownerName}.${s.name}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (enumCases.size > 0) {
|
|
298
|
+
const retiredSites = /* @__PURE__ */ new Set();
|
|
299
|
+
const retiredLines = /* @__PURE__ */ new Map();
|
|
300
|
+
const constructions = [];
|
|
301
|
+
for (const cand of enumCtorCandidates) {
|
|
302
|
+
const key = cand.head ?? cand.owner;
|
|
303
|
+
if (!key || !enumCases.has(`${key}.${cand.leaf}`)) continue;
|
|
304
|
+
if (cand.removeCalls) {
|
|
305
|
+
const siteKey = `${cand.source}\0${cand.leaf}`;
|
|
306
|
+
const left = (callSiteCounts.get(siteKey) ?? 0) - 1;
|
|
307
|
+
callSiteCounts.set(siteKey, left);
|
|
308
|
+
if (typeof cand.line === "number") {
|
|
309
|
+
const set = retiredLines.get(siteKey);
|
|
310
|
+
if (set) set.add(cand.line);
|
|
311
|
+
else retiredLines.set(siteKey, /* @__PURE__ */ new Set([cand.line]));
|
|
312
|
+
}
|
|
313
|
+
if (left <= 0) retiredSites.add(siteKey);
|
|
314
|
+
}
|
|
315
|
+
constructions.push({ source: cand.source, target: key, line: cand.line });
|
|
316
|
+
}
|
|
317
|
+
if (retiredSites.size > 0) {
|
|
318
|
+
for (let i = relationships.length - 1; i >= 0; i--) {
|
|
319
|
+
const r = relationships[i];
|
|
320
|
+
if (r.type !== ASTEngine.RelationshipType.Calls) continue;
|
|
321
|
+
if (!retiredSites.has(`${r.source}\0${String(r.target)}`)) continue;
|
|
322
|
+
relationships.splice(i, 1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (callSiteLines.size > 0) {
|
|
326
|
+
for (const r of relationships) {
|
|
327
|
+
if (r.type !== ASTEngine.RelationshipType.Calls) continue;
|
|
328
|
+
const key = `${r.source}\0${String(r.target)}`;
|
|
329
|
+
const lines = callSiteLines.get(key);
|
|
330
|
+
if (!lines) continue;
|
|
331
|
+
const retired = retiredLines.get(key);
|
|
332
|
+
if (!retired) continue;
|
|
333
|
+
let kept;
|
|
334
|
+
for (const l of lines) {
|
|
335
|
+
if (!retired.has(l)) {
|
|
336
|
+
kept = l;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (typeof kept === "number") r.line_number = kept;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const c of constructions) {
|
|
344
|
+
const ekey = `${c.source}\0instantiates\0${c.target}`;
|
|
345
|
+
if (emittedCalls.has(ekey)) continue;
|
|
346
|
+
emittedCalls.add(ekey);
|
|
347
|
+
relationships.push({
|
|
348
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
349
|
+
source: c.source,
|
|
350
|
+
target: c.target,
|
|
351
|
+
isExternal: true,
|
|
352
|
+
weight: 0.7,
|
|
353
|
+
confidence: 0.95,
|
|
354
|
+
// The CONVERTED call site's line, carried on the candidate.
|
|
355
|
+
...typeof c.line === "number" ? { line_number: c.line } : {},
|
|
356
|
+
metadata: { isEnumConstruction: true }
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const p in patternsObj) {
|
|
362
|
+
metadata.patterns.push(p);
|
|
363
|
+
}
|
|
364
|
+
const emittedFrameworks = SWIFT_FRAMEWORK_ORDER.filter((f) => frameworksObj[f] === true);
|
|
365
|
+
const unlistedFrameworks = [];
|
|
366
|
+
for (const f in frameworksObj) {
|
|
367
|
+
if (frameworksObj[f] === true && SWIFT_FRAMEWORK_ORDER.indexOf(f) === -1) unlistedFrameworks.push(f);
|
|
368
|
+
}
|
|
369
|
+
unlistedFrameworks.sort();
|
|
370
|
+
for (let i = 0; i < unlistedFrameworks.length; i++) emittedFrameworks.push(unlistedFrameworks[i]);
|
|
371
|
+
metadata.detectedFrameworks = emittedFrameworks;
|
|
372
|
+
metadata.framework = emittedFrameworks.length > 0 ? emittedFrameworks[0] : void 0;
|
|
373
|
+
metadata.frameworkPatterns = emittedFrameworks.map((f) => ({
|
|
374
|
+
framework: f,
|
|
375
|
+
patterns: SWIFT_FRAMEWORK_PATTERNS[f] ?? ["usage"]
|
|
376
|
+
}));
|
|
377
|
+
for (const s of symbols) {
|
|
378
|
+
if (s.kind === ASTEngine.SymbolKind.Method || s.kind === ASTEngine.SymbolKind.Constructor || s.kind === ASTEngine.SymbolKind.Function) {
|
|
379
|
+
const owner = s.ownerName || "root";
|
|
380
|
+
symbolMap.set(`${owner}.${s.name}`, s);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
for (const s of symbols) {
|
|
384
|
+
const _owner = s.ownerName || s.scope;
|
|
385
|
+
if (_owner && s.name && s.kind !== ASTEngine.SymbolKind.Extension) {
|
|
386
|
+
const key = `${_owner}\0${s.name}`;
|
|
387
|
+
if (!emittedContains.has(key)) {
|
|
388
|
+
emittedContains.add(key);
|
|
389
|
+
relationships.push({
|
|
390
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
391
|
+
source: _owner,
|
|
392
|
+
target: s.name,
|
|
393
|
+
isExternal: false,
|
|
394
|
+
weight: 1,
|
|
395
|
+
confidence: 1,
|
|
396
|
+
// The DECLARATION's own line (the member's range start).
|
|
397
|
+
...typeof s.range?.startLine === "number" ? { line_number: s.range.startLine } : {},
|
|
398
|
+
metadata: {}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (const imp of imports) {
|
|
404
|
+
if (imp.module) {
|
|
405
|
+
relationships.push({
|
|
406
|
+
type: ASTEngine.RelationshipType.References,
|
|
407
|
+
source: "root",
|
|
408
|
+
target: imp.module,
|
|
409
|
+
isExternal: true,
|
|
410
|
+
weight: 0.9,
|
|
411
|
+
confidence: 1,
|
|
412
|
+
// The import statement's own line (handleImport records the range).
|
|
413
|
+
...typeof imp.range?.startLine === "number" ? { line_number: imp.range.startLine } : {},
|
|
414
|
+
// `isImportNameReference` is the marker the `references` family uses to
|
|
415
|
+
// say this edge names a MODULE brought into scope rather than a symbol
|
|
416
|
+
// the graph holds (INCOMING_SWIFT JOB 2, preserved by DERIV_EXPORTS_SWIFT4).
|
|
417
|
+
// It is why leaving `references` in place beside `imports` is not a
|
|
418
|
+
// duplicate claim: the two carry different markers and answer different
|
|
419
|
+
// questions ("what names came into scope" vs "what does this file depend on").
|
|
420
|
+
//
|
|
421
|
+
// M14 SWIFT RESIDUAL — THE CLAIM MOVES ONTO THE KEY THE ARBITER READS.
|
|
422
|
+
// `isExternal: true` has been set on this RELATIONSHIP since INCOMING_SWIFT
|
|
423
|
+
// JOB 2, and `externalClaimOf` (script/m14/incoming-rule.ts:396) reads
|
|
424
|
+
// `rel.metadata` ONLY — so the statement was invisible and the lane's `extOK`
|
|
425
|
+
// column read 0 while its own sentence above said "a MODULE brought into scope
|
|
426
|
+
// rather than a symbol the graph holds". Measured on the 400-file bounded run
|
|
427
|
+
// (`m14-swift-cause-probe.mts`): 916 `references` + 900 `imports` edges are
|
|
428
|
+
// unresolvable, every one of them naming a module no file in the run declares
|
|
429
|
+
// (Foundation, UIKit, XCTest, Flutter, NIOCore …). Publishing the SAME claim
|
|
430
|
+
// inside `metadata` is ROUTING, not a new judgement. It cannot move a
|
|
431
|
+
// resolution: `classifyEdge` consults a claim only where the run declares no
|
|
432
|
+
// symbol of that name (`:1147`), and with a declaration present it reads the
|
|
433
|
+
// claim "to RECORD it, never to move the outcome" (`:1188-1194`) — so
|
|
434
|
+
// `resolved`, `collRes` and `amb(decl)` are untouched in both directions.
|
|
435
|
+
metadata: { importModule: imp.module, isImportNameReference: true, isExternal: true }
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
{
|
|
440
|
+
const seen = /* @__PURE__ */ new Set();
|
|
441
|
+
for (const re of reExports) {
|
|
442
|
+
const key = `${moduleSource}\0${re.module}`;
|
|
443
|
+
if (seen.has(key)) continue;
|
|
444
|
+
seen.add(key);
|
|
445
|
+
const reLine = this.nodeLine(re.node);
|
|
446
|
+
relationships.push({
|
|
447
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
448
|
+
source: moduleSource,
|
|
449
|
+
target: re.module,
|
|
450
|
+
isExternal: true,
|
|
451
|
+
weight: 0.9,
|
|
452
|
+
confidence: 1,
|
|
453
|
+
// The MODULE NAME's own line — the site a reader checks, the same node
|
|
454
|
+
// the ImportInfo row and the references edge are lined at (
|
|
455
|
+
// `@_exported\nimport Basic` lines the edge at the module name, 11,
|
|
456
|
+
// never the attribute's line, 10).
|
|
457
|
+
...typeof reLine === "number" ? { line_number: reLine } : {},
|
|
458
|
+
// M14 SWIFT RESIDUAL: `@_exported import M` re-exports a MODULE, and the
|
|
459
|
+
// relationship already says `isExternal: true` (`:768`) — but the arbiter
|
|
460
|
+
// reads `metadata`. Same routing, same safety, as the `references`/`imports`
|
|
461
|
+
// edges: a claim is consulted only where the run declares no symbol of that
|
|
462
|
+
// name, so no resolution can move. The `exports` INFO row below keeps its
|
|
463
|
+
// metadata unchanged: it is a declaration, not an external claim.
|
|
464
|
+
metadata: { exportForm: re.form, reExport: true, reexportsModule: re.module, isExternal: true }
|
|
465
|
+
});
|
|
466
|
+
if (!emittedExportNames.has(re.module)) {
|
|
467
|
+
emittedExportNames.add(re.module);
|
|
468
|
+
exports.push({
|
|
469
|
+
name: re.module,
|
|
470
|
+
type: "module",
|
|
471
|
+
range: {
|
|
472
|
+
startLine: re.node.startPosition.row + 1,
|
|
473
|
+
startColumn: re.node.startPosition.column + 1,
|
|
474
|
+
endLine: re.node.endPosition.row + 1,
|
|
475
|
+
endColumn: re.node.endPosition.column + 1,
|
|
476
|
+
startIndex: re.node.startIndex,
|
|
477
|
+
endIndex: re.node.endIndex
|
|
478
|
+
},
|
|
479
|
+
metadata: { exportForm: re.form, reExport: true, reexportsModule: re.module }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const emittedImports = /* @__PURE__ */ new Set();
|
|
485
|
+
const scopedImportsPerModule = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const imp of imports) {
|
|
487
|
+
const imd = imp.metadata ?? {};
|
|
488
|
+
const im = typeof imd.module === "string" ? imd.module : null;
|
|
489
|
+
if (!im || imd.isScopedImport !== true) continue;
|
|
490
|
+
scopedImportsPerModule.set(im, (scopedImportsPerModule.get(im) ?? 0) + 1);
|
|
491
|
+
}
|
|
492
|
+
for (const imp of imports) {
|
|
493
|
+
const md = imp.metadata ?? {};
|
|
494
|
+
const spelled = typeof imp.module === "string" ? imp.module : "";
|
|
495
|
+
const moduleName = typeof md.module === "string" && md.module ? md.module : spelled.split(".")[0] || null;
|
|
496
|
+
if (!moduleName || emittedImports.has(moduleName)) continue;
|
|
497
|
+
emittedImports.add(moduleName);
|
|
498
|
+
const scopedHere = scopedImportsPerModule.get(moduleName) ?? 0;
|
|
499
|
+
const edgeMd = {
|
|
500
|
+
...md,
|
|
501
|
+
// `isImport` is the MARKER the completeness section-B inventory reads,
|
|
502
|
+
// so the kind is claimed twice over (go GO precedent) and the evidence
|
|
503
|
+
// tier stays import_based.
|
|
504
|
+
isImport: true,
|
|
505
|
+
isModuleDependency: true,
|
|
506
|
+
// #910: the importer is this FILE, so the indexer must be told this is
|
|
507
|
+
// a file-level edge rather than a scope placeholder named `root`.
|
|
508
|
+
fileLevel: true,
|
|
509
|
+
// M14 SWIFT RESIDUAL: the same module-external claim the `references` edge
|
|
510
|
+
// carries, on the key the arbiter reads — see the block at that edge above.
|
|
511
|
+
// `isExternal` was already `true` on this relationship (`:893`); `metadata` is
|
|
512
|
+
// the only place `externalClaimOf` looks.
|
|
513
|
+
isExternal: true
|
|
514
|
+
};
|
|
515
|
+
if (scopedHere > 1) {
|
|
516
|
+
delete edgeMd.scopedItem;
|
|
517
|
+
edgeMd.scopedItemCount = scopedHere;
|
|
518
|
+
}
|
|
519
|
+
relationships.push({
|
|
520
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
521
|
+
source: "root",
|
|
522
|
+
target: moduleName,
|
|
523
|
+
isExternal: true,
|
|
524
|
+
weight: 0.9,
|
|
525
|
+
confidence: 1,
|
|
526
|
+
// The MODULE NAME's own line (handleImport records the name node's
|
|
527
|
+
// range), the same site the references edge names.
|
|
528
|
+
...typeof imp.range?.startLine === "number" ? { line_number: imp.range.startLine } : {},
|
|
529
|
+
metadata: edgeMd
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
this.resolveOverrides(
|
|
533
|
+
overrideTasks,
|
|
534
|
+
classHierarchy,
|
|
535
|
+
symbolMap,
|
|
536
|
+
relationships,
|
|
537
|
+
context?.overrideCompanion ?? cross.companion,
|
|
538
|
+
cross.getParents(classHierarchy)
|
|
539
|
+
);
|
|
540
|
+
this.classifyEvidenceTier(relationships);
|
|
541
|
+
return {
|
|
542
|
+
symbols,
|
|
543
|
+
relationships,
|
|
544
|
+
imports,
|
|
545
|
+
exports,
|
|
546
|
+
metadata,
|
|
547
|
+
errors: metadata.errors || []
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/** Pre-pass: record every protocol/class/struct/actor/enum/extension name. */
|
|
551
|
+
collectDeclaredKinds(rootNode, out) {
|
|
552
|
+
const stack = [rootNode];
|
|
553
|
+
let guard = 0;
|
|
554
|
+
while (stack.length > 0 && guard++ < 5e5) {
|
|
555
|
+
const n = stack.pop();
|
|
556
|
+
if (n.type === "protocol_declaration") {
|
|
557
|
+
const nm = this.findChildByType(n, "type_identifier");
|
|
558
|
+
if (nm) out.set(nm.text, "interface");
|
|
559
|
+
} else if (n.type === "class_declaration") {
|
|
560
|
+
const nm = this.findChildByType(n, "type_identifier") || this.findChildByType(n, "user_type");
|
|
561
|
+
if (nm) {
|
|
562
|
+
const leaf = nm.type === "user_type" && nm.namedChildCount > 0 ? nm.namedChild(0).text : nm.text;
|
|
563
|
+
if (this.hasAnonToken(n, "class")) out.set(leaf, "class");
|
|
564
|
+
else if (this.hasAnonToken(n, "struct")) out.set(leaf, "struct");
|
|
565
|
+
else if (this.hasAnonToken(n, "actor")) out.set(leaf, "actor");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
569
|
+
const child = n.namedChild(i);
|
|
570
|
+
if (child) stack.push(child);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// ============================================================================
|
|
575
|
+
// EVIDENCE TIERS
|
|
576
|
+
// ============================================================================
|
|
577
|
+
/**
|
|
578
|
+
* Evidence-tier classification (managed-OO family contract — mirrors
|
|
579
|
+
* dart/csharp/java; M14 section D requires >=2 distinct evidenceClass
|
|
580
|
+
* values). Same rules as the family: structural (grammar clauses, conf
|
|
581
|
+
* 1.0) | usage_contextual (calls, mixin uses) | import_based (import
|
|
582
|
+
* directives) | inferred (confidence < 1 edges).
|
|
583
|
+
*/
|
|
584
|
+
classifyEvidenceTier(relationships) {
|
|
585
|
+
for (const r of relationships) {
|
|
586
|
+
const type = r.type ?? r.relationshipType ?? "";
|
|
587
|
+
const md = r.metadata ?? {};
|
|
588
|
+
if (type === "contains") {
|
|
589
|
+
md.evidenceClass = "structural";
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (type === "imports" || type === "references") {
|
|
593
|
+
md.evidenceClass = "import_based";
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (type === "exports") {
|
|
597
|
+
md.evidenceClass = md.reExport === true ? "import_based" : "structural";
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (type === "calls" || type === "uses") {
|
|
601
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.85 ? "usage_contextual" : "inferred";
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (type === "instantiates" || type === "extends" || type === "implements") {
|
|
605
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.9 ? "structural" : "inferred";
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (type === "overrides") {
|
|
609
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.9 ? "structural" : "inferred";
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
md.evidenceClass = "usage_contextual";
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// ==========================================================================
|
|
616
|
+
// Declaration handlers
|
|
617
|
+
// ==========================================================================
|
|
618
|
+
handleProtocol(node, symbols, relationships, parent, patternsObj, classHierarchy, localScope = false) {
|
|
619
|
+
patternsObj["Protocol-Oriented Programming"] = true;
|
|
620
|
+
const nameNode = this.findChildByType(node, "type_identifier") || this.firstNamedChild(node);
|
|
621
|
+
if (!nameNode) return parent;
|
|
622
|
+
const meta = { isProtocol: true, hasGenerics: this.hasChildType(node, "type_parameters") };
|
|
623
|
+
if (meta.hasGenerics) patternsObj["Generics"] = true;
|
|
624
|
+
const vis = this.getVisibility(node);
|
|
625
|
+
const symbol = this.createSymbol(nameNode.text, ASTEngine.SymbolKind.Interface, nameNode, parent, symbols, meta, vis, this.getDeclaredVisibility(node), localScope);
|
|
626
|
+
const baseSites = this.extractBaseSites(node);
|
|
627
|
+
const parents = [];
|
|
628
|
+
for (let i = 0; i < baseSites.length; i++) {
|
|
629
|
+
const base = baseSites[i].name;
|
|
630
|
+
if (base === "AnyObject" || base === "Any") continue;
|
|
631
|
+
parents.push(base);
|
|
632
|
+
relationships.push({
|
|
633
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
634
|
+
source: symbol.name,
|
|
635
|
+
target: base,
|
|
636
|
+
isExternal: true,
|
|
637
|
+
weight: 1,
|
|
638
|
+
confidence: 1,
|
|
639
|
+
// The base SPECIFIER's own line in the inheritance clause.
|
|
640
|
+
line_number: this.nodeLine(baseSites[i].node),
|
|
641
|
+
metadata: { isImplements: false }
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
if (parents.length > 0) classHierarchy.set(symbol.name, parents);
|
|
645
|
+
return symbol;
|
|
646
|
+
}
|
|
647
|
+
handleDeclaration(node, symbols, relationships, parent, patternsObj, classHierarchy, inFileKinds, crossFileKinds, superByClass, localScope = false) {
|
|
648
|
+
let kind = ASTEngine.SymbolKind.Class;
|
|
649
|
+
const recoveredKind = node.type === "ERROR" ? this.recoveryKind(node) : null;
|
|
650
|
+
if (recoveredKind) {
|
|
651
|
+
kind = recoveredKind;
|
|
652
|
+
} else if (this.hasAnonToken(node, "actor")) {
|
|
653
|
+
kind = ASTEngine.SymbolKind.Actor;
|
|
654
|
+
} else if (this.hasAnonToken(node, "struct")) {
|
|
655
|
+
kind = ASTEngine.SymbolKind.Struct;
|
|
656
|
+
} else if (this.hasAnonToken(node, "enum")) {
|
|
657
|
+
kind = ASTEngine.SymbolKind.Enum;
|
|
658
|
+
} else if (this.hasAnonToken(node, "extension")) {
|
|
659
|
+
kind = ASTEngine.SymbolKind.Extension;
|
|
660
|
+
}
|
|
661
|
+
let nameNode = null;
|
|
662
|
+
const recoveredName = node.type === "ERROR" ? this.recoveryNameNode(node) : null;
|
|
663
|
+
if (recoveredName) {
|
|
664
|
+
nameNode = recoveredName;
|
|
665
|
+
} else if (kind === ASTEngine.SymbolKind.Extension) {
|
|
666
|
+
const ut = this.findChildByType(node, "user_type");
|
|
667
|
+
if (ut) {
|
|
668
|
+
nameNode = this.lastTypeIdentifier(ut);
|
|
669
|
+
} else {
|
|
670
|
+
nameNode = this.findDescendantTypeIdentifier(node);
|
|
671
|
+
}
|
|
672
|
+
} else {
|
|
673
|
+
nameNode = this.findChildByType(node, "type_identifier") || this.findChildByType(node, "user_type") || this.firstNamedChild(node);
|
|
674
|
+
}
|
|
675
|
+
if (!nameNode) return parent;
|
|
676
|
+
let rawName = nameNode.type === "user_type" && nameNode.namedChildCount > 0 ? nameNode.namedChild(0).text : nameNode.text;
|
|
677
|
+
if (kind === ASTEngine.SymbolKind.Extension && nameNode.type === "user_type") {
|
|
678
|
+
const leaf = this.lastTypeIdentifier(nameNode);
|
|
679
|
+
if (leaf) rawName = leaf.text;
|
|
680
|
+
}
|
|
681
|
+
const meta = {
|
|
682
|
+
hasGenerics: this.hasChildType(node, "type_parameters")
|
|
683
|
+
};
|
|
684
|
+
if (meta.hasGenerics) patternsObj["Generics"] = true;
|
|
685
|
+
if (kind === ASTEngine.SymbolKind.Struct) meta.isValueType = true;
|
|
686
|
+
if (kind === ASTEngine.SymbolKind.Enum) meta.isValueType = true;
|
|
687
|
+
const vis = this.getVisibility(node);
|
|
688
|
+
if (kind === ASTEngine.SymbolKind.Enum) {
|
|
689
|
+
const rawType = this.findEnumRawType(node);
|
|
690
|
+
if (rawType && ENUM_RAW_VALUE_TYPES.has(rawType)) meta.rawType = rawType;
|
|
691
|
+
}
|
|
692
|
+
const symbol = this.createSymbol(rawName, kind, nameNode, parent, symbols, meta, vis, this.getDeclaredVisibility(node), localScope);
|
|
693
|
+
const baseSites = this.extractBaseSites(node);
|
|
694
|
+
const bases = baseSites.map((b) => b.name);
|
|
695
|
+
const parents = [];
|
|
696
|
+
if (kind === ASTEngine.SymbolKind.Class || kind === ASTEngine.SymbolKind.Struct || kind === ASTEngine.SymbolKind.Actor) {
|
|
697
|
+
const firstIsSuperclass = kind === ASTEngine.SymbolKind.Class && (bases.length > 0 ? (inFileKinds.get(bases[0]) ?? crossFileKinds?.get(bases[0]) ?? "class") === "class" : true);
|
|
698
|
+
for (let i = 0; i < bases.length; i++) {
|
|
699
|
+
const base = bases[i];
|
|
700
|
+
parents.push(base);
|
|
701
|
+
const isExtends = firstIsSuperclass && i === 0;
|
|
702
|
+
if (isExtends && superByClass) superByClass.set(symbol.name, base);
|
|
703
|
+
relationships.push({
|
|
704
|
+
type: isExtends ? ASTEngine.RelationshipType.Extends : ASTEngine.RelationshipType.Implements,
|
|
705
|
+
source: symbol.name,
|
|
706
|
+
target: base,
|
|
707
|
+
isExternal: true,
|
|
708
|
+
weight: isExtends ? 1 : 0.9,
|
|
709
|
+
confidence: 1,
|
|
710
|
+
line_number: this.nodeLine(baseSites[i].node),
|
|
711
|
+
metadata: { isImplements: !isExtends }
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (parents.length > 0) classHierarchy.set(symbol.name, parents);
|
|
715
|
+
} else if (kind === ASTEngine.SymbolKind.Extension) {
|
|
716
|
+
for (let i = 0; i < baseSites.length; i++) {
|
|
717
|
+
const base = baseSites[i].name;
|
|
718
|
+
parents.push(base);
|
|
719
|
+
relationships.push({
|
|
720
|
+
type: ASTEngine.RelationshipType.Implements,
|
|
721
|
+
source: symbol.name,
|
|
722
|
+
target: base,
|
|
723
|
+
isExternal: true,
|
|
724
|
+
weight: 0.9,
|
|
725
|
+
confidence: 1,
|
|
726
|
+
line_number: this.nodeLine(baseSites[i].node),
|
|
727
|
+
metadata: { isImplements: true }
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
} else if (kind === ASTEngine.SymbolKind.Enum) {
|
|
731
|
+
const skipFirst = bases.length > 0 && ENUM_RAW_VALUE_TYPES.has(bases[0]);
|
|
732
|
+
for (let i = skipFirst ? 1 : 0; i < bases.length; i++) {
|
|
733
|
+
const base = bases[i];
|
|
734
|
+
relationships.push({
|
|
735
|
+
type: ASTEngine.RelationshipType.Implements,
|
|
736
|
+
source: symbol.name,
|
|
737
|
+
target: base,
|
|
738
|
+
isExternal: true,
|
|
739
|
+
weight: 0.9,
|
|
740
|
+
confidence: 1,
|
|
741
|
+
line_number: this.nodeLine(baseSites[i].node),
|
|
742
|
+
metadata: { isImplements: true }
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (kind === ASTEngine.SymbolKind.Class || kind === ASTEngine.SymbolKind.Struct || kind === ASTEngine.SymbolKind.Extension) {
|
|
747
|
+
if (bases.includes("View")) {
|
|
748
|
+
this.extractRenderRelationships(node, symbol.name, relationships);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return symbol;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* `renders` — the rendered component named by a View body's TOP-LEVEL DSL
|
|
755
|
+
* call. Swift's rendering entry point is `var body: some View { … }` on a
|
|
756
|
+
* type conforming to `View`; the component the body's outermost
|
|
757
|
+
* `call_expression` names is what the type renders (the dart lane's
|
|
758
|
+
* `build`-returns-`Widget` shape, `real-dart-analyzer.ts:4274`). Deeper
|
|
759
|
+
* constructions stay their existing `calls`/`instantiates` rows — only the
|
|
760
|
+
* top-level expression is the rendered tree's root, and every other
|
|
761
|
+
* construction inside it is an ARGUMENT or a CHILD, not the render.
|
|
762
|
+
*/
|
|
763
|
+
extractRenderRelationships(node, className, relationships) {
|
|
764
|
+
const classBody = this.findChildByType(node, "class_body");
|
|
765
|
+
if (!classBody) return;
|
|
766
|
+
for (let i = 0; i < classBody.namedChildCount; i++) {
|
|
767
|
+
const member = classBody.namedChild(i);
|
|
768
|
+
if (!member || member.type !== "property_declaration") continue;
|
|
769
|
+
const pattern = this.findChildByType(member, "pattern");
|
|
770
|
+
if (!pattern || pattern.text !== "body") continue;
|
|
771
|
+
const typeAnno = this.findChildByType(member, "type_annotation");
|
|
772
|
+
if (!typeAnno || typeAnno.text.trim() !== ": some View") continue;
|
|
773
|
+
const computed = this.findChildByType(member, "computed_property");
|
|
774
|
+
if (!computed) continue;
|
|
775
|
+
const statements = this.findChildByType(computed, "statements");
|
|
776
|
+
if (!statements) continue;
|
|
777
|
+
const expr = statements.namedChild(0);
|
|
778
|
+
if (!expr || expr.type !== "call_expression") continue;
|
|
779
|
+
const callee = this.findChildByType(expr, "simple_identifier") || this.findChildByType(expr, "identifier");
|
|
780
|
+
if (!callee) continue;
|
|
781
|
+
const target = callee.text;
|
|
782
|
+
const firstCode = target.length > 0 ? target.charCodeAt(0) : 0;
|
|
783
|
+
if (!(firstCode >= 65 && firstCode <= 90) || target === "View" || target === "Self") continue;
|
|
784
|
+
relationships.push({
|
|
785
|
+
source: className,
|
|
786
|
+
target,
|
|
787
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
788
|
+
confidence: 0.9,
|
|
789
|
+
line_number: this.nodeLine(callee),
|
|
790
|
+
metadata: { isRenderHost: true, renderHost: "body", renderTarget: target }
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
handleFunction(node, symbols, relationships, parent, overrideTasks, companion, localScope = false) {
|
|
795
|
+
const nameNode = this.findChildByType(node, "simple_identifier") || this.findChildByType(node, "identifier");
|
|
796
|
+
const opName = nameNode ? null : this.operatorName(node);
|
|
797
|
+
if (!nameNode && !opName) return parent;
|
|
798
|
+
const isMethod = !!parent && MEMBER_OWNER_KINDS.has(parent.kind);
|
|
799
|
+
const kind = isMethod ? ASTEngine.SymbolKind.Method : ASTEngine.SymbolKind.Function;
|
|
800
|
+
const vis = this.getVisibility(node);
|
|
801
|
+
const meta = {
|
|
802
|
+
hasGenerics: this.hasChildType(node, "type_parameters")
|
|
803
|
+
};
|
|
804
|
+
const funcAttrs = this.extractAttributes(node);
|
|
805
|
+
if (funcAttrs.length > 0) meta.annotations = funcAttrs;
|
|
806
|
+
const symbol = this.createSymbol(opName ?? nameNode.text, kind, opName ? node : nameNode, parent, symbols, meta, vis, this.getDeclaredVisibility(node), localScope);
|
|
807
|
+
this.collectFunctionMeta(node, meta, symbol);
|
|
808
|
+
symbol.complexity = this.calculateCyclomaticComplexity(node);
|
|
809
|
+
if (this.hasDescendantAnonToken(node, "override") && parent) {
|
|
810
|
+
overrideTasks.push({
|
|
811
|
+
fqn: `${parent.name}.${symbol.name}`,
|
|
812
|
+
methodName: symbol.name,
|
|
813
|
+
ownerFqn: parent.name,
|
|
814
|
+
line: symbol.range?.startLine
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
const returnNode = this.findReturnType(node);
|
|
818
|
+
if (returnNode) symbol.returnType = returnNode.text;
|
|
819
|
+
return symbol;
|
|
820
|
+
}
|
|
821
|
+
// ==========================================================================
|
|
822
|
+
// Variable / call / uses handlers
|
|
823
|
+
// ==========================================================================
|
|
824
|
+
handleVariable(node, symbols, parent, typeName, frameworksObj, patternsObj, localScope = false) {
|
|
825
|
+
if (localScope) return;
|
|
826
|
+
if (parent && !MEMBER_OWNER_KINDS.has(parent.kind)) return;
|
|
827
|
+
const vis = this.getVisibility(node);
|
|
828
|
+
const annotations = [];
|
|
829
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
830
|
+
const c = node.child(i);
|
|
831
|
+
if (c && (c.type === "modifiers" || c.type === "attribute")) {
|
|
832
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
833
|
+
const mod = c.namedChild(j);
|
|
834
|
+
if (!mod) continue;
|
|
835
|
+
if (mod.type === "attribute") {
|
|
836
|
+
const text = mod.text;
|
|
837
|
+
annotations.push(text);
|
|
838
|
+
if (text.indexOf("@State") !== -1 || text.indexOf("@Binding") !== -1 || text.indexOf("@Environment") !== -1 || text.indexOf("@ObservedObject") !== -1) {
|
|
839
|
+
frameworksObj["SwiftUI"] = true;
|
|
840
|
+
patternsObj["Property Wrappers"] = true;
|
|
841
|
+
}
|
|
842
|
+
if (text.indexOf("@Published") !== -1) {
|
|
843
|
+
frameworksObj["Combine"] = true;
|
|
844
|
+
patternsObj["Property Wrappers"] = true;
|
|
845
|
+
}
|
|
846
|
+
if (text.indexOf("@MainActor") !== -1 || text.indexOf("@Sendable") !== -1 || text.indexOf("@globalActor") !== -1) {
|
|
847
|
+
patternsObj["Swift Concurrency"] = true;
|
|
848
|
+
}
|
|
849
|
+
if (text.startsWith("@")) patternsObj["Property Wrappers"] = true;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const meta = {};
|
|
855
|
+
if (annotations.length > 0) meta.annotations = annotations;
|
|
856
|
+
const kind = parent ? ASTEngine.SymbolKind.Property : ASTEngine.SymbolKind.Variable;
|
|
857
|
+
const patternNodes = [];
|
|
858
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
859
|
+
const c = node.namedChild(i);
|
|
860
|
+
if (c && c.type === "pattern") patternNodes.push(c);
|
|
861
|
+
}
|
|
862
|
+
if (patternNodes.length > 0) {
|
|
863
|
+
for (const pn of patternNodes) {
|
|
864
|
+
const idNode = this.findChildByType(pn, "simple_identifier") || this.findChildByType(pn, "identifier") || this.firstNamedChild(pn);
|
|
865
|
+
if (idNode) {
|
|
866
|
+
this.createSymbol(idNode.text, kind, idNode, parent, symbols, meta, vis, this.getDeclaredVisibility(node), localScope);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
const idNode = this.findChildByType(node, "simple_identifier") || this.findChildByType(node, "identifier");
|
|
871
|
+
if (idNode) {
|
|
872
|
+
this.createSymbol(idNode.text, kind, idNode, parent, symbols, meta, vis, this.getDeclaredVisibility(node));
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
handleCall(node, relationships, parent, emittedCalls, enumCtorCandidates, superByClass, callSiteCounts, callSiteLines) {
|
|
877
|
+
if (this.isCasePatternRecovery(node)) return;
|
|
878
|
+
const source = parent ? parent.name : "root";
|
|
879
|
+
const funcNode = this.findChildByType(node, "simple_identifier") || this.findChildByType(node, "identifier") || this.findChildByType(node, "navigation_expression");
|
|
880
|
+
if (!funcNode) {
|
|
881
|
+
if (enumCtorCandidates) {
|
|
882
|
+
const pre = this.findChildByType(node, "prefix_expression");
|
|
883
|
+
const dotFirst = pre ? pre.child(0) : null;
|
|
884
|
+
if (pre && dotFirst && !dotFirst.isNamed && dotFirst.type === ".") {
|
|
885
|
+
let leaf = null;
|
|
886
|
+
for (let i = 0; i < pre.namedChildCount; i++) {
|
|
887
|
+
const c = pre.namedChild(i);
|
|
888
|
+
if (c && (c.type === "simple_identifier" || c.type === "identifier")) {
|
|
889
|
+
leaf = c.text;
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const owner = parent?.ownerName ?? null;
|
|
894
|
+
if (leaf && owner) {
|
|
895
|
+
enumCtorCandidates.push({ source, head: null, owner, leaf, removeCalls: false, line: this.nodeLine(node) });
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (this.isSubscriptRead(node)) return;
|
|
902
|
+
if (funcNode.type === "navigation_expression") {
|
|
903
|
+
const leaf = this.navigationLeaf(funcNode);
|
|
904
|
+
if (!leaf || leaf === "$0") return;
|
|
905
|
+
if (leaf === "init") {
|
|
906
|
+
const recv = this.navigationReceiver(funcNode);
|
|
907
|
+
if (recv === "super" || recv === "self" || recv === "Self") {
|
|
908
|
+
const owner = parent ? MEMBER_OWNER_KINDS.has(parent.kind) ? parent.name : parent.ownerName ?? null : null;
|
|
909
|
+
let target2 = null;
|
|
910
|
+
if ((recv === "self" || recv === "Self") && owner) target2 = owner;
|
|
911
|
+
else if (recv === "super" && owner && superByClass) target2 = superByClass.get(owner) ?? null;
|
|
912
|
+
if (target2) {
|
|
913
|
+
const key = `${source}\0instantiates\0${target2}`;
|
|
914
|
+
if (!emittedCalls.has(key)) {
|
|
915
|
+
emittedCalls.add(key);
|
|
916
|
+
relationships.push({
|
|
917
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
918
|
+
source,
|
|
919
|
+
target: target2,
|
|
920
|
+
isExternal: true,
|
|
921
|
+
weight: 0.7,
|
|
922
|
+
confidence: 0.95,
|
|
923
|
+
// The DELEGATION call site's line (`self.init()` / `super.init()`).
|
|
924
|
+
...typeof this.nodeLine(node) === "number" ? { line_number: this.nodeLine(node) } : {},
|
|
925
|
+
metadata: { isSuperInit: recv === "super", isSelfInit: recv !== "super" }
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
this.emitCall(relationships, source, leaf, emittedCalls, callSiteCounts, this.nodeLine(this.navigationLeafNode(funcNode)), callSiteLines);
|
|
933
|
+
if (enumCtorCandidates) {
|
|
934
|
+
const chain = this.navigationChain(funcNode);
|
|
935
|
+
const receiver = chain.length >= 2 ? chain[chain.length - 2] : null;
|
|
936
|
+
if (receiver && this.isTypeName(receiver) && receiver !== "Self" && receiver !== "super" && receiver !== "this") {
|
|
937
|
+
enumCtorCandidates.push({ source, head: receiver, owner: null, leaf, removeCalls: true, line: this.nodeLine(this.navigationLeafNode(funcNode)) });
|
|
938
|
+
} else if (receiver === "Self") {
|
|
939
|
+
const owner = parent?.ownerName ?? null;
|
|
940
|
+
if (owner) enumCtorCandidates.push({ source, head: null, owner, leaf, removeCalls: true, line: this.nodeLine(this.navigationLeafNode(funcNode)) });
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const target = this.stripNameQuoting(funcNode.text);
|
|
946
|
+
if (!target || BUILTIN_BARE_CALLS.has(target)) return;
|
|
947
|
+
if (target.startsWith("XCT")) return;
|
|
948
|
+
const firstChar = target.charCodeAt(0);
|
|
949
|
+
const isCapitalized = firstChar >= 65 && firstChar <= 90;
|
|
950
|
+
if (isCapitalized) {
|
|
951
|
+
const key = `${source}\0instantiates\0${target}`;
|
|
952
|
+
if (!emittedCalls.has(key)) {
|
|
953
|
+
emittedCalls.add(key);
|
|
954
|
+
relationships.push({
|
|
955
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
956
|
+
source,
|
|
957
|
+
target,
|
|
958
|
+
isExternal: true,
|
|
959
|
+
weight: 0.7,
|
|
960
|
+
confidence: 1,
|
|
961
|
+
// The construction site's callee node line.
|
|
962
|
+
...typeof this.nodeLine(funcNode) === "number" ? { line_number: this.nodeLine(funcNode) } : {},
|
|
963
|
+
metadata: { isInstantiation: true }
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
} else {
|
|
967
|
+
this.emitCall(relationships, source, target, emittedCalls, callSiteCounts, this.nodeLine(funcNode), callSiteLines);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
emitCall(relationships, source, target, emittedCalls, callSiteCounts, line, callSiteLines) {
|
|
971
|
+
if (callSiteCounts) {
|
|
972
|
+
const siteKey = `${source}\0${target}`;
|
|
973
|
+
callSiteCounts.set(siteKey, (callSiteCounts.get(siteKey) ?? 0) + 1);
|
|
974
|
+
if (typeof line === "number") {
|
|
975
|
+
const arr = callSiteLines?.get(siteKey);
|
|
976
|
+
if (arr) arr.push(line);
|
|
977
|
+
else callSiteLines?.set(siteKey, [line]);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const key = `${source}\0calls\0${target}`;
|
|
981
|
+
if (emittedCalls.has(key)) return;
|
|
982
|
+
emittedCalls.add(key);
|
|
983
|
+
relationships.push({
|
|
984
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
985
|
+
source,
|
|
986
|
+
target,
|
|
987
|
+
isExternal: true,
|
|
988
|
+
weight: 1,
|
|
989
|
+
confidence: 0.95,
|
|
990
|
+
// The CALLEE node's own line — the site a reader checks.
|
|
991
|
+
...typeof line === "number" ? { line_number: line } : {},
|
|
992
|
+
metadata: {}
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
handleSelfUse(node, relationships, typeName, parent, emittedUses) {
|
|
996
|
+
{
|
|
997
|
+
let a = node.parent;
|
|
998
|
+
while (a && (a.type === "directly_assignable_expression" || a.type === "postfix_expression" || a.type === "navigation_expression" || a.type === "navigation_suffix")) a = a.parent;
|
|
999
|
+
if (a && a.type === "assignment") {
|
|
1000
|
+
const lhs = a.namedChild(0);
|
|
1001
|
+
if (lhs && lhs.startIndex <= node.startIndex && lhs.endIndex >= node.endIndex) return;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
const hasSelf = this.hasSelfReceiver(node);
|
|
1005
|
+
if (!hasSelf) return;
|
|
1006
|
+
const par = node.parent;
|
|
1007
|
+
if (par && par.type === "call_expression" && !this.isSubscriptRead(par)) return;
|
|
1008
|
+
const leaf = this.navigationLeaf(node);
|
|
1009
|
+
if (!leaf) return;
|
|
1010
|
+
const source = (parent && (parent.kind === ASTEngine.SymbolKind.Method || parent.kind === ASTEngine.SymbolKind.Constructor) ? parent.name : null) || typeName || (parent?.ownerName ? parent.ownerName : null) || parent?.name || "root";
|
|
1011
|
+
const key = `${source}\0uses\0${leaf}`;
|
|
1012
|
+
if (emittedUses.has(key)) return;
|
|
1013
|
+
emittedUses.add(key);
|
|
1014
|
+
relationships.push({
|
|
1015
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
1016
|
+
source,
|
|
1017
|
+
target: leaf,
|
|
1018
|
+
isExternal: false,
|
|
1019
|
+
weight: 0.7,
|
|
1020
|
+
confidence: 0.95,
|
|
1021
|
+
// The leaf segment's own node line (`self.a.b` -> the `b` node).
|
|
1022
|
+
...typeof this.nodeLine(this.navigationLeafNode(node)) === "number" ? { line_number: this.nodeLine(this.navigationLeafNode(node)) } : {},
|
|
1023
|
+
metadata: { isFieldAccess: true }
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* M49 #754 (CONV-SW-1): BARE member reads. The main walk only emits uses
|
|
1028
|
+
* for `self.x` navigation reads, so a plain `return count` on a declared
|
|
1029
|
+
* property emitted NOTHING while `return self.count` did (probe-confirmed
|
|
1030
|
+
* live: `uses edges on x: 0`). Mirrors java #739 / dart #752. Gate A: the
|
|
1031
|
+
* name must resolve to a DECLARED MEMBER of the enclosing type (the symbol
|
|
1032
|
+
* table's ownerName IS the enclosing type); gate B (CGD15): a name the
|
|
1033
|
+
* CURRENT function binds locally is not a member read — without it every
|
|
1034
|
+
* local/loop variable fabricates an edge.
|
|
1035
|
+
*/
|
|
1036
|
+
handleBareUses(rootNode, relationships, symbols, emittedUses) {
|
|
1037
|
+
const membersByOwner = /* @__PURE__ */ new Map();
|
|
1038
|
+
for (const s of symbols) {
|
|
1039
|
+
if (!s.ownerName) continue;
|
|
1040
|
+
let set = membersByOwner.get(s.ownerName);
|
|
1041
|
+
if (!set) {
|
|
1042
|
+
set = /* @__PURE__ */ new Set();
|
|
1043
|
+
membersByOwner.set(s.ownerName, set);
|
|
1044
|
+
}
|
|
1045
|
+
set.add(s.name);
|
|
1046
|
+
}
|
|
1047
|
+
const NOT_A_READ = /* @__PURE__ */ new Set([
|
|
1048
|
+
"pattern",
|
|
1049
|
+
"parameter",
|
|
1050
|
+
"function_declaration",
|
|
1051
|
+
"protocol_function_declaration",
|
|
1052
|
+
"class_declaration",
|
|
1053
|
+
"protocol_declaration",
|
|
1054
|
+
"struct_declaration",
|
|
1055
|
+
"enum_declaration",
|
|
1056
|
+
"extension_declaration",
|
|
1057
|
+
"actor_declaration",
|
|
1058
|
+
"typealias_declaration",
|
|
1059
|
+
"associatedtype_declaration",
|
|
1060
|
+
"type_annotation",
|
|
1061
|
+
"user_type",
|
|
1062
|
+
"import_declaration",
|
|
1063
|
+
"attribute",
|
|
1064
|
+
"enum_entry",
|
|
1065
|
+
"value_argument_label"
|
|
1066
|
+
]);
|
|
1067
|
+
const TYPE_DECLS = /* @__PURE__ */ new Set([
|
|
1068
|
+
"class_declaration",
|
|
1069
|
+
"protocol_declaration",
|
|
1070
|
+
"struct_declaration",
|
|
1071
|
+
"enum_declaration",
|
|
1072
|
+
"extension_declaration",
|
|
1073
|
+
"actor_declaration"
|
|
1074
|
+
]);
|
|
1075
|
+
const FUNC_SCOPES = /* @__PURE__ */ new Set([
|
|
1076
|
+
"function_declaration",
|
|
1077
|
+
"protocol_function_declaration",
|
|
1078
|
+
"init_declaration",
|
|
1079
|
+
"deinit_declaration"
|
|
1080
|
+
]);
|
|
1081
|
+
const VALUE_SCOPES = /* @__PURE__ */ new Set(["lambda_literal", "property_declaration"]);
|
|
1082
|
+
const isWrite = (n) => {
|
|
1083
|
+
let a = n.parent;
|
|
1084
|
+
while (a && (a.type === "directly_assignable_expression" || a.type === "postfix_expression" || a.type === "navigation_expression" || a.type === "navigation_suffix")) a = a.parent;
|
|
1085
|
+
if (a && a.type === "assignment") {
|
|
1086
|
+
const lhs = a.namedChild(0);
|
|
1087
|
+
return !!lhs && lhs.startIndex <= n.startIndex && lhs.endIndex >= n.endIndex;
|
|
1088
|
+
}
|
|
1089
|
+
return false;
|
|
1090
|
+
};
|
|
1091
|
+
const ownNameSpan = (scope) => {
|
|
1092
|
+
if (scope.type === "property_declaration") {
|
|
1093
|
+
const pat = this.findChildByType(scope, "pattern");
|
|
1094
|
+
const id = pat ? this.findChildByType(pat, "simple_identifier") : null;
|
|
1095
|
+
return id ? id.startIndex : -1;
|
|
1096
|
+
}
|
|
1097
|
+
return -1;
|
|
1098
|
+
};
|
|
1099
|
+
const collectBound = (node, out) => {
|
|
1100
|
+
const own = ownNameSpan(node);
|
|
1101
|
+
const w = [node];
|
|
1102
|
+
while (w.length > 0) {
|
|
1103
|
+
const m = w.pop();
|
|
1104
|
+
if (m.type === "simple_identifier" && m.startIndex !== own) {
|
|
1105
|
+
const pt = m.parent?.type ?? "";
|
|
1106
|
+
if (pt === "parameter" || pt === "pattern") out.add(m.text);
|
|
1107
|
+
}
|
|
1108
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
1109
|
+
const c = m.namedChild(i);
|
|
1110
|
+
if (c) w.push(c);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
const extendLocals = (locals, scope) => {
|
|
1115
|
+
const bound = /* @__PURE__ */ new Set();
|
|
1116
|
+
collectBound(scope, bound);
|
|
1117
|
+
if (bound.size === 0) return locals;
|
|
1118
|
+
const next = new Set(locals);
|
|
1119
|
+
for (const nm of bound) next.add(nm);
|
|
1120
|
+
return next;
|
|
1121
|
+
};
|
|
1122
|
+
const walk = (n, ty, fn, locals) => {
|
|
1123
|
+
const t = n.type;
|
|
1124
|
+
let cTy = ty;
|
|
1125
|
+
let cFn = fn;
|
|
1126
|
+
let cLocals = locals;
|
|
1127
|
+
if (TYPE_DECLS.has(t)) {
|
|
1128
|
+
const nameNode = this.firstDescendantOfType(n, "type_identifier") || this.firstDescendantOfType(n, "simple_identifier");
|
|
1129
|
+
if (nameNode) {
|
|
1130
|
+
cTy = nameNode.text;
|
|
1131
|
+
cFn = null;
|
|
1132
|
+
}
|
|
1133
|
+
} else if (this.isNominalRecovery(n)) {
|
|
1134
|
+
const nm = this.recoveryName(n);
|
|
1135
|
+
if (nm) {
|
|
1136
|
+
cTy = nm;
|
|
1137
|
+
cFn = null;
|
|
1138
|
+
}
|
|
1139
|
+
} else if (FUNC_SCOPES.has(t)) {
|
|
1140
|
+
cFn = t === "init_declaration" ? "constructor" : this.findChildByType(n, "simple_identifier")?.text ?? cFn;
|
|
1141
|
+
cLocals = extendLocals(locals, n);
|
|
1142
|
+
} else if (VALUE_SCOPES.has(t)) {
|
|
1143
|
+
cLocals = extendLocals(locals, n);
|
|
1144
|
+
}
|
|
1145
|
+
if (t === "simple_identifier") {
|
|
1146
|
+
const txt = n.text;
|
|
1147
|
+
const pt = n.parent?.type ?? "";
|
|
1148
|
+
let lookup = txt;
|
|
1149
|
+
if (lookup.startsWith("$")) {
|
|
1150
|
+
const rest = lookup.slice(1);
|
|
1151
|
+
if (!rest || this.isAllDigits(rest)) {
|
|
1152
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1153
|
+
const c = n.namedChild(i);
|
|
1154
|
+
if (c) walk(c, cTy, cFn, cLocals);
|
|
1155
|
+
}
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
lookup = rest;
|
|
1159
|
+
}
|
|
1160
|
+
lookup = this.stripNameQuoting(lookup);
|
|
1161
|
+
const isMember = !!cTy && (membersByOwner.get(cTy)?.has(lookup) ?? false);
|
|
1162
|
+
const isImplicitMember = pt === "prefix_expression" && (n.parent?.text ?? "").startsWith(".");
|
|
1163
|
+
if (!NOT_A_READ.has(pt) && !isImplicitMember && pt !== "navigation_suffix" && pt !== "call_expression" && !isWrite(n) && !cLocals.has(txt) && !cLocals.has(lookup) && isMember) {
|
|
1164
|
+
const source = cFn || cTy || "root";
|
|
1165
|
+
const key = `${source}\0uses\0${lookup}`;
|
|
1166
|
+
if (!emittedUses.has(key)) {
|
|
1167
|
+
emittedUses.add(key);
|
|
1168
|
+
relationships.push({
|
|
1169
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
1170
|
+
source,
|
|
1171
|
+
target: lookup,
|
|
1172
|
+
isExternal: false,
|
|
1173
|
+
weight: 0.7,
|
|
1174
|
+
confidence: 0.6,
|
|
1175
|
+
line_number: this.nodeLine(n),
|
|
1176
|
+
metadata: { isBareRead: true }
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1182
|
+
const c = n.namedChild(i);
|
|
1183
|
+
if (c) walk(c, cTy, cFn, cLocals);
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
walk(rootNode, null, null, /* @__PURE__ */ new Set());
|
|
1187
|
+
}
|
|
1188
|
+
// ==========================================================================
|
|
1189
|
+
// Override resolution (Java/C# shared machinery)
|
|
1190
|
+
// ==========================================================================
|
|
1191
|
+
/**
|
|
1192
|
+
* The PROJECT's cross-file declaration tables, read from
|
|
1193
|
+
* `AnalyzeContext.projectIndex` when a caller supplied one.
|
|
1194
|
+
*
|
|
1195
|
+
* WHY THIS EXISTS, WITH THE NUMBER. `overrides` resolves in three passes
|
|
1196
|
+
* (exact in-file BFS -> companion-verified declaring parent -> first-parent
|
|
1197
|
+
* guess at confidence 0.5). Pass 2 needs BOTH a companion (owner -> declared
|
|
1198
|
+
* members) and a way to walk bases it cannot see in this file. Measured over
|
|
1199
|
+
* the 1,256-file draw with `script/m14/swift-overrides-premise-probe.ts`:
|
|
1200
|
+
*
|
|
1201
|
+
* tier edges parent declares the method
|
|
1202
|
+
* pass1 in-file (1.0) 71 71 (100.0%)
|
|
1203
|
+
* pass3 first-parent (0.5) 210 87 ( 41.4%)
|
|
1204
|
+
*
|
|
1205
|
+
* so the guess's premise — "the first hierarchy parent declares the method" —
|
|
1206
|
+
* fails for 123 of 210 edges (58.6%). swift-nio shows the mechanism:
|
|
1207
|
+
* `final class PipeChannel: BaseStreamSocketChannel<PipePair>` overriding
|
|
1208
|
+
* `connectSocket`, where `connectSocket` is declared two levels up, on
|
|
1209
|
+
* `BaseSocketChannel` (Sources/NIOPosix/BaseSocketChannel.swift:479). The
|
|
1210
|
+
* guess names the DIRECT base, which does not declare it.
|
|
1211
|
+
*
|
|
1212
|
+
* Pass 2 could not fix that, and the reason is a wiring fact rather than a
|
|
1213
|
+
* logic one: Pass 2 reads `context.overrideCompanion`, and
|
|
1214
|
+
* `src/ast/parallel-indexer.ts` builds the batch companion only for the
|
|
1215
|
+
* languages its `PROJECT_INDEX_EXTENSIONS` map lists — today `".sol"` — so no
|
|
1216
|
+
* Swift caller has ever supplied one. `src/ast/pool-worker.ts`'s `parse_batch`
|
|
1217
|
+
* supplies `context.projectIndex` instead, and the Dart lane already reads
|
|
1218
|
+
* that channel (`dartCompanionFor`). This method is the Swift equivalent: it
|
|
1219
|
+
* reads the channel production is designed to fill, so the moment the indexer
|
|
1220
|
+
* is told to build an index for `.swift`, this lane needs no further change.
|
|
1221
|
+
*
|
|
1222
|
+
* ADDITIVE AND INERT WHEN ABSENT. With no `projectIndex` — a single-file
|
|
1223
|
+
* parse, every fixture, the differential — `companion` is undefined, `kinds`
|
|
1224
|
+
* is `context.typeKinds` exactly as before, and `getParents` returns the
|
|
1225
|
+
* per-file hierarchy, so output is byte-identical to before this method
|
|
1226
|
+
* existed. Nothing here decides anything on its own: it only supplies the
|
|
1227
|
+
* cross-file answers the file cannot see.
|
|
1228
|
+
*/
|
|
1229
|
+
crossFileTables(context, classHierarchy) {
|
|
1230
|
+
const pi = context?.projectIndex;
|
|
1231
|
+
const kinds = context?.typeKinds ?? (pi?.typeKinds instanceof Map ? pi.typeKinds : void 0);
|
|
1232
|
+
if (!pi || !(pi.typeMembers instanceof Map) || !(pi.typeBases instanceof Map)) {
|
|
1233
|
+
return { companion: void 0, kinds, getParents: (local) => (t) => local.get(t) ?? [] };
|
|
1234
|
+
}
|
|
1235
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1236
|
+
const closure = (owner, seen = /* @__PURE__ */ new Set()) => {
|
|
1237
|
+
const hit = cache.get(owner);
|
|
1238
|
+
if (hit) return hit;
|
|
1239
|
+
const out = /* @__PURE__ */ new Set();
|
|
1240
|
+
if (seen.has(owner)) return out;
|
|
1241
|
+
seen.add(owner);
|
|
1242
|
+
for (const m of pi.typeMembers.get(owner) ?? []) out.add(m);
|
|
1243
|
+
for (const b of pi.typeBases.get(owner) ?? []) {
|
|
1244
|
+
for (const m of closure(b, seen)) out.add(m);
|
|
1245
|
+
}
|
|
1246
|
+
cache.set(owner, out);
|
|
1247
|
+
return out;
|
|
1248
|
+
};
|
|
1249
|
+
const companion = /* @__PURE__ */ new Map();
|
|
1250
|
+
for (const owner of pi.typeMembers.keys()) companion.set(owner, closure(owner));
|
|
1251
|
+
return {
|
|
1252
|
+
companion,
|
|
1253
|
+
kinds,
|
|
1254
|
+
// The project's bases first, the file's own second: a type declared in
|
|
1255
|
+
// THIS file is authoritative here, and a type the file never mentions can
|
|
1256
|
+
// only come from the index.
|
|
1257
|
+
getParents: (local) => (t) => {
|
|
1258
|
+
const localParents = local.get(t);
|
|
1259
|
+
if (localParents && localParents.length > 0) return localParents;
|
|
1260
|
+
const projectParents = pi.typeBases.get(t);
|
|
1261
|
+
return projectParents && projectParents.length > 0 ? projectParents : [];
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
resolveOverrides(tasks, classHierarchy, symbolMap, relationships, companion, getParents) {
|
|
1266
|
+
const parentsOf = getParents ?? ((t) => classHierarchy.get(t) ?? []);
|
|
1267
|
+
for (const task of tasks) {
|
|
1268
|
+
let targetFqn = null;
|
|
1269
|
+
let confidence = 1;
|
|
1270
|
+
let reasonCode;
|
|
1271
|
+
const queue = [];
|
|
1272
|
+
const ownerH = classHierarchy.get(task.ownerFqn);
|
|
1273
|
+
if (ownerH) {
|
|
1274
|
+
for (let i = 0; i < ownerH.length; i++) queue.push(ownerH[i]);
|
|
1275
|
+
}
|
|
1276
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1277
|
+
while (queue.length > 0) {
|
|
1278
|
+
const parentName = queue.shift();
|
|
1279
|
+
if (seen.has(parentName)) continue;
|
|
1280
|
+
seen.add(parentName);
|
|
1281
|
+
const potentialTargetFqn = `${parentName}.${task.methodName}`;
|
|
1282
|
+
if (symbolMap.has(potentialTargetFqn)) {
|
|
1283
|
+
targetFqn = potentialTargetFqn;
|
|
1284
|
+
break;
|
|
1285
|
+
}
|
|
1286
|
+
for (const next of parentsOf(parentName)) queue.push(next);
|
|
1287
|
+
}
|
|
1288
|
+
if (!targetFqn && companion && ownerH && ownerH.length > 0) {
|
|
1289
|
+
const declaring = findDeclaringParent(task.methodName, ownerH, companion, parentsOf);
|
|
1290
|
+
if (declaring) {
|
|
1291
|
+
targetFqn = `${declaring}.${task.methodName}`;
|
|
1292
|
+
confidence = 0.9;
|
|
1293
|
+
reasonCode = "companion-index";
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (!targetFqn && ownerH && ownerH.length > 0) {
|
|
1297
|
+
targetFqn = `${ownerH[0]}.${task.methodName}`;
|
|
1298
|
+
confidence = 0.5;
|
|
1299
|
+
reasonCode = "first-parent-guess";
|
|
1300
|
+
}
|
|
1301
|
+
if (targetFqn) {
|
|
1302
|
+
relationships.push({
|
|
1303
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
1304
|
+
source: task.fqn,
|
|
1305
|
+
target: targetFqn,
|
|
1306
|
+
isExternal: true,
|
|
1307
|
+
weight: 0.8,
|
|
1308
|
+
confidence,
|
|
1309
|
+
...typeof task.line === "number" ? { line_number: task.line } : {},
|
|
1310
|
+
metadata: { relationshipType: "method_override", isSwiftOverride: true, ...reasonCode ? { reasonCode } : {} }
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
// ==========================================================================
|
|
1316
|
+
// Symbol creation / helpers
|
|
1317
|
+
// ==========================================================================
|
|
1318
|
+
/**
|
|
1319
|
+
* #924 (swift half): Swift's keyword-escape quoting is NOT part of a name.
|
|
1320
|
+
* ``case `continue` `` declares the identifier `continue`; ``static var
|
|
1321
|
+
* `default` `` declares `default` — and every REFERENCE in the corpus spells
|
|
1322
|
+
* them without backticks (`flags: Flags = .default`), so a published name that
|
|
1323
|
+
* keeps the syntax is a name no reference can ever match. Measured at full
|
|
1324
|
+
* extent (`script/m14/tmp/sw-name-census.ts`): 29 symbols and 40 targets
|
|
1325
|
+
* carried the backticks. Exactly one surrounding pair is stripped; a name
|
|
1326
|
+
* that merely contains a backtick keeps it.
|
|
1327
|
+
*/
|
|
1328
|
+
stripNameQuoting(text) {
|
|
1329
|
+
if (text.length >= 2 && text.charCodeAt(0) === 96 && text.charCodeAt(text.length - 1) === 96) {
|
|
1330
|
+
return text.slice(1, -1);
|
|
1331
|
+
}
|
|
1332
|
+
return text;
|
|
1333
|
+
}
|
|
1334
|
+
createSymbol(name, kind, node, parent, symbols, meta = {}, visibility = "internal", declaredVisibility = null, localScope = false) {
|
|
1335
|
+
name = this.stripNameQuoting(name);
|
|
1336
|
+
if (parent && parent.kind === ASTEngine.SymbolKind.Extension && declaredVisibility === null && parent.visibility) {
|
|
1337
|
+
visibility = parent.visibility;
|
|
1338
|
+
}
|
|
1339
|
+
const functionScoped = localScope || !!parent && (parent.kind === ASTEngine.SymbolKind.Function || parent.kind === ASTEngine.SymbolKind.Method || parent.kind === ASTEngine.SymbolKind.Constructor);
|
|
1340
|
+
const owner = functionScoped ? null : parent;
|
|
1341
|
+
const symbol = {
|
|
1342
|
+
id: parent ? `${parent.id}.${name}` : name,
|
|
1343
|
+
name,
|
|
1344
|
+
kind,
|
|
1345
|
+
range: {
|
|
1346
|
+
startLine: node.startPosition.row + 1,
|
|
1347
|
+
startColumn: node.startPosition.column + 1,
|
|
1348
|
+
endLine: node.endPosition.row + 1,
|
|
1349
|
+
endColumn: node.endPosition.column + 1,
|
|
1350
|
+
startIndex: node.startIndex,
|
|
1351
|
+
endIndex: node.endIndex
|
|
1352
|
+
},
|
|
1353
|
+
parentId: owner ? owner.id : void 0,
|
|
1354
|
+
ownerName: owner ? owner.name : void 0,
|
|
1355
|
+
parent: owner ? owner.name : void 0,
|
|
1356
|
+
metadata: meta,
|
|
1357
|
+
visibility
|
|
1358
|
+
};
|
|
1359
|
+
if (meta.isAsync) symbol.isAsync = true;
|
|
1360
|
+
if (meta.throws) symbol.throws = "true";
|
|
1361
|
+
if (meta.annotations) symbol.annotations = JSON.stringify(meta.annotations);
|
|
1362
|
+
symbols.push(symbol);
|
|
1363
|
+
return symbol;
|
|
1364
|
+
}
|
|
1365
|
+
collectFunctionMeta(node, meta, symbol) {
|
|
1366
|
+
let isAsync = false;
|
|
1367
|
+
let throws = false;
|
|
1368
|
+
const nodeQueue = [node];
|
|
1369
|
+
while (nodeQueue.length > 0) {
|
|
1370
|
+
const c = nodeQueue.pop();
|
|
1371
|
+
if (c.text === "async" || c.type === "async") isAsync = true;
|
|
1372
|
+
if (c.type === "throw_keyword" || c.text === "throws" || c.type === "throws") throws = true;
|
|
1373
|
+
for (let i = c.childCount - 1; i >= 0; i--) {
|
|
1374
|
+
if (c.child(i)) nodeQueue.push(c.child(i));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (isAsync) {
|
|
1378
|
+
meta.isAsync = true;
|
|
1379
|
+
symbol.isAsync = true;
|
|
1380
|
+
}
|
|
1381
|
+
if (throws) {
|
|
1382
|
+
meta.throws = true;
|
|
1383
|
+
symbol.throws = "true";
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* The declaration's OWN access-level modifier, or null when it states none.
|
|
1388
|
+
*
|
|
1389
|
+
* The null case is load-bearing (DERIV_EXPORTS_SWIFT4): a member of an
|
|
1390
|
+
* extension INHERITS the extension's level only when it declares none of its
|
|
1391
|
+
* own, so "no modifier" and "explicitly internal" must be distinguishable —
|
|
1392
|
+
* `getVisibility` returns "internal" for both, which is right for the symbol
|
|
1393
|
+
* field and wrong for the inheritance rule.
|
|
1394
|
+
*/
|
|
1395
|
+
getDeclaredVisibility(node) {
|
|
1396
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1397
|
+
const child = node.namedChild(i);
|
|
1398
|
+
if (child && child.type === "modifiers") {
|
|
1399
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
1400
|
+
const mod = child.namedChild(j);
|
|
1401
|
+
if (mod) {
|
|
1402
|
+
if (mod.text === "public") return "public";
|
|
1403
|
+
if (mod.text === "private") return "private";
|
|
1404
|
+
if (mod.text === "fileprivate") return "fileprivate";
|
|
1405
|
+
if (mod.text === "internal") return "internal";
|
|
1406
|
+
if (mod.text === "open") return "open";
|
|
1407
|
+
if (mod.text === "package") return "package";
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
getVisibility(node) {
|
|
1415
|
+
return this.getDeclaredVisibility(node) ?? "internal";
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* The EXPORTING access levels, or null when the declaration is not part of the
|
|
1419
|
+
* module's interface. Swift has no `export` keyword (measured: 150 node types
|
|
1420
|
+
* over 400 corpus files, none an export declaration, 0 anonymous `export`
|
|
1421
|
+
* tokens) — its export construct IS access control. What counts as an export
|
|
1422
|
+
* is measured, over 3,656 files (script/m14/swift-import-export-probe.ts) with
|
|
1423
|
+
* 52,113 file-scope declarations as the denominator: 5,708 `public`, 43
|
|
1424
|
+
* `open`, against 1,325 explicit `internal`, 1,470 `private`, 195
|
|
1425
|
+
* `fileprivate` and 43,368 with no modifier at all — 5,751 of 52,113 (11.04%).
|
|
1426
|
+
* `internal`, `private`, `fileprivate` and the no-modifier default are the
|
|
1427
|
+
* ABSENCE of an export, so they are not in this set. `package` IS in the
|
|
1428
|
+
* condition below as a declared grammar-version boundary, not a measured one:
|
|
1429
|
+
* the pinned grammar (npm tree-sitter-swift 0.7.1) cannot reduce it — all six
|
|
1430
|
+
* synthetic shapes parse to an ERROR while `public func` reduces cleanly, and
|
|
1431
|
+
* the corpus probe finds 0 `package` visibility_modifier nodes in 3,656 files
|
|
1432
|
+
* — so it cannot fire on this corpus and this lane does not claim it does.
|
|
1433
|
+
*/
|
|
1434
|
+
exportingVisibility(node) {
|
|
1435
|
+
const vis = this.getDeclaredVisibility(node);
|
|
1436
|
+
if (vis === "public" || vis === "open" || vis === "package") return vis;
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* The MODULE-LEVEL SOURCE of an `exports` edge: the file's own leaf name
|
|
1441
|
+
* (typescript's moduleLeafName() convention), e.g.
|
|
1442
|
+
* `…/fixtures/swift/exports-visibility.swift` -> `exports-visibility`.
|
|
1443
|
+
*
|
|
1444
|
+
* FOREIGN ON PURPOSE: never `root`. `root` is in parallel-indexer's
|
|
1445
|
+
* SYMBOL_NAME_DENYLIST, so an edge sourced there can never bind to a stored
|
|
1446
|
+
* symbol; the leaf name is what every other lane spells for a file-as-module
|
|
1447
|
+
* endpoint (go's packageName, python's moduleName). This is a NAME for the
|
|
1448
|
+
* file the edge was read from, not a claim about SwiftPM's module name, which
|
|
1449
|
+
* no Swift source file contains.
|
|
1450
|
+
*/
|
|
1451
|
+
moduleLeafName(filePath) {
|
|
1452
|
+
let start = 0;
|
|
1453
|
+
for (let i = filePath.length - 1; i >= 0; i--) {
|
|
1454
|
+
if (filePath[i] === "/") {
|
|
1455
|
+
start = i + 1;
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
let end = filePath.length;
|
|
1460
|
+
for (let i = start; i < filePath.length; i++) {
|
|
1461
|
+
if (filePath[i] === ".") {
|
|
1462
|
+
end = i;
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return filePath.slice(start, end);
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* DERIV_EXPORTS_SWIFT4 — does this declaration publish a name for the module?
|
|
1470
|
+
*
|
|
1471
|
+
* Returns the kind the export row carries, or null when the declaration
|
|
1472
|
+
* exports nothing. Three gates, in order:
|
|
1473
|
+
*
|
|
1474
|
+
* 1. an EXPORTING access level (`exportingVisibility`): `public`/`open`/
|
|
1475
|
+
* `package` and nothing else;
|
|
1476
|
+
* 2. NOT an extension: an extension symbol is named after the EXTENDED type
|
|
1477
|
+
* (`public extension Array`), so exporting it would name a declaration
|
|
1478
|
+
* this file does not contain — measured at 54 file-scope sites in a
|
|
1479
|
+
* 3,656-file draw, all of which would be fabrications;
|
|
1480
|
+
* 3. the declaration KINDS that can carry a level: a nominal
|
|
1481
|
+
* (`class`/`struct`/`enum`/`actor`), a protocol, a function, a
|
|
1482
|
+
* property binding and a typealias.
|
|
1483
|
+
*
|
|
1484
|
+
* The caller gates on `parentSymbol === null` (file scope) — a member and a
|
|
1485
|
+
* function-local type never reach here.
|
|
1486
|
+
*/
|
|
1487
|
+
isExportingDeclaration(node) {
|
|
1488
|
+
if (this.exportingVisibility(node) === null) return null;
|
|
1489
|
+
if (node.type === "class_declaration" && this.hasAnonToken(node, "extension")) return null;
|
|
1490
|
+
if (node.type === "ERROR") {
|
|
1491
|
+
if (!this.isNominalRecovery(node)) return null;
|
|
1492
|
+
return this.recoveryKind(node);
|
|
1493
|
+
}
|
|
1494
|
+
if (node.type === "class_declaration") {
|
|
1495
|
+
const valueType = this.hasAnonToken(node, "struct") || this.hasAnonToken(node, "enum");
|
|
1496
|
+
if (valueType) {
|
|
1497
|
+
if (this.hasAnonToken(node, "struct")) return ASTEngine.SymbolKind.Struct;
|
|
1498
|
+
if (this.hasAnonToken(node, "enum")) return ASTEngine.SymbolKind.Enum;
|
|
1499
|
+
}
|
|
1500
|
+
if (this.hasAnonToken(node, "actor")) return ASTEngine.SymbolKind.Actor;
|
|
1501
|
+
return ASTEngine.SymbolKind.Class;
|
|
1502
|
+
}
|
|
1503
|
+
if (node.type === "protocol_declaration") return ASTEngine.SymbolKind.Interface;
|
|
1504
|
+
if (node.type === "function_declaration" || node.type === "protocol_function_declaration") {
|
|
1505
|
+
return ASTEngine.SymbolKind.Function;
|
|
1506
|
+
}
|
|
1507
|
+
if (node.type === "property_declaration" || node.type === "protocol_property_declaration") {
|
|
1508
|
+
return ASTEngine.SymbolKind.Variable;
|
|
1509
|
+
}
|
|
1510
|
+
if (node.type === "typealias_declaration") return ASTEngine.SymbolKind.Type;
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* The NAME (and its node, for the line rule) a declaration publishes — one
|
|
1515
|
+
* entry per binding, because a multi-binding declaration (`var a = 1, b = 2`)
|
|
1516
|
+
* carries a sibling `pattern` per binding (SWIFTD18) and publishing only the
|
|
1517
|
+
* first would silently drop the rest.
|
|
1518
|
+
*
|
|
1519
|
+
* Polled by the export emission only: the symbol table already carries these
|
|
1520
|
+
* names, but the export LINE must come from the declaration's own name node
|
|
1521
|
+
* (a decorated declaration's line is its NAME's line, never the
|
|
1522
|
+
* decorator's — pinned by the `Decorated` row of swift/exports-visibility and
|
|
1523
|
+
* mutant SW-EXP-14).
|
|
1524
|
+
*/
|
|
1525
|
+
exportedDeclarationNames(node) {
|
|
1526
|
+
const out = [];
|
|
1527
|
+
if (node.type === "property_declaration" || node.type === "protocol_property_declaration") {
|
|
1528
|
+
const pat = this.findChildByType(node, "pattern");
|
|
1529
|
+
if (pat) {
|
|
1530
|
+
const pats = [];
|
|
1531
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1532
|
+
const c = node.namedChild(i);
|
|
1533
|
+
if (c && c.type === "pattern") pats.push(c);
|
|
1534
|
+
}
|
|
1535
|
+
for (const pn of pats) {
|
|
1536
|
+
const idNode = this.findChildByType(pn, "simple_identifier") || this.findChildByType(pn, "identifier") || this.firstNamedChild(pn);
|
|
1537
|
+
if (idNode) out.push({ name: idNode.text, node: idNode });
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return out;
|
|
1541
|
+
}
|
|
1542
|
+
if (node.type === "protocol_declaration") {
|
|
1543
|
+
const nm = this.findChildByType(node, "type_identifier");
|
|
1544
|
+
if (nm) out.push({ name: nm.text, node: nm });
|
|
1545
|
+
return out;
|
|
1546
|
+
}
|
|
1547
|
+
if (node.type === "class_declaration") {
|
|
1548
|
+
const nm = this.findChildByType(node, "type_identifier") || this.findChildByType(node, "user_type");
|
|
1549
|
+
if (nm) {
|
|
1550
|
+
const leaf = nm.type === "user_type" ? this.lastTypeIdentifier(nm) : nm;
|
|
1551
|
+
if (leaf) out.push({ name: leaf.text, node: leaf });
|
|
1552
|
+
}
|
|
1553
|
+
return out;
|
|
1554
|
+
}
|
|
1555
|
+
if (node.type === "function_declaration" || node.type === "protocol_function_declaration") {
|
|
1556
|
+
const nm = this.findChildByType(node, "simple_identifier") || this.findChildByType(node, "identifier");
|
|
1557
|
+
if (nm) out.push({ name: nm.text, node: nm });
|
|
1558
|
+
else {
|
|
1559
|
+
const opName = this.operatorName(node);
|
|
1560
|
+
if (opName) out.push({ name: opName, node });
|
|
1561
|
+
}
|
|
1562
|
+
return out;
|
|
1563
|
+
}
|
|
1564
|
+
if (node.type === "typealias_declaration") {
|
|
1565
|
+
const nm = this.findChildByType(node, "type_identifier") || this.firstNamedChild(node);
|
|
1566
|
+
if (nm) out.push({ name: nm.text, node: nm });
|
|
1567
|
+
return out;
|
|
1568
|
+
}
|
|
1569
|
+
if (node.type === "ERROR") {
|
|
1570
|
+
const nm = this.recoveryNameNode(node);
|
|
1571
|
+
if (nm) out.push({ name: nm.text, node: nm });
|
|
1572
|
+
return out;
|
|
1573
|
+
}
|
|
1574
|
+
return out;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* DERIV_EXPORTS_SWIFT4 — publish ONE file-scope declaration as an `exports`
|
|
1578
|
+
* edge and an `ExportInfo` row.
|
|
1579
|
+
*
|
|
1580
|
+
* `swiftExport: true` is the marker the CLASSIFICATION keys on indirectly
|
|
1581
|
+
* (`reExport` absent -> structural): the access level is a grammar clause read
|
|
1582
|
+
* off the declaration's own `modifiers` node, so nothing here is inferred and
|
|
1583
|
+
* no reason code is owed (honesty contract ).
|
|
1584
|
+
*/
|
|
1585
|
+
emitDeclarationExport(node, moduleSource, relationships, exports, emitted) {
|
|
1586
|
+
const vis = this.exportingVisibility(node);
|
|
1587
|
+
if (!vis) return;
|
|
1588
|
+
const kind = this.isExportingDeclaration(node) ?? ASTEngine.SymbolKind.Class;
|
|
1589
|
+
for (const resolved of this.exportedDeclarationNames(node)) {
|
|
1590
|
+
this.publishDeclarationExport(node, resolved, kind, vis, moduleSource, relationships, exports, emitted);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
/** One published NAME: the row, the edge, and the (source, name) dedupe. */
|
|
1594
|
+
publishDeclarationExport(node, resolved, kind, vis, moduleSource, relationships, exports, emitted) {
|
|
1595
|
+
const name = this.stripNameQuoting(resolved.name);
|
|
1596
|
+
if (!name || emitted.has(name)) return;
|
|
1597
|
+
const nameNode = resolved.node;
|
|
1598
|
+
if (nameNode.startIndex < node.startIndex || nameNode.endIndex > node.endIndex) return;
|
|
1599
|
+
emitted.add(name);
|
|
1600
|
+
exports.push({
|
|
1601
|
+
name,
|
|
1602
|
+
kind,
|
|
1603
|
+
range: {
|
|
1604
|
+
startLine: nameNode.startPosition.row + 1,
|
|
1605
|
+
startColumn: nameNode.startPosition.column + 1,
|
|
1606
|
+
endLine: nameNode.endPosition.row + 1,
|
|
1607
|
+
endColumn: nameNode.endPosition.column + 1,
|
|
1608
|
+
startIndex: nameNode.startIndex,
|
|
1609
|
+
endIndex: nameNode.endIndex
|
|
1610
|
+
},
|
|
1611
|
+
visibility: vis,
|
|
1612
|
+
metadata: { exportForm: "visibility_modifier", accessLevel: vis }
|
|
1613
|
+
});
|
|
1614
|
+
relationships.push({
|
|
1615
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
1616
|
+
source: moduleSource,
|
|
1617
|
+
target: name,
|
|
1618
|
+
isExternal: false,
|
|
1619
|
+
weight: 1,
|
|
1620
|
+
confidence: 1,
|
|
1621
|
+
...typeof this.nodeLine(nameNode) === "number" ? { line_number: this.nodeLine(nameNode) } : {},
|
|
1622
|
+
metadata: { exportForm: "visibility_modifier", accessLevel: vis, swiftExport: true }
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
/** `@_exported` — the underscored re-export attribute, by its NAME node. */
|
|
1626
|
+
hasExportedAttribute(node) {
|
|
1627
|
+
return this.hasAttributeNamed(node, "_exported");
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* The form in which an import RE-PUBLISHES its module, or null for an
|
|
1631
|
+
* ordinary import (which is a dependency and nothing else).
|
|
1632
|
+
*
|
|
1633
|
+
* Two forms, both pinned by swift/exports-visibility:
|
|
1634
|
+
* - `public import M` (SE-0409) — the `public` modifier on the import;
|
|
1635
|
+
* - `@_exported import M` — Swift's literal re-export attribute.
|
|
1636
|
+
* `public` is checked first so a hypothetical `@_exported public import M`
|
|
1637
|
+
* keeps the access-controlled form; both publish the same fact.
|
|
1638
|
+
*/
|
|
1639
|
+
importReExportForm(node) {
|
|
1640
|
+
let reExportForm = null;
|
|
1641
|
+
if (node.type === "import_declaration") {
|
|
1642
|
+
if (this.exportingVisibility(node) === "public") reExportForm = "public_import";
|
|
1643
|
+
else if (this.hasExportedAttribute(node)) reExportForm = "underscored_exported_import";
|
|
1644
|
+
}
|
|
1645
|
+
return reExportForm;
|
|
1646
|
+
}
|
|
1647
|
+
extractAttributes(node) {
|
|
1648
|
+
const attrs = [];
|
|
1649
|
+
const queue = [node];
|
|
1650
|
+
while (queue.length > 0) {
|
|
1651
|
+
const c = queue.pop();
|
|
1652
|
+
if (c.type === "attribute") {
|
|
1653
|
+
const text = c.text.trim();
|
|
1654
|
+
if (text.startsWith("@")) attrs.push(text);
|
|
1655
|
+
}
|
|
1656
|
+
for (let i = c.childCount - 1; i >= 0; i--) {
|
|
1657
|
+
if (c.child(i)) queue.push(c.child(i));
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return attrs;
|
|
1661
|
+
}
|
|
1662
|
+
handleImport(node, imports, frameworksObj, reExports) {
|
|
1663
|
+
let spelled = "";
|
|
1664
|
+
let moduleNode = null;
|
|
1665
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1666
|
+
const c = node.child(i);
|
|
1667
|
+
if (c && (c.type === "simple_identifier" || c.type === "type_identifier" || c.type === "identifier")) {
|
|
1668
|
+
spelled = c.text;
|
|
1669
|
+
moduleNode = c;
|
|
1670
|
+
break;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
const moduleName = spelled.split(".")[0] ?? "";
|
|
1674
|
+
let scopeKind = "";
|
|
1675
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1676
|
+
const c = node.child(i);
|
|
1677
|
+
if (!c || c.isNamed) continue;
|
|
1678
|
+
if (c.type === "import") continue;
|
|
1679
|
+
scopeKind = c.type;
|
|
1680
|
+
break;
|
|
1681
|
+
}
|
|
1682
|
+
if (moduleName) {
|
|
1683
|
+
const site = moduleNode ?? node;
|
|
1684
|
+
const range = {
|
|
1685
|
+
startLine: site.startPosition.row + 1,
|
|
1686
|
+
startColumn: site.startPosition.column + 1,
|
|
1687
|
+
endLine: site.endPosition.row + 1,
|
|
1688
|
+
endColumn: site.endPosition.column + 1,
|
|
1689
|
+
startIndex: site.startIndex,
|
|
1690
|
+
endIndex: site.endIndex
|
|
1691
|
+
};
|
|
1692
|
+
const importMd = {
|
|
1693
|
+
module: moduleName,
|
|
1694
|
+
moduleSpecifier: spelled,
|
|
1695
|
+
isModuleDependency: true
|
|
1696
|
+
};
|
|
1697
|
+
if (spelled !== moduleName) importMd.submodulePath = spelled;
|
|
1698
|
+
if (scopeKind.length > 0) {
|
|
1699
|
+
importMd.isScopedImport = true;
|
|
1700
|
+
importMd.scopedKind = scopeKind;
|
|
1701
|
+
const item = spelled.split(".").slice(1).join(".");
|
|
1702
|
+
if (item.length > 0) importMd.scopedItem = item;
|
|
1703
|
+
}
|
|
1704
|
+
const reExportForm = this.importReExportForm(node);
|
|
1705
|
+
if (reExportForm) {
|
|
1706
|
+
importMd.isModuleReExport = true;
|
|
1707
|
+
importMd.reExportForm = reExportForm;
|
|
1708
|
+
importMd.reExport = true;
|
|
1709
|
+
}
|
|
1710
|
+
imports.push({ module: spelled, isExternal: true, range, metadata: importMd });
|
|
1711
|
+
if (reExportForm) {
|
|
1712
|
+
reExports.push({ module: spelled, node: site, form: reExportForm });
|
|
1713
|
+
}
|
|
1714
|
+
if (moduleName === "SwiftUI") frameworksObj["SwiftUI"] = true;
|
|
1715
|
+
if (moduleName === "Combine") frameworksObj["Combine"] = true;
|
|
1716
|
+
if (moduleName === "UIKit") frameworksObj["UIKit"] = true;
|
|
1717
|
+
if (moduleName === "Foundation") frameworksObj["Foundation"] = true;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* The NAME of an `attribute` node: its first named child.
|
|
1722
|
+
*
|
|
1723
|
+
* The attribute arrives as `import_declaration > modifiers > attribute`, and
|
|
1724
|
+
* the pinned grammar spells it `('@' (user_type (type_identifier)))` — so the
|
|
1725
|
+
* attribute's NAME is its first named child's text, never the attribute node's
|
|
1726
|
+
* own text and never the file's text. Reading the name node is what stops
|
|
1727
|
+
* `@objc(name: "_exported")` and a comment from producing a hit, and it is the
|
|
1728
|
+
* same rule the census's probe P2 uses on the other side of the comparison.
|
|
1729
|
+
*/
|
|
1730
|
+
attributeName(node) {
|
|
1731
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1732
|
+
const c = node.namedChild(i);
|
|
1733
|
+
if (c) return c.text;
|
|
1734
|
+
}
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
hasAttributeNamed(node, want) {
|
|
1738
|
+
const stack = [node];
|
|
1739
|
+
while (stack.length > 0) {
|
|
1740
|
+
const n = stack.pop();
|
|
1741
|
+
if (n.type === "attribute" && this.attributeName(n) === want) return true;
|
|
1742
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1743
|
+
const c = n.namedChild(i);
|
|
1744
|
+
if (c) stack.push(c);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
return false;
|
|
1748
|
+
}
|
|
1749
|
+
// ---- tree helpers (pure AST, no source scanning) ----
|
|
1750
|
+
findChildByType(node, type) {
|
|
1751
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1752
|
+
const c = node.namedChild(i);
|
|
1753
|
+
if (c && c.type === type) return c;
|
|
1754
|
+
}
|
|
1755
|
+
return null;
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Depth-first (pre-order) first named descendant of the given type. Needed
|
|
1759
|
+
* where the node of interest is wrapped: a type declaration's name sits in a
|
|
1760
|
+
* direct `type_identifier` child for `struct Foo`, but under `user_type` for
|
|
1761
|
+
* `extension Foo` — so a shallow search finds nothing and the declaration
|
|
1762
|
+
* reads as nameless (#754 corpus finding).
|
|
1763
|
+
*/
|
|
1764
|
+
firstDescendantOfType(node, type) {
|
|
1765
|
+
const stack = [node];
|
|
1766
|
+
while (stack.length > 0) {
|
|
1767
|
+
const x = stack.pop();
|
|
1768
|
+
if (x.type === type) return x;
|
|
1769
|
+
for (let i = x.namedChildCount - 1; i >= 0; i--) {
|
|
1770
|
+
const c = x.namedChild(i);
|
|
1771
|
+
if (c) stack.push(c);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return null;
|
|
1775
|
+
}
|
|
1776
|
+
hasChildType(node, type) {
|
|
1777
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1778
|
+
const c = node.namedChild(i);
|
|
1779
|
+
if (c && c.type === type) return true;
|
|
1780
|
+
}
|
|
1781
|
+
return false;
|
|
1782
|
+
}
|
|
1783
|
+
firstNamedChild(node) {
|
|
1784
|
+
return node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Per-symbol cyclomatic complexity (managed-OO family contract — csharp
|
|
1788
|
+
* #264 wave precedent). Mirrors the file-level counter's node set, adapted
|
|
1789
|
+
* to the pinned swift grammar (switch_entry per case, guard_statement,
|
|
1790
|
+
* catch_block, conjunction/disjunction/ternary expressions); base 1.
|
|
1791
|
+
*/
|
|
1792
|
+
calculateCyclomaticComplexity(node) {
|
|
1793
|
+
let complexity = 1;
|
|
1794
|
+
const complexityNodes = /* @__PURE__ */ new Set([
|
|
1795
|
+
"if_statement",
|
|
1796
|
+
"for_statement",
|
|
1797
|
+
"while_statement",
|
|
1798
|
+
"repeat_while_statement",
|
|
1799
|
+
"switch_entry",
|
|
1800
|
+
"guard_statement",
|
|
1801
|
+
"catch_block",
|
|
1802
|
+
"conjunction_expression",
|
|
1803
|
+
"disjunction_expression",
|
|
1804
|
+
"ternary_expression"
|
|
1805
|
+
]);
|
|
1806
|
+
const stack = [node];
|
|
1807
|
+
let guard = 0;
|
|
1808
|
+
while (stack.length > 0 && guard++ < 2e5) {
|
|
1809
|
+
const n = stack.pop();
|
|
1810
|
+
if (complexityNodes.has(n.type)) complexity++;
|
|
1811
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1812
|
+
const c = n.namedChild(i);
|
|
1813
|
+
if (c) stack.push(c);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return complexity;
|
|
1817
|
+
}
|
|
1818
|
+
/** `x[key]` parses as call_expression with a `[key]` call_suffix. */
|
|
1819
|
+
isSubscriptRead(node) {
|
|
1820
|
+
const suffix = this.findChildByType(node, "call_suffix");
|
|
1821
|
+
if (!suffix) return false;
|
|
1822
|
+
const args = this.findChildByType(suffix, "value_arguments");
|
|
1823
|
+
return !!args && this.hasAnonToken(args, "[");
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Operator overload name: the token immediately after `func`.
|
|
1827
|
+
*
|
|
1828
|
+
* The grammar reads most operator tokens as ANONYMOUS children (`==`, `+`,
|
|
1829
|
+
* `<`, `===`) but falls back to a NAMED `custom_operator` node for sequences
|
|
1830
|
+
* it has no reserved token for — measured on the pinned wasm: `func !==` and
|
|
1831
|
+
* `func ~=` both parse as `custom_operator`, `func ===`/`func <`/`func ==` as
|
|
1832
|
+
* anonymous tokens. The pre-fix loop accepted only anonymous children, so the
|
|
1833
|
+
* named forms fell through to the first anonymous child that was not a paren
|
|
1834
|
+
* — the parameter comma — and minted `operator ,`: measured in the corpus as
|
|
1835
|
+
* a symbol and an `exports` edge naming `operator ,` in
|
|
1836
|
+
* swift-corpus/SwiftCompilerSources/Sources/SIL/ApplySite.swift (the `~=`
|
|
1837
|
+
* overload), SIL/Instruction.swift and COM/IUnknown+Identity.swift (the `!==`
|
|
1838
|
+
* overloads). A comma is not an operator Swift can declare, so that endpoint
|
|
1839
|
+
* was FABRICATED, the defect class INCOMING_SWIFT JOB 2 forbids.
|
|
1840
|
+
*
|
|
1841
|
+
* `custom_operator` is the only named node accepted here; a named
|
|
1842
|
+
* `simple_identifier` is a normal function name (return null), and anything
|
|
1843
|
+
* else before the first paren is not a name token.
|
|
1844
|
+
*/
|
|
1845
|
+
operatorName(node) {
|
|
1846
|
+
let sawFunc = false;
|
|
1847
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1848
|
+
const c = node.child(i);
|
|
1849
|
+
if (!c) continue;
|
|
1850
|
+
if (!c.isNamed && c.type === "func") {
|
|
1851
|
+
sawFunc = true;
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
if (sawFunc) {
|
|
1855
|
+
if (c.isNamed && c.type === "simple_identifier") return null;
|
|
1856
|
+
if (c.isNamed && c.type === "custom_operator") return `operator ${c.text}`;
|
|
1857
|
+
if (!c.isNamed && c.type !== "(" && c.type !== ")" && c.type !== "{") {
|
|
1858
|
+
return `operator ${c.text}`;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
return null;
|
|
1863
|
+
}
|
|
1864
|
+
/** `defer { ... }` parses as call_expression(simple_identifier "defer", call_suffix > lambda_literal). */
|
|
1865
|
+
isDeferCall(node) {
|
|
1866
|
+
const fn = this.findChildByType(node, "simple_identifier") || this.findChildByType(node, "identifier");
|
|
1867
|
+
if (!fn || fn.text !== "defer") return false;
|
|
1868
|
+
const suffix = this.findChildByType(node, "call_suffix");
|
|
1869
|
+
return !!suffix && this.findChildByType(suffix, "lambda_literal") !== null;
|
|
1870
|
+
}
|
|
1871
|
+
/** Match an anonymous keyword token (`struct`, `enum`, `extension`, `actor`, `override`, `self`). */
|
|
1872
|
+
hasAnonToken(node, tokenType) {
|
|
1873
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1874
|
+
const c = node.child(i);
|
|
1875
|
+
if (c && !c.isNamed && c.type === tokenType) return true;
|
|
1876
|
+
}
|
|
1877
|
+
return false;
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Walk the navigation RECEIVER chain: navigation_expression > (nested
|
|
1881
|
+
* navigation_expression | self_expression | anonymous self token). Stops at
|
|
1882
|
+
* the first non-receiver child (a call_expression/try_expression) — the
|
|
1883
|
+
* suffix then applies to that call's RESULT, not to self.
|
|
1884
|
+
*/
|
|
1885
|
+
hasSelfReceiver(node) {
|
|
1886
|
+
let cur = node;
|
|
1887
|
+
let guard = 0;
|
|
1888
|
+
while (cur && guard++ < 64) {
|
|
1889
|
+
if (cur.type === "self_expression" || cur.type === "self" && !cur.isNamed) return true;
|
|
1890
|
+
let next = null;
|
|
1891
|
+
for (let i = 0; i < cur.namedChildCount; i++) {
|
|
1892
|
+
const c = cur.namedChild(i);
|
|
1893
|
+
if (!c) continue;
|
|
1894
|
+
if (c.type === "navigation_expression" || c.type === "self_expression") {
|
|
1895
|
+
next = c;
|
|
1896
|
+
break;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (!next && cur.type === "navigation_expression") {
|
|
1900
|
+
const spine = cur.namedChild(0);
|
|
1901
|
+
if (spine && (spine.type === "additive_expression" || spine.type === "prefix_expression")) {
|
|
1902
|
+
let selfChild = null;
|
|
1903
|
+
for (let i = 0; i < spine.namedChildCount; i++) {
|
|
1904
|
+
const c = spine.namedChild(i);
|
|
1905
|
+
if (c?.type === "self_expression") {
|
|
1906
|
+
selfChild = c;
|
|
1907
|
+
break;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
if (selfChild) return true;
|
|
1911
|
+
next = spine;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
if (!next) return false;
|
|
1915
|
+
cur = next;
|
|
1916
|
+
}
|
|
1917
|
+
return false;
|
|
1918
|
+
}
|
|
1919
|
+
/** Match an anonymous keyword token anywhere in the subtree (nested modifier tokens). */
|
|
1920
|
+
hasDescendantAnonToken(node, tokenType) {
|
|
1921
|
+
const stack = [node];
|
|
1922
|
+
while (stack.length > 0) {
|
|
1923
|
+
const c = stack.pop();
|
|
1924
|
+
if (c.type === tokenType && !c.isNamed) return true;
|
|
1925
|
+
for (let i = c.childCount - 1; i >= 0; i--) {
|
|
1926
|
+
const child = c.child(i);
|
|
1927
|
+
if (child) stack.push(child);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
return false;
|
|
1931
|
+
}
|
|
1932
|
+
/** For extensions: the type_identifier inside the user_type (the extended type). */
|
|
1933
|
+
findDescendantTypeIdentifier(node) {
|
|
1934
|
+
const stack = [node];
|
|
1935
|
+
while (stack.length > 0) {
|
|
1936
|
+
const c = stack.pop();
|
|
1937
|
+
if (c.type === "type_identifier") return c;
|
|
1938
|
+
for (let i = c.namedChildCount - 1; i >= 0; i--) {
|
|
1939
|
+
const child = c.namedChild(i);
|
|
1940
|
+
if (child) stack.push(child);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
findEnumRawType(node) {
|
|
1946
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1947
|
+
const c = node.namedChild(i);
|
|
1948
|
+
if (c && c.type === "inheritance_specifier") {
|
|
1949
|
+
const inner = this.findChildByType(c, "user_type") || this.findChildByType(c, "type_identifier");
|
|
1950
|
+
return inner ? inner.text : c.text;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
/** Base type names from the inheritance specifier (class/struct/actor). */
|
|
1956
|
+
/**
|
|
1957
|
+
* SW-D21 (#938): `case .X(let y)` recovery — the callee navigation's first
|
|
1958
|
+
* token is the reserved `case` keyword (parsed as a simple_identifier).
|
|
1959
|
+
* Swift never lets `case` name a value, so this is always grammar recovery.
|
|
1960
|
+
*/
|
|
1961
|
+
isCasePatternRecovery(callNode) {
|
|
1962
|
+
for (let i = 0; i < callNode.namedChildCount; i++) {
|
|
1963
|
+
const c = callNode.namedChild(i);
|
|
1964
|
+
if (!c || c.type !== "navigation_expression") continue;
|
|
1965
|
+
const head = this.firstNamedChild(c);
|
|
1966
|
+
if (head && (head.type === "simple_identifier" || head.type === "identifier") && head.text === "case") return true;
|
|
1967
|
+
}
|
|
1968
|
+
return false;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* SW-D23 (#940): is this ERROR node a nominal DECLARATION the pinned grammar
|
|
1972
|
+
* failed to reduce? True when its FIRST child is the anonymous nominal
|
|
1973
|
+
* keyword token and its own next named child is the type name (`user_type`
|
|
1974
|
+
* for an extension, an identifier otherwise). The name must be the
|
|
1975
|
+
* recovery's own name node — never a nested declaration's — so a top-level
|
|
1976
|
+
* `ERROR` that merely contains declarations somewhere below is not treated
|
|
1977
|
+
* as one itself.
|
|
1978
|
+
*/
|
|
1979
|
+
/**
|
|
1980
|
+
* The nominal kind of an ERROR-recovered header, read from the TOKENS: the
|
|
1981
|
+
* first anonymous nominal keyword child, provided nothing but header material
|
|
1982
|
+
* (`modifiers` / `attribute` / `comment`) precedes it. Declaration content
|
|
1983
|
+
* before a keyword means the keyword belongs to a nested declaration that the
|
|
1984
|
+
* recovery left as a SIBLING (measured: `extension SocketAddressError { ...
|
|
1985
|
+
* struct UnknownHost ... }` carries the nested `struct` token as a direct
|
|
1986
|
+
* child of the recovery, so a plain "does any child token say struct" test
|
|
1987
|
+
* names the extension a struct).
|
|
1988
|
+
*/
|
|
1989
|
+
recoveryKind(node) {
|
|
1990
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1991
|
+
const c = node.child(i);
|
|
1992
|
+
if (!c) continue;
|
|
1993
|
+
if (!c.isNamed) {
|
|
1994
|
+
if (c.type === "class") return ASTEngine.SymbolKind.Class;
|
|
1995
|
+
if (c.type === "struct") return ASTEngine.SymbolKind.Struct;
|
|
1996
|
+
if (c.type === "enum") return ASTEngine.SymbolKind.Enum;
|
|
1997
|
+
if (c.type === "extension") return ASTEngine.SymbolKind.Extension;
|
|
1998
|
+
if (c.type === "actor") return ASTEngine.SymbolKind.Actor;
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
if (c.type === "modifiers" || c.type === "attribute" || c.type === "comment") continue;
|
|
2002
|
+
return null;
|
|
2003
|
+
}
|
|
2004
|
+
return null;
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* The recovery's OWN name node: the first non-header child, which is the
|
|
2008
|
+
* extended `user_type` for an extension and an identifier otherwise. Anything
|
|
2009
|
+
* else (a nested declaration's type_identifier, a parameter type) is not this
|
|
2010
|
+
* header's name.
|
|
2011
|
+
*/
|
|
2012
|
+
recoveryNameNode(node) {
|
|
2013
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2014
|
+
const c = node.namedChild(i);
|
|
2015
|
+
if (!c) continue;
|
|
2016
|
+
if (c.type === "modifiers" || c.type === "attribute" || c.type === "comment") continue;
|
|
2017
|
+
if (c.type === "user_type" || c.type === "type_identifier" || c.type === "simple_identifier" || c.type === "identifier") return c;
|
|
2018
|
+
return null;
|
|
2019
|
+
}
|
|
2020
|
+
return null;
|
|
2021
|
+
}
|
|
2022
|
+
recoveryName(node) {
|
|
2023
|
+
const n = this.recoveryNameNode(node);
|
|
2024
|
+
if (!n) return null;
|
|
2025
|
+
if (n.type === "user_type") {
|
|
2026
|
+
const leaf = this.lastTypeIdentifier(n);
|
|
2027
|
+
return leaf ? leaf.text : null;
|
|
2028
|
+
}
|
|
2029
|
+
return n.text;
|
|
2030
|
+
}
|
|
2031
|
+
isNominalRecovery(node) {
|
|
2032
|
+
if (node.type !== "ERROR") return false;
|
|
2033
|
+
return this.recoveryKind(node) !== null && this.recoveryNameNode(node) !== null;
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* (#825 lineage): base sites WITH their name nodes, because a heritage
|
|
2037
|
+
* edge must name the base specifier's own line — the declaration's line is a
|
|
2038
|
+
* different site, and borrowing it is the fabrication the line rule forbids.
|
|
2039
|
+
*/
|
|
2040
|
+
extractBaseSites(node) {
|
|
2041
|
+
const sites = [];
|
|
2042
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2043
|
+
const c = node.namedChild(i);
|
|
2044
|
+
if (c && c.type === "inheritance_specifier") {
|
|
2045
|
+
const site = this.extractBaseSite(c);
|
|
2046
|
+
if (site) sites.push(site);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return sites;
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* The store's `line_number` for a node: its 1-based start row.
|
|
2053
|
+
* `undefined` when there is no node — the field's ruled absence state, never
|
|
2054
|
+
* a fabricated 0 and never an enclosing declaration's line (/ R6).
|
|
2055
|
+
*/
|
|
2056
|
+
nodeLine(node) {
|
|
2057
|
+
if (!node) return void 0;
|
|
2058
|
+
return node.startPosition.row + 1;
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Leaf of a (possibly dotted) user_type: the LAST sibling type_identifier
|
|
2062
|
+
* (`Outer.Inner` -> Inner, `Foundation.URLSession` -> URLSession). The
|
|
2063
|
+
* user_type node holds its dotted segments as sibling type_identifier
|
|
2064
|
+
* children in source order (probe-verified 2026-09), so the leaf is simply
|
|
2065
|
+
* the last one. SWIFTD19: the pre-fix code returned the FIRST segment,
|
|
2066
|
+
* fabricating extends/implements edges to the wrong (module/container)
|
|
2067
|
+
* name — e.g. `class Sub2: Foundation.URLSession` became extends Sub2 ->
|
|
2068
|
+
* Foundation, and `extension Foo: SomeMod.Proto` became implements Foo ->
|
|
2069
|
+
* SomeMod.
|
|
2070
|
+
*/
|
|
2071
|
+
lastTypeIdentifier(ut) {
|
|
2072
|
+
let last = null;
|
|
2073
|
+
for (let i = 0; i < ut.namedChildCount; i++) {
|
|
2074
|
+
const c = ut.namedChild(i);
|
|
2075
|
+
if (c && c.type === "type_identifier") last = c;
|
|
2076
|
+
}
|
|
2077
|
+
if (last) return last;
|
|
2078
|
+
const stack = [ut];
|
|
2079
|
+
let best = null;
|
|
2080
|
+
while (stack.length > 0) {
|
|
2081
|
+
const c = stack.pop();
|
|
2082
|
+
if (c.type === "type_identifier") {
|
|
2083
|
+
if (!best || c.startIndex >= best.startIndex) best = c;
|
|
2084
|
+
}
|
|
2085
|
+
for (let i = c.namedChildCount - 1; i >= 0; i--) {
|
|
2086
|
+
const child = c.namedChild(i);
|
|
2087
|
+
if (child) stack.push(child);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return best;
|
|
2091
|
+
}
|
|
2092
|
+
extractBaseSite(spec) {
|
|
2093
|
+
for (let i = 0; i < spec.namedChildCount; i++) {
|
|
2094
|
+
const c = spec.namedChild(i);
|
|
2095
|
+
if (!c) continue;
|
|
2096
|
+
if (c.type === "type_identifier") return { name: c.text, node: c };
|
|
2097
|
+
if (c.type === "user_type") {
|
|
2098
|
+
const leaf = this.lastTypeIdentifier(c);
|
|
2099
|
+
if (leaf) return { name: leaf.text, node: leaf };
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const txt = spec.text.trim();
|
|
2103
|
+
return txt && !txt.includes(" ") ? { name: txt, node: spec } : null;
|
|
2104
|
+
}
|
|
2105
|
+
/** SW-F: all-ASCII-digits check without regex (family contract). */
|
|
2106
|
+
isAllDigits(text) {
|
|
2107
|
+
if (text.length === 0) return false;
|
|
2108
|
+
for (let i = 0; i < text.length; i++) {
|
|
2109
|
+
const c = text.charCodeAt(i);
|
|
2110
|
+
if (c < 48 || c > 57) return false;
|
|
2111
|
+
}
|
|
2112
|
+
return true;
|
|
2113
|
+
}
|
|
2114
|
+
/** SW-G: receiver head of a navigation (`super`/`self`/`Self`/identifier). */
|
|
2115
|
+
navigationReceiver(node) {
|
|
2116
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2117
|
+
const c = node.namedChild(i);
|
|
2118
|
+
if (!c) continue;
|
|
2119
|
+
if (c.type === "self_expression") return "self";
|
|
2120
|
+
if (c.type === "super_expression") return "super";
|
|
2121
|
+
if (c.type === "simple_identifier" || c.type === "identifier" || c.type === "type_identifier") return c.text;
|
|
2122
|
+
if (c.type === "navigation_expression") return this.navigationReceiver(c);
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
return null;
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* SW-B: document-order member texts of a navigation chain
|
|
2129
|
+
* (`P.Foo.bar` -> [P, Foo, bar]). The DIRECT receiver of the leaf method
|
|
2130
|
+
* is second-to-last — the ultimate head is wrong for 3+ chains. Never
|
|
2131
|
+
* descends into nested call_expressions (arguments are not chain
|
|
2132
|
+
* members: `a.b[x]` must yield [a, b], never [a, b, x]).
|
|
2133
|
+
*/
|
|
2134
|
+
navigationChain(node) {
|
|
2135
|
+
const out = [];
|
|
2136
|
+
const stack = [node];
|
|
2137
|
+
const seen = [];
|
|
2138
|
+
while (stack.length > 0) {
|
|
2139
|
+
const n = stack.pop();
|
|
2140
|
+
if (n !== node && n.type === "call_expression") continue;
|
|
2141
|
+
seen.push(n);
|
|
2142
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2143
|
+
const c = n.namedChild(i);
|
|
2144
|
+
if (c) stack.push(c);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
seen.sort((a, b) => a.startIndex - b.startIndex);
|
|
2148
|
+
for (const n of seen) {
|
|
2149
|
+
if (n.type === "simple_identifier" || n.type === "identifier" || n.type === "type_identifier") {
|
|
2150
|
+
out.push(n.text);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
return out;
|
|
2154
|
+
}
|
|
2155
|
+
/** SW-B: capitalized type-name signal (SWIFTD17 rule, shared). */
|
|
2156
|
+
isTypeName(text) {
|
|
2157
|
+
if (text.length === 0) return false;
|
|
2158
|
+
const code = text.charCodeAt(0);
|
|
2159
|
+
return code >= 65 && code <= 90;
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* SW-A (ultimate review): NON-self navigation member reads. Every
|
|
2163
|
+
* navigation_suffix member reads unless (a) it is the invoked callee (its
|
|
2164
|
+
* navigation is the func of a non-subscript call and it is the last
|
|
2165
|
+
* suffix), (b) the chain is self-rooted (handleSelfUse owns those),
|
|
2166
|
+
* (c) it sits in an assignment-LHS span (SCD1), (d) it is an implicit-dot
|
|
2167
|
+
* member (`.regular` — base inferred, bare-uses parity), or (e) it names
|
|
2168
|
+
* self/Self/$0. Shares emittedUses with handleSelfUse (first metadata
|
|
2169
|
+
* wins) and sources identically (method > type > owner > name > root).
|
|
2170
|
+
*/
|
|
2171
|
+
handleMemberUses(node, relationships, parent, typeName, emittedUses) {
|
|
2172
|
+
if (this.hasSelfReceiver(node)) return;
|
|
2173
|
+
const source = (parent && (parent.kind === ASTEngine.SymbolKind.Method || parent.kind === ASTEngine.SymbolKind.Constructor) ? parent.name : null) || typeName || (parent?.ownerName ? parent.ownerName : null) || parent?.name || "root";
|
|
2174
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2175
|
+
const c = node.namedChild(i);
|
|
2176
|
+
if (!c || c.type !== "navigation_suffix") continue;
|
|
2177
|
+
const m = this.findChildByType(c, "simple_identifier") || this.findChildByType(c, "identifier");
|
|
2178
|
+
if (!m || !m.text) continue;
|
|
2179
|
+
const member = this.stripNameQuoting(m.text);
|
|
2180
|
+
if (member === "$0" || member === "self" || member === "Self") continue;
|
|
2181
|
+
const par = node.parent;
|
|
2182
|
+
if (par && par.type === "call_expression" && !this.isSubscriptRead(par)) {
|
|
2183
|
+
let last = false;
|
|
2184
|
+
for (let j = node.namedChildCount - 1; j >= 0; j--) {
|
|
2185
|
+
const s = node.namedChild(j);
|
|
2186
|
+
if (s && s.type === "navigation_suffix") {
|
|
2187
|
+
last = s.startIndex === c.startIndex && s.endIndex === c.endIndex;
|
|
2188
|
+
break;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (last) continue;
|
|
2192
|
+
}
|
|
2193
|
+
let outer = node;
|
|
2194
|
+
while (outer && outer.parent && outer.parent.type === "navigation_expression") outer = outer.parent;
|
|
2195
|
+
const firstTok = outer ? outer.child(0) : null;
|
|
2196
|
+
if (firstTok && !firstTok.isNamed && firstTok.type === ".") continue;
|
|
2197
|
+
let a = node.parent;
|
|
2198
|
+
while (a && (a.type === "directly_assignable_expression" || a.type === "postfix_expression" || a.type === "navigation_expression" || a.type === "navigation_suffix")) a = a.parent;
|
|
2199
|
+
if (a && a.type === "assignment") {
|
|
2200
|
+
const lhs = a.namedChild(0);
|
|
2201
|
+
if (lhs && lhs.startIndex <= c.startIndex && lhs.endIndex >= c.endIndex) continue;
|
|
2202
|
+
}
|
|
2203
|
+
const key = `${source}\0uses\0${member}`;
|
|
2204
|
+
if (emittedUses.has(key)) continue;
|
|
2205
|
+
emittedUses.add(key);
|
|
2206
|
+
relationships.push({
|
|
2207
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2208
|
+
source,
|
|
2209
|
+
target: member,
|
|
2210
|
+
isExternal: false,
|
|
2211
|
+
weight: 0.7,
|
|
2212
|
+
confidence: 0.9,
|
|
2213
|
+
// The member identifier node inside this suffix.
|
|
2214
|
+
line_number: this.nodeLine(m),
|
|
2215
|
+
metadata: { isMemberRead: true }
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* SW-D (ultimate review): key-path members (`\.name`, `\C.name`) always
|
|
2221
|
+
* read the member identity.
|
|
2222
|
+
*/
|
|
2223
|
+
handleKeyPathUses(node, relationships, parent, typeName, emittedUses) {
|
|
2224
|
+
const source = (parent && (parent.kind === ASTEngine.SymbolKind.Method || parent.kind === ASTEngine.SymbolKind.Constructor) ? parent.name : null) || typeName || (parent?.ownerName ? parent.ownerName : null) || parent?.name || "root";
|
|
2225
|
+
const stack = [node];
|
|
2226
|
+
while (stack.length > 0) {
|
|
2227
|
+
const n = stack.pop();
|
|
2228
|
+
if (n.type === "navigation_suffix") {
|
|
2229
|
+
const m = this.findChildByType(n, "simple_identifier") || this.findChildByType(n, "identifier");
|
|
2230
|
+
if (m && m.text && m.text !== "self" && m.text !== "Self") {
|
|
2231
|
+
const keyPathMember = this.stripNameQuoting(m.text);
|
|
2232
|
+
const key = `${source}\0uses\0${keyPathMember}`;
|
|
2233
|
+
if (!emittedUses.has(key)) {
|
|
2234
|
+
emittedUses.add(key);
|
|
2235
|
+
relationships.push({
|
|
2236
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2237
|
+
source,
|
|
2238
|
+
target: keyPathMember,
|
|
2239
|
+
isExternal: false,
|
|
2240
|
+
weight: 0.7,
|
|
2241
|
+
confidence: 0.9,
|
|
2242
|
+
line_number: this.nodeLine(m),
|
|
2243
|
+
metadata: { isMemberRead: true, isKeyPath: true }
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
2250
|
+
const c = n.namedChild(i);
|
|
2251
|
+
if (c) stack.push(c);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
/**
|
|
2256
|
+
* The leaf IDENTIFIER NODE of a navigation (`users.first` -> the `first`
|
|
2257
|
+
* node) — the site a `uses`/`calls` edge's line must name. Paired with
|
|
2258
|
+
* `navigationLeaf` (the string form) so no line is ever taken from a
|
|
2259
|
+
* different segment of the chain.
|
|
2260
|
+
*/
|
|
2261
|
+
navigationLeafNode(node) {
|
|
2262
|
+
let leaf = null;
|
|
2263
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2264
|
+
const c = node.namedChild(i);
|
|
2265
|
+
if (!c) continue;
|
|
2266
|
+
if (c.type === "navigation_suffix") {
|
|
2267
|
+
leaf = this.findChildByType(c, "simple_identifier") || this.findChildByType(c, "identifier") || c;
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
if (leaf) return leaf;
|
|
2271
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2272
|
+
const c = node.namedChild(i);
|
|
2273
|
+
if (c && c.type === "simple_identifier") leaf = c;
|
|
2274
|
+
}
|
|
2275
|
+
return leaf;
|
|
2276
|
+
}
|
|
2277
|
+
/** Leaf name of a navigation_expression: `users.first` -> `first`. */
|
|
2278
|
+
navigationLeaf(node) {
|
|
2279
|
+
let leaf = null;
|
|
2280
|
+
let sawSuffix = false;
|
|
2281
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2282
|
+
const c = node.namedChild(i);
|
|
2283
|
+
if (!c) continue;
|
|
2284
|
+
if (c.type === "navigation_suffix") {
|
|
2285
|
+
sawSuffix = true;
|
|
2286
|
+
leaf = this.findChildByType(c, "simple_identifier") || this.findChildByType(c, "identifier");
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
if (leaf) return this.stripNameQuoting(leaf.text);
|
|
2290
|
+
if (sawSuffix) return null;
|
|
2291
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2292
|
+
const c = node.namedChild(i);
|
|
2293
|
+
if (c && c.type === "simple_identifier") leaf = c;
|
|
2294
|
+
}
|
|
2295
|
+
return leaf ? this.stripNameQuoting(leaf.text) : null;
|
|
2296
|
+
}
|
|
2297
|
+
/** Return type node after the `->` anonymous token (lightweight). */
|
|
2298
|
+
findReturnType(node) {
|
|
2299
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2300
|
+
const c = node.child(i);
|
|
2301
|
+
if (c && !c.isNamed && c.type === "->") {
|
|
2302
|
+
const nxt = node.child(i + 1);
|
|
2303
|
+
if (nxt && nxt.isNamed) return nxt;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
return null;
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
export {
|
|
2310
|
+
RealSwiftAnalyzer
|
|
2311
|
+
};
|