@intentius/chant-lexicon-terraform 0.57.0 → 0.58.0

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.
Files changed (381) hide show
  1. package/dist/composites/terraform-adopt-op.d.ts +126 -0
  2. package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
  3. package/dist/composites/terraform-apply-op.d.ts +63 -16
  4. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  5. package/dist/composites/terraform-watch-op.d.ts +80 -36
  6. package/dist/composites/terraform-watch-op.d.ts.map +1 -1
  7. package/dist/config.d.ts +49 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/describe-resources.d.ts +256 -16
  10. package/dist/describe-resources.d.ts.map +1 -1
  11. package/dist/hcl/descend.d.ts +98 -0
  12. package/dist/hcl/descend.d.ts.map +1 -0
  13. package/dist/hcl/marker.d.ts +130 -0
  14. package/dist/hcl/marker.d.ts.map +1 -0
  15. package/dist/hcl/parse.d.ts +142 -9
  16. package/dist/hcl/parse.d.ts.map +1 -1
  17. package/dist/hcl/references.d.ts +76 -0
  18. package/dist/hcl/references.d.ts.map +1 -0
  19. package/dist/hcl/roots.d.ts +22 -3
  20. package/dist/hcl/roots.d.ts.map +1 -1
  21. package/dist/hcl/suppressions.d.ts +54 -0
  22. package/dist/hcl/suppressions.d.ts.map +1 -0
  23. package/dist/hcl/value.d.ts +77 -0
  24. package/dist/hcl/value.d.ts.map +1 -0
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/integrity.json +32 -5
  28. package/dist/lint/audit-catalog.d.ts +3 -2
  29. package/dist/lint/audit-catalog.d.ts.map +1 -1
  30. package/dist/lint/audit-lineage.d.ts +68 -0
  31. package/dist/lint/audit-lineage.d.ts.map +1 -1
  32. package/dist/lint/post-synth/blocks.d.ts +53 -0
  33. package/dist/lint/post-synth/blocks.d.ts.map +1 -0
  34. package/dist/lint/post-synth/fixtures/load.d.ts +37 -0
  35. package/dist/lint/post-synth/fixtures/load.d.ts.map +1 -0
  36. package/dist/lint/post-synth/index.d.ts.map +1 -1
  37. package/dist/lint/post-synth/module-source.d.ts +38 -0
  38. package/dist/lint/post-synth/module-source.d.ts.map +1 -0
  39. package/dist/lint/post-synth/scope.d.ts +65 -0
  40. package/dist/lint/post-synth/scope.d.ts.map +1 -0
  41. package/dist/lint/post-synth/tf001.d.ts +18 -0
  42. package/dist/lint/post-synth/tf001.d.ts.map +1 -1
  43. package/dist/lint/post-synth/tf002.d.ts +32 -0
  44. package/dist/lint/post-synth/tf002.d.ts.map +1 -0
  45. package/dist/lint/post-synth/tf003.d.ts +22 -0
  46. package/dist/lint/post-synth/tf003.d.ts.map +1 -0
  47. package/dist/lint/post-synth/tf004.d.ts +22 -0
  48. package/dist/lint/post-synth/tf004.d.ts.map +1 -0
  49. package/dist/lint/post-synth/tf005.d.ts +28 -0
  50. package/dist/lint/post-synth/tf005.d.ts.map +1 -0
  51. package/dist/lint/post-synth/tf006.d.ts +21 -0
  52. package/dist/lint/post-synth/tf006.d.ts.map +1 -0
  53. package/dist/lint/post-synth/tf007.d.ts +21 -0
  54. package/dist/lint/post-synth/tf007.d.ts.map +1 -0
  55. package/dist/lint/post-synth/tf008.d.ts +22 -0
  56. package/dist/lint/post-synth/tf008.d.ts.map +1 -0
  57. package/dist/lint/post-synth/tf009.d.ts +24 -0
  58. package/dist/lint/post-synth/tf009.d.ts.map +1 -0
  59. package/dist/lint/post-synth/tf010.d.ts +19 -0
  60. package/dist/lint/post-synth/tf010.d.ts.map +1 -0
  61. package/dist/lint/post-synth/tf011.d.ts +18 -0
  62. package/dist/lint/post-synth/tf011.d.ts.map +1 -0
  63. package/dist/lint/post-synth/tf012.d.ts +17 -0
  64. package/dist/lint/post-synth/tf012.d.ts.map +1 -0
  65. package/dist/lint/post-synth/tf013.d.ts +21 -0
  66. package/dist/lint/post-synth/tf013.d.ts.map +1 -0
  67. package/dist/lint/post-synth/tf014.d.ts +25 -0
  68. package/dist/lint/post-synth/tf014.d.ts.map +1 -0
  69. package/dist/lint/post-synth/tf015.d.ts +21 -0
  70. package/dist/lint/post-synth/tf015.d.ts.map +1 -0
  71. package/dist/lint/post-synth/tf016.d.ts +30 -0
  72. package/dist/lint/post-synth/tf016.d.ts.map +1 -0
  73. package/dist/lint/post-synth/tf017.d.ts +19 -0
  74. package/dist/lint/post-synth/tf017.d.ts.map +1 -0
  75. package/dist/lint/post-synth/tf018.d.ts +20 -0
  76. package/dist/lint/post-synth/tf018.d.ts.map +1 -0
  77. package/dist/lint/post-synth/tf019.d.ts +25 -0
  78. package/dist/lint/post-synth/tf019.d.ts.map +1 -0
  79. package/dist/lint/post-synth/tf020.d.ts +38 -0
  80. package/dist/lint/post-synth/tf020.d.ts.map +1 -0
  81. package/dist/lint/post-synth/tf021.d.ts +41 -0
  82. package/dist/lint/post-synth/tf021.d.ts.map +1 -0
  83. package/dist/lint/post-synth/tf022.d.ts +24 -0
  84. package/dist/lint/post-synth/tf022.d.ts.map +1 -0
  85. package/dist/lint/post-synth/tf024.d.ts +24 -0
  86. package/dist/lint/post-synth/tf024.d.ts.map +1 -0
  87. package/dist/lint/post-synth/tf025.d.ts +29 -0
  88. package/dist/lint/post-synth/tf025.d.ts.map +1 -0
  89. package/dist/lint/post-synth/tf026.d.ts +21 -0
  90. package/dist/lint/post-synth/tf026.d.ts.map +1 -0
  91. package/dist/lint/rules/fixtures/load.d.ts +13 -0
  92. package/dist/lint/rules/fixtures/load.d.ts.map +1 -0
  93. package/dist/lint/rules/plan-before-apply.d.ts +11 -0
  94. package/dist/lint/rules/plan-before-apply.d.ts.map +1 -1
  95. package/dist/lint/secret-shape.d.ts +68 -0
  96. package/dist/lint/secret-shape.d.ts.map +1 -0
  97. package/dist/live-ownership.d.ts +55 -0
  98. package/dist/live-ownership.d.ts.map +1 -0
  99. package/dist/lsp/option-keys.d.ts.map +1 -1
  100. package/dist/manifest.json +1 -1
  101. package/dist/okf/index.md +24 -0
  102. package/dist/okf/rules/TF002.md +11 -0
  103. package/dist/okf/rules/TF003.md +11 -0
  104. package/dist/okf/rules/TF004.md +11 -0
  105. package/dist/okf/rules/TF005.md +11 -0
  106. package/dist/okf/rules/TF006.md +11 -0
  107. package/dist/okf/rules/TF007.md +11 -0
  108. package/dist/okf/rules/TF008.md +11 -0
  109. package/dist/okf/rules/TF009.md +11 -0
  110. package/dist/okf/rules/TF010.md +11 -0
  111. package/dist/okf/rules/TF011.md +11 -0
  112. package/dist/okf/rules/TF012.md +11 -0
  113. package/dist/okf/rules/TF013.md +11 -0
  114. package/dist/okf/rules/TF014.md +11 -0
  115. package/dist/okf/rules/TF015.md +11 -0
  116. package/dist/okf/rules/TF016.md +11 -0
  117. package/dist/okf/rules/TF017.md +11 -0
  118. package/dist/okf/rules/TF018.md +11 -0
  119. package/dist/okf/rules/TF019.md +11 -0
  120. package/dist/okf/rules/TF020.md +11 -0
  121. package/dist/okf/rules/TF021.md +11 -0
  122. package/dist/okf/rules/TF022.md +11 -0
  123. package/dist/okf/rules/TF024.md +11 -0
  124. package/dist/okf/rules/TF025.md +11 -0
  125. package/dist/okf/rules/TF026.md +11 -0
  126. package/dist/op/activities/index.d.ts +13 -6
  127. package/dist/op/activities/index.d.ts.map +1 -1
  128. package/dist/op/activities/live-detect.d.ts +51 -0
  129. package/dist/op/activities/live-detect.d.ts.map +1 -0
  130. package/dist/op/activities/terraform.d.ts +445 -17
  131. package/dist/op/activities/terraform.d.ts.map +1 -1
  132. package/dist/op/activity-contracts.d.ts +148 -0
  133. package/dist/op/activity-contracts.d.ts.map +1 -0
  134. package/dist/op/adoption.d.ts +118 -0
  135. package/dist/op/adoption.d.ts.map +1 -0
  136. package/dist/op/builders.d.ts +46 -6
  137. package/dist/op/builders.d.ts.map +1 -1
  138. package/dist/op/resolve-root-mode.d.ts +45 -0
  139. package/dist/op/resolve-root-mode.d.ts.map +1 -0
  140. package/dist/plugin.d.ts.map +1 -1
  141. package/dist/rules/blocks.ts +109 -0
  142. package/dist/rules/module-source.ts +121 -0
  143. package/dist/rules/plan-before-apply.ts +11 -0
  144. package/dist/rules/scope.ts +107 -0
  145. package/dist/rules/tf001.ts +32 -8
  146. package/dist/rules/tf002.ts +151 -0
  147. package/dist/rules/tf003.ts +71 -0
  148. package/dist/rules/tf004.ts +63 -0
  149. package/dist/rules/tf005.ts +76 -0
  150. package/dist/rules/tf006.ts +53 -0
  151. package/dist/rules/tf007.ts +78 -0
  152. package/dist/rules/tf008.ts +75 -0
  153. package/dist/rules/tf009.ts +59 -0
  154. package/dist/rules/tf010.ts +51 -0
  155. package/dist/rules/tf011.ts +49 -0
  156. package/dist/rules/tf012.ts +47 -0
  157. package/dist/rules/tf013.ts +63 -0
  158. package/dist/rules/tf014.ts +72 -0
  159. package/dist/rules/tf015.ts +63 -0
  160. package/dist/rules/tf016.ts +75 -0
  161. package/dist/rules/tf017.ts +50 -0
  162. package/dist/rules/tf018.ts +72 -0
  163. package/dist/rules/tf019.ts +72 -0
  164. package/dist/rules/tf020.ts +123 -0
  165. package/dist/rules/tf021.ts +126 -0
  166. package/dist/rules/tf022.ts +61 -0
  167. package/dist/rules/tf024.ts +70 -0
  168. package/dist/rules/tf025.ts +101 -0
  169. package/dist/rules/tf026.ts +80 -0
  170. package/dist/skills/chant-terraform.md +1 -1
  171. package/package.json +7 -2
  172. package/src/__fixtures__/live/main.tf +28 -0
  173. package/src/__fixtures__/live-adopt/estate.chdf.hcl +6 -0
  174. package/src/__fixtures__/live-adopt/main.tf +21 -0
  175. package/src/__fixtures__/live-estate/README.md +58 -0
  176. package/src/__fixtures__/live-estate/compute.tf +10 -0
  177. package/src/__fixtures__/live-estate/estate.chdf.hcl +1 -0
  178. package/src/__fixtures__/live-estate/logs.tf +8 -0
  179. package/src/__fixtures__/live-estate/network.tf +24 -0
  180. package/src/__fixtures__/live-estate/rule.tf +11 -0
  181. package/src/__fixtures__/live-estate/unowned.tf +21 -0
  182. package/src/__fixtures__/live-estate/versions.tf +31 -0
  183. package/src/__fixtures__/live-ls.json +98 -0
  184. package/src/__fixtures__/live-plan.json +285 -0
  185. package/src/__fixtures__/live-sidecar/estate.chdf.hcl +3 -0
  186. package/src/__fixtures__/live-sidecar/main.tf +24 -0
  187. package/src/__fixtures__/module-tree/README.md +16 -0
  188. package/src/__fixtures__/module-tree/cycle/main.tf +3 -0
  189. package/src/__fixtures__/module-tree/cycle/modules/a/main.tf +9 -0
  190. package/src/__fixtures__/module-tree/cycle/modules/b/main.tf +9 -0
  191. package/src/__fixtures__/module-tree/missing/main.tf +9 -0
  192. package/src/__fixtures__/module-tree/outside/root/main.tf +9 -0
  193. package/src/__fixtures__/module-tree/root/main.tf +28 -0
  194. package/src/__fixtures__/module-tree/root/modules/cdn/main.tf +15 -0
  195. package/src/__fixtures__/module-tree/root/modules/cdn/modules/bucket/main.tf +11 -0
  196. package/src/__fixtures__/module-tree/shared/main.tf +5 -0
  197. package/src/__fixtures__/references/main.tf +56 -0
  198. package/src/codegen/lint-rules-page.test.ts +28 -0
  199. package/src/composites/terraform-adopt-op.acceptance.test.ts +166 -0
  200. package/src/composites/terraform-adopt-op.test.ts +170 -0
  201. package/src/composites/terraform-adopt-op.ts +197 -0
  202. package/src/composites/terraform-apply-op.acceptance.test.ts +183 -5
  203. package/src/composites/terraform-apply-op.test.ts +167 -15
  204. package/src/composites/terraform-apply-op.ts +105 -28
  205. package/src/composites/terraform-watch-op.test.ts +85 -26
  206. package/src/composites/terraform-watch-op.ts +130 -73
  207. package/src/config.ts +32 -3
  208. package/src/describe-resources.live.test.ts +598 -0
  209. package/src/describe-resources.test.ts +53 -0
  210. package/src/describe-resources.ts +811 -28
  211. package/src/hcl/descend.test.ts +162 -0
  212. package/src/hcl/descend.ts +273 -0
  213. package/src/hcl/marker.test.ts +147 -0
  214. package/src/hcl/marker.ts +235 -0
  215. package/src/hcl/parse.test.ts +69 -0
  216. package/src/hcl/parse.ts +249 -27
  217. package/src/hcl/references.test.ts +116 -0
  218. package/src/hcl/references.ts +172 -0
  219. package/src/hcl/roots.ts +59 -5
  220. package/src/hcl/suppressions.test.ts +147 -0
  221. package/src/hcl/suppressions.ts +204 -0
  222. package/src/hcl/value.test.ts +73 -0
  223. package/src/hcl/value.ts +106 -0
  224. package/src/index.ts +31 -1
  225. package/src/lint/audit-catalog.ts +264 -3
  226. package/src/lint/audit-lineage.ts +319 -1
  227. package/src/lint/audit.test.ts +40 -1
  228. package/src/lint/post-synth/blocks.ts +109 -0
  229. package/src/lint/post-synth/fixtures/TF001/negative.tf +28 -0
  230. package/src/lint/post-synth/fixtures/TF001/positive.tf +24 -0
  231. package/src/lint/post-synth/fixtures/TF002/negative.tf +17 -0
  232. package/src/lint/post-synth/fixtures/TF002/positive-legacy.tf +10 -0
  233. package/src/lint/post-synth/fixtures/TF002/positive-no-version.tf +12 -0
  234. package/src/lint/post-synth/fixtures/TF002/positive.tf +7 -0
  235. package/src/lint/post-synth/fixtures/TF003/negative.tf +10 -0
  236. package/src/lint/post-synth/fixtures/TF003/positive.tf +8 -0
  237. package/src/lint/post-synth/fixtures/TF004/negative-git.tf +3 -0
  238. package/src/lint/post-synth/fixtures/TF004/negative-local.tf +3 -0
  239. package/src/lint/post-synth/fixtures/TF004/negative.tf +4 -0
  240. package/src/lint/post-synth/fixtures/TF004/positive.tf +3 -0
  241. package/src/lint/post-synth/fixtures/TF005/negative-local.tf +3 -0
  242. package/src/lint/post-synth/fixtures/TF005/negative-registry.tf +3 -0
  243. package/src/lint/post-synth/fixtures/TF005/negative-sha.tf +3 -0
  244. package/src/lint/post-synth/fixtures/TF005/negative.tf +3 -0
  245. package/src/lint/post-synth/fixtures/TF005/positive-branch.tf +3 -0
  246. package/src/lint/post-synth/fixtures/TF005/positive-non-semver.tf +3 -0
  247. package/src/lint/post-synth/fixtures/TF005/positive.tf +3 -0
  248. package/src/lint/post-synth/fixtures/TF006/negative.tf +5 -0
  249. package/src/lint/post-synth/fixtures/TF006/positive-empty-default.tf +11 -0
  250. package/src/lint/post-synth/fixtures/TF006/positive.tf +6 -0
  251. package/src/lint/post-synth/fixtures/TF007/negative.tf +10 -0
  252. package/src/lint/post-synth/fixtures/TF007/positive.tf +9 -0
  253. package/src/lint/post-synth/fixtures/TF008/negative-variable.tf +10 -0
  254. package/src/lint/post-synth/fixtures/TF008/negative.tf +8 -0
  255. package/src/lint/post-synth/fixtures/TF008/positive-other-provider.tf +10 -0
  256. package/src/lint/post-synth/fixtures/TF008/positive.tf +5 -0
  257. package/src/lint/post-synth/fixtures/TF009/negative.tf +10 -0
  258. package/src/lint/post-synth/fixtures/TF009/positive.tf +4 -0
  259. package/src/lint/post-synth/fixtures/TF010/negative.tf +5 -0
  260. package/src/lint/post-synth/fixtures/TF010/positive.tf +4 -0
  261. package/src/lint/post-synth/fixtures/TF011/negative.tf +5 -0
  262. package/src/lint/post-synth/fixtures/TF011/positive-blank.tf +5 -0
  263. package/src/lint/post-synth/fixtures/TF011/positive.tf +4 -0
  264. package/src/lint/post-synth/fixtures/TF012/negative.tf +4 -0
  265. package/src/lint/post-synth/fixtures/TF012/positive.tf +3 -0
  266. package/src/lint/post-synth/fixtures/TF013/negative.tf +8 -0
  267. package/src/lint/post-synth/fixtures/TF013/positive-data.tf +8 -0
  268. package/src/lint/post-synth/fixtures/TF013/positive.tf +8 -0
  269. package/src/lint/post-synth/fixtures/TF014/negative/main.tf +26 -0
  270. package/src/lint/post-synth/fixtures/TF014/negative/modules/cdn/main.tf +19 -0
  271. package/src/lint/post-synth/fixtures/TF014/positive/main.tf +16 -0
  272. package/src/lint/post-synth/fixtures/TF014/positive/modules/cdn/main.tf +15 -0
  273. package/src/lint/post-synth/fixtures/TF015/negative/main.tf +13 -0
  274. package/src/lint/post-synth/fixtures/TF015/negative/modules/cdn/main.tf +16 -0
  275. package/src/lint/post-synth/fixtures/TF015/positive/main.tf +13 -0
  276. package/src/lint/post-synth/fixtures/TF015/positive/modules/cdn/main.tf +14 -0
  277. package/src/lint/post-synth/fixtures/TF016/negative-template.tf +7 -0
  278. package/src/lint/post-synth/fixtures/TF016/negative.tf +16 -0
  279. package/src/lint/post-synth/fixtures/TF016/positive-shared-file.tf +12 -0
  280. package/src/lint/post-synth/fixtures/TF016/positive.tf +13 -0
  281. package/src/lint/post-synth/fixtures/TF017/negative.tf +4 -0
  282. package/src/lint/post-synth/fixtures/TF017/positive.tf +5 -0
  283. package/src/lint/post-synth/fixtures/TF018/negative.tf +9 -0
  284. package/src/lint/post-synth/fixtures/TF018/positive-data.tf +9 -0
  285. package/src/lint/post-synth/fixtures/TF018/positive.tf +4 -0
  286. package/src/lint/post-synth/fixtures/TF019/negative.tf +14 -0
  287. package/src/lint/post-synth/fixtures/TF019/positive-output.tf +5 -0
  288. package/src/lint/post-synth/fixtures/TF019/positive.tf +16 -0
  289. package/src/lint/post-synth/fixtures/TF020/negative.tf +43 -0
  290. package/src/lint/post-synth/fixtures/TF020/positive.tf +32 -0
  291. package/src/lint/post-synth/fixtures/TF020/scoped/main.tf +21 -0
  292. package/src/lint/post-synth/fixtures/TF020/scoped/modules/cdn/main.tf +13 -0
  293. package/src/lint/post-synth/fixtures/TF021/negative.tf +31 -0
  294. package/src/lint/post-synth/fixtures/TF021/positive-length.tf +8 -0
  295. package/src/lint/post-synth/fixtures/TF021/positive.tf +8 -0
  296. package/src/lint/post-synth/fixtures/TF022/negative-locator.tf +7 -0
  297. package/src/lint/post-synth/fixtures/TF022/negative.tf +10 -0
  298. package/src/lint/post-synth/fixtures/TF022/positive-nested.tf +10 -0
  299. package/src/lint/post-synth/fixtures/TF022/positive.tf +6 -0
  300. package/src/lint/post-synth/fixtures/TF023/negative/main.tf +15 -0
  301. package/src/lint/post-synth/fixtures/TF023/positive/main.tf +9 -0
  302. package/src/lint/post-synth/fixtures/TF023/positive/state.json +24 -0
  303. package/src/lint/post-synth/fixtures/TF024/live-clean/main.tf +9 -0
  304. package/src/lint/post-synth/fixtures/TF024/live-with-backend/main.tf +14 -0
  305. package/src/lint/post-synth/fixtures/TF024/live-with-cloud/main.tf +16 -0
  306. package/src/lint/post-synth/fixtures/TF025/live-clean/main.tf +13 -0
  307. package/src/lint/post-synth/fixtures/TF025/live-with-workspace-ref/main.tf +13 -0
  308. package/src/lint/post-synth/fixtures/TF026/live-default/main.tf +9 -0
  309. package/src/lint/post-synth/fixtures/TF026/live-explicit-delete/main.tf +13 -0
  310. package/src/lint/post-synth/fixtures/TF026/live-keep/main.tf +13 -0
  311. package/src/lint/post-synth/fixtures/load.ts +108 -0
  312. package/src/lint/post-synth/index.ts +48 -0
  313. package/src/lint/post-synth/module-rules.test.ts +153 -0
  314. package/src/lint/post-synth/module-source.test.ts +75 -0
  315. package/src/lint/post-synth/module-source.ts +121 -0
  316. package/src/lint/post-synth/post-synth.test.ts +14 -15
  317. package/src/lint/post-synth/scope.ts +107 -0
  318. package/src/lint/post-synth/single-block.test.ts +263 -0
  319. package/src/lint/post-synth/suppression.test.ts +110 -0
  320. package/src/lint/post-synth/tf001.ts +32 -8
  321. package/src/lint/post-synth/tf002.test.ts +93 -0
  322. package/src/lint/post-synth/tf002.ts +151 -0
  323. package/src/lint/post-synth/tf003.test.ts +75 -0
  324. package/src/lint/post-synth/tf003.ts +71 -0
  325. package/src/lint/post-synth/tf004.test.ts +47 -0
  326. package/src/lint/post-synth/tf004.ts +63 -0
  327. package/src/lint/post-synth/tf005.test.ts +57 -0
  328. package/src/lint/post-synth/tf005.ts +76 -0
  329. package/src/lint/post-synth/tf006.ts +53 -0
  330. package/src/lint/post-synth/tf007.ts +78 -0
  331. package/src/lint/post-synth/tf008.ts +75 -0
  332. package/src/lint/post-synth/tf009.ts +59 -0
  333. package/src/lint/post-synth/tf010.ts +51 -0
  334. package/src/lint/post-synth/tf011.ts +49 -0
  335. package/src/lint/post-synth/tf012.ts +47 -0
  336. package/src/lint/post-synth/tf013.ts +63 -0
  337. package/src/lint/post-synth/tf014.ts +72 -0
  338. package/src/lint/post-synth/tf015.ts +63 -0
  339. package/src/lint/post-synth/tf016.ts +75 -0
  340. package/src/lint/post-synth/tf017.ts +50 -0
  341. package/src/lint/post-synth/tf018.ts +72 -0
  342. package/src/lint/post-synth/tf019.ts +72 -0
  343. package/src/lint/post-synth/tf020.ts +123 -0
  344. package/src/lint/post-synth/tf021.ts +126 -0
  345. package/src/lint/post-synth/tf022.ts +61 -0
  346. package/src/lint/post-synth/tf024.test.ts +42 -0
  347. package/src/lint/post-synth/tf024.ts +70 -0
  348. package/src/lint/post-synth/tf025.test.ts +46 -0
  349. package/src/lint/post-synth/tf025.ts +101 -0
  350. package/src/lint/post-synth/tf026.test.ts +56 -0
  351. package/src/lint/post-synth/tf026.ts +80 -0
  352. package/src/lint/presets.test.ts +72 -0
  353. package/src/lint/rules/fixtures/TF101/negative-op.ts +2 -0
  354. package/src/lint/rules/fixtures/TF101/positive-op.ts +2 -0
  355. package/src/lint/rules/fixtures/load.ts +53 -0
  356. package/src/lint/rules/plan-before-apply.test.ts +112 -7
  357. package/src/lint/rules/plan-before-apply.ts +11 -0
  358. package/src/lint/secret-shape.test.ts +150 -0
  359. package/src/lint/secret-shape.ts +159 -0
  360. package/src/live-ownership.ts +60 -0
  361. package/src/lsp/completions.test.ts +3 -2
  362. package/src/lsp/option-keys.ts +10 -5
  363. package/src/op/activities/choudoufu.acceptance.test.ts +143 -0
  364. package/src/op/activities/choudoufu.test.ts +693 -0
  365. package/src/op/activities/index.ts +40 -4
  366. package/src/op/activities/live-detect.test.ts +143 -0
  367. package/src/op/activities/live-detect.ts +162 -0
  368. package/src/op/activities/registry.test.ts +11 -3
  369. package/src/op/activities/terraform.test.ts +7 -3
  370. package/src/op/activities/terraform.ts +823 -69
  371. package/src/op/activity-contracts.test.ts +147 -0
  372. package/src/op/activity-contracts.ts +178 -0
  373. package/src/op/adoption.test.ts +170 -0
  374. package/src/op/adoption.ts +241 -0
  375. package/src/op/builders.test.ts +60 -5
  376. package/src/op/builders.ts +93 -5
  377. package/src/op/resolve-root-mode.test.ts +85 -0
  378. package/src/op/resolve-root-mode.ts +72 -0
  379. package/src/plugin.test.ts +68 -0
  380. package/src/plugin.ts +75 -9
  381. package/src/skills/chant-terraform.md +1 -1
package/src/hcl/parse.ts CHANGED
@@ -18,14 +18,20 @@
18
18
  * changed in `packages/core/package.json` for this.
19
19
  */
20
20
 
21
- import { readdirSync, readFileSync } from "node:fs";
21
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
22
22
  import { join } from "node:path";
23
23
  import { loadHcl2json, type Hcl2Json } from "@intentius/chant/terraform/parse";
24
24
  import { DECLARABLE_MARKER, type Declarable } from "@intentius/chant/declarable";
25
+ import type { SuppressionDirective } from "@intentius/chant/lint/suppressions";
26
+ import type { TerraformDeleteMode } from "../config";
27
+ import { scanSuppressions, directivesFor, type FileScan } from "./suppressions";
25
28
 
26
29
  /** A parsed HCL block body, as `@cdktf/hcl2json` encodes it. */
27
30
  export type BlockBody = Record<string, unknown>;
28
31
 
32
+ /** Whether a root runs under choudoufu with a declared estate (#2103). */
33
+ export type TerraformRootMode = "live" | "state";
34
+
29
35
  /** The entity every block becomes. `props` is what post-synth checks read. */
30
36
  export interface TerraformEntity extends Declarable {
31
37
  readonly lexicon: "terraform";
@@ -39,7 +45,70 @@ export interface TerraformEntity extends Declarable {
39
45
  readonly file: string;
40
46
  /** Configured root name this block belongs to. */
41
47
  readonly root: string;
48
+ /**
49
+ * The chain of `module.<name>` calls that reached this block, outermost
50
+ * first (chant #2112). Empty or absent on a block declared in the root
51
+ * module itself; `["module.cdn"]` on a block of the module `./modules/cdn`
52
+ * called as `cdn` from the root; `["module.cdn", "module.bucket"]` one
53
+ * level deeper. The same chain is in the entity key, which is
54
+ * `<root>/module.cdn/<address>` for the first case, so a reader can tell
55
+ * root scope from child scope from either.
56
+ *
57
+ * The names are the CALL names (`module.<label>`), not the directory the
58
+ * source points at, matching `terraform show -json`'s `child_modules[]`
59
+ * addresses, choudoufu's marker grammar, and tflint's `Callers:` chain.
60
+ */
61
+ readonly callers?: readonly string[];
62
+ /**
63
+ * Whether this root is live: `terraform.binary` is `"choudoufu"` and an
64
+ * estate is declared (a `live` block or an `estate.chdf.hcl` sidecar).
65
+ * Set on every entity of the root, not just the `Terraform::Live` one, so
66
+ * a post-synth check or `describeResources()` reads it without
67
+ * re-parsing (#2103). Absent when the caller supplied no `binary` (the
68
+ * `chant audit` content path, which has no project config to read).
69
+ */
70
+ readonly mode?: TerraformRootMode;
71
+ /** The declared estate name. Present only when `mode` is `"live"`. */
72
+ readonly estate?: string;
73
+ /** `terraform.roots.<name>.workspace`, recorded so TF025 can flag a non-default one on a live root. */
74
+ readonly workspace?: string;
75
+ /**
76
+ * `terraform.roots.<name>.delete` (#2106), recorded so TF026 can check a
77
+ * live root declaring `"never"` against its `policy` block's
78
+ * `undeclared_tagged` setting. Present regardless of mode, same as
79
+ * `workspace`; only a live root's TF026 reads it.
80
+ */
81
+ readonly delete?: TerraformDeleteMode;
82
+ /**
83
+ * 1-based line the block's header starts on, from a line scan over the
84
+ * raw source (chant #2111): hcl2json exposes no ranges to read this from
85
+ * instead (see `../hcl/suppressions.ts`). Undefined when the scan's block
86
+ * regex didn't recognize the header (unusual formatting); a suppression
87
+ * comment simply can't anchor to that block then.
88
+ */
89
+ readonly line?: number;
90
+ /**
91
+ * The raw text of the file this block came from, verbatim.
92
+ *
93
+ * Every other field here is post-`hcl2json`, and that parse is lossy in
94
+ * one way that matters: it renders a bare reference (`value = var.x`) and
95
+ * a quoted interpolation (`value = "${var.x}"`) as the same string,
96
+ * `"${var.x}"`. The second is the deprecated pre-0.12 form TF016 reports
97
+ * and the first is idiomatic, so a check written against `body` alone
98
+ * would flag every reference in the root. The source text is where the
99
+ * quotes still exist. Empty when a caller built the entity by hand.
100
+ */
101
+ readonly source: string;
42
102
  };
103
+ /**
104
+ * `# chant-ignore`/`chant-ignore-file`/`chant-ignore-block` directives that
105
+ * apply to this entity (chant #2111): the ones anchored to its own block,
106
+ * plus any file-level one. Read generically by
107
+ * `@intentius/chant/lint/suppressions`'s `entitySuppressions()`; see that
108
+ * module's doc comment for why this is a plain duck-typed field rather than
109
+ * a `PostSynthContext` addition.
110
+ */
111
+ readonly suppressions?: readonly SuppressionDirective[];
43
112
  }
44
113
 
45
114
  /** `entityType` per block kind. */
@@ -51,6 +120,11 @@ export const MODULE_TYPE = "Terraform::Module";
51
120
  export const VARIABLE_TYPE = "Terraform::Variable";
52
121
  export const OUTPUT_TYPE = "Terraform::Output";
53
122
  export const LOCALS_TYPE = "Terraform::Locals";
123
+ /** A `live { estate = "..." }` block, or an `estate.chdf.hcl` sidecar's content (#2103). */
124
+ export const LIVE_TYPE = "Terraform::Live";
125
+
126
+ /** The sidecar filename choudoufu reads an estate declaration from when no in-block `live { }` is used. */
127
+ export const LIVE_SIDECAR_FILENAME = "estate.chdf.hcl";
54
128
 
55
129
  /**
56
130
  * Build one entity. Written out rather than run through `createResource`
@@ -66,13 +140,30 @@ export function terraformEntity(
66
140
  body: BlockBody,
67
141
  file: string,
68
142
  root: string,
143
+ extra?: { mode?: TerraformRootMode; estate?: string; workspace?: string; delete?: TerraformDeleteMode; callers?: readonly string[] },
144
+ source: string = "",
145
+ line?: number,
146
+ suppressions?: readonly SuppressionDirective[],
69
147
  ): TerraformEntity {
70
148
  return {
71
149
  [DECLARABLE_MARKER]: true,
72
150
  lexicon: "terraform",
73
151
  entityType,
74
152
  kind: "resource",
75
- props: { address, body, file, root },
153
+ props: {
154
+ address,
155
+ body,
156
+ file,
157
+ root,
158
+ source,
159
+ line,
160
+ ...(extra?.mode !== undefined ? { mode: extra.mode } : {}),
161
+ ...(extra?.estate !== undefined ? { estate: extra.estate } : {}),
162
+ ...(extra?.workspace !== undefined ? { workspace: extra.workspace } : {}),
163
+ ...(extra?.delete !== undefined ? { delete: extra.delete } : {}),
164
+ ...(extra?.callers !== undefined && extra.callers.length > 0 ? { callers: extra.callers } : {}),
165
+ },
166
+ suppressions,
76
167
  };
77
168
  }
78
169
 
@@ -115,6 +206,18 @@ export function listTerraformFiles(dir: string): string[] {
115
206
  .sort();
116
207
  }
117
208
 
209
+ /**
210
+ * Read the `estate.chdf.hcl` sidecar beside `dir`'s `.tf` files, if present.
211
+ * Sibling to {@link listTerraformFiles}: the sidecar is plain HCL (a bare
212
+ * `estate = "..."` attribute, no wrapper block) and is never a `.tf` file, so
213
+ * it falls outside that glob and needs its own read.
214
+ */
215
+ export function readLiveSidecarFile(dir: string): TerraformFile | undefined {
216
+ const path = join(dir, LIVE_SIDECAR_FILENAME);
217
+ if (!existsSync(path)) return undefined;
218
+ return { name: LIVE_SIDECAR_FILENAME, source: readFileSync(path, "utf-8") };
219
+ }
220
+
118
221
  function asArray(value: unknown): unknown[] {
119
222
  return Array.isArray(value) ? value : [];
120
223
  }
@@ -125,29 +228,85 @@ function asRecord(value: unknown): Record<string, unknown> {
125
228
  : {};
126
229
  }
127
230
 
231
+ /** Every `live { ... }` block nested inside every `terraform { ... }` block of `tree`. */
232
+ function liveBlocksIn(tree: Record<string, unknown>): BlockBody[] {
233
+ const out: BlockBody[] = [];
234
+ for (const tfBody of asArray(tree["terraform"])) {
235
+ const body = asRecord(tfBody);
236
+ for (const liveBody of asArray(body["live"])) out.push(asRecord(liveBody));
237
+ }
238
+ return out;
239
+ }
240
+
241
+ /**
242
+ * Options threading the config-level facts a root's mode depends on into the
243
+ * parse, so `blocksToEntities` can stamp `mode`/`estate`/`workspace` onto
244
+ * every entity without a second pass over the project config (#2103).
245
+ */
246
+ export interface TerraformRootModeOptions {
247
+ /** `terraform.binary`. A root is live only when this is `"choudoufu"` and an estate is declared. */
248
+ binary?: string;
249
+ /** `terraform.roots.<name>.workspace`, recorded verbatim regardless of mode. */
250
+ workspace?: string;
251
+ /** `terraform.roots.<name>.delete`, recorded verbatim regardless of mode (#2106). */
252
+ delete?: TerraformDeleteMode;
253
+ }
254
+
128
255
  /**
129
256
  * Parse a set of files into entities keyed `<root>/<address>`. Two blocks that
130
257
  * genuinely share an address (two `locals` blocks, or the same address in two
131
258
  * files of one root) are numbered `~2`, `~3` rather than overwriting.
259
+ *
260
+ * Recognises both ways an estate is declared: a `live { }` block nested in a
261
+ * `terraform { }` block of any file here, and, when `sidecar` is given, the
262
+ * `estate.chdf.hcl` sidecar's bare `estate = "..."` attribute. Either becomes
263
+ * a {@link LIVE_TYPE} entity, and the root is live (`mode: "live"`) exactly
264
+ * when `modeOptions.binary` is `"choudoufu"` and one of the two named an
265
+ * estate; that verdict, and the estate name when live, is then stamped onto
266
+ * every entity this call returns, sidecar and in-block declaration alike so
267
+ * a post-synth check or `describeResources()` reads it without re-parsing.
268
+ * When both forms are present the sidecar wins, matching choudoufu's own
269
+ * "the sidecar is the leading one" (choudoufu refuses the combination
270
+ * outright; this lexicon does not police that here, see TF024/TF025 for
271
+ * what it does police on a live root).
272
+ *
273
+ * `callers` is the module-call chain these files were reached through
274
+ * (chant #2112), empty for a root module. When it is non-empty the keys
275
+ * become `<root>/module.<name>/<address>` (one segment per caller) and every
276
+ * entity carries the chain on `props.callers`. `./descend.ts` is what fills
277
+ * it in; nothing else should pass it.
132
278
  */
133
279
  export async function blocksToEntities(
134
280
  files: readonly TerraformFile[],
135
281
  root: string,
136
282
  hcl2json?: Hcl2Json,
283
+ modeOptions?: TerraformRootModeOptions,
284
+ sidecar?: TerraformFile,
285
+ callers: readonly string[] = [],
137
286
  ): Promise<Map<string, Declarable>> {
138
287
  const parser = hcl2json ?? (await loadHcl2json());
139
288
  const entities = new Map<string, Declarable>();
289
+ const prefix = callers.length > 0 ? `${root}/${callers.join("/")}` : root;
140
290
 
141
- const add = (entityType: string, address: string, body: unknown, file: string): void => {
291
+ const add = (entityType: string, address: string, body: unknown, file: TerraformFile, scan: FileScan): void => {
292
+ const { line, suppressions } = directivesFor(scan, address);
142
293
  const entity = terraformEntity(
143
294
  entityType,
144
295
  address,
145
296
  (typeof body === "object" && body !== null ? body : {}) as BlockBody,
146
- file,
297
+ file.name,
147
298
  root,
299
+ {
300
+ ...(modeOptions?.workspace !== undefined ? { workspace: modeOptions.workspace } : {}),
301
+ ...(modeOptions?.delete !== undefined ? { delete: modeOptions.delete } : {}),
302
+ ...(callers.length > 0 ? { callers } : {}),
303
+ },
304
+ file.source,
305
+ line,
306
+ suppressions,
148
307
  );
149
- let key = `${root}/${address}`;
150
- for (let n = 2; entities.has(key); n++) key = `${root}/${address}~${n}`;
308
+ let key = `${prefix}/${address}`;
309
+ for (let n = 2; entities.has(key); n++) key = `${prefix}/${address}~${n}`;
151
310
  entities.set(key, entity);
152
311
  };
153
312
 
@@ -156,9 +315,10 @@ export async function blocksToEntities(
156
315
  tree: Record<string, unknown>,
157
316
  section: string,
158
317
  entityType: string,
159
- file: string,
318
+ file: TerraformFile,
319
+ scan: FileScan,
160
320
  ): void => {
161
- for (const body of asArray(tree[section])) add(entityType, section, body, file);
321
+ for (const body of asArray(tree[section])) add(entityType, section, body, file, scan);
162
322
  };
163
323
 
164
324
  /** `provider`, `module`, `variable`, `output`: one label, so `Record<name, body[]>`. */
@@ -167,10 +327,11 @@ export async function blocksToEntities(
167
327
  section: string,
168
328
  entityType: string,
169
329
  address: (name: string) => string,
170
- file: string,
330
+ file: TerraformFile,
331
+ scan: FileScan,
171
332
  ): void => {
172
333
  for (const [name, bodies] of Object.entries(asRecord(tree[section]))) {
173
- for (const body of asArray(bodies)) add(entityType, address(name), body, file);
334
+ for (const body of asArray(bodies)) add(entityType, address(name), body, file, scan);
174
335
  }
175
336
  };
176
337
 
@@ -180,56 +341,117 @@ export async function blocksToEntities(
180
341
  section: string,
181
342
  entityType: string,
182
343
  address: (type: string, name: string) => string,
183
- file: string,
344
+ file: TerraformFile,
345
+ scan: FileScan,
184
346
  ): void => {
185
347
  for (const [type, named] of Object.entries(asRecord(tree[section]))) {
186
348
  for (const [name, bodies] of Object.entries(asRecord(named))) {
187
- for (const body of asArray(bodies)) add(entityType, address(type, name), body, file);
349
+ for (const body of asArray(bodies)) add(entityType, address(type, name), body, file, scan);
188
350
  }
189
351
  }
190
352
  };
191
353
 
192
354
  for (const file of files) {
193
355
  const tree = (await parser.parse(file.name, file.source)) as Record<string, unknown>;
194
- unlabelled(tree, "terraform", TERRAFORM_TYPE, file.name);
195
- unlabelled(tree, "locals", LOCALS_TYPE, file.name);
196
- oneLabel(tree, "provider", PROVIDER_TYPE, (n) => `provider.${n}`, file.name);
197
- oneLabel(tree, "module", MODULE_TYPE, (n) => `module.${n}`, file.name);
198
- oneLabel(tree, "variable", VARIABLE_TYPE, (n) => `var.${n}`, file.name);
199
- oneLabel(tree, "output", OUTPUT_TYPE, (n) => `output.${n}`, file.name);
200
- twoLabels(tree, "resource", RESOURCE_TYPE, (t, n) => `${t}.${n}`, file.name);
201
- twoLabels(tree, "data", DATA_TYPE, (t, n) => `data.${t}.${n}`, file.name);
356
+ const scan = scanSuppressions(file.name, file.source);
357
+ unlabelled(tree, "terraform", TERRAFORM_TYPE, file, scan);
358
+ for (const liveBody of liveBlocksIn(tree)) add(LIVE_TYPE, "live", liveBody, file, scan);
359
+ unlabelled(tree, "locals", LOCALS_TYPE, file, scan);
360
+ oneLabel(tree, "provider", PROVIDER_TYPE, (n) => `provider.${n}`, file, scan);
361
+ oneLabel(tree, "module", MODULE_TYPE, (n) => `module.${n}`, file, scan);
362
+ oneLabel(tree, "variable", VARIABLE_TYPE, (n) => `var.${n}`, file, scan);
363
+ oneLabel(tree, "output", OUTPUT_TYPE, (n) => `output.${n}`, file, scan);
364
+ twoLabels(tree, "resource", RESOURCE_TYPE, (t, n) => `${t}.${n}`, file, scan);
365
+ twoLabels(tree, "data", DATA_TYPE, (t, n) => `data.${t}.${n}`, file, scan);
366
+ }
367
+
368
+ if (sidecar) {
369
+ const tree = (await parser.parse(sidecar.name, sidecar.source)) as Record<string, unknown>;
370
+ if (typeof tree["estate"] === "string") add(LIVE_TYPE, "live", tree, sidecar, scanSuppressions(sidecar.name, sidecar.source));
371
+ }
372
+
373
+ // Which of the (at most two) Live entities names the estate, preferring the
374
+ // sidecar over an in-block declaration when both are present.
375
+ let estate: string | undefined;
376
+ for (const entity of entities.values()) {
377
+ if (entity.entityType !== LIVE_TYPE) continue;
378
+ const props = (entity as TerraformEntity).props;
379
+ const name = typeof props.body["estate"] === "string" ? (props.body["estate"] as string) : undefined;
380
+ if (name === undefined) continue;
381
+ if (estate === undefined || props.file === LIVE_SIDECAR_FILENAME) estate = name;
382
+ }
383
+
384
+ const mode: TerraformRootMode = modeOptions?.binary === "choudoufu" && estate !== undefined ? "live" : "state";
385
+ for (const [key, entity] of entities) {
386
+ const te = entity as TerraformEntity;
387
+ const stamped: TerraformEntity = {
388
+ ...te,
389
+ props: { ...te.props, mode, ...(mode === "live" ? { estate } : {}) },
390
+ };
391
+ entities.set(key, stamped);
202
392
  }
203
393
 
204
394
  return entities;
205
395
  }
206
396
 
207
397
  /**
208
- * Parse a root module directory. Reads every `.tf` directly under `dir`.
209
- * Throws whatever the parser throws for malformed HCL; `buildRoots()` is where
210
- * that becomes a warning.
398
+ * Parse a root module directory. Reads every `.tf` directly under `dir`, plus
399
+ * the `estate.chdf.hcl` sidecar beside them when present. Throws whatever the
400
+ * parser throws for malformed HCL; `buildRoots()` is where that becomes a
401
+ * warning.
211
402
  */
212
403
  export async function parseTerraformRootDir(
213
404
  dir: string,
214
405
  root: string,
215
406
  hcl2json?: Hcl2Json,
407
+ modeOptions?: TerraformRootModeOptions,
408
+ callers: readonly string[] = [],
216
409
  ): Promise<Map<string, Declarable>> {
217
410
  const files = listTerraformFiles(dir).map((name) => ({
218
411
  name,
219
412
  source: readFileSync(join(dir, name), "utf-8"),
220
413
  }));
221
- return blocksToEntities(files, root, hcl2json);
414
+ // A child module gets no sidecar read: `estate.chdf.hcl` declares the
415
+ // estate a ROOT runs against, and choudoufu reads it from the root's own
416
+ // directory only. See `./descend.ts`.
417
+ const sidecar = callers.length === 0 ? readLiveSidecarFile(dir) : undefined;
418
+ return blocksToEntities(files, root, hcl2json, modeOptions, sidecar, callers);
222
419
  }
223
420
 
224
421
  /**
225
422
  * Parse the joined-file string form `chant audit` produces for a discovered
226
- * root module (`AuditInput.content`). Unwired for now: #2085's
227
- * `auditEntities()` is what calls it.
423
+ * root module (`AuditInput.content`). `auditEntities()` is what calls it; that
424
+ * hook's single-argument contract carries no project config, so `modeOptions`
425
+ * is left undefined there and every entity gets `mode: "state"`; the audit
426
+ * path does not detect live mode (#2103). `modeOptions` exists here mainly for
427
+ * tests exercising the mode-stamping behaviour directly against inline HCL,
428
+ * the same way `parseTerraformRootDir`'s callers exercise it against files.
228
429
  */
229
430
  export async function parseTerraformRootContent(
230
431
  content: string,
231
432
  root: string,
232
433
  hcl2json?: Hcl2Json,
434
+ modeOptions?: TerraformRootModeOptions,
233
435
  ): Promise<Map<string, Declarable>> {
234
- return blocksToEntities(splitBundleContent(content), root, hcl2json);
436
+ return blocksToEntities(splitBundleContent(content), root, hcl2json, modeOptions);
437
+ }
438
+
439
+ /**
440
+ * The module scope an entity key names: the part before the address.
441
+ *
442
+ * `"app/aws_s3_bucket.assets"` is scope `"app"` (the root module);
443
+ * `"app/module.cdn/aws_s3_bucket.assets"` is scope `"app/module.cdn"`. Keys
444
+ * are built here (see {@link blocksToEntities}), so the split lives here too:
445
+ * the reference index (`./references.ts`) and the module-scoped checks read
446
+ * scopes through it rather than re-deriving the key shape.
447
+ */
448
+ export function scopeOfKey(key: string): string {
449
+ const slash = key.lastIndexOf("/");
450
+ return slash === -1 ? "" : key.slice(0, slash);
451
+ }
452
+
453
+ /** The `module.<name>` chain in an entity key, outermost first. Empty for a root-module block. */
454
+ export function callersOfKey(key: string): string[] {
455
+ const parts = key.split("/");
456
+ return parts.length <= 2 ? [] : parts.slice(1, -1);
235
457
  }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The reference index (chant #2112), against
3
+ * `src/__fixtures__/references/main.tf`, which carries every form the index
4
+ * collects exactly once.
5
+ */
6
+
7
+ import { readFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { beforeAll, describe, expect, it } from "vitest";
11
+ import type { Declarable } from "@intentius/chant/declarable";
12
+ import { blocksToEntities } from "./parse";
13
+ import {
14
+ buildReferenceIndex,
15
+ collectReferences,
16
+ isReferenced,
17
+ providerReference,
18
+ referencesInBody,
19
+ referencesTo,
20
+ type ReferenceIndex,
21
+ } from "./references";
22
+
23
+ const FIXTURE = join(dirname(fileURLToPath(import.meta.url)), "..", "__fixtures__", "references", "main.tf");
24
+
25
+ let entities: Map<string, Declarable>;
26
+ let index: ReferenceIndex;
27
+
28
+ beforeAll(async () => {
29
+ entities = await blocksToEntities([{ name: "main.tf", source: readFileSync(FIXTURE, "utf-8") }], "app");
30
+ index = buildReferenceIndex(entities);
31
+ });
32
+
33
+ describe("collectReferences", () => {
34
+ it("reads each reference form out of one expression string", () => {
35
+ expect(collectReferences("${var.region}").sort()).toEqual(["var.region"]);
36
+ expect(collectReferences("${local.tags}").sort()).toEqual(["local.tags"]);
37
+ expect(collectReferences("${data.aws_ami.ubuntu.id}").sort()).toEqual(["data.aws_ami.ubuntu"]);
38
+ expect(collectReferences("${module.cdn.url}").sort()).toEqual(["module.cdn"]);
39
+ });
40
+
41
+ it("does not read a data source as a bare type reference", () => {
42
+ expect(collectReferences("${data.aws_ami.ubuntu.id}")).not.toContain("var.aws_ami");
43
+ });
44
+
45
+ it("reads several references out of one interpolated string", () => {
46
+ expect(collectReferences("assets-${var.region}-${local.suffix}").sort()).toEqual([
47
+ "local.suffix",
48
+ "var.region",
49
+ ]);
50
+ });
51
+
52
+ it("finds nothing in a string that names nothing", () => {
53
+ expect(collectReferences("a plain value")).toEqual([]);
54
+ });
55
+ });
56
+
57
+ describe("providerReference", () => {
58
+ it("reads the provider meta-argument's own form", () => {
59
+ expect(providerReference("${aws.replica}")).toBe("provider.aws.replica");
60
+ expect(providerReference("aws.replica")).toBe("provider.aws.replica");
61
+ });
62
+
63
+ it("is not fooled by an expression that merely contains a dot", () => {
64
+ expect(providerReference("${var.region}.example.com")).toBeUndefined();
65
+ expect(providerReference(42)).toBeUndefined();
66
+ });
67
+ });
68
+
69
+ describe("referencesInBody", () => {
70
+ it("reads a provider reference from the meta-argument and from a providers map", () => {
71
+ expect(referencesInBody({ provider: "${aws.replica}" })).toContain("provider.aws.replica");
72
+ expect(referencesInBody({ providers: { aws: "${aws.replica}" } })).toContain("provider.aws.replica");
73
+ });
74
+
75
+ it("descends through nested blocks", () => {
76
+ expect(referencesInBody({ tags: { Name: "${var.name}" } })).toContain("var.name");
77
+ });
78
+ });
79
+
80
+ describe("buildReferenceIndex over the fixture", () => {
81
+ it("collects every reference form in the root scope", () => {
82
+ const refs = [...(index.scopes.get("app") ?? new Map()).keys()].sort();
83
+ expect(refs).toEqual([
84
+ "data.aws_ami.ubuntu",
85
+ "local.bucket_name",
86
+ "local.tags",
87
+ "module.cdn",
88
+ "provider.aws.replica",
89
+ "var.region",
90
+ "var.subnets",
91
+ ]);
92
+ });
93
+
94
+ it("records which block each reference came from", () => {
95
+ expect([...referencesTo(index, "app", "module.cdn")]).toEqual(["app/output.cdn_url"]);
96
+ expect([...referencesTo(index, "app", "data.aws_ami.ubuntu")]).toEqual(["app/aws_instance.web"]);
97
+ });
98
+
99
+ it("counts a reference from the declaration's own body only when asked to", () => {
100
+ // `local.bucket_name` is built from `var.region` inside the same `locals`
101
+ // block, and read again by `aws_s3_bucket.replica`.
102
+ expect([...referencesTo(index, "app", "var.region")]).toEqual(["app/locals"]);
103
+ expect(isReferenced(index, "app", "var.region")).toBe(true);
104
+ expect(isReferenced(index, "app", "var.region", "app/locals")).toBe(false);
105
+ });
106
+
107
+ it("knows nothing about a token nobody names", () => {
108
+ expect(isReferenced(index, "app", "var.absent")).toBe(false);
109
+ expect(referencesTo(index, "app", "var.absent").size).toBe(0);
110
+ });
111
+
112
+ it("keeps scopes apart", () => {
113
+ expect(index.scopes.has("app")).toBe(true);
114
+ expect(isReferenced(index, "app/module.cdn", "var.region")).toBe(false);
115
+ });
116
+ });