Skip to content

Commit 9a199b4

Browse files
authored
feat(string): add normalize method (#441)
1 parent 50e4856 commit 9a199b4

File tree

5 files changed

+88
-15
lines changed

5 files changed

+88
-15
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ oxc_syntax = "0.30.3"
2323
rand = "0.8.5"
2424
ryu-js = "1.0.1"
2525
sonic-rs = "0.3.13"
26+
unicode-normalization = "0.1.24"
2627
wtf8 = "0.1"

nova_vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ rand = { workspace = true }
2020
ryu-js = { workspace = true }
2121
small_string = { path = "../small_string" }
2222
sonic-rs = { workspace = true, optional = true }
23+
unicode-normalization = { workspace = true }
2324
wtf8 = { workspace = true }
2425

2526
[features]

nova_vm/src/ecmascript/builtins/text_processing/string_objects/string_prototype.rs

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use std::{cmp::max, collections::VecDeque, iter::repeat};
5+
use std::{cmp::max, collections::VecDeque, iter::repeat, str::FromStr};
66

77
use small_string::SmallString;
8+
use unicode_normalization::{
9+
is_nfc_quick, is_nfd_quick, is_nfkc_quick, is_nfkd_quick, IsNormalized, UnicodeNormalization,
10+
};
811

912
use crate::{
1013
ecmascript::{
@@ -567,8 +570,44 @@ impl StringPrototype {
567570
todo!()
568571
}
569572

570-
fn normalize(_agent: &mut Agent, _this_value: Value, _: ArgumentsList) -> JsResult<Value> {
571-
todo!()
573+
/// ### [22.1.3.15 String.prototype.normalize ( \[ form \] )](https://tc39.es/ecma262/#sec-string.prototype.normalize)
574+
fn normalize(
575+
agent: &mut Agent,
576+
this_value: Value,
577+
arguments: ArgumentsList,
578+
) -> JsResult<Value> {
579+
// 1. Let O be ? RequireObjectCoercible(this value).
580+
let o = require_object_coercible(agent, this_value)?;
581+
582+
// 2. Let S be ? ToString(O).
583+
let s = to_string(agent, o)?;
584+
585+
// 3. If form is undefined, let f be "NFC".
586+
let form = arguments.get(0);
587+
let f = if form.is_undefined() {
588+
NormalizeForm::Nfc
589+
} else {
590+
// 4. Else, let f be ? ToString(form).
591+
let f = to_string(agent, form)?;
592+
let form_result = NormalizeForm::from_str(f.as_str(agent));
593+
match form_result {
594+
Ok(form) => form,
595+
// 5. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError exception.
596+
Err(()) => {
597+
return Err(agent.throw_exception_with_static_message(
598+
ExceptionType::RangeError,
599+
"The normalization form should be one of NFC, NFD, NFKC, NFKD.",
600+
))
601+
}
602+
}
603+
};
604+
605+
// 6. Let ns be the String value that is the result of normalizing S into the normalization form named by f as specified in the latest Unicode Standard, Normalization Forms.
606+
match unicode_normalize(s.as_str(agent), f) {
607+
// 7. Return ns.
608+
None => Ok(s.into_value()),
609+
Some(ns) => Ok(Value::from_string(agent, ns).into_value()),
610+
}
572611
}
573612

574613
/// ### [22.1.3.16 String.prototype.padEnd ( maxLength \[ , fillString \] )](https://tc39.es/ecma262/#sec-string.prototype.padend)
@@ -1478,3 +1517,45 @@ enum TrimWhere {
14781517
End,
14791518
StartAndEnd,
14801519
}
1520+
1521+
enum NormalizeForm {
1522+
Nfc,
1523+
Nfd,
1524+
Nfkc,
1525+
Nfkd,
1526+
}
1527+
1528+
impl FromStr for NormalizeForm {
1529+
type Err = ();
1530+
1531+
fn from_str(input: &str) -> Result<NormalizeForm, Self::Err> {
1532+
match input {
1533+
"NFC" => Ok(NormalizeForm::Nfc),
1534+
"NFD" => Ok(NormalizeForm::Nfd),
1535+
"NFKC" => Ok(NormalizeForm::Nfkc),
1536+
"NFKD" => Ok(NormalizeForm::Nfkd),
1537+
_ => Err(()),
1538+
}
1539+
}
1540+
}
1541+
1542+
fn unicode_normalize(s: &str, f: NormalizeForm) -> Option<std::string::String> {
1543+
match f {
1544+
NormalizeForm::Nfc => match is_nfc_quick(s.chars()) {
1545+
IsNormalized::Yes => None,
1546+
_ => Some(s.nfc().collect::<std::string::String>()),
1547+
},
1548+
NormalizeForm::Nfd => match is_nfd_quick(s.chars()) {
1549+
IsNormalized::Yes => None,
1550+
_ => Some(s.nfd().collect::<std::string::String>()),
1551+
},
1552+
NormalizeForm::Nfkc => match is_nfkc_quick(s.chars()) {
1553+
IsNormalized::Yes => None,
1554+
_ => Some(s.nfkc().collect::<std::string::String>()),
1555+
},
1556+
NormalizeForm::Nfkd => match is_nfkd_quick(s.chars()) {
1557+
IsNormalized::Yes => None,
1558+
_ => Some(s.nfkd().collect::<std::string::String>()),
1559+
},
1560+
}
1561+
}

tests/expectations.json

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6207,16 +6207,6 @@
62076207
"built-ins/String/prototype/matchAll/regexp-prototype-matchAll-v-u-flag.js": "CRASH",
62086208
"built-ins/String/prototype/matchAll/this-val-non-obj-coercible.js": "CRASH",
62096209
"built-ins/String/prototype/matchAll/toString-this-val.js": "CRASH",
6210-
"built-ins/String/prototype/normalize/form-is-not-valid-throws.js": "CRASH",
6211-
"built-ins/String/prototype/normalize/return-abrupt-from-form-as-symbol.js": "CRASH",
6212-
"built-ins/String/prototype/normalize/return-abrupt-from-form.js": "CRASH",
6213-
"built-ins/String/prototype/normalize/return-abrupt-from-this-as-symbol.js": "CRASH",
6214-
"built-ins/String/prototype/normalize/return-abrupt-from-this.js": "CRASH",
6215-
"built-ins/String/prototype/normalize/return-normalized-string-from-coerced-form.js": "CRASH",
6216-
"built-ins/String/prototype/normalize/return-normalized-string-using-default-parameter.js": "CRASH",
6217-
"built-ins/String/prototype/normalize/return-normalized-string.js": "CRASH",
6218-
"built-ins/String/prototype/normalize/this-is-null-throws.js": "CRASH",
6219-
"built-ins/String/prototype/normalize/this-is-undefined-throws.js": "CRASH",
62206210
"built-ins/String/prototype/padEnd/normal-operation.js": "CRASH",
62216211
"built-ins/String/prototype/padStart/normal-operation.js": "CRASH",
62226212
"built-ins/String/prototype/repeat/repeat-string-n-times.js": "TIMEOUT",

tests/metrics.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"results": {
3-
"crash": 16307,
3+
"crash": 16297,
44
"fail": 8261,
5-
"pass": 20680,
5+
"pass": 20690,
66
"skip": 40,
77
"timeout": 3,
88
"unresolved": 0

0 commit comments

Comments
 (0)