From 6462fac6f47db739bd755b935c9b7cb80ff45ceb Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 6 Aug 2019 13:28:29 +0200 Subject: [PATCH] fix: Recognize custom built ES5 era errors --- error/is.js | 27 ++++++++++++++++++++++----- test/error/is.js | 18 +++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/error/is.js b/error/is.js index 8166170..bf04c80 100644 --- a/error/is.js +++ b/error/is.js @@ -1,28 +1,45 @@ "use strict"; -var isPrototype = require("../prototype/is"); +var isPrototype = require("../prototype/is") + , isPlainObject = require("../plain-object/is"); var objectToString = Object.prototype.toString; // Recognize host specific errors (e.g. DOMException) -var errorTaggedStringRe = /^\[object .*(?:Error|Exception)\]$/; +var errorTaggedStringRe = /^\[object .*(?:Error|Exception)\]$/ + , errorNameRe = /^[^\s]*(?:Error|Exception)$/; module.exports = function (value) { if (!value) return false; + var name; // Sanity check (reject objects which do not expose common Error interface) try { + name = value.name; + if (typeof name !== "string") return false; if (typeof value.message !== "string") return false; } catch (error) { return false; } - // Ensure its a native Error object (has [[ErrorData]] slot) - // Note: There seems no 100% bulletproof way of confirming that as: + // Ensure its a native-like Error object + // (has [[ErrorData]] slot, or was created to resemble one) + // Note: It's not a 100% bulletproof check of confirming that as: // - In ES2015+ string tag can be overriden via Symbol.toStringTag property // - Host errors do not share native error tag. Still we rely on assumption that // tag for each error will end either with `Error` or `Exception` string - if (!errorTaggedStringRe.test(objectToString.call(value))) return false; + // - In pre ES2015 era, no custom errors will share the error tag. + if (!errorTaggedStringRe.test(objectToString.call(value))) { + // Definitely not an ES2015 error instance, but could still be an error + // (created via e.g. CustomError.prototype = Object.create(Error.prototype)) + try { + if (name !== value.constructor.name) return false; + } catch (error) { + return false; + } + if (!errorNameRe.test(name)) return false; + if (isPlainObject(value)) return false; + } return !isPrototype(value); }; diff --git a/test/error/is.js b/test/error/is.js index 6f640ee..12692b4 100644 --- a/test/error/is.js +++ b/test/error/is.js @@ -15,17 +15,25 @@ describe("error/is", function () { assert.equal(isError(Error.prototype), false); }); + if (typeof Object.create === "function") { + it("Should return true on custom built ES5 era error", function () { + var CustomEs5Error = function () { Error.call(this); }; + CustomEs5Error.prototype = Object.create(Error.prototype); + assert.equal(isError(new CustomEs5Error()), true); + }); + + it("Should return false on object with no prototype", function () { + assert.equal(isError(Object.create(null)), false); + }); + } + it("Should return false on plain object", function () { assert.equal(isError({}), false); }); it("Should return false on function", function () { assert.equal(isError(function () { return true; }), false); }); it("Should return false on array", function () { assert.equal(isError([]), false); }); - if (typeof Object.create === "function") { - it("Should return false on object with no prototype", function () { - assert.equal(isError(Object.create(null)), false); - }); - } + it("Should return false on string", function () { assert.equal(isError("foo"), false); }); it("Should return false on empty string", function () { assert.equal(isError(""), false); }); it("Should return false on number", function () { assert.equal(isError(123), false); });