-
-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Per rule autofix configuration #125
base: main
Are you sure you want to change the base?
feat: Per rule autofix configuration #125
Conversation
Hi @Samuel-Therrien-Beslogic!, thanks for the Pull Request The pull request title isn't properly formatted. We ask that you update the pull request title to match this format, as we use it to generate changelogs and automate releases.
To Fix: You can fix this problem by clicking 'Edit' next to the pull request title at the top of this page. Read more about contributing to ESLint here |
Hi @Samuel-Therrien-Beslogic!, thanks for the Pull Request The pull request title isn't properly formatted. We ask that you update the pull request title to match this format, as we use it to generate changelogs and automate releases.
To Fix: You can fix this problem by clicking 'Edit' next to the pull request title at the top of this page. Read more about contributing to ESLint here |
What about instead of disabling autofixes, it turns it into suggestions? |
That is what I would intend. I don't think suggestions need to be disabled as they're not "automated". I'll try to update my RFC to make this clearer. One of the open question is whether suggestions could also be disabled (my proposal says no, but I've left the door open). The only case I could see it being valid is if a plugin has really bad suggestions and the maintainers are not updating it. |
I think this RFC, where it only allows "demoting" autofixes to suggestions on a per-rule basis, is excellent and more than sufficient. |
…ed" to suggestions.
@Samuel-Therrien-Beslogic please be sure to sign the CLA. |
I sent the Corporate Contributor CLA to my employer (I know this is just a doc, and not code, but they preferred it given it was done under work hours). The person in charge told me he received the email. So hopefully that should be done soon :) |
To that end, if we're only using the autofixes portion of the config to turn off autofixes for specific rules, I'd propose having the config just be an array of rule names to turn autofix off, rather than it being a
|
I guess you might need more than just an array in order to re-enable autofixes in later configs. You have it described in the RFC with export default [
{
autofixes: {
// We don't want this to autofix, as a rule suddenly not failing should require human attention
'@eslint-community/eslint-comments/no-unused-disable': false,
},
rules: {
'@eslint-community/eslint-comments/no-unused-disable': 'error',
},
},
{
files: ['*.spec.js'],
autofixes: {
// Let's pretend we want this to be autofixed in tests, for the sake of the RFC
'@eslint-community/eslint-comments/no-unused-disable': true,
},
}
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for getting this started. I left a note regarding providing implementation details, which is required for an RFC.
Explain the design with enough detail that someone familiar with ESLint | ||
can implement it by reading this document. Please get into specifics | ||
of your approach, corner cases, and examples of how the change will be | ||
used. Be sure to define any new terms in this section. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're missing details on how this will be implemented. Please take a look at the code and see how you expect this feature to be implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I really need to learn about how ESLint is implemented I can, but idk when I'll get to it. I'd be more than happy if an existing contributor is willing to fill in this section for me as I am not personally concerned with implementation details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the code a bit, I would say this is probably a good place to start exploring: https://github.com/eslint/eslint/blob/main/lib/eslint/eslint.js#L386-L394
It's what's being used to determine if a rule should have a fix applied to it, based on a few different conditions. Based on the response from that, a "fixer" is passed back to the linter to use as part of its fix attempt loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Samuel-Therrien-Beslogic exploring the implementation is part of the RFC process. We can't really evaluate any proposal without it.
Thinking more on the name, maybe something like export default [
{
disableAutofixes: {
// We don't want this to autofix, as a rule suddenly not failing should require human attention
'@eslint-community/eslint-comments/no-unused-disable': true,
},
rules: {
'@eslint-community/eslint-comments/no-unused-disable': 'error',
},
},
{
files: ['*.spec.js'],
disableAutofixes: {
// Let's pretend we want this to be autofixed in tests, for the sake of the RFC
'@eslint-community/eslint-comments/no-unused-disable': false,
},
}
]; |
Thanks. I am not familiar with flat-configs yet (too many plugins haven't migrated to v9, and I will need to rewrite our entire configs)
Agreed. This is also closer to the inspiration cited with Ruff.
Indeed a record seems cleaner to be able to re-enable. One could allow an array to I've added an entry about this in the FAQ |
used. Be sure to define any new terms in this section. | ||
--> | ||
|
||
Similar to how Ruff (<https://docs.astral.sh/ruff/settings/#lint_unfixable>) does it, a top-level key to specify which rules to not autofix would be in my opinion the least disruptive and forward/backwards compatible. It should be overridable in per-file configurations, and picked up when extending a configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative could be to expand rule config values to allow objects in which this would be a property. For example:
export default [
{
rules: {
"@eslint-community/eslint-comments/no-unused-disable": {
severity: "error",
options: [/* ... */],
disableAutofixes: true
}
}
}
];
The advantages of this approach are that all the configurations for the rule can be in one place, and it would be easier to add more "meta" options if needed in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We considered this a while back, even without trying to turn off autofixing. I'm not a fan of forcing rules to have to write out "severity" and "options", which is why we stuck with just an array.
This would also complicate rule configuration merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given a large number of config in the community are already in array format, I don't think we can drop it; instead, we can support both:
"@eslint-community/eslint-comments/no-unused-disable": ["error", {...}]
// is as the same as:
"@eslint-community/eslint-comments/no-unused-disable": {
severity: "error",
options: [/* ... */],
disableAutofixes: false, // the default
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think anyone was suggesting dropping the current format. I just think an object format adds additional complexity when merging rule configurations that isn't necessary. Plus, to modify a rule to disable autofix, you'd first need to convert the array into an object vs. adding a new key elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After thinking about this some more, I've come to believe that @mdjermanovic's approach and reasoning makes sense. While it will complicate merging of configs, it does give us the flexibility to add additional options per rule in the future if necessary (for example: eslint/eslint#19342). I do have a concern about the additional number of objects we'll be creating, but we can work through that as an implementation detail.
I'd like to discuss whether disableAutofix
is the right wording, as I think I'd prefer autofix: false
instead of disableAutofix: true
, but otherwise, I think this is the correct way to go.
Some hints for implementation details:
- Update
lib/config/flat-config-schema.js
to detect and properly merge all forms of rule configuration into objects - Update
lib/linter/linter.js
to read rule configurations as objects instead of arrays. Also need to convert config comment rules into object configs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
autofix: false
sounds good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to configure rules as suggested by @mdjermanovic in https://github.com/eslint/rfcs/pull/125/files#r1819151079, except for using autofix: false
instead of disableAutofixes: true
, i.e.:
export default [
{
rules: {
"@eslint-community/eslint-comments/no-unused-disable": {
severity: "error",
options: [/* ... */],
autofix: false
}
}
}
];
Since there is agreement on this part I think it's fine to update the RFC accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had some reticence on going this route as a use-case I see is wanting to disable autofix for a pre-configured rule, without having to reconfigure it.
But with flat-configs, it may not be too bad to import the rule you want from the config you're extending and spreading its configuration. I can see utilities like tseslint.config
providing support for that if there's demand.
Maybe it could become possible to configure a rule's single property (configure a rule without enabling it, change from error to warning without changing the configuration, etc.)
I do agree it opens the door to more flexibility. And I agree to use autofix
in this context to avoid the double-negative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can stipulate that when merging, we'll just merge the keys that are specified and keep everything else. So you could do this:
{
rules: {
"some-rule": { autofix: false }
}
}
And when the configuration was combined with other configurations for some-rule
, we'd use the existing severity and options. So, similar to what we do when people just put "error"
to change the severity and we end up using the existing options.
used. Be sure to define any new terms in this section. | ||
--> | ||
|
||
Similar to how Ruff (<https://docs.astral.sh/ruff/settings/#lint_unfixable>) does it, a top-level key to specify which rules to not autofix would be in my opinion the least disruptive and forward/backwards compatible. It should be overridable in per-file configurations, and picked up when extending a configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative could be to expand rule config values to allow objects in which this would be a property. For example:
export default [ { rules: { "@eslint-community/eslint-comments/no-unused-disable": { severity: "error", options: [/* ... */], disableAutofixes: true } } } ];The advantages of this approach are that all the configurations for the rule can be in one place, and it would be easier to add more "meta" options if needed in the future.
Another advantage is that autofixes could be turned off from the CLI (with --rule 'some-rule: { disableAutofixes: true, ...}'
) or inline with /* eslint */
config comments without introducing additional syntax.
disableAutofixes: { | ||
// We don't want this to autofix, as a rule suddenly not failing should require human attention | ||
"@eslint-community/eslint-comments/no-unused-disable": true, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just learned about https://eslint.org/docs/latest/use/configure/configuration-files#reporting-unused-disable-directives . Which I believe should be possible to disable the autofix just like any other rules as proposed by this RFC. However, it doesn't expose itself as a rule. How should this be handled?
One could:
- add a property
export default [{
linterOptions: {
reportUnusedDisableDirectives: "error",
autofixUnusedDisableDirectives: false
}
}];
- Change the config value to accept an object (just like regular rules)
export default [{
linterOptions: {
reportUnusedDisableDirectives: ["error", {autofix: false}]
}
}];
- Pretend
reportUnusedDisableDirectives
is a rule name and special-case it:
export default [{
disableAutofixes: {
"reportUnusedDisableDirectives": true
},
}];
- Accept that
reportUnusedDisableDirectives
's autofix should be configurable, but not handle it in this RFC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the approach will depend heavily on the approach taken for rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If rules would be configured as discussed in the thread under #125 (comment), then I think the following makes the most sense for reportUnusedDisableDirectives
:
export default [{
linterOptions: {
reportUnusedDisableDirectives: {
severity: "error",
autofix: false
}
}
}];
disableAutofixes: { | ||
// We don't want this to autofix, as a rule suddenly not failing should require human attention | ||
"@eslint-community/eslint-comments/no-unused-disable": true, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should disableAutofixes
go in linterOptions
? (as a note, if we do, I need to update the documentation location provided in this RFC)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. linterOptions
has no idea about rules, so I don't think it should know about filtering out rules from autofixing.
Summary
This feature aims to make it possible to control autofixes through shareable configuration on a per-rule basis.
Related Issues
eslint/eslint#18696