From 72c890fc2c0b1185fe815c7fa74aa26d71bcf86f Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 11 Nov 2019 09:12:02 -0800 Subject: [PATCH] fix(eslint-plugin): [require-await] better handle nesting --- .../eslint-plugin/src/rules/require-await.ts | 131 ++++-------------- .../tests/rules/require-await.test.ts | 9 ++ 2 files changed, 37 insertions(+), 103 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index cc5ecc8bf609..5f27f8ad8a1d 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -1,6 +1,5 @@ import { TSESTree, - TSESLint, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/require-await'; @@ -11,11 +10,6 @@ import * as util from '../util'; type Options = util.InferOptionsTypeFromRule; type MessageIds = util.InferMessageIdsTypeFromRule; -interface ScopeInfo { - upper: ScopeInfo | null; - returnsPromise: boolean; -} - export default util.createRule({ name: 'require-await', meta: { @@ -35,82 +29,6 @@ export default util.createRule({ const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); - let scopeInfo: ScopeInfo | null = null; - - /** - * Push the scope info object to the stack. - * - * @returns {void} - */ - function enterFunction( - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression, - ): void { - scopeInfo = { - upper: scopeInfo, - returnsPromise: false, - }; - - switch (node.type) { - case AST_NODE_TYPES.FunctionDeclaration: - rules.FunctionDeclaration(node); - break; - - case AST_NODE_TYPES.FunctionExpression: - rules.FunctionExpression(node); - break; - - case AST_NODE_TYPES.ArrowFunctionExpression: - rules.ArrowFunctionExpression(node); - - // If body type is not BlockStatment, we need to check the return type here - if (node.body.type !== AST_NODE_TYPES.BlockStatement) { - const expression = parserServices.esTreeNodeToTSNodeMap.get( - node.body, - ); - scopeInfo.returnsPromise = isThenableType(expression); - } - - break; - } - } - - /** - * Pop the top scope info object from the stack. - * Passes through to the base rule if the function doesn't return a promise - * - * @param {ASTNode} node - The node exiting - * @returns {void} - */ - function exitFunction( - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression, - ): void { - if (scopeInfo) { - if (!scopeInfo.returnsPromise) { - switch (node.type) { - case AST_NODE_TYPES.FunctionDeclaration: - rules['FunctionDeclaration:exit'](node); - break; - - case AST_NODE_TYPES.FunctionExpression: - rules['FunctionExpression:exit'](node); - break; - - case AST_NODE_TYPES.ArrowFunctionExpression: - rules['ArrowFunctionExpression:exit'](node); - break; - } - } - - scopeInfo = scopeInfo.upper; - } - } - /** * Checks if the node returns a thenable type * @@ -124,34 +42,41 @@ export default util.createRule({ } return { - 'FunctionDeclaration[async = true]': enterFunction, - 'FunctionExpression[async = true]': enterFunction, - 'ArrowFunctionExpression[async = true]': enterFunction, - 'FunctionDeclaration[async = true]:exit': exitFunction, - 'FunctionExpression[async = true]:exit': exitFunction, - 'ArrowFunctionExpression[async = true]:exit': exitFunction, - - ReturnStatement(node): void { - if (!scopeInfo) { - return; + 'FunctionDeclaration[async = true]': rules.FunctionDeclaration, + 'FunctionExpression[async = true]': rules.FunctionExpression, + 'ArrowFunctionExpression[async = true]'( + node: TSESTree.ArrowFunctionExpression, + ): void { + rules.ArrowFunctionExpression(node); + + // If body type is not BlockStatment, we need to check the return type here + if (node.body.type !== AST_NODE_TYPES.BlockStatement) { + const expression = parserServices.esTreeNodeToTSNodeMap.get( + node.body, + ); + if (expression && isThenableType(expression)) { + // tell the base rule to mark the scope as having an await so it ignores it + rules.AwaitExpression(node as never); + } } + }, + 'FunctionDeclaration[async = true]:exit': + rules['FunctionDeclaration:exit'], + 'FunctionExpression[async = true]:exit': rules['FunctionExpression:exit'], + 'ArrowFunctionExpression[async = true]:exit': + rules['ArrowFunctionExpression:exit'], + AwaitExpression: rules.AwaitExpression, + ForOfStatement: rules.ForOfStatement, + ReturnStatement(node): void { const { expression } = parserServices.esTreeNodeToTSNodeMap.get< ts.ReturnStatement >(node); - if (!expression) { - return; + if (expression && isThenableType(expression)) { + // tell the base rule to mark the scope as having an await so it ignores it + rules.AwaitExpression(node as never); } - - scopeInfo.returnsPromise = isThenableType(expression); }, - - AwaitExpression: rules.AwaitExpression as TSESLint.RuleFunction< - TSESTree.Node - >, - ForOfStatement: rules.ForOfStatement as TSESLint.RuleFunction< - TSESTree.Node - >, }; }, }); diff --git a/packages/eslint-plugin/tests/rules/require-await.test.ts b/packages/eslint-plugin/tests/rules/require-await.test.ts index 38a925de1d2c..09b54bd3a99d 100644 --- a/packages/eslint-plugin/tests/rules/require-await.test.ts +++ b/packages/eslint-plugin/tests/rules/require-await.test.ts @@ -128,6 +128,15 @@ ruleTester.run('require-await', rule, { return Promise.resolve(x); }`, }, + // https://github.com/typescript-eslint/typescript-eslint/issues/1188 + ` +async function testFunction(): Promise { + await Promise.all([1, 2, 3].map( + // this should not trigger an error on the parent function + async value => Promise.resolve(value) + )) +} + `, ], invalid: [