summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2021-06-11 18:06:20 +0100
committerLinus Groh <mail@linusgroh.de>2021-06-11 18:49:50 +0100
commitcbd7437d40c6d58c475938e45ffa4265dbbc5ac0 (patch)
tree26a977d1addf64c514bf42516def408ca4f12bcb
parent2f03eb8628dbb2d6c643c09309d92237dd0152fc (diff)
downloadserenity-cbd7437d40c6d58c475938e45ffa4265dbbc5ac0.zip
LibJS: Implement AggregateError
-rw-r--r--Userland/Libraries/LibJS/CMakeLists.txt3
-rw-r--r--Userland/Libraries/LibJS/Forward.h31
-rw-r--r--Userland/Libraries/LibJS/Runtime/AggregateError.cpp29
-rw-r--r--Userland/Libraries/LibJS/Runtime/AggregateError.h23
-rw-r--r--Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.cpp50
-rw-r--r--Userland/Libraries/LibJS/Runtime/AggregateErrorConstructor.h28
-rw-r--r--Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.cpp27
-rw-r--r--Userland/Libraries/LibJS/Runtime/AggregateErrorPrototype.h22
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.cpp9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.js59
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.message.js21
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/AggregateError/AggregateError.prototype.name.js13
13 files changed, 301 insertions, 15 deletions
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 <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AggregateError.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+AggregateError* AggregateError::create(GlobalObject& global_object, String const& message, Vector<Value> const& errors)
+{
+ auto& vm = global_object.vm();
+ auto* error = global_object.heap().allocate<AggregateError>(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 <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class AggregateError : public Object {
+ JS_OBJECT(Error, Object);
+
+public:
+ static AggregateError* create(GlobalObject&, String const& message, Vector<Value> 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 <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AggregateError.h>
+#include <LibJS/Runtime/AggregateErrorConstructor.h>
+#include <LibJS/Runtime/ErrorConstructor.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+
+namespace JS {
+
+AggregateErrorConstructor::AggregateErrorConstructor(GlobalObject& global_object)
+ : NativeFunction(*static_cast<Object*>(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 <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+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 <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AggregateErrorPrototype.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+
+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 <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+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 <LibJS/Interpreter.h>
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
+#include <LibJS/Runtime/AggregateErrorConstructor.h>
+#include <LibJS/Runtime/AggregateErrorPrototype.h>
#include <LibJS/Runtime/ArrayBufferConstructor.h>
#include <LibJS/Runtime/ArrayBufferPrototype.h>
#include <LibJS/Runtime/ArrayConstructor.h>
@@ -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<ErrorPrototype>(*this, *this);
+
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
if (!m_##snake_name##_prototype) \
m_##snake_name##_prototype = heap().allocate<PrototypeName>(*this, *this);
@@ -130,6 +135,10 @@ void GlobalObject::initialize_global_object()
define_property(vm.names.JSON, heap().allocate<JSONObject>(*this, *this), attr);
define_property(vm.names.Reflect, heap().allocate<ReflectObject>(*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");
+ });
+});