@@ -13,6 +13,8 @@ import {UncaughtError} from '../PageCollector.js';
1313import * as DevTools from '../third_party/index.js' ;
1414import type { ConsoleMessage } from '../third_party/index.js' ;
1515
16+ import type { IssueFormatter } from './IssueFormatter.js' ;
17+
1618export interface ConsoleFormatterOptions {
1719 fetchDetailedData ?: boolean ;
1820 id : number ;
@@ -32,6 +34,7 @@ interface ConsoleMessageConcise {
3234 text : string ;
3335 argsCount : number ;
3436 id : number ;
37+ count ?: number ;
3538}
3639
3740interface ConsoleMessageDetailed extends ConsoleMessageConcise {
@@ -54,7 +57,7 @@ export class ConsoleFormatter {
5457
5558 readonly isIgnored : IgnoreCheck ;
5659
57- private constructor ( params : {
60+ protected constructor ( params : {
5861 id : number ;
5962 type : string ;
6063 text : string ;
@@ -201,6 +204,48 @@ export class ConsoleFormatter {
201204 } ;
202205 }
203206
207+ /**
208+ * Groups consecutive messages with the same type, text, and argument count.
209+ * Similar to Chrome DevTools' console grouping behavior.
210+ */
211+ static groupConsecutive (
212+ messages : Array < ConsoleFormatter | IssueFormatter > ,
213+ ) : Array < ConsoleFormatter | IssueFormatter > {
214+ const grouped : Array < {
215+ message : ConsoleFormatter | IssueFormatter ;
216+ count : number ;
217+ } > = [ ] ;
218+ for ( const msg of messages ) {
219+ const prev = grouped [ grouped . length - 1 ] ;
220+ if (
221+ prev &&
222+ prev . message instanceof ConsoleFormatter &&
223+ msg instanceof ConsoleFormatter &&
224+ prev . message . #type === msg . #type &&
225+ prev . message . #text === msg . #text &&
226+ prev . message . #argCount === msg . #argCount
227+ ) {
228+ prev . count ++ ;
229+ } else {
230+ grouped . push ( { message : msg , count : 1 } ) ;
231+ }
232+ }
233+ return grouped . map ( ( { message, count} ) =>
234+ count > 1 && message instanceof ConsoleFormatter
235+ ? new GroupedConsoleFormatter (
236+ {
237+ id : message . #id,
238+ type : message . #type,
239+ text : message . #text,
240+ argCount : message . #argCount,
241+ isIgnored : message . isIgnored ,
242+ } ,
243+ count ,
244+ )
245+ : message ,
246+ ) ;
247+ }
248+
204249 toJSONDetailed ( ) : ConsoleMessageDetailed {
205250 return {
206251 id : this . #id,
@@ -215,8 +260,37 @@ export class ConsoleFormatter {
215260 }
216261}
217262
263+ export class GroupedConsoleFormatter extends ConsoleFormatter {
264+ readonly #count: number ;
265+
266+ constructor (
267+ params : {
268+ id : number ;
269+ type : string ;
270+ text : string ;
271+ argCount : number ;
272+ isIgnored : IgnoreCheck ;
273+ } ,
274+ count : number ,
275+ ) {
276+ super ( params ) ;
277+ this . #count = count ;
278+ }
279+
280+ override toString ( ) : string {
281+ return convertConsoleMessageConciseToString ( this . toJSON ( ) ) ;
282+ }
283+
284+ override toJSON ( ) : ConsoleMessageConcise {
285+ const json = super . toJSON ( ) ;
286+ json . count = this . #count;
287+ return json ;
288+ }
289+ }
290+
218291function convertConsoleMessageConciseToString ( msg : ConsoleMessageConcise ) {
219- return `msgid=${ msg . id } [${ msg . type } ] ${ msg . text } (${ msg . argsCount } args)` ;
292+ const countSuffix = msg . count && msg . count > 1 ? ` [${ msg . count } times]` : '' ;
293+ return `msgid=${ msg . id } [${ msg . type } ] ${ msg . text } (${ msg . argsCount } args)${ countSuffix } ` ;
220294}
221295
222296function convertConsoleMessageConciseDetailedToString (
0 commit comments