@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.
- 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 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 @@
|
|
|
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"}
|