Skip to content

Commit

Permalink
prefer-number-properties: Add Infinity support (#874)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Oct 20, 2020
1 parent 6495c71 commit 643169b
Show file tree
Hide file tree
Showing 8 changed files with 539 additions and 14 deletions.
53 changes: 53 additions & 0 deletions docs/rules/prefer-number-properties.md
Expand Up @@ -7,6 +7,8 @@ Enforces the use of:
- [`Number.isNaN()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) over [`isNaN()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN) *(they have slightly [different behavior](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN#Description))*
- [`Number.isFinite()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) over [`isFinite()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) *(they have slightly [different behavior](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite#Description))*
- [`Number.NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN) over [`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN) *(fixable)*
- [`Number.POSITIVE_INFINITY`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY) over [`Infinity`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity) *(fixable)*
- [`Number.NEGATIVE_INFINITY`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NEGATIVE_INFINITY) over [`-Infinity`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity) *(fixable)*

This rule is partly fixable.

Expand All @@ -32,6 +34,14 @@ const foo = isFinite(10);
if (Object.is(foo, NaN)) {}
```

```js
const isPositiveZero = value => value === 0 && 1 / value === Infinity;
```

```js
const isNegativeZero = value => value === 0 && 1 / value === -Infinity;
```

```js
const {parseInt} = Number;
const foo = parseInt('10', 2);
Expand All @@ -58,3 +68,46 @@ const foo = Number.isFinite(10);
```js
if (Object.is(foo, Number.NaN)) {}
```

```js
const isPositiveZero = value => value === 0 && 1 / value === Number.POSITIVE_INFINITY;
```

```js
const isNegativeZero = value => value === 0 && 1 / value === Number.NEGATIVE_INFINITY;
```

# Options

Type: `object`

### checkInfinity

Type: `boolean`\
Default: `true`

Pass `checkInfinity: false` to disable check on `Infinity`.

#### Fail

```js
// eslint unicorn/prefer-number-properties: ["error", {checkInfinity: true}]
const foo = Infinity;
```

```js
// eslint unicorn/prefer-number-properties: ["error", {checkInfinity: true}]
const foo = -Infinity;
```

#### Pass

```js
// eslint unicorn/prefer-number-properties: ["error", {checkInfinity: false}]
const foo = Infinity;
```

```js
// eslint unicorn/prefer-number-properties: ["error", {checkInfinity: false}]
const foo = -Infinity;
```
57 changes: 49 additions & 8 deletions rules/prefer-number-properties.js
Expand Up @@ -9,7 +9,7 @@ const PROPERTY_ERROR_MESSAGE_ID = 'property-error';
const messages = {
[METHOD_ERROR_MESSAGE_ID]: 'Prefer `Number.{{name}}()` over `{{name}}()`.',
[METHOD_SUGGESTION_MESSAGE_ID]: 'Replace `{{name}}()` with `Number.{{name}}()`.',
[PROPERTY_ERROR_MESSAGE_ID]: 'Prefer `Number.{{name}}` over `{{name}}`.'
[PROPERTY_ERROR_MESSAGE_ID]: 'Prefer `Number.{{property}}` over `{{identifier}}`.'
};

const methods = {
Expand All @@ -30,7 +30,7 @@ const methodsSelector = [

const propertiesSelector = [
'Identifier',
'[name="NaN"]',
':matches([name="NaN"],[name="Infinity"])',
`:not(${
[
'MemberExpression[computed=false] > Identifier.property',
Expand All @@ -46,10 +46,19 @@ const propertiesSelector = [
})`
].join('');

const isNegative = node => {
const {parent} = node;
return parent && parent.type === 'UnaryExpression' && parent.operator === '-' && parent.argument === node;
};

const create = context => {
const sourceCode = context.getSourceCode();
const options = {
checkInfinity: true,
...context.options[0]
};

// Cache `NaN` in `foo = {NaN}`
// Cache `NaN` and `Infinity` in `foo = {NaN, Infinity}`
const reported = new WeakSet();

return {
Expand Down Expand Up @@ -93,20 +102,51 @@ const create = context => {
}

const {name} = node;
context.report({
if (name === 'Infinity' && !options.checkInfinity) {
return;
}

let property = name;
if (name === 'Infinity') {
property = isNegative(node) ? 'NEGATIVE_INFINITY' : 'POSITIVE_INFINITY';
}

const problem = {
node,
messageId: PROPERTY_ERROR_MESSAGE_ID,
data: {
name
},
fix: fixer => renameIdentifier(node, `Number.${name}`, fixer, sourceCode)
});
identifier: name,
property
}
};

if (property === 'NEGATIVE_INFINITY') {
problem.node = node.parent;
problem.data.identifier = '-Infinity';
problem.fix = fixer => fixer.replaceText(node.parent, 'Number.NEGATIVE_INFINITY');
} else {
problem.fix = fixer => renameIdentifier(node, `Number.${property}`, fixer, sourceCode);
}

context.report(problem);
reported.add(node);
}
};
};

const schema = [
{
type: 'object',
properties: {
checkInfinity: {
type: 'boolean',
default: true
}
},
additionalProperties: false
}
];

module.exports = {
create,
meta: {
Expand All @@ -115,6 +155,7 @@ module.exports = {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema,
messages
}
};
2 changes: 1 addition & 1 deletion rules/utils/cartesian-product-samples.js
Expand Up @@ -9,7 +9,7 @@ const getTotal = combinations => {
return total;
};

module.exports = (combinations, length = Infinity) => {
module.exports = (combinations, length = Number.POSITIVE_INFINITY) => {
const total = getTotal(combinations);

const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => {
Expand Down
2 changes: 1 addition & 1 deletion rules/utils/method-selector.js
Expand Up @@ -11,7 +11,7 @@ module.exports = options => {
property = ''
} = {
min: 0,
max: Infinity,
max: Number.POSITIVE_INFINITY,
...options
};

Expand Down
64 changes: 61 additions & 3 deletions test/prefer-number-properties.js
Expand Up @@ -153,12 +153,13 @@ test({
]
});

// NaN
// `NaN` and `Infinity`
const errorNaN = [
{
messageId: PROPERTY_ERROR_MESSAGE_ID,
data: {
name: 'NaN'
identifier: 'NaN',
property: 'NaN'
}
}
];
Expand All @@ -167,6 +168,8 @@ test({
'const foo = Number.NaN;',
'const foo = window.Number.NaN;',
'const foo = bar.NaN;',
'const foo = nan;',
'const foo = "NaN";',
// Shadowed
outdent`
function foo () {
Expand All @@ -177,7 +180,36 @@ test({
'const {NaN} = {};',
'function NaN() {}',
'class NaN {}',
'class Foo { NaN(){}}'
'class Foo { NaN(){}}',

'const foo = Number.POSITIVE_INFINITY;',
'const foo = window.Number.POSITIVE_INFINITY;',
'const foo = bar.POSITIVE_INFINITY;',
'const foo = Number.Infinity;',
'const foo = window.Number.Infinity;',
'const foo = bar.Infinity;',
'const foo = infinity;',
'const foo = "Infinity";',
'const foo = "-Infinity";',
// Shadowed
outdent`
function foo () {
const Infinity = 2
return Infinity
}
`,
'const {Infinity} = {};',
'function Infinity() {}',
'class Infinity {}',
'class Foo { Infinity(){}}',
{
code: 'const foo = Infinity;',
options: [{checkInfinity: false}]
},
{
code: 'const foo = -Infinity;',
options: [{checkInfinity: false}]
}
],
invalid: [
{
Expand Down Expand Up @@ -250,3 +282,29 @@ test.typescript({
],
invalid: []
});

test.visualize([
'const foo = Infinity;',
'if (Number.isNaN(Infinity)) {}',
'if (Object.is(foo, Infinity)) {}',
'const foo = bar[Infinity];',
'const foo = {Infinity};',
'const foo = {Infinity: Infinity};',
'const foo = {Infinity: -Infinity};',
'const {foo = Infinity} = {};',
'const {foo = -Infinity} = {};',
'const foo = Infinity.toString();',
'const foo = -Infinity.toString();',
'const foo = (-Infinity).toString();',
'const foo = +Infinity;',
'const foo = ++Infinity;',
'const foo = +-Infinity;',
'const foo = -Infinity;',
'const foo = --Infinity;',
'const foo = -(-Infinity);',
'const foo = -(--Infinity);',
'const foo = 1 - Infinity;',
'const foo = 1 - -Infinity;',
'const isPositiveZero = value => value === 0 && 1 / value === Infinity;',
'const isNegativeZero = value => value === 0 && 1 / value === -Infinity;'
]);

0 comments on commit 643169b

Please sign in to comment.