Change Request: Allow multiple configs of a rule - PR made
See original GitHub issueESLint version
v8.9.0
What problem do you want to solve?
Eslint currently does not (easily) allow for a rule to be applied more than once with different configurations.
Example rule config with suggested feature:
"settings": {
"alternative-rule-appendices": ["-rule-duplicate"]
},
"rules": {
"max-lines": [
"warn",
{
"max": 350,
"skipBlankLines": true,
"skipComments": false
}
],
"max-lines-rule-duplicate": [ // This rule gets interpreted as being named max-lines
"error",
{
"max": 450,
"skipBlankLines": true,
"skipComments": false
}
],
}
Others have sought this feature before, but have either had their request rejected, not found a solution, or not documented their solution (links in no particular order): https://stackoverflow.com/questions/34077756/including-both-errors-and-warnings-for-same-eslint-rule https://stackoverflow.com/questions/53331483/eslint-error-and-warning-for-the-same-rule-with-different-options https://github.com/eslint/eslint/issues/6776 https://github.com/eslint/eslint/issues/11089 https://github.com/eslint/eslint/discussions/14688
I personally wanted this feature for the reason in the example. Files above 350 lines should be considered for refactoring while files <450 should definitely be refactored as a rule - with exceptions needing explicit approval and justification.
Edit: Disregard the stricken sections and their code blocks - look at the bottom or the pull request instead.
What do you think is the correct solution?
Add the following to line 1188 in function getRule in lib/linter/linter.js
if (ruleId.slice(-4) === '-alt') {
let id = ruleId.slice(0, -4)
return (
(slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(id)) ||
slots.ruleMap.get(id)
);
}
the resulting function would look like:
function getRule(slots, ruleId) {
if (ruleId.slice(-4) === '-alt') {
let id = ruleId.slice(0, -4)
return (
(slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(id)) ||
slots.ruleMap.get(id)
);
}
return (
(slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) ||
slots.ruleMap.get(ruleId)
);
}
This change works in my setup, but I have not run further tests. I just wanted to share my solution as it seemed some others wanted one. I believe it has potential for being backwards-compatible and low maintenance, but I leave final say to the experts.
Participation
- I am willing to submit a pull request for this change. https://github.com/eslint/eslint/pull/15621
Additional comments
As I’m sure you can tell, this change can be modified to allow more rule copies - however I’m not sure of the performance impact, nor the amount of use it would get. Just change line 1188-1189 to:
if (ruleId.slice(-2) in ['-1', '-2', '-3']) {
let id = ruleId.slice(0, -2)
There might very well be much more elegant or performant solutions - I am very, very new to JS relative to most of you I’m sure.
Edits
Edit: To add thoughts from the exchange with @ljharb below: An ideal solution would probably be to add a new setting to the config. Default is an empty array, and in config you enter a list of strings to check for in rules as above. This allows the end user to adapt to eventual conflicts with plugin rule names.
str.slice(idx) in [arr] doesn’t work for that though, and I can’t think of an easy way to only check the end of the rule name string when the string it is checked against is of unknown/dynamic length. Like I wrote - there is probably an elegant solution, but I could use some help.
Edit 2: Where userDefinedArray is the array of strings specified in config file, and default is an empty array.
function getRule(slots, ruleId) {
for (let i = 0; i < userDefinedArray.length; i++) {
const userDefStr = userDefinedArray[i]
if (ruleID.endsWith(userDefStr)) {
const index = ruleID.lastIndexOf(userDefStr);
const id = ruleID.slice(0, index)
return (
(slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(id)) ||
slots.ruleMap.get(id)
);
}
}
return (
(slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) ||
slots.ruleMap.get(ruleId)
);
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:11 (5 by maintainers)
Thanks for all the work and discussion that was put into this. We aren’t making any changes to the current config system while we are working on a new one.
In the new system, it’s possible to create multiple references to the same rule as long as you provide a unique name. You can include multiple copies of the same plugin with different namespaces to allow you to specify plugin rules more than once: https://github.com/eslint/rfcs/tree/main/designs/2019-config-simplification#plugins-specifying-their-own-namespaces
All we would need to do is expose core rules in some way that allows you to treat them like another plugin and the problem is solved.
I do see its value, e.g. for the rules like no-restricted-xx:
however i don’t think it’s the right way to go. Just a thought: good to have a straightforward way(doesn’t have to be in eslint core) to help users wrap an existing rule to a new one. e.g.
so, you can use and config the new rule.