diff --git a/docs/src/rules/array-callback-return.md b/docs/src/rules/array-callback-return.md index 965d18537af..625cb14ced3 100644 --- a/docs/src/rules/array-callback-return.md +++ b/docs/src/rules/array-callback-return.md @@ -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 @@ -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) { @@ -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); +}); ``` ::: @@ -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. diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index 24a33d16c99..bda9ab139a6 100644 --- a/lib/rules/array-callback-return.js +++ b/lib/rules/array-callback-return.js @@ -162,6 +162,10 @@ module.exports = { checkForEach: { type: "boolean", default: false + }, + allowVoid: { + type: "boolean", + default: false } }, additionalProperties: false @@ -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 = { @@ -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 { @@ -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 { diff --git a/tests/lib/rules/array-callback-return.js b/tests/lib/rules/array-callback-return.js index 6343d3e4027..bc2e8555266 100644 --- a/tests/lib/rules/array-callback-return.js +++ b/tests/lib/rules/array-callback-return.js @@ -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: [ @@ -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() {})", @@ -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 {