Skip to content

Commit 8933fe7

Browse files
authored
feat: Catch undefined and Boolean() in no-constant-condition (#15613)
Identify `undefined` and `Boolean(somethingConstant)` as both being constant.
1 parent d2255db commit 8933fe7

3 files changed

Lines changed: 62 additions & 5 deletions

File tree

docs/rules/no-constant-condition.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ if (new Boolean(x)) {
4242
doSomethingAlways();
4343
}
4444

45+
if (Boolean(1)) {
46+
doSomethingAlways();
47+
}
48+
49+
if (undefined) {
50+
doSomethingUnfinished();
51+
}
52+
4553
if (x ||= true) {
4654
doSomethingAlways();
4755
}

lib/rules/no-constant-condition.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,30 @@ module.exports = {
120120
return false;
121121
}
122122

123+
/**
124+
* Checks if an identifier is a reference to a global variable.
125+
* @param {ASTNode} node An identifier node to check.
126+
* @returns {boolean} `true` if the identifier is a reference to a global variable.
127+
*/
128+
function isReferenceToGlobalVariable(node) {
129+
const scope = context.getScope();
130+
const reference = scope.references.find(ref => ref.identifier === node);
131+
132+
return Boolean(
133+
reference &&
134+
reference.resolved &&
135+
reference.resolved.scope.type === "global" &&
136+
reference.resolved.defs.length === 0
137+
);
138+
}
139+
123140
/**
124141
* Checks if a node has a constant truthiness value.
125142
* @param {ASTNode} node The AST node to check.
126-
* @param {boolean} inBooleanPosition `false` if checking branch of a condition.
127-
* `true` in all other cases. When `false`, checks if -- for both string and
128-
* number -- if coerced to that type, the value will be constant.
143+
* @param {boolean} inBooleanPosition `true` if checking the test of a
144+
* condition. `false` in all other cases. When `false`, checks if -- for
145+
* both string and number -- if coerced to that type, the value will
146+
* be constant.
129147
* @returns {Bool} true when node's truthiness is constant
130148
* @private
131149
*/
@@ -215,6 +233,15 @@ module.exports = {
215233
return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
216234
case "SpreadElement":
217235
return isConstant(node.argument, inBooleanPosition);
236+
case "CallExpression":
237+
if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
238+
if (node.arguments.length === 0 || isConstant(node.arguments[0], true)) {
239+
return isReferenceToGlobalVariable(node.callee);
240+
}
241+
}
242+
return false;
243+
case "Identifier":
244+
return node.name === "undefined" && isReferenceToGlobalVariable(node);
218245

219246
// no default
220247
}

tests/lib/rules/no-constant-condition.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,17 @@ ruleTester.run("no-constant-condition", rule, {
185185
"if (`${[a]}`) {}",
186186
"if (+[a]) {}",
187187
"if (0 - [a]) {}",
188-
"if (1 * [a]) {}"
188+
"if (1 * [a]) {}",
189+
190+
// Boolean function
191+
"if (Boolean(a)) {}",
192+
"if (Boolean(...args)) {}",
193+
"if (foo.Boolean(1)) {}",
194+
"function foo(Boolean) { if (Boolean(1)) {} }",
195+
"const Boolean = () => {}; if (Boolean(1)) {}",
196+
{ code: "if (Boolean()) {}", globals: { Boolean: "off" } },
197+
"const undefined = 'lol'; if (undefined) {}",
198+
{ code: "if (undefined) {}", globals: { undefined: "off" } }
189199
],
190200
invalid: [
191201
{ code: "for(;true;);", errors: [{ messageId: "unexpected", type: "Literal" }] },
@@ -396,6 +406,18 @@ ruleTester.run("no-constant-condition", rule, {
396406
{ code: "if(new Number(foo)) {}", errors: [{ messageId: "unexpected" }] },
397407

398408
// Spreading a constant array
399-
{ code: "if(`${[...['a']]}`) {}", errors: [{ messageId: "unexpected" }] }
409+
{ code: "if(`${[...['a']]}`) {}", errors: [{ messageId: "unexpected" }] },
410+
411+
/*
412+
* undefined is always falsy (except in old browsers that let you
413+
* re-assign, but that's an abscure enough edge case to not worry about)
414+
*/
415+
{ code: "if (undefined) {}", errors: [{ messageId: "unexpected" }] },
416+
417+
// Coercion to boolean via Boolean function
418+
{ code: "if (Boolean(1)) {}", errors: [{ messageId: "unexpected" }] },
419+
{ code: "if (Boolean()) {}", errors: [{ messageId: "unexpected" }] },
420+
{ code: "if (Boolean([a])) {}", errors: [{ messageId: "unexpected" }] },
421+
{ code: "if (Boolean(1)) { function Boolean() {}}", errors: [{ messageId: "unexpected" }] }
400422
]
401423
});

0 commit comments

Comments
 (0)