Repository: eslint/eslint
File: lib/rules/no-constant-binary-expression.js (JavaScript)

Code before:
        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": {
            const isLeftConstant = hasConstantNullishness(scope, node.left);
            const isRightConstant = hasConstantNullishness(scope, node.right);

            return isLeftConstant || isRightConstant;
        }
        case "AssignmentExpression":
            if (node.operator === "=") {
                return hasConstantNullishness(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 a numeric or
             * string (non-nullish) value:
             *   "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="
             */

            return true;
        case "UnaryExpression":

            /*
             * "void" Always returns `undefined`
             * "typeof" All types are strings, and thus non-nullish

Review comment (suggestion):
These would be false positives, as the left-hand sides of these `??` do not have constant nullishness so the expressions neither always nor never short-circuit:

```js
const first = (foo && true) ?? bar;
//             ^^^^^^^^^^^ Unexpected constant nullishness on the left-hand side of a `??` expression.

const second = null ?? foo ?? bar;
//             ^^^^^^^^^^^ Unexpected constant nullishness on the left-hand side of a `??` expression.

const third = foo ?? null ?? bar;
//            ^^^^^^^^^^^ Unexpected constant nullishness on the left-hand side of a `??` expression.

```

We should check if the operator is `??` and if its right side is non-nullish.

Code after:
        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);
            }

            /*