diff --git a/.github/renovate-tracked-deps.json b/.github/renovate-tracked-deps.json index 9afd9aa5..96c8a43a 100644 --- a/.github/renovate-tracked-deps.json +++ b/.github/renovate-tracked-deps.json @@ -24,6 +24,10 @@ "packageName": "checkstyle/checkstyle", "datasource": "github-releases" }, + "dotenv-linter": { + "packageName": "dotenv-linter/dotenv-linter", + "datasource": "github-releases" + }, "editorconfig-checker": { "packageName": "editorconfig-checker/editorconfig-checker", "datasource": "github-releases" @@ -118,6 +122,7 @@ "aqua:owenlamont/ryl", "biome", "checkstyle", + "dotenv-linter", "dotnet", "editorconfig-checker", "go", diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 60eae426..85d46bae 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -68,6 +68,7 @@ "aqua:owenlamont/ryl", "biome", "checkstyle", + "dotenv-linter", "editorconfig-checker", "golangci-lint", "google-java-format", diff --git a/README.md b/README.md index 0f4bbd96..a63857f4 100644 --- a/README.md +++ b/README.md @@ -158,14 +158,15 @@ For Flint contributor workflow and local testing tips, see ### Files / Formats -| Name | Linter | Formatter | -| -------- | ------------------------------------------ | ---------------------------------------------- | -| JSON | [`biome`](docs/linters/biome.md) | [`biome-format`](docs/linters/biome-format.md) | -| Markdown | [`rumdl`](docs/linters/rumdl.md) | [`rumdl`](docs/linters/rumdl.md) | -| Shell | [`shellcheck`](docs/linters/shellcheck.md) | [`shfmt`](docs/linters/shfmt.md) | -| TOML | — | [`taplo`](docs/linters/taplo.md) | -| XML | [`xmllint`](docs/linters/xmllint.md) | — | -| YAML | [`ryl`](docs/linters/ryl.md) | [`ryl`](docs/linters/ryl.md) | +| Name | Linter | Formatter | +| -------- | ------------------------------------------------ | ------------------------------------------------ | +| Dotenv | [`dotenv-linter`](docs/linters/dotenv-linter.md) | [`dotenv-linter`](docs/linters/dotenv-linter.md) | +| JSON | [`biome`](docs/linters/biome.md) | [`biome-format`](docs/linters/biome-format.md) | +| Markdown | [`rumdl`](docs/linters/rumdl.md) | [`rumdl`](docs/linters/rumdl.md) | +| Shell | [`shellcheck`](docs/linters/shellcheck.md) | [`shfmt`](docs/linters/shfmt.md) | +| TOML | — | [`taplo`](docs/linters/taplo.md) | +| XML | [`xmllint`](docs/linters/xmllint.md) | — | +| YAML | [`ryl`](docs/linters/ryl.md) | [`ryl`](docs/linters/ryl.md) | ### Tooling / CI diff --git a/default.json b/default.json index 3df93944..b65e9175 100644 --- a/default.json +++ b/default.json @@ -32,6 +32,7 @@ "aqua:owenlamont/ryl", "biome", "checkstyle", + "dotenv-linter", "editorconfig-checker", "golangci-lint", "google-java-format", diff --git a/docs/linters.md b/docs/linters.md index f8333814..dbc61882 100644 --- a/docs/linters.md +++ b/docs/linters.md @@ -22,14 +22,15 @@ page with its behavior, configuration, and examples. ### Files / Formats -| Name | Linter | Formatter | -| -------- | ------------------------------------- | ----------------------------------------- | -| JSON | [`biome`](linters/biome.md) | [`biome-format`](linters/biome-format.md) | -| Markdown | [`rumdl`](linters/rumdl.md) | [`rumdl`](linters/rumdl.md) | -| Shell | [`shellcheck`](linters/shellcheck.md) | [`shfmt`](linters/shfmt.md) | -| TOML | — | [`taplo`](linters/taplo.md) | -| XML | [`xmllint`](linters/xmllint.md) | — | -| YAML | [`ryl`](linters/ryl.md) | [`ryl`](linters/ryl.md) | +| Name | Linter | Formatter | +| -------- | ------------------------------------------- | ------------------------------------------- | +| Dotenv | [`dotenv-linter`](linters/dotenv-linter.md) | [`dotenv-linter`](linters/dotenv-linter.md) | +| JSON | [`biome`](linters/biome.md) | [`biome-format`](linters/biome-format.md) | +| Markdown | [`rumdl`](linters/rumdl.md) | [`rumdl`](linters/rumdl.md) | +| Shell | [`shellcheck`](linters/shellcheck.md) | [`shfmt`](linters/shfmt.md) | +| TOML | — | [`taplo`](linters/taplo.md) | +| XML | [`xmllint`](linters/xmllint.md) | — | +| YAML | [`ryl`](linters/ryl.md) | [`ryl`](linters/ryl.md) | ### Tooling / CI diff --git a/docs/linters/dotenv-linter.md b/docs/linters/dotenv-linter.md new file mode 100644 index 00000000..6199e447 --- /dev/null +++ b/docs/linters/dotenv-linter.md @@ -0,0 +1,34 @@ +# `dotenv-linter` + + + + +| | | +| -------- | --------------------------------------------------------------- | +| Project | [dotenv-linter](https://github.com/dotenv-linter/dotenv-linter) | +| Fix | yes | +| Binary | `dotenv-linter` | +| Scope | [files](../linters.md#scope-files) | +| Patterns | `.env .env.* *.env` | + + + +`dotenv-linter` checks and safely formats dotenv environment files without +printing their values. + +> [!WARNING] +> Do not commit `.env` files containing secrets to git. + +Flint checks only explicit `.env`-style files: `.env`, `.env.*`, and files +ending in `.env`. It passes those file paths rather than a directory, so +unrelated YAML, Compose, and application configuration files are never scanned. + +Both check and fix mode disable dotenv-linter's update check, avoiding +unexpected network access. Fix mode also uses `--no-backup`, so it does not +leave secret-bearing backup files behind: + +```bash +flint run --fix dotenv-linter +``` + +The fixer is serialized with other Flint formatters that may own the same file. diff --git a/mise.toml b/mise.toml index c6f113ff..cefead2b 100644 --- a/mise.toml +++ b/mise.toml @@ -14,6 +14,7 @@ actionlint = "1.7.12" "aqua:owenlamont/ryl" = "0.21.0" biome = "2.5.5" checkstyle = "13.8.0" +dotenv-linter = "4.0.0" editorconfig-checker = "3.8.0" golangci-lint = "2.12.2" google-java-format = "1.35.0" diff --git a/src/main.rs b/src/main.rs index 709ebf52..0317cca2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1362,6 +1362,7 @@ zizmor zizmor active fast yes Audit Gi hadolint hadolint missing fast no Lint Dockerfiles Dockerfile Dockerfile.* *.dockerfile xmllint xmllint missing fast no Validate XML files are well-formed *.xml typos typos active fast yes Check for common spelling mistakes * +dotenv-linter dotenv-linter missing fast yes Lint dotenv environment files without printing their values .env .env.* *.env editorconfig-checker ec active fast no Check files comply with EditorConfig settings * golangci-lint golangci-lint missing fast no Lint Go code; uses --new-from-rev to scope analysis to changed code *.go ruff ruff active fast yes Lint Python code *.py diff --git a/src/registry/checks.rs b/src/registry/checks.rs index 78925340..0db0a038 100644 --- a/src/registry/checks.rs +++ b/src/registry/checks.rs @@ -28,6 +28,8 @@ const GOLANGCI_LINT_CONFIG_URL: &str = "https://golangci-lint.run/usage/configur const GOOGLE_JAVA_FORMAT_URL: &str = "https://github.com/google/google-java-format"; const CHECKSTYLE_URL: &str = "https://github.com/checkstyle/checkstyle"; const CHECKSTYLE_CONFIG_URL: &str = "https://checkstyle.org/config.html"; +const DOTENV_LINTER_URL: &str = "https://github.com/dotenv-linter/dotenv-linter"; +const DOTENV_LINTER_CONFIG_URL: &str = "https://dotenv-linter.github.io/"; const KUBE_LINTER_URL: &str = "https://github.com/stackrox/kube-linter"; const KUBE_LINTER_CONFIG_URL: &str = "https://docs.kubelinter.io/"; const HADOLINT_URL: &str = "https://github.com/hadolint/hadolint"; @@ -390,6 +392,27 @@ fn check_typos() -> Check { .mise_tool("typos") } +fn check_dotenv_linter() -> Check { + Check::files( + "dotenv-linter", + "dotenv-linter check --plain --skip-updates {FILES}", + &[".env", ".env.*", "*.env"], + ) + .fix("dotenv-linter fix --plain --skip-updates --no-backup {FILES}") + .mise_tool("dotenv-linter") + .project_url(DOTENV_LINTER_URL) + .config_doc_url(DOTENV_LINTER_CONFIG_URL) + .overview( + OverviewSection::FilesFormats, + "Dotenv", + OverviewRole::Both, + Some("Environment-file syntax and consistency"), + ) + .desc("Lint dotenv environment files without printing their values") + .formatter() + .style() +} + fn check_editorconfig_checker() -> Check { // Defer to formatters that enforce line length — those are the ones // that conflict with ec's max_line_length editorconfig check. @@ -769,6 +792,7 @@ pub fn builtin() -> Vec { check_hadolint(), check_xmllint(), check_typos(), + check_dotenv_linter(), check_editorconfig_checker(), check_golangci_lint(), check_ruff(), diff --git a/tests/cases/dotenv-linter/auto-fix/files/.env b/tests/cases/dotenv-linter/auto-fix/files/.env new file mode 100644 index 00000000..b4088bb7 --- /dev/null +++ b/tests/cases/dotenv-linter/auto-fix/files/.env @@ -0,0 +1 @@ +bad line diff --git a/tests/cases/dotenv-linter/auto-fix/files/mise.toml b/tests/cases/dotenv-linter/auto-fix/files/mise.toml new file mode 100644 index 00000000..d96574ef --- /dev/null +++ b/tests/cases/dotenv-linter/auto-fix/files/mise.toml @@ -0,0 +1,2 @@ +[tools] +"dotenv-linter" = "4.0.0" diff --git a/tests/cases/dotenv-linter/auto-fix/test.toml b/tests/cases/dotenv-linter/auto-fix/test.toml new file mode 100644 index 00000000..2eb53985 --- /dev/null +++ b/tests/cases/dotenv-linter/auto-fix/test.toml @@ -0,0 +1,20 @@ +[expected] +args = "run --full --fix dotenv-linter" +exit = 1 +stderr = ''' +flint: fixed: dotenv-linter — commit before pushing +''' + +[expected.files] +".env" = "PORT=8080\n" + +[fake_bins] +dotenv-linter = ''' +#!/bin/sh +set -eu +case "$1" in + check) exit 1 ;; + fix) printf 'PORT=8080\n' > .env ;; + *) exit 1 ;; +esac +''' diff --git a/tests/cases/dotenv-linter/clean/files/.env b/tests/cases/dotenv-linter/clean/files/.env new file mode 100644 index 00000000..701b0b0b --- /dev/null +++ b/tests/cases/dotenv-linter/clean/files/.env @@ -0,0 +1,2 @@ +NAME=flint +PORT=8080 diff --git a/tests/cases/dotenv-linter/clean/files/mise.toml b/tests/cases/dotenv-linter/clean/files/mise.toml new file mode 100644 index 00000000..d96574ef --- /dev/null +++ b/tests/cases/dotenv-linter/clean/files/mise.toml @@ -0,0 +1,2 @@ +[tools] +"dotenv-linter" = "4.0.0" diff --git a/tests/cases/dotenv-linter/clean/test.toml b/tests/cases/dotenv-linter/clean/test.toml new file mode 100644 index 00000000..4c25aa9d --- /dev/null +++ b/tests/cases/dotenv-linter/clean/test.toml @@ -0,0 +1,3 @@ +[expected] +args = "run --full dotenv-linter" +exit = 0