Skip to content

Commit 8d8ce43

Browse files
fiskersindresorhus
andauthored
Add prefer-number-properties rule (#622)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent 1ad43d0 commit 8d8ce43

File tree

5 files changed

+427
-0
lines changed

5 files changed

+427
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Prefer `Number` static properties over global ones.
2+
3+
Enforces the use of:
4+
5+
- [`Number.parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt) over [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) *(fixable)*
6+
- [`Number.parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat) over [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat) *(fixable)*
7+
- [`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))*
8+
- [`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))*
9+
- [`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)*
10+
11+
This rule is partly fixable.
12+
13+
## Fail
14+
15+
```js
16+
const foo = parseInt('10', 2);
17+
```
18+
19+
```js
20+
const foo = parseFloat('10.5');
21+
```
22+
23+
```js
24+
const foo = isNaN(10);
25+
```
26+
27+
```js
28+
const foo = isFinite(10);
29+
```
30+
31+
```js
32+
if (Object.is(foo, NaN)) {}
33+
```
34+
35+
```js
36+
const {parseInt} = Number;
37+
const foo = parseInt('10', 2);
38+
```
39+
40+
## Pass
41+
42+
```js
43+
const foo = Number.parseInt('10', 2);
44+
```
45+
46+
```js
47+
const foo = Number.parseFloat('10.5');
48+
```
49+
50+
```js
51+
const foo = Number.isNaN(10);
52+
```
53+
54+
```js
55+
const foo = Number.isFinite(10);
56+
```
57+
58+
```js
59+
if (Object.is(foo, Number.NaN)) {}
60+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ module.exports = {
5353
'unicorn/prefer-negative-index': 'error',
5454
'unicorn/prefer-node-append': 'error',
5555
'unicorn/prefer-node-remove': 'error',
56+
'unicorn/prefer-number-properties': 'error',
5657
'unicorn/prefer-query-selector': 'error',
5758
'unicorn/prefer-reflect-apply': 'error',
5859
// TODO: Enable this by default when it's shipping in a Node.js LTS version.

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Configure it in `package.json`.
6969
"unicorn/prefer-negative-index": "error",
7070
"unicorn/prefer-node-append": "error",
7171
"unicorn/prefer-node-remove": "error",
72+
"unicorn/prefer-number-properties": "error",
7273
"unicorn/prefer-query-selector": "error",
7374
"unicorn/prefer-reflect-apply": "error",
7475
"unicorn/prefer-replace-all": "off",
@@ -124,6 +125,7 @@ Configure it in `package.json`.
124125
- [prefer-negative-index](docs/rules/prefer-negative-index.md) - Prefer negative index over `.length - index` for `{String,Array,TypedArray}#slice()` and `Array#splice()`. *(fixable)*
125126
- [prefer-node-append](docs/rules/prefer-node-append.md) - Prefer `Node#append()` over `Node#appendChild()`. *(fixable)*
126127
- [prefer-node-remove](docs/rules/prefer-node-remove.md) - Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. *(fixable)*
128+
- [prefer-number-properties](docs/rules/prefer-number-properties.md) - Prefer `Number` static properties over global ones. *(fixable)*
127129
- [prefer-query-selector](docs/rules/prefer-query-selector.md) - Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`. *(partly fixable)*
128130
- [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) - Prefer `Reflect.apply()` over `Function#apply()`. *(fixable)*
129131
- [prefer-replace-all](docs/rules/prefer-replace-all.md) - Prefer `String#replaceAll()` over regex searches with the global flag. *(fixable)*

rules/prefer-number-properties.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
'use strict';
2+
const getDocumentationUrl = require('./utils/get-documentation-url');
3+
const isShadowed = require('./utils/is-shadowed');
4+
const renameIdentifier = require('./utils/rename-identifier');
5+
6+
const methodMessageId = 'method';
7+
const propertyMessageId = 'property';
8+
9+
const methods = {
10+
// Safe
11+
parseInt: true,
12+
parseFloat: true,
13+
// Unsafe
14+
isNaN: false,
15+
isFinite: false
16+
};
17+
18+
const methodsSelector = [
19+
'CallExpression',
20+
'>',
21+
'Identifier',
22+
`:matches(${Object.keys(methods).map(name => `[name="${name}"]`).join(', ')})`
23+
].join('');
24+
25+
const propertiesSelector = [
26+
`:not(${
27+
[
28+
'MemberExpression[computed=false]',
29+
'FunctionDeclaration',
30+
'ClassDeclaration',
31+
'MethodDefinition'
32+
].join(', ')
33+
})`,
34+
'>',
35+
'Identifier',
36+
'[name="NaN"]'
37+
].join('');
38+
39+
const create = context => {
40+
const sourceCode = context.getSourceCode();
41+
42+
return {
43+
[methodsSelector]: node => {
44+
if (isShadowed(context.getScope(), node)) {
45+
return;
46+
}
47+
48+
const {name} = node;
49+
const isSafe = methods[name];
50+
51+
const problem = {
52+
node,
53+
messageId: methodMessageId,
54+
data: {
55+
name
56+
}
57+
};
58+
59+
const fix = fixer => renameIdentifier(node, `Number.${name}`, fixer, sourceCode);
60+
61+
if (isSafe) {
62+
problem.fix = fix;
63+
} else {
64+
problem.suggest = [{messageId: methodMessageId, fix}];
65+
}
66+
67+
context.report(problem);
68+
},
69+
[propertiesSelector]: node => {
70+
if (isShadowed(context.getScope(), node)) {
71+
return;
72+
}
73+
74+
const {parent} = node;
75+
76+
if (
77+
parent &&
78+
(
79+
(parent.type === 'VariableDeclarator' && parent.id === node) ||
80+
(parent.type === 'Property' && !parent.shorthand && parent.key === node) ||
81+
(parent.type === 'TSDeclareFunction' && parent.id === node) ||
82+
parent.type === 'TSEnumMember' ||
83+
parent.type === 'TSPropertySignature'
84+
)
85+
) {
86+
return;
87+
}
88+
89+
const {name} = node;
90+
context.report({
91+
node,
92+
messageId: propertyMessageId,
93+
data: {
94+
name
95+
},
96+
fix: fixer => renameIdentifier(node, `Number.${name}`, fixer, sourceCode)
97+
});
98+
}
99+
};
100+
};
101+
102+
module.exports = {
103+
create,
104+
meta: {
105+
type: 'suggestion',
106+
docs: {
107+
url: getDocumentationUrl(__filename)
108+
},
109+
fixable: 'code',
110+
messages: {
111+
[methodMessageId]: 'Prefer `Number.{{name}}()` over `{{name}}()`.',
112+
[propertyMessageId]: 'Prefer `Number.{{name}}` over `{{name}}`.'
113+
}
114+
}
115+
};

0 commit comments

Comments
 (0)