Skip to content

Commit 104c0b5

Browse files
snitin315mdjermanovic
andauthoredJun 26, 2021
Update: improve use-isnan rule to detect Number.NaN (fixes #14715) (#14718)
* Update: improve `isNaNIdentifier` to detect `Number.isNaN` (fixes #14715) * Chore: add test cases for `Number.NaN` * Docs: add more examples for `use-isnan` * Chore: improve logic and add more test cases * Docs: Update docs/rules/use-isnan.md Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
1 parent b08170b commit 104c0b5

File tree

3 files changed

+396
-1
lines changed

3 files changed

+396
-1
lines changed
 

‎docs/rules/use-isnan.md

+48
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ if (foo == NaN) {
2525
if (foo != NaN) {
2626
// ...
2727
}
28+
29+
if (foo == Number.NaN) {
30+
// ...
31+
}
32+
33+
if (foo != Number.NaN) {
34+
// ...
35+
}
2836
```
2937

3038
Examples of **correct** code for this rule:
@@ -77,6 +85,26 @@ switch (NaN) {
7785
break;
7886
// ...
7987
}
88+
89+
switch (foo) {
90+
case Number.NaN:
91+
bar();
92+
break;
93+
case 1:
94+
baz();
95+
break;
96+
// ...
97+
}
98+
99+
switch (Number.NaN) {
100+
case a:
101+
bar();
102+
break;
103+
case b:
104+
baz();
105+
break;
106+
// ...
107+
}
80108
```
81109

82110
Examples of **correct** code for this rule with `"enforceForSwitchCase"` option set to `true` (default):
@@ -126,6 +154,26 @@ switch (NaN) {
126154
break;
127155
// ...
128156
}
157+
158+
switch (foo) {
159+
case Number.NaN:
160+
bar();
161+
break;
162+
case 1:
163+
baz();
164+
break;
165+
// ...
166+
}
167+
168+
switch (Number.NaN) {
169+
case a:
170+
bar();
171+
break;
172+
case b:
173+
baz();
174+
break;
175+
// ...
176+
}
129177
```
130178

131179
### enforceForIndexOf

‎lib/rules/use-isnan.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ const astUtils = require("./utils/ast-utils");
2121
* @returns {boolean} `true` if the node is 'NaN' identifier.
2222
*/
2323
function isNaNIdentifier(node) {
24-
return Boolean(node) && node.type === "Identifier" && node.name === "NaN";
24+
return Boolean(node) && (
25+
astUtils.isSpecificId(node, "NaN") ||
26+
astUtils.isSpecificMemberAccess(node, "Number", "NaN")
27+
);
2528
}
2629

2730
//------------------------------------------------------------------------------

‎tests/lib/rules/use-isnan.js

+344
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ ruleTester.run("use-isnan", rule, {
3636
"foo(NaN / 2)",
3737
"foo(2 / NaN)",
3838
"var x; if (x = NaN) { }",
39+
"var x = Number.NaN;",
40+
"isNaN(Number.NaN) === true;",
41+
"Number.isNaN(Number.NaN) === true;",
42+
"foo(Number.NaN + 1);",
43+
"foo(1 + Number.NaN);",
44+
"foo(Number.NaN - 1)",
45+
"foo(1 - Number.NaN)",
46+
"foo(Number.NaN * 2)",
47+
"foo(2 * Number.NaN)",
48+
"foo(Number.NaN / 2)",
49+
"foo(2 / Number.NaN)",
50+
"var x; if (x = Number.NaN) { }",
51+
"x === Number[NaN];",
3952

4053
//------------------------------------------------------------------------------
4154
// enforceForSwitchCase
@@ -105,13 +118,71 @@ ruleTester.run("use-isnan", rule, {
105118
code: "switch(foo) { case bar: break; case 1: break; default: break; }",
106119
options: [{ enforceForSwitchCase: true }]
107120
},
121+
{
122+
code: "switch(Number.NaN) { case foo: break; }",
123+
options: [{ enforceForSwitchCase: false }]
124+
},
125+
{
126+
code: "switch(foo) { case Number.NaN: break; }",
127+
options: [{ enforceForSwitchCase: false }]
128+
},
129+
{
130+
code: "switch(NaN) { case Number.NaN: break; }",
131+
options: [{ enforceForSwitchCase: false }]
132+
},
133+
{
134+
code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }",
135+
options: [{ enforceForSwitchCase: false }]
136+
},
137+
{
138+
code: "switch(foo) { case bar: Number.NaN; }",
139+
options: [{ enforceForSwitchCase: true }]
140+
},
141+
{
142+
code: "switch(foo) { default: Number.NaN; }",
143+
options: [{ enforceForSwitchCase: true }]
144+
},
145+
{
146+
code: "switch(Number.Nan) {}",
147+
options: [{ enforceForSwitchCase: true }]
148+
},
149+
{
150+
code: "switch('Number.NaN') { default: break; }",
151+
options: [{ enforceForSwitchCase: true }]
152+
},
153+
{
154+
code: "switch(foo(Number.NaN)) {}",
155+
options: [{ enforceForSwitchCase: true }]
156+
},
157+
{
158+
code: "switch(foo.Number.NaN) {}",
159+
options: [{ enforceForSwitchCase: true }]
160+
},
161+
{
162+
code: "switch(foo) { case Number.Nan: break }",
163+
options: [{ enforceForSwitchCase: true }]
164+
},
165+
{
166+
code: "switch(foo) { case 'Number.NaN': break }",
167+
options: [{ enforceForSwitchCase: true }]
168+
},
169+
{
170+
code: "switch(foo) { case foo(Number.NaN): break }",
171+
options: [{ enforceForSwitchCase: true }]
172+
},
173+
{
174+
code: "switch(foo) { case foo.Number.NaN: break }",
175+
options: [{ enforceForSwitchCase: true }]
176+
},
108177

109178
//------------------------------------------------------------------------------
110179
// enforceForIndexOf
111180
//------------------------------------------------------------------------------
112181

113182
"foo.indexOf(NaN)",
114183
"foo.lastIndexOf(NaN)",
184+
"foo.indexOf(Number.NaN)",
185+
"foo.lastIndexOf(Number.NaN)",
115186
{
116187
code: "foo.indexOf(NaN)",
117188
options: [{}]
@@ -200,6 +271,79 @@ ruleTester.run("use-isnan", rule, {
200271
{
201272
code: "foo.lastIndexOf(NaN())",
202273
options: [{ enforceForIndexOf: true }]
274+
},
275+
{
276+
code: "foo.indexOf(Number.NaN)",
277+
options: [{}]
278+
},
279+
{
280+
code: "foo.lastIndexOf(Number.NaN)",
281+
options: [{}]
282+
},
283+
{
284+
code: "foo.indexOf(Number.NaN)",
285+
options: [{ enforceForIndexOf: false }]
286+
},
287+
{
288+
code: "foo.lastIndexOf(Number.NaN)",
289+
options: [{ enforceForIndexOf: false }]
290+
},
291+
{
292+
code: "indexOf(Number.NaN)",
293+
options: [{ enforceForIndexOf: true }]
294+
},
295+
{
296+
code: "lastIndexOf(Number.NaN)",
297+
options: [{ enforceForIndexOf: true }]
298+
},
299+
{
300+
code: "new foo.indexOf(Number.NaN)",
301+
options: [{ enforceForIndexOf: true }]
302+
},
303+
{
304+
code: "foo.bar(Number.NaN)",
305+
options: [{ enforceForIndexOf: true }]
306+
},
307+
{
308+
code: "foo.IndexOf(Number.NaN)",
309+
options: [{ enforceForIndexOf: true }]
310+
},
311+
{
312+
code: "foo[indexOf](Number.NaN)",
313+
options: [{ enforceForIndexOf: true }]
314+
},
315+
{
316+
code: "foo[lastIndexOf](Number.NaN)",
317+
options: [{ enforceForIndexOf: true }]
318+
},
319+
{
320+
code: "indexOf.foo(Number.NaN)",
321+
options: [{ enforceForIndexOf: true }]
322+
},
323+
{
324+
code: "foo.lastIndexOf(Number.Nan)",
325+
options: [{ enforceForIndexOf: true }]
326+
},
327+
{
328+
code: "foo.indexOf(a, Number.NaN)",
329+
options: [{ enforceForIndexOf: true }]
330+
},
331+
{
332+
code: "foo.lastIndexOf(Number.NaN, b)",
333+
options: [{ enforceForIndexOf: true }]
334+
},
335+
{
336+
code: "foo.lastIndexOf(Number.NaN, NaN)",
337+
options: [{ enforceForIndexOf: true }]
338+
},
339+
{
340+
code: "foo.indexOf(...Number.NaN)",
341+
options: [{ enforceForIndexOf: true }],
342+
parserOptions: { ecmaVersion: 6 }
343+
},
344+
{
345+
code: "foo.lastIndexOf(Number.NaN())",
346+
options: [{ enforceForIndexOf: true }]
203347
}
204348
],
205349
invalid: [
@@ -267,6 +411,79 @@ ruleTester.run("use-isnan", rule, {
267411
code: "\"abc\" >= NaN;",
268412
errors: [comparisonError]
269413
},
414+
{
415+
code: "123 == Number.NaN;",
416+
errors: [comparisonError]
417+
},
418+
{
419+
code: "123 === Number.NaN;",
420+
errors: [comparisonError]
421+
},
422+
{
423+
code: "Number.NaN === \"abc\";",
424+
errors: [comparisonError]
425+
},
426+
{
427+
code: "Number.NaN == \"abc\";",
428+
errors: [comparisonError]
429+
},
430+
{
431+
code: "123 != Number.NaN;",
432+
errors: [comparisonError]
433+
},
434+
{
435+
code: "123 !== Number.NaN;",
436+
errors: [comparisonError]
437+
},
438+
{
439+
code: "Number.NaN !== \"abc\";",
440+
errors: [comparisonError]
441+
},
442+
{
443+
code: "Number.NaN != \"abc\";",
444+
errors: [comparisonError]
445+
},
446+
{
447+
code: "Number.NaN < \"abc\";",
448+
errors: [comparisonError]
449+
},
450+
{
451+
code: "\"abc\" < Number.NaN;",
452+
errors: [comparisonError]
453+
},
454+
{
455+
code: "Number.NaN > \"abc\";",
456+
errors: [comparisonError]
457+
},
458+
{
459+
code: "\"abc\" > Number.NaN;",
460+
errors: [comparisonError]
461+
},
462+
{
463+
code: "Number.NaN <= \"abc\";",
464+
errors: [comparisonError]
465+
},
466+
{
467+
code: "\"abc\" <= Number.NaN;",
468+
errors: [comparisonError]
469+
},
470+
{
471+
code: "Number.NaN >= \"abc\";",
472+
errors: [comparisonError]
473+
},
474+
{
475+
code: "\"abc\" >= Number.NaN;",
476+
errors: [comparisonError]
477+
},
478+
{
479+
code: "x === Number?.NaN;",
480+
parserOptions: { ecmaVersion: 2020 },
481+
errors: [comparisonError]
482+
},
483+
{
484+
code: "x === Number['NaN'];",
485+
errors: [comparisonError]
486+
},
270487

271488
//------------------------------------------------------------------------------
272489
// enforceForSwitchCase
@@ -351,6 +568,85 @@ ruleTester.run("use-isnan", rule, {
351568
{ messageId: "caseNaN", type: "SwitchCase", column: 15 }
352569
]
353570
},
571+
{
572+
code: "switch(Number.NaN) { case foo: break; }",
573+
errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
574+
},
575+
{
576+
code: "switch(foo) { case Number.NaN: break; }",
577+
errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
578+
},
579+
{
580+
code: "switch(Number.NaN) { case foo: break; }",
581+
options: [{}],
582+
errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
583+
},
584+
{
585+
code: "switch(foo) { case Number.NaN: break; }",
586+
options: [{}],
587+
errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
588+
},
589+
{
590+
code: "switch(Number.NaN) {}",
591+
options: [{ enforceForSwitchCase: true }],
592+
errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
593+
},
594+
{
595+
code: "switch(Number.NaN) { case foo: break; }",
596+
options: [{ enforceForSwitchCase: true }],
597+
errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
598+
},
599+
{
600+
code: "switch(Number.NaN) { default: break; }",
601+
options: [{ enforceForSwitchCase: true }],
602+
errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
603+
},
604+
{
605+
code: "switch(Number.NaN) { case foo: break; default: break; }",
606+
options: [{ enforceForSwitchCase: true }],
607+
errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
608+
},
609+
{
610+
code: "switch(foo) { case Number.NaN: }",
611+
options: [{ enforceForSwitchCase: true }],
612+
errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
613+
},
614+
{
615+
code: "switch(foo) { case Number.NaN: break; }",
616+
options: [{ enforceForSwitchCase: true }],
617+
errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
618+
},
619+
{
620+
code: "switch(foo) { case (Number.NaN): break; }",
621+
options: [{ enforceForSwitchCase: true }],
622+
errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
623+
},
624+
{
625+
code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }",
626+
options: [{ enforceForSwitchCase: true }],
627+
errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 32 }]
628+
},
629+
{
630+
code: "switch(foo) { case bar: case Number.NaN: default: break; }",
631+
options: [{ enforceForSwitchCase: true }],
632+
errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 25 }]
633+
},
634+
{
635+
code: "switch(foo) { case bar: break; case NaN: break; case baz: break; case Number.NaN: break; }",
636+
options: [{ enforceForSwitchCase: true }],
637+
errors: [
638+
{ messageId: "caseNaN", type: "SwitchCase", column: 32 },
639+
{ messageId: "caseNaN", type: "SwitchCase", column: 66 }
640+
]
641+
},
642+
{
643+
code: "switch(Number.NaN) { case Number.NaN: break; }",
644+
options: [{ enforceForSwitchCase: true }],
645+
errors: [
646+
{ messageId: "switchNaN", type: "SwitchStatement", column: 1 },
647+
{ messageId: "caseNaN", type: "SwitchCase", column: 22 }
648+
]
649+
},
354650

355651
//------------------------------------------------------------------------------
356652
// enforceForIndexOf
@@ -403,6 +699,54 @@ ruleTester.run("use-isnan", rule, {
403699
options: [{ enforceForIndexOf: true }],
404700
parserOptions: { ecmaVersion: 2020 },
405701
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
702+
},
703+
{
704+
code: "foo.indexOf(Number.NaN)",
705+
options: [{ enforceForIndexOf: true }],
706+
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
707+
},
708+
{
709+
code: "foo.lastIndexOf(Number.NaN)",
710+
options: [{ enforceForIndexOf: true }],
711+
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
712+
},
713+
{
714+
code: "foo['indexOf'](Number.NaN)",
715+
options: [{ enforceForIndexOf: true }],
716+
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
717+
},
718+
{
719+
code: "foo['lastIndexOf'](Number.NaN)",
720+
options: [{ enforceForIndexOf: true }],
721+
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
722+
},
723+
{
724+
code: "foo().indexOf(Number.NaN)",
725+
options: [{ enforceForIndexOf: true }],
726+
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
727+
},
728+
{
729+
code: "foo.bar.lastIndexOf(Number.NaN)",
730+
options: [{ enforceForIndexOf: true }],
731+
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
732+
},
733+
{
734+
code: "foo.indexOf?.(Number.NaN)",
735+
options: [{ enforceForIndexOf: true }],
736+
parserOptions: { ecmaVersion: 2020 },
737+
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
738+
},
739+
{
740+
code: "foo?.indexOf(Number.NaN)",
741+
options: [{ enforceForIndexOf: true }],
742+
parserOptions: { ecmaVersion: 2020 },
743+
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
744+
},
745+
{
746+
code: "(foo?.indexOf)(Number.NaN)",
747+
options: [{ enforceForIndexOf: true }],
748+
parserOptions: { ecmaVersion: 2020 },
749+
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
406750
}
407751
]
408752
});

0 commit comments

Comments
 (0)
Please sign in to comment.