function isConstant(scope, node, inBooleanPosition) {
	// node.elements can return null values in the case of sparse arrays ex. [,]
	if (!node) {
		return true;
	}
	switch (node.type) {
		case "Literal":
		case "ArrowFunctionExpression":
		case "FunctionExpression":
			return true;
		case "ClassExpression":
		case "ObjectExpression":
			/**
			 * 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 "TemplateLiteral":
			return (
				(inBooleanPosition &&
					node.quasis.some(quasi => quasi.value.cooked.length)) ||
				node.expressions.every(exp => isConstant(scope, exp, false))
			);

		case "ArrayExpression": {
			if (!inBooleanPosition) {
				return node.elements.every(element =>
					isConstant(scope, element, false),
				);
			}
			return true;
		}

		case "UnaryExpression":
			if (
				node.operator === "void" ||
				(node.operator === "typeof" && inBooleanPosition)
			) {
				return true;
			}

			if (node.operator === "!") {
				return isConstant(scope, node.argument, true);
			}

			return isConstant(scope, node.argument, false);

		case "BinaryExpression":
			return (
				isConstant(scope, node.left, false) &&
				isConstant(scope, node.right, false) &&
				node.operator !== "in"
			);

		case "LogicalExpression": {
			const isLeftConstant = isConstant(
				scope,
				node.left,
				inBooleanPosition,
			);
			const isRightConstant = isConstant(
				scope,
				node.right,
				inBooleanPosition,
			);
			const isLeftShortCircuit =
				isLeftConstant && isLogicalIdentity(node.left, node.operator);
			const isRightShortCircuit =
				inBooleanPosition &&
				isRightConstant &&
				isLogicalIdentity(node.right, node.operator);

			return (
				(isLeftConstant && isRightConstant) ||
				isLeftShortCircuit ||
				isRightShortCircuit
			);
		}
		case "NewExpression":
			return inBooleanPosition;
		case "AssignmentExpression":
			if (node.operator === "=") {
				return isConstant(scope, node.right, inBooleanPosition);
			}

			if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
				return isLogicalIdentity(
					node.right,
					node.operator.slice(0, -1),
				);
			}

			return false;

		case "SequenceExpression":
			return isConstant(
				scope,
				node.expressions.at(-1),
				inBooleanPosition,
			);
		case "SpreadElement":
			return isConstant(scope, node.argument, inBooleanPosition);
		case "CallExpression":
			if (
				node.callee.type === "Identifier" &&
				node.callee.name === "Boolean"
			) {
				if (
					node.arguments.length === 0 ||
					isConstant(scope, node.arguments[0], true)
				) {
					return isReferenceToGlobalVariable(scope, node.callee);
				}
			}
			return false;
		case "Identifier":
			return (
				node.name === "undefined" &&
				isReferenceToGlobalVariable(scope, node)
			);

		// no default
	}
	return false;
}
Checks if a  node has a constant truthiness value.
@param {Scope} scope Scope in which the node appears.
@param {ASTNode} node The AST node to check.
@param {boolean} inBooleanPosition `true` if checking the test of a
condition. `false` in all other cases. When `false`, checks if -- for
both string and number -- if coerced to that type, the value will
be constant.
@returns {boolean} true when node's truthiness is constant
@private