-
-
Notifications
You must be signed in to change notification settings - Fork 354
/
simple-array-search-rule.js
128 lines (112 loc) · 4.04 KB
/
simple-array-search-rule.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
'use strict';
const {hasSideEffect, isParenthesized, findVariable} = require('eslint-utils');
const {matches, methodCallSelector} = require('../selectors/index.js');
const isFunctionSelfUsedInside = require('../utils/is-function-self-used-inside.js');
const getBinaryExpressionSelector = path => [
`[${path}.type="BinaryExpression"]`,
`[${path}.operator="==="]`,
`:matches([${path}.left.type="Identifier"], [${path}.right.type="Identifier"])`,
].join('');
const getFunctionSelector = path => [
`[${path}.generator!=true]`,
`[${path}.async!=true]`,
`[${path}.params.length=1]`,
`[${path}.params.0.type="Identifier"]`,
].join('');
const callbackFunctionSelector = path => matches([
// Matches `foo.findIndex(bar => bar === baz)`
[
`[${path}.type="ArrowFunctionExpression"]`,
getFunctionSelector(path),
getBinaryExpressionSelector(`${path}.body`),
].join(''),
// Matches `foo.findIndex(bar => {return bar === baz})`
// Matches `foo.findIndex(function (bar) {return bar === baz})`
[
`:matches([${path}.type="ArrowFunctionExpression"], [${path}.type="FunctionExpression"])`,
getFunctionSelector(path),
`[${path}.body.type="BlockStatement"]`,
`[${path}.body.body.length=1]`,
`[${path}.body.body.0.type="ReturnStatement"]`,
getBinaryExpressionSelector(`${path}.body.body.0.argument`),
].join(''),
]);
const isIdentifierNamed = ({type, name}, expectName) => type === 'Identifier' && name === expectName;
function simpleArraySearchRule({method, replacement}) {
// Add prefix to avoid conflicts in `prefer-includes` rule
const MESSAGE_ID_PREFIX = `prefer-${replacement}-over-${method}/`;
const ERROR = `${MESSAGE_ID_PREFIX}error`;
const SUGGESTION = `${MESSAGE_ID_PREFIX}suggestion`;
const ERROR_MESSAGES = {
findIndex: 'Use `.indexOf()` instead of `.findIndex()` when looking for the index of an item.',
findLastIndex: 'Use `.lastIndexOf()` instead of `findLastIndex() when looking for the index of an item.`',
some: `Use \`.${replacement}()\` instead of \`.${method}()\` when checking value existence.`,
};
const messages = {
[ERROR]: ERROR_MESSAGES[method],
[SUGGESTION]: `Replace \`.${method}()\` with \`.${replacement}()\`.`,
};
const selector = [
methodCallSelector({
method,
argumentsLength: 1,
}),
callbackFunctionSelector('arguments.0'),
].join('');
function createListeners(context) {
const sourceCode = context.getSourceCode();
const {scopeManager} = sourceCode;
return {
[selector](node) {
const [callback] = node.arguments;
const binaryExpression = callback.body.type === 'BinaryExpression'
? callback.body
: callback.body.body[0].argument;
const [parameter] = callback.params;
const {left, right} = binaryExpression;
const {name} = parameter;
let searchValueNode;
let parameterInBinaryExpression;
if (isIdentifierNamed(left, name)) {
searchValueNode = right;
parameterInBinaryExpression = left;
} else if (isIdentifierNamed(right, name)) {
searchValueNode = left;
parameterInBinaryExpression = right;
} else {
return;
}
const callbackScope = scopeManager.acquire(callback);
if (
// `parameter` is used somewhere else
findVariable(callbackScope, parameter).references.some(({identifier}) => identifier !== parameterInBinaryExpression)
|| isFunctionSelfUsedInside(callback, callbackScope)
) {
return;
}
const method = node.callee.property;
const problem = {
node: method,
messageId: ERROR,
suggest: [],
};
const fix = function * (fixer) {
let text = sourceCode.getText(searchValueNode);
if (isParenthesized(searchValueNode, sourceCode) && !isParenthesized(callback, sourceCode)) {
text = `(${text})`;
}
yield fixer.replaceText(method, replacement);
yield fixer.replaceText(callback, text);
};
if (hasSideEffect(searchValueNode, sourceCode)) {
problem.suggest.push({messageId: SUGGESTION, fix});
} else {
problem.fix = fix;
}
return problem;
},
};
}
return {messages, createListeners};
}
module.exports = simpleArraySearchRule;