LibJS: Implement AggregateError

This commit is contained in:
Linus Groh 2021-06-11 18:06:20 +01:00
parent 2f03eb8628
commit cbd7437d40
Notes: sideshowbarker 2024-07-18 12:25:26 +09:00
13 changed files with 301 additions and 15 deletions

View file

@ -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

View file

@ -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 \

View file

@ -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)
{
}
}

View file

@ -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;
};
}

View file

@ -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);
}
}

View file

@ -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; }
};
}

View file

@ -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);
}
}

View file

@ -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;
};
}

View file

@ -100,6 +100,7 @@ namespace JS {
P(entries) \
P(enumerable) \
P(error) \
P(errors) \
P(escape) \
P(eval) \
P(every) \

View file

@ -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);

View file

@ -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);
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});