Skip to content

Commit 3a58802

Browse files
authored
prefer-number-properties: Fix some edge cases (part 2) (sindresorhus#1176)
1 parent e7e25b8 commit 3a58802

File tree

5 files changed

+216
-31
lines changed

5 files changed

+216
-31
lines changed

rules/prefer-number-properties.js

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const getDocumentationUrl = require('./utils/get-documentation-url');
33
const isShadowed = require('./utils/is-shadowed');
44
const renameIdentifier = require('./utils/rename-identifier');
5+
const referenceIdentifierSelector = require('./utils/reference-identifier-selector');
56

67
const METHOD_ERROR_MESSAGE_ID = 'method-error';
78
const METHOD_SUGGESTION_MESSAGE_ID = 'method-suggestion';
@@ -28,36 +29,7 @@ const methodsSelector = [
2829
`:matches(${Object.keys(methods).map(name => `[name="${name}"]`).join(', ')})`
2930
].join('');
3031

31-
const propertiesSelector = [
32-
'Identifier',
33-
':matches([name="NaN"],[name="Infinity"])',
34-
`:not(${
35-
[
36-
'MemberExpression[computed=false] > .property',
37-
'FunctionDeclaration > .id',
38-
'ClassDeclaration > .id',
39-
// TODO: remove `ClassProperty` when `babel` and `typescript` support `FieldDefinition`
40-
'ClassProperty[computed=false] > .key',
41-
'FieldDefinition[computed=false] > .key',
42-
'MethodDefinition[computed=false] > .key',
43-
'VariableDeclarator > .id',
44-
'Property[shorthand=false][computed=false] > .key',
45-
'LabeledStatement > .label',
46-
'ContinueStatement > .label',
47-
'BreakStatement > .label',
48-
'ExportSpecifier > .local',
49-
'ExportSpecifier > .exported',
50-
'ExportAllDeclaration > .exported',
51-
'ImportSpecifier > .local',
52-
'ImportSpecifier > .imported',
53-
'ImportNamespaceSpecifier > .local',
54-
'ImportDefaultSpecifier > .local',
55-
'TSDeclareFunction > .id',
56-
'TSEnumMember > .id',
57-
'TSPropertySignature > .key'
58-
].join(', ')
59-
})`
60-
].join('');
32+
const propertiesSelector = referenceIdentifierSelector(['NaN', 'Infinity']);
6133

6234
const isNegative = node => {
6335
const {parent} = node;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
const nonReferenceSelector = [
4+
// `foo.Identifier`
5+
'MemberExpression[computed=false] > .property',
6+
// `function Identifier() {}`
7+
'FunctionDeclaration > .id',
8+
// `const foo = function Identifier() {}`
9+
'FunctionExpression > .id',
10+
// `class Identifier() {}`
11+
'ClassDeclaration > .id',
12+
// `const foo = class Identifier() {}`
13+
'ClassExpression > .id',
14+
// TODO: remove `ClassProperty` when `babel` and `typescript` support `PropertyDefinition`
15+
'ClassProperty[computed=false] > .key',
16+
// `class Foo {Identifier = 1}`
17+
'PropertyDefinition[computed=false] > .key',
18+
// `class Foo {Identifier() {}}`
19+
'MethodDefinition[computed=false] > .key',
20+
// `const Identifier = 1`
21+
'VariableDeclarator > .id',
22+
// `const foo = {Identifier: 1}`
23+
'ObjectExpression > Property[shorthand=false][computed=false].properties > .key',
24+
// `const {Identifier} = {}`
25+
// `const {Identifier: foo} = {}`
26+
'ObjectPattern > Property[computed=false].properties > .key',
27+
// `const {Identifier} = {}`
28+
// `const {foo: Identifier} = {}`
29+
'ObjectPattern > Property.properties > .value',
30+
// `const [Identifier] = []`
31+
'ArrayPattern > .elements',
32+
// `function foo(Identifier) {}`
33+
// `const foo = function(Identifier) {}`
34+
// `const foo = (Identifier) => {}`
35+
':function > .params',
36+
/*
37+
```
38+
Identifier: for (const foo of bar) {
39+
continue Identifier;
40+
break Identifier;
41+
}
42+
```
43+
*/
44+
'LabeledStatement > .label',
45+
'ContinueStatement > .label',
46+
'BreakStatement > .label',
47+
// `export {Identifier as foo}`
48+
'ExportSpecifier > .local',
49+
// `export {foo as Identifier}`
50+
'ExportSpecifier > .exported',
51+
// `export * as Identifier from 'foo'`
52+
'ExportAllDeclaration > .exported',
53+
// `import {foo as Identifier} from 'foo'`
54+
'ImportSpecifier > .local',
55+
// `import {Identifier as foo} from 'foo'`
56+
'ImportSpecifier > .imported',
57+
// `import * as Identifier from 'foo'`
58+
'ImportNamespaceSpecifier > .local',
59+
// `import Identifier from 'foo'`
60+
'ImportDefaultSpecifier > .local',
61+
62+
// TypeScript
63+
'TSDeclareFunction > .id',
64+
'TSEnumMember > .id',
65+
'TSPropertySignature > .key'
66+
].join(', ');
67+
68+
function referenceIdentifierSelector(nameOrNames) {
69+
const selector = [
70+
'Identifier',
71+
`:not(${nonReferenceSelector})`
72+
];
73+
74+
/* istanbul ignore else: no rule use single name yet */
75+
if (Array.isArray(nameOrNames)) {
76+
selector.push(`:matches(${nameOrNames.map(name => `[name="${name}"]`).join(', ')})`);
77+
} else {
78+
selector.push(`[name="${nameOrNames}"]`);
79+
}
80+
81+
return selector.join('');
82+
}
83+
84+
module.exports = referenceIdentifierSelector;

test/prefer-number-properties.mjs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,20 @@ test({
184184
}
185185
`,
186186
'const {NaN} = {};',
187+
'const {a: NaN} = {};',
188+
'const {[a]: NaN} = {};',
189+
'const [NaN] = [];',
187190
'function NaN() {}',
191+
'const foo = function NaN() {}',
192+
'function foo(NaN) {}',
193+
'foo = function (NaN) {}',
194+
'foo = (NaN) => {}',
195+
'function foo({NaN}) {}',
196+
'function foo({a: NaN}) {}',
197+
'function foo({[a]: NaN}) {}',
198+
'function foo([NaN]) {}',
188199
'class NaN {}',
200+
'const Foo = class NaN {}',
189201
'class Foo {NaN(){}}',
190202
outdent`
191203
NaN: for (const foo of bar) {
@@ -358,6 +370,13 @@ test.snapshot({
358370
'const foo = 1 - Infinity;',
359371
'const foo = 1 - -Infinity;',
360372
'const isPositiveZero = value => value === 0 && 1 / value === Infinity;',
361-
'const isNegativeZero = value => value === 0 && 1 / value === -Infinity;'
373+
'const isNegativeZero = value => value === 0 && 1 / value === -Infinity;',
374+
375+
'const {a = NaN} = {};',
376+
'const {[NaN]: a = NaN} = {};',
377+
'const [a = NaN] = [];',
378+
'function foo({a = NaN}) {}',
379+
'function foo({[NaN]: a = NaN}) {}',
380+
'function foo([a = NaN]) {}'
362381
]
363382
});

test/snapshots/prefer-number-properties.mjs.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,3 +497,113 @@ Generated by [AVA](https://avajs.dev).
497497
> 1 | const isNegativeZero = value => value === 0 && 1 / value === -Infinity;␊
498498
| ^^^^^^^^^ Prefer \`Number.NEGATIVE_INFINITY\` over \`-Infinity\`.␊
499499
`
500+
501+
## Invalid #31
502+
1 | const {a = NaN} = {};
503+
504+
> Output
505+
506+
`␊
507+
1 | const {a = Number.NaN} = {};␊
508+
`
509+
510+
> Error 1/1
511+
512+
`␊
513+
> 1 | const {a = NaN} = {};␊
514+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
515+
`
516+
517+
## Invalid #32
518+
1 | const {[NaN]: a = NaN} = {};
519+
520+
> Output
521+
522+
`␊
523+
1 | const {[Number.NaN]: a = Number.NaN} = {};␊
524+
`
525+
526+
> Error 1/2
527+
528+
`␊
529+
> 1 | const {[NaN]: a = NaN} = {};␊
530+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
531+
`
532+
533+
> Error 2/2
534+
535+
`␊
536+
> 1 | const {[NaN]: a = NaN} = {};␊
537+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
538+
`
539+
540+
## Invalid #33
541+
1 | const [a = NaN] = [];
542+
543+
> Output
544+
545+
`␊
546+
1 | const [a = Number.NaN] = [];␊
547+
`
548+
549+
> Error 1/1
550+
551+
`␊
552+
> 1 | const [a = NaN] = [];␊
553+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
554+
`
555+
556+
## Invalid #34
557+
1 | function foo({a = NaN}) {}
558+
559+
> Output
560+
561+
`␊
562+
1 | function foo({a = Number.NaN}) {}␊
563+
`
564+
565+
> Error 1/1
566+
567+
`␊
568+
> 1 | function foo({a = NaN}) {}␊
569+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
570+
`
571+
572+
## Invalid #35
573+
1 | function foo({[NaN]: a = NaN}) {}
574+
575+
> Output
576+
577+
`␊
578+
1 | function foo({[Number.NaN]: a = Number.NaN}) {}␊
579+
`
580+
581+
> Error 1/2
582+
583+
`␊
584+
> 1 | function foo({[NaN]: a = NaN}) {}␊
585+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
586+
`
587+
588+
> Error 2/2
589+
590+
`␊
591+
> 1 | function foo({[NaN]: a = NaN}) {}␊
592+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
593+
`
594+
595+
## Invalid #36
596+
1 | function foo([a = NaN]) {}
597+
598+
> Output
599+
600+
`␊
601+
1 | function foo([a = Number.NaN]) {}␊
602+
`
603+
604+
> Error 1/1
605+
606+
`␊
607+
> 1 | function foo([a = NaN]) {}␊
608+
| ^^^ Prefer \`Number.NaN\` over \`NaN\`.␊
609+
`
293 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)