Skip to content

Commit 2649d10

Browse files
committed
[compiler][wip] Improve diagnostic infra
Work in progress, i'm experimenting with revamping our diagnostic infra. Starting with a better format for representing errors, with an ability to point ot multiple locations, along with better printing of errors. Of course, Babel still controls the printing in the majority case so this still needs more work.
1 parent 1517c63 commit 2649d10

File tree

317 files changed

+3560
-626
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

317 files changed

+3560
-626
lines changed

compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
pipelineUsesReanimatedPlugin,
1313
} from '../Entrypoint/Reanimated';
1414
import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences';
15+
import {CompilerError} from '..';
1516

1617
const ENABLE_REACT_COMPILER_TIMINGS =
1718
process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1';
@@ -34,51 +35,58 @@ export default function BabelPluginReactCompiler(
3435
*/
3536
Program: {
3637
enter(prog, pass): void {
37-
const filename = pass.filename ?? 'unknown';
38-
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
39-
performance.mark(`${filename}:start`, {
40-
detail: 'BabelPlugin:Program:start',
41-
});
42-
}
43-
let opts = parsePluginOptions(pass.opts);
44-
const isDev =
45-
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
46-
process.env['NODE_ENV'] === 'development';
47-
if (
48-
opts.enableReanimatedCheck === true &&
49-
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
50-
) {
51-
opts = injectReanimatedFlag(opts);
52-
}
53-
if (
54-
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
55-
isDev
56-
) {
57-
opts = {
58-
...opts,
59-
environment: {
60-
...opts.environment,
61-
enableResetCacheOnSourceFileChanges: true,
62-
},
63-
};
64-
}
65-
const result = compileProgram(prog, {
66-
opts,
67-
filename: pass.filename ?? null,
68-
comments: pass.file.ast.comments ?? [],
69-
code: pass.file.code,
70-
});
71-
validateNoUntransformedReferences(
72-
prog,
73-
pass.filename ?? null,
74-
opts.logger,
75-
opts.environment,
76-
result,
77-
);
78-
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
79-
performance.mark(`${filename}:end`, {
80-
detail: 'BabelPlugin:Program:end',
38+
try {
39+
const filename = pass.filename ?? 'unknown';
40+
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
41+
performance.mark(`${filename}:start`, {
42+
detail: 'BabelPlugin:Program:start',
43+
});
44+
}
45+
let opts = parsePluginOptions(pass.opts);
46+
const isDev =
47+
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
48+
process.env['NODE_ENV'] === 'development';
49+
if (
50+
opts.enableReanimatedCheck === true &&
51+
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
52+
) {
53+
opts = injectReanimatedFlag(opts);
54+
}
55+
if (
56+
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
57+
isDev
58+
) {
59+
opts = {
60+
...opts,
61+
environment: {
62+
...opts.environment,
63+
enableResetCacheOnSourceFileChanges: true,
64+
},
65+
};
66+
}
67+
const result = compileProgram(prog, {
68+
opts,
69+
filename: pass.filename ?? null,
70+
comments: pass.file.ast.comments ?? [],
71+
code: pass.file.code,
8172
});
73+
validateNoUntransformedReferences(
74+
prog,
75+
pass.filename ?? null,
76+
opts.logger,
77+
opts.environment,
78+
result,
79+
);
80+
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
81+
performance.mark(`${filename}:end`, {
82+
detail: 'BabelPlugin:Program:end',
83+
});
84+
}
85+
} catch (e) {
86+
if (e instanceof CompilerError) {
87+
throw new Error(e.printErrorMessage(pass.file.code));
88+
}
89+
throw e;
8290
}
8391
},
8492
exit(_, pass): void {

compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts

Lines changed: 202 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import {codeFrameColumns} from '@babel/code-frame';
89
import type {SourceLocation} from './HIR';
910
import {Err, Ok, Result} from './Utils/Result';
1011
import {assertExhaustive} from './Utils/utils';
@@ -44,6 +45,24 @@ export enum ErrorSeverity {
4445
Invariant = 'Invariant',
4546
}
4647

48+
export type CompilerDiagnosticOptions = {
49+
severity: ErrorSeverity;
50+
category: string;
51+
description: string;
52+
details: Array<CompilerDiagnosticDetail>;
53+
suggestions?: Array<CompilerSuggestion> | null | undefined;
54+
};
55+
56+
export type CompilerDiagnosticDetail =
57+
/**
58+
* A/the source of the error
59+
*/
60+
{
61+
kind: 'error';
62+
loc: SourceLocation;
63+
message: string;
64+
};
65+
4766
export enum CompilerSuggestionOperation {
4867
InsertBefore,
4968
InsertAfter,
@@ -74,6 +93,94 @@ export type CompilerErrorDetailOptions = {
7493
suggestions?: Array<CompilerSuggestion> | null | undefined;
7594
};
7695

96+
export class CompilerDiagnostic {
97+
options: CompilerDiagnosticOptions;
98+
99+
constructor(options: CompilerDiagnosticOptions) {
100+
this.options = options;
101+
}
102+
103+
get category(): CompilerDiagnosticOptions['category'] {
104+
return this.options.category;
105+
}
106+
get description(): CompilerDiagnosticOptions['description'] {
107+
return this.options.description;
108+
}
109+
get severity(): CompilerDiagnosticOptions['severity'] {
110+
return this.options.severity;
111+
}
112+
get suggestions(): CompilerDiagnosticOptions['suggestions'] {
113+
return this.options.suggestions;
114+
}
115+
116+
primaryLocation(): SourceLocation | null {
117+
return this.options.details.filter(d => d.kind === 'error')[0]?.loc ?? null;
118+
}
119+
120+
printErrorMessage(source: string): string {
121+
const buffer = [
122+
printErrorSummary(this.severity, this.category),
123+
'\n\n',
124+
this.description,
125+
];
126+
for (const detail of this.options.details) {
127+
switch (detail.kind) {
128+
case 'error': {
129+
const loc = detail.loc;
130+
if (typeof loc === 'symbol') {
131+
continue;
132+
}
133+
let codeFrame: string;
134+
try {
135+
codeFrame = codeFrameColumns(
136+
source,
137+
{
138+
start: {
139+
line: loc.start.line,
140+
column: loc.start.column + 1,
141+
},
142+
end: {
143+
line: loc.end.line,
144+
column: loc.end.column + 1,
145+
},
146+
},
147+
{
148+
message: detail.message,
149+
},
150+
);
151+
} catch (e) {
152+
codeFrame = detail.message;
153+
}
154+
buffer.push(
155+
`\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`,
156+
);
157+
buffer.push(codeFrame);
158+
break;
159+
}
160+
default: {
161+
assertExhaustive(
162+
detail.kind,
163+
`Unexpected detail kind ${(detail as any).kind}`,
164+
);
165+
}
166+
}
167+
}
168+
return buffer.join('');
169+
}
170+
171+
toString(): string {
172+
const buffer = [printErrorSummary(this.severity, this.category)];
173+
if (this.description != null) {
174+
buffer.push(`. ${this.description}.`);
175+
}
176+
const loc = this.primaryLocation();
177+
if (loc != null && typeof loc !== 'symbol') {
178+
buffer.push(` (${loc.start.line}:${loc.start.column})`);
179+
}
180+
return buffer.join('');
181+
}
182+
}
183+
77184
/*
78185
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
79186
* aggregated into a single {@link CompilerError} later.
@@ -101,24 +208,62 @@ export class CompilerErrorDetail {
101208
return this.options.suggestions;
102209
}
103210

104-
printErrorMessage(): string {
105-
const buffer = [`${this.severity}: ${this.reason}`];
211+
primaryLocation(): SourceLocation | null {
212+
return this.loc;
213+
}
214+
215+
printErrorMessage(source: string): string {
216+
const buffer = [printErrorSummary(this.severity, this.reason)];
106217
if (this.description != null) {
107-
buffer.push(`. ${this.description}`);
218+
buffer.push(`\n\n${this.description}.`);
108219
}
109-
if (this.loc != null && typeof this.loc !== 'symbol') {
110-
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
220+
const loc = this.loc;
221+
if (loc != null && typeof loc !== 'symbol') {
222+
let codeFrame: string;
223+
try {
224+
codeFrame = codeFrameColumns(
225+
source,
226+
{
227+
start: {
228+
line: loc.start.line,
229+
column: loc.start.column + 1,
230+
},
231+
end: {
232+
line: loc.end.line,
233+
column: loc.end.column + 1,
234+
},
235+
},
236+
{
237+
message: this.reason,
238+
},
239+
);
240+
} catch (e) {
241+
codeFrame = '';
242+
}
243+
buffer.push(
244+
`\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`,
245+
);
246+
buffer.push(codeFrame);
247+
buffer.push('\n\n');
111248
}
112249
return buffer.join('');
113250
}
114251

115252
toString(): string {
116-
return this.printErrorMessage();
253+
const buffer = [printErrorSummary(this.severity, this.reason)];
254+
if (this.description != null) {
255+
buffer.push(`. ${this.description}.`);
256+
}
257+
const loc = this.loc;
258+
if (loc != null && typeof loc !== 'symbol') {
259+
buffer.push(` (${loc.start.line}:${loc.start.column})`);
260+
}
261+
return buffer.join('');
117262
}
118263
}
119264

120265
export class CompilerError extends Error {
121-
details: Array<CompilerErrorDetail> = [];
266+
details: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
122267

123268
static invariant(
124269
condition: unknown,
@@ -136,6 +281,12 @@ export class CompilerError extends Error {
136281
}
137282
}
138283

284+
static throwDiagnostic(options: CompilerDiagnosticOptions): never {
285+
const errors = new CompilerError();
286+
errors.pushDiagnostic(new CompilerDiagnostic(options));
287+
throw errors;
288+
}
289+
139290
static throwTodo(
140291
options: Omit<CompilerErrorDetailOptions, 'severity'>,
141292
): never {
@@ -210,6 +361,21 @@ export class CompilerError extends Error {
210361
return this.name;
211362
}
212363

364+
printErrorMessage(source: string): string {
365+
return (
366+
`Found ${this.details.length} error${this.details.length === 1 ? '' : 's'}:\n` +
367+
this.details.map(detail => detail.printErrorMessage(source)).join('\n')
368+
);
369+
}
370+
371+
merge(other: CompilerError): void {
372+
this.details.push(...other.details);
373+
}
374+
375+
pushDiagnostic(diagnostic: CompilerDiagnostic): void {
376+
this.details.push(diagnostic);
377+
}
378+
213379
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
214380
const detail = new CompilerErrorDetail({
215381
reason: options.reason,
@@ -260,3 +426,32 @@ export class CompilerError extends Error {
260426
});
261427
}
262428
}
429+
430+
function printErrorSummary(severity: ErrorSeverity, message: string): string {
431+
let severityCategory: string;
432+
switch (severity) {
433+
case ErrorSeverity.InvalidConfig:
434+
case ErrorSeverity.InvalidJS:
435+
case ErrorSeverity.InvalidReact:
436+
case ErrorSeverity.UnsupportedJS: {
437+
severityCategory = 'Error';
438+
break;
439+
}
440+
case ErrorSeverity.CannotPreserveMemoization: {
441+
severityCategory = 'Memoization';
442+
break;
443+
}
444+
case ErrorSeverity.Invariant: {
445+
severityCategory = 'Invariant';
446+
break;
447+
}
448+
case ErrorSeverity.Todo: {
449+
severityCategory = 'Todo';
450+
break;
451+
}
452+
default: {
453+
assertExhaustive(severity, `Unexpected severity '${severity}'`);
454+
}
455+
}
456+
return `${severityCategory}: ${message}`;
457+
}

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88
import * as t from '@babel/types';
99
import {z} from 'zod';
10-
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
10+
import {
11+
CompilerDiagnostic,
12+
CompilerError,
13+
CompilerErrorDetail,
14+
CompilerErrorDetailOptions,
15+
} from '../CompilerError';
1116
import {
1217
EnvironmentConfig,
1318
ExternalFunction,
@@ -224,7 +229,7 @@ export type LoggerEvent =
224229
export type CompileErrorEvent = {
225230
kind: 'CompileError';
226231
fnLoc: t.SourceLocation | null;
227-
detail: CompilerErrorDetailOptions;
232+
detail: CompilerErrorDetail | CompilerDiagnostic;
228233
};
229234
export type CompileDiagnosticEvent = {
230235
kind: 'CompileDiagnostic';

0 commit comments

Comments
 (0)