| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 | /** * @fileoverview Rule to flag unnecessary double negation in Boolean contexts * @author Brandon Mills */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const astUtils = require("./utils/ast-utils");const eslintUtils = require("eslint-utils");const precedence = astUtils.getPrecedence;//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",        docs: {            description: "Disallow unnecessary boolean casts",            recommended: true,            url: "https://eslint.org/docs/rules/no-extra-boolean-cast"        },        schema: [{            type: "object",            properties: {                enforceForLogicalOperands: {                    type: "boolean",                    default: false                }            },            additionalProperties: false        }],        fixable: "code",        messages: {            unexpectedCall: "Redundant Boolean call.",            unexpectedNegation: "Redundant double negation."        }    },    create(context) {        const sourceCode = context.getSourceCode();        // Node types which have a test which will coerce values to booleans.        const BOOLEAN_NODE_TYPES = new Set([            "IfStatement",            "DoWhileStatement",            "WhileStatement",            "ConditionalExpression",            "ForStatement"        ]);        /**         * Check if a node is a Boolean function or constructor.         * @param {ASTNode} node the node         * @returns {boolean} If the node is Boolean function or constructor         */        function isBooleanFunctionOrConstructorCall(node) {            // Boolean(<bool>) and new Boolean(<bool>)            return (node.type === "CallExpression" || node.type === "NewExpression") &&                    node.callee.type === "Identifier" &&                        node.callee.name === "Boolean";        }        /**         * Checks whether the node is a logical expression and that the option is enabled         * @param {ASTNode} node the node         * @returns {boolean} if the node is a logical expression and option is enabled         */        function isLogicalContext(node) {            return node.type === "LogicalExpression" &&            (node.operator === "||" || node.operator === "&&") &&            (context.options.length && context.options[0].enforceForLogicalOperands === true);        }        /**         * Check if a node is in a context where its value would be coerced to a boolean at runtime.         * @param {ASTNode} node The node         * @returns {boolean} If it is in a boolean context         */        function isInBooleanContext(node) {            return (                (isBooleanFunctionOrConstructorCall(node.parent) &&                node === node.parent.arguments[0]) ||                (BOOLEAN_NODE_TYPES.has(node.parent.type) &&                    node === node.parent.test) ||                // !<bool>                (node.parent.type === "UnaryExpression" &&                    node.parent.operator === "!")            );        }        /**         * Checks whether the node is a context that should report an error         * Acts recursively if it is in a logical context         * @param {ASTNode} node the node         * @returns {boolean} If the node is in one of the flagged contexts         */        function isInFlaggedContext(node) {            if (node.parent.type === "ChainExpression") {                return isInFlaggedContext(node.parent);            }            return isInBooleanContext(node) ||            (isLogicalContext(node.parent) &&            // For nested logical statements            isInFlaggedContext(node.parent)            );        }        /**         * Check if a node has comments inside.         * @param {ASTNode} node The node to check.         * @returns {boolean} `true` if it has comments inside.         */        function hasCommentsInside(node) {            return Boolean(sourceCode.getCommentsInside(node).length);        }        /**         * Checks if the given node is wrapped in grouping parentheses. Parentheses for constructs such as if() don't count.         * @param {ASTNode} node The node to check.         * @returns {boolean} `true` if the node is parenthesized.         * @private         */        function isParenthesized(node) {            return eslintUtils.isParenthesized(1, node, sourceCode);        }        /**         * Determines whether the given node needs to be parenthesized when replacing the previous node.         * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list         * of possible parent node types. By the same assumption, the node's role in a particular parent is already known.         * For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child.         * @param {ASTNode} previousNode Previous node.         * @param {ASTNode} node The node to check.         * @throws {Error} (Unreachable.)         * @returns {boolean} `true` if the node needs to be parenthesized.         */        function needsParens(previousNode, node) {            if (previousNode.parent.type === "ChainExpression") {                return needsParens(previousNode.parent, node);            }            if (isParenthesized(previousNode)) {                // parentheses around the previous node will stay, so there is no need for an additional pair                return false;            }            // parent of the previous node will become parent of the replacement node            const parent = previousNode.parent;            switch (parent.type) {                case "CallExpression":                case "NewExpression":                    return node.type === "SequenceExpression";                case "IfStatement":                case "DoWhileStatement":                case "WhileStatement":                case "ForStatement":                    return false;                case "ConditionalExpression":                    return precedence(node) <= precedence(parent);                case "UnaryExpression":                    return precedence(node) < precedence(parent);                case "LogicalExpression":                    if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) {                        return true;                    }                    if (previousNode === parent.left) {                        return precedence(node) < precedence(parent);                    }                    return precedence(node) <= precedence(parent);                /* c8 ignore next */                default:                    throw new Error(`Unexpected parent type: ${parent.type}`);            }        }        return {            UnaryExpression(node) {                const parent = node.parent;                // Exit early if it's guaranteed not to match                if (node.operator !== "!" ||                          parent.type !== "UnaryExpression" ||                          parent.operator !== "!") {                    return;                }                if (isInFlaggedContext(parent)) {                    context.report({                        node: parent,                        messageId: "unexpectedNegation",                        fix(fixer) {                            if (hasCommentsInside(parent)) {                                return null;                            }                            if (needsParens(parent, node.argument)) {                                return fixer.replaceText(parent, `(${sourceCode.getText(node.argument)})`);                            }                            let prefix = "";                            const tokenBefore = sourceCode.getTokenBefore(parent);                            const firstReplacementToken = sourceCode.getFirstToken(node.argument);                            if (                                tokenBefore &&                                tokenBefore.range[1] === parent.range[0] &&                                !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)                            ) {                                prefix = " ";                            }                            return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument));                        }                    });                }            },            CallExpression(node) {                if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") {                    return;                }                if (isInFlaggedContext(node)) {                    context.report({                        node,                        messageId: "unexpectedCall",                        fix(fixer) {                            const parent = node.parent;                            if (node.arguments.length === 0) {                                if (parent.type === "UnaryExpression" && parent.operator === "!") {                                    /*                                     * !Boolean() -> true                                     */                                    if (hasCommentsInside(parent)) {                                        return null;                                    }                                    const replacement = "true";                                    let prefix = "";                                    const tokenBefore = sourceCode.getTokenBefore(parent);                                    if (                                        tokenBefore &&                                        tokenBefore.range[1] === parent.range[0] &&                                        !astUtils.canTokensBeAdjacent(tokenBefore, replacement)                                    ) {                                        prefix = " ";                                    }                                    return fixer.replaceText(parent, prefix + replacement);                                }                                /*                                 * Boolean() -> false                                 */                                if (hasCommentsInside(node)) {                                    return null;                                }                                return fixer.replaceText(node, "false");                            }                            if (node.arguments.length === 1) {                                const argument = node.arguments[0];                                if (argument.type === "SpreadElement" || hasCommentsInside(node)) {                                    return null;                                }                                /*                                 * Boolean(expression) -> expression                                 */                                if (needsParens(node, argument)) {                                    return fixer.replaceText(node, `(${sourceCode.getText(argument)})`);                                }                                return fixer.replaceText(node, sourceCode.getText(argument));                            }                            // two or more arguments                            return null;                        }                    });                }            }        };    }};
 |