2 * @fileoverview Disallow parenthesising higher precedence subexpressions.
3 * @author Michael Ficarra
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const { isParenthesized
: isParenthesizedRaw
} = require("eslint-utils");
12 const astUtils
= require("./utils/ast-utils.js");
19 description
: "disallow unnecessary parentheses",
20 category
: "Possible Errors",
22 url
: "https://eslint.org/docs/rules/no-extra-parens"
48 conditionalAssign
: { type
: "boolean" },
49 nestedBinaryExpressions
: { type
: "boolean" },
50 returnAssign
: { type
: "boolean" },
51 ignoreJSX
: { enum: ["none", "all", "single-line", "multi-line"] },
52 enforceForArrowConditionals
: { type
: "boolean" },
53 enforceForSequenceExpressions
: { type
: "boolean" },
54 enforceForNewInMemberExpressions
: { type
: "boolean" }
56 additionalProperties
: false
66 unexpected
: "Unnecessary parentheses around expression."
71 const sourceCode
= context
.getSourceCode();
73 const tokensToIgnore
= new WeakSet();
74 const precedence
= astUtils
.getPrecedence
;
75 const ALL_NODES
= context
.options
[0] !== "functions";
76 const EXCEPT_COND_ASSIGN
= ALL_NODES
&& context
.options
[1] && context
.options
[1].conditionalAssign
=== false;
77 const NESTED_BINARY
= ALL_NODES
&& context
.options
[1] && context
.options
[1].nestedBinaryExpressions
=== false;
78 const EXCEPT_RETURN_ASSIGN
= ALL_NODES
&& context
.options
[1] && context
.options
[1].returnAssign
=== false;
79 const IGNORE_JSX
= ALL_NODES
&& context
.options
[1] && context
.options
[1].ignoreJSX
;
80 const IGNORE_ARROW_CONDITIONALS
= ALL_NODES
&& context
.options
[1] &&
81 context
.options
[1].enforceForArrowConditionals
=== false;
82 const IGNORE_SEQUENCE_EXPRESSIONS
= ALL_NODES
&& context
.options
[1] &&
83 context
.options
[1].enforceForSequenceExpressions
=== false;
84 const IGNORE_NEW_IN_MEMBER_EXPR
= ALL_NODES
&& context
.options
[1] &&
85 context
.options
[1].enforceForNewInMemberExpressions
=== false;
87 const PRECEDENCE_OF_ASSIGNMENT_EXPR
= precedence({ type
: "AssignmentExpression" });
88 const PRECEDENCE_OF_UPDATE_EXPR
= precedence({ type
: "UpdateExpression" });
93 * Determines if this rule should be enforced for a node given the current configuration.
94 * @param {ASTNode} node The node to be checked.
95 * @returns {boolean} True if the rule should be enforced for this node.
98 function ruleApplies(node
) {
99 if (node
.type
=== "JSXElement" || node
.type
=== "JSXFragment") {
100 const isSingleLine
= node
.loc
.start
.line
=== node
.loc
.end
.line
;
102 switch (IGNORE_JSX
) {
104 // Exclude this JSX element from linting
108 // Exclude this JSX element if it is multi-line element
112 // Exclude this JSX element if it is single-line element
114 return !isSingleLine
;
116 // Nothing special to be done for JSX elements
124 if (node
.type
=== "SequenceExpression" && IGNORE_SEQUENCE_EXPRESSIONS
) {
128 return ALL_NODES
|| node
.type
=== "FunctionExpression" || node
.type
=== "ArrowFunctionExpression";
132 * Determines if a node is surrounded by parentheses.
133 * @param {ASTNode} node The node to be checked.
134 * @returns {boolean} True if the node is parenthesised.
137 function isParenthesised(node
) {
138 return isParenthesizedRaw(1, node
, sourceCode
);
142 * Determines if a node is surrounded by parentheses twice.
143 * @param {ASTNode} node The node to be checked.
144 * @returns {boolean} True if the node is doubly parenthesised.
147 function isParenthesisedTwice(node
) {
148 return isParenthesizedRaw(2, node
, sourceCode
);
152 * Determines if a node is surrounded by (potentially) invalid parentheses.
153 * @param {ASTNode} node The node to be checked.
154 * @returns {boolean} True if the node is incorrectly parenthesised.
157 function hasExcessParens(node
) {
158 return ruleApplies(node
) && isParenthesised(node
);
162 * Determines if a node that is expected to be parenthesised is surrounded by
163 * (potentially) invalid extra parentheses.
164 * @param {ASTNode} node The node to be checked.
165 * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
168 function hasDoubleExcessParens(node
) {
169 return ruleApplies(node
) && isParenthesisedTwice(node
);
173 * Determines if a node that is expected to be parenthesised is surrounded by
174 * (potentially) invalid extra parentheses with considering precedence level of the node.
175 * If the preference level of the node is not higher or equal to precedence lower limit, it also checks
176 * whether the node is surrounded by parentheses twice or not.
177 * @param {ASTNode} node The node to be checked.
178 * @param {number} precedenceLowerLimit The lower limit of precedence.
179 * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
182 function hasExcessParensWithPrecedence(node
, precedenceLowerLimit
) {
183 if (ruleApplies(node
) && isParenthesised(node
)) {
185 precedence(node
) >= precedenceLowerLimit
||
186 isParenthesisedTwice(node
)
195 * Determines if a node test expression is allowed to have a parenthesised assignment
196 * @param {ASTNode} node The node to be checked.
197 * @returns {boolean} True if the assignment can be parenthesised.
200 function isCondAssignException(node
) {
201 return EXCEPT_COND_ASSIGN
&& node
.test
.type
=== "AssignmentExpression";
205 * Determines if a node is in a return statement
206 * @param {ASTNode} node The node to be checked.
207 * @returns {boolean} True if the node is in a return statement.
210 function isInReturnStatement(node
) {
211 for (let currentNode
= node
; currentNode
; currentNode
= currentNode
.parent
) {
213 currentNode
.type
=== "ReturnStatement" ||
214 (currentNode
.type
=== "ArrowFunctionExpression" && currentNode
.body
.type
!== "BlockStatement")
224 * Determines if a constructor function is newed-up with parens
225 * @param {ASTNode} newExpression The NewExpression node to be checked.
226 * @returns {boolean} True if the constructor is called with parens.
229 function isNewExpressionWithParens(newExpression
) {
230 const lastToken
= sourceCode
.getLastToken(newExpression
);
231 const penultimateToken
= sourceCode
.getTokenBefore(lastToken
);
233 return newExpression
.arguments
.length
> 0 ||
236 // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens
237 astUtils
.isOpeningParenToken(penultimateToken
) &&
238 astUtils
.isClosingParenToken(lastToken
) &&
239 newExpression
.callee
.range
[1] < newExpression
.range
[1]
244 * Determines if a node is or contains an assignment expression
245 * @param {ASTNode} node The node to be checked.
246 * @returns {boolean} True if the node is or contains an assignment expression.
249 function containsAssignment(node
) {
250 if (node
.type
=== "AssignmentExpression") {
253 if (node
.type
=== "ConditionalExpression" &&
254 (node
.consequent
.type
=== "AssignmentExpression" || node
.alternate
.type
=== "AssignmentExpression")) {
257 if ((node
.left
&& node
.left
.type
=== "AssignmentExpression") ||
258 (node
.right
&& node
.right
.type
=== "AssignmentExpression")) {
266 * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
267 * @param {ASTNode} node The node to be checked.
268 * @returns {boolean} True if the assignment can be parenthesised.
271 function isReturnAssignException(node
) {
272 if (!EXCEPT_RETURN_ASSIGN
|| !isInReturnStatement(node
)) {
276 if (node
.type
=== "ReturnStatement") {
277 return node
.argument
&& containsAssignment(node
.argument
);
279 if (node
.type
=== "ArrowFunctionExpression" && node
.body
.type
!== "BlockStatement") {
280 return containsAssignment(node
.body
);
282 return containsAssignment(node
);
287 * Determines if a node following a [no LineTerminator here] restriction is
288 * surrounded by (potentially) invalid extra parentheses.
289 * @param {Token} token The token preceding the [no LineTerminator here] restriction.
290 * @param {ASTNode} node The node to be checked.
291 * @returns {boolean} True if the node is incorrectly parenthesised.
294 function hasExcessParensNoLineTerminator(token
, node
) {
295 if (token
.loc
.end
.line
=== node
.loc
.start
.line
) {
296 return hasExcessParens(node
);
299 return hasDoubleExcessParens(node
);
303 * Determines whether a node should be preceded by an additional space when removing parens
304 * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
305 * @returns {boolean} `true` if a space should be inserted before the node
308 function requiresLeadingSpace(node
) {
309 const leftParenToken
= sourceCode
.getTokenBefore(node
);
310 const tokenBeforeLeftParen
= sourceCode
.getTokenBefore(leftParenToken
, { includeComments
: true });
311 const tokenAfterLeftParen
= sourceCode
.getTokenAfter(leftParenToken
, { includeComments
: true });
313 return tokenBeforeLeftParen
&&
314 tokenBeforeLeftParen
.range
[1] === leftParenToken
.range
[0] &&
315 leftParenToken
.range
[1] === tokenAfterLeftParen
.range
[0] &&
316 !astUtils
.canTokensBeAdjacent(tokenBeforeLeftParen
, tokenAfterLeftParen
);
320 * Determines whether a node should be followed by an additional space when removing parens
321 * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
322 * @returns {boolean} `true` if a space should be inserted after the node
325 function requiresTrailingSpace(node
) {
326 const nextTwoTokens
= sourceCode
.getTokensAfter(node
, { count
: 2 });
327 const rightParenToken
= nextTwoTokens
[0];
328 const tokenAfterRightParen
= nextTwoTokens
[1];
329 const tokenBeforeRightParen
= sourceCode
.getLastToken(node
);
331 return rightParenToken
&& tokenAfterRightParen
&&
332 !sourceCode
.isSpaceBetweenTokens(rightParenToken
, tokenAfterRightParen
) &&
333 !astUtils
.canTokensBeAdjacent(tokenBeforeRightParen
, tokenAfterRightParen
);
337 * Determines if a given expression node is an IIFE
338 * @param {ASTNode} node The node to check
339 * @returns {boolean} `true` if the given node is an IIFE
341 function isIIFE(node
) {
342 return node
.type
=== "CallExpression" && node
.callee
.type
=== "FunctionExpression";
346 * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment.
347 * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax,
348 * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary.
349 * @param {ASTNode} [node] The node to check
350 * @returns {boolean} `true` if the given node can be a valid assignment target
352 function canBeAssignmentTarget(node
) {
353 return node
&& (node
.type
=== "Identifier" || node
.type
=== "MemberExpression");
358 * @param {ASTNode} node node to evaluate
362 function report(node
) {
363 const leftParenToken
= sourceCode
.getTokenBefore(node
);
364 const rightParenToken
= sourceCode
.getTokenAfter(node
);
366 if (!isParenthesisedTwice(node
)) {
367 if (tokensToIgnore
.has(sourceCode
.getFirstToken(node
))) {
371 if (isIIFE(node
) && !isParenthesised(node
.callee
)) {
381 function finishReport() {
384 loc
: leftParenToken
.loc
,
385 messageId
: "unexpected",
387 const parenthesizedSource
= sourceCode
.text
.slice(leftParenToken
.range
[1], rightParenToken
.range
[0]);
389 return fixer
.replaceTextRange([
390 leftParenToken
.range
[0],
391 rightParenToken
.range
[1]
392 ], (requiresLeadingSpace(node
) ? " " : "") + parenthesizedSource
+ (requiresTrailingSpace(node
) ? " " : ""));
398 reportsBuffer
.reports
.push({ node
, finishReport
});
406 * Evaluate a argument of the node.
407 * @param {ASTNode} node node to evaluate
411 function checkArgumentWithPrecedence(node
) {
412 if (hasExcessParensWithPrecedence(node
.argument
, precedence(node
))) {
413 report(node
.argument
);
418 * Check if a member expression contains a call expression
419 * @param {ASTNode} node MemberExpression node to evaluate
420 * @returns {boolean} true if found, false if not
422 function doesMemberExpressionContainCallExpression(node
) {
423 let currentNode
= node
.object
;
424 let currentNodeType
= node
.object
.type
;
426 while (currentNodeType
=== "MemberExpression") {
427 currentNode
= currentNode
.object
;
428 currentNodeType
= currentNode
.type
;
431 return currentNodeType
=== "CallExpression";
435 * Evaluate a new call
436 * @param {ASTNode} node node to evaluate
440 function checkCallNew(node
) {
441 const callee
= node
.callee
;
443 if (hasExcessParensWithPrecedence(callee
, precedence(node
))) {
444 const hasNewParensException
= callee
.type
=== "NewExpression" && !isNewExpressionWithParens(callee
);
447 hasDoubleExcessParens(callee
) ||
448 !isIIFE(node
) && !hasNewParensException
&& !(
450 // Allow extra parens around a new expression if they are intervening parentheses.
451 node
.type
=== "NewExpression" &&
452 callee
.type
=== "MemberExpression" &&
453 doesMemberExpressionContainCallExpression(callee
)
460 .filter(arg
=> hasExcessParensWithPrecedence(arg
, PRECEDENCE_OF_ASSIGNMENT_EXPR
))
465 * Evaluate binary logicals
466 * @param {ASTNode} node node to evaluate
470 function checkBinaryLogical(node
) {
471 const prec
= precedence(node
);
472 const leftPrecedence
= precedence(node
.left
);
473 const rightPrecedence
= precedence(node
.right
);
474 const isExponentiation
= node
.operator
=== "**";
475 const shouldSkipLeft
= NESTED_BINARY
&& (node
.left
.type
=== "BinaryExpression" || node
.left
.type
=== "LogicalExpression");
476 const shouldSkipRight
= NESTED_BINARY
&& (node
.right
.type
=== "BinaryExpression" || node
.right
.type
=== "LogicalExpression");
478 if (!shouldSkipLeft
&& hasExcessParens(node
.left
)) {
480 !(node
.left
.type
=== "UnaryExpression" && isExponentiation
) &&
481 (leftPrecedence
> prec
|| (leftPrecedence
=== prec
&& !isExponentiation
)) ||
482 isParenthesisedTwice(node
.left
)
488 if (!shouldSkipRight
&& hasExcessParens(node
.right
)) {
490 (rightPrecedence
> prec
|| (rightPrecedence
=== prec
&& isExponentiation
)) ||
491 isParenthesisedTwice(node
.right
)
499 * Check the parentheses around the super class of the given class definition.
500 * @param {ASTNode} node The node of class declarations to check.
503 function checkClass(node
) {
504 if (!node
.superClass
) {
509 * If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
510 * Otherwise, parentheses are needed.
512 const hasExtraParens
= precedence(node
.superClass
) > PRECEDENCE_OF_UPDATE_EXPR
513 ? hasExcessParens(node
.superClass
)
514 : hasDoubleExcessParens(node
.superClass
);
516 if (hasExtraParens
) {
517 report(node
.superClass
);
522 * Check the parentheses around the argument of the given spread operator.
523 * @param {ASTNode} node The node of spread elements/properties to check.
526 function checkSpreadOperator(node
) {
527 if (hasExcessParensWithPrecedence(node
.argument
, PRECEDENCE_OF_ASSIGNMENT_EXPR
)) {
528 report(node
.argument
);
533 * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
534 * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
537 function checkExpressionOrExportStatement(node
) {
538 const firstToken
= isParenthesised(node
) ? sourceCode
.getTokenBefore(node
) : sourceCode
.getFirstToken(node
);
539 const secondToken
= sourceCode
.getTokenAfter(firstToken
, astUtils
.isNotOpeningParenToken
);
540 const thirdToken
= secondToken
? sourceCode
.getTokenAfter(secondToken
) : null;
541 const tokenAfterClosingParens
= secondToken
? sourceCode
.getTokenAfter(secondToken
, astUtils
.isNotClosingParenToken
) : null;
544 astUtils
.isOpeningParenToken(firstToken
) &&
546 astUtils
.isOpeningBraceToken(secondToken
) ||
547 secondToken
.type
=== "Keyword" && (
548 secondToken
.value
=== "function" ||
549 secondToken
.value
=== "class" ||
550 secondToken
.value
=== "let" &&
551 tokenAfterClosingParens
&&
553 astUtils
.isOpeningBracketToken(tokenAfterClosingParens
) ||
554 tokenAfterClosingParens
.type
=== "Identifier"
557 secondToken
&& secondToken
.type
=== "Identifier" && secondToken
.value
=== "async" && thirdToken
&& thirdToken
.type
=== "Keyword" && thirdToken
.value
=== "function"
560 tokensToIgnore
.add(secondToken
);
563 if (hasExcessParens(node
)) {
569 * Finds the path from the given node to the specified ancestor.
570 * @param {ASTNode} node First node in the path.
571 * @param {ASTNode} ancestor Last node in the path.
572 * @returns {ASTNode[]} Path, including both nodes.
573 * @throws {Error} If the given node does not have the specified ancestor.
575 function pathToAncestor(node
, ancestor
) {
577 let currentNode
= node
;
579 while (currentNode
!== ancestor
) {
581 currentNode
= currentNode
.parent
;
583 /* istanbul ignore if */
584 if (currentNode
=== null) {
585 throw new Error("Nodes are not in the ancestor-descendant relationship.");
588 path
.push(currentNode
);
595 * Finds the path from the given node to the specified descendant.
596 * @param {ASTNode} node First node in the path.
597 * @param {ASTNode} descendant Last node in the path.
598 * @returns {ASTNode[]} Path, including both nodes.
599 * @throws {Error} If the given node does not have the specified descendant.
601 function pathToDescendant(node
, descendant
) {
602 return pathToAncestor(descendant
, node
).reverse();
606 * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer
607 * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop.
608 * @param {ASTNode} node Ancestor of an 'in' expression.
609 * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself.
610 * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis.
612 function isSafelyEnclosingInExpression(node
, child
) {
614 case "ArrayExpression":
616 case "BlockStatement":
617 case "ObjectExpression":
618 case "ObjectPattern":
619 case "TemplateLiteral":
621 case "ArrowFunctionExpression":
622 case "FunctionExpression":
623 return node
.params
.includes(child
);
624 case "CallExpression":
625 case "NewExpression":
626 return node
.arguments
.includes(child
);
627 case "MemberExpression":
628 return node
.computed
&& node
.property
=== child
;
629 case "ConditionalExpression":
630 return node
.consequent
=== child
;
637 * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately.
638 * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings.
641 function startNewReportsBuffering() {
643 upper
: reportsBuffer
,
644 inExpressionNodes
: [],
650 * Ends the current reports buffering.
653 function endCurrentReportsBuffering() {
654 const { upper
, inExpressionNodes
, reports
} = reportsBuffer
;
657 upper
.inExpressionNodes
.push(...inExpressionNodes
);
658 upper
.reports
.push(...reports
);
661 // flush remaining reports
662 reports
.forEach(({ finishReport
}) => finishReport());
665 reportsBuffer
= upper
;
669 * Checks whether the given node is in the current reports buffer.
670 * @param {ASTNode} node Node to check.
671 * @returns {boolean} True if the node is in the current buffer, false otherwise.
673 function isInCurrentReportsBuffer(node
) {
674 return reportsBuffer
.reports
.some(r
=> r
.node
=== node
);
678 * Removes the given node from the current reports buffer.
679 * @param {ASTNode} node Node to remove.
682 function removeFromCurrentReportsBuffer(node
) {
683 reportsBuffer
.reports
= reportsBuffer
.reports
.filter(r
=> r
.node
!== node
);
687 ArrayExpression(node
) {
689 .filter(e
=> e
&& hasExcessParensWithPrecedence(e
, PRECEDENCE_OF_ASSIGNMENT_EXPR
))
695 .filter(e
=> canBeAssignmentTarget(e
) && hasExcessParens(e
))
699 ArrowFunctionExpression(node
) {
700 if (isReturnAssignException(node
)) {
704 if (node
.body
.type
=== "ConditionalExpression" &&
705 IGNORE_ARROW_CONDITIONALS
710 if (node
.body
.type
!== "BlockStatement") {
711 const firstBodyToken
= sourceCode
.getFirstToken(node
.body
, astUtils
.isNotOpeningParenToken
);
712 const tokenBeforeFirst
= sourceCode
.getTokenBefore(firstBodyToken
);
714 if (astUtils
.isOpeningParenToken(tokenBeforeFirst
) && astUtils
.isOpeningBraceToken(firstBodyToken
)) {
715 tokensToIgnore
.add(firstBodyToken
);
717 if (hasExcessParensWithPrecedence(node
.body
, PRECEDENCE_OF_ASSIGNMENT_EXPR
)) {
723 AssignmentExpression(node
) {
724 if (canBeAssignmentTarget(node
.left
) && hasExcessParens(node
.left
)) {
728 if (!isReturnAssignException(node
) && hasExcessParensWithPrecedence(node
.right
, precedence(node
))) {
733 BinaryExpression(node
) {
734 if (reportsBuffer
&& node
.operator
=== "in") {
735 reportsBuffer
.inExpressionNodes
.push(node
);
738 checkBinaryLogical(node
);
741 CallExpression
: checkCallNew
,
745 .filter(member
=> member
.type
=== "MethodDefinition" && member
.computed
&& member
.key
)
746 .filter(member
=> hasExcessParensWithPrecedence(member
.key
, PRECEDENCE_OF_ASSIGNMENT_EXPR
))
747 .forEach(member
=> report(member
.key
));
750 ConditionalExpression(node
) {
751 if (isReturnAssignException(node
)) {
755 !isCondAssignException(node
) &&
756 hasExcessParensWithPrecedence(node
.test
, precedence({ type
: "LogicalExpression", operator
: "||" }))
761 if (hasExcessParensWithPrecedence(node
.consequent
, PRECEDENCE_OF_ASSIGNMENT_EXPR
)) {
762 report(node
.consequent
);
765 if (hasExcessParensWithPrecedence(node
.alternate
, PRECEDENCE_OF_ASSIGNMENT_EXPR
)) {
766 report(node
.alternate
);
770 DoWhileStatement(node
) {
771 if (hasExcessParens(node
.test
) && !isCondAssignException(node
)) {
776 ExportDefaultDeclaration
: node
=> checkExpressionOrExportStatement(node
.declaration
),
777 ExpressionStatement
: node
=> checkExpressionOrExportStatement(node
.expression
),
779 "ForInStatement, ForOfStatement"(node
) {
780 if (node
.left
.type
!== "VariableDeclarator") {
781 const firstLeftToken
= sourceCode
.getFirstToken(node
.left
, astUtils
.isNotOpeningParenToken
);
784 firstLeftToken
.value
=== "let" && (
787 * If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
788 * Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
790 (firstLeftToken
.range
[1] === node
.left
.range
[1] || /*
791 * If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
792 * Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
794 astUtils
.isOpeningBracketToken(
795 sourceCode
.getTokenAfter(firstLeftToken
, astUtils
.isNotClosingParenToken
)
799 tokensToIgnore
.add(firstLeftToken
);
803 if (node
.type
=== "ForOfStatement") {
804 const hasExtraParens
= node
.right
.type
=== "SequenceExpression"
805 ? hasDoubleExcessParens(node
.right
)
806 : hasExcessParens(node
.right
);
808 if (hasExtraParens
) {
811 } else if (hasExcessParens(node
.right
)) {
815 if (hasExcessParens(node
.left
)) {
821 if (node
.test
&& hasExcessParens(node
.test
) && !isCondAssignException(node
)) {
825 if (node
.update
&& hasExcessParens(node
.update
)) {
830 startNewReportsBuffering();
832 if (hasExcessParens(node
.init
)) {
838 "ForStatement > *.init:exit"(node
) {
841 * Removing parentheses around `in` expressions might change semantics and cause errors.
843 * For example, this valid for loop:
844 * for (let a = (b in c); ;);
845 * after removing parentheses would be treated as an invalid for-in loop:
846 * for (let a = b in c; ;);
849 if (reportsBuffer
.reports
.length
) {
850 reportsBuffer
.inExpressionNodes
.forEach(inExpressionNode
=> {
851 const path
= pathToDescendant(node
, inExpressionNode
);
854 for (let i
= 0; i
< path
.length
; i
++) {
855 const pathNode
= path
[i
];
857 if (i
< path
.length
- 1) {
858 const nextPathNode
= path
[i
+ 1];
860 if (isSafelyEnclosingInExpression(pathNode
, nextPathNode
)) {
862 // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]').
867 if (isParenthesised(pathNode
)) {
868 if (isInCurrentReportsBuffer(pathNode
)) {
870 // This node was supposed to be reported, but parentheses might be necessary.
872 if (isParenthesisedTwice(pathNode
)) {
875 * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses.
876 * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses.
877 * The remaining pair is safely enclosing the 'in' expression.
882 // Exclude the outermost node only.
883 if (!nodeToExclude
) {
884 nodeToExclude
= pathNode
;
887 // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside.
891 // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'.
897 // Exclude the node from the list (i.e. treat parentheses as necessary)
898 removeFromCurrentReportsBuffer(nodeToExclude
);
902 endCurrentReportsBuffering();
906 if (hasExcessParens(node
.test
) && !isCondAssignException(node
)) {
911 ImportExpression(node
) {
912 const { source
} = node
;
914 if (source
.type
=== "SequenceExpression") {
915 if (hasDoubleExcessParens(source
)) {
918 } else if (hasExcessParens(source
)) {
923 LogicalExpression
: checkBinaryLogical
,
925 MemberExpression(node
) {
926 const nodeObjHasExcessParens
= hasExcessParens(node
.object
);
929 nodeObjHasExcessParens
&&
930 precedence(node
.object
) >= precedence(node
) &&
934 astUtils
.isDecimalInteger(node
.object
) ||
936 // RegExp literal is allowed to have parens (#1589)
937 (node
.object
.type
=== "Literal" && node
.object
.regex
)
944 if (nodeObjHasExcessParens
&&
945 node
.object
.type
=== "CallExpression" &&
946 node
.parent
.type
!== "NewExpression") {
950 if (nodeObjHasExcessParens
&&
951 !IGNORE_NEW_IN_MEMBER_EXPR
&&
952 node
.object
.type
=== "NewExpression" &&
953 isNewExpressionWithParens(node
.object
)) {
957 if (node
.computed
&& hasExcessParens(node
.property
)) {
958 report(node
.property
);
962 NewExpression
: checkCallNew
,
964 ObjectExpression(node
) {
966 .filter(property
=> property
.value
&& hasExcessParensWithPrecedence(property
.value
, PRECEDENCE_OF_ASSIGNMENT_EXPR
))
967 .forEach(property
=> report(property
.value
));
970 ObjectPattern(node
) {
972 .filter(property
=> {
973 const value
= property
.value
;
975 return canBeAssignmentTarget(value
) && hasExcessParens(value
);
976 }).forEach(property
=> report(property
.value
));
981 const { key
} = node
;
983 if (key
&& hasExcessParensWithPrecedence(key
, PRECEDENCE_OF_ASSIGNMENT_EXPR
)) {
990 const argument
= node
.argument
;
992 if (canBeAssignmentTarget(argument
) && hasExcessParens(argument
)) {
997 ReturnStatement(node
) {
998 const returnToken
= sourceCode
.getFirstToken(node
);
1000 if (isReturnAssignException(node
)) {
1004 if (node
.argument
&&
1005 hasExcessParensNoLineTerminator(returnToken
, node
.argument
) &&
1007 // RegExp literal is allowed to have parens (#1589)
1008 !(node
.argument
.type
=== "Literal" && node
.argument
.regex
)) {
1009 report(node
.argument
);
1013 SequenceExpression(node
) {
1014 const precedenceOfNode
= precedence(node
);
1017 .filter(e
=> hasExcessParensWithPrecedence(e
, precedenceOfNode
))
1022 if (node
.test
&& hasExcessParens(node
.test
)) {
1027 SwitchStatement(node
) {
1028 if (hasExcessParens(node
.discriminant
)) {
1029 report(node
.discriminant
);
1033 ThrowStatement(node
) {
1034 const throwToken
= sourceCode
.getFirstToken(node
);
1036 if (hasExcessParensNoLineTerminator(throwToken
, node
.argument
)) {
1037 report(node
.argument
);
1041 UnaryExpression
: checkArgumentWithPrecedence
,
1042 UpdateExpression
: checkArgumentWithPrecedence
,
1043 AwaitExpression
: checkArgumentWithPrecedence
,
1045 VariableDeclarator(node
) {
1047 node
.init
&& hasExcessParensWithPrecedence(node
.init
, PRECEDENCE_OF_ASSIGNMENT_EXPR
) &&
1049 // RegExp literal is allowed to have parens (#1589)
1050 !(node
.init
.type
=== "Literal" && node
.init
.regex
)
1056 WhileStatement(node
) {
1057 if (hasExcessParens(node
.test
) && !isCondAssignException(node
)) {
1062 WithStatement(node
) {
1063 if (hasExcessParens(node
.object
)) {
1064 report(node
.object
);
1068 YieldExpression(node
) {
1069 if (node
.argument
) {
1070 const yieldToken
= sourceCode
.getFirstToken(node
);
1072 if ((precedence(node
.argument
) >= precedence(node
) &&
1073 hasExcessParensNoLineTerminator(yieldToken
, node
.argument
)) ||
1074 hasDoubleExcessParens(node
.argument
)) {
1075 report(node
.argument
);
1080 ClassDeclaration
: checkClass
,
1081 ClassExpression
: checkClass
,
1083 SpreadElement
: checkSpreadOperator
,
1084 SpreadProperty
: checkSpreadOperator
,
1085 ExperimentalSpreadProperty
: checkSpreadOperator
,
1087 TemplateLiteral(node
) {
1089 .filter(e
=> e
&& hasExcessParens(e
))
1093 AssignmentPattern(node
) {
1094 const { left
, right
} = node
;
1096 if (canBeAssignmentTarget(left
) && hasExcessParens(left
)) {
1100 if (right
&& hasExcessParensWithPrecedence(right
, PRECEDENCE_OF_ASSIGNMENT_EXPR
)) {