diff --git a/docs/rules/use-isnan.md b/docs/rules/use-isnan.md index ae9fefe36af..67c83c96a90 100644 --- a/docs/rules/use-isnan.md +++ b/docs/rules/use-isnan.md @@ -25,6 +25,14 @@ if (foo == NaN) { if (foo != NaN) { // ... } + +if (foo == Number.NaN) { + // ... +} + +if (foo != Number.NaN) { + // ... +} ``` Examples of **correct** code for this rule: @@ -77,6 +85,26 @@ switch (NaN) { break; // ... } + +switch (foo) { + case Number.NaN: + bar(); + break; + case 1: + baz(); + break; + // ... +} + +switch (Number.NaN) { + case a: + bar(); + break; + case b: + baz(); + break; + // ... +} ``` Examples of **correct** code for this rule with `"enforceForSwitchCase"` option set to `true` (default): @@ -126,6 +154,26 @@ switch (NaN) { break; // ... } + +switch (foo) { + case Number.NaN: + bar(); + break; + case 1: + baz(); + break; + // ... +} + +switch (Number.NaN) { + case a: + bar(); + break; + case b: + baz(); + break; + // ... +} ``` ### enforceForIndexOf diff --git a/lib/rules/use-isnan.js b/lib/rules/use-isnan.js index 0c7e888c976..ef95b21314a 100644 --- a/lib/rules/use-isnan.js +++ b/lib/rules/use-isnan.js @@ -21,7 +21,10 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is 'NaN' identifier. */ function isNaNIdentifier(node) { - return Boolean(node) && node.type === "Identifier" && node.name === "NaN"; + return Boolean(node) && ( + astUtils.isSpecificId(node, "NaN") || + astUtils.isSpecificMemberAccess(node, "Number", "NaN") + ); } //------------------------------------------------------------------------------ diff --git a/tests/lib/rules/use-isnan.js b/tests/lib/rules/use-isnan.js index c5a4b6bd686..a9bfe3a67d7 100644 --- a/tests/lib/rules/use-isnan.js +++ b/tests/lib/rules/use-isnan.js @@ -36,6 +36,19 @@ ruleTester.run("use-isnan", rule, { "foo(NaN / 2)", "foo(2 / NaN)", "var x; if (x = NaN) { }", + "var x = Number.NaN;", + "isNaN(Number.NaN) === true;", + "Number.isNaN(Number.NaN) === true;", + "foo(Number.NaN + 1);", + "foo(1 + Number.NaN);", + "foo(Number.NaN - 1)", + "foo(1 - Number.NaN)", + "foo(Number.NaN * 2)", + "foo(2 * Number.NaN)", + "foo(Number.NaN / 2)", + "foo(2 / Number.NaN)", + "var x; if (x = Number.NaN) { }", + "x === Number[NaN];", //------------------------------------------------------------------------------ // enforceForSwitchCase @@ -105,6 +118,62 @@ ruleTester.run("use-isnan", rule, { code: "switch(foo) { case bar: break; case 1: break; default: break; }", options: [{ enforceForSwitchCase: true }] }, + { + code: "switch(Number.NaN) { case foo: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(NaN) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(foo) { case bar: Number.NaN; }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { default: Number.NaN; }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(Number.Nan) {}", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch('Number.NaN') { default: break; }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo(Number.NaN)) {}", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo.Number.NaN) {}", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case Number.Nan: break }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case 'Number.NaN': break }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case foo(Number.NaN): break }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case foo.Number.NaN: break }", + options: [{ enforceForSwitchCase: true }] + }, //------------------------------------------------------------------------------ // enforceForIndexOf @@ -112,6 +181,8 @@ ruleTester.run("use-isnan", rule, { "foo.indexOf(NaN)", "foo.lastIndexOf(NaN)", + "foo.indexOf(Number.NaN)", + "foo.lastIndexOf(Number.NaN)", { code: "foo.indexOf(NaN)", options: [{}] @@ -200,6 +271,79 @@ ruleTester.run("use-isnan", rule, { { code: "foo.lastIndexOf(NaN())", options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.indexOf(Number.NaN)", + options: [{}] + }, + { + code: "foo.lastIndexOf(Number.NaN)", + options: [{}] + }, + { + code: "foo.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: false }] + }, + { + code: "foo.lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: false }] + }, + { + code: "indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "new foo.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.bar(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.IndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo[indexOf](Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo[lastIndexOf](Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "indexOf.foo(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.lastIndexOf(Number.Nan)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.indexOf(a, Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.lastIndexOf(Number.NaN, b)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.lastIndexOf(Number.NaN, NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.indexOf(...Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "foo.lastIndexOf(Number.NaN())", + options: [{ enforceForIndexOf: true }] } ], invalid: [ @@ -267,6 +411,79 @@ ruleTester.run("use-isnan", rule, { code: "\"abc\" >= NaN;", errors: [comparisonError] }, + { + code: "123 == Number.NaN;", + errors: [comparisonError] + }, + { + code: "123 === Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN === \"abc\";", + errors: [comparisonError] + }, + { + code: "Number.NaN == \"abc\";", + errors: [comparisonError] + }, + { + code: "123 != Number.NaN;", + errors: [comparisonError] + }, + { + code: "123 !== Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN !== \"abc\";", + errors: [comparisonError] + }, + { + code: "Number.NaN != \"abc\";", + errors: [comparisonError] + }, + { + code: "Number.NaN < \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" < Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN > \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" > Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN <= \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" <= Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN >= \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" >= Number.NaN;", + errors: [comparisonError] + }, + { + code: "x === Number?.NaN;", + parserOptions: { ecmaVersion: 2020 }, + errors: [comparisonError] + }, + { + code: "x === Number['NaN'];", + errors: [comparisonError] + }, //------------------------------------------------------------------------------ // enforceForSwitchCase @@ -351,6 +568,85 @@ ruleTester.run("use-isnan", rule, { { messageId: "caseNaN", type: "SwitchCase", column: 15 } ] }, + { + code: "switch(Number.NaN) { case foo: break; }", + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(Number.NaN) { case foo: break; }", + options: [{}], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + options: [{}], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(Number.NaN) {}", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(Number.NaN) { case foo: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(Number.NaN) { default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(Number.NaN) { case foo: break; default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(foo) { case Number.NaN: }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(foo) { case (Number.NaN): break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 32 }] + }, + { + code: "switch(foo) { case bar: case Number.NaN: default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 25 }] + }, + { + code: "switch(foo) { case bar: break; case NaN: break; case baz: break; case Number.NaN: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [ + { messageId: "caseNaN", type: "SwitchCase", column: 32 }, + { messageId: "caseNaN", type: "SwitchCase", column: 66 } + ] + }, + { + code: "switch(Number.NaN) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [ + { messageId: "switchNaN", type: "SwitchStatement", column: 1 }, + { messageId: "caseNaN", type: "SwitchCase", column: 22 } + ] + }, //------------------------------------------------------------------------------ // enforceForIndexOf @@ -403,6 +699,54 @@ ruleTester.run("use-isnan", rule, { options: [{ enforceForIndexOf: true }], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] + }, + { + code: "foo.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }] + }, + { + code: "foo.lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }] + }, + { + code: "foo['indexOf'](Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }] + }, + { + code: "foo['lastIndexOf'](Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }] + }, + { + code: "foo().indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }] + }, + { + code: "foo.bar.lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }] + }, + { + code: "foo.indexOf?.(Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] + }, + { + code: "foo?.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] + }, + { + code: "(foo?.indexOf)(Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] } ] });