diff --git a/Userland/Libraries/LibJS/Runtime/AggregateError.cpp b/Userland/Libraries/LibJS/Runtime/AggregateError.cpp index 7b8110c127..98e62fe894 100644 --- a/Userland/Libraries/LibJS/Runtime/AggregateError.cpp +++ b/Userland/Libraries/LibJS/Runtime/AggregateError.cpp @@ -10,15 +10,9 @@ namespace JS { -AggregateError* AggregateError::create(GlobalObject& global_object, String const& message, Vector const& errors) +AggregateError* AggregateError::create(GlobalObject& global_object) { - auto& vm = global_object.vm(); - auto* error = global_object.heap().allocate(global_object, *global_object.aggregate_error_prototype()); - u8 attr = Attribute::Writable | Attribute::Configurable; - if (!message.is_null()) - error->define_property(vm.names.message, js_string(vm, message), attr); - error->define_property(vm.names.errors, Array::create_from(global_object, errors), attr); - return error; + return global_object.heap().allocate(global_object, *global_object.aggregate_error_prototype()); } AggregateError::AggregateError(Object& prototype) diff --git a/Userland/Libraries/LibJS/Runtime/AggregateError.h b/Userland/Libraries/LibJS/Runtime/AggregateError.h index 9160d38fee..818f527b3a 100644 --- a/Userland/Libraries/LibJS/Runtime/AggregateError.h +++ b/Userland/Libraries/LibJS/Runtime/AggregateError.h @@ -14,7 +14,7 @@ class AggregateError : public Object { JS_OBJECT(Error, Object); public: - static AggregateError* create(GlobalObject&, String const& message, Vector const& errors); + static AggregateError* create(GlobalObject&); explicit AggregateError(Object& prototype); virtual ~AggregateError() override = default; diff --git a/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp index d7e8e74dd3..233de0db29 100644 --- a/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -34,17 +35,29 @@ Value AggregateErrorConstructor::call() Value AggregateErrorConstructor::construct(Function&) { auto& vm = this->vm(); - String message; + // FIXME: Use OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%") + auto* aggregate_error = AggregateError::create(global_object()); + + u8 attr = Attribute::Writable | Attribute::Configurable; + if (!vm.argument(1).is_undefined()) { - message = vm.argument(1).to_string(global_object()); + auto message = vm.argument(1).to_string(global_object()); if (vm.exception()) return {}; + aggregate_error->define_property(vm.names.message, js_string(vm, message), attr); } + + aggregate_error->install_error_cause(vm.argument(2)); + if (vm.exception()) + return {}; + auto errors_list = iterable_to_list(global_object(), vm.argument(0)); if (vm.exception()) return {}; - // FIXME: 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »). - return AggregateError::create(global_object(), message, errors_list); + + aggregate_error->define_property(vm.names.errors, Array::create_from(global_object(), errors_list), attr); + + return aggregate_error; } } diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index cf7db4f0ba..cac94e060c 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -69,6 +69,7 @@ namespace JS { P(byteOffset) \ P(call) \ P(callee) \ + P(cause) \ P(cbrt) \ P(ceil) \ P(charAt) \ diff --git a/Userland/Libraries/LibJS/Runtime/Error.cpp b/Userland/Libraries/LibJS/Runtime/Error.cpp index 081077463f..c2fb594ce5 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.cpp +++ b/Userland/Libraries/LibJS/Runtime/Error.cpp @@ -10,14 +10,17 @@ namespace JS { -Error* Error::create(GlobalObject& global_object, const String& message) +Error* Error::create(GlobalObject& global_object) +{ + return global_object.heap().allocate(global_object, *global_object.error_prototype()); +} + +Error* Error::create(GlobalObject& global_object, String const& message) { auto& vm = global_object.vm(); - auto* error = global_object.heap().allocate(global_object, *global_object.error_prototype()); - if (!message.is_null()) { - u8 attr = Attribute::Writable | Attribute::Configurable; - error->define_property(vm.names.message, js_string(vm, message), attr); - } + auto* error = Error::create(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + error->define_property(vm.names.message, js_string(vm, message), attr); return error; } @@ -26,21 +29,24 @@ Error::Error(Object& prototype) { } -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ - ClassName* ClassName::create(GlobalObject& global_object, const String& message) \ - { \ - auto& vm = global_object.vm(); \ - auto* error = global_object.heap().allocate(global_object, *global_object.snake_name##_prototype()); \ - if (!message.is_null()) { \ - u8 attr = Attribute::Writable | Attribute::Configurable; \ - error->define_property(vm.names.message, js_string(vm, message), attr); \ - } \ - return error; \ - } \ - \ - ClassName::ClassName(Object& prototype) \ - : Error(prototype) \ - { \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ClassName* ClassName::create(GlobalObject& global_object) \ + { \ + return global_object.heap().allocate(global_object, *global_object.snake_name##_prototype()); \ + } \ + \ + ClassName* ClassName::create(GlobalObject& global_object, String const& message) \ + { \ + auto& vm = global_object.vm(); \ + auto* error = ClassName::create(global_object); \ + u8 attr = Attribute::Writable | Attribute::Configurable; \ + error->define_property(vm.names.message, js_string(vm, message), attr); \ + return error; \ + } \ + \ + ClassName::ClassName(Object& prototype) \ + : Error(prototype) \ + { \ } JS_ENUMERATE_NATIVE_ERRORS diff --git a/Userland/Libraries/LibJS/Runtime/Error.h b/Userland/Libraries/LibJS/Runtime/Error.h index c0fe793035..774a376dd5 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.h +++ b/Userland/Libraries/LibJS/Runtime/Error.h @@ -16,7 +16,8 @@ class Error : public Object { JS_OBJECT(Error, Object); public: - static Error* create(GlobalObject&, const String& message = {}); + static Error* create(GlobalObject&); + static Error* create(GlobalObject&, String const& message); explicit Error(Object& prototype); virtual ~Error() override = default; @@ -30,7 +31,8 @@ public: JS_OBJECT(ClassName, Error); \ \ public: \ - static ClassName* create(GlobalObject&, const String& message = {}); \ + static ClassName* create(GlobalObject&); \ + static ClassName* create(GlobalObject&, String const& message); \ \ explicit ClassName(Object& prototype); \ virtual ~ClassName() override = default; \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp index f7eae02791..bae4f731e7 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp @@ -31,13 +31,23 @@ Value ErrorConstructor::call() Value ErrorConstructor::construct(Function&) { auto& vm = this->vm(); - String message; + // FIXME: Use OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%") + auto* error = Error::create(global_object()); + + u8 attr = Attribute::Writable | Attribute::Configurable; + if (!vm.argument(0).is_undefined()) { - message = vm.argument(0).to_string(global_object()); + auto message = vm.argument(0).to_string(global_object()); if (vm.exception()) return {}; + error->define_property(vm.names.message, js_string(vm, message), attr); } - return Error::create(global_object(), message); + + error->install_error_cause(vm.argument(1)); + if (vm.exception()) + return {}; + + return error; } #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ @@ -64,13 +74,24 @@ Value ErrorConstructor::construct(Function&) Value ConstructorName::construct(Function&) \ { \ auto& vm = this->vm(); \ - String message = ""; \ + /* FIXME: Use OrdinaryCreateFromConstructor( \ + * FIXME: newTarget, "%NativeError.prototype%"). */ \ + auto* error = ClassName::create(global_object()); \ + \ + u8 attr = Attribute::Writable | Attribute::Configurable; \ + \ if (!vm.argument(0).is_undefined()) { \ - message = vm.argument(0).to_string(global_object()); \ + auto message = vm.argument(0).to_string(global_object()); \ if (vm.exception()) \ return {}; \ + error->define_property(vm.names.message, js_string(vm, message), attr); \ } \ - return ClassName::create(global_object(), message); \ + \ + error->install_error_cause(vm.argument(1)); \ + if (vm.exception()) \ + return {}; \ + \ + return error; \ } JS_ENUMERATE_NATIVE_ERRORS diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 6ce6a92d4a..7935cbf358 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -1015,6 +1015,21 @@ Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const return {}; } +// 20.5.8.1 InstallErrorCause, https://tc39.es/proposal-error-cause/#sec-errorobjects-install-error-cause +void Object::install_error_cause(Value options) +{ + auto& vm = this->vm(); + if (!options.is_object()) + return; + auto& options_object = options.as_object(); + if (!options_object.has_property(vm.names.cause)) + return; + auto cause = options_object.get(vm.names.cause).value_or(js_undefined()); + if (vm.exception()) + return; + define_property(vm.names.cause, cause, Attribute::Writable | Attribute::Configurable); +} + Value Object::invoke_internal(const StringOrSymbol& property_name, Optional arguments) { auto& vm = this->vm(); diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index 307273130e..fa40ef18d9 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -129,6 +129,8 @@ public: IndexedProperties& indexed_properties() { return m_indexed_properties; } void set_indexed_property_elements(Vector&& values) { m_indexed_properties = IndexedProperties(move(values)); } + void install_error_cause(Value options); + [[nodiscard]] Value invoke_internal(const StringOrSymbol& property_name, Optional arguments); template diff --git a/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js index de73e18e3a..1578d823ad 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js +++ b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js @@ -56,4 +56,11 @@ describe("normal behavior", () => { }).errors ).toEqual(errors); }); + + test("supports options object with cause", () => { + const cause = new Error(); + const error = new AggregateError([], "test", { cause }); + expect(error.hasOwnProperty("cause")).toBeTrue(); + expect(error.cause).toBe(cause); + }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Error/Error.js b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.js index 2926e0741c..82e61674db 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Error/Error.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.js @@ -31,4 +31,31 @@ describe("normal behavior", () => { expect(TypeError()).toBeInstanceOf(TypeError); expect(new TypeError()).toBeInstanceOf(TypeError); }); + + test("supports options object with cause", () => { + const errors = [Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError]; + const cause = new Error(); + errors.forEach(T => { + const error = new T("test", { cause }); + expect(error.hasOwnProperty("cause")).toBeTrue(); + expect(error.cause).toBe(cause); + }); + }); + + test("supports options object with cause (chained)", () => { + let error; + try { + try { + throw new Error("foo"); + } catch (e) { + throw new Error("bar", { cause: e }); + } + } catch (e) { + error = new Error("baz", { cause: e }); + } + expect(error.message).toBe("baz"); + expect(error.cause.message).toBe("bar"); + expect(error.cause.cause.message).toBe("foo"); + expect(error.cause.cause.cause).toBe(undefined); + }); });