@rushstack/eslint-patch
Advanced tools
Comparing version 1.7.0 to 1.7.1
{ | ||
"name": "@rushstack/eslint-patch", | ||
"version": "1.7.0", | ||
"description": "A patch that improves how ESLint loads plugins when working in a monorepo with a reusable toolchain", | ||
"version": "1.7.1", | ||
"description": "Enhance ESLint with better support for large scale monorepos", | ||
"main": "lib/usage.js", | ||
@@ -21,7 +21,12 @@ "license": "MIT", | ||
"relative", | ||
"package" | ||
"package", | ||
"bulk", | ||
"suppressions", | ||
"monorepo", | ||
"monkey", | ||
"patch" | ||
], | ||
"devDependencies": { | ||
"@rushstack/heft": "0.62.0", | ||
"@rushstack/heft-node-rig": "2.3.2", | ||
"@rushstack/heft": "0.64.0", | ||
"@rushstack/heft-node-rig": "2.4.0", | ||
"@types/node": "18.17.15", | ||
@@ -28,0 +33,0 @@ "typescript": "~5.3.3", |
308
README.md
# @rushstack/eslint-patch | ||
A patch that improves how ESLint loads plugins when working in a monorepo with a reusable toolchain | ||
Enhance your [ESLint](https://eslint.org/) with better support for large scale monorepos! | ||
# modern-module-resolution | ||
This is a runtime patch that enables new/experimental features for ESLint. It operates as a "monkey patch" | ||
that gets loaded with **.eslintrc.js** and modifies the ESLint engine in memory. This approach works | ||
with your existing ESLint version (no need to install a forked ESLint), and is fully interoperable with | ||
companion tools such as the ESLint extensions for VS Code and WebStorm. | ||
## What it does | ||
This package provides several independently loadable features: | ||
This patch is a workaround for a longstanding [ESLint feature request](https://github.com/eslint/eslint/issues/3458) | ||
that would allow a shared ESLint config to bring along its own plugins, rather than imposing peer dependencies | ||
on every consumer of the config. In a monorepo scenario, this enables your lint setup to be consolidated in a | ||
single NPM package. Doing so greatly reduces the copy+pasting and version management for all the other projects | ||
that use your standard lint rule set, but don't want to be bothered with the details. | ||
- `eslint-bulk-suppressions` enables you to roll out new lint rules in your monorepo without having to | ||
clutter up source files with thousands of machine-generated `// eslint-ignore-next-line` directives. | ||
Instead, the "bulk suppressions" for legacy violations are managed in a separate file called | ||
**.eslint-bulk-suppressions.json**. | ||
ESLint provides partial solutions such as the `--resolve-plugins-relative-to` CLI option, however they are | ||
awkward to use. For example, the VS Code extension for ESLint must be manually configured with this CLI option. | ||
If some developers use other editors such as WebStorm, a different manual configuration is needed. | ||
Also, the `--resolve-plugins-relative-to` parameter does not support multiple paths, for example if a config package | ||
builds upon another package that also provides plugins. See | ||
[this discussion](https://github.com/eslint/eslint/issues/3458#issuecomment-516666620) | ||
for additional technical background. | ||
- `modern-module-resolution` allows an ESLint config package to provide plugin dependencies, avoiding the | ||
problem where hundreds of projects in a monorepo need to copy+paste the same `"devDependencies"` in | ||
every **package.json** file. | ||
> NOTE: ESLint 8.21.0 has now introduced a new `ESLINT_USE_FLAT_CONFIG` mode that may reduce the need | ||
for the `modern-module-resolution` patch. | ||
## Why it's a patch | ||
- `custom-config-package-names` enables [rig packages](https://heft.rushstack.io/pages/intro/rig_packages/) | ||
to provide shareable configs for ESLint, by removing the requirement that `eslint-config` must appear in | ||
the NPM package name. | ||
ESLint's long awaited module resolver overhaul still has not materialized as of ESLint 8. As a stopgap, | ||
we created a small **.eslintrc.js** patch that solves the problem adequately for most real world scenarios. | ||
This patch was proposed as an ESLint feature with [PR 12460](https://github.com/eslint/eslint/pull/12460), however | ||
the maintainers were not able to accept it unless it is reworked into a fully correct design. Such a requirement | ||
would impose the same hurdles as the original GitHub issue; thus, it seems best to stay with the patch approach. | ||
Contributions welcome! If you have more ideas for experimental ESLint enhancements that might benefit | ||
large scale monorepos, consider adding them to this patch. | ||
Since the patch is now in wide use, we've converted it into a proper NPM package to simplify maintenance. | ||
# eslint-bulk-suppressions feature | ||
## How to use it | ||
<!-- ## is correct here, but ### looks better in NPM's rendering --> | ||
Add a `require()` call to the to top of the **.eslintrc.js** file for each project that depends on your shared | ||
ESLint config, for example: | ||
### What it does | ||
**.eslintrc.js** | ||
```ts | ||
require("@rushstack/eslint-patch/modern-module-resolution"); | ||
As your monorepo evolves and grows, there's an ongoing need to expand and improve lint rules. But whenever a | ||
new rule is enabled, there may be hundreds or thousands of "legacy violations" in existing source files. | ||
How to handle that? We could fix the old code, but that's often prohibitively expensive and may even cause | ||
regressions. We could disable the rule for those projects or files, but we want new code to follow the rule. | ||
An effective solution is to inject thousands of `// eslint-ignore-next-line` lines, but these "bulk suppressions" | ||
have an unintended side effect: It normalizes the practice of suppressing lint rules. If people get used to | ||
seeing `// eslint-ignore-next-line` everywhere, nobody will notice when humans suppress the rules for new code. | ||
That would undermine the mission of establishing better code standards. | ||
// Add your "extends" boilerplate here, for example: | ||
module.exports = { | ||
extends: ['@your-company/eslint-config'], | ||
parserOptions: { tsconfigRootDir: __dirname } | ||
}; | ||
``` | ||
The `eslint-bulk-suppressions` feature introduces a way to store machine-generated suppressions in a separate | ||
file **.eslint-bulk-suppressions.json** which can even be protected using `CODEOWNERS` policies, since that file | ||
will generally only change when new lint rules are introduced, or in occasional circumstances when existing files | ||
are being moved or renamed. In this way `// eslint-ignore-next-line` remains a directive written by humans | ||
and hopefully rarely needed. | ||
With this change, the local project no longer needs any ESLint plugins in its **package.json** file. | ||
Instead, the hypothetical `@your-company/eslint-config` NPM package would declare the plugins as its | ||
own dependencies. | ||
This patch works by modifying the ESLint engine so that its module resolver will load relative to the folder of | ||
the referencing config file, rather than the project folder. The patch is compatible with ESLint 6, 7, and 8. | ||
It also works with any editor extensions that load ESLint as a library. | ||
### Why it's a patch | ||
There is a second patch in this package that removes the restriction on eslint configuration package names. | ||
Similarly to the first, this patch is applied by adding a `require()` call to the top of the **.eslintrc.js**, | ||
for example: | ||
As with `modern-module-resolution`, our hope is for this feature to eventually be incorporated as an official | ||
feature of ESLint. Starting out as an unofficial patch allows faster iteration and community feedback. | ||
**.eslintrc.js** | ||
```ts | ||
require("@rushstack/eslint-patch/modern-module-resolution"); | ||
require("@rushstack/eslint-patch/custom-config-package-names"); // <-- Add this line | ||
// Add your "extends" boilerplate here, for example: | ||
module.exports = { | ||
extends: [ | ||
'@your-company/build-rig/profile/default/includes/eslint/node' // Notice the package name does not start with "eslint-config-" | ||
], | ||
parserOptions: { tsconfigRootDir: __dirname } | ||
}; | ||
``` | ||
### How to use it | ||
For an even leaner setup, `@your-company/eslint-config` can provide the patches as its own dependency. See | ||
[@rushstack/eslint-config](https://www.npmjs.com/package/@rushstack/eslint-config) for a real world example | ||
and recommended approach. | ||
1. Add `@rushstack/eslint-patch` as a dependency of your project: | ||
# eslint-bulk-suppressions | ||
```bash | ||
cd your-project | ||
npm install --save-dev @rushstack/eslint-patch | ||
``` | ||
A tool that allows bulk suppression of ESLint warnings/errors in a large, old codebase when introducing new ESLint rules. | ||
2. Globally install the [`@rushstack/eslint-bulk`](https://www.npmjs.com/package/@rushstack/eslint-bulk) | ||
command line interface (CLI) package. For example: | ||
## What it does | ||
```bash | ||
npm install --global @rushstack/eslint-bulk | ||
``` | ||
This tool is designed to address the issue of introducing new ESLint rules to a large, old codebase, which often | ||
results in hundreds to tens of thousands of retroactive issues being reported by ESLint. This can clutter the | ||
ESLint output, making it difficult to read and potentially causing developers to overlook new ESLint issues. It | ||
also makes it impractical to use merge request pipelines that block ESLint warnings/errors. | ||
This installs the `eslint-bulk` shell command for managing the **.eslint-bulk-suppressions.json** files. | ||
With it you can generate new suppressions as well as "prune" old suppressions that are no longer needed. | ||
The tool provides a mechanism for recording all retroactively introduced ESLint warnings/errors in a | ||
"bulk suppressions" file, hiding them from the ESLint output. This allows developers to still get most of the | ||
benefits of ESLint, as any new code written will be annotated by ESLint and can be fixed in bite-sized portions. | ||
It also allows the use of merge request pipelines to block newly written error-prone code without blocking legacy | ||
code that has been battle-tested. | ||
3. Load the patch by adding the following `require()` statement as the first line of | ||
your **.eslintrc.js** file. For example: | ||
## Why it's a patch | ||
The bulk suppressions feature is implemented as a monkey-patch, inspired by the modern-module-resolution | ||
implementation. We prefer it as a patch because it allows users to opt-in to using the tool at their own discretion. | ||
Similar to modern-module-resolution, the use case is much more pronounced in large codebases where ESLint | ||
warnings/errors can appear at magnitudes of thousands rather than tens. Besides reducing bundle size, this also | ||
allows us to gauge interest in this tool. | ||
**.eslintrc.js** | ||
```js | ||
require("@rushstack/eslint-patch/eslint-bulk-suppressions"); // 👈 add this line | ||
This approach inevitably results in forwards compatibility issues with versions of ESLint. The patch has | ||
some logic to determine which version of ESLint you're using and uses the corresponding patch file. | ||
module.exports = { | ||
rules: { | ||
rule1: 'error', | ||
rule2: 'warning' | ||
}, | ||
parserOptions: { tsconfigRootDir: __dirname } | ||
}; | ||
``` | ||
## How to use it | ||
Typical workflow: | ||
To use the tool, you need to add a `require()` call to the top of the **.eslintrc.js** file for each project | ||
that you want to use the tool with, for example: | ||
1. Checkout your `main` branch, which is in a clean state where ESLint reports no violations. | ||
2. Update your configuration to enable the latest lint rules; ESLint now reports thousands of legacy violations. | ||
3. Run `eslint-bulk suppress --all ./src` to update **.eslint-bulk-suppressions.json.** | ||
4. ESLint now no longer reports violations, so commit the results to Git and merge your pull request. | ||
5. Over time, engineers may improve some of the suppressed code, in which case the associated suppressions are no longer needed. | ||
6. Run `eslint-bulk prune` periodically to find and remove unnecessary suppressions from **.eslint-bulk-suppressions.json**, ensuring that new violations will now get caught in those scopes. | ||
**.eslintrc.js** | ||
```js | ||
require("@rushstack/eslint-patch/eslint-bulk-suppressions"); | ||
### "eslint-bulk suppress" command | ||
module.exports = { | ||
rules: { | ||
rule1: 'error', | ||
rule2: 'warning' | ||
}, | ||
parserOptions: { tsconfigRootDir: __dirname } | ||
}; | ||
```bash | ||
eslint-bulk suppress --rule NAME1 [--rule NAME2...] PATH1 [PATH2...] | ||
eslint-bulk suppress --all PATH1 [PATH2...] | ||
``` | ||
We also highly recommend globally installing the companion CLI tool to your local system with | ||
Use this command to automatically generate bulk suppressions for the specified lint rules and file paths. | ||
The path argument is a [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) with the same syntax | ||
as path arguments for the `eslint` command. | ||
### "eslint-bulk prune" command | ||
Use this command to automatically delete all unnecessary suppression entries in all | ||
**.eslint-bulk-suppressions.json** files under the current working directory. | ||
```bash | ||
npm i -g @rushstack/eslint-bulk | ||
eslint-bulk prune | ||
``` | ||
The **eslint-bulk** package is a set of command line tools to use with the ESLint bulk suppressions patch. | ||
eslint-bulk commands must be run in the same current working directory containing your package's pertaining | ||
.eslintrc.js or .eslintrc.cjs file. | ||
### Implementation notes | ||
## eslint-bulk suppress | ||
The `eslint-bulk` command is a thin wrapper whose behavior is actually provided by the patch itself. | ||
In this way, if your monorepo contains projects using different versions of this package, the same globally | ||
installed `eslint-bulk` command can be used under any project folder, and it will always invoke the correct | ||
version of the engine compatible with that project. Because the patch is loaded by ESLint, the `eslint-bulk` | ||
command must be invoked in a project folder that contains an **.eslintrc.js** configuration with correctly | ||
installed **package.json** dependencies. | ||
Use this command to automatically generate bulk suppressions for the given files and given rules. | ||
Supply the paths as the main argument. The paths argument is a glob pattern that follows the same | ||
rules as the "files" argument in the "eslint" command. | ||
Here's an example of the bulk suppressions file content: | ||
```bash | ||
eslint-bulk suppress --rule NAME1 [--rule NAME2...] PATH1 [PATH2...] | ||
eslint-bulk suppress --all PATH1 [PATH2...] | ||
**.eslint-bulk-suppressions.json** | ||
```js | ||
{ | ||
"suppressions": [ | ||
{ | ||
"rule": "no-var", | ||
"file": "./src/your-file.ts", | ||
"scopeId": ".ExampleClass.exampleMethod" | ||
} | ||
] | ||
} | ||
``` | ||
The `rule` field is the ESLint rule name. The `file` field is the source file path, relative to the **eslintrc.js** file. The `scopeId` is a special string built from the names of containing structures. (For implementation details, take a look at the [calculateScopeId()](https://github.com/microsoft/rushstack/blob/e95c51088341f01516ee5a7639d57c3f6dce8772/eslint/eslint-patch/src/eslint-bulk-suppressions/bulk-suppressions-patch.ts#L52) function.) The `scopeId` identifies a region of code where the rule should be suppressed, while being reasonably stable across edits of the source file. | ||
## eslint-bulk prune | ||
# modern-module-resolution feature | ||
Use this command to automatically delete all unused suppression entries in all .eslint-bulk-suppressions.json | ||
files under the current working directory. | ||
### What it does | ||
```bash | ||
eslint-bulk prune | ||
This patch is a workaround for a longstanding [ESLint feature request](https://github.com/eslint/eslint/issues/3458) | ||
that would allow a shareable ESLint config to bring along its own plugins, rather than imposing peer dependencies | ||
on every consumer of the config. In a monorepo scenario, this enables your lint setup to be consolidated in a | ||
single NPM package. Doing so greatly reduces the copy+pasting and version management for all the other projects | ||
that use your standard lint rule set, but don't want to be bothered with the details. | ||
> **NOTE:** ESLint 8.21.0 has now introduced a new `ESLINT_USE_FLAT_CONFIG` mode that may reduce the need | ||
> for this patch. | ||
### Why it's a patch | ||
We initially proposed this feature in a pull request for the official ESLint back in 2019, however the | ||
maintainers preferred to implement a more comprehensive overhaul of the ESLint config engine. It ultimately | ||
shipped with the experimental new `ESLINT_USE_FLAT_CONFIG` mode (still opt-in as of ESLint 8). | ||
While waiting for that, Rush Stack's `modern-module-resolution` patch provided a reliable interim solution. | ||
We will continue to maintain this patch as long as it is being widely used, but we encourage you to check out | ||
`ESLINT_USE_FLAT_CONFIG` and see if it meets your needs. | ||
### How to use it | ||
1. Add `@rushstack/eslint-patch` as a dependency of your project: | ||
```bash | ||
cd your-project | ||
npm install --save-dev @rushstack/eslint-patch | ||
``` | ||
2. Add a `require()` call to the to top of the **.eslintrc.js** file for each project that depends | ||
on your shareable ESLint config, for example: | ||
**.eslintrc.js** | ||
```ts | ||
require("@rushstack/eslint-patch/modern-module-resolution"); // 👈 add this line | ||
// Add your "extends" boilerplate here, for example: | ||
module.exports = { | ||
extends: ['@your-company/eslint-config'], | ||
parserOptions: { tsconfigRootDir: __dirname } | ||
}; | ||
``` | ||
With this change, the local project no longer needs any ESLint plugins in its **package.json** file. | ||
Instead, the hypothetical `@your-company/eslint-config` NPM package would declare the plugins as its | ||
own dependencies. | ||
This patch works by modifying the ESLint engine so that its module resolver will load relative to the folder of | ||
the referencing config file, rather than the project folder. The patch is compatible with ESLint 6, 7, and 8. | ||
It also works with any editor extensions that load ESLint as a library. | ||
For an even leaner setup, `@your-company/eslint-config` can provide the patches as its own dependency. | ||
See [@rushstack/eslint-config](https://github.com/microsoft/rushstack/blob/main/eslint/eslint-config/patch/modern-module-resolution.js) for a real world example. | ||
# custom-config-package-names feature | ||
### What it does | ||
Load the `custom-config-package-names` patch to remove ESLint's | ||
[naming requirement](https://eslint.org/docs/latest/extend/shareable-configs) | ||
that `eslint-config` must be part of the NPM package name for shareable configs. | ||
This is useful because Rush Stack's [rig package](https://heft.rushstack.io/pages/intro/rig_packages/) | ||
specification defines a way for many different tooling configurations and dependencies to be shared | ||
via a single NPM package, for example | ||
[`@rushstack/heft-web-rig`](https://www.npmjs.com/package/@rushstack/heft-web-rig). | ||
Rigs avoid a lot of copy+pasting of dependencies in a large scale monorepo. | ||
Rig packages always include the `-rig` suffix in their name. It doesn't make sense to enforce | ||
that `eslint-config` should also appear in the name of a package that includes shareable configs | ||
for many other tools besides ESLint. | ||
### How to use it | ||
Continuing the example above, to load this patch you would add a second line to your config file: | ||
**.eslintrc.js** | ||
```ts | ||
require("@rushstack/eslint-patch/modern-module-resolution"); | ||
require("@rushstack/eslint-patch/custom-config-package-names"); // 👈 add this line | ||
// Add your "extends" boilerplate here, for example: | ||
module.exports = { | ||
extends: [ | ||
'@your-company/build-rig/profile/default/includes/eslint/node' // Notice the package name does not start with "eslint-config-" | ||
], | ||
parserOptions: { tsconfigRootDir: __dirname } | ||
}; | ||
``` | ||
# Links | ||
@@ -160,2 +250,4 @@ | ||
- [`@rushstack/eslint-bulk`](https://www.npmjs.com/package/@rushstack/eslint-bulk) CLI package | ||
`@rushstack/eslint-patch` is part of the [Rush Stack](https://rushstack.io/) family of projects. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
209393
253