mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(ecmascript): correct parseFloat decimal prefix scanning (#27695)
Correct `parseFloat` constant folding to stop at numeric separators and accept an exponent after a decimal point without fractional digits. `parseFloat('1_2')` now folds to `1` instead of `12`, `parseFloat('1e2_3')` to `100` instead of `1e23`, and `parseFloat('1.e2')` to `100` instead of `1`.
This commit is contained in:
1 parent
b7a1df3ef8
commit
863b359d49
2 files changed
+74
-34
No files matched your search
@@ -702,7 +702,7 @@ fn try_fold_global_parse_float<'a>(
|
||||
return Some(ConstantValue::Number(f64::NAN));
|
||||
};
|
||||
|
||||
let parsed = trimmed_prefix.cow_replace('_', "").parse::<f64>().unwrap_or_else(|_| {
|
||||
let parsed = trimmed_prefix.parse::<f64>().unwrap_or_else(|_| {
|
||||
unreachable!(
|
||||
"StrDecimalLiteral should be parse-able with Rust FromStr for f64: {trimmed_prefix}"
|
||||
)
|
||||
@@ -719,24 +719,11 @@ fn find_str_decimal_literal_prefix(input: &str) -> Option<&str> {
|
||||
fn match_decimal_digits(s: &str) -> Option<usize> {
|
||||
let bytes = s.as_bytes();
|
||||
if bytes.first().is_none_or(|b| !b.is_ascii_digit()) {
|
||||
// must have at least one digit
|
||||
return None;
|
||||
}
|
||||
let mut iter = bytes.iter().enumerate().skip(1);
|
||||
while let Some((i, &b)) = iter.next() {
|
||||
match b {
|
||||
b'0'..=b'9' => {}
|
||||
b'_' => {
|
||||
let Some((i, &b)) = iter.next() else {
|
||||
// must have at least one digit after _
|
||||
return Some(i); // without _
|
||||
};
|
||||
if !b.is_ascii_digit() {
|
||||
// must have at least one digit after _
|
||||
return Some(i); // without _
|
||||
}
|
||||
}
|
||||
_ => return Some(i),
|
||||
for (i, b) in bytes.iter().enumerate().skip(1) {
|
||||
if !b.is_ascii_digit() {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
Some(s.len())
|
||||
@@ -784,23 +771,15 @@ fn find_str_decimal_literal_prefix(input: &str) -> Option<&str> {
|
||||
last_index += end_of_decimal_digits;
|
||||
s = &s[end_of_decimal_digits..];
|
||||
|
||||
// DecimalDigits . DecimalDigits ExponentPart
|
||||
if s.starts_with('.') {
|
||||
last_index += 1;
|
||||
s = &s[1..];
|
||||
let Some(end_of_decimal_digits) = match_decimal_digits(s) else {
|
||||
return Some(&input[..last_index - 1]); // without .
|
||||
};
|
||||
last_index += end_of_decimal_digits;
|
||||
s = &s[end_of_decimal_digits..];
|
||||
let Some(end_of_exponent_part) = match_exponent_part(s) else {
|
||||
return Some(&input[..last_index]);
|
||||
};
|
||||
last_index += end_of_exponent_part;
|
||||
return Some(&input[..last_index]);
|
||||
if let Some(end_of_decimal_digits) = match_decimal_digits(s) {
|
||||
last_index += end_of_decimal_digits;
|
||||
s = &s[end_of_decimal_digits..];
|
||||
}
|
||||
}
|
||||
|
||||
// DecimalDigits ExponentPart
|
||||
let Some(end_of_exponent_part) = match_exponent_part(s) else {
|
||||
return Some(&input[..last_index]);
|
||||
};
|
||||
@@ -892,9 +871,17 @@ fn test_find_str_decimal_literal_prefix() {
|
||||
assert_eq!(find_str_decimal_literal_prefix("0a"), Some("0"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("+0a"), Some("+0"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("-0a"), Some("-0"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0."), Some("0"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0.e"), Some("0"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0.e1"), Some("0"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0."), Some("0."));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0.e"), Some("0."));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0.e1"), Some("0.e1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.e+2"), Some("1.e+2"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.e-2"), Some("1.e-2"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.e+"), Some("1."));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.e-"), Some("1."));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.e2_3"), Some("1.e2"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.e_2"), Some("1."));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.e2é"), Some("1.e2"));
|
||||
assert_eq!(find_str_decimal_literal_prefix(".e2"), None);
|
||||
assert_eq!(find_str_decimal_literal_prefix("0.1"), Some("0.1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0.1."), Some("0.1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("0.1e"), Some("0.1"));
|
||||
@@ -904,6 +891,14 @@ fn test_find_str_decimal_literal_prefix() {
|
||||
assert_eq!(find_str_decimal_literal_prefix(".1e"), Some(".1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix(".1e1"), Some(".1e1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1_"), Some("1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1_1"), Some("1_1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1_1_"), Some("1_1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1_1"), Some("1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1_1_"), Some("1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1_é"), Some("1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1.2_3"), Some("1.2"));
|
||||
assert_eq!(find_str_decimal_literal_prefix(".1_2"), Some(".1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1e_2"), Some("1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1e+_2"), Some("1"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("1e2_3"), Some("1e2"));
|
||||
assert_eq!(find_str_decimal_literal_prefix("_1"), None);
|
||||
assert_eq!(find_str_decimal_literal_prefix("._1"), None);
|
||||
}
|
||||
@@ -772,6 +772,51 @@ fn test_fold_parse_numbers() {
|
||||
test("x = parseInt('')", "x = NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_parse_float_numeric_separators() {
|
||||
test("x = parseFloat('1_2')", "x = 1");
|
||||
test("x = parseFloat('-1_2')", "x = -1");
|
||||
test("x = parseFloat('-0_1')", "x = -0");
|
||||
test("x = parseFloat('1_')", "x = 1");
|
||||
test("x = parseFloat('1__2')", "x = 1");
|
||||
test("x = parseFloat('1_é')", "x = 1");
|
||||
test("x = parseFloat('1_.2')", "x = 1");
|
||||
test("x = parseFloat('1.2_3')", "x = 1.2");
|
||||
test("x = parseFloat('.1_2')", "x = 0.1");
|
||||
test("x = parseFloat('1._2')", "x = 1");
|
||||
test("x = parseFloat('1.2_e3')", "x = 1.2");
|
||||
test("x = parseFloat('1e_2')", "x = 1");
|
||||
test("x = parseFloat('1e+_2')", "x = 1");
|
||||
test("x = parseFloat('1e-_2')", "x = 1");
|
||||
test("x = parseFloat('1e2_3')", "x = 100");
|
||||
test("x = parseFloat('1e+2_3')", "x = 100");
|
||||
test("x = parseFloat('1e-2_3')", "x = 0.01");
|
||||
test("x = parseFloat('1.2e3_4')", "x = 1200");
|
||||
test("x = parseFloat('_1')", "x = NaN");
|
||||
test("x = parseFloat('+_1')", "x = NaN");
|
||||
test("x = parseFloat('._1')", "x = NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_parse_float_exponent_after_decimal_point() {
|
||||
test("x = parseFloat('1.e2')", "x = 100");
|
||||
test("x = parseFloat('+1.E2')", "x = 100");
|
||||
test("x = parseFloat('1.e+2')", "x = 100");
|
||||
test("x = parseFloat('1.e-2')", "x = 0.01");
|
||||
test("x = parseFloat('-1.e2')", "x = -100");
|
||||
test("x = parseFloat('-0.e2')", "x = -0");
|
||||
test("x = parseFloat('1.e2é')", "x = 100");
|
||||
test("x = parseFloat('1.e2_3')", "x = 100");
|
||||
test("x = parseFloat('1.e_2')", "x = 1");
|
||||
test("x = parseFloat('1.e+_2')", "x = 1");
|
||||
test("x = parseFloat('1.e')", "x = 1");
|
||||
test("x = parseFloat('1.e+')", "x = 1");
|
||||
test("x = parseFloat('1.e-')", "x = 1");
|
||||
test("x = parseFloat('1.')", "x = 1");
|
||||
test("x = parseFloat('1..e2')", "x = 1");
|
||||
test("x = parseFloat('.e2')", "x = NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_parse_octal_numbers() {
|
||||
test("x = parseInt('021', 8)", "x = 17");
|
||||
|
||||
Reference in new issue
Block a user