From ece8ca82cd03460ba7ceda242e89bc71ba195e15 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 25 Jun 2025 02:55:42 -0700 Subject: [PATCH] Update JS API snapshot to group exports in single block (#52235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52235 Adds `organizeDeclarations` transform, replacing `sortTypeDefinitions`. All `export declare ...` statements are now collected and represented at the end of the snapshot in a single `export {}` block — significantly improving readability and diffing. Changelog: [Internal] Reviewed By: j-piasecki Differential Revision: D77150017 fbshipit-source-id: 1bd451c0e2a18fd6fc0504970b10a5d2502ac872 --- scripts/build-types/BuildApiSnapshot.js | 2 +- ...nitions.d.ts => organizeDeclarations.d.ts} | 0 ...snap => organizeDeclarations-test.js.snap} | 19 ++-- ...s-test.js => organizeDeclarations-test.js} | 10 +- .../typescript/organizeDeclarations.js | 94 +++++++++++++++++++ .../typescript/sortTypeDefinitions.js | 42 --------- 6 files changed, 110 insertions(+), 57 deletions(-) rename scripts/build-types/transforms/typescript/__fixtures__/{sortTypeDefinitions.d.ts => organizeDeclarations.d.ts} (100%) rename scripts/build-types/transforms/typescript/__tests__/__snapshots__/{sortTypeDefinitions-test.js.snap => organizeDeclarations-test.js.snap} (53%) rename scripts/build-types/transforms/typescript/__tests__/{sortTypeDefinitions-test.js => organizeDeclarations-test.js} (63%) create mode 100644 scripts/build-types/transforms/typescript/organizeDeclarations.js delete mode 100644 scripts/build-types/transforms/typescript/sortTypeDefinitions.js diff --git a/scripts/build-types/BuildApiSnapshot.js b/scripts/build-types/BuildApiSnapshot.js index 92d14a68ab6..4393ebe3acb 100644 --- a/scripts/build-types/BuildApiSnapshot.js +++ b/scripts/build-types/BuildApiSnapshot.js @@ -36,10 +36,10 @@ const inputFilesPostTransforms: $ReadOnlyArray> = [ const postTransforms: $ReadOnlyArray> = [ require('./transforms/typescript/stripUnstableApis'), - require('./transforms/typescript/sortTypeDefinitions'), require('./transforms/typescript/sortProperties'), require('./transforms/typescript/sortUnions'), require('./transforms/removeUndefinedFromOptionalMembers'), + require('./transforms/typescript/organizeDeclarations'), ]; async function buildAPISnapshot(validate: boolean) { diff --git a/scripts/build-types/transforms/typescript/__fixtures__/sortTypeDefinitions.d.ts b/scripts/build-types/transforms/typescript/__fixtures__/organizeDeclarations.d.ts similarity index 100% rename from scripts/build-types/transforms/typescript/__fixtures__/sortTypeDefinitions.d.ts rename to scripts/build-types/transforms/typescript/__fixtures__/organizeDeclarations.d.ts diff --git a/scripts/build-types/transforms/typescript/__tests__/__snapshots__/sortTypeDefinitions-test.js.snap b/scripts/build-types/transforms/typescript/__tests__/__snapshots__/organizeDeclarations-test.js.snap similarity index 53% rename from scripts/build-types/transforms/typescript/__tests__/__snapshots__/sortTypeDefinitions-test.js.snap rename to scripts/build-types/transforms/typescript/__tests__/__snapshots__/organizeDeclarations-test.js.snap index 06c12f9f1f8..93781a8a908 100644 --- a/scripts/build-types/transforms/typescript/__tests__/__snapshots__/sortTypeDefinitions-test.js.snap +++ b/scripts/build-types/transforms/typescript/__tests__/__snapshots__/organizeDeclarations-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`sortTypeDefinitions should divide top-lelvel declarations into exported and non-exported sections 1`] = ` +exports[`organizeDeclarations should sort declarations and move exports into single export block 1`] = ` "/** * Copyright (c) Meta Platforms, Inc. and affiliates. * @@ -9,16 +9,17 @@ exports[`sortTypeDefinitions should divide top-lelvel declarations into exported */ import * as React from \\"react\\"; -export declare type ExportedFoo = \\"A\\" | \\"B\\"; -export declare type ExportedBar = null | string | number; -export declare class ExportedBaz { - foo: string; -} -export declare function exportedFn(): void; -declare type Foo = \\"A\\" | \\"B\\"; declare type Bar = null | string | number; declare class Baz { foo: string; } -declare function fn(): void;" +declare type ExportedBar = null | string | number; +declare class ExportedBaz { + foo: string; +} +declare function exportedFn(): void; +declare type ExportedFoo = \\"A\\" | \\"B\\"; +declare function fn(): void; +declare type Foo = \\"A\\" | \\"B\\"; +export { ExportedBar, ExportedBaz, ExportedFoo, exportedFn };" `; diff --git a/scripts/build-types/transforms/typescript/__tests__/sortTypeDefinitions-test.js b/scripts/build-types/transforms/typescript/__tests__/organizeDeclarations-test.js similarity index 63% rename from scripts/build-types/transforms/typescript/__tests__/sortTypeDefinitions-test.js rename to scripts/build-types/transforms/typescript/__tests__/organizeDeclarations-test.js index 4bb40ed3628..ae9d9b07e70 100644 --- a/scripts/build-types/transforms/typescript/__tests__/sortTypeDefinitions-test.js +++ b/scripts/build-types/transforms/typescript/__tests__/organizeDeclarations-test.js @@ -8,23 +8,23 @@ * @format */ -const sortTypeDefinitionsVisitor = require('../sortTypeDefinitions'); +const organizeDeclarations = require('../organizeDeclarations'); const babel = require('@babel/core'); const {promises: fs} = require('fs'); const path = require('path'); async function translate(code: string): Promise { const result = await babel.transformAsync(code, { - plugins: ['@babel/plugin-syntax-typescript', sortTypeDefinitionsVisitor], + plugins: ['@babel/plugin-syntax-typescript', organizeDeclarations], }); return result.code; } -describe('sortTypeDefinitions', () => { - test('should divide top-lelvel declarations into exported and non-exported sections', async () => { +describe('organizeDeclarations', () => { + test('should sort declarations and move exports into single export block', async () => { const code = await fs.readFile( - path.join(__dirname, '../__fixtures__/sortTypeDefinitions.d.ts'), + path.join(__dirname, '../__fixtures__/organizeDeclarations.d.ts'), 'utf-8', ); const result = await translate(code); diff --git a/scripts/build-types/transforms/typescript/organizeDeclarations.js b/scripts/build-types/transforms/typescript/organizeDeclarations.js new file mode 100644 index 00000000000..561e1d469af --- /dev/null +++ b/scripts/build-types/transforms/typescript/organizeDeclarations.js @@ -0,0 +1,94 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +import type {PluginObj} from '@babel/core'; + +import * as t from '@babel/types'; + +/** + * A visitor that organizes top level type declarations into our desired API + * snapshot format. + * + * - Sorts declarations alphabetically. + * - Moves all exports into a single block at the end of the file. + */ +const visitor: PluginObj = { + visitor: { + Program(path) { + const exportedIdentifiers: Set = new Set(); + + // Collect exported identifiers + path.get('body').forEach(nodePath => { + if (nodePath.isExportNamedDeclaration()) { + if (nodePath.node.declaration) { + const declaration = nodePath.node.declaration; + if ( + t.isTSInterfaceDeclaration(declaration) || + t.isTSTypeAliasDeclaration(declaration) || + t.isFunctionDeclaration(declaration) || + t.isClassDeclaration(declaration) || + t.isVariableDeclaration(declaration) || + t.isTSDeclareFunction(declaration) || + t.isTSModuleDeclaration(declaration) || + t.isTSEnumDeclaration(declaration) + ) { + if (declaration.id && declaration.id.name != null) { + exportedIdentifiers.add(declaration.id.name); + } else if ( + t.isVariableDeclaration(declaration) && + declaration.declarations.length > 0 + ) { + declaration.declarations.forEach(declarator => { + if (t.isIdentifier(declarator.id)) { + exportedIdentifiers.add(declarator.id.name); + } + }); + } + nodePath.replaceWith(declaration); // Remove export + } else { + throw new Error( + `Unexpected declaration type for top-level export: ${declaration.type}`, + ); + } + } else if (nodePath.node.specifiers) { + nodePath.node.specifiers.forEach(specifier => { + if (specifier.type === 'ExportSpecifier') { + exportedIdentifiers.add(specifier.local.name); + } + }); + nodePath.remove(); // Remove export statement + } + } + }); + + // Sort declarations alphabetically + path.node.body.sort((a, b) => { + const aName = a.id?.name || ''; + const bName = b.id?.name || ''; + return aName.localeCompare(bName); + }); + + // Move all exports into single `export {}` block + if (exportedIdentifiers.size > 0) { + const sortedIdentifiers = Array.from(exportedIdentifiers).sort(); + const exportStatement = t.exportNamedDeclaration( + // $FlowIgnore[incompatible-call] + null, + sortedIdentifiers.map(name => + t.exportSpecifier(t.identifier(name), t.identifier(name)), + ), + ); + path.pushContainer('body', exportStatement); + } + }, + }, +}; + +module.exports = visitor; diff --git a/scripts/build-types/transforms/typescript/sortTypeDefinitions.js b/scripts/build-types/transforms/typescript/sortTypeDefinitions.js deleted file mode 100644 index f0e9d142daf..00000000000 --- a/scripts/build-types/transforms/typescript/sortTypeDefinitions.js +++ /dev/null @@ -1,42 +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. - * - * @flow strict-local - * @format - */ - -import type {PluginObj} from '@babel/core'; - -import * as t from '@babel/types'; - -const visitor: PluginObj = { - visitor: { - Program(path) { - path.node.body.sort((a, b) => { - // Push import declarations at the top of the file - if (t.isImportDeclaration(a) && !t.isImportDeclaration(b)) { - return -1; - } else if (!t.isImportDeclaration(a) && t.isImportDeclaration(b)) { - return 1; - } - - const aExported = t.isExportNamedDeclaration(a); - const bExported = t.isExportNamedDeclaration(b); - - // Push exported declarations before non-exported declarations - if (aExported && !bExported) { - return -1; - } else if (!aExported && bExported) { - return 1; - } - - return 0; - }); - }, - }, -}; - -module.exports = visitor;