diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 626652d13e..f32692d559 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -512,6 +512,7 @@ namespace JS { P(timeZone) \ P(timeZoneName) \ P(timeZones) \ + P(toArray) \ P(toDateString) \ P(toExponential) \ P(toFixed) \ diff --git a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp index b6a5dae112..9fca042f27 100644 --- a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -36,6 +37,7 @@ ThrowCompletionOr IteratorPrototype::initialize(Realm& realm) define_native_function(realm, vm.names.drop, drop, 1, attr); define_native_function(realm, vm.names.flatMap, flat_map, 1, attr); define_native_function(realm, vm.names.reduce, reduce, 1, attr); + define_native_function(realm, vm.names.toArray, to_array, 0, attr); return {}; } @@ -516,4 +518,36 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce) } } +// 3.1.3.8 Iterator.prototype.toArray ( ), https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.toarray +JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::to_array) +{ + auto& realm = *vm.current_realm(); + + // 1. Let O be the this value. + // 2. If O is not an Object, throw a TypeError exception. + auto object = TRY(this_object(vm)); + + // 3. Let iterated be ? GetIteratorDirect(O). + auto iterated = TRY(get_iterator_direct(vm, object)); + + // 4. Let items be a new empty List. + Vector items; + + // 5. Repeat, + while (true) { + // a. Let next be ? IteratorStep(iterated). + auto next = TRY(iterator_step(vm, iterated)); + + // b. If next is false, return CreateArrayFromList(items). + if (!next) + return Array::create_from(realm, items); + + // c. Let value be ? IteratorValue(next). + auto value = TRY(iterator_value(vm, *next)); + + // d. Append value to items. + TRY_OR_THROW_OOM(vm, items.try_append(value)); + } +} + } diff --git a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h index 5785bdf6ba..d696bf4767 100644 --- a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h @@ -28,6 +28,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(drop); JS_DECLARE_NATIVE_FUNCTION(flat_map); JS_DECLARE_NATIVE_FUNCTION(reduce); + JS_DECLARE_NATIVE_FUNCTION(to_array); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.prototype.toArray.js b/Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.prototype.toArray.js new file mode 100644 index 0000000000..43487cc330 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.prototype.toArray.js @@ -0,0 +1,58 @@ +describe("errors", () => { + test("iterator's next method throws", () => { + function TestError() {} + + class TestIterator extends Iterator { + next() { + throw new TestError(); + } + } + + expect(() => { + new TestIterator().toArray(); + }).toThrow(TestError); + }); + + test("value returned by iterator's next method throws", () => { + function TestError() {} + + class TestIterator extends Iterator { + next() { + return { + done: false, + get value() { + throw new TestError(); + }, + }; + } + } + + expect(() => { + new TestIterator().toArray(); + }).toThrow(TestError); + }); +}); + +describe("normal behavior", () => { + test("length is 0", () => { + expect(Iterator.prototype.toArray).toHaveLength(0); + }); + + test("empty list", () => { + function* generator() {} + + const result = generator().toArray(); + expect(result).toEqual([]); + }); + + test("non-empty list", () => { + function* generator() { + yield 1; + yield 2; + yield 3; + } + + const result = generator().toArray(); + expect(result).toEqual([1, 2, 3]); + }); +});