Skip to content

Commit

Permalink
chore: added checks for async/sycn yield gen
Browse files Browse the repository at this point in the history
  • Loading branch information
anikethsaha committed Mar 25, 2020
1 parent 3c21b0e commit 6d8c439
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 5 deletions.
53 changes: 50 additions & 3 deletions packages/eslint-plugin/src/rules/require-await.ts
Expand Up @@ -11,6 +11,9 @@ interface ScopeInfo {
upper: ScopeInfo | null;
hasAwait: boolean;
hasAsync: boolean;
isGen: boolean;
hasDelegateGen: boolean;
isAsyncYield: boolean;
}
type FunctionNode =
| TSESTree.FunctionDeclaration
Expand Down Expand Up @@ -49,9 +52,29 @@ export default util.createRule({
upper: scopeInfo,
hasAwait: false,
hasAsync: node.async,
isGen: node.generator || false,
hasDelegateGen: 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;
}

/**
* Pop the top scope info object from the stack.
* Also, it reports the function if needed.
Expand All @@ -63,10 +86,15 @@ export default util.createRule({
}

if (
!node.generator &&
!isValidAsyncGenerator(node) &&
node.async &&
!scopeInfo.hasAwait &&
!isEmptyFunction(node)
!isEmptyFunction(node) &&
!(
scopeInfo.isGen &&
scopeInfo.hasDelegateGen &&
!scopeInfo.isAsyncYield
)
) {
context.report({
node,
Expand Down Expand Up @@ -97,10 +125,28 @@ export default util.createRule({
if (!scopeInfo) {
return;
}

scopeInfo.hasAwait = true;
}

/**
* mark `scopeInfo.hasDelegateGen` to `true` if its a generator
* function and the delegate is `true`
*/
function markAsHasDelegateGen(node: FunctionNode): void {
if (!scopeInfo || !scopeInfo.isGen) {
return;
}

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

// TODO : async function* test1() {yield* asyncGenerator() }
scopeInfo.hasDelegateGen = true;
}

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

AwaitExpression: markAsHasAwait,
'ForOfStatement[await = true]': markAsHasAwait,
'FunctionDeclaration[generator = true] > BlockStatement > ExpressionStatement > YieldExpression[delegate = true]': markAsHasDelegateGen,

// check body-less async arrow function.
// ignore `async () => await foo` because it's obviously correct
Expand Down
93 changes: 91 additions & 2 deletions packages/eslint-plugin/tests/rules/require-await.test.ts
Expand Up @@ -120,9 +120,21 @@ async function testFunction(): Promise<void> {
}
`,
'async function* run() { }',
'async function* run() { yield* 1 }',
'async function* asyncGenerator() { await Promise.resolve(); yield 1 }',
'function* test6() { yield* syncGenerator() }',
'function* test8() { yield syncGenerator() }',
'function* syncGenerator() { yield 1 }',
// FIXME
`
async function* asyncGenerator() {
await Promise.resolve()
yield 1
}
async function* test1() {yield* asyncGenerator() }
`,
'const foo : () => void = async function *(){}',
'const foo = async function *(){ console.log("bar") }',
'async function* run() { console.log("bar") }',
'async function* foo() : Promise<string> { return new Promise((res) => res(`hello`)) }',
],

invalid: [
Expand Down Expand Up @@ -179,6 +191,83 @@ async function testFunction(): Promise<void> {
},
],
},
{
code: 'async function* foo() : void { doSomething() }',
errors: [
{
messageId: 'missingAwait',
data: {
name: "Async generator function 'foo'",
},
},
],
},
{
code: 'async function* foo() { yield 1 }',
errors: [
{
messageId: 'missingAwait',
data: {
name: "Async generator function 'foo'",
},
},
],
},
{
code: 'async function* run() { console.log("bar") }',
errors: [
{
messageId: 'missingAwait',
data: {
name: "Async generator function 'run'",
},
},
],
},
{
code: 'const foo = async function *(){ console.log("bar") }',
errors: [
{
messageId: 'missingAwait',
data: {
name: 'Async generator function',
},
},
],
},
{
code: 'async function* syncGenerator() { yield 1 }',
errors: [
{
messageId: 'missingAwait',
data: {
name: "Async generator function 'syncGenerator'",
},
},
],
},
{
code: 'async function* test3() { yield asyncGenerator() }',
errors: [
{
messageId: 'missingAwait',
data: {
name: "Async generator function 'test3'",
},
},
],
},
{
code: 'async function* test7() { yield syncGenerator() }',
errors: [
{
messageId: 'missingAwait',
data: {
name: "Async generator function 'test7'",
},
},
],
},
],
});

Expand Down

0 comments on commit 6d8c439

Please sign in to comment.