@synapsestudios/lintofu 0.0.0-stage → 0.9.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.
Files changed (261) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +151 -2
  3. package/dist/configs/category-recommended.d.ts +3 -0
  4. package/dist/configs/category-recommended.d.ts.map +1 -0
  5. package/dist/configs/category-recommended.js +12 -0
  6. package/dist/configs/category-recommended.js.map +1 -0
  7. package/dist/configs/recommended.d.ts +3 -0
  8. package/dist/configs/recommended.d.ts.map +1 -0
  9. package/dist/configs/recommended.js +12 -0
  10. package/dist/configs/recommended.js.map +1 -0
  11. package/dist/index.d.ts +198 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +65 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/language.d.ts +34 -0
  16. package/dist/language.d.ts.map +1 -0
  17. package/dist/language.js +72 -0
  18. package/dist/language.js.map +1 -0
  19. package/dist/parser/index.d.ts +10 -0
  20. package/dist/parser/index.d.ts.map +1 -0
  21. package/dist/parser/index.js +43 -0
  22. package/dist/parser/index.js.map +1 -0
  23. package/dist/parser/lintofu-parser.wasm +0 -0
  24. package/dist/parser/types.d.ts +261 -0
  25. package/dist/parser/types.d.ts.map +1 -0
  26. package/dist/parser/types.js +3 -0
  27. package/dist/parser/types.js.map +1 -0
  28. package/dist/parser/wasm-exec.cjs +575 -0
  29. package/dist/project-index.d.ts +40 -0
  30. package/dist/project-index.d.ts.map +1 -0
  31. package/dist/project-index.js +89 -0
  32. package/dist/project-index.js.map +1 -0
  33. package/dist/rules/aws/acm-certificate-create-before-destroy-required.d.ts +6 -0
  34. package/dist/rules/aws/acm-certificate-create-before-destroy-required.d.ts.map +1 -0
  35. package/dist/rules/aws/acm-certificate-create-before-destroy-required.js +47 -0
  36. package/dist/rules/aws/acm-certificate-create-before-destroy-required.js.map +1 -0
  37. package/dist/rules/aws/no-api-gateway-deployment-stage-args.d.ts +6 -0
  38. package/dist/rules/aws/no-api-gateway-deployment-stage-args.d.ts.map +1 -0
  39. package/dist/rules/aws/no-api-gateway-deployment-stage-args.js +56 -0
  40. package/dist/rules/aws/no-api-gateway-deployment-stage-args.js.map +1 -0
  41. package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.d.ts +6 -0
  42. package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.d.ts.map +1 -0
  43. package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.js +88 -0
  44. package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.js.map +1 -0
  45. package/dist/rules/aws/no-db-instance-name-attribute.d.ts +6 -0
  46. package/dist/rules/aws/no-db-instance-name-attribute.d.ts.map +1 -0
  47. package/dist/rules/aws/no-db-instance-name-attribute.js +45 -0
  48. package/dist/rules/aws/no-db-instance-name-attribute.js.map +1 -0
  49. package/dist/rules/aws/no-deprecated-lambda-runtime.d.ts +6 -0
  50. package/dist/rules/aws/no-deprecated-lambda-runtime.d.ts.map +1 -0
  51. package/dist/rules/aws/no-deprecated-lambda-runtime.js +91 -0
  52. package/dist/rules/aws/no-deprecated-lambda-runtime.js.map +1 -0
  53. package/dist/rules/aws/no-eip-vpc-attribute.d.ts +6 -0
  54. package/dist/rules/aws/no-eip-vpc-attribute.d.ts.map +1 -0
  55. package/dist/rules/aws/no-eip-vpc-attribute.js +66 -0
  56. package/dist/rules/aws/no-eip-vpc-attribute.js.map +1 -0
  57. package/dist/rules/aws/no-eks-addon-resolve-conflicts.d.ts +6 -0
  58. package/dist/rules/aws/no-eks-addon-resolve-conflicts.d.ts.map +1 -0
  59. package/dist/rules/aws/no-eks-addon-resolve-conflicts.js +36 -0
  60. package/dist/rules/aws/no-eks-addon-resolve-conflicts.js.map +1 -0
  61. package/dist/rules/aws/no-hardcoded-provider-credentials.d.ts +6 -0
  62. package/dist/rules/aws/no-hardcoded-provider-credentials.d.ts.map +1 -0
  63. package/dist/rules/aws/no-hardcoded-provider-credentials.js +42 -0
  64. package/dist/rules/aws/no-hardcoded-provider-credentials.js.map +1 -0
  65. package/dist/rules/aws/no-iam-policy-attachment.d.ts +6 -0
  66. package/dist/rules/aws/no-iam-policy-attachment.d.ts.map +1 -0
  67. package/dist/rules/aws/no-iam-policy-attachment.js +28 -0
  68. package/dist/rules/aws/no-iam-policy-attachment.js.map +1 -0
  69. package/dist/rules/aws/no-iam-role-inline-policies.d.ts +6 -0
  70. package/dist/rules/aws/no-iam-role-inline-policies.d.ts.map +1 -0
  71. package/dist/rules/aws/no-iam-role-inline-policies.js +43 -0
  72. package/dist/rules/aws/no-iam-role-inline-policies.js.map +1 -0
  73. package/dist/rules/aws/no-instance-cpu-count-attributes.d.ts +6 -0
  74. package/dist/rules/aws/no-instance-cpu-count-attributes.d.ts.map +1 -0
  75. package/dist/rules/aws/no-instance-cpu-count-attributes.js +94 -0
  76. package/dist/rules/aws/no-instance-cpu-count-attributes.js.map +1 -0
  77. package/dist/rules/aws/no-legacy-security-group-rules.d.ts +6 -0
  78. package/dist/rules/aws/no-legacy-security-group-rules.d.ts.map +1 -0
  79. package/dist/rules/aws/no-legacy-security-group-rules.js +39 -0
  80. package/dist/rules/aws/no-legacy-security-group-rules.js.map +1 -0
  81. package/dist/rules/aws/no-mixed-security-group-rule-forms.d.ts +6 -0
  82. package/dist/rules/aws/no-mixed-security-group-rule-forms.d.ts.map +1 -0
  83. package/dist/rules/aws/no-mixed-security-group-rule-forms.js +63 -0
  84. package/dist/rules/aws/no-mixed-security-group-rule-forms.js.map +1 -0
  85. package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.d.ts +6 -0
  86. package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.d.ts.map +1 -0
  87. package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.js +100 -0
  88. package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.js.map +1 -0
  89. package/dist/rules/aws/no-s3-bucket-inline-config.d.ts +6 -0
  90. package/dist/rules/aws/no-s3-bucket-inline-config.d.ts.map +1 -0
  91. package/dist/rules/aws/no-s3-bucket-inline-config.js +67 -0
  92. package/dist/rules/aws/no-s3-bucket-inline-config.js.map +1 -0
  93. package/dist/rules/aws/no-s3-bucket-object.d.ts +6 -0
  94. package/dist/rules/aws/no-s3-bucket-object.d.ts.map +1 -0
  95. package/dist/rules/aws/no-s3-bucket-object.js +41 -0
  96. package/dist/rules/aws/no-s3-bucket-object.js.map +1 -0
  97. package/dist/rules/aws/utils.d.ts +4 -0
  98. package/dist/rules/aws/utils.d.ts.map +1 -0
  99. package/dist/rules/aws/utils.js +17 -0
  100. package/dist/rules/aws/utils.js.map +1 -0
  101. package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.d.ts +6 -0
  102. package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.d.ts.map +1 -0
  103. package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.js +66 -0
  104. package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.js.map +1 -0
  105. package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.d.ts +10 -0
  106. package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.d.ts.map +1 -0
  107. package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.js +104 -0
  108. package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.js.map +1 -0
  109. package/dist/rules/cloudflare/ruleset-requires-managed-by-description.d.ts +9 -0
  110. package/dist/rules/cloudflare/ruleset-requires-managed-by-description.d.ts.map +1 -0
  111. package/dist/rules/cloudflare/ruleset-requires-managed-by-description.js +52 -0
  112. package/dist/rules/cloudflare/ruleset-requires-managed-by-description.js.map +1 -0
  113. package/dist/rules/cloudflare/utils.d.ts +17 -0
  114. package/dist/rules/cloudflare/utils.d.ts.map +1 -0
  115. package/dist/rules/cloudflare/utils.js +37 -0
  116. package/dist/rules/cloudflare/utils.js.map +1 -0
  117. package/dist/rules/core/documented-outputs.d.ts +6 -0
  118. package/dist/rules/core/documented-outputs.d.ts.map +1 -0
  119. package/dist/rules/core/documented-outputs.js +41 -0
  120. package/dist/rules/core/documented-outputs.js.map +1 -0
  121. package/dist/rules/core/documented-variables.d.ts +6 -0
  122. package/dist/rules/core/documented-variables.d.ts.map +1 -0
  123. package/dist/rules/core/documented-variables.js +41 -0
  124. package/dist/rules/core/documented-variables.js.map +1 -0
  125. package/dist/rules/core/module-source-pinned.d.ts +6 -0
  126. package/dist/rules/core/module-source-pinned.d.ts.map +1 -0
  127. package/dist/rules/core/module-source-pinned.js +47 -0
  128. package/dist/rules/core/module-source-pinned.js.map +1 -0
  129. package/dist/rules/core/no-bool-as-count.d.ts +6 -0
  130. package/dist/rules/core/no-bool-as-count.d.ts.map +1 -0
  131. package/dist/rules/core/no-bool-as-count.js +69 -0
  132. package/dist/rules/core/no-bool-as-count.js.map +1 -0
  133. package/dist/rules/core/no-duplicate-map-keys.d.ts +6 -0
  134. package/dist/rules/core/no-duplicate-map-keys.d.ts.map +1 -0
  135. package/dist/rules/core/no-duplicate-map-keys.js +42 -0
  136. package/dist/rules/core/no-duplicate-map-keys.js.map +1 -0
  137. package/dist/rules/core/no-empty-list-equality.d.ts +6 -0
  138. package/dist/rules/core/no-empty-list-equality.d.ts.map +1 -0
  139. package/dist/rules/core/no-empty-list-equality.js +48 -0
  140. package/dist/rules/core/no-empty-list-equality.js.map +1 -0
  141. package/dist/rules/core/no-external-refs-in-destroy-provisioner.d.ts +6 -0
  142. package/dist/rules/core/no-external-refs-in-destroy-provisioner.d.ts.map +1 -0
  143. package/dist/rules/core/no-external-refs-in-destroy-provisioner.js +74 -0
  144. package/dist/rules/core/no-external-refs-in-destroy-provisioner.js.map +1 -0
  145. package/dist/rules/core/no-legacy-count-attribute.d.ts +6 -0
  146. package/dist/rules/core/no-legacy-count-attribute.d.ts.map +1 -0
  147. package/dist/rules/core/no-legacy-count-attribute.js +64 -0
  148. package/dist/rules/core/no-legacy-count-attribute.js.map +1 -0
  149. package/dist/rules/core/no-legacy-dot-index.d.ts +6 -0
  150. package/dist/rules/core/no-legacy-dot-index.d.ts.map +1 -0
  151. package/dist/rules/core/no-legacy-dot-index.js +79 -0
  152. package/dist/rules/core/no-legacy-dot-index.js.map +1 -0
  153. package/dist/rules/core/no-legacy-ignore-changes-wildcard.d.ts +6 -0
  154. package/dist/rules/core/no-legacy-ignore-changes-wildcard.d.ts.map +1 -0
  155. package/dist/rules/core/no-legacy-ignore-changes-wildcard.js +40 -0
  156. package/dist/rules/core/no-legacy-ignore-changes-wildcard.js.map +1 -0
  157. package/dist/rules/core/no-legacy-list-function.d.ts +6 -0
  158. package/dist/rules/core/no-legacy-list-function.d.ts.map +1 -0
  159. package/dist/rules/core/no-legacy-list-function.js +67 -0
  160. package/dist/rules/core/no-legacy-list-function.js.map +1 -0
  161. package/dist/rules/core/no-legacy-lookup.d.ts +6 -0
  162. package/dist/rules/core/no-legacy-lookup.d.ts.map +1 -0
  163. package/dist/rules/core/no-legacy-lookup.js +53 -0
  164. package/dist/rules/core/no-legacy-lookup.js.map +1 -0
  165. package/dist/rules/core/no-legacy-map-function.d.ts +6 -0
  166. package/dist/rules/core/no-legacy-map-function.d.ts.map +1 -0
  167. package/dist/rules/core/no-legacy-map-function.js +90 -0
  168. package/dist/rules/core/no-legacy-map-function.js.map +1 -0
  169. package/dist/rules/core/no-legacy-provider-proxy.d.ts +6 -0
  170. package/dist/rules/core/no-legacy-provider-proxy.d.ts.map +1 -0
  171. package/dist/rules/core/no-legacy-provider-proxy.js +43 -0
  172. package/dist/rules/core/no-legacy-provider-proxy.js.map +1 -0
  173. package/dist/rules/core/no-provider-version-constraint.d.ts +6 -0
  174. package/dist/rules/core/no-provider-version-constraint.d.ts.map +1 -0
  175. package/dist/rules/core/no-provider-version-constraint.js +33 -0
  176. package/dist/rules/core/no-provider-version-constraint.js.map +1 -0
  177. package/dist/rules/core/no-quoted-depends-on.d.ts +6 -0
  178. package/dist/rules/core/no-quoted-depends-on.d.ts.map +1 -0
  179. package/dist/rules/core/no-quoted-depends-on.js +55 -0
  180. package/dist/rules/core/no-quoted-depends-on.js.map +1 -0
  181. package/dist/rules/core/no-quoted-ignore-changes.d.ts +6 -0
  182. package/dist/rules/core/no-quoted-ignore-changes.d.ts.map +1 -0
  183. package/dist/rules/core/no-quoted-ignore-changes.js +63 -0
  184. package/dist/rules/core/no-quoted-ignore-changes.js.map +1 -0
  185. package/dist/rules/core/no-quoted-provider-reference.d.ts +6 -0
  186. package/dist/rules/core/no-quoted-provider-reference.d.ts.map +1 -0
  187. package/dist/rules/core/no-quoted-provider-reference.js +69 -0
  188. package/dist/rules/core/no-quoted-provider-reference.js.map +1 -0
  189. package/dist/rules/core/no-quoted-type-constraints.d.ts +6 -0
  190. package/dist/rules/core/no-quoted-type-constraints.d.ts.map +1 -0
  191. package/dist/rules/core/no-quoted-type-constraints.js +55 -0
  192. package/dist/rules/core/no-quoted-type-constraints.js.map +1 -0
  193. package/dist/rules/core/no-reserved-variable-names.d.ts +6 -0
  194. package/dist/rules/core/no-reserved-variable-names.d.ts.map +1 -0
  195. package/dist/rules/core/no-reserved-variable-names.js +41 -0
  196. package/dist/rules/core/no-reserved-variable-names.js.map +1 -0
  197. package/dist/rules/core/no-splat-bracket-index.d.ts +6 -0
  198. package/dist/rules/core/no-splat-bracket-index.d.ts.map +1 -0
  199. package/dist/rules/core/no-splat-bracket-index.js +30 -0
  200. package/dist/rules/core/no-splat-bracket-index.js.map +1 -0
  201. package/dist/rules/core/no-template-provider.d.ts +6 -0
  202. package/dist/rules/core/no-template-provider.d.ts.map +1 -0
  203. package/dist/rules/core/no-template-provider.js +63 -0
  204. package/dist/rules/core/no-template-provider.js.map +1 -0
  205. package/dist/rules/core/no-terraform-env-reference.d.ts +6 -0
  206. package/dist/rules/core/no-terraform-env-reference.d.ts.map +1 -0
  207. package/dist/rules/core/no-terraform-env-reference.js +35 -0
  208. package/dist/rules/core/no-terraform-env-reference.js.map +1 -0
  209. package/dist/rules/core/no-winrm-connection.d.ts +6 -0
  210. package/dist/rules/core/no-winrm-connection.d.ts.map +1 -0
  211. package/dist/rules/core/no-winrm-connection.js +31 -0
  212. package/dist/rules/core/no-winrm-connection.js.map +1 -0
  213. package/dist/rules/core/prefer-bare-expression.d.ts +6 -0
  214. package/dist/rules/core/prefer-bare-expression.d.ts.map +1 -0
  215. package/dist/rules/core/prefer-bare-expression.js +23 -0
  216. package/dist/rules/core/prefer-bare-expression.js.map +1 -0
  217. package/dist/rules/core/provider-source-required.d.ts +6 -0
  218. package/dist/rules/core/provider-source-required.d.ts.map +1 -0
  219. package/dist/rules/core/provider-source-required.js +45 -0
  220. package/dist/rules/core/provider-source-required.js.map +1 -0
  221. package/dist/rules/core/remote-state-outputs-prefix.d.ts +6 -0
  222. package/dist/rules/core/remote-state-outputs-prefix.d.ts.map +1 -0
  223. package/dist/rules/core/remote-state-outputs-prefix.js +54 -0
  224. package/dist/rules/core/remote-state-outputs-prefix.js.map +1 -0
  225. package/dist/rules/core/required-providers-pinned.d.ts +6 -0
  226. package/dist/rules/core/required-providers-pinned.d.ts.map +1 -0
  227. package/dist/rules/core/required-providers-pinned.js +37 -0
  228. package/dist/rules/core/required-providers-pinned.js.map +1 -0
  229. package/dist/rules/core/required-version-declared.d.ts +6 -0
  230. package/dist/rules/core/required-version-declared.d.ts.map +1 -0
  231. package/dist/rules/core/required-version-declared.js +38 -0
  232. package/dist/rules/core/required-version-declared.js.map +1 -0
  233. package/dist/rules/core/typed-variables.d.ts +6 -0
  234. package/dist/rules/core/typed-variables.d.ts.map +1 -0
  235. package/dist/rules/core/typed-variables.js +32 -0
  236. package/dist/rules/core/typed-variables.js.map +1 -0
  237. package/dist/rules/core/utils.d.ts +31 -0
  238. package/dist/rules/core/utils.d.ts.map +1 -0
  239. package/dist/rules/core/utils.js +185 -0
  240. package/dist/rules/core/utils.js.map +1 -0
  241. package/dist/rules/index.d.ts +157 -0
  242. package/dist/rules/index.d.ts.map +1 -0
  243. package/dist/rules/index.js +101 -0
  244. package/dist/rules/index.js.map +1 -0
  245. package/dist/rules/types.d.ts +57 -0
  246. package/dist/rules/types.d.ts.map +1 -0
  247. package/dist/rules/types.js +15 -0
  248. package/dist/rules/types.js.map +1 -0
  249. package/dist/source-code.d.ts +84 -0
  250. package/dist/source-code.d.ts.map +1 -0
  251. package/dist/source-code.js +170 -0
  252. package/dist/source-code.js.map +1 -0
  253. package/dist/types.d.ts +67 -0
  254. package/dist/types.d.ts.map +1 -0
  255. package/dist/types.js +2 -0
  256. package/dist/types.js.map +1 -0
  257. package/dist/visitor-keys.d.ts +3 -0
  258. package/dist/visitor-keys.d.ts.map +1 -0
  259. package/dist/visitor-keys.js +30 -0
  260. package/dist/visitor-keys.js.map +1 -0
  261. package/package.json +88 -4
package/README.md CHANGED
@@ -1,3 +1,152 @@
1
- # Temporary Holding Version
1
+ # lintofu
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An ESLint plugin for linting [OpenTofu](https://opentofu.org) configurations.
4
+
5
+ > [!WARNING]
6
+ > The parser depends on Go's [`syscall/js`](https://pkg.go.dev/syscall/js), which the Go team has declared experimental and exempt from the Go compatibility promise. A future Go release _may_ break the functionality of this plugin. `lintofu` will stay on `0.x` until `syscall/js` stabilizes or we replace it. See [ADR 0001](./docs/adr/0001-syscall-js-experimental.md).
7
+
8
+ This is a pre-release and experimental ESLint plugin.
9
+
10
+ Published to npm as [`@synapsestudios/lintofu`](https://www.npmjs.com/package/@synapsestudios/lintofu).
11
+
12
+ ## Head striaght to the docs?
13
+
14
+ The lintofu docs are located here: [synapsestudios.github.io/lintofu](https://synapsestudios.github.io/lintofu/) the frontend that represents the documentation located in [`docs/`](./docs).
15
+
16
+ ## What it is
17
+
18
+ `lintofu` is an **ESLint language plugin** (the same mechanism that powers [`@eslint/json`](https://github.com/eslint/json) and [`@eslint/css`](https://github.com/eslint/css)).
19
+
20
+ It parses `.tf` and `.tofu` files using HashiCorp's own `hcl/v2` parser (compiled to WebAssembly) and exposes the AST to ESLint rules so you can enforce OpenTofu or Terraform coding conventions alongside the rest of your ESLint rules.
21
+
22
+ ### What it (is not)
23
+
24
+ We (Synapse Studios) have been using this internally for a few months. This statement is to provide clarity on the kind of expectations you should have when engaging with it, we will accept contributions from the community, however, we can make no promises as to what kind of attention we will be able to give it when it comes to approving/reviewing/or otherwise engaging with community feedback.
25
+
26
+ This will be (for the foreseeable future) _best effort_ and sometimes we will be too busy to triage incoming pull requests or bug reports.
27
+
28
+ ### AI Disclosure
29
+
30
+ To be clear, AI _has_ been used to help build this tool, but the specifications, features, rules, the way in which the plugin handles rule precedence, processing, and the like have _all_ been defined by humans clearly, with insight and scrutiny from a team of experienced DevOps engineers and Software Engineers in order to build something that isn't complete slop. We're tired of slop.
31
+
32
+ ## Scope
33
+
34
+ `lintofu` asserts against syntactic problems, stylistic conventions, and some provider specific code smells. We provide a list of somewhat generic (but other times opinionated) set of core rules, listed in our [rules index](https://synapsestudios.github.io/lintofu/rules/), that you can take advantage of.
35
+
36
+ ### Our intentions for `lintofu`:
37
+
38
+ We built lintofu as a stop-gap until feature rich fully fledged linting is available. You can read more about that from the [OpenTofu linting RFC](https://github.com/opentofu/opentofu/pull/3999). This isn't meant to be a complete replacement for what will soon be the linter implementation in OpenTofu, however, it _is_ meant to provide all of the necessary facilities and interfaces to build a comprehensive suite of assertions and rules at the same level.
39
+
40
+ ## Install
41
+
42
+ ### Pre-requisites
43
+
44
+ Required versions:
45
+ - Node >= `22.13`
46
+ - ESLint >= `9.x`
47
+
48
+ ### Setup
49
+
50
+ ```sh
51
+ npm install --save-dev @synapsestudios/lintofu
52
+ ```
53
+
54
+ If you installed it from GitHub Packages before, delete these lines from your `.npmrc`. While they're there, npm resolves every `@synapsestudios` package from GitHub Packages, which no longer gets new releases:
55
+
56
+ ```
57
+ @synapsestudios:registry=https://npm.pkg.github.com
58
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
59
+ ```
60
+
61
+ ## Supported versions
62
+
63
+ We would like to have as broad of compatibility as possible, so our aim is to have full compatibility with the major cloud providers in which our rules are tested against, from the _last_ major version to the latest major version.
64
+
65
+ As this plugin ages, we expect to have to provide _some_ backwards compatibility with our rules that may target older providers' code smells, but our focus will be on moving the tool forwards with the rest of the ecosystem. Our full decision making record and rationale on this is defined in [ADR 0005](./docs/adr/0005-version-support-policy.md).
66
+
67
+ The parser wraps HashiCorp's `hcl/v2`, which parses HCL2, the syntax that shipped with Terraform 0.12. We _will not be supporting `hcl/v1` or Terraform versions that pre-date `0.12`.
68
+
69
+ <!-- lintofu:support-matrix:start -->
70
+
71
+ | Tool | Versions | Status |
72
+ | --------- | -------------- | ----------- |
73
+ | OpenTofu | 1.6 and newer | supported |
74
+ | Terraform | 1.0 and newer | supported |
75
+ | Terraform | 0.12 – 0.15 | best effort |
76
+ | Terraform | 0.11 and older | unsupported |
77
+
78
+ <!-- lintofu:support-matrix:end -->
79
+
80
+ - **supported**: We focus on building rules that apply to these versions.
81
+ - **best effort**: Some rules may target these specific versions and the gotchas associated with upgrading _from_ them, but we won't be writing rules that target specific syntax that is only applicable to these versions.
82
+ - **unsupported**: The parser will reject these.
83
+
84
+ <!-- lintofu:tested-versions:start -->
85
+
86
+ ## Tested provider versions
87
+
88
+ Provider-specific rules are validated against these versions. This is a list located here: [`provider-versions.json`](./provider-versions.json) which defines which provider versions we test against in CI.
89
+
90
+ - aws provider `v6.42.0`
91
+ - cloudflare provider `v5.24.0`
92
+
93
+ <!-- lintofu:tested-versions:end -->
94
+
95
+ ## Usage
96
+
97
+ ```js
98
+ // eslint.config.js
99
+ import lintofu from "@synapsestudios/lintofu";
100
+
101
+ export default [lintofu.configs.recommended];
102
+ ```
103
+
104
+ Or opt into specific rules and error statuses.
105
+
106
+ ```js
107
+ // eslint.config.js
108
+ import lintofu from "@synapsestudios/lintofu";
109
+
110
+ export default [
111
+ {
112
+ files: ["**/*.tf", "**/*.tofu"],
113
+ plugins: { lintofu },
114
+ language: "lintofu/opentofu",
115
+ rules: {
116
+ "lintofu/documented-variables": "error",
117
+ "lintofu/required-providers-pinned": "error",
118
+ },
119
+ },
120
+ ];
121
+ ```
122
+
123
+ ## Rules
124
+
125
+ Every rule's description, severity, category, recommended flag, and autofix support lives in the generated **[Rule index](https://synapsestudios.github.io/lintofu/rules/)** on the documentation site
126
+
127
+ ## Requesting a rule
128
+
129
+ To suggest a new rule, open a [rule request](https://github.com/synapsestudios/lintofu/issues/new?template=rule-request.yml). The form asks for a snippet the rule should flag, the compliant form, a category, a suggested severity, whether an autofix seems possible, and any references. One of our maintainers will pick up your issue and approve it or reject it with a reason.
130
+
131
+ ## Parser services
132
+
133
+ Rules can reach a lazily-built, cross-file index through
134
+ `context.sourceCode.parserServices.projectIndex`. The `ProjectIndex` interface
135
+ and its module-directory scope are documented in the developer guide:
136
+ [on the site](https://synapsestudios.github.io/lintofu/developer-guide/parser-services)
137
+ or [`docs/developer-guide/parser-services.md`](./docs/developer-guide/parser-services.md)
138
+ in this repository.
139
+
140
+ ## Writing your own rule
141
+
142
+ `lintofu` re-exports the rule-authoring types it uses internally, so a custom rule type-checks without reaching into private modules. We have an example of this [on the site](https://synapsestudios.github.io/lintofu/developer-guide/writing-a-rule) or [`docs/developer-guide/writing-a-rule.md`](./docs/developer-guide/writing-a-rule.md) in this repository.
143
+
144
+ ## Architecture
145
+
146
+ The parser is Go: `github.com/hashicorp/hcl/v2/hclsyntax` compiled to
147
+ WebAssembly. We have a simple ASCII representation of the architecture here [on the site](https://synapsestudios.github.io/lintofu/developer-guide/architecture) or [`docs/developer-guide/architecture.md`](./docs/developer-guide/architecture.md) in this repository.
148
+
149
+ ## Known limitations
150
+
151
+ - **`.tf.json` / `.tofu.json` not supported.** The JSON form of HCL is not 1:1 with native syntax; see [hashicorp/hcl#627 for the rationale](https://github.com/hashicorp/hcl/issues/627).
152
+ - **No TFLint custom-rule compatibility.** We are _considering_ adding `tflint` support so that users can import rules, but we haven't made a decision on whether or not it should happen.
@@ -0,0 +1,3 @@
1
+ import { type RuleWithDocs } from '../rules/types.js';
2
+ export declare function buildCategoryRecommendedRules(rules: Record<string, RuleWithDocs>, category: string): Record<string, 'error' | 'warn'>;
3
+ //# sourceMappingURL=category-recommended.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category-recommended.d.ts","sourceRoot":"","sources":["../../src/configs/category-recommended.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE3E,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACnC,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CASlC"}
@@ -0,0 +1,12 @@
1
+ import { resolveRuleSeverity } from '../rules/types.js';
2
+ export function buildCategoryRecommendedRules(rules, category) {
3
+ const out = {};
4
+ for (const [name, rule] of Object.entries(rules)) {
5
+ const docs = rule.meta?.docs;
6
+ if (docs?.recommended === true && docs.category === category) {
7
+ out[`lintofu/${name}`] = resolveRuleSeverity(docs);
8
+ }
9
+ }
10
+ return out;
11
+ }
12
+ //# sourceMappingURL=category-recommended.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category-recommended.js","sourceRoot":"","sources":["../../src/configs/category-recommended.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAqB,MAAM,mBAAmB,CAAC;AAE3E,MAAM,UAAU,6BAA6B,CAC3C,KAAmC,EACnC,QAAgB;IAEhB,MAAM,GAAG,GAAqC,EAAE,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;QAC7B,IAAI,IAAI,EAAE,WAAW,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC7D,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type RuleWithDocs } from '../rules/types.js';
2
+ export declare function buildRecommendedRules(rules: Record<string, RuleWithDocs>): Record<string, 'error' | 'warn'>;
3
+ //# sourceMappingURL=recommended.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.d.ts","sourceRoot":"","sources":["../../src/configs/recommended.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE3E,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CASlC"}
@@ -0,0 +1,12 @@
1
+ import { resolveRuleSeverity } from '../rules/types.js';
2
+ export function buildRecommendedRules(rules) {
3
+ const out = {};
4
+ for (const [name, rule] of Object.entries(rules)) {
5
+ const docs = rule.meta?.docs;
6
+ if (docs?.recommended === true) {
7
+ out[`lintofu/${name}`] = resolveRuleSeverity(docs);
8
+ }
9
+ }
10
+ return out;
11
+ }
12
+ //# sourceMappingURL=recommended.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.js","sourceRoot":"","sources":["../../src/configs/recommended.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAqB,MAAM,mBAAmB,CAAC;AAE3E,MAAM,UAAU,qBAAqB,CACnC,KAAmC;IAEnC,MAAM,GAAG,GAAqC,EAAE,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;QAC7B,IAAI,IAAI,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;YAC/B,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,198 @@
1
+ import type { Plugin } from '@eslint/core';
2
+ import { OpenTofuLanguage } from './language.js';
3
+ interface TierConfig {
4
+ plugins: Record<string, Plugin>;
5
+ files: string[];
6
+ language: string;
7
+ rules: Record<string, 'error' | 'warn'>;
8
+ }
9
+ /**
10
+ * the lintofu eslint plugin: `meta`, the `lintofu/opentofu` language,
11
+ * every bundled rule, and the generated `recommended` and
12
+ * `<provider>/recommended` configs.
13
+ */
14
+ declare const plugin: {
15
+ /** plugin name and version eslint reports. */
16
+ meta: {
17
+ name: string;
18
+ version: string;
19
+ };
20
+ /**
21
+ * the opentofu/terraform hcl language implementation; select it with
22
+ * `language: 'lintofu/opentofu'`.
23
+ */
24
+ languages: {
25
+ opentofu: OpenTofuLanguage;
26
+ };
27
+ /** every bundled rule, keyed by rule name. */
28
+ rules: {
29
+ 'documented-variables': import("./types.js").OpenTofuRuleDefinition<{
30
+ MessageIds: "missingDescription";
31
+ }>;
32
+ 'no-reserved-variable-names': import("./types.js").OpenTofuRuleDefinition<{
33
+ MessageIds: "reservedName";
34
+ }>;
35
+ 'documented-outputs': import("./types.js").OpenTofuRuleDefinition<{
36
+ MessageIds: "missingDescription";
37
+ }>;
38
+ 'required-providers-pinned': import("./types.js").OpenTofuRuleDefinition<{
39
+ MessageIds: "unpinnedProvider";
40
+ }>;
41
+ 'provider-source-required': import("./types.js").OpenTofuRuleDefinition<{
42
+ MessageIds: "missingSource" | "legacyStringForm";
43
+ }>;
44
+ 'module-source-pinned': import("./types.js").OpenTofuRuleDefinition<{
45
+ MessageIds: "unpinnedSource";
46
+ }>;
47
+ 'prefer-bare-expression': import("./types.js").OpenTofuRuleDefinition<{
48
+ MessageIds: "preferBareExpression";
49
+ }>;
50
+ 'no-legacy-list-function': import("./types.js").OpenTofuRuleDefinition<{
51
+ MessageIds: "legacyListFunction";
52
+ }>;
53
+ 'no-legacy-map-function': import("./types.js").OpenTofuRuleDefinition<{
54
+ MessageIds: "legacyMapFunction";
55
+ }>;
56
+ 'no-bool-as-count': import("./types.js").OpenTofuRuleDefinition<{
57
+ MessageIds: "boolAsCount";
58
+ }>;
59
+ 'no-legacy-provider-proxy': import("./types.js").OpenTofuRuleDefinition<{
60
+ MessageIds: "legacyProviderProxy";
61
+ }>;
62
+ 'no-template-provider': import("./types.js").OpenTofuRuleDefinition<{
63
+ MessageIds: "useTemplatefile" | "useCloudinitProvider" | "archivedTemplateProvider";
64
+ }>;
65
+ 'no-terraform-env-reference': import("./types.js").OpenTofuRuleDefinition<{
66
+ MessageIds: "terraformEnvReference";
67
+ }>;
68
+ 'no-quoted-type-constraints': import("./types.js").OpenTofuRuleDefinition<{
69
+ MessageIds: "quotedTypeConstraint";
70
+ }>;
71
+ 'no-splat-bracket-index': import("./types.js").OpenTofuRuleDefinition<{
72
+ MessageIds: "splatBracketIndex";
73
+ }>;
74
+ 'remote-state-outputs-prefix': import("./types.js").OpenTofuRuleDefinition<{
75
+ MessageIds: "missingOutputsPrefix";
76
+ }>;
77
+ 'no-external-refs-in-destroy-provisioner': import("./types.js").OpenTofuRuleDefinition<{
78
+ MessageIds: "disallowedReference";
79
+ }>;
80
+ 'no-legacy-ignore-changes-wildcard': import("./types.js").OpenTofuRuleDefinition<{
81
+ MessageIds: "legacyIgnoreChangesWildcard";
82
+ }>;
83
+ 'no-legacy-count-attribute': import("./types.js").OpenTofuRuleDefinition<{
84
+ MessageIds: "legacyCountAttribute";
85
+ }>;
86
+ 'no-iam-role-inline-policies': import("./types.js").OpenTofuRuleDefinition<{
87
+ MessageIds: "inlinePolicy" | "managedPolicyArns";
88
+ }>;
89
+ 'no-legacy-security-group-rules': import("./types.js").OpenTofuRuleDefinition<{
90
+ MessageIds: "legacySecurityGroupRule";
91
+ }>;
92
+ 'no-mixed-security-group-rule-forms': import("./types.js").OpenTofuRuleDefinition<{
93
+ MessageIds: "mixedForms";
94
+ }>;
95
+ 'no-pinned-minor-with-auto-upgrade': import("./types.js").OpenTofuRuleDefinition<{
96
+ MessageIds: "pinnedMinorWithAutoUpgrade";
97
+ }>;
98
+ 'no-aurora-pinned-minor-with-auto-upgrade': import("./types.js").OpenTofuRuleDefinition<{
99
+ MessageIds: "pinnedMinorWithAutoUpgrade";
100
+ }>;
101
+ 'no-iam-policy-attachment': import("./types.js").OpenTofuRuleDefinition<{
102
+ MessageIds: "iamPolicyAttachment";
103
+ }>;
104
+ 'no-s3-bucket-inline-config': import("./types.js").OpenTofuRuleDefinition<{
105
+ MessageIds: "inlineConfig";
106
+ }>;
107
+ 'no-legacy-dot-index': import("./types.js").OpenTofuRuleDefinition<{
108
+ MessageIds: "dotIndex" | "dotSplat";
109
+ }>;
110
+ 'no-legacy-lookup': import("./types.js").OpenTofuRuleDefinition<{
111
+ MessageIds: "legacyLookup";
112
+ }>;
113
+ 'no-quoted-depends-on': import("./types.js").OpenTofuRuleDefinition<{
114
+ MessageIds: "quotedDependsOn";
115
+ }>;
116
+ 'no-quoted-provider-reference': import("./types.js").OpenTofuRuleDefinition<{
117
+ MessageIds: "quotedProviderReference";
118
+ }>;
119
+ 'no-quoted-ignore-changes': import("./types.js").OpenTofuRuleDefinition<{
120
+ MessageIds: "quotedIgnoreChanges";
121
+ }>;
122
+ 'no-empty-list-equality': import("./types.js").OpenTofuRuleDefinition<{
123
+ MessageIds: "emptyListEquality";
124
+ }>;
125
+ 'no-winrm-connection': import("./types.js").OpenTofuRuleDefinition<{
126
+ MessageIds: "winrmConnection";
127
+ }>;
128
+ 'typed-variables': import("./types.js").OpenTofuRuleDefinition<{
129
+ MessageIds: "missingType";
130
+ }>;
131
+ 'no-eip-vpc-attribute': import("./types.js").OpenTofuRuleDefinition<{
132
+ MessageIds: "eipVpcAttribute";
133
+ }>;
134
+ 'no-instance-cpu-count-attributes': import("./types.js").OpenTofuRuleDefinition<{
135
+ MessageIds: "cpuCountAttribute";
136
+ }>;
137
+ 'required-version-declared': import("./types.js").OpenTofuRuleDefinition<{
138
+ MessageIds: "missingRequiredVersion";
139
+ }>;
140
+ 'no-duplicate-map-keys': import("./types.js").OpenTofuRuleDefinition<{
141
+ MessageIds: "duplicateMapKey";
142
+ }>;
143
+ 'no-provider-version-constraint': import("./types.js").OpenTofuRuleDefinition<{
144
+ MessageIds: "providerVersionConstraint";
145
+ }>;
146
+ 'no-db-instance-name-attribute': import("./types.js").OpenTofuRuleDefinition<{
147
+ MessageIds: "dbInstanceName";
148
+ }>;
149
+ 'no-eks-addon-resolve-conflicts': import("./types.js").OpenTofuRuleDefinition<{
150
+ MessageIds: "resolveConflicts";
151
+ }>;
152
+ 'no-api-gateway-deployment-stage-args': import("./types.js").OpenTofuRuleDefinition<{
153
+ MessageIds: "deploymentStageArg";
154
+ }>;
155
+ 'no-s3-bucket-object': import("./types.js").OpenTofuRuleDefinition<{
156
+ MessageIds: "deprecatedS3BucketObject";
157
+ }>;
158
+ 'no-hardcoded-provider-credentials': import("./types.js").OpenTofuRuleDefinition<{
159
+ MessageIds: "hardcodedCredential";
160
+ }>;
161
+ 'no-deprecated-lambda-runtime': import("./types.js").OpenTofuRuleDefinition<{
162
+ MessageIds: "deprecatedRuntime";
163
+ }>;
164
+ 'acm-certificate-create-before-destroy-required': import("./types.js").OpenTofuRuleDefinition<{
165
+ MessageIds: "missingCreateBeforeDestroy";
166
+ }>;
167
+ 'access-rule-country-block-requires-enterprise': import("./types.js").OpenTofuRuleDefinition<{
168
+ MessageIds: "countryBlockRequiresEnterprise" | "invalidPlan";
169
+ }>;
170
+ 'ruleset-requires-managed-by-description': import("./types.js").OpenTofuRuleDefinition<{
171
+ RuleOptions: [{
172
+ description?: string;
173
+ }?];
174
+ MessageIds: "missingDescription";
175
+ }>;
176
+ 'dns-record-requires-managed-by-annotation': import("./types.js").OpenTofuRuleDefinition<{
177
+ RuleOptions: [{
178
+ comment?: string;
179
+ tag?: string;
180
+ }?];
181
+ MessageIds: "missingComment" | "missingTag" | "invalidPlan";
182
+ }>;
183
+ };
184
+ configs: {
185
+ "aws/recommended": TierConfig;
186
+ "cloudflare/recommended": TierConfig;
187
+ /**
188
+ * every rule with `recommended: true`, at its declared severity
189
+ * (error when the rule omits one). preconfigured for `.tf` and
190
+ * `.tofu` files.
191
+ */
192
+ recommended: TierConfig;
193
+ };
194
+ };
195
+ export default plugin;
196
+ export { OpenTofuLanguage } from './language.js';
197
+ export * from './types.js';
198
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAejD,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;CACzC;AAkBD;;;;GAIG;AACH,QAAA,MAAM,MAAM;IACV,8CAA8C;;;;;IAK9C;;;OAGG;;;;IAIH,8CAA8C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAvD9B,CAAC;;;;;;uBAsClB,CAAD;mBAAa,CAAC;;;;;;;;QAoBV;;;;WAIG;;;CASN,CAAC;AAMF,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,65 @@
1
+ import { buildCategoryRecommendedRules } from './configs/category-recommended.js';
2
+ import { buildRecommendedRules } from './configs/recommended.js';
3
+ import { OpenTofuLanguage } from './language.js';
4
+ import { initialize } from './parser/index.js';
5
+ import { rules } from './rules/index.js';
6
+ // ESLint's Language.parse contract is sync; this top-level await blocks
7
+ // the plugin export until the WASM parser is ready. ESLint's config loader
8
+ // uses dynamic import which awaits TLA, so consumers don't see the boot.
9
+ await initialize();
10
+ const TOFU_FILES = ['**/*.tf', '**/*.tofu'];
11
+ const PROVIDER_CATEGORIES = ['aws', 'cloudflare'];
12
+ function tierConfig(tierRules) {
13
+ return {
14
+ plugins: {},
15
+ files: TOFU_FILES,
16
+ language: 'lintofu/opentofu',
17
+ rules: tierRules,
18
+ };
19
+ }
20
+ const providerConfigs = Object.fromEntries(PROVIDER_CATEGORIES.map((category) => [
21
+ `${category}/recommended`,
22
+ tierConfig(buildCategoryRecommendedRules(rules, category)),
23
+ ]));
24
+ /**
25
+ * the lintofu eslint plugin: `meta`, the `lintofu/opentofu` language,
26
+ * every bundled rule, and the generated `recommended` and
27
+ * `<provider>/recommended` configs.
28
+ */
29
+ const plugin = {
30
+ /** plugin name and version eslint reports. */
31
+ meta: {
32
+ name: 'lintofu',
33
+ version: '0.0.0',
34
+ },
35
+ /**
36
+ * the opentofu/terraform hcl language implementation; select it with
37
+ * `language: 'lintofu/opentofu'`.
38
+ */
39
+ languages: {
40
+ opentofu: new OpenTofuLanguage(),
41
+ },
42
+ /** every bundled rule, keyed by rule name. */
43
+ rules,
44
+ configs: {
45
+ /**
46
+ * every rule with `recommended: true`, at its declared severity
47
+ * (error when the rule omits one). preconfigured for `.tf` and
48
+ * `.tofu` files.
49
+ */
50
+ recommended: tierConfig(buildRecommendedRules(rules)),
51
+ /**
52
+ * one `<provider>/recommended` config per provider category, holding
53
+ * the recommended rules of that category. same file coverage as
54
+ * `recommended`.
55
+ */
56
+ ...providerConfigs,
57
+ },
58
+ };
59
+ for (const config of Object.values(plugin.configs)) {
60
+ config.plugins = { lintofu: plugin };
61
+ }
62
+ export default plugin;
63
+ export { OpenTofuLanguage } from './language.js';
64
+ export * from './types.js';
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,wEAAwE;AACxE,2EAA2E;AAC3E,yEAAyE;AACzE,MAAM,UAAU,EAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AAE5C,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,YAAY,CAAU,CAAC;AAW3D,SAAS,UAAU,CAAC,SAA2C;IAC7D,OAAO;QACL,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,kBAAkB;QAC5B,KAAK,EAAE,SAAS;KACjB,CAAC;AACJ,CAAC;AAED,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW,CACxC,mBAAmB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;IACpC,GAAG,QAAQ,cAAc;IACzB,UAAU,CAAC,6BAA6B,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;CAC3D,CAAC,CACsD,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,GAAG;IACb,8CAA8C;IAC9C,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;KACjB;IACD;;;OAGG;IACH,SAAS,EAAE;QACT,QAAQ,EAAE,IAAI,gBAAgB,EAAE;KACjC;IACD,8CAA8C;IAC9C,KAAK;IACL,OAAO,EAAE;QACP;;;;WAIG;QACH,WAAW,EAAE,UAAU,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACrD;;;;WAIG;QACH,GAAG,eAAe;KACnB;CACF,CAAC;AAEF,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IACnD,MAAM,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC;AAED,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,cAAc,YAAY,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { File, Language, LanguageContext, OkParseResult, ParseResult } from '@eslint/core';
2
+ import { type Body } from './parser/index.js';
3
+ import { OpenTofuSourceCode } from './source-code.js';
4
+ /**
5
+ * The eslint Language implementation for opentofu/terraform hcl. The plugin
6
+ * registers it as `lintofu/opentofu`.
7
+ */
8
+ export declare class OpenTofuLanguage implements Language {
9
+ readonly fileType: "text";
10
+ /** 1-indexed lines. */
11
+ readonly lineStart: 1;
12
+ /** 1-indexed columns. */
13
+ readonly columnStart: 1;
14
+ readonly nodeTypeKey = "type";
15
+ /**
16
+ * No-op. The Language interface requires this method even when there are
17
+ * no options to validate.
18
+ */
19
+ validateLanguageOptions(_languageOptions: unknown): void;
20
+ /**
21
+ * Parses hcl. Parser diagnostics with severity 'error' fail the parse;
22
+ * warnings do not. Comments ride on the parse result's open index
23
+ * signature and are read back in {@link OpenTofuLanguage.createSourceCode};
24
+ * they are not part of the ast.
25
+ */
26
+ parse(file: File, _context: LanguageContext): ParseResult<Body>;
27
+ /**
28
+ * Builds the {@link OpenTofuSourceCode} rules receive as
29
+ * `context.sourceCode`. The module directory is the dirname of the file's
30
+ * physical path, which scopes the project index.
31
+ */
32
+ createSourceCode(file: File, parseResult: OkParseResult<Body>, _context: LanguageContext): OpenTofuSourceCode;
33
+ }
34
+ //# sourceMappingURL=language.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,QAAQ,EACR,eAAe,EACf,aAAa,EACb,WAAW,EACZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,KAAK,IAAI,EAA2B,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,QAAQ;IAC/C,QAAQ,CAAC,QAAQ,EAAG,MAAM,CAAU;IACpC,uBAAuB;IACvB,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAU;IAChC,yBAAyB;IACzB,QAAQ,CAAC,WAAW,EAAG,CAAC,CAAU;IAClC,QAAQ,CAAC,WAAW,UAAU;IAE9B;;;OAGG;IAEH,uBAAuB,CAAC,gBAAgB,EAAE,OAAO,GAAG,IAAI;IAExD;;;;;OAKG;IACH,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,GAAG,WAAW,CAAC,IAAI,CAAC;IAwB/D;;;;OAIG;IACH,gBAAgB,CACd,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,aAAa,CAAC,IAAI,CAAC,EAChC,QAAQ,EAAE,eAAe,GACxB,kBAAkB;CActB"}
@@ -0,0 +1,72 @@
1
+ import { dirname, isAbsolute, resolve } from 'node:path';
2
+ import { parseSync } from './parser/index.js';
3
+ import { OpenTofuSourceCode } from './source-code.js';
4
+ /**
5
+ * The eslint Language implementation for opentofu/terraform hcl. The plugin
6
+ * registers it as `lintofu/opentofu`.
7
+ */
8
+ export class OpenTofuLanguage {
9
+ fileType = 'text';
10
+ /** 1-indexed lines. */
11
+ lineStart = 1;
12
+ /** 1-indexed columns. */
13
+ columnStart = 1;
14
+ nodeTypeKey = 'type';
15
+ /**
16
+ * No-op. The Language interface requires this method even when there are
17
+ * no options to validate.
18
+ */
19
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
20
+ validateLanguageOptions(_languageOptions) { }
21
+ /**
22
+ * Parses hcl. Parser diagnostics with severity 'error' fail the parse;
23
+ * warnings do not. Comments ride on the parse result's open index
24
+ * signature and are read back in {@link OpenTofuLanguage.createSourceCode};
25
+ * they are not part of the ast.
26
+ */
27
+ parse(file, _context) {
28
+ const text = typeof file.body === 'string'
29
+ ? file.body
30
+ : new TextDecoder().decode(file.body);
31
+ const result = parseSync(text, { filename: file.path });
32
+ const errors = result.diagnostics.filter((d) => d.severity === 'error');
33
+ if (errors.length > 0) {
34
+ return {
35
+ ok: false,
36
+ errors: errors.map((d) => ({
37
+ message: d.summary,
38
+ line: d.range.start.line,
39
+ column: d.range.start.column,
40
+ endLine: d.range.end.line,
41
+ endColumn: d.range.end.column,
42
+ })),
43
+ };
44
+ }
45
+ return { ok: true, ast: result.ast, comments: result.comments };
46
+ }
47
+ /**
48
+ * Builds the {@link OpenTofuSourceCode} rules receive as
49
+ * `context.sourceCode`. The module directory is the dirname of the file's
50
+ * physical path, which scopes the project index.
51
+ */
52
+ createSourceCode(file, parseResult, _context) {
53
+ const text = typeof file.body === 'string'
54
+ ? file.body
55
+ : new TextDecoder().decode(file.body);
56
+ const moduleDir = deriveModuleDir(file.physicalPath);
57
+ const comments = parseResult.comments ?? [];
58
+ return new OpenTofuSourceCode({
59
+ text,
60
+ ast: parseResult.ast,
61
+ comments,
62
+ moduleDir,
63
+ });
64
+ }
65
+ }
66
+ function deriveModuleDir(filePath) {
67
+ if (!filePath)
68
+ return undefined;
69
+ const abs = isAbsolute(filePath) ? filePath : resolve(filePath);
70
+ return dirname(abs);
71
+ }
72
+ //# sourceMappingURL=language.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language.js","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAA2B,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IAClB,QAAQ,GAAG,MAAe,CAAC;IACpC,uBAAuB;IACd,SAAS,GAAG,CAAU,CAAC;IAChC,yBAAyB;IAChB,WAAW,GAAG,CAAU,CAAC;IACzB,WAAW,GAAG,MAAM,CAAC;IAE9B;;;OAGG;IACH,gEAAgE;IAChE,uBAAuB,CAAC,gBAAyB,IAAS,CAAC;IAE3D;;;;;OAKG;IACH,KAAK,CAAC,IAAU,EAAE,QAAyB;QACzC,MAAM,IAAI,GACR,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;YAC3B,CAAC,CAAC,IAAI,CAAC,IAAI;YACX,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAExD,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;QACxE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBACzB,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI;oBACxB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM;oBAC5B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI;oBACzB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM;iBAC9B,CAAC,CAAC;aACJ,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CACd,IAAU,EACV,WAAgC,EAChC,QAAyB;QAEzB,MAAM,IAAI,GACR,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;YAC3B,CAAC,CAAC,IAAI,CAAC,IAAI;YACX,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAI,WAAW,CAAC,QAAkC,IAAI,EAAE,CAAC;QACvE,OAAO,IAAI,kBAAkB,CAAC;YAC5B,IAAI;YACJ,GAAG,EAAE,WAAW,CAAC,GAAG;YACpB,QAAQ;YACR,SAAS;SACV,CAAC,CAAC;IACL,CAAC;CACF;AAED,SAAS,eAAe,CAAC,QAA4B;IACnD,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ParseResult } from './types.js';
2
+ export type * from './types.js';
3
+ export declare function initialize(): Promise<void>;
4
+ export declare function parseSync(source: string, opts?: {
5
+ filename?: string;
6
+ }): ParseResult;
7
+ export declare function parse(source: string, opts?: {
8
+ filename?: string;
9
+ }): Promise<ParseResult>;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,mBAAmB,YAAY,CAAC;AAwChC,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhD;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,WAAW,CAUb;AAED,wBAAsB,KAAK,CACzB,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,WAAW,CAAC,CAGtB"}