feat(49962): Disallow comparison against NaN (#50626)

* feat(49962): disallow comparison against NaN

* change diagnostic message

* use global NaN symbol for NaN equality comparisons
This commit is contained in:
Oleksandr T 2022-09-20 23:16:44 +03:00 committed by GitHub
parent 23746af766
commit e002159ad1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 651 additions and 10 deletions

View File

@ -999,6 +999,7 @@ namespace ts {
let deferredGlobalOmitSymbol: Symbol | undefined;
let deferredGlobalAwaitedSymbol: Symbol | undefined;
let deferredGlobalBigIntType: ObjectType | undefined;
let deferredGlobalNaNSymbol: Symbol | undefined;
let deferredGlobalRecordSymbol: Symbol | undefined;
const allPotentiallyUnusedIdentifiers = new Map<Path, PotentiallyUnusedIdentifier[]>(); // key is file name
@ -14343,6 +14344,10 @@ namespace ts {
return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType;
}
function getGlobalNaNSymbol(): Symbol | undefined {
return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false));
}
function getGlobalRecordSymbol(): Symbol | undefined {
deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol;
return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol;
@ -34495,6 +34500,7 @@ namespace ts {
const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true");
}
checkNaNEquality(errorNode, operator, left, right);
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
return booleanType;
@ -34727,6 +34733,29 @@ namespace ts {
return undefined;
}
}
function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) {
const isLeftNaN = isGlobalNaN(skipParentheses(left));
const isRightNaN = isGlobalNaN(skipParentheses(right));
if (isLeftNaN || isRightNaN) {
const err = error(errorNode, Diagnostics.This_condition_will_always_return_0,
tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword));
if (isLeftNaN && isRightNaN) return;
const operatorString = operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? tokenToString(SyntaxKind.ExclamationToken) : "";
const location = isLeftNaN ? right : left;
const expression = skipParentheses(location);
addRelatedInfo(err, createDiagnosticForNode(location, Diagnostics.Did_you_mean_0,
`${operatorString}Number.isNaN(${isEntityNameExpression(expression) ? entityNameToString(expression) : "..."})`));
}
}
function isGlobalNaN(expr: Expression): boolean {
if (isIdentifier(expr) && expr.escapedText === "NaN") {
const globalNaNSymbol = getGlobalNaNSymbol();
return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr);
}
return false;
}
}
function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] {

View File

@ -3563,6 +3563,10 @@
"category": "Error",
"code": 2844
},
"This condition will always return '{0}'.": {
"category": "Error",
"code": 2845
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
@ -7356,7 +7360,14 @@
"category": "Message",
"code": 95173
},
"Use `{0}`.": {
"category": "Message",
"code": 95174
},
"Use `Number.isNaN` in all conditions.": {
"category": "Message",
"code": 95175
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",

View File

@ -94,15 +94,6 @@ namespace ts.codefix {
}
}
function findAncestorMatchingSpan(sourceFile: SourceFile, span: TextSpan): Node {
const end = textSpanEnd(span);
let token = getTokenAtPosition(sourceFile, span.start);
while (token.end < end) {
token = token.parent;
}
return token;
}
function tryGetConstraintFromDiagnosticMessage(messageText: string | DiagnosticMessageChain) {
const [_, constraint] = flattenDiagnosticMessageText(messageText, "\n", 0).match(/`extends (.*)`/) || [];
return constraint;

View File

@ -0,0 +1,65 @@
/* @internal */
namespace ts.codefix {
const fixId = "fixNaNEquality";
const errorCodes = [
Diagnostics.This_condition_will_always_return_0.code,
];
registerCodeFix({
errorCodes,
getCodeActions(context) {
const { sourceFile, span, program } = context;
const info = getInfo(program, sourceFile, span);
if (info === undefined) return;
const { suggestion, expression, arg } = info;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, arg, expression));
return [createCodeFixAction(fixId, changes, [Diagnostics.Use_0, suggestion], fixId, Diagnostics.Use_Number_isNaN_in_all_conditions)];
},
fixIds: [fixId],
getAllCodeActions: context => {
return codeFixAll(context, errorCodes, (changes, diag) => {
const info = getInfo(context.program, diag.file, createTextSpan(diag.start, diag.length));
if (info) {
doChange(changes, diag.file, info.arg, info.expression);
}
});
}
});
interface Info {
suggestion: string;
expression: BinaryExpression;
arg: Expression;
}
function getInfo(program: Program, sourceFile: SourceFile, span: TextSpan): Info | undefined {
const diag = find(program.getSemanticDiagnostics(sourceFile), diag => diag.start === span.start && diag.length === span.length);
if (diag === undefined || diag.relatedInformation === undefined) return;
const related = find(diag.relatedInformation, related => related.code === Diagnostics.Did_you_mean_0.code);
if (related === undefined || related.file === undefined || related.start === undefined || related.length === undefined) return;
const token = findAncestorMatchingSpan(related.file, createTextSpan(related.start, related.length));
if (token === undefined) return;
if (isExpression(token) && isBinaryExpression(token.parent)) {
return { suggestion: getSuggestion(related.messageText), expression: token.parent, arg: token };
}
return undefined;
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, arg: Expression, expression: BinaryExpression) {
const callExpression = factory.createCallExpression(
factory.createPropertyAccessExpression(factory.createIdentifier("Number"), factory.createIdentifier("isNaN")), /*typeArguments*/ undefined, [arg]);
const operator = expression.operatorToken.kind ;
changes.replaceNode(sourceFile, expression,
operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken
? factory.createPrefixUnaryExpression(SyntaxKind.ExclamationToken, callExpression) : callExpression);
}
function getSuggestion(messageText: string | DiagnosticMessageChain) {
const [_, suggestion] = flattenDiagnosticMessageText(messageText, "\n", 0).match(/\'(.*)\'/) || [];
return suggestion;
}
}

View File

@ -737,4 +737,13 @@ namespace ts.codefix {
export function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) {
symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*isValidTypeOnlyUseSite*/ true));
}
export function findAncestorMatchingSpan(sourceFile: SourceFile, span: TextSpan): Node {
const end = textSpanEnd(span);
let token = getTokenAtPosition(sourceFile, span.start);
while (token.end < end) {
token = token.parent;
}
return token;
}
}

View File

@ -82,6 +82,7 @@
"codefixes/fixConstructorForDerivedNeedSuperCall.ts",
"codefixes/fixEnableExperimentalDecorators.ts",
"codefixes/fixEnableJsxFlag.ts",
"codefixes/fixNaNEquality.ts",
"codefixes/fixModuleAndTargetOptions.ts",
"codefixes/fixPropertyAssignment.ts",
"codefixes/fixExtendsInterfaceBecomesImplements.ts",

View File

@ -0,0 +1,109 @@
tests/cases/compiler/nanEquality.ts(3,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(4,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(6,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(7,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(9,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(10,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(12,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(13,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(15,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(16,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(18,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(19,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(21,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(22,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(24,5): error TS2845: This condition will always return 'false'.
tests/cases/compiler/nanEquality.ts(25,5): error TS2845: This condition will always return 'true'.
tests/cases/compiler/nanEquality.ts(29,5): error TS2845: This condition will always return 'false'.
==== tests/cases/compiler/nanEquality.ts (17 errors) ====
declare const x: number;
if (x === NaN) {}
~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:3:5: Did you mean 'Number.isNaN(x)'?
if (NaN === x) {}
~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:4:13: Did you mean 'Number.isNaN(x)'?
if (x == NaN) {}
~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:6:5: Did you mean 'Number.isNaN(x)'?
if (NaN == x) {}
~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:7:12: Did you mean 'Number.isNaN(x)'?
if (x !== NaN) {}
~~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:9:5: Did you mean '!Number.isNaN(x)'?
if (NaN !== x) {}
~~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:10:13: Did you mean '!Number.isNaN(x)'?
if (x != NaN) {}
~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:12:5: Did you mean '!Number.isNaN(x)'?
if (NaN != x) {}
~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:13:12: Did you mean '!Number.isNaN(x)'?
if (x === ((NaN))) {}
~~~~~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:15:5: Did you mean 'Number.isNaN(x)'?
if (((NaN)) === x) {}
~~~~~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:16:17: Did you mean 'Number.isNaN(x)'?
if (x !== ((NaN))) {}
~~~~~~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:18:5: Did you mean '!Number.isNaN(x)'?
if (((NaN)) !== x) {}
~~~~~~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:19:17: Did you mean '!Number.isNaN(x)'?
if (NaN === NaN) {}
~~~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
if (NaN !== NaN) {}
~~~~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
if (NaN == NaN) {}
~~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
if (NaN != NaN) {}
~~~~~~~~~~
!!! error TS2845: This condition will always return 'true'.
// ...
declare let y: any;
if (NaN === y[0][1]) {}
~~~~~~~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:29:13: Did you mean 'Number.isNaN(...)'?
function t1(value: number, NaN: number) {
return value === NaN; // ok
}
function t2(value: number, NaN: number) {
return NaN == value; // ok
}
function t3(NaN: number) {
return NaN === NaN; // ok
}

View File

@ -0,0 +1,71 @@
//// [nanEquality.ts]
declare const x: number;
if (x === NaN) {}
if (NaN === x) {}
if (x == NaN) {}
if (NaN == x) {}
if (x !== NaN) {}
if (NaN !== x) {}
if (x != NaN) {}
if (NaN != x) {}
if (x === ((NaN))) {}
if (((NaN)) === x) {}
if (x !== ((NaN))) {}
if (((NaN)) !== x) {}
if (NaN === NaN) {}
if (NaN !== NaN) {}
if (NaN == NaN) {}
if (NaN != NaN) {}
// ...
declare let y: any;
if (NaN === y[0][1]) {}
function t1(value: number, NaN: number) {
return value === NaN; // ok
}
function t2(value: number, NaN: number) {
return NaN == value; // ok
}
function t3(NaN: number) {
return NaN === NaN; // ok
}
//// [nanEquality.js]
if (x === NaN) { }
if (NaN === x) { }
if (x == NaN) { }
if (NaN == x) { }
if (x !== NaN) { }
if (NaN !== x) { }
if (x != NaN) { }
if (NaN != x) { }
if (x === ((NaN))) { }
if (((NaN)) === x) { }
if (x !== ((NaN))) { }
if (((NaN)) !== x) { }
if (NaN === NaN) { }
if (NaN !== NaN) { }
if (NaN == NaN) { }
if (NaN != NaN) { }
if (NaN === y[0][1]) { }
function t1(value, NaN) {
return value === NaN; // ok
}
function t2(value, NaN) {
return NaN == value; // ok
}
function t3(NaN) {
return NaN === NaN; // ok
}

View File

@ -0,0 +1,105 @@
=== tests/cases/compiler/nanEquality.ts ===
declare const x: number;
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
if (x === NaN) {}
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (NaN === x) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
if (x == NaN) {}
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (NaN == x) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
if (x !== NaN) {}
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (NaN !== x) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
if (x != NaN) {}
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (NaN != x) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
if (x === ((NaN))) {}
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (((NaN)) === x) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
if (x !== ((NaN))) {}
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (((NaN)) !== x) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(nanEquality.ts, 0, 13))
if (NaN === NaN) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (NaN !== NaN) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (NaN == NaN) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
if (NaN != NaN) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
// ...
declare let y: any;
>y : Symbol(y, Decl(nanEquality.ts, 27, 11))
if (NaN === y[0][1]) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>y : Symbol(y, Decl(nanEquality.ts, 27, 11))
function t1(value: number, NaN: number) {
>t1 : Symbol(t1, Decl(nanEquality.ts, 28, 23))
>value : Symbol(value, Decl(nanEquality.ts, 30, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 30, 26))
return value === NaN; // ok
>value : Symbol(value, Decl(nanEquality.ts, 30, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 30, 26))
}
function t2(value: number, NaN: number) {
>t2 : Symbol(t2, Decl(nanEquality.ts, 32, 1))
>value : Symbol(value, Decl(nanEquality.ts, 34, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 34, 26))
return NaN == value; // ok
>NaN : Symbol(NaN, Decl(nanEquality.ts, 34, 26))
>value : Symbol(value, Decl(nanEquality.ts, 34, 12))
}
function t3(NaN: number) {
>t3 : Symbol(t3, Decl(nanEquality.ts, 36, 1))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
return NaN === NaN; // ok
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
}

View File

@ -0,0 +1,137 @@
=== tests/cases/compiler/nanEquality.ts ===
declare const x: number;
>x : number
if (x === NaN) {}
>x === NaN : boolean
>x : number
>NaN : number
if (NaN === x) {}
>NaN === x : boolean
>NaN : number
>x : number
if (x == NaN) {}
>x == NaN : boolean
>x : number
>NaN : number
if (NaN == x) {}
>NaN == x : boolean
>NaN : number
>x : number
if (x !== NaN) {}
>x !== NaN : boolean
>x : number
>NaN : number
if (NaN !== x) {}
>NaN !== x : boolean
>NaN : number
>x : number
if (x != NaN) {}
>x != NaN : boolean
>x : number
>NaN : number
if (NaN != x) {}
>NaN != x : boolean
>NaN : number
>x : number
if (x === ((NaN))) {}
>x === ((NaN)) : boolean
>x : number
>((NaN)) : number
>(NaN) : number
>NaN : number
if (((NaN)) === x) {}
>((NaN)) === x : boolean
>((NaN)) : number
>(NaN) : number
>NaN : number
>x : number
if (x !== ((NaN))) {}
>x !== ((NaN)) : boolean
>x : number
>((NaN)) : number
>(NaN) : number
>NaN : number
if (((NaN)) !== x) {}
>((NaN)) !== x : boolean
>((NaN)) : number
>(NaN) : number
>NaN : number
>x : number
if (NaN === NaN) {}
>NaN === NaN : boolean
>NaN : number
>NaN : number
if (NaN !== NaN) {}
>NaN !== NaN : boolean
>NaN : number
>NaN : number
if (NaN == NaN) {}
>NaN == NaN : boolean
>NaN : number
>NaN : number
if (NaN != NaN) {}
>NaN != NaN : boolean
>NaN : number
>NaN : number
// ...
declare let y: any;
>y : any
if (NaN === y[0][1]) {}
>NaN === y[0][1] : boolean
>NaN : number
>y[0][1] : any
>y[0] : any
>y : any
>0 : 0
>1 : 1
function t1(value: number, NaN: number) {
>t1 : (value: number, NaN: number) => boolean
>value : number
>NaN : number
return value === NaN; // ok
>value === NaN : boolean
>value : number
>NaN : number
}
function t2(value: number, NaN: number) {
>t2 : (value: number, NaN: number) => boolean
>value : number
>NaN : number
return NaN == value; // ok
>NaN == value : boolean
>NaN : number
>value : number
}
function t3(NaN: number) {
>t3 : (NaN: number) => boolean
>NaN : number
return NaN === NaN; // ok
>NaN === NaN : boolean
>NaN : number
>NaN : number
}

View File

@ -0,0 +1,41 @@
declare const x: number;
if (x === NaN) {}
if (NaN === x) {}
if (x == NaN) {}
if (NaN == x) {}
if (x !== NaN) {}
if (NaN !== x) {}
if (x != NaN) {}
if (NaN != x) {}
if (x === ((NaN))) {}
if (((NaN)) === x) {}
if (x !== ((NaN))) {}
if (((NaN)) !== x) {}
if (NaN === NaN) {}
if (NaN !== NaN) {}
if (NaN == NaN) {}
if (NaN != NaN) {}
// ...
declare let y: any;
if (NaN === y[0][1]) {}
function t1(value: number, NaN: number) {
return value === NaN; // ok
}
function t2(value: number, NaN: number) {
return NaN == value; // ok
}
function t3(NaN: number) {
return NaN === NaN; // ok
}

View File

@ -0,0 +1,10 @@
/// <reference path="fourslash.ts" />
////declare const x: number;
////[|if (x === NaN) {}|]
verify.codeFix({
index: 0,
description: "Use `Number.isNaN(x)`.",
newRangeContent: "if (Number.isNaN(x)) {}",
});

View File

@ -0,0 +1,10 @@
/// <reference path="fourslash.ts" />
////declare const x: number;
////[|if (NaN === x) {}|]
verify.codeFix({
index: 0,
description: "Use `Number.isNaN(x)`.",
newRangeContent: "if (Number.isNaN(x)) {}",
});

View File

@ -0,0 +1,10 @@
/// <reference path="fourslash.ts" />
////declare const x: number;
////[|if (x !== NaN) {}|]
verify.codeFix({
index: 0,
description: "Use `!Number.isNaN(x)`.",
newRangeContent: "if (!Number.isNaN(x)) {}",
});

View File

@ -0,0 +1,10 @@
/// <reference path="fourslash.ts" />
////declare const x: number;
////[|if (NaN !== x) {}|]
verify.codeFix({
index: 0,
description: "Use `!Number.isNaN(x)`.",
newRangeContent: "if (!Number.isNaN(x)) {}",
});

View File

@ -0,0 +1,10 @@
/// <reference path="fourslash.ts" />
////declare const x: any;
////[|if (NaN !== x[0][1]) {}|]
verify.codeFix({
index: 0,
description: "Use `!Number.isNaN(...)`.",
newRangeContent: "if (!Number.isNaN(x[0][1])) {}",
});

View File

@ -0,0 +1,22 @@
/// <reference path="fourslash.ts" />
////declare const x: number;
////declare const y: any;
////if (x === NaN) {}
////if (NaN === x) {}
////if (x !== NaN) {}
////if (NaN !== x) {}
////if (NaN === y[0][1]) {}
verify.codeFixAll({
fixId: "fixNaNEquality",
fixAllDescription: ts.Diagnostics.Use_Number_isNaN_in_all_conditions.message,
newFileContent:
`declare const x: number;
declare const y: any;
if (Number.isNaN(x)) {}
if (Number.isNaN(x)) {}
if (!Number.isNaN(x)) {}
if (!Number.isNaN(x)) {}
if (Number.isNaN(y[0][1])) {}`
});