@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,1518 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ASTEngine
|
|
3
|
+
} from "./chunk-3ZTOHHSW.js";
|
|
4
|
+
import "./chunk-BQ3OVQ3P.js";
|
|
5
|
+
import "./chunk-SK6YVHFZ.js";
|
|
6
|
+
import "./chunk-2BSKD7O3.js";
|
|
7
|
+
import {
|
|
8
|
+
BINDING_SEP
|
|
9
|
+
} from "./chunk-KAET47ZL.js";
|
|
10
|
+
import "./chunk-KCDECRJY.js";
|
|
11
|
+
import "./chunk-MLKGABMK.js";
|
|
12
|
+
|
|
13
|
+
// src/ast/real-objective-c-analyzer.ts
|
|
14
|
+
var BARE_READ_SKIP = /* @__PURE__ */ new Set([
|
|
15
|
+
"self",
|
|
16
|
+
"super",
|
|
17
|
+
"nil",
|
|
18
|
+
"Nil",
|
|
19
|
+
"NULL",
|
|
20
|
+
"YES",
|
|
21
|
+
"NO",
|
|
22
|
+
"true",
|
|
23
|
+
"false",
|
|
24
|
+
"_cmd"
|
|
25
|
+
]);
|
|
26
|
+
var DECLARATOR_WRAPPERS = /* @__PURE__ */ new Set([
|
|
27
|
+
"array_declarator",
|
|
28
|
+
"pointer_declarator",
|
|
29
|
+
"parenthesized_declarator",
|
|
30
|
+
"attributed_declarator",
|
|
31
|
+
"abstract_pointer_declarator",
|
|
32
|
+
"abstract_block_pointer_declarator",
|
|
33
|
+
"block_pointer_declarator",
|
|
34
|
+
"function_declarator"
|
|
35
|
+
]);
|
|
36
|
+
function objcPartTarget(writtenPath) {
|
|
37
|
+
const segments = writtenPath.split("/").filter((segment) => segment.length > 0);
|
|
38
|
+
if (segments.length === 0) return writtenPath;
|
|
39
|
+
if (segments.length > 1) {
|
|
40
|
+
const module = segments[0];
|
|
41
|
+
const moduleDot = module.lastIndexOf(".");
|
|
42
|
+
return moduleDot > 0 ? module.slice(0, moduleDot) : module;
|
|
43
|
+
}
|
|
44
|
+
const file = segments[0];
|
|
45
|
+
const dot = file.lastIndexOf(".");
|
|
46
|
+
return dot > 0 ? file.slice(0, dot) : file;
|
|
47
|
+
}
|
|
48
|
+
function isUnterminatedDeclaration(node) {
|
|
49
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
50
|
+
const child = node.child(i);
|
|
51
|
+
if (child && child.isMissing && child.type === ";") return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
function hasParseError(node) {
|
|
56
|
+
const stack = [node];
|
|
57
|
+
let guard = 0;
|
|
58
|
+
while (stack.length > 0 && guard++ < 2e4) {
|
|
59
|
+
const n = stack.pop();
|
|
60
|
+
if (!n) continue;
|
|
61
|
+
if (n.type === "ERROR") return true;
|
|
62
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
63
|
+
const c = n.child(i);
|
|
64
|
+
if (c) stack.push(c);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
var OBJC_FRAMEWORK_ORDER = ["UIKit", "AppKit", "Cocoa", "Foundation"];
|
|
70
|
+
var OBJC_FRAMEWORK_PATTERNS = {
|
|
71
|
+
UIKit: ["ui_framework"],
|
|
72
|
+
AppKit: ["ui_framework"],
|
|
73
|
+
Cocoa: ["app_umbrella"],
|
|
74
|
+
Foundation: ["base_library"]
|
|
75
|
+
};
|
|
76
|
+
var OBJC_LINKAGE_UNIT_KINDS = /* @__PURE__ */ new Set([
|
|
77
|
+
ASTEngine.SymbolKind.Class,
|
|
78
|
+
ASTEngine.SymbolKind.Interface,
|
|
79
|
+
ASTEngine.SymbolKind.Struct,
|
|
80
|
+
ASTEngine.SymbolKind.Union,
|
|
81
|
+
ASTEngine.SymbolKind.Enum,
|
|
82
|
+
ASTEngine.SymbolKind.Type,
|
|
83
|
+
ASTEngine.SymbolKind.Function,
|
|
84
|
+
ASTEngine.SymbolKind.Variable,
|
|
85
|
+
ASTEngine.SymbolKind.Constant
|
|
86
|
+
]);
|
|
87
|
+
var OBJC_EXPORT_MARKERS = /* @__PURE__ */ new Set([
|
|
88
|
+
// Foundation's spelling of `extern __attribute__((visibility("default")))` —
|
|
89
|
+
// the marker every framework header uses for its published surface.
|
|
90
|
+
"FOUNDATION_EXPORT",
|
|
91
|
+
// C's own keyword for external linkage. One occurrence in the corpus, and the
|
|
92
|
+
// grammar has a node type for it, so leaving it out would be the arbitrary
|
|
93
|
+
// choice rather than the principled one.
|
|
94
|
+
"extern"
|
|
95
|
+
]);
|
|
96
|
+
var RealObjectiveCAnalyzer = class {
|
|
97
|
+
analyze(rootNode, content, filePath) {
|
|
98
|
+
const symbols = [];
|
|
99
|
+
const relationships = [];
|
|
100
|
+
const imports = [];
|
|
101
|
+
const exports = [];
|
|
102
|
+
const frameworks = /* @__PURE__ */ new Set();
|
|
103
|
+
const errors = [];
|
|
104
|
+
const walkStack = [
|
|
105
|
+
{ node: rootNode, isExit: false, activeScope: "global", parentType: "", isInBody: false, owner: "", bindable: false }
|
|
106
|
+
];
|
|
107
|
+
let valueFrame = null;
|
|
108
|
+
const blockFrameStack = [];
|
|
109
|
+
const globals = /* @__PURE__ */ new Set();
|
|
110
|
+
const classMembers = /* @__PURE__ */ new Map();
|
|
111
|
+
const classSuper = /* @__PURE__ */ new Map();
|
|
112
|
+
const internalLinkage = /* @__PURE__ */ new Set();
|
|
113
|
+
const exportMarkers = /* @__PURE__ */ new Map();
|
|
114
|
+
const exportLineByName = /* @__PURE__ */ new Map();
|
|
115
|
+
const enumCases = /* @__PURE__ */ new Set();
|
|
116
|
+
{
|
|
117
|
+
const preStack = [rootNode];
|
|
118
|
+
while (preStack.length > 0) {
|
|
119
|
+
const pn = preStack.pop();
|
|
120
|
+
if (!pn || pn.isMissing) continue;
|
|
121
|
+
if (pn.type === "enum_specifier") {
|
|
122
|
+
const elist = this.findChild(pn, (n) => n.type === "enumerator_list");
|
|
123
|
+
if (elist) {
|
|
124
|
+
for (let ei = 0; ei < elist.namedChildCount; ei++) {
|
|
125
|
+
const en = elist.namedChild(ei);
|
|
126
|
+
if (!en || en.type !== "enumerator") continue;
|
|
127
|
+
const nm = this.findChild(en, (n) => n.type === "identifier");
|
|
128
|
+
if (nm && nm.text.trim()) enumCases.add(nm.text);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (pn.type === "class_interface" || pn.type === "class_implementation") {
|
|
133
|
+
const cn = this.findChild(pn, (n) => n.type === "identifier" || n.type === "type_identifier");
|
|
134
|
+
if (cn) {
|
|
135
|
+
let members = classMembers.get(cn.text);
|
|
136
|
+
if (!members) {
|
|
137
|
+
members = /* @__PURE__ */ new Set();
|
|
138
|
+
classMembers.set(cn.text, members);
|
|
139
|
+
}
|
|
140
|
+
const memStack = [pn];
|
|
141
|
+
while (memStack.length > 0) {
|
|
142
|
+
const mn = memStack.pop();
|
|
143
|
+
if (!mn) continue;
|
|
144
|
+
if (mn !== pn && (mn.type === "class_interface" || mn.type === "class_implementation" || mn.type === "protocol_declaration")) continue;
|
|
145
|
+
if (mn.type === "property_declaration" || mn.type === "instance_variable") {
|
|
146
|
+
const mname = this.findDeclaredName(mn);
|
|
147
|
+
if (mname) members.add(mname);
|
|
148
|
+
}
|
|
149
|
+
for (let mi = mn.namedChildCount - 1; mi >= 0; mi--) {
|
|
150
|
+
const mc = mn.namedChild(mi);
|
|
151
|
+
if (mc) memStack.push(mc);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (let pi = pn.namedChildCount - 1; pi >= 0; pi--) {
|
|
157
|
+
const pc = pn.namedChild(pi);
|
|
158
|
+
if (pc) preStack.push(pc);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const bindValue = (name) => {
|
|
163
|
+
if (!name) return;
|
|
164
|
+
if (blockFrameStack.length > 0) blockFrameStack[blockFrameStack.length - 1].add(name);
|
|
165
|
+
else if (valueFrame) valueFrame.add(name);
|
|
166
|
+
else globals.add(name);
|
|
167
|
+
};
|
|
168
|
+
const isBoundValue = (name, owner) => {
|
|
169
|
+
for (let bi = blockFrameStack.length - 1; bi >= 0; bi--) {
|
|
170
|
+
if (blockFrameStack[bi].has(name)) return true;
|
|
171
|
+
}
|
|
172
|
+
if (valueFrame && valueFrame.has(name)) return true;
|
|
173
|
+
const cm = classMembers.get(owner);
|
|
174
|
+
if (cm && cm.has(name)) return true;
|
|
175
|
+
if (globals.has(name)) return true;
|
|
176
|
+
if (enumCases.has(name)) return true;
|
|
177
|
+
return false;
|
|
178
|
+
};
|
|
179
|
+
while (walkStack.length > 0) {
|
|
180
|
+
const state = walkStack.pop();
|
|
181
|
+
if (!state) continue;
|
|
182
|
+
if (state.isExit) {
|
|
183
|
+
blockFrameStack.pop();
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const { node, isExit, activeScope, parentType, isInBody, owner, bindable } = state;
|
|
187
|
+
if (isExit || !node || node.isMissing) continue;
|
|
188
|
+
let nextActiveScope = activeScope;
|
|
189
|
+
let nextIsInBody = isInBody;
|
|
190
|
+
let nextOwner = owner;
|
|
191
|
+
let nextBindable = bindable;
|
|
192
|
+
if (node.type === "preproc_include") {
|
|
193
|
+
let modName = null;
|
|
194
|
+
let modNode = null;
|
|
195
|
+
let isSystem = false;
|
|
196
|
+
for (let ci = 0; ci < node.namedChildCount; ci++) {
|
|
197
|
+
const cn = node.namedChild(ci);
|
|
198
|
+
if (!cn) continue;
|
|
199
|
+
if (cn.type === "system_lib_string") {
|
|
200
|
+
isSystem = true;
|
|
201
|
+
const txt = cn.text;
|
|
202
|
+
if (txt.length >= 2) {
|
|
203
|
+
modName = txt.slice(1, txt.length - 1);
|
|
204
|
+
modNode = cn;
|
|
205
|
+
}
|
|
206
|
+
} else if (cn.type === "string_literal") {
|
|
207
|
+
const content2 = this.findChild(cn, (n) => n.type === "string_content");
|
|
208
|
+
modName = content2 ? content2.text : cn.text;
|
|
209
|
+
modNode = content2 ?? cn;
|
|
210
|
+
}
|
|
211
|
+
if (modName) break;
|
|
212
|
+
}
|
|
213
|
+
if (modName) {
|
|
214
|
+
if (modName.includes("UIKit")) frameworks.add("UIKit");
|
|
215
|
+
if (modName.includes("Foundation")) frameworks.add("Foundation");
|
|
216
|
+
if (modName.includes("AppKit")) frameworks.add("AppKit");
|
|
217
|
+
if (modName.includes("Cocoa")) frameworks.add("Cocoa");
|
|
218
|
+
imports.push({
|
|
219
|
+
module: modName,
|
|
220
|
+
items: [],
|
|
221
|
+
importType: "include",
|
|
222
|
+
lineNumber: node.startPosition.row + 1,
|
|
223
|
+
isExternal: isSystem || !modName.startsWith(".")
|
|
224
|
+
});
|
|
225
|
+
relationships.push({
|
|
226
|
+
source: "root",
|
|
227
|
+
target: modName,
|
|
228
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
229
|
+
confidence: 1,
|
|
230
|
+
line_number: this.nodeLine(modNode ?? node),
|
|
231
|
+
metadata: {
|
|
232
|
+
relationshipType: "include_dependency",
|
|
233
|
+
isObjCImport: true,
|
|
234
|
+
isObjCModuleImport: false,
|
|
235
|
+
isImport: true,
|
|
236
|
+
isSystem,
|
|
237
|
+
module: modName,
|
|
238
|
+
fileLevel: true
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
relationships.push({
|
|
242
|
+
source: "root",
|
|
243
|
+
target: objcPartTarget(modName),
|
|
244
|
+
type: ASTEngine.RelationshipType.PartOf,
|
|
245
|
+
confidence: 1,
|
|
246
|
+
line_number: this.nodeLine(modNode ?? node),
|
|
247
|
+
metadata: {
|
|
248
|
+
partRole: "library",
|
|
249
|
+
partPath: modName,
|
|
250
|
+
isFileInclusion: true,
|
|
251
|
+
isObjCImport: true,
|
|
252
|
+
isObjCModuleImport: false,
|
|
253
|
+
isSystem,
|
|
254
|
+
fileLevel: true,
|
|
255
|
+
evidenceClass: "import_based"
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
relationships.push({
|
|
259
|
+
source: "root",
|
|
260
|
+
target: modName,
|
|
261
|
+
type: ASTEngine.RelationshipType.References,
|
|
262
|
+
confidence: 0.9,
|
|
263
|
+
line_number: this.nodeLine(modNode ?? node),
|
|
264
|
+
metadata: { isObjCImport: true, isObjCModuleImport: false, reasonCode: "include-target-not-resolved" }
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (node.type === "module_import") {
|
|
269
|
+
const nameNode = this.findChild(node, (n) => n.type === "identifier" || n.type === "type_identifier");
|
|
270
|
+
const modName = nameNode ? nameNode.text.trim() : "";
|
|
271
|
+
if (modName) {
|
|
272
|
+
imports.push({
|
|
273
|
+
module: modName,
|
|
274
|
+
items: [],
|
|
275
|
+
importType: "module",
|
|
276
|
+
lineNumber: node.startPosition.row + 1,
|
|
277
|
+
isExternal: true
|
|
278
|
+
});
|
|
279
|
+
relationships.push({
|
|
280
|
+
source: "root",
|
|
281
|
+
target: modName,
|
|
282
|
+
type: ASTEngine.RelationshipType.References,
|
|
283
|
+
confidence: 0.9,
|
|
284
|
+
line_number: this.nodeLine(nameNode ?? node),
|
|
285
|
+
// Its OWN code, not the include's: the target names a MODULE and nothing in this
|
|
286
|
+
// walk knows whether the build defines a module under that name. Pinned by
|
|
287
|
+
// objc/module-imports ("the sub-1.0 twin carries its OWN reason code").
|
|
288
|
+
metadata: { isObjCImport: true, isObjCModuleImport: true, reasonCode: "clang-module-name-not-resolved" }
|
|
289
|
+
});
|
|
290
|
+
relationships.push({
|
|
291
|
+
source: "root",
|
|
292
|
+
target: modName,
|
|
293
|
+
type: ASTEngine.RelationshipType.Imports,
|
|
294
|
+
confidence: 1,
|
|
295
|
+
line_number: this.nodeLine(nameNode ?? node),
|
|
296
|
+
metadata: {
|
|
297
|
+
relationshipType: "module_import",
|
|
298
|
+
isObjCImport: true,
|
|
299
|
+
isObjCModuleImport: true,
|
|
300
|
+
isImport: true,
|
|
301
|
+
isSystem: false,
|
|
302
|
+
module: modName,
|
|
303
|
+
fileLevel: true
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (node.type === "class_interface" || node.type === "class_implementation") {
|
|
309
|
+
const identNodes = [];
|
|
310
|
+
for (let ci = 0; ci < node.namedChildCount; ci++) {
|
|
311
|
+
const cn = node.namedChild(ci);
|
|
312
|
+
if (cn && cn.type === "identifier") identNodes.push(cn.text);
|
|
313
|
+
}
|
|
314
|
+
const nameNode = this.findChild(node, (n) => n.type === "identifier" || n.type === "type_identifier");
|
|
315
|
+
if (nameNode) {
|
|
316
|
+
nextActiveScope = nameNode.text;
|
|
317
|
+
nextOwner = nameNode.text;
|
|
318
|
+
let hasParen = false;
|
|
319
|
+
for (let ci = 0; ci < node.childCount; ci++) {
|
|
320
|
+
const c = node.child(ci);
|
|
321
|
+
if (c && !c.isNamed && c.type === "(") {
|
|
322
|
+
hasParen = true;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const isCategory = hasParen;
|
|
327
|
+
if (isCategory) {
|
|
328
|
+
symbols.push({
|
|
329
|
+
name: nameNode.text,
|
|
330
|
+
kind: ASTEngine.SymbolKind.Extension,
|
|
331
|
+
range: this.getRange(node),
|
|
332
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
333
|
+
metadata: { objcSpecific: true, isCategory: true, categoryName: identNodes[1] }
|
|
334
|
+
});
|
|
335
|
+
for (const p of this.objcInheritanceLists(node).protocols) {
|
|
336
|
+
relationships.push({ source: nameNode.text, target: p.name, type: ASTEngine.RelationshipType.Implements, confidence: 1, line_number: this.nodeLine(p.node), metadata: { objcSpecific: true, isProtocol: true, isCategoryProtocol: true } });
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
const { superClass: superName, superNode, protocols: protocolList } = this.objcInheritanceLists(node);
|
|
340
|
+
const protocols = protocolList.map((p) => p.name);
|
|
341
|
+
if (superName) classSuper.set(nameNode.text, superName);
|
|
342
|
+
if (superName) {
|
|
343
|
+
relationships.push({ source: nameNode.text, target: superName, type: ASTEngine.RelationshipType.Extends, confidence: 1, line_number: this.nodeLine(superNode ?? nameNode), metadata: { objcSpecific: true } });
|
|
344
|
+
}
|
|
345
|
+
for (const p of protocolList) {
|
|
346
|
+
relationships.push({ source: nameNode.text, target: p.name, type: ASTEngine.RelationshipType.Implements, confidence: 1, line_number: this.nodeLine(p.node ?? nameNode), metadata: { objcSpecific: true, isProtocol: true } });
|
|
347
|
+
}
|
|
348
|
+
symbols.push({
|
|
349
|
+
name: nameNode.text,
|
|
350
|
+
kind: ASTEngine.SymbolKind.Class,
|
|
351
|
+
range: this.getRange(node),
|
|
352
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
353
|
+
metadata: { objcSpecific: true, type: node.type, superClass: superName, protocols }
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} else if (node.type === "protocol_declaration") {
|
|
358
|
+
const nameNode = this.findChild(node, (n) => n.type === "identifier");
|
|
359
|
+
if (nameNode) {
|
|
360
|
+
nextActiveScope = nameNode.text;
|
|
361
|
+
symbols.push({
|
|
362
|
+
name: nameNode.text,
|
|
363
|
+
kind: ASTEngine.SymbolKind.Interface,
|
|
364
|
+
range: this.getRange(node),
|
|
365
|
+
metadata: { objcSpecific: true, isProtocol: true }
|
|
366
|
+
});
|
|
367
|
+
const refList = this.findChild(node, (n) => n.type === "protocol_reference_list");
|
|
368
|
+
if (refList) {
|
|
369
|
+
for (let ci = 0; ci < refList.namedChildCount; ci++) {
|
|
370
|
+
const cn = refList.namedChild(ci);
|
|
371
|
+
if (cn && cn.type === "identifier") {
|
|
372
|
+
relationships.push({ source: nameNode.text, target: cn.text, type: ASTEngine.RelationshipType.Extends, confidence: 1, line_number: this.nodeLine(cn), metadata: { objcSpecific: true, isProtocolInheritance: true } });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else if (node.type === "method_declaration" || node.type === "method_definition") {
|
|
378
|
+
const sel = this.fullMethodSelector(node);
|
|
379
|
+
if (sel) {
|
|
380
|
+
nextActiveScope = sel;
|
|
381
|
+
if (node.type === "method_definition") {
|
|
382
|
+
valueFrame = /* @__PURE__ */ new Set();
|
|
383
|
+
nextBindable = true;
|
|
384
|
+
}
|
|
385
|
+
const isClassMethod = this.isClassMethodToken(node);
|
|
386
|
+
symbols.push({
|
|
387
|
+
name: sel,
|
|
388
|
+
kind: ASTEngine.SymbolKind.Method,
|
|
389
|
+
range: this.getRange(node),
|
|
390
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
391
|
+
complexity: this.calculateCyclomaticComplexity(node),
|
|
392
|
+
metadata: { objcSpecific: true, isClassMethod, isInstanceMethod: !isClassMethod, selector: sel }
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
} else if (node.type === "function_definition") {
|
|
396
|
+
const nameNode = this.findFunctionName(node);
|
|
397
|
+
if (nameNode) {
|
|
398
|
+
for (let si = 0; si < node.namedChildCount; si++) {
|
|
399
|
+
const sc = node.namedChild(si);
|
|
400
|
+
if (sc && sc.type === "storage_class_specifier" && sc.text.trim() === "static") {
|
|
401
|
+
internalLinkage.add(nameNode.text);
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
nextActiveScope = nameNode.text;
|
|
406
|
+
valueFrame = /* @__PURE__ */ new Set();
|
|
407
|
+
nextOwner = "";
|
|
408
|
+
nextBindable = true;
|
|
409
|
+
globals.add(nameNode.text);
|
|
410
|
+
symbols.push({
|
|
411
|
+
name: nameNode.text,
|
|
412
|
+
kind: ASTEngine.SymbolKind.Function,
|
|
413
|
+
range: this.getRange(node),
|
|
414
|
+
complexity: this.calculateCyclomaticComplexity(node),
|
|
415
|
+
metadata: { objcSpecific: true }
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
} else if (node.type === "property_declaration") {
|
|
419
|
+
const nameNode = this.findDeclaredName(node);
|
|
420
|
+
if (nameNode) {
|
|
421
|
+
symbols.push({
|
|
422
|
+
name: nameNode,
|
|
423
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
424
|
+
range: this.getRange(node),
|
|
425
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
426
|
+
metadata: { objcSpecific: true }
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
} else if (node.type === "instance_variable") {
|
|
430
|
+
const nameNode = this.findDeclaredName(node);
|
|
431
|
+
if (nameNode) {
|
|
432
|
+
symbols.push({
|
|
433
|
+
name: nameNode,
|
|
434
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
435
|
+
range: this.getRange(node),
|
|
436
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
437
|
+
metadata: { objcSpecific: true, isInstanceVariable: true }
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
} else if (node.type === "struct_specifier" || node.type === "union_specifier") {
|
|
441
|
+
const sname = this.findChild(node, (n) => n.type === "type_identifier");
|
|
442
|
+
const tname = sname ? sname.text : this.typedefName(node);
|
|
443
|
+
const defines = !!this.findChild(node, (n) => n.type === "field_declaration_list") || node.parent != null && node.parent.type === "type_definition";
|
|
444
|
+
if (tname && defines) {
|
|
445
|
+
symbols.push({
|
|
446
|
+
name: tname,
|
|
447
|
+
kind: ASTEngine.SymbolKind.Struct,
|
|
448
|
+
range: this.getRange(node),
|
|
449
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
450
|
+
metadata: { objcSpecific: true, isCStruct: true }
|
|
451
|
+
});
|
|
452
|
+
const flist = this.findChild(node, (n) => n.type === "field_declaration_list");
|
|
453
|
+
if (flist) {
|
|
454
|
+
for (let fi = 0; fi < flist.namedChildCount; fi++) {
|
|
455
|
+
const fd = flist.namedChild(fi);
|
|
456
|
+
if (!fd || fd.type !== "field_declaration") continue;
|
|
457
|
+
const fname = this.deepFind(fd, (n) => n.type === "field_identifier");
|
|
458
|
+
if (fname && fname.text.trim()) {
|
|
459
|
+
symbols.push({
|
|
460
|
+
name: fname.text,
|
|
461
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
462
|
+
range: this.getRange(fd),
|
|
463
|
+
ownerName: tname,
|
|
464
|
+
metadata: { objcSpecific: true, isStructField: true }
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} else if (node.type === "enum_specifier") {
|
|
471
|
+
const ename = this.findChild(node, (n) => n.type === "type_identifier");
|
|
472
|
+
const macroArgs = this.findChild(node, (n) => n.type === "macro_enum_arguments");
|
|
473
|
+
let macroName = null;
|
|
474
|
+
if (macroArgs) {
|
|
475
|
+
for (let i = macroArgs.namedChildCount - 1; i >= 0; i--) {
|
|
476
|
+
const c = macroArgs.namedChild(i);
|
|
477
|
+
if (c && c.text && c.text.trim()) {
|
|
478
|
+
macroName = c.text.trim();
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const tname = macroName ?? (ename ? ename.text : this.typedefName(node));
|
|
484
|
+
const defines = !!this.findChild(node, (n) => n.type === "enumerator_list") || node.parent != null && node.parent.type === "type_definition";
|
|
485
|
+
if (tname && defines) {
|
|
486
|
+
symbols.push({
|
|
487
|
+
name: tname,
|
|
488
|
+
kind: ASTEngine.SymbolKind.Enum,
|
|
489
|
+
range: this.getRange(node),
|
|
490
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
491
|
+
metadata: { objcSpecific: true, isCEnum: true }
|
|
492
|
+
});
|
|
493
|
+
const elist = this.findChild(node, (n) => n.type === "enumerator_list");
|
|
494
|
+
if (elist) {
|
|
495
|
+
for (let ei = 0; ei < elist.namedChildCount; ei++) {
|
|
496
|
+
const en = elist.namedChild(ei);
|
|
497
|
+
if (!en || en.type !== "enumerator") continue;
|
|
498
|
+
const mname = this.findChild(en, (n) => n.type === "identifier");
|
|
499
|
+
if (mname && mname.text.trim()) {
|
|
500
|
+
symbols.push({
|
|
501
|
+
name: mname.text,
|
|
502
|
+
kind: ASTEngine.SymbolKind.EnumMember,
|
|
503
|
+
range: this.getRange(en),
|
|
504
|
+
ownerName: tname,
|
|
505
|
+
metadata: { objcSpecific: true }
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
} else if (node.type === "type_definition") {
|
|
512
|
+
let alias = null;
|
|
513
|
+
for (let ti = 0; ti < node.namedChildCount; ti++) {
|
|
514
|
+
const tc = node.namedChild(ti);
|
|
515
|
+
if (tc && (tc.type === "type_identifier" || tc.type === "identifier")) alias = tc;
|
|
516
|
+
}
|
|
517
|
+
const hasAggregate = !!this.findChild(node, (n) => n.type === "struct_specifier" || n.type === "union_specifier" || n.type === "enum_specifier");
|
|
518
|
+
if (alias && !hasAggregate) {
|
|
519
|
+
symbols.push({
|
|
520
|
+
name: alias.text,
|
|
521
|
+
kind: ASTEngine.SymbolKind.Type,
|
|
522
|
+
range: this.getRange(node),
|
|
523
|
+
ownerName: activeScope !== "global" ? activeScope : void 0,
|
|
524
|
+
metadata: { objcSpecific: true, isTypedef: true }
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
} else if (node.type === "block_literal") {
|
|
528
|
+
if (nextBindable) blockFrameStack.push(/* @__PURE__ */ new Set());
|
|
529
|
+
} else if (node.type === "declaration" && activeScope === "global") {
|
|
530
|
+
if (!isUnterminatedDeclaration(node) && !hasParseError(node)) {
|
|
531
|
+
let exportMarker = null;
|
|
532
|
+
let storageStatic = false;
|
|
533
|
+
for (let si = 0; si < node.namedChildCount; si++) {
|
|
534
|
+
const sc = node.namedChild(si);
|
|
535
|
+
if (!sc || sc.type !== "storage_class_specifier") continue;
|
|
536
|
+
const st = sc.text.trim();
|
|
537
|
+
if (st === "static") storageStatic = true;
|
|
538
|
+
if (!exportMarker && OBJC_EXPORT_MARKERS.has(st)) exportMarker = st;
|
|
539
|
+
}
|
|
540
|
+
for (let di = 0; di < node.namedChildCount; di++) {
|
|
541
|
+
const dc = node.namedChild(di);
|
|
542
|
+
if (!dc) continue;
|
|
543
|
+
let dname = null;
|
|
544
|
+
const isPrototype = dc.type === "function_declarator" || (dc.type === "pointer_declarator" || dc.type === "array_declarator" || dc.type === "parenthesized_declarator" || dc.type === "attributed_declarator") && this.declaratorChainHasFunctionDeclarator(dc);
|
|
545
|
+
if (dc.type === "init_declarator") {
|
|
546
|
+
dname = this.declaratorName(dc);
|
|
547
|
+
} else if (dc.type === "identifier") {
|
|
548
|
+
dname = dc.text;
|
|
549
|
+
} else if (isPrototype || dc.type === "pointer_declarator" || dc.type === "array_declarator" || dc.type === "parenthesized_declarator" || dc.type === "attributed_declarator") {
|
|
550
|
+
dname = this.declaratorName(dc);
|
|
551
|
+
}
|
|
552
|
+
if (dname) {
|
|
553
|
+
symbols.push({
|
|
554
|
+
name: dname,
|
|
555
|
+
kind: isPrototype ? ASTEngine.SymbolKind.Function : ASTEngine.SymbolKind.Variable,
|
|
556
|
+
range: this.getRange(node),
|
|
557
|
+
metadata: isPrototype ? { objcSpecific: true, isCFunctionPrototype: true } : { objcSpecific: true }
|
|
558
|
+
});
|
|
559
|
+
globals.add(dname);
|
|
560
|
+
if (storageStatic) internalLinkage.add(dname);
|
|
561
|
+
if (exportMarker) {
|
|
562
|
+
exportMarkers.set(dname, exportMarker);
|
|
563
|
+
exportLineByName.set(dname, this.nodeLine(node));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
} else if (node.type === "declaration" && nextBindable) {
|
|
569
|
+
for (let di = 0; di < node.namedChildCount; di++) {
|
|
570
|
+
const dc = node.namedChild(di);
|
|
571
|
+
if (!dc) continue;
|
|
572
|
+
if (dc.type === "init_declarator") {
|
|
573
|
+
const dname = this.declaratorName(dc);
|
|
574
|
+
if (dname) bindValue(dname);
|
|
575
|
+
} else if (dc.type === "identifier") {
|
|
576
|
+
bindValue(dc.text);
|
|
577
|
+
} else if (dc.type === "pointer_declarator" || dc.type === "array_declarator" || dc.type === "parenthesized_declarator" || dc.type === "attributed_declarator") {
|
|
578
|
+
const dname = this.declaratorName(dc);
|
|
579
|
+
if (dname) bindValue(dname);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} else if ((node.type === "method_parameter" || node.type === "parameter_declaration") && nextBindable) {
|
|
583
|
+
const pname = this.declaratorName(node);
|
|
584
|
+
if (pname) bindValue(pname);
|
|
585
|
+
} else if (node.type === "for_statement" && nextBindable) {
|
|
586
|
+
const cc = node.childCount;
|
|
587
|
+
for (let fi = 0; fi < cc; fi++) {
|
|
588
|
+
const fc = node.child(fi);
|
|
589
|
+
if (fc && !fc.isNamed && fc.type === "in" && fi > 0) {
|
|
590
|
+
for (let fj = fi - 1; fj >= 0; fj--) {
|
|
591
|
+
const pc = node.child(fj);
|
|
592
|
+
if (pc && pc.isNamed) {
|
|
593
|
+
if (pc.type === "identifier") bindValue(pc.text);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
} else if (node.type === "message_expression") {
|
|
601
|
+
const msgSel = this.fullMessageSelector(node);
|
|
602
|
+
if (msgSel && activeScope !== "global" && isInBody) {
|
|
603
|
+
const firstKw = msgSel.split(":")[0];
|
|
604
|
+
if (firstKw === "alloc" || firstKw === "new") {
|
|
605
|
+
let receiverLeaf = null;
|
|
606
|
+
let recvTargetNode = null;
|
|
607
|
+
const receiver = node.namedChild(0);
|
|
608
|
+
if (receiver) {
|
|
609
|
+
if (receiver.type === "identifier" || receiver.type === "type_identifier") {
|
|
610
|
+
const keyword = receiver.text === "self" || receiver.text === "super" ? receiver.text : null;
|
|
611
|
+
if (keyword) {
|
|
612
|
+
const resolved = keyword === "self" ? owner : classSuper.get(owner);
|
|
613
|
+
if (resolved && resolved !== "global") {
|
|
614
|
+
receiverLeaf = resolved;
|
|
615
|
+
recvTargetNode = receiver;
|
|
616
|
+
}
|
|
617
|
+
} else {
|
|
618
|
+
receiverLeaf = receiver.text;
|
|
619
|
+
recvTargetNode = receiver;
|
|
620
|
+
}
|
|
621
|
+
} else if (receiver.type === "message_expression") {
|
|
622
|
+
const innerSel = this.findChild(receiver, (n) => n.type === "identifier");
|
|
623
|
+
if (innerSel && (innerSel.text === "alloc" || innerSel.text === "new")) {
|
|
624
|
+
const innerRecv = receiver.namedChild(0);
|
|
625
|
+
if (innerRecv && (innerRecv.type === "identifier" || innerRecv.type === "type_identifier")) {
|
|
626
|
+
receiverLeaf = innerRecv.text;
|
|
627
|
+
recvTargetNode = innerRecv;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (receiverLeaf) {
|
|
633
|
+
relationships.push({ source: activeScope, target: receiverLeaf, type: ASTEngine.RelationshipType.Instantiates, confidence: 0.9, line_number: this.nodeLine(recvTargetNode ?? node), metadata: { isObjCAlloc: true, reasonCode: "allocator-receiver-not-cross-file-checked" } });
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
relationships.push({
|
|
637
|
+
source: activeScope,
|
|
638
|
+
target: msgSel,
|
|
639
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
640
|
+
confidence: 0.95,
|
|
641
|
+
line_number: this.nodeLine(this.fullMessageSelectorNode(node) ?? node),
|
|
642
|
+
metadata: { isMessageExpression: true, reasonCode: "dynamic-dispatch-selector" }
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
const msgKids = [];
|
|
646
|
+
for (let mi = 0; mi < node.childCount; mi++) {
|
|
647
|
+
const mc = node.child(mi);
|
|
648
|
+
if (!mc || !mc.isNamed || mc.type !== "identifier") continue;
|
|
649
|
+
const nxt = mi + 1 < node.childCount ? node.child(mi + 1) : null;
|
|
650
|
+
msgKids.push({ n: mc, isKeyword: !!(nxt && !nxt.isNamed && nxt.type === ":") });
|
|
651
|
+
}
|
|
652
|
+
const recvNode = node.namedChild(0);
|
|
653
|
+
const hasKeyword = msgKids.some((k) => k.isKeyword);
|
|
654
|
+
let lastNonRecv = -1;
|
|
655
|
+
if (!hasKeyword) {
|
|
656
|
+
for (let ki = msgKids.length - 1; ki >= 0; ki--) {
|
|
657
|
+
if (!this.sameNode(msgKids[ki].n, recvNode)) {
|
|
658
|
+
lastNonRecv = ki;
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
for (let ki = 0; ki < msgKids.length; ki++) {
|
|
664
|
+
const kk = msgKids[ki];
|
|
665
|
+
if (kk.isKeyword || ki === lastNonRecv) continue;
|
|
666
|
+
const ktext = kk.n.text.trim();
|
|
667
|
+
if (!ktext || BARE_READ_SKIP.has(ktext)) continue;
|
|
668
|
+
if (this.sameNode(kk.n, recvNode)) {
|
|
669
|
+
if (ktext === "self" || ktext === "super") continue;
|
|
670
|
+
}
|
|
671
|
+
if (isBoundValue(ktext, nextOwner)) {
|
|
672
|
+
relationships.push({
|
|
673
|
+
source: activeScope,
|
|
674
|
+
target: ktext,
|
|
675
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
676
|
+
confidence: 0.9,
|
|
677
|
+
line_number: this.nodeLine(kk.n),
|
|
678
|
+
metadata: { isBareRead: true, isMessageArgument: !this.sameNode(kk.n, recvNode), reasonCode: "value-read-by-scope-inference" }
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
} else if (node.type === "call_expression") {
|
|
684
|
+
const funcNode = node.childForFieldName("function") ?? this.findChild(node, (n) => n.type === "identifier");
|
|
685
|
+
const inAtDirective = (() => {
|
|
686
|
+
let p = node.parent;
|
|
687
|
+
let g = 0;
|
|
688
|
+
while (p && g++ < 3) {
|
|
689
|
+
if (p.type === "at_expression") return true;
|
|
690
|
+
if (p.type === "message_expression" || p.type === "call_expression") return false;
|
|
691
|
+
p = p.parent;
|
|
692
|
+
}
|
|
693
|
+
return false;
|
|
694
|
+
})();
|
|
695
|
+
if (funcNode && activeScope !== "global" && isInBody && !(inAtDirective && funcNode.text === "protocol")) {
|
|
696
|
+
if (funcNode.type === "field_expression") {
|
|
697
|
+
const fieldNode = funcNode.childForFieldName("field") ?? this.findChild(funcNode, (n) => n.type === "field_identifier");
|
|
698
|
+
if (fieldNode && fieldNode.text.trim()) {
|
|
699
|
+
relationships.push({
|
|
700
|
+
source: activeScope,
|
|
701
|
+
target: fieldNode.text,
|
|
702
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
703
|
+
confidence: 0.95,
|
|
704
|
+
line_number: this.nodeLine(fieldNode),
|
|
705
|
+
metadata: { isFieldInvocation: true, reasonCode: "field-callee-kind-not-determined" }
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
relationships.push({
|
|
710
|
+
source: activeScope,
|
|
711
|
+
target: funcNode.text,
|
|
712
|
+
type: ASTEngine.RelationshipType.Calls,
|
|
713
|
+
confidence: 0.95,
|
|
714
|
+
line_number: this.nodeLine(funcNode),
|
|
715
|
+
metadata: { isCFunctionCall: true }
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
} else if (node.type === "field_expression" && activeScope !== "global" && isInBody) {
|
|
720
|
+
const field = node.childForFieldName("field") ?? this.findChild(node, (n) => n.type === "field_identifier");
|
|
721
|
+
if (field && field.text.trim()) {
|
|
722
|
+
if (parentType !== "assignment_expression") {
|
|
723
|
+
relationships.push({
|
|
724
|
+
source: activeScope,
|
|
725
|
+
target: field.text,
|
|
726
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
727
|
+
confidence: 0.9,
|
|
728
|
+
line_number: this.nodeLine(field),
|
|
729
|
+
// Honesty §4.1: sub-1.0 rows carry their own machine-readable code, and
|
|
730
|
+
// `uses` has ONE — the read is exact (the field name is in the source) and
|
|
731
|
+
// what the row infers is WHICH binding the name denotes, resolved against
|
|
732
|
+
// the enclosing class's members. The `calls` rows for the same node keep a
|
|
733
|
+
// different word (`field-callee-kind-not-determined`), because the call is
|
|
734
|
+
// about the callable's KIND and the read is about the binding
|
|
735
|
+
// (objc/generics-and-reason-codes pins the family, objc/field-invocation
|
|
736
|
+
// pins that the two labels on one node are told apart by their flags).
|
|
737
|
+
metadata: { isFieldRead: true, relationshipType: "field_access", reasonCode: "value-read-by-scope-inference" }
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
} else if (node.type === "identifier" && nextBindable && activeScope !== "global") {
|
|
742
|
+
const text = node.text.trim();
|
|
743
|
+
if (text && !BARE_READ_SKIP.has(text)) {
|
|
744
|
+
const parent = node.parent;
|
|
745
|
+
const ptype = parent ? parent.type : "";
|
|
746
|
+
let isName = false;
|
|
747
|
+
if (ptype === "method_declaration" || ptype === "method_definition" || ptype === "method_parameter" || ptype === "parameter_declaration" || ptype === "property_implementation" || ptype === "selector_expression" || ptype === "function_declarator" || ptype === "message_expression" || ptype === "declaration") {
|
|
748
|
+
isName = true;
|
|
749
|
+
} else if (ptype === "call_expression" && parent && parent.namedChild(0) && this.sameNode(parent.namedChild(0), node)) {
|
|
750
|
+
isName = true;
|
|
751
|
+
} else if (ptype === "assignment_expression" && parent && parent.namedChild(0) && this.spanContains(parent.namedChild(0), node)) {
|
|
752
|
+
isName = true;
|
|
753
|
+
} else if (ptype === "for_statement" && parent && this.isForInVariable(parent, node)) {
|
|
754
|
+
isName = true;
|
|
755
|
+
} else if (this.isDeclaratorName(node) || this.isParameterDeclaredName(node)) {
|
|
756
|
+
isName = true;
|
|
757
|
+
}
|
|
758
|
+
if (!isName && isBoundValue(text, nextOwner)) {
|
|
759
|
+
relationships.push({
|
|
760
|
+
source: activeScope,
|
|
761
|
+
target: text,
|
|
762
|
+
type: ASTEngine.RelationshipType.Uses,
|
|
763
|
+
confidence: 0.9,
|
|
764
|
+
line_number: this.nodeLine(node),
|
|
765
|
+
// Honesty §4.1: the read is exact — the identifier is in the source — and what
|
|
766
|
+
// the row INFERS is which binding the name denotes (parameter, body local,
|
|
767
|
+
// same-file ivar/property, file global). That is the whole of the doubt, so it
|
|
768
|
+
// is the whole of the code (objc/generics-and-reason-codes pins the word).
|
|
769
|
+
metadata: { isBareRead: true, reasonCode: "value-read-by-scope-inference" }
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const childCount = node.namedChildCount;
|
|
775
|
+
nextIsInBody = nextIsInBody || node.type === "method_definition" || node.type === "method_declaration" || node.type === "function_definition" || node.type === "block_literal";
|
|
776
|
+
if (node.type === "block_literal" && nextBindable) {
|
|
777
|
+
walkStack.push({ node, isExit: true, activeScope: nextActiveScope, parentType: node.type, isInBody: nextIsInBody, owner: nextOwner, bindable: nextBindable });
|
|
778
|
+
}
|
|
779
|
+
for (let i = childCount - 1; i >= 0; i--) {
|
|
780
|
+
const child = node.namedChild(i);
|
|
781
|
+
if (child) {
|
|
782
|
+
walkStack.push({ node: child, isExit: false, activeScope: nextActiveScope, parentType: node.type, isInBody: nextIsInBody, owner: nextOwner, bindable: nextBindable });
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const uniqueSymbolsMap = /* @__PURE__ */ new Map();
|
|
787
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
788
|
+
const s = symbols[i];
|
|
789
|
+
const catKey = s.kind === ASTEngine.SymbolKind.Extension && s.metadata?.isCategory ? `:${s.metadata?.categoryName ?? ""}` : "";
|
|
790
|
+
uniqueSymbolsMap.set(`${s.kind}:${s.ownerName || ""}:${s.name}${catKey}`, s);
|
|
791
|
+
}
|
|
792
|
+
const uniqueSymbols = Array.from(uniqueSymbolsMap.values());
|
|
793
|
+
const uniqueRelMap = /* @__PURE__ */ new Map();
|
|
794
|
+
for (let i = 0; i < relationships.length; i++) {
|
|
795
|
+
const rk = `${relationships[i].type}|${relationships[i].source}->${relationships[i].target}`;
|
|
796
|
+
const prev = uniqueRelMap.get(rk);
|
|
797
|
+
if (prev) {
|
|
798
|
+
const pm = prev.metadata ?? {};
|
|
799
|
+
const nm = relationships[i].metadata ?? {};
|
|
800
|
+
if (nm.isFieldRead) pm.isFieldRead = true;
|
|
801
|
+
if (nm.isBareRead) pm.isBareRead = true;
|
|
802
|
+
prev.metadata = pm;
|
|
803
|
+
if (prev.line_number === void 0 && relationships[i].line_number !== void 0) {
|
|
804
|
+
prev.line_number = relationships[i].line_number;
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
uniqueRelMap.set(rk, relationships[i]);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
const uniqueRelationships = Array.from(uniqueRelMap.values());
|
|
811
|
+
const relKeySet = new Set(uniqueRelMap.keys());
|
|
812
|
+
for (const s of uniqueSymbols) {
|
|
813
|
+
if (s.ownerName || s.scope) continue;
|
|
814
|
+
if (s.kind === ASTEngine.SymbolKind.Extension) continue;
|
|
815
|
+
if (!OBJC_LINKAGE_UNIT_KINDS.has(s.kind)) continue;
|
|
816
|
+
if (internalLinkage.has(s.name)) continue;
|
|
817
|
+
const key = `${ASTEngine.RelationshipType.Exports}|root->${s.name}`;
|
|
818
|
+
if (relKeySet.has(key)) continue;
|
|
819
|
+
relKeySet.add(key);
|
|
820
|
+
const marker = exportMarkers.get(s.name);
|
|
821
|
+
uniqueRelationships.push({
|
|
822
|
+
source: "root",
|
|
823
|
+
target: s.name,
|
|
824
|
+
type: ASTEngine.RelationshipType.Exports,
|
|
825
|
+
confidence: 1,
|
|
826
|
+
...exportLineByName.has(s.name) ? { line_number: exportLineByName.get(s.name) } : s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
827
|
+
metadata: {
|
|
828
|
+
relationshipType: "module_export",
|
|
829
|
+
isExport: true,
|
|
830
|
+
isObjCExport: true,
|
|
831
|
+
fileLevel: true,
|
|
832
|
+
...marker ? { exportMarker: marker } : {}
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
for (const s of uniqueSymbols) {
|
|
837
|
+
const _owner = s.ownerName || s.scope;
|
|
838
|
+
if (_owner) {
|
|
839
|
+
const key = `${ASTEngine.RelationshipType.Contains}|${_owner}->${s.name}`;
|
|
840
|
+
if (!relKeySet.has(key)) {
|
|
841
|
+
relKeySet.add(key);
|
|
842
|
+
uniqueRelationships.push({
|
|
843
|
+
source: _owner,
|
|
844
|
+
target: s.name,
|
|
845
|
+
type: ASTEngine.RelationshipType.Contains,
|
|
846
|
+
confidence: 1,
|
|
847
|
+
// M59.A7: the line of the name this edge NAMES — the member's own declared
|
|
848
|
+
// name line, taken from the symbol's range (the symbol and the edge come from
|
|
849
|
+
// one selection: the same node named both).
|
|
850
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
851
|
+
metadata: { isContained: true }
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const classNameSet = /* @__PURE__ */ new Set();
|
|
857
|
+
for (const s of uniqueSymbols) {
|
|
858
|
+
if (s.kind === ASTEngine.SymbolKind.Class) classNameSet.add(s.name);
|
|
859
|
+
}
|
|
860
|
+
const baseByClass = /* @__PURE__ */ new Map();
|
|
861
|
+
for (const r of uniqueRelationships) {
|
|
862
|
+
if (r.type === ASTEngine.RelationshipType.Extends && classNameSet.has(String(r.source)) && !r.metadata?.isProtocolInheritance) {
|
|
863
|
+
const t = String(r.target);
|
|
864
|
+
if (t && t !== "NSObject" && t !== "NSProxy") baseByClass.set(String(r.source), t);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const methodsByOwner = /* @__PURE__ */ new Map();
|
|
868
|
+
for (const s of uniqueSymbols) {
|
|
869
|
+
if (s.kind === ASTEngine.SymbolKind.Method && s.ownerName) {
|
|
870
|
+
let names = methodsByOwner.get(s.ownerName);
|
|
871
|
+
if (!names) {
|
|
872
|
+
names = /* @__PURE__ */ new Set();
|
|
873
|
+
methodsByOwner.set(s.ownerName, names);
|
|
874
|
+
}
|
|
875
|
+
names.add(s.name);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
for (const s of uniqueSymbols) {
|
|
879
|
+
if (s.kind !== ASTEngine.SymbolKind.Method || !s.ownerName) continue;
|
|
880
|
+
const base = baseByClass.get(s.ownerName);
|
|
881
|
+
if (!base) continue;
|
|
882
|
+
const inFileBaseMethods = classNameSet.has(base) ? methodsByOwner.get(base) : void 0;
|
|
883
|
+
if (inFileBaseMethods?.has(s.name)) {
|
|
884
|
+
const key = `${ASTEngine.RelationshipType.Overrides}|${s.ownerName}.${s.name}->${base}.${s.name}`;
|
|
885
|
+
if (!relKeySet.has(key)) {
|
|
886
|
+
relKeySet.add(key);
|
|
887
|
+
uniqueRelationships.push({
|
|
888
|
+
source: `${s.ownerName}.${s.name}`,
|
|
889
|
+
target: `${base}.${s.name}`,
|
|
890
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
891
|
+
confidence: 0.95,
|
|
892
|
+
// M59.A7: the OVERRIDING member's own declaration line (the base's copy lives
|
|
893
|
+
// in another translation unit, and the row is written here).
|
|
894
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
895
|
+
// The VERIFIED branch names its own evidence: the superclass is declared in THIS
|
|
896
|
+
// file and declares the same full selector, so this row is a verification. The
|
|
897
|
+
// guessed branch below keeps `first-parent-guess` — one code for both would call
|
|
898
|
+
// a verification a guess (objc/selector-attributes pins the pair).
|
|
899
|
+
metadata: { isOverride: true, reasonCode: "verified-base-declared-in-file" }
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
} else if (!classNameSet.has(base)) {
|
|
903
|
+
const key = `${ASTEngine.RelationshipType.Overrides}|${s.ownerName}.${s.name}->${base}.${s.name}`;
|
|
904
|
+
if (!relKeySet.has(key)) {
|
|
905
|
+
relKeySet.add(key);
|
|
906
|
+
uniqueRelationships.push({
|
|
907
|
+
source: `${s.ownerName}.${s.name}`,
|
|
908
|
+
target: `${base}.${s.name}`,
|
|
909
|
+
type: ASTEngine.RelationshipType.Overrides,
|
|
910
|
+
confidence: 0.5,
|
|
911
|
+
...s.range?.startLine ? { line_number: s.range.startLine } : {},
|
|
912
|
+
metadata: { isOverride: true, reasonCode: "first-parent-guess" }
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const fileFunctions = /* @__PURE__ */ new Set();
|
|
918
|
+
for (const s of uniqueSymbols) {
|
|
919
|
+
if (s.kind === ASTEngine.SymbolKind.Function) fileFunctions.add(s.name);
|
|
920
|
+
}
|
|
921
|
+
for (const r of uniqueRelationships) {
|
|
922
|
+
if (r.type !== ASTEngine.RelationshipType.Calls) continue;
|
|
923
|
+
const md = r.metadata ?? {};
|
|
924
|
+
if (!md.isCFunctionCall || md.reasonCode) continue;
|
|
925
|
+
if (!fileFunctions.has(String(r.target))) md.reasonCode = "callee-not-resolved-in-unit";
|
|
926
|
+
}
|
|
927
|
+
const publishedNames = /* @__PURE__ */ new Set();
|
|
928
|
+
for (const s of uniqueSymbols) if (s.name) publishedNames.add(s.name);
|
|
929
|
+
for (const r of uniqueRelationships) {
|
|
930
|
+
if (r.type !== ASTEngine.RelationshipType.Uses) continue;
|
|
931
|
+
const md = r.metadata ?? {};
|
|
932
|
+
if (md.isBareRead !== true || md.isFieldRead === true) continue;
|
|
933
|
+
const leaf = String(r.target ?? "");
|
|
934
|
+
if (!leaf || !r.source || publishedNames.has(leaf)) continue;
|
|
935
|
+
r.target = `${r.source}${BINDING_SEP}${leaf}`;
|
|
936
|
+
md.localBinding = true;
|
|
937
|
+
}
|
|
938
|
+
this.classifyEvidenceTier(uniqueRelationships);
|
|
939
|
+
{
|
|
940
|
+
const seenExportNames = /* @__PURE__ */ new Set();
|
|
941
|
+
for (let i = 0; i < uniqueSymbols.length; i++) {
|
|
942
|
+
const s = uniqueSymbols[i];
|
|
943
|
+
if (s.ownerName) continue;
|
|
944
|
+
if (s.kind === ASTEngine.SymbolKind.Extension) continue;
|
|
945
|
+
if (seenExportNames.has(s.name)) continue;
|
|
946
|
+
seenExportNames.add(s.name);
|
|
947
|
+
exports.push({ name: s.name, kind: s.kind, range: s.range, visibility: internalLinkage.has(s.name) ? "private" : "public" });
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const detectedFrameworks = [...frameworks];
|
|
951
|
+
detectedFrameworks.sort((a, b) => {
|
|
952
|
+
const ra = OBJC_FRAMEWORK_ORDER.indexOf(a);
|
|
953
|
+
const rb = OBJC_FRAMEWORK_ORDER.indexOf(b);
|
|
954
|
+
if (ra === -1 && rb === -1) return a < b ? -1 : a > b ? 1 : 0;
|
|
955
|
+
if (ra === -1) return 1;
|
|
956
|
+
if (rb === -1) return -1;
|
|
957
|
+
return ra - rb;
|
|
958
|
+
});
|
|
959
|
+
const frameworkPatterns = detectedFrameworks.map((f) => ({
|
|
960
|
+
framework: f,
|
|
961
|
+
patterns: OBJC_FRAMEWORK_PATTERNS[f] ?? ["usage"]
|
|
962
|
+
}));
|
|
963
|
+
return {
|
|
964
|
+
language: "objective-c",
|
|
965
|
+
symbols: uniqueSymbols,
|
|
966
|
+
relationships: uniqueRelationships,
|
|
967
|
+
imports,
|
|
968
|
+
exports,
|
|
969
|
+
errors,
|
|
970
|
+
metadata: {
|
|
971
|
+
complexity: uniqueSymbols.length,
|
|
972
|
+
linesOfCode: content.split("\n").length,
|
|
973
|
+
detectedFrameworks,
|
|
974
|
+
framework: detectedFrameworks.length > 0 ? detectedFrameworks[0] : void 0,
|
|
975
|
+
frameworkPatterns
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Evidence-tier classification — same rules as the swift/dart/csharp
|
|
981
|
+
* family (see swift wave #266 for the full rationale).
|
|
982
|
+
*/
|
|
983
|
+
classifyEvidenceTier(relationships) {
|
|
984
|
+
for (const r of relationships) {
|
|
985
|
+
const type = r.type ?? r.relationshipType ?? "";
|
|
986
|
+
const md = r.metadata ?? {};
|
|
987
|
+
if (type === "contains") {
|
|
988
|
+
md.evidenceClass = "structural";
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
if (type === "exports") {
|
|
992
|
+
md.evidenceClass = "structural";
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (type === "imports" || type === "references") {
|
|
996
|
+
md.evidenceClass = "import_based";
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (type === "part_of") {
|
|
1000
|
+
md.evidenceClass = "import_based";
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (type === "calls" || type === "uses") {
|
|
1004
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.85 ? "usage_contextual" : "inferred";
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (type === "instantiates" || type === "extends" || type === "implements") {
|
|
1008
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.9 ? "structural" : "inferred";
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (type === "overrides") {
|
|
1012
|
+
md.evidenceClass = (r.confidence ?? 0) >= 0.9 ? "structural" : "inferred";
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
md.evidenceClass = "usage_contextual";
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Per-symbol cyclomatic complexity (managed-OO family contract — csharp
|
|
1020
|
+
* #264 wave precedent). The objc grammar is a C superset, so the node set
|
|
1021
|
+
* mirrors the C analyzer's exactly: branch constructs + short-circuit
|
|
1022
|
+
* &&/|| operators only; base 1. Probe-verified against the pinned wasm
|
|
1023
|
+
* (do_statement confirmed present, 2026-08-30).
|
|
1024
|
+
*/
|
|
1025
|
+
calculateCyclomaticComplexity(node) {
|
|
1026
|
+
let complexity = 1;
|
|
1027
|
+
const complexityNodes = /* @__PURE__ */ new Set([
|
|
1028
|
+
"if_statement",
|
|
1029
|
+
"for_statement",
|
|
1030
|
+
"while_statement",
|
|
1031
|
+
"do_statement",
|
|
1032
|
+
"case_statement",
|
|
1033
|
+
"conditional_expression"
|
|
1034
|
+
]);
|
|
1035
|
+
const stack = [node];
|
|
1036
|
+
let guard = 0;
|
|
1037
|
+
while (stack.length > 0 && guard++ < 2e5) {
|
|
1038
|
+
const n = stack.pop();
|
|
1039
|
+
if (complexityNodes.has(n.type)) complexity++;
|
|
1040
|
+
else if (n.type === "binary_expression") {
|
|
1041
|
+
const op = n.childForFieldName("operator");
|
|
1042
|
+
if (op && (op.text === "&&" || op.text === "||")) complexity++;
|
|
1043
|
+
}
|
|
1044
|
+
for (let i = n.namedChildCount - 1; i >= 0; i--) {
|
|
1045
|
+
const c = n.namedChild(i);
|
|
1046
|
+
if (c) stack.push(c);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return complexity;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* OC-E helpers: bare-identifier read classification. web-tree-sitter
|
|
1053
|
+
* returns fresh wrappers per accessor call, so node identity is compared
|
|
1054
|
+
* by span (start/end offsets), never by reference.
|
|
1055
|
+
*/
|
|
1056
|
+
sameNode(a, b) {
|
|
1057
|
+
if (!a || !b) return false;
|
|
1058
|
+
return a.startIndex === b.startIndex && a.endIndex === b.endIndex;
|
|
1059
|
+
}
|
|
1060
|
+
spanContains(outer, inner) {
|
|
1061
|
+
if (!outer || !inner) return false;
|
|
1062
|
+
return outer.startIndex <= inner.startIndex && outer.endIndex >= inner.endIndex;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* True when the identifier is the DECLARED name of an init_declarator
|
|
1066
|
+
* (`int y = ...`, `int arr[10] = ...`, `Counter g = ...`): climb through
|
|
1067
|
+
* name-wrapping declarators (array/pointer/parenthesized); the name is
|
|
1068
|
+
* the declarator subtree under the init_declarator's FIRST named child.
|
|
1069
|
+
* Initializer values live outside that subtree and stay reads.
|
|
1070
|
+
*/
|
|
1071
|
+
isDeclaratorName(node) {
|
|
1072
|
+
let cur = node;
|
|
1073
|
+
let p = node.parent ?? null;
|
|
1074
|
+
while (p && DECLARATOR_WRAPPERS.has(p.type) && cur) {
|
|
1075
|
+
cur = p;
|
|
1076
|
+
p = p.parent ?? null;
|
|
1077
|
+
}
|
|
1078
|
+
if (!p || p.type !== "init_declarator" || !cur) return false;
|
|
1079
|
+
const head = p.namedChild(0);
|
|
1080
|
+
return !!head && this.spanContains(head, cur);
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* OC-E: true when the identifier is the DECLARED NAME of a parameter
|
|
1084
|
+
* (`void (^handler)(NSString *)`, `NSString *name`, `int n`). Structural, not
|
|
1085
|
+
* textual: climb the name-wrapping declarators and require that the node we
|
|
1086
|
+
* land on IS the enclosing parameter's own `declarator` field — a declarator
|
|
1087
|
+
* subtree holds no expressions, so nothing inside it can be a value read, and
|
|
1088
|
+
* OC-E's rule is that "declaration names" are names, never reads.
|
|
1089
|
+
*
|
|
1090
|
+
* The direct `identifier < parameter_declaration` shape was already covered by
|
|
1091
|
+
* the parent-type test. The BLOCK-POINTER parameter is the shape that leaked:
|
|
1092
|
+
* its name nests `identifier < block_pointer_declarator <
|
|
1093
|
+
* parenthesized_declarator < function_declarator < parameter_declaration`, so
|
|
1094
|
+
* neither the parent-type test nor isDeclaratorName (which needs an
|
|
1095
|
+
* init_declarator) could see it, and the declaration line was published as a
|
|
1096
|
+
* `uses` read. Measured before the fix (script/m14/tmp/oc-callee-uses.ts):
|
|
1097
|
+
* `void register_block(void (^handler)(NSString *)) { int x = 0; }` emitted
|
|
1098
|
+
* `uses register_block -> handler` with NO read anywhere in the file — while
|
|
1099
|
+
* the C analyzer, handed the same function-pointer parameter, emits nothing
|
|
1100
|
+
* (family parity), and the M12 fixture read it as a false positive against a
|
|
1101
|
+
* truth that only counts the call.
|
|
1102
|
+
*/
|
|
1103
|
+
isParameterDeclaredName(node) {
|
|
1104
|
+
let cur = node;
|
|
1105
|
+
let p = node.parent ?? null;
|
|
1106
|
+
while (p && DECLARATOR_WRAPPERS.has(p.type) && cur) {
|
|
1107
|
+
cur = p;
|
|
1108
|
+
p = p.parent ?? null;
|
|
1109
|
+
}
|
|
1110
|
+
if (!p || !cur) return false;
|
|
1111
|
+
if (p.type !== "parameter_declaration" && p.type !== "method_parameter") return false;
|
|
1112
|
+
const declared = p.childForFieldName("declarator");
|
|
1113
|
+
if (!declared) return p.type === "method_parameter";
|
|
1114
|
+
return this.spanContains(declared, cur);
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* True when the identifier is a for-in loop variable
|
|
1118
|
+
* (`for (id x in items)`): the named sibling directly preceding the
|
|
1119
|
+
* anonymous `in` token.
|
|
1120
|
+
*/
|
|
1121
|
+
isForInVariable(forNode, node) {
|
|
1122
|
+
const cc = forNode.childCount;
|
|
1123
|
+
for (let i = 0; i < cc; i++) {
|
|
1124
|
+
const c = forNode.child(i);
|
|
1125
|
+
if (c && !c.isNamed && c.type === "in" && i > 0) {
|
|
1126
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
1127
|
+
const pc = forNode.child(j);
|
|
1128
|
+
if (pc && pc.isNamed) return this.sameNode(pc, node);
|
|
1129
|
+
}
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* The bound name declared by an init_declarator, method_parameter, or
|
|
1137
|
+
* parameter_declaration: the declarator identifier (through wrappers for
|
|
1138
|
+
* arrays/pointers; first document-order identifier for parameters, where
|
|
1139
|
+
* type names are type_identifier and `id` is a typedefed_specifier).
|
|
1140
|
+
*/
|
|
1141
|
+
declaratorName(node) {
|
|
1142
|
+
if (node.type === "init_declarator") {
|
|
1143
|
+
const head = node.namedChild(0);
|
|
1144
|
+
if (!head) return null;
|
|
1145
|
+
const stack2 = [head];
|
|
1146
|
+
while (stack2.length > 0) {
|
|
1147
|
+
const c = stack2.pop();
|
|
1148
|
+
if (c.type === "identifier") return c.text;
|
|
1149
|
+
for (let i = c.namedChildCount - 1; i >= 0; i--) {
|
|
1150
|
+
const ch = c.namedChild(i);
|
|
1151
|
+
if (ch) stack2.push(ch);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
const direct = this.findChild(node, (n) => n.type === "identifier");
|
|
1157
|
+
if (direct) return direct.text;
|
|
1158
|
+
const stack = [node];
|
|
1159
|
+
while (stack.length > 0) {
|
|
1160
|
+
const c = stack.pop();
|
|
1161
|
+
if (c.type === "identifier") return c.text;
|
|
1162
|
+
for (let i = c.namedChildCount - 1; i >= 0; i--) {
|
|
1163
|
+
const ch = c.namedChild(i);
|
|
1164
|
+
if (ch) stack.push(ch);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* OC-D: the typedef alias naming an anonymous aggregate
|
|
1171
|
+
* (`typedef struct {...} Size` -> "Size"): the LAST type_identifier
|
|
1172
|
+
* child directly under the enclosing type_definition.
|
|
1173
|
+
*/
|
|
1174
|
+
typedefName(node) {
|
|
1175
|
+
const p = node.parent;
|
|
1176
|
+
if (!p || p.type !== "type_definition") return null;
|
|
1177
|
+
let alias = null;
|
|
1178
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
1179
|
+
const c = p.namedChild(i);
|
|
1180
|
+
if (c && (c.type === "type_identifier" || c.type === "identifier")) alias = c;
|
|
1181
|
+
}
|
|
1182
|
+
return alias ? alias.text : null;
|
|
1183
|
+
}
|
|
1184
|
+
// Walk struct_declaration → struct_declarator → (pointer/reference) →
|
|
1185
|
+
// identifier to find the declared property/ivar name.
|
|
1186
|
+
findDeclaredName(node) {
|
|
1187
|
+
const walkForIdentifier = (n) => {
|
|
1188
|
+
if (!n) return null;
|
|
1189
|
+
if (n.type === "identifier") return n;
|
|
1190
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1191
|
+
const found = walkForIdentifier(n.namedChild(i));
|
|
1192
|
+
if (found) return found;
|
|
1193
|
+
}
|
|
1194
|
+
return null;
|
|
1195
|
+
};
|
|
1196
|
+
const idNode = walkForIdentifier(node);
|
|
1197
|
+
if (!idNode) return null;
|
|
1198
|
+
const structDecl = this.findChild(node, (n) => n.type === "struct_declaration");
|
|
1199
|
+
if (structDecl) {
|
|
1200
|
+
const sd = this.findChild(structDecl, (n) => n.type === "struct_declarator");
|
|
1201
|
+
if (sd) {
|
|
1202
|
+
const deep = walkForIdentifier(sd);
|
|
1203
|
+
if (deep) return deep.text;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return idNode.text;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* OBJCD9: does this declarator subtree reach a `function_declarator`?
|
|
1210
|
+
*
|
|
1211
|
+
* A declaration is a function PROTOTYPE when its declarator chain contains one,
|
|
1212
|
+
* whatever wraps it: `void f(int)` nests the declarator bare, `NSString * f(d)`
|
|
1213
|
+
* nests it under a `pointer_declarator`. The wrapper is a return type, not a
|
|
1214
|
+
* different kind of thing, and the pre-fix arm list read the wrapper as if it
|
|
1215
|
+
* were. Structural — `node.type` and a pre-order walk — with no text test and
|
|
1216
|
+
* no regex.
|
|
1217
|
+
*/
|
|
1218
|
+
declaratorChainHasFunctionDeclarator(node) {
|
|
1219
|
+
const stack = [node];
|
|
1220
|
+
let guard = 0;
|
|
1221
|
+
while (stack.length > 0 && guard++ < 1e4) {
|
|
1222
|
+
const c = stack.pop();
|
|
1223
|
+
if (c.type === "function_declarator") return true;
|
|
1224
|
+
if (c.type === "parameter_list") continue;
|
|
1225
|
+
for (let i = c.namedChildCount - 1; i >= 0; i--) {
|
|
1226
|
+
const ch = c.namedChild(i);
|
|
1227
|
+
if (ch) stack.push(ch);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* OBJCD5: the defining name of a function_definition — the first
|
|
1234
|
+
* function_declarator in document order that is NOT inside the body
|
|
1235
|
+
* (compound_statement), then its first identifier child. Handles the
|
|
1236
|
+
* pointer_declarator > function_declarator nesting of pointer-return C
|
|
1237
|
+
* helpers. The head always precedes any parameter-nested function-pointer
|
|
1238
|
+
* declarator in the tree, so pre-order first-hit is exact (probe-verified
|
|
1239
|
+
* against the pinned tree-sitter-objc, 2026-09).
|
|
1240
|
+
*/
|
|
1241
|
+
findFunctionName(node) {
|
|
1242
|
+
const stack = [node];
|
|
1243
|
+
while (stack.length > 0) {
|
|
1244
|
+
const c = stack.pop();
|
|
1245
|
+
if (c.type === "compound_statement") continue;
|
|
1246
|
+
if (c.type === "function_declarator") {
|
|
1247
|
+
const id = this.findChild(c, (n) => n.type === "identifier");
|
|
1248
|
+
return id || c;
|
|
1249
|
+
}
|
|
1250
|
+
for (let i = c.namedChildCount - 1; i >= 0; i--) {
|
|
1251
|
+
const child = c.namedChild(i);
|
|
1252
|
+
if (child) stack.push(child);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
// The `+`/`-` sign is an anonymous token child of a method node
|
|
1258
|
+
// (`method_declaration [-:"-"] [;:";"]`) — scan the non-named children for
|
|
1259
|
+
// the `+` token. Pure AST, never source-text scanning.
|
|
1260
|
+
isClassMethodToken(node) {
|
|
1261
|
+
const count = node.childCount;
|
|
1262
|
+
for (let i = 0; i < count; i++) {
|
|
1263
|
+
const c = node.child(i);
|
|
1264
|
+
if (c && !c.isNamed && c.type === "+") return true;
|
|
1265
|
+
}
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* OBJCD7: full ObjC selector of a method_declaration/method_definition.
|
|
1270
|
+
* The pinned grammar nests keyword identifiers at the TOP level of the
|
|
1271
|
+
* method node (parameter locals live INSIDE the method_parameter nodes,
|
|
1272
|
+
* probe-verified 2026-09): `- (void)storeImage:(UIImage*)image
|
|
1273
|
+
* forName:(NSString*)name` -> top-level children are identifier
|
|
1274
|
+
* "storeImage", method_parameter, identifier "forName", method_parameter.
|
|
1275
|
+
* A keyword gets ':' when the NEXT named sibling is a method_parameter.
|
|
1276
|
+
*
|
|
1277
|
+
* A selector is that keyword chain and NOTHING ELSE. The grammar also puts a
|
|
1278
|
+
* method's TRAILING ANNOTATION MACROS at the method node's top level as bare
|
|
1279
|
+
* identifiers with no `method_parameter` after them:
|
|
1280
|
+
*
|
|
1281
|
+
* method_declaration "- (instancetype)initWithBaseURL:(NSURL *)url
|
|
1282
|
+
* sessionConfiguration:(NSURLSessionConfiguration *)c
|
|
1283
|
+
* NS_DESIGNATED_INITIALIZER;"
|
|
1284
|
+
* identifier "initWithBaseURL"
|
|
1285
|
+
* method_parameter ":(NSURL *)url"
|
|
1286
|
+
* identifier "sessionConfiguration"
|
|
1287
|
+
* method_parameter ":(NSURLSessionConfiguration *)c"
|
|
1288
|
+
* identifier "NS_DESIGNATED_INITIALIZER" <- an annotation, not a keyword
|
|
1289
|
+
*
|
|
1290
|
+
* Concatenating every top-level identifier published
|
|
1291
|
+
* `initWithBaseURL:sessionConfiguration:NS_DESIGNATED_INITIALIZER` — a symbol
|
|
1292
|
+
* no class in the world declares, and an `overrides` TARGET
|
|
1293
|
+
* (`AFURLSessionManager.<that name>`) that resolves to nothing. Measured over
|
|
1294
|
+
* the admitted universe by script/m60/objc-selector-integrity.ts before this
|
|
1295
|
+
* fix: 79 method nodes in 29 files (NS_REFINED_FOR_SWIFT 62,
|
|
1296
|
+
* NS_DESIGNATED_INITIALIZER 15, NS_SWIFT_NOTHROW 2) and 109 fabricated
|
|
1297
|
+
* `overrides` targets.
|
|
1298
|
+
*
|
|
1299
|
+
* So the chain STOPS at the first bare identifier: after a keyword it is an
|
|
1300
|
+
* annotation; with no keyword seen it is the zero-argument selector and
|
|
1301
|
+
* anything after it is an annotation too. The alternative reading — a bare
|
|
1302
|
+
* identifier BEFORE the keywords is the selector — is not a guess: the same
|
|
1303
|
+
* probe counted the shapes of all 2,891 method nodes and `bareThenKeyword` is
|
|
1304
|
+
* 0, `bareOnlyMulti` 0, `keywordChain` 1,595, `singleBare` 1,217,
|
|
1305
|
+
* `bareAfterKeyword` 79. The rule is the grammar's, and the census is what
|
|
1306
|
+
* would refute it.
|
|
1307
|
+
*/
|
|
1308
|
+
fullMethodSelector(node) {
|
|
1309
|
+
let sel = "";
|
|
1310
|
+
const count = node.namedChildCount;
|
|
1311
|
+
for (let i = 0; i < count; i++) {
|
|
1312
|
+
const c = node.namedChild(i);
|
|
1313
|
+
if (!c) continue;
|
|
1314
|
+
if (c.type === "identifier") {
|
|
1315
|
+
const next = i + 1 < count ? node.namedChild(i + 1) : null;
|
|
1316
|
+
if (next && next.type === "method_parameter") {
|
|
1317
|
+
sel += c.text + ":";
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (sel === "") sel = c.text;
|
|
1321
|
+
break;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return sel;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* OBJCD7: full selector of a message_expression. The grammar flattens
|
|
1328
|
+
* keywords and arguments as SIBLINGS; a keyword is an identifier whose
|
|
1329
|
+
* NEXT child is an anonymous ':' token (the colons are direct anonymous
|
|
1330
|
+
* children of message_expression, probe-verified 2026-09). Zero-argument
|
|
1331
|
+
* messages have no ':' — the selector is then the LAST identifier child
|
|
1332
|
+
* (receiver identifiers live inside nested nodes for complex receivers;
|
|
1333
|
+
* `[foo bar]` -> bar, `[self.imageTable removeAllObjects]` ->
|
|
1334
|
+
* removeAllObjects, `[[Foo alloc] init]` -> init).
|
|
1335
|
+
*/
|
|
1336
|
+
/**
|
|
1337
|
+
* The FIRST selector node of a message send — the same selection `fullMessageSelector`
|
|
1338
|
+
* builds its text from, returned as a NODE.
|
|
1339
|
+
*
|
|
1340
|
+
* The M59.A7 contract is that a relationship's `line_number` is the line of the NAME it
|
|
1341
|
+
* names, and a name captured as a string has no line. swift's round paid for this lesson
|
|
1342
|
+
* with a surviving `calls Endpoint -> method` edge that claimed the CONVERTED site's line,
|
|
1343
|
+
* because the text and the line came from two different selections; here they come from one
|
|
1344
|
+
* walk, and the equivalence probe re-checks that the node's own text is a prefix of the
|
|
1345
|
+
* published selector (a keyword send) or equal to it (a zero-argument send).
|
|
1346
|
+
*/
|
|
1347
|
+
fullMessageSelectorNode(node) {
|
|
1348
|
+
let firstKeyword = null;
|
|
1349
|
+
let lastIdentifier = null;
|
|
1350
|
+
const count = node.childCount;
|
|
1351
|
+
for (let i = 0; i < count; i++) {
|
|
1352
|
+
const c = node.child(i);
|
|
1353
|
+
if (!c) continue;
|
|
1354
|
+
if (c.isNamed && c.type === "identifier") {
|
|
1355
|
+
const nxt = i + 1 < count ? node.child(i + 1) : null;
|
|
1356
|
+
if (nxt && !nxt.isNamed && nxt.type === ":") {
|
|
1357
|
+
if (!firstKeyword) firstKeyword = c;
|
|
1358
|
+
} else {
|
|
1359
|
+
lastIdentifier = c;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return firstKeyword ?? lastIdentifier;
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* The 1-based line a node is WRITTEN on. The unit of the M59.A7 contract, and deliberately
|
|
1367
|
+
* computed from the node itself: two runs of the same file must produce the same number.
|
|
1368
|
+
*/
|
|
1369
|
+
nodeLine(n) {
|
|
1370
|
+
return n.startPosition.row + 1;
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* OC-ONT-6 — the three lists Objective-C spells with ONE token sequence.
|
|
1374
|
+
*
|
|
1375
|
+
* `@interface Cache <KeyType, ObjectType> : Store <KeyType, ObjectType> <CacheProtocol>`
|
|
1376
|
+
* has three `parameterized_arguments` children, and the pinned grammar names all three
|
|
1377
|
+
* identically: the class's own lightweight-generic TYPE PARAMETERS, its superclass's TYPE
|
|
1378
|
+
* ARGUMENTS, and its ADOPTED PROTOCOLS. Reading every list as an adoption list published
|
|
1379
|
+
* `implements Cache -> KeyType` / `-> ObjectType` — the class's own type parameters as
|
|
1380
|
+
* protocols, names no file declares (objc/generics-and-reason-codes).
|
|
1381
|
+
*
|
|
1382
|
+
* The lists are separated by POSITION, and position is exact here rather than heuristic
|
|
1383
|
+
* because the pinned grammar keeps the `:` and `(` tokens as direct children:
|
|
1384
|
+
*
|
|
1385
|
+
* 1. a list BEFORE the `:` is part of the DECLARATION — the class's own type parameters,
|
|
1386
|
+
* never a protocol;
|
|
1387
|
+
* 2. the FIRST list after the `:` is the superclass's type ARGUMENTS exactly when every
|
|
1388
|
+
* name in it is one of those parameters, and is otherwise the adoption list: the
|
|
1389
|
+
* ordinary `@interface Plain : NSObject <NSCopying>` declares no parameters, so its
|
|
1390
|
+
* single list adopts;
|
|
1391
|
+
* 3. every later list is an adoption list.
|
|
1392
|
+
*
|
|
1393
|
+
* A class EXTENSION re-declares the enclosing class's parameters in front of its `()`
|
|
1394
|
+
* (`@interface Cache <KeyType, ObjectType> ()`), so its lists are all declaration-region
|
|
1395
|
+
* and none adopts; a CATEGORY is the mirror image — `@interface Plain (Trim)
|
|
1396
|
+
* <NSFastEnumeration>` — and its list sits AFTER the `(`, so a category's lists all adopt.
|
|
1397
|
+
* The two positions are mirrors, which is why the rule cannot be "a list before the paren"
|
|
1398
|
+
* alone (objc/generics-and-reason-codes pins both).
|
|
1399
|
+
*
|
|
1400
|
+
* The same partition is re-derived independently in
|
|
1401
|
+
* script/m60/objc-construct-site-probe.ts so the F5 differential compares two derivations
|
|
1402
|
+
* rather than one against itself.
|
|
1403
|
+
*/
|
|
1404
|
+
objcInheritanceLists(node) {
|
|
1405
|
+
const membersOf = (pa) => {
|
|
1406
|
+
const out = [];
|
|
1407
|
+
for (let i = 0; i < pa.namedChildCount; i++) {
|
|
1408
|
+
const m = pa.namedChild(i);
|
|
1409
|
+
if (m && (m.type === "type_identifier" || m.type === "identifier" || m.type === "type_name")) {
|
|
1410
|
+
out.push({ name: m.text, node: m });
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return out;
|
|
1414
|
+
};
|
|
1415
|
+
let colonIdx = -1;
|
|
1416
|
+
let parenIdx = -1;
|
|
1417
|
+
const lists = [];
|
|
1418
|
+
for (let ci = 0; ci < node.childCount; ci++) {
|
|
1419
|
+
const c = node.child(ci);
|
|
1420
|
+
if (!c) continue;
|
|
1421
|
+
if (!c.isNamed && c.type === ":" && colonIdx < 0) {
|
|
1422
|
+
colonIdx = ci;
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
if (!c.isNamed && c.type === "(" && parenIdx < 0) {
|
|
1426
|
+
parenIdx = ci;
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
if (c.isNamed && c.type === "parameterized_arguments") lists.push({ node: c, idx: ci });
|
|
1430
|
+
}
|
|
1431
|
+
const isCategory = parenIdx >= 0 && (colonIdx < 0 || parenIdx < colonIdx);
|
|
1432
|
+
const isDeclarationRegion = (idx) => colonIdx >= 0 ? idx < colonIdx : isCategory && idx < parenIdx;
|
|
1433
|
+
const typeParams = [];
|
|
1434
|
+
for (const l of lists) {
|
|
1435
|
+
if (!isDeclarationRegion(l.idx)) continue;
|
|
1436
|
+
for (const m of membersOf(l.node)) typeParams.push(m.name);
|
|
1437
|
+
}
|
|
1438
|
+
const paramSet = new Set(typeParams);
|
|
1439
|
+
let superClass = null;
|
|
1440
|
+
let superNode = null;
|
|
1441
|
+
const protocols = [];
|
|
1442
|
+
let argsConsumed = false;
|
|
1443
|
+
for (let ci = 0; ci < node.childCount; ci++) {
|
|
1444
|
+
const c = node.child(ci);
|
|
1445
|
+
if (!c) continue;
|
|
1446
|
+
if (colonIdx >= 0 && ci > colonIdx && superClass === null && c.isNamed && (c.type === "identifier" || c.type === "type_identifier")) {
|
|
1447
|
+
superClass = c.text;
|
|
1448
|
+
superNode = c;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
if (!c.isNamed || c.type !== "parameterized_arguments") continue;
|
|
1452
|
+
if (isDeclarationRegion(ci)) continue;
|
|
1453
|
+
const mem = membersOf(c);
|
|
1454
|
+
if (colonIdx >= 0 && !argsConsumed && mem.length > 0 && mem.every((m) => paramSet.has(m.name))) {
|
|
1455
|
+
argsConsumed = true;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
for (const m of mem) protocols.push(m);
|
|
1459
|
+
if (colonIdx >= 0) argsConsumed = true;
|
|
1460
|
+
}
|
|
1461
|
+
return { superClass, superNode, typeParams, protocols };
|
|
1462
|
+
}
|
|
1463
|
+
fullMessageSelector(node) {
|
|
1464
|
+
let sel = "";
|
|
1465
|
+
let lastId = null;
|
|
1466
|
+
const count = node.childCount;
|
|
1467
|
+
for (let i = 0; i < count; i++) {
|
|
1468
|
+
const c = node.child(i);
|
|
1469
|
+
if (!c) continue;
|
|
1470
|
+
if (c.isNamed && c.type === "identifier") {
|
|
1471
|
+
const nxt = i + 1 < count ? node.child(i + 1) : null;
|
|
1472
|
+
if (nxt && !nxt.isNamed && nxt.type === ":") {
|
|
1473
|
+
sel += c.text + ":";
|
|
1474
|
+
} else {
|
|
1475
|
+
lastId = c.text;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (sel) return sel;
|
|
1480
|
+
return lastId ?? "";
|
|
1481
|
+
}
|
|
1482
|
+
// Pure cursor helper protecting V8 from GC Thrashing
|
|
1483
|
+
findChild(node, predicate) {
|
|
1484
|
+
const count = node.namedChildCount;
|
|
1485
|
+
for (let i = 0; i < count; i++) {
|
|
1486
|
+
const child = node.namedChild(i);
|
|
1487
|
+
if (child && predicate(child)) return child;
|
|
1488
|
+
}
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
// Deep pre-order variant of findChild (first document-order match in the
|
|
1492
|
+
// subtree, including the node itself).
|
|
1493
|
+
deepFind(node, predicate) {
|
|
1494
|
+
const stack = [node];
|
|
1495
|
+
while (stack.length > 0) {
|
|
1496
|
+
const c = stack.pop();
|
|
1497
|
+
if (predicate(c)) return c;
|
|
1498
|
+
for (let i = c.namedChildCount - 1; i >= 0; i--) {
|
|
1499
|
+
const ch = c.namedChild(i);
|
|
1500
|
+
if (ch) stack.push(ch);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return null;
|
|
1504
|
+
}
|
|
1505
|
+
getRange(node) {
|
|
1506
|
+
return {
|
|
1507
|
+
startLine: node.startPosition.row + 1,
|
|
1508
|
+
startColumn: node.startPosition.column + 1,
|
|
1509
|
+
endLine: node.endPosition.row + 1,
|
|
1510
|
+
endColumn: node.endPosition.column + 1,
|
|
1511
|
+
startIndex: node.startIndex,
|
|
1512
|
+
endIndex: node.endIndex
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
export {
|
|
1517
|
+
RealObjectiveCAnalyzer
|
|
1518
|
+
};
|