From 8f8d839a5dd5f29c51be2c3701d25961e8f82ee8 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Feb 2022 22:25:22 -0800 Subject: [PATCH] feat: Catch more cases in no-constant-condition Identify `undefined` and `Boolean(somethingConstant)` as both being constant. --- lib/rules/no-constant-condition.js | 31 +++++++++++++++++++++--- tests/lib/rules/no-constant-condition.js | 26 ++++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 8adc9bca4db6..d5a990f78a9f 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -120,12 +120,28 @@ module.exports = { return false; } + /** + * Checks if a identifier has been defined in this scope. + * @param {AstNode} node An identifier node to check. + * @returns {boolean} Is the identifier unresolved? + */ + function identifierIsUnresolved(node) { + const scope = context.getScope(); + const reference = scope.references.find(ref => ref.identifier === node); + + return ( + typeof reference === "undefined" || + reference.resolved === null || + reference.resolved.defs.length === 0); + } + /** * Checks if a node has a constant truthiness value. * @param {ASTNode} node The AST node to check. - * @param {boolean} inBooleanPosition `false` if checking branch of a condition. - * `true` in all other cases. When `false`, checks if -- for both string and - * number -- if coerced to that type, the value will be constant. + * @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 {Bool} true when node's truthiness is constant * @private */ @@ -215,6 +231,15 @@ module.exports = { return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition); case "SpreadElement": return isConstant(node.argument, inBooleanPosition); + case "CallExpression": + if (node.callee.type === "Identifier" && node.callee.name === "Boolean") { + if (node.arguments.length === 0 || isConstant(node.arguments[0], true)) { + return identifierIsUnresolved(node.callee); + } + } + return false; + case "Identifier": + return node.name === "undefined" && identifierIsUnresolved(node); // no default } diff --git a/tests/lib/rules/no-constant-condition.js b/tests/lib/rules/no-constant-condition.js index 9cca27b904d2..4ced3aa2d947 100644 --- a/tests/lib/rules/no-constant-condition.js +++ b/tests/lib/rules/no-constant-condition.js @@ -185,7 +185,15 @@ ruleTester.run("no-constant-condition", rule, { "if (`${[a]}`) {}", "if (+[a]) {}", "if (0 - [a]) {}", - "if (1 * [a]) {}" + "if (1 * [a]) {}", + + // Boolean function + "if (Boolean(a)) {}", + "if (Boolean(...args)) {}", + "if (foo.Boolean(1)) {}", + "function foo(Boolean) { if (Boolean(1)) {} }", + "const Boolean = () => {}; if (Boolean(1)) {}", + "const undefined = 'lol'; if (undefined) {}" ], invalid: [ { code: "for(;true;);", errors: [{ messageId: "unexpected", type: "Literal" }] }, @@ -396,6 +404,20 @@ ruleTester.run("no-constant-condition", rule, { { code: "if(new Number(foo)) {}", errors: [{ messageId: "unexpected" }] }, // Spreading a constant array - { code: "if(`${[...['a']]}`) {}", errors: [{ messageId: "unexpected" }] } + { code: "if(`${[...['a']]}`) {}", errors: [{ messageId: "unexpected" }] }, + + /* + * undefined is always falsy (except in old browsers that let you + * re-assign, but that's an abscure enough edge case to not worry about) + */ + { code: "if (undefined) {}", errors: [{ messageId: "unexpected" }] }, + { code: "if (undefined) {}", globals: { undefined: "off" }, errors: [{ messageId: "unexpected" }] }, + + // Coercion to boolean via Boolean function + { code: "if (Boolean(1)) {}", errors: [{ messageId: "unexpected" }] }, + { code: "if (Boolean()) {}", errors: [{ messageId: "unexpected" }] }, + { code: "if (Boolean([a])) {}", errors: [{ messageId: "unexpected" }] }, + { code: "if (Boolean(1)) { function Boolean() {}}", errors: [{ messageId: "unexpected" }] }, + { code: "if (Boolean()) {}", globals: { Boolean: "off" }, errors: [{ messageId: "unexpected" }] } ] });