diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index fb7af79148..1d384b1685 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -98,6 +98,7 @@ set(SOURCES Runtime/PromiseReaction.cpp Runtime/PromiseResolvingFunction.cpp Runtime/PropertyAttributes.cpp + Runtime/PropertyDescriptor.cpp Runtime/ProxyConstructor.cpp Runtime/ProxyObject.cpp Runtime/Reference.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index e10a0f74e9..ef249ac01a 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -144,6 +144,8 @@ class PrimitiveString; class PromiseReaction; class PromiseReactionJob; class PromiseResolveThenableJob; +class PropertyAttributes; +class PropertyDescriptor; class PropertyName; class Reference; class ScopeNode; diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp new file mode 100644 index 0000000000..ef69b75a7e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace JS { + +// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit, +// but the names conflict - this is the easiest solution. :^) +#if 0 +// 6.2.5.1 IsAccessorDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isaccessordescriptor +bool PropertyDescriptor::is_accessor_descriptor() const +{ + // 1. If Desc is undefined, return false. + + // 2. If both Desc.[[Get]] and Desc.[[Set]] are absent, return false. + if (!get.has_value() && !set.has_value()) + return false; + + // 3. Return true. + return true; +} + +// 6.2.5.2 IsDataDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isdatadescriptor +bool PropertyDescriptor::is_data_descriptor() const +{ + // 1. If Desc is undefined, return false. + + // 2. If both Desc.[[Value]] and Desc.[[Writable]] are absent, return false. + if (!value.has_value() && !writable.has_value()) + return false; + + // 3. Return true. + return true; +} + +// 6.2.5.3 IsGenericDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isgenericdescriptor +bool PropertyDescriptor::is_generic_descriptor() const +{ + // 1. If Desc is undefined, return false. + + // 2. If IsAccessorDescriptor(Desc) and IsDataDescriptor(Desc) are both false, return true. + if (!is_accessor_descriptor() && !is_data_descriptor()) + return true; + + // 3. Return false. + return false; +} + +// 6.2.5.4 FromPropertyDescriptor ( Desc ), https://tc39.es/ecma262/#sec-frompropertydescriptor +Value from_property_descriptor(GlobalObject& global_object, Optional const& property_descriptor) +{ + if (!property_descriptor.has_value()) + return js_undefined(); + auto& vm = global_object.vm(); + auto* object = Object::create(global_object, global_object.object_prototype()); + if (property_descriptor->value.has_value()) + object->create_data_property_or_throw(vm.names.value, *property_descriptor->value); + if (property_descriptor->writable.has_value()) + object->create_data_property_or_throw(vm.names.writable, Value(*property_descriptor->writable)); + if (property_descriptor->get.has_value()) + object->create_data_property_or_throw(vm.names.get, *property_descriptor->get ? Value(*property_descriptor->get) : js_undefined()); + if (property_descriptor->set.has_value()) + object->create_data_property_or_throw(vm.names.set, *property_descriptor->set ? Value(*property_descriptor->set) : js_undefined()); + if (property_descriptor->enumerable.has_value()) + object->create_data_property_or_throw(vm.names.enumerable, Value(*property_descriptor->enumerable)); + if (property_descriptor->configurable.has_value()) + object->create_data_property_or_throw(vm.names.configurable, Value(*property_descriptor->configurable)); + return object; +} + +// 6.2.5.5 ToPropertyDescriptor ( Obj ), https://tc39.es/ecma262/#sec-topropertydescriptor +PropertyDescriptor to_property_descriptor(GlobalObject& global_object, Value argument) +{ + auto& vm = global_object.vm(); + if (!argument.is_object()) { + vm.throw_exception(global_object, ErrorType::NotAnObject, argument.to_string_without_side_effects()); + return {}; + } + auto& object = argument.as_object(); + PropertyDescriptor descriptor; + auto has_enumerable = object.has_property(vm.names.enumerable); + if (vm.exception()) + return {}; + if (has_enumerable) { + auto enumerable = object.get(vm.names.enumerable); + if (vm.exception()) + return {}; + descriptor.enumerable = enumerable.to_boolean(); + } + auto has_configurable = object.has_property(vm.names.configurable); + if (vm.exception()) + return {}; + if (has_configurable) { + auto configurable = object.get(vm.names.configurable); + if (vm.exception()) + return {}; + descriptor.configurable = configurable.to_boolean(); + } + auto has_value = object.has_property(vm.names.value); + if (vm.exception()) + return {}; + if (has_value) { + auto value = object.get(vm.names.value); + if (vm.exception()) + return {}; + descriptor.value = value; + } + auto has_writable = object.has_property(vm.names.writable); + if (vm.exception()) + return {}; + if (has_writable) { + auto writable = object.get(vm.names.writable); + if (vm.exception()) + return {}; + descriptor.writable = writable.to_boolean(); + } + auto has_get = object.has_property(vm.names.get); + if (vm.exception()) + return {}; + if (has_get) { + auto getter = object.get(vm.names.get); + if (vm.exception()) + return {}; + if (!getter.is_function() && !getter.is_undefined()) { + vm.throw_exception(global_object, ErrorType::AccessorBadField, "get"); + return {}; + } + descriptor.get = getter.is_function() ? &getter.as_function() : nullptr; + } + auto has_set = object.has_property(vm.names.set); + if (vm.exception()) + return {}; + if (has_set) { + auto setter = object.get(vm.names.set); + if (vm.exception()) + return {}; + if (!setter.is_function() && !setter.is_undefined()) { + vm.throw_exception(global_object, ErrorType::AccessorBadField, "set"); + return {}; + } + descriptor.set = setter.is_function() ? &setter.as_function() : nullptr; + } + if (descriptor.get.has_value() || descriptor.set.has_value()) { + if (descriptor.value.has_value() || descriptor.writable.has_value()) { + vm.throw_exception(global_object, ErrorType::AccessorValueOrWritable); + return {}; + } + } + return descriptor; +} + +// 6.2.5.6 CompletePropertyDescriptor ( Desc ), https://tc39.es/ecma262/#sec-completepropertydescriptor +void PropertyDescriptor::complete() +{ + if (is_generic_descriptor() || is_data_descriptor()) { + if (!value.has_value()) + value = Value {}; + if (!writable.has_value()) + writable = false; + } else { + if (!get.has_value()) + get = nullptr; + if (!set.has_value()) + set = nullptr; + } + if (!enumerable.has_value()) + enumerable = false; + if (!configurable.has_value()) + configurable = false; +} + +// Non-standard, just a convenient way to get from three Optional to PropertyAttributes. +PropertyAttributes PropertyDescriptor::attributes() const +{ + u8 attributes = 0; + if (writable.value_or(false)) + attributes |= Attribute::Writable; + if (enumerable.value_or(false)) + attributes |= Attribute::Enumerable; + if (configurable.value_or(false)) + attributes |= Attribute::Configurable; + return { attributes }; +} +#endif + +} diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h new file mode 100644 index 0000000000..f505b94a21 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS { + +// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit, +// but the names conflict - this is the easiest solution. :^) +#if 0 +// 6.2.5 The Property Descriptor Specification Type, https://tc39.es/ecma262/#sec-property-descriptor-specification-type + +Value from_property_descriptor(GlobalObject&, Optional const&); +PropertyDescriptor to_property_descriptor(GlobalObject&, Value); + +class PropertyDescriptor { +public: + [[nodiscard]] bool is_accessor_descriptor() const; + [[nodiscard]] bool is_data_descriptor() const; + [[nodiscard]] bool is_generic_descriptor() const; + + [[nodiscard]] PropertyAttributes attributes() const; + + void complete(); + + // Not a standard abstract operation, but "If every field in Desc is absent". + [[nodiscard]] bool is_empty() const + { + return !value.has_value() && !get.has_value() && !set.has_value() && !writable.has_value() && !enumerable.has_value() && !configurable.has_value(); + } + + Optional value {}; + Optional get {}; + Optional set {}; + Optional writable {}; + Optional enumerable {}; + Optional configurable {}; +}; + +} + +namespace AK { + +template<> +struct Formatter : Formatter { + void format(FormatBuilder& builder, JS::PropertyDescriptor const& property_descriptor) + { + Vector parts; + if (property_descriptor.value.has_value()) + parts.append(String::formatted("[[Value]]: {}", property_descriptor.value->to_string_without_side_effects())); + if (property_descriptor.get.has_value()) + parts.append(String::formatted("[[Get]]: JS::Function* @ {:p}", *property_descriptor.get)); + if (property_descriptor.set.has_value()) + parts.append(String::formatted("[[Set]]: JS::Function* @ {:p}", *property_descriptor.set)); + if (property_descriptor.writable.has_value()) + parts.append(String::formatted("[[Writable]]: {}", *property_descriptor.writable)); + if (property_descriptor.enumerable.has_value()) + parts.append(String::formatted("[[Enumerable]]: {}", *property_descriptor.enumerable)); + if (property_descriptor.configurable.has_value()) + parts.append(String::formatted("[[Configurable]]: {}", *property_descriptor.configurable)); + Formatter::format(builder, String::formatted("PropertyDescriptor {{ {} }}", String::join(", ", parts))); + } +}; +#endif + +}