diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index f26254a3495..d5106d81be5 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -94,6 +94,7 @@ namespace JS { P(cosh) \ P(count) \ P(countReset) \ + P(create) \ P(debug) \ P(defineProperties) \ P(defineProperty) \ diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp index 6138f438d7a..17ab27a8432 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -64,6 +64,7 @@ void ObjectConstructor::initialize(GlobalObject& global_object) define_native_function(vm.names.keys, keys, 1, attr); define_native_function(vm.names.values, values, 1, attr); define_native_function(vm.names.entries, entries, 1, attr); + define_native_function(vm.names.create, create, 2, attr); } ObjectConstructor::~ObjectConstructor() @@ -311,4 +312,31 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::entries) return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::KeyAndValue)); } +// 20.1.2.2 Object.create, https://tc39.es/ecma262/#sec-object.create +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::create) +{ + auto prototype_value = vm.argument(0); + auto properties = vm.argument(1); + + Object* prototype; + if (prototype_value.is_null()) { + prototype = nullptr; + } else if (prototype_value.is_object()) { + prototype = &prototype_value.as_object(); + } else { + vm.throw_exception(global_object, ErrorType::ObjectPrototypeWrongType); + return {}; + } + + auto* object = Object::create_empty(global_object); + object->set_prototype(prototype); + + if (!properties.is_undefined()) { + object->define_properties(properties); + if (vm.exception()) + return {}; + } + return object; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h index a31dcc05dee..9a2ae160706 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h @@ -61,6 +61,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(keys); JS_DECLARE_NATIVE_FUNCTION(values); JS_DECLARE_NATIVE_FUNCTION(entries); + JS_DECLARE_NATIVE_FUNCTION(create); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.create.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.create.js new file mode 100644 index 00000000000..a4d8788cdbb --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.create.js @@ -0,0 +1,57 @@ +test("length is 2", () => { + expect(Object.create).toHaveLength(2); +}); + +describe("errors", () => { + test("non-object protpotype value", () => { + expect(() => Object.create(42)).toThrowWithMessage( + TypeError, + "Prototype must be an object or null" + ); + }); +}); + +describe("normal behavior", () => { + test("creates object with given prototype", () => { + let o; + + o = Object.create(null); + expect(o).toEqual({}); + expect(Object.getPrototypeOf(o)).toBe(null); + + const p = {}; + o = Object.create(p); + expect(o).toEqual({}); + expect(Object.getPrototypeOf(o)).toBe(p); + }); + + test("creates object with properties from propertiesObject, if given", () => { + const o = Object.create( + {}, + { + foo: { + writable: true, + configurable: true, + value: "foo", + }, + bar: { + enumerable: true, + value: "bar", + }, + } + ); + expect(Object.getOwnPropertyNames(o)).toEqual(["foo", "bar"]); + 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, + }); + }); +});