/**
							 | 
						|
								 * @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;
							 | 
						|
								                        }
							 | 
						|
								                    });
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        };
							 | 
						|
								
							 | 
						|
								    }
							 | 
						|
								};
							 |