Skip to content

Commit

Permalink
feat: allowVoid option in array-callback-return (#17564)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Tanujkanti4441 authored Sep 17, 2023
1 parent bd7a71f commit 85a3d9e
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 3 deletions.
45 changes: 43 additions & 2 deletions docs/src/rules/array-callback-return.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,13 @@ var bar = foo.map(node => node.getAttribute("id"));

## Options

This rule accepts a configuration object with two options:
This rule accepts a configuration object with three options:

* `"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.
* `"checkForEach": false` (default) When set to `true`, rule will also report `forEach` callbacks that return a value.
* `"allowVoid": false` (default) When set to `true`, allows `void` in `forEach` callbacks, so rule will not report the return value with a `void` operator.

**Note:** `{ "allowVoid": true }` works only if `checkForEach` option is set to `true`.

### allowImplicit

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

myArray.forEach(function(item) {
return handleItem(item)
return handleItem(item);
});

myArray.forEach(function(item) {
Expand All @@ -132,11 +135,24 @@ myArray.forEach(function(item) {
handleItem(item);
});

myArray.forEach(function(item) {
if (item < 0) {
return void x;
}
handleItem(item);
});

myArray.forEach(item => handleItem(item));

myArray.forEach(item => void handleItem(item));

myArray.forEach(item => {
return handleItem(item);
});

myArray.forEach(item => {
return void handleItem(item);
});
```

:::
Expand Down Expand Up @@ -171,6 +187,31 @@ myArray.forEach(item => {

:::

### allowVoid

Examples of **correct** code for the `{ "allowVoid": true }` option:

:::correct

```js
/*eslint array-callback-return: ["error", { checkForEach: true, allowVoid: true }]*/

myArray.forEach(item => void handleItem(item));

myArray.forEach(item => {
return void handleItem(item);
});

myArray.forEach(item => {
if (item < 0) {
return void x;
}
handleItem(item);
});
```

:::

## Known Limitations

This rule checks callback functions of methods with the given names, *even if* the object which has the method is *not* an array.
Expand Down
18 changes: 17 additions & 1 deletion lib/rules/array-callback-return.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ module.exports = {
checkForEach: {
type: "boolean",
default: false
},
allowVoid: {
type: "boolean",
default: false
}
},
additionalProperties: false
Expand All @@ -178,7 +182,7 @@ module.exports = {

create(context) {

const options = context.options[0] || { allowImplicit: false, checkForEach: false };
const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false };
const sourceCode = context.sourceCode;

let funcInfo = {
Expand Down Expand Up @@ -209,6 +213,12 @@ module.exports = {

if (funcInfo.arrayMethodName === "forEach") {
if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
if (options.allowVoid &&
node.body.type === "UnaryExpression" &&
node.body.operator === "void") {
return;
}

messageId = "expectedNoReturnValue";
}
} else {
Expand Down Expand Up @@ -291,6 +301,12 @@ module.exports = {

// if checkForEach: true, returning a value at any path inside a forEach is not allowed
if (options.checkForEach && node.argument) {
if (options.allowVoid &&
node.argument.type === "UnaryExpression" &&
node.argument.operator === "void") {
return;
}

messageId = "expectedNoReturnValue";
}
} else {
Expand Down
21 changes: 21 additions & 0 deletions tests/lib/rules/array-callback-return.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const checkForEachOptions = [{ checkForEach: true }];

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

const checkForEachAllowVoid = [{ checkForEach: true, allowVoid: true }];

ruleTester.run("array-callback-return", rule, {
valid: [

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

// options: { checkForEach: true, allowVoid: true }
{ code: "foo.forEach((x) => void x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
{ code: "foo.forEach((x) => void bar(x))", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
{ code: "foo.forEach(function (x) { return void bar(x); })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
{ code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },
{ code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } },

"Arrow.from(x, function() {})",
"foo.abc(function() {})",
"every(function() {})",
Expand Down Expand Up @@ -217,6 +226,18 @@ ruleTester.run("array-callback-return", rule, {
{ code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
{ code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
{ code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Array.prototype.every() expects a return value from function."] },
{ code: "foo.forEach((x) => void x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => void bar(x))", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },

// options: { checkForEach: true, allowVoid: true }
{ code: "foo.forEach((x) => x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => !x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => { return x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => { return !x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => { if (a === b) { return x; } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },
{ code: "foo.forEach((x) => { if (a === b) { return !x } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] },

// full location tests
{
Expand Down

0 comments on commit 85a3d9e

Please sign in to comment.