Skip to content

Commit 85a3d9e

Browse files
authoredSep 17, 2023
feat: allowVoid option in array-callback-return (#17564)
* feat: allowVoid option in array-callback-return Refs #17285 * feat: refactor code and add docs * feat: allow void in return-statement * feat: add more tests for allowVoid
1 parent bd7a71f commit 85a3d9e

File tree

3 files changed

+81
-3
lines changed

3 files changed

+81
-3
lines changed
 

‎docs/src/rules/array-callback-return.md

+43-2
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,13 @@ var bar = foo.map(node => node.getAttribute("id"));
9292

9393
## Options
9494

95-
This rule accepts a configuration object with two options:
95+
This rule accepts a configuration object with three options:
9696

9797
* `"allowImplicit": false` (default) When set to `true`, allows callbacks of methods that require a return value to implicitly return `undefined` with a `return` statement containing no expression.
9898
* `"checkForEach": false` (default) When set to `true`, rule will also report `forEach` callbacks that return a value.
99+
* `"allowVoid": false` (default) When set to `true`, allows `void` in `forEach` callbacks, so rule will not report the return value with a `void` operator.
100+
101+
**Note:** `{ "allowVoid": true }` works only if `checkForEach` option is set to `true`.
99102

100103
### allowImplicit
101104

@@ -122,7 +125,7 @@ Examples of **incorrect** code for the `{ "checkForEach": true }` option:
122125
/*eslint array-callback-return: ["error", { checkForEach: true }]*/
123126

124127
myArray.forEach(function(item) {
125-
return handleItem(item)
128+
return handleItem(item);
126129
});
127130

128131
myArray.forEach(function(item) {
@@ -132,11 +135,24 @@ myArray.forEach(function(item) {
132135
handleItem(item);
133136
});
134137

138+
myArray.forEach(function(item) {
139+
if (item < 0) {
140+
return void x;
141+
}
142+
handleItem(item);
143+
});
144+
135145
myArray.forEach(item => handleItem(item));
136146

147+
myArray.forEach(item => void handleItem(item));
148+
137149
myArray.forEach(item => {
138150
return handleItem(item);
139151
});
152+
153+
myArray.forEach(item => {
154+
return void handleItem(item);
155+
});
140156
```
141157

142158
:::
@@ -171,6 +187,31 @@ myArray.forEach(item => {
171187

172188
:::
173189

190+
### allowVoid
191+
192+
Examples of **correct** code for the `{ "allowVoid": true }` option:
193+
194+
:::correct
195+
196+
```js
197+
/*eslint array-callback-return: ["error", { checkForEach: true, allowVoid: true }]*/
198+
199+
myArray.forEach(item => void handleItem(item));
200+
201+
myArray.forEach(item => {
202+
return void handleItem(item);
203+
});
204+
205+
myArray.forEach(item => {
206+
if (item < 0) {
207+
return void x;
208+
}
209+
handleItem(item);
210+
});
211+
```
212+
213+
:::
214+
174215
## Known Limitations
175216

176217
This rule checks callback functions of methods with the given names, *even if* the object which has the method is *not* an array.

‎lib/rules/array-callback-return.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ module.exports = {
162162
checkForEach: {
163163
type: "boolean",
164164
default: false
165+
},
166+
allowVoid: {
167+
type: "boolean",
168+
default: false
165169
}
166170
},
167171
additionalProperties: false
@@ -178,7 +182,7 @@ module.exports = {
178182

179183
create(context) {
180184

181-
const options = context.options[0] || { allowImplicit: false, checkForEach: false };
185+
const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false };
182186
const sourceCode = context.sourceCode;
183187

184188
let funcInfo = {
@@ -209,6 +213,12 @@ module.exports = {
209213

210214
if (funcInfo.arrayMethodName === "forEach") {
211215
if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
216+
if (options.allowVoid &&
217+
node.body.type === "UnaryExpression" &&
218+
node.body.operator === "void") {
219+
return;
220+
}
221+
212222
messageId = "expectedNoReturnValue";
213223
}
214224
} else {
@@ -291,6 +301,12 @@ module.exports = {
291301

292302
// if checkForEach: true, returning a value at any path inside a forEach is not allowed
293303
if (options.checkForEach && node.argument) {
304+
if (options.allowVoid &&
305+
node.argument.type === "UnaryExpression" &&
306+
node.argument.operator === "void") {
307+
return;
308+
}
309+
294310
messageId = "expectedNoReturnValue";
295311
}
296312
} else {

‎tests/lib/rules/array-callback-return.js

+21
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const checkForEachOptions = [{ checkForEach: true }];
2424

2525
const allowImplicitCheckForEach = [{ allowImplicit: true, checkForEach: true }];
2626

27+
const checkForEachAllowVoid = [{ checkForEach: true, allowVoid: true }];
28+
2729
ruleTester.run("array-callback-return", rule, {
2830
valid: [
2931

@@ -114,6 +116,13 @@ ruleTester.run("array-callback-return", rule, {
114116
{ code: "foo.every(function() { try { bar(); } finally { return 1; } })", options: checkForEachOptions },
115117
{ code: "foo.every(function() { return; })", options: allowImplicitCheckForEach },
116118

119+
// options: { checkForEach: true, allowVoid: true }
120+
{ code: "foo.forEach((x) => void x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
121+
{ code: "foo.forEach((x) => void bar(x))", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
122+
{ code: "foo.forEach(function (x) { return void bar(x); })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
123+
{ code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
124+
{ code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
125+
117126
"Arrow.from(x, function() {})",
118127
"foo.abc(function() {})",
119128
"every(function() {})",
@@ -217,6 +226,18 @@ ruleTester.run("array-callback-return", rule, {
217226
{ code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
218227
{ code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
219228
{ code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Array.prototype.every() expects a return value from function."] },
229+
{ code: "foo.forEach((x) => void x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
230+
{ code: "foo.forEach((x) => void bar(x))", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
231+
{ code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
232+
{ code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
233+
234+
// options: { checkForEach: true, allowVoid: true }
235+
{ code: "foo.forEach((x) => x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
236+
{ code: "foo.forEach((x) => !x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
237+
{ code: "foo.forEach((x) => { return x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
238+
{ code: "foo.forEach((x) => { return !x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
239+
{ code: "foo.forEach((x) => { if (a === b) { return x; } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
240+
{ code: "foo.forEach((x) => { if (a === b) { return !x } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
220241

221242
// full location tests
222243
{

0 commit comments

Comments
 (0)
Please sign in to comment.