From bd6140b2bc28a9abb7f5b7f584774e0bbdad1e71 Mon Sep 17 00:00:00 2001 From: auvred Date: Tue, 28 Nov 2023 17:51:28 +0000 Subject: [PATCH 1/2] feat(eslint-plugin): [require-await] allow yielding Promise in async generators --- .../eslint-plugin/src/rules/require-await.ts | 42 +++++++++++-------- .../tests/rules/require-await.test.ts | 24 +++++++++++ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index f834e17decec..95685f0dff55 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -112,32 +112,40 @@ export default createRule({ } /** - * mark `scopeInfo.isAsyncYield` to `true` if its a generator - * function and the delegate is `true` + * Mark `scopeInfo.isAsyncYield` to `true` if it + * 1) delegates async generator function + * or + * 2) yields thenable type */ - function markAsHasDelegateGen(node: TSESTree.YieldExpression): void { + function visitYieldExpression(node: TSESTree.YieldExpression): void { if (!scopeInfo?.isGen || !node.argument) { return; } if (node.argument.type === AST_NODE_TYPES.Literal) { - // making this `false` as for literals we don't need to check the definition + // ignoring this as for literals we don't need to check the definition // eg : async function* run() { yield* 1 } - scopeInfo.isAsyncYield ||= false; + return; } - const type = services.getTypeAtLocation(node.argument); - const typesToCheck = expandUnionOrIntersectionType(type); - for (const type of typesToCheck) { - const asyncIterator = tsutils.getWellKnownSymbolPropertyOfType( - type, - 'asyncIterator', - checker, - ); - if (asyncIterator !== undefined) { - scopeInfo.isAsyncYield = true; - break; + if (node.delegate) { + const type = services.getTypeAtLocation(node.argument); + const typesToCheck = expandUnionOrIntersectionType(type); + for (const type of typesToCheck) { + const asyncIterator = tsutils.getWellKnownSymbolPropertyOfType( + type, + 'asyncIterator', + checker, + ); + if (asyncIterator !== undefined) { + scopeInfo.isAsyncYield = true; + break; + } } + } else if ( + isThenableType(services.esTreeNodeToTSNodeMap.get(node.argument)) + ) { + scopeInfo.isAsyncYield = true; } } @@ -152,7 +160,7 @@ export default createRule({ AwaitExpression: markAsHasAwait, 'VariableDeclaration[kind = "await using"]': markAsHasAwait, 'ForOfStatement[await = true]': markAsHasAwait, - 'YieldExpression[delegate = true]': markAsHasDelegateGen, + YieldExpression: visitYieldExpression, // check body-less async arrow function. // ignore `async () => await foo` because it's obviously correct diff --git a/packages/eslint-plugin/tests/rules/require-await.test.ts b/packages/eslint-plugin/tests/rules/require-await.test.ts index 7bcb1d6a0b4f..f8f9624e26c8 100644 --- a/packages/eslint-plugin/tests/rules/require-await.test.ts +++ b/packages/eslint-plugin/tests/rules/require-await.test.ts @@ -237,6 +237,30 @@ async function* foo(): Promise { await using foo = new Bar(); }; `, + ` + async function* test1() { + yield Promise.resolve(1); + } + `, + ` + function asyncFunction() { + return Promise.resolve(1); + } + async function* test1() { + yield asyncFunction(); + } + `, + ` + declare const asyncFunction: () => Promise; + async function* test1() { + yield asyncFunction(); + } + `, + ` + async function* test1() { + yield new Promise(() => {}); + } + `, ], invalid: [ From 3a1c869de4ad0d69a56648e293bda9f72d683d50 Mon Sep 17 00:00:00 2001 From: auvred Date: Tue, 28 Nov 2023 17:57:59 +0000 Subject: [PATCH 2/2] chore: refactor `visitYieldExpression` a bit --- .../eslint-plugin/src/rules/require-await.ts | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 95685f0dff55..dd033c7cbe2d 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -128,24 +128,25 @@ export default createRule({ return; } - if (node.delegate) { - const type = services.getTypeAtLocation(node.argument); - const typesToCheck = expandUnionOrIntersectionType(type); - for (const type of typesToCheck) { - const asyncIterator = tsutils.getWellKnownSymbolPropertyOfType( - type, - 'asyncIterator', - checker, - ); - if (asyncIterator !== undefined) { - scopeInfo.isAsyncYield = true; - break; - } + if (!node.delegate) { + if (isThenableType(services.esTreeNodeToTSNodeMap.get(node.argument))) { + scopeInfo.isAsyncYield = true; + } + return; + } + + const type = services.getTypeAtLocation(node.argument); + const typesToCheck = expandUnionOrIntersectionType(type); + for (const type of typesToCheck) { + const asyncIterator = tsutils.getWellKnownSymbolPropertyOfType( + type, + 'asyncIterator', + checker, + ); + if (asyncIterator !== undefined) { + scopeInfo.isAsyncYield = true; + break; } - } else if ( - isThenableType(services.esTreeNodeToTSNodeMap.get(node.argument)) - ) { - scopeInfo.isAsyncYield = true; } }