Prefer common interface files when building the type graph (#49494)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49494

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D69786939

fbshipit-source-id: 3b0481e83a528afbdf1d29734d2aea9cad740f58
This commit is contained in:
Iwo Plaza
2025-02-19 06:00:03 -08:00
committed by Facebook GitHub Bot
parent f4927fc03c
commit ee90f2d97f
2 changed files with 82 additions and 52 deletions
+11 -52
View File
@@ -11,10 +11,10 @@
const {PACKAGES_DIR, REPO_ROOT} = require('../../consts');
const getRequireStack = require('./resolution/getRequireStack');
const resolveTypeInputFile = require('./resolution/resolveTypeInputFile');
const translatedModuleTemplate = require('./templates/translatedModule.d.ts-template');
const translateSourceFile = require('./translateSourceFile');
const debug = require('debug')('build-types:main');
const {existsSync, promises: fs} = require('fs');
const {promises: fs} = require('fs');
const micromatch = require('micromatch');
const path = require('path');
@@ -69,6 +69,14 @@ async function buildTypes(): Promise<void> {
const dependencyEdges: DependencyEdges = [];
while (files.size > 0) {
for (const file of files) {
const interfaceFile = resolveTypeInputFile(file);
if (interfaceFile) {
files.delete(file);
translatedFiles.add(file);
files.add(interfaceFile);
}
}
const dependencies = await translateSourceFiles(dependencyEdges, files);
dependencyEdges.push(...dependencies);
@@ -93,46 +101,6 @@ async function translateSourceFiles(
inputFiles: Iterable<string>,
): Promise<DependencyEdges> {
const files = new Set<string>([...inputFiles]);
// Require common interface file (js.flow) or base implementation (.js) for
// platform-specific files (.android.js or .ios.js)
for (const file of files) {
const [pathWithoutExt, extension] = splitPathAndExtension(file);
if (/(\.android\.js|\.ios\.js)$/.test(extension)) {
files.delete(file);
let resolved = false;
for (const ext of ['.js.flow', '.js']) {
let interfaceFile = pathWithoutExt + ext;
if (files.has(interfaceFile)) {
resolved = true;
break;
}
if (existsSync(interfaceFile)) {
files.add(interfaceFile);
resolved = true;
debug(
'Resolved %s to %s',
path.relative(REPO_ROOT, file),
path.relative(REPO_ROOT, interfaceFile),
);
break;
}
}
if (!resolved) {
throw new Error(
`No common interface found for ${file}.[android|ios].js. This ` +
'should either be a base .js implementation or a .js.flow interface file.',
);
}
}
}
const dependencies: DependencyEdges = [];
await Promise.all(
@@ -160,7 +128,7 @@ async function translateSourceFiles(
console.error(`Failed to build ${path.relative(REPO_ROOT, file)}\n`, e);
const requireStack = getRequireStack(dependencyEdges, file);
if (requireStack.length > 0) {
console.error('Chain of imports that led to this file:');
console.error('Require stack:');
for (const stackEntry of requireStack) {
console.error(`- ${stackEntry}`);
}
@@ -188,15 +156,6 @@ function getBuildPath(file: string): string {
);
}
function splitPathAndExtension(file: string): [string, string] {
const lastSep = file.lastIndexOf(path.sep);
const extensionStart = file.indexOf('.', lastSep);
return [
file.substring(0, extensionStart),
file.substring(extensionStart, file.length),
];
}
function stripDocblock(source: string): string {
return source.replace(/\/\*\*[\s\S]*?\*\/\n/, '');
}
@@ -0,0 +1,71 @@
/**
* 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
*/
const {REPO_ROOT} = require('../../../consts');
const debug = require('debug')('build-types:resolution');
const fs = require('fs');
const path = require('path');
const TYPEDEF_MAPPING: Record<string, $ReadOnlyArray<string>> = {
'.android.js': ['.js.flow', '.js'],
'.ios.js': ['.js.flow', '.js'],
'.js': ['.js.flow'],
};
/**
* Resolve the Flow file defining the type interface for a given source file.
*
* Ensures common interface file (js.flow) or base implementation (.js) exists for
* platform-specific files (.android.js or .ios.js).
*/
function resolveTypeInputFile(file: string): string | null {
const [pathWithoutExt, extension] = splitPathAndExtension(file);
const extsToCheck = TYPEDEF_MAPPING[extension];
if (!extsToCheck) {
return null;
}
for (const ext of extsToCheck) {
const interfaceFile = pathWithoutExt + ext;
if (fs.existsSync(interfaceFile)) {
debug(
'Resolved %s to %s',
path.relative(REPO_ROOT, file),
path.relative(REPO_ROOT, interfaceFile),
);
return interfaceFile;
}
}
if (extension === '.js') {
// .js files do not require a common interface
return null;
}
throw new Error(
`No common interface found for ${file}.[android|ios].js. This ` +
'should either be a base .js implementation or a .js.flow interface file.',
);
}
function splitPathAndExtension(file: string): [string, string] {
const lastSep = file.lastIndexOf(path.sep);
const extensionStart = file.indexOf('.', lastSep);
return [
file.substring(0, extensionStart),
file.substring(extensionStart, file.length),
];
}
module.exports = resolveTypeInputFile;