eslint-seatbelt
Enable ESLint rules and prevent new errors today with a 2-file PR, then gradually fix the existing errors over time.
eslint-seatbelt is designed to work like a seatbelt ratchet: it starts loose, but can only get tighter.
Why eslint-seatbelt?
eslint-seatbelt is an open-source re-implementation of a Notion internal tool written by the same author in 2022. It was originally implemented to manage a multi-year migration to React function components, and has since proven invaluable in maintaining our large, fast-moving codebase with hundreds of active contributors. Read more on the Notion blog.
eslint-seatbelt improves on publicly available bulk suppression tools in a few ways:
Most other tools store error information in hierarchical formats like JSON or YAML that make merge conflicts confusing and painful. eslint-seatbelt stores errors in a simple .tsv tab-separated values file which minimizes (but doesn't totally eliminate) merge pain.
Other tools require complicated workflows invasive wrapper scripts, some going so far as to monkey-patching in a replacement linter implementation. eslint-seatbelt is a regular ESLint plugin (using the processor API) so it integrates effortlessly with your editor, pre-commit hooks, and CI. It "tightens the seatbelt" by automatically reducing the allowed errors per file whenever you run eslint during development. In CI, state is frozen and checked for consistency with the current file error counts, so no one can forget to tighten the seatbelt.
Setup
- ESLint >=8:
npm add --save-dev eslint-seatbelt
- ESLint <=7:
npm add --save-dev eslint-plugin-eslint-seatbelt@npm:eslint-seatbelt (note this aliases the package as eslint-plugin-eslint-seatbelt which is required by ESLint <=7)
ESLint 8+ flat config
import seatbelt from 'eslint-seatbelt'
export default [
seatbelt.configs.enable,
]
export default [
{
plugins: { 'eslint-seatbelt': seatbelt },
rules: { 'eslint-seatbelt/configure': 'error' },
processor: seatbelt.processors.seatbelt,
}
]
Legacy eslintrc config
module.exports = {
plugins: ["eslint-seatbelt"],
extends: ["plugin:eslint-seatbelt/enable-legacy"]
}
module.exports = {
plugins: ["eslint-seatbelt"],
rules: { "eslint-seatbelt/configure": "error" },
processor: "eslint-seatbelt/seatbelt",
}
Workflow
Introducing a new rule
-
Add the rule to your ESLint config in "error" mode. (with eslint-seatbelt, configuring rules in "warning" mode is pointless)
@@ -18,6 +18,7 @@ export default tseslint.config(
seatbelt.configs.enable,
{
rules: {
+ "@typescript-eslint/ban-ts-comment": "error",
-
Run SEATBELT_INCREASE=<rule> eslint to increase allowed errors.
-
Commit the changes to your ESLint config (eslint.config.mjs) and seatbelt file (eslint.seatbelt.tsv)
After fixing errors
Verify seatbelt file is up-to-date
SEATBELT_FROZEN=1 eslint or CI=1 eslint
Introduce ESLint to an existing codebase
eslint-seatbelt makes it easy to introduce ESLint to an existing unlinted codebase.
- Install eslint, eslint-seatbelt, and your favorite ESLint plugins.
- Configure plugins, rules, and set up eslint-seatbelt as described above.
- Run
SEATBELT_INCREASE=ALL eslint --fix to either fix or allow existing errors.
- Commit changes.
Configuration
By default eslint-seatbelt stores error counts in a file named eslint.seatbelt.tsv in the current working directory. No configuration is required beyond setting up the plugin as described above.
If you prefer to customize this location or other options, you can pass configuration to eslint-seatbelt by one of the following methods:
-
Defined in the shared settings object in your ESLint config. This
requires also configuring the eslint-seatbelt/configure rule.
const config = [
{
settings: {
seatbelt: {
}
},
rules: {
"eslint-seatbelt/configure": "error",
}
}
]
-
Using the eslint-seatbelt/configure rule in your ESLint config.
This can be used to override settings for specific files in legacy ESLint configs.
Any configuration provided here will override the shared settings object.
module.exports = {
rules: {
"eslint-seatbelt/configure": "error",
},
overrides: [
{
files: ["some/path/*"],
rules: {
"eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]
},
},
],
}
-
The settings in config files can be overridden with environment variables when running eslint or other tools.
SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint
Config reference
Copied from ./src/SeatbeltConfig.ts
export interface SeatbeltConfig {
seatbeltFile?: string
keepRules?: RuleId[] | "all"
allowIncreaseRules?: RuleId[] | "all"
frozen?: boolean
disable?: boolean
threadsafe?: boolean
verbose?: boolean | "stdout" | "stderr" | ((...message: unknown[]) => void)
}
Contributing
This project uses pnpm for package management.
Improvement ideas
Thanks & Acknowledgements
- Dedicated to Nora Tarano, who inspired me to write the open-source version. I love you.
- Thank you to Ben Kraft for improvements to the Notion internal version, and for discussing changes for the open-source version.
- Thank you to Notion Labs, Inc for supporting engineering excellence. If you like this sort of thing, consider joining us!
Ratchet_Gear_and_Pawl.gif by Arglin Kampling - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=123530838