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

Commit a08f550

Browse files
authored
chore: implement formatting for UncaughtError (#875)
1 parent b5369a6 commit a08f550

File tree

4 files changed

+132
-32
lines changed

4 files changed

+132
-32
lines changed

src/DevtoolsUtils.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,14 +236,20 @@ export async function createStackTraceForConsoleMessage(
236236
_targetId(): string | undefined;
237237
};
238238
const rawStackTrace = message._rawStackTrace();
239-
if (!rawStackTrace) {
240-
return undefined;
239+
if (rawStackTrace) {
240+
return createStackTrace(devTools, rawStackTrace, message._targetId());
241241
}
242+
return undefined;
243+
}
242244

245+
export async function createStackTrace(
246+
devTools: TargetUniverse,
247+
rawStackTrace: Protocol.Runtime.StackTrace,
248+
targetId: string | undefined,
249+
): Promise<DevTools.StackTrace.StackTrace.StackTrace> {
243250
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
244-
const messageTargetId = message._targetId();
245-
const target = messageTargetId
246-
? targetManager.targetById(messageTargetId) || devTools.target
251+
const target = targetId
252+
? targetManager.targetById(targetId) || devTools.target
247253
: devTools.target;
248254
const model = target.model(DevTools.DebuggerModel) as DevTools.DebuggerModel;
249255

src/formatters/ConsoleFormatter.ts

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
import {
88
createStackTraceForConsoleMessage,
9+
createStackTrace,
910
type TargetUniverse,
1011
} from '../DevtoolsUtils.js';
12+
import type {UncaughtError} from '../PageCollector.js';
1113
import type * as DevTools from '../third_party/index.js';
1214
import type {ConsoleMessage} from '../third_party/index.js';
1315

@@ -19,13 +21,13 @@ export interface ConsoleFormatterOptions {
1921
}
2022

2123
export class ConsoleFormatter {
22-
#msg: ConsoleMessage | Error;
24+
#msg: ConsoleMessage | Error | UncaughtError;
2325
#resolvedArgs: unknown[] = [];
2426
#resolvedStackTrace?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
2527
#id?: number;
2628

2729
private constructor(
28-
msg: ConsoleMessage | Error,
30+
msg: ConsoleMessage | Error | UncaughtError,
2931
options?: ConsoleFormatterOptions,
3032
) {
3133
this.#msg = msg;
@@ -34,7 +36,7 @@ export class ConsoleFormatter {
3436
}
3537

3638
static async from(
37-
msg: ConsoleMessage | Error,
39+
msg: ConsoleMessage | Error | UncaughtError,
3840
options?: ConsoleFormatterOptions,
3941
): Promise<ConsoleFormatter> {
4042
const formatter = new ConsoleFormatter(msg, options);
@@ -44,27 +46,44 @@ export class ConsoleFormatter {
4446
return formatter;
4547
}
4648

49+
#isConsoleMessage(
50+
msg: ConsoleMessage | Error | UncaughtError,
51+
): msg is ConsoleMessage {
52+
// No `instanceof` as tests mock `ConsoleMessage`.
53+
return 'args' in msg && typeof msg.args === 'function';
54+
}
55+
4756
async #loadDetailedData(devTools?: TargetUniverse): Promise<void> {
4857
if (this.#msg instanceof Error) {
4958
return;
5059
}
5160

52-
this.#resolvedArgs = await Promise.all(
53-
this.#msg.args().map(async (arg, i) => {
54-
try {
55-
return await arg.jsonValue();
56-
} catch {
57-
return `<error: Argument ${i} is no longer available>`;
58-
}
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+
}
6172

6273
if (devTools) {
6374
try {
64-
this.#resolvedStackTrace = await createStackTraceForConsoleMessage(
65-
devTools,
66-
this.#msg,
67-
);
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+
}
6887
} catch {
6988
// ignore
7089
}
@@ -75,10 +94,7 @@ export class ConsoleFormatter {
7594
toString(): string {
7695
const type = this.#getType();
7796
const text = this.#getText();
78-
const argsCount =
79-
this.#msg instanceof Error
80-
? 0
81-
: this.#resolvedArgs.length || this.#msg.args().length;
97+
const argsCount = this.#getArgsCount();
8298
const idPart = this.#id !== undefined ? `msgid=${this.#id} ` : '';
8399
return `${idPart}[${type}] ${text} (${argsCount} args)`;
84100
}
@@ -95,21 +111,21 @@ export class ConsoleFormatter {
95111
}
96112

97113
#getType(): string {
98-
if (this.#msg instanceof Error) {
114+
if (!this.#isConsoleMessage(this.#msg)) {
99115
return 'error';
100116
}
101117
return this.#msg.type();
102118
}
103119

104120
#getText(): string {
105-
if (this.#msg instanceof Error) {
121+
if (!this.#isConsoleMessage(this.#msg)) {
106122
return this.#msg.message;
107123
}
108124
return this.#msg.text();
109125
}
110126

111127
#getArgs(): unknown[] {
112-
if (this.#msg instanceof Error) {
128+
if (!this.#isConsoleMessage(this.#msg)) {
113129
return [];
114130
}
115131
if (this.#resolvedArgs.length > 0) {
@@ -123,6 +139,13 @@ export class ConsoleFormatter {
123139
return [];
124140
}
125141

142+
#getArgsCount(): number {
143+
if (!this.#isConsoleMessage(this.#msg)) {
144+
return 0;
145+
}
146+
return this.#resolvedArgs.length || this.#msg.args().length;
147+
}
148+
126149
#formatArg(arg: unknown) {
127150
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
128151
}
@@ -185,10 +208,7 @@ export class ConsoleFormatter {
185208
return {
186209
type: this.#getType(),
187210
text: this.#getText(),
188-
argsCount:
189-
this.#msg instanceof Error
190-
? 0
191-
: this.#resolvedArgs.length || this.#msg.args().length,
211+
argsCount: this.#getArgsCount(),
192212
id: this.#id,
193213
};
194214
}

tests/formatters/ConsoleFormatter.test.js.snapshot

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ exports[`ConsoleFormatter > toString > formats a console.log message with one ar
1010
msgid=2 [log] Processing file: (1 args)
1111
`;
1212

13+
exports[`ConsoleFormatter > toString > formats an UncaughtError 1`] = `
14+
msgid=4 [error] Uncaught TypeError: Cannot read properties of undefined (0 args)
15+
`;
16+
1317
exports[`ConsoleFormatter > toStringDetailed > formats a console message with a stack trace 1`] = `
1418
ID: 5
1519
Message: log> Hello stack trace!
@@ -45,6 +49,16 @@ Message: log> Processing file:
4549
Arg #0: file.txt
4650
`;
4751

52+
exports[`ConsoleFormatter > toStringDetailed > formats an UncaughtError with a stack trace 1`] = `
53+
ID: 7
54+
Message: error> Uncaught TypeError: Cannot read properties of undefined
55+
### Stack trace
56+
at foo (foo.ts:10:2)
57+
at bar (foo.ts:20:2)
58+
--- setTimeout -------------------------
59+
at schedule (util.ts:5:2)
60+
`;
61+
4862
exports[`ConsoleFormatter > toStringDetailed > handles \"Execution context is not available\" error in args 1`] = `
4963
ID: 6
5064
Message: log> Processing file:

tests/formatters/ConsoleFormatter.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import assert from 'node:assert';
88
import {describe, it} from 'node:test';
99

1010
import {ConsoleFormatter} from '../../src/formatters/ConsoleFormatter.js';
11+
import {UncaughtError} from '../../src/PageCollector.js';
1112
import type {ConsoleMessage} from '../../src/third_party/index.js';
1213
import type {DevTools} from '../../src/third_party/index.js';
1314

@@ -66,6 +67,18 @@ describe('ConsoleFormatter', () => {
6667
).toString();
6768
t.assert.snapshot?.(result);
6869
});
70+
71+
it('formats an UncaughtError', async t => {
72+
const error = new UncaughtError(
73+
'Uncaught TypeError: Cannot read properties of undefined',
74+
undefined,
75+
'<mock target ID>',
76+
);
77+
const result = (
78+
await ConsoleFormatter.from(error, {id: 4, fetchDetailedData: true})
79+
).toString();
80+
t.assert.snapshot?.(result);
81+
});
6982
});
7083

7184
describe('toStringDetailed', () => {
@@ -184,6 +197,53 @@ describe('ConsoleFormatter', () => {
184197
t.assert.snapshot?.(result);
185198
assert.ok(result.includes('<error: Argument 0 is no longer available>'));
186199
});
200+
201+
it('formats an UncaughtError with a stack trace', async t => {
202+
const stackTrace = {
203+
syncFragment: {
204+
frames: [
205+
{
206+
line: 10,
207+
column: 2,
208+
url: 'foo.ts',
209+
name: 'foo',
210+
},
211+
{
212+
line: 20,
213+
column: 2,
214+
url: 'foo.ts',
215+
name: 'bar',
216+
},
217+
],
218+
},
219+
asyncFragments: [
220+
{
221+
description: 'setTimeout',
222+
frames: [
223+
{
224+
line: 5,
225+
column: 2,
226+
url: 'util.ts',
227+
name: 'schedule',
228+
},
229+
],
230+
},
231+
],
232+
} 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+
);
238+
239+
const result = (
240+
await ConsoleFormatter.from(error, {
241+
id: 7,
242+
resolvedStackTraceForTesting: stackTrace,
243+
})
244+
).toStringDetailed();
245+
t.assert.snapshot?.(result);
246+
});
187247
});
188248
describe('toJSON', () => {
189249
it('formats a console.log message', async () => {

0 commit comments

Comments
 (0)