Skip to content
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');
});
});
});