Skip to content

Commit a7eff35

Browse files
committed
fix(no-floating-promises): avoid recursive checker calls
1 parent eea92ec commit a7eff35

3 files changed

Lines changed: 103 additions & 2 deletions

File tree

internal/rule_tester/__snapshots__/no-floating-promises.snap

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,3 +1684,26 @@ Help: The promise must end with a call to .catch, or end with a call to .then wi
16841684
Suggestion 1: [floatingFixVoid] Add void operator to ignore.
16851685
Suggestion 2: [floatingFixAwait] Add await operator.
16861686
---
1687+
1688+
[TestNoFloatingPromisesRule/invalid-101 - 1]
1689+
Diagnostic 1: floatingVoid (14:9 - 20:13)
1690+
Message: Promises must be awaited, add void operator to ignore.
1691+
Help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.
1692+
13 |
1693+
14 | (async () => {
1694+
| ~~~~~~~~~~~~~~
1695+
15 | wrapNode(() => {
1696+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~
1697+
16 | const node = createNode();
1698+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1699+
17 |
1700+
18 | return wrapNode<typeof node.getNextNode<any>>(node.getNextNode);
1701+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1702+
19 | });
1703+
| ~~~~~~~~~~~~~
1704+
20 | })();
1705+
| ~~~~~~~~~~~~~
1706+
21 |
1707+
Suggestion 1: [floatingFixVoid] Add void operator to ignore.
1708+
Suggestion 2: [floatingFixAwait] Add await operator.
1709+
---

internal/rules/no_floating_promises/no_floating_promises.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,48 @@ var NoFloatingPromisesRule = rule.Rule{
256256
return true
257257
}
258258

259+
isFunctionExpression := func(node *ast.Node) bool {
260+
return ast.IsArrowFunction(node) || ast.IsFunctionExpression(node)
261+
}
262+
263+
skipOuterExpressions := func(node *ast.Node) *ast.Node {
264+
return ast.SkipOuterExpressions(node, ast.OEKAll)
265+
}
266+
267+
isAsyncIifeCall := func(node *ast.Node) bool {
268+
if !ast.IsCallExpression(node) {
269+
return false
270+
}
271+
callee := skipOuterExpressions(node.AsCallExpression().Expression)
272+
return isFunctionExpression(callee) && ast.IsAsyncFunction(callee)
273+
}
274+
275+
getStaticPropertyName := func(node *ast.Node) string {
276+
if !ast.IsPropertyAccessExpression(node) {
277+
return ""
278+
}
279+
return node.AsPropertyAccessExpression().Name().Text()
280+
}
281+
282+
var containsTypeArgumentCall func(node *ast.Node) bool
283+
containsTypeArgumentCall = func(node *ast.Node) bool {
284+
if ast.IsCallExpression(node) && node.AsCallExpression().TypeArguments != nil {
285+
return true
286+
}
287+
return node.ForEachChild(func(child *ast.Node) bool {
288+
return containsTypeArgumentCall(child)
289+
})
290+
}
291+
292+
containsFunctionArgumentWithTypeArgumentCall := func(callExpr *ast.CallExpression) bool {
293+
for _, arg := range callExpr.Arguments.Nodes {
294+
if isFunctionExpression(arg) && containsTypeArgumentCall(arg) {
295+
return true
296+
}
297+
}
298+
return false
299+
}
300+
259301
var isUnhandledPromise func(
260302
node *ast.Node,
261303
) (
@@ -293,6 +335,44 @@ var NoFloatingPromisesRule = rule.Rule{
293335
return isUnhandledPromise(node.Expression())
294336
}
295337

338+
if ast.IsCallExpression(node) {
339+
callExpr := node.AsCallExpression()
340+
callee := skipOuterExpressions(callExpr.Expression)
341+
342+
if isAsyncIifeCall(node) {
343+
return true, false, false
344+
}
345+
346+
if ast.IsAccessExpression(callee) && isAsyncIifeCall(callee.Expression()) {
347+
switch getStaticPropertyName(callee) {
348+
case "catch":
349+
if !isKnownArgumentAt(callExpr.Arguments, 0) {
350+
return true, false, false
351+
}
352+
if isFunctionExpression(callExpr.Arguments.Nodes[0]) {
353+
return false, false, false
354+
}
355+
return true, true, false
356+
case "then":
357+
if !isKnownArgumentAt(callExpr.Arguments, 1) {
358+
return true, false, false
359+
}
360+
if isFunctionExpression(callExpr.Arguments.Nodes[1]) {
361+
return false, false, false
362+
}
363+
return true, true, false
364+
case "finally":
365+
return isUnhandledPromise(callee.Expression())
366+
}
367+
}
368+
369+
// typescript-go can recurse indefinitely when resolving some generic
370+
// calls inside callback return types.
371+
if containsFunctionArgumentWithTypeArgumentCall(callExpr) {
372+
return false, false, false
373+
}
374+
}
375+
296376
// Check the type. At this point it can't be unhandled if it isn't a promise
297377
// or array thereof.
298378

internal/rules/no_floating_promises/no_floating_promises_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,6 @@ myAsyncFunction();
807807
Options: rule_tester.OptionsFromJSON[NoFloatingPromisesOptions](`{"allowForKnownSafeCalls": ["myAsyncFunction"]}`),
808808
},
809809
{
810-
Skip: true,
811810
Code: `
812811
interface CustomNode<P> {
813812
getNextNode: () => CustomNode<P>;
@@ -5606,7 +5605,6 @@ await Promise.reject('foo').then(...[], () => {});
56065605
},
56075606
},
56085607
{
5609-
Skip: true,
56105608
Code: `
56115609
interface CustomNode<P> {
56125610
getNextNode: () => CustomNode<P>;

0 commit comments

Comments
 (0)