|
| 1 | +# Adding a New Linter |
| 2 | + |
| 3 | +Add an entry to `builtin()` in `src/registry.rs` using the |
| 4 | +builder pattern: |
| 5 | + |
| 6 | +```rust |
| 7 | +// File scope — invoked per file |
| 8 | +Check::file("mytool", "mytool --check {FILE}", &["*.ext"]) |
| 9 | + .fix("mytool --fix {FILE}"), |
| 10 | + |
| 11 | +// Files scope — invoked once with all matched files (absolute paths) |
| 12 | +Check::files("mytool", "mytool {FILES}", &["*.ext"]) |
| 13 | + .fix("mytool --fix {FILES}"), |
| 14 | + |
| 15 | +// Files scope — invoked once with all matched files (relative to project root) |
| 16 | +// Use {RELFILES} when the tool requires paths relative to the project root |
| 17 | +// (e.g. dotnet format --include). |
| 18 | +Check::files("mytool", "mytool --include {RELFILES}", &["*.ext"]) |
| 19 | + .fix("mytool --fix --include {RELFILES}"), |
| 20 | + |
| 21 | +// Project scope — invoked once, skipped if no *.ext changed |
| 22 | +Check::project("mytool", "mytool run", &["*.ext"]), |
| 23 | +``` |
| 24 | + |
| 25 | +Available builder modifiers: |
| 26 | + |
| 27 | +| Method | Purpose | |
| 28 | +| ---------------------------- | ----------------------------------------------------------------------------- | |
| 29 | +| `.fix(cmd)` | Enable `--fix` mode with this command | |
| 30 | +| `.bin(name)` | Override binary name (when check name ≠ binary) | |
| 31 | +| `.mise_tool(name)` | Look up availability under a different mise key (e.g. `rust` for `cargo-fmt`) | |
| 32 | +| `.version_req(range)` | Restrict to a semver range (e.g. `">=1.0.0"`) | |
| 33 | +| `.excludes(names)` | Skip files already owned by these active checks | |
| 34 | +| `.slow()` | Mark as slow — skipped by `--fast-only` | |
| 35 | +| `.linter_config(file, flag)` | Inject a config flag when `FLINT_CONFIG_DIR/<file>` exists (see below) | |
| 36 | + |
| 37 | +## Config File Injection (`.linter_config`) |
| 38 | + |
| 39 | +Use `.linter_config(filename, flag)` when the tool supports an explicit config |
| 40 | +file path via a CLI flag. At runtime, if `FLINT_CONFIG_DIR/<filename>` exists, |
| 41 | +flint injects `flag <abs-path>` right after the binary name in the command. |
| 42 | +If the file is absent the flag is silently omitted — native config discovery |
| 43 | +remains in effect. |
| 44 | + |
| 45 | +```rust |
| 46 | +// Example: markdownlint accepts --config <path> |
| 47 | +Check::file("markdownlint", "markdownlint {FILE}", &["*.md"]) |
| 48 | + .fix("markdownlint --fix {FILE}") |
| 49 | + .linter_config(".markdownlint.json", "--config"), |
| 50 | +// → markdownlint --config /repo/.github/config/.markdownlint.json <file> |
| 51 | +``` |
| 52 | + |
| 53 | +**When NOT to use it:** |
| 54 | + |
| 55 | +- The tool has no explicit `--config`/`--rcfile`/equivalent flag (e.g. `shfmt`) |
| 56 | +- The flag accepts a **directory** rather than a file (e.g. biome's |
| 57 | + `--config-path <dir>`) — a different injection shape is needed. For biome, |
| 58 | + check for `biome.json` existence but pass `config_dir` itself as the arg: |
| 59 | + `biome --config-path <config_dir> check <file>`. This requires a variant of |
| 60 | + `.linter_config` that injects the directory rather than the full file path |
| 61 | + (not yet implemented) |
| 62 | +- The tool is project-scoped and its config must live at the project root to |
| 63 | + function (no explicit `--config` flag exists) |
| 64 | + |
| 65 | +Look up the tool's `--help` or man page for the config flag name and expected |
| 66 | +argument type before adding `.linter_config`. |
| 67 | + |
| 68 | +For checks that need custom logic (not a simple command template), add a module |
| 69 | +under `src/linters/` and use `CheckKind::Special`. |
| 70 | + |
| 71 | +## Changed-files scoping |
| 72 | + |
| 73 | +Most linters use `file` or `files` scope, so they naturally receive only changed |
| 74 | +files as arguments. `golangci-lint` uses `project` scope but scopes internally via |
| 75 | +`--new-from-rev={MERGE_BASE}`. |
| 76 | + |
| 77 | +**`cargo-clippy` cannot scope to changed files.** Cargo has no git-aware flag |
| 78 | +equivalent to `--new-from-rev`. It still skips entirely when no `*.rs` files |
| 79 | +changed, but when it does run it checks the whole project. Workspace support |
| 80 | +(`-p <pkg> --no-deps` per changed package) would be a future improvement. |
0 commit comments