豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit 0d78685

Browse files
authored
refactor: cleanup string and structured console formatters (ChromeDevTools#1005)
- typed JSON objects for structured output - same source is used for text formatting
1 parent bc3c40e commit 0d78685

File tree

3 files changed

+823
-673
lines changed

3 files changed

+823
-673
lines changed

src/formatters/ConsoleFormatter.ts

Lines changed: 145 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)