@@ -14,16 +14,38 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|"
14
14
// Helpers
15
15
//------------------------------------------------------------------------------
16
16
17
+ /**
18
+ * Checks whether or not a node is `null` or `undefined`. Similar to the one
19
+ * found in ast-utils.js, but this one correctly handles the edge case that
20
+ * `undefined` has been redefined.
21
+ * @param {Scope } scope Scope in which the expression was found.
22
+ * @param {ASTNode } node A node to check.
23
+ * @returns {boolean } Whether or not the node is a `null` or `undefined`.
24
+ * @public
25
+ */
26
+ function isNullOrUndefined ( scope , node ) {
27
+ return (
28
+ isNullLiteral ( node ) ||
29
+ ( node . type === "Identifier" && node . name === "undefined" && isReferenceToGlobalVariable ( scope , node ) ) ||
30
+ ( node . type === "UnaryExpression" && node . operator === "void" )
31
+ ) ;
32
+ }
33
+
17
34
/**
18
35
* Test if an AST node has a statically knowable constant nullishness. Meaning,
19
36
* it will always resolve to a constant value of either: `null`, `undefined`
20
37
* or not `null` _or_ `undefined`. An expression that can vary between those
21
38
* three states at runtime would return `false`.
22
39
* @param {Scope } scope The scope in which the node was found.
23
40
* @param {ASTNode } node The AST node being tested.
41
+ * @param {boolean } nonNullish if `true` then nullish values are not considered constant.
24
42
* @returns {boolean } Does `node` have constant nullishness?
25
43
*/
26
- function hasConstantNullishness ( scope , node ) {
44
+ function hasConstantNullishness ( scope , node , nonNullish ) {
45
+ if ( nonNullish && isNullOrUndefined ( scope , node ) ) {
46
+ return false ;
47
+ }
48
+
27
49
switch ( node . type ) {
28
50
case "ObjectExpression" : // Objects are never nullish
29
51
case "ArrayExpression" : // Arrays are never nullish
@@ -45,9 +67,12 @@ function hasConstantNullishness(scope, node) {
45
67
return ( functionName === "Boolean" || functionName === "String" || functionName === "Number" ) &&
46
68
isReferenceToGlobalVariable ( scope , node . callee ) ;
47
69
}
70
+ case "LogicalExpression" : {
71
+ return node . operator === "??" && hasConstantNullishness ( scope , node . right , true ) ;
72
+ }
48
73
case "AssignmentExpression" :
49
74
if ( node . operator === "=" ) {
50
- return hasConstantNullishness ( scope , node . right ) ;
75
+ return hasConstantNullishness ( scope , node . right , nonNullish ) ;
51
76
}
52
77
53
78
/*
@@ -80,7 +105,7 @@ function hasConstantNullishness(scope, node) {
80
105
case "SequenceExpression" : {
81
106
const last = node . expressions [ node . expressions . length - 1 ] ;
82
107
83
- return hasConstantNullishness ( scope , last ) ;
108
+ return hasConstantNullishness ( scope , last , nonNullish ) ;
84
109
}
85
110
case "Identifier" :
86
111
return node . name === "undefined" && isReferenceToGlobalVariable ( scope , node ) ;
@@ -378,24 +403,6 @@ function isAlwaysNew(scope, node) {
378
403
}
379
404
}
380
405
381
- /**
382
- * Checks whether or not a node is `null` or `undefined`. Similar to the one
383
- * found in ast-utils.js, but this one correctly handles the edge case that
384
- * `undefined` has been redefined.
385
- * @param {Scope } scope Scope in which the expression was found.
386
- * @param {ASTNode } node A node to check.
387
- * @returns {boolean } Whether or not the node is a `null` or `undefined`.
388
- * @public
389
- */
390
- function isNullOrUndefined ( scope , node ) {
391
- return (
392
- isNullLiteral ( node ) ||
393
- ( node . type === "Identifier" && node . name === "undefined" && isReferenceToGlobalVariable ( scope , node ) ) ||
394
- ( node . type === "UnaryExpression" && node . operator === "void" )
395
- ) ;
396
- }
397
-
398
-
399
406
/**
400
407
* Checks if one operand will cause the result to be constant.
401
408
* @param {Scope } scope Scope in which the expression was found.
@@ -407,14 +414,14 @@ function isNullOrUndefined(scope, node) {
407
414
function findBinaryExpressionConstantOperand ( scope , a , b , operator ) {
408
415
if ( operator === "==" || operator === "!=" ) {
409
416
if (
410
- ( isNullOrUndefined ( scope , a ) && hasConstantNullishness ( scope , b ) ) ||
417
+ ( isNullOrUndefined ( scope , a ) && hasConstantNullishness ( scope , b , false ) ) ||
411
418
( isStaticBoolean ( scope , a ) && hasConstantLooseBooleanComparison ( scope , b ) )
412
419
) {
413
420
return b ;
414
421
}
415
422
} else if ( operator === "===" || operator === "!==" ) {
416
423
if (
417
- ( isNullOrUndefined ( scope , a ) && hasConstantNullishness ( scope , b ) ) ||
424
+ ( isNullOrUndefined ( scope , a ) && hasConstantNullishness ( scope , b , false ) ) ||
418
425
( isStaticBoolean ( scope , a ) && hasConstantStrictBooleanComparison ( scope , b ) )
419
426
) {
420
427
return b ;
@@ -453,7 +460,7 @@ module.exports = {
453
460
454
461
if ( ( operator === "&&" || operator === "||" ) && isConstant ( scope , left , true ) ) {
455
462
context . report ( { node : left , messageId : "constantShortCircuit" , data : { property : "truthiness" , operator } } ) ;
456
- } else if ( operator === "??" && hasConstantNullishness ( scope , left ) ) {
463
+ } else if ( operator === "??" && hasConstantNullishness ( scope , left , false ) ) {
457
464
context . report ( { node : left , messageId : "constantShortCircuit" , data : { property : "nullishness" , operator } } ) ;
458
465
}
459
466
} ,
0 commit comments