eslint-plugin-unicorn-x
Version:
More than 100 powerful ESLint rules
133 lines (122 loc) • 3.7 kB
JavaScript
import {isMethodCall} from './ast/index.js';
import {
isNodeValueNotFunction,
isArrayPrototypeProperty,
} from './utils/index.js';
const MESSAGE_ID_REDUCE = 'reduce';
const MESSAGE_ID_REDUCE_RIGHT = 'reduceRight';
const messages = {
[MESSAGE_ID_REDUCE]:
'`Array#reduce()` is not allowed. Prefer other types of loop for readability.',
[MESSAGE_ID_REDUCE_RIGHT]:
'`Array#reduceRight()` is not allowed. Prefer other types of loop for readability. You may want to call `Array#toReversed()` before looping it.',
};
const cases = [
// `array.{reduce,reduceRight}()`
{
test: (callExpression) =>
isMethodCall(callExpression, {
methods: ['reduce', 'reduceRight'],
minimumArguments: 1,
maximumArguments: 2,
optionalCall: false,
optionalMember: false,
}) && !isNodeValueNotFunction(callExpression.arguments[0]),
getMethodNode: (callExpression) => callExpression.callee.property,
isSimpleOperation(callExpression) {
const [callback] = callExpression.arguments;
return (
callback &&
// `array.reduce((accumulator, element) => accumulator + element)`
((callback.type === 'ArrowFunctionExpression' &&
callback.body.type === 'BinaryExpression') ||
// `array.reduce((accumulator, element) => {return accumulator + element;})`
// `array.reduce(function (accumulator, element){return accumulator + element;})`
((callback.type === 'ArrowFunctionExpression' ||
callback.type === 'FunctionExpression') &&
callback.body.type === 'BlockStatement' &&
callback.body.body.length === 1 &&
callback.body.body[0].type === 'ReturnStatement' &&
callback.body.body[0].argument.type === 'BinaryExpression'))
);
},
},
// `[].{reduce,reduceRight}.call()` and `Array.{reduce,reduceRight}.call()`
{
test: (callExpression) =>
isMethodCall(callExpression, {
method: 'call',
optionalCall: false,
optionalMember: false,
}) &&
isArrayPrototypeProperty(callExpression.callee.object, {
properties: ['reduce', 'reduceRight'],
}) &&
(!callExpression.arguments[1] ||
!isNodeValueNotFunction(callExpression.arguments[1])),
getMethodNode: (callExpression) => callExpression.callee.object.property,
},
// `[].{reduce,reduceRight}.apply()` and `Array.{reduce,reduceRight}.apply()`
{
test: (callExpression) =>
isMethodCall(callExpression, {
method: 'apply',
optionalCall: false,
optionalMember: false,
}) &&
isArrayPrototypeProperty(callExpression.callee.object, {
properties: ['reduce', 'reduceRight'],
}),
getMethodNode: (callExpression) => callExpression.callee.object.property,
},
];
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
allowSimpleOperations: {
type: 'boolean',
},
},
},
];
/** @param {import('eslint').Rule.RuleContext} context */
const create = (context) => {
const {allowSimpleOperations} = {
allowSimpleOperations: true,
...context.options[0],
};
return {
*CallExpression(callExpression) {
for (const {test, getMethodNode, isSimpleOperation} of cases) {
if (!test(callExpression)) {
continue;
}
if (allowSimpleOperations && isSimpleOperation?.(callExpression)) {
continue;
}
const methodNode = getMethodNode(callExpression);
yield {
node: methodNode,
messageId: methodNode.name,
};
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
const config = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Disallow `Array#reduce()` and `Array#reduceRight()`.',
recommended: true,
},
schema,
defaultOptions: [{allowSimpleOperations: true}],
messages,
},
};
export default config;