@@ -27,9 +27,21 @@ export type IgnoreCheck = (
2727 frame : DevTools . DevTools . StackTrace . StackTrace . Frame ,
2828) => boolean ;
2929
30- export class ConsoleFormatter {
31- static readonly #STACK_TRACE_MAX_LINES = 50 ;
30+ interface ConsoleMessageConcise {
31+ type : string ;
32+ text : string ;
33+ argsCount : number ;
34+ id : number ;
35+ }
36+
37+ interface ConsoleMessageDetailed extends ConsoleMessageConcise {
38+ // pre-formatted args.
39+ args : string [ ] ;
40+ // pre-formatted stacktrace.
41+ stackTrace ?: string ;
42+ }
3243
44+ export class ConsoleFormatter {
3345 readonly #id: number ;
3446 readonly #type: string ;
3547 readonly #text: string ;
@@ -40,7 +52,7 @@ export class ConsoleFormatter {
4052 readonly #stack?: DevTools . DevTools . StackTrace . StackTrace . StackTrace ;
4153 readonly #cause?: SymbolizedError ;
4254
43- readonly # isIgnored: IgnoreCheck ;
55+ readonly isIgnored : IgnoreCheck ;
4456
4557 private constructor ( params : {
4658 id : number ;
@@ -59,7 +71,7 @@ export class ConsoleFormatter {
5971 this . #resolvedArgs = params . resolvedArgs ?? [ ] ;
6072 this . #stack = params . stack ;
6173 this . #cause = params . cause ;
62- this . # isIgnored = params . isIgnored ;
74+ this . isIgnored = params . isIgnored ;
6375 }
6476
6577 static async from (
@@ -160,20 +172,12 @@ export class ConsoleFormatter {
160172
161173 // The short format for a console message.
162174 toString ( ) : string {
163- return `msgid= ${ this . #id } [ ${ this . #type } ] ${ this . #text } ( ${ this . #argCount } args)` ;
175+ return convertConsoleMessageConciseToString ( this . toJSON ( ) ) ;
164176 }
165177
166178 // The verbose format for a console message, including all details.
167179 toStringDetailed ( ) : string {
168- const result = [
169- `ID: ${ this . #id} ` ,
170- `Message: ${ this . #type} > ${ this . #text} ` ,
171- this . #formatArgs( ) ,
172- this . #formatStackTrace( this . #stack, this . #cause, {
173- includeHeading : true ,
174- } ) ,
175- ] . filter ( line => ! ! line ) ;
176- return result . join ( '\n' ) ;
180+ return convertConsoleMessageConciseDetailedToString ( this . toJSONDetailed ( ) ) ;
177181 }
178182
179183 #getArgs( ) : unknown [ ] {
@@ -188,140 +192,159 @@ export class ConsoleFormatter {
188192 return [ ] ;
189193 }
190194
191- #formatArg( arg : unknown ) {
192- if ( arg instanceof SymbolizedError ) {
193- return [
194- arg . message ,
195- this . #formatStackTrace( arg . stackTrace , arg . cause , {
196- includeHeading : false ,
197- } ) ,
198- ]
199- . filter ( line => ! ! line )
200- . join ( '\n' ) ;
201- }
202- return typeof arg === 'object' ? JSON . stringify ( arg ) : String ( arg ) ;
195+ toJSON ( ) : ConsoleMessageConcise {
196+ return {
197+ type : this . #type,
198+ text : this . #text,
199+ argsCount : this . #argCount,
200+ id : this . #id,
201+ } ;
203202 }
204203
205- #formatArgs( ) : string {
206- const args = this . #getArgs( ) ;
204+ toJSONDetailed ( ) : ConsoleMessageDetailed {
205+ return {
206+ id : this . #id,
207+ type : this . #type,
208+ text : this . #text,
209+ argsCount : this . #argCount,
210+ args : this . #getArgs( ) . map ( arg => formatArg ( arg , this ) ) ,
211+ stackTrace : this . #stack
212+ ? formatStackTrace ( this . #stack, this . #cause, this )
213+ : undefined ,
214+ } ;
215+ }
216+ }
207217
208- if ( ! args . length ) {
209- return '' ;
210- }
218+ function convertConsoleMessageConciseToString ( msg : ConsoleMessageConcise ) {
219+ return `msgid= ${ msg . id } [ ${ msg . type } ] ${ msg . text } ( ${ msg . argsCount } args)` ;
220+ }
211221
212- const result = [ '### Arguments' ] ;
222+ function convertConsoleMessageConciseDetailedToString (
223+ msg : ConsoleMessageDetailed ,
224+ ) {
225+ const result = [
226+ `ID: ${ msg . id } ` ,
227+ `Message: ${ msg . type } > ${ msg . text } ` ,
228+ formatArgs ( msg ) ,
229+ ...( msg . stackTrace ? [ '### Stack trace' , msg . stackTrace ] : [ ] ) ,
230+ ] . filter ( line => ! ! line ) ;
231+ return result . join ( '\n' ) ;
232+ }
213233
214- for ( const [ key , arg ] of args . entries ( ) ) {
215- result . push ( `Arg #${ key } : ${ this . #formatArg( arg ) } ` ) ;
216- }
234+ function formatArgs ( msg : ConsoleMessageDetailed ) : string {
235+ const args = msg . args ;
217236
218- return result . join ( '\n' ) ;
237+ if ( ! args . length ) {
238+ return '' ;
219239 }
220240
221- #formatStackTrace(
222- stackTrace : DevTools . DevTools . StackTrace . StackTrace . StackTrace | undefined ,
223- cause : SymbolizedError | undefined ,
224- opts : { includeHeading : boolean } ,
225- ) : string {
226- if ( ! stackTrace ) {
227- return '' ;
228- }
241+ const result = [ '### Arguments' ] ;
229242
230- const lines = this . #formatStackTraceInner ( stackTrace , cause ) ;
231- const includedLines = lines . slice (
232- 0 ,
233- ConsoleFormatter . #STACK_TRACE_MAX_LINES ,
234- ) ;
235- const reminderCount = lines . length - includedLines . length ;
243+ for ( const [ key , arg ] of args . entries ( ) ) {
244+ result . push ( `Arg # ${ key } : ${ arg } ` ) ;
245+ }
246+
247+ return result . join ( '\n' ) ;
248+ }
236249
250+ function formatArg ( arg : unknown , formatter : { isIgnored : IgnoreCheck } ) {
251+ if ( arg instanceof SymbolizedError ) {
237252 return [
238- opts . includeHeading ? '### Stack trace' : '' ,
239- ... includedLines ,
240- reminderCount > 0 ? `... and ${ reminderCount } more frames` : '' ,
241- 'Note: line and column numbers use 1-based indexing' ,
253+ arg . message ,
254+ arg . stackTrace
255+ ? formatStackTrace ( arg . stackTrace , arg . cause , formatter )
256+ : undefined ,
242257 ]
243258 . filter ( line => ! ! line )
244259 . join ( '\n' ) ;
245260 }
261+ return typeof arg === 'object' ? JSON . stringify ( arg ) : String ( arg ) ;
262+ }
246263
247- #formatStackTraceInner(
248- stackTrace : DevTools . DevTools . StackTrace . StackTrace . StackTrace | undefined ,
249- cause : SymbolizedError | undefined ,
250- ) : string [ ] {
251- if ( ! stackTrace ) {
252- return [ ] ;
253- }
254-
255- return [
256- ...this . #formatFragment( stackTrace . syncFragment ) ,
257- ...stackTrace . asyncFragments
258- . map ( this . #formatAsyncFragment. bind ( this ) )
259- . flat ( ) ,
260- ...this . #formatCause( cause ) ,
261- ] ;
262- }
264+ const STACK_TRACE_MAX_LINES = 50 ;
265+
266+ function formatStackTrace (
267+ stackTrace : DevTools . DevTools . StackTrace . StackTrace . StackTrace ,
268+ cause : SymbolizedError | undefined ,
269+ formatter : { isIgnored : IgnoreCheck } ,
270+ ) : string {
271+ const lines = formatStackTraceInner ( stackTrace , cause , formatter ) ;
272+ const includedLines = lines . slice ( 0 , STACK_TRACE_MAX_LINES ) ;
273+ const reminderCount = lines . length - includedLines . length ;
274+
275+ return [
276+ ...includedLines ,
277+ reminderCount > 0 ? `... and ${ reminderCount } more frames` : '' ,
278+ 'Note: line and column numbers use 1-based indexing' ,
279+ ]
280+ . filter ( line => ! ! line )
281+ . join ( '\n' ) ;
282+ }
263283
264- #formatFragment(
265- fragment : DevTools . DevTools . StackTrace . StackTrace . Fragment ,
266- ) : string [ ] {
267- const frames = fragment . frames . filter ( frame => ! this . #isIgnored( frame ) ) ;
268- return frames . map ( this . #formatFrame. bind ( this ) ) ;
284+ function formatStackTraceInner (
285+ stackTrace : DevTools . DevTools . StackTrace . StackTrace . StackTrace | undefined ,
286+ cause : SymbolizedError | undefined ,
287+ formatter : { isIgnored : IgnoreCheck } ,
288+ ) : string [ ] {
289+ if ( ! stackTrace ) {
290+ return [ ] ;
269291 }
270292
271- #formatAsyncFragment(
272- fragment : DevTools . DevTools . StackTrace . StackTrace . AsyncFragment ,
273- ) : string [ ] {
274- const formattedFrames = this . #formatFragment( fragment ) ;
275- if ( formattedFrames . length === 0 ) {
276- return [ ] ;
277- }
293+ return [
294+ ...formatFragment ( stackTrace . syncFragment , formatter ) ,
295+ ...stackTrace . asyncFragments
296+ . map ( item => formatAsyncFragment ( item , formatter ) )
297+ . flat ( ) ,
298+ ...formatCause ( cause , formatter ) ,
299+ ] ;
300+ }
278301
279- const separatorLineLength = 40 ;
280- const prefix = `--- ${ fragment . description || 'async' } ` ;
281- const separator = prefix + '-' . repeat ( separatorLineLength - prefix . length ) ;
282- return [ separator , ...formattedFrames ] ;
283- }
302+ function formatFragment (
303+ fragment : DevTools . DevTools . StackTrace . StackTrace . Fragment ,
304+ formatter : { isIgnored : IgnoreCheck } ,
305+ ) : string [ ] {
306+ const frames = fragment . frames . filter ( frame => ! formatter . isIgnored ( frame ) ) ;
307+ return frames . map ( formatFrame ) ;
308+ }
284309
285- #formatFrame( frame : DevTools . DevTools . StackTrace . StackTrace . Frame ) : string {
286- let result = `at ${ frame . name ?? '<anonymous>' } ` ;
287- if ( frame . uiSourceCode ) {
288- const location = frame . uiSourceCode . uiLocation ( frame . line , frame . column ) ;
289- result += ` (${ location . linkText ( /* skipTrim */ false , /* showColumnNumber */ true ) } )` ;
290- } else if ( frame . url ) {
291- result += ` (${ frame . url } :${ frame . line } :${ frame . column } )` ;
292- }
293- return result ;
310+ function formatAsyncFragment (
311+ fragment : DevTools . DevTools . StackTrace . StackTrace . AsyncFragment ,
312+ formatter : { isIgnored : IgnoreCheck } ,
313+ ) : string [ ] {
314+ const formattedFrames = formatFragment ( fragment , formatter ) ;
315+ if ( formattedFrames . length === 0 ) {
316+ return [ ] ;
294317 }
295318
296- #formatCause( cause : SymbolizedError | undefined ) : string [ ] {
297- if ( ! cause ) {
298- return [ ] ;
299- }
319+ const separatorLineLength = 40 ;
320+ const prefix = `--- ${ fragment . description || 'async' } ` ;
321+ const separator = prefix + '-' . repeat ( separatorLineLength - prefix . length ) ;
322+ return [ separator , ...formattedFrames ] ;
323+ }
300324
301- return [
302- `Caused by: ${ cause . message } ` ,
303- ...this . #formatStackTraceInner( cause . stackTrace , cause . cause ) ,
304- ] ;
325+ function formatFrame (
326+ frame : DevTools . DevTools . StackTrace . StackTrace . Frame ,
327+ ) : string {
328+ let result = `at ${ frame . name ?? '<anonymous>' } ` ;
329+ if ( frame . uiSourceCode ) {
330+ const location = frame . uiSourceCode . uiLocation ( frame . line , frame . column ) ;
331+ result += ` (${ location . linkText ( /* skipTrim */ false , /* showColumnNumber */ true ) } )` ;
332+ } else if ( frame . url ) {
333+ result += ` (${ frame . url } :${ frame . line } :${ frame . column } )` ;
305334 }
335+ return result ;
336+ }
306337
307- toJSON ( ) : object {
308- return {
309- type : this . #type,
310- text : this . #text,
311- argsCount : this . #argCount,
312- id : this . #id,
313- } ;
338+ function formatCause (
339+ cause : SymbolizedError | undefined ,
340+ formatter : { isIgnored : IgnoreCheck } ,
341+ ) : string [ ] {
342+ if ( ! cause ) {
343+ return [ ] ;
314344 }
315345
316- toJSONDetailed ( ) : object {
317- return {
318- id : this . #id,
319- type : this . #type,
320- text : this . #text,
321- args : this . #getArgs( ) . map ( arg =>
322- typeof arg === 'object' ? arg : String ( arg ) ,
323- ) ,
324- stackTrace : this . #stack,
325- } ;
326- }
346+ return [
347+ `Caused by: ${ cause . message } ` ,
348+ ...formatStackTraceInner ( cause . stackTrace , cause . cause , formatter ) ,
349+ ] ;
327350}
0 commit comments