Skip to content

Commit d812ad1

Browse files
authoredMay 25, 2024··
prefer-includes: Check .lastIndexOf() (#2368)
1 parent a449af9 commit d812ad1

File tree

6 files changed

+294
-54
lines changed

6 files changed

+294
-54
lines changed
 

‎docs/rules/prefer-includes.md

+32-28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence
1+
# Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence
22

33
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).
44

@@ -7,7 +7,7 @@
77
<!-- end auto-generated rule header -->
88
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
99

10-
All built-ins have `.includes()` in addition to `.indexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()`.
10+
All built-ins have `.includes()` in addition to `.indexOf()` and `.lastIndexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()` and `.lastIndexOf()`.
1111

1212
[`Array#some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) is intended for more complex needs. If you are just looking for the index where the given item is present, the code can be simplified to use [`Array#includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). This applies to any search with a literal, a variable, or any expression that doesn't have any explicit side effects. However, if the expression you are looking for relies on an item related to the function (its arguments, the function self, etc.), the case is still valid.
1313

@@ -16,99 +16,103 @@ This rule is fixable, unless the search expression in `Array#some()` has side ef
1616
## Fail
1717

1818
```js
19-
[].indexOf('foo') !== -1;
19+
array.indexOf('foo') !== -1;
2020
```
2121

2222
```js
23-
x.indexOf('foo') != -1;
23+
array.indexOf('foo') !== -1;
2424
```
2525

2626
```js
27-
str.indexOf('foo') > -1;
27+
string.lastIndexOf('foo') !== -1;
2828
```
2929

3030
```js
31-
'foobar'.indexOf('foo') >= 0;
31+
array.lastIndexOf('foo') !== -1;
3232
```
3333

3434
```js
35-
x.indexOf('foo') === -1
35+
foo.indexOf('foo') != -1;
3636
```
3737

3838
```js
39-
const isFound = foo.some(x => x === 'foo');
39+
foo.indexOf('foo') >= 0;
4040
```
4141

4242
```js
43-
const isFound = foo.some(x => 'foo' === x);
43+
foo.indexOf('foo') > -1;
4444
```
4545

4646
```js
47-
const isFound = foo.some(x => {
48-
return x === 'foo';
49-
});
47+
foo.indexOf('foo') === -1
5048
```
5149

52-
## Pass
50+
```js
51+
foo.some(x => x === 'foo');
52+
```
5353

5454
```js
55-
const str = 'foobar';
55+
foo.some(x => 'foo' === x);
5656
```
5757

5858
```js
59-
str.indexOf('foo') !== -n;
59+
foo.some(x => {
60+
return x === 'foo';
61+
});
6062
```
6163

64+
## Pass
65+
6266
```js
63-
str.indexOf('foo') !== 1;
67+
foo.indexOf('foo') !== -n;
6468
```
6569

6670
```js
67-
!str.indexOf('foo') === 1;
71+
foo.indexOf('foo') !== 1;
6872
```
6973

7074
```js
71-
!str.indexOf('foo') === -n;
75+
foo.indexOf('foo') === 1;
7276
```
7377

7478
```js
75-
str.includes('foo');
79+
foo.includes('foo');
7680
```
7781

7882
```js
79-
[1,2,3].includes(4);
83+
foo.includes(4);
8084
```
8185

8286
```js
83-
const isFound = foo.includes('foo');
87+
foo.includes('foo');
8488
```
8589

8690
```js
87-
const isFound = foo.some(x => x == undefined);
91+
foo.some(x => x == undefined);
8892
```
8993

9094
```js
91-
const isFound = foo.some(x => x !== 'foo');
95+
foo.some(x => x !== 'foo');
9296
```
9397

9498
```js
95-
const isFound = foo.some((x, index) => x === index);
99+
foo.some((x, index) => x === index);
96100
```
97101

98102
```js
99-
const isFound = foo.some(x => (x === 'foo') && isValid());
103+
foo.some(x => (x === 'foo') && isValid());
100104
```
101105

102106
```js
103-
const isFound = foo.some(x => y === 'foo');
107+
foo.some(x => y === 'foo');
104108
```
105109

106110
```js
107-
const isFound = foo.some(x => y.x === 'foo');
111+
foo.some(x => y.x === 'foo');
108112
```
109113

110114
```js
111-
const isFound = foo.some(x => {
115+
foo.some(x => {
112116
const bar = getBar();
113117
return x === bar;
114118
});

‎readme.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
188188
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. || | 💡 |
189189
| [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. || | |
190190
| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. || 🔧 | 💡 |
191-
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. || 🔧 | 💡 |
191+
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. || 🔧 | 💡 |
192192
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | 🔧 | |
193193
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. || 🔧 | |
194194
| [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. || | 💡 |

‎rules/prefer-includes.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ const {isLiteral} = require('./ast/index.js');
55

66
const MESSAGE_ID = 'prefer-includes';
77
const messages = {
8-
[MESSAGE_ID]: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.',
8+
[MESSAGE_ID]: 'Use `.includes()`, rather than `.{{method}}()`, when checking for existence.',
99
};
10-
// Ignore {_,lodash,underscore}.indexOf
10+
// Ignore `{_,lodash,underscore}.{indexOf,lastIndexOf}`
1111
const ignoredVariables = new Set(['_', 'lodash', 'underscore']);
1212
const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name);
1313
const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1;
@@ -30,6 +30,9 @@ const getProblem = (context, node, target, argumentsNodes) => {
3030
return {
3131
node: memberExpressionNode.property,
3232
messageId: MESSAGE_ID,
33+
data: {
34+
method: node.left.callee.property.name,
35+
},
3336
fix(fixer) {
3437
const replacement = `${isNegativeResult(node) ? '!' : ''}${targetSource}.includes(${argumentsSource.join(', ')})`;
3538
return fixer.replaceText(node, replacement);
@@ -49,7 +52,7 @@ const create = context => {
4952
context.on('BinaryExpression', node => {
5053
const {left, right, operator} = node;
5154

52-
if (!isMethodNamed(left, 'indexOf')) {
55+
if (!isMethodNamed(left, 'indexOf') && !isMethodNamed(left, 'lastIndexOf')) {
5356
return;
5457
}
5558

@@ -86,7 +89,7 @@ module.exports = {
8689
meta: {
8790
type: 'suggestion',
8891
docs: {
89-
description: 'Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence.',
92+
description: 'Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence.',
9093
recommended: true,
9194
},
9295
fixable: 'code',

‎test/prefer-includes.mjs

+13-11
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,22 @@ const {test} = getTester(import.meta);
55

66
test.snapshot({
77
valid: [
8-
'str.indexOf(\'foo\') !== -n',
9-
'str.indexOf(\'foo\') !== 1',
10-
'str.indexOf(\'foo\') === -2',
11-
'!str.indexOf(\'foo\') === 1',
12-
'!str.indexOf(\'foo\') === -n',
8+
...[
9+
'str.indexOf(\'foo\') !== -n',
10+
'str.indexOf(\'foo\') !== 1',
11+
'str.indexOf(\'foo\') === -2',
12+
'!str.indexOf(\'foo\') === 1',
13+
'!str.indexOf(\'foo\') === -n',
14+
'null.indexOf(\'foo\') !== 1',
15+
'something.indexOf(foo, 0, another) !== -1',
16+
'_.indexOf(foo, bar) !== -1',
17+
'lodash.indexOf(foo, bar) !== -1',
18+
'underscore.indexOf(foo, bar) !== -1',
19+
].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]),
1320
'str.includes(\'foo\')',
1421
'\'foobar\'.includes(\'foo\')',
1522
'[1,2,3].includes(4)',
16-
'null.indexOf(\'foo\') !== 1',
1723
'f(0) < 0',
18-
'something.indexOf(foo, 0, another) !== -1',
19-
'_.indexOf(foo, bar) !== -1',
20-
'lodash.indexOf(foo, bar) !== -1',
21-
'underscore.indexOf(foo, bar) !== -1',
2224
],
2325
invalid: [
2426
'\'foobar\'.indexOf(\'foo\') !== -1',
@@ -32,7 +34,7 @@ test.snapshot({
3234
'(a || b).indexOf(\'foo\') === -1',
3335
'foo.indexOf(bar, 0) !== -1',
3436
'foo.indexOf(bar, 1) !== -1',
35-
],
37+
].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]),
3638
});
3739

3840
const {snapshot, typescript} = tests({

0 commit comments

Comments
 (0)
Please sign in to comment.