Skip to content

Commit 96f4479

Browse files
committed
chore: add 'FormattableMessage' interface to simplify ConsoleFormatter
1 parent 7e1ec81 commit 96f4479

2 files changed

Lines changed: 89 additions & 114 deletions

File tree

src/formatters/ConsoleFormatter.ts

Lines changed: 75 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -15,86 +15,92 @@ import type {ConsoleMessage} from '../third_party/index.js';
1515

1616
export interface ConsoleFormatterOptions {
1717
fetchDetailedData?: boolean;
18-
id?: number;
18+
id: number;
1919
devTools?: TargetUniverse;
2020
resolvedStackTraceForTesting?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
2121
}
2222

23+
export interface FormattableMessage {
24+
type: string;
25+
text: string;
26+
argsCount: number;
27+
28+
// Present when details are fetched.
29+
args: unknown[];
30+
stackTrace?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
31+
}
32+
2333
export class ConsoleFormatter {
24-
#msg: ConsoleMessage | Error | UncaughtError;
25-
#resolvedArgs: unknown[] = [];
26-
#resolvedStackTrace?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
27-
#id?: number;
34+
readonly #id: number;
35+
readonly #msg: FormattableMessage;
2836

29-
private constructor(
30-
msg: ConsoleMessage | Error | UncaughtError,
31-
options?: ConsoleFormatterOptions,
32-
) {
37+
constructor(id: number, msg: FormattableMessage) {
38+
this.#id = id;
3339
this.#msg = msg;
34-
this.#id = options?.id;
35-
this.#resolvedStackTrace = options?.resolvedStackTraceForTesting;
3640
}
3741

3842
static async from(
39-
msg: ConsoleMessage | Error | UncaughtError,
40-
options?: ConsoleFormatterOptions,
43+
message: ConsoleMessage | Error | UncaughtError,
44+
options: ConsoleFormatterOptions,
4145
): Promise<ConsoleFormatter> {
42-
const formatter = new ConsoleFormatter(msg, options);
43-
if (options?.fetchDetailedData) {
44-
await formatter.#loadDetailedData(options?.devTools);
46+
if (ConsoleFormatter.#isConsoleMessage(message)) {
47+
const msg: FormattableMessage = {
48+
type: message.type(),
49+
text: message.text(),
50+
argsCount: message.args().length,
51+
args: [],
52+
};
53+
if (options.fetchDetailedData) {
54+
msg.args = await Promise.all(
55+
message.args().map(async (arg, i) => {
56+
try {
57+
return await arg.jsonValue();
58+
} catch {
59+
return `<error: Argument ${i} is no longer available>`;
60+
}
61+
}),
62+
);
63+
if (options.devTools) {
64+
msg.stackTrace = await createStackTraceForConsoleMessage(
65+
options.devTools,
66+
message,
67+
);
68+
}
69+
}
70+
return new ConsoleFormatter(options.id, msg);
4571
}
46-
return formatter;
72+
73+
const msg: FormattableMessage = {
74+
type: 'error',
75+
text: message.message,
76+
argsCount: 0,
77+
args: [],
78+
};
79+
if (
80+
options.fetchDetailedData &&
81+
options.devTools &&
82+
'stackTrace' in message &&
83+
message.stackTrace
84+
) {
85+
msg.stackTrace = await createStackTrace(
86+
options.devTools,
87+
message.stackTrace,
88+
message.targetId,
89+
);
90+
}
91+
return new ConsoleFormatter(options.id, msg);
4792
}
4893

49-
#isConsoleMessage(
94+
static #isConsoleMessage(
5095
msg: ConsoleMessage | Error | UncaughtError,
5196
): msg is ConsoleMessage {
5297
// No `instanceof` as tests mock `ConsoleMessage`.
5398
return 'args' in msg && typeof msg.args === 'function';
5499
}
55100

56-
async #loadDetailedData(devTools?: TargetUniverse): Promise<void> {
57-
if (this.#msg instanceof Error) {
58-
return;
59-
}
60-
61-
if (this.#isConsoleMessage(this.#msg)) {
62-
this.#resolvedArgs = await Promise.all(
63-
this.#msg.args().map(async (arg, i) => {
64-
try {
65-
return await arg.jsonValue();
66-
} catch {
67-
return `<error: Argument ${i} is no longer available>`;
68-
}
69-
}),
70-
);
71-
}
72-
73-
if (devTools) {
74-
try {
75-
if (this.#isConsoleMessage(this.#msg)) {
76-
this.#resolvedStackTrace = await createStackTraceForConsoleMessage(
77-
devTools,
78-
this.#msg,
79-
);
80-
} else if (this.#msg.stackTrace) {
81-
this.#resolvedStackTrace = await createStackTrace(
82-
devTools,
83-
this.#msg.stackTrace,
84-
this.#msg.targetId,
85-
);
86-
}
87-
} catch {
88-
// ignore
89-
}
90-
}
91-
}
92-
93101
// The short format for a console message.
94102
toString(): string {
95-
const type = this.#getType();
96-
const text = this.#getText();
97-
const argsCount = this.#getArgsCount();
103+
const {type, text, argsCount} = this.#msg;
98104
const idPart = this.#id !== undefined ? `msgid=${this.#id} ` : '';
99105
return `${idPart}[${type}] ${text} (${argsCount} args)`;
100106
}
@@ -103,49 +109,25 @@ export class ConsoleFormatter {
103109
toStringDetailed(): string {
104110
const result = [
105111
this.#id !== undefined ? `ID: ${this.#id}` : '',
106-
`Message: ${this.#getType()}> ${this.#getText()}`,
112+
`Message: ${this.#msg.type}> ${this.#msg.text}`,
107113
this.#formatArgs(),
108-
this.#formatStackTrace(this.#resolvedStackTrace),
114+
this.#formatStackTrace(this.#msg.stackTrace),
109115
].filter(line => !!line);
110116
return result.join('\n');
111117
}
112118

113-
#getType(): string {
114-
if (!this.#isConsoleMessage(this.#msg)) {
115-
return 'error';
116-
}
117-
return this.#msg.type();
118-
}
119-
120-
#getText(): string {
121-
if (!this.#isConsoleMessage(this.#msg)) {
122-
return this.#msg.message;
123-
}
124-
return this.#msg.text();
125-
}
126-
127119
#getArgs(): unknown[] {
128-
if (!this.#isConsoleMessage(this.#msg)) {
129-
return [];
130-
}
131-
if (this.#resolvedArgs.length > 0) {
132-
const args = [...this.#resolvedArgs];
120+
if (this.#msg.args.length > 0) {
121+
const args = [...this.#msg.args];
133122
// If there is no text, the first argument serves as text (see formatMessage).
134-
if (!this.#msg.text()) {
123+
if (!this.#msg.text) {
135124
args.shift();
136125
}
137126
return args;
138127
}
139128
return [];
140129
}
141130

142-
#getArgsCount(): number {
143-
if (!this.#isConsoleMessage(this.#msg)) {
144-
return 0;
145-
}
146-
return this.#resolvedArgs.length || this.#msg.args().length;
147-
}
148-
149131
#formatArg(arg: unknown) {
150132
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
151133
}
@@ -208,22 +190,22 @@ export class ConsoleFormatter {
208190
}
209191
toJSON(): object {
210192
return {
211-
type: this.#getType(),
212-
text: this.#getText(),
213-
argsCount: this.#getArgsCount(),
193+
type: this.#msg.type,
194+
text: this.#msg.text,
195+
argsCount: this.#msg.argsCount,
214196
id: this.#id,
215197
};
216198
}
217199

218200
toJSONDetailed(): object {
219201
return {
220202
id: this.#id,
221-
type: this.#getType(),
222-
text: this.#getText(),
203+
type: this.#msg.type,
204+
text: this.#msg.text,
223205
args: this.#getArgs().map(arg =>
224206
typeof arg === 'object' ? arg : String(arg),
225207
),
226-
stackTrace: this.#resolvedStackTrace,
208+
stackTrace: this.#msg.stackTrace,
227209
};
228210
}
229211
}

tests/formatters/ConsoleFormatter.test.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,6 @@ describe('ConsoleFormatter', () => {
132132
});
133133

134134
it('formats a console message with a stack trace', async t => {
135-
const message = createMockMessage({
136-
type: () => 'log',
137-
text: () => 'Hello stack trace!',
138-
});
139135
const stackTrace = {
140136
syncFragment: {
141137
frames: [
@@ -168,12 +164,13 @@ describe('ConsoleFormatter', () => {
168164
],
169165
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;
170166

171-
const result = (
172-
await ConsoleFormatter.from(message, {
173-
id: 5,
174-
resolvedStackTraceForTesting: stackTrace,
175-
})
176-
).toStringDetailed();
167+
const result = new ConsoleFormatter(5, {
168+
type: 'log',
169+
text: 'Hello stack trace!',
170+
argsCount: 0,
171+
args: [],
172+
stackTrace,
173+
}).toStringDetailed();
177174
t.assert.snapshot?.(result);
178175
});
179176

@@ -230,18 +227,14 @@ describe('ConsoleFormatter', () => {
230227
},
231228
],
232229
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;
233-
const error = new UncaughtError(
234-
'Uncaught TypeError: Cannot read properties of undefined',
235-
undefined,
236-
'<mock target ID>',
237-
);
238230

239-
const result = (
240-
await ConsoleFormatter.from(error, {
241-
id: 7,
242-
resolvedStackTraceForTesting: stackTrace,
243-
})
244-
).toStringDetailed();
231+
const result = new ConsoleFormatter(7, {
232+
type: 'error',
233+
text: 'Uncaught TypeError: Cannot read properties of undefined',
234+
argsCount: 0,
235+
args: [],
236+
stackTrace,
237+
}).toStringDetailed();
245238
t.assert.snapshot?.(result);
246239
});
247240
});

0 commit comments

Comments
 (0)