From b4a53f74a6bf51f6d2bed3b18f60283f3edad914 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Fri, 14 Mar 2025 16:12:54 +0200 Subject: [PATCH 1/8] initial implementation --- .../src/rules/promise-function-async.ts | 16 ++++++++++++++-- .../tests/rules/promise-function-async.test.ts | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 06fdd024c0e9..10a39ff0aafb 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 both a promise and a non-promise.', }, 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/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 5ab4887334dc..92f3dddc2d36 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: 'missingAsyncMixedReturn', }, ], output: ` From 9f2900ccd87c54ea914379bb74271ca21ccfe669 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Fri, 14 Mar 2025 16:13:25 +0200 Subject: [PATCH 2/8] tests --- .../rules/promise-function-async.test.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) 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 92f3dddc2d36..0c90c3eab0a7 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: 'missingAsyncMixedReturn', + messageId: 'missingAsyncHybridReturn', }, ], output: ` @@ -836,6 +836,36 @@ async function promiseInUnionWithoutExplicitReturnType(p: boolean) { }, { 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( From db52ca5b26505452b772acf8aa6f0882c7e07f81 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Fri, 14 Mar 2025 16:17:18 +0200 Subject: [PATCH 3/8] snapshots --- .../docs-eslint-output-snapshots/promise-function-async.shot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..cb6bd99f2ea8 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 both a promise and a non-promise. return p ? 'value' : Promise.resolve('value'); } " From 8b51b8dd97a2d56fcc24da100c9f8ac191c280da Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Fri, 14 Mar 2025 16:37:08 +0200 Subject: [PATCH 4/8] cover overloading case --- .../rules/promise-function-async.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) 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 0c90c3eab0a7..da1999e93e82 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -836,6 +836,35 @@ 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; } From 9eb0f5b6e84a36c5eb97c476e419af48da484c06 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Tue, 18 Mar 2025 19:55:27 +0200 Subject: [PATCH 5/8] Update packages/eslint-plugin/src/rules/promise-function-async.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- packages/eslint-plugin/src/rules/promise-function-async.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 10a39ff0aafb..372475f1b643 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -38,7 +38,7 @@ export default createRule({ 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 both a promise and a non-promise.', + 'Functions that return promises must be async. Consider adding an explicit return type annotation if the function is intended to return either a promise or a non-promise.', }, schema: [ { From 577b247218ec3b962d24f1fd5c6c0c57d3ab9be2 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Tue, 18 Mar 2025 21:14:36 +0200 Subject: [PATCH 6/8] snapshots --- .../docs-eslint-output-snapshots/promise-function-async.shot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cb6bd99f2ea8..70b324735fcd 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. Consider adding an explicit return type annotation if the function is intended to return both a promise and a non-promise. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Functions that return promises must be async. Consider adding an explicit return type annotation if the function is intended to return either a promise or a non-promise. return p ? 'value' : Promise.resolve('value'); } " From f5b6f7593eedaaa2dc0ecd4d39bf55eb0ea9e18e Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Tue, 1 Apr 2025 18:50:44 +0300 Subject: [PATCH 7/8] Update packages/eslint-plugin/src/rules/promise-function-async.ts Co-authored-by: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> --- packages/eslint-plugin/src/rules/promise-function-async.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 372475f1b643..1739e1508937 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -38,7 +38,7 @@ export default createRule({ 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 either a promise or a non-promise.', + '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: [ { From 6831cbfb872ca787b0dfa02f7654ff14bec4a60a Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Tue, 1 Apr 2025 19:20:10 +0300 Subject: [PATCH 8/8] snapshots --- .../docs-eslint-output-snapshots/promise-function-async.shot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 70b324735fcd..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. Consider adding an explicit return type annotation if the function is intended to return either a promise or a non-promise. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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'); } "