Skip to content

Commit 3e6ae43

Browse files
authored
prefer-array-some: Check .findLast() (#1897)
1 parent d286dbc commit 3e6ae43

File tree

4 files changed

+87
-26
lines changed

4 files changed

+87
-26
lines changed

docs/rules/prefer-array-some.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`
1+
# Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`
22

33
<!-- Do not manually modify RULE_NOTICE part. Run: `npm run generate-rule-notices` -->
44
<!-- RULE_NOTICE -->
@@ -13,11 +13,11 @@ Prefer using [`Array#some`](https://developer.mozilla.org/en-US/docs/Web/JavaScr
1313

1414
We only check `.filter().length > 0` and `.filter().length !== 0`. These two non-zero length check styles are allowed in [`unicorn/explicit-length-check`](./explicit-length-check.md#options) rule. It is recommended to use them together.
1515

16-
- Using [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) to ensure at least one element in the array passes a given check.
16+
- Using [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) or [`Array#findLast()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLast) to ensure at least one element in the array passes a given check.
1717

18-
- Comparing the result of [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) with `undefined`.
18+
- Comparing the result of [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) or [`Array#findLast()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLast) with `undefined`.
1919

20-
This rule is fixable for `.filter(…).length` check and has a suggestion for `.find(…)`.
20+
This rule is fixable for `.filter(…).length` check and has a suggestion for `.{find,findLast}(…)`.
2121

2222
## Fail
2323

@@ -51,12 +51,36 @@ const hasUnicorn = array.find(element => isUnicorn(element) !== undefined;
5151
const hasUnicorn = array.find(element => isUnicorn(element) != null;
5252
```
5353
54+
```js
55+
if (array.find(element => isUnicorn(element))) {
56+
//
57+
}
58+
```
59+
60+
```js
61+
const foo = array.findLast(element => isUnicorn(element)) ? bar : baz;
62+
```
63+
64+
```js
65+
const hasUnicorn = array.findLast(element => isUnicorn(element) !== undefined;
66+
```
67+
68+
```js
69+
const hasUnicorn = array.findLast(element => isUnicorn(element) != null;
70+
```
71+
5472
```vue
5573
<template>
5674
<div v-if="array.find(element => isUnicorn(element))">Vue</div>
5775
</template>
5876
```
5977
78+
```vue
79+
<template>
80+
<div v-if="array.findLast(element => isUnicorn(element))">Vue</div>
81+
</template>
82+
```
83+
6084
```vue
6185
<template>
6286
<div v-if="array.filter(element => isUnicorn(element)).length > 0">Vue</div>
@@ -79,6 +103,10 @@ if (array.some(element => isUnicorn(element))) {
79103
const foo = array.find(element => isUnicorn(element)) || bar;
80104
```
81105
106+
```js
107+
const foo = array.findLast(element => isUnicorn(element)) || bar;
108+
```
109+
82110
```vue
83111
<template>
84112
<div v-if="array.some(element => isUnicorn(element))">Vue</div>

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Each rule has emojis denoting:
109109
| [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. || 🔧 | |
110110
| [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()`. || 🔧 | |
111111
| [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#indexOf()` over `Array#findIndex()` when looking for the index of an item. || 🔧 | 💡 |
112-
| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`. || 🔧 | 💡 |
112+
| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`. || 🔧 | 💡 |
113113
| [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | | 🔧 | 💡 |
114114
| [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. || | 💡 |
115115
| [prefer-date-now](docs/rules/prefer-date-now.md) | Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. || 🔧 | |

rules/prefer-array-some.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ const ERROR_ID_ARRAY_SOME = 'some';
1010
const SUGGESTION_ID_ARRAY_SOME = 'some-suggestion';
1111
const ERROR_ID_ARRAY_FILTER = 'filter';
1212
const messages = {
13-
[ERROR_ID_ARRAY_SOME]: 'Prefer `.some(…)` over `.find(…)`.',
14-
[SUGGESTION_ID_ARRAY_SOME]: 'Replace `.find(…)` with `.some(…)`.',
13+
[ERROR_ID_ARRAY_SOME]: 'Prefer `.some(…)` over `.{{method}}(…)`.',
14+
[SUGGESTION_ID_ARRAY_SOME]: 'Replace `.{{method}}(…)` with `.some(…)`.',
1515
[ERROR_ID_ARRAY_FILTER]: 'Prefer `.some(…)` over non-zero length check from `.filter(…)`.',
1616
};
1717

18-
const arrayFindCallSelector = methodCallSelector({
19-
method: 'find',
18+
const arrayFindOrFindLastCallSelector = methodCallSelector({
19+
methods: ['find', 'findLast'],
2020
minimumArguments: 1,
2121
maximumArguments: 2,
2222
});
@@ -59,30 +59,32 @@ const arrayFilterCallSelector = [
5959

6060
/** @param {import('eslint').Rule.RuleContext} context */
6161
const create = context => ({
62-
[arrayFindCallSelector](findCall) {
63-
const isCompare = isCheckingUndefined(findCall);
64-
if (!isCompare && !isBooleanNode(findCall)) {
62+
[arrayFindOrFindLastCallSelector](callExpression) {
63+
const isCompare = isCheckingUndefined(callExpression);
64+
if (!isCompare && !isBooleanNode(callExpression)) {
6565
return;
6666
}
6767

68-
const findProperty = findCall.callee.property;
68+
const methodNode = callExpression.callee.property;
6969
return {
70-
node: findProperty,
70+
node: methodNode,
7171
messageId: ERROR_ID_ARRAY_SOME,
72+
data: {method: methodNode.name},
7273
suggest: [
7374
{
7475
messageId: SUGGESTION_ID_ARRAY_SOME,
76+
data: {method: methodNode.name},
7577
* fix(fixer) {
76-
yield fixer.replaceText(findProperty, 'some');
78+
yield fixer.replaceText(methodNode, 'some');
7779

7880
if (!isCompare) {
7981
return;
8082
}
8183

82-
const parenthesizedRange = getParenthesizedRange(findCall, context.getSourceCode());
83-
yield fixer.replaceTextRange([parenthesizedRange[1], findCall.parent.range[1]], '');
84+
const parenthesizedRange = getParenthesizedRange(callExpression, context.getSourceCode());
85+
yield fixer.replaceTextRange([parenthesizedRange[1], callExpression.parent.range[1]], '');
8486

85-
if (findCall.parent.operator === '!=' || findCall.parent.operator === '!==') {
87+
if (callExpression.parent.operator === '!=' || callExpression.parent.operator === '!==') {
8688
return;
8789
}
8890

@@ -133,7 +135,7 @@ module.exports = {
133135
meta: {
134136
type: 'suggestion',
135137
docs: {
136-
description: 'Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`.',
138+
description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`.',
137139
},
138140
fixable: 'code',
139141
messages,

test/prefer-array-some.mjs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ const {test} = getTester(import.meta);
55

66
const ERROR_ID_ARRAY_SOME = 'some';
77
const SUGGESTION_ID_ARRAY_SOME = 'some-suggestion';
8-
const invalidCase = ({code, suggestionOutput}) => ({
8+
const invalidCase = ({code, suggestionOutput, method}) => ({
99
code,
1010
errors: [
1111
{
1212
messageId: ERROR_ID_ARRAY_SOME,
13+
data: {method},
1314
suggestions: [
1415
{
1516
messageId: SUGGESTION_ID_ARRAY_SOME,
17+
data: {method},
1618
output: suggestionOutput,
1719
},
1820
],
@@ -45,7 +47,10 @@ test({
4547
'foo.find()',
4648
'foo.find(fn, thisArgument, extraArgument)',
4749
'foo.find(...argumentsArray)',
48-
].map(code => `if (${code}) {}`),
50+
].flatMap(code => [
51+
`if (${code}) {}`,
52+
`if (${code.replace('find', 'findLast')}) {}`,
53+
]),
4954
],
5055
invalid: [
5156
...[
@@ -56,20 +61,30 @@ test({
5661
'while (foo.find(fn)) foo.shift();',
5762
'do {foo.shift();} while (foo.find(fn));',
5863
'for (; foo.find(fn); ) foo.shift();',
59-
].map(code => invalidCase({
60-
code,
61-
suggestionOutput: code.replace('find', 'some'),
62-
})),
64+
].flatMap(code => [
65+
invalidCase({
66+
code,
67+
suggestionOutput: code.replace('find', 'some'),
68+
method: 'find',
69+
}),
70+
invalidCase({
71+
code: code.replace('find', 'findLast'),
72+
suggestionOutput: code.replace('find', 'some'),
73+
method: 'findLast',
74+
}),
75+
]),
6376
// Comments
6477
invalidCase({
6578
code: 'console.log(foo /* comment 1 */ . /* comment 2 */ find /* comment 3 */ (fn) ? a : b)',
6679
suggestionOutput: 'console.log(foo /* comment 1 */ . /* comment 2 */ some /* comment 3 */ (fn) ? a : b)',
80+
method: 'find',
6781
}),
6882
// This should not be reported, but `jQuery.find()` is always `truly`,
6983
// It should not use as a boolean
7084
invalidCase({
7185
code: 'if (jQuery.find(".outer > div")) {}',
7286
suggestionOutput: 'if (jQuery.some(".outer > div")) {}',
87+
method: 'find',
7388
}),
7489
// Actual messages
7590
{
@@ -86,6 +101,20 @@ test({
86101
},
87102
],
88103
},
104+
{
105+
code: 'if (foo.findLast(fn)) {}',
106+
errors: [
107+
{
108+
message: 'Prefer `.some(…)` over `.findLast(…)`.',
109+
suggestions: [
110+
{
111+
desc: 'Replace `.findLast(…)` with `.some(…)`.',
112+
output: 'if (foo.some(fn)) {}',
113+
},
114+
],
115+
},
116+
],
117+
},
89118
],
90119
});
91120

@@ -184,10 +213,12 @@ test.vue({
184213
invalidCase({
185214
code: '<template><div v-if="foo.find(fn)"></div></template>',
186215
suggestionOutput: '<template><div v-if="foo.some(fn)"></div></template>',
216+
method: 'find',
187217
}),
188218
invalidCase({
189-
code: '<script>if (foo.find(fn));</script>',
219+
code: '<script>if (foo.findLast(fn));</script>',
190220
suggestionOutput: '<script>if (foo.some(fn));</script>',
221+
method: 'findLast',
191222
}),
192223
{
193224
code: '<template><div v-if="foo.filter(fn).length > 0"></div></template>',

0 commit comments

Comments
 (0)