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: no-extra-parens incorrect precedence (fixes #7978) #7999

Merged
merged 1 commit into from Feb 3, 2017

Conversation

alberto
Copy link
Member

@alberto alberto commented Jan 29, 2017

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

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

What changes did you make? (Give an overview)
Fix bug #7978

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

@mention-bot
Copy link

@alberto, thanks for your PR! By analyzing the history of the files in this pull request, we identified @vitorbal, @not-an-aardvark and @kaicataldo to be potential reviewers.

@eslintbot
Copy link

LGTM

function isNewExpressionWithParens(newExpression) {
const lastToken = sourceCode.getLastToken(newExpression);

return lastToken.value === ")";
Copy link
Member

Choose a reason for hiding this comment

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

This function will incorrectly return true for

new (Foo)

As a result, the rule still incorrectly reports the following code, even with this patch:

(new (Foo || Bar))()

I think the correct way to do this is to check the second-to-last token as well, like the implementation in new-parens.

@@ -120,6 +121,8 @@ ruleTester.run("no-extra-parens", rule, {
"a(b)(c)",
"a((b, c))",
"new new A",
{ code: "1 ** 2 ** 3", parserOptions: { ecmaVersion: 7 } },
{ code: "1 ** (2 ** 3)", parserOptions: { ecmaVersion: 7 } },
Copy link
Member

Choose a reason for hiding this comment

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

This should be invalid -- the parens are unnecessary in that case because ** is right-associative.

2 ** 3 ** 4

// is equivalent to

2 ** (3 ** 4)

So the existing rule also behaves incorrectly in this case:

(2 ** 3) ** 4 // this should be valid

@eslintbot
Copy link

LGTM

@alberto
Copy link
Member Author

alberto commented Jan 30, 2017

Good catch @not-an-aardvark, thanks!
Let me know if you think it's ok now.

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

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

Thanks -- I think this solution is fine, but I do have a suggestion for how it could be improved.

const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");

if (!shouldSkipLeft && hasExcessParens(node.left) && precedence(node.left) >= prec) {
if (!shouldSkipLeft && hasExcessParens(node.left) && (precedence(node.left) > prec || (precedence(node.left) === prec && node.operator !== "**"))) {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of adding a special case here for **, I think it would be better to update the precedence function in ast-utils to give ** a higher precedence. (The function already gives * and / a higher precedence than + and -.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I thought precendence was already set for exponentiation. I added the conditions to deal with the right associative nature of **.

@@ -395,13 +413,14 @@ module.exports = {
*/
function dryBinaryLogical(node) {
const prec = precedence(node);
const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) ||
node.left.type === "UnaryExpression" && node.operator === "**";
Copy link
Member Author

Choose a reason for hiding this comment

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

@not-an-aardvark or did you mean here?

Copy link
Member

Choose a reason for hiding this comment

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

I meant here.

Copy link
Member Author

@alberto alberto Jan 30, 2017

Choose a reason for hiding this comment

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

I mean I don't see how adding a precedence for ** (which I will anyways, I'm not arguing it should be there) would change the code I added to deal with associativity, (the examples you mentioned).

Copy link
Member

Choose a reason for hiding this comment

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

Before updating the precedence function, the rule incorrectly considered this code valid:

async function foo(){
  (await foo) ** bar;
}

edit: Actually, never mind that example -- removing the parens there is a syntax error.

@eslintbot
Copy link

LGTM

@eslintbot
Copy link

LGTM

@@ -673,6 +673,8 @@ module.exports = {
case "/":
case "%":
return 13;
case "**":
return 15;
Copy link
Member

Choose a reason for hiding this comment

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

It doesn't really matter, but is it intentional that you skipped 14 here?

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 followed this table.

@not-an-aardvark not-an-aardvark added accepted There is consensus among the team that this change meets the criteria for inclusion bug ESLint is working incorrectly rule Relates to ESLint's core rules labels Feb 1, 2017
@ilyavolodin ilyavolodin merged commit f2a3580 into master Feb 3, 2017
@alberto alberto deleted the issue7978 branch February 17, 2017 22:08
@eslint-deprecated eslint-deprecated bot locked and limited conversation to collaborators Feb 6, 2018
@eslint-deprecated eslint-deprecated bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Feb 6, 2018
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 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

5 participants