@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.
- package/LICENSE +661 -0
- package/README.md +151 -2
- package/dist/configs/category-recommended.d.ts +3 -0
- package/dist/configs/category-recommended.d.ts.map +1 -0
- package/dist/configs/category-recommended.js +12 -0
- package/dist/configs/category-recommended.js.map +1 -0
- package/dist/configs/recommended.d.ts +3 -0
- package/dist/configs/recommended.d.ts.map +1 -0
- package/dist/configs/recommended.js +12 -0
- package/dist/configs/recommended.js.map +1 -0
- package/dist/index.d.ts +198 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/language.d.ts +34 -0
- package/dist/language.d.ts.map +1 -0
- package/dist/language.js +72 -0
- package/dist/language.js.map +1 -0
- package/dist/parser/index.d.ts +10 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +43 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/lintofu-parser.wasm +0 -0
- package/dist/parser/types.d.ts +261 -0
- package/dist/parser/types.d.ts.map +1 -0
- package/dist/parser/types.js +3 -0
- package/dist/parser/types.js.map +1 -0
- package/dist/parser/wasm-exec.cjs +575 -0
- package/dist/project-index.d.ts +40 -0
- package/dist/project-index.d.ts.map +1 -0
- package/dist/project-index.js +89 -0
- package/dist/project-index.js.map +1 -0
- package/dist/rules/aws/acm-certificate-create-before-destroy-required.d.ts +6 -0
- package/dist/rules/aws/acm-certificate-create-before-destroy-required.d.ts.map +1 -0
- package/dist/rules/aws/acm-certificate-create-before-destroy-required.js +47 -0
- package/dist/rules/aws/acm-certificate-create-before-destroy-required.js.map +1 -0
- package/dist/rules/aws/no-api-gateway-deployment-stage-args.d.ts +6 -0
- package/dist/rules/aws/no-api-gateway-deployment-stage-args.d.ts.map +1 -0
- package/dist/rules/aws/no-api-gateway-deployment-stage-args.js +56 -0
- package/dist/rules/aws/no-api-gateway-deployment-stage-args.js.map +1 -0
- package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.d.ts +6 -0
- package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.d.ts.map +1 -0
- package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.js +88 -0
- package/dist/rules/aws/no-aurora-pinned-minor-with-auto-upgrade.js.map +1 -0
- package/dist/rules/aws/no-db-instance-name-attribute.d.ts +6 -0
- package/dist/rules/aws/no-db-instance-name-attribute.d.ts.map +1 -0
- package/dist/rules/aws/no-db-instance-name-attribute.js +45 -0
- package/dist/rules/aws/no-db-instance-name-attribute.js.map +1 -0
- package/dist/rules/aws/no-deprecated-lambda-runtime.d.ts +6 -0
- package/dist/rules/aws/no-deprecated-lambda-runtime.d.ts.map +1 -0
- package/dist/rules/aws/no-deprecated-lambda-runtime.js +91 -0
- package/dist/rules/aws/no-deprecated-lambda-runtime.js.map +1 -0
- package/dist/rules/aws/no-eip-vpc-attribute.d.ts +6 -0
- package/dist/rules/aws/no-eip-vpc-attribute.d.ts.map +1 -0
- package/dist/rules/aws/no-eip-vpc-attribute.js +66 -0
- package/dist/rules/aws/no-eip-vpc-attribute.js.map +1 -0
- package/dist/rules/aws/no-eks-addon-resolve-conflicts.d.ts +6 -0
- package/dist/rules/aws/no-eks-addon-resolve-conflicts.d.ts.map +1 -0
- package/dist/rules/aws/no-eks-addon-resolve-conflicts.js +36 -0
- package/dist/rules/aws/no-eks-addon-resolve-conflicts.js.map +1 -0
- package/dist/rules/aws/no-hardcoded-provider-credentials.d.ts +6 -0
- package/dist/rules/aws/no-hardcoded-provider-credentials.d.ts.map +1 -0
- package/dist/rules/aws/no-hardcoded-provider-credentials.js +42 -0
- package/dist/rules/aws/no-hardcoded-provider-credentials.js.map +1 -0
- package/dist/rules/aws/no-iam-policy-attachment.d.ts +6 -0
- package/dist/rules/aws/no-iam-policy-attachment.d.ts.map +1 -0
- package/dist/rules/aws/no-iam-policy-attachment.js +28 -0
- package/dist/rules/aws/no-iam-policy-attachment.js.map +1 -0
- package/dist/rules/aws/no-iam-role-inline-policies.d.ts +6 -0
- package/dist/rules/aws/no-iam-role-inline-policies.d.ts.map +1 -0
- package/dist/rules/aws/no-iam-role-inline-policies.js +43 -0
- package/dist/rules/aws/no-iam-role-inline-policies.js.map +1 -0
- package/dist/rules/aws/no-instance-cpu-count-attributes.d.ts +6 -0
- package/dist/rules/aws/no-instance-cpu-count-attributes.d.ts.map +1 -0
- package/dist/rules/aws/no-instance-cpu-count-attributes.js +94 -0
- package/dist/rules/aws/no-instance-cpu-count-attributes.js.map +1 -0
- package/dist/rules/aws/no-legacy-security-group-rules.d.ts +6 -0
- package/dist/rules/aws/no-legacy-security-group-rules.d.ts.map +1 -0
- package/dist/rules/aws/no-legacy-security-group-rules.js +39 -0
- package/dist/rules/aws/no-legacy-security-group-rules.js.map +1 -0
- package/dist/rules/aws/no-mixed-security-group-rule-forms.d.ts +6 -0
- package/dist/rules/aws/no-mixed-security-group-rule-forms.d.ts.map +1 -0
- package/dist/rules/aws/no-mixed-security-group-rule-forms.js +63 -0
- package/dist/rules/aws/no-mixed-security-group-rule-forms.js.map +1 -0
- package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.d.ts +6 -0
- package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.d.ts.map +1 -0
- package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.js +100 -0
- package/dist/rules/aws/no-pinned-minor-with-auto-upgrade.js.map +1 -0
- package/dist/rules/aws/no-s3-bucket-inline-config.d.ts +6 -0
- package/dist/rules/aws/no-s3-bucket-inline-config.d.ts.map +1 -0
- package/dist/rules/aws/no-s3-bucket-inline-config.js +67 -0
- package/dist/rules/aws/no-s3-bucket-inline-config.js.map +1 -0
- package/dist/rules/aws/no-s3-bucket-object.d.ts +6 -0
- package/dist/rules/aws/no-s3-bucket-object.d.ts.map +1 -0
- package/dist/rules/aws/no-s3-bucket-object.js +41 -0
- package/dist/rules/aws/no-s3-bucket-object.js.map +1 -0
- package/dist/rules/aws/utils.d.ts +4 -0
- package/dist/rules/aws/utils.d.ts.map +1 -0
- package/dist/rules/aws/utils.js +17 -0
- package/dist/rules/aws/utils.js.map +1 -0
- package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.d.ts +6 -0
- package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.d.ts.map +1 -0
- package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.js +66 -0
- package/dist/rules/cloudflare/access-rule-country-block-requires-enterprise.js.map +1 -0
- package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.d.ts +10 -0
- package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.d.ts.map +1 -0
- package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.js +104 -0
- package/dist/rules/cloudflare/dns-record-requires-managed-by-annotation.js.map +1 -0
- package/dist/rules/cloudflare/ruleset-requires-managed-by-description.d.ts +9 -0
- package/dist/rules/cloudflare/ruleset-requires-managed-by-description.d.ts.map +1 -0
- package/dist/rules/cloudflare/ruleset-requires-managed-by-description.js +52 -0
- package/dist/rules/cloudflare/ruleset-requires-managed-by-description.js.map +1 -0
- package/dist/rules/cloudflare/utils.d.ts +17 -0
- package/dist/rules/cloudflare/utils.d.ts.map +1 -0
- package/dist/rules/cloudflare/utils.js +37 -0
- package/dist/rules/cloudflare/utils.js.map +1 -0
- package/dist/rules/core/documented-outputs.d.ts +6 -0
- package/dist/rules/core/documented-outputs.d.ts.map +1 -0
- package/dist/rules/core/documented-outputs.js +41 -0
- package/dist/rules/core/documented-outputs.js.map +1 -0
- package/dist/rules/core/documented-variables.d.ts +6 -0
- package/dist/rules/core/documented-variables.d.ts.map +1 -0
- package/dist/rules/core/documented-variables.js +41 -0
- package/dist/rules/core/documented-variables.js.map +1 -0
- package/dist/rules/core/module-source-pinned.d.ts +6 -0
- package/dist/rules/core/module-source-pinned.d.ts.map +1 -0
- package/dist/rules/core/module-source-pinned.js +47 -0
- package/dist/rules/core/module-source-pinned.js.map +1 -0
- package/dist/rules/core/no-bool-as-count.d.ts +6 -0
- package/dist/rules/core/no-bool-as-count.d.ts.map +1 -0
- package/dist/rules/core/no-bool-as-count.js +69 -0
- package/dist/rules/core/no-bool-as-count.js.map +1 -0
- package/dist/rules/core/no-duplicate-map-keys.d.ts +6 -0
- package/dist/rules/core/no-duplicate-map-keys.d.ts.map +1 -0
- package/dist/rules/core/no-duplicate-map-keys.js +42 -0
- package/dist/rules/core/no-duplicate-map-keys.js.map +1 -0
- package/dist/rules/core/no-empty-list-equality.d.ts +6 -0
- package/dist/rules/core/no-empty-list-equality.d.ts.map +1 -0
- package/dist/rules/core/no-empty-list-equality.js +48 -0
- package/dist/rules/core/no-empty-list-equality.js.map +1 -0
- package/dist/rules/core/no-external-refs-in-destroy-provisioner.d.ts +6 -0
- package/dist/rules/core/no-external-refs-in-destroy-provisioner.d.ts.map +1 -0
- package/dist/rules/core/no-external-refs-in-destroy-provisioner.js +74 -0
- package/dist/rules/core/no-external-refs-in-destroy-provisioner.js.map +1 -0
- package/dist/rules/core/no-legacy-count-attribute.d.ts +6 -0
- package/dist/rules/core/no-legacy-count-attribute.d.ts.map +1 -0
- package/dist/rules/core/no-legacy-count-attribute.js +64 -0
- package/dist/rules/core/no-legacy-count-attribute.js.map +1 -0
- package/dist/rules/core/no-legacy-dot-index.d.ts +6 -0
- package/dist/rules/core/no-legacy-dot-index.d.ts.map +1 -0
- package/dist/rules/core/no-legacy-dot-index.js +79 -0
- package/dist/rules/core/no-legacy-dot-index.js.map +1 -0
- package/dist/rules/core/no-legacy-ignore-changes-wildcard.d.ts +6 -0
- package/dist/rules/core/no-legacy-ignore-changes-wildcard.d.ts.map +1 -0
- package/dist/rules/core/no-legacy-ignore-changes-wildcard.js +40 -0
- package/dist/rules/core/no-legacy-ignore-changes-wildcard.js.map +1 -0
- package/dist/rules/core/no-legacy-list-function.d.ts +6 -0
- package/dist/rules/core/no-legacy-list-function.d.ts.map +1 -0
- package/dist/rules/core/no-legacy-list-function.js +67 -0
- package/dist/rules/core/no-legacy-list-function.js.map +1 -0
- package/dist/rules/core/no-legacy-lookup.d.ts +6 -0
- package/dist/rules/core/no-legacy-lookup.d.ts.map +1 -0
- package/dist/rules/core/no-legacy-lookup.js +53 -0
- package/dist/rules/core/no-legacy-lookup.js.map +1 -0
- package/dist/rules/core/no-legacy-map-function.d.ts +6 -0
- package/dist/rules/core/no-legacy-map-function.d.ts.map +1 -0
- package/dist/rules/core/no-legacy-map-function.js +90 -0
- package/dist/rules/core/no-legacy-map-function.js.map +1 -0
- package/dist/rules/core/no-legacy-provider-proxy.d.ts +6 -0
- package/dist/rules/core/no-legacy-provider-proxy.d.ts.map +1 -0
- package/dist/rules/core/no-legacy-provider-proxy.js +43 -0
- package/dist/rules/core/no-legacy-provider-proxy.js.map +1 -0
- package/dist/rules/core/no-provider-version-constraint.d.ts +6 -0
- package/dist/rules/core/no-provider-version-constraint.d.ts.map +1 -0
- package/dist/rules/core/no-provider-version-constraint.js +33 -0
- package/dist/rules/core/no-provider-version-constraint.js.map +1 -0
- package/dist/rules/core/no-quoted-depends-on.d.ts +6 -0
- package/dist/rules/core/no-quoted-depends-on.d.ts.map +1 -0
- package/dist/rules/core/no-quoted-depends-on.js +55 -0
- package/dist/rules/core/no-quoted-depends-on.js.map +1 -0
- package/dist/rules/core/no-quoted-ignore-changes.d.ts +6 -0
- package/dist/rules/core/no-quoted-ignore-changes.d.ts.map +1 -0
- package/dist/rules/core/no-quoted-ignore-changes.js +63 -0
- package/dist/rules/core/no-quoted-ignore-changes.js.map +1 -0
- package/dist/rules/core/no-quoted-provider-reference.d.ts +6 -0
- package/dist/rules/core/no-quoted-provider-reference.d.ts.map +1 -0
- package/dist/rules/core/no-quoted-provider-reference.js +69 -0
- package/dist/rules/core/no-quoted-provider-reference.js.map +1 -0
- package/dist/rules/core/no-quoted-type-constraints.d.ts +6 -0
- package/dist/rules/core/no-quoted-type-constraints.d.ts.map +1 -0
- package/dist/rules/core/no-quoted-type-constraints.js +55 -0
- package/dist/rules/core/no-quoted-type-constraints.js.map +1 -0
- package/dist/rules/core/no-reserved-variable-names.d.ts +6 -0
- package/dist/rules/core/no-reserved-variable-names.d.ts.map +1 -0
- package/dist/rules/core/no-reserved-variable-names.js +41 -0
- package/dist/rules/core/no-reserved-variable-names.js.map +1 -0
- package/dist/rules/core/no-splat-bracket-index.d.ts +6 -0
- package/dist/rules/core/no-splat-bracket-index.d.ts.map +1 -0
- package/dist/rules/core/no-splat-bracket-index.js +30 -0
- package/dist/rules/core/no-splat-bracket-index.js.map +1 -0
- package/dist/rules/core/no-template-provider.d.ts +6 -0
- package/dist/rules/core/no-template-provider.d.ts.map +1 -0
- package/dist/rules/core/no-template-provider.js +63 -0
- package/dist/rules/core/no-template-provider.js.map +1 -0
- package/dist/rules/core/no-terraform-env-reference.d.ts +6 -0
- package/dist/rules/core/no-terraform-env-reference.d.ts.map +1 -0
- package/dist/rules/core/no-terraform-env-reference.js +35 -0
- package/dist/rules/core/no-terraform-env-reference.js.map +1 -0
- package/dist/rules/core/no-winrm-connection.d.ts +6 -0
- package/dist/rules/core/no-winrm-connection.d.ts.map +1 -0
- package/dist/rules/core/no-winrm-connection.js +31 -0
- package/dist/rules/core/no-winrm-connection.js.map +1 -0
- package/dist/rules/core/prefer-bare-expression.d.ts +6 -0
- package/dist/rules/core/prefer-bare-expression.d.ts.map +1 -0
- package/dist/rules/core/prefer-bare-expression.js +23 -0
- package/dist/rules/core/prefer-bare-expression.js.map +1 -0
- package/dist/rules/core/provider-source-required.d.ts +6 -0
- package/dist/rules/core/provider-source-required.d.ts.map +1 -0
- package/dist/rules/core/provider-source-required.js +45 -0
- package/dist/rules/core/provider-source-required.js.map +1 -0
- package/dist/rules/core/remote-state-outputs-prefix.d.ts +6 -0
- package/dist/rules/core/remote-state-outputs-prefix.d.ts.map +1 -0
- package/dist/rules/core/remote-state-outputs-prefix.js +54 -0
- package/dist/rules/core/remote-state-outputs-prefix.js.map +1 -0
- package/dist/rules/core/required-providers-pinned.d.ts +6 -0
- package/dist/rules/core/required-providers-pinned.d.ts.map +1 -0
- package/dist/rules/core/required-providers-pinned.js +37 -0
- package/dist/rules/core/required-providers-pinned.js.map +1 -0
- package/dist/rules/core/required-version-declared.d.ts +6 -0
- package/dist/rules/core/required-version-declared.d.ts.map +1 -0
- package/dist/rules/core/required-version-declared.js +38 -0
- package/dist/rules/core/required-version-declared.js.map +1 -0
- package/dist/rules/core/typed-variables.d.ts +6 -0
- package/dist/rules/core/typed-variables.d.ts.map +1 -0
- package/dist/rules/core/typed-variables.js +32 -0
- package/dist/rules/core/typed-variables.js.map +1 -0
- package/dist/rules/core/utils.d.ts +31 -0
- package/dist/rules/core/utils.d.ts.map +1 -0
- package/dist/rules/core/utils.js +185 -0
- package/dist/rules/core/utils.js.map +1 -0
- package/dist/rules/index.d.ts +157 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +101 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/types.d.ts +57 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +15 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/source-code.d.ts +84 -0
- package/dist/source-code.d.ts.map +1 -0
- package/dist/source-code.js +170 -0
- package/dist/source-code.js.map +1 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/visitor-keys.d.ts +3 -0
- package/dist/visitor-keys.d.ts.map +1 -0
- package/dist/visitor-keys.js +30 -0
- package/dist/visitor-keys.js.map +1 -0
- package/package.json +88 -4
package/README.md
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
1
|
-
#
|
|
1
|
+
# lintofu
|
|
2
2
|
|
|
3
|
-
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/language.js
ADDED
|
@@ -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"}
|