Skip to content

Commit 3c6aec7

Browse files
committed
fix(no-floating-promises): avoid recursive checker calls
1 parent 4d71eb8 commit 3c6aec7

3 files changed

Lines changed: 117 additions & 2 deletions

File tree

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,3 +1684,38 @@ 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+
---
1710+
1711+
[TestNoFloatingPromisesRule/invalid-102 - 1]
1712+
Diagnostic 1: floatingVoid (4:1 - 4:22)
1713+
Message: Promises must be awaited, add void operator to ignore.
1714+
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.
1715+
3 |
1716+
4 | maybePromise('value');
1717+
| ~~~~~~~~~~~~~~~~~~~~~~
1718+
5 |
1719+
Suggestion 1: [floatingFixVoid] Add void operator to ignore.
1720+
Suggestion 2: [floatingFixAwait] Add await operator.
1721+
---

internal/rules/no_floating_promises/no_floating_promises.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,54 @@ var NoFloatingPromisesRule = rule.Rule{
200200
}
201201
return false
202202
}
203+
var returnTypeNeedsCallInstantiation func(t *checker.Type) bool
204+
returnTypeNeedsCallInstantiation = func(t *checker.Type) bool {
205+
if t.Flags()&checker.TypeFlagsInstantiableNonPrimitive != 0 {
206+
return true
207+
}
208+
if t.Flags()&checker.TypeFlagsUnionOrIntersection != 0 {
209+
for _, part := range t.Types() {
210+
if returnTypeNeedsCallInstantiation(part) {
211+
return true
212+
}
213+
}
214+
}
215+
216+
for _, typePart := range utils.UnionTypeParts(t) {
217+
apparent := checker.Checker_getApparentType(ctx.TypeChecker, typePart)
218+
if !checker.Checker_isArrayType(ctx.TypeChecker, apparent) && !checker.IsTupleType(apparent) {
219+
continue
220+
}
221+
for _, typeArg := range checker.Checker_getTypeArguments(ctx.TypeChecker, apparent) {
222+
if returnTypeNeedsCallInstantiation(typeArg) {
223+
return true
224+
}
225+
}
226+
}
227+
return false
228+
}
229+
callReturnsKnownNonPromise := func(callExpr *ast.CallExpression) bool {
230+
calleeType := ctx.TypeChecker.GetTypeAtLocation(callExpr.Expression)
231+
signatures := utils.GetCallSignatures(ctx.TypeChecker, calleeType)
232+
if len(signatures) == 0 {
233+
return false
234+
}
235+
236+
for _, signature := range signatures {
237+
returnType := checker.Checker_getReturnTypeOfSignature(ctx.TypeChecker, signature)
238+
if returnType == nil || returnType.Flags()&checker.TypeFlagsTypeParameter != 0 {
239+
return false
240+
}
241+
if returnTypeNeedsCallInstantiation(returnType) {
242+
return false
243+
}
244+
if isPromiseArray(callExpr.AsNode(), returnType) || isPromiseLike(callExpr.AsNode(), returnType) {
245+
return false
246+
}
247+
}
248+
249+
return true
250+
}
203251

204252
isKnownSafePromiseReturn := func(node *ast.Node) bool {
205253
if len(opts.AllowForKnownSafeCalls) == 0 {
@@ -296,6 +344,10 @@ var NoFloatingPromisesRule = rule.Rule{
296344
// Check the type. At this point it can't be unhandled if it isn't a promise
297345
// or array thereof.
298346

347+
if ast.IsCallExpression(node) && callReturnsKnownNonPromise(node.AsCallExpression()) {
348+
return false, false, false
349+
}
350+
299351
t := ctx.TypeChecker.GetTypeAtLocation(node)
300352
if isPromiseArray(node, t) {
301353
return true, false, true

internal/rules/no_floating_promises/no_floating_promises_test.go

Lines changed: 30 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>;
@@ -5678,6 +5676,36 @@ await Promise.reject('foo').then(...[], () => {});
56785676
return wrapNode<typeof node.getNextNode<any>>(node.getNextNode);
56795677
});
56805678
})();
5679+
`,
5680+
},
5681+
},
5682+
},
5683+
},
5684+
},
5685+
{
5686+
Code: `
5687+
declare function maybePromise<T>(value: T): T extends string ? Promise<void> : void;
5688+
5689+
maybePromise('value');
5690+
`,
5691+
Errors: []rule_tester.InvalidTestCaseError{
5692+
{
5693+
MessageId: "floatingVoid",
5694+
Suggestions: []rule_tester.InvalidTestCaseSuggestion{
5695+
{
5696+
MessageId: "floatingFixVoid",
5697+
Output: `
5698+
declare function maybePromise<T>(value: T): T extends string ? Promise<void> : void;
5699+
5700+
void maybePromise('value');
5701+
`,
5702+
},
5703+
{
5704+
MessageId: "floatingFixAwait",
5705+
Output: `
5706+
declare function maybePromise<T>(value: T): T extends string ? Promise<void> : void;
5707+
5708+
await maybePromise('value');
56815709
`,
56825710
},
56835711
},

0 commit comments

Comments
 (0)