From e4891af9709c7f604a5118c341f99bc9f19d72a9 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 10 Nov 2024 05:59:08 +1300 Subject: [PATCH] LibJS: Implement Error.isError Implementing the stage 2.7 proposal: https://github.com/tc39/proposal-is-error --- .../LibJS/Runtime/CommonPropertyNames.h | 1 + .../LibJS/Runtime/ErrorConstructor.cpp | 12 +++++++ .../LibJS/Runtime/ErrorConstructor.h | 2 ++ Userland/Libraries/LibJS/Runtime/Value.cpp | 9 ++++++ Userland/Libraries/LibJS/Runtime/Value.h | 1 + .../Tests/builtins/Error/Error.isError.js | 32 +++++++++++++++++++ 6 files changed, 57 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Error/Error.isError.js diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 4f6679f4b4f..591da6f5cd1 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -311,6 +311,7 @@ namespace JS { P(is) \ P(isArray) \ P(isDisjointFrom) \ + P(isError) \ P(isExtensible) \ P(isFinite) \ P(isFrozen) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp index 8b7786720ad..4e3b609a03f 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp @@ -27,6 +27,9 @@ void ErrorConstructor::initialize(Realm& realm) define_direct_property(vm.names.prototype, realm.intrinsics().error_prototype(), 0); define_direct_property(vm.names.length, Value(1), Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(realm, vm.names.isError, is_error, 1, attr); } // 20.5.1.1 Error ( message [ , options ] ), https://tc39.es/ecma262/#sec-error-message @@ -120,4 +123,13 @@ ThrowCompletionOr> ErrorConstructor::construct(FunctionObje JS_ENUMERATE_NATIVE_ERRORS #undef __JS_ENUMERATE +// 20.5.2.1 Error.isError ( arg ), https://tc39.es/proposal-is-error/#sec-error.iserror +JS_DEFINE_NATIVE_FUNCTION(ErrorConstructor::is_error) +{ + auto arg = vm.argument(0); + + // 1. Return IsError(arg). + return Value(arg.is_error()); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h index 29fbf37bc26..a046c05835e 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h @@ -26,6 +26,8 @@ private: explicit ErrorConstructor(Realm&); virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(is_error); }; #define DECLARE_NATIVE_ERROR_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName) \ diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp index bac62a850a1..139245df505 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.cpp +++ b/Userland/Libraries/LibJS/Runtime/Value.cpp @@ -257,6 +257,15 @@ Array& Value::as_array() return static_cast(as_object()); } +// 20.5.8.2 IsError ( argument ), https://tc39.es/proposal-is-error/#sec-iserror +bool Value::is_error() const +{ + // 1. If argument is not an Object, return false. + // 2. If argument has an [[ErrorData]] internal slot, return true. + // 3. Return false. + return is_object() && is(as_object()); +} + // 7.2.3 IsCallable ( argument ), https://tc39.es/ecma262/#sec-iscallable bool Value::is_function() const { diff --git a/Userland/Libraries/LibJS/Runtime/Value.h b/Userland/Libraries/LibJS/Runtime/Value.h index 8653d5ea57d..3d736a3ff2e 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.h +++ b/Userland/Libraries/LibJS/Runtime/Value.h @@ -148,6 +148,7 @@ public: ThrowCompletionOr is_array(VM&) const; bool is_function() const; bool is_constructor() const; + bool is_error() const; ThrowCompletionOr is_regexp(VM&) const; bool is_nan() const diff --git a/Userland/Libraries/LibJS/Tests/builtins/Error/Error.isError.js b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.isError.js new file mode 100644 index 00000000000..71df198eb61 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.isError.js @@ -0,0 +1,32 @@ +test("Error.isError length is 1", () => { + expect(Error.isError).toHaveLength(1); +}); + +test("Error.isError arguments that evaluate to false", () => { + expect(Error.isError()).toBeFalse(); + expect(Error.isError("1")).toBeFalse(); + expect(Error.isError("foo")).toBeFalse(); + expect(Error.isError(1)).toBeFalse(); + expect(Error.isError(1, 2, 3)).toBeFalse(); + expect(Error.isError(undefined)).toBeFalse(); + expect(Error.isError(null)).toBeFalse(); + expect(Error.isError(Infinity)).toBeFalse(); + expect(Error.isError({})).toBeFalse(); +}); + +test("Error.isError arguments that evaluate to true", () => { + expect(Error.isError(new Error())).toBeTrue(); + expect(Error.isError(new EvalError())).toBeTrue(); + expect(Error.isError(new RangeError())).toBeTrue(); + expect(Error.isError(new ReferenceError())).toBeTrue(); + expect(Error.isError(new SyntaxError())).toBeTrue(); + expect(Error.isError(new TypeError())).toBeTrue(); + expect(Error.isError(new URIError())).toBeTrue(); + expect(Error.isError(new SuppressedError())).toBeTrue(); + + class MySuppressedError extends SuppressedError {} + expect(Error.isError(new MySuppressedError())).toBeTrue(); + + class MyTypeError extends TypeError {} + expect(Error.isError(new MyTypeError())).toBeTrue(); +});