diff --git a/.eslintrc.js b/.eslintrc.js index 54373f38e6..4c3ed2b85b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -60,14 +60,5 @@ module.exports = { 'import/no-commonjs': 'off', }, }, - { - files: [ - // flow-api-translator doesn't support translating `empty` - 'packages/metro-file-map/types/flow-types.d.ts', - ], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - }, - }, ], }; diff --git a/packages/metro-file-map/src/lib/dependencyExtractor.d.ts b/packages/metro-file-map/src/lib/dependencyExtractor.d.ts deleted file mode 100644 index 934a76244e..0000000000 --- a/packages/metro-file-map/src/lib/dependencyExtractor.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -declare const dependencyExtractor: { - extract: (code: string) => Set; -}; - -export = dependencyExtractor; diff --git a/packages/metro-file-map/src/plugins/dependencies/dependencyExtractor.d.ts b/packages/metro-file-map/src/plugins/dependencies/dependencyExtractor.d.ts deleted file mode 100644 index b8cf6d95c9..0000000000 --- a/packages/metro-file-map/src/plugins/dependencies/dependencyExtractor.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @generated by js1 build metro-ts-defs / yarn run build-ts-defs - */ - -declare const dependencyExtractor: { - extract(code: string): Set; -}; - -export = dependencyExtractor; diff --git a/packages/metro-file-map/src/plugins/dependencies/worker.d.ts b/packages/metro-file-map/src/plugins/dependencies/worker.d.ts deleted file mode 100644 index c7b79538f0..0000000000 --- a/packages/metro-file-map/src/plugins/dependencies/worker.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @oncall react_native - */ - -import type { - MetadataWorker, - V8Serializable, - WorkerMessage, -} from '../../flow-types'; - -declare class DependencyExtractorWorker implements MetadataWorker { - constructor(opts: Readonly<{dependencyExtractor: null | undefined | string}>); - processFile( - data: WorkerMessage, - utils: Readonly<{getContent: () => Buffer}>, - ): V8Serializable; -} -export = DependencyExtractorWorker; diff --git a/packages/metro-file-map/src/plugins/dependencies/worker.js b/packages/metro-file-map/src/plugins/dependencies/worker.js index 2828e2e02c..ce5eb3e7f4 100644 --- a/packages/metro-file-map/src/plugins/dependencies/worker.js +++ b/packages/metro-file-map/src/plugins/dependencies/worker.js @@ -19,7 +19,7 @@ const defaultDependencyExtractor = require('./dependencyExtractor'); import type {MetadataWorker, WorkerMessage, V8Serializable, DependencyExtractor} from '../../flow-types'; */ -module.exports = class DependencyExtractorWorker /*:: implements MetadataWorker */ { +class DependencyExtractorWorker /*:: implements MetadataWorker */ { // prettier-ignore /*:: readonly */ #dependencyExtractor /*: ?DependencyExtractor['extract'] */; @@ -56,4 +56,6 @@ module.exports = class DependencyExtractorWorker /*:: implements MetadataWorker // Return as array (PerFileData type) return Array.from(dependencies); } -}; +} + +module.exports = DependencyExtractorWorker; diff --git a/packages/metro-file-map/src/plugins/haste/worker.d.ts b/packages/metro-file-map/src/plugins/haste/worker.d.ts deleted file mode 100644 index eec44e52da..0000000000 --- a/packages/metro-file-map/src/plugins/haste/worker.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import type { - MetadataWorker, - V8Serializable, - WorkerMessage, -} from '../../flow-types'; - -declare class Worker implements MetadataWorker { - constructor(opts: Readonly<{hasteImplModulePath: null | undefined | string}>); - processFile( - data: WorkerMessage, - utils: Readonly<{getContent: () => Buffer}>, - ): V8Serializable; -} - -export = Worker; diff --git a/packages/metro-file-map/src/plugins/haste/worker.js b/packages/metro-file-map/src/plugins/haste/worker.js index 63254a9ae2..6eb0a2f3e5 100644 --- a/packages/metro-file-map/src/plugins/haste/worker.js +++ b/packages/metro-file-map/src/plugins/haste/worker.js @@ -21,7 +21,7 @@ import type {MetadataWorker, WorkerMessage, V8Serializable} from '../../flow-typ const PACKAGE_JSON = path.sep + 'package.json'; -module.exports = class Worker /*:: implements MetadataWorker */ { +class Worker /*:: implements MetadataWorker */ { /*:: readonly */ #hasteImpl /*: ?Readonly<{getHasteName: string => ?string}> */ = null; @@ -63,4 +63,6 @@ module.exports = class Worker /*:: implements MetadataWorker */ { } return hasteName; } -}; +} + +module.exports = Worker; diff --git a/packages/metro-file-map/src/worker.d.ts b/packages/metro-file-map/src/worker.d.ts deleted file mode 100644 index 1c1e3d94c2..0000000000 --- a/packages/metro-file-map/src/worker.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import type { - WorkerMessage, - WorkerMetadata, - WorkerSetupArgs, -} from './flow-types'; - -declare const worker: { - /** - * Called automatically by jest-worker before the first call to `worker` when - * this module is used as worker thread or child process. - */ - setup: (args: WorkerSetupArgs) => void; - /** - * Called by jest-worker with each workload - */ - processFile: (data: WorkerMessage) => WorkerMetadata; - /** - * Exposed for use outside a jest-worker context, ie when processing in-band. - */ - Worker: { - new (setupArgs: WorkerSetupArgs): { - processFile(data: WorkerMessage): WorkerMetadata; - }; - }; -}; - -export = worker; diff --git a/packages/metro-file-map/src/workerExclusionList.d.ts b/packages/metro-file-map/src/workerExclusionList.d.ts deleted file mode 100644 index ac3fba8b76..0000000000 --- a/packages/metro-file-map/src/workerExclusionList.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -declare const exclusionList: ReadonlySet; - -export = exclusionList; diff --git a/packages/metro-minify-terser/API.md b/packages/metro-minify-terser/API.md index 3cde1aeebe..45e1507f12 100644 --- a/packages/metro-minify-terser/API.md +++ b/packages/metro-minify-terser/API.md @@ -4,7 +4,9 @@ ```ts -const minifierFn: typeof minifier; -export default minifierFn; +const $$MODULE_EXPORTS$$: typeof minifier; + +type $$MODULE_EXPORTS$$ = typeof $$MODULE_EXPORTS$$; +export default $$MODULE_EXPORTS$$; ``` diff --git a/packages/metro-minify-terser/src/index.d.ts b/packages/metro-minify-terser/src/index.d.ts deleted file mode 100644 index 39ef5fc46f..0000000000 --- a/packages/metro-minify-terser/src/index.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @oncall react_native - */ - -import minifier from './minifier'; - -declare const minifierFn: typeof minifier; -export = minifierFn; diff --git a/packages/metro-runtime/API-modules-HMRClient.md b/packages/metro-runtime/API-modules-HMRClient.md new file mode 100644 index 0000000000..1849e12ca1 --- /dev/null +++ b/packages/metro-runtime/API-modules-HMRClient.md @@ -0,0 +1,20 @@ +## API Report File for "metro-runtime" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import EventEmitter = require('./vendor/eventemitter3'); + +class HMRClient extends EventEmitter { + constructor(url: string); + close(): void; + disable(): void; + enable(): void; + hasPendingUpdates(): boolean; + isEnabled(): boolean; + send(message: string): void; +} +export default HMRClient; + +``` diff --git a/packages/metro-runtime/API-modules-asyncRequire.md b/packages/metro-runtime/API-modules-asyncRequire.md new file mode 100644 index 0000000000..49ae822ea4 --- /dev/null +++ b/packages/metro-runtime/API-modules-asyncRequire.md @@ -0,0 +1,22 @@ +## API Report File for "metro-runtime" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +const $$MODULE_EXPORTS$$: AsyncRequire; + +type $$MODULE_EXPORTS$$ = typeof $$MODULE_EXPORTS$$; +export default $$MODULE_EXPORTS$$; + +export type AsyncRequire = { + (moduleID: number, paths: DependencyMapPaths, moduleName?: string): Promise; + unstable_importMaybeSync(moduleID: number, paths: DependencyMapPaths): Promise | T; + prefetch(moduleID: number, paths: DependencyMapPaths, moduleName?: string): void; +}; + +export type DependencyMapPaths = null | undefined | Readonly<{[moduleID: number | string]: ReadonlyJsonData}>; + +export type ReadonlyJsonData = null | boolean | number | string | ReadonlyArray | Readonly<{[$$Key$$: string]: ReadonlyJsonData}>; + +``` diff --git a/packages/metro-runtime/API-modules-null-module.md b/packages/metro-runtime/API-modules-null-module.md new file mode 100644 index 0000000000..a7ade4eeda --- /dev/null +++ b/packages/metro-runtime/API-modules-null-module.md @@ -0,0 +1,12 @@ +## API Report File for "metro-runtime" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +const $$MODULE_EXPORTS$$: null; + +type $$MODULE_EXPORTS$$ = typeof $$MODULE_EXPORTS$$; +export default $$MODULE_EXPORTS$$; + +``` diff --git a/packages/metro-runtime/API-polyfills-require.md b/packages/metro-runtime/API-polyfills-require.md new file mode 100644 index 0000000000..d0a5fdf23b --- /dev/null +++ b/packages/metro-runtime/API-polyfills-require.md @@ -0,0 +1,41 @@ +## API Report File for "metro-runtime" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +export type ArrayIndexable = Readonly<{[indexer: number]: T}>; + +export type DefineFn = (factory: FactoryFn, moduleId: number, dependencyMap?: DependencyMap, verboseName?: string, inverseDependencies?: InverseDependencyMap) => void; + +export type DependencyMap = Readonly< + ArrayIndexable & { + paths?: {[id in ModuleID]: string} | undefined; + } +>; + +export type Exports = any; + +export type FactoryFn = ( + global: object, + require: RequireFn, + metroImportDefault: RequireFn, + metroImportAll: RequireFn, + moduleObject: {exports: {}}, + exports: {}, + dependencyMap: null | undefined | DependencyMap, +) => void; + +export type InverseDependencyMap = {[key in ModuleID]: Array}; + +export function metroImportAll(moduleId: ModuleID | VerboseModuleNameForDev | number): any | Exports | {[$$Key$$: string]: any}; + +export function metroImportDefault(moduleId: ModuleID | VerboseModuleNameForDev): any | Exports; + +export type ModuleID = number; + +export type RequireFn = (id: ModuleID | VerboseModuleNameForDev) => Exports; + +export type VerboseModuleNameForDev = string; + +``` diff --git a/packages/metro-runtime/src/modules/asyncRequire.js b/packages/metro-runtime/src/modules/asyncRequire.js index c2fab9e1f9..400416fdf7 100644 --- a/packages/metro-runtime/src/modules/asyncRequire.js +++ b/packages/metro-runtime/src/modules/asyncRequire.js @@ -32,6 +32,24 @@ type DependencyMapPaths = ?Readonly<{ [moduleID: number | string]: ReadonlyJsonData, }>; +type AsyncRequire = { + ( + moduleID: number, + paths: DependencyMapPaths, + moduleName?: string, + ): Promise, + unstable_importMaybeSync( + moduleID: number, + paths: DependencyMapPaths, + ): Promise | T, + prefetch( + moduleID: number, + paths: DependencyMapPaths, + moduleName?: string, + ): void, + ... +}; + declare var __METRO_GLOBAL_PREFIX__: string; function maybeLoadBundle( @@ -97,4 +115,5 @@ asyncRequire.prefetch = function ( ); }; -module.exports = asyncRequire; +// The explicit type carries the statics through to generated type definitions. +module.exports = asyncRequire as AsyncRequire; diff --git a/packages/metro-symbolicate/src/index.d.ts b/packages/metro-symbolicate/src/index.d.ts deleted file mode 100644 index 84510675f4..0000000000 --- a/packages/metro-symbolicate/src/index.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @oncall react_native - */ - -// No exports diff --git a/scripts/eslint/typescript.js b/scripts/eslint/typescript.js index 42c393b58a..c79351dbba 100644 --- a/scripts/eslint/typescript.js +++ b/scripts/eslint/typescript.js @@ -27,4 +27,18 @@ module.exports = { ], plugins: ['@typescript-eslint'], parser: '@typescript-eslint/parser', + rules: { + // These files are generated from Flow sources, which Flow itself checks - + // an `any` here reflects one in the Flow source, or a construct the + // translator can't express. + '@typescript-eslint/no-explicit-any': 'off', + // Noise in generated definitions: a declaration a CommonJS module exports + // via `typeof` is reported as unused. The generator runs this rule itself, + // with `--fix`, to drop declarations that really are unused. + '@typescript-eslint/no-unused-vars': 'off', + // Flow's inexact `{...}` translates to `{}`, which this rule flags. The + // Flow sources that produce one are worth fixing rather than silencing, so + // this is off only until they are. + '@typescript-eslint/no-empty-object-type': 'off', + }, }; diff --git a/scripts/generateTypeScriptDefinitions.js b/scripts/generateTypeScriptDefinitions.js index bc91beef5a..d13a2721b9 100644 --- a/scripts/generateTypeScriptDefinitions.js +++ b/scripts/generateTypeScriptDefinitions.js @@ -56,7 +56,6 @@ const IGNORED_PATTERNS = [ 'packages/metro/src/cli.js', 'packages/**/third-party', 'packages/metro/src/integration_tests', - 'packages/metro-runtime/**/!(types*).js', ]; function isSourceTSDeclaration(filePath: string): boolean { @@ -82,6 +81,13 @@ export async function generateTsDefsForJsGlobs( fix: true, cwd: WORKSPACE_ROOT, overrideConfig: { + rules: { + // Off when linting the repo, because a declaration exported via + // `typeof` is reported as unused - but the generator relies on it to + // drop declarations left unused by translation, and acts only on + // "is defined but never used". + '@typescript-eslint/no-unused-vars': 'error', + }, parserOptions: { // typescript-eslint writes its "version of TypeScript which is not // officially supported" warning straight to `console.log`, bypassing @@ -138,6 +144,10 @@ export async function generateTsDefsForJsGlobs( // corresponding `.js` file, which is enforced to be a transparent // entry file that only registers Babel and re-exports the module. toProcess.set(filePath.replace(/\.flow\.js$/, '.js'), filePath); + } else if (filePath.endsWith('.js.flow')) { + // A .js.flow declaration file is the Flow source of truth for the + // adjacent .js file, which may be untyped (e.g. vendored code). + toProcess.set(filePath.replace(/\.flow$/, ''), filePath); } else if (filePath.endsWith('.js') && !toProcess.has(filePath)) { toProcess.set(filePath, filePath); } else if (isSourceTSDeclaration(filePath)) { @@ -157,23 +167,28 @@ export async function generateTsDefsForJsGlobs( absoluteTsFile: string, sourceFile: string, ) { - // Lint and fix the generated output + // Lint and fix the generated output. `lintText` returns no result at all + // for a path .eslintignore covers - `**/vendor/**`, for one - in which case + // the output stands unlinted. let [lintResult] = await linter.lintText(sourceContent, { filePath: absoluteTsFile, }); - let lintedOutput = lintResult.output ?? sourceContent; + let lintedOutput = lintResult?.output ?? sourceContent; const withoutUnusedGeneratedDeclarations = - removeUnusedGeneratedDeclarations(lintedOutput, lintResult.messages); + removeUnusedGeneratedDeclarations( + lintedOutput, + lintResult?.messages ?? [], + ); if (withoutUnusedGeneratedDeclarations !== lintedOutput) { [lintResult] = await linter.lintText(withoutUnusedGeneratedDeclarations, { filePath: absoluteTsFile, }); - lintedOutput = lintResult.output ?? withoutUnusedGeneratedDeclarations; + lintedOutput = lintResult?.output ?? withoutUnusedGeneratedDeclarations; } - if (logger && lintResult.messages.length > 0) { + if (logger && lintResult != null && lintResult.messages.length > 0) { logger.warn(sourceFile, lintResult.messages); } @@ -249,28 +264,19 @@ export async function generateTsDefsForJsGlobs( } try { const flowDef = await translateFlowToFlowDef( - source, + expandFlowCommentTypes(source), {}, {mungeUnderscores}, ); - if (flowDef.includes('declare module.exports')) { - errors.push({ - sourceFile, - error: new Error( - 'module.exports is not supported by TypeScript auto-generation', - ), - }); - } else { - const tsDef = await translateFlowDefToTSDef(flowDef); + const tsDef = await translateFlowDefToTSDef(flowDef); - const beforeLint = tsDef - // Fix up gap left in license header by removal of atflow - .replace('\n *\n *\n', '\n *\n') - // TypeScript has no analogue for __proto__: null - .replace(/__proto__: null[,;]?/g, ''); + const beforeLint = tsDef + // Fix up gap left in license header by removal of atflow + .replace('\n *\n *\n', '\n *\n') + // TypeScript has no analogue for __proto__: null + .replace(/__proto__: null[,;]?/g, ''); - await writeOutputFile(beforeLint, absoluteTsFile, sourceFile); - } + await writeOutputFile(beforeLint, absoluteTsFile, sourceFile); } catch (error) { errors.push({sourceFile, error}); } @@ -302,6 +308,20 @@ export async function generateTsDefsForJsGlobs( } } +// Files that run in Node without a Babel transform (e.g. worker entry points) +// carry their types in Flow comment syntax, which the translator doesn't see. +// Rewrite them as regular Flow syntax, per +// https://flow.org/en/docs/types/comments/: +// `/*: T */` -> `: T` +// `/*:: code */` and `/*flow-include code */` -> `code` +function expandFlowCommentTypes(source: string): string { + return source.replace( + /\/\*(::|flow-include|:)([\s\S]*?)\*\//g, + (_match, marker: string, content: string) => + marker === ':' ? ':' + content : content, + ); +} + function removeUnusedGeneratedDeclarations( sourceContent: string, messages: ReadonlyArray,