|                                                                                                                                                                                                                                                                                                                             |  | /** * @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-community/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/latest/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.sourceCode;
        // 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;                        }                    });                }            }        };
    }};
 |