LibJS: Implement Object.defineProperties()
This commit is contained in:
parent
275da6fcc9
commit
da8a35a79e
Notes:
sideshowbarker
2024-07-18 20:35:02 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/da8a35a79e1 Pull-request: https://github.com/SerenityOS/serenity/pull/6218 Reviewed-by: https://github.com/Lubrsi
6 changed files with 120 additions and 0 deletions
|
@ -95,6 +95,7 @@ namespace JS {
|
|||
P(count) \
|
||||
P(countReset) \
|
||||
P(debug) \
|
||||
P(defineProperties) \
|
||||
P(defineProperty) \
|
||||
P(deleteProperty) \
|
||||
P(description) \
|
||||
|
|
|
@ -900,6 +900,49 @@ bool Object::define_native_property(const StringOrSymbol& property_name, AK::Fun
|
|||
return define_property(property_name, heap().allocate_without_global_object<NativeProperty>(move(getter), move(setter)), attribute);
|
||||
}
|
||||
|
||||
// 20.1.2.3.1 ObjectDefineProperties, https://tc39.es/ecma262/#sec-objectdefineproperties
|
||||
void Object::define_properties(Value properties)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
auto* props = properties.to_object(global_object());
|
||||
if (!props)
|
||||
return;
|
||||
auto keys = props->get_own_properties(PropertyKind::Key);
|
||||
if (vm.exception())
|
||||
return;
|
||||
struct NameAndDescriptor {
|
||||
PropertyName name;
|
||||
PropertyDescriptor descriptor;
|
||||
};
|
||||
Vector<NameAndDescriptor> descriptors;
|
||||
for (auto& key : keys) {
|
||||
auto property_name = PropertyName::from_value(global_object(), key);
|
||||
auto property_descriptor = props->get_own_property_descriptor(property_name);
|
||||
if (property_descriptor.has_value() && property_descriptor->attributes.is_enumerable()) {
|
||||
auto descriptor_object = props->get(property_name);
|
||||
if (vm.exception())
|
||||
return;
|
||||
if (!descriptor_object.is_object()) {
|
||||
vm.throw_exception<TypeError>(global_object(), ErrorType::NotAnObject, descriptor_object.to_string_without_side_effects());
|
||||
return;
|
||||
}
|
||||
auto descriptor = PropertyDescriptor::from_dictionary(vm, descriptor_object.as_object());
|
||||
if (vm.exception())
|
||||
return;
|
||||
descriptors.append({ property_name, descriptor });
|
||||
}
|
||||
}
|
||||
for (auto& [name, descriptor] : descriptors) {
|
||||
// FIXME: The spec has both of this handled by DefinePropertyOrThrow(O, P, desc).
|
||||
// We should invest some time in improving object property handling, it not being
|
||||
// super close to the spec makes this and other things unnecessarily complicated.
|
||||
if (descriptor.is_accessor_descriptor())
|
||||
define_accessor(name, descriptor.getter, descriptor.setter, descriptor.attributes);
|
||||
else
|
||||
define_property(name, descriptor.value, descriptor.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
void Object::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Cell::visit_edges(visitor);
|
||||
|
|
|
@ -115,6 +115,8 @@ public:
|
|||
bool define_native_function(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)>, i32 length = 0, PropertyAttributes attributes = default_attributes);
|
||||
bool define_native_property(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes);
|
||||
|
||||
void define_properties(Value properties);
|
||||
|
||||
virtual bool delete_property(const PropertyName&);
|
||||
|
||||
virtual bool is_array() const { return false; }
|
||||
|
|
|
@ -49,6 +49,7 @@ void ObjectConstructor::initialize(GlobalObject& global_object)
|
|||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(vm.names.defineProperty, define_property_, 3, attr);
|
||||
define_native_function(vm.names.defineProperties, define_properties, 2, attr);
|
||||
define_native_function(vm.names.is, is, 2, attr);
|
||||
define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 2, attr);
|
||||
define_native_function(vm.names.getOwnPropertyNames, get_own_property_names, 1, attr);
|
||||
|
@ -250,6 +251,21 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_)
|
|||
return &object;
|
||||
}
|
||||
|
||||
// 20.1.2.3 Object.defineProperties, https://tc39.es/ecma262/#sec-object.defineproperties
|
||||
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_properties)
|
||||
{
|
||||
if (!vm.argument(0).is_object()) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument");
|
||||
return {};
|
||||
}
|
||||
auto& object = vm.argument(0).as_object();
|
||||
auto properties = vm.argument(1);
|
||||
object.define_properties(properties);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
return &object;
|
||||
}
|
||||
|
||||
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is)
|
||||
{
|
||||
return Value(same_value(vm.argument(0), vm.argument(1)));
|
||||
|
|
|
@ -46,6 +46,7 @@ private:
|
|||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(define_property_);
|
||||
JS_DECLARE_NATIVE_FUNCTION(define_properties);
|
||||
JS_DECLARE_NATIVE_FUNCTION(is);
|
||||
JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor);
|
||||
JS_DECLARE_NATIVE_FUNCTION(get_own_property_names);
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
test("length is 2", () => {
|
||||
expect(Object.defineProperties).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("non-object argument", () => {
|
||||
expect(() => Object.defineProperties(42, {})).toThrowWithMessage(
|
||||
TypeError,
|
||||
"Object argument is not an object"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("returns given object", () => {
|
||||
const o = {};
|
||||
expect(Object.defineProperties(o, {})).toBe(o);
|
||||
});
|
||||
|
||||
test("defines given properties on object", () => {
|
||||
const properties = {
|
||||
foo: {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: "foo",
|
||||
},
|
||||
bar: {
|
||||
enumerable: true,
|
||||
value: "bar",
|
||||
},
|
||||
baz: {
|
||||
get() {},
|
||||
set() {},
|
||||
},
|
||||
};
|
||||
const o = Object.defineProperties({}, properties);
|
||||
expect(Object.getOwnPropertyNames(o)).toEqual(["foo", "bar", "baz"]);
|
||||
expect(Object.getOwnPropertyDescriptor(o, "foo")).toEqual({
|
||||
value: "foo",
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
expect(Object.getOwnPropertyDescriptor(o, "bar")).toEqual({
|
||||
value: "bar",
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
});
|
||||
expect(Object.getOwnPropertyDescriptor(o, "baz")).toEqual({
|
||||
get: properties.baz.get,
|
||||
set: properties.baz.set,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue