@@ -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,14 @@ 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 (
181+ this . toJSONDetailed ( ) ,
182+ ) ;
177183 }
178184
179185 #getArgs( ) : unknown [ ] {
@@ -188,140 +194,162 @@ export class ConsoleFormatter {
188194 return [ ] ;
189195 }
190196
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 ) ;
197+ toJSON ( ) : ConsoleMessageConcise {
198+ return {
199+ type : this . #type,
200+ text : this . #text,
201+ argsCount : this . #argCount,
202+ id : this . #id,
203+ } ;
203204 }
204205
205- #formatArgs( ) : string {
206- const args = this . #getArgs( ) ;
206+ toJSONDetailed ( ) : ConsoleMessageDetailed {
207+ return {
208+ id : this . #id,
209+ type : this . #type,
210+ text : this . #text,
211+ argsCount : this . #argCount,
212+ args : this . #getArgs( ) . map ( arg => formatArg ( arg , this ) ) ,
213+ stackTrace : this . #stack ? formatStackTrace ( this . #stack, this . #cause, this ) : 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 ? [
230+ '### Stack trace' ,
231+ msg . stackTrace ,
232+ ] : [ ] )
233+ ] . filter ( line => ! ! line ) ;
234+ return result . join ( '\n' ) ;
235+ }
213236
214- for ( const [ key , arg ] of args . entries ( ) ) {
215- result . push ( `Arg #${ key } : ${ this . #formatArg( arg ) } ` ) ;
216- }
237+ function formatArgs ( msg : ConsoleMessageDetailed ) : string {
238+ const args = msg . args ;
217239
218- return result . join ( '\n' ) ;
240+ if ( ! args . length ) {
241+ return '' ;
219242 }
220243
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- }
244+ const result = [ '### Arguments' ] ;
229245
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 ;
246+ for ( const [ key , arg ] of args . entries ( ) ) {
247+ result . push ( `Arg # ${ key } : ${ arg } ` ) ;
248+ }
249+
250+ return result . join ( '\n' ) ;
251+ }
236252
253+ function formatArg ( arg : unknown , formatter : { isIgnored : IgnoreCheck } ) {
254+ if ( arg instanceof SymbolizedError ) {
237255 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' ,
256+ arg . message ,
257+ arg . stackTrace ? formatStackTrace (
258+ arg . stackTrace ,
259+ arg . cause ,
260+ formatter ,
261+ ) : undefined ,
242262 ]
243263 . filter ( line => ! ! line )
244264 . join ( '\n' ) ;
245265 }
266+ return typeof arg === 'object' ? JSON . stringify ( arg ) : String ( arg ) ;
267+ }
246268
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- }
269+ const STACK_TRACE_MAX_LINES = 50 ;
270+
271+ function formatStackTrace (
272+ stackTrace : DevTools . DevTools . StackTrace . StackTrace . StackTrace ,
273+ cause : SymbolizedError | undefined ,
274+ formatter : { isIgnored : IgnoreCheck } ,
275+ ) : string {
276+ const lines = formatStackTraceInner ( stackTrace , cause , formatter ) ;
277+ const includedLines = lines . slice ( 0 , STACK_TRACE_MAX_LINES ) ;
278+ const reminderCount = lines . length - includedLines . length ;
279+
280+ return [
281+ ...includedLines ,
282+ reminderCount > 0 ? `... and ${ reminderCount } more frames` : '' ,
283+ 'Note: line and column numbers use 1-based indexing' ,
284+ ]
285+ . filter ( line => ! ! line )
286+ . join ( '\n' ) ;
287+ }
263288
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 ) ) ;
289+ function formatStackTraceInner (
290+ stackTrace : DevTools . DevTools . StackTrace . StackTrace . StackTrace | undefined ,
291+ cause : SymbolizedError | undefined ,
292+ formatter : { isIgnored : IgnoreCheck } ,
293+ ) : string [ ] {
294+ if ( ! stackTrace ) {
295+ return [ ] ;
269296 }
270297
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- }
298+ return [
299+ ...formatFragment ( stackTrace . syncFragment , formatter ) ,
300+ ...stackTrace . asyncFragments
301+ . map ( item => formatAsyncFragment ( item , formatter ) )
302+ . flat ( ) ,
303+ ...formatCause ( cause , formatter ) ,
304+ ] ;
305+ }
278306
279- const separatorLineLength = 40 ;
280- const prefix = `--- ${ fragment . description || 'async' } ` ;
281- const separator = prefix + '-' . repeat ( separatorLineLength - prefix . length ) ;
282- return [ separator , ...formattedFrames ] ;
283- }
307+ function formatFragment (
308+ fragment : DevTools . DevTools . StackTrace . StackTrace . Fragment ,
309+ formatter : { isIgnored : IgnoreCheck } ,
310+ ) : string [ ] {
311+ const frames = fragment . frames . filter ( frame => ! formatter . isIgnored ( frame ) ) ;
312+ return frames . map ( formatFrame ) ;
313+ }
284314
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 ;
315+ function formatAsyncFragment (
316+ fragment : DevTools . DevTools . StackTrace . StackTrace . AsyncFragment ,
317+ formatter : { isIgnored : IgnoreCheck } ,
318+ ) : string [ ] {
319+ const formattedFrames = formatFragment ( fragment , formatter ) ;
320+ if ( formattedFrames . length === 0 ) {
321+ return [ ] ;
294322 }
295323
296- #formatCause( cause : SymbolizedError | undefined ) : string [ ] {
297- if ( ! cause ) {
298- return [ ] ;
299- }
324+ const separatorLineLength = 40 ;
325+ const prefix = `--- ${ fragment . description || 'async' } ` ;
326+ const separator = prefix + '-' . repeat ( separatorLineLength - prefix . length ) ;
327+ return [ separator , ...formattedFrames ] ;
328+ }
300329
301- return [
302- `Caused by: ${ cause . message } ` ,
303- ...this . #formatStackTraceInner( cause . stackTrace , cause . cause ) ,
304- ] ;
330+ function formatFrame (
331+ frame : DevTools . DevTools . StackTrace . StackTrace . Frame ,
332+ ) : string {
333+ let result = `at ${ frame . name ?? '<anonymous>' } ` ;
334+ if ( frame . uiSourceCode ) {
335+ const location = frame . uiSourceCode . uiLocation ( frame . line , frame . column ) ;
336+ result += ` (${ location . linkText ( /* skipTrim */ false , /* showColumnNumber */ true ) } )` ;
337+ } else if ( frame . url ) {
338+ result += ` (${ frame . url } :${ frame . line } :${ frame . column } )` ;
305339 }
340+ return result ;
341+ }
306342
307- toJSON ( ) : object {
308- return {
309- type : this . #type,
310- text : this . #text,
311- argsCount : this . #argCount,
312- id : this . #id,
313- } ;
343+ function formatCause (
344+ cause : SymbolizedError | undefined ,
345+ formatter : { isIgnored : IgnoreCheck } ,
346+ ) : string [ ] {
347+ if ( ! cause ) {
348+ return [ ] ;
314349 }
315350
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- }
351+ return [
352+ `Caused by: ${ cause . message } ` ,
353+ ...formatStackTraceInner ( cause . stackTrace , cause . cause , formatter ) ,
354+ ] ;
327355}
0 commit comments