@samitouri / QOS-React / commits / 2ae8b3dacf

[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);