Skip to content

Backend coverage

poly resolves every file through a tiered model:

  1. Curated Rust backends for high-fidelity lint and format support.
  2. Native-toolchain backends for canonical first-party formatters — plus shellcheck — when configured or present on PATH.
  3. Tree-sitter generic formatting for identified languages without a dedicated backend.
  4. Optional catalog tools from the embedded registry.
Language or files Backend Lint Format
JavaScript / TypeScript / JSX / TSX oxc yes yes
JSON / JSONC oxc parse diagnostics + formatter yes yes
Python ruff internals yes yes
TOML taplo yes yes
Markdown rumdl yes yes
SQL sqruff yes yes
YAML saphyr + pretty_yaml yes yes
CSS / SCSS malva (format) + biome (lint) yes yes
Less malva no yes
HTML / Vue / Svelte / Astro / Angular / templates / XML markup_fmt no yes
GraphQL graphql-parser + pretty_graphql (parse-error lint + format) + biome (rule lint) yes yes
HCL / Terraform hcl-edit + hcl-rs, tree-sitter for comment-preserving format fallback yes yes
Dockerfile dockerfile-parser, hadolint-style rules yes no
.env files (.env, .env.*, *.env) dotenv-analyzer yes no
INI and compatible (.ini, .cfg, .desktop, .npmrc, .editorconfig, .pylintrc, .coveragerc, .flake8, …) rust-ini yes no
Nix alejandra no yes
Ruby rubyfmt no yes
PHP mago yes yes
R opt-in styler (via Rscript), tree-sitter fallback otherwise no yes
Go gofmt when present (default-on), tree-sitter fallback otherwise no yes
Rust rustfmt when present (default-on), tree-sitter fallback otherwise no yes
Zig opt-in zig fmt, tree-sitter fallback otherwise no yes
Java opt-in google-java-format, tree-sitter fallback otherwise no yes
Kotlin opt-in ktfmt, tree-sitter fallback otherwise no yes
Swift opt-in swift-format, tree-sitter fallback otherwise no yes
Dart opt-in dart format, tree-sitter fallback otherwise no yes
Gleam opt-in gleam format, tree-sitter fallback otherwise no yes
Shell shellcheck when present (default-on), opt-in shfmt, tree-sitter fallback otherwise optional optional
All text files typos spell-check yes no
Any recognized language opt-in uncomment comment removal opt-in no
Other identified grammars tree-sitter generic tier no best effort

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.

Beyond the dedicated backends above, two lint tiers run on every language:

  • The native quality metric engine — file and function length, type length, parameter count, nesting depth, cyclomatic complexity, and lazy ignore markers. 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: off and are one extend_select away.

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 in-process alongside the backends in the table, at warning severity — so neither fails an unconfigured CI on its own.

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:

[lint.typescript.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.

.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.

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’s key=value syntax, not INI’s.
  • .gitconfig — quoted subsections rust-ini cannot parse.
  • systemd units — duplicate keys are legal there.

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.

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 = true
files = "**/*.{js,ts}"