From 4147ac6c8543672a66ea1c327bf0c9532c1f57cc Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Sun, 30 Aug 2026 17:40:23 +0100 Subject: [PATCH] [0.84] Types: Annotate `Promise` returns on async functions Summary: `flow-api-translator` assumes that a function without an explicit return type annotation returns `void`. That isn't true of `async` functions, which always return a `Promise`, so our generated `.d.ts` declares seven async functions as returning `void` - including the public `Server#end()`, `Watcher#watch()`, `Watcher#close()` and `DependencyGraph#end()`. TypeScript consumers awaiting those get `await-thenable` from typescript-eslint, or quietly don't await at all. I've fixed the translator upstream in https://github.com/facebook/flow/pull/9486, but 0.84.x pins `flow-api-translator` 0.35.0 and I'd rather not bump the toolchain on a release branch, so this annotates the return types at source instead. It's a no-op for Flow, which already infers `Promise` in every case here, and it's what we want in the source regardless. `main` will pick the translator fix up with the next version bump, after which these annotations are still correct, just no longer load-bearing. To find the full set I translated every file the generator covers with 0.35.0 patched and unpatched and diffed the output - these seven declarations, across five files, are all of them on this branch. Changelog: ``` - **[Fix]**: Types: async methods including `Server#end`, `Watcher#watch`/`#close` and `DependencyGraph#end` are declared as returning `Promise` rather than `void` ``` Test plan: ``` yarn run build-ts-defs # updates exactly the five .d.ts files, no other churn yarn typecheck # No errors! yarn typecheck-ts yarn jest packages/metro/src/Server/__tests__/Server-test.js packages/metro-file-map/src/__tests__ packages/metro-file-map/src/watchers/__tests__ ``` --- packages/buck-worker-tool/src/profiling.js | 6 ++++-- packages/buck-worker-tool/types/profiling.d.ts | 6 +++--- packages/metro-file-map/src/Watcher.js | 6 ++++-- packages/metro-file-map/src/watchers/NativeWatcher.js | 2 +- packages/metro-file-map/types/Watcher.d.ts | 6 +++--- packages/metro-file-map/types/watchers/NativeWatcher.d.ts | 4 ++-- packages/metro/src/Server.js | 2 +- packages/metro/src/node-haste/DependencyGraph.js | 2 +- packages/metro/types/Server.d.ts | 4 ++-- packages/metro/types/node-haste/DependencyGraph.d.ts | 4 ++-- 10 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/buck-worker-tool/src/profiling.js b/packages/buck-worker-tool/src/profiling.js index 3903bc0b0e..f39dd606ec 100644 --- a/packages/buck-worker-tool/src/profiling.js +++ b/packages/buck-worker-tool/src/profiling.js @@ -25,7 +25,7 @@ function getInspectorSession() { return currentInspectorSession; } -export async function startProfiling() { +export async function startProfiling(): Promise { if (isProfiling) { return; } @@ -36,7 +36,9 @@ export async function startProfiling() { isProfiling = true; } -export async function stopProfilingAndWrite(workerName: ?string) { +export async function stopProfilingAndWrite( + workerName: ?string, +): Promise { if (!isProfiling) { return; } diff --git a/packages/buck-worker-tool/types/profiling.d.ts b/packages/buck-worker-tool/types/profiling.d.ts index b2d545e7f7..b850f299e2 100644 --- a/packages/buck-worker-tool/types/profiling.d.ts +++ b/packages/buck-worker-tool/types/profiling.d.ts @@ -6,7 +6,7 @@ * * @noformat * @oncall react_native - * @generated SignedSource<> + * @generated SignedSource<<09802f56d759b9edc8e6453a35bed569>> * * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js * Original file: packages/buck-worker-tool/src/profiling.js @@ -15,7 +15,7 @@ * yarn run build-ts-defs (OSS) */ -export declare function startProfiling(): void; +export declare function startProfiling(): Promise; export declare function stopProfilingAndWrite( workerName: null | undefined | string, -): void; +): Promise; diff --git a/packages/metro-file-map/src/Watcher.js b/packages/metro-file-map/src/Watcher.js index 99e10618a1..59b3c9a0e6 100644 --- a/packages/metro-file-map/src/Watcher.js +++ b/packages/metro-file-map/src/Watcher.js @@ -183,7 +183,9 @@ export class Watcher extends EventEmitter { return delta; } - async watch(onChange: (change: WatcherBackendChangeEvent) => void) { + async watch( + onChange: (change: WatcherBackendChangeEvent) => void, + ): Promise { const {extensions, ignorePatternForWatch, useWatchman} = this.#options; // WatchmanWatcher > NativeWatcher > FallbackWatcher @@ -268,7 +270,7 @@ export class Watcher extends EventEmitter { resolveHealthCheck(); } - async close() { + async close(): Promise { await Promise.all(this.#backends.map(watcher => watcher.stopWatching())); this.#activeWatcher = null; } diff --git a/packages/metro-file-map/src/watchers/NativeWatcher.js b/packages/metro-file-map/src/watchers/NativeWatcher.js index 3c11a384fe..d687fd1f1e 100644 --- a/packages/metro-file-map/src/watchers/NativeWatcher.js +++ b/packages/metro-file-map/src/watchers/NativeWatcher.js @@ -95,7 +95,7 @@ export default class NativeWatcher extends AbstractWatcher { } } - async _handleEvent(event: string, relativePath: string) { + async _handleEvent(event: string, relativePath: string): Promise { const absolutePath = path.resolve(this.root, relativePath); if (this.doIgnore(relativePath)) { debug( diff --git a/packages/metro-file-map/types/Watcher.d.ts b/packages/metro-file-map/types/Watcher.d.ts index 9dff3ba096..a43ebf59cd 100644 --- a/packages/metro-file-map/types/Watcher.d.ts +++ b/packages/metro-file-map/types/Watcher.d.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @noformat - * @generated SignedSource<<25fee66c7d26ad53cdd5bbab454fe50b>> + * @generated SignedSource<> * * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js * Original file: packages/metro-file-map/src/Watcher.js @@ -68,7 +68,7 @@ export declare class Watcher extends EventEmitter { subpath: string, currentFileSystem: CrawlerOptions['previousState']['fileSystem'], ): Promise; - watch(onChange: (change: WatcherBackendChangeEvent) => void): void; - close(): void; + watch(onChange: (change: WatcherBackendChangeEvent) => void): Promise; + close(): Promise; checkHealth(timeout: number): Promise; } diff --git a/packages/metro-file-map/types/watchers/NativeWatcher.d.ts b/packages/metro-file-map/types/watchers/NativeWatcher.d.ts index b118598f7e..8b2069ffc6 100644 --- a/packages/metro-file-map/types/watchers/NativeWatcher.d.ts +++ b/packages/metro-file-map/types/watchers/NativeWatcher.d.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @noformat - * @generated SignedSource<> + * @generated SignedSource<<99faac6446a910cc70728424f342908c>> * * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js * Original file: packages/metro-file-map/src/watchers/NativeWatcher.js @@ -50,6 +50,6 @@ declare class NativeWatcher extends AbstractWatcher { * End watching. */ stopWatching(): Promise; - _handleEvent(event: string, relativePath: string): void; + _handleEvent(event: string, relativePath: string): Promise; } export default NativeWatcher; diff --git a/packages/metro/src/Server.js b/packages/metro/src/Server.js index c21f619851..d0f68a9eba 100644 --- a/packages/metro/src/Server.js +++ b/packages/metro/src/Server.js @@ -201,7 +201,7 @@ export default class Server { this._nextBundleBuildNumber = 1; } - async end() { + async end(): Promise { if (!this._isEnded) { await this._bundler.end(); this._isEnded = true; diff --git a/packages/metro/src/node-haste/DependencyGraph.js b/packages/metro/src/node-haste/DependencyGraph.js index 7cf3a80a54..f1efe1b104 100644 --- a/packages/metro/src/node-haste/DependencyGraph.js +++ b/packages/metro/src/node-haste/DependencyGraph.js @@ -284,7 +284,7 @@ export default class DependencyGraph extends EventEmitter { return this._haste; } - async end() { + async end(): Promise { await this.ready(); await this._haste.end(); } diff --git a/packages/metro/types/Server.d.ts b/packages/metro/types/Server.d.ts index 0f94dbd03d..52439a65ca 100644 --- a/packages/metro/types/Server.d.ts +++ b/packages/metro/types/Server.d.ts @@ -6,7 +6,7 @@ * * @noformat * @oncall react_native - * @generated SignedSource<<03b526801403adb05b3b0f6c25b25ed5>> + * @generated SignedSource<<58a8bc4229ee86a5c93af2ea51195b97>> * * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js * Original file: packages/metro/src/Server.js @@ -120,7 +120,7 @@ declare class Server { _fetchTimings: Array; _activeFetchCount: number; constructor(config: ConfigT, options?: ServerOptions); - end(): void; + end(): Promise; getBundler(): IncrementalBundler; getCreateModuleId(): (path: string) => number; _serializeGraph( diff --git a/packages/metro/types/node-haste/DependencyGraph.d.ts b/packages/metro/types/node-haste/DependencyGraph.d.ts index 6daabfb1c1..150546b496 100644 --- a/packages/metro/types/node-haste/DependencyGraph.d.ts +++ b/packages/metro/types/node-haste/DependencyGraph.d.ts @@ -6,7 +6,7 @@ * * @noformat * @oncall react_native - * @generated SignedSource<<13f1483d2a732241f8d9eae463399b0e>> + * @generated SignedSource<<0ae2c74336868865b420d900a230cea7>> * * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js * Original file: packages/metro/src/node-haste/DependencyGraph.js @@ -70,7 +70,7 @@ declare class DependencyGraph extends EventEmitter { mixedPath: string, ): Promise<{content?: Buffer; sha1: string}>; getWatcher(): EventEmitter; - end(): void; + end(): Promise; /** Given a search context, return a list of file paths matching the query. */ matchFilesWithContext( from: string,