- Alpha
- On npm: @ts-r.dev/lint
ts-r-lint: linter
A linter with 103 ESLint and typescript-eslint rules that reads eslint.config, .oxlintrc.json or biome.json. No type-aware rules yet.
ts-r-lint runs ESLint-compatible rules over JavaScript and TypeScript. It reads the config you already have, reports in ESLint's formats and exits with ESLint's codes, so it can stand in for eslint in scripts and CI when your config only uses the rules it has.
What works today
- 103 rules: all 64 rules of ESLint 10's
recommendedset, the typescript-eslint 8.71recommendedandstrictrules that don't need type information, and a few more.ts-r-lint --list-rulesprints them. - Every case of the published ESLint and typescript-eslint tests for those rules matches: 8,597 of 8,597.
- Configs: ESLint flat config,
.oxlintrc.jsonandbiome.json(see below). --fixfor rules with fixes.eslint-disablecomments and/* eslint */inline config, with reports of unused disable comments..gitignoreis honoured unless you pass--no-gitignore.- Output formats
stylish,json,sarifandgithub(GitHub Actions annotations).
Not yet
- No type-aware rules. Rules that need type information are skipped and listed on stderr as "needs type information".
- No ESLint plugins other than typescript-eslint. Rules from other plugins are skipped and listed as "plugin not implemented".
Design record: 0006 lint.
Command line
ts-r-lint [options] [paths...]
Lints JavaScript and TypeScript files with ESLint-compatible rules and configs
(eslint.config.*, .oxlintrc.json, biome.json).
Options:
--fix Apply fixes and write files
-f, --format <name> stylish (default), json, sarif, github
-c, --config <path> Use this config for every file
--max-warnings <n> Exit 1 when there are more than n warnings
--quiet Report errors only (warn rules are not run)
-o, --output-file <path> Write the report to a file
--threads <n> Worker threads (default: min(4, cores))
--rule <name> Only run this rule (repeatable)
--no-gitignore Do not honour .gitignore files
--no-warn-ignored Do not warn about ignored files named explicitly
--no-inline-config Ignore eslint-disable and /* eslint */ comments
--report-unused-disable-directives
Report unused eslint-disable comments as errors
--report-unused-disable-directives-severity <sev>
off, warn or error
--list-rules Print the built-in rules and exit
-h, --help Show this help
-v, --version Show the version
--quietdoesn't runwarnrules at all, rather than running them and hiding the result.--rulenarrows a run to one or more rules, which helps when you compare against ESLint.- Only the report goes to stdout (or to
--output-file), so-f jsoncan be piped. Notes go to stderr. - ESLint doesn't read
.gitignore. Use--no-gitignorewhen you compare the two.
Exit codes match ESLint's: 0 when there are no errors (and warnings are within --max-warnings), 1 when there are errors or too many warnings, 2 for a config or usage error.
You can also run it through the launcher as ts-r lint.
Config
For each file, ts-r-lint looks at or above the file's directory, in this order:
| Order | Config | How it is read |
|---|---|---|
| 1 | Nearest eslint.config.{js,mjs,cjs,ts,mts,cts} | Evaluated by Node (all configs in one process), then cached in the lint-config namespace of ~/.cache/ts-r |
| 2 | Nearest .oxlintrc.json | Read directly |
| 3 | Nearest biome.json or biome.jsonc | Read directly; Biome rules with an ESLint equivalent |
| 4 | None found | ESLint recommended, plus typescript-eslint recommended for TypeScript files |
Flat configs merge per file with ESLint 10's rules. The config cache is invalidated when the config, anything it imports locally or the nearest lockfile changes. --config uses one config for every file instead, with patterns relative to the current directory.
Rules a config turns on that ts-r-lint doesn't have are listed once per config on stderr, with the reason: needs type information, plugin not implemented, not implemented yet, or unknown.
Environment variables:
| Variable | Effect |
|---|---|
TS_R_NODE | Node binary used to evaluate eslint.config.* (default: node on PATH) |
TS_R_LINT_NODE_WRAPPER | Command to wrap that Node process with |
TS_R_LINT_NO_CONFIG_CACHE=1 | Evaluate the config every run (for cold timings) |
Next to ESLint
ts-r-lint works on its own. A common split is to run ts-r-lint on every save or commit for the fast rules, and keep ESLint in CI for type-aware rules and plugins ts-r-lint doesn't have. In the editor, ts-r-lsp can show the same diagnostics.
Measured
Like-for-like, on tRPC's server and client sources (147 files): tRPC's own ESLint config with every rule ts-r-lint doesn't implement removed, the same file for both tools (cargo xtask bench --repos trpc lint-trpc-same-rules, median of 5 runs):
| Run | ts-r-lint | ESLint 10.10.0 |
|---|---|---|
| Same 48 rules, no type information, config evaluated by Node every run | 733 ms | 1,015 ms |
| Same, with ts-r-lint's config cache (its default) | 15.5 ms | 1,015 ms |
Both report 0 problems from those rules. Most of ts-r-lint's uncached time is Node evaluating eslint.config.js. With the 32 type-aware rules as well the runs aren't comparable yet: on tRPC the parity gate turns them off, and with the gate off ts-r-lint reports 11 problems ESLint doesn't.
Apple M-series Mac (a shared laptop), 4 threads, each project's own lint config, on public projects pinned in bench/repos.toml (cargo xtask bench --repos, median of 3 to 5 runs):
| Project | ts-r-lint | ts-r-lint, no type check | Project's linter |
|---|---|---|---|
| tRPC server and client sources | 186 ms | 20 ms | ESLint (type-aware): 2,566 ms |
TypeORM packages/typeorm/src | 810 ms | 194 ms | ESLint (type-aware): 6,718 ms |
| Playwright core sources | 725 ms | 59 ms | ESLint: 5,441 ms |
date-fns pkgs/core/src | 76 ms | oxlint: 348 ms |
These aren't like-for-like yet. ESLint runs plugin rules ts-r-lint doesn't have (on TypeORM, most of ESLint's problems come from jsdoc/* rules), and when ts-r-check's diagnostics differ from tsc's on a project, ts-r-lint runs its rules without type information (the parity gate), so the first column measures a type check plus the untyped rules. The type-aware rules themselves match ESLint's test suites (see Correctness).
Method and commits: same rules, lint and correctness.