ts-suppress
Incremental TypeScript strictness adoption via bulk error suppression.
Instead of scattering @ts-ignore or @ts-expect-error comments throughout your codebase, ts-suppress captures all TypeScript errors into a single .ts-suppressions.json file. This lets you enable stricter compiler options immediately and fix errors at your own pace.
Install
npm install -D ts-suppress
pnpm add -D ts-suppress
yarn add -D ts-suppress
bun add -d ts-suppress
Note: TypeScript >= 5.9.3 is a peer dependency.
Usage
# Create an empty .ts-suppressions.json
npx ts-suppress init
# Snapshot all current TypeScript errors
npx ts-suppress suppress
# Verify all errors are suppressed and no suppressions are stale (useful in CI)
npx ts-suppress check
# Add new suppressions and remove stale ones in a single pass
npx ts-suppress update
Every command accepts --log-level <level> (silent, error, warn, log, info (default), debug, trace, verbose). Use --log-level debug to trace each diagnostic's scope and raw message — handy when investigating why a suppression didn't match the error you expected.
Typical Workflow
- Enable a stricter TypeScript option (e.g.
"strict": true) - Run
npx ts-suppress suppressto baseline all existing errors - Commit
.ts-suppressions.json - Add
npx ts-suppress checkto CI - Fix errors over time —
checkwill flag stale suppressions as you go - Run
npx ts-suppress updateto sync the suppression file after fixing errors
How It Works
A suppression's identity is file + code + scope:
- file — relative path to the source file
- code — TypeScript error code (e.g.
2322) - scope — the dot-path of the enclosing named AST node (e.g.
MyClass.myMethod), empty string for module-level code
Example .ts-suppressions.json entry:
{ "file": "src/api.ts", "code": 2322, "scope": "MyClass.myMethod" }
Suppressions with the same file + code + scope are matched by occurrence count, not deduplicated. If a scope has N errors of one code, the file holds N identical entries; fix one and check reports the remaining N−1 as still-unsuppressed.
Tradeoff: because identity is anchored to the enclosing named node rather than the error message, suppressions are sticky — they survive refactors that don't move or rename that node, even if the error's wording changes. The flip side is that the tool can't tell when an error morphs into a different error of the same code inside the same scope: if you fix the original problem but introduce a new TS2322 in the same method, it stays silently suppressed. Module-level errors (outside any named function, class, or block) all share the empty "" scope, so distinct module-level errors of the same code are indistinguishable from each other.
The check command diffs the current diagnostics against the suppression file and reports:
- Unsuppressed errors — new errors not yet in the suppression file
- Stale suppressions — entries that no longer match any current error (i.e. errors that have been fixed)
check exits 0 when both lists are empty and 1 otherwise, so it plugs directly into CI.
Comparison with ts-bulk-suppress
ts-suppress is inspired by ts-bulk-suppress by TikTok and shares the same core idea: capture TypeScript errors into an external file instead of scattering @ts-ignore comments. The two tools take different approaches to the problem.
| ts-suppress | ts-bulk-suppress | |
|---|---|---|
| Suppression file | Single .ts-suppressions.json |
.ts-bulk-suppressions.json |
| Error identification | file + error code + scope | file + error code + scope |
| tsc integration | Standalone — reads diagnostics via TypeScript compiler API | Wraps/intercepts tsc output |
| CLI interface | Separate commands: init, suppress, check, update |
Flag-based: --gen-bulk-suppress, --changed |
| Runtime dependencies | 2 (cac, consola) + TypeScript as peer dep | 37 packages |
| Maintenance | Actively maintained | Last published 2024 |
Key differences
- AST-anchored scope — Each suppression's scope is the dot-path of the enclosing named AST node, computed by walking the tree rather than parsing tsc's text output. See How It Works for the tradeoffs this brings.
- No tsc patching — ts-suppress uses the TypeScript compiler API directly to collect diagnostics rather than wrapping or intercepting tsc. This avoids coupling to tsc's output format.
- Explicit CLI commands — Each operation (
init,suppress,check,update) is a separate command rather than a flag, making the workflow easier to script and understand.
Acknowledgements
Inspired by ts-bulk-suppress by TikTok.
License
MIT