Description
I have a crazy idea. It's probably a stupid idea, but it's an idea none the less..
The idea
The idea is that we should consider exporting every single base eslint rule from the plugin.
Or alternately (as @j-f1 suggested, and I kind of like), create a new plugin (@typescript-eslint/eslint-plugin-eslint-compat
or similar).
Why would I suggest this?
People keep reporting issues with base eslint rules.
The chance of fixing them upstream is more often than not going to be pretty low because they (rightfully so) don't want typescript specific code within eslint - they only want their rules to handle the "true" ESTree representation.
This means that if we want to patch a rule, we have to create a rule extension within the plugin - either by copying the entire rule source and patching it, or by manipulating the AST before it reaches the base rule's code.
This works well, and we've got a number of rules which do this. The problem comes with adoption after that. Once we release a rule extension, we then need to promote to users that the new extension exists, so that they can correct their config to turn on our rule, and turn off the base rule.
This is a huge problem for users that use pre-defined config sets, because they often don't know that they have the "broken" rule turned on until it breaks for them, then they have to investigate our documentation to see if an extension exists, then investigate the config to find the existing setting value, and then make the switch.
Solutions
We can export every single base eslint rule without having to worry about copying code or fiddling with the rules by essentially doing this:
import eslintRules from 'eslint/lib/built-in-rules-index';
import ourRuleExtensions from './rules';
const exports = {
...Object.keys(eslintRules).reduce((acc, k) => {
acc[`@typescript-eslint/${k}`] = eslintRules[k];
return acc;
}, {}),
// override base rules with our extensions
...ourRuleExtensions,
}
The important bit is that last spread - we override the base rule implementations with the custom extended implementations.
Doing this would mean the following:
Positives:
- We can bugfix eslint rules with only a minor version bump in most cases.
- Releasing a new rule extension would be transparent to the end user - they are already using, say
@typescript-eslint/new-cap
, so if we patch it, they have to make zero config changes.
Negatives:
- Makes it much harder to configure and work with other eslint configs (read on...).
Helper
This would be clunky as heck for end users who use existing community configs.
They would have to do some manual coding or copy pasting to use an existing config with our plugin. This obviously is NOT AN OPTION.
So we would have to provide a config helper function to make this work seamlessly.
This has already been proposed and thought about by the community and maintainers (i.e. #301).
The idea would be that we would provide a config to a helper function, and it would translate the resolved config so it prefixes the base rules where appropriate:
// .eslintrc.js
const { configHelper } = require('@typescript-eslint/eslint-plugin');
module.exports = typescriptEslintConfigHelper({
extends: ["prettier"],
rules: {
semi: "error"
}
})
which translates the config
{
"rules": {
// rules from prettier
"@typescript-eslint/array-bracket-newline": "off",
"@typescript-eslint/array-bracket-spacing": "off",
"@typescript-eslint/array-element-newline": "off",
"@typescript-eslint/arrow-parens": "off",
// ... etc ....
// rules from config
"@typescript-eslint/semi": "error",
}
}
The great thing about providing a helper is that it now requires literally zero effort for a user to use this plugin with something like airbnb's config
We could also design the helper (or helperS?) so that we can make it easier for users to work with mixed (js/ts) codebases - by ensuring that typescript specific rules (such as explicit-function-return
don't get turned on for JS files).