Skip to content
Closed
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
47 changes: 45 additions & 2 deletions src/formatters/ConsoleFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,12 @@ export class ConsoleFormatter {
}

// The short format for a console message.
toString(): string {
return `msgid=${this.#id} [${this.#type}] ${this.#text} (${this.#argCount} args)`;
toString(): string {
const topFrame = this.#getTopFrameLocation();
const locationStr = topFrame
? ` (${topFrame.url}:${topFrame.lineNumber}:${topFrame.columnNumber})`
: '';
return `msgid=${this.#id} [${this.#type}] ${this.#text}${locationStr} (${this.#argCount} args)`;
}

// The verbose format for a console message, including all details.
Expand Down Expand Up @@ -305,15 +309,18 @@ export class ConsoleFormatter {
}

toJSON(): object {
const location = this.#getTopFrameLocation();
return {
type: this.#type,
text: this.#text,
argsCount: this.#argCount,
id: this.#id,
...(location ? { location } : {}),
};
}

toJSONDetailed(): object {

return {
id: this.#id,
type: this.#type,
Expand All @@ -324,4 +331,40 @@ export class ConsoleFormatter {
stackTrace: this.#stack,
};
}

#getTopFrameLocation():
| { url: string; lineNumber: number; columnNumber: number }
| undefined {

if (!this.#stack) {
return undefined;
}

const frame = this.#stack.syncFragment.frames.find(
f => !this.#isIgnored(f),
);

if (!frame) {
return undefined;
}

if (frame.uiSourceCode) {
const location = frame.uiSourceCode.uiLocation(frame.line, frame.column);
return {
url: location.uiSourceCode.url(),
lineNumber: location.lineNumber + 1,
columnNumber: location.columnNumber! + 1,
};
}

if (frame.url) {
return {
url: frame.url,
lineNumber: frame.line,
columnNumber: frame.column,
};
}

return undefined;
}
}
97 changes: 97 additions & 0 deletions tests/formatters/ConsoleFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,100 @@
});
});
});

describe('ConsoleFormatter - Source Map & Heavy Error Tests', () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tests do not use any source maps. Can we also embed them into existing tests that already verify console message details and cover the list expectations there too?


it('handles heavy/nested errors with cause chain', async () => {
const innerFrame = { line: 5, column: 1, url: 'lib.js', name: 'inner' };
const outerFrame = { line: 10, column: 2, url: 'app.js', name: 'outer' };

const innerError = SymbolizedError.createForTesting(
'Inner error',
{ syncFragment: { frames: [innerFrame] }, asyncFragments: [] } as any,

Check failure on line 685 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check correct format

Unexpected any. Specify a different type

Check failure on line 685 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check docs updated

Unexpected any. Specify a different type
);

const outerError = SymbolizedError.createForTesting(
'Outer error',
{ syncFragment: { frames: [outerFrame] }, asyncFragments: [] } as any,

Check failure on line 690 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check correct format

Unexpected any. Specify a different type

Check failure on line 690 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check docs updated

Unexpected any. Specify a different type
innerError,
);

const uncaughtError = new UncaughtError({} as Protocol.Runtime.ExceptionDetails, 'heavy-1');

const formatter = await ConsoleFormatter.from(uncaughtError, {
id: 99,
resolvedCauseForTesting: outerError,
resolvedStackTraceForTesting: outerError.stackTrace as any,

Check failure on line 699 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check correct format

Unexpected any. Specify a different type

Check failure on line 699 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check docs updated

Unexpected any. Specify a different type
});

const detailed = formatter.toStringDetailed();
assert.ok(detailed.includes('Outer error'));
assert.ok(detailed.includes('Caused by: Inner error'));
assert.ok(detailed.includes('inner'));
assert.ok(detailed.includes('outer'));
});

it('handles async fragments correctly', async () => {
const syncFrame = { name: 'syncFunc', line: 1, column: 1, url: 'sync.js' };
const asyncFrame = { name: 'asyncFunc', line: 5, column: 2, url: 'async.js' };

const stackTrace = {
syncFragment: { frames: [syncFrame] },
asyncFragments: [{ description: 'asyncTask', frames: [asyncFrame] }],
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;

const error = SymbolizedError.createForTesting(
'Async error',
stackTrace,
);

const uncaughtError = new UncaughtError({} as Protocol.Runtime.ExceptionDetails, 'async-1');

const formatter = await ConsoleFormatter.from(uncaughtError, {
id: 100,
resolvedCauseForTesting: error,
resolvedStackTraceForTesting: stackTrace,
});

const detailed = formatter.toStringDetailed();
assert.ok(detailed.includes('--- asyncTask'));
assert.ok(detailed.includes('asyncFunc'));
});

it('includes first stack line in toString()', async () => {
const mockFrame = { name: 'firstFunc', url: 'first.js', line: 10, column: 5 };
const stackTrace = {
syncFragment: { frames: [mockFrame] },
asyncFragments: [],
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;

const msg = createMockMessage({ type: () => 'error', text: () => 'Test error' });
const formatter = await ConsoleFormatter.from(msg, {
id: 200,
resolvedStackTraceForTesting: stackTrace,
});

const str = formatter.toString();
assert.ok(str.includes('at firstFunc (first.js:10:5)'), 'First stack line should appear in toString()');
});

it('includes first stack line in toJSON()', async () => {
const mockFrame = { name: 'firstFunc', url: 'main.js', line: 15, column: 3 };
const stackTrace = {
syncFragment: { frames: [mockFrame] },
asyncFragments: [],
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;

const msg = createMockMessage({ type: () => 'log', text: () => 'Logging error' });
const formatter = await ConsoleFormatter.from(msg, {
id: 201,
resolvedStackTraceForTesting: stackTrace,
});

const json = formatter.toJSON();

Check failure on line 766 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check correct format

'json' is assigned a value but never used. Allowed unused vars must match /^_/u

Check failure on line 766 in tests/formatters/ConsoleFormatter.test.ts

View workflow job for this annotation

GitHub Actions / [Required] Check docs updated

'json' is assigned a value but never used. Allowed unused vars must match /^_/u
const str = formatter.toString();
assert.ok(str.includes('at firstFunc (main.js:15:3)'), 'First stack line should appear in toJSON() output via toString() check');
});

});

Loading