Skip to content
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

fix: Remove no-extra-parens autofix for potential directives #17022

Merged
merged 10 commits into from Jun 13, 2023

Conversation

fasttime
Copy link
Member

@fasttime fasttime commented Mar 26, 2023

Prerequisites checklist

What is the purpose of this pull request? (put an "X" next to an item)

[ ] Documentation update
[x] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

References #16988

What changes did you make? (Give an overview)

This PR fixes an issue with the no-extra-parens rule that could cause the autofix to change the behavior of the code. The solution consists in disabling the autofix in situations where it would otherwise introduce a new directive.
This solution has been discussed in issue #16988, and I started working on the no-extra-parens because I think that there is sufficient consensus on how to handle the autofix in the case of this specific rule.

The problem addressed here is that a statement like ("use strict"); can mutate into a use strict directive if the parentheses are removed, and that would change the semantics of the code. To determine whether the autofix should be disabled, we decided to check if a string literal is the sole child of an ExpressionStatement.
Since there is no danger that the no-extra-parens and quotes rule fix the same node concurrently (see #17022 (comment)), it is safe for no-extra-parens to remove parentheses from a template literal.

Is there anything you'd like reviewers to focus on?

@eslint-github-bot eslint-github-bot bot added the bug ESLint is working incorrectly label Mar 26, 2023
@netlify
Copy link

netlify bot commented Mar 26, 2023

Deploy Preview for docs-eslint canceled.

Name Link
🔨 Latest commit 9d94ba4
🔍 Latest deploy log https://app.netlify.com/sites/docs-eslint/deploys/64677f287913be00085b9eef

@fasttime fasttime marked this pull request as ready for review March 26, 2023 21:59
@fasttime fasttime requested a review from a team as a code owner March 26, 2023 21:59
@fasttime fasttime added accepted There is consensus among the team that this change meets the criteria for inclusion rule Relates to ESLint's core rules autofix This change is related to ESLint's autofixing capabilities labels Mar 26, 2023
Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM. One spot where I thought we could clarify things a bit.

Can also update the documentation to note this change?

lib/rules/no-extra-parens.js Outdated Show resolved Hide resolved
@fasttime
Copy link
Member Author

Can also update the documentation to note this change?

Thanks @nzakas, I've added a sentence to the Rule Details. I think there is no special way to format a note like this?

if (isParenthesisedTwice(node)) {
return true;
}
return !astUtils.isExpressionInOrJustAfterDirectivePrologue(sourceCode, node.parent);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we'd just check if the parent is ExpressionStatement as we don't know what fixes other rules are applying in the code that precedes it.

For example:

/* eslint no-extra-parens: "error" */
/* eslint prettier/prettier: "error" */

; //
("use strict");

after --fix, "use strict" still ends up being a directive:

/* eslint no-extra-parens: "error" */
/* eslint prettier/prettier: "error" */

//
"use strict";

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if compatibility with Prettier is a concern then we could just check the parent node as you suggest. I don't know how we could stop third-party rules from interfering if another autofix is applied to the ExpressionStatement node itself or to its children rather than to a sibling node (e.g. ("use " + "strict");("use strict");), but from what I can tell, that is not what Prettier does.

In this case we should also change the behavior of the quotes rule, because fox example

/* eslint quotes: "error" */
/* eslint prettier/prettier: "error" */

; //
`use strict`;

autofixes to

/* eslint quotes: "error" */
/* eslint prettier/prettier: "error" */

//
"use strict";

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if compatibility with Prettier is a concern then we could just check the parent node as you suggest.

Not specifically with Prettier, any custom or third-party rules.

I don't know how we could stop third-party rules from interfering if another autofix is applied to the ExpressionStatement node itself or to its children rather than to a sibling node (e.g. ("use " + "strict");("use strict");),

That shouldn't cause problems because the ranges will be overlapping so ESLint would apply only one of the fixes:

https://eslint.org/docs/latest/extend/custom-rules#conflicting-fixes

I think that non-directive ExpressionStatement > Literal is a very edge case, and it's a meaningless code that should be manually fixed anyway, so I'd vote for the safest solution which is also the simplest for us. @nzakas what do you think?

/* eslint quotes: "error" */
/* eslint prettier/prettier: "error" */

; //
`use strict`;

Yes, I don't think this should be autofixed either.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That shouldn't cause problems because the ranges will be overlapping so ESLint would apply only one of the fixes:

https://eslint.org/docs/latest/extend/custom-rules#conflicting-fixes

Ah, understood. I didn't realize that removing the parentheses is handled as one single fix by the linter. Thanks for the explanation @mdjermanovic.

@@ -21,6 +21,8 @@ This rule always ignores extra parentheses around the following:
* immediately-invoked function expressions (also known as IIFEs) such as `var x = (function () {})();` and `var x = (function () {}());` to avoid conflicts with the [wrap-iife](wrap-iife) rule
* arrow function arguments to avoid conflicts with the [arrow-parens](arrow-parens) rule

Problems reported by this rule can be fixed automatically, except when removing the parentheses would create a new directive.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more that we need to show some examples, as I don't know that most people will understand what "removing the parentheses would create a new directive" actually means.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback @nzakas! Would it be also fine for you if we disable the autofix for all string literals that are direct children of an ExpressionStatement, as suggested in this comment by @mdjermanovic? This would improve interoperability with third-party rules at the expenses of a few autofixes in some rare edge cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that's fine with me. We don't guarantee that all autofixes will be applied, so it's fine to pick and choose.

@github-actions
Copy link

Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update.

@github-actions github-actions bot added the Stale label Apr 23, 2023
@Rec0iL99 Rec0iL99 removed the Stale label Apr 25, 2023
@github-actions
Copy link

github-actions bot commented May 5, 2023

Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update.

@github-actions github-actions bot added the Stale label May 5, 2023
@Rec0iL99
Copy link
Member

Rec0iL99 commented May 6, 2023

Don't close. Not stale.

Would it be also fine for you if we disable the autofix for all string literals that are direct children of an ExpressionStatement, as suggested #17022 (comment) by @mdjermanovic?

@nzakas there's a reply from @fasttime for you to look into here. Thanks.

@Rec0iL99 Rec0iL99 removed the Stale label May 6, 2023
@fasttime fasttime self-assigned this May 6, 2023
@nzakas
Copy link
Member

nzakas commented May 9, 2023

Thanks, replied!

lib/rules/quotes.js Outdated Show resolved Hide resolved
if (isParenthesisedTwice(node)) {
return true;
}
return node.parent.type !== "ExpressionStatement";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we also check if the ExpressionStatement is at the top level of a program or function body?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, if it isn't at the top level of a program or function body then the fix seems safe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, if it isn't at the top level of a program or function body then the fix seems safe.

Thanks! This is done in 7a2800d. I also took the time to refactor similar code in other rules that do the same kind of checks.

@fasttime
Copy link
Member Author

Thanks for the suggestions @mdjermanovic @nzakas. I've updated the autofix logic as per discussion above and I've added an example to the rule docs that shows the effect of the disabled autofix. I've also reverted my changes in ast-utils and moved isExpressionInOrJustAfterDirectivePrologue() back into quotes.js, but still keeping the new name and the fix that checks for parentheses. Let me know if I overlooked something.

@fasttime
Copy link
Member Author

Thanks for the suggestions @mdjermanovic, it's all updated in aa16e4c now.

@@ -212,7 +213,7 @@ module.exports = {

// Directive Prologues.
case "ExpressionStatement":
return isPartOfDirectivePrologue(node);
return !astUtils.isParenthesised(sourceCode, node) && isExpressionInOrJustAfterDirectivePrologue(node);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checking for parentheses can produce more lint errors in existing code:

/* eslint quotes: ["error", "backtick"] */

("use strict") // error after this change

So we should tag this as feat: per our semver polices. However, since the PR is about no-extra-parens, and this change isn't just refactoring, it might be better to extract all changes to the quotes rule into a separate PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, since the PR is about no-extra-parens, and this change isn't just refactoring, it might be better to extract all changes to the quotes rule into a separate PR?

Agreed. These fixes in the quotes rule were not expected from my side, they came more as a side effect of refactoring. I'm going to revert these changes for now and I will submit a new PR to update quotes once the changes in ast-utils.js are merged.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9d94ba4.

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! Leaving it open for @nzakas to verify.

@mdjermanovic
Copy link
Member

@fasttime this should also be rebased because it includes a change in the docs.

@fasttime fasttime force-pushed the issue16988-no-extra-parens branch from 9d94ba4 to 74004b9 Compare June 8, 2023 14:36
@linux-foundation-easycla
Copy link

CLA Missing ID CLA Not Signed

@fasttime
Copy link
Member Author

fasttime commented Jun 8, 2023

@fasttime this should also be rebased because it includes a change in the docs.

Rebased on top of main. I have no idea why the EasyCLA check is failing.

@snitin315
Copy link
Contributor

/easycla

@linux-foundation-easycla
Copy link

CLA Missing ID CLA Not Signed

@snitin315
Copy link
Contributor

I think the bug in CLA is when a commit is co-authored. Maybe try amending the commit to remove the co-author?

Copy link
Contributor

@snitin315 snitin315 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@fasttime
Copy link
Member Author

I think the bug in CLA is when a commit is co-authored. Maybe try amending the commit to remove the co-author?

Thanks @snitin315, I hadn't noticed that there is a problem with the EasyCLA bot. Now that you mention it, since this is not the only affected PR, maybe we should just wait until the issue is fixed?

@mdjermanovic
Copy link
Member

/easycla

Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@nzakas nzakas merged commit 54383e6 into main Jun 13, 2023
17 checks passed
@nzakas nzakas deleted the issue16988-no-extra-parens branch June 13, 2023 15:33
@eslint-github-bot eslint-github-bot bot locked and limited conversation to collaborators Dec 11, 2023
@eslint-github-bot eslint-github-bot bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Dec 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion autofix This change is related to ESLint's autofixing capabilities bug ESLint is working incorrectly rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants