diff --git a/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Libraries/LibJS/Runtime/ArrayConstructor.cpp index 0e4d4ce71a..4e0715038c 100644 --- a/Libraries/LibJS/Runtime/ArrayConstructor.cpp +++ b/Libraries/LibJS/Runtime/ArrayConstructor.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include 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 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); diff --git a/Libraries/LibJS/Runtime/ArrayConstructor.h b/Libraries/LibJS/Runtime/ArrayConstructor.h index 91262d5122..0e24cf0536 100644 --- a/Libraries/LibJS/Runtime/ArrayConstructor.h +++ b/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); }; diff --git a/Libraries/LibJS/Tests/builtins/Array/Array.from.js b/Libraries/LibJS/Tests/builtins/Array/Array.from.js new file mode 100644 index 0000000000..fe0cdefefc --- /dev/null +++ b/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); + }); +});