From bc82f31a850218f6ef9cca3b00ac7fa9889b2cb6 Mon Sep 17 00:00:00 2001 From: sushichan044 <71284054+sushichan044@users.noreply.github.com> Date: Sat, 12 Apr 2025 15:24:51 +0900 Subject: [PATCH 1/7] init --- designs/2025-bulk-suppressions-api/README.md | 104 +++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 designs/2025-bulk-suppressions-api/README.md diff --git a/designs/2025-bulk-suppressions-api/README.md b/designs/2025-bulk-suppressions-api/README.md new file mode 100644 index 00000000..6a80ca41 --- /dev/null +++ b/designs/2025-bulk-suppressions-api/README.md @@ -0,0 +1,104 @@ +- Repo: eslint/eslint +- Start Date: 2025-04-11 +- RFC PR: (leave this empty, to be filled in later) +- Authors: [Kentaro Suzuki](https://github.com/sushichan044) + +# Add support for bulk suppressions in `ESLint` and `LegacyESLint` classes + +## Summary + + + +## Motivation + + + +## Detailed Design + + + +## Documentation + + + +## Drawbacks + + + +## Backwards Compatibility Analysis + + + +## Alternatives + + + +## Open Questions + + + +## Help Needed + + + +## Frequently Asked Questions + + + +## Related Discussions + + From f63e63ff66c01b922ac8379b0e60701f7055031c Mon Sep 17 00:00:00 2001 From: sushichan044 <71284054+sushichan044@users.noreply.github.com> Date: Tue, 6 May 2025 02:34:31 +0900 Subject: [PATCH 2/7] feat: add support for bulk suppressions in `ESLint` and `LegacyESLint` classes --- designs/2025-bulk-suppressions-api/README.md | 199 ++++++++++++++++++- 1 file changed, 198 insertions(+), 1 deletion(-) diff --git a/designs/2025-bulk-suppressions-api/README.md b/designs/2025-bulk-suppressions-api/README.md index 6a80ca41..8c4bda91 100644 --- a/designs/2025-bulk-suppressions-api/README.md +++ b/designs/2025-bulk-suppressions-api/README.md @@ -1,5 +1,5 @@ - Repo: eslint/eslint -- Start Date: 2025-04-11 +- Start Date: 2025-05-06 - RFC PR: (leave this empty, to be filled in later) - Authors: [Kentaro Suzuki](https://github.com/sushichan044) @@ -9,11 +9,20 @@ +This RFC proposes extending the bulk suppressions feature, introduced in ESLint 9.24.0 for CLI usage, to be available through the Node.js API via the `ESLint` and `LegacyESLint` classes. + ## Motivation +Currently, the bulk suppression feature introduced in ESLint 9.24.0 is only available via the CLI. +This leads to inconsistencies when ESLint is used programmatically via its Node.js API, such as in IDE integrations. + +This leads to inconsistencies when ESLint is used programmatically. Violations suppressed using `eslint-suppressions.json` (especially when using a custom location via the CLI) might not be recognized when using the Node.js API, leading to incorrect error reporting in environments like IDEs. + +This RFC aims to resolve this discrepancy by adding support for bulk suppressions to the `ESLint` and `LegacyESLint` Node.js APIs, ensuring consistent linting results and configuration capabilities across both interfaces. + ## Detailed Design used. Be sure to define any new terms in this section. --> +This proposal integrates the existing bulk suppression functionality into the `ESLint` and `LegacyESLint` Node.js API classes by leveraging the internal `SuppressionsService`. A new `suppressionsLocation` option is introduced in the constructors. + +1. New Constructor Option (`suppressionsLocation`) + - Both `ESLintOptions` and `LegacyESLintOptions` will accept a new optional property: `suppressionsLocation`. + - `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`. + +2. Service Instantiation and Configuration + - Upon instantiation, the `ESLint` and `LegacyESLint` constructors will create an instance of `SuppressionsService`. + - A new constructor option, `suppressionsLocation` (string, optional), will be added to both classes. + - If provided, this path (relative to `cwd`) specifies the suppression file. + - If not provided, ESLint defaults to searching for `eslint-suppressions.json` in the `cwd`. + - The constructor will resolve the final absolute path to the suppression file (using `suppressionsLocation` or the default) and pass it to the `SuppressionsService` constructor. + +3. Applying Suppressions + - Within the `lintFiles()` and `lintText()` methods of both classes, *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, along with any unused suppressions. + - The final, suppression-applied results will be returned to the user. + +### Example code of `lib/eslint/eslint.js` + +```javascript +import fs from "node:fs"; +import { getCacheFile } from "./eslint-helpers.js"; +import { SuppressionsService } from "../services/suppressions-service.js"; + +class ESLint { + constructor(options = {}) { + const processedOptions = processOptions(options); + + // ... existing constructor logic to initialize options, linter, cache, configLoader ... + + const suppressionsFilePath = getCacheFile( + processedOptions.suppressionsLocation, + processedOptions.cwd, + { prefix: "suppressions_" } + ); + + privateMembers.set(this, { + options: processedOptions, + linter: /* ... */, + lintResultCache: /* ... */, + configLoader: /* ... */ + suppressionsFilePath, + }); + } + + async lintFiles(patterns) { + const { options, suppressionsFilePath, lintResultCache, /* ... other needed members */ } = privateMembers.get(this); + let suppressionResults = null; + + // Existing lint logic to get initial `results` (LintResult[]) + const results = /* LintResult[] */ + + // Persist the cache to disk before applying suppressions. + if (lintResultCache) { + lintResultCache.reconcile(); + } + + const finalResults = results.filter(result => !!result); + + if (!fs.existsSync(suppressionsFilePath)) { + return finalResults; + } + + const suppressions = new SuppressionsService({ + filePath: suppressionsFilePath, + cwd: options.cwd, + }); + + return suppressions.applySuppressions( + finalResults, + await suppressions.load(), + ); + } + + async lintText(code, options = {}) { + const { + options: eslintOptions, // Renamed to avoid conflict with method options + suppressionsFilePath, + linter, + configLoader + } = privateMembers.get(this); + + const { filePath: providedFilePath, warnIgnored, ...otherOptions } = options || {}; + + const results = []; + + // --- Existing lintText logic to determine config, etc. --- + + // Do lint. + results.push( + verifyText({ + text: code, + filePath: resolvedFilename.endsWith("__placeholder__.js") + ? "" + : resolvedFilename, + configs, + cwd, + fix: fixer, + allowInlineConfig, + ruleFilter, + stats, + linter, + }), + ); + + if (!fs.existsSync(suppressionsFilePath)) { + return processLintReport(this, { results }); + } + + const suppressions = new SuppressionsService({ + filePath: suppressionsFilePath, + cwd: eslintOptions.cwd, + }); + + const suppressedResults = suppressions.applySuppressions( + results, + await suppressions.load(), + ); + + // Return the suppression-applied results + return processLintReport(this, { results: suppressedResults }); + } +} +``` + ## Documentation on the ESLint blog to explain the motivation? --> +The documentation updates will reflect that this change aligns the Node.js API behavior with the existing CLI functionality. + +1. API Documentation (`ESLint`/`LegacyESLint` classes): + - Add the new `suppressionsLocation` option to the constructor options documentation for both `ESLint` and `LegacyESLint`, explaining its purpose (specifying the suppression file path) and behavior (relative to `cwd`, default lookup). + - Add a note to the descriptions of `lintText()` and `lintFiles()` methods stating that suppressions are automatically applied based on the resolved suppression file path (either from `suppressionsLocation` or the default `eslint-suppressions.json` in `cwd`). Example: "Applies suppressions from the resolved suppression file (`suppressionsLocation` option or `eslint-suppressions.json` in `cwd`), if found." + +2. Bulk Suppressions User Guide Page: + - Update the existing user guide page for Bulk Suppressions. + - Add a section or note clarifying that the feature is now also available when using the `ESLint` and `LegacyESLint` Node.js APIs. + - Explicitly mention how the suppression file is located when using the API: "Note: When using the Node.js API, ESLint searches for the suppression file specified by the `suppressionsLocation` constructor option. If this option is not provided, it defaults to looking for `eslint-suppressions.json` in the `cwd` (current working directory)." + +3. Release Notes: + - Include an entry in the release notes announcing the availability of bulk suppressions in the Node.js API, **highlighting the new `suppressionsLocation` option**. + ## Drawbacks implementing this RFC as possible. --> +- API Complexity: Introduces a new option (`suppressionsLocation`) to the constructor API surface for both `ESLint` and `LegacyESLint`, slightly increasing complexity compared to only supporting the default file location. +- Performance: The overhead of potentially resolving `suppressionsLocation` and then searching for/parsing the suppression file is introduced. However, this aligns the API\'s behavior and capabilities with the CLI. +- Complexity: Introduces `SuppressionsService` interaction into `ESLint`/`LegacyESLint`, but reuses existing internal logic. + ## Backwards Compatibility Analysis to existing users? --> +This change is designed to be backward-compatible. + +- New Option is Optional: The new `suppressionsLocation` option is optional. Existing code that does not provide this option will continue to work, defaulting to the behavior of looking for `eslint-suppressions.json` in the `cwd`. +- Automatic Application: By integrating bulk suppression handling directly into the existing `lintText()` and `lintFiles()` methods, users who utilize a suppression file (either at the default location or specified via the new option) will automatically benefit simply by updating their ESLint version. +- Alignment with CLI: This approach aligns the Node.js API behavior *and configuration options* more closely with the established CLI behavior. +- Non-Breaking: Since the core change only alters behavior when a suppression file is found (based on the new option or default location), it is considered a non-breaking change for existing API consumers. + ## Alternatives projects have already implemented a similar feature. --> +- No `suppressionsLocation` API Option: The initial consideration was to *not* add the `suppressionsLocation` option to the API, only implementing the default lookup in `cwd`. This was simpler but rejected because it lacked full consistency with the CLI\'s `--suppressions-location` flag, preventing API users from specifying a custom file path. Adding the option provides greater flexibility and closer parity with the CLI. +- Separate API Method for Applying Suppressions: Introducing new methods like `lintTextWithSuppressions()` was rejected as inconsistent and burdensome for users compared to automatic application within existing methods. +- Including Suppression File Manipulation Options in `lintText`/`lintFiles`: The CLI includes flags like `--suppress-all`, `--suppress-rule `, and `--prune-suppressions` which generate, update, or prune the suppression file based on lint results. Adding corresponding options to the `lintText` and `lintFiles` API methods was considered. However, this approach was rejected because: + - It would introduce file-writing side effects into API methods primarily designed for linting (reading and analyzing code). This could lead to unexpected behavior for API consumers. + - It blurs the responsibility of the `lintText`/`lintFiles` methods. + ## Open Questions you can remove this section. --> +- Specific implementation examples for the `LegacyESLint` class. +- Should functionality equivalent to CLI flags like `--suppress-all` or `--suppress-rule` be supported via separate API methods in the future? + ## Help Needed of help would you need from the team? --> +I intend to implement this feature. + ## Frequently Asked Questions in this section. --> +Potential questions regarding alternative design approaches are addressed in the `Alternatives` section. + ## Related Discussions If there is an issue, pull request, or other URL that provides useful context for this proposal, please include those links here. --> + +### Current Usage of ESLint Node.js API + +While improving IDE support is not the primary goal of this RFC, the following investigation provides context on how various tools integrate with ESLint\'s Node.js API. + +#### IDE Integrations + +- VSCode: Uses [`ESLint.lintText()` / `LegacyESLint.lintText()`](https://github.com/microsoft/vscode-eslint/blob/c0e753713ea9935667e849d91e549adbff213e7e/server/src/eslint.ts#L1192-L1243) for validation. + +- Zed: Interacts with the `vscode-eslint` server via `--stdio`. No specific changes needed beyond updating `vscode-eslint`. + - + - + +- JetBrains IDEs: Implementation details are unknown (closed-source). + +- Neovim: Integrations like [nvim-eslint](https://github.com/esmuellert/nvim-eslint) typically use `vscode-eslint`. No specific changes needed beyond updating `vscode-eslint`. + +#### ESLint Wrappers / Utilities + +- xo: Uses [`ESLint.lintFiles()` / `ESLint.lintText()`](https://github.com/xojs/xo/blob/529e6c4ac75f6165044f7ea87bad0b9831803efd/lib/xo.ts#L333-L395) for linting. + +- eslint-interactive: Uses [`ESLint.lintFiles()`](https://github.com/mizdra/eslint-interactive/blob/96f3fa34eb6fa150056aa48c0bc2c3e322ef3549/src/core.ts#L85-L93) for linting. From c5b4134d87d0250bdbcf0298e848834c992881f5 Mon Sep 17 00:00:00 2001 From: sushichan044 <71284054+sushichan044@users.noreply.github.com> Date: Wed, 7 May 2025 00:42:01 +0900 Subject: [PATCH 3/7] add changes to cli --- designs/2025-bulk-suppressions-api/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/designs/2025-bulk-suppressions-api/README.md b/designs/2025-bulk-suppressions-api/README.md index 8c4bda91..d9392d7c 100644 --- a/designs/2025-bulk-suppressions-api/README.md +++ b/designs/2025-bulk-suppressions-api/README.md @@ -54,6 +54,10 @@ This proposal integrates the existing bulk suppression functionality into the `E - This method takes the raw linting results and the loaded suppressions (from the resolved file path) and returns the results with suppressions applied, along with any unused suppressions. - The final, suppression-applied results will be returned to the user. +4. Changes to ESLint CLI + - With the integration of suppression handling into the `ESLint` and `LegacyESLint` APIs, the ESLint CLI (`lib/cli.js`) will be updated. + - Specifically, direct calls to `SuppressionsService` within the CLI will be removed. The CLI will now leverage the updated API methods to handle bulk suppressions, ensuring that the CLI's behavior is consistent with the API's new capabilities. This change simplifies the CLI's implementation by delegating suppression logic to the core API. + ### Example code of `lib/eslint/eslint.js` ```javascript From 8b6e9734a1b02b128d751449bf335bf968f8fe3f Mon Sep 17 00:00:00 2001 From: Kentaro Suzuki <71284054+sushichan044@users.noreply.github.com> Date: Thu, 8 May 2025 23:22:36 +0900 Subject: [PATCH 4/7] fill PR Co-authored-by: Nicholas C. Zakas --- designs/2025-bulk-suppressions-api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2025-bulk-suppressions-api/README.md b/designs/2025-bulk-suppressions-api/README.md index d9392d7c..7437509c 100644 --- a/designs/2025-bulk-suppressions-api/README.md +++ b/designs/2025-bulk-suppressions-api/README.md @@ -1,6 +1,6 @@ - Repo: eslint/eslint - Start Date: 2025-05-06 -- RFC PR: (leave this empty, to be filled in later) +- RFC PR: https://github.com/eslint/rfcs/pull/133 - Authors: [Kentaro Suzuki](https://github.com/sushichan044) # Add support for bulk suppressions in `ESLint` and `LegacyESLint` classes From fa25623767c24c22f0aafd4bae2236103111e728 Mon Sep 17 00:00:00 2001 From: sushichan044 <71284054+sushichan044@users.noreply.github.com> Date: Wed, 28 May 2025 19:59:16 +0900 Subject: [PATCH 5/7] perf: instantiate SuppressionsService once in constructor --- designs/2025-bulk-suppressions-api/README.md | 29 ++++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/designs/2025-bulk-suppressions-api/README.md b/designs/2025-bulk-suppressions-api/README.md index 7437509c..ddca1ed9 100644 --- a/designs/2025-bulk-suppressions-api/README.md +++ b/designs/2025-bulk-suppressions-api/README.md @@ -66,6 +66,12 @@ import { getCacheFile } from "./eslint-helpers.js"; import { SuppressionsService } from "../services/suppressions-service.js"; class ESLint { + /** + * The suppressions service to use for suppressing messages. + * @type {SuppressionsService} + */ + #suppressionsService; + constructor(options = {}) { const processedOptions = processOptions(options); @@ -77,17 +83,21 @@ class ESLint { { prefix: "suppressions_" } ); + this.#suppressionsService = new SuppressionsService({ + filepath: suppressionsFilePath, + cwd: processedOptions.cwd, + }) + privateMembers.set(this, { options: processedOptions, linter: /* ... */, lintResultCache: /* ... */, configLoader: /* ... */ - suppressionsFilePath, }); } async lintFiles(patterns) { - const { options, suppressionsFilePath, lintResultCache, /* ... other needed members */ } = privateMembers.get(this); + const { option, lintResultCache, /* ... other needed members */ } = privateMembers.get(this); let suppressionResults = null; // Existing lint logic to get initial `results` (LintResult[]) @@ -104,12 +114,7 @@ class ESLint { return finalResults; } - const suppressions = new SuppressionsService({ - filePath: suppressionsFilePath, - cwd: options.cwd, - }); - - return suppressions.applySuppressions( + return this.#suppressionsService.applySuppressions( finalResults, await suppressions.load(), ); @@ -118,7 +123,6 @@ class ESLint { async lintText(code, options = {}) { const { options: eslintOptions, // Renamed to avoid conflict with method options - suppressionsFilePath, linter, configLoader } = privateMembers.get(this); @@ -150,12 +154,7 @@ class ESLint { return processLintReport(this, { results }); } - const suppressions = new SuppressionsService({ - filePath: suppressionsFilePath, - cwd: eslintOptions.cwd, - }); - - const suppressedResults = suppressions.applySuppressions( + const suppressedResults = this.#suppressionsService.applySuppressions( results, await suppressions.load(), ); From 00ba3e9e6c7d9dc232908841b6c056a7c8ce4601 Mon Sep 17 00:00:00 2001 From: sushichan044 <71284054+sushichan044@users.noreply.github.com> Date: Wed, 28 May 2025 20:25:23 +0900 Subject: [PATCH 6/7] docs: explicitly show scope --- designs/2025-bulk-suppressions-api/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/designs/2025-bulk-suppressions-api/README.md b/designs/2025-bulk-suppressions-api/README.md index ddca1ed9..8ade59c2 100644 --- a/designs/2025-bulk-suppressions-api/README.md +++ b/designs/2025-bulk-suppressions-api/README.md @@ -1,15 +1,17 @@ - Repo: eslint/eslint - Start Date: 2025-05-06 -- RFC PR: https://github.com/eslint/rfcs/pull/133 +- RFC PR: - Authors: [Kentaro Suzuki](https://github.com/sushichan044) -# Add support for bulk suppressions in `ESLint` and `LegacyESLint` classes +# Consider bulk suppressions when running Lint via the Node.js API ## Summary -This RFC proposes extending the bulk suppressions feature, introduced in ESLint 9.24.0 for CLI usage, to be available through the Node.js API via the `ESLint` and `LegacyESLint` classes. +This RFC proposes integrating bulk suppressions support into the Node.js API via the `ESLint` and `LegacyESLint` classes, specifically focusing on considering existing bulk suppressions when linting files or text through the API. This change ensures that suppression files (`eslint-suppressions.json`) created via CLI commands are automatically respected when using the programmatic API, maintaining consistency between CLI and API behavior. + +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. ## Motivation From 40473fd3ba2991c20222505c167d8516aa5cd3f9 Mon Sep 17 00:00:00 2001 From: sushichan044 <71284054+sushichan044@users.noreply.github.com> Date: Tue, 17 Jun 2025 10:48:58 +0900 Subject: [PATCH 7/7] we don't need to focus on privateMembers in ESLint class --- designs/2025-bulk-suppressions-api/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/designs/2025-bulk-suppressions-api/README.md b/designs/2025-bulk-suppressions-api/README.md index 8ade59c2..7714878b 100644 --- a/designs/2025-bulk-suppressions-api/README.md +++ b/designs/2025-bulk-suppressions-api/README.md @@ -89,13 +89,6 @@ class ESLint { filepath: suppressionsFilePath, cwd: processedOptions.cwd, }) - - privateMembers.set(this, { - options: processedOptions, - linter: /* ... */, - lintResultCache: /* ... */, - configLoader: /* ... */ - }); } async lintFiles(patterns) {