Skip to content

Commit e002159

Browse files
authoredSep 20, 2022
feat(49962): Disallow comparison against NaN (#50626)
* feat(49962): disallow comparison against NaN * change diagnostic message * use global NaN symbol for NaN equality comparisons
1 parent 23746af commit e002159

17 files changed

+651
-10
lines changed
 

‎src/compiler/checker.ts

+29
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,7 @@ namespace ts {
999999
let deferredGlobalOmitSymbol: Symbol | undefined;
10001000
let deferredGlobalAwaitedSymbol: Symbol | undefined;
10011001
let deferredGlobalBigIntType: ObjectType | undefined;
1002+
let deferredGlobalNaNSymbol: Symbol | undefined;
10021003
let deferredGlobalRecordSymbol: Symbol | undefined;
10031004

10041005
const allPotentiallyUnusedIdentifiers = new Map<Path, PotentiallyUnusedIdentifier[]>(); // key is file name
@@ -14343,6 +14344,10 @@ namespace ts {
1434314344
return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType;
1434414345
}
1434514346

14347+
function getGlobalNaNSymbol(): Symbol | undefined {
14348+
return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false));
14349+
}
14350+
1434614351
function getGlobalRecordSymbol(): Symbol | undefined {
1434714352
deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol;
1434814353
return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol;
@@ -34495,6 +34500,7 @@ namespace ts {
3449534500
const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
3449634501
error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true");
3449734502
}
34503+
checkNaNEquality(errorNode, operator, left, right);
3449834504
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
3449934505
return booleanType;
3450034506

@@ -34727,6 +34733,29 @@ namespace ts {
3472734733
return undefined;
3472834734
}
3472934735
}
34736+
34737+
function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) {
34738+
const isLeftNaN = isGlobalNaN(skipParentheses(left));
34739+
const isRightNaN = isGlobalNaN(skipParentheses(right));
34740+
if (isLeftNaN || isRightNaN) {
34741+
const err = error(errorNode, Diagnostics.This_condition_will_always_return_0,
34742+
tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword));
34743+
if (isLeftNaN && isRightNaN) return;
34744+
const operatorString = operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? tokenToString(SyntaxKind.ExclamationToken) : "";
34745+
const location = isLeftNaN ? right : left;
34746+
const expression = skipParentheses(location);
34747+
addRelatedInfo(err, createDiagnosticForNode(location, Diagnostics.Did_you_mean_0,
34748+
`${operatorString}Number.isNaN(${isEntityNameExpression(expression) ? entityNameToString(expression) : "..."})`));
34749+
}
34750+
}
34751+
34752+
function isGlobalNaN(expr: Expression): boolean {
34753+
if (isIdentifier(expr) && expr.escapedText === "NaN") {
34754+
const globalNaNSymbol = getGlobalNaNSymbol();
34755+
return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr);
34756+
}
34757+
return false;
34758+
}
3473034759
}
3473134760

3473234761
function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] {

‎src/compiler/diagnosticMessages.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -3563,6 +3563,10 @@
35633563
"category": "Error",
35643564
"code": 2844
35653565
},
3566+
"This condition will always return '{0}'.": {
3567+
"category": "Error",
3568+
"code": 2845
3569+
},
35663570

35673571
"Import declaration '{0}' is using private name '{1}'.": {
35683572
"category": "Error",
@@ -7356,7 +7360,14 @@
73567360
"category": "Message",
73577361
"code": 95173
73587362
},
7359-
7363+
"Use `{0}`.": {
7364+
"category": "Message",
7365+
"code": 95174
7366+
},
7367+
"Use `Number.isNaN` in all conditions.": {
7368+
"category": "Message",
7369+
"code": 95175
7370+
},
73607371

73617372
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
73627373
"category": "Error",

‎src/services/codefixes/fixAddMissingConstraint.ts

-9
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,6 @@ namespace ts.codefix {
9494
}
9595
}
9696

97-
function findAncestorMatchingSpan(sourceFile: SourceFile, span: TextSpan): Node {
98-
const end = textSpanEnd(span);
99-
let token = getTokenAtPosition(sourceFile, span.start);
100-
while (token.end < end) {
101-
token = token.parent;
102-
}
103-
return token;
104-
}
105-
10697
function tryGetConstraintFromDiagnosticMessage(messageText: string | DiagnosticMessageChain) {
10798
const [_, constraint] = flattenDiagnosticMessageText(messageText, "\n", 0).match(/`extends (.*)`/) || [];
10899
return constraint;
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "fixNaNEquality";
4+
const errorCodes = [
5+
Diagnostics.This_condition_will_always_return_0.code,
6+
];
7+
8+
registerCodeFix({
9+
errorCodes,
10+
getCodeActions(context) {
11+
const { sourceFile, span, program } = context;
12+
const info = getInfo(program, sourceFile, span);
13+
if (info === undefined) return;
14+
15+
const { suggestion, expression, arg } = info;
16+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, arg, expression));
17+
return [createCodeFixAction(fixId, changes, [Diagnostics.Use_0, suggestion], fixId, Diagnostics.Use_Number_isNaN_in_all_conditions)];
18+
},
19+
fixIds: [fixId],
20+
getAllCodeActions: context => {
21+
return codeFixAll(context, errorCodes, (changes, diag) => {
22+
const info = getInfo(context.program, diag.file, createTextSpan(diag.start, diag.length));
23+
if (info) {
24+
doChange(changes, diag.file, info.arg, info.expression);
25+
}
26+
});
27+
}
28+
});
29+
30+
interface Info {
31+
suggestion: string;
32+
expression: BinaryExpression;
33+
arg: Expression;
34+
}
35+
36+
function getInfo(program: Program, sourceFile: SourceFile, span: TextSpan): Info | undefined {
37+
const diag = find(program.getSemanticDiagnostics(sourceFile), diag => diag.start === span.start && diag.length === span.length);
38+
if (diag === undefined || diag.relatedInformation === undefined) return;
39+
40+
const related = find(diag.relatedInformation, related => related.code === Diagnostics.Did_you_mean_0.code);
41+
if (related === undefined || related.file === undefined || related.start === undefined || related.length === undefined) return;
42+
43+
const token = findAncestorMatchingSpan(related.file, createTextSpan(related.start, related.length));
44+
if (token === undefined) return;
45+
46+
if (isExpression(token) && isBinaryExpression(token.parent)) {
47+
return { suggestion: getSuggestion(related.messageText), expression: token.parent, arg: token };
48+
}
49+
return undefined;
50+
}
51+
52+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, arg: Expression, expression: BinaryExpression) {
53+
const callExpression = factory.createCallExpression(
54+
factory.createPropertyAccessExpression(factory.createIdentifier("Number"), factory.createIdentifier("isNaN")), /*typeArguments*/ undefined, [arg]);
55+
const operator = expression.operatorToken.kind ;
56+
changes.replaceNode(sourceFile, expression,
57+
operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken
58+
? factory.createPrefixUnaryExpression(SyntaxKind.ExclamationToken, callExpression) : callExpression);
59+
}
60+
61+
function getSuggestion(messageText: string | DiagnosticMessageChain) {
62+
const [_, suggestion] = flattenDiagnosticMessageText(messageText, "\n", 0).match(/\'(.*)\'/) || [];
63+
return suggestion;
64+
}
65+
}

‎src/services/codefixes/helpers.ts

+9
Original file line numberDiff line numberDiff line change
@@ -737,4 +737,13 @@ namespace ts.codefix {
737737
export function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) {
738738
symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*isValidTypeOnlyUseSite*/ true));
739739
}
740+
741+
export function findAncestorMatchingSpan(sourceFile: SourceFile, span: TextSpan): Node {
742+
const end = textSpanEnd(span);
743+
let token = getTokenAtPosition(sourceFile, span.start);
744+
while (token.end < end) {
745+
token = token.parent;
746+
}
747+
return token;
748+
}
740749
}

‎src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"codefixes/fixConstructorForDerivedNeedSuperCall.ts",
8383
"codefixes/fixEnableExperimentalDecorators.ts",
8484
"codefixes/fixEnableJsxFlag.ts",
85+
"codefixes/fixNaNEquality.ts",
8586
"codefixes/fixModuleAndTargetOptions.ts",
8687
"codefixes/fixPropertyAssignment.ts",
8788
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
tests/cases/compiler/nanEquality.ts(3,5): error TS2845: This condition will always return 'false'.
2+
tests/cases/compiler/nanEquality.ts(4,5): error TS2845: This condition will always return 'false'.
3+
tests/cases/compiler/nanEquality.ts(6,5): error TS2845: This condition will always return 'false'.
4+
tests/cases/compiler/nanEquality.ts(7,5): error TS2845: This condition will always return 'false'.
5+
tests/cases/compiler/nanEquality.ts(9,5): error TS2845: This condition will always return 'true'.
6+
tests/cases/compiler/nanEquality.ts(10,5): error TS2845: This condition will always return 'true'.
7+
tests/cases/compiler/nanEquality.ts(12,5): error TS2845: This condition will always return 'true'.
8+
tests/cases/compiler/nanEquality.ts(13,5): error TS2845: This condition will always return 'true'.
9+
tests/cases/compiler/nanEquality.ts(15,5): error TS2845: This condition will always return 'false'.
10+
tests/cases/compiler/nanEquality.ts(16,5): error TS2845: This condition will always return 'false'.
11+
tests/cases/compiler/nanEquality.ts(18,5): error TS2845: This condition will always return 'true'.
12+
tests/cases/compiler/nanEquality.ts(19,5): error TS2845: This condition will always return 'true'.
13+
tests/cases/compiler/nanEquality.ts(21,5): error TS2845: This condition will always return 'false'.
14+
tests/cases/compiler/nanEquality.ts(22,5): error TS2845: This condition will always return 'true'.
15+
tests/cases/compiler/nanEquality.ts(24,5): error TS2845: This condition will always return 'false'.
16+
tests/cases/compiler/nanEquality.ts(25,5): error TS2845: This condition will always return 'true'.
17+
tests/cases/compiler/nanEquality.ts(29,5): error TS2845: This condition will always return 'false'.
18+
19+
20+
==== tests/cases/compiler/nanEquality.ts (17 errors) ====
21+
declare const x: number;
22+
23+
if (x === NaN) {}
24+
~~~~~~~~~
25+
!!! error TS2845: This condition will always return 'false'.
26+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:3:5: Did you mean 'Number.isNaN(x)'?
27+
if (NaN === x) {}
28+
~~~~~~~~~
29+
!!! error TS2845: This condition will always return 'false'.
30+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:4:13: Did you mean 'Number.isNaN(x)'?
31+
32+
if (x == NaN) {}
33+
~~~~~~~~
34+
!!! error TS2845: This condition will always return 'false'.
35+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:6:5: Did you mean 'Number.isNaN(x)'?
36+
if (NaN == x) {}
37+
~~~~~~~~
38+
!!! error TS2845: This condition will always return 'false'.
39+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:7:12: Did you mean 'Number.isNaN(x)'?
40+
41+
if (x !== NaN) {}
42+
~~~~~~~~~
43+
!!! error TS2845: This condition will always return 'true'.
44+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:9:5: Did you mean '!Number.isNaN(x)'?
45+
if (NaN !== x) {}
46+
~~~~~~~~~
47+
!!! error TS2845: This condition will always return 'true'.
48+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:10:13: Did you mean '!Number.isNaN(x)'?
49+
50+
if (x != NaN) {}
51+
~~~~~~~~
52+
!!! error TS2845: This condition will always return 'true'.
53+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:12:5: Did you mean '!Number.isNaN(x)'?
54+
if (NaN != x) {}
55+
~~~~~~~~
56+
!!! error TS2845: This condition will always return 'true'.
57+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:13:12: Did you mean '!Number.isNaN(x)'?
58+
59+
if (x === ((NaN))) {}
60+
~~~~~~~~~~~~~
61+
!!! error TS2845: This condition will always return 'false'.
62+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:15:5: Did you mean 'Number.isNaN(x)'?
63+
if (((NaN)) === x) {}
64+
~~~~~~~~~~~~~
65+
!!! error TS2845: This condition will always return 'false'.
66+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:16:17: Did you mean 'Number.isNaN(x)'?
67+
68+
if (x !== ((NaN))) {}
69+
~~~~~~~~~~~~~
70+
!!! error TS2845: This condition will always return 'true'.
71+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:18:5: Did you mean '!Number.isNaN(x)'?
72+
if (((NaN)) !== x) {}
73+
~~~~~~~~~~~~~
74+
!!! error TS2845: This condition will always return 'true'.
75+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:19:17: Did you mean '!Number.isNaN(x)'?
76+
77+
if (NaN === NaN) {}
78+
~~~~~~~~~~~
79+
!!! error TS2845: This condition will always return 'false'.
80+
if (NaN !== NaN) {}
81+
~~~~~~~~~~~
82+
!!! error TS2845: This condition will always return 'true'.
83+
84+
if (NaN == NaN) {}
85+
~~~~~~~~~~
86+
!!! error TS2845: This condition will always return 'false'.
87+
if (NaN != NaN) {}
88+
~~~~~~~~~~
89+
!!! error TS2845: This condition will always return 'true'.
90+
91+
// ...
92+
declare let y: any;
93+
if (NaN === y[0][1]) {}
94+
~~~~~~~~~~~~~~~
95+
!!! error TS2845: This condition will always return 'false'.
96+
!!! related TS1369 tests/cases/compiler/nanEquality.ts:29:13: Did you mean 'Number.isNaN(...)'?
97+
98+
function t1(value: number, NaN: number) {
99+
return value === NaN; // ok
100+
}
101+
102+
function t2(value: number, NaN: number) {
103+
return NaN == value; // ok
104+
}
105+
106+
function t3(NaN: number) {
107+
return NaN === NaN; // ok
108+
}
109+
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//// [nanEquality.ts]
2+
declare const x: number;
3+
4+
if (x === NaN) {}
5+
if (NaN === x) {}
6+
7+
if (x == NaN) {}
8+
if (NaN == x) {}
9+
10+
if (x !== NaN) {}
11+
if (NaN !== x) {}
12+
13+
if (x != NaN) {}
14+
if (NaN != x) {}
15+
16+
if (x === ((NaN))) {}
17+
if (((NaN)) === x) {}
18+
19+
if (x !== ((NaN))) {}
20+
if (((NaN)) !== x) {}
21+
22+
if (NaN === NaN) {}
23+
if (NaN !== NaN) {}
24+
25+
if (NaN == NaN) {}
26+
if (NaN != NaN) {}
27+
28+
// ...
29+
declare let y: any;
30+
if (NaN === y[0][1]) {}
31+
32+
function t1(value: number, NaN: number) {
33+
return value === NaN; // ok
34+
}
35+
36+
function t2(value: number, NaN: number) {
37+
return NaN == value; // ok
38+
}
39+
40+
function t3(NaN: number) {
41+
return NaN === NaN; // ok
42+
}
43+
44+
45+
//// [nanEquality.js]
46+
if (x === NaN) { }
47+
if (NaN === x) { }
48+
if (x == NaN) { }
49+
if (NaN == x) { }
50+
if (x !== NaN) { }
51+
if (NaN !== x) { }
52+
if (x != NaN) { }
53+
if (NaN != x) { }
54+
if (x === ((NaN))) { }
55+
if (((NaN)) === x) { }
56+
if (x !== ((NaN))) { }
57+
if (((NaN)) !== x) { }
58+
if (NaN === NaN) { }
59+
if (NaN !== NaN) { }
60+
if (NaN == NaN) { }
61+
if (NaN != NaN) { }
62+
if (NaN === y[0][1]) { }
63+
function t1(value, NaN) {
64+
return value === NaN; // ok
65+
}
66+
function t2(value, NaN) {
67+
return NaN == value; // ok
68+
}
69+
function t3(NaN) {
70+
return NaN === NaN; // ok
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
=== tests/cases/compiler/nanEquality.ts ===
2+
declare const x: number;
3+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
4+
5+
if (x === NaN) {}
6+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
7+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
8+
9+
if (NaN === x) {}
10+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
11+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
12+
13+
if (x == NaN) {}
14+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
15+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
16+
17+
if (NaN == x) {}
18+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
19+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
20+
21+
if (x !== NaN) {}
22+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
23+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
24+
25+
if (NaN !== x) {}
26+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
27+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
28+
29+
if (x != NaN) {}
30+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
31+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
32+
33+
if (NaN != x) {}
34+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
35+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
36+
37+
if (x === ((NaN))) {}
38+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
39+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
40+
41+
if (((NaN)) === x) {}
42+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
43+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
44+
45+
if (x !== ((NaN))) {}
46+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
47+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
48+
49+
if (((NaN)) !== x) {}
50+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
51+
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
52+
53+
if (NaN === NaN) {}
54+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
55+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
56+
57+
if (NaN !== NaN) {}
58+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
59+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
60+
61+
if (NaN == NaN) {}
62+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
63+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
64+
65+
if (NaN != NaN) {}
66+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
67+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
68+
69+
// ...
70+
declare let y: any;
71+
>y : Symbol(y, Decl(nanEquality.ts, 27, 11))
72+
73+
if (NaN === y[0][1]) {}
74+
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
75+
>y : Symbol(y, Decl(nanEquality.ts, 27, 11))
76+
77+
function t1(value: number, NaN: number) {
78+
>t1 : Symbol(t1, Decl(nanEquality.ts, 28, 23))
79+
>value : Symbol(value, Decl(nanEquality.ts, 30, 12))
80+
>NaN : Symbol(NaN, Decl(nanEquality.ts, 30, 26))
81+
82+
return value === NaN; // ok
83+
>value : Symbol(value, Decl(nanEquality.ts, 30, 12))
84+
>NaN : Symbol(NaN, Decl(nanEquality.ts, 30, 26))
85+
}
86+
87+
function t2(value: number, NaN: number) {
88+
>t2 : Symbol(t2, Decl(nanEquality.ts, 32, 1))
89+
>value : Symbol(value, Decl(nanEquality.ts, 34, 12))
90+
>NaN : Symbol(NaN, Decl(nanEquality.ts, 34, 26))
91+
92+
return NaN == value; // ok
93+
>NaN : Symbol(NaN, Decl(nanEquality.ts, 34, 26))
94+
>value : Symbol(value, Decl(nanEquality.ts, 34, 12))
95+
}
96+
97+
function t3(NaN: number) {
98+
>t3 : Symbol(t3, Decl(nanEquality.ts, 36, 1))
99+
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
100+
101+
return NaN === NaN; // ok
102+
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
103+
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
104+
}
105+
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
=== tests/cases/compiler/nanEquality.ts ===
2+
declare const x: number;
3+
>x : number
4+
5+
if (x === NaN) {}
6+
>x === NaN : boolean
7+
>x : number
8+
>NaN : number
9+
10+
if (NaN === x) {}
11+
>NaN === x : boolean
12+
>NaN : number
13+
>x : number
14+
15+
if (x == NaN) {}
16+
>x == NaN : boolean
17+
>x : number
18+
>NaN : number
19+
20+
if (NaN == x) {}
21+
>NaN == x : boolean
22+
>NaN : number
23+
>x : number
24+
25+
if (x !== NaN) {}
26+
>x !== NaN : boolean
27+
>x : number
28+
>NaN : number
29+
30+
if (NaN !== x) {}
31+
>NaN !== x : boolean
32+
>NaN : number
33+
>x : number
34+
35+
if (x != NaN) {}
36+
>x != NaN : boolean
37+
>x : number
38+
>NaN : number
39+
40+
if (NaN != x) {}
41+
>NaN != x : boolean
42+
>NaN : number
43+
>x : number
44+
45+
if (x === ((NaN))) {}
46+
>x === ((NaN)) : boolean
47+
>x : number
48+
>((NaN)) : number
49+
>(NaN) : number
50+
>NaN : number
51+
52+
if (((NaN)) === x) {}
53+
>((NaN)) === x : boolean
54+
>((NaN)) : number
55+
>(NaN) : number
56+
>NaN : number
57+
>x : number
58+
59+
if (x !== ((NaN))) {}
60+
>x !== ((NaN)) : boolean
61+
>x : number
62+
>((NaN)) : number
63+
>(NaN) : number
64+
>NaN : number
65+
66+
if (((NaN)) !== x) {}
67+
>((NaN)) !== x : boolean
68+
>((NaN)) : number
69+
>(NaN) : number
70+
>NaN : number
71+
>x : number
72+
73+
if (NaN === NaN) {}
74+
>NaN === NaN : boolean
75+
>NaN : number
76+
>NaN : number
77+
78+
if (NaN !== NaN) {}
79+
>NaN !== NaN : boolean
80+
>NaN : number
81+
>NaN : number
82+
83+
if (NaN == NaN) {}
84+
>NaN == NaN : boolean
85+
>NaN : number
86+
>NaN : number
87+
88+
if (NaN != NaN) {}
89+
>NaN != NaN : boolean
90+
>NaN : number
91+
>NaN : number
92+
93+
// ...
94+
declare let y: any;
95+
>y : any
96+
97+
if (NaN === y[0][1]) {}
98+
>NaN === y[0][1] : boolean
99+
>NaN : number
100+
>y[0][1] : any
101+
>y[0] : any
102+
>y : any
103+
>0 : 0
104+
>1 : 1
105+
106+
function t1(value: number, NaN: number) {
107+
>t1 : (value: number, NaN: number) => boolean
108+
>value : number
109+
>NaN : number
110+
111+
return value === NaN; // ok
112+
>value === NaN : boolean
113+
>value : number
114+
>NaN : number
115+
}
116+
117+
function t2(value: number, NaN: number) {
118+
>t2 : (value: number, NaN: number) => boolean
119+
>value : number
120+
>NaN : number
121+
122+
return NaN == value; // ok
123+
>NaN == value : boolean
124+
>NaN : number
125+
>value : number
126+
}
127+
128+
function t3(NaN: number) {
129+
>t3 : (NaN: number) => boolean
130+
>NaN : number
131+
132+
return NaN === NaN; // ok
133+
>NaN === NaN : boolean
134+
>NaN : number
135+
>NaN : number
136+
}
137+

‎tests/cases/compiler/nanEquality.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
declare const x: number;
2+
3+
if (x === NaN) {}
4+
if (NaN === x) {}
5+
6+
if (x == NaN) {}
7+
if (NaN == x) {}
8+
9+
if (x !== NaN) {}
10+
if (NaN !== x) {}
11+
12+
if (x != NaN) {}
13+
if (NaN != x) {}
14+
15+
if (x === ((NaN))) {}
16+
if (((NaN)) === x) {}
17+
18+
if (x !== ((NaN))) {}
19+
if (((NaN)) !== x) {}
20+
21+
if (NaN === NaN) {}
22+
if (NaN !== NaN) {}
23+
24+
if (NaN == NaN) {}
25+
if (NaN != NaN) {}
26+
27+
// ...
28+
declare let y: any;
29+
if (NaN === y[0][1]) {}
30+
31+
function t1(value: number, NaN: number) {
32+
return value === NaN; // ok
33+
}
34+
35+
function t2(value: number, NaN: number) {
36+
return NaN == value; // ok
37+
}
38+
39+
function t3(NaN: number) {
40+
return NaN === NaN; // ok
41+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare const x: number;
4+
////[|if (x === NaN) {}|]
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: "Use `Number.isNaN(x)`.",
9+
newRangeContent: "if (Number.isNaN(x)) {}",
10+
});
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare const x: number;
4+
////[|if (NaN === x) {}|]
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: "Use `Number.isNaN(x)`.",
9+
newRangeContent: "if (Number.isNaN(x)) {}",
10+
});
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare const x: number;
4+
////[|if (x !== NaN) {}|]
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: "Use `!Number.isNaN(x)`.",
9+
newRangeContent: "if (!Number.isNaN(x)) {}",
10+
});
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare const x: number;
4+
////[|if (NaN !== x) {}|]
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: "Use `!Number.isNaN(x)`.",
9+
newRangeContent: "if (!Number.isNaN(x)) {}",
10+
});
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare const x: any;
4+
////[|if (NaN !== x[0][1]) {}|]
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: "Use `!Number.isNaN(...)`.",
9+
newRangeContent: "if (!Number.isNaN(x[0][1])) {}",
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare const x: number;
4+
////declare const y: any;
5+
////if (x === NaN) {}
6+
////if (NaN === x) {}
7+
////if (x !== NaN) {}
8+
////if (NaN !== x) {}
9+
////if (NaN === y[0][1]) {}
10+
11+
verify.codeFixAll({
12+
fixId: "fixNaNEquality",
13+
fixAllDescription: ts.Diagnostics.Use_Number_isNaN_in_all_conditions.message,
14+
newFileContent:
15+
`declare const x: number;
16+
declare const y: any;
17+
if (Number.isNaN(x)) {}
18+
if (Number.isNaN(x)) {}
19+
if (!Number.isNaN(x)) {}
20+
if (!Number.isNaN(x)) {}
21+
if (Number.isNaN(y[0][1])) {}`
22+
});

0 commit comments

Comments
 (0)
Please sign in to comment.