Skip to content

Commit 153eb2c

Browse files
manovotnysindresorhusfisker
authored
Add allowSimpleOperations option to no-array-reduce rule (#1418)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com> Co-authored-by: fisker Cheung <lionkay@gmail.com>
1 parent efdd90e commit 153eb2c

File tree

3 files changed

+152
-26
lines changed

3 files changed

+152
-26
lines changed

docs/rules/no-array-reduce.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Disallow `Array#reduce()` and `Array#reduceRight()`
22

3-
`Array#reduce()` and `Array#reduceRight()` usually result in [hard-to-read](https://twitter.com/jaffathecake/status/1213077702300852224) and [less performant](https://www.richsnapp.com/article/2019/06-09-reduce-spread-anti-pattern) code. In almost every case, it can be replaced by `.map`, `.filter`, or a `for-of` loop. It's only somewhat useful in the rare case of summing up numbers.
3+
`Array#reduce()` and `Array#reduceRight()` usually result in [hard-to-read](https://twitter.com/jaffathecake/status/1213077702300852224) and [less performant](https://www.richsnapp.com/article/2019/06-09-reduce-spread-anti-pattern) code. In almost every case, it can be replaced by `.map`, `.filter`, or a `for-of` loop.
4+
5+
It's only somewhat useful in the rare case of summing up numbers, which is allowed by default.
46

57
Use `eslint-disable` comment if you really need to use it.
68

@@ -39,10 +41,34 @@ Array.prototype.reduce.call(array, reducer);
3941
array.reduce(reducer, initialValue);
4042
```
4143

44+
```js
45+
array.reduce((total, value) => total + value);
46+
```
47+
4248
```js
4349
let result = initialValue;
4450

4551
for (const element of array) {
4652
result += element;
4753
}
4854
```
55+
## Options
56+
57+
### allowSimpleOperations
58+
59+
Type: `boolean`\
60+
Default: `true`
61+
62+
Allow simple operations (like addition, subtraction, etc.) in a `reduce` call.
63+
64+
Set it to `false` to disable reduce completely.
65+
66+
```js
67+
// eslint unicorn/no-array-reduce: ["error", {"allowSimpleOperations": true}]
68+
array.reduce((total, item) => total + item) // Passes
69+
```
70+
71+
```js
72+
// eslint unicorn/no-array-reduce: ["error", {"allowSimpleOperations": false}]
73+
array.reduce((total, item) => total + item) // Fails
74+
```

rules/no-array-reduce.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const {get} = require('lodash');
23
const {methodCallSelector} = require('./selectors/index.js');
34
const {arrayPrototypeMethodSelector, notFunctionSelector, matches} = require('./selectors/index.js');
45

@@ -34,14 +35,47 @@ const selector = matches([
3435
].join(''),
3536
]);
3637

37-
const create = () => {
38+
const schema = [
39+
{
40+
type: 'object',
41+
properties: {
42+
allowSimpleOperations: {
43+
type: 'boolean',
44+
default: true,
45+
},
46+
},
47+
},
48+
];
49+
50+
const create = context => {
51+
const {allowSimpleOperations} = {allowSimpleOperations: true, ...context.options[0]};
52+
3853
return {
3954
[selector](node) {
40-
return {
55+
const callback = get(node, 'parent.parent.arguments[0]', {});
56+
const problem = {
4157
node,
4258
messageId: MESSAGE_ID,
4359
data: {method: node.name},
4460
};
61+
62+
if (!allowSimpleOperations) {
63+
return problem;
64+
}
65+
66+
if (callback.type === 'ArrowFunctionExpression' && callback.body.type === 'BinaryExpression') {
67+
return;
68+
}
69+
70+
if ((callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression') &&
71+
callback.body.type === 'BlockStatement' &&
72+
callback.body.body.length === 1 &&
73+
callback.body.body[0].type === 'ReturnStatement' &&
74+
callback.body.body[0].argument.type === 'BinaryExpression') {
75+
return;
76+
}
77+
78+
return problem;
4579
},
4680
};
4781
};
@@ -53,6 +87,7 @@ module.exports = {
5387
docs: {
5488
description: 'Disallow `Array#reduce()` and `Array#reduceRight()`.',
5589
},
90+
schema,
5691
messages,
5792
},
5893
};

test/no-array-reduce.mjs

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -83,42 +83,107 @@ test({
8383
// We are not checking arguments length
8484

8585
// `reduce-like`
86-
'arr.reducex(foo)',
87-
'arr.xreduce(foo)',
88-
'[].reducex.call(arr, foo)',
89-
'[].xreduce.call(arr, foo)',
90-
'Array.prototype.reducex.call(arr, foo)',
91-
'Array.prototype.xreduce.call(arr, foo)',
86+
'array.reducex(foo)',
87+
'array.xreduce(foo)',
88+
'[].reducex.call(array, foo)',
89+
'[].xreduce.call(array, foo)',
90+
'Array.prototype.reducex.call(array, foo)',
91+
'Array.prototype.xreduce.call(array, foo)',
9292

9393
// Second argument is not a function
9494
...notFunctionTypes.map(data => `Array.prototype.reduce.call(foo, ${data})`),
9595

96-
].flatMap(code => [code, code.replace('reduce', 'reduceRight')]),
96+
// Option: allowSimpleOperations
97+
'array.reduce((total, item) => total + item)',
98+
'array.reduce((total, item) => { return total - item })',
99+
'array.reduce(function (total, item) { return total * item })',
100+
'array.reduce((total, item) => total + item, 0)',
101+
'array.reduce((total, item) => { return total - item }, 0 )',
102+
'array.reduce(function (total, item) { return total * item }, 0)',
103+
outdent`
104+
array.reduce((total, item) => {
105+
return (total / item) * 100;
106+
}, 0);
107+
`,
108+
'array.reduce((total, item) => { return total + item }, 0)',
109+
].flatMap(testCase => [testCase, testCase.replace('reduce', 'reduceRight')]),
97110
invalid: [
98-
'arr.reduce((total, item) => total + item)',
99-
'arr.reduce((total, item) => total + item, 0)',
100-
'arr.reduce(function (total, item) { return total + item }, 0)',
101-
'arr.reduce(function (total, item) { return total + item })',
102-
'arr.reduce((str, item) => str += item, "")',
111+
'array.reduce((str, item) => str += item, "")',
103112
outdent`
104-
arr.reduce((obj, item) => {
113+
array.reduce((obj, item) => {
105114
obj[item] = null;
106115
return obj;
107116
}, {})
108117
`,
109-
'arr.reduce((obj, item) => ({ [item]: null }), {})',
118+
'array.reduce((obj, item) => ({ [item]: null }), {})',
110119
outdent`
111120
const hyphenate = (str, char) => \`\${str}-\${char}\`;
112121
["a", "b", "c"].reduce(hyphenate);
113122
`,
114-
'[].reduce.call(arr, (s, i) => s + i)',
115-
'[].reduce.call(arr, sum);',
123+
'[].reduce.call(array, (s, i) => s + i)',
124+
'[].reduce.call(array, sum);',
116125
'[].reduce.call(sum);',
117-
'Array.prototype.reduce.call(arr, (s, i) => s + i)',
118-
'Array.prototype.reduce.call(arr, sum);',
119-
'[].reduce.apply(arr, [(s, i) => s + i])',
120-
'[].reduce.apply(arr, [sum]);',
121-
'Array.prototype.reduce.apply(arr, [(s, i) => s + i])',
122-
'Array.prototype.reduce.apply(arr, [sum]);',
123-
].flatMap(code => [{code, errors: errorsReduce}, {code: code.replace('reduce', 'reduceRight'), errors: errorsReduceRight}]),
126+
'Array.prototype.reduce.call(array, (s, i) => s + i)',
127+
'Array.prototype.reduce.call(array, sum);',
128+
'[].reduce.apply(array, [(s, i) => s + i])',
129+
'[].reduce.apply(array, [sum]);',
130+
'Array.prototype.reduce.apply(array, [(s, i) => s + i])',
131+
'Array.prototype.reduce.apply(array, [sum]);',
132+
outdent`
133+
array.reduce((total, item) => {
134+
return total + doComplicatedThings(item);
135+
function doComplicatedThings(item) {
136+
return item + 1;
137+
}
138+
}, 0);
139+
`,
140+
141+
// Option: allowSimpleOperations
142+
{
143+
code: 'array.reduce((total, item) => total + item)',
144+
options: [{allowSimpleOperations: false}],
145+
},
146+
{
147+
code: 'array.reduce((total, item) => { return total - item })',
148+
options: [{allowSimpleOperations: false}],
149+
},
150+
{
151+
code: 'array.reduce(function (total, item) { return total * item })',
152+
options: [{allowSimpleOperations: false}],
153+
},
154+
{
155+
code: 'array.reduce((total, item) => total + item, 0)',
156+
options: [{allowSimpleOperations: false}],
157+
},
158+
{
159+
code: 'array.reduce((total, item) => { return total - item }, 0 )',
160+
options: [{allowSimpleOperations: false}],
161+
},
162+
{
163+
code: 'array.reduce(function (total, item) { return total * item }, 0)',
164+
options: [{allowSimpleOperations: false}],
165+
},
166+
{
167+
code: outdent`
168+
array.reduce((total, item) => {
169+
return (total / item) * 100;
170+
}, 0);
171+
`,
172+
options: [{allowSimpleOperations: false}],
173+
},
174+
].flatMap(testCase => {
175+
const {code, options} = testCase;
176+
177+
if (options) {
178+
return [
179+
{code, errors: errorsReduce, options},
180+
{code: code.replace('reduce', 'reduceRight'), errors: errorsReduceRight, options},
181+
];
182+
}
183+
184+
return [
185+
{code: testCase, errors: errorsReduce},
186+
{code: testCase.replace('reduce', 'reduceRight'), errors: errorsReduceRight},
187+
];
188+
}),
124189
});

0 commit comments

Comments
 (0)