From bdf22294a69d2f771d1b59f708f835c59ee6c2c5 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 23 Jan 2025 07:16:27 -0800 Subject: [PATCH] Add source transformation pipeline to build-types (#48893) Summary: Updates `build-types` to support source file AST transforms, and templates out an initial `stripPrivateProperties` transform. - Also, parallelise file translation via `Promise.all`. Changelog: [Internal] Reviewed By: iwoplaza Differential Revision: D68558012 --- scripts/build/build-types.js | 59 ++++++++-------- .../transforms/stripPrivateProperties.js | 30 +++++++++ .../build/build-types/translateSourceFile.js | 67 +++++++++++++++++++ 3 files changed, 125 insertions(+), 31 deletions(-) create mode 100644 scripts/build/build-types/transforms/stripPrivateProperties.js create mode 100644 scripts/build/build-types/translateSourceFile.js diff --git a/scripts/build/build-types.js b/scripts/build/build-types.js index e361003d486e..4ef8dc39eab9 100644 --- a/scripts/build/build-types.js +++ b/scripts/build/build-types.js @@ -10,8 +10,8 @@ */ const {PACKAGES_DIR, REPO_ROOT} = require('../consts'); +const translateSourceFile = require('./build-types/translateSourceFile'); const chalk = require('chalk'); -const translate = require('flow-api-translator'); const {promises: fs} = require('fs'); const glob = require('glob'); const micromatch = require('micromatch'); @@ -62,38 +62,35 @@ async function main() { '\n', ); - for (const file of files) { - if (micromatch.isMatch(file, IGNORE_PATTERN)) { - continue; - } - - const buildPath = getBuildPath(file); - const source = await fs.readFile(file, 'utf-8'); - const prettierConfig = {parser: 'babel'}; - - await fs.mkdir(path.dirname(buildPath), {recursive: true}); - - try { - const typescriptDef = await translate.translateFlowToTSDef( - source, - prettierConfig, - ); - - if ( - /Unsupported feature: Translating ".*" is currently not supported/.test( - typescriptDef, - ) - ) { - throw new Error( - 'Syntax unsupported by flow-api-translator used in ' + file, - ); + await Promise.all( + files.map(async file => { + if (micromatch.isMatch(file, IGNORE_PATTERN)) { + return; } - await fs.writeFile(buildPath, typescriptDef); - } catch (e) { - console.error(`Failed to build ${path.relative(REPO_ROOT, file)}`); - } - } + const buildPath = getBuildPath(file); + const source = await fs.readFile(file, 'utf-8'); + await fs.mkdir(path.dirname(buildPath), {recursive: true}); + + try { + const typescriptDef = await translateSourceFile(source); + + if ( + /Unsupported feature: Translating ".*" is currently not supported/.test( + typescriptDef, + ) + ) { + throw new Error( + 'Syntax unsupported by flow-api-translator used in ' + file, + ); + } + + await fs.writeFile(buildPath, typescriptDef); + } catch (e) { + console.error(`Failed to build ${path.relative(REPO_ROOT, file)}`); + } + }), + ); } function getPackageName(file /*: string */) /*: string */ { diff --git a/scripts/build/build-types/transforms/stripPrivateProperties.js b/scripts/build/build-types/transforms/stripPrivateProperties.js new file mode 100644 index 000000000000..e7ceb7e232e7 --- /dev/null +++ b/scripts/build/build-types/transforms/stripPrivateProperties.js @@ -0,0 +1,30 @@ +/** + * 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 + * @oncall react_native + */ + +/*:: +import type {TransformVisitor} from 'hermes-transform'; +import type {TransformASTResult} from 'hermes-transform/dist/transform/transformAST'; +import type {ParseResult} from 'hermes-transform/dist/transform/parse'; + */ + +const {transformAST} = require('hermes-transform/dist/transform/transformAST'); + +const visitors /*: TransformVisitor */ = context => ({ + // TODO +}); + +async function stripPrivateProperties( + source /*: ParseResult */, +) /*: Promise */ { + return transformAST(source, visitors); +} + +module.exports = stripPrivateProperties; diff --git a/scripts/build/build-types/translateSourceFile.js b/scripts/build/build-types/translateSourceFile.js new file mode 100644 index 000000000000..ef1ad061e33a --- /dev/null +++ b/scripts/build/build-types/translateSourceFile.js @@ -0,0 +1,67 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +/*:: +import type {ParseResult} from 'hermes-transform/dist/transform/parse'; +import type {TransformASTResult} from 'hermes-transform/dist/transform/transformAST'; +*/ + +const translate = require('flow-api-translator'); +const {parse} = require('hermes-transform'); + +/*:: +type TransformFn = (ParseResult) => Promise; +*/ + +const preTransforms /*: Array */ = [ + require('./transforms/stripPrivateProperties'), +]; +const prettierOptions = {parser: 'babel'}; + +/** + * Translate the public API of a Flow source file to TypeScript definition. + * + * This uses [flow-api-translator](https://www.npmjs.com/package/flow-api-translator), + * and applies extra transformations such as stripping private properties. + */ +async function translateSourceFile( + source /*: string */, +) /*: Promise */ { + // Parse Flow source + const parsed = await parse(source); + + // Apply pre-transforms + const preTransformResult = await applyTransforms(parsed, preTransforms); + + // Translate to TypeScript defs + return await translate.translateFlowToTSDef( + preTransformResult.code, + prettierOptions, + ); +} + +async function applyTransforms( + source /*: ParseResult */, + transforms /*: $ReadOnlyArray */, +) /*: Promise */ { + return transforms.reduce((input, transform) => { + return input.then(async result => { + const transformed = await transform(result); + return { + ...result, + ast: transformed.ast, + code: transformed.mutatedCode, + }; + }); + }, Promise.resolve(source)); +} + +module.exports = translateSourceFile;