Skip to content

Commit 32851cc

Browse files
fiskersindresorhus
authored andcommitted
Improve prefer-includes rule (#411)
1 parent 061989f commit 32851cc

File tree

2 files changed

+58
-19
lines changed

2 files changed

+58
-19
lines changed

rules/prefer-includes.js

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,30 @@
22
const getDocsUrl = require('./utils/get-docs-url');
33
const isMethodNamed = require('./utils/is-method-named');
44

5+
// Ignore {_,lodash,underscore}.indexOf
6+
const ignoredVariables = new Set(['_', 'lodash', 'underscore']);
57
const isNegativeOne = (operator, value) => operator === '-' && value === 1;
8+
const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name);
69

7-
const report = (context, node, target, pattern) => {
10+
const report = (context, node, target, argumentsNodes) => {
811
const sourceCode = context.getSourceCode();
912
const memberExpressionNode = target.parent;
1013
const dotToken = sourceCode.getTokenBefore(memberExpressionNode.property);
1114
const targetSource = sourceCode.getText().slice(memberExpressionNode.range[0], dotToken.range[0]);
12-
const patternSource = sourceCode.getText(pattern);
15+
16+
// Strip default `fromIndex`
17+
if (argumentsNodes.length === 2 && argumentsNodes[1].type === 'Literal' && argumentsNodes[1].value === 0) {
18+
argumentsNodes = argumentsNodes.slice(0, 1);
19+
}
20+
21+
const argumentsSource = argumentsNodes.map(argument => sourceCode.getText(argument));
1322

1423
context.report({
1524
node,
1625
message: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.',
1726
fix: fixer => {
1827
const isNot = node => ['===', '==', '<'].includes(node.operator) ? '!' : '';
19-
const replacement = `${isNot(node)}${targetSource}.includes(${patternSource})`;
28+
const replacement = `${isNot(node)}${targetSource}.includes(${argumentsSource.join(', ')})`;
2029
return fixer.replaceText(node, replacement);
2130
}
2231
});
@@ -26,29 +35,39 @@ const create = context => ({
2635
BinaryExpression: node => {
2736
const {left, right} = node;
2837

29-
if (isMethodNamed(left, 'indexOf')) {
30-
const target = left.callee.object;
31-
const pattern = left.arguments[0];
38+
if (!isMethodNamed(left, 'indexOf')) {
39+
return;
40+
}
41+
42+
const target = left.callee.object;
3243

33-
if (right.type === 'UnaryExpression') {
34-
const {argument} = right;
44+
if (isIgnoredTarget(target)) {
45+
return;
46+
}
47+
48+
const {arguments: argumentsNodes} = left;
3549

36-
if (argument.type !== 'Literal') {
37-
return false;
38-
}
50+
// Ignore something.indexOf(foo, 0, another)
51+
if (argumentsNodes.length > 2) {
52+
return;
53+
}
3954

40-
const {value} = argument;
55+
if (right.type === 'UnaryExpression') {
56+
const {argument} = right;
4157

42-
if (['!==', '!=', '>', '===', '=='].includes(node.operator) && isNegativeOne(right.operator, value)) {
43-
report(context, node, target, pattern);
44-
}
58+
if (argument.type !== 'Literal') {
59+
return;
4560
}
4661

47-
if (right.type === 'Literal' && ['>=', '<'].includes(node.operator) && right.value === 0) {
48-
report(context, node, target, pattern);
62+
const {value} = argument;
63+
64+
if (['!==', '!=', '>', '===', '=='].includes(node.operator) && isNegativeOne(right.operator, value)) {
65+
report(context, node, target, argumentsNodes);
4966
}
67+
}
5068

51-
return false;
69+
if (right.type === 'Literal' && ['>=', '<'].includes(node.operator) && right.value === 0) {
70+
report(context, node, target, argumentsNodes);
5271
}
5372
}
5473
});

test/prefer-includes.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ ruleTester.run('prefer-includes', rule, {
1717
valid: [
1818
'str.indexOf(\'foo\') !== -n',
1919
'str.indexOf(\'foo\') !== 1',
20+
'str.indexOf(\'foo\') === -2',
2021
'!str.indexOf(\'foo\') === 1',
2122
'!str.indexOf(\'foo\') === -n',
2223
'str.includes(\'foo\')',
2324
'\'foobar\'.includes(\'foo\')',
2425
'[1,2,3].includes(4)',
2526
'null.indexOf(\'foo\') !== 1',
26-
'f(0) < 0'
27+
'f(0) < 0',
28+
'something.indexOf(foo, 0, another) !== -1',
29+
'_.indexOf(foo, bar) !== -1',
30+
'lodash.indexOf(foo, bar) !== -1',
31+
'underscore.indexOf(foo, bar) !== -1'
2732
],
2833
invalid: [
2934
{
@@ -41,6 +46,11 @@ ruleTester.run('prefer-includes', rule, {
4146
output: 'str.includes(\'foo\')',
4247
errors
4348
},
49+
{
50+
code: 'str.indexOf(\'foo\') == -1',
51+
output: '!str.includes(\'foo\')',
52+
errors
53+
},
4454
{
4555
code: '\'foobar\'.indexOf(\'foo\') >= 0',
4656
output: '\'foobar\'.includes(\'foo\')',
@@ -65,6 +75,16 @@ ruleTester.run('prefer-includes', rule, {
6575
code: '(a || b).indexOf(\'foo\') === -1',
6676
output: '!(a || b).includes(\'foo\')',
6777
errors
78+
},
79+
{
80+
code: 'foo.indexOf(bar, 0) !== -1',
81+
output: 'foo.includes(bar)',
82+
errors
83+
},
84+
{
85+
code: 'foo.indexOf(bar, 1) !== -1',
86+
output: 'foo.includes(bar, 1)',
87+
errors
6888
}
6989
]
7090
});

0 commit comments

Comments
 (0)