[compiler] Use new diagnostic printing in playground (#33767)
Per title --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33767). * #33981 * #33777 * __->__ #33767
Joseph Savona committed
Jul 24, 2025 at 15:47 UTC
2ae8b3dacf2cd93900d86bc11f22768d507ddce7
6 files changed
+72
-34
compiler/apps/playground/components/Editor/EditorImpl.tsx
+5
-4
@@ -11,6 +11,7 @@ import * as t from '@babel/types';
11
import BabelPluginReactCompiler, {
12
CompilerError,
13
CompilerErrorDetail,
14
+ CompilerDiagnostic,
15
Effect,
16
ErrorSeverity,
17
parseConfigPragmaForTests,
@@ -144,7 +145,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
145
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
146
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
147
const error = new CompilerError();
147
- const otherErrors: Array<CompilerErrorDetail> = [];
148
+ const otherErrors: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
149
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
150
const entry = results.get(result.name);
151
if (Array.isArray(entry)) {
@@ -214,7 +215,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
215
debugLogIRs: logIR,
216
logEvent: (_filename: string | null, event: LoggerEvent) => {
217
if (event.kind === 'CompileError') {
217
- otherErrors.push(new CompilerErrorDetail(event.detail));
218
+ otherErrors.push(event.detail);
219
}
220
},
221
},
@@ -226,7 +227,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
227
* (i.e. object shape that is not CompilerError)
228
*/
229
if (err instanceof CompilerError && err.details.length > 0) {
229
- error.details.push(...err.details);
230
+ error.merge(err);
231
} else {
232
/**
233
* Handle unexpected failures by logging (to get a stack trace)
@@ -245,7 +246,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
246
}
247
// Only include logger errors if there weren't other errors
248
if (!error.hasErrors() && otherErrors.length !== 0) {
248
- otherErrors.forEach(e => error.push(e));
249
+ otherErrors.forEach(e => error.details.push(e));
250
}
251
if (error.hasErrors()) {
252
return [{kind: 'err', results, error: error}, language];
compiler/apps/playground/components/Editor/Input.tsx
+7
-2
@@ -36,13 +36,18 @@ export default function Input({errors, language}: Props): JSX.Element {
36
const uri = monaco.Uri.parse(`file:///index.js`);
37
const model = monaco.editor.getModel(uri);
38
invariant(model, 'Model must exist for the selected input file.');
39
- renderReactCompilerMarkers({monaco, model, details: errors});
39
+ renderReactCompilerMarkers({
40
+ monaco,
41
+ model,
42
+ details: errors,
43
+ source: store.source,
44
+ });
45
/**
46
* N.B. that `tabSize` is a model property, not an editor property.
47
* So, the tab size has to be set per model.
48
*/
49
model.updateOptions({tabSize: 2});
45
- }, [monaco, errors]);
50
+ }, [monaco, errors, store.source]);
51
52
useEffect(() => {
53
/**
compiler/apps/playground/components/Editor/Output.tsx
+24
-14
@@ -142,6 +142,17 @@ async function tabify(
142
</>,
143
);
144
}
145
+ } else if (compilerOutput.kind === 'err') {
146
+ const errors = compilerOutput.error.printErrorMessage(source, {
147
+ eslint: false,
148
+ });
149
+ reorderedTabs.set(
150
+ 'Errors',
151
+ <TextTabContent
152
+ output={errors}
153
+ diff={null}
154
+ showInfoPanel={false}></TextTabContent>,
155
+ );
156
}
157
tabs.forEach((tab, name) => {
158
reorderedTabs.set(name, tab);
@@ -166,6 +177,19 @@ function Output({store, compilerOutput}: Props): JSX.Element {
177
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
178
() => new Map(),
179
);
180
+
181
+ /*
182
+ * Update the active tab back to the output or errors tab when the compilation state
183
+ * changes between success/failure.
184
+ */
185
+ const [previousOutputKind, setPreviousOutputKind] = useState(
186
+ compilerOutput.kind,
187
+ );
188
+ if (compilerOutput.kind !== previousOutputKind) {
189
+ setPreviousOutputKind(compilerOutput.kind);
190
+ setTabsOpen(new Set([compilerOutput.kind === 'ok' ? 'JS' : 'Errors']));
191
+ }
192
+
193
useEffect(() => {
194
tabify(store.source, compilerOutput).then(tabs => {
195
setTabs(tabs);
@@ -196,20 +220,6 @@ function Output({store, compilerOutput}: Props): JSX.Element {
220
tabs={tabs}
221
changedPasses={changedPasses}
222
/>
199
- {compilerOutput.kind === 'err' ? (
200
- <div
201
- className="flex flex-wrap absolute bottom-0 bg-white grow border-y border-grey-200 transition-all ease-in"
202
- style={{width: 'calc(100vw - 650px)'}}>
203
- <div className="w-full p-4 basis-full border-b">
204
- <h2>COMPILER ERRORS</h2>
205
- </div>
206
- <pre
207
- className="p-4 basis-full text-red-600 overflow-y-scroll whitespace-pre-wrap"
208
- style={{width: 'calc(100vw - 650px)', height: '150px'}}>
209
- <code>{compilerOutput.error.toString()}</code>
210
- </pre>
211
- </div>
212
- ) : null}
223
</>
224
);
225
}
compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts
+22
-10
@@ -6,7 +6,11 @@
6
*/
7
8
import {Monaco} from '@monaco-editor/react';
9
-import {CompilerErrorDetail, ErrorSeverity} from 'babel-plugin-react-compiler';
9
+import {
10
+ CompilerDiagnostic,
11
+ CompilerErrorDetail,
12
+ ErrorSeverity,
13
+} from 'babel-plugin-react-compiler';
14
import {MarkerSeverity, type editor} from 'monaco-editor';
15
16
function mapReactCompilerSeverityToMonaco(
@@ -22,38 +26,46 @@ function mapReactCompilerSeverityToMonaco(
26
}
27
28
function mapReactCompilerDiagnosticToMonacoMarker(
25
- detail: CompilerErrorDetail,
29
+ detail: CompilerErrorDetail | CompilerDiagnostic,
30
monaco: Monaco,
31
+ source: string,
32
): editor.IMarkerData | null {
28
- if (detail.loc == null || typeof detail.loc === 'symbol') {
33
+ const loc = detail.primaryLocation();
34
+ if (loc == null || typeof loc === 'symbol') {
35
return null;
36
}
37
const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco);
32
- let message = detail.printErrorMessage();
38
+ let message = detail.printErrorMessage(source, {eslint: true});
39
return {
40
severity,
41
message,
36
- startLineNumber: detail.loc.start.line,
37
- startColumn: detail.loc.start.column + 1,
38
- endLineNumber: detail.loc.end.line,
39
- endColumn: detail.loc.end.column + 1,
42
+ startLineNumber: loc.start.line,
43
+ startColumn: loc.start.column + 1,
44
+ endLineNumber: loc.end.line,
45
+ endColumn: loc.end.column + 1,
46
};
47
}
48
49
type ReactCompilerMarkerConfig = {
50
monaco: Monaco;
51
model: editor.ITextModel;
46
- details: Array<CompilerErrorDetail>;
52
+ details: Array<CompilerErrorDetail | CompilerDiagnostic>;
53
+ source: string;
54
};
55
let decorations: Array<string> = [];
56
export function renderReactCompilerMarkers({
57
monaco,
58
model,
59
details,
60
+ source,
61
}: ReactCompilerMarkerConfig): void {
62
const markers: Array<editor.IMarkerData> = [];
63
for (const detail of details) {
56
- const marker = mapReactCompilerDiagnosticToMonacoMarker(detail, monaco);
64
+ const marker = mapReactCompilerDiagnosticToMonacoMarker(
65
+ detail,
66
+ monaco,
67
+ source,
68
+ );
69
if (marker == null) {
70
continue;
71
}
compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts
+1
-3
@@ -84,9 +84,7 @@ export default function BabelPluginReactCompiler(
84
}
85
} catch (e) {
86
if (e instanceof CompilerError) {
87
- throw new Error(
88
- e.printErrorMessage(pass.file.code, {eslint: false}),
89
- );
87
+ throw e.withPrintedMessage(pass.file.code, {eslint: false});
88
}
89
throw e;
90
}
compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
+13
-1
@@ -262,6 +262,7 @@ export class CompilerErrorDetail {
262
263
export class CompilerError extends Error {
264
details: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
265
+ printedMessage: string | null = null;
266
267
static invariant(
268
condition: unknown,
@@ -347,18 +348,29 @@ export class CompilerError extends Error {
348
}
349
350
override get message(): string {
350
- return this.toString();
351
+ return this.printedMessage ?? this.toString();
352
}
353
354
override set message(_message: string) {}
355
356
override toString(): string {
357
+ if (this.printedMessage) {
358
+ return this.printedMessage;
359
+ }
360
if (Array.isArray(this.details)) {
361
return this.details.map(detail => detail.toString()).join('\n\n');
362
}
363
return this.name;
364
}
365
366
+ withPrintedMessage(
367
+ source: string,
368
+ options: PrintErrorMessageOptions,
369
+ ): CompilerError {
370
+ this.printedMessage = this.printErrorMessage(source, options);
371
+ return this;
372
+ }
373
+
374
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
375
if (options.eslint && this.details.length === 1) {
376
return this.details[0].printErrorMessage(source, options);