Explorar o código

LibJS: Implement ECMA-402 Array.prototype.toLocaleString

Turns out the only difference between our existing implementation and
the ECMA-402 implementation is we weren't passing the locales and
options list to each element.toLocaleString invocation.

This also adds spec comments to the definition.
Timothy Flynn %!s(int64=3) %!d(string=hai) anos
pai
achega
39ab1a8999

+ 33 - 8
Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp

@@ -388,9 +388,13 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string)
     return TRY(vm.call(join_function.as_function(), this_object));
     return TRY(vm.call(join_function.as_function(), this_object));
 }
 }
 
 
-// 23.1.3.30 Array.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-array.prototype.tolocalestring
+// 18.5.1 Array.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sup-array.prototype.tolocalestring
 JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
 JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
 {
 {
+    auto locales = vm.argument(0);
+    auto options = vm.argument(1);
+
+    // 1. Let array be ? ToObject(this value).
     auto* this_object = TRY(vm.this_value(global_object).to_object(global_object));
     auto* this_object = TRY(vm.this_value(global_object).to_object(global_object));
 
 
     if (s_array_join_seen_objects.contains(this_object))
     if (s_array_join_seen_objects.contains(this_object))
@@ -400,20 +404,41 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
         s_array_join_seen_objects.remove(this_object);
         s_array_join_seen_objects.remove(this_object);
     };
     };
 
 
+    // 2. Let len be ? ToLength(? Get(array, "length")).
     auto length = TRY(length_of_array_like(global_object, *this_object));
     auto length = TRY(length_of_array_like(global_object, *this_object));
 
 
-    String separator = ","; // NOTE: This is implementation-specific.
+    // 3. Let separator be the String value for the list-separator String appropriate for the host environment's current locale (this is derived in an implementation-defined way).
+    constexpr auto separator = ","sv;
+
+    // 4. Let R be the empty String.
     StringBuilder builder;
     StringBuilder builder;
+
+    // 5. Let k be 0.
+    // 6. Repeat, while k < len,
     for (size_t i = 0; i < length; ++i) {
     for (size_t i = 0; i < length; ++i) {
-        if (i > 0)
+        // a. If k > 0, then
+        if (i > 0) {
+            // i. Set R to the string-concatenation of R and separator.
             builder.append(separator);
             builder.append(separator);
+        }
+
+        // b. Let nextElement be ? Get(array, ! ToString(k)).
         auto value = TRY(this_object->get(i));
         auto value = TRY(this_object->get(i));
-        if (value.is_nullish())
-            continue;
-        auto locale_string_result = TRY(value.invoke(global_object, vm.names.toLocaleString));
-        auto string = TRY(locale_string_result.to_string(global_object));
-        builder.append(string);
+
+        // c. If nextElement is not undefined or null, then
+        if (!value.is_nullish()) {
+            // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)).
+            auto locale_string_result = TRY(value.invoke(global_object, vm.names.toLocaleString, locales, options));
+
+            // ii. Set R to the string-concatenation of R and S.
+            auto string = TRY(locale_string_result.to_string(global_object));
+            builder.append(string);
+        }
+
+        // d. Increase k by 1.
     }
     }
+
+    // 7. Return R.
     return js_string(vm, builder.to_string());
     return js_string(vm, builder.to_string());
 }
 }
 
 

+ 8 - 0
Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toLocaleString.js

@@ -59,4 +59,12 @@ describe("normal behavior", () => {
         // [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
         // [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
         expect(a.toLocaleString()).toBe("foo,,1,2,,bar");
         expect(a.toLocaleString()).toBe("foo,,1,2,,bar");
     });
     });
+
+    test("with options", () => {
+        expect([12, 34].toLocaleString("en")).toBe("12");
+        expect([12, 34].toLocaleString("ar")).toBe("\u0661\u0662,\u0663\u0664");
+
+        expect([0.234].toLocaleString("en", { style: "percent" })).toBe("23%");
+        expect([0.234].toLocaleString("ar", { style: "percent" })).toBe("\u0662\u0663\u066a\u061c");
+    });
 });
 });