Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefer-array-some rule #887

Merged
merged 16 commits into from Dec 24, 2020
41 changes: 41 additions & 0 deletions docs/rules/prefer-array-some.md
@@ -0,0 +1,41 @@
# Prefer `.some(…)` over `.find(…)`.

Prefer use [`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 testing array.
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved

## Fail

```js
if (array.find(element => element === '🦄')) {
}
```

```js
const foo = array.find(element => element === '🦄') ? bar : baz;
```

```js
while (array.find(element => element === '🦄')) {
array.shift();
}
```

## Pass

```js
if (array.some(element => element === '🦄')) {
}
```

```js
const foo = array.some(element => element === '🦄') ? bar : baz;
```

```js
const foo = bar ? array.find(element => element === '🦄') : '';
```

```js
while (array.some(element => element === '🦄')) {
array.shift();
}
```
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -53,6 +53,7 @@ module.exports = {
'unicorn/numeric-separators-style': 'off',
'unicorn/prefer-add-event-listener': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-dataset': 'error',
'unicorn/prefer-event-key': 'error',
// TODO: Enable this by default when targeting Node.js 12.
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -68,6 +68,7 @@ Configure it in `package.json`.
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-add-event-listener": "error",
"unicorn/prefer-array-find": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-dataset": "error",
"unicorn/prefer-event-key": "error",
"unicorn/prefer-flat-map": "error",
Expand Down Expand Up @@ -134,6 +135,7 @@ Configure it in `package.json`.
- [numeric-separators-style](docs/rules/numeric-separators-style.md) - Enforce the style of numeric separators by correctly grouping digits. *(fixable)*
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)*
- [prefer-array-find](docs/rules/prefer-array-find.md) - Prefer `.find(…)` over the first element from `.filter(…)`. *(partly fixable)*
- [prefer-array-some](docs/rules/prefer-array-some.md) - Prefer `.some(…)` over `.find(…)`.
- [prefer-dataset](docs/rules/prefer-dataset.md) - Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. *(fixable)*
- [prefer-event-key](docs/rules/prefer-event-key.md) - Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. *(partly fixable)*
- [prefer-flat-map](docs/rules/prefer-flat-map.md) - Prefer `.flatMap(…)` over `.map(…).flat()`. *(fixable)*
Expand Down
53 changes: 53 additions & 0 deletions rules/prefer-array-some.js
@@ -0,0 +1,53 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');

const MESSAGE_ID_ERROR = 'error';
const MESSAGE_ID_SUGGESTION = 'suggestion';
const messages = {
[MESSAGE_ID_ERROR]: 'Prefer `.some(…)` over `.find(…)`.',
[MESSAGE_ID_SUGGESTION]: 'Replace `.find(…)` with `.some(…)`.'
};

const selector = [
':matches(IfStatement, ConditionalExpression, ForStatement, WhileStatement, DoWhileStatement)',
'>',
methodSelector({
name: 'find',
min: 1,
max: 2
}),
'.test',
'>',
'.callee',
'>',
'.property'
].join('');

const create = context => {
return {
[selector](node) {
context.report({
node,
messageId: MESSAGE_ID_ERROR,
suggest: [
{
messageId: MESSAGE_ID_SUGGESTION,
fix: fixer => fixer.replaceText(node, 'some')
}
]
});
}
};
};

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
messages
}
};
137 changes: 137 additions & 0 deletions test/prefer-array-some.js
@@ -0,0 +1,137 @@
import {outdent} from 'outdent';
import {flatten} from 'lodash';
import {test} from './utils/test';

const MESSAGE_ID_ERROR = 'error';
const MESSAGE_ID_SUGGESTION = 'suggestion';

const invalidCase = ({code, suggestionOutput}) => ({
code,
output: code,
errors: [
{
messageId: MESSAGE_ID_ERROR,
suggestions: [
{
messageId: MESSAGE_ID_SUGGESTION,
output: suggestionOutput
}
]
}
]
});

test({
valid: [
'if (foo.some(fn)) {}',
'if (foo.every(fn)) {}',

// Not `{IfStatement, ConditionalExpression, ForStatement, WhileStatement, DoWhileStatement}.test`
'if (true) foo.find(fn); else foo.find(fn);',
'if (true) { foo.find(fn); } else { foo.find(fn); }',
'true ? foo.find(fn) : foo.find(fn)',
'for (foo.find(fn); true; foo.find(fn)) foo.find(fn);',
'while(true) foo.find(fn);',
'do foo.find(fn) ; while(true)',
// `SwitchCase.test`
'switch (foo.find(fn)){ case foo.find(fn): foo.find(fn)}',

// Not matched `CallExpression`
...flatten(
[
// Not `CallExpression`
'new foo.find(fn)',
// Not `MemberExpression`
'find(fn)',
// `callee.property` is not a `Identifier`
'foo["find"](fn)',
'foo["fi" + "nd"](fn)',
'foo[`find`](fn)',
// Computed
'foo[find](fn)',
// Not `.find`
'foo.notFind(fn)',
// More or less argument(s)
'foo.find()',
'foo.find(fn, thisArgument, extraArgument)',
'foo.find(...argumentsArray)'
].map(code => [
`${code} ? 1 : 2`,
`if (${code}) {}`
])
)
],
invalid: [
invalidCase({
code: 'if (foo.find(fn)) {}',
suggestionOutput: 'if (foo.some(fn)) {}'
}),
invalidCase({
code: 'console.log(foo.find(fn) ? a : b)',
suggestionOutput: 'console.log(foo.some(fn) ? a : b)'
}),
invalidCase({
code: 'for(;foo.find(fn);) foo.shift();',
suggestionOutput: 'for(;foo.some(fn);) foo.shift();'
}),
invalidCase({
code: 'while(foo.find(fn)) foo.shift();',
suggestionOutput: 'while(foo.some(fn)) foo.shift();'
}),
invalidCase({
code: 'do {foo.shift();} while(foo.find(fn));',
suggestionOutput: 'do {foo.shift();} while(foo.some(fn));'
}),
invalidCase({
code: 'if (foo.find(fn, thisArgument)) {}',
suggestionOutput: 'if (foo.some(fn, thisArgument)) {}'
}),
invalidCase({
code: 'if (foo().bar.find(fn)) {}',
suggestionOutput: 'if (foo().bar.some(fn)) {}'
}),
// Comments
invalidCase({
code: 'console.log(foo /* comment 1 */ . /* comment 2 */ find /* comment 3 */ (fn) ? a : b)',
suggestionOutput: 'console.log(foo /* comment 1 */ . /* comment 2 */ some /* comment 3 */ (fn) ? a : b)'
}),
// This should not be reported, but `jQuery.find()` is always `truly`,
// Nobody use it in `IfStatement.test`
invalidCase({
code: 'if(jQuery.find(".outer > div")) {}',
suggestionOutput: 'if(jQuery.some(".outer > div")) {}'
}),
// Actual messages
{
code: 'if (foo.find(fn)) {}',
output: 'if (foo.find(fn)) {}',
errors: [
{
message: 'Prefer `.some(…)` over `.find(…)`.',
suggestions: [
{
desc: 'Replace `.find(…)` with `.some(…)`.',
output: 'if (foo.some(fn)) {}'
}
]
}
]
}
]
});

test.visualize([
'if (array.find(element => element === "🦄")) {}',
'const foo = array.find(element => element === "🦄") ? bar : baz;',
outdent`
if (
array
/* correct */.find(element => Array.isArray(element))
/* incorrect */.find(element => element/* incorrect */.find(fn) ? 1 : 0)
) {
console.log(jQuery/* correct */.find('div'));
} else {
console.log(array/* incorrect */.find(fn) ? 'yes' : 'no');
}
`
]);
65 changes: 65 additions & 0 deletions test/snapshots/prefer-array-some.js.md
@@ -0,0 +1,65 @@
# Snapshot report for `test/prefer-array-some.js`

The actual snapshot is saved in `prefer-array-some.js.snap`.

Generated by [AVA](https://avajs.dev).

## prefer-array-some - #1

> Snapshot 1

`␊
Error 1/1:␊
> 1 | if (array.find(element => element === "🦄")) {}␊
| ^^^^ Prefer `.some(…)` over `.find(…)`.␊
`

## prefer-array-some - #2

> Snapshot 1

`␊
Error 1/1:␊
> 1 | const foo = array.find(element => element === "🦄") ? bar : baz;␊
| ^^^^ Prefer `.some(…)` over `.find(…)`.␊
`

## prefer-array-some - #3

> Snapshot 1

`␊
Error 1/3:␊
1 | if (␊
2 | array␊
3 | /* correct */.find(element => Array.isArray(element))␊
> 4 | /* incorrect */.find(element => element/* incorrect */.find(fn) ? 1 : 0)␊
| ^^^^ Prefer `.some(…)` over `.find(…)`.␊
5 | ) {␊
6 | console.log(jQuery/* correct */.find('div'));␊
7 | } else {␊
8 | console.log(array/* incorrect */.find(fn) ? 'yes' : 'no');␊
9 | }␊
Error 2/3:␊
1 | if (␊
2 | array␊
3 | /* correct */.find(element => Array.isArray(element))␊
> 4 | /* incorrect */.find(element => element/* incorrect */.find(fn) ? 1 : 0)␊
| ^^^^ Prefer `.some(…)` over `.find(…)`.␊
5 | ) {␊
6 | console.log(jQuery/* correct */.find('div'));␊
7 | } else {␊
8 | console.log(array/* incorrect */.find(fn) ? 'yes' : 'no');␊
9 | }␊
Error 3/3:␊
1 | if (␊
2 | array␊
3 | /* correct */.find(element => Array.isArray(element))␊
4 | /* incorrect */.find(element => element/* incorrect */.find(fn) ? 1 : 0)␊
5 | ) {␊
6 | console.log(jQuery/* correct */.find('div'));␊
7 | } else {␊
> 8 | console.log(array/* incorrect */.find(fn) ? 'yes' : 'no');␊
| ^^^^ Prefer `.some(…)` over `.find(…)`.␊
9 | }␊
`
Binary file added test/snapshots/prefer-array-some.js.snap
Binary file not shown.