Languages and engines
Every file is handled by one of four mechanisms (defined in the glossary):
- Engines from components — signed downloads a core build fetches per language; linked in on
the bundled build. Configured as
[engines.<id>]. See Components and offline use for the component list;poly components listnames the engines in each. - Host tools — a language’s canonical CLI, used when it is on
PATH(rustfmt,gofmt,shellcheckby default; the rest opt-in). - Tree-sitter tier — best-effort formatting for identified languages with nothing better.
- Recipes and catalog tools — opt-in
[tools.<name>]. See Toolchains, runtimes and recipes.
Language table
Section titled “Language table”In the How it runs column, ↓ marks a recipe tool polylint can download as a pinned,
checksummed release (macOS and Linux), and RT one that needs a host runtime.
| Language or files | Engine | Lint | Format | How it runs |
|---|---|---|---|---|
| JavaScript / TypeScript / JSX / TSX | oxc | yes | yes | component web |
| JSON / JSONC | oxc parse diagnostics + formatter | yes | yes | component web |
| Python | ruff internals | yes | yes | component python |
| Python (type checking) | opt-in project check, ty ([engines.python-types] backend = "ty") or pyrefly (backend = "pyrefly"); imports resolve through the detected virtualenv’s site-packages; --only/--skip accept python-types for either, and a backend whose component is disabled or missing is an error, not a silent skip (see Python type checking) |
opt-in | no | component python (ty) or pyrefly |
| JS/TS workspaces (npm, pnpm, yarn, bun) | skif (opt-in dependency, manifest, version-policy and dead-code project check; [engines.skif] enabled = true) |
opt-in | no | component web |
| TOML | taplo (lint) + oxc_formatter_toml (format; taplo’s formatter when a taplo layout key is set) |
yes | yes | component web |
| Markdown | rumdl (lint) + oxc_formatter_markdown (format; rumdl’s fixer when a rumdl rule key is set) |
yes | yes | component web |
Markdown ADRs (NNNN-*.md) |
adrlint (opt-in MADR structural rules) | opt-in | no | component web |
| SQL | sqruff | yes | yes | component sql |
| YAML | serde_yaml (libyaml, the grammar PyYAML implements) + oxc_formatter_yaml |
yes | yes | component web |
| CSS / SCSS | oxc_formatter_css (format; malva when a malva key is set) + biome (lint) |
yes | yes | components web, biome |
| Less | oxc_formatter_css (malva when a malva key is set) |
no | yes | component web |
| HTML / Vue / Svelte / Astro / Angular / templates / XML | markup_fmt |
no | yes | component web |
| GraphQL | graphql-parser + pretty_graphql (parse-error lint + format) + biome (rule lint) |
yes | yes | components misc, biome |
| HCL / Terraform | hcl-edit (lint) + a port of hclwrite.Format (the layout terraform fmt and terragrunt hclfmt produce); opt-in tflint, tofu, terraform |
yes | yes | component misc; recipes (tflint ↓, tofu ↓, terraform) |
Dockerfile (Dockerfile, Dockerfile.*, Dockerfile-*, *.dockerfile, Containerfile, …) |
vendored dockerfile-parser, hadolint-style rules; opt-in hadolint |
yes | no | component misc; recipe hadolint ↓ |
.env files (.env, .env.*, *.env) |
dotenv-analyzer | yes | no | component misc |
INI and compatible (.ini, .cfg, .desktop, .npmrc, .editorconfig, .pylintrc, .coveragerc, .flake8, …) |
rust-ini |
yes | no | component misc |
| Nix | alejandra | no | yes | component nix |
Ruby (*.rb, Gemfile, Rakefile, *.gemspec, *.rake, *.ru, Podfile, …) |
rubyfmt | no | yes | component ruby |
| PHP | mago | yes | yes | component php |
| Shell | shfmt (the host shfmt in the bundled build, or with [components] disable = ["sh"]); shellcheck when present (default-on) |
optional | yes | component sh (Go sidecar); host shellcheck |
| Go | gofmt when present (default-on), imports grouped as goimports does; tree-sitter fallback otherwise; opt-in golangci-lint |
opt-in | yes | host; recipe golangci-lint ↓ |
| Rust | rustfmt when present (default-on), tree-sitter fallback otherwise; cargo clippy, cargo-sort, cargo-machete, cargo-deny in the whole-project phase |
whole-project | yes | host |
| Java | opt-in google-java-format, tree-sitter fallback otherwise; opt-in checkstyle, palantir-java-format |
opt-in | yes | host; recipes checkstyle ↓ RT (JRE), palantir-java-format ↓ |
| Kotlin | opt-in ktfmt, tree-sitter fallback otherwise |
no | yes | host |
| Swift | opt-in swift-format, tree-sitter fallback otherwise |
no | yes | host |
| Zig | opt-in zig fmt, tree-sitter fallback otherwise |
no | yes | host |
| Dart | opt-in dart format, tree-sitter fallback otherwise |
no | yes | host |
| Gleam | opt-in gleam format, tree-sitter fallback otherwise |
no | yes | host |
| R | opt-in styler (via Rscript), tree-sitter fallback otherwise |
no | yes | host |
| Groovy | opt-in npm-groovy-lint |
opt-in | opt-in | recipe RT (Node) |
| Protobuf | opt-in buf, tree-sitter fallback otherwise |
opt-in | yes | recipe buf ↓ |
Starlark / Bazel (BUILD, *.bzl, WORKSPACE, Tiltfile, …) |
opt-in buildifier, tree-sitter fallback otherwise |
opt-in | yes | recipe buildifier ↓ |
| GitHub Actions workflows | opt-in actionlint, gau |
opt-in | no | recipes actionlint ↓, gau |
| Helm charts | opt-in helm, helm-template |
opt-in | no | recipes ↓ |
pyproject.toml |
opt-in pyproject-fmt |
no | opt-in | recipe RT (Python) |
| All text files | opt-in typos spell-check ([engines.typos] enabled = true) |
opt-in | no | component typos |
| Any recognized language | opt-in uncomment comment removal |
opt-in | no | component uncomment |
| Other identified grammars (C, C++, C#, Elixir, …) | tree-sitter generic tier | no | best effort | tree-sitter |
Lint in this table is the language’s own engine. The two cross-cutting tiers below add findings on
top, including where the column says no.
Unsupported or unknown file types are skipped unless tree-sitter-language-pack can identify them.
Some whitespace-sensitive data, template or patch grammars intentionally no-op rather than risk a
destructive rewrite. Lock files (Cargo.lock, pnpm-lock.yaml, yarn.lock, …) are never linted or
formatted.
The two cross-cutting lint tiers
Section titled “The two cross-cutting lint tiers”Beyond the dedicated engines above, two lint tiers run on every language:
- The native
qualitymetric engine — file and function length, type length, parameter count, nesting depth, cyclomatic complexity, lazy ignore markers, and test files the runner will not collect. See code quality metrics. - A built-in ast-grep rule pack of 26 rules across C#, Elixir, Go, Java, Kotlin, Python, Ruby,
Rust and Swift. 13 are on by default; the other 13 author their own
severity: offand are oneextend_selectaway.
A pack rule whose findings are concentrated in generated or test code declares those paths in its
own YAML, with the standard ast-grep ignores: key — placeholder-implementation excludes
**/*_generated.rs, force-cast excludes **/RustBridge/**, not-null-assertion excludes Kotlin
test source sets, and unwrap-used excludes test and generated paths. Those are defaults: a
[per_file_ignores] entry naming the rule replaces them, and every finding they drop is listed in
the run’s suppressed array. See
per-file ignores.
Both run alongside the engines in the table, at warning severity — so neither fails an unconfigured CI on its own.
Backend notes
Section titled “Backend notes”Architecture decision records
Section titled “Architecture decision records”Numbered ADR files (NNNN-*.md) can additionally be checked against the MADR template by the
opt-in adrlint backend. It is separate from rumdl on purpose: rumdl judges the Markdown, adrlint
judges the decision record’s section structure, front-matter metadata, list style and document-local
link anchors. Enable it with [engines.adrlint] enabled = true.
It runs the document rules per file, and the tree-wide rules — numbering, gaps, duplicates and file
names (MADR41–MADR44) and the immutability lock (ADR50/ADR51) — as a project check over the ADR log.
Link-target existence (MADR12a/MADR12c) and external links (MADR11) depend on the tree or the network,
which a content-hash-cached engine cannot model honestly; the adrlint CLI remains the tool for those. See
architecture decision records.
Reaching an oxlint plugin
Section titled “Reaching an oxlint plugin”oxlint compiles in whole plugins that its default set (unicorn, typescript, oxc) leaves
switched off — vitest, jest, jsx-a11y, promise, import, node, vue and others. Rule
selection cannot turn one on: a filter resolves only against the rules of plugins that are already
enabled, so extend_select = ["vitest/expect-expect"] on its own does nothing at all. Name the
plugin instead:
[engines.oxc]plugins = ["vitest"]The plugin’s correctness rules come on with it, and poly’s own categories are layered on top, so
the rules then behave like any other. An unrecognised plugin name fails the run rather than being
ignored — a dropped name would leave you believing a rule set is live over a run that checks
nothing of the kind.
dotenv
Section titled “dotenv”.env files autofix with poly lint --fix, and inline # dotenv-linter:off <Check> /
# dotenv-linter:on <Check> comments suppress a check for the lines between them — the same
directive syntax the standalone dotenv-linter CLI understands.
Rules: DuplicatedKey, EndingBlankLine, ExtraBlankLine, IncorrectDelimiter,
KeyWithoutValue, LeadingCharacter, LowercaseKey, QuoteCharacter, SpaceCharacter,
SubstitutionKey, TrailingWhitespace, UnorderedKey, ValueWithoutQuotes, SchemaViolation.
INI is lint-only, by design
Section titled “INI is lint-only, by design”rust-ini’s parser discards comments while parsing, so writing its model back out would silently
delete every comment in the file. poly never does that — INI formatting stays with the tree-sitter
generic tier instead, which preserves comments structurally.
Rules: parse-error (a real syntax error, with the parser’s own line and column), duplicate-key,
duplicate-section, key-without-value, inconsistent-separator, trailing-whitespace.
Detection is deliberately narrow. These are never treated as INI:
*.conf— most are not INI; nginx, httpd and friends use their own syntax.*.properties— Java’skey=valuesyntax, not INI’s..gitconfig— quoted subsectionsrust-inicannot parse.- systemd units — duplicate keys are legal there.
The tree-sitter generic tier
Section titled “The tree-sitter generic tier”The generic tier identifies and best-effort formats hundreds of grammars, including first-class
detection for C, C++, Elixir, Protobuf and the long tail covered by tree-sitter-language-pack.
Java and Kotlin reach it only as the fallback when their opt-in native formatter is absent; both,
along with C, C++, C# and Ruby, are languages the cross-cutting code-quality tier lints in full.
The optional tool catalog
Section titled “The optional tool catalog”For everything else, opt into tools from the embedded mdsf
catalog. Entries are PATH-probed and skipped when absent, so enabling one never breaks a run:
[tools.prettier]enabled = truefiles = "**/*.{js,ts}"