From 698062b831e1bcb02f361c79cee0aee4ae33fc17 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Mon, 13 Jun 2022 08:01:46 +0100 Subject: [PATCH] LibJS: Implement Array.prototype.with() --- .../LibJS/Runtime/ArrayPrototype.cpp | 57 +++++++++++++++++++ .../Libraries/LibJS/Runtime/ArrayPrototype.h | 1 + .../Array.prototype-generic-functions.js | 6 ++ .../builtins/Array/Array.prototype.with.js | 46 +++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.with.js diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp index 2e0d6adc99..1be2d3f56c 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -78,6 +78,7 @@ void ArrayPrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.toReversed, to_reversed, 0, attr); define_native_function(vm.names.toSorted, to_sorted, 1, attr); define_native_function(vm.names.toSpliced, to_spliced, 2, attr); + define_native_function(vm.names.with, with, 2, attr); // Use define_direct_property here instead of define_native_function so that // Object.is(Array.prototype[Symbol.iterator], Array.prototype.values) @@ -1991,4 +1992,60 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_spliced) return array; } +// 1.1.1.7 Array.prototype.with ( index, value ), https://tc39.es/proposal-change-array-by-copy/#sec-array.prototype.with +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::with) +{ + auto index = vm.argument(0); + auto value = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto relative_index = TRY(index.to_integer_or_infinity(global_object)); + + double actual_index; + + // 4. If relativeIndex ≥ 0, let actualIndex be relativeIndex. + if (relative_index >= 0) + actual_index = relative_index; + // 5. Else, let actualIndex be len + relativeIndex. + else + actual_index = static_cast(length) + relative_index; + + // 6. If actualIndex ≥ len or actualIndex < 0, throw a RangeError exception. + if (actual_index >= static_cast(length) || actual_index < 0) + return vm.throw_completion(global_object, ErrorType::IndexOutOfRange, actual_index, length); + + // 7. Let A be ? ArrayCreate(𝔽(len)). + auto* array = TRY(Array::create(global_object, length)); + + // 8. Let k be 0. + // 9. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + Value from_value; + + // b. If k is actualIndex, let fromValue be value. + if (k == static_cast(actual_index)) + from_value = value; + // c. Else, let fromValue be ? Get(O, Pk). + else + from_value = TRY(object->get(property_key)); + + // d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). + MUST(array->create_data_property_or_throw(property_key, from_value)); + + // e. Set k to k + 1. + } + + // 10. Return A. + return array; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h index f3fbc9b067..2adc369b9b 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h @@ -59,6 +59,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(to_reversed); JS_DECLARE_NATIVE_FUNCTION(to_sorted); JS_DECLARE_NATIVE_FUNCTION(to_spliced); + JS_DECLARE_NATIVE_FUNCTION(with); }; ThrowCompletionOr array_merge_sort(GlobalObject&, FunctionObject* compare_func, MarkedVector& arr_to_sort); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js index 4c0f7cb427..7d568d859f 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js @@ -360,4 +360,10 @@ describe("ability to work with generic non-array objects", () => { expect(result).toEqual(["foo", "hello", "friends", "baz", undefined]); expect(result).not.toBe(o); }); + + test("with", () => { + const result = Array.prototype.with.call(o, 2, "hello"); + expect(result).toEqual(["foo", "bar", "hello", "baz", undefined]); + expect(result).not.toBe(o); + }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.with.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.with.js new file mode 100644 index 0000000000..8bacb23bb6 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.with.js @@ -0,0 +1,46 @@ +describe("normal behavior", () => { + test("length is 2", () => { + expect(Array.prototype.with).toHaveLength(2); + }); + + test("basic functionality", () => { + const a = [1, 2, 3, 4, 5]; + const values = [ + [0, "foo", ["foo", 2, 3, 4, 5]], + [-5, "foo", ["foo", 2, 3, 4, 5]], + [4, "foo", [1, 2, 3, 4, "foo"]], + [-1, "foo", [1, 2, 3, 4, "foo"]], + ]; + for (const [index, value, expected] of values) { + const b = a.with(index, value); + expect(a).not.toBe(b); + expect(a).toEqual([1, 2, 3, 4, 5]); + expect(b).toEqual(expected); + } + }); +}); + +describe("errors", () => { + test("null or undefined this value", () => { + expect(() => { + Array.prototype.with.call(); + }).toThrowWithMessage(TypeError, "ToObject on null or undefined"); + + expect(() => { + Array.prototype.with.call(undefined); + }).toThrowWithMessage(TypeError, "ToObject on null or undefined"); + + expect(() => { + Array.prototype.with.call(null); + }).toThrowWithMessage(TypeError, "ToObject on null or undefined"); + }); + + test("out of range index", () => { + expect(() => { + [].with(0, "foo"); + }).toThrowWithMessage(RangeError, "Index 0 is out of range of array length 0"); + expect(() => { + [].with(-1, "foo"); + }).toThrowWithMessage(RangeError, "Index -1 is out of range of array length 0"); + }); +});