mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:57:45 +00:00
LibJS: Implement the Error Cause proposal
Currently stage 3. https://github.com/tc39/proposal-error-cause
This commit is contained in:
parent
8d77a3297a
commit
862ba64037
11 changed files with 130 additions and 42 deletions
|
@ -10,15 +10,9 @@
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
AggregateError* AggregateError::create(GlobalObject& global_object, String const& message, Vector<Value> const& errors)
|
AggregateError* AggregateError::create(GlobalObject& global_object)
|
||||||
{
|
{
|
||||||
auto& vm = global_object.vm();
|
return global_object.heap().allocate<AggregateError>(global_object, *global_object.aggregate_error_prototype());
|
||||||
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)
|
AggregateError::AggregateError(Object& prototype)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class AggregateError : public Object {
|
||||||
JS_OBJECT(Error, Object);
|
JS_OBJECT(Error, Object);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static AggregateError* create(GlobalObject&, String const& message, Vector<Value> const& errors);
|
static AggregateError* create(GlobalObject&);
|
||||||
|
|
||||||
explicit AggregateError(Object& prototype);
|
explicit AggregateError(Object& prototype);
|
||||||
virtual ~AggregateError() override = default;
|
virtual ~AggregateError() override = default;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <LibJS/Runtime/AggregateError.h>
|
#include <LibJS/Runtime/AggregateError.h>
|
||||||
#include <LibJS/Runtime/AggregateErrorConstructor.h>
|
#include <LibJS/Runtime/AggregateErrorConstructor.h>
|
||||||
|
#include <LibJS/Runtime/Array.h>
|
||||||
#include <LibJS/Runtime/ErrorConstructor.h>
|
#include <LibJS/Runtime/ErrorConstructor.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/IteratorOperations.h>
|
#include <LibJS/Runtime/IteratorOperations.h>
|
||||||
|
@ -34,17 +35,29 @@ Value AggregateErrorConstructor::call()
|
||||||
Value AggregateErrorConstructor::construct(Function&)
|
Value AggregateErrorConstructor::construct(Function&)
|
||||||
{
|
{
|
||||||
auto& vm = this->vm();
|
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()) {
|
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())
|
if (vm.exception())
|
||||||
return {};
|
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));
|
auto errors_list = iterable_to_list(global_object(), vm.argument(0));
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ namespace JS {
|
||||||
P(byteOffset) \
|
P(byteOffset) \
|
||||||
P(call) \
|
P(call) \
|
||||||
P(callee) \
|
P(callee) \
|
||||||
|
P(cause) \
|
||||||
P(cbrt) \
|
P(cbrt) \
|
||||||
P(ceil) \
|
P(ceil) \
|
||||||
P(charAt) \
|
P(charAt) \
|
||||||
|
|
|
@ -10,14 +10,17 @@
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
Error* Error::create(GlobalObject& global_object, const String& message)
|
Error* Error::create(GlobalObject& global_object)
|
||||||
|
{
|
||||||
|
return global_object.heap().allocate<Error>(global_object, *global_object.error_prototype());
|
||||||
|
}
|
||||||
|
|
||||||
|
Error* Error::create(GlobalObject& global_object, String const& message)
|
||||||
{
|
{
|
||||||
auto& vm = global_object.vm();
|
auto& vm = global_object.vm();
|
||||||
auto* error = global_object.heap().allocate<Error>(global_object, *global_object.error_prototype());
|
auto* error = Error::create(global_object);
|
||||||
if (!message.is_null()) {
|
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
error->define_property(vm.names.message, js_string(vm, message), attr);
|
||||||
error->define_property(vm.names.message, js_string(vm, message), attr);
|
|
||||||
}
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,21 +29,24 @@ Error::Error(Object& prototype)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||||
ClassName* ClassName::create(GlobalObject& global_object, const String& message) \
|
ClassName* ClassName::create(GlobalObject& global_object) \
|
||||||
{ \
|
{ \
|
||||||
auto& vm = global_object.vm(); \
|
return global_object.heap().allocate<ClassName>(global_object, *global_object.snake_name##_prototype()); \
|
||||||
auto* error = global_object.heap().allocate<ClassName>(global_object, *global_object.snake_name##_prototype()); \
|
} \
|
||||||
if (!message.is_null()) { \
|
\
|
||||||
u8 attr = Attribute::Writable | Attribute::Configurable; \
|
ClassName* ClassName::create(GlobalObject& global_object, String const& message) \
|
||||||
error->define_property(vm.names.message, js_string(vm, message), attr); \
|
{ \
|
||||||
} \
|
auto& vm = global_object.vm(); \
|
||||||
return error; \
|
auto* error = ClassName::create(global_object); \
|
||||||
} \
|
u8 attr = Attribute::Writable | Attribute::Configurable; \
|
||||||
\
|
error->define_property(vm.names.message, js_string(vm, message), attr); \
|
||||||
ClassName::ClassName(Object& prototype) \
|
return error; \
|
||||||
: Error(prototype) \
|
} \
|
||||||
{ \
|
\
|
||||||
|
ClassName::ClassName(Object& prototype) \
|
||||||
|
: Error(prototype) \
|
||||||
|
{ \
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_ENUMERATE_NATIVE_ERRORS
|
JS_ENUMERATE_NATIVE_ERRORS
|
||||||
|
|
|
@ -16,7 +16,8 @@ class Error : public Object {
|
||||||
JS_OBJECT(Error, Object);
|
JS_OBJECT(Error, Object);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Error* create(GlobalObject&, const String& message = {});
|
static Error* create(GlobalObject&);
|
||||||
|
static Error* create(GlobalObject&, String const& message);
|
||||||
|
|
||||||
explicit Error(Object& prototype);
|
explicit Error(Object& prototype);
|
||||||
virtual ~Error() override = default;
|
virtual ~Error() override = default;
|
||||||
|
@ -30,7 +31,8 @@ public:
|
||||||
JS_OBJECT(ClassName, Error); \
|
JS_OBJECT(ClassName, Error); \
|
||||||
\
|
\
|
||||||
public: \
|
public: \
|
||||||
static ClassName* create(GlobalObject&, const String& message = {}); \
|
static ClassName* create(GlobalObject&); \
|
||||||
|
static ClassName* create(GlobalObject&, String const& message); \
|
||||||
\
|
\
|
||||||
explicit ClassName(Object& prototype); \
|
explicit ClassName(Object& prototype); \
|
||||||
virtual ~ClassName() override = default; \
|
virtual ~ClassName() override = default; \
|
||||||
|
|
|
@ -31,13 +31,23 @@ Value ErrorConstructor::call()
|
||||||
Value ErrorConstructor::construct(Function&)
|
Value ErrorConstructor::construct(Function&)
|
||||||
{
|
{
|
||||||
auto& vm = this->vm();
|
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()) {
|
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())
|
if (vm.exception())
|
||||||
return {};
|
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) \
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||||
|
@ -64,13 +74,24 @@ Value ErrorConstructor::construct(Function&)
|
||||||
Value ConstructorName::construct(Function&) \
|
Value ConstructorName::construct(Function&) \
|
||||||
{ \
|
{ \
|
||||||
auto& vm = this->vm(); \
|
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()) { \
|
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()) \
|
if (vm.exception()) \
|
||||||
return {}; \
|
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
|
JS_ENUMERATE_NATIVE_ERRORS
|
||||||
|
|
|
@ -1015,6 +1015,21 @@ Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const
|
||||||
return {};
|
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<MarkedValueList> arguments)
|
Value Object::invoke_internal(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments)
|
||||||
{
|
{
|
||||||
auto& vm = this->vm();
|
auto& vm = this->vm();
|
||||||
|
|
|
@ -129,6 +129,8 @@ public:
|
||||||
IndexedProperties& indexed_properties() { return m_indexed_properties; }
|
IndexedProperties& indexed_properties() { return m_indexed_properties; }
|
||||||
void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); }
|
void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); }
|
||||||
|
|
||||||
|
void install_error_cause(Value options);
|
||||||
|
|
||||||
[[nodiscard]] Value invoke_internal(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments);
|
[[nodiscard]] Value invoke_internal(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments);
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
|
|
|
@ -56,4 +56,11 @@ describe("normal behavior", () => {
|
||||||
}).errors
|
}).errors
|
||||||
).toEqual(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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,4 +31,31 @@ describe("normal behavior", () => {
|
||||||
expect(TypeError()).toBeInstanceOf(TypeError);
|
expect(TypeError()).toBeInstanceOf(TypeError);
|
||||||
expect(new 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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue