diff --git a/packages/eslint-plugin/docs/rules/no-misused-promises.md b/packages/eslint-plugin/docs/rules/no-misused-promises.md index a50cf28108c..42063d7931f 100644 --- a/packages/eslint-plugin/docs/rules/no-misused-promises.md +++ b/packages/eslint-plugin/docs/rules/no-misused-promises.md @@ -19,10 +19,18 @@ Both are enabled by default type Options = [ { checksConditionals?: boolean; - checksVoidReturn?: boolean; + checksVoidReturn?: boolean | ChecksVoidReturnOptions; }, ]; +interface ChecksVoidReturnOptions { + arguments?: boolean; + attributes?: boolean; + properties?: boolean; + returns?: boolean; + variables?: boolean; +} + const defaultOptions: Options = [ { checksConditionals: true, @@ -31,7 +39,26 @@ const defaultOptions: Options = [ ]; ``` -If you don't want functions that return promises where a void return is +### `"checksConditionals"` + +If you don't want to check conditionals, you can configure the rule with `"checksConditionals": false`: + +```json +{ + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksConditionals": false + } + ] +} +``` + +Doing so prevents the rule from looking at code like `if (somePromise)`. + +### `"checksVoidReturn"` + +Likewise, if you don't want functions that return promises where a void return is expected to be checked, your configuration will look like this: ```json @@ -45,15 +72,26 @@ expected to be checked, your configuration will look like this: } ``` -Likewise, if you don't want to check conditionals, you can configure the rule -like this: +You can disable selective parts of the `checksVoidReturn` option by providing an object that disables specific checks. +The following options are supported: + +- `arguments`: Disables checking an asynchronous function passed as argument where the parameter type expects a function that returns `void` +- `attributes`: Disables checking an asynchronous function passed as a JSX attribute expected to be a function that returns `void` +- `properties`: Disables checking an asynchronous function passed as an object property expected to be a function that returns `void` +- `returns`: Disables checking an asynchronous function returned in a function whose return type is a function that returns `void` +- `variables`: Disables checking an asynchronous function used as a variable whose return type is a function that returns `void` + +For example, if you don't mind that passing a `() => Promise` to a `() => void` parameter or JSX attribute can lead to a floating unhandled Promise: ```json { "@typescript-eslint/no-misused-promises": [ "error", { - "checksConditionals": false + "checksVoidReturn": { + "arguments": false, + "attributes": false + } } ] } diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index b50ad53901e..9c747864ae4 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -7,10 +7,18 @@ import * as util from '../util'; type Options = [ { checksConditionals?: boolean; - checksVoidReturn?: boolean; + checksVoidReturn?: boolean | ChecksVoidReturnOptions; }, ]; +interface ChecksVoidReturnOptions { + arguments?: boolean; + attributes?: boolean; + properties?: boolean; + returns?: boolean; + variables?: boolean; +} + type MessageId = | 'conditional' | 'voidReturnArgument' @@ -19,6 +27,34 @@ type MessageId = | 'voidReturnReturnValue' | 'voidReturnAttribute'; +function parseChecksVoidReturn( + checksVoidReturn: boolean | ChecksVoidReturnOptions | undefined, +): ChecksVoidReturnOptions | false { + switch (checksVoidReturn) { + case false: + return false; + + case true: + case undefined: + return { + arguments: true, + attributes: true, + properties: true, + returns: true, + variables: true, + }; + + default: + return { + arguments: checksVoidReturn.arguments ?? true, + attributes: checksVoidReturn.attributes ?? true, + properties: checksVoidReturn.properties ?? true, + returns: checksVoidReturn.returns ?? true, + variables: checksVoidReturn.variables ?? true, + }; + } +} + export default util.createRule({ name: 'no-misused-promises', meta: { @@ -48,7 +84,20 @@ export default util.createRule({ type: 'boolean', }, checksVoidReturn: { - type: 'boolean', + oneOf: [ + { type: 'boolean' }, + { + additionalProperties: false, + properties: { + arguments: { type: 'boolean' }, + attributes: { type: 'boolean' }, + properties: { type: 'boolean' }, + returns: { type: 'boolean' }, + variables: { type: 'boolean' }, + }, + type: 'object', + }, + ], }, }, }, @@ -80,15 +129,29 @@ export default util.createRule({ WhileStatement: checkTestConditional, }; - const voidReturnChecks: TSESLint.RuleListener = { - CallExpression: checkArguments, - NewExpression: checkArguments, - AssignmentExpression: checkAssignment, - VariableDeclarator: checkVariableDeclaration, - Property: checkProperty, - ReturnStatement: checkReturnStatement, - JSXAttribute: checkJSXAttribute, - }; + checksVoidReturn = parseChecksVoidReturn(checksVoidReturn); + + const voidReturnChecks: TSESLint.RuleListener = checksVoidReturn + ? { + ...(checksVoidReturn.arguments && { + CallExpression: checkArguments, + NewExpression: checkArguments, + }), + ...(checksVoidReturn.attributes && { + JSXAttribute: checkJSXAttribute, + }), + ...(checksVoidReturn.properties && { + Property: checkProperty, + }), + ...(checksVoidReturn.returns && { + ReturnStatement: checkReturnStatement, + }), + ...(checksVoidReturn.variables && { + AssignmentExpression: checkAssignment, + VariableDeclarator: checkVariableDeclaration, + }), + } + : {}; function checkTestConditional(node: { test: TSESTree.Expression | null; diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts index 1cc0576fb30..f643f4d91e1 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -586,6 +586,21 @@ f = async () => { }, { code: ` +let f: () => void; +f = async () => { + return 3; +}; + `, + errors: [ + { + line: 3, + messageId: 'voidReturnVariable', + }, + ], + options: [{ checksVoidReturn: { variables: true } }], + }, + { + code: ` const f: () => void = async () => { return 0; }; @@ -636,6 +651,21 @@ const obj: O = { { code: ` type O = { f: () => void }; +const obj: O = { + f: async () => 'foo', +}; + `, + errors: [ + { + line: 4, + messageId: 'voidReturnProperty', + }, + ], + options: [{ checksVoidReturn: { properties: true } }], + }, + { + code: ` +type O = { f: () => void }; const f = async () => 0; const obj: O = { f, @@ -708,6 +738,36 @@ function f(): () => void { }, { code: ` +function f(): () => void { + return async () => 0; +} + `, + errors: [ + { + line: 3, + messageId: 'voidReturnReturnValue', + }, + ], + options: [{ checksVoidReturn: { returns: true } }], + }, + { + code: ` +type O = { + func: () => void; +}; +const Component = (obj: O) => null; + 0} />; + `, + filename: 'react.tsx', + errors: [ + { + line: 6, + messageId: 'voidReturnAttribute', + }, + ], + }, + { + code: ` type O = { func: () => void; }; @@ -721,6 +781,7 @@ const Component = (obj: O) => null; messageId: 'voidReturnAttribute', }, ], + options: [{ checksVoidReturn: { attributes: true } }], }, { code: `