diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 5e583864102..790d5ea8811 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -9,9 +9,6 @@ // Helpers //------------------------------------------------------------------------------ -const EQUALITY_OPERATORS = ["===", "!==", "==", "!="]; -const RELATIONAL_OPERATORS = [">", "<", ">=", "<=", "in", "instanceof"]; - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -56,6 +53,35 @@ module.exports = { // Helpers //-------------------------------------------------------------------------- + /** + * Returns literal's value converted to the Boolean type + * @param {ASTNode} node any `Literal` node + * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy, + * `null` when it cannot be determined. + */ + function getBooleanValue(node) { + if (node.value === null) { + + /* + * it might be a null literal or bigint/regex literal in unsupported environments . + * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral + * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral + */ + + if (node.raw === "null") { + return false; + } + + // regex is always truthy + if (typeof node.regex === "object") { + return true; + } + + return null; + } + + return !!node.value; + } /** * Checks if a branch node of LogicalExpression short circuits the whole condition @@ -66,15 +92,23 @@ module.exports = { function isLogicalIdentity(node, operator) { switch (node.type) { case "Literal": - return (operator === "||" && node.value === true) || - (operator === "&&" && node.value === false); + return (operator === "||" && getBooleanValue(node) === true) || + (operator === "&&" && getBooleanValue(node) === false); case "UnaryExpression": return (operator === "&&" && node.operator === "void"); case "LogicalExpression": - return isLogicalIdentity(node.left, node.operator) || - isLogicalIdentity(node.right, node.operator); + + /* + * handles `a && false || b` + * `false` is an identity element of `&&` but not `||` + */ + return operator === node.operator && + ( + isLogicalIdentity(node.left, node.operator) || + isLogicalIdentity(node.right, node.operator) + ); // no default } @@ -129,21 +163,9 @@ module.exports = { const isLeftConstant = isConstant(node.left, inBooleanPosition); const isRightConstant = isConstant(node.right, inBooleanPosition); const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); - const isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator)); + const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator)); return (isLeftConstant && isRightConstant) || - ( - - // in the case of an "OR", we need to know if the right constant value is truthy - node.operator === "||" && - isRightConstant && - node.right.value && - ( - !node.parent || - node.parent.type !== "BinaryExpression" || - !(EQUALITY_OPERATORS.includes(node.parent.operator) || RELATIONAL_OPERATORS.includes(node.parent.operator)) - ) - ) || isLeftShortCircuit || isRightShortCircuit; } diff --git a/tests/lib/rules/no-constant-condition.js b/tests/lib/rules/no-constant-condition.js index 2aa4fa73dff..b83b337a65f 100644 --- a/tests/lib/rules/no-constant-condition.js +++ b/tests/lib/rules/no-constant-condition.js @@ -74,12 +74,11 @@ ruleTester.run("no-constant-condition", rule, { "if(true && typeof abc==='string'){}", // #11181, string literals - "if('str' || a){}", "if('str1' && a){}", "if(a && 'str'){}", - "if('str' || abc==='str'){}", // #11306 + "if ((foo || true) === 'baz') {}", "if ((foo || 'bar') === 'baz') {}", "if ((foo || 'bar') !== 'baz') {}", "if ((foo || 'bar') == 'baz') {}", @@ -90,6 +89,19 @@ ruleTester.run("no-constant-condition", rule, { "if ((foo || 233) <= 666) {}", "if ((key || 'k') in obj) {}", "if ((foo || {}) instanceof obj) {}", + "if ((foo || 'bar' || 'bar') === 'bar');", + { + code: "if ((foo || 1n) === 'baz') {}", + parserOptions: { ecmaVersion: 11 } + }, + { + code: "if (a && 0n || b);", + parserOptions: { ecmaVersion: 11 } + }, + { + code: "if(1n && a){};", + parserOptions: { ecmaVersion: 11 } + }, // #12225 "if ('' + [y] === '' + [ty]) {}", @@ -121,11 +133,14 @@ ruleTester.run("no-constant-condition", rule, { { code: "for(;`foo`;);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "for(;`foo${bar}`;);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "do{}while(true)", errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "do{}while('1')", errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "do{}while(0)", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "do{}while(t = -2)", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] }, { code: "do{}while(``)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "do{}while(`foo`)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "do{}while(`foo${bar}`)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "true ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "1 ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "q = 0 ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "(q = 0) ? 1 : 2;", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] }, { code: "`` ? 1 : 2;", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, @@ -133,6 +148,7 @@ ruleTester.run("no-constant-condition", rule, { { code: "`foo${bar}` ? 1 : 2;", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "if(-2);", errors: [{ messageId: "unexpected", type: "UnaryExpression" }] }, { code: "if(true);", errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(1);", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "if({});", errors: [{ messageId: "unexpected", type: "ObjectExpression" }] }, { code: "if(0 < 1);", errors: [{ messageId: "unexpected", type: "BinaryExpression" }] }, { code: "if(0 || 1);", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, @@ -143,6 +159,7 @@ ruleTester.run("no-constant-condition", rule, { { code: "if(`${'bar'}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "if(`${'bar' + `foo`}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "if(`foo${false || true}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, + { code: "if(`foo${0 || 1}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "if(`foo${bar}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "if(`${bar}foo`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, @@ -152,6 +169,7 @@ ruleTester.run("no-constant-condition", rule, { { code: "while(x = 1);", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] }, { code: "while(function(){});", errors: [{ messageId: "unexpected", type: "FunctionExpression" }] }, { code: "while(true);", errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "while(1);", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "while(() => {});", errors: [{ messageId: "unexpected", type: "ArrowFunctionExpression" }] }, { code: "while(`foo`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, { code: "while(``);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, @@ -180,12 +198,15 @@ ruleTester.run("no-constant-condition", rule, { // #5693 { code: "if(false && abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if(true || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, + { code: "if(1 || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if(abc==='str' || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if(abc==='str' || true || def ==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if(false || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if(typeof abc==='str' || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, // #11181, string literals + { code: "if('str' || a){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, + { code: "if('str' || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if('str1' || 'str2'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if('str1' && 'str2'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if(abc==='str' || 'str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, @@ -272,6 +293,17 @@ ruleTester.run("no-constant-condition", rule, { { code: "if ([,] + ''){}", errors: [{ messageId: "unexpected", type: "BinaryExpression" }] - } + }, + + // #13238 + { code: "if(/foo/ui);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0b0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0o0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0x0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0b1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0o1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0x1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "if(0x1n || foo);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "LogicalExpression" }] } ] });