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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/DevtoolsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,20 @@ export async function createStackTraceForConsoleMessage(
_targetId(): string | undefined;
};
const rawStackTrace = message._rawStackTrace();
if (!rawStackTrace) {
return undefined;
if (rawStackTrace) {
return createStackTrace(devTools, rawStackTrace, message._targetId());
}
return undefined;
}

export async function createStackTrace(
devTools: TargetUniverse,
rawStackTrace: Protocol.Runtime.StackTrace,
targetId: string | undefined,
): Promise<DevTools.StackTrace.StackTrace.StackTrace> {
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
const messageTargetId = message._targetId();
const target = messageTargetId
? targetManager.targetById(messageTargetId) || devTools.target
const target = targetId
? targetManager.targetById(targetId) || devTools.target
: devTools.target;
const model = target.model(DevTools.DebuggerModel) as DevTools.DebuggerModel;

Expand Down
74 changes: 47 additions & 27 deletions src/formatters/ConsoleFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

import {
createStackTraceForConsoleMessage,
createStackTrace,
type TargetUniverse,
} from '../DevtoolsUtils.js';
import type {UncaughtError} from '../PageCollector.js';
import type * as DevTools from '../third_party/index.js';
import type {ConsoleMessage} from '../third_party/index.js';

Expand All @@ -19,13 +21,13 @@ export interface ConsoleFormatterOptions {
}

export class ConsoleFormatter {
#msg: ConsoleMessage | Error;
#msg: ConsoleMessage | Error | UncaughtError;
#resolvedArgs: unknown[] = [];
#resolvedStackTrace?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
#id?: number;

private constructor(
msg: ConsoleMessage | Error,
msg: ConsoleMessage | Error | UncaughtError,
options?: ConsoleFormatterOptions,
) {
this.#msg = msg;
Expand All @@ -34,7 +36,7 @@ export class ConsoleFormatter {
}

static async from(
msg: ConsoleMessage | Error,
msg: ConsoleMessage | Error | UncaughtError,
options?: ConsoleFormatterOptions,
): Promise<ConsoleFormatter> {
const formatter = new ConsoleFormatter(msg, options);
Expand All @@ -44,27 +46,44 @@ export class ConsoleFormatter {
return formatter;
}

#isConsoleMessage(
msg: ConsoleMessage | Error | UncaughtError,
): msg is ConsoleMessage {
// No `instanceof` as tests mock `ConsoleMessage`.
return 'args' in msg && typeof msg.args === 'function';
}

async #loadDetailedData(devTools?: TargetUniverse): Promise<void> {
if (this.#msg instanceof Error) {
return;
}

this.#resolvedArgs = await Promise.all(
this.#msg.args().map(async (arg, i) => {
try {
return await arg.jsonValue();
} catch {
return `<error: Argument ${i} is no longer available>`;
}
}),
);
if (this.#isConsoleMessage(this.#msg)) {
this.#resolvedArgs = await Promise.all(
this.#msg.args().map(async (arg, i) => {
try {
return await arg.jsonValue();
} catch {
return `<error: Argument ${i} is no longer available>`;
}
}),
);
}

if (devTools) {
try {
this.#resolvedStackTrace = await createStackTraceForConsoleMessage(
devTools,
this.#msg,
);
if (this.#isConsoleMessage(this.#msg)) {
this.#resolvedStackTrace = await createStackTraceForConsoleMessage(
devTools,
this.#msg,
);
} else if (this.#msg.stackTrace) {
this.#resolvedStackTrace = await createStackTrace(
devTools,
this.#msg.stackTrace,
this.#msg.targetId,
);
}
} catch {
// ignore
}
Expand All @@ -75,10 +94,7 @@ export class ConsoleFormatter {
toString(): string {
const type = this.#getType();
const text = this.#getText();
const argsCount =
this.#msg instanceof Error
? 0
: this.#resolvedArgs.length || this.#msg.args().length;
const argsCount = this.#getArgsCount();
const idPart = this.#id !== undefined ? `msgid=${this.#id} ` : '';
return `${idPart}[${type}] ${text} (${argsCount} args)`;
}
Expand All @@ -95,21 +111,21 @@ export class ConsoleFormatter {
}

#getType(): string {
if (this.#msg instanceof Error) {
if (!this.#isConsoleMessage(this.#msg)) {
return 'error';
}
return this.#msg.type();
}

#getText(): string {
if (this.#msg instanceof Error) {
if (!this.#isConsoleMessage(this.#msg)) {
return this.#msg.message;
}
return this.#msg.text();
}

#getArgs(): unknown[] {
if (this.#msg instanceof Error) {
if (!this.#isConsoleMessage(this.#msg)) {
return [];
}
if (this.#resolvedArgs.length > 0) {
Expand All @@ -123,6 +139,13 @@ export class ConsoleFormatter {
return [];
}

#getArgsCount(): number {
if (!this.#isConsoleMessage(this.#msg)) {
return 0;
}
return this.#resolvedArgs.length || this.#msg.args().length;
}

#formatArg(arg: unknown) {
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
}
Expand Down Expand Up @@ -185,10 +208,7 @@ export class ConsoleFormatter {
return {
type: this.#getType(),
text: this.#getText(),
argsCount:
this.#msg instanceof Error
? 0
: this.#resolvedArgs.length || this.#msg.args().length,
argsCount: this.#getArgsCount(),
id: this.#id,
};
}
Expand Down
14 changes: 14 additions & 0 deletions tests/formatters/ConsoleFormatter.test.js.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ exports[`ConsoleFormatter > toString > formats a console.log message with one ar
msgid=2 [log] Processing file: (1 args)
`;

exports[`ConsoleFormatter > toString > formats an UncaughtError 1`] = `
msgid=4 [error] Uncaught TypeError: Cannot read properties of undefined (0 args)
`;

exports[`ConsoleFormatter > toStringDetailed > formats a console message with a stack trace 1`] = `
ID: 5
Message: log> Hello stack trace!
Expand Down Expand Up @@ -45,6 +49,16 @@ Message: log> Processing file:
Arg #0: file.txt
`;

exports[`ConsoleFormatter > toStringDetailed > formats an UncaughtError with a stack trace 1`] = `
ID: 7
Message: error> Uncaught TypeError: Cannot read properties of undefined
### Stack trace
at foo (foo.ts:10:2)
at bar (foo.ts:20:2)
--- setTimeout -------------------------
at schedule (util.ts:5:2)
`;

exports[`ConsoleFormatter > toStringDetailed > handles \"Execution context is not available\" error in args 1`] = `
ID: 6
Message: log> Processing file:
Expand Down
60 changes: 60 additions & 0 deletions tests/formatters/ConsoleFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import assert from 'node:assert';
import {describe, it} from 'node:test';

import {ConsoleFormatter} from '../../src/formatters/ConsoleFormatter.js';
import {UncaughtError} from '../../src/PageCollector.js';
import type {ConsoleMessage} from '../../src/third_party/index.js';
import type {DevTools} from '../../src/third_party/index.js';

Expand Down Expand Up @@ -66,6 +67,18 @@ describe('ConsoleFormatter', () => {
).toString();
t.assert.snapshot?.(result);
});

it('formats an UncaughtError', async t => {
const error = new UncaughtError(
'Uncaught TypeError: Cannot read properties of undefined',
undefined,
'<mock target ID>',
);
const result = (
await ConsoleFormatter.from(error, {id: 4, fetchDetailedData: true})
).toString();
t.assert.snapshot?.(result);
});
});

describe('toStringDetailed', () => {
Expand Down Expand Up @@ -184,6 +197,53 @@ describe('ConsoleFormatter', () => {
t.assert.snapshot?.(result);
assert.ok(result.includes('<error: Argument 0 is no longer available>'));
});

it('formats an UncaughtError with a stack trace', async t => {
const stackTrace = {
syncFragment: {
frames: [
{
line: 10,
column: 2,
url: 'foo.ts',
name: 'foo',
},
{
line: 20,
column: 2,
url: 'foo.ts',
name: 'bar',
},
],
},
asyncFragments: [
{
description: 'setTimeout',
frames: [
{
line: 5,
column: 2,
url: 'util.ts',
name: 'schedule',
},
],
},
],
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;
const error = new UncaughtError(
'Uncaught TypeError: Cannot read properties of undefined',
undefined,
'<mock target ID>',
);

const result = (
await ConsoleFormatter.from(error, {
id: 7,
resolvedStackTraceForTesting: stackTrace,
})
).toStringDetailed();
t.assert.snapshot?.(result);
});
});
describe('toJSON', () => {
it('formats a console.log message', async () => {
Expand Down