129 lines
4.1 KiB
JavaScript
129 lines
4.1 KiB
JavaScript
'use strict';
|
|
|
|
const {hasSideEffect, isParenthesized, findVariable} = require('@eslint-community/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;
|