diff options
5 files changed, 161 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp index 9cd731d5cf..8dbc154a38 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -65,6 +65,7 @@ void ArrayPrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.at, at, 1, attr); define_native_function(vm.names.keys, keys, 0, attr); define_native_function(vm.names.entries, entries, 0, attr); + define_native_function(vm.names.copyWithin, copy_within, 2, attr); // Use define_property here instead of define_native_function so that // Object.is(Array.prototype[Symbol.iterator], Array.prototype.values) @@ -1378,6 +1379,90 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat_map) return new_array; } +// 23.1.3.3 Array.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-array.prototype.copywithin +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::copy_within) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + auto length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + + auto relative_target = vm.argument(0).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + double to; + if (relative_target < 0) + to = max(length + relative_target, 0.0); + else + to = min(relative_target, (double)length); + + auto relative_start = vm.argument(1).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + double from; + if (relative_start < 0) + from = max(length + relative_start, 0.0); + else + from = min(relative_start, (double)length); + + auto relative_end = vm.argument(2).is_undefined() ? length : vm.argument(2).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + double final; + if (relative_end < 0) + final = max(length + relative_end, 0.0); + else + final = min(relative_end, (double)length); + + double count = min(final - from, length - to); + + i32 direction = 1; + + if (from < to && to < from + count) { + direction = -1; + from = from + count - 1; + to = to + count - 1; + } + + if (count < 0) { + return this_object; + } + + size_t from_i = from; + size_t to_i = to; + size_t count_i = count; + + while (count_i > 0) { + auto from_present = this_object->has_property(from_i); + if (vm.exception()) + return {}; + + if (from_present) { + auto from_value = this_object->get(from_i).value_or(js_undefined()); + if (vm.exception()) + return {}; + this_object->put(to_i, from_value); + if (vm.exception()) + return {}; + } else { + this_object->delete_property(to_i); + if (vm.exception()) + return {}; + } + + from_i += direction; + to_i += direction; + --count_i; + } + + return this_object; +} + // 1.1 Array.prototype.at ( index ), https://tc39.es/proposal-relative-indexing-method/#sec-array.prototype.at JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::at) { diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h index 7d998f03a9..7d392762ec 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h @@ -51,6 +51,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(at); JS_DECLARE_NATIVE_FUNCTION(keys); JS_DECLARE_NATIVE_FUNCTION(entries); + JS_DECLARE_NATIVE_FUNCTION(copy_within); }; } diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 394b43ca26..2999509eb9 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -82,6 +82,7 @@ namespace JS { P(console) \ P(construct) \ P(constructor) \ + P(copyWithin) \ P(cos) \ P(cosh) \ P(count) \ 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 af1b189834..c509ea3056 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 @@ -130,6 +130,35 @@ describe("ability to work with generic non-array objects", () => { } }); + test("copyWithin", () => { + const initial_o = { length: 5, 0: "foo", 1: "bar", 3: "baz" }; + { + const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" }; + // returns value and modifies + expect(Array.prototype.copyWithin.call(o, 0, 0)).toEqual(o); + expect(o).toEqual(initial_o); + } + + { + const o = {}; + expect(Array.prototype.copyWithin.call(o, 1, 16, 32)).toEqual(o); + expect(o).toEqual({}); + } + + { + const o = { length: 100 }; + expect(Array.prototype.copyWithin.call(o, 1, 16, 32)).toEqual(o); + expect(o).toEqual({ length: 100 }); + } + + { + const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" }; + // returns value and modifies + expect(Array.prototype.copyWithin.call(o, 2, 0)).toEqual(o); + expect(o).toEqual({ length: 5, 0: "foo", 1: "bar", 2: "foo", 3: "bar" }); + } + }); + const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" }; test("every", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.copyWithin.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.copyWithin.js new file mode 100644 index 0000000000..831269574a --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.copyWithin.js @@ -0,0 +1,45 @@ +test("length is 2", () => { + expect(Array.prototype.copyWithin).toHaveLength(2); +}); + +describe("normal behavior", () => { + test("Noop", () => { + var array = [1, 2]; + array.copyWithin(0, 0); + expect(array).toEqual([1, 2]); + }); + + test("basic behavior", () => { + var array = [1, 2, 3]; + + var b = array.copyWithin(1, 2); + expect(b).toEqual(array); + expect(array).toEqual([1, 3, 3]); + + b = array.copyWithin(2, 0); + expect(b).toEqual(array); + expect(array).toEqual([1, 3, 1]); + }); + + test("start > target", () => { + var array = [1, 2, 3]; + var b = array.copyWithin(0, 1); + expect(b).toEqual(array); + expect(array).toEqual([2, 3, 3]); + }); + + test("overwriting behavior", () => { + var array = [1, 2, 3]; + var b = array.copyWithin(1, 0); + expect(b).toEqual(array); + expect(array).toEqual([1, 1, 2]); + }); + + test("specify end", () => { + var array = [1, 2, 3]; + + b = array.copyWithin(2, 0, 1); + expect(b).toEqual(array); + expect(array).toEqual([1, 2, 1]); + }); +}); |