From e4d0b428ce6adf079d750f6847573e9e256f8a6a Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 18 Jun 2021 07:01:09 +0530 Subject: [PATCH 1/5] Update: improve `isNaNIdentifier` to detect `Number.isNaN` (fixes #14715) --- lib/rules/use-isnan.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rules/use-isnan.js b/lib/rules/use-isnan.js index 0c7e888c976..d30aa80dc2d 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) && (node.type === "Identifier" && node.name === "NaN" || + (node.type === "MemberExpression" && + node.object.type === "Identifier" && node.object.name === "Number" && + node.property.type === "Identifier" && node.property.name === "NaN")); } //------------------------------------------------------------------------------ From bffbbd730664cf7b468343154d7b09772a48ccad Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 18 Jun 2021 07:21:20 +0530 Subject: [PATCH 2/5] Chore: add test cases for `Number.NaN` --- tests/lib/rules/use-isnan.js | 334 +++++++++++++++++++++++++++++++++++ 1 file changed, 334 insertions(+) diff --git a/tests/lib/rules/use-isnan.js b/tests/lib/rules/use-isnan.js index c5a4b6bd686..ac7a0af3959 100644 --- a/tests/lib/rules/use-isnan.js +++ b/tests/lib/rules/use-isnan.js @@ -36,6 +36,18 @@ 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) { }", //------------------------------------------------------------------------------ // enforceForSwitchCase @@ -105,6 +117,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 +180,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 +270,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 +410,70 @@ 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] + }, //------------------------------------------------------------------------------ // enforceForSwitchCase @@ -351,6 +558,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 +689,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" } }] } ] }); From 02eb88dfc03fb2307a80b21a1e888ff9092f19b7 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 18 Jun 2021 07:29:23 +0530 Subject: [PATCH 3/5] Docs: add more examples for `use-isnan` --- docs/rules/use-isnan.md | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/rules/use-isnan.md b/docs/rules/use-isnan.md index ae9fefe36af..45f2954f6c1 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,25 @@ 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 From 2600d3b347fc8ec43afd52df42a8260caa22b706 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Mon, 21 Jun 2021 18:37:08 +0530 Subject: [PATCH 4/5] Chore: improve logic and add more test cases --- lib/rules/use-isnan.js | 8 ++++---- tests/lib/rules/use-isnan.js | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/rules/use-isnan.js b/lib/rules/use-isnan.js index d30aa80dc2d..ef95b21314a 100644 --- a/lib/rules/use-isnan.js +++ b/lib/rules/use-isnan.js @@ -21,10 +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" || - (node.type === "MemberExpression" && - node.object.type === "Identifier" && node.object.name === "Number" && - node.property.type === "Identifier" && node.property.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 ac7a0af3959..a9bfe3a67d7 100644 --- a/tests/lib/rules/use-isnan.js +++ b/tests/lib/rules/use-isnan.js @@ -48,6 +48,7 @@ ruleTester.run("use-isnan", rule, { "foo(Number.NaN / 2)", "foo(2 / Number.NaN)", "var x; if (x = Number.NaN) { }", + "x === Number[NaN];", //------------------------------------------------------------------------------ // enforceForSwitchCase @@ -474,6 +475,15 @@ ruleTester.run("use-isnan", rule, { code: "\"abc\" >= Number.NaN;", errors: [comparisonError] }, + { + code: "x === Number?.NaN;", + parserOptions: { ecmaVersion: 2020 }, + errors: [comparisonError] + }, + { + code: "x === Number['NaN'];", + errors: [comparisonError] + }, //------------------------------------------------------------------------------ // enforceForSwitchCase From 11e2f15336bb174c31b3011866eb0aa988a876eb Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Tue, 22 Jun 2021 18:56:25 +0530 Subject: [PATCH 5/5] Docs: Update docs/rules/use-isnan.md Co-authored-by: Milos Djermanovic --- docs/rules/use-isnan.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/rules/use-isnan.md b/docs/rules/use-isnan.md index 45f2954f6c1..67c83c96a90 100644 --- a/docs/rules/use-isnan.md +++ b/docs/rules/use-isnan.md @@ -172,7 +172,8 @@ switch (Number.NaN) { case b: baz(); break; - / + // ... +} ``` ### enforceForIndexOf