diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp index 685a71df07..6f0ff37cfa 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp @@ -34,6 +34,8 @@ void TypedArrayPrototype::initialize(GlobalObject& object) define_native_function(vm.names.fill, fill, 1, attr); define_native_function(vm.names.find, find, 1, attr); define_native_function(vm.names.findIndex, find_index, 1, attr); + define_native_function(vm.names.findLast, find_last, 1, attr); + define_native_function(vm.names.findLastIndex, find_last_index, 1, attr); define_native_function(vm.names.forEach, for_each, 1, attr); define_native_function(vm.names.includes, includes, 1, attr); define_native_function(vm.names.indexOf, index_of, 1, attr); @@ -129,6 +131,34 @@ static void for_each_item(VM& vm, GlobalObject& global_object, const String& nam } } +static void for_each_item_from_last(VM& vm, GlobalObject& global_object, const String& name, Function callback) +{ + auto* typed_array = validate_typed_array_from_this(global_object); + if (!typed_array) + return; + + auto initial_length = typed_array->array_length(); + + auto* callback_function = callback_from_args(global_object, name); + if (!callback_function) + return; + + auto this_value = vm.argument(1); + + for (ssize_t i = (ssize_t)initial_length - 1; i >= 0; --i) { + auto value = typed_array->get(i); + if (vm.exception()) + return; + + auto callback_result = vm.call(*callback_function, this_value, value, Value((i32)i), typed_array); + if (vm.exception()) + return; + + if (callback(i, value, callback_result) == IterationDecision::Break) + break; + } +} + // 23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList ), https://tc39.es/ecma262/#typedarray-species-create static TypedArrayBase* typed_array_species_create(GlobalObject& global_object, TypedArrayBase const& exemplar, MarkedValueList arguments) { @@ -303,6 +333,34 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_index) return Value(result_index); } +// 4 %TypedArray%.prototype.findLast ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlast +JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_last) +{ + auto result = js_undefined(); + for_each_item_from_last(vm, global_object, "findLast", [&](auto, auto value, auto callback_result) { + if (callback_result.to_boolean()) { + result = value; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return result; +} + +// 5 %TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlastindex +JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_last_index) +{ + auto result_index = -1; + for_each_item_from_last(vm, global_object, "findLastIndex", [&](auto index, auto, auto callback_result) { + if (callback_result.to_boolean()) { + result_index = index; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return Value(result_index); +} + // 23.2.3.12 %TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::for_each) { diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h index af97c5a459..2e24624676 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h @@ -30,6 +30,8 @@ private: JS_DECLARE_NATIVE_FUNCTION(fill); JS_DECLARE_NATIVE_FUNCTION(find); JS_DECLARE_NATIVE_FUNCTION(find_index); + JS_DECLARE_NATIVE_FUNCTION(find_last); + JS_DECLARE_NATIVE_FUNCTION(find_last_index); JS_DECLARE_NATIVE_FUNCTION(for_each); JS_DECLARE_NATIVE_FUNCTION(includes); JS_DECLARE_NATIVE_FUNCTION(index_of); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js new file mode 100644 index 0000000000..7c7879b401 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js @@ -0,0 +1,121 @@ +const TYPED_ARRAYS = [ + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + Int8Array, + Int16Array, + Int32Array, + Float32Array, + Float64Array, +]; + +const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; + +test("length is 1", () => { + TYPED_ARRAYS.forEach(T => { + expect(T.prototype.findLast).toHaveLength(1); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + expect(T.prototype.findLast).toHaveLength(1); + }); +}); + +describe("errors", () => { + function errorTests(T) { + test(`requires at least one argument (${T.name})`, () => { + expect(() => { + new T().findLast(); + }).toThrowWithMessage( + TypeError, + "TypedArray.prototype.findLast() requires at least one argument" + ); + }); + + test(`callback must be a function (${T.name})`, () => { + expect(() => { + new T().findLast(undefined); + }).toThrowWithMessage(TypeError, "undefined is not a function"); + }); + } + + TYPED_ARRAYS.forEach(T => errorTests(T)); + BIGINT_TYPED_ARRAYS.forEach(T => errorTests(T)); +}); + +describe("normal behaviour", () => { + test("basic functionality", () => { + TYPED_ARRAYS.forEach(T => { + const typedArray = new T([1, 2, 3]); + + expect(typedArray.findLast(value => value === 1)).toBe(1); + expect(typedArray.findLast(value => value === 2)).toBe(2); + expect(typedArray.findLast(value => value === 3)).toBe(3); + expect(typedArray.findLast((value, index) => index === 1)).toBe(2); + expect(typedArray.findLast(value => value == "1")).toBe(1); + expect(typedArray.findLast(value => value === 10)).toBeUndefined(); + + const typedArrayDuplicates = new T([2, 1, 2, 3, 1]); + + expect(typedArrayDuplicates.findLast(value => value < 3)).toBe(1); + expect(typedArrayDuplicates.findLast(value => value < 2)).toBe(1); + expect(typedArrayDuplicates.findLast(value => value > 1)).toBe(3); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + const typedArray = new T([1n, 2n, 3n]); + + expect(typedArray.findLast(value => value === 1n)).toBe(1n); + expect(typedArray.findLast(value => value === 2n)).toBe(2n); + expect(typedArray.findLast(value => value === 3n)).toBe(3n); + expect(typedArray.findLast((value, index) => index === 1)).toBe(2n); + expect(typedArray.findLast(value => value == 1)).toBe(1n); + expect(typedArray.findLast(value => value == "1")).toBe(1n); + expect(typedArray.findLast(value => value === 1)).toBeUndefined(); + + const typedArrayDuplicates = new T([2n, 1n, 2n, 3n, 1n]); + + expect(typedArrayDuplicates.findLast(value => value < 3)).toBe(1n); + expect(typedArrayDuplicates.findLast(value => value < 2)).toBe(1n); + expect(typedArrayDuplicates.findLast(value => value > 1)).toBe(3n); + }); + }); + + test("never calls callback with empty array", () => { + function emptyTest(T) { + var callbackCalled = 0; + expect( + new T().findLast(() => { + callbackCalled++; + }) + ).toBeUndefined(); + expect(callbackCalled).toBe(0); + } + + TYPED_ARRAYS.forEach(T => emptyTest(T)); + BIGINT_TYPED_ARRAYS.forEach(T => emptyTest(T)); + }); + + test("calls callback once for every item", () => { + TYPED_ARRAYS.forEach(T => { + var callbackCalled = 0; + expect( + new T([1, 2, 3]).findLast(() => { + callbackCalled++; + }) + ).toBeUndefined(); + expect(callbackCalled).toBe(3); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + var callbackCalled = 0; + expect( + new T([1n, 2n, 3n]).findLast(() => { + callbackCalled++; + }) + ).toBeUndefined(); + expect(callbackCalled).toBe(3); + }); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js new file mode 100644 index 0000000000..54b42b0758 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js @@ -0,0 +1,121 @@ +const TYPED_ARRAYS = [ + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + Int8Array, + Int16Array, + Int32Array, + Float32Array, + Float64Array, +]; + +const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; + +test("length is 1", () => { + TYPED_ARRAYS.forEach(T => { + expect(T.prototype.findLastIndex).toHaveLength(1); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + expect(T.prototype.findLastIndex).toHaveLength(1); + }); +}); + +describe("errors", () => { + function errorTests(T) { + test(`requires at least one argument (${T.name})`, () => { + expect(() => { + new T().findLastIndex(); + }).toThrowWithMessage( + TypeError, + "TypedArray.prototype.findLastIndex() requires at least one argument" + ); + }); + + test(`callback must be a function (${T.name})`, () => { + expect(() => { + new T().findLastIndex(undefined); + }).toThrowWithMessage(TypeError, "undefined is not a function"); + }); + } + + TYPED_ARRAYS.forEach(T => errorTests(T)); + BIGINT_TYPED_ARRAYS.forEach(T => errorTests(T)); +}); + +describe("normal behaviour", () => { + test("basic functionality", () => { + TYPED_ARRAYS.forEach(T => { + const typedArray = new T([1, 2, 3]); + + expect(typedArray.findLastIndex(value => value === 1)).toBe(0); + expect(typedArray.findLastIndex(value => value === 2)).toBe(1); + expect(typedArray.findLastIndex(value => value === 3)).toBe(2); + expect(typedArray.findLastIndex((value, index) => index === 1)).toBe(1); + expect(typedArray.findLastIndex(value => value == "1")).toBe(0); + expect(typedArray.findLastIndex(value => value === 10)).toBe(-1); + + const typedArrayDuplicates = new T([1, 2, 3, 1]); + + expect(typedArrayDuplicates.findLastIndex(value => value === 1)).toBe(3); + expect(typedArrayDuplicates.findLastIndex(value => value === 2)).toBe(1); + expect(typedArrayDuplicates.findLastIndex(value => value === 3)).toBe(2); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + const typedArray = new T([1n, 2n, 3n]); + + expect(typedArray.findLastIndex(value => value === 1n)).toBe(0); + expect(typedArray.findLastIndex(value => value === 2n)).toBe(1); + expect(typedArray.findLastIndex(value => value === 3n)).toBe(2); + expect(typedArray.findLastIndex((value, index) => index === 1)).toBe(1); + expect(typedArray.findLastIndex(value => value == 1)).toBe(0); + expect(typedArray.findLastIndex(value => value == "1")).toBe(0); + expect(typedArray.findLastIndex(value => value === 1)).toBe(-1); + + const typedArrayDuplicates = new T([1n, 2n, 3n, 1n]); + + expect(typedArrayDuplicates.findLastIndex(value => value === 1n)).toBe(3); + expect(typedArrayDuplicates.findLastIndex(value => value === 2n)).toBe(1); + expect(typedArrayDuplicates.findLastIndex(value => value === 3n)).toBe(2); + }); + }); + + test("never calls callback with empty array", () => { + function emptyTest(T) { + var callbackCalled = 0; + expect( + new T().findLastIndex(() => { + callbackCalled++; + }) + ).toBe(-1); + expect(callbackCalled).toBe(0); + } + + TYPED_ARRAYS.forEach(T => emptyTest(T)); + BIGINT_TYPED_ARRAYS.forEach(T => emptyTest(T)); + }); + + test("calls callback once for every item", () => { + TYPED_ARRAYS.forEach(T => { + var callbackCalled = 0; + expect( + new T([1, 2, 3]).findLastIndex(() => { + callbackCalled++; + }) + ).toBe(-1); + expect(callbackCalled).toBe(3); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + var callbackCalled = 0; + expect( + new T([1n, 2n, 3n]).findLastIndex(() => { + callbackCalled++; + }) + ).toBe(-1); + expect(callbackCalled).toBe(3); + }); + }); +});