LibJS: Implement Object.defineProperties()

This commit is contained in:
Linus Groh 2021-04-10 18:39:11 +02:00 committed by Andreas Kling
parent 275da6fcc9
commit da8a35a79e
Notes: sideshowbarker 2024-07-18 20:35:02 +09:00
6 changed files with 120 additions and 0 deletions

View file

@ -95,6 +95,7 @@ namespace JS {
P(count) \
P(countReset) \
P(debug) \
P(defineProperties) \
P(defineProperty) \
P(deleteProperty) \
P(description) \

View file

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

View file

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

View file

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

View file

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

View file

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