Skip to content

Commit 2bfc7c2

Browse files
authored
Refactor simpleArraySearchRule (#2111)
1 parent 64342cb commit 2bfc7c2

File tree

5 files changed

+128
-105
lines changed

5 files changed

+128
-105
lines changed

rules/prefer-array-index-of.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ const lastIndexOfOverFindLastIndexRule = simpleArraySearchRule({
1313

1414
/** @type {import('eslint').Rule.RuleModule} */
1515
module.exports = {
16-
create: context => ({
17-
...indexOfOverFindIndexRule.createListeners(context),
18-
...lastIndexOfOverFindLastIndexRule.createListeners(context),
19-
}),
16+
create(context) {
17+
indexOfOverFindIndexRule.listen(context);
18+
lastIndexOfOverFindLastIndexRule.listen(context);
19+
},
2020
meta: {
2121
type: 'suggestion',
2222
docs: {

rules/prefer-includes.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ const includesOverSomeRule = simpleArraySearchRule({
4343
});
4444

4545
/** @param {import('eslint').Rule.RuleContext} context */
46-
const create = context => ({
47-
BinaryExpression(node) {
46+
const create = context => {
47+
includesOverSomeRule.listen(context);
48+
49+
context.on('BinaryExpression', node => {
4850
const {left, right, operator} = node;
4951

5052
if (!isMethodNamed(left, 'indexOf')) {
@@ -75,9 +77,8 @@ const create = context => ({
7577
argumentsNodes,
7678
);
7779
}
78-
},
79-
...includesOverSomeRule.createListeners(context),
80-
});
80+
});
81+
};
8182

8283
/** @type {import('eslint').Rule.RuleModule} */
8384
module.exports = {
Lines changed: 92 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
'use strict';
22

33
const {hasSideEffect, isParenthesized, findVariable} = require('@eslint-community/eslint-utils');
4-
const {matches, methodCallSelector} = require('../selectors/index.js');
5-
const isFunctionSelfUsedInside = require('../utils/is-function-self-used-inside.js');
6-
7-
const getBinaryExpressionSelector = path => [
8-
`[${path}.type="BinaryExpression"]`,
9-
`[${path}.operator="==="]`,
10-
`:matches([${path}.left.type="Identifier"], [${path}.right.type="Identifier"])`,
11-
].join('');
12-
const getFunctionSelector = path => [
13-
`[${path}.generator!=true]`,
14-
`[${path}.async!=true]`,
15-
`[${path}.params.length=1]`,
16-
`[${path}.params.0.type="Identifier"]`,
17-
].join('');
18-
const callbackFunctionSelector = path => matches([
4+
const {isMethodCall} = require('../ast/index.js');
5+
const {isSameIdentifier, isFunctionSelfUsedInside} = require('../utils/index.js');
6+
7+
const isSimpleCompare = (node, compareNode) =>
8+
node.type === 'BinaryExpression'
9+
&& node.operator === '==='
10+
&& (
11+
isSameIdentifier(node.left, compareNode)
12+
|| isSameIdentifier(node.right, compareNode)
13+
);
14+
const isSimpleCompareCallbackFunction = node =>
1915
// Matches `foo.findIndex(bar => bar === baz)`
20-
[
21-
`[${path}.type="ArrowFunctionExpression"]`,
22-
getFunctionSelector(path),
23-
getBinaryExpressionSelector(`${path}.body`),
24-
].join(''),
16+
(
17+
node.type === 'ArrowFunctionExpression'
18+
&& !node.async
19+
&& node.params.length === 1
20+
&& isSimpleCompare(node.body, node.params[0])
21+
)
2522
// Matches `foo.findIndex(bar => {return bar === baz})`
2623
// Matches `foo.findIndex(function (bar) {return bar === baz})`
27-
[
28-
`:matches([${path}.type="ArrowFunctionExpression"], [${path}.type="FunctionExpression"])`,
29-
getFunctionSelector(path),
30-
`[${path}.body.type="BlockStatement"]`,
31-
`[${path}.body.body.length=1]`,
32-
`[${path}.body.body.0.type="ReturnStatement"]`,
33-
getBinaryExpressionSelector(`${path}.body.body.0.argument`),
34-
].join(''),
35-
]);
24+
|| (
25+
(node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression')
26+
&& !node.async
27+
&& !node.generator
28+
&& node.params.length === 1
29+
&& node.body.type === 'BlockStatement'
30+
&& node.body.body.length === 1
31+
&& node.body.body[0].type === 'ReturnStatement'
32+
&& isSimpleCompare(node.body.body[0].argument, node.params[0])
33+
);
3634
const isIdentifierNamed = ({type, name}, expectName) => type === 'Identifier' && name === expectName;
3735

3836
function simpleArraySearchRule({method, replacement}) {
@@ -51,78 +49,80 @@ function simpleArraySearchRule({method, replacement}) {
5149
[SUGGESTION]: `Replace \`.${method}()\` with \`.${replacement}()\`.`,
5250
};
5351

54-
const selector = [
55-
methodCallSelector({
56-
method,
57-
argumentsLength: 1,
58-
}),
59-
callbackFunctionSelector('arguments.0'),
60-
].join('');
61-
62-
function createListeners(context) {
52+
function listen(context) {
6353
const {sourceCode} = context;
6454
const {scopeManager} = sourceCode;
6555

66-
return {
67-
[selector](node) {
68-
const [callback] = node.arguments;
69-
const binaryExpression = callback.body.type === 'BinaryExpression'
70-
? callback.body
71-
: callback.body.body[0].argument;
72-
const [parameter] = callback.params;
73-
const {left, right} = binaryExpression;
74-
const {name} = parameter;
75-
76-
let searchValueNode;
77-
let parameterInBinaryExpression;
78-
if (isIdentifierNamed(left, name)) {
79-
searchValueNode = right;
80-
parameterInBinaryExpression = left;
81-
} else if (isIdentifierNamed(right, name)) {
82-
searchValueNode = left;
83-
parameterInBinaryExpression = right;
84-
} else {
85-
return;
56+
context.on('CallExpression', callExpression => {
57+
if (
58+
!isMethodCall(callExpression, {
59+
method,
60+
argumentsLength: 1,
61+
optionalCall: false,
62+
optionalMember: false,
63+
})
64+
|| !isSimpleCompareCallbackFunction(callExpression.arguments[0])
65+
) {
66+
return;
67+
}
68+
69+
const [callback] = callExpression.arguments;
70+
const binaryExpression = callback.body.type === 'BinaryExpression'
71+
? callback.body
72+
: callback.body.body[0].argument;
73+
const [parameter] = callback.params;
74+
const {left, right} = binaryExpression;
75+
const {name} = parameter;
76+
77+
let searchValueNode;
78+
let parameterInBinaryExpression;
79+
if (isIdentifierNamed(left, name)) {
80+
searchValueNode = right;
81+
parameterInBinaryExpression = left;
82+
} else if (isIdentifierNamed(right, name)) {
83+
searchValueNode = left;
84+
parameterInBinaryExpression = right;
85+
} else {
86+
return;
87+
}
88+
89+
const callbackScope = scopeManager.acquire(callback);
90+
if (
91+
// `parameter` is used somewhere else
92+
findVariable(callbackScope, parameter).references.some(({identifier}) => identifier !== parameterInBinaryExpression)
93+
|| isFunctionSelfUsedInside(callback, callbackScope)
94+
) {
95+
return;
96+
}
97+
98+
const methodNode = callExpression.callee.property;
99+
const problem = {
100+
node: methodNode,
101+
messageId: ERROR,
102+
suggest: [],
103+
};
104+
105+
const fix = function * (fixer) {
106+
let text = sourceCode.getText(searchValueNode);
107+
if (isParenthesized(searchValueNode, sourceCode) && !isParenthesized(callback, sourceCode)) {
108+
text = `(${text})`;
86109
}
87110

88-
const callbackScope = scopeManager.acquire(callback);
89-
if (
90-
// `parameter` is used somewhere else
91-
findVariable(callbackScope, parameter).references.some(({identifier}) => identifier !== parameterInBinaryExpression)
92-
|| isFunctionSelfUsedInside(callback, callbackScope)
93-
) {
94-
return;
95-
}
111+
yield fixer.replaceText(methodNode, replacement);
112+
yield fixer.replaceText(callback, text);
113+
};
96114

97-
const method = node.callee.property;
98-
const problem = {
99-
node: method,
100-
messageId: ERROR,
101-
suggest: [],
102-
};
103-
104-
const fix = function * (fixer) {
105-
let text = sourceCode.getText(searchValueNode);
106-
if (isParenthesized(searchValueNode, sourceCode) && !isParenthesized(callback, sourceCode)) {
107-
text = `(${text})`;
108-
}
109-
110-
yield fixer.replaceText(method, replacement);
111-
yield fixer.replaceText(callback, text);
112-
};
113-
114-
if (hasSideEffect(searchValueNode, sourceCode)) {
115-
problem.suggest.push({messageId: SUGGESTION, fix});
116-
} else {
117-
problem.fix = fix;
118-
}
115+
if (hasSideEffect(searchValueNode, sourceCode)) {
116+
problem.suggest.push({messageId: SUGGESTION, fix});
117+
} else {
118+
problem.fix = fix;
119+
}
119120

120-
return problem;
121-
},
122-
};
121+
return problem;
122+
});
123123
}
124124

125-
return {messages, createListeners};
125+
return {messages, listen};
126126
}
127127

128128
module.exports = simpleArraySearchRule;

rules/utils/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module.exports = {
2424
getVariableIdentifiers: require('./get-variable-identifiers.js'),
2525
isArrayPrototypeProperty,
2626
isBooleanNode,
27+
isFunctionSelfUsedInside: require('./is-function-self-used-inside.js'),
2728
isLogicalExpression: require('./is-logical-expression.js'),
2829
isNodeMatches,
2930
isNodeMatchesNameOrPath,

rules/utils/rule.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,36 @@ function reportProblems(create) {
7777
}
7878

7979
const wrapped = context => {
80-
const listeners = create(context);
80+
const listeners = {};
81+
const addListener = (selector, listener) => {
82+
listeners[selector] ??= [];
83+
listeners[selector].push(listener);
84+
};
85+
86+
const contextProxy = new Proxy(context, {
87+
get(target, property, receiver) {
88+
if (property === 'on') {
89+
return addListener;
90+
}
91+
92+
return Reflect.get(target, property, receiver);
93+
},
94+
});
8195

82-
if (!listeners) {
83-
return {};
96+
for (const [selector, listener] of Object.entries(create(contextProxy) ?? {})) {
97+
addListener(selector, listener);
8498
}
8599

86100
return Object.fromEntries(
87101
Object.entries(listeners)
88-
.map(([selector, listener]) => [selector, reportListenerProblems(listener, context)]),
102+
.map(([selector, listeners]) => [
103+
selector,
104+
(...listenerArguments) => {
105+
for (const listener of listeners) {
106+
reportListenerProblems(listener, context)(...listenerArguments);
107+
}
108+
},
109+
]),
89110
);
90111
};
91112

0 commit comments

Comments
 (0)