From cbd7437d40c6d58c475938e45ffa4265dbbc5ac0 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Fri, 11 Jun 2021 18:06:20 +0100 Subject: LibJS: Implement AggregateError --- Userland/Libraries/LibJS/CMakeLists.txt | 3 ++ Userland/Libraries/LibJS/Forward.h | 31 ++++++------ .../Libraries/LibJS/Runtime/AggregateError.cpp | 29 +++++++++++ Userland/Libraries/LibJS/Runtime/AggregateError.h | 23 +++++++++ .../LibJS/Runtime/AggregateErrorConstructor.cpp | 50 ++++++++++++++++++ .../LibJS/Runtime/AggregateErrorConstructor.h | 28 ++++++++++ .../LibJS/Runtime/AggregateErrorPrototype.cpp | 27 ++++++++++ .../LibJS/Runtime/AggregateErrorPrototype.h | 22 ++++++++ .../Libraries/LibJS/Runtime/CommonPropertyNames.h | 1 + Userland/Libraries/LibJS/Runtime/GlobalObject.cpp | 9 ++++ .../builtins/AggregateError/AggregateError.js | 59 ++++++++++++++++++++++ .../AggregateError.prototype.message.js | 21 ++++++++ .../AggregateError.prototype.name.js | 13 +++++ 13 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 Userland/Libraries/LibJS/Runtime/AggregateError.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/AggregateError.h create mode 100644 Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.message.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.name.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 104538c481..533d663f78 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -17,6 +17,9 @@ set(SOURCES Lexer.cpp MarkupGenerator.cpp Parser.cpp + Runtime/AggregateError.cpp + Runtime/AggregateErrorConstructor.cpp + Runtime/AggregateErrorPrototype.cpp Runtime/Array.cpp Runtime/ArrayBuffer.cpp Runtime/ArrayBufferConstructor.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index f2096d0ba6..769ad374b6 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -25,21 +25,22 @@ void name([[maybe_unused]] JS::VM& vm, [[maybe_unused]] JS::GlobalObject& global_object, [[maybe_unused]] JS::Value value) // NOTE: Proxy is not included here as it doesn't have a prototype - m_proxy_constructor is initialized separately. -#define JS_ENUMERATE_NATIVE_OBJECTS_EXCLUDING_TEMPLATES \ - __JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor, void) \ - __JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \ - __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \ - __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \ - __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \ - __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \ - __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor, void) \ - __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor, void) \ - __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor, void) \ - __JS_ENUMERATE(Promise, promise, PromisePrototype, PromiseConstructor, void) \ - __JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor, void) \ - __JS_ENUMERATE(Set, set, SetPrototype, SetConstructor, void) \ - __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor, void) \ - __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor, void) \ +#define JS_ENUMERATE_NATIVE_OBJECTS_EXCLUDING_TEMPLATES \ + __JS_ENUMERATE(AggregateError, aggregate_error, AggregateErrorPrototype, AggregateErrorConstructor, void) \ + __JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor, void) \ + __JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \ + __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \ + __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \ + __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \ + __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \ + __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor, void) \ + __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor, void) \ + __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor, void) \ + __JS_ENUMERATE(Promise, promise, PromisePrototype, PromiseConstructor, void) \ + __JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor, void) \ + __JS_ENUMERATE(Set, set, SetPrototype, SetConstructor, void) \ + __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor, void) \ + __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor, void) \ __JS_ENUMERATE(WeakSet, weak_set, WeakSetPrototype, WeakSetConstructor, void) #define JS_ENUMERATE_NATIVE_OBJECTS \ diff --git a/Userland/Libraries/LibJS/Runtime/AggregateError.cpp b/Userland/Libraries/LibJS/Runtime/AggregateError.cpp new file mode 100644 index 0000000000..7b8110c127 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AggregateError.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS { + +AggregateError* AggregateError::create(GlobalObject& global_object, String const& message, Vector const& errors) +{ + 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; +} + +AggregateError::AggregateError(Object& prototype) + : Object(prototype) +{ +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/AggregateError.h b/Userland/Libraries/LibJS/Runtime/AggregateError.h new file mode 100644 index 0000000000..9160d38fee --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AggregateError.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS { + +class AggregateError : public Object { + JS_OBJECT(Error, Object); + +public: + static AggregateError* create(GlobalObject&, String const& message, Vector const& errors); + + explicit AggregateError(Object& prototype); + virtual ~AggregateError() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp new file mode 100644 index 0000000000..d7e8e74dd3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace JS { + +AggregateErrorConstructor::AggregateErrorConstructor(GlobalObject& global_object) + : NativeFunction(*static_cast(global_object.error_constructor())) +{ +} + +void AggregateErrorConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.aggregate_error_prototype(), 0); + define_property(vm.names.length, Value(2), Attribute::Configurable); +} + +Value AggregateErrorConstructor::call() +{ + return construct(*this); +} + +// 20.5.7.1 The AggregateError Constructor, https://tc39.es/ecma262/#sec-aggregate-error-constructor +Value AggregateErrorConstructor::construct(Function&) +{ + auto& vm = this->vm(); + String message; + if (!vm.argument(1).is_undefined()) { + message = vm.argument(1).to_string(global_object()); + 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); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.h b/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.h new file mode 100644 index 0000000000..8353ea3d84 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS { + +class AggregateErrorConstructor final : public NativeFunction { + JS_OBJECT(AggregateErrorConstructor, NativeFunction); + +public: + explicit AggregateErrorConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~AggregateErrorConstructor() override = default; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.cpp new file mode 100644 index 0000000000..c90390337a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS { + +AggregateErrorPrototype::AggregateErrorPrototype(GlobalObject& global_object) + : Object(*global_object.error_prototype()) +{ +} + +void AggregateErrorPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_property(vm.names.name, js_string(vm, "AggregateError"), attr); + define_property(vm.names.message, js_string(vm, ""), attr); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.h b/Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.h new file mode 100644 index 0000000000..2820175dfc --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS { + +class AggregateErrorPrototype final : public Object { + JS_OBJECT(AggregateErrorPrototype, Object); + +public: + explicit AggregateErrorPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~AggregateErrorPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index d0254ec4e9..cf7db4f0ba 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -100,6 +100,7 @@ namespace JS { P(entries) \ P(enumerable) \ P(error) \ + P(errors) \ P(escape) \ P(eval) \ P(every) \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 1f6f251aaf..b1c440af9e 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -94,6 +96,9 @@ void GlobalObject::initialize_global_object() Object::set_prototype(m_object_prototype); + // This must be initialized before allocating AggregateErrorPrototype, which uses ErrorPrototype as its prototype. + m_error_prototype = heap().allocate(*this, *this); + #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ if (!m_##snake_name##_prototype) \ m_##snake_name##_prototype = heap().allocate(*this, *this); @@ -130,6 +135,10 @@ void GlobalObject::initialize_global_object() define_property(vm.names.JSON, heap().allocate(*this, *this), attr); define_property(vm.names.Reflect, heap().allocate(*this, *this), attr); + // This must be initialized before allocating AggregateErrorConstructor, which uses ErrorConstructor as its prototype. + initialize_constructor(vm.names.Error, m_error_constructor, m_error_prototype); + + add_constructor(vm.names.AggregateError, m_aggregate_error_constructor, m_aggregate_error_prototype); add_constructor(vm.names.Array, m_array_constructor, m_array_prototype); add_constructor(vm.names.ArrayBuffer, m_array_buffer_constructor, m_array_buffer_prototype); add_constructor(vm.names.BigInt, m_bigint_constructor, m_bigint_prototype); diff --git a/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js new file mode 100644 index 0000000000..de73e18e3a --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js @@ -0,0 +1,59 @@ +describe("errors", () => { + test("first argument must be coercible to object", () => { + expect(() => { + new AggregateError(); + }).toThrowWithMessage(TypeError, "ToObject on null or undefined"); + }); + + test("@@iterator throws", () => { + expect(() => { + new AggregateError({ + [Symbol.iterator]() { + throw Error("oops!"); + }, + }); + }).toThrowWithMessage(Error, "oops!"); + }); +}); + +describe("normal behavior", () => { + test("length is 2", () => { + expect(AggregateError).toHaveLength(2); + }); + + test("name is AggregateError", () => { + expect(AggregateError.name).toBe("AggregateError"); + }); + + test("Prototype of the AggregateError constructor is the Error constructor", () => { + expect(Object.getPrototypeOf(AggregateError)).toBe(Error); + }); + + test("Prototype of AggregateError.prototype is Error.prototype", () => { + expect(Object.getPrototypeOf(AggregateError.prototype)).toBe(Error.prototype); + }); + + test("basic functionality", () => { + expect(AggregateError([])).toBeInstanceOf(AggregateError); + expect(new AggregateError([])).toBeInstanceOf(AggregateError); + expect(new AggregateError([]).toString()).toBe("AggregateError"); + expect(new AggregateError([], "Foo").toString()).toBe("AggregateError: Foo"); + expect(new AggregateError([]).hasOwnProperty("errors")).toBeTrue(); + const errors = [1, 2, 3]; + expect(new AggregateError(errors).errors).toEqual(errors); + expect(new AggregateError(errors).errors).not.toBe(errors); + expect( + new AggregateError({ + [Symbol.iterator]: (i = 0) => ({ + next() { + if (i < 3) { + i++; + return { value: i }; + } + return { value: null, done: true }; + }, + }), + }).errors + ).toEqual(errors); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.message.js b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.message.js new file mode 100644 index 0000000000..ed1d53242d --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.message.js @@ -0,0 +1,21 @@ +describe("normal behavior", () => { + test("initial message value is empty string", () => { + expect(AggregateError.prototype.message).toBe(""); + }); + + test("Error gets message via prototype by default", () => { + const error = new AggregateError([]); + expect(error.hasOwnProperty("message")).toBeFalse(); + expect(error.message).toBe(""); + AggregateError.prototype.message = "Well hello friends"; + expect(error.message).toBe("Well hello friends"); + }); + + test("Error gets message via object if given to constructor", () => { + const error = new AggregateError([], "Custom error message"); + expect(error.hasOwnProperty("message")).toBeTrue(); + expect(error.message).toBe("Custom error message"); + AggregateError.prototype.message = "Well hello friends"; + expect(error.message).toBe("Custom error message"); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.name.js b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.name.js new file mode 100644 index 0000000000..eb0600d37c --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.name.js @@ -0,0 +1,13 @@ +describe("normal behavior", () => { + test("initial name value is type name", () => { + expect(AggregateError.prototype.name).toBe("AggregateError"); + }); + + test("Error gets name via prototype", () => { + const error = new AggregateError([]); + expect(error.hasOwnProperty("name")).toBeFalse(); + expect(error.name).toBe("AggregateError"); + AggregateError.prototype.name = "Foo"; + expect(error.name).toBe("Foo"); + }); +}); -- cgit v1.2.3