Skip to content

Commit 227c8cc

Browse files
authored
feat: respect suppressions during autofix (#146)
* feat: respect suppressions during autofix * add implementation approach * clarify suppressions snapshot and fixer flow * define "suppressions snapshot" * apply suggestions
1 parent 85b1518 commit 227c8cc

1 file changed

Lines changed: 172 additions & 0 deletions

File tree

  • designs/2026-respect-suppressions-during-autofix
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
- Repo: eslint/eslint
2+
- Start Date: 2026-03-27
3+
- RFC PR: eslint/rfcs#146
4+
- Authors: sethamus
5+
6+
# Respect Bulk Suppressions During Autofix
7+
8+
## Summary
9+
10+
ESLint should stop applying autofixes for a rule in any file where that rule is suppressed via the suppressions file. This should depend only on whether the suppressions data contains a matching file-and-rule entry, not on the stored suppression count.
11+
12+
For example, if `eslint-suppressions.json` indicates that `semi` is suppressed for `src/a.js`, then `semi` fixes are disabled for `src/a.js`. Other rules in `src/a.js` may still be autofixed, and `semi` may still be autofixed in files where it is not suppressed.
13+
14+
## Motivation
15+
16+
Bulk suppressions are meant to separate an existing backlog from new work. That separation breaks down when `--fix` rewrites code that the suppressions file is explicitly marking as out of scope.
17+
18+
This is especially painful in large or inherited codebases, where a developer may make a small change, run `eslint --fix`, and end up with a large unrelated diff because ESLint also fixed suppressed rules in the same files. Even when those fixes are technically safe, they create churn in code that teams have deliberately deferred.
19+
20+
## Detailed Design
21+
22+
### High-Level Behavior
23+
24+
When bulk suppressions are active for a run, ESLint should treat each suppressed file-and-rule pair as ineligible for autofix.
25+
26+
Examples:
27+
28+
- If `semi` is suppressed for `src/a.js`, then `semi` fixes are skipped in `src/a.js`.
29+
- If `semi` is not suppressed for `src/b.js`, then `semi` fixes may still be applied in `src/b.js`.
30+
31+
The stored suppression count is ignored for autofix purposes. A single suppression entry for a file and rule is enough to disable autofix for that rule in that file.
32+
33+
### Why Count Is Ignored
34+
35+
Bulk suppressions are stored as counts, not as identities for specific lint messages. Because ESLint cannot reliably tell which current violations correspond to historical suppressed ones, this RFC does not try to autofix only the "new" violations.
36+
37+
Instead, once a rule is suppressed for a file, autofix for that rule is disabled for the entire file until the suppression entry is removed. This is intentionally coarse:
38+
39+
- `eslint-suppressions.json` says `src/a.js -> semi -> count: 3`
40+
- `src/a.js` currently has 5 `semi` errors
41+
42+
For reporting, ESLint can continue to use the existing count-based suppression behavior. In this example, because the current count exceeds the stored count, none of the `semi` errors are suppressed and ESLint reports all 5. For autofix, however, all `semi` fixes in `src/a.js` are still skipped because `semi` is suppressed for that file.
43+
44+
### CLI Behavior
45+
46+
For the CLI, this behavior should happen automatically whenever the run uses bulk suppressions.
47+
48+
That means a command such as:
49+
50+
```bash
51+
eslint --fix
52+
```
53+
54+
should respect the active suppressions file for that invocation.
55+
56+
The same should apply to:
57+
58+
```bash
59+
eslint --fix-dry-run
60+
```
61+
62+
Likewise:
63+
64+
```bash
65+
eslint --fix --suppress-rule semi
66+
```
67+
68+
should use the suppressions file that exists at the start of the run when deciding which fixes to skip. Newly added suppressions from the same invocation should not retroactively change which fixes were already eligible earlier in the run.
69+
70+
### Node.js API Behavior
71+
72+
This RFC does not add a separate API option. When using the Node.js API, autofix should respect suppressions whenever `applySuppressions` is enabled for the run. For example:
73+
74+
```js
75+
const eslint = new ESLint({
76+
applySuppressions: true,
77+
fix: true,
78+
});
79+
```
80+
81+
In this configuration:
82+
83+
- suppressed file-and-rule pairs are not autofixed
84+
- unsuppressed file-and-rule pairs remain autofixable
85+
86+
For `lintText()`, this behavior only applies when `filePath` is provided, because suppressions are keyed by file path.
87+
88+
### Reporting Semantics Remain Separate
89+
90+
This RFC only changes autofix eligibility. ESLint may continue to use counts for reporting and pruning exactly as it does today.
91+
92+
As a result, reporting and autofix operate at different levels of precision:
93+
94+
- reporting remains count-based
95+
- autofix becomes file-and-rule based
96+
97+
That difference is intentional.
98+
99+
### Implementation Approach
100+
101+
The implementation would likely touch the following files:
102+
103+
A **suppressions snapshot** is the in-memory representation of the `eslint-suppressions.json` file contents, read once at the start of the lint run. By capturing the suppressions data before linting begins, autofix decisions are based on a stable, point-in-time view of the file rather than a version that may change during the run (for example, if `--suppress-rule` writes new entries to the file during the same invocation).
104+
105+
- `lib/cli.js`: for CLI runs with `--fix` or `--fix-dry-run`, resolve the active suppressions file before linting, load its current contents, and pass that suppressions snapshot into the `ESLint` instance used for fix generation. This snapshot will be passed via the `ESLint` constructor options using an internal `Symbol`. This makes autofix depend on the suppressions file contents loaded before linting begins.
106+
- `lib/eslint/eslint.js`: initialize `SuppressionsService` when suppressions are needed for fix filtering as well as reporting. For Node.js API calls with `applySuppressions` and `fix` enabled, load suppressions early enough in each `lintFiles()` or `lintText()` run before building the fixer. In `lintText()`, compute the suppressed rules only when `filePath` is provided and build the suppressions-aware fixer before passing it to `verifyText()`. For worker threads, pass the suppressions snapshot through `workerData`.
107+
- `lib/eslint/worker.js`: receive the suppressions snapshot via `workerData` and pass it to `lintFile()`.
108+
- `lib/eslint/eslint-helpers.js`: extend the fixer-building logic used by `lintFile()` so it composes the existing fix predicate with a suppressions-aware predicate. For the current file, if `message.ruleId` appears in that file's suppressions entry, return `false` so the fix is skipped.
109+
- `lib/services/suppressions-service.js`: add an optional parameter to `suppress()` so it can accept a preloaded snapshot. Add a helper that maps a file path to the set of suppressed rule IDs for that file, reusing the existing relative-path normalization logic.
110+
- `lib/linter/linter.js`: no new suppression logic should be necessary. `verifyAndFix()` already passes `options.fix` through to `SourceCodeFixer.applyFixes()`, so the composed predicate from the higher layers should be enough.
111+
112+
### Example
113+
114+
Suppose `eslint-suppressions.json` contains:
115+
116+
```json
117+
{
118+
"src/a.js": {
119+
"semi": {
120+
"count": 3
121+
}
122+
}
123+
}
124+
```
125+
126+
And suppose `src/a.js` currently has:
127+
128+
- 5 `semi` errors
129+
- 2 `indent` errors
130+
131+
With this RFC's behavior:
132+
133+
- `semi` fixes are skipped in `src/a.js`
134+
- `indent` fixes may still be applied in `src/a.js`
135+
136+
## Documentation
137+
138+
This RFC requires updates to:
139+
140+
- [CLI options documentation](https://eslint.org/docs/latest/use/command-line-interface#options): update the `--fix` and `--fix-dry-run` documentation to explain that fixes are skipped for rules suppressed via the suppressions file.
141+
- [Bulk suppressions documentation](https://eslint.org/docs/latest/use/suppressions): explain that suppressions affect autofix eligibility, and that users must remove a suppression entry before intentionally bulk-fixing that rule in that file.
142+
- [Node.js API reference](https://eslint.org/docs/latest/integrate/nodejs-api): document that when `applySuppressions` is enabled, autofix also respects suppressions.
143+
144+
## Drawbacks
145+
146+
The main drawback is that this behavior is intentionally coarse. If a rule is suppressed for a file and a new violation of that rule is later introduced in the same file, ESLint will still skip autofixing that new violation.
147+
148+
Reporting and autofix also use different matching rules. If the current count exceeds the stored suppression count, ESLint reports all violations for that rule in the file, but this RFC would still skip autofixes for that rule in that file.
149+
150+
## Backwards Compatibility Analysis
151+
152+
This proposal changes behavior for autofix runs where bulk suppressions are active. In the CLI, rules suppressed via the suppressions file would no longer be autofixed in those files. In the Node.js API, the same behavior would apply when `applySuppressions` is enabled. This is a behavior change, but it is consistent with how other effectively ignored problems already behave: problems ignored by `eslint-disable` comments are not autofixed, and warnings ignored by `--quiet` are also not autofixed.
153+
154+
Users who want to bulk-fix a suppressed rule can still do so by removing the relevant suppression entry before running autofix.
155+
156+
## Alternatives
157+
158+
### Make the Behavior Opt-In
159+
160+
Another alternative is to keep the current behavior by default and make this opt-in through a CLI flag and a Node.js API option. This was rejected because the current behavior is better understood as unexpected, and the same principle already applies elsewhere in ESLint: effectively ignored lint problems are not autofixed.
161+
162+
## Open Questions
163+
164+
Should ESLint provide any user-facing indication that fixes were skipped because the corresponding file-and-rule pairs are suppressed?
165+
166+
## Help Needed
167+
168+
I can implement this RFC.
169+
170+
## Related Discussions
171+
172+
- eslint/eslint#20062

0 commit comments

Comments
 (0)