Skip to content

Commit

Permalink
Add support for cloning Errors
Browse files Browse the repository at this point in the history
  • Loading branch information
johanholmerin committed Jul 28, 2021
1 parent 125c2ee commit 05569e3
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 4 deletions.
19 changes: 19 additions & 0 deletions index.js
Expand Up @@ -20,6 +20,7 @@ const SUPPORTS_DOMQUAD = typeof DOMQuad !== "undefined";
const SUPPORTS_DOMRECT = typeof DOMRect !== "undefined";
const SUPPORTS_SHAREDARRAYBUFFER = typeof SharedArrayBuffer !== "undefined";
const SUPPORTS_BIGINT = typeof BigInt !== "undefined";
const SUPPORTS_DOMEXCEPTION = typeof DOMException !== "undefined";

// Primitives types except Symbol
const PRIMITIVE_TYPES = ["undefined", "boolean", "number", "string", "bigint"];
Expand Down Expand Up @@ -60,6 +61,20 @@ function cloneObject(obj, set) {
set(new Number(obj));
} else if (obj instanceof RegExp) {
set(new RegExp(obj));
} else if (SUPPORTS_DOMEXCEPTION && obj instanceof DOMException) {
set(new DOMException(obj.message, obj.name));
} else if (obj instanceof Error) {
const Constructor = globalThis[obj.name] || Error;
const newError = new Constructor();
set(newError);
if (hasOwn(obj, "message")) {
const message = Object.getOwnPropertyDescriptor(obj, "message");
if ("value" in message) {
newError.message = String(obj.message);
}
}
if ("stack" in obj) newError.stack = clone(obj.stack);
if (hasOwn(obj, "cause")) newError.cause = clone(obj.cause);
} else if (SUPPORTS_BIGINT && obj instanceof BigInt) {
set(Object(obj.valueOf()));
} else if (SUPPORTS_FILE && obj instanceof File) {
Expand Down Expand Up @@ -126,6 +141,10 @@ function cloneObject(obj, set) {
}
}

function hasOwn(obj, name) {
return Object.prototype.hasOwnProperty.call(obj, name);
}

function isObject(obj) {
const proto = Object.getPrototypeOf(obj);
return (
Expand Down
176 changes: 176 additions & 0 deletions tests/error.js
@@ -0,0 +1,176 @@
import structuredClone from "../index.js";

const { describe, it, before } = intern.getPlugin("interface.bdd");
const { expect } = intern.getPlugin("chai");

describe("error", function() {
it("supports cloning errors", function() {
const error = new Error("test_error");
const clonedError = structuredClone(error);

expect(clonedError).not.to.equal(error);
expect(clonedError).to.be.an.instanceOf(Error);
expect(clonedError.constructor).to.equal(error.constructor);
expect(clonedError.message).to.equal(error.message);
expect(clonedError.name).to.equal(error.name);
expect(clonedError.stack).to.equal(error.stack);
});

it("Empty Error objects can be cloned", function() {
const error = new Error();
expect(error.hasOwnProperty("message")).to.be.false;
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(Error.prototype);
expect(clonedError.constructor).equal(Error);
expect(clonedError.name).equal("Error");
expect(clonedError.hasOwnProperty("message")).to.be.false;
expect(clonedError.foo).equal(undefined);
});

it("Error objects can be cloned", function() {
const error = Error("some message");
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(Error.prototype);
expect(clonedError.constructor).equal(Error);
expect(clonedError.name).equal("Error");
expect(clonedError.message).equal("some message");
expect(clonedError.foo).equal(undefined);
});

it("EvalError objects can be cloned", function() {
const error = EvalError("some message");
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(EvalError.prototype);
expect(clonedError.constructor).equal(EvalError);
expect(clonedError.name).equal("EvalError");
expect(clonedError.message).equal("some message");
expect(clonedError.foo).equal(undefined);
});

it("RangeError objects can be cloned", function() {
const error = RangeError("some message");
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(RangeError.prototype);
expect(clonedError.constructor).equal(RangeError);
expect(clonedError.name).equal("RangeError");
expect(clonedError.message).equal("some message");
expect(clonedError.foo).equal(undefined);
});

it("ReferenceError objects can be cloned", function() {
const error = ReferenceError("some message");
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(ReferenceError.prototype);
expect(clonedError.constructor).equal(ReferenceError);
expect(clonedError.name).equal("ReferenceError");
expect(clonedError.message).equal("some message");
expect(clonedError.foo).equal(undefined);
});

it("SyntaxError objects can be cloned", function() {
const error = SyntaxError("some message");
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(SyntaxError.prototype);
expect(clonedError.constructor).equal(SyntaxError);
expect(clonedError.name).equal("SyntaxError");
expect(clonedError.message).equal("some message");
expect(clonedError.foo).equal(undefined);
});

it("TypeError objects can be cloned", function() {
const error = TypeError("some message");
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(TypeError.prototype);
expect(clonedError.constructor).equal(TypeError);
expect(clonedError.name).equal("TypeError");
expect(clonedError.message).equal("some message");
expect(clonedError.foo).equal(undefined);
});

it("URIError objects can be cloned", function() {
const error = URIError("some message");
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(URIError.prototype);
expect(clonedError.constructor).equal(URIError);
expect(clonedError.name).equal("URIError");
expect(clonedError.message).equal("some message");
expect(clonedError.foo).equal(undefined);
});

it("Cloning a modified Error", function() {
const error = URIError("some message");
Object.setPrototypeOf(error, SyntaxError.prototype);
error.message = { toString: () => "another message" };
error.constructor = RangeError;
error.name = "TypeError";
error.foo = "bar";
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(TypeError.prototype);
expect(clonedError.constructor).equal(TypeError);
expect(clonedError.name).equal("TypeError");
expect(clonedError.message).equal("another message");
expect(clonedError.foo).equal(undefined);
});

it("Error.message: getter is ignored when cloning", function() {
const error = Error();
Object.defineProperty(error, "message", { get: () => "hello" });
expect(error.message).equal("hello");
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(Error.prototype);
expect(clonedError.constructor).equal(Error);
expect(clonedError.name).equal("Error");
expect(clonedError.hasOwnProperty("message")).to.be.false;
expect(clonedError.foo).equal(undefined);
});

it("Error.message: undefined property is stringified", function() {
const error = Error();
error.message = undefined;
expect(error.message).equal(undefined);
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(Error.prototype);
expect(clonedError.constructor).equal(Error);
expect(clonedError.name).equal("Error");
expect(clonedError.message).equal("undefined");
expect(clonedError.foo).equal(undefined);
});

describe("DOMException", function() {
before(function(suite) {
if (typeof DOMException === "undefined") {
suite.skip();
}
});

it("objects can be cloned", function() {
const error = new DOMException("some message", "IndexSizeError");
const clonedError = structuredClone(error);

expect(Object.getPrototypeOf(clonedError)).equal(DOMException.prototype);
expect(clonedError.constructor).equal(DOMException);
expect(clonedError.name).equal("IndexSizeError");
expect(clonedError.message).equal("some message");
expect(clonedError.code).equal(DOMException.INDEX_SIZE_ERR);
expect(clonedError.foo).equal(undefined);
});
});
});
4 changes: 0 additions & 4 deletions tests/uncloneable.js
Expand Up @@ -12,10 +12,6 @@ describe("uncloneables", function() {
expect(() => structuredClone(function() {})).to.throw();
});

it("can not clone a Error", function() {
expect(() => structuredClone(new Error())).to.throw();
});

it("can not clone a Symbol", function() {
expect(() => structuredClone(new Symbol())).to.throw();
});
Expand Down

0 comments on commit 05569e3

Please sign in to comment.