|                                                                                                                                                                                                                                                                                                                                                                 |  | /** * @fileoverview Rule to require or disallow yoda comparisons * @author Nicholas C. Zakas */"use strict";
//--------------------------------------------------------------------------
// Requirements
//--------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/** * Determines whether an operator is a comparison operator. * @param {string} operator The operator to check. * @returns {boolean} Whether or not it is a comparison operator. */function isComparisonOperator(operator) {    return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator);}
/** * Determines whether an operator is an equality operator. * @param {string} operator The operator to check. * @returns {boolean} Whether or not it is an equality operator. */function isEqualityOperator(operator) {    return /^(==|===)$/u.test(operator);}
/** * Determines whether an operator is one used in a range test. * Allowed operators are `<` and `<=`. * @param {string} operator The operator to check. * @returns {boolean} Whether the operator is used in range tests. */function isRangeTestOperator(operator) {    return ["<", "<="].includes(operator);}
/** * Determines whether a non-Literal node is a negative number that should be * treated as if it were a single Literal node. * @param {ASTNode} node Node to test. * @returns {boolean} True if the node is a negative number that looks like a *                    real literal and should be treated as such. */function isNegativeNumericLiteral(node) {    return (        node.type === "UnaryExpression" &&        node.operator === "-" &&        node.prefix &&        astUtils.isNumericLiteral(node.argument)    );}
/** * Determines whether a non-Literal node should be treated as a single Literal node. * @param {ASTNode} node Node to test * @returns {boolean} True if the node should be treated as a single Literal node. */function looksLikeLiteral(node) {    return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node);}
/** * Attempts to derive a Literal node from nodes that are treated like literals. * @param {ASTNode} node Node to normalize. * @returns {ASTNode} One of the following options. *  1. The original node if the node is already a Literal *  2. A normalized Literal node with the negative number as the value if the *     node represents a negative number literal. *  3. A normalized Literal node with the string as the value if the node is *     a Template Literal without expression. *  4. Otherwise `null`. */function getNormalizedLiteral(node) {    if (node.type === "Literal") {        return node;    }
    if (isNegativeNumericLiteral(node)) {        return {            type: "Literal",            value: -node.argument.value,            raw: `-${node.argument.value}`        };    }
    if (astUtils.isStaticTemplateLiteral(node)) {        return {            type: "Literal",            value: node.quasis[0].value.cooked,            raw: node.quasis[0].value.raw        };    }
    return null;}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: 'Require or disallow "Yoda" conditions',            recommended: false,            url: "https://eslint.org/docs/latest/rules/yoda"        },
        schema: [            {                enum: ["always", "never"]            },            {                type: "object",                properties: {                    exceptRange: {                        type: "boolean",                        default: false                    },                    onlyEquality: {                        type: "boolean",                        default: false                    }                },                additionalProperties: false            }        ],
        fixable: "code",        messages: {            expected:                "Expected literal to be on the {{expectedSide}} side of {{operator}}."        }    },
    create(context) {
        // Default to "never" (!always) if no option
        const always = context.options[0] === "always";        const exceptRange =            context.options[1] && context.options[1].exceptRange;        const onlyEquality =            context.options[1] && context.options[1].onlyEquality;
        const sourceCode = context.sourceCode;
        /**         * Determines whether node represents a range test.         * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"         * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and         * both operators must be `<` or `<=`. Finally, the literal on the left side         * must be less than or equal to the literal on the right side so that the         * test makes any sense.         * @param {ASTNode} node LogicalExpression node to test.         * @returns {boolean} Whether node is a range test.         */        function isRangeTest(node) {            const left = node.left,                right = node.right;
            /**             * Determines whether node is of the form `0 <= x && x < 1`.             * @returns {boolean} Whether node is a "between" range test.             */            function isBetweenTest() {                if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) {                    const leftLiteral = getNormalizedLiteral(left.left);                    const rightLiteral = getNormalizedLiteral(right.right);
                    if (leftLiteral === null && rightLiteral === null) {                        return false;                    }
                    if (rightLiteral === null || leftLiteral === null) {                        return true;                    }
                    if (leftLiteral.value <= rightLiteral.value) {                        return true;                    }                }                return false;            }
            /**             * Determines whether node is of the form `x < 0 || 1 <= x`.             * @returns {boolean} Whether node is an "outside" range test.             */            function isOutsideTest() {                if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) {                    const leftLiteral = getNormalizedLiteral(left.right);                    const rightLiteral = getNormalizedLiteral(right.left);
                    if (leftLiteral === null && rightLiteral === null) {                        return false;                    }
                    if (rightLiteral === null || leftLiteral === null) {                        return true;                    }
                    if (leftLiteral.value <= rightLiteral.value) {                        return true;                    }                }
                return false;            }
            /**             * Determines whether node is wrapped in parentheses.             * @returns {boolean} Whether node is preceded immediately by an open             *                    paren token and followed immediately by a close             *                    paren token.             */            function isParenWrapped() {                return astUtils.isParenthesised(sourceCode, node);            }
            return (                node.type === "LogicalExpression" &&                left.type === "BinaryExpression" &&                right.type === "BinaryExpression" &&                isRangeTestOperator(left.operator) &&                isRangeTestOperator(right.operator) &&                (isBetweenTest() || isOutsideTest()) &&                isParenWrapped()            );        }
        const OPERATOR_FLIP_MAP = {            "===": "===",            "!==": "!==",            "==": "==",            "!=": "!=",            "<": ">",            ">": "<",            "<=": ">=",            ">=": "<="        };
        /**         * Returns a string representation of a BinaryExpression node with its sides/operator flipped around.         * @param {ASTNode} node The BinaryExpression node         * @returns {string} A string representation of the node with the sides and operator flipped         */        function getFlippedString(node) {            const operatorToken = sourceCode.getFirstTokenBetween(                node.left,                node.right,                token => token.value === node.operator            );            const lastLeftToken = sourceCode.getTokenBefore(operatorToken);            const firstRightToken = sourceCode.getTokenAfter(operatorToken);
            const source = sourceCode.getText();
            const leftText = source.slice(                node.range[0],                lastLeftToken.range[1]            );            const textBeforeOperator = source.slice(                lastLeftToken.range[1],                operatorToken.range[0]            );            const textAfterOperator = source.slice(                operatorToken.range[1],                firstRightToken.range[0]            );            const rightText = source.slice(                firstRightToken.range[0],                node.range[1]            );
            const tokenBefore = sourceCode.getTokenBefore(node);            const tokenAfter = sourceCode.getTokenAfter(node);            let prefix = "";            let suffix = "";
            if (                tokenBefore &&                tokenBefore.range[1] === node.range[0] &&                !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken)            ) {                prefix = " ";            }
            if (                tokenAfter &&                node.range[1] === tokenAfter.range[0] &&                !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)            ) {                suffix = " ";            }
            return (                prefix +                rightText +                textBeforeOperator +                OPERATOR_FLIP_MAP[operatorToken.value] +                textAfterOperator +                leftText +                suffix            );        }
        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------
        return {            BinaryExpression(node) {                const expectedLiteral = always ? node.left : node.right;                const expectedNonLiteral = always ? node.right : node.left;
                // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
                if (                    (expectedNonLiteral.type === "Literal" ||                        looksLikeLiteral(expectedNonLiteral)) &&                    !(                        expectedLiteral.type === "Literal" ||                        looksLikeLiteral(expectedLiteral)                    ) &&                    !(!isEqualityOperator(node.operator) && onlyEquality) &&                    isComparisonOperator(node.operator) &&                    !(exceptRange && isRangeTest(node.parent))                ) {                    context.report({                        node,                        messageId: "expected",                        data: {                            operator: node.operator,                            expectedSide: always ? "left" : "right"                        },                        fix: fixer =>                            fixer.replaceText(node, getFlippedString(node))                    });                }            }        };    }};
 |