@synapsestudios/lintofu 0.0.0-stage → 0.9.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 (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 can break us. `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
+ Pre-1.0. API and rule set may change. Published to GitHub Packages under the `@synapsestudios` scope while it's being tested internally; a move to the public npm registry will follow.
9
+
10
+ **Documentation:** [synapsestudios.github.io/lintofu](https://synapsestudios.github.io/lintofu/) covers the user guide, the developer guide, and the full rule index. The same pages are in [`docs/`](./docs), which is what resolves when reading this file from the npm tarball.
11
+
12
+ ## What it is
13
+
14
+ `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)). 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 coding conventions alongside the rest of your ESLint rules.
15
+
16
+ ## Scope
17
+
18
+ `lintofu` checks syntactic and stylistic conventions: documentation, pinning, and the structural patterns listed in the [rule index](./docs/rules/index.md). Its boundaries:
19
+
20
+ - Semantic validation belongs to `tofu validate`. `lintofu` leaves that to OpenTofu.
21
+ - Semantic and evaluated rules (type-checking, symbol resolution) belong to the future built-in linter described in the [OpenTofu linting RFC](https://github.com/opentofu/opentofu/pull/3999), which needs the full language runtime. `lintofu` stays on the syntactic and stylistic side, where ESLint's ecosystem already fits.
22
+ - `lintofu` is a fresh ESLint-native tool. It shares no code with TFLint, which lacks OpenTofu-specific feature support.
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ npm install --save-dev @synapsestudios/lintofu
28
+ ```
29
+
30
+ The package lives in GitHub Packages, so consumers need an `.npmrc` that maps the `@synapsestudios` scope to the registry and supplies a token with `read:packages`:
31
+
32
+ ```
33
+ @synapsestudios:registry=https://npm.pkg.github.com
34
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
35
+ ```
36
+
37
+ Requires Node ^20.19, ^22.13, or >=24 and ESLint ^9 || ^10.
38
+
39
+ ## Supported versions
40
+
41
+ Providers: latest major plus one prior (AWS provider v6 and v5 today); older majors are best effort. Rules that only apply from a given provider major declare it in `meta.docs.appliesTo`. Full policy in [ADR 0005](./docs/adr/0005-version-support-policy.md).
42
+
43
+ The parser wraps HashiCorp's `hcl/v2`, which parses HCL2, the syntax that shipped with Terraform 0.12. That sets a hard floor: Terraform 0.12 and newer parse, Terraform 0.11 and older (HCL1) do not. OpenTofu forked from Terraform 1.5, so every OpenTofu release is above the floor.
44
+
45
+ Above the floor, the same ADR defines the per-generation status:
46
+
47
+ <!-- lintofu:support-matrix:start -->
48
+
49
+ | Tool | Versions | Status |
50
+ | --------- | -------------- | ----------- |
51
+ | OpenTofu | 1.6 and newer | supported |
52
+ | Terraform | 1.0 and newer | supported |
53
+ | Terraform | 0.12 – 0.15 | best effort |
54
+ | Terraform | 0.11 and older | unsupported |
55
+
56
+ <!-- lintofu:support-matrix:end -->
57
+
58
+ - **supported**: generations the rules are written for; advice is kept valid for them as part of normal maintenance.
59
+ - **best effort**: configurations parse and lint. Many rules exist specifically to flag upgrade hazards in these generations (legacy `list(...)`/`map(...)`, quoted type constraints, and friends), but we fix issues as they're reported rather than checking proactively.
60
+ - **unsupported**: HCL1 syntax. The parser rejects these files.
61
+
62
+ These statuses are policy commitments, not CI results: no workflow runs real OpenTofu or Terraform binaries yet. This table is maintained by hand and will be replaced by generated output once the version test harness exists.
63
+
64
+ <!-- lintofu:tested-versions:start -->
65
+
66
+ ## Tested provider versions
67
+
68
+ Provider-specific rules are validated against these versions. [`provider-versions.json`](./provider-versions.json) is the source of truth; edit it and run `npm run docs:versions` to update this section. CI fails if the two drift.
69
+
70
+ - aws provider v6.42.0
71
+ - cloudflare provider v5.24.0
72
+
73
+ <!-- lintofu:tested-versions:end -->
74
+
75
+ ## Usage
76
+
77
+ ```js
78
+ // eslint.config.js
79
+ import lintofu from "@synapsestudios/lintofu";
80
+
81
+ export default [lintofu.configs.recommended];
82
+ ```
83
+
84
+ Or wire up manually for full control:
85
+
86
+ ```js
87
+ // eslint.config.js
88
+ import lintofu from "@synapsestudios/lintofu";
89
+
90
+ export default [
91
+ {
92
+ files: ["**/*.tf", "**/*.tofu"],
93
+ plugins: { lintofu },
94
+ language: "lintofu/opentofu",
95
+ rules: {
96
+ "lintofu/documented-variables": "error",
97
+ "lintofu/required-providers-pinned": "error",
98
+ },
99
+ },
100
+ ];
101
+ ```
102
+
103
+ ## Rules
104
+
105
+ Every rule's description, severity, category, recommended flag, and autofix
106
+ support lives in the generated rule index:
107
+
108
+ - **[Rule index](https://synapsestudios.github.io/lintofu/rules/)** on the
109
+ documentation site
110
+ - [`docs/rules/index.md`](./docs/rules/index.md) in this repository, the same
111
+ content, which resolves here today while the site is still private
112
+
113
+ The index is generated from each rule's `meta.docs` by `npm run docs:rules`, and
114
+ CI fails on a rule with no docs page or a stale index, so it cannot drift from
115
+ the rules that actually ship.
116
+
117
+ ## Requesting a rule
118
+
119
+ 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. A maintainer turns the request into a rule issue from there.
120
+
121
+ ## Parser services
122
+
123
+ Rules can reach a lazily-built, cross-file index through
124
+ `context.sourceCode.parserServices.projectIndex`. The `ProjectIndex` interface
125
+ and its module-directory scope are documented in the developer guide:
126
+ [on the site](https://synapsestudios.github.io/lintofu/developer-guide/parser-services)
127
+ or [`docs/developer-guide/parser-services.md`](./docs/developer-guide/parser-services.md)
128
+ in this repository.
129
+
130
+ ## Writing your own rule
131
+
132
+ `lintofu` re-exports the rule-authoring types it uses internally, so a custom
133
+ rule type-checks without reaching into private modules. The worked example is in
134
+ the developer guide:
135
+ [on the site](https://synapsestudios.github.io/lintofu/developer-guide/writing-a-rule)
136
+ or [`docs/developer-guide/writing-a-rule.md`](./docs/developer-guide/writing-a-rule.md)
137
+ in this repository.
138
+
139
+ ## Architecture
140
+
141
+ The parser is Go: `github.com/hashicorp/hcl/v2/hclsyntax` compiled to
142
+ WebAssembly. The full picture is in the developer guide:
143
+ [on the site](https://synapsestudios.github.io/lintofu/developer-guide/architecture)
144
+ or [`docs/developer-guide/architecture.md`](./docs/developer-guide/architecture.md)
145
+ in this repository.
146
+
147
+ ## Known limitations
148
+
149
+ - **Experimental dependency on `syscall/js`.** See [ADR 0001](./docs/adr/0001-syscall-js-experimental.md). The project inherits its experimental status from Go's WASM JS interop package. Pinning a Go version in CI is the mitigation.
150
+ - **`.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).
151
+ - **No TFLint custom-rule compatibility.** An opinionated migration example may land later; a general compatibility layer is not planned for early releases.
152
+ - **Performance is unprofiled.** Go+WASM boot costs ~100ms per ESLint process; per-file parse is sub-millisecond after boot.
@@ -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"}