securityos/node_modules/eslint-plugin-unicorn/rules/shared/simple-array-search-rule.js

129 lines
4.1 KiB
JavaScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
'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;