Browse Source

LibJS: Implement basic functionality of Array.from()

The optional 2nd and 3rd arguments are not yet implemented.

This assumes that `this` is the Array constructor and doesn't yet
implement the more general behavior in the ES6 spec that allows
transferring this method to other constructors.
Nico Weber 4 năm trước cách đây
mục cha
commit
8ebef785eb

+ 40 - 0
Libraries/LibJS/Runtime/ArrayConstructor.cpp

@@ -32,6 +32,7 @@
 #include <LibJS/Runtime/ArrayConstructor.h>
 #include <LibJS/Runtime/Error.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
 #include <LibJS/Runtime/Shape.h>
 
 namespace JS {
@@ -53,6 +54,7 @@ void ArrayConstructor::initialize(GlobalObject& global_object)
     define_property("length", Value(1), Attribute::Configurable);
 
     u8 attr = Attribute::Writable | Attribute::Configurable;
+    define_native_function("from", from, 1, attr);
     define_native_function("isArray", is_array, 1, attr);
     define_native_function("of", of, 0, attr);
 }
@@ -84,6 +86,44 @@ Value ArrayConstructor::construct(Interpreter& interpreter, Function&)
     return call(interpreter);
 }
 
+JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
+{
+    auto value = interpreter.argument(0);
+    auto object = value.to_object(interpreter, global_object);
+    if (!object)
+        return {};
+
+    auto* array = Array::create(global_object);
+
+    // Array.from() lets you create Arrays from:
+    if (auto size = object->indexed_properties().array_like_size()) {
+        // * array-like objects (objects with a length property and indexed elements)
+        Vector<Value> elements;
+        elements.ensure_capacity(size);
+        for (size_t i = 0; i < size; ++i) {
+            elements.append(object->get(i));
+            if (interpreter.exception())
+                return {};
+        }
+        array->set_indexed_property_elements(move(elements));
+    } else {
+        // * iterable objects
+        get_iterator_values(global_object, value, [&](Value& element) {
+            if (interpreter.exception())
+                return IterationDecision::Break;
+            array->indexed_properties().append(element);
+            return IterationDecision::Continue;
+        });
+        if (interpreter.exception())
+            return {};
+    }
+
+    // FIXME: if interpreter.argument_count() >= 2: mapFn
+    // FIXME: if interpreter.argument_count() >= 3: thisArg
+
+    return array;
+}
+
 JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array)
 {
     auto value = interpreter.argument(0);

+ 1 - 0
Libraries/LibJS/Runtime/ArrayConstructor.h

@@ -44,6 +44,7 @@ public:
 private:
     virtual bool has_constructor() const override { return true; }
 
+    JS_DECLARE_NATIVE_FUNCTION(from);
     JS_DECLARE_NATIVE_FUNCTION(is_array);
     JS_DECLARE_NATIVE_FUNCTION(of);
 };

+ 68 - 0
Libraries/LibJS/Tests/builtins/Array/Array.from.js

@@ -0,0 +1,68 @@
+test("length is 1", () => {
+    expect(Array.from).toHaveLength(1);
+});
+
+describe("normal behavior", () => {
+    test("empty array", () => {
+        var a = Array.from([]);
+        expect(a instanceof Array).toBeTrue();
+        expect(a).toHaveLength(0);
+    });
+
+    test("empty string", () => {
+        var a = Array.from("");
+        expect(a instanceof Array).toBeTrue();
+        expect(a).toHaveLength(0);
+    });
+
+    test("non-empty array", () => {
+        var a = Array.from([5, 8, 1]);
+        expect(a instanceof Array).toBeTrue();
+        expect(a).toHaveLength(3);
+        expect(a[0]).toBe(5);
+        expect(a[1]).toBe(8);
+        expect(a[2]).toBe(1);
+    });
+
+    test("non-empty string", () => {
+        var a = Array.from("what");
+        expect(a instanceof Array).toBeTrue();
+        expect(a).toHaveLength(4);
+        expect(a[0]).toBe("w");
+        expect(a[1]).toBe("h");
+        expect(a[2]).toBe("a");
+        expect(a[3]).toBe("t");
+    });
+
+    test("shallow array copy", () => {
+        var a = [1, 2, 3];
+        var b = Array.from([a]);
+        expect(b instanceof Array).toBeTrue();
+        expect(b).toHaveLength(1);
+        b[0][0] = 4;
+        expect(a[0]).toBe(4);
+    });
+
+    test("from iterator", () => {
+        function rangeIterator(begin, end) {
+            return {
+                [Symbol.iterator]() {
+                    let value = begin - 1;
+                    return {
+                        next() {
+                            if (value < end)
+                                value += 1;
+                            return { value: value, done: value >= end };
+                        },
+                    };
+                },
+            };
+        }
+
+        var a = Array.from(rangeIterator(8, 10));
+        expect(a instanceof Array).toBeTrue();
+        expect(a).toHaveLength(2);
+        expect(a[0]).toBe(8);
+        expect(a[1]).toBe(9);
+    });
+});