diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 06fdd024c0e9..1739e1508937 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -23,7 +23,7 @@ export type Options = [ checkMethodDeclarations?: boolean; }, ]; -export type MessageIds = 'missingAsync'; +export type MessageIds = 'missingAsync' | 'missingAsyncHybridReturn'; export default createRule({ name: 'promise-function-async', @@ -37,6 +37,8 @@ export default createRule({ fixable: 'code', messages: { missingAsync: 'Functions that return promises must be async.', + missingAsyncHybridReturn: + 'Functions that return promises must be async. Consider adding an explicit return type annotation if the function is intended to return a union of promise and non-promise types.', }, schema: [ { @@ -164,10 +166,20 @@ export default createRule({ ), ) ) { + const isHybridReturnType = returnTypes.some( + type => + type.isUnion() && + !type.types.every(part => + containsAllTypesByName(part, true, allAllowedPromiseNames), + ), + ); + context.report({ loc: getFunctionHeadLoc(node, context.sourceCode), node, - messageId: 'missingAsync', + messageId: isHybridReturnType + ? 'missingAsyncHybridReturn' + : 'missingAsync', fix: fixer => { if ( node.parent.type === AST_NODE_TYPES.MethodDefinition || diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/promise-function-async.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/promise-function-async.shot index 0c91850303e9..e85a8bf8c387 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/promise-function-async.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/promise-function-async.shot @@ -12,7 +12,7 @@ function functionReturnsPromise() { } function functionReturnsUnionWithPromiseImplicitly(p: boolean) { -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Functions that return promises must be async. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Functions that return promises must be async. Consider adding an explicit return type annotation if the function is intended to return a union of promise and non-promise types. return p ? 'value' : Promise.resolve('value'); } " diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 5ab4887334dc..da1999e93e82 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -825,7 +825,7 @@ function promiseInUnionWithoutExplicitReturnType(p: boolean) { `, errors: [ { - messageId, + messageId: 'missingAsyncHybridReturn', }, ], output: ` @@ -836,6 +836,65 @@ async function promiseInUnionWithoutExplicitReturnType(p: boolean) { }, { code: ` +function test1(): 'one' | Promise<'one'>; +function test1(a: number): Promise; +function test1(a?: number) { + if (a) { + return Promise.resolve(a); + } + + return Math.random() > 0.5 ? 'one' : Promise.resolve('one'); +} + `, + errors: [ + { + messageId: 'missingAsyncHybridReturn', + }, + ], + output: ` +function test1(): 'one' | Promise<'one'>; +function test1(a: number): Promise; +async function test1(a?: number) { + if (a) { + return Promise.resolve(a); + } + + return Math.random() > 0.5 ? 'one' : Promise.resolve('one'); +} + `, + }, + { + code: ` +class PromiseType { + s?: string; +} + +function promiseInUnionWithoutExplicitReturnType(p: boolean) { + return p ? new PromiseType() : 5; +} + `, + errors: [ + { + messageId: 'missingAsyncHybridReturn', + }, + ], + options: [ + { + allowedPromiseNames: ['PromiseType'], + }, + ], + output: ` +class PromiseType { + s?: string; +} + +async function promiseInUnionWithoutExplicitReturnType(p: boolean) { + return p ? new PromiseType() : 5; +} + `, + }, + { + code: ` function overloadingThatCanReturnPromise(): Promise; function overloadingThatCanReturnPromise(a: boolean): Promise; function overloadingThatCanReturnPromise(