510 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * @fileoverview Rule to flag constant comparisons and logical expressions that always/never short circuit
 | 
						|
 * @author Jordan Eldredge <https://jordaneldredge.com>
 | 
						|
 */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const globals = require("globals");
 | 
						|
const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator } = require("./utils/ast-utils");
 | 
						|
 | 
						|
const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|", "^", "&", "**", "<<", ">>", ">>>"]);
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Helpers
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
/**
 | 
						|
 * Checks whether or not a node is `null` or `undefined`. Similar to the one
 | 
						|
 * found in ast-utils.js, but this one correctly handles the edge case that
 | 
						|
 * `undefined` has been redefined.
 | 
						|
 * @param {Scope} scope Scope in which the expression was found.
 | 
						|
 * @param {ASTNode} node A node to check.
 | 
						|
 * @returns {boolean} Whether or not the node is a `null` or `undefined`.
 | 
						|
 * @public
 | 
						|
 */
 | 
						|
function isNullOrUndefined(scope, node) {
 | 
						|
    return (
 | 
						|
        isNullLiteral(node) ||
 | 
						|
        (node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) ||
 | 
						|
        (node.type === "UnaryExpression" && node.operator === "void")
 | 
						|
    );
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Test if an AST node has a statically knowable constant nullishness. Meaning,
 | 
						|
 * it will always resolve to a constant value of either: `null`, `undefined`
 | 
						|
 * or not `null` _or_ `undefined`. An expression that can vary between those
 | 
						|
 * three states at runtime would return `false`.
 | 
						|
 * @param {Scope} scope The scope in which the node was found.
 | 
						|
 * @param {ASTNode} node The AST node being tested.
 | 
						|
 * @param {boolean} nonNullish if `true` then nullish values are not considered constant.
 | 
						|
 * @returns {boolean} Does `node` have constant nullishness?
 | 
						|
 */
 | 
						|
function hasConstantNullishness(scope, node, nonNullish) {
 | 
						|
    if (nonNullish && isNullOrUndefined(scope, node)) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    switch (node.type) {
 | 
						|
        case "ObjectExpression": // Objects are never nullish
 | 
						|
        case "ArrayExpression": // Arrays are never nullish
 | 
						|
        case "ArrowFunctionExpression": // Functions never nullish
 | 
						|
        case "FunctionExpression": // Functions are never nullish
 | 
						|
        case "ClassExpression": // Classes are never nullish
 | 
						|
        case "NewExpression": // Objects are never nullish
 | 
						|
        case "Literal": // Nullish, or non-nullish, literals never change
 | 
						|
        case "TemplateLiteral": // A string is never nullish
 | 
						|
        case "UpdateExpression": // Numbers are never nullish
 | 
						|
        case "BinaryExpression": // Numbers, strings, or booleans are never nullish
 | 
						|
            return true;
 | 
						|
        case "CallExpression": {
 | 
						|
            if (node.callee.type !== "Identifier") {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            const functionName = node.callee.name;
 | 
						|
 | 
						|
            return (functionName === "Boolean" || functionName === "String" || functionName === "Number") &&
 | 
						|
                isReferenceToGlobalVariable(scope, node.callee);
 | 
						|
        }
 | 
						|
        case "LogicalExpression": {
 | 
						|
            return node.operator === "??" && hasConstantNullishness(scope, node.right, true);
 | 
						|
        }
 | 
						|
        case "AssignmentExpression":
 | 
						|
            if (node.operator === "=") {
 | 
						|
                return hasConstantNullishness(scope, node.right, nonNullish);
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * Handling short-circuiting assignment operators would require
 | 
						|
             * walking the scope. We won't attempt that (for now...) /
 | 
						|
             */
 | 
						|
            if (isLogicalAssignmentOperator(node.operator)) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * The remaining assignment expressions all result in a numeric or
 | 
						|
             * string (non-nullish) value:
 | 
						|
             *   "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="
 | 
						|
             */
 | 
						|
 | 
						|
            return true;
 | 
						|
        case "UnaryExpression":
 | 
						|
 | 
						|
            /*
 | 
						|
             * "void" Always returns `undefined`
 | 
						|
             * "typeof" All types are strings, and thus non-nullish
 | 
						|
             * "!" Boolean is never nullish
 | 
						|
             * "delete" Returns a boolean, which is never nullish
 | 
						|
             * Math operators always return numbers or strings, neither of which
 | 
						|
             * are non-nullish "+", "-", "~"
 | 
						|
             */
 | 
						|
 | 
						|
            return true;
 | 
						|
        case "SequenceExpression": {
 | 
						|
            const last = node.expressions[node.expressions.length - 1];
 | 
						|
 | 
						|
            return hasConstantNullishness(scope, last, nonNullish);
 | 
						|
        }
 | 
						|
        case "Identifier":
 | 
						|
            return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
 | 
						|
        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
 | 
						|
        case "JSXFragment":
 | 
						|
            return false;
 | 
						|
        default:
 | 
						|
            return false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Test if an AST node is a boolean value that never changes. Specifically we
 | 
						|
 * test for:
 | 
						|
 * 1. Literal booleans (`true` or `false`)
 | 
						|
 * 2. Unary `!` expressions with a constant value
 | 
						|
 * 3. Constant booleans created via the `Boolean` global function
 | 
						|
 * @param {Scope} scope The scope in which the node was found.
 | 
						|
 * @param {ASTNode} node The node to test
 | 
						|
 * @returns {boolean} Is `node` guaranteed to be a boolean?
 | 
						|
 */
 | 
						|
function isStaticBoolean(scope, node) {
 | 
						|
    switch (node.type) {
 | 
						|
        case "Literal":
 | 
						|
            return typeof node.value === "boolean";
 | 
						|
        case "CallExpression":
 | 
						|
            return node.callee.type === "Identifier" && node.callee.name === "Boolean" &&
 | 
						|
              isReferenceToGlobalVariable(scope, node.callee) &&
 | 
						|
              (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true));
 | 
						|
        case "UnaryExpression":
 | 
						|
            return node.operator === "!" && isConstant(scope, node.argument, true);
 | 
						|
        default:
 | 
						|
            return false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Test if an AST node will always give the same result when compared to a
 | 
						|
 * boolean value. Note that comparison to boolean values is different than
 | 
						|
 * truthiness.
 | 
						|
 * https://262.ecma-international.org/5.1/#sec-11.9.3
 | 
						|
 *
 | 
						|
 * Javascript `==` operator works by converting the boolean to `1` (true) or
 | 
						|
 * `+0` (false) and then checks the values `==` equality to that number.
 | 
						|
 * @param {Scope} scope The scope in which node was found.
 | 
						|
 * @param {ASTNode} node The node to test.
 | 
						|
 * @returns {boolean} Will `node` always coerce to the same boolean value?
 | 
						|
 */
 | 
						|
function hasConstantLooseBooleanComparison(scope, node) {
 | 
						|
    switch (node.type) {
 | 
						|
        case "ObjectExpression":
 | 
						|
        case "ClassExpression":
 | 
						|
 | 
						|
            /**
 | 
						|
             * In theory objects like:
 | 
						|
             *
 | 
						|
             * `{toString: () => a}`
 | 
						|
             * `{valueOf: () => a}`
 | 
						|
             *
 | 
						|
             * Or a classes like:
 | 
						|
             *
 | 
						|
             * `class { static toString() { return a } }`
 | 
						|
             * `class { static valueOf() { return a } }`
 | 
						|
             *
 | 
						|
             * Are not constant verifiably when `inBooleanPosition` is
 | 
						|
             * false, but it's an edge case we've opted not to handle.
 | 
						|
             */
 | 
						|
            return true;
 | 
						|
        case "ArrayExpression": {
 | 
						|
            const nonSpreadElements = node.elements.filter(e =>
 | 
						|
 | 
						|
                // Elements can be `null` in sparse arrays: `[,,]`;
 | 
						|
                e !== null && e.type !== "SpreadElement");
 | 
						|
 | 
						|
 | 
						|
            /*
 | 
						|
             * Possible future direction if needed: We could check if the
 | 
						|
             * single value would result in variable boolean comparison.
 | 
						|
             * For now we will err on the side of caution since `[x]` could
 | 
						|
             * evaluate to `[0]` or `[1]`.
 | 
						|
             */
 | 
						|
            return node.elements.length === 0 || nonSpreadElements.length > 1;
 | 
						|
        }
 | 
						|
        case "ArrowFunctionExpression":
 | 
						|
        case "FunctionExpression":
 | 
						|
            return true;
 | 
						|
        case "UnaryExpression":
 | 
						|
            if (node.operator === "void" || // Always returns `undefined`
 | 
						|
                node.operator === "typeof" // All `typeof` strings, when coerced to number, are not 0 or 1.
 | 
						|
            ) {
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
            if (node.operator === "!") {
 | 
						|
                return isConstant(scope, node.argument, true);
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * We won't try to reason about +, -, ~, or delete
 | 
						|
             * In theory, for the mathematical operators, we could look at the
 | 
						|
             * argument and try to determine if it coerces to a constant numeric
 | 
						|
             * value.
 | 
						|
             */
 | 
						|
            return false;
 | 
						|
        case "NewExpression": // Objects might have custom `.valueOf` or `.toString`.
 | 
						|
            return false;
 | 
						|
        case "CallExpression": {
 | 
						|
            if (node.callee.type === "Identifier" &&
 | 
						|
                node.callee.name === "Boolean" &&
 | 
						|
                isReferenceToGlobalVariable(scope, node.callee)
 | 
						|
            ) {
 | 
						|
                return node.arguments.length === 0 || isConstant(scope, node.arguments[0], true);
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        case "Literal": // True or false, literals never change
 | 
						|
            return true;
 | 
						|
        case "Identifier":
 | 
						|
            return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
 | 
						|
        case "TemplateLiteral":
 | 
						|
 | 
						|
            /*
 | 
						|
             * In theory we could try to check if the quasi are sufficient to
 | 
						|
             * prove that the expression will always be true, but it would be
 | 
						|
             * tricky to get right. For example: `000.${foo}000`
 | 
						|
             */
 | 
						|
            return node.expressions.length === 0;
 | 
						|
        case "AssignmentExpression":
 | 
						|
            if (node.operator === "=") {
 | 
						|
                return hasConstantLooseBooleanComparison(scope, node.right);
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * Handling short-circuiting assignment operators would require
 | 
						|
             * walking the scope. We won't attempt that (for now...)
 | 
						|
             *
 | 
						|
             * The remaining assignment expressions all result in a numeric or
 | 
						|
             * string (non-nullish) values which could be truthy or falsy:
 | 
						|
             *   "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="
 | 
						|
             */
 | 
						|
            return false;
 | 
						|
        case "SequenceExpression": {
 | 
						|
            const last = node.expressions[node.expressions.length - 1];
 | 
						|
 | 
						|
            return hasConstantLooseBooleanComparison(scope, last);
 | 
						|
        }
 | 
						|
        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
 | 
						|
        case "JSXFragment":
 | 
						|
            return false;
 | 
						|
        default:
 | 
						|
            return false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Test if an AST node will always give the same result when _strictly_ compared
 | 
						|
 * to a boolean value. This can happen if the expression can never be boolean, or
 | 
						|
 * if it is always the same boolean value.
 | 
						|
 * @param {Scope} scope The scope in which the node was found.
 | 
						|
 * @param {ASTNode} node The node to test
 | 
						|
 * @returns {boolean} Will `node` always give the same result when compared to a
 | 
						|
 * static boolean value?
 | 
						|
 */
 | 
						|
function hasConstantStrictBooleanComparison(scope, node) {
 | 
						|
    switch (node.type) {
 | 
						|
        case "ObjectExpression": // Objects are not booleans
 | 
						|
        case "ArrayExpression": // Arrays are not booleans
 | 
						|
        case "ArrowFunctionExpression": // Functions are not booleans
 | 
						|
        case "FunctionExpression":
 | 
						|
        case "ClassExpression": // Classes are not booleans
 | 
						|
        case "NewExpression": // Objects are not booleans
 | 
						|
        case "TemplateLiteral": // Strings are not booleans
 | 
						|
        case "Literal": // True, false, or not boolean, literals never change.
 | 
						|
        case "UpdateExpression": // Numbers are not booleans
 | 
						|
            return true;
 | 
						|
        case "BinaryExpression":
 | 
						|
            return NUMERIC_OR_STRING_BINARY_OPERATORS.has(node.operator);
 | 
						|
        case "UnaryExpression": {
 | 
						|
            if (node.operator === "delete") {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            if (node.operator === "!") {
 | 
						|
                return isConstant(scope, node.argument, true);
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * The remaining operators return either strings or numbers, neither
 | 
						|
             * of which are boolean.
 | 
						|
             */
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        case "SequenceExpression": {
 | 
						|
            const last = node.expressions[node.expressions.length - 1];
 | 
						|
 | 
						|
            return hasConstantStrictBooleanComparison(scope, last);
 | 
						|
        }
 | 
						|
        case "Identifier":
 | 
						|
            return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
 | 
						|
        case "AssignmentExpression":
 | 
						|
            if (node.operator === "=") {
 | 
						|
                return hasConstantStrictBooleanComparison(scope, node.right);
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * Handling short-circuiting assignment operators would require
 | 
						|
             * walking the scope. We won't attempt that (for now...)
 | 
						|
             */
 | 
						|
            if (isLogicalAssignmentOperator(node.operator)) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * The remaining assignment expressions all result in either a number
 | 
						|
             * or a string, neither of which can ever be boolean.
 | 
						|
             */
 | 
						|
            return true;
 | 
						|
        case "CallExpression": {
 | 
						|
            if (node.callee.type !== "Identifier") {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            const functionName = node.callee.name;
 | 
						|
 | 
						|
            if (
 | 
						|
                (functionName === "String" || functionName === "Number") &&
 | 
						|
                isReferenceToGlobalVariable(scope, node.callee)
 | 
						|
            ) {
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
            if (functionName === "Boolean" && isReferenceToGlobalVariable(scope, node.callee)) {
 | 
						|
                return (
 | 
						|
                    node.arguments.length === 0 || isConstant(scope, node.arguments[0], true));
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
 | 
						|
        case "JSXFragment":
 | 
						|
            return false;
 | 
						|
        default:
 | 
						|
            return false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Test if an AST node will always result in a newly constructed object
 | 
						|
 * @param {Scope} scope The scope in which the node was found.
 | 
						|
 * @param {ASTNode} node The node to test
 | 
						|
 * @returns {boolean} Will `node` always be new?
 | 
						|
 */
 | 
						|
function isAlwaysNew(scope, node) {
 | 
						|
    switch (node.type) {
 | 
						|
        case "ObjectExpression":
 | 
						|
        case "ArrayExpression":
 | 
						|
        case "ArrowFunctionExpression":
 | 
						|
        case "FunctionExpression":
 | 
						|
        case "ClassExpression":
 | 
						|
            return true;
 | 
						|
        case "NewExpression": {
 | 
						|
            if (node.callee.type !== "Identifier") {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * All the built-in constructors are always new, but
 | 
						|
             * user-defined constructors could return a sentinel
 | 
						|
             * object.
 | 
						|
             *
 | 
						|
             * Catching these is especially useful for primitive constructors
 | 
						|
             * which return boxed values, a surprising gotcha' in JavaScript.
 | 
						|
             */
 | 
						|
            return Object.hasOwnProperty.call(globals.builtin, node.callee.name) &&
 | 
						|
              isReferenceToGlobalVariable(scope, node.callee);
 | 
						|
        }
 | 
						|
        case "Literal":
 | 
						|
 | 
						|
            // Regular expressions are objects, and thus always new
 | 
						|
            return typeof node.regex === "object";
 | 
						|
        case "SequenceExpression": {
 | 
						|
            const last = node.expressions[node.expressions.length - 1];
 | 
						|
 | 
						|
            return isAlwaysNew(scope, last);
 | 
						|
        }
 | 
						|
        case "AssignmentExpression":
 | 
						|
            if (node.operator === "=") {
 | 
						|
                return isAlwaysNew(scope, node.right);
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
        case "ConditionalExpression":
 | 
						|
            return isAlwaysNew(scope, node.consequent) && isAlwaysNew(scope, node.alternate);
 | 
						|
        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
 | 
						|
        case "JSXFragment":
 | 
						|
            return false;
 | 
						|
        default:
 | 
						|
            return false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Checks if one operand will cause the result to be constant.
 | 
						|
 * @param {Scope} scope Scope in which the expression was found.
 | 
						|
 * @param {ASTNode} a One side of the expression
 | 
						|
 * @param {ASTNode} b The other side of the expression
 | 
						|
 * @param {string} operator The binary expression operator
 | 
						|
 * @returns {ASTNode | null} The node which will cause the expression to have a constant result.
 | 
						|
 */
 | 
						|
function findBinaryExpressionConstantOperand(scope, a, b, operator) {
 | 
						|
    if (operator === "==" || operator === "!=") {
 | 
						|
        if (
 | 
						|
            (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) ||
 | 
						|
            (isStaticBoolean(scope, a) && hasConstantLooseBooleanComparison(scope, b))
 | 
						|
        ) {
 | 
						|
            return b;
 | 
						|
        }
 | 
						|
    } else if (operator === "===" || operator === "!==") {
 | 
						|
        if (
 | 
						|
            (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) ||
 | 
						|
            (isStaticBoolean(scope, a) && hasConstantStrictBooleanComparison(scope, b))
 | 
						|
        ) {
 | 
						|
            return b;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
}
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Rule Definition
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
/** @type {import('../shared/types').Rule} */
 | 
						|
module.exports = {
 | 
						|
    meta: {
 | 
						|
        type: "problem",
 | 
						|
        docs: {
 | 
						|
            description: "Disallow expressions where the operation doesn't affect the value",
 | 
						|
            recommended: false,
 | 
						|
            url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression"
 | 
						|
        },
 | 
						|
        schema: [],
 | 
						|
        messages: {
 | 
						|
            constantBinaryOperand: "Unexpected constant binary expression. Compares constantly with the {{otherSide}}-hand side of the `{{operator}}`.",
 | 
						|
            constantShortCircuit: "Unexpected constant {{property}} on the left-hand side of a `{{operator}}` expression.",
 | 
						|
            alwaysNew: "Unexpected comparison to newly constructed object. These two values can never be equal.",
 | 
						|
            bothAlwaysNew: "Unexpected comparison of two newly constructed objects. These two values can never be equal."
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    create(context) {
 | 
						|
        const sourceCode = context.sourceCode;
 | 
						|
 | 
						|
        return {
 | 
						|
            LogicalExpression(node) {
 | 
						|
                const { operator, left } = node;
 | 
						|
                const scope = sourceCode.getScope(node);
 | 
						|
 | 
						|
                if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) {
 | 
						|
                    context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } });
 | 
						|
                } else if (operator === "??" && hasConstantNullishness(scope, left, false)) {
 | 
						|
                    context.report({ node: left, messageId: "constantShortCircuit", data: { property: "nullishness", operator } });
 | 
						|
                }
 | 
						|
            },
 | 
						|
            BinaryExpression(node) {
 | 
						|
                const scope = sourceCode.getScope(node);
 | 
						|
                const { right, left, operator } = node;
 | 
						|
                const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator);
 | 
						|
                const leftConstantOperand = findBinaryExpressionConstantOperand(scope, right, left, operator);
 | 
						|
 | 
						|
                if (rightConstantOperand) {
 | 
						|
                    context.report({ node: rightConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "left" } });
 | 
						|
                } else if (leftConstantOperand) {
 | 
						|
                    context.report({ node: leftConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "right" } });
 | 
						|
                } else if (operator === "===" || operator === "!==") {
 | 
						|
                    if (isAlwaysNew(scope, left)) {
 | 
						|
                        context.report({ node: left, messageId: "alwaysNew" });
 | 
						|
                    } else if (isAlwaysNew(scope, right)) {
 | 
						|
                        context.report({ node: right, messageId: "alwaysNew" });
 | 
						|
                    }
 | 
						|
                } else if (operator === "==" || operator === "!=") {
 | 
						|
 | 
						|
                    /*
 | 
						|
                     * If both sides are "new", then both sides are objects and
 | 
						|
                     * therefore they will be compared by reference even with `==`
 | 
						|
                     * equality.
 | 
						|
                     */
 | 
						|
                    if (isAlwaysNew(scope, left) && isAlwaysNew(scope, right)) {
 | 
						|
                        context.report({ node: left, messageId: "bothAlwaysNew" });
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
            }
 | 
						|
 | 
						|
            /*
 | 
						|
             * In theory we could handle short-circuiting assignment operators,
 | 
						|
             * for some constant values, but that would require walking the
 | 
						|
             * scope to find the value of the variable being assigned. This is
 | 
						|
             * dependant on https://github.com/eslint/eslint/issues/13776
 | 
						|
             *
 | 
						|
             * AssignmentExpression() {},
 | 
						|
             */
 | 
						|
        };
 | 
						|
    }
 | 
						|
};
 |