Skip to content

Commit b6a5a50

Browse files
authored
Add prefer-array-some rule (#887)
1 parent 72e0390 commit b6a5a50

File tree

10 files changed

+311
-67
lines changed

10 files changed

+311
-67
lines changed

docs/rules/prefer-array-some.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Prefer `.some(…)` over `.find(…)`.
2+
3+
Prefer using [`Array#some`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) over [`Array#find`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) when ensuring at least one element in the array passes a given check.
4+
5+
## Fail
6+
7+
```js
8+
if (array.find(element => element === '🦄')) {
9+
//
10+
}
11+
```
12+
13+
```js
14+
const foo = array.find(element => element === '🦄') ? bar : baz;
15+
```
16+
17+
## Pass
18+
19+
```js
20+
if (array.some(element => element === '🦄')) {
21+
//
22+
}
23+
```
24+
25+
```js
26+
const foo = array.find(element => element === '🦄') || bar;
27+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ module.exports = {
6565
'unicorn/numeric-separators-style': 'off',
6666
'unicorn/prefer-add-event-listener': 'error',
6767
'unicorn/prefer-array-find': 'error',
68+
'unicorn/prefer-array-some': 'error',
6869
'unicorn/prefer-dataset': 'error',
6970
'unicorn/prefer-date-now': 'error',
7071
'unicorn/prefer-default-parameters': 'error',

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Configure it in `package.json`.
7070
"unicorn/numeric-separators-style": "off",
7171
"unicorn/prefer-add-event-listener": "error",
7272
"unicorn/prefer-array-find": "error",
73+
"unicorn/prefer-array-some": "error",
7374
"unicorn/prefer-dataset": "error",
7475
"unicorn/prefer-date-now": "error",
7576
"unicorn/prefer-default-parameters": "error",
@@ -140,6 +141,7 @@ Configure it in `package.json`.
140141
- [numeric-separators-style](docs/rules/numeric-separators-style.md) - Enforce the style of numeric separators by correctly grouping digits. *(fixable)*
141142
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)*
142143
- [prefer-array-find](docs/rules/prefer-array-find.md) - Prefer `.find(…)` over the first element from `.filter(…)`. *(partly fixable)*
144+
- [prefer-array-some](docs/rules/prefer-array-some.md) - Prefer `.some(…)` over `.find(…)`.
143145
- [prefer-dataset](docs/rules/prefer-dataset.md) - Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. *(fixable)*
144146
- [prefer-date-now](docs/rules/prefer-date-now.md) - Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. *(fixable)*
145147
- [prefer-default-parameters](docs/rules/prefer-default-parameters.md) - Prefer default parameters over reassignment. *(fixable)*

rules/explicit-length-check.js

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const {isParenthesized} = require('eslint-utils');
33
const getDocumentationUrl = require('./utils/get-documentation-url');
44
const isLiteralValue = require('./utils/is-literal-value');
5+
const {isBooleanNode, getBooleanAncestor} = require('./utils/boolean');
56

67
const TYPE_NON_ZERO = 'non-zero';
78
const TYPE_ZERO = 'zero';
@@ -12,23 +13,6 @@ const messages = {
1213
[MESSAGE_ID_SUGGESTION]: 'Replace `.length` with `.length {{code}}`.'
1314
};
1415

15-
const isLogicNot = node =>
16-
node &&
17-
node.type === 'UnaryExpression' &&
18-
node.operator === '!';
19-
const isLogicNotArgument = node =>
20-
isLogicNot(node.parent) &&
21-
node.parent.argument === node;
22-
const isBooleanCall = node =>
23-
node &&
24-
node.type === 'CallExpression' &&
25-
node.callee &&
26-
node.callee.type === 'Identifier' &&
27-
node.callee.name === 'Boolean' &&
28-
node.arguments.length === 1;
29-
const isBooleanCallArgument = node =>
30-
isBooleanCall(node.parent) &&
31-
node.parent.arguments[0] === node;
3216
const isCompareRight = (node, operator, value) =>
3317
node.type === 'BinaryExpression' &&
3418
node.operator === operator &&
@@ -72,23 +56,6 @@ const lengthSelector = [
7256
'[property.name="length"]'
7357
].join('');
7458

75-
function getBooleanAncestor(node) {
76-
let isNegative = false;
77-
// eslint-disable-next-line no-constant-condition
78-
while (true) {
79-
if (isLogicNotArgument(node)) {
80-
isNegative = !isNegative;
81-
node = node.parent;
82-
} else if (isBooleanCallArgument(node)) {
83-
node = node.parent;
84-
} else {
85-
break;
86-
}
87-
}
88-
89-
return {node, isNegative};
90-
}
91-
9259
function getLengthCheckNode(node) {
9360
node = node.parent;
9461

@@ -135,37 +102,6 @@ function getLengthCheckNode(node) {
135102
return {};
136103
}
137104

138-
function isBooleanNode(node) {
139-
if (
140-
isLogicNot(node) ||
141-
isLogicNotArgument(node) ||
142-
isBooleanCall(node) ||
143-
isBooleanCallArgument(node)
144-
) {
145-
return true;
146-
}
147-
148-
const {parent} = node;
149-
if (
150-
(
151-
parent.type === 'IfStatement' ||
152-
parent.type === 'ConditionalExpression' ||
153-
parent.type === 'WhileStatement' ||
154-
parent.type === 'DoWhileStatement' ||
155-
parent.type === 'ForStatement'
156-
) &&
157-
parent.test === node
158-
) {
159-
return true;
160-
}
161-
162-
if (parent.type === 'LogicalExpression') {
163-
return isBooleanNode(parent);
164-
}
165-
166-
return false;
167-
}
168-
169105
function create(context) {
170106
const options = {
171107
'non-zero': 'greater-than',

rules/prefer-array-some.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
const getDocumentationUrl = require('./utils/get-documentation-url');
3+
const methodSelector = require('./utils/method-selector');
4+
const {isBooleanNode} = require('./utils/boolean');
5+
6+
const MESSAGE_ID_ERROR = 'error';
7+
const MESSAGE_ID_SUGGESTION = 'suggestion';
8+
const messages = {
9+
[MESSAGE_ID_ERROR]: 'Prefer `.some(…)` over `.find(…)`.',
10+
[MESSAGE_ID_SUGGESTION]: 'Replace `.find(…)` with `.some(…)`.'
11+
};
12+
13+
const arrayFindCallSelector = methodSelector({
14+
name: 'find',
15+
min: 1,
16+
max: 2
17+
});
18+
19+
const create = context => {
20+
return {
21+
[arrayFindCallSelector](node) {
22+
if (isBooleanNode(node)) {
23+
node = node.callee.property;
24+
context.report({
25+
node,
26+
messageId: MESSAGE_ID_ERROR,
27+
suggest: [
28+
{
29+
messageId: MESSAGE_ID_SUGGESTION,
30+
fix: fixer => fixer.replaceText(node, 'some')
31+
}
32+
]
33+
});
34+
}
35+
}
36+
};
37+
};
38+
39+
module.exports = {
40+
create,
41+
meta: {
42+
type: 'suggestion',
43+
docs: {
44+
url: getDocumentationUrl(__filename)
45+
},
46+
messages
47+
}
48+
};

rules/prefer-event-key.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ const create = context => {
171171

172172
if (
173173
references &&
174-
references.find(reference => isPropertyOf(node, reference.identifier))
174+
references.some(reference => isPropertyOf(node, reference.identifier))
175175
) {
176176
report(node);
177177
}
@@ -201,7 +201,7 @@ const create = context => {
201201
// Make sure initObject is a reference of eventVariable
202202
if (
203203
references &&
204-
references.find(reference => reference.identifier === initObject)
204+
references.some(reference => reference.identifier === initObject)
205205
) {
206206
report(node.value);
207207
return;

rules/utils/boolean.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use strict';
2+
3+
const isLogicNot = node =>
4+
node &&
5+
node.type === 'UnaryExpression' &&
6+
node.operator === '!';
7+
const isLogicNotArgument = node =>
8+
isLogicNot(node.parent) &&
9+
node.parent.argument === node;
10+
const isBooleanCallArgument = node =>
11+
isBooleanCall(node.parent) &&
12+
node.parent.arguments[0] === node;
13+
const isBooleanCall = node =>
14+
node &&
15+
node.type === 'CallExpression' &&
16+
node.callee &&
17+
node.callee.type === 'Identifier' &&
18+
node.callee.name === 'Boolean' &&
19+
node.arguments.length === 1;
20+
21+
/**
22+
Check if the value of node is a `boolean`.
23+
24+
@param {Node} node
25+
@returns {boolean}
26+
*/
27+
function isBooleanNode(node) {
28+
if (
29+
isLogicNot(node) ||
30+
isLogicNotArgument(node) ||
31+
isBooleanCall(node) ||
32+
isBooleanCallArgument(node)
33+
) {
34+
return true;
35+
}
36+
37+
const {parent} = node;
38+
if (
39+
(
40+
parent.type === 'IfStatement' ||
41+
parent.type === 'ConditionalExpression' ||
42+
parent.type === 'WhileStatement' ||
43+
parent.type === 'DoWhileStatement' ||
44+
parent.type === 'ForStatement'
45+
) &&
46+
parent.test === node
47+
) {
48+
return true;
49+
}
50+
51+
if (parent.type === 'LogicalExpression') {
52+
return isBooleanNode(parent);
53+
}
54+
55+
return false;
56+
}
57+
58+
/**
59+
Get the boolean type-casting ancestor.
60+
61+
@typedef {{ node: Node, isNegative: boolean }} Result
62+
63+
@param {Node} node
64+
@returns {Result}
65+
*/
66+
function getBooleanAncestor(node) {
67+
let isNegative = false;
68+
// eslint-disable-next-line no-constant-condition
69+
while (true) {
70+
if (isLogicNotArgument(node)) {
71+
isNegative = !isNegative;
72+
node = node.parent;
73+
} else if (isBooleanCallArgument(node)) {
74+
node = node.parent;
75+
} else {
76+
break;
77+
}
78+
}
79+
80+
return {node, isNegative};
81+
}
82+
83+
module.exports = {isBooleanNode, getBooleanAncestor};

0 commit comments

Comments
 (0)