@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,3298 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findDeclaringParent,
|
|
3
|
+
localEdgeLabel,
|
|
4
|
+
localEdgeName
|
|
5
|
+
} from "./chunk-OWISBAOF.js";
|
|
6
|
+
import {
|
|
7
|
+
ASTEngine
|
|
8
|
+
} from "./chunk-3ZTOHHSW.js";
|
|
9
|
+
import "./chunk-BQ3OVQ3P.js";
|
|
10
|
+
import "./chunk-SK6YVHFZ.js";
|
|
11
|
+
import "./chunk-2BSKD7O3.js";
|
|
12
|
+
import "./chunk-KAET47ZL.js";
|
|
13
|
+
import "./chunk-KCDECRJY.js";
|
|
14
|
+
import "./chunk-MLKGABMK.js";
|
|
15
|
+
|
|
16
|
+
// src/ast/real-csharp-analyzer.ts
|
|
17
|
+
function csharpCleanTypeLeaf(text) {
|
|
18
|
+
const lt = text.indexOf("<");
|
|
19
|
+
const base = lt > 0 ? text.slice(0, lt) : text;
|
|
20
|
+
const q = base.indexOf("?");
|
|
21
|
+
const noQ = q > 0 ? base.slice(0, q) : base;
|
|
22
|
+
const br = noQ.indexOf("[");
|
|
23
|
+
return (br > 0 ? noQ.slice(0, br) : noQ).trim();
|
|
24
|
+
}
|
|
25
|
+
function isCSharpNameSeparator(ch) {
|
|
26
|
+
return ch === "." || ch === "<" || ch === ">" || ch === "," || ch === "[" || ch === "]";
|
|
27
|
+
}
|
|
28
|
+
function csharpTypeNameText(text) {
|
|
29
|
+
const out = [];
|
|
30
|
+
let pendingSpace = false;
|
|
31
|
+
for (let i = 0; i < text.length; i++) {
|
|
32
|
+
const ch = text[i];
|
|
33
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v") {
|
|
34
|
+
if (out.length > 0) pendingSpace = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (pendingSpace) {
|
|
38
|
+
const last = out[out.length - 1];
|
|
39
|
+
if (!isCSharpNameSeparator(last) && !isCSharpNameSeparator(ch)) out.push(" ");
|
|
40
|
+
pendingSpace = false;
|
|
41
|
+
}
|
|
42
|
+
out.push(ch);
|
|
43
|
+
}
|
|
44
|
+
return out.join("");
|
|
45
|
+
}
|
|
46
|
+
var CSHARP_DECLARED_TYPE_NODES = /* @__PURE__ */ new Set([
|
|
47
|
+
"identifier",
|
|
48
|
+
"generic_name",
|
|
49
|
+
"qualified_name",
|
|
50
|
+
"predefined_type",
|
|
51
|
+
"array_type",
|
|
52
|
+
"pointer_type",
|
|
53
|
+
"nullable_type",
|
|
54
|
+
"ref_type",
|
|
55
|
+
"tuple_type",
|
|
56
|
+
// C2a: a `global::`-qualified name with a SINGLE segment is an `alias_qualified_name` — the
|
|
57
|
+
// grammar only wraps it in a `qualified_name` when the path has a dot after the qualifier. The
|
|
58
|
+
// set's two consumers are the return-type and indexer-type fallback scans, so without this the
|
|
59
|
+
// declared-type graph lost the dependency for exactly the qualified spelling it exists to
|
|
60
|
+
// normalise (measured: `public global::Root RetAlias()` published NO `return_type` ref while
|
|
61
|
+
// `public Root RetPlain()` published one — script/m14/tmp/cs-alias-position-battery3.ts).
|
|
62
|
+
"alias_qualified_name"
|
|
63
|
+
]);
|
|
64
|
+
var CSHARP_SYMBOL_DECLARATIONS = /* @__PURE__ */ new Set([
|
|
65
|
+
"namespace_declaration",
|
|
66
|
+
"file_scoped_namespace_declaration",
|
|
67
|
+
"class_declaration",
|
|
68
|
+
"struct_declaration",
|
|
69
|
+
"interface_declaration",
|
|
70
|
+
"record_declaration",
|
|
71
|
+
"enum_declaration",
|
|
72
|
+
"enum_member_declaration",
|
|
73
|
+
"delegate_declaration",
|
|
74
|
+
"event_declaration",
|
|
75
|
+
"operator_declaration",
|
|
76
|
+
"conversion_operator_declaration",
|
|
77
|
+
"method_declaration",
|
|
78
|
+
"constructor_declaration",
|
|
79
|
+
"property_declaration",
|
|
80
|
+
"indexer_declaration"
|
|
81
|
+
]);
|
|
82
|
+
function csharpMemberDeclaredTypeText(memberLike) {
|
|
83
|
+
const TYPES = CSHARP_DECLARED_TYPE_NODES;
|
|
84
|
+
const resolveHolder = (holder2, nameFieldChild2) => {
|
|
85
|
+
const tf = holder2?.childForFieldName?.("type");
|
|
86
|
+
if (tf) return String(tf.text ?? "").trim();
|
|
87
|
+
for (const c of holder2?.namedChildren ?? []) {
|
|
88
|
+
if (!c || !TYPES.has(c.type)) continue;
|
|
89
|
+
if (nameFieldChild2 && c.startIndex === nameFieldChild2.startIndex && c.endIndex === nameFieldChild2.endIndex) continue;
|
|
90
|
+
return String(c.text ?? "").trim();
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
};
|
|
94
|
+
if (memberLike.type === "field_declaration") {
|
|
95
|
+
const vd = memberLike.namedChildren?.find((c) => c.type === "variable_declaration");
|
|
96
|
+
if (vd) {
|
|
97
|
+
const t = resolveHolder(vd, null);
|
|
98
|
+
if (t) return t;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (memberLike.type === "variable_declarator") {
|
|
102
|
+
const parentDecl = memberLike.parent;
|
|
103
|
+
if (parentDecl && parentDecl.type === "variable_declaration") {
|
|
104
|
+
const t = resolveHolder(parentDecl, null);
|
|
105
|
+
if (t) return t;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const nameFieldChild = memberLike.childForFieldName?.("name") ?? null;
|
|
109
|
+
const holder = memberLike.type === "field_declaration" ? memberLike.namedChildren?.find((c) => c.type === "variable_declaration") : memberLike;
|
|
110
|
+
return resolveHolder(holder, nameFieldChild);
|
|
111
|
+
}
|
|
112
|
+
function csharpTargetTypedNewSite(nodeLike) {
|
|
113
|
+
let isElement = false;
|
|
114
|
+
let cur = nodeLike.parent;
|
|
115
|
+
while (cur) {
|
|
116
|
+
if (cur.type === "collection_expression" || cur.type === "collection_element" || cur.type === "expression_element" || cur.type === "initializer_expression" || cur.type === "array_creation_expression") {
|
|
117
|
+
isElement = isElement || cur.type === "collection_element" || cur.type === "expression_element" || cur.type === "collection_expression" || cur.type === "initializer_expression" || cur.type === "array_creation_expression";
|
|
118
|
+
}
|
|
119
|
+
if (cur.type === "object_creation_expression" || cur.type === "implicit_object_creation_expression" || cur.type === "anonymous_object_creation_expression") {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (cur.type === "invocation_expression" || cur.type === "argument_list" || // ≡ /^(binary|conditional|prefix_unary|postfix_unary|cast|as_|await|coalesce|conditional_access|element_access|member_access)_expression$/ —
|
|
123
|
+
// anchored alternation over the fixed expression-node set: endsWith("_expression") + membership excludes arbitrary *_expression node types (equals-check per disjunct; as_* is startsWith).
|
|
124
|
+
CSHARP_TARGETISH_EXPRESSIONS.has(cur.type)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (cur.type === "variable_declarator" || cur.type === "property_declaration" || cur.type === "indexer_declaration" || cur.type === "field_declaration") {
|
|
128
|
+
return { kind: "declaration", decl: cur, isElement };
|
|
129
|
+
}
|
|
130
|
+
if (cur.type === "assignment_expression") {
|
|
131
|
+
const lhs = cur.childForFieldName?.("left");
|
|
132
|
+
if (!lhs) return null;
|
|
133
|
+
const name = lhs.type === "identifier" ? lhs.text : lhs.childForFieldName?.("name")?.text ?? "";
|
|
134
|
+
return name ? { kind: "assignment", memberName: name, isElement } : null;
|
|
135
|
+
}
|
|
136
|
+
cur = cur.parent;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function csharpResolveTargetTypedNew(nodeLike, memberTypes) {
|
|
141
|
+
const site = csharpTargetTypedNewSite(nodeLike);
|
|
142
|
+
if (!site) return null;
|
|
143
|
+
if (site.kind === "declaration") {
|
|
144
|
+
const declared2 = csharpMemberDeclaredTypeText(site.decl);
|
|
145
|
+
if (!declared2) return null;
|
|
146
|
+
if (declared2 === "var") return null;
|
|
147
|
+
return csharpTargetFromDeclaredType(declared2, site.isElement);
|
|
148
|
+
}
|
|
149
|
+
const declared = memberTypes?.get(site.memberName);
|
|
150
|
+
return declared ? csharpTargetFromDeclaredType(declared, site.isElement) : null;
|
|
151
|
+
}
|
|
152
|
+
function csharpTargetFromDeclaredType(typeText, isElement) {
|
|
153
|
+
let t = csharpTypeSpelling(typeText).trim();
|
|
154
|
+
if (t === "var") return "";
|
|
155
|
+
if (t.endsWith("?")) t = t.slice(0, -1).trim();
|
|
156
|
+
if (isElement) {
|
|
157
|
+
const br = t.indexOf("[");
|
|
158
|
+
if (br > 0) return csharpCleanTypeLeaf(t.slice(0, br));
|
|
159
|
+
const lt = t.indexOf("<");
|
|
160
|
+
if (lt > 0 && t.endsWith(">")) {
|
|
161
|
+
const inner = t.slice(lt + 1, -1);
|
|
162
|
+
const first = inner.split(",")[0].trim();
|
|
163
|
+
return csharpCleanTypeLeaf(first);
|
|
164
|
+
}
|
|
165
|
+
return csharpCleanTypeLeaf(t);
|
|
166
|
+
}
|
|
167
|
+
return csharpCleanTypeLeaf(t);
|
|
168
|
+
}
|
|
169
|
+
var CSHARP_KEYWORDS = /* @__PURE__ */ new Set([
|
|
170
|
+
"if",
|
|
171
|
+
"for",
|
|
172
|
+
"foreach",
|
|
173
|
+
"while",
|
|
174
|
+
"do",
|
|
175
|
+
"switch",
|
|
176
|
+
"case",
|
|
177
|
+
"else",
|
|
178
|
+
"return",
|
|
179
|
+
"throw",
|
|
180
|
+
"using",
|
|
181
|
+
"new",
|
|
182
|
+
"delegate",
|
|
183
|
+
"in",
|
|
184
|
+
"is",
|
|
185
|
+
"as",
|
|
186
|
+
"sizeof",
|
|
187
|
+
"fixed",
|
|
188
|
+
"lock",
|
|
189
|
+
"checked",
|
|
190
|
+
"unchecked",
|
|
191
|
+
"params",
|
|
192
|
+
"ref",
|
|
193
|
+
"out",
|
|
194
|
+
"base",
|
|
195
|
+
"this",
|
|
196
|
+
// CS-B2 (csharp review): `nameof(x)` parses as an invocation_expression
|
|
197
|
+
// with an identifier callee — without this entry every nameof site
|
|
198
|
+
// fabricated a `calls -> nameof` edge (probe-verified). `typeof`/`default`
|
|
199
|
+
// never parse as invocations (dedicated nodes), so only nameof belongs.
|
|
200
|
+
"get",
|
|
201
|
+
"set",
|
|
202
|
+
"nameof"
|
|
203
|
+
]);
|
|
204
|
+
var CSHARP_TARGETISH_EXPRESSIONS = /* @__PURE__ */ new Set([
|
|
205
|
+
"binary_expression",
|
|
206
|
+
"conditional_expression",
|
|
207
|
+
"prefix_unary_expression",
|
|
208
|
+
"postfix_unary_expression",
|
|
209
|
+
"cast_expression",
|
|
210
|
+
"as_expression",
|
|
211
|
+
"await_expression",
|
|
212
|
+
"coalesce_expression",
|
|
213
|
+
"conditional_access_expression",
|
|
214
|
+
"element_access_expression",
|
|
215
|
+
"member_access_expression"
|
|
216
|
+
]);
|
|
217
|
+
var CSHARP_BUILTINS = /* @__PURE__ */ new Set([
|
|
218
|
+
// Console output — filtered (like Java's System.out.println)
|
|
219
|
+
"Console.WriteLine",
|
|
220
|
+
"Console.Write",
|
|
221
|
+
"Console.WriteAsync",
|
|
222
|
+
"Console.Error.WriteLine",
|
|
223
|
+
"Console.Error.Write",
|
|
224
|
+
"Console.Error.WriteAsync",
|
|
225
|
+
"Console.Out.WriteLine",
|
|
226
|
+
"Console.Out.Write",
|
|
227
|
+
"Console.Out.WriteAsync",
|
|
228
|
+
// System-level noise
|
|
229
|
+
"Environment.Exit",
|
|
230
|
+
"Environment.GetEnvironmentVariable",
|
|
231
|
+
"Environment.GetEnvironmentVariables",
|
|
232
|
+
"Environment.SetEnvironmentVariable",
|
|
233
|
+
"GC.Collect",
|
|
234
|
+
"GC.SuppressFinalize",
|
|
235
|
+
"GC.WaitForPendingFinalizers",
|
|
236
|
+
"Debug.WriteLine",
|
|
237
|
+
"Debug.Write",
|
|
238
|
+
"Debug.Print",
|
|
239
|
+
"Trace.WriteLine",
|
|
240
|
+
"Trace.Write",
|
|
241
|
+
// Object methods (C# equivalent of Java's Object methods)
|
|
242
|
+
"ToString",
|
|
243
|
+
"GetHashCode",
|
|
244
|
+
"Equals",
|
|
245
|
+
"GetType",
|
|
246
|
+
"ReferenceEquals"
|
|
247
|
+
]);
|
|
248
|
+
function csharpUsingDirectiveTarget(node) {
|
|
249
|
+
if (!node) return null;
|
|
250
|
+
if (node.hasError) return null;
|
|
251
|
+
const firstNamed = (pred) => {
|
|
252
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
253
|
+
const c = node.namedChild(i);
|
|
254
|
+
if (c && pred(c)) return c;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
};
|
|
258
|
+
const nameNode = firstNamed((n) => n.type === "identifier" || n.type === "qualified_name" || n.type === "alias_qualified_name");
|
|
259
|
+
if (!nameNode) return null;
|
|
260
|
+
let eq = null;
|
|
261
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
262
|
+
const c = node.child(i);
|
|
263
|
+
if (c && c.type === "=") {
|
|
264
|
+
eq = c;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const aliasNode = eq ? node.childForFieldName?.("name") ?? null : null;
|
|
269
|
+
let targetAfterEq = null;
|
|
270
|
+
if (eq) {
|
|
271
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
272
|
+
const c = node.namedChild(i);
|
|
273
|
+
if (c && c.startIndex >= eq.endIndex) {
|
|
274
|
+
targetAfterEq = c;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const targetNode = targetAfterEq ?? firstNamed((n) => n.type === "qualified_name" || n.type === "alias_qualified_name") ?? nameNode;
|
|
280
|
+
const spelled = csharpTypeNameText(csharpTypeSpelling(String(targetNode?.text ?? "")));
|
|
281
|
+
const module = csharpCleanTypeLeaf(spelled);
|
|
282
|
+
if (!module) return null;
|
|
283
|
+
return {
|
|
284
|
+
module: csharpUsingDirectiveModule(targetNode, module),
|
|
285
|
+
typeArguments: csharpTypeExpressionArguments(targetNode),
|
|
286
|
+
targetNode,
|
|
287
|
+
alias: aliasNode ? csharpTypeNameText(String(aliasNode.text ?? "")) : null,
|
|
288
|
+
kind: csharpUsingDirectiveKind(node, aliasNode != null)
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function csharpUsingDirectiveModule(targetNode, fallback) {
|
|
292
|
+
if (targetNode?.type !== "tuple_type") return fallback;
|
|
293
|
+
const parts = [];
|
|
294
|
+
for (const el of targetNode.namedChildren ?? []) {
|
|
295
|
+
if (el?.type !== "tuple_element") continue;
|
|
296
|
+
const ty = el.childForFieldName?.("type") ?? el.namedChild(0);
|
|
297
|
+
if (!ty) continue;
|
|
298
|
+
const leaf = csharpCleanTypeLeaf(csharpTypeNameText(csharpTypeSpelling(String(ty.text ?? ""))));
|
|
299
|
+
if (leaf) parts.push(leaf);
|
|
300
|
+
}
|
|
301
|
+
return parts.length ? `(${parts.join(",")})` : fallback;
|
|
302
|
+
}
|
|
303
|
+
function csharpUsingDirectiveKind(node, hasAlias) {
|
|
304
|
+
let isGlobal = false;
|
|
305
|
+
let isStatic = false;
|
|
306
|
+
for (let i = 0; i < (node?.childCount ?? 0); i++) {
|
|
307
|
+
const c = node.child(i);
|
|
308
|
+
if (!c || c.isNamed) continue;
|
|
309
|
+
if (c.type === "global") isGlobal = true;
|
|
310
|
+
else if (c.type === "static") isStatic = true;
|
|
311
|
+
}
|
|
312
|
+
if (hasAlias) return isGlobal ? "global_alias" : "alias";
|
|
313
|
+
if (isStatic) return isGlobal ? "global_static" : "static";
|
|
314
|
+
return isGlobal ? "global_namespace" : "namespace";
|
|
315
|
+
}
|
|
316
|
+
function csharpTypeExpressionArguments(targetNode) {
|
|
317
|
+
if (!targetNode) return [];
|
|
318
|
+
const out = [];
|
|
319
|
+
const seen = /* @__PURE__ */ new Set();
|
|
320
|
+
const add = (raw) => {
|
|
321
|
+
const name = csharpCleanTypeLeaf(csharpTypeNameText(csharpTypeSpelling(raw)));
|
|
322
|
+
if (!name || name === "var" || name === "dynamic" || name === "nint" || name === "nuint") return;
|
|
323
|
+
if (seen.has(name)) return;
|
|
324
|
+
seen.add(name);
|
|
325
|
+
out.push(name);
|
|
326
|
+
};
|
|
327
|
+
const walkArgument = (n) => {
|
|
328
|
+
if (!n) return;
|
|
329
|
+
const t = n.type;
|
|
330
|
+
if (t === "predefined_type" || t === "implicit_type" || t === "void_keyword") return;
|
|
331
|
+
if (t === "identifier") {
|
|
332
|
+
add(String(n.text ?? ""));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (t === "tuple_type") {
|
|
336
|
+
for (const el of n.namedChildren ?? []) {
|
|
337
|
+
if (el.type !== "tuple_element") continue;
|
|
338
|
+
walkArgument(el.childForFieldName?.("type") ?? el.namedChild(0));
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (t === "generic_name") {
|
|
343
|
+
const base = n.namedChild(0);
|
|
344
|
+
if (base) walkArgument(base);
|
|
345
|
+
const args = (n.namedChildren ?? []).find((c) => c.type === "type_argument_list");
|
|
346
|
+
if (args) for (const a of args.namedChildren ?? []) walkArgument(a);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (t === "qualified_name" || t === "alias_qualified_name") {
|
|
350
|
+
const leaf = csharpTypeLeafNode(n);
|
|
351
|
+
if (leaf) {
|
|
352
|
+
add(String(leaf.text ?? ""));
|
|
353
|
+
const owner = n.namedChild(n.namedChildCount - 1);
|
|
354
|
+
if (owner && owner.type === "generic_name") {
|
|
355
|
+
const args = (owner.namedChildren ?? []).find((c) => c.type === "type_argument_list");
|
|
356
|
+
if (args) for (const a of args.namedChildren ?? []) walkArgument(a);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
for (const c of n.namedChildren ?? []) walkArgument(c);
|
|
362
|
+
};
|
|
363
|
+
const collectFromTarget = (n) => {
|
|
364
|
+
if (!n) return;
|
|
365
|
+
const t = n.type;
|
|
366
|
+
if (t === "generic_name") {
|
|
367
|
+
const args = (n.namedChildren ?? []).find((c) => c.type === "type_argument_list");
|
|
368
|
+
if (args) for (const a of args.namedChildren ?? []) walkArgument(a);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (t === "qualified_name" || t === "alias_qualified_name") {
|
|
372
|
+
const last = n.namedChild(n.namedChildCount - 1);
|
|
373
|
+
if (last && last.type === "generic_name") collectFromTarget(last);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (t === "array_type" || t === "pointer_type" || t === "nullable_type" || t === "ref_type") {
|
|
377
|
+
const inner = n.namedChild(0);
|
|
378
|
+
if (inner) collectFromTarget(inner);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (t === "tuple_type") {
|
|
382
|
+
for (const el of n.namedChildren ?? []) {
|
|
383
|
+
if (el.type !== "tuple_element") continue;
|
|
384
|
+
collectFromTarget(el.childForFieldName?.("type") ?? el.namedChild(0));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
collectFromTarget(targetNode);
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
var CSHARP_BUILTIN_TYPE_SPELLINGS = /* @__PURE__ */ new Set(["dynamic", "nint", "nuint", "var"]);
|
|
392
|
+
var CSHARP_RENDER_HOSTS = /* @__PURE__ */ new Set(["RenderComponent", "RenderChild"]);
|
|
393
|
+
var DOTNET_CITATION_ROOTS = [
|
|
394
|
+
"System.Text.Json",
|
|
395
|
+
"Newtonsoft.Json",
|
|
396
|
+
"System.Xml",
|
|
397
|
+
"System.Net.Http.Json",
|
|
398
|
+
"Microsoft.AspNetCore.Http.Json",
|
|
399
|
+
"BenchmarkDotNet.Exporters.Csv",
|
|
400
|
+
"BenchmarkDotNet.Exporters.Json"
|
|
401
|
+
];
|
|
402
|
+
function dotnetCitationRoots(target) {
|
|
403
|
+
const t = String(target ?? "");
|
|
404
|
+
if (t.length === 0) return [];
|
|
405
|
+
return DOTNET_CITATION_ROOTS.filter((root) => t === root || t.startsWith(`${root}.`));
|
|
406
|
+
}
|
|
407
|
+
var CSHARP_CALLABLE_DECLARATIONS = /* @__PURE__ */ new Set([
|
|
408
|
+
"method_declaration",
|
|
409
|
+
"constructor_declaration",
|
|
410
|
+
"delegate_declaration",
|
|
411
|
+
"operator_declaration",
|
|
412
|
+
"conversion_operator_declaration",
|
|
413
|
+
"local_function_statement"
|
|
414
|
+
]);
|
|
415
|
+
var CSHARP_TYPE_DECLARATIONS = /* @__PURE__ */ new Set([
|
|
416
|
+
"class_declaration",
|
|
417
|
+
"struct_declaration",
|
|
418
|
+
"interface_declaration",
|
|
419
|
+
"record_declaration",
|
|
420
|
+
"enum_declaration"
|
|
421
|
+
]);
|
|
422
|
+
function csharpTypeSpelling(text) {
|
|
423
|
+
const GLOBAL = "global";
|
|
424
|
+
if (text.length <= GLOBAL.length + 2) return text;
|
|
425
|
+
for (let i2 = 0; i2 < GLOBAL.length; i2++) {
|
|
426
|
+
if (text.charCodeAt(i2) !== GLOBAL.charCodeAt(i2)) return text;
|
|
427
|
+
}
|
|
428
|
+
const isWs = (c) => c === 32 || c === 9 || c === 10 || c === 13;
|
|
429
|
+
let i = GLOBAL.length;
|
|
430
|
+
while (i < text.length && isWs(text.charCodeAt(i))) i++;
|
|
431
|
+
if (text.charCodeAt(i) !== 58 || text.charCodeAt(i + 1) !== 58) return text;
|
|
432
|
+
i += 2;
|
|
433
|
+
while (i < text.length && isWs(text.charCodeAt(i))) i++;
|
|
434
|
+
return i < text.length ? text.slice(i) : text;
|
|
435
|
+
}
|
|
436
|
+
function csharpTypeDeclarationKind(node) {
|
|
437
|
+
switch (node.type) {
|
|
438
|
+
case "class_declaration":
|
|
439
|
+
return ASTEngine.SymbolKind.Class;
|
|
440
|
+
case "record_declaration": {
|
|
441
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
442
|
+
const c = node.child(i);
|
|
443
|
+
if (c && !c.isNamed && c.type === "struct") return ASTEngine.SymbolKind.Struct;
|
|
444
|
+
}
|
|
445
|
+
return ASTEngine.SymbolKind.Class;
|
|
446
|
+
}
|
|
447
|
+
case "struct_declaration":
|
|
448
|
+
return ASTEngine.SymbolKind.Struct;
|
|
449
|
+
case "interface_declaration":
|
|
450
|
+
return ASTEngine.SymbolKind.Interface;
|
|
451
|
+
case "enum_declaration":
|
|
452
|
+
return ASTEngine.SymbolKind.Enum;
|
|
453
|
+
default:
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function csharpTypeLeafNode(n) {
|
|
458
|
+
if (!n) return void 0;
|
|
459
|
+
const t = n.type;
|
|
460
|
+
if (t === "identifier") return n;
|
|
461
|
+
if (t === "generic_name" || t === "primary_constructor_base_type") return csharpTypeLeafNode(n.namedChild(0));
|
|
462
|
+
if (t === "qualified_name" || t === "alias_qualified_name") return csharpTypeLeafNode(n.namedChild(n.namedChildCount - 1));
|
|
463
|
+
return void 0;
|
|
464
|
+
}
|
|
465
|
+
function csharpDeclaredTypeLeaves(typeRoot, isBoundTypeParameter) {
|
|
466
|
+
const acc = [];
|
|
467
|
+
const accept = (node, raw) => {
|
|
468
|
+
const name = raw.trim();
|
|
469
|
+
if (!name || CSHARP_BUILTIN_TYPE_SPELLINGS.has(name)) return;
|
|
470
|
+
if (isBoundTypeParameter(node, name)) return;
|
|
471
|
+
acc.push({ node, name });
|
|
472
|
+
};
|
|
473
|
+
const walk = (n) => {
|
|
474
|
+
if (!n) return;
|
|
475
|
+
const t = n.type;
|
|
476
|
+
if (t === "predefined_type" || t === "implicit_type" || t === "void_keyword") return;
|
|
477
|
+
if (t === "identifier") {
|
|
478
|
+
accept(n, String(n.text ?? ""));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (t === "qualified_name" || t === "alias_qualified_name") {
|
|
482
|
+
const leaf = csharpTypeLeafNode(n);
|
|
483
|
+
if (leaf) accept(leaf, String(leaf.text ?? ""));
|
|
484
|
+
const lastSegment = n.namedChild(n.namedChildCount - 1);
|
|
485
|
+
if (lastSegment && lastSegment.type === "generic_name") {
|
|
486
|
+
const args = lastSegment.namedChildren.find((c) => c.type === "type_argument_list");
|
|
487
|
+
if (args) for (const a of args.namedChildren) walk(a);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (t === "generic_name" || t === "primary_constructor_base_type") {
|
|
492
|
+
walk(n.namedChild(0));
|
|
493
|
+
const args = n.namedChildren.find((c) => c.type === "type_argument_list");
|
|
494
|
+
if (args) for (const a of args.namedChildren) walk(a);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (t === "tuple_element") {
|
|
498
|
+
walk(n.childForFieldName("type") ?? n.namedChild(0));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
for (const c of n.namedChildren) walk(c);
|
|
502
|
+
};
|
|
503
|
+
walk(typeRoot);
|
|
504
|
+
return acc;
|
|
505
|
+
}
|
|
506
|
+
function csharpDeclaredTypeRefs(root) {
|
|
507
|
+
const out = [];
|
|
508
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
509
|
+
const isBoundTypeParameter = (from, name) => {
|
|
510
|
+
let cur = from;
|
|
511
|
+
while (cur) {
|
|
512
|
+
if (cur.type === "class_declaration" || cur.type === "struct_declaration" || cur.type === "interface_declaration" || cur.type === "record_declaration" || cur.type === "method_declaration" || cur.type === "delegate_declaration" || cur.type === "local_function_statement" || cur.type === "operator_declaration" || cur.type === "conversion_operator_declaration") {
|
|
513
|
+
const tp = cur.childForFieldName("type_parameters") ?? cur.namedChildren.find((c) => c.type === "type_parameter_list");
|
|
514
|
+
if (tp) {
|
|
515
|
+
const w = [tp];
|
|
516
|
+
while (w.length > 0) {
|
|
517
|
+
const m = w.pop();
|
|
518
|
+
if ((m.type === "type_parameter" || m.type === "identifier") && m.text.trim() === name) return true;
|
|
519
|
+
for (const c of m.namedChildren) w.push(c);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
cur = cur.parent;
|
|
524
|
+
}
|
|
525
|
+
return false;
|
|
526
|
+
};
|
|
527
|
+
const enclosing = (from, kinds) => {
|
|
528
|
+
let cur = from.parent;
|
|
529
|
+
while (cur) {
|
|
530
|
+
if (kinds.has(cur.type)) {
|
|
531
|
+
const nm = cur.childForFieldName("name")?.text;
|
|
532
|
+
if (nm) return nm;
|
|
533
|
+
}
|
|
534
|
+
cur = cur.parent;
|
|
535
|
+
}
|
|
536
|
+
return "";
|
|
537
|
+
};
|
|
538
|
+
const push = (source, typeNode, relationshipType, declaredOn) => {
|
|
539
|
+
if (!source || !typeNode) return;
|
|
540
|
+
for (const leaf of csharpDeclaredTypeLeaves(typeNode, isBoundTypeParameter)) {
|
|
541
|
+
const line = leaf.node.startPosition.row + 1;
|
|
542
|
+
const key = `${source}\0${leaf.name}\0${line}`;
|
|
543
|
+
if (emitted.has(key)) continue;
|
|
544
|
+
emitted.add(key);
|
|
545
|
+
out.push({ source, target: leaf.name, line, relationshipType, declaredOn });
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const typeRootOf = (n) => {
|
|
549
|
+
if (!n) return null;
|
|
550
|
+
const tf = n.childForFieldName("type");
|
|
551
|
+
if (tf) return tf;
|
|
552
|
+
if (n.type === "field_declaration" || n.type === "event_field_declaration") {
|
|
553
|
+
const vd = n.namedChildren.find((c) => c.type === "variable_declaration");
|
|
554
|
+
return vd ? typeRootOf(vd) : null;
|
|
555
|
+
}
|
|
556
|
+
return null;
|
|
557
|
+
};
|
|
558
|
+
const returnTypeNode = (n) => {
|
|
559
|
+
const tf = n.childForFieldName("type");
|
|
560
|
+
if (tf) return tf;
|
|
561
|
+
const nameChild = n.childForFieldName("name");
|
|
562
|
+
for (const c of n.namedChildren) {
|
|
563
|
+
if (c.type === "modifier" || c.type === "attribute_list" || c.type === "parameter_list" || c.type === "type_parameter_list" || c.type === "block" || c.type === "arrow_expression_clause" || c.type === "where_clause" || c.type === "constructor_initializer" || c.type === "type_parameter_constraints_clause") continue;
|
|
564
|
+
if (nameChild && c.startIndex === nameChild.startIndex && c.endIndex === nameChild.endIndex) continue;
|
|
565
|
+
if (CSHARP_DECLARED_TYPE_NODES.has(c.type)) return c;
|
|
566
|
+
}
|
|
567
|
+
return null;
|
|
568
|
+
};
|
|
569
|
+
const stack = [root];
|
|
570
|
+
while (stack.length > 0) {
|
|
571
|
+
const n = stack.pop();
|
|
572
|
+
switch (n.type) {
|
|
573
|
+
case "field_declaration":
|
|
574
|
+
case "event_field_declaration": {
|
|
575
|
+
const vd = n.namedChildren.find((c) => c.type === "variable_declaration");
|
|
576
|
+
push(enclosing(n, CSHARP_TYPE_DECLARATIONS), typeRootOf(vd), "declared_type", n.type);
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
case "property_declaration":
|
|
580
|
+
case "indexer_declaration":
|
|
581
|
+
case "event_declaration": {
|
|
582
|
+
push(enclosing(n, CSHARP_TYPE_DECLARATIONS), typeRootOf(n), "declared_type", n.type);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case "method_declaration":
|
|
586
|
+
case "delegate_declaration":
|
|
587
|
+
case "operator_declaration":
|
|
588
|
+
case "conversion_operator_declaration": {
|
|
589
|
+
const owner = n.childForFieldName("name")?.text ?? enclosing(n, CSHARP_TYPE_DECLARATIONS);
|
|
590
|
+
push(owner, returnTypeNode(n), "return_type", "return_type");
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
case "parameter": {
|
|
594
|
+
push(enclosing(n, CSHARP_CALLABLE_DECLARATIONS), typeRootOf(n), "declared_type", "parameter");
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
case "variable_declaration": {
|
|
598
|
+
if (n.parent?.type === "local_declaration_statement") {
|
|
599
|
+
push(
|
|
600
|
+
enclosing(n, CSHARP_CALLABLE_DECLARATIONS),
|
|
601
|
+
typeRootOf(n),
|
|
602
|
+
"declared_type",
|
|
603
|
+
"local_declaration_statement"
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
for (const c of n.namedChildren) stack.push(c);
|
|
610
|
+
}
|
|
611
|
+
return out;
|
|
612
|
+
}
|
|
613
|
+
var RealCSharpAnalyzer = class {
|
|
614
|
+
analyze(rootNode, content, filePath, context) {
|
|
615
|
+
const symbols = [];
|
|
616
|
+
const relationships = [];
|
|
617
|
+
const imports = [];
|
|
618
|
+
const exports = [];
|
|
619
|
+
const errors = [];
|
|
620
|
+
const symbolMap = /* @__PURE__ */ new Map();
|
|
621
|
+
const classHierarchy = /* @__PURE__ */ new Map();
|
|
622
|
+
const overrideTasks = [];
|
|
623
|
+
const interfaceTasks = [];
|
|
624
|
+
const interfaceMembers = /* @__PURE__ */ new Map();
|
|
625
|
+
const classMembers = /* @__PURE__ */ new Map();
|
|
626
|
+
const typeKinds = /* @__PURE__ */ new Map();
|
|
627
|
+
this.collectDeclaredTypeKinds(rootNode, typeKinds);
|
|
628
|
+
const memberTypes = /* @__PURE__ */ new Map();
|
|
629
|
+
const memberTypeNodes = /* @__PURE__ */ new Map();
|
|
630
|
+
this.collectMemberTypes(rootNode, memberTypes, memberTypeNodes);
|
|
631
|
+
const scopeStack = [];
|
|
632
|
+
let fileScopedNamespace = void 0;
|
|
633
|
+
const stack = [{ node: rootNode }];
|
|
634
|
+
let cyclomaticComplexity = 1;
|
|
635
|
+
while (stack.length > 0) {
|
|
636
|
+
const item = stack.pop();
|
|
637
|
+
const { node, parentContext, isExit } = item;
|
|
638
|
+
if (isExit) {
|
|
639
|
+
if (parentContext) {
|
|
640
|
+
if (scopeStack.length > 0 && scopeStack[scopeStack.length - 1] === parentContext) {
|
|
641
|
+
scopeStack.pop();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (node.isMissing || node.hasError) {
|
|
647
|
+
if (node.type === "ERROR" || node.type === "MISSING") {
|
|
648
|
+
errors.push({
|
|
649
|
+
message: `Syntax ${node.type} detected.`,
|
|
650
|
+
range: this.nodeToRange(node),
|
|
651
|
+
severity: "warning"
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
let newContext = parentContext;
|
|
656
|
+
const type = node.type;
|
|
657
|
+
let pushedScope = false;
|
|
658
|
+
const complexityNodes = /* @__PURE__ */ new Set([
|
|
659
|
+
"if_statement",
|
|
660
|
+
"for_statement",
|
|
661
|
+
"foreach_statement",
|
|
662
|
+
"while_statement",
|
|
663
|
+
// CS-B3 (csharp review): BOTH catch forms parse as `catch_clause`
|
|
664
|
+
// (which CONTAINS an optional `catch_declaration`) — counting the
|
|
665
|
+
// inner declaration counted typed catches but never bare `catch { }`.
|
|
666
|
+
// Count the clause instead (exactly once either way); matches
|
|
667
|
+
// swift's catch_block.
|
|
668
|
+
"case_switch_label",
|
|
669
|
+
"catch_clause",
|
|
670
|
+
"conditional_expression",
|
|
671
|
+
"logical_and_expression",
|
|
672
|
+
"logical_or_expression",
|
|
673
|
+
// CSD2 (re-review 2026-09-04, probe): switch EXPRESSION arms are
|
|
674
|
+
// decision points exactly like case labels — a 3-arm switch
|
|
675
|
+
// expression measured complexity 1 (its legacy statement twin
|
|
676
|
+
// counted every case_switch_label). 42 arms in the first 400 corpus
|
|
677
|
+
// files were systematically undercounted.
|
|
678
|
+
"switch_expression_arm"
|
|
679
|
+
]);
|
|
680
|
+
if (complexityNodes.has(type)) cyclomaticComplexity++;
|
|
681
|
+
if (type === "using_directive") {
|
|
682
|
+
const nameNode = this.findChild(
|
|
683
|
+
node,
|
|
684
|
+
(n) => n.type === "identifier" || n.type === "qualified_name" || n.type === "alias_qualified_name"
|
|
685
|
+
);
|
|
686
|
+
if (nameNode) {
|
|
687
|
+
const usingTarget = csharpUsingDirectiveTarget(node);
|
|
688
|
+
if (!usingTarget) {
|
|
689
|
+
if (node.hasError) {
|
|
690
|
+
errors.push({
|
|
691
|
+
message: "Syntax ERROR detected in using directive.",
|
|
692
|
+
range: this.nodeToRange(node),
|
|
693
|
+
severity: "warning"
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
const { module: text, typeArguments, targetNode: nameNode2, alias: usingAlias, kind: usingKind } = usingTarget;
|
|
699
|
+
imports.push({
|
|
700
|
+
module: text,
|
|
701
|
+
source: text,
|
|
702
|
+
imported: [],
|
|
703
|
+
range: this.nodeToRange(node),
|
|
704
|
+
isTypeOnly: false
|
|
705
|
+
});
|
|
706
|
+
relationships.push({
|
|
707
|
+
source: "root",
|
|
708
|
+
target: text,
|
|
709
|
+
type: ASTEngine.RelationshipType.References,
|
|
710
|
+
confidence: 0.9,
|
|
711
|
+
// M59.A7 (#825): the line of the node that NAMES the target — the
|
|
712
|
+
// qualified_name / identifier the using directive writes, which for
|
|
713
|
+
// the alias form (`using A = B.C;`) is the TARGET, never the alias.
|
|
714
|
+
// `nodeNamingToken` descends to the last segment's own node, so a
|
|
715
|
+
// WRAPPED qualified name stamps the line its leaf is written on
|
|
716
|
+
// rather than the line the name starts on.
|
|
717
|
+
line_number: this.nodeLine(this.nodeNamingToken(nameNode2, text) ?? nameNode2),
|
|
718
|
+
// isImport (family contract — c/go emit import deps with isImport)
|
|
719
|
+
// lets the completeness section-B `imports` pin and the tier
|
|
720
|
+
// classifier mark this import_based.
|
|
721
|
+
metadata: {
|
|
722
|
+
isUsingDirective: true,
|
|
723
|
+
isImport: true,
|
|
724
|
+
...usingAlias ? { isUsingAlias: true, usingAlias } : {}
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
for (const arg of typeArguments) {
|
|
728
|
+
if (arg === text) continue;
|
|
729
|
+
relationships.push({
|
|
730
|
+
source: "root",
|
|
731
|
+
target: arg,
|
|
732
|
+
type: ASTEngine.RelationshipType.References,
|
|
733
|
+
confidence: 0.9,
|
|
734
|
+
line_number: this.nodeLine(this.nodeNamingToken(nameNode2, arg) ?? nameNode2),
|
|
735
|
+
metadata: { relationshipType: "using_type_argument" }
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
const citationRoots = dotnetCitationRoots(text);
|
|
739
|
+
relationships.push({
|
|
740
|
+
source: "root",
|
|
741
|
+
target: text,
|
|
742
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
743
|
+
confidence: 1,
|
|
744
|
+
line_number: this.nodeLine(this.nodeNamingToken(nameNode2, text) ?? nameNode2),
|
|
745
|
+
metadata: {
|
|
746
|
+
isUsingDirective: true,
|
|
747
|
+
isImport: true,
|
|
748
|
+
relationshipType: "using_import",
|
|
749
|
+
importKind: usingKind,
|
|
750
|
+
...usingAlias ? { isUsingAlias: true, usingAlias } : {},
|
|
751
|
+
...citationRoots.length > 0 ? { languageNamespace: citationRoots, languageNamespacePolicy: "strict-absent" } : {}
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (type === "extern_alias_directive") {
|
|
757
|
+
const aliasNode = node.childForFieldName?.("name") ?? this.findChild(node, "identifier");
|
|
758
|
+
const aliasText = aliasNode ? csharpTypeNameText(String(aliasNode.text ?? "")) : "";
|
|
759
|
+
if (aliasText) {
|
|
760
|
+
imports.push({
|
|
761
|
+
module: aliasText,
|
|
762
|
+
source: aliasText,
|
|
763
|
+
imported: [],
|
|
764
|
+
range: this.nodeToRange(node),
|
|
765
|
+
isTypeOnly: false
|
|
766
|
+
});
|
|
767
|
+
relationships.push({
|
|
768
|
+
source: "root",
|
|
769
|
+
target: aliasText,
|
|
770
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
771
|
+
confidence: 1,
|
|
772
|
+
line_number: this.nodeLine(aliasNode),
|
|
773
|
+
metadata: {
|
|
774
|
+
isImport: true,
|
|
775
|
+
isExternAlias: true,
|
|
776
|
+
relationshipType: "extern_alias_import",
|
|
777
|
+
importKind: "extern_alias"
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (type === "namespace_declaration") {
|
|
783
|
+
if (fileScopedNamespace) {
|
|
784
|
+
const fi = scopeStack.indexOf(fileScopedNamespace);
|
|
785
|
+
if (fi >= 0) scopeStack.splice(fi, 1);
|
|
786
|
+
fileScopedNamespace = void 0;
|
|
787
|
+
}
|
|
788
|
+
const rawName = this.extractNamespaceName(node);
|
|
789
|
+
const fullName = rawName.indexOf(".") >= 0 ? rawName : this.buildFullName(scopeStack, rawName);
|
|
790
|
+
symbols.push({
|
|
791
|
+
name: rawName,
|
|
792
|
+
kind: ASTEngine.SymbolKind.Namespace,
|
|
793
|
+
range: this.nodeToRange(node),
|
|
794
|
+
visibility: "public",
|
|
795
|
+
metadata: { isCSharpNamespace: true, fullName }
|
|
796
|
+
});
|
|
797
|
+
newContext = {
|
|
798
|
+
node,
|
|
799
|
+
name: rawName,
|
|
800
|
+
rawName,
|
|
801
|
+
kind: ASTEngine.SymbolKind.Namespace,
|
|
802
|
+
fqn: fullName,
|
|
803
|
+
visibility: "public",
|
|
804
|
+
isInterface: false,
|
|
805
|
+
parents: []
|
|
806
|
+
};
|
|
807
|
+
scopeStack.push(newContext);
|
|
808
|
+
pushedScope = true;
|
|
809
|
+
} else if (type === "file_scoped_namespace_declaration") {
|
|
810
|
+
if (fileScopedNamespace) {
|
|
811
|
+
const fi = scopeStack.indexOf(fileScopedNamespace);
|
|
812
|
+
if (fi >= 0) scopeStack.splice(fi, 1);
|
|
813
|
+
fileScopedNamespace = void 0;
|
|
814
|
+
}
|
|
815
|
+
const rawName = this.extractNamespaceName(node);
|
|
816
|
+
const fullName = rawName.indexOf(".") >= 0 ? rawName : this.buildFullName(scopeStack, rawName);
|
|
817
|
+
symbols.push({
|
|
818
|
+
name: rawName,
|
|
819
|
+
kind: ASTEngine.SymbolKind.Namespace,
|
|
820
|
+
range: this.nodeToRange(node),
|
|
821
|
+
visibility: "public",
|
|
822
|
+
metadata: { isCSharpNamespace: true, fullName, isFileScoped: true }
|
|
823
|
+
});
|
|
824
|
+
fileScopedNamespace = {
|
|
825
|
+
node,
|
|
826
|
+
name: rawName,
|
|
827
|
+
rawName,
|
|
828
|
+
kind: ASTEngine.SymbolKind.Namespace,
|
|
829
|
+
fqn: fullName,
|
|
830
|
+
visibility: "public",
|
|
831
|
+
isInterface: false,
|
|
832
|
+
parents: []
|
|
833
|
+
};
|
|
834
|
+
newContext = fileScopedNamespace;
|
|
835
|
+
scopeStack.push(newContext);
|
|
836
|
+
pushedScope = false;
|
|
837
|
+
} else if (type === "class_declaration" || type === "struct_declaration" || type === "interface_declaration" || type === "record_declaration") {
|
|
838
|
+
let rawName = node.childForFieldName("name")?.text || this.childText(node, "identifier") || "anonymous";
|
|
839
|
+
const fullName = this.buildFullName(scopeStack, rawName);
|
|
840
|
+
const isInterface = type === "interface_declaration";
|
|
841
|
+
const kind = csharpTypeDeclarationKind(node) ?? ASTEngine.SymbolKind.Class;
|
|
842
|
+
const visibility = this.extractVisibility(node);
|
|
843
|
+
typeKinds.set(rawName, kind);
|
|
844
|
+
const ownerScope = parentContext?.kind === ASTEngine.SymbolKind.Namespace ? parentContext : fileScopedNamespace && !parentContext ? fileScopedNamespace : void 0;
|
|
845
|
+
const parents = [];
|
|
846
|
+
const baseList = this.findChild(node, "base_list");
|
|
847
|
+
if (baseList) {
|
|
848
|
+
const baseTypes = [];
|
|
849
|
+
const baseNodes = [];
|
|
850
|
+
for (let i = 0; i < baseList.namedChildCount; i++) {
|
|
851
|
+
const baseType = baseList.namedChild(i);
|
|
852
|
+
if (!baseType) continue;
|
|
853
|
+
if (baseType.type === "primary_constructor_base_type") {
|
|
854
|
+
const tn = this.findChild(baseType, (n) => n.type === "identifier" || n.type === "generic_name");
|
|
855
|
+
if (tn) {
|
|
856
|
+
baseTypes.push(this.cleanTypeName(tn.text));
|
|
857
|
+
baseNodes.push(tn);
|
|
858
|
+
}
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (baseType.type === "argument_list") continue;
|
|
862
|
+
baseTypes.push(this.cleanTypeName(csharpTypeNameText(baseType.text)));
|
|
863
|
+
baseNodes.push(baseType);
|
|
864
|
+
}
|
|
865
|
+
if (isInterface) {
|
|
866
|
+
for (let i = 0; i < baseTypes.length; i++) {
|
|
867
|
+
const b = baseTypes[i];
|
|
868
|
+
parents.push(b);
|
|
869
|
+
relationships.push({
|
|
870
|
+
source: this.declarationPath(scopeStack, rawName),
|
|
871
|
+
target: b,
|
|
872
|
+
type: ASTEngine.RelationshipType.Extends,
|
|
873
|
+
confidence: 1,
|
|
874
|
+
line_number: this.nodeLine(this.nodeNamingToken(baseNodes[i], b) ?? baseNodes[i]),
|
|
875
|
+
metadata: { isInterfaceBase: true }
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
} else {
|
|
879
|
+
const crossFileKinds = context?.typeKinds;
|
|
880
|
+
for (let i = 0; i < baseTypes.length; i++) {
|
|
881
|
+
const b = baseTypes[i];
|
|
882
|
+
parents.push(b);
|
|
883
|
+
const dotIdx = b.lastIndexOf(".");
|
|
884
|
+
const leaf = dotIdx > 0 ? b.slice(dotIdx + 1) : b;
|
|
885
|
+
const declaredKind = typeKinds.get(b) || crossFileKinds?.get(b) || typeKinds.get(leaf) || crossFileKinds?.get(leaf);
|
|
886
|
+
const isStruct = kind === ASTEngine.SymbolKind.Struct;
|
|
887
|
+
const looksLikeInterface = !declaredKind && (isStruct || i === 0 && leaf.charCodeAt(0) === 73 && leaf.charCodeAt(1) >= 65 && leaf.charCodeAt(1) <= 90 && leaf.length > 2);
|
|
888
|
+
const isImplements = declaredKind === ASTEngine.SymbolKind.Interface || looksLikeInterface || i > 0 && !declaredKind;
|
|
889
|
+
relationships.push({
|
|
890
|
+
source: this.declarationPath(scopeStack, rawName),
|
|
891
|
+
target: b,
|
|
892
|
+
type: isImplements ? ASTEngine.RelationshipType.Implements : ASTEngine.RelationshipType.Extends,
|
|
893
|
+
confidence: 1,
|
|
894
|
+
// the leaf's own node: a wrapped qualified base writes its last
|
|
895
|
+
// segment on a different line than the name starts on
|
|
896
|
+
line_number: this.nodeLine(this.nodeNamingToken(baseNodes[i], b) ?? baseNodes[i]),
|
|
897
|
+
metadata: { isImplements }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
classHierarchy.set(fullName, parents);
|
|
903
|
+
if (rawName !== fullName && !classHierarchy.has(rawName)) classHierarchy.set(rawName, parents);
|
|
904
|
+
const typeParamsNode = this.findChild(node, "type_parameter_list");
|
|
905
|
+
const genericTypes = [];
|
|
906
|
+
if (typeParamsNode) {
|
|
907
|
+
for (let i = 0; i < typeParamsNode.namedChildCount; i++) {
|
|
908
|
+
const child = typeParamsNode.namedChild(i);
|
|
909
|
+
if (child) genericTypes.push(child.text);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const isRecord = stringIncludes(type, "record");
|
|
913
|
+
const paramList = this.findChild(node, "parameter_list");
|
|
914
|
+
if (isRecord && paramList) {
|
|
915
|
+
for (let i = 0; i < paramList.namedChildCount; i++) {
|
|
916
|
+
const p = paramList.namedChild(i);
|
|
917
|
+
if (p && p.type === "parameter") {
|
|
918
|
+
const pName = p.childForFieldName("name")?.text || this.childText(p, "identifier") || "anonymous";
|
|
919
|
+
const pType = this.extractTypeString(p);
|
|
920
|
+
symbols.push({
|
|
921
|
+
name: pName,
|
|
922
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
923
|
+
range: this.nodeToRange(p),
|
|
924
|
+
visibility: "public",
|
|
925
|
+
ownerName: rawName,
|
|
926
|
+
ownerKind: kind,
|
|
927
|
+
metadata: { hasGet: true, hasInit: true, dataType: pType, isRecordProperty: true }
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const sym = {
|
|
933
|
+
name: rawName,
|
|
934
|
+
kind,
|
|
935
|
+
range: this.nodeToRange(node),
|
|
936
|
+
visibility,
|
|
937
|
+
ownerName: ownerScope ? ownerScope.rawName : parentContext?.rawName,
|
|
938
|
+
ownerKind: ownerScope ? ownerScope.kind : parentContext?.kind,
|
|
939
|
+
decorators: this.extractAttributes(node),
|
|
940
|
+
metadata: {
|
|
941
|
+
isCSharpType: true,
|
|
942
|
+
isStruct: kind === ASTEngine.SymbolKind.Struct,
|
|
943
|
+
isValueType: kind === ASTEngine.SymbolKind.Struct,
|
|
944
|
+
isRecord,
|
|
945
|
+
genericTypes: genericTypes.length > 0 ? genericTypes : void 0,
|
|
946
|
+
fullName,
|
|
947
|
+
isInterface
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
symbols.push(sym);
|
|
951
|
+
const exportTopLevel = (!parentContext || parentContext.kind === ASTEngine.SymbolKind.Namespace) && !this.hasModifier(node, "file") && visibility === "public";
|
|
952
|
+
if (exportTopLevel) {
|
|
953
|
+
this.pushExportInfo(exports, fullName, genericTypes.length, this.nodeLine(node.childForFieldName("name") ?? this.declarationNameNode(node)), kind, visibility);
|
|
954
|
+
}
|
|
955
|
+
newContext = {
|
|
956
|
+
node,
|
|
957
|
+
name: fullName,
|
|
958
|
+
rawName,
|
|
959
|
+
kind,
|
|
960
|
+
fqn: fullName,
|
|
961
|
+
visibility,
|
|
962
|
+
symbolRef: sym,
|
|
963
|
+
isInterface,
|
|
964
|
+
parents
|
|
965
|
+
};
|
|
966
|
+
scopeStack.push(newContext);
|
|
967
|
+
pushedScope = true;
|
|
968
|
+
} else if (type === "enum_declaration") {
|
|
969
|
+
const rawName = node.childForFieldName("name")?.text || this.childText(node, "identifier") || "anonymous";
|
|
970
|
+
const fullName = this.buildFullName(scopeStack, rawName);
|
|
971
|
+
const visibility = this.extractVisibility(node);
|
|
972
|
+
typeKinds.set(rawName, ASTEngine.SymbolKind.Enum);
|
|
973
|
+
const ownerScope = parentContext?.kind === ASTEngine.SymbolKind.Namespace ? parentContext : fileScopedNamespace && !parentContext ? fileScopedNamespace : void 0;
|
|
974
|
+
let baseType;
|
|
975
|
+
const baseList = this.findChild(node, "base_list");
|
|
976
|
+
if (baseList) baseType = baseList.namedChild(0)?.text;
|
|
977
|
+
symbols.push({
|
|
978
|
+
name: rawName,
|
|
979
|
+
kind: ASTEngine.SymbolKind.Enum,
|
|
980
|
+
range: this.nodeToRange(node),
|
|
981
|
+
visibility,
|
|
982
|
+
ownerName: ownerScope ? ownerScope.rawName : parentContext?.rawName,
|
|
983
|
+
ownerKind: ownerScope ? ownerScope.kind : parentContext?.kind,
|
|
984
|
+
decorators: this.extractAttributes(node),
|
|
985
|
+
metadata: { isEnum: true, baseType, fullName }
|
|
986
|
+
});
|
|
987
|
+
if ((!parentContext || parentContext.kind === ASTEngine.SymbolKind.Namespace) && visibility === "public") {
|
|
988
|
+
this.pushExportInfo(exports, fullName, 0, this.nodeLine(node.childForFieldName("name") ?? this.declarationNameNode(node)), ASTEngine.SymbolKind.Enum, visibility);
|
|
989
|
+
}
|
|
990
|
+
newContext = {
|
|
991
|
+
node,
|
|
992
|
+
name: rawName,
|
|
993
|
+
rawName,
|
|
994
|
+
kind: ASTEngine.SymbolKind.Enum,
|
|
995
|
+
fqn: fullName,
|
|
996
|
+
visibility,
|
|
997
|
+
isInterface: false,
|
|
998
|
+
parents: []
|
|
999
|
+
};
|
|
1000
|
+
scopeStack.push(newContext);
|
|
1001
|
+
pushedScope = true;
|
|
1002
|
+
} else if (type === "enum_member_declaration") {
|
|
1003
|
+
const name = node.childForFieldName("name")?.text || this.childText(node, "identifier") || "anonymous";
|
|
1004
|
+
symbols.push({
|
|
1005
|
+
name,
|
|
1006
|
+
kind: ASTEngine.SymbolKind.EnumMember,
|
|
1007
|
+
range: this.nodeToRange(node),
|
|
1008
|
+
visibility: "public",
|
|
1009
|
+
ownerName: parentContext?.rawName,
|
|
1010
|
+
ownerKind: parentContext?.kind,
|
|
1011
|
+
metadata: {}
|
|
1012
|
+
});
|
|
1013
|
+
} else if (type === "delegate_declaration") {
|
|
1014
|
+
const name = node.childForFieldName("name")?.text || this.childText(node, "identifier") || "anonymous";
|
|
1015
|
+
const returnType = node.childForFieldName("type")?.text || "void";
|
|
1016
|
+
const parameters = [];
|
|
1017
|
+
const paramList = this.findChild(node, "parameter_list");
|
|
1018
|
+
if (paramList) {
|
|
1019
|
+
for (let i = 0; i < paramList.namedChildCount; i++) {
|
|
1020
|
+
const parameter = paramList.namedChild(i);
|
|
1021
|
+
if (parameter?.type !== "parameter") continue;
|
|
1022
|
+
const parameterName = parameter.childForFieldName("name")?.text || "anonymous";
|
|
1023
|
+
parameters.push({ name: parameterName, type: parameter.childForFieldName("type")?.text });
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const visibility = this.extractVisibility(node);
|
|
1027
|
+
const ownerScope = parentContext?.kind === ASTEngine.SymbolKind.Namespace ? parentContext : fileScopedNamespace && !parentContext ? fileScopedNamespace : void 0;
|
|
1028
|
+
const delegateFullName = this.buildFullName(scopeStack, name);
|
|
1029
|
+
const delegateTypeParams = this.findChild(node, "type_parameter_list");
|
|
1030
|
+
const delegateArity = delegateTypeParams ? delegateTypeParams.namedChildCount : 0;
|
|
1031
|
+
symbols.push({
|
|
1032
|
+
name,
|
|
1033
|
+
kind: ASTEngine.SymbolKind.Delegate,
|
|
1034
|
+
range: this.nodeToRange(node),
|
|
1035
|
+
visibility,
|
|
1036
|
+
ownerName: ownerScope ? ownerScope.rawName : parentContext?.rawName,
|
|
1037
|
+
ownerKind: ownerScope ? ownerScope.kind : parentContext?.kind,
|
|
1038
|
+
parameters,
|
|
1039
|
+
returnType,
|
|
1040
|
+
decorators: this.extractAttributes(node),
|
|
1041
|
+
metadata: { isDelegate: true, returnType, fullName: delegateFullName, genericTypes: delegateArity > 0 ? delegateArity : void 0 }
|
|
1042
|
+
});
|
|
1043
|
+
if ((!parentContext || parentContext.kind === ASTEngine.SymbolKind.Namespace) && !this.hasModifier(node, "file") && visibility === "public") {
|
|
1044
|
+
this.pushExportInfo(exports, delegateFullName, delegateArity, this.nodeLine(node.childForFieldName("name") ?? this.declarationNameNode(node)), ASTEngine.SymbolKind.Delegate, visibility);
|
|
1045
|
+
}
|
|
1046
|
+
} else if (type === "event_field_declaration") {
|
|
1047
|
+
const varDecl = this.findChild(node, "variable_declaration");
|
|
1048
|
+
const visibility = this.extractVisibility(node);
|
|
1049
|
+
if (varDecl) {
|
|
1050
|
+
for (let i = 0; i < varDecl.namedChildCount; i++) {
|
|
1051
|
+
const child = varDecl.namedChild(i);
|
|
1052
|
+
if (child && child.type === "variable_declarator") {
|
|
1053
|
+
const name = this.childText(child, "identifier") || "anonymous";
|
|
1054
|
+
this.collectMemberContract(
|
|
1055
|
+
node,
|
|
1056
|
+
name,
|
|
1057
|
+
parentContext,
|
|
1058
|
+
parentContext ? parentContext.fqn : "",
|
|
1059
|
+
parentContext ? `${parentContext.rawName}.${name}` : name,
|
|
1060
|
+
interfaceTasks,
|
|
1061
|
+
interfaceMembers,
|
|
1062
|
+
classMembers,
|
|
1063
|
+
this.findChild(child, (n) => n.type === "identifier") ?? void 0
|
|
1064
|
+
);
|
|
1065
|
+
symbols.push({
|
|
1066
|
+
name,
|
|
1067
|
+
kind: ASTEngine.SymbolKind.Event,
|
|
1068
|
+
range: this.nodeToRange(node),
|
|
1069
|
+
visibility,
|
|
1070
|
+
ownerName: parentContext?.rawName,
|
|
1071
|
+
ownerKind: parentContext?.kind,
|
|
1072
|
+
decorators: this.extractAttributes(node),
|
|
1073
|
+
metadata: { isEvent: true }
|
|
1074
|
+
});
|
|
1075
|
+
if (this.hasModifier(node, "override")) {
|
|
1076
|
+
overrideTasks.push({
|
|
1077
|
+
fqn: parentContext ? `${parentContext.rawName}.${name}` : name,
|
|
1078
|
+
methodName: name,
|
|
1079
|
+
signature: "",
|
|
1080
|
+
memberKind: "event",
|
|
1081
|
+
ownerFqn: parentContext ? parentContext.fqn : "",
|
|
1082
|
+
line: this.nodeLine(this.findChild(child, (n) => n.type === "identifier") ?? child)
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
} else if (type === "operator_declaration" || type === "conversion_operator_declaration") {
|
|
1089
|
+
const opField = node.childForFieldName("operator");
|
|
1090
|
+
const convTargetNode = type === "conversion_operator_declaration" ? node.childForFieldName("type") : null;
|
|
1091
|
+
const modifierKeyword = this.anonymousChildNode(node, "implicit") ?? this.anonymousChildNode(node, "explicit");
|
|
1092
|
+
const checkedKeyword = this.anonymousChildNode(node, "checked");
|
|
1093
|
+
const opName = modifierKeyword ? `${modifierKeyword.type}${convTargetNode ? ` ${csharpTypeNameText(csharpTypeSpelling(convTargetNode.text))}` : ""}` : opField ? `operator ${checkedKeyword ? "checked " : ""}${opField.text}` : "operator";
|
|
1094
|
+
const visibility = this.extractVisibility(node);
|
|
1095
|
+
symbols.push({
|
|
1096
|
+
name: opName,
|
|
1097
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
1098
|
+
range: this.nodeToRange(node),
|
|
1099
|
+
visibility,
|
|
1100
|
+
ownerName: parentContext?.rawName,
|
|
1101
|
+
ownerKind: parentContext?.kind,
|
|
1102
|
+
complexity: this.calculateCyclomaticComplexity(node),
|
|
1103
|
+
metadata: { isOperatorOverload: true, isConversionOperator: type === "conversion_operator_declaration" }
|
|
1104
|
+
});
|
|
1105
|
+
} else if (type === "event_declaration") {
|
|
1106
|
+
const nameNode = node.childForFieldName("name");
|
|
1107
|
+
const name = nameNode?.text || this.childText(node, "identifier") || "anonymous";
|
|
1108
|
+
const typeNode = this.findChild(
|
|
1109
|
+
node,
|
|
1110
|
+
(n) => n.type === "qualified_name" || (n.type === "identifier" || n.type === "generic_name") && n.id !== nameNode?.id
|
|
1111
|
+
);
|
|
1112
|
+
this.collectMemberContract(
|
|
1113
|
+
node,
|
|
1114
|
+
name,
|
|
1115
|
+
parentContext,
|
|
1116
|
+
parentContext ? parentContext.fqn : "",
|
|
1117
|
+
parentContext ? `${parentContext.rawName}.${name}` : name,
|
|
1118
|
+
interfaceTasks,
|
|
1119
|
+
interfaceMembers,
|
|
1120
|
+
classMembers
|
|
1121
|
+
);
|
|
1122
|
+
symbols.push({
|
|
1123
|
+
name,
|
|
1124
|
+
kind: ASTEngine.SymbolKind.Event,
|
|
1125
|
+
range: this.nodeToRange(node),
|
|
1126
|
+
visibility: this.extractVisibility(node),
|
|
1127
|
+
ownerName: parentContext?.rawName,
|
|
1128
|
+
ownerKind: parentContext?.kind,
|
|
1129
|
+
decorators: this.extractAttributes(node),
|
|
1130
|
+
metadata: { isEvent: true, isAccessorForm: true, dataType: typeNode?.text }
|
|
1131
|
+
});
|
|
1132
|
+
if (this.hasModifier(node, "override")) {
|
|
1133
|
+
overrideTasks.push({
|
|
1134
|
+
fqn: parentContext ? `${parentContext.rawName}.${name}` : name,
|
|
1135
|
+
methodName: name,
|
|
1136
|
+
signature: "",
|
|
1137
|
+
memberKind: "event",
|
|
1138
|
+
ownerFqn: parentContext ? parentContext.fqn : "",
|
|
1139
|
+
line: this.nodeLine(nameNode ?? this.declarationNameNode(node))
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
} else if (type === "constructor_initializer") {
|
|
1143
|
+
const target = this.matchesBaseInitializer(node.text) ? "base" : "this";
|
|
1144
|
+
if (parentContext?.name) {
|
|
1145
|
+
relationships.push({
|
|
1146
|
+
source: parentContext.name === "constructor" ? parentContext.rawName || "constructor" : parentContext.name,
|
|
1147
|
+
target,
|
|
1148
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
1149
|
+
confidence: 0.95,
|
|
1150
|
+
// M59.A7 (#825): the `base`/`this` KEYWORD, not the initializer: the
|
|
1151
|
+
// node starts at the `:` and a wrapped initializer writes the
|
|
1152
|
+
// keyword on the next line. Both tokens are anonymous children.
|
|
1153
|
+
line_number: this.nodeLine(this.initializerKeywordNode(node)),
|
|
1154
|
+
metadata: { isConstructorInitializer: true }
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
} else if (type === "method_declaration" || type === "constructor_declaration" || type === "destructor_declaration") {
|
|
1158
|
+
let name = node.childForFieldName("name")?.text || this.childText(node, "identifier") || (type === "constructor_declaration" ? parentContext?.rawName : "anonymous") || "anonymous";
|
|
1159
|
+
if (type === "constructor_declaration") name = "constructor";
|
|
1160
|
+
if (type === "destructor_declaration") name = "Finalize";
|
|
1161
|
+
const visibility = this.extractVisibility(node);
|
|
1162
|
+
const returnType = type === "constructor_declaration" || type === "destructor_declaration" ? "void" : node.childForFieldName("type")?.text || this.extractTypeString(node) || "void";
|
|
1163
|
+
const parameters = [];
|
|
1164
|
+
let isExtensionMethod = false;
|
|
1165
|
+
let sigParams = "";
|
|
1166
|
+
const paramList = this.findChild(node, "parameter_list");
|
|
1167
|
+
if (paramList) {
|
|
1168
|
+
let inParamsForm = false;
|
|
1169
|
+
let paramsTypeNode = null;
|
|
1170
|
+
for (let i = 0; i < paramList.childCount; i++) {
|
|
1171
|
+
const child = paramList.child(i);
|
|
1172
|
+
if (!child) continue;
|
|
1173
|
+
const childType = child.type;
|
|
1174
|
+
if (childType === "params") {
|
|
1175
|
+
inParamsForm = true;
|
|
1176
|
+
paramsTypeNode = null;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
if (childType === "parameter") {
|
|
1180
|
+
inParamsForm = false;
|
|
1181
|
+
paramsTypeNode = null;
|
|
1182
|
+
const pName = child.childForFieldName("name")?.text || this.childText(child, "identifier") || "anonymous";
|
|
1183
|
+
const pTypeField = child.childForFieldName("type")?.text;
|
|
1184
|
+
let pType = pTypeField || this.extractTypeString(child) || void 0;
|
|
1185
|
+
if (!pTypeField && pType && pType === pName) pType = void 0;
|
|
1186
|
+
if (child.text.startsWith("this ")) isExtensionMethod = true;
|
|
1187
|
+
parameters.push(pType ? { name: pName, type: pType } : { name: pName });
|
|
1188
|
+
if (sigParams.length > 0) sigParams += ",";
|
|
1189
|
+
sigParams += pType ?? csharpTypeNameText(child.text);
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (!inParamsForm) continue;
|
|
1193
|
+
if (!paramsTypeNode && CSHARP_DECLARED_TYPE_NODES.has(childType)) {
|
|
1194
|
+
paramsTypeNode = child;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
if (childType === "identifier" || childType === "generic_name" || childType === "qualified_name") {
|
|
1198
|
+
const pName = csharpTypeNameText(child.text);
|
|
1199
|
+
if (pName.length > 0) {
|
|
1200
|
+
const pType = paramsTypeNode ? csharpTypeNameText(paramsTypeNode.text) : void 0;
|
|
1201
|
+
parameters.push(pType ? { name: pName, type: pType } : { name: pName });
|
|
1202
|
+
if (sigParams.length > 0) sigParams += ",";
|
|
1203
|
+
sigParams += pType ?? pName;
|
|
1204
|
+
}
|
|
1205
|
+
inParamsForm = false;
|
|
1206
|
+
paramsTypeNode = null;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
const signature = `(${sigParams})`;
|
|
1211
|
+
const ownerFqn = parentContext ? parentContext.fqn : "";
|
|
1212
|
+
const fqn = parentContext ? `${ownerFqn}.${name}${signature}` : `${name}${signature}`;
|
|
1213
|
+
const localOwnerPrefix = parentContext ? `${parentContext.rawName}.${name}${signature}` : `${name}${signature}`;
|
|
1214
|
+
const sym = {
|
|
1215
|
+
name,
|
|
1216
|
+
kind: type === "constructor_declaration" ? ASTEngine.SymbolKind.Constructor : ASTEngine.SymbolKind.Method,
|
|
1217
|
+
range: this.nodeToRange(node),
|
|
1218
|
+
visibility,
|
|
1219
|
+
ownerName: parentContext?.rawName,
|
|
1220
|
+
ownerKind: parentContext?.kind,
|
|
1221
|
+
parameters,
|
|
1222
|
+
returnType,
|
|
1223
|
+
decorators: this.extractAttributes(node),
|
|
1224
|
+
// Per-symbol cyclomatic complexity (family contract — JVM/rust/go/c
|
|
1225
|
+
// all emit symbol.complexity; csharp only had file-level complexity, so
|
|
1226
|
+
// the completeness section-C payload pin was impossible. Added csharp
|
|
1227
|
+
// #264 wave, same counter as the file-level one).
|
|
1228
|
+
complexity: this.calculateCyclomaticComplexity(node),
|
|
1229
|
+
metadata: {
|
|
1230
|
+
returnType,
|
|
1231
|
+
isExtensionMethod,
|
|
1232
|
+
isVirtual: this.hasModifier(node, "virtual"),
|
|
1233
|
+
isOverride: this.hasModifier(node, "override"),
|
|
1234
|
+
isAbstract: this.hasModifier(node, "abstract"),
|
|
1235
|
+
isStatic: this.hasModifier(node, "static")
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
symbols.push(sym);
|
|
1239
|
+
symbolMap.set(fqn, sym);
|
|
1240
|
+
symbolMap.set(localOwnerPrefix, sym);
|
|
1241
|
+
this.collectMemberContract(node, name, parentContext, ownerFqn, localOwnerPrefix, interfaceTasks, interfaceMembers, classMembers);
|
|
1242
|
+
if (this.hasModifier(node, "override")) {
|
|
1243
|
+
overrideTasks.push({
|
|
1244
|
+
fqn: localOwnerPrefix,
|
|
1245
|
+
methodName: name,
|
|
1246
|
+
signature,
|
|
1247
|
+
memberKind: "method",
|
|
1248
|
+
ownerFqn,
|
|
1249
|
+
// M59.A7 (#825): the NAME node, not the declaration's start — an
|
|
1250
|
+
// attribute above the override would otherwise be the claimed site
|
|
1251
|
+
// of a name written on the next line.
|
|
1252
|
+
line: this.nodeLine(this.declarationNameNode(node))
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
newContext = {
|
|
1256
|
+
node,
|
|
1257
|
+
name,
|
|
1258
|
+
rawName: name,
|
|
1259
|
+
kind: type === "constructor_declaration" ? ASTEngine.SymbolKind.Constructor : ASTEngine.SymbolKind.Method,
|
|
1260
|
+
fqn: ownerFqn,
|
|
1261
|
+
visibility,
|
|
1262
|
+
symbolRef: sym,
|
|
1263
|
+
isInterface: parentContext?.isInterface ?? false,
|
|
1264
|
+
parents: []
|
|
1265
|
+
};
|
|
1266
|
+
scopeStack.push(newContext);
|
|
1267
|
+
pushedScope = true;
|
|
1268
|
+
} else if (type === "invocation_expression") {
|
|
1269
|
+
let inPattern = false;
|
|
1270
|
+
{
|
|
1271
|
+
let q = node.parent;
|
|
1272
|
+
while (q) {
|
|
1273
|
+
const t = q.type;
|
|
1274
|
+
if (t === "constant_pattern" || t === "is_pattern_expression") {
|
|
1275
|
+
inPattern = true;
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
if (t === "invocation_expression" || t.includes("statement") || t.includes("declaration") || t.includes("block") || t.includes("function") || t === "lambda_expression" || t === "local_function_statement" || t === "query_expression" || t === "method_declaration" || t === "constructor_declaration") break;
|
|
1279
|
+
q = q.parent;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
if (inPattern) {
|
|
1283
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
1284
|
+
const child = node.namedChild(i);
|
|
1285
|
+
if (child) stack.push({ node: child, parentContext: newContext });
|
|
1286
|
+
}
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
const callExpr = this.buildCSharpCallExpression(node);
|
|
1290
|
+
const calleeNode = this.csharpCallCalleeNode(node);
|
|
1291
|
+
const enclosingKind = parentContext?.kind;
|
|
1292
|
+
const isCallSite = enclosingKind === ASTEngine.SymbolKind.Method || enclosingKind === ASTEngine.SymbolKind.Constructor || enclosingKind === ASTEngine.SymbolKind.Property || // Class-body expression contexts (field/type-member initializers and
|
|
1293
|
+
// attribute arguments) — csharp #264 Station-6: calls like
|
|
1294
|
+
// `Thread.AllocateDataSlot()`, `PSTraceSource.GetTracer(...)`,
|
|
1295
|
+
// `DependencyProperty.Register(...)` in static field initializers
|
|
1296
|
+
// live at class scope, so the enclosing context is the Class/Struct/
|
|
1297
|
+
// Interface, not a method. Previously dropped, the differential
|
|
1298
|
+
// oracle surfaced them ALL as MISSING (it derives every
|
|
1299
|
+
// invocation_expression leaf from `(expr)`).
|
|
1300
|
+
enclosingKind === ASTEngine.SymbolKind.Class || enclosingKind === ASTEngine.SymbolKind.Struct || enclosingKind === ASTEngine.SymbolKind.Interface;
|
|
1301
|
+
const callSource = parentContext?.rawName || "global";
|
|
1302
|
+
if (callExpr && callSource && (isCallSite || !parentContext)) {
|
|
1303
|
+
const target = String(callExpr.split(".").pop() || callExpr).split("<")[0];
|
|
1304
|
+
const isCSharpKeyword = CSHARP_KEYWORDS.has(target);
|
|
1305
|
+
if (!isCSharpKeyword && this.isPlainIdentifier(target) && !this.isCSharpBuiltin(callExpr) && this.isCSharpCalleeNameNode(calleeNode)) {
|
|
1306
|
+
relationships.push({
|
|
1307
|
+
source: callSource,
|
|
1308
|
+
target,
|
|
1309
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
1310
|
+
confidence: 0.9,
|
|
1311
|
+
// M59.A7 (#825): the CALLEE LEAF, never the call node — an
|
|
1312
|
+
// invocation starts at its receiver, so `helper\n .Strip()`
|
|
1313
|
+
// would otherwise be stamped on the receiver's line.
|
|
1314
|
+
line_number: this.nodeLine(this.csharpCallCalleeNode(node)),
|
|
1315
|
+
metadata: { csharpCallExpression: callExpr }
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
{
|
|
1320
|
+
const fn = node.childForFieldName("function");
|
|
1321
|
+
const nameNode = fn && fn.type === "member_access_expression" ? fn.childForFieldName("name") : fn;
|
|
1322
|
+
if (nameNode && (nameNode.type === "generic_name" || nameNode.type === "identifier")) {
|
|
1323
|
+
const idNode = nameNode.type === "generic_name" ? nameNode.childForFieldName("name") ?? nameNode.namedChild(0) : nameNode;
|
|
1324
|
+
const host = (idNode?.text ?? "").split("<")[0];
|
|
1325
|
+
if (CSHARP_RENDER_HOSTS.has(host)) {
|
|
1326
|
+
const renderSource = parentContext?.rawName || "global";
|
|
1327
|
+
const tal = nameNode.type === "generic_name" ? nameNode.namedChildren.find((c) => c && c.type === "type_argument_list") : void 0;
|
|
1328
|
+
const typeArg = tal?.namedChild(0);
|
|
1329
|
+
if (typeArg) {
|
|
1330
|
+
relationships.push({
|
|
1331
|
+
source: renderSource,
|
|
1332
|
+
target: this.cleanTypeName(csharpTypeNameText(typeArg.text)),
|
|
1333
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
1334
|
+
confidence: 0.9,
|
|
1335
|
+
line_number: this.nodeLine(typeArg),
|
|
1336
|
+
metadata: { isRenderHost: true, renderHost: host }
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
const args = node.childForFieldName("arguments");
|
|
1340
|
+
const findTypeof = (n, depth) => {
|
|
1341
|
+
if (depth > 3) return null;
|
|
1342
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1343
|
+
const c = n.namedChild(i);
|
|
1344
|
+
if (!c) continue;
|
|
1345
|
+
if (c.type === "typeof_expression") return c;
|
|
1346
|
+
const deeper = findTypeof(c, depth + 1);
|
|
1347
|
+
if (deeper) return deeper;
|
|
1348
|
+
}
|
|
1349
|
+
return null;
|
|
1350
|
+
};
|
|
1351
|
+
for (const a of args?.namedChildren ?? []) {
|
|
1352
|
+
if (!a) continue;
|
|
1353
|
+
const to = findTypeof(a, 0);
|
|
1354
|
+
if (!to) continue;
|
|
1355
|
+
const tn = this.findChild(to, (n) => n.type === "identifier" || n.type === "qualified_name" || n.type === "generic_name");
|
|
1356
|
+
if (!tn) continue;
|
|
1357
|
+
relationships.push({
|
|
1358
|
+
source: renderSource,
|
|
1359
|
+
target: this.cleanTypeName(csharpTypeNameText(tn.text)),
|
|
1360
|
+
type: ASTEngine.RelationshipType.Renders,
|
|
1361
|
+
confidence: 0.9,
|
|
1362
|
+
line_number: this.nodeLine(tn),
|
|
1363
|
+
metadata: { isRenderHost: true, renderHost: host }
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
} else if (type === "object_creation_expression") {
|
|
1370
|
+
const typeNode = this.findChild(
|
|
1371
|
+
node,
|
|
1372
|
+
(n) => n.type === "identifier" || n.type === "generic_name" || n.type === "qualified_name" || n.type === "predefined_type" || // C2a: `new global::Root()` — single-segment alias shape. Without this the edge was
|
|
1373
|
+
// not merely mis-spelled, it was ABSENT (measured: `new Root()` published
|
|
1374
|
+
// `instantiates -> Root` while `new global::Root()` published nothing).
|
|
1375
|
+
n.type === "alias_qualified_name"
|
|
1376
|
+
);
|
|
1377
|
+
if (typeNode) {
|
|
1378
|
+
const source = parentContext?.rawName || "global";
|
|
1379
|
+
relationships.push({
|
|
1380
|
+
source,
|
|
1381
|
+
target: this.cleanTypeName(typeNode.text),
|
|
1382
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
1383
|
+
confidence: 0.95,
|
|
1384
|
+
line_number: this.nodeLine(typeNode),
|
|
1385
|
+
metadata: { isNew: true }
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
} else if (type === "implicit_object_creation_expression") {
|
|
1389
|
+
const declaredType = this.resolveImplicitNewType(node, memberTypes);
|
|
1390
|
+
if (declaredType) {
|
|
1391
|
+
const source = parentContext?.rawName || "global";
|
|
1392
|
+
relationships.push({
|
|
1393
|
+
source,
|
|
1394
|
+
target: declaredType,
|
|
1395
|
+
type: ASTEngine.RelationshipType.Instantiates,
|
|
1396
|
+
confidence: 0.95,
|
|
1397
|
+
line_number: this.nodeLine(
|
|
1398
|
+
this.targetTypedNewTypeNode(node, memberTypes, memberTypeNodes, declaredType)
|
|
1399
|
+
),
|
|
1400
|
+
metadata: { isNew: true, isImplicit: true }
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
} else if (type === "property_declaration" || type === "indexer_declaration") {
|
|
1404
|
+
const name = type === "indexer_declaration" ? "this" : node.childForFieldName("name")?.text || this.childText(node, "identifier") || "anonymous";
|
|
1405
|
+
const dataType = this.extractTypeString(node);
|
|
1406
|
+
const visibility = this.extractVisibility(node);
|
|
1407
|
+
let hasGet = false;
|
|
1408
|
+
let hasSet = false;
|
|
1409
|
+
let hasInit = false;
|
|
1410
|
+
let isAutoProperty = false;
|
|
1411
|
+
const accessorList = this.findChild(node, "accessor_list");
|
|
1412
|
+
if (accessorList) {
|
|
1413
|
+
isAutoProperty = true;
|
|
1414
|
+
for (let i = 0; i < accessorList.namedChildCount; i++) {
|
|
1415
|
+
const acc = accessorList.namedChild(i);
|
|
1416
|
+
if (acc && acc.type === "accessor_declaration") {
|
|
1417
|
+
const accText = acc.text;
|
|
1418
|
+
if (stringIncludes(accText, "get")) hasGet = true;
|
|
1419
|
+
if (stringIncludes(accText, "set")) hasSet = true;
|
|
1420
|
+
if (stringIncludes(accText, "init")) hasInit = true;
|
|
1421
|
+
if (this.findChild(acc, "block") || this.findChild(acc, "arrow_expression_clause")) {
|
|
1422
|
+
isAutoProperty = false;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
} else if (this.findChild(node, "arrow_expression_clause")) {
|
|
1427
|
+
hasGet = true;
|
|
1428
|
+
}
|
|
1429
|
+
symbols.push({
|
|
1430
|
+
name,
|
|
1431
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1432
|
+
range: this.nodeToRange(node),
|
|
1433
|
+
visibility,
|
|
1434
|
+
ownerName: parentContext?.rawName,
|
|
1435
|
+
ownerKind: parentContext?.kind,
|
|
1436
|
+
decorators: this.extractAttributes(node),
|
|
1437
|
+
complexity: this.calculateCyclomaticComplexity(node),
|
|
1438
|
+
metadata: { hasGet, hasSet, hasInit, isAutoProperty, dataType }
|
|
1439
|
+
});
|
|
1440
|
+
this.collectMemberContract(
|
|
1441
|
+
node,
|
|
1442
|
+
name,
|
|
1443
|
+
parentContext,
|
|
1444
|
+
parentContext ? parentContext.fqn : "",
|
|
1445
|
+
parentContext ? `${parentContext.rawName}.${name}` : name,
|
|
1446
|
+
interfaceTasks,
|
|
1447
|
+
interfaceMembers,
|
|
1448
|
+
classMembers
|
|
1449
|
+
);
|
|
1450
|
+
if (this.hasModifier(node, "override")) {
|
|
1451
|
+
overrideTasks.push({
|
|
1452
|
+
fqn: parentContext ? `${parentContext.rawName}.${name}` : name,
|
|
1453
|
+
methodName: name,
|
|
1454
|
+
signature: "",
|
|
1455
|
+
memberKind: type === "indexer_declaration" ? "indexer" : "property",
|
|
1456
|
+
ownerFqn: parentContext ? parentContext.fqn : "",
|
|
1457
|
+
line: this.nodeLine(this.declarationNameNode(node))
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
newContext = {
|
|
1461
|
+
node,
|
|
1462
|
+
name,
|
|
1463
|
+
rawName: name,
|
|
1464
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1465
|
+
fqn: parentContext ? `${parentContext.fqn}.${name}` : name,
|
|
1466
|
+
visibility,
|
|
1467
|
+
symbolRef: symbols[symbols.length - 1],
|
|
1468
|
+
isInterface: parentContext?.isInterface ?? false,
|
|
1469
|
+
parents: []
|
|
1470
|
+
};
|
|
1471
|
+
scopeStack.push(newContext);
|
|
1472
|
+
pushedScope = true;
|
|
1473
|
+
} else if (type === "field_declaration") {
|
|
1474
|
+
const dataType = this.extractTypeString(node);
|
|
1475
|
+
const varDecl = this.findChild(node, "variable_declaration");
|
|
1476
|
+
const visibility = this.extractVisibility(node);
|
|
1477
|
+
if (varDecl) {
|
|
1478
|
+
for (let i = 0; i < varDecl.namedChildCount; i++) {
|
|
1479
|
+
const child = varDecl.namedChild(i);
|
|
1480
|
+
if (child && child.type === "variable_declarator") {
|
|
1481
|
+
const name = this.childText(child, "identifier") || "anonymous";
|
|
1482
|
+
symbols.push({
|
|
1483
|
+
name,
|
|
1484
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
1485
|
+
range: this.nodeToRange(node),
|
|
1486
|
+
visibility,
|
|
1487
|
+
ownerName: parentContext?.rawName,
|
|
1488
|
+
ownerKind: parentContext?.kind,
|
|
1489
|
+
decorators: this.extractAttributes(node),
|
|
1490
|
+
metadata: { dataType, isField: true }
|
|
1491
|
+
});
|
|
1492
|
+
if (this.hasModifier(node, "override")) {
|
|
1493
|
+
const declNameNode = this.findChild(child, (n) => n.type === "identifier") ?? child;
|
|
1494
|
+
overrideTasks.push({
|
|
1495
|
+
fqn: parentContext ? `${parentContext.rawName}.${name}` : name,
|
|
1496
|
+
methodName: name,
|
|
1497
|
+
signature: "",
|
|
1498
|
+
memberKind: "field",
|
|
1499
|
+
ownerFqn: parentContext ? parentContext.fqn : "",
|
|
1500
|
+
line: this.nodeLine(declNameNode)
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
if (pushedScope) {
|
|
1508
|
+
stack.push({ node, parentContext: newContext, isExit: true });
|
|
1509
|
+
}
|
|
1510
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
1511
|
+
const child = node.namedChild(i);
|
|
1512
|
+
if (child) {
|
|
1513
|
+
stack.push({ node: child, parentContext: newContext });
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const membersByOwner = /* @__PURE__ */ new Map();
|
|
1518
|
+
for (const sym of symbols) {
|
|
1519
|
+
if (!sym.ownerName || !sym.name) continue;
|
|
1520
|
+
let set = membersByOwner.get(sym.ownerName);
|
|
1521
|
+
if (!set) {
|
|
1522
|
+
set = /* @__PURE__ */ new Set();
|
|
1523
|
+
membersByOwner.set(sym.ownerName, set);
|
|
1524
|
+
}
|
|
1525
|
+
set.add(sym.name);
|
|
1526
|
+
}
|
|
1527
|
+
this.resolveOverrides(overrideTasks, classHierarchy, symbolMap, relationships, membersByOwner, context?.overrideCompanion);
|
|
1528
|
+
this.resolveInterfaceImplementations(interfaceTasks, classHierarchy, interfaceMembers, classMembers, relationships);
|
|
1529
|
+
const primaryType = symbols.find((s) => {
|
|
1530
|
+
if (s.kind === ASTEngine.SymbolKind.Namespace) return false;
|
|
1531
|
+
if (s.ownerKind && s.ownerKind !== ASTEngine.SymbolKind.Namespace) return false;
|
|
1532
|
+
return s.kind === ASTEngine.SymbolKind.Class || s.kind === ASTEngine.SymbolKind.Interface || s.kind === ASTEngine.SymbolKind.Enum || s.kind === ASTEngine.SymbolKind.Struct || s.kind === ASTEngine.SymbolKind.Delegate;
|
|
1533
|
+
});
|
|
1534
|
+
if (primaryType) {
|
|
1535
|
+
for (const rel of relationships) {
|
|
1536
|
+
if (rel.type === ASTEngine.RelationshipType.References && rel.source === "root") {
|
|
1537
|
+
rel.source = primaryType.name;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const containmentSites = this.collectContainmentSites(rootNode);
|
|
1542
|
+
for (const s of symbols) {
|
|
1543
|
+
const _owner = s.ownerName || s.scope;
|
|
1544
|
+
if (_owner) {
|
|
1545
|
+
relationships.push({
|
|
1546
|
+
source: _owner,
|
|
1547
|
+
target: s.name,
|
|
1548
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
1549
|
+
confidence: 1,
|
|
1550
|
+
// M59.A7 (#825): the member's own declaration — but the line its NAME
|
|
1551
|
+
// is written on, which is NOT always the declaration's start line: an
|
|
1552
|
+
// attribute (`[Parameter]`) or a wrapped signature puts the name a
|
|
1553
|
+
// line or more later. Measured on a 200-file draw: the range-start
|
|
1554
|
+
// stamp claimed a line the target is not written on for 653 of 23,924
|
|
1555
|
+
// relationships, every one of them this shape.
|
|
1556
|
+
line_number: this.containmentLine(s, containmentSites),
|
|
1557
|
+
metadata: {}
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
for (const e of exports) {
|
|
1562
|
+
if (typeof e.name !== "string" || typeof e.arity !== "number") continue;
|
|
1563
|
+
relationships.push({
|
|
1564
|
+
source: "root",
|
|
1565
|
+
target: e.name,
|
|
1566
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
1567
|
+
confidence: 1,
|
|
1568
|
+
line_number: typeof e.line === "number" ? e.line : void 0,
|
|
1569
|
+
metadata: {
|
|
1570
|
+
relationshipType: "assembly_public_surface",
|
|
1571
|
+
evidenceClass: "structural",
|
|
1572
|
+
exportForm: "public_top_level_type",
|
|
1573
|
+
visibility: "public",
|
|
1574
|
+
arity: e.arity
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
this.analyzeFieldAccessUses(rootNode, relationships, symbols);
|
|
1579
|
+
this.analyzeCSharpBareIdentifierUses(rootNode, relationships, symbols, classHierarchy);
|
|
1580
|
+
for (const ref of csharpDeclaredTypeRefs(rootNode)) {
|
|
1581
|
+
relationships.push({
|
|
1582
|
+
source: ref.source,
|
|
1583
|
+
target: ref.target,
|
|
1584
|
+
type: ASTEngine.RelationshipType.References,
|
|
1585
|
+
confidence: 0.9,
|
|
1586
|
+
...ref.line ? { line_number: ref.line } : {},
|
|
1587
|
+
metadata: { relationshipType: ref.relationshipType, declaredOn: ref.declaredOn }
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
this.classifyEvidenceTier(relationships);
|
|
1591
|
+
const { detectedFrameworks, frameworkPatterns } = this.detectCSharpFrameworks(rootNode);
|
|
1592
|
+
return {
|
|
1593
|
+
symbols,
|
|
1594
|
+
relationships,
|
|
1595
|
+
imports,
|
|
1596
|
+
exports,
|
|
1597
|
+
errors,
|
|
1598
|
+
metadata: {
|
|
1599
|
+
cyclomaticComplexity,
|
|
1600
|
+
complexity: cyclomaticComplexity,
|
|
1601
|
+
linesOfCode: content.split("\n").length,
|
|
1602
|
+
hasTypeInference: true,
|
|
1603
|
+
framework: detectedFrameworks.length > 0 ? detectedFrameworks[0] : void 0,
|
|
1604
|
+
detectedFrameworks,
|
|
1605
|
+
frameworkPatterns,
|
|
1606
|
+
hasFrameworkIntelligence: true,
|
|
1607
|
+
analysisVersion: "4.1.0-pure-ast-m12"
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Evidence-tier classification (family contract — mirrors JVM/rust/zig; M14
|
|
1613
|
+
* section D requires >=2 distinct evidenceClass values). Same rules as the
|
|
1614
|
+
* JVM family: structural (grammar clauses, conf 1.0) | usage_contextual
|
|
1615
|
+
* (calls, field access) | import_based (using-directive dependencies) |
|
|
1616
|
+
* reference_qualified (type references) | inferred (confidence < 1 edges).
|
|
1617
|
+
*/
|
|
1618
|
+
classifyEvidenceTier(relationships) {
|
|
1619
|
+
for (const r of relationships) {
|
|
1620
|
+
const type = r.type ?? r.relationshipType ?? "";
|
|
1621
|
+
const md = r.metadata ?? {};
|
|
1622
|
+
if (type === "contains") {
|
|
1623
|
+
md.evidenceClass = "structural";
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
if (type === "references") {
|
|
1627
|
+
md.evidenceClass = md.isUsingDirective || md.isImport ? "import_based" : "reference_qualified";
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
if (type === "imports") {
|
|
1631
|
+
md.evidenceClass = "import_based";
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
if (type === "calls" || type === "uses") {
|
|
1635
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.85 ? "usage_contextual" : "inferred";
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (type === "instantiates" || type === "extends" || type === "implements") {
|
|
1639
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.9 ? "structural" : "inferred";
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
if (type === "overrides") {
|
|
1643
|
+
md.evidenceClass = "structural";
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
if (type === "exports") {
|
|
1647
|
+
md.evidenceClass = "structural";
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
md.evidenceClass = "usage_contextual";
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* CONV-CS-2 (#751): framework detection — the family contract (java
|
|
1655
|
+
* sibling): metadata carries `framework` (deterministic primary pick),
|
|
1656
|
+
* `detectedFrameworks` (always an array, [] when none) and
|
|
1657
|
+
* `frameworkPatterns`. Markers are grammar-level: using-directive roots
|
|
1658
|
+
* (incl. static / alias / global forms), base-list type names and
|
|
1659
|
+
* attribute names. The emitted order is the fixed CS_FRAMEWORK_ORDER
|
|
1660
|
+
* (application frameworks most-specific first, test frameworks last), so
|
|
1661
|
+
* source/import order can never change the payload. String ops only (R2
|
|
1662
|
+
* shrink-only: no new regex literals); edges are untouched — fixtures +
|
|
1663
|
+
* mutants gate this surface.
|
|
1664
|
+
*/
|
|
1665
|
+
detectCSharpFrameworks(rootNode) {
|
|
1666
|
+
const usings = /* @__PURE__ */ new Set();
|
|
1667
|
+
const bases = /* @__PURE__ */ new Set();
|
|
1668
|
+
const attributes = /* @__PURE__ */ new Set();
|
|
1669
|
+
const stack = [rootNode];
|
|
1670
|
+
while (stack.length > 0) {
|
|
1671
|
+
const n = stack.pop();
|
|
1672
|
+
const t = n.type;
|
|
1673
|
+
if (t === "using_directive") {
|
|
1674
|
+
const path = csharpUsingDirectivePath(n.text);
|
|
1675
|
+
if (path) usings.add(path);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (t === "base_list") {
|
|
1679
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1680
|
+
const leaf = csharpTypeLeafName(n.namedChild(i));
|
|
1681
|
+
if (leaf) bases.add(leaf);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (t === "attribute") {
|
|
1685
|
+
const first = n.namedChild(0);
|
|
1686
|
+
const leaf = first ? csharpTypeLeafName(first) : void 0;
|
|
1687
|
+
if (leaf) attributes.add(leaf);
|
|
1688
|
+
}
|
|
1689
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1690
|
+
const c = n.namedChild(i);
|
|
1691
|
+
if (c) stack.push(c);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
const detected = [];
|
|
1695
|
+
for (let i = 0; i < CS_FRAMEWORK_ORDER.length; i++) {
|
|
1696
|
+
const fw = CS_FRAMEWORK_ORDER[i];
|
|
1697
|
+
if (CS_FRAMEWORK_MARKERS[fw](usings, bases, attributes)) detected.push(fw);
|
|
1698
|
+
}
|
|
1699
|
+
return {
|
|
1700
|
+
detectedFrameworks: detected,
|
|
1701
|
+
frameworkPatterns: detected.map((fw) => ({ framework: fw, patterns: CS_FRAMEWORK_PATTERNS[fw] }))
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Per-symbol cyclomatic complexity (family contract — csharp #264 wave).
|
|
1706
|
+
* Same node set as the file-level counter above so per-symbol and
|
|
1707
|
+
* file-level agree; base complexity 1.
|
|
1708
|
+
*/
|
|
1709
|
+
calculateCyclomaticComplexity(node) {
|
|
1710
|
+
let complexity = 1;
|
|
1711
|
+
const complexityNodes = /* @__PURE__ */ new Set([
|
|
1712
|
+
"if_statement",
|
|
1713
|
+
"for_statement",
|
|
1714
|
+
"foreach_statement",
|
|
1715
|
+
"while_statement",
|
|
1716
|
+
// CS-B3: `catch_clause` (not `catch_declaration`) — both catch forms
|
|
1717
|
+
// parse as catch_clause containing an optional declaration.
|
|
1718
|
+
"case_switch_label",
|
|
1719
|
+
"catch_clause",
|
|
1720
|
+
"conditional_expression",
|
|
1721
|
+
"logical_and_expression",
|
|
1722
|
+
"logical_or_expression",
|
|
1723
|
+
// CSD2: keep per-symbol in sync with the file-level counter above.
|
|
1724
|
+
"switch_expression_arm"
|
|
1725
|
+
]);
|
|
1726
|
+
const stack = [node];
|
|
1727
|
+
while (stack.length > 0) {
|
|
1728
|
+
const n = stack.pop();
|
|
1729
|
+
if (complexityNodes.has(n.type)) complexity++;
|
|
1730
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1731
|
+
const c = n.namedChild(i);
|
|
1732
|
+
if (c) stack.push(c);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return complexity;
|
|
1736
|
+
}
|
|
1737
|
+
/** Pre-pass: record every type declaration's kind for in-file base-list resolution. */
|
|
1738
|
+
// CSD11: declarator name -> declared type for fields and properties.
|
|
1739
|
+
// Feeds assignment-form target-typed `new()` resolution (see
|
|
1740
|
+
// resolveImplicitNewType). Locals are NOT collected — a local resolved
|
|
1741
|
+
// through an assignment LHS would need a locals map with block scoping;
|
|
1742
|
+
// out of scope here, honestly unresolved instead.
|
|
1743
|
+
collectMemberTypes(rootNode, memberTypes, memberTypeNodes) {
|
|
1744
|
+
const stack = [rootNode];
|
|
1745
|
+
while (stack.length > 0) {
|
|
1746
|
+
const n = stack.pop();
|
|
1747
|
+
const t = n.type;
|
|
1748
|
+
if (t === "field_declaration" || t === "property_declaration") {
|
|
1749
|
+
const isField = t === "field_declaration";
|
|
1750
|
+
const declaredType = csharpMemberDeclaredTypeText(n) ?? void 0;
|
|
1751
|
+
const names = [];
|
|
1752
|
+
if (isField) {
|
|
1753
|
+
const varDecl = this.findChild(n, "variable_declaration");
|
|
1754
|
+
if (varDecl) {
|
|
1755
|
+
for (let i = 0; i < varDecl.namedChildCount; i++) {
|
|
1756
|
+
const d = varDecl.namedChild(i);
|
|
1757
|
+
if (d?.type === "variable_declarator") {
|
|
1758
|
+
const nm = this.childText(d, "identifier");
|
|
1759
|
+
if (nm) names.push(nm);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
} else {
|
|
1764
|
+
const nm = n.childForFieldName("name")?.text || this.childText(n, "identifier");
|
|
1765
|
+
if (nm) names.push(nm);
|
|
1766
|
+
}
|
|
1767
|
+
const declaredTypeNode = this.memberDeclaredTypeNode(n);
|
|
1768
|
+
if (declaredType) {
|
|
1769
|
+
for (const nm of names) if (!memberTypes.has(nm)) memberTypes.set(nm, declaredType);
|
|
1770
|
+
}
|
|
1771
|
+
if (declaredTypeNode) {
|
|
1772
|
+
for (const nm of names) if (!memberTypeNodes.has(nm)) memberTypeNodes.set(nm, declaredTypeNode);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1776
|
+
const child = n.namedChild(i);
|
|
1777
|
+
if (child) stack.push(child);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* M59.A7 (#825): where each SYMBOL's name is written.
|
|
1783
|
+
*
|
|
1784
|
+
* The containment edge's contract is "the line of the node that NAMES the
|
|
1785
|
+
* target", and for a member that node is the declaration's NAME — which the
|
|
1786
|
+
* symbol's `range` cannot stand in for, because the range starts at the
|
|
1787
|
+
* declaration node and a C# declaration can begin a line or more before its
|
|
1788
|
+
* name (attributes such as `[Parameter]`, modifiers on their own line, a
|
|
1789
|
+
* wrapped signature). One walk over the declarations this analyzer turns into
|
|
1790
|
+
* symbols, keyed by the declaration's start index AND its name, because the
|
|
1791
|
+
* start index alone is ambiguous: a multi-declarator field (`int a, b;`)
|
|
1792
|
+
* produces two symbols that share the field_declaration's range. Keying by
|
|
1793
|
+
* name is also what makes the mapping self-protecting — a symbol whose name the
|
|
1794
|
+
* declaration does NOT write (a constructor is named `constructor`, an
|
|
1795
|
+
* operator `operator +`) finds no entry here and so can never claim a line the
|
|
1796
|
+
* target is not written on.
|
|
1797
|
+
*/
|
|
1798
|
+
collectContainmentSites(rootNode) {
|
|
1799
|
+
const named = /* @__PURE__ */ new Map();
|
|
1800
|
+
const synthesized = /* @__PURE__ */ new Set();
|
|
1801
|
+
const stack = [rootNode];
|
|
1802
|
+
while (stack.length > 0) {
|
|
1803
|
+
const n = stack.pop();
|
|
1804
|
+
const t = n.type;
|
|
1805
|
+
if (t === "field_declaration" || t === "event_field_declaration") {
|
|
1806
|
+
const varDecl = this.findChild(n, "variable_declaration");
|
|
1807
|
+
if (varDecl) {
|
|
1808
|
+
for (let i = 0; i < varDecl.namedChildCount; i++) {
|
|
1809
|
+
const d = varDecl.namedChild(i);
|
|
1810
|
+
if (!d || d.type !== "variable_declarator") continue;
|
|
1811
|
+
const id = this.findChild(d, "identifier");
|
|
1812
|
+
if (id) named.set(`${n.startIndex}\0${id.text}`, id.startPosition.row + 1);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
} else if (t === "destructor_declaration" || t === "constructor_declaration") {
|
|
1816
|
+
synthesized.add(n.startIndex);
|
|
1817
|
+
} else if (CSHARP_SYMBOL_DECLARATIONS.has(t)) {
|
|
1818
|
+
const nameNode = this.declarationNameNode(n);
|
|
1819
|
+
if (nameNode) named.set(`${n.startIndex}\0${nameNode.text}`, nameNode.startPosition.row + 1);
|
|
1820
|
+
}
|
|
1821
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1822
|
+
const child = n.namedChild(i);
|
|
1823
|
+
if (child) stack.push(child);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return { named, synthesized };
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* The containment edge's line: the NAME node where the declaration writes the
|
|
1830
|
+
* symbol's name, the declaration's own start line otherwise (a synthesised or
|
|
1831
|
+
* rewritten name still has a navigable declaration, and the oracle classifies
|
|
1832
|
+
* an uncheckable target as `notCheckable` rather than as a failed claim), and
|
|
1833
|
+
* NO line for a declaration whose symbol name is written nowhere in the file.
|
|
1834
|
+
*/
|
|
1835
|
+
containmentLine(s, sites) {
|
|
1836
|
+
const start = s.range?.startIndex;
|
|
1837
|
+
if (typeof start !== "number") return void 0;
|
|
1838
|
+
if (sites.synthesized.has(start)) return void 0;
|
|
1839
|
+
return sites.named.get(`${start}\0${s.name}`) ?? s.range?.startLine;
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* The node that WRITES a declaration's name — the symbol pass's own rule
|
|
1843
|
+
* (`childForFieldName("name")`, then the first identifier), plus the one shape
|
|
1844
|
+
* where the name is not a field: an indexer's symbol is named `this`, which the
|
|
1845
|
+
* grammar writes as an anonymous keyword child.
|
|
1846
|
+
*/
|
|
1847
|
+
declarationNameNode(node) {
|
|
1848
|
+
const field = node.childForFieldName("name");
|
|
1849
|
+
if (field) return field;
|
|
1850
|
+
if (node.type === "indexer_declaration") {
|
|
1851
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1852
|
+
const child = node.child(i);
|
|
1853
|
+
if (child && child.type === "this") return child;
|
|
1854
|
+
}
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
return this.findChild(node, "identifier");
|
|
1858
|
+
}
|
|
1859
|
+
collectDeclaredTypeKinds(rootNode, typeKinds) {
|
|
1860
|
+
const stack = [rootNode];
|
|
1861
|
+
while (stack.length > 0) {
|
|
1862
|
+
const n = stack.pop();
|
|
1863
|
+
const t = n.type;
|
|
1864
|
+
if (t === "class_declaration" || t === "struct_declaration" || t === "interface_declaration" || t === "record_declaration" || t === "enum_declaration") {
|
|
1865
|
+
const name = n.childForFieldName("name")?.text || this.childText(n, "identifier");
|
|
1866
|
+
if (name) {
|
|
1867
|
+
const kind = t === "interface_declaration" ? ASTEngine.SymbolKind.Interface : t === "enum_declaration" ? ASTEngine.SymbolKind.Enum : csharpTypeDeclarationKind(n) ?? ASTEngine.SymbolKind.Class;
|
|
1868
|
+
typeKinds.set(name, kind);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1872
|
+
const child = n.namedChild(i);
|
|
1873
|
+
if (child) stack.push(child);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* C923 (#923): record one member declaration for the interface-implementation contract — or,
|
|
1879
|
+
* when the declaration IS the interface's, record the CONTRACT it declares.
|
|
1880
|
+
*
|
|
1881
|
+
* The contract side is indexed under BOTH the interface's FQN and its local name, because a
|
|
1882
|
+
* base-list entry is written either way (`class C : IFoo` / `class C : Ns.IFoo`), and the
|
|
1883
|
+
* resolution below looks the ancestor up by the name the base list actually wrote.
|
|
1884
|
+
*/
|
|
1885
|
+
collectMemberContract(node, name, parentContext, ownerFqn, localLabel, tasks, members, classMembers, lineNode) {
|
|
1886
|
+
if (!parentContext) return;
|
|
1887
|
+
const kind = parentContext.kind;
|
|
1888
|
+
if (kind !== ASTEngine.SymbolKind.Class && kind !== ASTEngine.SymbolKind.Struct && kind !== ASTEngine.SymbolKind.Interface) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (parentContext.isInterface) {
|
|
1892
|
+
for (const key of [parentContext.fqn, parentContext.rawName]) {
|
|
1893
|
+
if (!key) continue;
|
|
1894
|
+
let set = members.get(key);
|
|
1895
|
+
if (!set) {
|
|
1896
|
+
set = /* @__PURE__ */ new Set();
|
|
1897
|
+
members.set(key, set);
|
|
1898
|
+
}
|
|
1899
|
+
set.add(name);
|
|
1900
|
+
}
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
for (const key of [parentContext.fqn, parentContext.rawName]) {
|
|
1904
|
+
if (!key) continue;
|
|
1905
|
+
let set = classMembers.get(key);
|
|
1906
|
+
if (!set) {
|
|
1907
|
+
set = /* @__PURE__ */ new Set();
|
|
1908
|
+
classMembers.set(key, set);
|
|
1909
|
+
}
|
|
1910
|
+
set.add(name);
|
|
1911
|
+
}
|
|
1912
|
+
tasks.push({
|
|
1913
|
+
fqn: localLabel,
|
|
1914
|
+
ownerFqn,
|
|
1915
|
+
memberName: name,
|
|
1916
|
+
explicitIface: this.explicitInterfaceName(node),
|
|
1917
|
+
// M59.A7: the NAME node's line. A field-like event writes its name on a
|
|
1918
|
+
// `variable_declarator`, not on the declaration's `name` field, so the caller passes that
|
|
1919
|
+
// node — otherwise the edge is stamped with an honest-but-avoidable no-line.
|
|
1920
|
+
line: this.nodeLine(lineNode ?? this.declarationNameNode(node))
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* The interface an explicit interface member writes (`void IFoo.M()` → `IFoo`). The trailing
|
|
1925
|
+
* `.` is punctuation, not part of the name, so it is cut by a linear scan (no regex).
|
|
1926
|
+
*/
|
|
1927
|
+
explicitInterfaceName(node) {
|
|
1928
|
+
const spec = this.findChild(node, (n) => n.type === "explicit_interface_specifier");
|
|
1929
|
+
if (!spec) return null;
|
|
1930
|
+
const cleaned = this.cleanTypeName(spec.text);
|
|
1931
|
+
let end = cleaned.length;
|
|
1932
|
+
while (end > 0) {
|
|
1933
|
+
const c = cleaned.charCodeAt(end - 1);
|
|
1934
|
+
if (c === 46 || c === 32 || c === 9 || c === 10 || c === 13) end--;
|
|
1935
|
+
else break;
|
|
1936
|
+
}
|
|
1937
|
+
const name = cleaned.slice(0, end);
|
|
1938
|
+
return name.length > 0 ? name : null;
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* C923 (#923): the member-level implements contract, mirroring the family's five-lane
|
|
1942
|
+
* precedent (`overrides M -> I.m`, measured in java, kotlin, typescript, dart and python).
|
|
1943
|
+
*
|
|
1944
|
+
* explicit (`void IFoo.M()`): ALWAYS. The interface is written in the declaration, so the
|
|
1945
|
+
* edge is derivable from the tree alone at any scope — measured on csharp's two pinned
|
|
1946
|
+
* corpora, 215 of 352 explicit members name an interface the corpus text never declares,
|
|
1947
|
+
* so requiring resolution would drop 61% of a class that needs none.
|
|
1948
|
+
* implicit (a member that satisfies a base-list interface): only where the member set of
|
|
1949
|
+
* that interface is KNOWN, which is the case exactly when the interface is declared in
|
|
1950
|
+
* this file. An interface the analyzer cannot see stays an honest no-edge (measured in the
|
|
1951
|
+
* full extent: 273 member declarations whose contract lives in another file, and 2,107
|
|
1952
|
+
* types whose bases are not in the corpus text at all — both disclosed, neither guessed).
|
|
1953
|
+
*
|
|
1954
|
+
* No caller context is consulted, deliberately: a rule that changes with an optional companion
|
|
1955
|
+
* would be a rule a fixture cannot pin (fixtures pass no context), and the family's own doctrine
|
|
1956
|
+
* is that nothing ships unverified. The cross-file extension is measured and registered as an
|
|
1957
|
+
* open scope limit instead (issue #923).
|
|
1958
|
+
*
|
|
1959
|
+
* First ancestor in declaration order that both IS an interface and declares the member wins —
|
|
1960
|
+
* the family's rule for a name-keyed target (cf. findDeclaringParent).
|
|
1961
|
+
*/
|
|
1962
|
+
resolveInterfaceImplementations(tasks, classHierarchy, interfaceMembers, classMembers, relationships) {
|
|
1963
|
+
if (tasks.length === 0) return;
|
|
1964
|
+
const declaresMember = (iface, member) => interfaceMembers.get(iface)?.has(member) === true;
|
|
1965
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
1966
|
+
for (const task of tasks) {
|
|
1967
|
+
let target = null;
|
|
1968
|
+
if (task.explicitIface) {
|
|
1969
|
+
target = `${task.explicitIface}.${task.memberName}`;
|
|
1970
|
+
} else if (task.ownerFqn) {
|
|
1971
|
+
const queue = [...classHierarchy.get(task.ownerFqn) ?? []];
|
|
1972
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1973
|
+
while (queue.length > 0) {
|
|
1974
|
+
const parent = queue.shift();
|
|
1975
|
+
if (seen.has(parent)) continue;
|
|
1976
|
+
seen.add(parent);
|
|
1977
|
+
if (declaresMember(parent, task.memberName)) {
|
|
1978
|
+
target = `${parent}.${task.memberName}`;
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
if (classMembers.get(parent)?.has(task.memberName)) continue;
|
|
1982
|
+
for (const next of classHierarchy.get(parent) ?? []) {
|
|
1983
|
+
if (!seen.has(next)) queue.push(next);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (!target) continue;
|
|
1988
|
+
const source = localEdgeLabel(task.fqn);
|
|
1989
|
+
const label = localEdgeName(target);
|
|
1990
|
+
if (!source || !label) continue;
|
|
1991
|
+
const key = `${source}|${label}`;
|
|
1992
|
+
if (emitted.has(key)) continue;
|
|
1993
|
+
emitted.add(key);
|
|
1994
|
+
relationships.push({
|
|
1995
|
+
source,
|
|
1996
|
+
target: label,
|
|
1997
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
1998
|
+
confidence: 1,
|
|
1999
|
+
line_number: task.line,
|
|
2000
|
+
metadata: {
|
|
2001
|
+
relationshipType: "interface_member_implementation",
|
|
2002
|
+
isCSharpInterfaceImpl: true,
|
|
2003
|
+
explicitInterfaceMember: task.explicitIface != null
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
resolveOverrides(tasks, classHierarchy, symbolMap, relationships, membersByOwner, companion) {
|
|
2009
|
+
for (const task of tasks) {
|
|
2010
|
+
let targetFqn = null;
|
|
2011
|
+
const memberKind = task.memberKind ?? "method";
|
|
2012
|
+
const isMethod = memberKind === "method";
|
|
2013
|
+
const queue = [];
|
|
2014
|
+
const ownerH = classHierarchy.get(task.ownerFqn);
|
|
2015
|
+
if (ownerH) {
|
|
2016
|
+
for (let i = 0; i < ownerH.length; i++) queue.push(ownerH[i]);
|
|
2017
|
+
}
|
|
2018
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2019
|
+
while (queue.length > 0) {
|
|
2020
|
+
const parent = queue.shift();
|
|
2021
|
+
if (seen.has(parent)) continue;
|
|
2022
|
+
seen.add(parent);
|
|
2023
|
+
if (isMethod) {
|
|
2024
|
+
const potentialTargetFqn = `${parent}.${task.methodName}${task.signature}`;
|
|
2025
|
+
if (symbolMap.has(potentialTargetFqn)) {
|
|
2026
|
+
targetFqn = potentialTargetFqn;
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
} else if (membersByOwner.get(parent)?.has(task.methodName)) {
|
|
2030
|
+
targetFqn = `${parent}.${task.methodName}`;
|
|
2031
|
+
break;
|
|
2032
|
+
}
|
|
2033
|
+
const pH = classHierarchy.get(parent);
|
|
2034
|
+
if (pH) {
|
|
2035
|
+
for (let i = 0; i < pH.length; i++) queue.push(pH[i]);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (!targetFqn && isMethod && companion && ownerH && ownerH.length > 0) {
|
|
2039
|
+
const declaring = findDeclaringParent(
|
|
2040
|
+
task.methodName,
|
|
2041
|
+
ownerH,
|
|
2042
|
+
companion,
|
|
2043
|
+
(t) => classHierarchy.get(t) ?? []
|
|
2044
|
+
);
|
|
2045
|
+
if (declaring) targetFqn = `${declaring}.${task.methodName}${task.signature}`;
|
|
2046
|
+
}
|
|
2047
|
+
if (!targetFqn && ownerH && ownerH.length > 0) {
|
|
2048
|
+
targetFqn = `${ownerH[0]}.${task.methodName}${isMethod ? task.signature : ""}`;
|
|
2049
|
+
}
|
|
2050
|
+
if (targetFqn) {
|
|
2051
|
+
const localSource = localEdgeLabel(task.fqn);
|
|
2052
|
+
const localTarget = localEdgeName(targetFqn);
|
|
2053
|
+
relationships.push({
|
|
2054
|
+
source: localSource,
|
|
2055
|
+
target: localTarget,
|
|
2056
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
2057
|
+
confidence: 1,
|
|
2058
|
+
line_number: task.line,
|
|
2059
|
+
metadata: {
|
|
2060
|
+
relationshipType: isMethod ? "method_override" : `${memberKind}_override`,
|
|
2061
|
+
isCSharpOverride: true,
|
|
2062
|
+
overriddenMemberKind: memberKind
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Field-access Uses pass (csharp #264 wave, Station 3). Emits `uses
|
|
2070
|
+
* containingFn -> field` for member_access_expression on a field (e.g.
|
|
2071
|
+
* `this.Name`, `obj.Radius`) that is NOT the callee of a call. Mirror of the
|
|
2072
|
+
* go/rust selector-uses family contract. Call-callee member accesses are
|
|
2073
|
+
* skipped (calls analysis emits the leaf-target calls edge instead).
|
|
2074
|
+
*/
|
|
2075
|
+
analyzeFieldAccessUses(rootNode, relationships, symbols) {
|
|
2076
|
+
const stack = [rootNode];
|
|
2077
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2078
|
+
while (stack.length > 0) {
|
|
2079
|
+
const node = stack.pop();
|
|
2080
|
+
if (node.type === "member_access_expression" || node.type === "member_binding_expression") {
|
|
2081
|
+
let isCallee = false;
|
|
2082
|
+
const parent = node.parent;
|
|
2083
|
+
if (parent && parent.type === "invocation_expression") {
|
|
2084
|
+
const fn = parent.childForFieldName("function");
|
|
2085
|
+
if (fn && fn.id === node.id) isCallee = true;
|
|
2086
|
+
} else if (parent && parent.type === "conditional_access_expression") {
|
|
2087
|
+
const gp = parent.parent;
|
|
2088
|
+
if (gp && gp.type === "invocation_expression" && gp.childForFieldName("function")?.id === parent.id) isCallee = true;
|
|
2089
|
+
}
|
|
2090
|
+
let isLhsWrite = false;
|
|
2091
|
+
{
|
|
2092
|
+
let a = node.parent;
|
|
2093
|
+
while (a && (a.type === "member_access_expression" || a.type === "member_binding_expression")) a = a.parent;
|
|
2094
|
+
if (a && a.type === "assignment_expression") {
|
|
2095
|
+
const lhs = a.childForFieldName("left");
|
|
2096
|
+
if (lhs && lhs.startIndex <= node.startIndex && lhs.endIndex >= node.endIndex) isLhsWrite = true;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
if (!isCallee && !isLhsWrite) {
|
|
2100
|
+
const field = node.childForFieldName("name");
|
|
2101
|
+
const expr = node.childForFieldName("expression");
|
|
2102
|
+
if (field && (expr || node.type === "member_binding_expression")) {
|
|
2103
|
+
const containing = this.findContainingCSharpFunction(node);
|
|
2104
|
+
const src = containing || "global";
|
|
2105
|
+
const tgt = field.text;
|
|
2106
|
+
const key = `${src}.${tgt}`;
|
|
2107
|
+
if (!seen.has(key)) {
|
|
2108
|
+
seen.add(key);
|
|
2109
|
+
relationships.push({
|
|
2110
|
+
source: src,
|
|
2111
|
+
target: tgt,
|
|
2112
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2113
|
+
confidence: 0.7,
|
|
2114
|
+
// M59.A7 (#825): the member-access NAME field (`obj\n .Radius`
|
|
2115
|
+
// writes Radius on line 2, the access node starts on line 1).
|
|
2116
|
+
line_number: this.nodeLine(field),
|
|
2117
|
+
metadata: { relationshipType: "csharp_field_access", fieldAccess: key }
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
2124
|
+
const child = node.namedChild(i);
|
|
2125
|
+
if (child) stack.push(child);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* CS-R1: bare-identifier value reads (see the call site for the contract).
|
|
2131
|
+
* Inventory = Property-kind member names keyed by owner (full name and
|
|
2132
|
+
* leaf); resolution walks the lexical class chain innermost-first, then
|
|
2133
|
+
* base classes transitively (capped), then lexically enclosing classes.
|
|
2134
|
+
* Methods, enum members, events and delegates never resolve bare
|
|
2135
|
+
* (method groups, qualified-only members, ruled boundaries).
|
|
2136
|
+
*/
|
|
2137
|
+
analyzeCSharpBareIdentifierUses(rootNode, relationships, symbols, classHierarchy) {
|
|
2138
|
+
const importSpans = [];
|
|
2139
|
+
{
|
|
2140
|
+
const importStack = [rootNode];
|
|
2141
|
+
while (importStack.length > 0) {
|
|
2142
|
+
const n = importStack.pop();
|
|
2143
|
+
if (n.type === "using_directive" || n.type === "extern_alias_directive") {
|
|
2144
|
+
importSpans.push([n.startIndex, n.endIndex]);
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
for (let k = 0; k < n.childCount; k++) {
|
|
2148
|
+
const kid = n.child(k);
|
|
2149
|
+
if (kid) importStack.push(kid);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
const leafOf = (s) => {
|
|
2154
|
+
const segs = [];
|
|
2155
|
+
for (const a of s.split("::")) {
|
|
2156
|
+
for (const b of a.split(".")) {
|
|
2157
|
+
if (b) segs.push(b);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
return segs.length > 0 ? segs[segs.length - 1] : s;
|
|
2161
|
+
};
|
|
2162
|
+
const membersByOwner = /* @__PURE__ */ new Map();
|
|
2163
|
+
const addMember = (owner, name) => {
|
|
2164
|
+
if (!owner || !name || name === "_") return;
|
|
2165
|
+
for (const key of [owner, leafOf(owner)]) {
|
|
2166
|
+
let s = membersByOwner.get(key);
|
|
2167
|
+
if (!s) {
|
|
2168
|
+
s = /* @__PURE__ */ new Set();
|
|
2169
|
+
membersByOwner.set(key, s);
|
|
2170
|
+
}
|
|
2171
|
+
s.add(name);
|
|
2172
|
+
}
|
|
2173
|
+
};
|
|
2174
|
+
for (const s of symbols) {
|
|
2175
|
+
if (s.kind === ASTEngine.SymbolKind.Property && s.ownerName) addMember(s.ownerName, s.name);
|
|
2176
|
+
}
|
|
2177
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
2178
|
+
const scopeStack = [];
|
|
2179
|
+
scopeStack.push({ names: /* @__PURE__ */ new Set(), cls: null });
|
|
2180
|
+
const isShadowed = (name) => {
|
|
2181
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) {
|
|
2182
|
+
if (scopeStack[i].names.has(name)) return true;
|
|
2183
|
+
}
|
|
2184
|
+
return false;
|
|
2185
|
+
};
|
|
2186
|
+
const isResolvableRead = (name) => {
|
|
2187
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) {
|
|
2188
|
+
const fr = scopeStack[i];
|
|
2189
|
+
if (!fr.cls) continue;
|
|
2190
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2191
|
+
const queue = [fr.cls];
|
|
2192
|
+
let depth = 0;
|
|
2193
|
+
while (queue.length > 0 && depth < 10) {
|
|
2194
|
+
depth++;
|
|
2195
|
+
const cur = queue.shift();
|
|
2196
|
+
if (seen.has(cur)) continue;
|
|
2197
|
+
seen.add(cur);
|
|
2198
|
+
const mm = membersByOwner.get(cur) ?? membersByOwner.get(leafOf(cur));
|
|
2199
|
+
if (mm && mm.has(name)) return true;
|
|
2200
|
+
for (const b of classHierarchy.get(cur) ?? classHierarchy.get(leafOf(cur)) ?? []) {
|
|
2201
|
+
if (!seen.has(b)) queue.push(b);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
return false;
|
|
2206
|
+
};
|
|
2207
|
+
const firstIdent = (n) => {
|
|
2208
|
+
if (!n) return null;
|
|
2209
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2210
|
+
const c = n.namedChild(i);
|
|
2211
|
+
if (c && c.type === "identifier") return c;
|
|
2212
|
+
}
|
|
2213
|
+
return null;
|
|
2214
|
+
};
|
|
2215
|
+
const declaratorName = (declNode) => {
|
|
2216
|
+
let cur = declNode;
|
|
2217
|
+
while (cur) {
|
|
2218
|
+
if (cur.type === "identifier") return cur;
|
|
2219
|
+
let next = null;
|
|
2220
|
+
for (let i = 0; i < cur.namedChildCount; i++) {
|
|
2221
|
+
const c = cur.namedChild(i);
|
|
2222
|
+
if (c && (c.type === "identifier" || c.type.endsWith("declarator"))) {
|
|
2223
|
+
next = c;
|
|
2224
|
+
break;
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
if (!next || next === cur) return null;
|
|
2228
|
+
cur = next;
|
|
2229
|
+
}
|
|
2230
|
+
return null;
|
|
2231
|
+
};
|
|
2232
|
+
const declaratorRole = (declNode) => {
|
|
2233
|
+
let cur = declNode.parent;
|
|
2234
|
+
while (cur) {
|
|
2235
|
+
const t = cur.type;
|
|
2236
|
+
if (t === "method_declaration" || t === "constructor_declaration" || t === "destructor_declaration" || t === "operator_declaration" || t === "conversion_operator_declaration" || t === "local_function_statement" || t === "lambda_expression" || t === "block" || t === "accessor_declaration") return "local";
|
|
2237
|
+
if (t === "class_declaration" || t === "struct_declaration" || t === "interface_declaration" || t === "record_declaration" || t === "enum_declaration") return "member";
|
|
2238
|
+
cur = cur.parent;
|
|
2239
|
+
}
|
|
2240
|
+
return "local";
|
|
2241
|
+
};
|
|
2242
|
+
const hasEqualsAfter = (parent, node) => {
|
|
2243
|
+
let sawSelf = false;
|
|
2244
|
+
for (let i = 0; i < parent.childCount; i++) {
|
|
2245
|
+
const c = parent.child(i);
|
|
2246
|
+
if (!c) continue;
|
|
2247
|
+
if (c.startIndex === node.startIndex && c.endIndex === node.endIndex) {
|
|
2248
|
+
sawSelf = true;
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
if (!c.isNamed && sawSelf && (c.type === "=" || c.type === ":")) return true;
|
|
2252
|
+
}
|
|
2253
|
+
return false;
|
|
2254
|
+
};
|
|
2255
|
+
const stack = [{ node: rootNode, exit: false, pushed: false }];
|
|
2256
|
+
while (stack.length > 0) {
|
|
2257
|
+
const { node, exit, pushed } = stack.pop();
|
|
2258
|
+
if (exit) {
|
|
2259
|
+
if (pushed) scopeStack.pop();
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
const t = node.type;
|
|
2263
|
+
let pushedScope = false;
|
|
2264
|
+
const top = () => scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
|
|
2265
|
+
if (t === "method_declaration" || t === "constructor_declaration" || t === "destructor_declaration" || t === "operator_declaration" || t === "conversion_operator_declaration" || t === "local_function_statement") {
|
|
2266
|
+
const frame = { names: /* @__PURE__ */ new Set(), cls: null };
|
|
2267
|
+
const plist = this.findChild(node, (n) => n.type === "parameter_list");
|
|
2268
|
+
if (plist) {
|
|
2269
|
+
for (let i = 0; i < plist.namedChildCount; i++) {
|
|
2270
|
+
const p = plist.namedChild(i);
|
|
2271
|
+
if (!p || p.type !== "parameter") continue;
|
|
2272
|
+
const nm = p.childForFieldName("name") ?? firstIdent(p);
|
|
2273
|
+
if (nm && nm.text !== "_") frame.names.add(nm.text);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
if (t === "local_function_statement") {
|
|
2277
|
+
const nm = node.childForFieldName("name") ?? firstIdent(node);
|
|
2278
|
+
const outer = top();
|
|
2279
|
+
if (outer && nm && nm.text !== "_") outer.names.add(nm.text);
|
|
2280
|
+
}
|
|
2281
|
+
scopeStack.push(frame);
|
|
2282
|
+
pushedScope = true;
|
|
2283
|
+
} else if (t === "block" || t === "lambda_expression" || t === "switch_expression_arm" || t === "query_expression" || t === "foreach_statement" || t === "for_statement" || t === "using_statement" || t === "catch_clause") {
|
|
2284
|
+
if (t === "lambda_expression") {
|
|
2285
|
+
const frame = { names: /* @__PURE__ */ new Set(), cls: null };
|
|
2286
|
+
const walk = [node];
|
|
2287
|
+
while (walk.length > 0) {
|
|
2288
|
+
const m = walk.pop();
|
|
2289
|
+
if (m !== node && (m.type === "block" || m.type === "lambda_expression")) continue;
|
|
2290
|
+
if (m.type === "parameter") {
|
|
2291
|
+
const pn = m.childForFieldName("name") ?? firstIdent(m);
|
|
2292
|
+
if (pn && pn.text !== "_") frame.names.add(pn.text);
|
|
2293
|
+
} else if (m.type === "implicit_parameter") {
|
|
2294
|
+
for (let i = 0; i < m.namedChildCount; i++) {
|
|
2295
|
+
const c = m.namedChild(i);
|
|
2296
|
+
if (c && c.type === "identifier" && c.text !== "_") frame.names.add(c.text);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
for (let i = m.namedChildCount - 1; i >= 0; i--) {
|
|
2300
|
+
const c = m.namedChild(i);
|
|
2301
|
+
if (c) walk.push(c);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
scopeStack.push(frame);
|
|
2305
|
+
pushedScope = true;
|
|
2306
|
+
} else if (t === "foreach_statement") {
|
|
2307
|
+
const frame = { names: /* @__PURE__ */ new Set(), cls: null };
|
|
2308
|
+
const isTypeish = (c) => c.type === "implicit_type" || c.type === "predefined_type" || c.type === "generic_name" || c.type === "qualified_name" || c.type === "nullable_type" || c.type === "array_type" || c.type === "pointer_type" || c.type === "tuple_type" || c.type === "ref_type";
|
|
2309
|
+
const kids = [];
|
|
2310
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2311
|
+
const c = node.namedChild(i);
|
|
2312
|
+
if (!c || c.type === "block") break;
|
|
2313
|
+
kids.push(c);
|
|
2314
|
+
}
|
|
2315
|
+
const hasType = kids.some((c) => isTypeish(c));
|
|
2316
|
+
if (hasType) {
|
|
2317
|
+
let bound = false;
|
|
2318
|
+
let seen = false;
|
|
2319
|
+
for (const c of kids) {
|
|
2320
|
+
if (isTypeish(c)) {
|
|
2321
|
+
seen = true;
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
if (!bound && seen && c.type === "identifier" && c.text !== "_") {
|
|
2325
|
+
frame.names.add(c.text);
|
|
2326
|
+
bound = true;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
} else {
|
|
2330
|
+
const ids = kids.filter((c) => c.type === "identifier" && c.text !== "_");
|
|
2331
|
+
for (let i = 1; i + 1 < ids.length; i++) frame.names.add(ids[i].text);
|
|
2332
|
+
}
|
|
2333
|
+
scopeStack.push(frame);
|
|
2334
|
+
pushedScope = true;
|
|
2335
|
+
} else {
|
|
2336
|
+
scopeStack.push({ names: /* @__PURE__ */ new Set(), cls: null });
|
|
2337
|
+
pushedScope = true;
|
|
2338
|
+
}
|
|
2339
|
+
} else if (t === "class_declaration" || t === "struct_declaration" || t === "interface_declaration" || t === "record_declaration") {
|
|
2340
|
+
const frame = { names: /* @__PURE__ */ new Set(), cls: null };
|
|
2341
|
+
const nm = node.childForFieldName("name")?.text ?? firstIdent(node)?.text ?? null;
|
|
2342
|
+
if (nm) frame.cls = nm;
|
|
2343
|
+
if (t !== "record_declaration") {
|
|
2344
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2345
|
+
const c = node.namedChild(i);
|
|
2346
|
+
if (c && c.type === "parameter_list") {
|
|
2347
|
+
for (let k = 0; k < c.namedChildCount; k++) {
|
|
2348
|
+
const p = c.namedChild(k);
|
|
2349
|
+
if (!p || p.type !== "parameter") continue;
|
|
2350
|
+
const pn = p.childForFieldName("name") ?? firstIdent(p);
|
|
2351
|
+
if (pn && pn.text !== "_") frame.names.add(pn.text);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
scopeStack.push(frame);
|
|
2357
|
+
pushedScope = true;
|
|
2358
|
+
} else if (t === "variable_declarator") {
|
|
2359
|
+
if (declaratorRole(node) === "local") {
|
|
2360
|
+
const f = top();
|
|
2361
|
+
const nm = declaratorName(node);
|
|
2362
|
+
if (f && nm && nm.text !== "_") f.names.add(nm.text);
|
|
2363
|
+
}
|
|
2364
|
+
} else if (t === "declaration_pattern") {
|
|
2365
|
+
const f = top();
|
|
2366
|
+
if (f) {
|
|
2367
|
+
for (let i = 1; i < node.namedChildCount; i++) {
|
|
2368
|
+
const c = node.namedChild(i);
|
|
2369
|
+
if (c && c.type === "identifier" && c.text !== "_") f.names.add(c.text);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
} else if (t === "declaration_expression") {
|
|
2373
|
+
const f = top();
|
|
2374
|
+
if (f) {
|
|
2375
|
+
let last = null;
|
|
2376
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2377
|
+
const c = node.namedChild(i);
|
|
2378
|
+
if (c && c.type === "identifier") last = c;
|
|
2379
|
+
}
|
|
2380
|
+
if (last && last.text !== "_") f.names.add(last.text);
|
|
2381
|
+
}
|
|
2382
|
+
} else if (t === "from_clause" || t === "join_clause" || t === "let_clause") {
|
|
2383
|
+
const f = top();
|
|
2384
|
+
if (f) {
|
|
2385
|
+
const id = firstIdent(node);
|
|
2386
|
+
if (id && id.text !== "_") f.names.add(id.text);
|
|
2387
|
+
}
|
|
2388
|
+
} else if (t === "catch_declaration") {
|
|
2389
|
+
const f = top();
|
|
2390
|
+
if (f) {
|
|
2391
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2392
|
+
const c = node.namedChild(i);
|
|
2393
|
+
if (!c || c.type !== "identifier" || c.text === "_") continue;
|
|
2394
|
+
let earlier = false;
|
|
2395
|
+
for (let k = 0; k < i; k++) {
|
|
2396
|
+
if (node.namedChild(k)) {
|
|
2397
|
+
earlier = true;
|
|
2398
|
+
break;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
if (earlier) f.names.add(c.text);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
} else if (t === "tuple_expression") {
|
|
2405
|
+
const p = node.parent;
|
|
2406
|
+
if (p && p.type === "assignment_expression" && p.namedChild(0) && node.startIndex >= p.namedChild(0).startIndex && node.endIndex <= (p.namedChild(0).endIndex ?? 0)) {
|
|
2407
|
+
const f = top();
|
|
2408
|
+
if (f) {
|
|
2409
|
+
const isTypeish = (c) => c.type === "implicit_type" || c.type === "predefined_type" || c.type === "generic_name" || c.type === "qualified_name" || c.type === "nullable_type" || c.type === "array_type" || c.type === "pointer_type" || c.type === "tuple_type" || c.type === "ref_type";
|
|
2410
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2411
|
+
const arg = node.namedChild(i);
|
|
2412
|
+
if (!arg || arg.type !== "argument") continue;
|
|
2413
|
+
const ids = [];
|
|
2414
|
+
let typed = false;
|
|
2415
|
+
for (let k = 0; k < arg.namedChildCount; k++) {
|
|
2416
|
+
const c = arg.namedChild(k);
|
|
2417
|
+
if (!c) continue;
|
|
2418
|
+
if (isTypeish(c)) typed = true;
|
|
2419
|
+
else if (c.type === "identifier" && c.text !== "_") ids.push(c);
|
|
2420
|
+
}
|
|
2421
|
+
const bind = typed ? ids : ids.slice(1);
|
|
2422
|
+
for (const b of bind) f.names.add(b.text);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
} else if (t === "identifier") {
|
|
2427
|
+
const name = node.text;
|
|
2428
|
+
const p = node.parent;
|
|
2429
|
+
const isAttrName = !!p && p.type === "attribute" && (() => {
|
|
2430
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
2431
|
+
const c = p.namedChild(i);
|
|
2432
|
+
if (c && (c.type === "identifier" || c.type === "generic_name" || c.type === "qualified_name")) {
|
|
2433
|
+
return c.startIndex === node.startIndex && c.endIndex === node.endIndex;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return false;
|
|
2437
|
+
})();
|
|
2438
|
+
const isNamedArgDesignator = !!p && p.type === "attribute_argument" && (() => {
|
|
2439
|
+
const first = firstIdent(p);
|
|
2440
|
+
return !!first && first.startIndex === node.startIndex && first.endIndex === node.endIndex && hasEqualsAfter(p, node);
|
|
2441
|
+
})();
|
|
2442
|
+
const isAnonDesignator = !!p && (() => {
|
|
2443
|
+
let q = p;
|
|
2444
|
+
while (q && q.type !== "anonymous_object_creation_expression" && q.type !== "block" && q.type !== "method_declaration" && q.type !== "constructor_declaration" && q.type !== "local_function_statement" && q.type !== "lambda_expression") {
|
|
2445
|
+
q = q.parent;
|
|
2446
|
+
}
|
|
2447
|
+
if (!q || q.type !== "anonymous_object_creation_expression") return false;
|
|
2448
|
+
let fresh = true;
|
|
2449
|
+
let foundFirst = false;
|
|
2450
|
+
for (let i = 0; i < q.childCount; i++) {
|
|
2451
|
+
const c = q.child(i);
|
|
2452
|
+
if (!c) continue;
|
|
2453
|
+
if (!c.isNamed) {
|
|
2454
|
+
if (c.type === "," || c.type === ";" || c.type === "{" || c.type === "}") {
|
|
2455
|
+
fresh = true;
|
|
2456
|
+
foundFirst = false;
|
|
2457
|
+
} else if (c.type === "=" || c.type === ":") fresh = false;
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
if (c.type === "identifier") {
|
|
2461
|
+
if (!foundFirst && fresh) {
|
|
2462
|
+
if (c.startIndex === node.startIndex && c.endIndex === node.endIndex) {
|
|
2463
|
+
return hasEqualsAfter(q, c);
|
|
2464
|
+
}
|
|
2465
|
+
foundFirst = true;
|
|
2466
|
+
} else {
|
|
2467
|
+
if (c.startIndex === node.startIndex && c.endIndex === node.endIndex) return false;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
return false;
|
|
2472
|
+
})();
|
|
2473
|
+
const isMemberDesignator = !!p && (p.type === "with_initializer" || p.type === "subpattern") && (() => {
|
|
2474
|
+
const first = firstIdent(p);
|
|
2475
|
+
return !!first && first.startIndex === node.startIndex && first.endIndex === node.endIndex;
|
|
2476
|
+
})();
|
|
2477
|
+
const isOutWrite = !!p && p.type === "argument" && (() => {
|
|
2478
|
+
let sawOut = false;
|
|
2479
|
+
for (let i = 0; i < p.childCount; i++) {
|
|
2480
|
+
const c = p.child(i);
|
|
2481
|
+
if (!c) continue;
|
|
2482
|
+
if (!c.isNamed && c.type === "out") {
|
|
2483
|
+
sawOut = true;
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
if (c.isNamed) {
|
|
2487
|
+
if (c.type === "declaration_expression") return false;
|
|
2488
|
+
if (c.type === "identifier" && c.startIndex === node.startIndex && c.endIndex === node.endIndex) return sawOut;
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
return false;
|
|
2493
|
+
})();
|
|
2494
|
+
const isDeconWrite = (() => {
|
|
2495
|
+
let cur = node;
|
|
2496
|
+
let q = p;
|
|
2497
|
+
while (q && (q.type === "tuple_expression" || q.type === "argument" || q.type === "parenthesized_expression")) {
|
|
2498
|
+
cur = q;
|
|
2499
|
+
q = q.parent;
|
|
2500
|
+
}
|
|
2501
|
+
if (!q || q.type !== "assignment_expression") return false;
|
|
2502
|
+
const lhs = q.namedChild(0);
|
|
2503
|
+
return !!lhs && cur.startIndex >= lhs.startIndex && cur.endIndex <= lhs.endIndex;
|
|
2504
|
+
})();
|
|
2505
|
+
const isMemberDeclName = (() => {
|
|
2506
|
+
if (!p) return false;
|
|
2507
|
+
if (p.type === "variable_declarator") {
|
|
2508
|
+
const nm = declaratorName(p);
|
|
2509
|
+
return !!nm && nm.startIndex === node.startIndex && nm.endIndex === node.endIndex && declaratorRole(p) === "member";
|
|
2510
|
+
}
|
|
2511
|
+
if (p.type === "field_declaration") {
|
|
2512
|
+
const first = firstIdent(p);
|
|
2513
|
+
return !!first && first.startIndex === node.startIndex && first.endIndex === node.endIndex;
|
|
2514
|
+
}
|
|
2515
|
+
return false;
|
|
2516
|
+
})();
|
|
2517
|
+
const isTypeContextName = (() => {
|
|
2518
|
+
if (!p) return false;
|
|
2519
|
+
if (p.type === "type_parameter_list") return true;
|
|
2520
|
+
let q = p;
|
|
2521
|
+
while (q) {
|
|
2522
|
+
const t2 = q.type;
|
|
2523
|
+
if (t2 === "type_parameter_constraints_clause" || t2 === "base_list") return true;
|
|
2524
|
+
if (t2 === "tuple_type") return true;
|
|
2525
|
+
if (t2.includes("statement") || t2.includes("declaration") || t2.includes("block") || t2.includes("function") || t2.includes("body") || t2.includes("clause") && t2 !== "type_parameter_constraints_clause") return false;
|
|
2526
|
+
q = q.parent;
|
|
2527
|
+
}
|
|
2528
|
+
return false;
|
|
2529
|
+
})();
|
|
2530
|
+
const isForeachWrite = !!p && p.type === "foreach_statement" && (() => {
|
|
2531
|
+
let last = null;
|
|
2532
|
+
for (let i = p.namedChildCount - 1; i >= 0; i--) {
|
|
2533
|
+
const c = p.namedChild(i);
|
|
2534
|
+
if (c && c.type !== "block") {
|
|
2535
|
+
last = c;
|
|
2536
|
+
break;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
if (!last || last.type === "block") return false;
|
|
2540
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
2541
|
+
const c = p.namedChild(i);
|
|
2542
|
+
if (!c || c === last || c.type === "block") continue;
|
|
2543
|
+
if (c.type === "identifier" && c.startIndex === node.startIndex && c.endIndex === node.endIndex) return true;
|
|
2544
|
+
}
|
|
2545
|
+
return false;
|
|
2546
|
+
})();
|
|
2547
|
+
const isDeconArgName = !!p && p.type === "argument" && (() => {
|
|
2548
|
+
let q = p.parent;
|
|
2549
|
+
while (q && q.type === "parenthesized_expression") q = q.parent;
|
|
2550
|
+
if (!q || q.type !== "tuple_expression") return false;
|
|
2551
|
+
const a = q.parent;
|
|
2552
|
+
if (!a || a.type !== "assignment_expression") return false;
|
|
2553
|
+
const lhs = a.namedChild(0);
|
|
2554
|
+
if (!lhs || q.startIndex < lhs.startIndex || q.endIndex > (lhs.endIndex ?? 0)) return false;
|
|
2555
|
+
return node.type === "identifier";
|
|
2556
|
+
})();
|
|
2557
|
+
const isImportName = (() => {
|
|
2558
|
+
if (!p) return false;
|
|
2559
|
+
for (let i = 0; i < importSpans.length; i++) {
|
|
2560
|
+
const sp = importSpans[i];
|
|
2561
|
+
if (p.startIndex >= sp[0] && p.endIndex <= sp[1]) return true;
|
|
2562
|
+
}
|
|
2563
|
+
return false;
|
|
2564
|
+
})();
|
|
2565
|
+
if (name && name !== "_" && !isAttrName && !isNamedArgDesignator && !isAnonDesignator && !isMemberDesignator && !isOutWrite && !isDeconWrite && !isDeconArgName && !isMemberDeclName && !isForeachWrite && !isImportName && !isTypeContextName && !this.isInValueSetter(node) && !this.isNameofOperand(node) && !this.isCSharpCallCalleeShaped(node) && !this.isCSharpMemberLeaf(node) && !this.isCSharpQualifiedNamePart(node) && !this.isCSharpNamePosition(node) && !this.isCSharpPreprocText(node) && !this.isCSharpLabelOrDiscard(node) && !this.isCSharpEntityName(node) && !this.isCSharpAssignLhs(node) && !isShadowed(name) && isResolvableRead(name)) {
|
|
2566
|
+
let clsScope = null;
|
|
2567
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) {
|
|
2568
|
+
if (scopeStack[i].cls) {
|
|
2569
|
+
clsScope = scopeStack[i].cls;
|
|
2570
|
+
break;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
const src = this.findContainingCSharpFunction(node) || clsScope || "global";
|
|
2574
|
+
const key = `${src}::uses::${name}`;
|
|
2575
|
+
if (!emitted.has(key)) {
|
|
2576
|
+
emitted.add(key);
|
|
2577
|
+
relationships.push({
|
|
2578
|
+
source: src,
|
|
2579
|
+
target: name,
|
|
2580
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
2581
|
+
confidence: 1,
|
|
2582
|
+
// M59.A7 (#825): the identifier node IS the node that names the
|
|
2583
|
+
// target (this pass visits the read itself).
|
|
2584
|
+
line_number: this.nodeLine(node),
|
|
2585
|
+
metadata: { relationshipType: "identifier_usage", nodeType: node.type }
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
stack.push({ node, exit: true, pushed: pushedScope });
|
|
2591
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
2592
|
+
const child = node.namedChild(i);
|
|
2593
|
+
if (child) stack.push({ node: child, exit: false, pushed: false });
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* CS-R1 gate helpers (see analyzeCSharpBareIdentifierUses for the contract).
|
|
2599
|
+
* All climb-based, span-compared (never object identity — web-tree-sitter
|
|
2600
|
+
* wraps per retrieval), mirroring the family gates where they exist.
|
|
2601
|
+
*/
|
|
2602
|
+
/** True when `node` is the implicit `value` parameter of a set/init/add/remove accessor. */
|
|
2603
|
+
isInValueSetter(node) {
|
|
2604
|
+
if (node.text !== "value") return false;
|
|
2605
|
+
let cur = node.parent;
|
|
2606
|
+
while (cur) {
|
|
2607
|
+
const t = cur.type;
|
|
2608
|
+
if (t === "accessor_declaration") {
|
|
2609
|
+
for (let i = 0; i < cur.childCount; i++) {
|
|
2610
|
+
const c = cur.child(i);
|
|
2611
|
+
if (c && !c.isNamed && (c.type === "set" || c.type === "init" || c.type === "add" || c.type === "remove")) {
|
|
2612
|
+
return true;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
return false;
|
|
2616
|
+
}
|
|
2617
|
+
if (t === "method_declaration" || t === "constructor_declaration" || t === "local_function_statement" || t === "lambda_expression" || t === "class_declaration" || t === "struct_declaration" || t === "interface_declaration" || t === "record_declaration" || t === "enum_declaration") return false;
|
|
2618
|
+
cur = cur.parent;
|
|
2619
|
+
}
|
|
2620
|
+
return false;
|
|
2621
|
+
}
|
|
2622
|
+
/** True when `node` is (part of) the callee the calls branch owns. */
|
|
2623
|
+
isCSharpCallCalleeShaped(node) {
|
|
2624
|
+
let top = node;
|
|
2625
|
+
let p = node.parent;
|
|
2626
|
+
while (p && (p.type === "generic_name" || p.type === "parenthesized_expression")) {
|
|
2627
|
+
top = p;
|
|
2628
|
+
p = p.parent;
|
|
2629
|
+
}
|
|
2630
|
+
if (p && p.type === "member_access_expression") {
|
|
2631
|
+
const expr = p.childForFieldName("expression");
|
|
2632
|
+
if (expr && node.startIndex >= expr.startIndex && node.endIndex <= expr.endIndex) return false;
|
|
2633
|
+
top = p;
|
|
2634
|
+
p = p.parent;
|
|
2635
|
+
}
|
|
2636
|
+
if (p && p.type === "member_binding_expression") {
|
|
2637
|
+
top = p;
|
|
2638
|
+
p = p.parent;
|
|
2639
|
+
if (p && p.type === "conditional_access_expression") {
|
|
2640
|
+
top = p;
|
|
2641
|
+
p = p.parent;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
if (!p || p.type !== "invocation_expression") return false;
|
|
2645
|
+
const fn = p.childForFieldName("function");
|
|
2646
|
+
return !!fn && top.startIndex >= fn.startIndex && top.endIndex <= fn.endIndex;
|
|
2647
|
+
}
|
|
2648
|
+
/** True when `node` is the member LEAF the field pass owns. */
|
|
2649
|
+
isCSharpMemberLeaf(node) {
|
|
2650
|
+
const p = node.parent;
|
|
2651
|
+
if (!p || p.type !== "member_access_expression") return false;
|
|
2652
|
+
const field = p.childForFieldName("name");
|
|
2653
|
+
return !!field && field.startIndex === node.startIndex && field.endIndex === node.endIndex;
|
|
2654
|
+
}
|
|
2655
|
+
/** True when `node` sits under a qualified_name (types/namespaces — never value reads). */
|
|
2656
|
+
isCSharpQualifiedNamePart(node) {
|
|
2657
|
+
let cur = node.parent;
|
|
2658
|
+
while (cur) {
|
|
2659
|
+
if (cur.type === "qualified_name") return true;
|
|
2660
|
+
cur = cur.parent;
|
|
2661
|
+
}
|
|
2662
|
+
return false;
|
|
2663
|
+
}
|
|
2664
|
+
/** True for name/type positions that never read values. */
|
|
2665
|
+
isCSharpNamePosition(node) {
|
|
2666
|
+
let cur = node.parent;
|
|
2667
|
+
while (cur) {
|
|
2668
|
+
const t = cur.type;
|
|
2669
|
+
if (t === "typeof_expression" || t === "default_expression" || t === "sizeof_expression") return true;
|
|
2670
|
+
if (t === "is_expression" || t === "as_expression") {
|
|
2671
|
+
const first = cur.namedChild(0);
|
|
2672
|
+
return !first || node.startIndex < first.startIndex || node.endIndex > first.endIndex;
|
|
2673
|
+
}
|
|
2674
|
+
if (t.includes("statement") || t.includes("declaration") || t.includes("expression") || t.includes("clause") || t.includes("function") || t.includes("body") || t.includes("pattern") || t.includes("argument")) return false;
|
|
2675
|
+
cur = cur.parent;
|
|
2676
|
+
}
|
|
2677
|
+
return false;
|
|
2678
|
+
}
|
|
2679
|
+
/** True when `node` is the operand of a `nameof(...)` (names, never reads). */
|
|
2680
|
+
isNameofOperand(node) {
|
|
2681
|
+
const calleeIsNameof = (inv) => {
|
|
2682
|
+
const fn = inv.childForFieldName("function");
|
|
2683
|
+
return !!fn && fn.type === "identifier" && fn.text === "nameof";
|
|
2684
|
+
};
|
|
2685
|
+
const p0 = node.parent;
|
|
2686
|
+
if (p0 && p0.type === "invocation_expression") return calleeIsNameof(p0);
|
|
2687
|
+
let cur = p0;
|
|
2688
|
+
while (cur && (cur.type === "argument_list" || cur.type === "argument" || cur.type === "parenthesized_expression")) {
|
|
2689
|
+
cur = cur.parent;
|
|
2690
|
+
if (cur && cur.type === "invocation_expression") return calleeIsNameof(cur);
|
|
2691
|
+
}
|
|
2692
|
+
return false;
|
|
2693
|
+
}
|
|
2694
|
+
/** True for preprocessor-directive text with no code container between (CGD11 contract). */
|
|
2695
|
+
isCSharpPreprocText(node) {
|
|
2696
|
+
let cur = node.parent;
|
|
2697
|
+
while (cur) {
|
|
2698
|
+
const t = cur.type;
|
|
2699
|
+
if (t.includes("statement") || t.includes("declaration") || t.includes("declarator") || t.includes("specifier") || t.includes("enumerator") || t.includes("compound") || t.includes("argument_list") || t.includes("init") || t.includes("expression") || t.includes("function") || t.includes("class_body") || t.includes("field_declaration_list") || t.includes("block")) return false;
|
|
2700
|
+
if (t.startsWith("preproc_")) return true;
|
|
2701
|
+
cur = cur.parent;
|
|
2702
|
+
}
|
|
2703
|
+
return false;
|
|
2704
|
+
}
|
|
2705
|
+
/** True for goto/break/continue/label identifiers (never values). */
|
|
2706
|
+
isCSharpLabelOrDiscard(node) {
|
|
2707
|
+
const p = node.parent;
|
|
2708
|
+
return !!p && (p.type === "goto_statement" || p.type === "break_statement" || p.type === "continue_statement" || p.type === "labeled_statement");
|
|
2709
|
+
}
|
|
2710
|
+
/** True when `node` is the defining NAME of its entity parent, or a TYPE it declares. */
|
|
2711
|
+
/**
|
|
2712
|
+
* CS-D8 helper: is this identifier inside a declared TYPE, judged by which declaration owns a
|
|
2713
|
+
* `type` field that CONTAINS it? See the CS-D8 note in `isCSharpEntityName` for why the walk
|
|
2714
|
+
* ascends through name-shaped wrappers and why it is bounded.
|
|
2715
|
+
*/
|
|
2716
|
+
isDeclaredTypeSite(node) {
|
|
2717
|
+
const DECL = "declaration";
|
|
2718
|
+
let cur = node.parent;
|
|
2719
|
+
for (let hops = 0; cur && hops < 12; hops++) {
|
|
2720
|
+
const t = cur.type;
|
|
2721
|
+
if (t === "variable_declaration" || t === "parameter" || t === "property_declaration" || t === "indexer_declaration" || t === "event_declaration" || t === "event_field_declaration" || t === "variable_declarator" || t === "base_list" || t === "type_parameter_list" || t === "type_parameter_constraints_clause" || t === "delegate_declaration") {
|
|
2722
|
+
const tf = cur.childForFieldName("type");
|
|
2723
|
+
if (tf && node.startIndex >= tf.startIndex && node.endIndex <= tf.endIndex) return true;
|
|
2724
|
+
if (t === "variable_declarator") return false;
|
|
2725
|
+
}
|
|
2726
|
+
if (t.includes(DECL) || t === "class_body" || t === "block" || t.includes("statement")) return false;
|
|
2727
|
+
cur = cur.parent;
|
|
2728
|
+
}
|
|
2729
|
+
return false;
|
|
2730
|
+
}
|
|
2731
|
+
isCSharpEntityName(node) {
|
|
2732
|
+
const p = node.parent;
|
|
2733
|
+
if (!p) return false;
|
|
2734
|
+
if (this.isDeclaredTypeSite(node)) return true;
|
|
2735
|
+
{
|
|
2736
|
+
const nf0 = p?.childForFieldName?.("name");
|
|
2737
|
+
if (nf0 && nf0.startIndex === node.startIndex && nf0.endIndex === node.endIndex) return true;
|
|
2738
|
+
}
|
|
2739
|
+
if (p.type === "parameter") {
|
|
2740
|
+
const t = p.childForFieldName("type");
|
|
2741
|
+
if (t && node.startIndex >= t.startIndex && node.endIndex <= t.endIndex) return true;
|
|
2742
|
+
}
|
|
2743
|
+
{
|
|
2744
|
+
const nf = p.childForFieldName("name");
|
|
2745
|
+
if (nf && nf.startIndex === node.startIndex && nf.endIndex === node.endIndex) return true;
|
|
2746
|
+
}
|
|
2747
|
+
switch (p.type) {
|
|
2748
|
+
case "class_declaration":
|
|
2749
|
+
case "struct_declaration":
|
|
2750
|
+
case "interface_declaration":
|
|
2751
|
+
case "record_declaration":
|
|
2752
|
+
case "enum_declaration":
|
|
2753
|
+
case "delegate_declaration":
|
|
2754
|
+
case "method_declaration":
|
|
2755
|
+
case "constructor_declaration":
|
|
2756
|
+
case "destructor_declaration":
|
|
2757
|
+
case "operator_declaration":
|
|
2758
|
+
case "conversion_operator_declaration":
|
|
2759
|
+
case "property_declaration":
|
|
2760
|
+
case "indexer_declaration":
|
|
2761
|
+
case "event_declaration":
|
|
2762
|
+
case "event_field_declaration":
|
|
2763
|
+
case "enum_member_declaration":
|
|
2764
|
+
case "namespace_declaration":
|
|
2765
|
+
case "file_scoped_namespace_declaration":
|
|
2766
|
+
break;
|
|
2767
|
+
default:
|
|
2768
|
+
return false;
|
|
2769
|
+
}
|
|
2770
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
2771
|
+
const c = p.namedChild(i);
|
|
2772
|
+
if (c && c.type === "identifier") {
|
|
2773
|
+
return c.startIndex === node.startIndex && c.endIndex === node.endIndex;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
return false;
|
|
2777
|
+
}
|
|
2778
|
+
/** True when `node` is an assignment-LHS write (GO9-class climb). */
|
|
2779
|
+
isCSharpAssignLhs(node) {
|
|
2780
|
+
let cur = node;
|
|
2781
|
+
let p = node.parent;
|
|
2782
|
+
while (p && p.type === "parenthesized_expression") {
|
|
2783
|
+
cur = p;
|
|
2784
|
+
p = p.parent;
|
|
2785
|
+
}
|
|
2786
|
+
if (!p || p.type !== "assignment_expression") return false;
|
|
2787
|
+
const lhs = p.namedChild(0);
|
|
2788
|
+
return !!lhs && cur.startIndex >= lhs.startIndex && cur.endIndex <= lhs.endIndex;
|
|
2789
|
+
}
|
|
2790
|
+
/** Find the nearest enclosing method/constructor/property symbol name. */
|
|
2791
|
+
findContainingCSharpFunction(node) {
|
|
2792
|
+
let cur = node.parent;
|
|
2793
|
+
while (cur) {
|
|
2794
|
+
if (cur.type === "method_declaration" || cur.type === "constructor_declaration" || cur.type === "destructor_declaration") {
|
|
2795
|
+
const name = cur.childForFieldName("name")?.text || this.childText(cur, "identifier") || (cur.type === "constructor_declaration" ? "constructor" : "anonymous");
|
|
2796
|
+
if (cur.type === "constructor_declaration") return "constructor";
|
|
2797
|
+
if (cur.type === "destructor_declaration") return "Finalize";
|
|
2798
|
+
return name;
|
|
2799
|
+
}
|
|
2800
|
+
if (cur.type === "property_declaration" || cur.type === "indexer_declaration") {
|
|
2801
|
+
return cur.type === "indexer_declaration" ? "this" : cur.childForFieldName("name")?.text || this.childText(cur, "identifier") || "anonymous";
|
|
2802
|
+
}
|
|
2803
|
+
cur = cur.parent;
|
|
2804
|
+
}
|
|
2805
|
+
return null;
|
|
2806
|
+
}
|
|
2807
|
+
buildCSharpCallExpression(node) {
|
|
2808
|
+
const func = node.childForFieldName("function") || this.findChild(node, (n) => n.type === "identifier" || n.type === "generic_name" || n.type === "member_access_expression");
|
|
2809
|
+
if (!func) return null;
|
|
2810
|
+
if (func.type === "member_access_expression") {
|
|
2811
|
+
const nameNode = func.childForFieldName("name");
|
|
2812
|
+
const exprNode = func.childForFieldName("expression");
|
|
2813
|
+
if (!nameNode) return null;
|
|
2814
|
+
const leaf = nameNode.type === "generic_name" ? nameNode.childForFieldName("name")?.text || nameNode.text.split("<")[0] : nameNode.text;
|
|
2815
|
+
if (exprNode) {
|
|
2816
|
+
const expr = this.cleanTypeName(exprNode.text);
|
|
2817
|
+
return `${expr}.${leaf}`;
|
|
2818
|
+
}
|
|
2819
|
+
return leaf;
|
|
2820
|
+
}
|
|
2821
|
+
if (func.type === "generic_name") {
|
|
2822
|
+
return func.childForFieldName("name")?.text || func.text.split("<")[0];
|
|
2823
|
+
}
|
|
2824
|
+
return func.text;
|
|
2825
|
+
}
|
|
2826
|
+
resolveImplicitNewType(node, memberTypes) {
|
|
2827
|
+
return csharpResolveTargetTypedNew(node, memberTypes);
|
|
2828
|
+
}
|
|
2829
|
+
// ≡ /^[A-Za-z_][A-Za-z0-9_]*$/.test(s) — zero-regex helper (M49 closure
|
|
2830
|
+
// wave). \w ≡ [A-Za-z0-9_] in non-unicode mode.
|
|
2831
|
+
isPlainIdentifier(s) {
|
|
2832
|
+
if (s.length === 0) return false;
|
|
2833
|
+
const c0 = s.charCodeAt(0);
|
|
2834
|
+
const firstOk = c0 >= 65 && c0 <= 90 || c0 >= 97 && c0 <= 122 || c0 === 95;
|
|
2835
|
+
if (!firstOk) return false;
|
|
2836
|
+
for (let i = 1; i < s.length; i++) {
|
|
2837
|
+
const c = s.charCodeAt(i);
|
|
2838
|
+
const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95;
|
|
2839
|
+
if (!ok) return false;
|
|
2840
|
+
}
|
|
2841
|
+
return true;
|
|
2842
|
+
}
|
|
2843
|
+
// ≡ /:\s*base\s*\(/ (zero-regex helper, M49 closure wave): colon, any
|
|
2844
|
+
// whitespace run, "base", optional whitespace, open paren.
|
|
2845
|
+
matchesBaseInitializer(text) {
|
|
2846
|
+
let i = text.indexOf(":");
|
|
2847
|
+
if (i === -1) return false;
|
|
2848
|
+
i++;
|
|
2849
|
+
while (i < text.length && (text[i] === " " || text[i] === " " || text[i] === "\n" || text[i] === "\r")) i++;
|
|
2850
|
+
if (!text.startsWith("base", i)) return false;
|
|
2851
|
+
i += 4;
|
|
2852
|
+
while (i < text.length && (text[i] === " " || text[i] === " " || text[i] === "\n" || text[i] === "\r")) i++;
|
|
2853
|
+
return i < text.length && text[i] === "(";
|
|
2854
|
+
}
|
|
2855
|
+
isCSharpBuiltin(call) {
|
|
2856
|
+
if (CSHARP_BUILTINS.has(call)) return true;
|
|
2857
|
+
const leaf = call.split(".").pop() || call;
|
|
2858
|
+
if (leaf === "ToString" || leaf === "GetHashCode" || leaf === "Equals" || leaf === "GetType" || leaf === "ReferenceEquals") return true;
|
|
2859
|
+
return false;
|
|
2860
|
+
}
|
|
2861
|
+
extractVisibility(node) {
|
|
2862
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2863
|
+
const child = node.namedChild(i);
|
|
2864
|
+
if (child && child.type === "modifier") {
|
|
2865
|
+
const text = child.text;
|
|
2866
|
+
if (text === "public") return "public";
|
|
2867
|
+
if (text === "private") return "private";
|
|
2868
|
+
if (text === "protected") return "protected";
|
|
2869
|
+
if (text === "internal") return "internal";
|
|
2870
|
+
if (text === "file") return "private";
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
return void 0;
|
|
2874
|
+
}
|
|
2875
|
+
hasModifier(node, modifier) {
|
|
2876
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2877
|
+
const child = node.namedChild(i);
|
|
2878
|
+
if (child && child.type === "modifier" && child.text === modifier) return true;
|
|
2879
|
+
}
|
|
2880
|
+
return false;
|
|
2881
|
+
}
|
|
2882
|
+
extractNamespaceName(node) {
|
|
2883
|
+
const nameNode = this.findChild(node, (n) => n.type === "qualified_name" || n.type === "identifier");
|
|
2884
|
+
return nameNode ? nameNode.text : "anonymous";
|
|
2885
|
+
}
|
|
2886
|
+
cleanTypeName(text) {
|
|
2887
|
+
const spelled = csharpTypeSpelling(text);
|
|
2888
|
+
const lt = spelled.indexOf("<");
|
|
2889
|
+
const base = lt > 0 ? spelled.slice(0, lt) : spelled;
|
|
2890
|
+
const q = base.indexOf("?");
|
|
2891
|
+
return (q > 0 ? base.slice(0, q) : base).trim();
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* A type's DECLARATION path: itself, qualified by enclosing TYPE declarations only.
|
|
2895
|
+
*
|
|
2896
|
+
* `buildFullName` qualifies with every enclosing scope including NAMESPACES, which is
|
|
2897
|
+
* right for an FQN and wrong for a relationship source. This lane's hand-audited truth
|
|
2898
|
+
* labels an extends/implements edge at the declared name — `BaseService`, `UserService`,
|
|
2899
|
+
* `Shape`, `IRepository` — and for a NESTED type it keeps the enclosing type but still
|
|
2900
|
+
* drops the namespace: `UserService.User -> BaseService.Entity`, not
|
|
2901
|
+
* `Example.Service.UserService.User`. That is why the nested row matched before this fix
|
|
2902
|
+
* and the seven top-level ones did not: `UserService.User` happens to equal the declared
|
|
2903
|
+
* path while `Example.Base.BaseService` does not.
|
|
2904
|
+
*
|
|
2905
|
+
* So seven false negatives were a label, not a missing edge.
|
|
2906
|
+
*/
|
|
2907
|
+
declarationPath(scopeStack, name) {
|
|
2908
|
+
const segs = [];
|
|
2909
|
+
for (let i = 0; i < scopeStack.length; i++) {
|
|
2910
|
+
if (scopeStack[i].kind === ASTEngine.SymbolKind.Namespace) continue;
|
|
2911
|
+
segs.push(scopeStack[i].rawName);
|
|
2912
|
+
}
|
|
2913
|
+
segs.push(name);
|
|
2914
|
+
return segs.join(".");
|
|
2915
|
+
}
|
|
2916
|
+
/**
|
|
2917
|
+
* One assembly-export row per exported TYPE: the fully-qualified name, the declaration's
|
|
2918
|
+
* ARITY (generic arity is identity — `Reporter` and `Reporter<T>` are two types), the NAME's
|
|
2919
|
+
* own line, and the symbol kind. Deduped on (name, arity): `partial` declarations of one type
|
|
2920
|
+
* are ONE exported unit (on the FIRST declaration's line), while a partial split that crosses
|
|
2921
|
+
* arity stays two units. The M49 duplicate-provider invariant is why the dedupe is here.
|
|
2922
|
+
*/
|
|
2923
|
+
pushExportInfo(exports, name, arity, line, kind, visibility) {
|
|
2924
|
+
if (!name || visibility !== "public") return;
|
|
2925
|
+
for (const e of exports) if (e.name === name && e.arity === arity) return;
|
|
2926
|
+
exports.push({ name, kind, arity, line, visibility });
|
|
2927
|
+
}
|
|
2928
|
+
buildFullName(scopeStack, name) {
|
|
2929
|
+
if (scopeStack.length === 0) return name;
|
|
2930
|
+
let res = "";
|
|
2931
|
+
for (let i = 0; i < scopeStack.length; i++) {
|
|
2932
|
+
const raw = scopeStack[i].rawName;
|
|
2933
|
+
if (raw.indexOf(".") >= 0) res = raw + ".";
|
|
2934
|
+
else res += raw + ".";
|
|
2935
|
+
}
|
|
2936
|
+
return res + name;
|
|
2937
|
+
}
|
|
2938
|
+
extractTypeString(node) {
|
|
2939
|
+
const typeNodes = [
|
|
2940
|
+
"predefined_type",
|
|
2941
|
+
"type_identifier",
|
|
2942
|
+
"identifier",
|
|
2943
|
+
"generic_name",
|
|
2944
|
+
"array_type",
|
|
2945
|
+
"nullable_type",
|
|
2946
|
+
"tuple_type",
|
|
2947
|
+
"implicit_type"
|
|
2948
|
+
];
|
|
2949
|
+
for (let idx = 0; idx < typeNodes.length; idx++) {
|
|
2950
|
+
const t = typeNodes[idx];
|
|
2951
|
+
const found = this.findChild(node, (n) => n.type === t);
|
|
2952
|
+
if (found) return found.text;
|
|
2953
|
+
}
|
|
2954
|
+
return void 0;
|
|
2955
|
+
}
|
|
2956
|
+
extractAttributes(node) {
|
|
2957
|
+
const attrs = [];
|
|
2958
|
+
const attrLists = this.findAllChildren(node, "attribute_list");
|
|
2959
|
+
for (let idx = 0; idx < attrLists.length; idx++) {
|
|
2960
|
+
const list = attrLists[idx];
|
|
2961
|
+
for (let i = 0; i < list.namedChildCount; i++) {
|
|
2962
|
+
const attr = list.namedChild(i);
|
|
2963
|
+
if (attr && attr.type === "attribute") {
|
|
2964
|
+
const nameNode = this.findChild(attr, (n) => n.type === "identifier" || n.type === "generic_name" || n.type === "qualified_name" || n.type === "alias_qualified_name");
|
|
2965
|
+
if (nameNode) {
|
|
2966
|
+
const name = csharpCleanTypeLeaf(csharpTypeNameText(csharpTypeSpelling(nameNode.text)));
|
|
2967
|
+
if (name) attrs.push(name);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
return attrs;
|
|
2973
|
+
}
|
|
2974
|
+
/**
|
|
2975
|
+
* M59.A7 (#825): the 1-based line of the node that NAMES an edge's target.
|
|
2976
|
+
*
|
|
2977
|
+
* The contract is the family's, not this file's: a relationship's
|
|
2978
|
+
* `line_number` is the line where the TARGET is written — the callee for a
|
|
2979
|
+
* call, the read for a member use, the constructed type for an instantiation,
|
|
2980
|
+
* the symbol's own declaration for contains/overrides. It is checked
|
|
2981
|
+
* independently by script/m59/oracle-callsite.ts, which re-derives every edge
|
|
2982
|
+
* from the tree and requires the target token to appear on the claimed line.
|
|
2983
|
+
*
|
|
2984
|
+
* Three shapes are traps and all three were measured on this lane (probe:
|
|
2985
|
+
* script/m14/tmp/cs-line-shapes.ts) rather than assumed:
|
|
2986
|
+
* * an invocation node starts at its RECEIVER, so `helper\n .Strip()` needs
|
|
2987
|
+
* the callee leaf, not the call node;
|
|
2988
|
+
* * a `constructor_initializer` starts at its `:` and `base`/`this` are
|
|
2989
|
+
* anonymous children, so a wrapped initializer needs the keyword token or
|
|
2990
|
+
* it lands one line early;
|
|
2991
|
+
* * a target-typed `new()` names NO type at the call site at all — the
|
|
2992
|
+
* target is written in the DECLARATION (see targetTypedNewTypeNode).
|
|
2993
|
+
*
|
|
2994
|
+
* `undefined` is a first-class answer. A node that does not exist contributes
|
|
2995
|
+
* NO line rather than a fabricated 0, which is the rule `0 means unknown and
|
|
2996
|
+
* unknown must be absent` (#820/#821) applied to this field.
|
|
2997
|
+
*/
|
|
2998
|
+
nodeLine(node) {
|
|
2999
|
+
if (!node) return void 0;
|
|
3000
|
+
return node.startPosition.row + 1;
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* The `base` / `this` keyword of a constructor_initializer.
|
|
3004
|
+
*
|
|
3005
|
+
* Both are ANONYMOUS tokens in tree-sitter-c-sharp, so findChild (named
|
|
3006
|
+
* children only) cannot see them, and the initializer node itself starts at
|
|
3007
|
+
* the `:`. Measured: `public C(int x)\n :\n base(x)` puts the colon on 3
|
|
3008
|
+
* and the keyword on 4 — the keyword is the node that names the target.
|
|
3009
|
+
*/
|
|
3010
|
+
initializerKeywordNode(node) {
|
|
3011
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3012
|
+
const child = node.child(i);
|
|
3013
|
+
if (child && (child.type === "base" || child.type === "this")) return child;
|
|
3014
|
+
}
|
|
3015
|
+
return null;
|
|
3016
|
+
}
|
|
3017
|
+
/**
|
|
3018
|
+
* The FIRST anonymous child whose text is `text` — an operator or punctuation
|
|
3019
|
+
* token, which `findChild` (named children only) cannot see.
|
|
3020
|
+
*
|
|
3021
|
+
* Added for the M60.R1 paydown: a using directive's alias form is the only
|
|
3022
|
+
* shape that contains an `=`, and position (not text) is what tells the ALIAS
|
|
3023
|
+
* from the TARGET — see the using_directive branch. Returns the NODE, so the
|
|
3024
|
+
* caller can then ask for the child after it as well as its presence.
|
|
3025
|
+
*/
|
|
3026
|
+
anonymousChildNode(node, text) {
|
|
3027
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3028
|
+
const child = node.child(i);
|
|
3029
|
+
if (child && child.type === text) return child;
|
|
3030
|
+
}
|
|
3031
|
+
return null;
|
|
3032
|
+
}
|
|
3033
|
+
/** The first NAMED child that starts after `after` ends — the target of an
|
|
3034
|
+
* alias using, which the grammar writes after the `=`. */
|
|
3035
|
+
firstNamedChildAfter(node, after) {
|
|
3036
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3037
|
+
const child = node.namedChild(i);
|
|
3038
|
+
if (child && child.startIndex >= after.endIndex) return child;
|
|
3039
|
+
}
|
|
3040
|
+
return null;
|
|
3041
|
+
}
|
|
3042
|
+
/**
|
|
3043
|
+
* The node that CARRIES a declaration's declared type.
|
|
3044
|
+
*
|
|
3045
|
+
* Mirrors csharpMemberDeclaredTypeText's holder resolution exactly (the same
|
|
3046
|
+
* three shapes: field_declaration > variable_declaration, a variable_declarator
|
|
3047
|
+
* whose type lives on the enclosing variable_declaration, and property/
|
|
3048
|
+
* indexer/other declarations that hold the type themselves), because the two
|
|
3049
|
+
* must never disagree about WHICH declaration answered — one writes the text,
|
|
3050
|
+
* the other is used for the line stamp.
|
|
3051
|
+
*/
|
|
3052
|
+
memberDeclaredTypeNode(memberLike) {
|
|
3053
|
+
if (!memberLike) return null;
|
|
3054
|
+
let holder = memberLike;
|
|
3055
|
+
if (memberLike.type === "field_declaration") {
|
|
3056
|
+
holder = this.findChild(memberLike, "variable_declaration");
|
|
3057
|
+
} else if (memberLike.type === "variable_declarator" && memberLike.parent?.type === "variable_declaration") {
|
|
3058
|
+
holder = memberLike.parent;
|
|
3059
|
+
}
|
|
3060
|
+
if (!holder) return null;
|
|
3061
|
+
const typeField = holder.childForFieldName("type");
|
|
3062
|
+
if (typeField) return typeField;
|
|
3063
|
+
const nameChild = holder.childForFieldName("name");
|
|
3064
|
+
for (let i = 0; i < holder.namedChildCount; i++) {
|
|
3065
|
+
const child = holder.namedChild(i);
|
|
3066
|
+
if (!child || !CSHARP_DECLARED_TYPE_NODES.has(child.type)) continue;
|
|
3067
|
+
if (nameChild && child.startIndex === nameChild.startIndex && child.endIndex === nameChild.endIndex) continue;
|
|
3068
|
+
return child;
|
|
3069
|
+
}
|
|
3070
|
+
return null;
|
|
3071
|
+
}
|
|
3072
|
+
/**
|
|
3073
|
+
* The narrowest node inside a type annotation whose text IS `target`.
|
|
3074
|
+
*
|
|
3075
|
+
* For `List<EnumSettingEntry> xs = new();` the target is the ELEMENT type, so
|
|
3076
|
+
* the node that names it is the type argument, not the annotation. Matching is
|
|
3077
|
+
* by exact text (then by csharpCleanTypeLeaf, which is the same unwrapping the
|
|
3078
|
+
* target text went through), and the narrowest match wins. No match means the
|
|
3079
|
+
* target was resolved from something other than a written name — an honest
|
|
3080
|
+
* no-line, never a guess at a nearby line.
|
|
3081
|
+
*/
|
|
3082
|
+
nodeNamingToken(typeNode, target) {
|
|
3083
|
+
if (!typeNode) return null;
|
|
3084
|
+
const parts = target.split(".");
|
|
3085
|
+
const leaf = parts[parts.length - 1] || target;
|
|
3086
|
+
let best = null;
|
|
3087
|
+
const stack = [typeNode];
|
|
3088
|
+
while (stack.length > 0) {
|
|
3089
|
+
const n = stack.pop();
|
|
3090
|
+
const text = n.text;
|
|
3091
|
+
if (text === leaf || csharpCleanTypeLeaf(text) === leaf) {
|
|
3092
|
+
if (!best || n.endIndex - n.startIndex <= best.endIndex - best.startIndex) best = n;
|
|
3093
|
+
}
|
|
3094
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
3095
|
+
const child = n.namedChild(i);
|
|
3096
|
+
if (child) stack.push(child);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
return best;
|
|
3100
|
+
}
|
|
3101
|
+
/**
|
|
3102
|
+
* M59.A7 (#825), the target-typed-`new()` site.
|
|
3103
|
+
*
|
|
3104
|
+
* `List<T> xs = new();` writes its construction target in the DECLARATION, and
|
|
3105
|
+
* the assignment form (`xs = new();`) writes it on the MEMBER's declaration,
|
|
3106
|
+
* not on the assignment line. Both are reached through the shared walk
|
|
3107
|
+
* (csharpTargetTypedNewSite) so the stamp and the resolved target can never
|
|
3108
|
+
* describe different declarations; the member table carries the node for the
|
|
3109
|
+
* assignment form, because the declaration is in another subtree.
|
|
3110
|
+
*/
|
|
3111
|
+
targetTypedNewTypeNode(node, memberTypes, memberTypeNodes, target) {
|
|
3112
|
+
const site = csharpTargetTypedNewSite(node);
|
|
3113
|
+
if (!site) return null;
|
|
3114
|
+
const holder = site.kind === "declaration" ? site.decl : memberTypeNodes.get(site.memberName) ?? null;
|
|
3115
|
+
return this.nodeNamingToken(this.memberDeclaredTypeNode(holder), target);
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Is a call's callee a node that NAMES something?
|
|
3119
|
+
*
|
|
3120
|
+
* Found by a depth draw rather than by reading the code: on powershell's
|
|
3121
|
+
* `RunspaceConnectionInfo.cs` the grammar's error recovery (one ERROR node
|
|
3122
|
+
* spanning `false; return StartSSHProcessImpl(...)`) produced an
|
|
3123
|
+
* invocation_expression whose `function` field IS a `boolean_literal`, and the
|
|
3124
|
+
* analyzer emitted `calls StartSSHProcess -> false` — a fabricated call to a
|
|
3125
|
+
* literal, the only unexplained edge on a 900-file draw (line 2331, measured
|
|
3126
|
+
* with script/m14/tmp/cs-u2331-probe.ts). The old hygiene checked the target
|
|
3127
|
+
* STRING (keyword set + identifier shape + builtin set), and `false` is a
|
|
3128
|
+
* well-formed identifier as far as a string check can see. The rule is
|
|
3129
|
+
* structural instead: a callee names its target only if the node that carries
|
|
3130
|
+
* it is a NAME node (an identifier or a generic_name) — which is what the
|
|
3131
|
+
* existing keyword-list rejection encodes for the legal `if (...)` case, done
|
|
3132
|
+
* once for every junk-callee shape.
|
|
3133
|
+
*/
|
|
3134
|
+
isCSharpCalleeNameNode(node) {
|
|
3135
|
+
if (!node) return false;
|
|
3136
|
+
return node.type === "identifier" || node.type === "generic_name";
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* The node that names a call's target: the callee leaf.
|
|
3140
|
+
*
|
|
3141
|
+
* Mirrors buildCSharpCallExpression's leaf selection (the member-access NAME
|
|
3142
|
+
* field, a generic_name's name field, else the callee node itself) — that
|
|
3143
|
+
* function returns the text, this returns the node the text is written on.
|
|
3144
|
+
*/
|
|
3145
|
+
csharpCallCalleeNode(node) {
|
|
3146
|
+
const func = node.childForFieldName("function") || this.findChild(node, (n) => n.type === "identifier" || n.type === "generic_name" || n.type === "member_access_expression");
|
|
3147
|
+
if (!func) return null;
|
|
3148
|
+
if (func.type === "member_access_expression") {
|
|
3149
|
+
const nameNode = func.childForFieldName("name");
|
|
3150
|
+
if (!nameNode) return null;
|
|
3151
|
+
return nameNode.type === "generic_name" ? nameNode.childForFieldName("name") ?? nameNode : nameNode;
|
|
3152
|
+
}
|
|
3153
|
+
if (func.type === "generic_name") return func.childForFieldName("name") ?? func;
|
|
3154
|
+
if (func.type === "conditional_access_expression" || func.type === "member_binding_expression") {
|
|
3155
|
+
const binding = func.type === "member_binding_expression" ? func : this.findChild(func, (n) => n.type === "member_binding_expression");
|
|
3156
|
+
const leaf = binding ? this.csharpBindingLeaf(binding) : null;
|
|
3157
|
+
return leaf ?? func;
|
|
3158
|
+
}
|
|
3159
|
+
return func;
|
|
3160
|
+
}
|
|
3161
|
+
/**
|
|
3162
|
+
* The identifier a `member_binding_expression` binds: the member name after
|
|
3163
|
+
* the dot (`.Invoke` -> `Invoke`). Walks the binding's children backwards
|
|
3164
|
+
* because the dot is an anonymous token and the name is the last name node.
|
|
3165
|
+
*/
|
|
3166
|
+
csharpBindingLeaf(binding) {
|
|
3167
|
+
for (let i = binding.childCount - 1; i >= 0; i--) {
|
|
3168
|
+
const child = binding.child(i);
|
|
3169
|
+
if (!child) continue;
|
|
3170
|
+
if (child.type === "identifier") return child;
|
|
3171
|
+
if (child.type === "generic_name") return child.childForFieldName("name") ?? child;
|
|
3172
|
+
}
|
|
3173
|
+
return null;
|
|
3174
|
+
}
|
|
3175
|
+
nodeToRange(node) {
|
|
3176
|
+
return {
|
|
3177
|
+
startLine: node.startPosition.row + 1,
|
|
3178
|
+
startColumn: node.startPosition.column + 1,
|
|
3179
|
+
endLine: node.endPosition.row + 1,
|
|
3180
|
+
endColumn: node.endPosition.column + 1,
|
|
3181
|
+
startIndex: node.startIndex,
|
|
3182
|
+
endIndex: node.endIndex
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
findChild(node, type) {
|
|
3186
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3187
|
+
const child = node.namedChild(i);
|
|
3188
|
+
if (!child) continue;
|
|
3189
|
+
if (typeof type === "string" ? child.type === type : type(child)) return child;
|
|
3190
|
+
}
|
|
3191
|
+
return null;
|
|
3192
|
+
}
|
|
3193
|
+
findAllChildren(node, type) {
|
|
3194
|
+
const res = [];
|
|
3195
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3196
|
+
const child = node.namedChild(i);
|
|
3197
|
+
if (child && child.type === type) res.push(child);
|
|
3198
|
+
}
|
|
3199
|
+
return res;
|
|
3200
|
+
}
|
|
3201
|
+
childText(node, type) {
|
|
3202
|
+
const child = this.findChild(node, type);
|
|
3203
|
+
return child ? child.text : null;
|
|
3204
|
+
}
|
|
3205
|
+
};
|
|
3206
|
+
function stringIncludes(str, val) {
|
|
3207
|
+
return str.indexOf(val) !== -1;
|
|
3208
|
+
}
|
|
3209
|
+
var CS_FRAMEWORK_ORDER = [
|
|
3210
|
+
"ASP.NET Core",
|
|
3211
|
+
"Entity Framework Core",
|
|
3212
|
+
"Unity",
|
|
3213
|
+
"WPF",
|
|
3214
|
+
"Windows Forms",
|
|
3215
|
+
"xUnit",
|
|
3216
|
+
"NUnit",
|
|
3217
|
+
"MSTest"
|
|
3218
|
+
];
|
|
3219
|
+
var CS_FRAMEWORK_PATTERNS = {
|
|
3220
|
+
"ASP.NET Core": ["mvc_pattern", "api_endpoints"],
|
|
3221
|
+
"Entity Framework Core": ["orm_mapping", "repository_pattern"],
|
|
3222
|
+
Unity: ["game_engine"],
|
|
3223
|
+
WPF: ["ui_framework"],
|
|
3224
|
+
"Windows Forms": ["ui_framework"],
|
|
3225
|
+
xUnit: ["test_framework"],
|
|
3226
|
+
NUnit: ["test_framework"],
|
|
3227
|
+
MSTest: ["test_framework"]
|
|
3228
|
+
};
|
|
3229
|
+
function csharpSetHasAny(s, vs) {
|
|
3230
|
+
for (let i = 0; i < vs.length; i++) if (s.has(vs[i])) return true;
|
|
3231
|
+
return false;
|
|
3232
|
+
}
|
|
3233
|
+
function csharpUsingPrefix(usings, prefix) {
|
|
3234
|
+
for (const u of usings) {
|
|
3235
|
+
if (u === prefix || u.startsWith(prefix + ".")) return true;
|
|
3236
|
+
}
|
|
3237
|
+
return false;
|
|
3238
|
+
}
|
|
3239
|
+
var CS_FRAMEWORK_MARKERS = {
|
|
3240
|
+
"ASP.NET Core": (u, b, a) => csharpUsingPrefix(u, "Microsoft.AspNetCore") || csharpSetHasAny(b, ["Controller", "ControllerBase", "PageModel", "Hub"]) || csharpSetHasAny(a, ["ApiController", "HttpGet", "HttpPost", "HttpPut", "HttpDelete", "HttpPatch"]),
|
|
3241
|
+
"Entity Framework Core": (u, b) => csharpUsingPrefix(u, "Microsoft.EntityFrameworkCore") || b.has("DbContext"),
|
|
3242
|
+
Unity: (u, b) => csharpUsingPrefix(u, "UnityEngine") || csharpSetHasAny(b, ["MonoBehaviour", "ScriptableObject"]),
|
|
3243
|
+
WPF: (u, b) => {
|
|
3244
|
+
for (const x of u) {
|
|
3245
|
+
if ((x === "System.Windows" || x.startsWith("System.Windows.")) && !(x === "System.Windows.Forms" || x.startsWith("System.Windows.Forms."))) {
|
|
3246
|
+
return true;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
return csharpSetHasAny(b, ["Window", "UserControl"]);
|
|
3250
|
+
},
|
|
3251
|
+
"Windows Forms": (u, b) => csharpUsingPrefix(u, "System.Windows.Forms") || b.has("Form"),
|
|
3252
|
+
xUnit: (u, _b, a) => csharpUsingPrefix(u, "Xunit") || csharpSetHasAny(a, ["Fact", "Theory"]),
|
|
3253
|
+
NUnit: (u, _b, a) => csharpUsingPrefix(u, "NUnit") || a.has("TestFixture"),
|
|
3254
|
+
MSTest: (u, _b, a) => csharpUsingPrefix(u, "Microsoft.VisualStudio.TestTools") || csharpSetHasAny(a, ["TestClass", "TestMethod"])
|
|
3255
|
+
};
|
|
3256
|
+
function csharpUsingDirectivePath(text) {
|
|
3257
|
+
let t = text.trim();
|
|
3258
|
+
const semi = t.indexOf(";");
|
|
3259
|
+
if (semi >= 0) t = t.slice(0, semi);
|
|
3260
|
+
t = t.trim();
|
|
3261
|
+
for (; ; ) {
|
|
3262
|
+
if (t.startsWith("global ")) {
|
|
3263
|
+
t = t.slice(7).trim();
|
|
3264
|
+
continue;
|
|
3265
|
+
}
|
|
3266
|
+
if (t.startsWith("using ")) {
|
|
3267
|
+
t = t.slice(6).trim();
|
|
3268
|
+
continue;
|
|
3269
|
+
}
|
|
3270
|
+
if (t.startsWith("static ")) {
|
|
3271
|
+
t = t.slice(7).trim();
|
|
3272
|
+
continue;
|
|
3273
|
+
}
|
|
3274
|
+
break;
|
|
3275
|
+
}
|
|
3276
|
+
const eq = t.indexOf("=");
|
|
3277
|
+
if (eq >= 0) t = t.slice(eq + 1).trim();
|
|
3278
|
+
const space = t.indexOf(" ");
|
|
3279
|
+
if (space >= 0) t = t.slice(0, space);
|
|
3280
|
+
return t.length > 0 ? t : void 0;
|
|
3281
|
+
}
|
|
3282
|
+
function csharpTypeLeafName(n) {
|
|
3283
|
+
const leaf = csharpTypeLeafNode(n);
|
|
3284
|
+
return leaf ? leaf.text : void 0;
|
|
3285
|
+
}
|
|
3286
|
+
export {
|
|
3287
|
+
RealCSharpAnalyzer,
|
|
3288
|
+
csharpCleanTypeLeaf,
|
|
3289
|
+
csharpDeclaredTypeRefs,
|
|
3290
|
+
csharpMemberDeclaredTypeText,
|
|
3291
|
+
csharpResolveTargetTypedNew,
|
|
3292
|
+
csharpTargetFromDeclaredType,
|
|
3293
|
+
csharpTargetTypedNewSite,
|
|
3294
|
+
csharpTypeNameText,
|
|
3295
|
+
csharpTypeSpelling,
|
|
3296
|
+
csharpUsingDirectiveKind,
|
|
3297
|
+
csharpUsingDirectiveTarget
|
|
3298
|
+
};
|