mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 06:32:44 +00:00 
			
		
		
		
	LibJS: Implement Iterator.prototype.forEach
This commit is contained in:
		
							parent
							
								
									35380b2aef
								
							
						
					
					
						commit
						134bb44ca0
					
				
					 3 changed files with 139 additions and 0 deletions
				
			
		|  | @ -38,6 +38,7 @@ ThrowCompletionOr<void> IteratorPrototype::initialize(Realm& realm) | |||
|     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); | ||||
|     define_native_function(realm, vm.names.forEach, for_each, 1, attr); | ||||
| 
 | ||||
|     return {}; | ||||
| } | ||||
|  | @ -550,4 +551,47 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::to_array) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| // 3.1.3.9 Iterator.prototype.forEach ( fn ), https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.foreach
 | ||||
| JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::for_each) | ||||
| { | ||||
|     auto function = vm.argument(0); | ||||
| 
 | ||||
|     // 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. If IsCallable(fn) is false, throw a TypeError exception.
 | ||||
|     if (!function.is_function()) | ||||
|         return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "fn"sv); | ||||
| 
 | ||||
|     // 4. Let iterated be ? GetIteratorDirect(O).
 | ||||
|     auto iterated = TRY(get_iterator_direct(vm, object)); | ||||
| 
 | ||||
|     // 5. Let counter be 0.
 | ||||
|     size_t counter = 0; | ||||
| 
 | ||||
|     // 6. Repeat,
 | ||||
|     while (true) { | ||||
|         // a. Let next be ? IteratorStep(iterated).
 | ||||
|         auto next = TRY(iterator_step(vm, iterated)); | ||||
| 
 | ||||
|         // b. If next is false, return undefined.
 | ||||
|         if (!next) | ||||
|             return js_undefined(); | ||||
| 
 | ||||
|         // c. Let value be ? IteratorValue(next).
 | ||||
|         auto value = TRY(iterator_value(vm, *next)); | ||||
| 
 | ||||
|         // d. Let result be Completion(Call(fn, undefined, « value, 𝔽(counter) »)).
 | ||||
|         auto result = call(vm, function.as_function(), js_undefined(), value, Value { counter }); | ||||
| 
 | ||||
|         // e. IfAbruptCloseIterator(result, iterated).
 | ||||
|         if (result.is_error()) | ||||
|             return *TRY(iterator_close(vm, iterated, result.release_error())); | ||||
| 
 | ||||
|         // f. Set counter to counter + 1.
 | ||||
|         ++counter; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ private: | |||
|     JS_DECLARE_NATIVE_FUNCTION(flat_map); | ||||
|     JS_DECLARE_NATIVE_FUNCTION(reduce); | ||||
|     JS_DECLARE_NATIVE_FUNCTION(to_array); | ||||
|     JS_DECLARE_NATIVE_FUNCTION(for_each); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,94 @@ | |||
| describe("errors", () => { | ||||
|     test("called with non-callable object", () => { | ||||
|         expect(() => { | ||||
|             Iterator.prototype.forEach(Symbol.hasInstance); | ||||
|         }).toThrowWithMessage(TypeError, "fn is not a function"); | ||||
|     }); | ||||
| 
 | ||||
|     test("iterator's next method throws", () => { | ||||
|         function TestError() {} | ||||
| 
 | ||||
|         class TestIterator extends Iterator { | ||||
|             next() { | ||||
|                 throw new TestError(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         expect(() => { | ||||
|             new TestIterator().forEach(() => 0); | ||||
|         }).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().forEach(() => 0); | ||||
|         }).toThrow(TestError); | ||||
|     }); | ||||
| 
 | ||||
|     test("for-each function throws", () => { | ||||
|         function TestError() {} | ||||
| 
 | ||||
|         class TestIterator extends Iterator { | ||||
|             next() { | ||||
|                 return { | ||||
|                     done: false, | ||||
|                     value: 1, | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         expect(() => { | ||||
|             new TestIterator().forEach(() => { | ||||
|                 throw new TestError(); | ||||
|             }); | ||||
|         }).toThrow(TestError); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe("normal behavior", () => { | ||||
|     test("length is 1", () => { | ||||
|         expect(Iterator.prototype.forEach).toHaveLength(1); | ||||
|     }); | ||||
| 
 | ||||
|     test("for-each function sees every value", () => { | ||||
|         function* generator() { | ||||
|             yield "a"; | ||||
|             yield "b"; | ||||
|         } | ||||
| 
 | ||||
|         let count = 0; | ||||
| 
 | ||||
|         generator().forEach((value, index) => { | ||||
|             ++count; | ||||
| 
 | ||||
|             switch (index) { | ||||
|                 case 0: | ||||
|                     expect(value).toBe("a"); | ||||
|                     break; | ||||
|                 case 1: | ||||
|                     expect(value).toBe("b"); | ||||
|                     break; | ||||
|                 default: | ||||
|                     expect().fail(`Unexpected reducer invocation: value=${value} index=${index}`); | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             return value; | ||||
|         }, ""); | ||||
| 
 | ||||
|         expect(count).toBe(2); | ||||
|     }); | ||||
| }); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Flynn
						Timothy Flynn