@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,725 @@
|
|
|
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 "./chunk-KAET47ZL.js";
|
|
8
|
+
import "./chunk-KCDECRJY.js";
|
|
9
|
+
import "./chunk-MLKGABMK.js";
|
|
10
|
+
|
|
11
|
+
// src/ast/real-hcl-analyzer.ts
|
|
12
|
+
var TYPE_NAME_BLOCKS = /* @__PURE__ */ new Set(["resource", "data"]);
|
|
13
|
+
var SINGLE_LABEL_BLOCKS = /* @__PURE__ */ new Set(["variable", "output", "module", "provider"]);
|
|
14
|
+
var TOP_LEVEL_KINDS = {
|
|
15
|
+
resource: ASTEngine.SymbolKind.Class,
|
|
16
|
+
data: ASTEngine.SymbolKind.Class,
|
|
17
|
+
variable: ASTEngine.SymbolKind.Variable,
|
|
18
|
+
output: ASTEngine.SymbolKind.Variable,
|
|
19
|
+
module: ASTEngine.SymbolKind.Module,
|
|
20
|
+
provider: ASTEngine.SymbolKind.Module,
|
|
21
|
+
terraform: ASTEngine.SymbolKind.Module,
|
|
22
|
+
locals: ASTEngine.SymbolKind.Module
|
|
23
|
+
};
|
|
24
|
+
var TERRAFORM_TOP_LEVEL = new Set(Object.keys(TOP_LEVEL_KINDS));
|
|
25
|
+
var TERRAFRAMEWORK_NAME = "Terraform";
|
|
26
|
+
var BUILTIN_ROOTS = /* @__PURE__ */ new Set(["count", "each", "self", "path", "terraform"]);
|
|
27
|
+
var FUNCTION_DECL_BLOCKS = /* @__PURE__ */ new Set(["function"]);
|
|
28
|
+
var TARGET_BLOCK = "target";
|
|
29
|
+
var INHERITS_ATTRIBUTE = "inherits";
|
|
30
|
+
var OVERRIDE_BLOCK_KIND = {
|
|
31
|
+
override_resource: "resource",
|
|
32
|
+
override_data: "data",
|
|
33
|
+
override_module: "module"
|
|
34
|
+
};
|
|
35
|
+
var RealHclAnalyzer = class {
|
|
36
|
+
/** Per-analysis memo for `subtreeSize`, keyed by tree-sitter node id. */
|
|
37
|
+
sizeCache = /* @__PURE__ */ new Map();
|
|
38
|
+
analyze(rootNode, content, filePath) {
|
|
39
|
+
this.sizeCache.clear();
|
|
40
|
+
const symbols = [];
|
|
41
|
+
const relationships = [];
|
|
42
|
+
const imports = [];
|
|
43
|
+
const exports = [];
|
|
44
|
+
const errors = [];
|
|
45
|
+
const relKeys = /* @__PURE__ */ new Set();
|
|
46
|
+
const addRelationship = (source, target, type, lineNumber, metadata = {}) => {
|
|
47
|
+
const key = `${source}\0${type}\0${target}\0${lineNumber}`;
|
|
48
|
+
if (relKeys.has(key)) return;
|
|
49
|
+
relKeys.add(key);
|
|
50
|
+
if (!metadata.evidenceClass) {
|
|
51
|
+
metadata.evidenceClass = type === ASTEngine.RelationshipType.Contains ? "structural" : type === ASTEngine.RelationshipType.Imports ? "import_based" : type === ASTEngine.RelationshipType.Uses ? "builtin_qualified" : type === ASTEngine.RelationshipType.References ? "reference_qualified" : "structural";
|
|
52
|
+
}
|
|
53
|
+
relationships.push({ source, target, type, confidence: 1, line_number: lineNumber, metadata });
|
|
54
|
+
};
|
|
55
|
+
const terraformBlocks = /* @__PURE__ */ new Map();
|
|
56
|
+
const rootName = this.documentName(filePath);
|
|
57
|
+
const rootRange = this.nodeToRange(rootNode);
|
|
58
|
+
symbols.push({
|
|
59
|
+
name: rootName,
|
|
60
|
+
qualifiedName: rootName,
|
|
61
|
+
kind: ASTEngine.SymbolKind.Module,
|
|
62
|
+
range: rootRange,
|
|
63
|
+
scope: "",
|
|
64
|
+
visibility: "public",
|
|
65
|
+
metadata: { hclRoot: true, hclLanguage: "hcl" }
|
|
66
|
+
});
|
|
67
|
+
exports.push({ name: rootName, kind: ASTEngine.SymbolKind.Module, range: rootRange, visibility: "public" });
|
|
68
|
+
const functionDecls = /* @__PURE__ */ new Map();
|
|
69
|
+
const pendingCalls = [];
|
|
70
|
+
const targetDecls = /* @__PURE__ */ new Map();
|
|
71
|
+
const pendingInherits = [];
|
|
72
|
+
const stack = [
|
|
73
|
+
{
|
|
74
|
+
node: rootNode,
|
|
75
|
+
blockPath: [],
|
|
76
|
+
blockKind: void 0,
|
|
77
|
+
taken: /* @__PURE__ */ new Map([[rootName, 1]]),
|
|
78
|
+
iterators: /* @__PURE__ */ new Set(),
|
|
79
|
+
shadowed: /* @__PURE__ */ new Map(),
|
|
80
|
+
overrideKind: null,
|
|
81
|
+
enclosingBlockType: ""
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
const pushChildren = (node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType) => {
|
|
85
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
86
|
+
const child = node.namedChild(i);
|
|
87
|
+
if (child) stack.push({ node: child, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType });
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
while (stack.length > 0) {
|
|
91
|
+
const { node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType } = stack.pop();
|
|
92
|
+
const type = node.type;
|
|
93
|
+
if (type === "block") {
|
|
94
|
+
let blockTypeIndex = -1;
|
|
95
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
96
|
+
if (node.namedChild(i)?.type === "identifier") {
|
|
97
|
+
blockTypeIndex = i;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const blockType = blockTypeIndex >= 0 ? node.namedChild(blockTypeIndex).text : "";
|
|
102
|
+
const labels = [];
|
|
103
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
104
|
+
if (i === blockTypeIndex) continue;
|
|
105
|
+
const child = node.namedChild(i);
|
|
106
|
+
if (!child) continue;
|
|
107
|
+
if (child.type === "string_lit") labels.push(this.readStringLit(child));
|
|
108
|
+
else if (child.type === "identifier") labels.push(child.text);
|
|
109
|
+
}
|
|
110
|
+
if (!blockType) {
|
|
111
|
+
pushChildren(node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const address = this.blockAddress(blockType, labels);
|
|
115
|
+
const name = this.disambiguate(address, taken);
|
|
116
|
+
const kind = TOP_LEVEL_KINDS[blockType] ?? ASTEngine.SymbolKind.Class;
|
|
117
|
+
const qualified = [...blockPath, name].join(".");
|
|
118
|
+
const ownerPath = blockPath.length > 0 ? blockPath.join(".") : rootName;
|
|
119
|
+
const ownerLeaf = blockPath.length > 0 ? blockPath[blockPath.length - 1] : rootName;
|
|
120
|
+
if (TERRAFORM_TOP_LEVEL.has(blockType)) {
|
|
121
|
+
terraformBlocks.set(blockType, (terraformBlocks.get(blockType) ?? 0) + 1);
|
|
122
|
+
}
|
|
123
|
+
const isFunctionDecl = FUNCTION_DECL_BLOCKS.has(blockType);
|
|
124
|
+
if (isFunctionDecl && labels.length > 0 && !functionDecls.has(labels[0])) {
|
|
125
|
+
functionDecls.set(labels[0], name);
|
|
126
|
+
}
|
|
127
|
+
if (blockType === TARGET_BLOCK && labels.length > 0 && !targetDecls.has(labels[0])) {
|
|
128
|
+
targetDecls.set(labels[0], address);
|
|
129
|
+
}
|
|
130
|
+
symbols.push({
|
|
131
|
+
name,
|
|
132
|
+
qualifiedName: qualified,
|
|
133
|
+
kind,
|
|
134
|
+
complexity: this.subtreeSize(node),
|
|
135
|
+
range: this.nodeToRange(node),
|
|
136
|
+
scope: ownerPath,
|
|
137
|
+
visibility: "public",
|
|
138
|
+
ownerName: ownerLeaf,
|
|
139
|
+
ownerKind: blockPath.length > 0 ? blockKind : ASTEngine.SymbolKind.Module,
|
|
140
|
+
metadata: {
|
|
141
|
+
hclBlockType: blockType,
|
|
142
|
+
hclLabels: labels,
|
|
143
|
+
hclBlockKind: TERRAFORM_TOP_LEVEL.has(blockType) ? blockType : "nested",
|
|
144
|
+
hclAddress: address,
|
|
145
|
+
// Ordinal among the entities sharing this address in this scope,
|
|
146
|
+
// where the document root occupies slot 0 when its name collides
|
|
147
|
+
// with the block's own address (`moved.tf` holding `moved {}`).
|
|
148
|
+
hclDuplicateIndex: name === address ? 0 : (taken.get(address) ?? 1) - 1,
|
|
149
|
+
...isFunctionDecl ? { hclFunctionDecl: true } : {}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
addRelationship(ownerPath, qualified, ASTEngine.RelationshipType.Contains, node.startPosition.row + 1, {
|
|
153
|
+
hclSite: "block"
|
|
154
|
+
});
|
|
155
|
+
if (blockType === "resource" || blockType === "data") {
|
|
156
|
+
const providerType = labels[0];
|
|
157
|
+
if (providerType) {
|
|
158
|
+
addRelationship(rootName, providerType, ASTEngine.RelationshipType.Instantiates, node.startPosition.row + 1, {
|
|
159
|
+
hclSite: "provider-type",
|
|
160
|
+
instanceType: providerType,
|
|
161
|
+
instanceKind: blockType,
|
|
162
|
+
instanceName: labels[1] ?? "",
|
|
163
|
+
evidenceClass: "structural"
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (blockType === "module") {
|
|
168
|
+
const source = this.findModuleSource(node);
|
|
169
|
+
imports.push({
|
|
170
|
+
module: labels[0] ?? address,
|
|
171
|
+
path: source,
|
|
172
|
+
range: this.nodeToRange(node)
|
|
173
|
+
});
|
|
174
|
+
if (source) {
|
|
175
|
+
addRelationship(qualified, source, ASTEngine.RelationshipType.Imports, this.findModuleSourceLine(node) ?? node.startPosition.row + 1, {
|
|
176
|
+
hclSite: "module-source"
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (blockType === "include") {
|
|
181
|
+
const part = this.findIncludePath(node);
|
|
182
|
+
if (part) {
|
|
183
|
+
addRelationship(ownerPath, part.symbol, ASTEngine.RelationshipType.PartOf, part.line, {
|
|
184
|
+
hclSite: "terragrunt-include",
|
|
185
|
+
// MANDATORY on every `part_of` row (types.ts): the direction is the
|
|
186
|
+
// directive's own — THIS file carries the included file as a part.
|
|
187
|
+
partRole: "library",
|
|
188
|
+
partPath: part.text,
|
|
189
|
+
partSymbol: part.symbol,
|
|
190
|
+
isHclIncludePart: true,
|
|
191
|
+
evidenceClass: "include_literal"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let childIterators = iterators;
|
|
196
|
+
let childShadowed = shadowed;
|
|
197
|
+
if (blockType === "dynamic") {
|
|
198
|
+
const found = /* @__PURE__ */ new Set();
|
|
199
|
+
this.findDynamicIterators(node, labels, found);
|
|
200
|
+
if (found.size > 0) {
|
|
201
|
+
childIterators = new Set(iterators);
|
|
202
|
+
childShadowed = new Map(shadowed);
|
|
203
|
+
for (const it of found) {
|
|
204
|
+
childIterators.add(it);
|
|
205
|
+
childShadowed.set(it, (childShadowed.get(it) ?? 0) + 1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
pushChildren(node, [...blockPath, name], kind, /* @__PURE__ */ new Map(), childIterators, childShadowed, OVERRIDE_BLOCK_KIND[blockType] ?? null, blockType);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (type === "attribute") {
|
|
213
|
+
const keyNode = this.findChild(node, (n) => n.type === "identifier");
|
|
214
|
+
const key = keyNode?.text ?? "";
|
|
215
|
+
if (key) {
|
|
216
|
+
const ownerPath = blockPath.length > 0 ? blockPath.join(".") : rootName;
|
|
217
|
+
const ownerLeaf = blockPath.length > 0 ? blockPath[blockPath.length - 1] : rootName;
|
|
218
|
+
const name = blockPath.length === 0 && key === rootName ? `${key}#2` : key;
|
|
219
|
+
const qualified = [...blockPath, name].join(".") || name;
|
|
220
|
+
const expr = this.findChild(node, (n) => n.type === "expression");
|
|
221
|
+
const valueNode = expr ? expr.namedChild(0) : void 0;
|
|
222
|
+
if (enclosingBlockType === TARGET_BLOCK && key === INHERITS_ATTRIBUTE) {
|
|
223
|
+
for (const parent of this.readStringList(expr)) {
|
|
224
|
+
pendingInherits.push({ parent, line: keyNode.startPosition.row + 1, owner: ownerPath });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
symbols.push({
|
|
228
|
+
name,
|
|
229
|
+
qualifiedName: qualified,
|
|
230
|
+
kind: ASTEngine.SymbolKind.Property,
|
|
231
|
+
complexity: this.subtreeSize(node),
|
|
232
|
+
range: this.nodeToRange(node),
|
|
233
|
+
scope: ownerPath,
|
|
234
|
+
visibility: "public",
|
|
235
|
+
ownerName: ownerLeaf,
|
|
236
|
+
ownerKind: blockPath.length > 0 ? blockKind : ASTEngine.SymbolKind.Module,
|
|
237
|
+
metadata: { hclValueKind: this.readValueKind(expr), hclAddress: key }
|
|
238
|
+
});
|
|
239
|
+
addRelationship(ownerPath, qualified, ASTEngine.RelationshipType.Contains, keyNode.startPosition.row + 1, {
|
|
240
|
+
hclSite: "attribute"
|
|
241
|
+
});
|
|
242
|
+
if (expr) {
|
|
243
|
+
const refs = [];
|
|
244
|
+
this.extractReferences(expr, iterators, shadowed, refs, pendingCalls, ownerPath);
|
|
245
|
+
if (overrideKind !== null && key === "target") {
|
|
246
|
+
for (const ref of refs) {
|
|
247
|
+
if (BUILTIN_ROOTS.has(ref.path.split(".")[0])) continue;
|
|
248
|
+
addRelationship(ownerPath, ref.path, ASTEngine.RelationshipType.Overrides, keyNode.startPosition.row + 1, {
|
|
249
|
+
hclSite: "override-target",
|
|
250
|
+
hclOverrideKind: overrideKind,
|
|
251
|
+
evidenceClass: "structural"
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
for (const ref of refs) {
|
|
256
|
+
const root = ref.path.split(".")[0];
|
|
257
|
+
const builtin = BUILTIN_ROOTS.has(root);
|
|
258
|
+
addRelationship(
|
|
259
|
+
ownerPath,
|
|
260
|
+
ref.path,
|
|
261
|
+
builtin ? ASTEngine.RelationshipType.Uses : ASTEngine.RelationshipType.References,
|
|
262
|
+
ref.line,
|
|
263
|
+
{
|
|
264
|
+
hclSite: builtin ? "builtin-use" : "reference",
|
|
265
|
+
...builtin ? { hclBuiltin: true, languageNamespace: [...BUILTIN_ROOTS] } : {}
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
pushChildren(node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType);
|
|
275
|
+
}
|
|
276
|
+
for (const site of pendingInherits) {
|
|
277
|
+
const declared = targetDecls.get(site.parent);
|
|
278
|
+
if (!declared) continue;
|
|
279
|
+
addRelationship(site.owner, declared, ASTEngine.RelationshipType.Extends, site.line, {
|
|
280
|
+
hclSite: "inherits",
|
|
281
|
+
// The spelling AT the inheritance site, kept because the target is the parent
|
|
282
|
+
// DECLARATION's address (`target._common`) rather than the bare label `_common`.
|
|
283
|
+
hclInheritedTarget: site.parent,
|
|
284
|
+
evidenceClass: "structural"
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
for (const site of pendingCalls) {
|
|
288
|
+
const declared = functionDecls.get(site.callee);
|
|
289
|
+
if (!declared) continue;
|
|
290
|
+
addRelationship(site.owner, declared, ASTEngine.RelationshipType.Calls, site.line, {
|
|
291
|
+
hclSite: "function-call",
|
|
292
|
+
// The spelling AT the call site, kept because the target is the
|
|
293
|
+
// declaration's index identity (`function.bindir`) rather than the bare
|
|
294
|
+
// name the file writes.
|
|
295
|
+
hclCallee: site.callee,
|
|
296
|
+
evidenceClass: "structural"
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
for (const s of symbols) {
|
|
300
|
+
if (s.metadata?.hclRoot === true) continue;
|
|
301
|
+
if (s.scope === rootName) {
|
|
302
|
+
exports.push({ name: s.name, kind: s.kind, range: s.range, visibility: "public" });
|
|
303
|
+
addRelationship(rootName, s.name, ASTEngine.RelationshipType.Exports, s.range.startLine, {
|
|
304
|
+
hclSite: "export"
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
language: "hcl",
|
|
310
|
+
symbols,
|
|
311
|
+
relationships,
|
|
312
|
+
imports,
|
|
313
|
+
exports,
|
|
314
|
+
errors,
|
|
315
|
+
metadata: {
|
|
316
|
+
complexity: symbols.length,
|
|
317
|
+
linesOfCode: content.split("\n").length,
|
|
318
|
+
patterns: [],
|
|
319
|
+
framework: "HCL/Terraform",
|
|
320
|
+
// The M60.R8 three-key family contract, and all three agree:
|
|
321
|
+
// `framework` names the language's host ecosystem, `detectedFrameworks`
|
|
322
|
+
// is ALWAYS an array (`[]` when nothing was detected) and
|
|
323
|
+
// `frameworkPatterns` is aligned with it by `framework` name. Before this
|
|
324
|
+
// the analyzer published only `framework`, which the gate records as
|
|
325
|
+
// `missing-contract-key` and a consumer joining the two arrays by name
|
|
326
|
+
// cannot do.
|
|
327
|
+
//
|
|
328
|
+
// The detection is structural, not an extension guess: HCL is the host
|
|
329
|
+
// language of several distinct ecosystems, and the corpus proves they
|
|
330
|
+
// need telling apart — consul's agent config and packer's
|
|
331
|
+
// `.pkr.hcl` templates are HCL with no Terraform block in sight, while
|
|
332
|
+
// `terraform@db4eef4` is Terraform throughout. `data`, `variable`,
|
|
333
|
+
// `output`, `module`, `provider`, `terraform` and `locals` are the
|
|
334
|
+
// Terraform top-level block set; a document declaring none of them is
|
|
335
|
+
// published as HCL with an empty detection list rather than being
|
|
336
|
+
// claimed as Terraform on the strength of its extension.
|
|
337
|
+
detectedFrameworks: terraformBlocks.size > 0 ? [TERRAFRAMEWORK_NAME] : [],
|
|
338
|
+
frameworkPatterns: terraformBlocks.size > 0 ? [
|
|
339
|
+
{
|
|
340
|
+
framework: TERRAFRAMEWORK_NAME,
|
|
341
|
+
patterns: [...terraformBlocks.entries()].sort((a, b) => a[0] < b[0] ? -1 : 1).map(([blockType, count]) => ({ blockType, count }))
|
|
342
|
+
}
|
|
343
|
+
] : [],
|
|
344
|
+
hasFrameworkIntelligence: true,
|
|
345
|
+
hasTypeInference: false
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Terraform's native address for a block, with a `blockType` fallback so a
|
|
351
|
+
* label-less `data {}` / `resource "x" {}` never produces an empty name.
|
|
352
|
+
*/
|
|
353
|
+
blockAddress(blockType, labels) {
|
|
354
|
+
if (TYPE_NAME_BLOCKS.has(blockType)) {
|
|
355
|
+
if (labels.length >= 2) return `${labels[0]}.${labels[1]}`;
|
|
356
|
+
if (labels.length === 1) return `${labels[0]}`;
|
|
357
|
+
return blockType;
|
|
358
|
+
}
|
|
359
|
+
if (SINGLE_LABEL_BLOCKS.has(blockType)) {
|
|
360
|
+
return labels[0] ?? blockType;
|
|
361
|
+
}
|
|
362
|
+
return labels.length > 0 ? `${blockType}.${labels.join(".")}` : blockType;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Make a leaf name unique among its siblings. Terraform repeats `locals`,
|
|
366
|
+
* `moved`, `removed`, `import` and aliased `provider` blocks by design, and
|
|
367
|
+
* the HashiCorp DSLs repeat label-less blocks (waypoint's `parameter`).
|
|
368
|
+
* The first use keeps the bare address; later siblings get `#2`, `#3`, …
|
|
369
|
+
*/
|
|
370
|
+
disambiguate(address, taken) {
|
|
371
|
+
const seen = taken.get(address) ?? 0;
|
|
372
|
+
taken.set(address, seen + 1);
|
|
373
|
+
if (seen === 0) return address;
|
|
374
|
+
let n = seen + 1;
|
|
375
|
+
let candidate = `${address}#${n}`;
|
|
376
|
+
while (taken.has(candidate)) {
|
|
377
|
+
n += 1;
|
|
378
|
+
candidate = `${address}#${n}`;
|
|
379
|
+
}
|
|
380
|
+
taken.set(candidate, 1);
|
|
381
|
+
return candidate;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* The grammar node type that actually describes the value — unwrapping the
|
|
385
|
+
* `operation` container so a comparison is reported as `binary_operation`
|
|
386
|
+
* rather than as a node type that names no value kind.
|
|
387
|
+
*/
|
|
388
|
+
readValueKind(expr) {
|
|
389
|
+
if (!expr) return "missing_expression";
|
|
390
|
+
const value = expr.namedChild(0);
|
|
391
|
+
if (!value) return "empty_expression";
|
|
392
|
+
if (value.type === "operation") {
|
|
393
|
+
const inner = value.namedChild(0);
|
|
394
|
+
return inner ? inner.type : "operation";
|
|
395
|
+
}
|
|
396
|
+
return value.type;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Walk an expression subtree and collect dotted reference paths with the line
|
|
400
|
+
* each name is written on.
|
|
401
|
+
*
|
|
402
|
+
* A path is a maximal run of `variable_expr` (base identifier) followed by
|
|
403
|
+
* `get_attr` accessors. The grammar places that run as a set of SIBLINGS under
|
|
404
|
+
* whatever node owns the expression — `expression` for a plain value, but
|
|
405
|
+
* `binary_operation` / `unary_operation` under an `operation` wrapper for
|
|
406
|
+
* arithmetic and comparison, and `expression` again inside each
|
|
407
|
+
* `template_interpolation`. So the run is read from ANY node's children, and
|
|
408
|
+
* a parent that is itself part of a run is not re-read.
|
|
409
|
+
*
|
|
410
|
+
* `index` / `splat` / `attr_splat` / `full_splat` end the run without
|
|
411
|
+
* contributing a segment: Terraform resolves `aws_instance.web[*].id` and
|
|
412
|
+
* `var.list[0]` against the address accumulated so far, so publishing
|
|
413
|
+
* `aws_instance.web` / `var.list` is the edge Terraform itself means.
|
|
414
|
+
*
|
|
415
|
+
* Real Terraform references are always qualified (`var.x`, `aws_instance.web.id`),
|
|
416
|
+
* so a bare single-identifier path is a type-constraint keyword (`type = string`)
|
|
417
|
+
* or a function name — not a reference — and is skipped. `loopVars` carries
|
|
418
|
+
* `for`-intro identifiers and `dynamic` iterators so block-locals are never
|
|
419
|
+
* mistaken for module/resource references.
|
|
420
|
+
*
|
|
421
|
+
* `callSites` collects every `function_call` CALLEE with the line it is
|
|
422
|
+
* written on, and nothing decides here whether it becomes an edge: a callee is
|
|
423
|
+
* only callable if a `function` block declares it, and that map is complete
|
|
424
|
+
* only after the DFS finishes (a call may precede its declaration). So the
|
|
425
|
+
* candidate is recorded and resolved by the caller.
|
|
426
|
+
*/
|
|
427
|
+
extractReferences(node, loopVars, shadowed, out, callSites, owner) {
|
|
428
|
+
const type = node.type;
|
|
429
|
+
if (type === "function_call") {
|
|
430
|
+
const callee = this.findChild(node, (n) => n.type === "identifier");
|
|
431
|
+
if (callee) callSites.push({ callee: callee.text, line: callee.startPosition.row + 1, owner });
|
|
432
|
+
}
|
|
433
|
+
if (type === "for_expr" || type === "for_tuple_expr" || type === "for_object_expr") {
|
|
434
|
+
const childLoopVars = new Set(loopVars);
|
|
435
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
436
|
+
const child = node.namedChild(i);
|
|
437
|
+
if (child?.type !== "for_intro") continue;
|
|
438
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
439
|
+
const intro = child.namedChild(j);
|
|
440
|
+
if (intro?.type === "identifier") childLoopVars.add(intro.text);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
444
|
+
const child = node.namedChild(i);
|
|
445
|
+
if (child) this.extractReferences(child, childLoopVars, shadowed, out, callSites, owner);
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (type === "object_elem") {
|
|
450
|
+
const val = node.childForFieldName?.("val");
|
|
451
|
+
if (val) this.extractReferences(val, loopVars, shadowed, out, callSites, owner);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
455
|
+
const child = node.child(i);
|
|
456
|
+
if (!child) continue;
|
|
457
|
+
if (child.type !== "variable_expr" && child.type !== "get_attr") {
|
|
458
|
+
if (child.isNamed) this.extractReferences(child, loopVars, shadowed, out, callSites, owner);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const parts = [];
|
|
462
|
+
const nameLines = [];
|
|
463
|
+
let j = i;
|
|
464
|
+
for (; j < node.childCount; j++) {
|
|
465
|
+
const c = node.child(j);
|
|
466
|
+
if (!c || c.type !== "variable_expr" && c.type !== "get_attr") break;
|
|
467
|
+
const id = this.findChild(c, (n) => n.type === "identifier");
|
|
468
|
+
if (!id) break;
|
|
469
|
+
parts.push(id.text);
|
|
470
|
+
nameLines.push(id.startPosition.row + 1);
|
|
471
|
+
}
|
|
472
|
+
if (parts.length >= 2 && !loopVars.has(parts[0])) {
|
|
473
|
+
out.push({ path: parts.join("."), line: nameLines[0] });
|
|
474
|
+
}
|
|
475
|
+
i = j - 1;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* The loop local(s) a `dynamic` block binds: the `iterator` attribute's value
|
|
480
|
+
* when present, otherwise the block's own first label — Terraform's documented
|
|
481
|
+
* default, so `dynamic "egress" { content { x = egress.cidr } }` reads a
|
|
482
|
+
* block-local, not a project symbol. Without the label fallback the emitter
|
|
483
|
+
* published a false `egress.cidr` (caught by hcl/dynamic-iterator-scope).
|
|
484
|
+
*/
|
|
485
|
+
findDynamicIterators(blockNode, labels, out) {
|
|
486
|
+
const body = this.findChild(blockNode, (n) => n.type === "body");
|
|
487
|
+
if (!body) return;
|
|
488
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
489
|
+
const attr = body.namedChild(i);
|
|
490
|
+
if (attr?.type !== "attribute") continue;
|
|
491
|
+
const key = this.findChild(attr, (n) => n.type === "identifier");
|
|
492
|
+
if (key?.text !== "iterator") continue;
|
|
493
|
+
const expr = this.findChild(attr, (n) => n.type === "expression");
|
|
494
|
+
const ve = expr?.namedChild(0);
|
|
495
|
+
if (ve?.type !== "variable_expr") continue;
|
|
496
|
+
const id = this.findChild(ve, (n) => n.type === "identifier");
|
|
497
|
+
if (id) {
|
|
498
|
+
out.add(id.text);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (labels.length > 0) out.add(labels[0]);
|
|
503
|
+
}
|
|
504
|
+
/** Read a `string_lit` label/value without source-text regex (grammar field). */
|
|
505
|
+
readStringLit(node) {
|
|
506
|
+
const literal = this.findChild(node, (n) => n.type === "template_literal");
|
|
507
|
+
if (literal) return literal.text;
|
|
508
|
+
return node.text;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The string LITERALS of a value, in source order: `["_common"]` yields one, `["binary",
|
|
512
|
+
* "_platforms"]` yields two, and a bare `"x"` yields one. Only `string_lit` counts — an
|
|
513
|
+
* interpolated `quoted_template` names a COMPUTED value, not a declaration, and resolving it
|
|
514
|
+
* would be a guess dressed as an edge.
|
|
515
|
+
*/
|
|
516
|
+
readStringList(expr) {
|
|
517
|
+
if (!expr) return [];
|
|
518
|
+
const out = [];
|
|
519
|
+
const visit = (n) => {
|
|
520
|
+
if (n.type === "string_lit") {
|
|
521
|
+
out.push(this.readStringLit(n));
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
525
|
+
const c = n.namedChild(i);
|
|
526
|
+
if (c) visit(c);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
visit(expr);
|
|
530
|
+
return out;
|
|
531
|
+
}
|
|
532
|
+
/** Find a module block's `source` attribute value (unquoted via grammar field). */
|
|
533
|
+
findModuleSource(blockNode) {
|
|
534
|
+
const body = this.findChild(blockNode, (n) => n.type === "body");
|
|
535
|
+
if (!body) return void 0;
|
|
536
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
537
|
+
const attr = body.namedChild(i);
|
|
538
|
+
if (attr?.type !== "attribute") continue;
|
|
539
|
+
const key = this.findChild(attr, (n) => n.type === "identifier");
|
|
540
|
+
if (key?.text !== "source") continue;
|
|
541
|
+
const expr = this.findChild(attr, (n) => n.type === "expression");
|
|
542
|
+
if (!expr) return void 0;
|
|
543
|
+
const literal = this.findDescendant(expr, (n) => n.type === "template_literal");
|
|
544
|
+
if (literal) return literal.text;
|
|
545
|
+
return expr.text;
|
|
546
|
+
}
|
|
547
|
+
return void 0;
|
|
548
|
+
}
|
|
549
|
+
/** The line a module block's `source = …` key is written on. */
|
|
550
|
+
findModuleSourceLine(blockNode) {
|
|
551
|
+
const body = this.findChild(blockNode, (n) => n.type === "body");
|
|
552
|
+
if (!body) return null;
|
|
553
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
554
|
+
const attr = body.namedChild(i);
|
|
555
|
+
if (attr?.type !== "attribute") continue;
|
|
556
|
+
const key = this.findChild(attr, (n) => n.type === "identifier");
|
|
557
|
+
if (key?.text !== "source") continue;
|
|
558
|
+
return key.startPosition.row + 1;
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
/** The document's root name, from the file stem (JSON/YAML convention). */
|
|
563
|
+
documentName(filePath) {
|
|
564
|
+
const slash = filePath.lastIndexOf("/");
|
|
565
|
+
const base = slash >= 0 ? filePath.slice(slash + 1) : filePath;
|
|
566
|
+
const lower = base.toLowerCase();
|
|
567
|
+
for (const suffix of [".tfvars", ".tofu", ".hcl", ".tf"]) {
|
|
568
|
+
if (lower.endsWith(suffix)) {
|
|
569
|
+
const stem = base.slice(0, base.length - suffix.length);
|
|
570
|
+
return stem.length > 0 ? stem : base;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return base.length > 0 ? base : "hcl";
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* A terragrunt `include` block's `path`, ONLY when it is a single STRING
|
|
577
|
+
* LITERAL — `{ text, symbol, line }`, or null when the block names nothing
|
|
578
|
+
* this parse can know.
|
|
579
|
+
*
|
|
580
|
+
* WHAT IS REFUSED AND WHY. A computed value is not a file: `find_in_parent_folders()`
|
|
581
|
+
* is a function call whose result depends on the working directory tree, and
|
|
582
|
+
* `"${get_terragrunt_dir()}/root.hcl"` is an interpolated template whose static
|
|
583
|
+
* `root.hcl` half is one segment of a computed path. Publishing either would be a
|
|
584
|
+
* `part_of` whose target is an expression — an edge to nothing — so both publish
|
|
585
|
+
* NOTHING rather than a guess. The guard is structural (node types), not a
|
|
586
|
+
* spelling test: any interpolation, call, variable read, index, arithmetic or
|
|
587
|
+
* conditional anywhere in the value marks it computed.
|
|
588
|
+
*
|
|
589
|
+
* The SYMBOL is the included file's root-module symbol spelling: the basename's
|
|
590
|
+
* stem, with the same extension suffixes `documentName()` strips — because the
|
|
591
|
+
* arbiter resolves a target string against symbol names, and every HCL file's
|
|
592
|
+
* root symbol is named from its stem (`root.hcl` → `root`). The path itself is
|
|
593
|
+
* returned separately for `metadata.partPath`.
|
|
594
|
+
*/
|
|
595
|
+
findIncludePath(blockNode) {
|
|
596
|
+
const body = this.findChild(blockNode, (n) => n.type === "body");
|
|
597
|
+
if (!body) return null;
|
|
598
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
599
|
+
const attr = body.namedChild(i);
|
|
600
|
+
if (attr?.type !== "attribute") continue;
|
|
601
|
+
const key = this.findChild(attr, (n) => n.type === "identifier");
|
|
602
|
+
if (key?.text !== "path") continue;
|
|
603
|
+
const expr = this.findChild(attr, (n) => n.type === "expression");
|
|
604
|
+
if (!expr) return null;
|
|
605
|
+
if (this.expressionIsComputed(expr)) return null;
|
|
606
|
+
const literal = this.findDescendant(expr, (n) => n.type === "template_literal");
|
|
607
|
+
if (!literal) return null;
|
|
608
|
+
const text = literal.text;
|
|
609
|
+
const symbol = this.includePartSymbol(text);
|
|
610
|
+
if (!symbol) return null;
|
|
611
|
+
return { text, symbol, line: key.startPosition.row + 1 };
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* True when the value is a runtime expression rather than a literal this parse
|
|
617
|
+
* can name. Kept as a node-type walk (never source text): the grammar's own
|
|
618
|
+
* vocabulary is the contract, so a future construct cannot slip past a spelling
|
|
619
|
+
* test.
|
|
620
|
+
*/
|
|
621
|
+
expressionIsComputed(expr) {
|
|
622
|
+
const COMPUTED = /* @__PURE__ */ new Set([
|
|
623
|
+
"template_interpolation",
|
|
624
|
+
"function_call",
|
|
625
|
+
"variable_expr",
|
|
626
|
+
"get_attr",
|
|
627
|
+
"index",
|
|
628
|
+
"legacy_index",
|
|
629
|
+
"splat",
|
|
630
|
+
"attr_splat",
|
|
631
|
+
"full_splat",
|
|
632
|
+
"for_expr",
|
|
633
|
+
"conditional",
|
|
634
|
+
"binary_operation",
|
|
635
|
+
"unary_operation"
|
|
636
|
+
]);
|
|
637
|
+
let computed = false;
|
|
638
|
+
const visit = (n) => {
|
|
639
|
+
if (computed || !n) return;
|
|
640
|
+
if (COMPUTED.has(n.type)) {
|
|
641
|
+
computed = true;
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
for (let i = 0; i < n.namedChildCount; i++) visit(n.namedChild(i));
|
|
645
|
+
};
|
|
646
|
+
visit(expr);
|
|
647
|
+
return computed;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* The symbol-shaped target for an included file's path: its basename's stem.
|
|
651
|
+
* `./lib/root.hcl` → `root`, `root.hcl` → `root`, `x` → `x`.
|
|
652
|
+
*
|
|
653
|
+
* A name carrying characters that can only come from an expression or a glob
|
|
654
|
+
* (`$`, braces, quotes, `*`, `?`, shell metacharacters) is refused: the
|
|
655
|
+
* `expressionIsComputed` walk already excludes interpolations, so this is the
|
|
656
|
+
* second guard that keeps a path-shaped or glob-shaped string out of the target
|
|
657
|
+
* slot. Empty is refused for the same reason a label-less block names nothing.
|
|
658
|
+
*/
|
|
659
|
+
includePartSymbol(pathText) {
|
|
660
|
+
const slash = pathText.lastIndexOf("/");
|
|
661
|
+
const base = slash >= 0 ? pathText.slice(slash + 1) : pathText;
|
|
662
|
+
if (base.length === 0) return "";
|
|
663
|
+
for (let i = 0; i < base.length; i++) {
|
|
664
|
+
const c = base.charCodeAt(i);
|
|
665
|
+
if (c <= 32 || c === 34 || c === 39 || c === 96 || c === 36 || c === 40 || c === 41 || c === 123 || c === 125 || c === 91 || c === 93 || c === 42 || c === 63 || c === 59 || c === 124 || c === 38 || c === 60 || c === 62 || c === 92 || c === 126) {
|
|
666
|
+
return "";
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const lower = base.toLowerCase();
|
|
670
|
+
for (const suffix of [".tfvars", ".tofu", ".hcl", ".tf"]) {
|
|
671
|
+
if (lower.endsWith(suffix)) {
|
|
672
|
+
const stem = base.slice(0, base.length - suffix.length);
|
|
673
|
+
return stem.length > 0 ? stem : "";
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return base;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Nodes in a symbol's own subtree. Memoised on node id so the whole analysis
|
|
680
|
+
* stays O(nodes): the DFS already visits every node once, and a block's count
|
|
681
|
+
* deliberately includes its nested blocks — that is what makes "the biggest
|
|
682
|
+
* declaration in this file" a comparable number.
|
|
683
|
+
*/
|
|
684
|
+
subtreeSize(node) {
|
|
685
|
+
const cached = this.sizeCache.get(node.id);
|
|
686
|
+
if (cached !== void 0) return cached;
|
|
687
|
+
let total = 1;
|
|
688
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
689
|
+
const child = node.namedChild(i);
|
|
690
|
+
if (child) total += this.subtreeSize(child);
|
|
691
|
+
}
|
|
692
|
+
this.sizeCache.set(node.id, total);
|
|
693
|
+
return total;
|
|
694
|
+
}
|
|
695
|
+
nodeToRange(node) {
|
|
696
|
+
return {
|
|
697
|
+
startLine: node.startPosition.row + 1,
|
|
698
|
+
startColumn: node.startPosition.column + 1,
|
|
699
|
+
endLine: node.endPosition.row + 1,
|
|
700
|
+
endColumn: node.endPosition.column + 1,
|
|
701
|
+
startIndex: node.startIndex,
|
|
702
|
+
endIndex: node.endIndex
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
findChild(node, predicate) {
|
|
706
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
707
|
+
const child = node.namedChild(i);
|
|
708
|
+
if (child && predicate(child)) return child;
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
findDescendant(node, predicate) {
|
|
713
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
714
|
+
const child = node.namedChild(i);
|
|
715
|
+
if (!child) continue;
|
|
716
|
+
if (predicate(child)) return child;
|
|
717
|
+
const found = this.findDescendant(child, predicate);
|
|
718
|
+
if (found) return found;
|
|
719
|
+
}
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
export {
|
|
724
|
+
RealHclAnalyzer
|
|
725
|
+
};
|