• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Disallow parenthesising higher precedence subexpressions.
3 * @author Michael Ficarra
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11const { isParenthesized: isParenthesizedRaw } = require("eslint-utils");
12const astUtils = require("./utils/ast-utils.js");
13
14module.exports = {
15    meta: {
16        type: "layout",
17
18        docs: {
19            description: "disallow unnecessary parentheses",
20            category: "Possible Errors",
21            recommended: false,
22            url: "https://eslint.org/docs/rules/no-extra-parens"
23        },
24
25        fixable: "code",
26
27        schema: {
28            anyOf: [
29                {
30                    type: "array",
31                    items: [
32                        {
33                            enum: ["functions"]
34                        }
35                    ],
36                    minItems: 0,
37                    maxItems: 1
38                },
39                {
40                    type: "array",
41                    items: [
42                        {
43                            enum: ["all"]
44                        },
45                        {
46                            type: "object",
47                            properties: {
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" },
55                                enforceForFunctionPrototypeMethods: { type: "boolean" }
56                            },
57                            additionalProperties: false
58                        }
59                    ],
60                    minItems: 0,
61                    maxItems: 2
62                }
63            ]
64        },
65
66        messages: {
67            unexpected: "Unnecessary parentheses around expression."
68        }
69    },
70
71    create(context) {
72        const sourceCode = context.getSourceCode();
73
74        const tokensToIgnore = new WeakSet();
75        const precedence = astUtils.getPrecedence;
76        const ALL_NODES = context.options[0] !== "functions";
77        const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
78        const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
79        const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
80        const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
81        const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] &&
82            context.options[1].enforceForArrowConditionals === false;
83        const IGNORE_SEQUENCE_EXPRESSIONS = ALL_NODES && context.options[1] &&
84            context.options[1].enforceForSequenceExpressions === false;
85        const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] &&
86            context.options[1].enforceForNewInMemberExpressions === false;
87        const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
88            context.options[1].enforceForFunctionPrototypeMethods === false;
89
90        const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
91        const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
92
93        let reportsBuffer;
94
95        /**
96         * Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node.
97         * Example: function(){}.call()
98         * @param {ASTNode} node The node to be checked.
99         * @returns {boolean} True if the node is an immediate `call` or `apply` method call.
100         * @private
101         */
102        function isImmediateFunctionPrototypeMethodCall(node) {
103            const callNode = astUtils.skipChainExpression(node);
104
105            if (callNode.type !== "CallExpression") {
106                return false;
107            }
108            const callee = astUtils.skipChainExpression(callNode.callee);
109
110            return (
111                callee.type === "MemberExpression" &&
112                callee.object.type === "FunctionExpression" &&
113                ["call", "apply"].includes(astUtils.getStaticPropertyName(callee))
114            );
115        }
116
117        /**
118         * Determines if this rule should be enforced for a node given the current configuration.
119         * @param {ASTNode} node The node to be checked.
120         * @returns {boolean} True if the rule should be enforced for this node.
121         * @private
122         */
123        function ruleApplies(node) {
124            if (node.type === "JSXElement" || node.type === "JSXFragment") {
125                const isSingleLine = node.loc.start.line === node.loc.end.line;
126
127                switch (IGNORE_JSX) {
128
129                    // Exclude this JSX element from linting
130                    case "all":
131                        return false;
132
133                    // Exclude this JSX element if it is multi-line element
134                    case "multi-line":
135                        return isSingleLine;
136
137                    // Exclude this JSX element if it is single-line element
138                    case "single-line":
139                        return !isSingleLine;
140
141                    // Nothing special to be done for JSX elements
142                    case "none":
143                        break;
144
145                    // no default
146                }
147            }
148
149            if (node.type === "SequenceExpression" && IGNORE_SEQUENCE_EXPRESSIONS) {
150                return false;
151            }
152
153            if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) {
154                return false;
155            }
156
157            return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
158        }
159
160        /**
161         * Determines if a node is surrounded by parentheses.
162         * @param {ASTNode} node The node to be checked.
163         * @returns {boolean} True if the node is parenthesised.
164         * @private
165         */
166        function isParenthesised(node) {
167            return isParenthesizedRaw(1, node, sourceCode);
168        }
169
170        /**
171         * Determines if a node is surrounded by parentheses twice.
172         * @param {ASTNode} node The node to be checked.
173         * @returns {boolean} True if the node is doubly parenthesised.
174         * @private
175         */
176        function isParenthesisedTwice(node) {
177            return isParenthesizedRaw(2, node, sourceCode);
178        }
179
180        /**
181         * Determines if a node is surrounded by (potentially) invalid parentheses.
182         * @param {ASTNode} node The node to be checked.
183         * @returns {boolean} True if the node is incorrectly parenthesised.
184         * @private
185         */
186        function hasExcessParens(node) {
187            return ruleApplies(node) && isParenthesised(node);
188        }
189
190        /**
191         * Determines if a node that is expected to be parenthesised is surrounded by
192         * (potentially) invalid extra parentheses.
193         * @param {ASTNode} node The node to be checked.
194         * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
195         * @private
196         */
197        function hasDoubleExcessParens(node) {
198            return ruleApplies(node) && isParenthesisedTwice(node);
199        }
200
201        /**
202         * Determines if a node that is expected to be parenthesised is surrounded by
203         * (potentially) invalid extra parentheses with considering precedence level of the node.
204         * If the preference level of the node is not higher or equal to precedence lower limit, it also checks
205         * whether the node is surrounded by parentheses twice or not.
206         * @param {ASTNode} node The node to be checked.
207         * @param {number} precedenceLowerLimit The lower limit of precedence.
208         * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
209         * @private
210         */
211        function hasExcessParensWithPrecedence(node, precedenceLowerLimit) {
212            if (ruleApplies(node) && isParenthesised(node)) {
213                if (
214                    precedence(node) >= precedenceLowerLimit ||
215                    isParenthesisedTwice(node)
216                ) {
217                    return true;
218                }
219            }
220            return false;
221        }
222
223        /**
224         * Determines if a node test expression is allowed to have a parenthesised assignment
225         * @param {ASTNode} node The node to be checked.
226         * @returns {boolean} True if the assignment can be parenthesised.
227         * @private
228         */
229        function isCondAssignException(node) {
230            return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression";
231        }
232
233        /**
234         * Determines if a node is in a return statement
235         * @param {ASTNode} node The node to be checked.
236         * @returns {boolean} True if the node is in a return statement.
237         * @private
238         */
239        function isInReturnStatement(node) {
240            for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
241                if (
242                    currentNode.type === "ReturnStatement" ||
243                    (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement")
244                ) {
245                    return true;
246                }
247            }
248
249            return false;
250        }
251
252        /**
253         * Determines if a constructor function is newed-up with parens
254         * @param {ASTNode} newExpression The NewExpression node to be checked.
255         * @returns {boolean} True if the constructor is called with parens.
256         * @private
257         */
258        function isNewExpressionWithParens(newExpression) {
259            const lastToken = sourceCode.getLastToken(newExpression);
260            const penultimateToken = sourceCode.getTokenBefore(lastToken);
261
262            return newExpression.arguments.length > 0 ||
263                (
264
265                    // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens
266                    astUtils.isOpeningParenToken(penultimateToken) &&
267                    astUtils.isClosingParenToken(lastToken) &&
268                    newExpression.callee.range[1] < newExpression.range[1]
269                );
270        }
271
272        /**
273         * Determines if a node is or contains an assignment expression
274         * @param {ASTNode} node The node to be checked.
275         * @returns {boolean} True if the node is or contains an assignment expression.
276         * @private
277         */
278        function containsAssignment(node) {
279            if (node.type === "AssignmentExpression") {
280                return true;
281            }
282            if (node.type === "ConditionalExpression" &&
283                    (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
284                return true;
285            }
286            if ((node.left && node.left.type === "AssignmentExpression") ||
287                    (node.right && node.right.type === "AssignmentExpression")) {
288                return true;
289            }
290
291            return false;
292        }
293
294        /**
295         * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
296         * @param {ASTNode} node The node to be checked.
297         * @returns {boolean} True if the assignment can be parenthesised.
298         * @private
299         */
300        function isReturnAssignException(node) {
301            if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {
302                return false;
303            }
304
305            if (node.type === "ReturnStatement") {
306                return node.argument && containsAssignment(node.argument);
307            }
308            if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
309                return containsAssignment(node.body);
310            }
311            return containsAssignment(node);
312
313        }
314
315        /**
316         * Determines if a node following a [no LineTerminator here] restriction is
317         * surrounded by (potentially) invalid extra parentheses.
318         * @param {Token} token The token preceding the [no LineTerminator here] restriction.
319         * @param {ASTNode} node The node to be checked.
320         * @returns {boolean} True if the node is incorrectly parenthesised.
321         * @private
322         */
323        function hasExcessParensNoLineTerminator(token, node) {
324            if (token.loc.end.line === node.loc.start.line) {
325                return hasExcessParens(node);
326            }
327
328            return hasDoubleExcessParens(node);
329        }
330
331        /**
332         * Determines whether a node should be preceded by an additional space when removing parens
333         * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
334         * @returns {boolean} `true` if a space should be inserted before the node
335         * @private
336         */
337        function requiresLeadingSpace(node) {
338            const leftParenToken = sourceCode.getTokenBefore(node);
339            const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true });
340            const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true });
341
342            return tokenBeforeLeftParen &&
343                tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&
344                leftParenToken.range[1] === tokenAfterLeftParen.range[0] &&
345                !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen);
346        }
347
348        /**
349         * Determines whether a node should be followed by an additional space when removing parens
350         * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
351         * @returns {boolean} `true` if a space should be inserted after the node
352         * @private
353         */
354        function requiresTrailingSpace(node) {
355            const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
356            const rightParenToken = nextTwoTokens[0];
357            const tokenAfterRightParen = nextTwoTokens[1];
358            const tokenBeforeRightParen = sourceCode.getLastToken(node);
359
360            return rightParenToken && tokenAfterRightParen &&
361                !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&
362                !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
363        }
364
365        /**
366         * Determines if a given expression node is an IIFE
367         * @param {ASTNode} node The node to check
368         * @returns {boolean} `true` if the given node is an IIFE
369         */
370        function isIIFE(node) {
371            const maybeCallNode = astUtils.skipChainExpression(node);
372
373            return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression";
374        }
375
376        /**
377         * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment.
378         * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax,
379         * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary.
380         * @param {ASTNode} [node] The node to check
381         * @returns {boolean} `true` if the given node can be a valid assignment target
382         */
383        function canBeAssignmentTarget(node) {
384            return node && (node.type === "Identifier" || node.type === "MemberExpression");
385        }
386
387        /**
388         * Report the node
389         * @param {ASTNode} node node to evaluate
390         * @returns {void}
391         * @private
392         */
393        function report(node) {
394            const leftParenToken = sourceCode.getTokenBefore(node);
395            const rightParenToken = sourceCode.getTokenAfter(node);
396
397            if (!isParenthesisedTwice(node)) {
398                if (tokensToIgnore.has(sourceCode.getFirstToken(node))) {
399                    return;
400                }
401
402                if (isIIFE(node) && !isParenthesised(node.callee)) {
403                    return;
404                }
405            }
406
407            /**
408             * Finishes reporting
409             * @returns {void}
410             * @private
411             */
412            function finishReport() {
413                context.report({
414                    node,
415                    loc: leftParenToken.loc,
416                    messageId: "unexpected",
417                    fix(fixer) {
418                        const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
419
420                        return fixer.replaceTextRange([
421                            leftParenToken.range[0],
422                            rightParenToken.range[1]
423                        ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
424                    }
425                });
426            }
427
428            if (reportsBuffer) {
429                reportsBuffer.reports.push({ node, finishReport });
430                return;
431            }
432
433            finishReport();
434        }
435
436        /**
437         * Evaluate a argument of the node.
438         * @param {ASTNode} node node to evaluate
439         * @returns {void}
440         * @private
441         */
442        function checkArgumentWithPrecedence(node) {
443            if (hasExcessParensWithPrecedence(node.argument, precedence(node))) {
444                report(node.argument);
445            }
446        }
447
448        /**
449         * Check if a member expression contains a call expression
450         * @param {ASTNode} node MemberExpression node to evaluate
451         * @returns {boolean} true if found, false if not
452         */
453        function doesMemberExpressionContainCallExpression(node) {
454            let currentNode = node.object;
455            let currentNodeType = node.object.type;
456
457            while (currentNodeType === "MemberExpression") {
458                currentNode = currentNode.object;
459                currentNodeType = currentNode.type;
460            }
461
462            return currentNodeType === "CallExpression";
463        }
464
465        /**
466         * Evaluate a new call
467         * @param {ASTNode} node node to evaluate
468         * @returns {void}
469         * @private
470         */
471        function checkCallNew(node) {
472            const callee = node.callee;
473
474            if (hasExcessParensWithPrecedence(callee, precedence(node))) {
475                const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee);
476
477                if (
478                    hasDoubleExcessParens(callee) ||
479                    !isIIFE(node) &&
480                    !hasNewParensException &&
481                    !(
482
483                        // Allow extra parens around a new expression if they are intervening parentheses.
484                        node.type === "NewExpression" &&
485                        callee.type === "MemberExpression" &&
486                        doesMemberExpressionContainCallExpression(callee)
487                    ) &&
488                    !(!node.optional && callee.type === "ChainExpression")
489                ) {
490                    report(node.callee);
491                }
492            }
493            node.arguments
494                .filter(arg => hasExcessParensWithPrecedence(arg, PRECEDENCE_OF_ASSIGNMENT_EXPR))
495                .forEach(report);
496        }
497
498        /**
499         * Evaluate binary logicals
500         * @param {ASTNode} node node to evaluate
501         * @returns {void}
502         * @private
503         */
504        function checkBinaryLogical(node) {
505            const prec = precedence(node);
506            const leftPrecedence = precedence(node.left);
507            const rightPrecedence = precedence(node.right);
508            const isExponentiation = node.operator === "**";
509            const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
510            const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
511
512            if (!shouldSkipLeft && hasExcessParens(node.left)) {
513                if (
514                    !(node.left.type === "UnaryExpression" && isExponentiation) &&
515                    !astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) &&
516                    (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) ||
517                    isParenthesisedTwice(node.left)
518                ) {
519                    report(node.left);
520                }
521            }
522
523            if (!shouldSkipRight && hasExcessParens(node.right)) {
524                if (
525                    !astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) &&
526                    (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) ||
527                    isParenthesisedTwice(node.right)
528                ) {
529                    report(node.right);
530                }
531            }
532        }
533
534        /**
535         * Check the parentheses around the super class of the given class definition.
536         * @param {ASTNode} node The node of class declarations to check.
537         * @returns {void}
538         */
539        function checkClass(node) {
540            if (!node.superClass) {
541                return;
542            }
543
544            /*
545             * If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
546             * Otherwise, parentheses are needed.
547             */
548            const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
549                ? hasExcessParens(node.superClass)
550                : hasDoubleExcessParens(node.superClass);
551
552            if (hasExtraParens) {
553                report(node.superClass);
554            }
555        }
556
557        /**
558         * Check the parentheses around the argument of the given spread operator.
559         * @param {ASTNode} node The node of spread elements/properties to check.
560         * @returns {void}
561         */
562        function checkSpreadOperator(node) {
563            if (hasExcessParensWithPrecedence(node.argument, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
564                report(node.argument);
565            }
566        }
567
568        /**
569         * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
570         * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
571         * @returns {void}
572         */
573        function checkExpressionOrExportStatement(node) {
574            const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
575            const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
576            const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
577            const tokenAfterClosingParens = secondToken ? sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken) : null;
578
579            if (
580                astUtils.isOpeningParenToken(firstToken) &&
581                (
582                    astUtils.isOpeningBraceToken(secondToken) ||
583                    secondToken.type === "Keyword" && (
584                        secondToken.value === "function" ||
585                        secondToken.value === "class" ||
586                        secondToken.value === "let" &&
587                            tokenAfterClosingParens &&
588                            (
589                                astUtils.isOpeningBracketToken(tokenAfterClosingParens) ||
590                                tokenAfterClosingParens.type === "Identifier"
591                            )
592                    ) ||
593                    secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"
594                )
595            ) {
596                tokensToIgnore.add(secondToken);
597            }
598
599            const hasExtraParens = node.parent.type === "ExportDefaultDeclaration"
600                ? hasExcessParensWithPrecedence(node, PRECEDENCE_OF_ASSIGNMENT_EXPR)
601                : hasExcessParens(node);
602
603            if (hasExtraParens) {
604                report(node);
605            }
606        }
607
608        /**
609         * Finds the path from the given node to the specified ancestor.
610         * @param {ASTNode} node First node in the path.
611         * @param {ASTNode} ancestor Last node in the path.
612         * @returns {ASTNode[]} Path, including both nodes.
613         * @throws {Error} If the given node does not have the specified ancestor.
614         */
615        function pathToAncestor(node, ancestor) {
616            const path = [node];
617            let currentNode = node;
618
619            while (currentNode !== ancestor) {
620
621                currentNode = currentNode.parent;
622
623                /* istanbul ignore if */
624                if (currentNode === null) {
625                    throw new Error("Nodes are not in the ancestor-descendant relationship.");
626                }
627
628                path.push(currentNode);
629            }
630
631            return path;
632        }
633
634        /**
635         * Finds the path from the given node to the specified descendant.
636         * @param {ASTNode} node First node in the path.
637         * @param {ASTNode} descendant Last node in the path.
638         * @returns {ASTNode[]} Path, including both nodes.
639         * @throws {Error} If the given node does not have the specified descendant.
640         */
641        function pathToDescendant(node, descendant) {
642            return pathToAncestor(descendant, node).reverse();
643        }
644
645        /**
646         * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer
647         * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop.
648         * @param {ASTNode} node Ancestor of an 'in' expression.
649         * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself.
650         * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis.
651         */
652        function isSafelyEnclosingInExpression(node, child) {
653            switch (node.type) {
654                case "ArrayExpression":
655                case "ArrayPattern":
656                case "BlockStatement":
657                case "ObjectExpression":
658                case "ObjectPattern":
659                case "TemplateLiteral":
660                    return true;
661                case "ArrowFunctionExpression":
662                case "FunctionExpression":
663                    return node.params.includes(child);
664                case "CallExpression":
665                case "NewExpression":
666                    return node.arguments.includes(child);
667                case "MemberExpression":
668                    return node.computed && node.property === child;
669                case "ConditionalExpression":
670                    return node.consequent === child;
671                default:
672                    return false;
673            }
674        }
675
676        /**
677         * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately.
678         * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings.
679         * @returns {void}
680         */
681        function startNewReportsBuffering() {
682            reportsBuffer = {
683                upper: reportsBuffer,
684                inExpressionNodes: [],
685                reports: []
686            };
687        }
688
689        /**
690         * Ends the current reports buffering.
691         * @returns {void}
692         */
693        function endCurrentReportsBuffering() {
694            const { upper, inExpressionNodes, reports } = reportsBuffer;
695
696            if (upper) {
697                upper.inExpressionNodes.push(...inExpressionNodes);
698                upper.reports.push(...reports);
699            } else {
700
701                // flush remaining reports
702                reports.forEach(({ finishReport }) => finishReport());
703            }
704
705            reportsBuffer = upper;
706        }
707
708        /**
709         * Checks whether the given node is in the current reports buffer.
710         * @param {ASTNode} node Node to check.
711         * @returns {boolean} True if the node is in the current buffer, false otherwise.
712         */
713        function isInCurrentReportsBuffer(node) {
714            return reportsBuffer.reports.some(r => r.node === node);
715        }
716
717        /**
718         * Removes the given node from the current reports buffer.
719         * @param {ASTNode} node Node to remove.
720         * @returns {void}
721         */
722        function removeFromCurrentReportsBuffer(node) {
723            reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
724        }
725
726        return {
727            ArrayExpression(node) {
728                node.elements
729                    .filter(e => e && hasExcessParensWithPrecedence(e, PRECEDENCE_OF_ASSIGNMENT_EXPR))
730                    .forEach(report);
731            },
732
733            ArrayPattern(node) {
734                node.elements
735                    .filter(e => canBeAssignmentTarget(e) && hasExcessParens(e))
736                    .forEach(report);
737            },
738
739            ArrowFunctionExpression(node) {
740                if (isReturnAssignException(node)) {
741                    return;
742                }
743
744                if (node.body.type === "ConditionalExpression" &&
745                    IGNORE_ARROW_CONDITIONALS
746                ) {
747                    return;
748                }
749
750                if (node.body.type !== "BlockStatement") {
751                    const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
752                    const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
753
754                    if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
755                        tokensToIgnore.add(firstBodyToken);
756                    }
757                    if (hasExcessParensWithPrecedence(node.body, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
758                        report(node.body);
759                    }
760                }
761            },
762
763            AssignmentExpression(node) {
764                if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left)) {
765                    report(node.left);
766                }
767
768                if (!isReturnAssignException(node) && hasExcessParensWithPrecedence(node.right, precedence(node))) {
769                    report(node.right);
770                }
771            },
772
773            BinaryExpression(node) {
774                if (reportsBuffer && node.operator === "in") {
775                    reportsBuffer.inExpressionNodes.push(node);
776                }
777
778                checkBinaryLogical(node);
779            },
780
781            CallExpression: checkCallNew,
782
783            ClassBody(node) {
784                node.body
785                    .filter(member => member.type === "MethodDefinition" && member.computed && member.key)
786                    .filter(member => hasExcessParensWithPrecedence(member.key, PRECEDENCE_OF_ASSIGNMENT_EXPR))
787                    .forEach(member => report(member.key));
788            },
789
790            ConditionalExpression(node) {
791                if (isReturnAssignException(node)) {
792                    return;
793                }
794                if (
795                    !isCondAssignException(node) &&
796                    hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" }))
797                ) {
798                    report(node.test);
799                }
800
801                if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
802                    report(node.consequent);
803                }
804
805                if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
806                    report(node.alternate);
807                }
808            },
809
810            DoWhileStatement(node) {
811                if (hasExcessParens(node.test) && !isCondAssignException(node)) {
812                    report(node.test);
813                }
814            },
815
816            ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
817            ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
818
819            "ForInStatement, ForOfStatement"(node) {
820                if (node.left.type !== "VariableDeclarator") {
821                    const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
822
823                    if (
824                        firstLeftToken.value === "let" && (
825
826                            /*
827                             * If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
828                             * Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
829                             */
830                            (firstLeftToken.range[1] === node.left.range[1] || /*
831                             * If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
832                             * Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
833                             */
834                            astUtils.isOpeningBracketToken(
835                                sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
836                            ))
837                        )
838                    ) {
839                        tokensToIgnore.add(firstLeftToken);
840                    }
841                }
842
843                if (node.type === "ForOfStatement") {
844                    const hasExtraParens = node.right.type === "SequenceExpression"
845                        ? hasDoubleExcessParens(node.right)
846                        : hasExcessParens(node.right);
847
848                    if (hasExtraParens) {
849                        report(node.right);
850                    }
851                } else if (hasExcessParens(node.right)) {
852                    report(node.right);
853                }
854
855                if (hasExcessParens(node.left)) {
856                    report(node.left);
857                }
858            },
859
860            ForStatement(node) {
861                if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {
862                    report(node.test);
863                }
864
865                if (node.update && hasExcessParens(node.update)) {
866                    report(node.update);
867                }
868
869                if (node.init) {
870                    startNewReportsBuffering();
871
872                    if (hasExcessParens(node.init)) {
873                        report(node.init);
874                    }
875                }
876            },
877
878            "ForStatement > *.init:exit"(node) {
879
880                /*
881                 * Removing parentheses around `in` expressions might change semantics and cause errors.
882                 *
883                 * For example, this valid for loop:
884                 *      for (let a = (b in c); ;);
885                 * after removing parentheses would be treated as an invalid for-in loop:
886                 *      for (let a = b in c; ;);
887                 */
888
889                if (reportsBuffer.reports.length) {
890                    reportsBuffer.inExpressionNodes.forEach(inExpressionNode => {
891                        const path = pathToDescendant(node, inExpressionNode);
892                        let nodeToExclude;
893
894                        for (let i = 0; i < path.length; i++) {
895                            const pathNode = path[i];
896
897                            if (i < path.length - 1) {
898                                const nextPathNode = path[i + 1];
899
900                                if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) {
901
902                                    // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]').
903                                    return;
904                                }
905                            }
906
907                            if (isParenthesised(pathNode)) {
908                                if (isInCurrentReportsBuffer(pathNode)) {
909
910                                    // This node was supposed to be reported, but parentheses might be necessary.
911
912                                    if (isParenthesisedTwice(pathNode)) {
913
914                                        /*
915                                         * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses.
916                                         * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses.
917                                         * The remaining pair is safely enclosing the 'in' expression.
918                                         */
919                                        return;
920                                    }
921
922                                    // Exclude the outermost node only.
923                                    if (!nodeToExclude) {
924                                        nodeToExclude = pathNode;
925                                    }
926
927                                    // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside.
928
929                                } else {
930
931                                    // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'.
932                                    return;
933                                }
934                            }
935                        }
936
937                        // Exclude the node from the list (i.e. treat parentheses as necessary)
938                        removeFromCurrentReportsBuffer(nodeToExclude);
939                    });
940                }
941
942                endCurrentReportsBuffering();
943            },
944
945            IfStatement(node) {
946                if (hasExcessParens(node.test) && !isCondAssignException(node)) {
947                    report(node.test);
948                }
949            },
950
951            ImportExpression(node) {
952                const { source } = node;
953
954                if (source.type === "SequenceExpression") {
955                    if (hasDoubleExcessParens(source)) {
956                        report(source);
957                    }
958                } else if (hasExcessParens(source)) {
959                    report(source);
960                }
961            },
962
963            LogicalExpression: checkBinaryLogical,
964
965            MemberExpression(node) {
966                const nodeObjHasExcessParens = hasExcessParens(node.object) &&
967                    !(
968                        isImmediateFunctionPrototypeMethodCall(node.parent) &&
969                        node.parent.callee === node &&
970                        IGNORE_FUNCTION_PROTOTYPE_METHODS
971                    );
972
973                if (
974                    nodeObjHasExcessParens &&
975                    precedence(node.object) >= precedence(node) &&
976                    (
977                        node.computed ||
978                        !(
979                            astUtils.isDecimalInteger(node.object) ||
980
981                            // RegExp literal is allowed to have parens (#1589)
982                            (node.object.type === "Literal" && node.object.regex)
983                        )
984                    )
985                ) {
986                    report(node.object);
987                }
988
989                if (nodeObjHasExcessParens &&
990                  node.object.type === "CallExpression" &&
991                  node.parent.type !== "NewExpression") {
992                    report(node.object);
993                }
994
995                if (nodeObjHasExcessParens &&
996                  !IGNORE_NEW_IN_MEMBER_EXPR &&
997                  node.object.type === "NewExpression" &&
998                  isNewExpressionWithParens(node.object)) {
999                    report(node.object);
1000                }
1001
1002                if (nodeObjHasExcessParens &&
1003                    node.optional &&
1004                    node.object.type === "ChainExpression"
1005                ) {
1006                    report(node.object);
1007                }
1008
1009                if (node.computed && hasExcessParens(node.property)) {
1010                    report(node.property);
1011                }
1012            },
1013
1014            NewExpression: checkCallNew,
1015
1016            ObjectExpression(node) {
1017                node.properties
1018                    .filter(property => property.value && hasExcessParensWithPrecedence(property.value, PRECEDENCE_OF_ASSIGNMENT_EXPR))
1019                    .forEach(property => report(property.value));
1020            },
1021
1022            ObjectPattern(node) {
1023                node.properties
1024                    .filter(property => {
1025                        const value = property.value;
1026
1027                        return canBeAssignmentTarget(value) && hasExcessParens(value);
1028                    }).forEach(property => report(property.value));
1029            },
1030
1031            Property(node) {
1032                if (node.computed) {
1033                    const { key } = node;
1034
1035                    if (key && hasExcessParensWithPrecedence(key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
1036                        report(key);
1037                    }
1038                }
1039            },
1040
1041            RestElement(node) {
1042                const argument = node.argument;
1043
1044                if (canBeAssignmentTarget(argument) && hasExcessParens(argument)) {
1045                    report(argument);
1046                }
1047            },
1048
1049            ReturnStatement(node) {
1050                const returnToken = sourceCode.getFirstToken(node);
1051
1052                if (isReturnAssignException(node)) {
1053                    return;
1054                }
1055
1056                if (node.argument &&
1057                        hasExcessParensNoLineTerminator(returnToken, node.argument) &&
1058
1059                        // RegExp literal is allowed to have parens (#1589)
1060                        !(node.argument.type === "Literal" && node.argument.regex)) {
1061                    report(node.argument);
1062                }
1063            },
1064
1065            SequenceExpression(node) {
1066                const precedenceOfNode = precedence(node);
1067
1068                node.expressions
1069                    .filter(e => hasExcessParensWithPrecedence(e, precedenceOfNode))
1070                    .forEach(report);
1071            },
1072
1073            SwitchCase(node) {
1074                if (node.test && hasExcessParens(node.test)) {
1075                    report(node.test);
1076                }
1077            },
1078
1079            SwitchStatement(node) {
1080                if (hasExcessParens(node.discriminant)) {
1081                    report(node.discriminant);
1082                }
1083            },
1084
1085            ThrowStatement(node) {
1086                const throwToken = sourceCode.getFirstToken(node);
1087
1088                if (hasExcessParensNoLineTerminator(throwToken, node.argument)) {
1089                    report(node.argument);
1090                }
1091            },
1092
1093            UnaryExpression: checkArgumentWithPrecedence,
1094            UpdateExpression: checkArgumentWithPrecedence,
1095            AwaitExpression: checkArgumentWithPrecedence,
1096
1097            VariableDeclarator(node) {
1098                if (
1099                    node.init && hasExcessParensWithPrecedence(node.init, PRECEDENCE_OF_ASSIGNMENT_EXPR) &&
1100
1101                    // RegExp literal is allowed to have parens (#1589)
1102                    !(node.init.type === "Literal" && node.init.regex)
1103                ) {
1104                    report(node.init);
1105                }
1106            },
1107
1108            WhileStatement(node) {
1109                if (hasExcessParens(node.test) && !isCondAssignException(node)) {
1110                    report(node.test);
1111                }
1112            },
1113
1114            WithStatement(node) {
1115                if (hasExcessParens(node.object)) {
1116                    report(node.object);
1117                }
1118            },
1119
1120            YieldExpression(node) {
1121                if (node.argument) {
1122                    const yieldToken = sourceCode.getFirstToken(node);
1123
1124                    if ((precedence(node.argument) >= precedence(node) &&
1125                            hasExcessParensNoLineTerminator(yieldToken, node.argument)) ||
1126                            hasDoubleExcessParens(node.argument)) {
1127                        report(node.argument);
1128                    }
1129                }
1130            },
1131
1132            ClassDeclaration: checkClass,
1133            ClassExpression: checkClass,
1134
1135            SpreadElement: checkSpreadOperator,
1136            SpreadProperty: checkSpreadOperator,
1137            ExperimentalSpreadProperty: checkSpreadOperator,
1138
1139            TemplateLiteral(node) {
1140                node.expressions
1141                    .filter(e => e && hasExcessParens(e))
1142                    .forEach(report);
1143            },
1144
1145            AssignmentPattern(node) {
1146                const { left, right } = node;
1147
1148                if (canBeAssignmentTarget(left) && hasExcessParens(left)) {
1149                    report(left);
1150                }
1151
1152                if (right && hasExcessParensWithPrecedence(right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
1153                    report(right);
1154                }
1155            }
1156        };
1157
1158    }
1159};
1160