Skip to content

Commit

Permalink
fix: missing stack starting from node 21+
Browse files Browse the repository at this point in the history
  • Loading branch information
Marsup committed Oct 24, 2024
1 parent 01e8086 commit 8a4266e
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
24 changes: 23 additions & 1 deletion lib/clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const Utils = require('./utils');


const internals = {
needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap])
needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap]),
structuredCloneExists: typeof structuredClone === 'function'
};


Expand Down Expand Up @@ -86,6 +87,16 @@ module.exports = internals.clone = function (obj, options = {}, _seen = null) {
continue;
}

// Can only be covered in node 21+
/* $lab:coverage:off$ */
if (internals.structuredCloneExists &&
baseProto === Types.error &&
key === 'stack') {

continue; // Already a part of the base object
}
/* $lab:coverage:on$ */

const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor) {
if (descriptor.get ||
Expand Down Expand Up @@ -160,6 +171,17 @@ internals.base = function (obj, baseProto, options) {

return newObj;
}
// Can only be covered in node 21+
/* $lab:coverage:off$ */
else if (baseProto === Types.error && internals.structuredCloneExists) {
const err = structuredClone(obj); // Needed to copy internal stack state
if (proto !== baseProto) {
Object.setPrototypeOf(err, proto); // Fix prototype
}

return err;
}
/* $lab:coverage:on$ */

if (internals.needsProtoHack.has(baseProto)) {
const newObj = new proto.constructor();
Expand Down
53 changes: 53 additions & 0 deletions test/clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,59 @@ describe('clone()', () => {
expect(b).to.not.shallow.equal(a);
});

it('clones Error', () => {

class CustomError extends Error {
name = 'CustomError';
}

const a = new CustomError('bad');
a.test = Symbol('test');

const b = Hoek.clone(a);

expect(b).to.equal(a);
expect(b).to.not.shallow.equal(a);
expect(b).to.be.instanceOf(CustomError);
expect(b.stack).to.equal(a.stack); // Explicitly validate the .stack getters
});

it('clones Error with cause', { skip: process.version.startsWith('v14') }, () => {

const a = new TypeError('bad', { cause: new Error('embedded') });
const b = Hoek.clone(a);

expect(b).to.equal(a);
expect(b).to.not.shallow.equal(a);
expect(b).to.be.instanceOf(TypeError);
expect(b.stack).to.equal(a.stack); // Explicitly validate the .stack getters
expect(b.cause.stack).to.equal(a.cause.stack); // Explicitly validate the .stack getters
});

it('clones Error with error message', () => {

const a = new Error();
a.message = new Error('message');

const b = Hoek.clone(a);

//expect(b).to.equal(a); // deepEqual() always compares message using ===
expect(b.message).to.equal(a.message);
expect(b.message).to.not.shallow.equal(a.message);
expect(b.stack).to.equal(a.stack);
});

it('cloned Error handles late stack update', () => {

const a = new Error('bad');
const b = Hoek.clone(a);

a.stack = 'late update';

expect(b).to.equal(a);
expect(b.stack).to.not.equal(a.stack);
});

it('ignores symbols', () => {

const sym = Symbol();
Expand Down

0 comments on commit 8a4266e

Please sign in to comment.