| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751 | /** * @fileoverview Disallow parenthesising higher precedence subexpressions. * @author Michael Ficarra */"use strict";//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------const astUtils = require("../ast-utils.js");module.exports = {    meta: {        docs: {            description: "disallow unnecessary parentheses",            category: "Possible Errors",            recommended: false,            url: "https://eslint.org/docs/rules/no-extra-parens"        },        fixable: "code",        schema: {            anyOf: [                {                    type: "array",                    items: [                        {                            enum: ["functions"]                        }                    ],                    minItems: 0,                    maxItems: 1                },                {                    type: "array",                    items: [                        {                            enum: ["all"]                        },                        {                            type: "object",                            properties: {                                conditionalAssign: { type: "boolean" },                                nestedBinaryExpressions: { type: "boolean" },                                returnAssign: { type: "boolean" },                                ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },                                enforceForArrowConditionals: { type: "boolean" }                            },                            additionalProperties: false                        }                    ],                    minItems: 0,                    maxItems: 2                }            ]        },        messages: {            unexpected: "Gratuitous parentheses around expression."        }    },    create(context) {        const sourceCode = context.getSourceCode();        const tokensToIgnore = new WeakSet();        const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);        const precedence = astUtils.getPrecedence;        const ALL_NODES = context.options[0] !== "functions";        const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;        const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;        const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;        const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;        const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] &&            context.options[1].enforceForArrowConditionals === false;        const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });        const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });        /**         * Determines if this rule should be enforced for a node given the current configuration.         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the rule should be enforced for this node.         * @private         */        function ruleApplies(node) {            if (node.type === "JSXElement") {                const isSingleLine = node.loc.start.line === node.loc.end.line;                switch (IGNORE_JSX) {                    // Exclude this JSX element from linting                    case "all":                        return false;                    // Exclude this JSX element if it is multi-line element                    case "multi-line":                        return isSingleLine;                    // Exclude this JSX element if it is single-line element                    case "single-line":                        return !isSingleLine;                    // Nothing special to be done for JSX elements                    case "none":                        break;                    // no default                }            }            return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";        }        /**         * Determines if a node is surrounded by parentheses twice.         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the node is doubly parenthesised.         * @private         */        function isParenthesisedTwice(node) {            const previousToken = sourceCode.getTokenBefore(node, 1),                nextToken = sourceCode.getTokenAfter(node, 1);            return isParenthesised(node) && previousToken && nextToken &&                astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&                astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];        }        /**         * Determines if a node is surrounded by (potentially) invalid parentheses.         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the node is incorrectly parenthesised.         * @private         */        function hasExcessParens(node) {            return ruleApplies(node) && isParenthesised(node);        }        /**         * Determines if a node that is expected to be parenthesised is surrounded by         * (potentially) invalid extra parentheses.         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.         * @private         */        function hasDoubleExcessParens(node) {            return ruleApplies(node) && isParenthesisedTwice(node);        }        /**         * Determines if a node test expression is allowed to have a parenthesised assignment         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the assignment can be parenthesised.         * @private         */        function isCondAssignException(node) {            return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression";        }        /**         * Determines if a node is in a return statement         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the node is in a return statement.         * @private         */        function isInReturnStatement(node) {            for (let currentNode = node; currentNode; currentNode = currentNode.parent) {                if (                    currentNode.type === "ReturnStatement" ||                    (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement")                ) {                    return true;                }            }            return false;        }        /**         * Determines if a constructor function is newed-up with parens         * @param {ASTNode} newExpression - The NewExpression node to be checked.         * @returns {boolean} True if the constructor is called with parens.         * @private         */        function isNewExpressionWithParens(newExpression) {            const lastToken = sourceCode.getLastToken(newExpression);            const penultimateToken = sourceCode.getTokenBefore(lastToken);            return newExpression.arguments.length > 0 || astUtils.isOpeningParenToken(penultimateToken) && astUtils.isClosingParenToken(lastToken);        }        /**         * Determines if a node is or contains an assignment expression         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the node is or contains an assignment expression.         * @private         */        function containsAssignment(node) {            if (node.type === "AssignmentExpression") {                return true;            }            if (node.type === "ConditionalExpression" &&                    (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {                return true;            }            if ((node.left && node.left.type === "AssignmentExpression") ||                    (node.right && node.right.type === "AssignmentExpression")) {                return true;            }            return false;        }        /**         * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the assignment can be parenthesised.         * @private         */        function isReturnAssignException(node) {            if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {                return false;            }            if (node.type === "ReturnStatement") {                return node.argument && containsAssignment(node.argument);            }            if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {                return containsAssignment(node.body);            }            return containsAssignment(node);        }        /**         * Determines if a node following a [no LineTerminator here] restriction is         * surrounded by (potentially) invalid extra parentheses.         * @param {Token} token - The token preceding the [no LineTerminator here] restriction.         * @param {ASTNode} node - The node to be checked.         * @returns {boolean} True if the node is incorrectly parenthesised.         * @private         */        function hasExcessParensNoLineTerminator(token, node) {            if (token.loc.end.line === node.loc.start.line) {                return hasExcessParens(node);            }            return hasDoubleExcessParens(node);        }        /**         * Determines whether a node should be preceded by an additional space when removing parens         * @param {ASTNode} node node to evaluate; must be surrounded by parentheses         * @returns {boolean} `true` if a space should be inserted before the node         * @private         */        function requiresLeadingSpace(node) {            const leftParenToken = sourceCode.getTokenBefore(node);            const tokenBeforeLeftParen = sourceCode.getTokenBefore(node, 1);            const firstToken = sourceCode.getFirstToken(node);            return tokenBeforeLeftParen &&                tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&                leftParenToken.range[1] === firstToken.range[0] &&                !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, firstToken);        }        /**         * Determines whether a node should be followed by an additional space when removing parens         * @param {ASTNode} node node to evaluate; must be surrounded by parentheses         * @returns {boolean} `true` if a space should be inserted after the node         * @private         */        function requiresTrailingSpace(node) {            const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });            const rightParenToken = nextTwoTokens[0];            const tokenAfterRightParen = nextTwoTokens[1];            const tokenBeforeRightParen = sourceCode.getLastToken(node);            return rightParenToken && tokenAfterRightParen &&                !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&                !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);        }        /**         * Determines if a given expression node is an IIFE         * @param {ASTNode} node The node to check         * @returns {boolean} `true` if the given node is an IIFE         */        function isIIFE(node) {            return node.type === "CallExpression" && node.callee.type === "FunctionExpression";        }        /**         * Report the node         * @param {ASTNode} node node to evaluate         * @returns {void}         * @private         */        function report(node) {            const leftParenToken = sourceCode.getTokenBefore(node);            const rightParenToken = sourceCode.getTokenAfter(node);            if (!isParenthesisedTwice(node)) {                if (tokensToIgnore.has(sourceCode.getFirstToken(node))) {                    return;                }                if (isIIFE(node) && !isParenthesised(node.callee)) {                    return;                }            }            context.report({                node,                loc: leftParenToken.loc.start,                messageId: "unexpected",                fix(fixer) {                    const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);                    return fixer.replaceTextRange([                        leftParenToken.range[0],                        rightParenToken.range[1]                    ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));                }            });        }        /**         * Evaluate Unary update         * @param {ASTNode} node node to evaluate         * @returns {void}         * @private         */        function checkUnaryUpdate(node) {            if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") {                return;            }            if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {                report(node.argument);            }        }        /**         * Check if a member expression contains a call expression         * @param {ASTNode} node MemberExpression node to evaluate         * @returns {boolean} true if found, false if not         */        function doesMemberExpressionContainCallExpression(node) {            let currentNode = node.object;            let currentNodeType = node.object.type;            while (currentNodeType === "MemberExpression") {                currentNode = currentNode.object;                currentNodeType = currentNode.type;            }            return currentNodeType === "CallExpression";        }        /**         * Evaluate a new call         * @param {ASTNode} node node to evaluate         * @returns {void}         * @private         */        function checkCallNew(node) {            const callee = node.callee;            if (hasExcessParens(callee) && precedence(callee) >= precedence(node)) {                const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee);                if (                    hasDoubleExcessParens(callee) ||                    !isIIFE(node) && !hasNewParensException && !(                        /*                         * Allow extra parens around a new expression if                         * there are intervening parentheses.                         */                        callee.type === "MemberExpression" &&                        doesMemberExpressionContainCallExpression(callee)                    )                ) {                    report(node.callee);                }            }            if (node.arguments.length === 1) {                if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {                    report(node.arguments[0]);                }            } else {                node.arguments                    .filter(arg => hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)                    .forEach(report);            }        }        /**         * Evaluate binary logicals         * @param {ASTNode} node node to evaluate         * @returns {void}         * @private         */        function checkBinaryLogical(node) {            const prec = precedence(node);            const leftPrecedence = precedence(node.left);            const rightPrecedence = precedence(node.right);            const isExponentiation = node.operator === "**";            const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) ||              node.left.type === "UnaryExpression" && isExponentiation;            const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");            if (!shouldSkipLeft && hasExcessParens(node.left) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation))) {                report(node.left);            }            if (!shouldSkipRight && hasExcessParens(node.right) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation))) {                report(node.right);            }        }        /**         * Check the parentheses around the super class of the given class definition.         * @param {ASTNode} node The node of class declarations to check.         * @returns {void}         */        function checkClass(node) {            if (!node.superClass) {                return;            }            /*             * If `node.superClass` is a LeftHandSideExpression, parentheses are extra.             * Otherwise, parentheses are needed.             */            const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR                ? hasExcessParens(node.superClass)                : hasDoubleExcessParens(node.superClass);            if (hasExtraParens) {                report(node.superClass);            }        }        /**         * Check the parentheses around the argument of the given spread operator.         * @param {ASTNode} node The node of spread elements/properties to check.         * @returns {void}         */        function checkSpreadOperator(node) {            const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR                ? hasExcessParens(node.argument)                : hasDoubleExcessParens(node.argument);            if (hasExtraParens) {                report(node.argument);            }        }        /**         * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration         * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node         * @returns {void}         */        function checkExpressionOrExportStatement(node) {            const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);            const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);            const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;            if (                astUtils.isOpeningParenToken(firstToken) &&                (                    astUtils.isOpeningBraceToken(secondToken) ||                    secondToken.type === "Keyword" && (                        secondToken.value === "function" ||                        secondToken.value === "class" ||                        secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken))                    ) ||                    secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"                )            ) {                tokensToIgnore.add(secondToken);            }            if (hasExcessParens(node)) {                report(node);            }        }        return {            ArrayExpression(node) {                node.elements                    .filter(e => e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)                    .forEach(report);            },            ArrowFunctionExpression(node) {                if (isReturnAssignException(node)) {                    return;                }                if (node.body.type === "ConditionalExpression" &&                    IGNORE_ARROW_CONDITIONALS &&                    !isParenthesisedTwice(node.body)                ) {                    return;                }                if (node.body.type !== "BlockStatement") {                    const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);                    const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);                    if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {                        tokensToIgnore.add(firstBodyToken);                    }                    if (hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {                        report(node.body);                    }                }            },            AssignmentExpression(node) {                if (isReturnAssignException(node)) {                    return;                }                if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {                    report(node.right);                }            },            BinaryExpression: checkBinaryLogical,            CallExpression: checkCallNew,            ConditionalExpression(node) {                if (isReturnAssignException(node)) {                    return;                }                if (hasExcessParens(node.test) && precedence(node.test) >= precedence({ type: "LogicalExpression", operator: "||" })) {                    report(node.test);                }                if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {                    report(node.consequent);                }                if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {                    report(node.alternate);                }            },            DoWhileStatement(node) {                if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {                    report(node.test);                }            },            ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),            ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),            "ForInStatement, ForOfStatement"(node) {                if (node.left.type !== "VariableDeclarator") {                    const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);                    if (                        firstLeftToken.value === "let" && (                            /*                             * If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`                             * Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.                             */                            firstLeftToken.range[1] === node.left.range[1] ||                            /*                             * If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`                             * Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.                             */                            astUtils.isOpeningBracketToken(                                sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)                            )                        )                    ) {                        tokensToIgnore.add(firstLeftToken);                    }                }                if (!(node.type === "ForOfStatement" && node.right.type === "SequenceExpression") && hasExcessParens(node.right)) {                    report(node.right);                }                if (hasExcessParens(node.left)) {                    report(node.left);                }            },            ForStatement(node) {                if (node.init && hasExcessParens(node.init)) {                    report(node.init);                }                if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {                    report(node.test);                }                if (node.update && hasExcessParens(node.update)) {                    report(node.update);                }            },            IfStatement(node) {                if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {                    report(node.test);                }            },            LogicalExpression: checkBinaryLogical,            MemberExpression(node) {                const nodeObjHasExcessParens = hasExcessParens(node.object);                if (                    nodeObjHasExcessParens &&                    precedence(node.object) >= precedence(node) &&                    (                        node.computed ||                        !(                            astUtils.isDecimalInteger(node.object) ||                            // RegExp literal is allowed to have parens (#1589)                            (node.object.type === "Literal" && node.object.regex)                        )                    )                ) {                    report(node.object);                }                if (nodeObjHasExcessParens &&                  node.object.type === "CallExpression" &&                  node.parent.type !== "NewExpression") {                    report(node.object);                }                if (node.computed && hasExcessParens(node.property)) {                    report(node.property);                }            },            NewExpression: checkCallNew,            ObjectExpression(node) {                node.properties                    .filter(property => {                        const value = property.value;                        return value && hasExcessParens(value) && precedence(value) >= PRECEDENCE_OF_ASSIGNMENT_EXPR;                    }).forEach(property => report(property.value));            },            ReturnStatement(node) {                const returnToken = sourceCode.getFirstToken(node);                if (isReturnAssignException(node)) {                    return;                }                if (node.argument &&                        hasExcessParensNoLineTerminator(returnToken, node.argument) &&                        // RegExp literal is allowed to have parens (#1589)                        !(node.argument.type === "Literal" && node.argument.regex)) {                    report(node.argument);                }            },            SequenceExpression(node) {                node.expressions                    .filter(e => hasExcessParens(e) && precedence(e) >= precedence(node))                    .forEach(report);            },            SwitchCase(node) {                if (node.test && hasExcessParens(node.test)) {                    report(node.test);                }            },            SwitchStatement(node) {                if (hasDoubleExcessParens(node.discriminant)) {                    report(node.discriminant);                }            },            ThrowStatement(node) {                const throwToken = sourceCode.getFirstToken(node);                if (hasExcessParensNoLineTerminator(throwToken, node.argument)) {                    report(node.argument);                }            },            UnaryExpression: checkUnaryUpdate,            UpdateExpression: checkUnaryUpdate,            AwaitExpression: checkUnaryUpdate,            VariableDeclarator(node) {                if (node.init && hasExcessParens(node.init) &&                        precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&                        // RegExp literal is allowed to have parens (#1589)                        !(node.init.type === "Literal" && node.init.regex)) {                    report(node.init);                }            },            WhileStatement(node) {                if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {                    report(node.test);                }            },            WithStatement(node) {                if (hasDoubleExcessParens(node.object)) {                    report(node.object);                }            },            YieldExpression(node) {                if (node.argument) {                    const yieldToken = sourceCode.getFirstToken(node);                    if ((precedence(node.argument) >= precedence(node) &&                            hasExcessParensNoLineTerminator(yieldToken, node.argument)) ||                            hasDoubleExcessParens(node.argument)) {                        report(node.argument);                    }                }            },            ClassDeclaration: checkClass,            ClassExpression: checkClass,            SpreadElement: checkSpreadOperator,            SpreadProperty: checkSpreadOperator,            ExperimentalSpreadProperty: checkSpreadOperator        };    }};
 |