Skip to content

Commit 3617094

Browse files
authored
fix(lint/noFloatingPromises): select the matching overload (#10586)
1 parent d6d55d0 commit 3617094

17 files changed

Lines changed: 462 additions & 1 deletion
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#9568](https://github.com/biomejs/biome/issues/9568): [`noFloatingPromises`](https://biomejs.dev/linter/rules/no-floating-promises/) no longer reports a false positive when calling an overloaded function and the selected overload does not return a promise.
6+
7+
```ts
8+
function bestEffort(cb: () => Promise<number>): Promise<number>;
9+
function bestEffort(cb: () => number): number;
10+
function bestEffort(cb: () => number | Promise<number>): Promise<number> | number {
11+
return cb() as Promise<number> | number;
12+
}
13+
14+
// This resolves to the second overload, which returns `number`, so it is no
15+
// longer flagged as a floating promise.
16+
bestEffort(() => 42);
17+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* should not generate diagnostics */
2+
3+
function bestEffort<T>(cb: () => Promise<T>): Promise<T | undefined>;
4+
function bestEffort<T>(cb: () => T): T | undefined;
5+
function bestEffort<T>(cb: (() => T) | (() => Promise<T>)): Promise<T | undefined> | T | undefined {
6+
return undefined;
7+
}
8+
9+
function syncWork(): number {
10+
return 42;
11+
}
12+
13+
// Generic overload exactly like the reported issue: selects the second
14+
// overload (sync), so it must NOT be flagged.
15+
bestEffort(syncWork);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: issue9568_generic.ts
4+
---
5+
# Input
6+
```ts
7+
/* should not generate diagnostics */
8+
9+
function bestEffort<T>(cb: () => Promise<T>): Promise<T | undefined>;
10+
function bestEffort<T>(cb: () => T): T | undefined;
11+
function bestEffort<T>(cb: (() => T) | (() => Promise<T>)): Promise<T | undefined> | T | undefined {
12+
return undefined;
13+
}
14+
15+
function syncWork(): number {
16+
return 42;
17+
}
18+
19+
// Generic overload exactly like the reported issue: selects the second
20+
// overload (sync), so it must NOT be flagged.
21+
bestEffort(syncWork);
22+
23+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function bestEffort(cb: () => Promise<number>): Promise<number>;
2+
function bestEffort(cb: () => number): number;
3+
function bestEffort(cb: () => number | Promise<number>): Promise<number> | number {
4+
return cb() as Promise<number> | number;
5+
}
6+
7+
async function asyncWork(): Promise<number> {
8+
return 42;
9+
}
10+
11+
// Resolves to the first overload (async callback) -> returns a promise,
12+
// so this floating call must still be flagged.
13+
bestEffort(asyncWork);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: issue9568_invalid.ts
4+
---
5+
# Input
6+
```ts
7+
function bestEffort(cb: () => Promise<number>): Promise<number>;
8+
function bestEffort(cb: () => number): number;
9+
function bestEffort(cb: () => number | Promise<number>): Promise<number> | number {
10+
return cb() as Promise<number> | number;
11+
}
12+
13+
async function asyncWork(): Promise<number> {
14+
return 42;
15+
}
16+
17+
// Resolves to the first overload (async callback) -> returns a promise,
18+
// so this floating call must still be flagged.
19+
bestEffort(asyncWork);
20+
21+
```
22+
23+
# Diagnostics
24+
```
25+
issue9568_invalid.ts:13:1 lint/nursery/noFloatingPromises ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
26+
27+
i A "floating" Promise was found, meaning it is not properly handled and could lead to ignored errors or unexpected behavior.
28+
29+
11 │ // Resolves to the first overload (async callback) -> returns a promise,
30+
12 │ // so this floating call must still be flagged.
31+
> 13 │ bestEffort(asyncWork);
32+
│ ^^^^^^^^^^^^^^^^^^^^^^
33+
14 │
34+
35+
i This happens when a Promise is not awaited, lacks a `.catch` or `.then` rejection handler, or is not explicitly ignored using the `void` operator.
36+
37+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
38+
39+
40+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* should not generate diagnostics */
2+
3+
function bestEffort(cb: () => Promise<number>): Promise<number>;
4+
function bestEffort(cb: () => number): number;
5+
function bestEffort(cb: () => number | Promise<number>): Promise<number> | number {
6+
return cb() as Promise<number> | number;
7+
}
8+
9+
function syncWork(): number {
10+
return 42;
11+
}
12+
13+
// Resolves to the second overload (sync callback) -> returns `number`,
14+
// which is not a promise, so this must NOT be flagged.
15+
bestEffort(syncWork);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: issue9568_valid.ts
4+
---
5+
# Input
6+
```ts
7+
/* should not generate diagnostics */
8+
9+
function bestEffort(cb: () => Promise<number>): Promise<number>;
10+
function bestEffort(cb: () => number): number;
11+
function bestEffort(cb: () => number | Promise<number>): Promise<number> | number {
12+
return cb() as Promise<number> | number;
13+
}
14+
15+
function syncWork(): number {
16+
return 42;
17+
}
18+
19+
// Resolves to the second overload (sync callback) -> returns `number`,
20+
// which is not a promise, so this must NOT be flagged.
21+
bestEffort(syncWork);
22+
23+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* should not generate diagnostics */
2+
3+
interface SyncCb { (): void }
4+
interface AsyncCb { (): Promise<void> }
5+
6+
function run(cb: AsyncCb): Promise<void>;
7+
function run(cb: SyncCb): void;
8+
function run(cb: SyncCb | AsyncCb): Promise<void> | void {
9+
const result = cb();
10+
return result instanceof Promise ? result : undefined;
11+
}
12+
13+
const sync: SyncCb = () => {};
14+
15+
// SyncCb argument selects the second overload (returns `void`), must NOT be flagged.
16+
run(sync);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
assertion_line: 149
4+
expression: overloadCallableInterface.ts
5+
---
6+
# Input
7+
```ts
8+
/* should not generate diagnostics */
9+
10+
interface SyncCb { (): void }
11+
interface AsyncCb { (): Promise<void> }
12+
13+
function run(cb: AsyncCb): Promise<void>;
14+
function run(cb: SyncCb): void;
15+
function run(cb: SyncCb | AsyncCb): Promise<void> | void {
16+
const result = cb();
17+
return result instanceof Promise ? result : undefined;
18+
}
19+
20+
const sync: SyncCb = () => {};
21+
22+
// SyncCb argument selects the second overload (returns `void`), must NOT be flagged.
23+
run(sync);
24+
25+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* should not generate diagnostics */
2+
3+
function onlyAsync(cb: () => Promise<void>): Promise<void>;
4+
function onlyAsync(cb: () => Promise<void>, retries: number): Promise<void>;
5+
function onlyAsync(cb: () => Promise<void>, retries?: number): Promise<void> {
6+
return cb();
7+
}
8+
9+
function syncCb(): void {}
10+
11+
// Neither overload accepts a synchronous callback, so the call resolves to no
12+
// signature. Its type is unknown rather than the first overload's
13+
// `Promise<void>`, so it must NOT be flagged as a floating promise.
14+
onlyAsync(syncCb);

0 commit comments

Comments
 (0)