securityos/node_modules/eslint-plugin-unicorn/rules/no-array-callback-reference.js

251 lines
4.8 KiB
JavaScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
'use strict';
const {isParenthesized} = require('@eslint-community/eslint-utils');
const {methodCallSelector, notFunctionSelector} = require('./selectors/index.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';
const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name';
const REPLACE_WITH_NAME_MESSAGE_ID = 'replace-with-name';
const REPLACE_WITHOUT_NAME_MESSAGE_ID = 'replace-without-name';
const messages = {
[ERROR_WITH_NAME_MESSAGE_ID]: 'Do not pass function `{{name}}` directly to `.{{method}}(…)`.',
[ERROR_WITHOUT_NAME_MESSAGE_ID]: 'Do not pass function directly to `.{{method}}(…)`.',
[REPLACE_WITH_NAME_MESSAGE_ID]: 'Replace function `{{name}}` with `… => {{name}}({{parameters}})`.',
[REPLACE_WITHOUT_NAME_MESSAGE_ID]: 'Replace function with `… => …({{parameters}})`.',
};
const iteratorMethods = [
[
'every',
{
ignore: [
'Boolean',
],
},
],
[
'filter', {
extraSelector: '[callee.object.name!="Vue"]',
ignore: [
'Boolean',
],
},
],
[
'find',
{
ignore: [
'Boolean',
],
},
],
[
'findLast',
{
ignore: [
'Boolean',
],
},
],
[
'findIndex',
{
ignore: [
'Boolean',
],
},
],
[
'findLastIndex',
{
ignore: [
'Boolean',
],
},
],
[
'flatMap',
],
[
'forEach',
{
returnsUndefined: true,
},
],
[
'map',
{
extraSelector: '[callee.object.name!="types"]',
ignore: [
'String',
'Number',
'BigInt',
'Boolean',
'Symbol',
],
},
],
[
'reduce',
{
parameters: [
'accumulator',
'element',
'index',
'array',
],
minParameters: 2,
},
],
[
'reduceRight',
{
parameters: [
'accumulator',
'element',
'index',
'array',
],
minParameters: 2,
},
],
[
'some',
{
ignore: [
'Boolean',
],
},
],
].map(([method, options]) => {
options = {
parameters: ['element', 'index', 'array'],
ignore: [],
minParameters: 1,
extraSelector: '',
returnsUndefined: false,
...options,
};
return [method, options];
});
const ignoredCallee = [
// http://bluebirdjs.com/docs/api/promise.map.html
'Promise',
'React.Children',
'Children',
'lodash',
'underscore',
'_',
'Async',
'async',
'this',
'$',
'jQuery',
];
function getProblem(context, node, method, options) {
const {type} = node;
const name = type === 'Identifier' ? node.name : '';
if (type === 'Identifier' && options.ignore.includes(name)) {
return;
}
const problem = {
node,
messageId: name ? ERROR_WITH_NAME_MESSAGE_ID : ERROR_WITHOUT_NAME_MESSAGE_ID,
data: {
name,
method,
},
suggest: [],
};
const {parameters, minParameters, returnsUndefined} = options;
for (let parameterLength = minParameters; parameterLength <= parameters.length; parameterLength++) {
const suggestionParameters = parameters.slice(0, parameterLength).join(', ');
const suggest = {
messageId: name ? REPLACE_WITH_NAME_MESSAGE_ID : REPLACE_WITHOUT_NAME_MESSAGE_ID,
data: {
name,
parameters: suggestionParameters,
},
fix(fixer) {
const sourceCode = context.getSourceCode();
let nodeText = sourceCode.getText(node);
if (isParenthesized(node, sourceCode) || type === 'ConditionalExpression') {
nodeText = `(${nodeText})`;
}
return fixer.replaceText(
node,
returnsUndefined
? `(${suggestionParameters}) => { ${nodeText}(${suggestionParameters}); }`
: `(${suggestionParameters}) => ${nodeText}(${suggestionParameters})`,
);
},
};
problem.suggest.push(suggest);
}
return problem;
}
const ignoredFirstArgumentSelector = [
notFunctionSelector('arguments.0'),
// Ignore all `CallExpression`s include `function.bind()`
'[arguments.0.type!="CallExpression"]',
'[arguments.0.type!="FunctionExpression"]',
'[arguments.0.type!="ArrowFunctionExpression"]',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const rules = {};
for (const [method, options] of iteratorMethods) {
const selector = [
method === 'reduce' || method === 'reduceRight' ? '' : ':not(AwaitExpression) > ',
methodCallSelector({
method,
minimumArguments: 1,
maximumArguments: 2,
}),
options.extraSelector,
ignoredFirstArgumentSelector,
].join('');
rules[selector] = node => {
if (isNodeMatches(node.callee.object, ignoredCallee)) {
return;
}
if (node.callee.object.type === 'CallExpression' && isNodeMatches(node.callee.object.callee, ignoredCallee)) {
return;
}
const [iterator] = node.arguments;
return getProblem(context, iterator, method, options);
};
}
return rules;
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'problem',
docs: {
description: 'Prevent passing a function reference directly to iterator methods.',
},
hasSuggestions: true,
messages,
},
};