55 * LICENSE file in the root directory of this source tree.
66 */
77
8+ import { codeFrameColumns } from '@babel/code-frame' ;
89import type { SourceLocation } from './HIR' ;
910import { Err , Ok , Result } from './Utils/Result' ;
1011import { 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+
4766export 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
120265export 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+ }
0 commit comments