diff --git a/Cargo.lock b/Cargo.lock index 3449d6427e..2e44dda175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "ryu-js", "serde", "serde_json", + "unicode-normalization", ] [[package]] diff --git a/boa/Cargo.toml b/boa/Cargo.toml index 935b8219ee..82b4c4d04f 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -36,6 +36,7 @@ indexmap = "1.7.0" ryu-js = "0.2.1" chrono = "0.4.19" fast-float = "0.2.0" +unicode-normalization = "0.1.19" # Optional Dependencies measureme = { version = "9.1.2", optional = true } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index b2ee8972e0..851a28cb3c 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -30,6 +30,7 @@ use std::{ cmp::{max, min}, string::String as StdString, }; +use unicode_normalization::UnicodeNormalization; pub(crate) fn code_point_at(string: RcString, position: i32) -> Option<(u32, u8, bool)> { let size = string.encode_utf16().count() as i32; @@ -119,6 +120,7 @@ impl BuiltIn for String { .method(Self::index_of, "indexOf", 1) .method(Self::last_index_of, "lastIndexOf", 1) .method(Self::r#match, "match", 1) + .method(Self::normalize, "normalize", 1) .method(Self::pad_end, "padEnd", 1) .method(Self::pad_start, "padStart", 1) .method(Self::trim, "trim", 0) @@ -1329,6 +1331,40 @@ impl String { RegExp::match_all(&re, this.to_string(context)?.to_string(), context) } + /// `String.prototype.normalize( [ form ] )` + /// + /// The normalize() method normalizes a string into a form specified in the UnicodeĀ® Standard Annex #15 + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.normalize + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize + pub(crate) fn normalize(this: &Value, args: &[Value], context: &mut Context) -> Result { + let this = this.require_object_coercible(context)?; + let s = this.to_string(context)?; + let form = args.get(0).cloned().unwrap_or_default(); + + let f_str; + + let f = if form.is_undefined() { + "NFC" + } else { + f_str = form.to_string(context)?; + f_str.as_str() + }; + + match f { + "NFC" => Ok(Value::from(s.nfc().collect::())), + "NFD" => Ok(Value::from(s.nfd().collect::())), + "NFKC" => Ok(Value::from(s.nfkc().collect::())), + "NFKD" => Ok(Value::from(s.nfkd().collect::())), + _ => context + .throw_range_error("The normalization form should be one of NFC, NFD, NFKC, NFKD."), + } + } + /// `String.prototype.search( regexp )` /// /// The search() method executes a search for a match between a regular expression and this String object.