瀏覽代碼

LibJS: Implement Object.assign()

Linus Groh 4 年之前
父節點
當前提交
7e1bffdeb8

+ 1 - 0
Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

@@ -56,6 +56,7 @@ namespace JS {
     P(asin)                                  \
     P(asinh)                                 \
     P(assert)                                \
+    P(assign)                                \
     P(at)                                    \
     P(atan)                                  \
     P(atan2)                                 \

+ 34 - 0
Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp

@@ -46,6 +46,7 @@ void ObjectConstructor::initialize(GlobalObject& global_object)
     define_native_function(vm.names.entries, entries, 1, attr);
     define_native_function(vm.names.create, create, 2, attr);
     define_native_function(vm.names.hasOwn, has_own, 2, attr);
+    define_native_function(vm.names.assign, assign, 2, attr);
 }
 
 ObjectConstructor::~ObjectConstructor()
@@ -322,4 +323,37 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::has_own)
     return Value(object->has_own_property(property_key));
 }
 
+// 20.1.2.1 Object.assign, https://tc39.es/ecma262/#sec-object.assign
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::assign)
+{
+    auto* to = vm.argument(0).to_object(global_object);
+    if (vm.exception())
+        return {};
+    if (vm.argument_count() == 1)
+        return to;
+    for (size_t i = 1; i < vm.argument_count(); ++i) {
+        auto next_source = vm.argument(i);
+        if (next_source.is_nullish())
+            continue;
+        auto from = next_source.to_object(global_object);
+        VERIFY(!vm.exception());
+        auto keys = from->get_own_properties(PropertyKind::Key);
+        if (vm.exception())
+            return {};
+        for (auto& key : keys) {
+            auto property_name = PropertyName::from_value(global_object, key);
+            auto property_descriptor = from->get_own_property_descriptor(property_name);
+            if (!property_descriptor.has_value() || !property_descriptor->attributes.is_enumerable())
+                continue;
+            auto value = from->get(property_name);
+            if (vm.exception())
+                return {};
+            to->put(property_name, value);
+            if (vm.exception())
+                return {};
+        }
+    }
+    return to;
+}
+
 }

+ 1 - 0
Userland/Libraries/LibJS/Runtime/ObjectConstructor.h

@@ -43,6 +43,7 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(entries);
     JS_DECLARE_NATIVE_FUNCTION(create);
     JS_DECLARE_NATIVE_FUNCTION(has_own);
+    JS_DECLARE_NATIVE_FUNCTION(assign);
 };
 
 }

+ 42 - 0
Userland/Libraries/LibJS/Tests/builtins/Object/Object.assign.js

@@ -0,0 +1,42 @@
+test("length is 2", () => {
+    expect(Object.assign).toHaveLength(2);
+});
+
+describe("errors", () => {
+    test("first argument must coercible to object", () => {
+        expect(() => {
+            Object.assign(null);
+        }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+        expect(() => {
+            Object.assign(undefined);
+        }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+    });
+});
+
+describe("normal behavior", () => {
+    test("returns first argument coerced to object", () => {
+        const o = {};
+        expect(Object.assign(o)).toBe(o);
+        expect(Object.assign(o, {})).toBe(o);
+        expect(Object.assign(42)).toEqual(new Number(42));
+    });
+
+    test("alters first argument object if sources are given", () => {
+        const o = { foo: 0 };
+        expect(Object.assign(o, { foo: 1 })).toBe(o);
+        expect(o).toEqual({ foo: 1 });
+    });
+
+    test("merges objects", () => {
+        const s = Symbol();
+        expect(
+            Object.assign(
+                {},
+                { foo: 0, bar: "baz" },
+                { [s]: [1, 2, 3] },
+                { foo: 1 },
+                { [42]: "test" }
+            )
+        ).toEqual({ foo: 1, bar: "baz", [s]: [1, 2, 3], 42: "test" });
+    });
+});