Skip to content

Commit eb83af1

Browse files
authored
fix(eslint-plugin): [require-await] better handle nesting (#1193)
1 parent 9829dd3 commit eb83af1

File tree

2 files changed

+37
-103
lines changed

2 files changed

+37
-103
lines changed

Diff for: packages/eslint-plugin/src/rules/require-await.ts

+28-103
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
TSESTree,
3-
TSESLint,
43
AST_NODE_TYPES,
54
} from '@typescript-eslint/experimental-utils';
65
import baseRule from 'eslint/lib/rules/require-await';
@@ -11,11 +10,6 @@ import * as util from '../util';
1110
type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
1211
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
1312

14-
interface ScopeInfo {
15-
upper: ScopeInfo | null;
16-
returnsPromise: boolean;
17-
}
18-
1913
export default util.createRule<Options, MessageIds>({
2014
name: 'require-await',
2115
meta: {
@@ -35,82 +29,6 @@ export default util.createRule<Options, MessageIds>({
3529
const parserServices = util.getParserServices(context);
3630
const checker = parserServices.program.getTypeChecker();
3731

38-
let scopeInfo: ScopeInfo | null = null;
39-
40-
/**
41-
* Push the scope info object to the stack.
42-
*
43-
* @returns {void}
44-
*/
45-
function enterFunction(
46-
node:
47-
| TSESTree.FunctionDeclaration
48-
| TSESTree.FunctionExpression
49-
| TSESTree.ArrowFunctionExpression,
50-
): void {
51-
scopeInfo = {
52-
upper: scopeInfo,
53-
returnsPromise: false,
54-
};
55-
56-
switch (node.type) {
57-
case AST_NODE_TYPES.FunctionDeclaration:
58-
rules.FunctionDeclaration(node);
59-
break;
60-
61-
case AST_NODE_TYPES.FunctionExpression:
62-
rules.FunctionExpression(node);
63-
break;
64-
65-
case AST_NODE_TYPES.ArrowFunctionExpression:
66-
rules.ArrowFunctionExpression(node);
67-
68-
// If body type is not BlockStatment, we need to check the return type here
69-
if (node.body.type !== AST_NODE_TYPES.BlockStatement) {
70-
const expression = parserServices.esTreeNodeToTSNodeMap.get(
71-
node.body,
72-
);
73-
scopeInfo.returnsPromise = isThenableType(expression);
74-
}
75-
76-
break;
77-
}
78-
}
79-
80-
/**
81-
* Pop the top scope info object from the stack.
82-
* Passes through to the base rule if the function doesn't return a promise
83-
*
84-
* @param {ASTNode} node - The node exiting
85-
* @returns {void}
86-
*/
87-
function exitFunction(
88-
node:
89-
| TSESTree.FunctionDeclaration
90-
| TSESTree.FunctionExpression
91-
| TSESTree.ArrowFunctionExpression,
92-
): void {
93-
if (scopeInfo) {
94-
if (!scopeInfo.returnsPromise) {
95-
switch (node.type) {
96-
case AST_NODE_TYPES.FunctionDeclaration:
97-
rules['FunctionDeclaration:exit'](node);
98-
break;
99-
100-
case AST_NODE_TYPES.FunctionExpression:
101-
rules['FunctionExpression:exit'](node);
102-
break;
103-
104-
case AST_NODE_TYPES.ArrowFunctionExpression:
105-
rules['ArrowFunctionExpression:exit'](node);
106-
break;
107-
}
108-
}
109-
110-
scopeInfo = scopeInfo.upper;
111-
}
112-
}
113-
11432
/**
11533
* Checks if the node returns a thenable type
11634
*
@@ -124,34 +42,41 @@ export default util.createRule<Options, MessageIds>({
12442
}
12543

12644
return {
127-
'FunctionDeclaration[async = true]': enterFunction,
128-
'FunctionExpression[async = true]': enterFunction,
129-
'ArrowFunctionExpression[async = true]': enterFunction,
130-
'FunctionDeclaration[async = true]:exit': exitFunction,
131-
'FunctionExpression[async = true]:exit': exitFunction,
132-
'ArrowFunctionExpression[async = true]:exit': exitFunction,
133-
134-
ReturnStatement(node): void {
135-
if (!scopeInfo) {
136-
return;
45+
'FunctionDeclaration[async = true]': rules.FunctionDeclaration,
46+
'FunctionExpression[async = true]': rules.FunctionExpression,
47+
'ArrowFunctionExpression[async = true]'(
48+
node: TSESTree.ArrowFunctionExpression,
49+
): void {
50+
rules.ArrowFunctionExpression(node);
51+
52+
// If body type is not BlockStatment, we need to check the return type here
53+
if (node.body.type !== AST_NODE_TYPES.BlockStatement) {
54+
const expression = parserServices.esTreeNodeToTSNodeMap.get(
55+
node.body,
56+
);
57+
if (expression && isThenableType(expression)) {
58+
// tell the base rule to mark the scope as having an await so it ignores it
59+
rules.AwaitExpression(node as never);
60+
}
13761
}
62+
},
63+
'FunctionDeclaration[async = true]:exit':
64+
rules['FunctionDeclaration:exit'],
65+
'FunctionExpression[async = true]:exit': rules['FunctionExpression:exit'],
66+
'ArrowFunctionExpression[async = true]:exit':
67+
rules['ArrowFunctionExpression:exit'],
68+
AwaitExpression: rules.AwaitExpression,
69+
ForOfStatement: rules.ForOfStatement,
13870

71+
ReturnStatement(node): void {
13972
const { expression } = parserServices.esTreeNodeToTSNodeMap.get<
14073
ts.ReturnStatement
14174
>(node);
142-
if (!expression) {
143-
return;
75+
if (expression && isThenableType(expression)) {
76+
// tell the base rule to mark the scope as having an await so it ignores it
77+
rules.AwaitExpression(node as never);
14478
}
145-
146-
scopeInfo.returnsPromise = isThenableType(expression);
14779
},
148-
149-
AwaitExpression: rules.AwaitExpression as TSESLint.RuleFunction<
150-
TSESTree.Node
151-
>,
152-
ForOfStatement: rules.ForOfStatement as TSESLint.RuleFunction<
153-
TSESTree.Node
154-
>,
15580
};
15681
},
15782
});

Diff for: packages/eslint-plugin/tests/rules/require-await.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ ruleTester.run('require-await', rule, {
128128
return Promise.resolve(x);
129129
}`,
130130
},
131+
// https://github.com/typescript-eslint/typescript-eslint/issues/1188
132+
`
133+
async function testFunction(): Promise<void> {
134+
await Promise.all([1, 2, 3].map(
135+
// this should not trigger an error on the parent function
136+
async value => Promise.resolve(value)
137+
))
138+
}
139+
`,
131140
],
132141

133142
invalid: [

0 commit comments

Comments
 (0)