1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 02:37:35 +00:00

LibJS: Implement ECMA-402 String.prototype.toLocale{Lower,Upper}Case

This commit is contained in:
Timothy Flynn 2021-09-05 15:27:11 -04:00 committed by Linus Groh
parent 14086c69e7
commit 207319ecf1
5 changed files with 91 additions and 7 deletions

View file

@ -233,7 +233,7 @@ Vector<String> canonicalize_locale_list(GlobalObject& global_object, Value local
} }
// 9.2.2 BestAvailableLocale ( availableLocales, locale ), https://tc39.es/ecma402/#sec-bestavailablelocale // 9.2.2 BestAvailableLocale ( availableLocales, locale ), https://tc39.es/ecma402/#sec-bestavailablelocale
static Optional<String> best_available_locale(StringView const& locale) Optional<String> best_available_locale(StringView const& locale)
{ {
// 1. Let candidate be locale. // 1. Let candidate be locale.
StringView candidate = locale; StringView candidate = locale;

View file

@ -29,6 +29,7 @@ struct LocaleResult {
Optional<Unicode::LocaleID> is_structurally_valid_language_tag(StringView locale); Optional<Unicode::LocaleID> is_structurally_valid_language_tag(StringView locale);
String canonicalize_unicode_locale_id(Unicode::LocaleID& locale); String canonicalize_unicode_locale_id(Unicode::LocaleID& locale);
Vector<String> canonicalize_locale_list(GlobalObject&, Value locales); Vector<String> canonicalize_locale_list(GlobalObject&, Value locales);
Optional<String> best_available_locale(StringView const& locale);
Vector<String> best_fit_supported_locales(Vector<String> const& requested_locales); Vector<String> best_fit_supported_locales(Vector<String> const& requested_locales);
Vector<String> lookup_supported_locales(Vector<String> const& requested_locales); Vector<String> lookup_supported_locales(Vector<String> const& requested_locales);
Array* supported_locales(GlobalObject&, Vector<String> const& requested_locales, Value options); Array* supported_locales(GlobalObject&, Vector<String> const& requested_locales, Value options);

View file

@ -14,6 +14,7 @@
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Intl/AbstractOperations.h>
#include <LibJS/Runtime/PrimitiveString.h> #include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/RegExpObject.h> #include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/StringIterator.h> #include <LibJS/Runtime/StringIterator.h>
@ -22,6 +23,7 @@
#include <LibJS/Runtime/Utf16String.h> #include <LibJS/Runtime/Utf16String.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
#include <LibUnicode/CharacterTypes.h> #include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/Locale.h>
#include <string.h> #include <string.h>
namespace JS { namespace JS {
@ -372,27 +374,72 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::index_of)
return index.has_value() ? Value(*index) : Value(-1); return index.has_value() ? Value(*index) : Value(-1);
} }
// 22.1.3.24 String.prototype.toLocaleLowerCase ( [ reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-string.prototype.tolocalelowercase static Optional<String> resolve_best_locale(GlobalObject& global_object, Value locales)
// NOTE: This is the minimum toLocaleLowerCase implementation for engines without ECMA-402. {
// For details on these steps, see https://tc39.es/ecma402/#sup-string.prototype.tolocalelowercase
auto& vm = global_object.vm();
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = Intl::canonicalize_locale_list(global_object, locales);
if (vm.exception())
return {};
Optional<Unicode::LocaleID> requested_locale;
// 4. If requestedLocales is not an empty List, then
if (!requested_locales.is_empty()) {
// a. Let requestedLocale be requestedLocales[0].
requested_locale = Unicode::parse_unicode_locale_id(requested_locales[0]);
}
// 5. Else,
else {
// a. Let requestedLocale be DefaultLocale().
requested_locale = Unicode::parse_unicode_locale_id(Unicode::default_locale());
}
VERIFY(requested_locale.has_value());
// 6. Let noExtensionsLocale be the String value that is requestedLocale with any Unicode locale extension sequences (6.2.1) removed.
requested_locale->remove_extension_type<Unicode::LocaleExtension>();
auto no_extensions_locale = requested_locale->to_string();
// 7. Let availableLocales be a List with language tags that includes the languages for which the Unicode Character Database contains language sensitive case mappings. Implementations may add additional language tags if they support case mapping for additional locales.
// 8. Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
auto locale = Intl::best_available_locale(no_extensions_locale);
// 9. If locale is undefined, let locale be "und".
if (!locale.has_value())
locale = "und"sv;
return locale;
}
// 18.1.2 String.prototype.toLocaleLowerCase ( [ locales ] ), https://tc39.es/ecma402/#sup-string.prototype.tolocalelowercase
JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_lowercase) JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_lowercase)
{ {
auto string = ak_string_from(vm, global_object); auto string = ak_string_from(vm, global_object);
if (!string.has_value()) if (!string.has_value())
return {}; return {};
auto lowercase = Unicode::to_unicode_lowercase_full(*string); auto locale = resolve_best_locale(global_object, vm.argument(0));
if (!locale.has_value())
return {};
auto lowercase = Unicode::to_unicode_lowercase_full(*string, *locale);
return js_string(vm, move(lowercase)); return js_string(vm, move(lowercase));
} }
// 22.1.3.25 String.prototype.toLocaleUpperCase ( [ reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-string.prototype.tolocaleuppercase // 18.1.3 String.prototype.toLocaleUpperCase ( [ locales ] ), https://tc39.es/ecma402/#sup-string.prototype.tolocaleuppercase
// NOTE: This is the minimum toLocaleUpperCase implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_uppercase) JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_uppercase)
{ {
auto string = ak_string_from(vm, global_object); auto string = ak_string_from(vm, global_object);
if (!string.has_value()) if (!string.has_value())
return {}; return {};
auto uppercase = Unicode::to_unicode_uppercase_full(*string); auto locale = resolve_best_locale(global_object, vm.argument(0));
if (!locale.has_value())
return {};
auto uppercase = Unicode::to_unicode_uppercase_full(*string, *locale);
return js_string(vm, move(uppercase)); return js_string(vm, move(uppercase));
} }

View file

@ -25,4 +25,25 @@ test("special case folding", () => {
expect("\u1FB7".toLocaleLowerCase()).toBe("\u1FB7"); expect("\u1FB7".toLocaleLowerCase()).toBe("\u1FB7");
expect("\u1FC7".toLocaleLowerCase()).toBe("\u1FC7"); expect("\u1FC7".toLocaleLowerCase()).toBe("\u1FC7");
expect("\u1FF7".toLocaleLowerCase()).toBe("\u1FF7"); expect("\u1FF7".toLocaleLowerCase()).toBe("\u1FF7");
expect("I".toLocaleLowerCase()).toBe("i");
expect("I".toLocaleLowerCase("az")).toBe("\u0131");
expect("I".toLocaleLowerCase("tr")).toBe("\u0131");
expect("\u0130".toLocaleLowerCase()).toBe("\u0069\u0307");
expect("\u0130".toLocaleLowerCase("az")).toBe("i");
expect("\u0130".toLocaleLowerCase("tr")).toBe("i");
expect("I\u0307".toLocaleLowerCase()).toBe("i\u0307");
expect("I\u0307".toLocaleLowerCase("az")).toBe("i");
expect("I\u0307".toLocaleLowerCase("tr")).toBe("i");
expect("\u012e".toLocaleLowerCase()).toBe("\u012f");
expect("\u012e".toLocaleLowerCase("lt")).toBe("\u012f");
expect("\u012e\u0300".toLocaleLowerCase()).toBe("\u012f\u0300");
expect("\u012e\u0300".toLocaleLowerCase("lt")).toBe("\u012f\u0307\u0300");
expect("\u012e\u0300".toLocaleLowerCase(["en", "lt"])).toBe("\u012f\u0300");
expect("\u012e\u0300".toLocaleLowerCase(["lt", "en"])).toBe("\u012f\u0307\u0300");
}); });

View file

@ -27,4 +27,19 @@ test("special case folding", () => {
expect("\u1FB7".toLocaleUpperCase()).toBe("\u0391\u0342\u0399"); expect("\u1FB7".toLocaleUpperCase()).toBe("\u0391\u0342\u0399");
expect("\u1FC7".toLocaleUpperCase()).toBe("\u0397\u0342\u0399"); expect("\u1FC7".toLocaleUpperCase()).toBe("\u0397\u0342\u0399");
expect("\u1FF7".toLocaleUpperCase()).toBe("\u03A9\u0342\u0399"); expect("\u1FF7".toLocaleUpperCase()).toBe("\u03A9\u0342\u0399");
expect("i".toLocaleUpperCase()).toBe("I");
expect("i".toLocaleUpperCase("lt")).toBe("I");
expect("i\u0307".toLocaleUpperCase()).toBe("I\u0307");
expect("i\u0307".toLocaleUpperCase("lt")).toBe("I");
expect("j".toLocaleUpperCase()).toBe("J");
expect("j".toLocaleUpperCase("lt")).toBe("J");
expect("j\u0307".toLocaleUpperCase()).toBe("J\u0307");
expect("j\u0307".toLocaleUpperCase("lt")).toBe("J");
expect("j\u0307".toLocaleUpperCase(["en", "lt"])).toBe("J\u0307");
expect("j\u0307".toLocaleUpperCase(["lt", "en"])).toBe("J");
}); });