From da2967732301d4a9ace246cd0468381b010ea949 Mon Sep 17 00:00:00 2001 From: hle0 <91701075+hle0@users.noreply.github.com> Date: Sun, 24 Oct 2021 18:21:00 +0000 Subject: [PATCH] Implement prototype of `Intl` built-in (#1622) This pull request is related to #1180. It changes the following: - Creates the `Intl` global - Adds the `Intl.getCanonicalLocales` method At the moment it does not actually use ICU4X behind the scenes; `Intl.getCanonicalLocales` simply acts as if all the locales passed are canonical locales. This will not be the case in the final PR. Co-authored-by: RageKnify --- boa/src/builtins/intl/mod.rs | 141 +++++++++++++++++++++++++++++++++++ boa/src/builtins/mod.rs | 3 + 2 files changed, 144 insertions(+) create mode 100644 boa/src/builtins/intl/mod.rs diff --git a/boa/src/builtins/intl/mod.rs b/boa/src/builtins/intl/mod.rs new file mode 100644 index 0000000000..47ff367ef6 --- /dev/null +++ b/boa/src/builtins/intl/mod.rs @@ -0,0 +1,141 @@ +//! This module implements the global `Intl` object. +//! +//! `Intl` is a built-in object that has properties and methods for i18n. It's not a function object. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma402/#intl-object + +use indexmap::IndexSet; + +use crate::{ + builtins::{Array, BuiltIn, JsArgs}, + object::ObjectInitializer, + property::Attribute, + symbol::WellKnownSymbols, + BoaProfiler, Context, JsResult, JsString, JsValue, +}; + +/// JavaScript `Intl` object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Intl; + +impl BuiltIn for Intl { + const NAME: &'static str = "Intl"; + + const ATTRIBUTE: Attribute = Attribute::WRITABLE + .union(Attribute::NON_ENUMERABLE) + .union(Attribute::CONFIGURABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + let string_tag = WellKnownSymbols::to_string_tag(); + let object = ObjectInitializer::new(context) + .function(Self::get_canonical_locales, "getCanonicalLocales", 1) + .property( + string_tag, + Self::NAME, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .build(); + + object.into() + } +} + +impl Intl { + fn canonicalize_locale(locale: &str) -> JsString { + JsString::new(locale) + } + + fn canonicalize_locale_list( + args: &[JsValue], + context: &mut Context, + ) -> JsResult> { + // https://tc39.es/ecma402/#sec-canonicalizelocalelist + // 1. If locales is undefined, then + let locales = args.get_or_undefined(0); + if locales.is_undefined() { + // a. Return a new empty List. + return Ok(Vec::new()); + } + + let locales = &args[0]; + + // 2. Let seen be a new empty List. + let mut seen = IndexSet::new(); + + // 3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot, then + // TODO: check if Type(locales) is object and handle the internal slots + let o = if locales.is_string() { + // a. Let O be CreateArrayFromList(« locales »). + Array::create_array_from_list([locales.clone()], context) + } else { + // 4. Else, + // a. Let O be ? ToObject(locales). + locales.to_object(context)? + }; + + // 5. Let len be ? ToLength(? Get(O, "length")). + let len = o.length_of_array_like(context)?; + + // 6 Let k be 0. + // 7. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ToString(k). + // b. Let kPresent be ? HasProperty(O, Pk). + let k_present = o.has_property(k, context)?; + // c. If kPresent is true, then + if k_present { + // i. Let kValue be ? Get(O, Pk). + let k_value = o.get(k, context)?; + // ii. If Type(kValue) is not String or Object, throw a TypeError exception. + if !(k_value.is_object() || k_value.is_string()) { + return Err(context + .throw_type_error("locale should be a String or Object") + .unwrap_err()); + } + // iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then + // TODO: handle checks for InitializedLocale internal slot (there should be an if statement here) + // 1. Let tag be kValue.[[Locale]]. + // iv. Else, + // 1. Let tag be ? ToString(kValue). + let tag = k_value.to_string(context)?; + // v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception. + // TODO: implement `IsStructurallyValidLanguageTag` + + // vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag). + seen.insert(Self::canonicalize_locale(&tag)); + // vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen. + } + // d. Increase k by 1. + } + + // 8. Return seen. + Ok(seen.into_iter().collect::>()) + } + + /// Returns an array containing the canonical locale names. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN docs][mdn] + /// + /// [spec]: https://tc39.es/ecma402/#sec-intl.getcanonicallocales + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales + pub(crate) fn get_canonical_locales( + _: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let ll be ? CanonicalizeLocaleList(locales). + let ll = Self::canonicalize_locale_list(args, context)?; + // 2. Return CreateArrayFromList(ll). + Ok(JsValue::Object(Array::create_array_from_list( + ll.into_iter().map(|x| x.into()), + context, + ))) + } +} diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 1d6f45af40..fbdef7765f 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -12,6 +12,7 @@ pub mod error; pub mod function; pub mod global_this; pub mod infinity; +pub mod intl; pub mod intrinsics; pub mod iterable; pub mod json; @@ -39,6 +40,7 @@ pub(crate) use self::{ function::BuiltInFunctionObject, global_this::GlobalThis, infinity::Infinity, + intl::Intl, json::Json, map::map_iterator::MapIterator, map::Map, @@ -124,6 +126,7 @@ pub fn init(context: &mut Context) { BuiltInFunctionObject, BuiltInObjectObject, Math, + Intl, Json, Array, Proxy,