From e4e03bd9092d421e2ce71b09a00db517a850f532 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 18 Feb 2025 07:37:57 -0800 Subject: [PATCH] Trace the import path that leads to an untranslatable file (#49480) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49480 Changelog: [Internal] Reviewed By: huntie Differential Revision: D69752685 fbshipit-source-id: 94f70eeaec35c864c15b463f3afce929765f2c40 --- scripts/build/build-types/buildTypes.js | 27 +++++++--- .../build-types/resolution/getRequireStack.js | 51 +++++++++++++++++++ 2 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 scripts/build/build-types/resolution/getRequireStack.js diff --git a/scripts/build/build-types/buildTypes.js b/scripts/build/build-types/buildTypes.js index 53e6ff99a4b..de463c4b426 100644 --- a/scripts/build/build-types/buildTypes.js +++ b/scripts/build/build-types/buildTypes.js @@ -10,6 +10,7 @@ */ const {PACKAGES_DIR, REPO_ROOT} = require('../../consts'); +const getRequireStack = require('./resolution/getRequireStack'); const translatedModuleTemplate = require('./templates/translatedModule.d.ts-template'); const translateSourceFile = require('./translateSourceFile'); const debug = require('debug')('build-types:main'); @@ -64,14 +65,16 @@ async function buildTypes(): Promise { ENTRY_POINTS.map(file => path.join(REPO_ROOT, file)), ); const translatedFiles = new Set(); + const dependencyEdges: DependencyEdges = []; while (files.size > 0) { - const dependencies = await translateSourceFiles(files); + const dependencies = await translateSourceFiles(dependencyEdges, files); + dependencyEdges.push(...dependencies); files.forEach(file => translatedFiles.add(file)); files.clear(); - for (const dep of dependencies) { + for (const [, dep] of dependencies) { if ( !translatedFiles.has(dep) && !IGNORE_PATTERNS.some(pattern => micromatch.isMatch(dep, pattern)) @@ -80,13 +83,14 @@ async function buildTypes(): Promise { } } } - - await translateSourceFiles(files); } +type DependencyEdges = Array<[string, string]>; + async function translateSourceFiles( - inputFiles: $ReadOnlySet, -): Promise> { + dependencyEdges: DependencyEdges, + inputFiles: Iterable, +): Promise { const files = new Set([...inputFiles]); // Require common interface file (js.flow) or base implementation (.js) for @@ -128,7 +132,7 @@ async function translateSourceFiles( } } - const dependencies = new Set(); + const dependencies: DependencyEdges = []; await Promise.all( Array.from(files).map(async file => { @@ -140,7 +144,7 @@ async function translateSourceFiles( await translateSourceFile(source, file); for (const dep of fileDeps) { - dependencies.add(dep); + dependencies.push([file, dep]); } await fs.mkdir(path.dirname(buildPath), {recursive: true}); @@ -153,6 +157,13 @@ async function translateSourceFiles( ); } catch (e) { 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:'); + for (const stackEntry of requireStack) { + console.error(`- ${stackEntry}`); + } + } } }), ); diff --git a/scripts/build/build-types/resolution/getRequireStack.js b/scripts/build/build-types/resolution/getRequireStack.js new file mode 100644 index 00000000000..c45a912355f --- /dev/null +++ b/scripts/build/build-types/resolution/getRequireStack.js @@ -0,0 +1,51 @@ +/** + * 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 + */ + +type DependencyEdges = Array<[string, string]>; + +/** + * Given a containing dependency graph and an input file, get the first + * complete path found when traversing dependant module edges up to the root + * entry file. Any require cycles will be pruned. + * + * Time complexity: O(edges^2) + * Space complexity: O(edges) + */ +function getRequireStack(edges: DependencyEdges, file: string): Array { + const requireStack = new Set(); + const cycleRoots = new Set(); + let currentTarget = file; + + while (true) { + const edge = edges.find( + ([, targetFile]) => + targetFile === currentTarget && !cycleRoots.has(targetFile), + ); + + if (edge == null) { + break; + } + + const [sourceFile] = edge; + + if (requireStack.has(sourceFile)) { + requireStack.clear(); + cycleRoots.add(sourceFile); + } + + requireStack.add(sourceFile); + currentTarget = sourceFile; + } + + return Array.from(requireStack); +} + +module.exports = getRequireStack;