5
5
* LICENSE file in the root directory of this source tree.
6
6
*/
7
7
8
+ import { codeFrameColumns } from '@babel/code-frame' ;
8
9
import type { SourceLocation } from './HIR' ;
9
10
import { Err , Ok , Result } from './Utils/Result' ;
10
11
import { assertExhaustive } from './Utils/utils' ;
@@ -44,6 +45,24 @@ export enum ErrorSeverity {
44
45
Invariant = 'Invariant' ,
45
46
}
46
47
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
+
47
66
export enum CompilerSuggestionOperation {
48
67
InsertBefore ,
49
68
InsertAfter ,
@@ -74,6 +93,94 @@ export type CompilerErrorDetailOptions = {
74
93
suggestions ?: Array < CompilerSuggestion > | null | undefined ;
75
94
} ;
76
95
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
+
77
184
/*
78
185
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
79
186
* aggregated into a single {@link CompilerError} later.
@@ -101,24 +208,62 @@ export class CompilerErrorDetail {
101
208
return this . options . suggestions ;
102
209
}
103
210
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 ) ] ;
106
217
if ( this . description != null ) {
107
- buffer . push ( `. ${ this . description } ` ) ;
218
+ buffer . push ( `\n\n ${ this . description } . ` ) ;
108
219
}
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' ) ;
111
248
}
112
249
return buffer . join ( '' ) ;
113
250
}
114
251
115
252
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 ( '' ) ;
117
262
}
118
263
}
119
264
120
265
export class CompilerError extends Error {
121
- details : Array < CompilerErrorDetail > = [ ] ;
266
+ details : Array < CompilerErrorDetail | CompilerDiagnostic > = [ ] ;
122
267
123
268
static invariant (
124
269
condition : unknown ,
@@ -136,6 +281,12 @@ export class CompilerError extends Error {
136
281
}
137
282
}
138
283
284
+ static throwDiagnostic ( options : CompilerDiagnosticOptions ) : never {
285
+ const errors = new CompilerError ( ) ;
286
+ errors . pushDiagnostic ( new CompilerDiagnostic ( options ) ) ;
287
+ throw errors ;
288
+ }
289
+
139
290
static throwTodo (
140
291
options : Omit < CompilerErrorDetailOptions , 'severity' > ,
141
292
) : never {
@@ -210,6 +361,21 @@ export class CompilerError extends Error {
210
361
return this . name ;
211
362
}
212
363
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
+
213
379
push ( options : CompilerErrorDetailOptions ) : CompilerErrorDetail {
214
380
const detail = new CompilerErrorDetail ( {
215
381
reason : options . reason ,
@@ -260,3 +426,32 @@ export class CompilerError extends Error {
260
426
} ) ;
261
427
}
262
428
}
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
+ }
0 commit comments