From e48037546464cd468cfdb2d1dc76984db34f5276 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 26 Jun 2026 10:12:04 -0500 Subject: [PATCH] chore: report React Compiler migration errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- script/react-compiler-migration-status.mts | 164 +++++++++++++++++++-- 1 file changed, 153 insertions(+), 11 deletions(-) diff --git a/script/react-compiler-migration-status.mts b/script/react-compiler-migration-status.mts index cd6b866fa13..cb937afdd38 100644 --- a/script/react-compiler-migration-status.mts +++ b/script/react-compiler-migration-status.mts @@ -1,9 +1,35 @@ import path from 'node:path' import fs from 'node:fs' +import {transformFileSync} from '@babel/core' +import type {TransformOptions} from '@babel/core' +import {CompilerError} from 'babel-plugin-react-compiler' +import type {PluginOptions, Logger, LoggerEvent} from 'babel-plugin-react-compiler' import {files as compilerFiles, notMigrated as notMigratedFiles} from '../packages/react/script/react-compiler.mjs' const directory = path.resolve(import.meta.dirname, '..') -const files = compilerFiles.map(filepath => { +interface CompilerFile { + readonly filepath: string + readonly size: number +} + +interface CompilerFailure { + readonly line: number | null + readonly reason: string +} +type CompilerErrorDetail = CompilerError['details'][number] + +type BabelReactPresetOptions = { + readonly modules: false + readonly runtime: 'automatic' +} +type BabelTransformPlugin = [string, PluginOptions | Record] | string +type BabelTransformPreset = [string, BabelReactPresetOptions] | string +interface BabelTransformOptions extends TransformOptions { + readonly presets: Array + readonly plugins: Array +} + +const files: Array = compilerFiles.map(filepath => { const stats = fs.statSync(filepath) return { filepath, @@ -20,6 +46,12 @@ const notMigrated = notMigratedFiles.map(filepath => { size: stats.size, } }) +const notMigratedReports = notMigrated.map(file => { + return { + ...file, + failures: getCompilerFailures(file.filepath), + } +}) let totalSize = 0 @@ -33,7 +65,7 @@ for (const {size} of migrated) { migratedSize += size } -console.log(` +write(` # React Compiler Migration This report tracks our status migrating Primer React files to work with React Compiler. @@ -49,19 +81,19 @@ This report tracks our status migrating Primer React files to work with React Co ![Status by file size](https://geps.dev/progress/${Math.floor((migratedSize / totalSize) * 100)}) `) -console.log(` -## Not Migrated (${notMigrated.length}) +write(` +## Not Migrated (${notMigratedReports.length}) -| Filepath | Size (kB) | -| :------- | :-------- |`) +| Filepath | Size (kB) | Compiler errors | +| :------- | :-------- | :-------------- |`) -for (const {filepath, size} of notMigrated) { +for (const {filepath, size, failures} of notMigratedReports) { const relativePath = path.relative(directory, filepath) const link = `[\`${relativePath}\`](https://github.com/primer/react/blob/main/${relativePath})` - console.log(`| ${link} | ${round(size / 1024)}kB |`) + write(`| ${link} | ${round(size / 1024)}kB | ${formatCompilerFailures(failures)} |`) } -console.log(`## Migrated (${migrated.length}) +write(`## Migrated (${migrated.length})
View migrated files @@ -72,11 +104,121 @@ console.log(`## Migrated (${migrated.length}) for (const {filepath, size} of migrated) { const relativePath = path.relative(directory, filepath) const link = `[\`${relativePath}\`](https://github.com/primer/react/blob/main/${relativePath})` - console.log(`| ${link} | ${round(size / 1024)}kB |`) + write(`| ${link} | ${round(size / 1024)}kB |`) } -console.log(`\n
`) +write(`\n`) + +function write(value: string): void { + process.stdout.write(`${value}\n`) +} function round(value: number): number { return Math.round((value + Number.EPSILON) * 100) / 100 } + +function getCompilerFailures(filepath: string): Array { + const failures: Array = [] + const logger: Logger = { + logEvent(_filename: string | null, event: LoggerEvent) { + if (event.kind === 'CompileError') { + addCompilerFailure(failures, formatCompilerError(event)) + } + + if (event.kind === 'CompileSkip') { + addCompilerFailure(failures, { + line: getLocationLine(event.loc ?? event.fnLoc), + reason: event.reason, + }) + } + }, + } + + try { + transformFileSync(filepath, getBabelTransformOptions(logger)) + } catch (error: unknown) { + if (!(error instanceof CompilerError)) { + throw error + } + + for (const detail of error.details) { + addCompilerFailure(failures, formatCompilerErrorDetail(detail)) + } + } + + return failures +} + +function getBabelTransformOptions(logger: Logger): BabelTransformOptions { + return { + babelrc: false, + configFile: false, + presets: [ + '@babel/preset-typescript', + [ + '@babel/preset-react', + { + modules: false, + runtime: 'automatic', + }, + ], + ], + plugins: [ + [ + 'babel-plugin-react-compiler', + { + target: '18', + panicThreshold: 'all_errors', + logger, + }, + ], + 'macros', + 'add-react-displayname', + 'dev-expression', + '@babel/plugin-proposal-nullish-coalescing-operator', + '@babel/plugin-proposal-optional-chaining', + ], + } +} + +function formatCompilerError(event: Extract): CompilerFailure { + return formatCompilerErrorDetail(event.detail) +} + +function formatCompilerErrorDetail(detail: CompilerErrorDetail): CompilerFailure { + return { + line: getLocationLine(detail.primaryLocation()), + reason: detail.reason, + } +} + +function addCompilerFailure(failures: Array, failure: CompilerFailure): void { + const hasFailure = failures.some(existingFailure => { + return existingFailure.line === failure.line && existingFailure.reason === failure.reason + }) + + if (!hasFailure) { + failures.push(failure) + } +} + +function getLocationLine(location: {start: {line: number}} | null): number | null { + return location?.start.line ?? null +} + +function formatCompilerFailures(failures: Array): string { + if (failures.length === 0) { + return 'No compiler errors reported' + } + + return failures.map(formatCompilerFailure).join('; ') +} + +function formatCompilerFailure(failure: CompilerFailure): string { + const location = failure.line === null ? '' : `L${failure.line}: ` + return escapeTableCell(`${location}${failure.reason}`) +} + +function escapeTableCell(value: string): string { + return value.replaceAll('|', '\\|').replaceAll('\n', ' ') +}