# Technical Documentation: terraform-linters/tflint

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/terraform-linters/tflint)

### Overview (`README.md`)

# TFLint
[](https://github.com/terraform-linters/tflint/actions)
[](https://github.com/terraform-linters/tflint/releases/latest)
[](docs/user-guide/compatibility.md)
[](#license)

A Pluggable [Terraform](https://www.terraform.io/) Linter

## Features

TFLint is a framework and each feature is provided by plugins, the key features are as follows:

- Find possible errors (like invalid instance types) for Major Cloud providers (AWS/Azure/GCP).
- Warn about deprecated syntax, unused declarations.
- Enforce best practices, naming conventions.

## Installation

Download the appropriate archive from the [latest release](https://github.com/terraform-linters/tflint/releases/latest), verify it, and install the binary:

```console
curl -sSLO https://github.com/terraform-linters/tflint/releases/latest/download/tflint_linux_amd64.zip
curl -sSLO https://github.com/terraform-linters/tflint/releases/latest/download/checksums.txt
gh attestation verify checksums.txt -R terraform-linters/tflint
sha256sum --ignore-missing -c checksums.txt
unzip tflint_linux_amd64.zip
sudo install -c -v tflint /usr/local/bin/
```

Homebrew (macOS):

```console
brew install terraform-linters/tap/tflint
```

WinGet (Windows):

```console
winget install -e --id TerraformLinters.tflint
```

Go:

```console
go install github.com/terraform-linters/tflint@latest
```

### Verification

#### GitHub CLI (Recommended)

[Artifact Attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds) are available that can be verified using the GitHub CLI.

```console
gh attestation verify checksums.txt -R terraform-linters/tflint
sha256sum --ignore-missing -c checksums.txt
```

#### Cosign (Deprecated)

> [!WARNING]
> Cosign signatures are now deprecated. Please use GitHub CLI.

[Cosign](https://github.com/sigstore/cosign) `verify-blob` command ensures that the release was built with GitHub Actions in this repository.

```console
cosign verify-blob --certificate=checksums.txt.pem --signature=checksums.txt.keyless.sig --certificate-identity-regexp="^https://github.com/terraform-linters/tflint" --certificate-oidc-issuer=https://token.actions.githubusercontent.com checksums.txt
sha256sum --ignore-missing -c checksums.txt
```

### Docker

Instead of installing directly, you can use the Docker image:

```console
docker run --rm -v $(pwd):/data -t ghcr.io/terraform-linters/tflint
```

To download plugins, you can override the entrypoint to a shell (`sh`) to run `--init` and the main command in a single `docker run` command:

```console
 docker run --rm -v $(pwd):/data -t --entrypoint /bin/sh ghcr.io/terraform-linters/tflint -c "tflint --init && tflint"
```

### GitHub Actions

If you want to run on GitHub Actions, [setup-tflint](https://github.com/terraform-linters/setup-tflint) action is available.

## Getting Started

First, enable rules for [Terraform Language](https://www.terraform.io/language) (e.g. warn about deprecated syntax, unused declarations). [TFLint Ruleset for Terraform Language](https://github.com/terraform-linters/tflint-ruleset-terraform) is bundled with TFLint, so you can use it without installing it separately.

The bundled plugin enables the "recommended" preset by default, but you can disable the plugin or use a different preset. Declare the plugin block in `.tflint.hcl` like this:

```hcl
plugin "terraform" {
  enabled = true
  preset  = "recommended"
}
```

See the [tflint-ruleset-terraform documentation](https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/configuration.md) for more information.

Next, If you are using an AWS/Azure/GCP provider, it is a good idea to install the plugin and try it according to each usage:

- [Amazon Web Services](https://github.com/terraform-linters/tflint-ruleset-aws)
- [Microsoft Azure](https://github.com/terraform-linters/tflint-ruleset-azurerm)
- [Google Cloud](https://github.com/terraform-linters/tflint-ruleset-google)

If you want to extend TFLint with other plugins, you can declare the plugins in the config file and easily install them with `tflint --init`.

```hcl
plugin "foo" {
  enabled = true
  version = "0.1.0"
  source  = "github.com/org/tflint-ruleset-foo"
}
```

See also [Configuring Plugins](docs/user-guide/plugins.md).

You can discover plugins from other organizations on GitHub via the [`tflint-ruleset`](https://github.com/topics/tflint-ruleset) topic.

If you want to add custom rules that are not in existing plugins, you can build your own plugin or write your own policy in Rego. See [Writing Plugins](docs/developer-guide/plugins.md) or [OPA Ruleset](https://github.com/terraform-linters/tflint-ruleset-opa).

## Usage

TFLint inspects files under the current directory by default. You can change the behavior with the following options/arguments:

```
/* Detailed source-code truncated for AI context efficiency. */
```

See [User Guide](docs/user-guide) for details.

## Debugging

If you don't get the expected behavior, you can see the detailed logs when running with `TFLINT_LOG` environment variable.

```console
$ TFLINT_LOG=debug tflint
```

## Developing

See [Developer Guide](docs/developer-guide).

## Security

If you find a security vulnerability, please refer our [security policy](SECURITY.md).

## License

Please note that although much of this project is licensed under MPL 2.0, some files in the `terraform` package are licensed under BUSL 1.1.

For the reasons stated above, the executable forms (release binaries) is bound by both licenses.

See also https://discuss.hashicorp.com/t/hashicorp-projects-changing-license-to-business-source-license-v1-1/57106/7

---

### Overview (`docs/README.md`)

# TFLint Documentation

- [User Guide](user-guide)
- [Developer Guide](developer-guide)

---

### Overview (`terraform/README.md`)

# Forked Terraform packages

This directory contains a subset of code from Terraform's internal packages. However, the implementation is not exactly the same, it is just a fork, and simplifications and changes have been made according to our project.

Previously, TFLint uses Terraform as a library, but due to the [package internalization](https://github.com/hashicorp/terraform/issues/26418), it is no longer possible to import from external packages such as TFLint. This is why TFLint has its own fork.

This package provides functionality for static analysis of Terraform Language.

---

### Overview (`docs/developer-guide/README.md`)

# TFLint Developer Guide

The goal of this guide is to quickly understand how TFLint works and to help more developers contribute.

## Table of Contents

- [Architecture](architecture.md)
- [Building TFLint](building.md)
- [Writing Plugins](plugins.md)
- [Releasing a new version of TFLint](releasing.md)

---

### Overview (`docs/user-guide/README.md`)

# TFLint User Guide

This guide describes the various features of TFLint for end users.

## Table of Contents

- [Introduction](../../README.md) (README)
- [Configuring TFLint](config.md)
- [Configuring Plugins](plugins.md)
- [Switching working directory](working-directory.md)
- [Calling Modules](calling-modules.md)
- [Annotations](annotations.md)
- [Autofix](autofix.md)
- [Compatibility with Terraform](compatibility.md)
- [Environment Variables](./environment_variables.md)
- [Editor Integration](editor-integration.md)

---

### Overview (`terraform/ipaddr/README.md`)

# Forked IP address parsing functions

This directory contains a subset of code from the Go project's `net` package
as of Go 1.16, used under the Go project license which we've included here
in [`LICENSE`](LICENSE) and [`PATENTS`](PATENTS), which are also copied from
the Go project.

Terraform has its own fork of these functions because Go 1.17 included a
breaking change to reject IPv4 address octets written with leading zeros.

The Go project rationale for that change was that Go historically interpreted
leading-zero octets inconsistently with many other implementations, trimming
off the zeros and still treating the rest as decimal rather than treating the
octet as octal.

The Go team made the reasonable observation that having a function that
interprets a non-normalized form in a manner inconsistent with other
implementations may cause naive validation or policy checks to produce
incorrect results, and thus it's a potential security concern. For more
information, see [Go issue #30999](https://golang.org/issue/30999).

After careful consideration, the Terraform team has concluded that Terraform's
use of these functions as part of the implementation of the `cidrhost`,
`cidrsubnet`, `cidrsubnets`, and `cidrnetmask` functions has a more limited
impact than the general availability of these functions in the Go standard
library, and so we can't justify a similar exception to our Terraform 1.0
compatibility promises as the Go team made to their Go 1.0 compatibility
promises.

If you're considering using this package for new functionality _other than_ the
built-in functions mentioned above, please do so only if consistency with the
behavior of those functions is important. Otherwise, new features are not
burdened by the same compatibility constraints and so should typically prefer
to use the stricter interpretation of the upstream parsing functions.

---

### Overview (`terraform/tfhcl/README.md`)

# Forked `hcl/ext/dynblock` package

This package is a fork of [`github.com/hashicorp/hcl/ext/dynblock`](https://github.com/hashicorp/hcl/tree/main/ext/dynblock). TFLint maintains its own dynblock implementation to support not only dynamic blocks, but also count/for_each meta-arguments in the HCL layer.

---

### Developer Guide Api Compatibility (`docs/developer-guide/api_compatibility.md`)

# API Compatibility

This is an internal documentation summarizing the currently supported SDK and TFLint versions and any compatibility caveats.

Protocol version: 11  
SDK version: v0.16.0+
TFLint version: v0.46.0+

- Ephemeral value is introduced in SDK v0.22.0 and TFLint v0.55.0. TFLint returns ErrSensitive instead of ephemeral values to plugins built with SDK v0.21.0.
  - https://github.com/terraform-linters/tflint/pull/2178
  - https://github.com/terraform-linters/tflint-plugin-sdk/pull/358

---

### Developer Guide Architecture (`docs/developer-guide/architecture.md`)

# Architecture

TFLint is a pluggable linter and does not contain any rule implementations. Rules are provided as plugins, these are launched by TFLint as subprocesses and communicate over gRPC.

An important part of understanding TFLint's architecture is that TFLint (host) and a plugin act as both gRPC server/client.

For example, the plugin (client) to the host (server) requests to:

- Retrieve Terraform configs (e.g. `aws_instance.main`)
- Evaluate expressions (e.g. `var.foo`)
- Save reported issues to the host server

The host (client) to the plugin (server) requests to:

- Apply plugin configs
- Request to run inspections

The plugin system is implemented by [TFLint plugin SDK](https://github.com/terraform-linters/tflint-plugin-sdk). If you want to know more about `*.proto` and detailed gRPC server/client implementation, check out the SDK.

## Inspection Flow Diagram

The following diagram explains how an inspection is performed when a user executes command:

```
/* Detailed source-code truncated for AI context efficiency. */
```

### CLI (`cmd` package)

[The `cmd` package](https://github.com/terraform-linters/tflint/tree/master/cmd) is the entrypoint of the CLI. `cmd.CLI` has streams to stdout/stderr and prints a result to the screen.

Depending on the user's instructions, it does the following:

- Run inspection
- Initialize TFLint (install plugins)
- Start language server
- Print version info
- Run bundled plugin (internal use)

This package is responsible for parsing CLI flags and arguments. The parsed `cmd.Option` is converted to `tflint.Config` and merged with a config file.

### Load TFLint config (`tflint.LoadConfig`)

[The `tflint` package](https://github.com/terraform-linters/tflint/tree/master/tflint) provides many features related to TFLint, such as loading a config file (`.tflint.hcl`) and parsing annotations (`# tflint-ignore` comments).

The `tflint.LoadConfig` loads a config file and returns `tflint.Config`. This config will be used in later steps.

### Load Terraform config (`terraform.LoadConfig`)

[The `terraform` package](https://github.com/terraform-linters/tflint/tree/master/terraform) is a fork of [github.com/hashicorp/terraform/internal](https://pkg.go.dev/github.com/hashicorp/terraform/internal). This package is responsible for processing the Terraform semantics, such as parsing `*.tf` files, evaluating expressions, and loading modules.

The `terraform.LoadConfig` reads `*.tf` files as a `terraform.Config` in the given directory. These structures are designed to be as similar to Terraform core. See "The Design of `terraform` Package" section below for details.

### Discover plugins (`plugin.Discovery`)

[The `plugin` package](https://github.com/terraform-linters/tflint/tree/master/plugin) is responsible for the plugin system. This package contains gRPC server implementation, installation, discovery, etc.

The `plugin.Discovery` discovers installed plugins and launches plugin binaries as subprocesses. This detailed implementation is hidden by [github.com/hashicorp/go-plugin](https://github.com/hashicorp/go-plugin).

### Launch plugin processes (go-plugin)

The go-plugin launches a plugin binary as a subprocess. The launched plugin acts as a gRPC server and communicates with the host process.

The plugin server is called "RuleSet" server. Its behavior is implemented by a plugin developer.

### Apply configs to plugins (`ruleset.ApplyConfig`)

The `plugin.Discovery` returns a client for the RuleSet server it started. Use the `ApplyConfig` method to send the plugin config described in the config file to the server.

### Request inspection to plugins (`ruleset.Check`)

Similarly, send inspection requests to the server. The server responds to requests and runs an inspection, but the plugin needs access to `terraform.Config` (imagine `runner.GetResourceContent`).

For this, the host process launches a gRPC server to respond such requests. The host server is called "Runner" server. Its behavior is implemented by TFLint. The plugin is passed a client that corresponds to the Runner server.

### Respond to requests for Terraform config (`plugin.GRPCServer`)

The Runner server responds to requests from plugins to retrieve Terraform configs, evaluate expressions, etc. This implementation is contained in the `plugin` package.

### Save issues emitted by plugins (`plugin.GRPCServer`)

The Runner server saves issues emitted by plugins (imagine `runner.EmitIssue`). The saved issues will be printed to the screen in the next step.

### Print issues (`formatter.Print`)

[The `formatter` package](https://github.com/terraform-linters/tflint/tree/master/formatter)  processes and outputs issues in formats such as default, JSON, and SARIF.

## Inspection Sequence Diagram

The following diagram explains how the host process, RuleSet server, and Runner server each behave in inspection:

```mermaid
sequenceDiagram
  participant TFLint as TFLint (host)
  participant RuleSet as RuleSet (plugin)
  participant Runner as Runner (host as goroutie)
  TFLint->>RuleSet: Start server
  TFLint->>RuleSet: Apply plugin configs
  TFLint->>Runner: Start server
  TFLint->>RuleSet: Request to run inspection
  RuleSet->>Runner: Request to get Terraform configs
  Runner-->>RuleSet: Return resources/modules
  RuleSet->>Runner: Request to evaluate expressions
  Runner-->>RuleSet: Return evaluated values
  RuleSet->>Runner: Emit issues
  RuleSet-->>TFLint: End of inspection
  Runner-->>TFLint: Return issues
```

## The Design of `terraform` Package

The `terraform` package is a fork of Terraform internal packages. It is based on the same Terraform core, but with some implementation changes specifically for static analysis.

The following diagram explains the dependencies of each component:

```mermaid
flowchart TB
  files["Terraform config files<br/>(*.tf)"]-->parser["Language parser<br/>(terraform.Parser)"]
  subgraph child module
    modFiles["Terraform config files<br/>(*.tf)"]-->modParser["Language parser<br/>(terraform.Parser)"]
  end
  modParser-->modWalker["Module walker<br/>(terraform.ModuleWalker)"]  
  parser-- terraform.Module -->loader["Config loader<br/>(terraform.LoadConfig)"]
  modWalker-- terraform.Module -->loader
  loader-- terraform.Config -->gatherVars["Gather variables<br/>(terraform.VariableValues)"]
  valuesFile["Variable definitions files<br/>(*.tfvars)"]-- terraform.InputValues -->gatherVars
  cliFlag["CLI flags<br/>(--var)"]-- terraform.InputValues -->gatherVars
  gatherVars-- cty.Value -->evaluator["Evaluator<br/>(terraform.Evaluator)"]
  loader-- terraform.Config -->evaluator
```

### HCL and cty

The underlying technologies of Terraform language are [HCL](https://github.com/hashicorp/hcl) and [cty](https://github.com/zclconf/go-cty). The Terraform language implements its own semantics on top of these.

TFLint also leverages these technologies, making it highly compatible with Terraform.

### Differences from Terraform

The basic architecture is the same as Terraform. The biggest difference is that the `terraform.Evaluator` is state (`terraform.State`) independent. See also [Terraform Core Architecture Summary
](https://github.com/hashicorp/terraform/blob/main/docs/architecture.md).

State is not always available for static analysis as it can only be obtained by running terraform plan/apply. TFLint solves this problem in the same way as Terraform. Terraform has the ability to handle unknown values during first-time planning, and TFLint take advantage of this to always treat dynamic values as unknowns.

It has the same basic design as Terraform, so it has the advantage of being able to easily support future language extensions of Terraform.

### Lazy Schema Evaluation

Terraform has a predefined schema and uses it to decode HCL body. This is necessary to strictly define the syntax, but TFLint does not necessarily require a strict schema in order to support many versions of the Terraform language.

Against this background, `terraform.Module` in TFLint decodes minimal structure (e.g. `terraform.Variable`, `terraform.Resource`). The `terraform.Module` has native `hcl.File` and returns bodies based on the schema requested during inspection. In other words, it does lazy schema evaluation to achieve syntactic robustness.

For this reason, TFLint take a different approach to implementing `for_each` and `count` meta-arguments, and dynamic blocks than Terraform. Instead of being pre-expanded, it is expanded as an extension of `hcl.Body` when extracted by the schema.

---

### Developer Guide Building (`docs/developer-guide/building.md`)

# Building TFLint

Go 1.24 or higher is required to build TFLint from source code. Clone the source code and run the `make` command. Built binary will be placed in `dist` directory.

```console
$ git clone https://github.com/terraform-linters/tflint.git
$ cd tflint
$ make
mkdir -p dist
go build -v -o dist/tflint
```

## Run tests

If you change code, make sure that the tests you add and existing tests will be passed:

```console
$ make test
```

Some tests depending on Git submodules. Running `make test` will update these automatically, but if you run tests directly with `go test`, you need to update submodules manually:

```sh
git submodule init
git submodule update
```

## Run E2E tests

You can check the actual CLI behavior by running the E2E tests. Since the E2E tests uses the installed `tflint` command, it is necessary to add the path into `$PATH` environment so that the binary built by `go install` can be referenced.

```console
$ make e2e
```

---

### Developer Guide Plugins (`docs/developer-guide/plugins.md`)

# Writing Plugins

If you want to add custom rules, you can write ruleset plugins.

## Overview

Plugins are independent binaries and use [go-plugin](https://github.com/hashicorp/go-plugin) to communicate with TFLint over gRPC. TFLint executes the binary when the plugin is enabled, and the plugin process must act as an gRPC server for TFLint.

If you want to create a new plugin, [The template repository](https://github.com/terraform-linters/tflint-ruleset-template) is available to satisfy these specification. You can create your own repository from "Use this template" and easily add rules based on some reference rules.

The template repository uses the [SDK](https://github.com/terraform-linters/tflint-plugin-sdk) that wraps the go-plugin for communication with TFLint. See also the [Architecture](https://github.com/terraform-linters/tflint-plugin-sdk#architecture) section for the architecture of the plugin system.

## 1. Creating a repository from the template

Visit [tflint-ruleset-template](https://github.com/terraform-linters/tflint-ruleset-template) and click the "Use this template" button. Repository name must be `tflint-ruleset-*`.

For public repositories, you can optionally add the [`tflint-ruleset`](https://github.com/topics/tflint-ruleset) topic to make your ruleset discoverable on the topic page.

## 2. Building and installing the plugin

The created repository can be installed locally with `make install`. Enable the plugin as follows and verify that the installed plugin works.

```hcl
plugin "template" {
    enabled = true
}
```

```console
$ make install
go build
mkdir -p ~/.tflint.d/plugins
mv ./tflint-ruleset-template ~/.tflint.d/plugins
$ tflint -v
TFLint version 0.28.1
+ ruleset.template (0.1.0)
```

## 3. Changing/Adding the rules

Rename the ruleset and add/edit rules. After making changes, you can check the behavior with `make install`. See also the [tflint-plugin-sdk API reference](https://pkg.go.dev/github.com/terraform-linters/tflint-plugin-sdk) for communication with the host process.

## 4. Creating a GitHub Release

You can build and install your own ruleset locally as described above, but you can also install it automatically with `tflint --init`.

The requirements to support automatic installation are as follows:

- The built plugin binaries must be published on GitHub Release
- The release must be tagged with a name like `v1.1.1`
- The release must contain an asset with a name like `tflint-ruleset-{name}_{GOOS}_{GOARCH}.zip`
- The zip file must contain a binary named `tflint-ruleset-{name}` (`tflint-ruleset-{name}.exe` in Windows)
- The release must contain a checksum file for the zip file with the name `checksums.txt`
- The checksum file must contain a sha256 hash and filename

When signing a release, one of the following requirements must be met:

- [Artifact Attestation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds)
  - Include a step in your GitHub Actions workflow that uses the [`attest-build-provenance` action](https://github.com/actions/attest-build-provenance) for `checksums.txt`.
- PGP signing key
  - The release must contain a signature file for the checksum file with the name `checksums.txt.sig`
  - The signature file must be binary OpenPGP format

Releases that meet these requirements can be easily created by following the GoReleaser config in the template repository.

---

### Developer Guide Releasing (`docs/developer-guide/releasing.md`)

# Releasing a new version of TFLint

Maintainers with push access to TFLint can publish new releases.
Keep the following in mind when publishing a release:

- Avoid changing behavior in patch versions as much as possible. Patch version updates should only contain bug fixes.
  - However, if the behavior changes due to bug fixes, this is not the case.
- Write readable release notes. It would be nice to know what the benefits are of updating to this version, or what needs to be changed to update.

To do this efficiently, releasing is automated as much as possible. After merging the changes you want to release into the master branch, run `make release` on the master branch.

```console
$ make release
```

The `make release` does the following:

- Accept new version as input and automatically rewrites the necessary files.
- Automatically generate a release note. You can edit it interactively.
- Run the required tests.
- Create a commit, tag it and push it to the remote.

Note that this script does not build any binaries. These run on GitHub Actions. This script just pulls the trigger for it.

---

### User Guide Annotations (`docs/user-guide/annotations.md`)

# Annotations

TFLint supports several comment annotations for suppressing issues for specific lines or files. Annotations can only suppress _issues_ emitted from fully valid, parseable Terraform modules. _Errors_ cannot be ignored.

Rule configs can opt out of annotation-based suppression by setting
`ignorable = false`. In that case, `tflint-ignore` and `tflint-ignore-file`
annotations do not suppress issues from that rule.

Annotation comments can disable rules on specific lines:

```hcl
resource "aws_instance" "foo" {
    # tflint-ignore: aws_instance_invalid_type
    instance_type = "t1.2xlarge"
}
```

Multiple rules can be specified as a comma-separated list:

```hcl
resource "aws_instance" "foo" {
    # tflint-ignore: aws_instance_invalid_type, other_rule
    instance_type = "t1.2xlarge"
}
```

All rules can be ignored by specifying the `all` keyword:

```hcl
resource "aws_instance" "foo" {
    # tflint-ignore: all
    instance_type = "t1.2xlarge"
}
```

It's a good idea to add a reason for why a rule is ignored, especially temporarily:

```hcl
resource "aws_instance" "foo" {
    # This instance type is new and TFLint doesn't know about it yet
    # tflint-ignore: aws_instance_invalid_type
    instance_type = "t10.2xlarge"
}
```

Or, on the same line:

```hcl
resource "aws_instance" "foo" {
  # tflint-ignore: aws_instance_invalid_type # too new for TFLint
  instance_type = "t10.2xlarge" 
}
```

The `//` comment style is also supported, but Terraform recommends `#`.

```hcl
resource "aws_instance" "foo" {
  // tflint-ignore: aws_instance_invalid_type // too new for TFLint
  instance_type = "t10.2xlarge" 
}
```

## Files

To disable an entire file, you can also use the `tflint-ignore-file` annotation:

```hcl
# tflint-ignore-file: aws_instance_invalid_type

resource "aws_instance" "foo" {
  instance_type = "t1.2xlarge"
}
```

This annotation is valid only at the top of the file. The following cannot be used and will result in an error:

```hcl
resource "aws_instance" "foo" {
  # tflint-ignore-file: aws_instance_invalid_type
  instance_type = "t1.2xlarge"
}
```

```hcl
resource "aws_instance" "foo" { # tflint-ignore-file: aws_instance_invalid_type
  instance_type = "t1.2xlarge"
}
```

## JSON

The `tflint-ignore-file` annotation is also supported in Terraform JSON by using a top-level [comment property](https://developer.hashicorp.com/terraform/language/syntax/json#comment-properties):

```json
{
  "//": "tflint-ignore-file: aws_instance_invalid_type",
  "resource": {
    "aws_instance": {
      "foo": {
        "instance_type": "t2.micro"
      }
    }
  }
}
```

As with annotations in HCL files, multiple rules can be specified as a
comma-separated list:

```json
{
  "//": "tflint-ignore-file: aws_instance_invalid_type, other_rule",
  "resource": {
    "aws_instance": {
      "foo": {
        "instance_type": "t2.micro"
      }
    }
  }
}
```

Similarly, annotations in JSON can be followed with arbitrary comments, but the annotation must be the first thing in the comment property string:

```json
{
  "//": "tflint-ignore-file: aws_instance_invalid_type # This instance type is new and TFLint doesn't know about it yet",
  "resource": {
    "aws_instance": {
      "foo": {
        "instance_type": "t2.micro"
      }
    }
  }
}
```

The `tflint-ignore` annotation is not supported in JSON configuration.

---

### User Guide Autofix (`docs/user-guide/autofix.md`)

# Autofix

Some issues reported by TFLint can be auto-fixable. Auto-fixable issues are marked as "Fixable" as follows:

```console
$ tflint
1 issue(s) found:

Warning: [Fixable] Single line comments should begin with # (terraform_comment_syntax)

  on main.tf line 1:
   1: // locals values
   2: locals {

```

When run with the `--fix` option, TFLint will fix issues automatically.

```console
$ tflint --fix
1 issue(s) found:

Warning: [Fixed] Single line comments should begin with # (terraform_comment_syntax)

  on main.tf line 1:
   1: // locals values
   2: locals {

```

Please note that not all issues are fixable. The rule must support autofix.

If autofix is applied, it will automatically format the entire file. As a result, unrelated ranges may change.

---

### User Guide Calling Modules (`docs/user-guide/calling-modules.md`)

# Calling Modules

You can inspect not only the root module but also [module calls](https://developer.hashicorp.com/terraform/language/modules/syntax#calling-a-child-module). TFLint evaluates each call (i.e. `module` block) and emits any issues that result from the specified input variables.

```hcl
module "aws_instance" {
  source        = "./module"

  ami           = "ami-b73b63a0"
  instance_type = "t1.2xlarge"
}
```

```console
$ tflint
1 issue(s) found:

Error: instance_type is not a valid value (aws_instance_invalid_type)

  on template.tf line 5:
   5:   instance_type = "t1.2xlarge"

Callers:
   template.tf:5,19-31
   module/instance.tf:5,19-36

```

By default, TFLint only calls local modules whose the `source` is a relative path like `./*`. If you want to call remote modules (registry, git, etc.), you must run `terraform init` (or `terraform get`) before invoking TFLint so that modules are loaded into the `.terraform` directory. After that, invoke TFLint with `--call-module-type=all`.

```console
$ terraform init
$ tflint --call-module-type=all
```

The `--call-module-type` can also be set via configuration:

```hcl
config {
  call_module_type = "all"
}
```

If you don't want to call any modules, pass `--call-module-type=none`:

```console
$ tflint --call-module-type=none
```

If you want to ignore inspection for a particular module, you can use `--ignore-module`:

```console
$ tflint --ignore-module=./module
```

## Caveats

* Issues _must_ be associated with a variable that was passed to the module. If an issue within a child module is detected in an expression that does not reference a variable (`var`), it will be discarded.
* Rules that evaluate syntax rather than content _should_ ignore child modules.
* If you want to evaluate all TFLint rules on non-root modules, inspect directly against the module directories. Note that there is a difference between calling a child module in an inspection and inspecting a child module as the root module.

---

### User Guide Compatibility (`docs/user-guide/compatibility.md`)

# Compatibility with Terraform

TFLint interprets the [Terraform language](https://developer.hashicorp.com/terraform/language) with its own parser which is a fork of the Terraform's native one. This allows it to be parsed correctly even if Terraform is not installed at runtime.

The parser supports Terraform v1.x syntax and semantics. The language compatibility on Terraform v1.x is defined by [Compatibility Promises](https://developer.hashicorp.com/terraform/language/v1-compatibility-promises). TFLint follows this promise. New features are only supported in newer TFLint versions, and bug and experimental features compatibility are not guaranteed.

The latest supported version is Terraform v1.16.

## Input Variables

Like Terraform, TFLint supports the `--var`,` --var-file` options, environment variables (`TF_VAR_*`), and automatically loading variable definitions (`terraform.tfvars` and `*.auto.tfvars`) files. See [Input Variables](https://developer.hashicorp.com/terraform/language/values/variables).

Input variables are evaluated just like in Terraform:

```hcl
variable "instance_type" {
  default = "t2.micro"
}

resource "aws_instance" "foo" {
  instance_type = var.instance_type # => "t2.micro"
}
```

Unknown variables (e.g. no default) are ignored:

```hcl
variable "instance_type" {}

resource "aws_instance" "foo" {
  instance_type = var.instance_type # => ignored
}
```

Sensitive or ephemeral variables are ignored. This is to avoid unintended disclosure.

```hcl
variable "instance_type" {
  sensitive = true
  default   = "t2.micro"
}

resource "aws_instance" "foo" {
  instance_type = var.instance_type # => ignored
}
```

## Local Values

TFLint supports [Local Values](https://developer.hashicorp.com/terraform/language/values/locals).

```hcl
variable "foo" {
  default = "variable value"
}

locals {
  static   = "static value"
  variable = var.foo
  local    = local.static
  resource = aws_instance.main.arn
}

local.static   # => "static value"
local.variable # => "variable value"
local.local    # => "static value"
local.resource # => ignored (unknown)
```

## The `count` and `for_each` Meta-Arguments

TFLint supports the [`count`](https://developer.hashicorp.com/terraform/language/meta-arguments/count) and [`for_each`](https://developer.hashicorp.com/terraform/language/meta-arguments/for_each) meta-arguments.

```hcl
resource "aws_instance" "foo" {
  count = 0

  instance_type = "invalid" # => ignored because the resource is not created
}
```

```hcl
resource "aws_instance" "foo" {
  count = 2

  instance_type = "t${count.index}.micro" # => "t0.micro" and "t1.micro"
}
```

Note that this behavior may differ depending on a rule. Rules like `terraform_deprecated_syntax` will check resources regardless of the meta-argument values.

If the meta-arguments are unknown, the resource/module is ignored:

```hcl
variable "count" {}

resource "aws_instance" "foo" {
  count = var.count

  instance_type = "invalid" # => ignored
}
```

## The `path.*` and `terraform.*` Values

TFLint supports [filesystem and workspace info](https://developer.hashicorp.com/terraform/language/expressions/references#filesystem-and-workspace-info).

- `path.module`
- `path.root`
- `path.cwd`
- `terraform.workspace`.

The [`terraform.applying`](https://developer.hashicorp.com/terraform/language/functions/terraform-applying) always resolves to false.

## Unsupported Named Values

The values below are state-dependent or cannot be determined statically, so TFLint resolves them to unknown values.

- `<RESOURCE TYPE>.<NAME>`
- `resource.<RESOURCE TYPE>.<NAME>`
- `ephemeral.<RESOURCE TYPE>.<NAME>`
- `module.<MODULE NAME>`
- `data.<DATA TYPE>.<NAME>`
- `self.<NAME>`

The `ephemeral.<RESOURCE TYPE>.<NAME>` always resolves to an unknown value marked as ephemeral, which is the same in most cases as anything else, but some rules may treat it differently.

## Functions

[Built-in Functions](https://developer.hashicorp.com/terraform/language/functions) are fully supported. However, functions such as [`plantimestamp`](https://developer.hashicorp.com/terraform/language/functions/plantimestamp) whose return value cannot be determined statically will return an unknown value.

[Provider-defined functions](https://www.hashicorp.com/blog/terraform-1-8-adds-provider-functions-for-aws-google-cloud-and-kubernetes) always return unknown values, except for `provider::terraform::*` functions.

## Dynamic Blocks

TFLint supports [dynamic blocks](https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks).

```hcl
resource "aws_instance" "dynamic" {
  dynamic "ebs_block_device" {
    for_each = toset([
      { size = 10 },
      { size = 20 }
    ])
    content {
      volume_size = ebs_block_device.value["size"] # => 10 and 20
    }
  }
}
```

Similar to support for meta-arguments, some rules may process a dynamic block as-is without expansion.

If the `for_each` is unknown, the expanded block will be empty. If the `for_each` is sensitive or ephemeral, TFLint expands dynamic blocks like Terraform does, but the mark does not propagate to children and expressions containing iterators resolve to unknown. This is a backwards compatibility limitation that may be resolved in a future version.

## Modules

TFLint doesn't automatically inspect the content of modules themselves. However, by default, it will analyze their content in order to raise any issues that arise from attributes in module calls.

```hcl
resource "aws_instance" "static" {
  ebs_block_device {
    encrypted = false # => Must be encrypted
  }
}

module "aws_instance" {
  source = "./module/aws_instance"

  encrypted = false # => Must be encrypted
}
```

Remote modules can also be inspected. See [Calling Modules](./calling-modules.md) for details.

If the module source contains an unknown value, the module will be ignored:

```hcl
variable "module_path" {
  const = true
}

module "aws_instance" {
  source = var.module_path

  encrypted = false # => ignored
}
```

## Environment Variables

The following environment variables are supported:

- [TF_VAR_name](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_var_name)
- [TF_DATA_DIR](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_data_dir)
- [TF_WORKSPACE](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_workspace)

---

### User Guide Config (`docs/user-guide/config.md`)

# Configuring TFLint

You can change the behavior not only in CLI flags but also in config files. TFLint loads config files according to the following priority order:

1. File passed by the `--config` option
2. File set by the `TFLINT_CONFIG_FILE` environment variable
3. Current directory `./.tflint.hcl`
4. Current directory `./.tflint.json`
5. Home directory `~/.tflint.hcl`
6. Home directory `~/.tflint.json`

The config file can be written in either [HCL](https://github.com/hashicorp/hcl) or JSON format, determined by the file extension. JSON files use the [HCL-compatible JSON syntax](https://developer.hashicorp.com/terraform/language/syntax/json), following the same structure as Terraform's `.tf.json` files. An HCL example is shown below:

```hcl
tflint {
  required_version = ">= 0.50"
}

config {
  format = "compact"
  plugin_dir = "~/.tflint.d/plugins"

  call_module_type = "local"
  force = false
  disabled_by_default = false

  ignore_module = {
    "terraform-aws-modules/vpc/aws"            = true
    "terraform-aws-modules/security-group/aws" = true
  }

  varfile = ["example1.tfvars", "example2.tfvars"]
  variables = ["foo=bar", "bar=[\"baz\"]"]
}

plugin "aws" {
  enabled = true
  version = "0.4.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}

rule "aws_instance_invalid_type" {
  enabled = false
}
```

The same configuration can be written in JSON format as `.tflint.json`:

```json
{
  "tflint": {
    "required_version": ">= 0.50"
  },
  "config": {
    "format": "compact",
    "plugin_dir": "~/.tflint.d/plugins",
    "call_module_type": "local",
    "force": false,
    "disabled_by_default": false,
    "ignore_module": {
      "terraform-aws-modules/vpc/aws": true,
      "terraform-aws-modules/security-group/aws": true
    },
    "varfile": ["example1.tfvars", "example2.tfvars"],
    "variables": ["foo=bar", "bar=[\"baz\"]"]
  },
  "plugin": {
    "aws": {
      "enabled": true,
      "version": "0.4.0",
      "source": "github.com/terraform-linters/tflint-ruleset-aws"
    }
  },
  "rule": {
    "aws_instance_invalid_type": {
      "enabled": false
    }
  }
}
```

The file path is resolved relative to the module directory when `--chdir` or `--recursive` is used. To use a config file from the working directory when recursing, pass an absolute path:

```sh
tflint --recursive --config "$(pwd)/.tflint.hcl"
# or
tflint --recursive --config "$(pwd)/.tflint.json"
```

### `required_version`

Restrict the TFLint version used. This is almost the same as [Terraform's `required_version`](https://developer.hashicorp.com/terraform/language/settings#specifying-a-required-terraform-version).
You can write version constraints in the same way.

### `format`

CLI flag: `--format`

Change the output format. The following values are valid:

- default
- json
- checkstyle
- junit
- compact
- sarif

In recursive mode (`--recursive`), this field will be ignored in configuration files and must be set via a flag.

### `plugin_dir`

Set the plugin directory. The default is `~/.tflint.d/plugins` (or `./.tflint.d/plugins`). See also [Configuring Plugins](plugins.md#advanced-usage)

### `call_module_type`

CLI flag: `--call-module-type`

Select types of module to call. The following values are valid:

- all
- local (default)
- none

If you select `all`, you can call all (local and remote) modules. See [Calling Modules](./calling-modules.md).

```hcl
config {
  call_module_type = "all"
}
```

```console
$ tflint --call-module-type=all
```

### `force`

CLI flag: `--force`

Return zero exit status even if issues found. TFLint returns the following exit statuses on exit by default:

- 0: No issues found
- 1: Errors occurred
- 2: No errors occurred, but issues found

In recursive mode (`--recursive`), this field will be ignored in configuration files and must be set via a flag.

### `disabled_by_default`

CLI flag: `--only`

Only enable rules specifically enabled in the config or on the command line. All other rules, including defaults, are disabled. Note, usage of `--only` on the command line will ignore other rules passed in via `--enable-rule` or `--disable-rule`.

```hcl
config {
  disabled_by_default = true
  # other options here...
}

rule "aws_instance_invalid_type" {
  enabled = true
}

rule "aws_instance_previous_type" {
  enabled = true
}
```

```console
$ tflint --only aws_instance_invalid_type --only aws_instance_previous_type
```

### `ignore_module`

CLI flag: `--ignore-module`

Adding a module source to `ignore_module` will cause it to be ignored when [calling modules](./calling-modules.md). Note that you need to specify module sources rather than module ids for backward compatibility.

```hcl
config {
  call_module_type = "all"
  ignore_module = {
    "terraform-aws-modules/vpc/aws"            = true
    "terraform-aws-modules/security-group/aws" = true
  }
}
```

```console
$ tflint --ignore-module terraform-aws-modules/vpc/aws --ignore-module terraform-aws-modules/security-group/aws
```

### `varfile`

CLI flag: `--var-file`

Set Terraform variables from `tfvars` files. If `terraform.tfvars` or any `*.auto.tfvars` files are present, they will be automatically loaded.

```hcl
config {
  varfile = ["example1.tfvars", "example2.tfvars"]
}
```

```console
$ tflint --var-file example1.tfvars --var-file example2.tfvars
```

### `variables`

CLI flag: `--var`

Set a Terraform variable from a passed value. This flag can be set multiple times.

```hcl
config {
  variables = ["foo=bar", "bar=[\"baz\"]"]
}
```

```console
$ tflint --var "foo=bar" --var "bar=[\"baz\"]"
```

### `rule` blocks

CLI flag: `--enable-rule`, `--disable-rule`

You can configure TFLint rules using `rule` blocks. Each rule's implementation specifies whether it will be enabled by default. In some rulesets, the majority of rules are disabled by default. Use `rule` blocks to enable them:

```hcl
rule "terraform_unused_declarations" {
  enabled = true
}
```

The `enabled` attribute is required for all `rule` blocks. For rules that are enabled by default, set `enabled = false` to disable the rule:

```hcl
rule "aws_instance_previous_type" {
  enabled = false
}
```

By default, annotations can suppress issues from a rule. Set `ignorable = false`
to prevent `tflint-ignore` and `tflint-ignore-file` annotations from suppressing
that rule:

```hcl
rule "aws_instance_previous_type" {
  enabled   = true
  ignorable = false
}
```

Some rules support additional attributes that configure their behavior. See the documentation for each rule for details.

### `plugin` blocks

You can declare the plugin to use. See [Configuring Plugins](plugins.md)

## Rule config priority

The priority of rule configs is as follows:

1. `--only` (CLI flag)
2. `--enable-rule`, `--disable-rule` (CLI flag)
3. `rule` blocks (config file)
4. `preset` (config file, tflint-ruleset-terraform only)
5. `disabled_by_default` (config file)

---

### User Guide Editor Integration (`docs/user-guide/editor-integration.md`)

# Editor Integration

TFLint can also act as a Language Server to integrate with various editors. This server conforms to [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) v3.14.0 and can be used with an editor that implements any client. The following is an example in VS Code:



This server can be started with the `--langserver` option:

```console
$ tflint --langserver
14:21:51 cli.go:185: Starting language server...
```

Currently, it only supports diagnostics and subscribes the following methods:

- `initialize`
- `initialized`
- `shutdown`
- `exit`
- `textDocument/didOpen`
- `textDocument/didClose`
- `textDocument/didChange`
- `workspace/didChangeWatchedFiles`

---

### User Guide Environment Variables (`docs/user-guide/environment_variables.md`)

# Environment Variables

Below is a list of environment variables available in TFLint.

- `TFLINT_LOG`
  - Print logs to stderr. See [Debugging](../../README.md#debugging).
- `TFLINT_CONFIG_FILE`
  - Configure the config file path. See [Configuring TFLint](./config.md).
- `TFLINT_PLUGIN_DIR`
  - Configure the plugin directory. See [Configuring Plugins](./plugins.md).
- `TFLINT_DISABLE_VERSION_CHECK`
  - Disable version update notifications when running `tflint --version`. Set to `1` to disable.
- `GITHUB_TOKEN`
  - (Optional) Used for authenticated GitHub API requests when checking for updates and downloading plugins. Increases the rate limit from 60 to 5000 requests per hour. Useful if you encounter rate limit errors. You can obtain a token by creating a [GitHub personal access token](https://github.com/settings/tokens); no special scopes are required.
- `TFLINT_EXPERIMENTAL`
  - Enable experimental features. Note that experimental features are subject to change without notice. Currently there is no impact.
- `TF_VAR_name`
  - Set variables for compatibility with Terraform. See [Compatibility with Terraform](./compatibility.md).
- `TF_DATA_DIR`
  - Configure the `.terraform` directory for compatibility with Terraform. See [Compatibility with Terraform](./compatibility.md).
- `TF_WORKSPACE`
  - Set a workspace for compatibility with Terraform. See [Compatibility with Terraform](./compatibility.md).

---

### User Guide Plugins (`docs/user-guide/plugins.md`)

# Configuring Plugins

You can extend TFLint by installing any plugin. Declare plugins you want to use in the config file as follows:

```hcl
plugin "foo" {
  enabled = true
  version = "0.1.0"
  source  = "github.com/org/tflint-ruleset-foo"
}
```

After declaring the `version` and `source`, `tflint --init` can automatically install the plugin.

```console
$ tflint --init
Installing "foo" plugin...
Installed "foo" (source: github.com/org/tflint-ruleset-foo, version: 0.1.0)
$ tflint -v
TFLint version 0.28.1
+ ruleset.foo (0.1.0)
```

See also [Configuring TFLint](config.md) for the config file schema.

## Attributes

This section describes the attributes reserved by TFLint. Except for these, each plugin can extend the schema by defining any attributes/blocks. See the documentation for each plugin for details.

### `enabled` (required)

Enable the plugin. If set to false, the rules will not be used even if the plugin is installed.

### `source`

The source URL to install the plugin. Must be in the format `github.com/org/repo`.

### `version`

Plugin version. Do not prefix with "v". This attribute cannot be omitted when the `source` is set. Version constraints (like `>= 0.3`) are not supported.

### `signature`

Controls how TFLint verifies plugin releases. Valid values are:

- `auto`: Prefer `attestation` when available, then fall back to `pgp`. This is the default behavior.
- `attestation`: Require [artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds). Attestation verification in private repositories is not supported.
- `pgp`: Require PGP signature verification with `signing_key`.
- `none`: Skip plugin signature verification.

### `signing_key`

The signing key used when `signature = "pgp"` or `"auto"`.

## Plugin directory

Plugins are usually installed under `~/.tflint.d/plugins`. Exceptionally, if you already have `./.tflint.d/plugins` in your working directory, it will be installed there.

The automatically installed plugins are placed as `[plugin dir]/[source]/[version]/tflint-ruleset-[name]`. (`tflint-ruleset-[name].exe` in Windows).

If you want to change the plugin directory, you can change this with the [`plugin_dir`](config.md#plugin_dir) or `TFLINT_PLUGIN_DIR` environment variable.

## Avoiding rate limiting

When you install plugins with `tflint --init`, TFLint calls the GitHub API to get release metadata. By default, this is an unauthenticated request, subject to a rate limit of 60 requests per hour _per IP address_.

**Background:** [GitHub REST API: Rate Limiting](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting)

If you fetch plugins frequently in CI, you may hit this rate limit. If you run TFLint in a shared CI environment such as GitHub Actions, you will share this quota with other tenants and may encounter rate limiting errors regardless of how often you run TFLint. 

To increase the rate limit, you can send an authenticated request by authenticating your requests with an access token, by setting the `GITHUB_TOKEN` environment variable. In GitHub Actions, you can pass the built-in `GITHUB_TOKEN` that is injected into each job.

It's also a good idea to cache the plugin directory, as TFLint will only send requests if plugins aren't installed. The [setup-tflint action](https://github.com/terraform-linters/setup-tflint#usage) includes an example of caching in GitHub Actions.

If you host your plugins on GitHub Enterprise Server (GHES), you may need to use a different token than on GitHub.com. In this case, you can use a host-specific token like `GITHUB_TOKEN_example_com`. The hostname must be normalized with Punycode. Use "_" instead of "." and "__" instead of "-".

```hcl
# GITHUB_TOKEN will be used
plugin "foo" {
  source = "github.com/org/tflint-ruleset-foo"
}

# GITHUB_TOKEN_example_com will be used preferentially and will fall back to GITHUB_TOKEN if not set.
plugin "bar" {
  source = "example.com/org/tflint-ruleset-bar"
}
```

## Keeping plugins up to date

We recommend using automatic updates to keep your plugin version up-to-date. [Renovate supports TFLint plugins](https://docs.renovatebot.com/modules/manager/tflint-plugin/) to easily set up automated update workflows.

## Manual installation

You can also install the plugin manually. This is mainly useful for plugin development and for plugins that are not published on GitHub. In that case, omit the `source` and `version` attributes.

```hcl
plugin "foo" {
  enabled = true
}
```

When enabled, TFLint looks for the plugin binary following the pattern tflint-ruleset-\[name] (or tflint-ruleset-\[name].exe on Windows) directly in your plugins folder (e.g., ~/.tflint.d/plugins/ on Unix-like systems or C:\Users\\[username]\\.tflint.d\plugins\ on Windows).

Thus, with the configuration above where the plugin name is "foo", the executable must be named tflint-ruleset-foo (or tflint-ruleset-foo.exe on Windows). So you should move the binary into the plugin directory in advance.

## Bundled plugin

[TFLint Ruleset for Terraform Language](https://github.com/terraform-linters/tflint-ruleset-terraform) is built directly into TFLint binary. This is called a bundled plugin. Unlike other plugins, bundled plugins can be used without installation.

A bundled plugin is enabled by default without a plugin block declaration. The default config is below:

```hcl
plugin "terraform" {
  enabled = true
  preset  = "recommended"
}
```

You can also change the behavior of the bundled plugin by explicitly declaring a plugin block.

If you want to use a different version of tflint-ruleset-terraform instead of the bundled plugin, you can install it with `tflint --init` by specifying the `version` and `source`. In this case the bundled plugin will not be automatically enabled.

```hcl
plugin "terraform" {
  enabled = true
  preset  = "recommended"

  version = "0.1.0"
  source  = "github.com/terraform-linters/tflint-ruleset-terraform"
}
```

If you have tflint-ruleset-terraform manually installed, the bundled plugin will not be automatically enabled. In this case the manually installed version takes precedence.

---

### User Guide Working Directory (`docs/user-guide/working-directory.md`)

# Switching working directory

TFLint has `--chdir` and `--recursive` flags to inspect modules that are different from the current directory.

The `--chdir` flag is available just like Terraform:

```console
$ tflint --chdir=environments/production
```

Its behavior is the same as [Terraform's behavior](https://developer.hashicorp.com/terraform/cli/commands#switching-working-directory-with-chdir). You should be aware of the following points:

- Config files are loaded after acting on the `--chdir` option.
  - This means that `tflint --chdir=dir` will loads `dir/.tflint.hcl` instead of `./.tflint.hcl`.
- Relative paths are always resolved against the changed directory.
  - If you want to refer to the file in the original working directory, it is recommended to pass the absolute path using realpath(1) etc. e.g. `tflint --config=$(realpath .tflint.hcl)`.
- The `path.cwd` represents the original working directory. This is the same behavior as using `--chdir` in Terraform.

The `--recursive` flag enables recursive inspection. This is the same as running with `--chdir` for each directory.

```console
$ tflint --recursive
```

Recursive inspection is performed in parallel by default. The default parallelism is the number of CPUs. This can be controlled with `--max-workers`.

These flags are also valid for `--init` and `--version`. Recursive init is required when installing required plugins all at once:

```console
$ tflint --recursive --init
$ tflint --recursive --version
$ tflint --recursive
```

---

--- METRICS ---
- Files Extracted: 21
- Estimated Token Budget: ~13645 tokens
- Canonical Reference: https://codewiki.google/github.com/terraform-linters/tflint
