Skip to content

feat: Consider bulk suppressions via the Node.js API#142

Open
adf0nt3s wants to merge 16 commits intoeslint:mainfrom
adf0nt3s:bulk-suppressions-nodejs-api
Open

feat: Consider bulk suppressions via the Node.js API#142
adf0nt3s wants to merge 16 commits intoeslint:mainfrom
adf0nt3s:bulk-suppressions-nodejs-api

Conversation

@adf0nt3s
Copy link

Summary

This RFC continues work on the proposal for integrating bulk suppressions support into the Node.js API via the ESLint and LegacyESLint classes. It specifically focusing on considering existing bulk suppressions when linting files or text through the API, ensuring that suppression files (eslint-suppressions.json) created via CLI commands are automatically respected when using the programmatic API.

The scope is limited to applying existing suppressions during linting and does not include suppression file manipulation features (such as --suppress-all, --suppress-rule, or --prune-suppressions), which remain CLI-exclusive functionalities.

Related Issues

eslint/eslint#19603
#133

@eslint-github-bot
Copy link

Hi @adf0nt3s!, thanks for the Pull Request

The pull request title isn't properly formatted. We ask that you update the pull request title to match this format, as we use it to generate changelogs and automate releases.

  • The commit message tag wasn't recognized. Did you mean "docs", "fix", or "feat"?
  • There should be a space following the initial tag and colon, for example 'feat: Message'.
  • The first letter of the tag should be in lowercase

To Fix: You can fix this problem by clicking 'Edit' next to the pull request title at the top of this page.

Read more about contributing to ESLint here

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Jan 17, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@adf0nt3s adf0nt3s changed the title Bulk suppressions nodejs api feat: Consider bulk suppressions via the Node.js API Jan 17, 2026
- `suppressionsLocation: string | undefined`: Specifies the path to the suppressions file (`eslint-suppressions.json`). This path can be absolute or relative to the `cwd`.
- If `suppressionsLocation` is provided, ESLint will attempt to load suppressions from that specific file path.
- If `suppressionsLocation` is not provided (or is `undefined`), ESLint will default to looking for `eslint-suppressions.json` in the `cwd`.
- `applySuppressions`
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nzakas per your suggestion on the previous version of this RFC, i've introduced a new constructor option

@fasttime fasttime added the Initial Commenting This RFC is in the initial feedback stage label Jan 17, 2026
Comment on lines 184 to 188
## Backwards Compatibility Analysis

This change is designed to be backward-compatible.

- New Options are Optional: The new `suppressionsLocation` and `applySuppressions` options are optional. Existing code that does not provide these options will continue to work, with `applySuppressions` defaulting to `true` and `suppressionsLocation` defaulting to looking for `eslint-suppressions.json` in the `cwd`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When someone is already using the default suppression file eslint-suppressions.json, will applySuppressions defaulting to true change the current behavior for API users by not returning suppressed messages in LintResult#messages anymore? For example, suppressed messages will no longer appear in IDEs? If that's the case, it could be argued that this is a breaking change.

Note: I'm not suggesting to change the default to false, but would like to discuss if this is breaking change and how to handle it if that's the case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I can't speak definitively about the current API usage patterns amongst ESLint's consumers, but i'm very curious to hear from those that can.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're unsure, maybe it would be safer to have applySuppressions default to false for a start and switch it to true in a future major release?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable, i'll make that change in the RFC

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 to defaulting to false. I think this is the expected behavior from API consumers.

2. Service Instantiation and Configuration
- Upon instantiation, the `ESLint` and `LegacyESLint` constructors will create an instance of `SuppressionsService`.
- 2 new constructor options will be added to both classes.
- `suppressionsLocation` (string, optional)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe suppressionsLocation should have a special value (e.g. null) to not look up the suppression file. This allows restoring the old behavior and eliminates the file lookup (as mentioned in the drawbacks).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DMartens Would it be better instead to just not look up the file if applySuppressions is false? That also preserves the previous behavior and eliminates the performance hit, but may be a bit more intuitive than introducing a third option to suppressionsLocation.

Additionally, if we went the suppressionsLocation: null route, we'd also have to add some logic to throw if suppressionsLocations was null and applySuppressions was true.

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, I agree with you as the cache option is also split into "enabled" and "file path".
On the other hand, how should we handle suppressionFile: /some/path/to/suppressions.json with applySuppressions set to false (throw an error, print a warning, ...)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, print a warning. Doesn't feel important enough to me to throw an error and prevent the linting operation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't typically print warnings for API-level mistakes, only user mistakes (through the CLI). I'd say it's fine to quietly ignore suppressionFile when applySuppressions is false. That's not a situation an end user will run into.

- If not provided, defaults to `true`.

3. Applying Suppressions
- When `applySuppressions` is `true` (the default), the `lintFiles()` and `lintText()` methods will automatically apply suppressions to results before returning.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to note, when cache (--cache CLI flag or cache API option) is used, what will be stored in the cache file: original results or results with applied suppressions? Storing original results probably makes more sense.

Copy link
Author

@adf0nt3s adf0nt3s Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, seems like storing the suppressed results is asking for all sorts of dirty-state problems (like if a user changes suppressions without changing the source file). Adding a line now.

@adf0nt3s
Copy link
Author

Any other thoughts here? Thanks! :)

}),
);

if (!fs.existsSync(suppressionsFilePath) || eslintOptions.applySuppressions === false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When options.filePath is not set, looking up the suppressions file seems unnecessary. In that case, the filePath property of the lint result contains the string "<text>".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are suggesting something like this?

Suggested change
if (!fs.existsSync(suppressionsFilePath) || eslintOptions.applySuppressions === false) {
if (!providedFilePath || eslintOptions.applySuppressions === false) {

Is that correct? Im in favor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the variable to check in lintText is called just filePath. Apart from that, this should work. In fact there's no need to check explicitly if the suppressions file exists because the suppressions service will simply do nothing if it doesn't find it.

Suggested change
if (!fs.existsSync(suppressionsFilePath) || eslintOptions.applySuppressions === false) {
if (!filePath || eslintOptions.applySuppressions === false) {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fasttime Wait, i'm confused. According to the TSDoc strings, options.filePath is for the source code, not the suppression file.

/**
	 * Executes the current configuration on text.
	 * @param {string} code A string of JavaScript code to lint.
	 * @param {Object} [options] The options.
	 * @param {string} [options.filePath] The path to the file of the source code.
	 * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
	 * @returns {Promise<LintResult[]>} The results of linting the string of code given.
	 */
	async lintText(code, options = {}) {

What are we trying to handle here? An invalid source code path or a non-existent suppression file?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, i think i understand. We are saying two things here:

  1. If filePath (the path for the source code) is falsey, skip applying suppressions
  2. There is no need to check for the existence of the suppressions file at all, since we'll just no-op if it isn't present, so nothing needs to replace the fs.existsSync(suppressionsFilePath) check

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. filePath is the path of the file that contains the provided source code, or undefined if not specified.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, change made 👍

Comment on lines 184 to 188
## Backwards Compatibility Analysis

This change is designed to be backward-compatible.

- New Options are Optional: The new `suppressionsLocation` and `applySuppressions` options are optional. Existing code that does not provide these options will continue to work, with `applySuppressions` defaulting to `true` and `suppressionsLocation` defaulting to looking for `eslint-suppressions.json` in the `cwd`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're unsure, maybe it would be safer to have applySuppressions default to false for a start and switch it to true in a future major release?

Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this approach. It's straightforward both to implement and use. 👍

2. Service Instantiation and Configuration
- Upon instantiation, the `ESLint` and `LegacyESLint` constructors will create an instance of `SuppressionsService`.
- 2 new constructor options will be added to both classes.
- `suppressionsLocation` (string, optional)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't typically print warnings for API-level mistakes, only user mistakes (through the CLI). I'd say it's fine to quietly ignore suppressionFile when applySuppressions is false. That's not a situation an end user will run into.

- When `applySuppressions` is `true`, the `lintFiles()` and `lintText()` methods will automatically apply suppressions to results before returning.
- Within these methods, *after* the initial linting results are obtained, the `applySuppressions` method of the instantiated `SuppressionsService` will be called.
- This method takes the raw linting results and the loaded suppressions (from the resolved file path) and returns the results with suppressions applied.
- When caching is enabled, the **original lint results** are stored in the cache. Suppressions are applied after cache retrieval, so that changes to the suppression file take effect without needing to bust the cache.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. 👍


## Open Questions

- Should functionality equivalent to CLI flags like `--suppress-all` or `--suppress-rule` be supported via separate API methods in the future?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say no. The intent of this feature is to honor existing suppression files, not to provide APIs for all aspects of suppressions.

Copy link
Member

@fasttime fasttime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks.

@nzakas
Copy link
Member

nzakas commented Feb 11, 2026

Given that we have two approvals from the TSC, moving to Final Commenting.

@nzakas nzakas added Final Commenting This RFC is in the final week of commenting and removed Initial Commenting This RFC is in the initial feedback stage labels Feb 11, 2026
Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Final Commenting This RFC is in the final week of commenting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

Comments