Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(eslint-plugin): [require-await] handle async generators #1782

Merged
merged 9 commits into from Mar 31, 2020
56 changes: 54 additions & 2 deletions packages/eslint-plugin/src/rules/require-await.ts
Expand Up @@ -11,6 +11,8 @@ interface ScopeInfo {
upper: ScopeInfo | null;
hasAwait: boolean;
hasAsync: boolean;
isGen: boolean;
isAsyncYield: boolean;
}
type FunctionNode =
| TSESTree.FunctionDeclaration
Expand Down Expand Up @@ -49,9 +51,28 @@ export default util.createRule({
upper: scopeInfo,
hasAwait: false,
hasAsync: node.async,
isGen: node.generator || false,
isAsyncYield: false,
};
}

/**
* Returns `true` if the node is asycn, generator and returnType is Promise
*/
function isValidAsyncGenerator(node: FunctionNode): Boolean {
const { async, generator, returnType } = node;
if (async && generator && returnType) {
if (
!!~['Promise'].indexOf(
node.returnType?.typeAnnotation?.typeName?.name,
)
) {
return true;
}
}
return false;
}

anikethsaha marked this conversation as resolved.
Show resolved Hide resolved
/**
* Pop the top scope info object from the stack.
* Also, it reports the function if needed.
Expand All @@ -62,7 +83,13 @@ export default util.createRule({
return;
}

if (node.async && !scopeInfo.hasAwait && !isEmptyFunction(node)) {
if (
!isValidAsyncGenerator(node) &&
anikethsaha marked this conversation as resolved.
Show resolved Hide resolved
node.async &&
!scopeInfo.hasAwait &&
!isEmptyFunction(node) &&
!(scopeInfo.isGen && scopeInfo.isAsyncYield)
) {
context.report({
node,
loc: getFunctionHeadLoc(node, sourceCode),
Expand Down Expand Up @@ -92,10 +119,34 @@ export default util.createRule({
if (!scopeInfo) {
return;
}

scopeInfo.hasAwait = true;
}

/**
* mark `scopeInfo.isAsyncYield` to `true` if its a generator
* function and the delegate is `true`
*/
function markAsHasDelegateGen(node: FunctionNode): void {
anikethsaha marked this conversation as resolved.
Show resolved Hide resolved
if (!scopeInfo || !scopeInfo.isGen) {
return;
}

if (node?.argument?.type === AST_NODE_TYPES.Literal) {
// making this `true` as for literals we dont need to check the defination
// eg : async function* run() { yield* 1 }
scopeInfo.isAsyncYield = true;
anikethsaha marked this conversation as resolved.
Show resolved Hide resolved
}

const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node?.argument);
anikethsaha marked this conversation as resolved.
Show resolved Hide resolved
const type = checker.getTypeAtLocation(tsNode);
const symbol = type.getSymbol();

// async function* test1() {yield* asyncGenerator() }
if (symbol?.getName() === 'AsyncGenerator') {
scopeInfo.isAsyncYield = true;
}
}

return {
FunctionDeclaration: enterFunction,
FunctionExpression: enterFunction,
Expand All @@ -106,6 +157,7 @@ export default util.createRule({

AwaitExpression: markAsHasAwait,
'ForOfStatement[await = true]': markAsHasAwait,
'YieldExpression[delegate = true]': markAsHasDelegateGen,

// check body-less async arrow function.
// ignore `async () => await foo` because it's obviously correct
Expand Down