Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt stackWithCauses() to node.js output #23

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
20 changes: 14 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ class ErrorWithCause extends Error { // linemod-prefix-with: export
* @param {string} message
* @param {{ cause?: T }} [options]
*/
constructor (message, { cause } = {}) {
constructor (message, options) {
super(message);

/** @type {string} */
this.name = ErrorWithCause.name;
if (cause) {
/** @type {T} */
this.cause = cause;
if (options && Object.prototype.hasOwnProperty.call(options, 'cause')) {
/** @type {T|undefined} */
this.cause = options.cause;
}
/** @type {string} */
this.message = message;
Expand Down Expand Up @@ -103,11 +103,19 @@ const _stackWithCauses = (err, seen) => {

const cause = getErrorCause(err);

// TODO: Follow up in https://github.com/nodejs/node/issues/38725#issuecomment-920309092 on how to log stuff

if (cause) {
seen.add(err);
return (stack + '\ncaused by: ' + _stackWithCauses(cause, seen));
} else if (Object.prototype.hasOwnProperty.call(err, 'cause')) {
/** @type {string} */
let stringified;
try {
// @ts-ignore
stringified = JSON.stringify(err.cause);
} catch {
stringified = '<failed to stringify value>';
}
return (stack + '\ncaused by: ' + stringified);
} else {
return stack;
}
Expand Down
13 changes: 13 additions & 0 deletions test/error.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,17 @@ describe('ErrorWithCause', () => {
const err = new ErrorWithCause('Foo');
err.should.have.property('stack').that.is.a('string').which.startsWith('ErrorWithCause: Foo\n');
});

it('should set cause property when given undefined cause', () => {
(new ErrorWithCause('Foo', { cause: undefined })).should.have.property('cause', undefined);
});

it('should set cause property when given null cause', () => {
// eslint-disable-next-line unicorn/no-null
(new ErrorWithCause('Foo', { cause: null })).should.have.property('cause', null);
});

it('should set cause property when given false cause', () => {
(new ErrorWithCause('Foo', { cause: false })).should.have.property('cause', false);
});
});
50 changes: 50 additions & 0 deletions test/stack.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,54 @@ describe('stackWithCauses()', () => {
const result = stackWithCauses(err);
result.should.equal('xyz789\ncaused by: abc123\ncaused by: xyz789\ncauses have become circular...');
});

describe('should append non-Error causes to the end of the cause trail', () => {
it('for string causes', () => {
const err = new ErrorWithCause('foo', { cause: 'string cause' });
err.stack = 'xyz789';
stackWithCauses(err).should.equal('xyz789\ncaused by: "string cause"');
});

it('for number causes', () => {
const err = new ErrorWithCause('foo', { cause: 123 });
err.stack = 'xyz789';
stackWithCauses(err).should.equal('xyz789\ncaused by: 123');
});

it('for non-Error object causes', () => {
const err = new ErrorWithCause('foo', { cause: { name: 'TypeError', message: 'foo' } });
err.stack = 'xyz789';
stackWithCauses(err).should.equal('xyz789\ncaused by: {"name":"TypeError","message":"foo"}');
});

it('should not throw for circular non-Error object causes', () => {
const firstCause = { first: true };
const secondCause = { second: true, firstCause };

// @ts-ignore
firstCause.secondCause = secondCause;

const err = new ErrorWithCause('foo', { cause: firstCause });
err.stack = 'xyz789';
stackWithCauses(err).should.equal('xyz789\ncaused by: <failed to stringify value>');
});

// Copied from https://github.com/nodejs/node/blob/5e6f9c3e346b196ab299a3fce485d7aa5fbf3802/test/parallel/test-util-inspect.js#L663-L677
it('for falsy causes', () => {
const falsyCause1 = new ErrorWithCause('', { cause: false });
delete falsyCause1.stack;

// @ts-ignore
// eslint-disable-next-line unicorn/no-null
const falsyCause2 = new ErrorWithCause(undefined, { cause: null });
falsyCause2.stack = '';

const undefinedCause = new ErrorWithCause('', { cause: undefined });
undefinedCause.stack = '';

stackWithCauses(falsyCause1).should.equal('\ncaused by: false');
stackWithCauses(falsyCause2).should.equal('\ncaused by: null');
stackWithCauses(undefinedCause).should.equal('\ncaused by: undefined');
});
});
});