mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): improve .charCodeAt(arg) when arg is valid (#8534)
This commit is contained in:
parent
b1d018622b
commit
927f43ff84
3 changed files with 40 additions and 30 deletions
|
|
@ -9,26 +9,32 @@ pub trait StringCharAt {
|
|||
impl StringCharAt for &str {
|
||||
#[expect(clippy::cast_sign_loss)]
|
||||
fn char_at(&self, index: Option<f64>) -> Option<char> {
|
||||
let index = index.map_or(0, |x| x.to_int_32() as isize);
|
||||
let index = index.unwrap_or(0.0);
|
||||
if index.fract() != 0.0 || index.is_nan() || index.is_infinite() {
|
||||
return None;
|
||||
}
|
||||
let index = index.to_int_32() as isize;
|
||||
if index < 0 {
|
||||
None
|
||||
} else {
|
||||
self.chars().nth(index as usize)
|
||||
self.encode_utf16().nth(index as usize).and_then(|n| char::from_u32(u32::from(n)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::StringCharAt;
|
||||
|
||||
#[test]
|
||||
fn test_evaluate_string_char_at() {
|
||||
use crate::string_char_at::StringCharAt;
|
||||
assert_eq!("test".char_at(Some(0.0)), Some('t'));
|
||||
assert_eq!("test".char_at(Some(1.0)), Some('e'));
|
||||
assert_eq!("test".char_at(Some(2.0)), Some('s'));
|
||||
assert_eq!("test".char_at(Some(-1.0)), None);
|
||||
assert_eq!("test".char_at(Some(-1.1)), None);
|
||||
assert_eq!("test".char_at(Some(-1_073_741_825.0)), None);
|
||||
let s = "test";
|
||||
assert_eq!(s.char_at(Some(0.0)), Some('t'));
|
||||
assert_eq!(s.char_at(Some(1.0)), Some('e'));
|
||||
assert_eq!(s.char_at(Some(2.0)), Some('s'));
|
||||
assert_eq!(s.char_at(Some(0.5)), None);
|
||||
assert_eq!(s.char_at(Some(-1.0)), None);
|
||||
assert_eq!(s.char_at(Some(-1.1)), None);
|
||||
assert_eq!(s.char_at(Some(-1_073_741_825.0)), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,19 +14,21 @@ impl StringCharCodeAt for &str {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::StringCharCodeAt;
|
||||
|
||||
#[test]
|
||||
fn test_evaluate_char_code_at() {
|
||||
use crate::StringCharCodeAt;
|
||||
|
||||
assert_eq!("abcde".char_code_at(Some(0.0)), Some(97));
|
||||
assert_eq!("abcde".char_code_at(Some(1.0)), Some(98));
|
||||
assert_eq!("abcde".char_code_at(Some(2.0)), Some(99));
|
||||
assert_eq!("abcde".char_code_at(Some(3.0)), Some(100));
|
||||
assert_eq!("abcde".char_code_at(Some(4.0)), Some(101));
|
||||
assert_eq!("abcde".char_code_at(Some(5.0)), None);
|
||||
assert_eq!("abcde".char_code_at(Some(-1.0)), None);
|
||||
assert_eq!("abcde".char_code_at(None), Some(97));
|
||||
assert_eq!("abcde".char_code_at(Some(0.0)), Some(97));
|
||||
let s = "abcde";
|
||||
assert_eq!(s.char_code_at(Some(0.0)), Some(97));
|
||||
assert_eq!(s.char_code_at(Some(1.0)), Some(98));
|
||||
assert_eq!(s.char_code_at(Some(2.0)), Some(99));
|
||||
assert_eq!(s.char_code_at(Some(3.0)), Some(100));
|
||||
assert_eq!(s.char_code_at(Some(4.0)), Some(101));
|
||||
assert_eq!(s.char_code_at(Some(5.0)), None);
|
||||
assert_eq!(s.char_code_at(Some(-1.0)), None);
|
||||
assert_eq!(s.char_code_at(None), Some(97));
|
||||
assert_eq!(s.char_code_at(Some(0.0)), Some(97));
|
||||
assert_eq!(s.char_code_at(Some(f64::NAN)), None);
|
||||
assert_eq!(s.char_code_at(Some(f64::INFINITY)), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ impl<'a> PeepholeReplaceKnownMethods {
|
|||
Some(ctx.ast.expression_string_literal(ce.span, result, None))
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_lossless)]
|
||||
fn try_fold_string_char_code_at(
|
||||
ce: &CallExpression<'a>,
|
||||
object: &Expression<'a>,
|
||||
|
|
@ -196,10 +197,14 @@ impl<'a> PeepholeReplaceKnownMethods {
|
|||
Some(Argument::SpreadElement(_)) => None,
|
||||
Some(e) => Ctx(ctx).get_side_free_number_value(e.to_expression()),
|
||||
}?;
|
||||
// TODO: if `result` is `None`, return `NaN` instead of skipping the optimization
|
||||
let result = s.value.as_str().char_code_at(Some(char_at_index))?;
|
||||
#[expect(clippy::cast_lossless)]
|
||||
Some(ctx.ast.expression_numeric_literal(ce.span, result as f64, None, NumberBase::Decimal))
|
||||
let value = if (0.0..65536.0).contains(&char_at_index) {
|
||||
s.value.as_str().char_code_at(Some(char_at_index))? as f64
|
||||
} else if char_at_index.is_nan() || char_at_index.is_infinite() {
|
||||
return None;
|
||||
} else {
|
||||
f64::NAN
|
||||
};
|
||||
Some(ctx.ast.expression_numeric_literal(ce.span, value, None, NumberBase::Decimal))
|
||||
}
|
||||
|
||||
fn try_fold_string_replace(
|
||||
|
|
@ -606,18 +611,15 @@ mod test {
|
|||
fold("x = 'abcde'.charCodeAt(2)", "x = 99");
|
||||
fold("x = 'abcde'.charCodeAt(3)", "x = 100");
|
||||
fold("x = 'abcde'.charCodeAt(4)", "x = 101");
|
||||
fold_same("x = 'abcde'.charCodeAt(5)"); // or x = (0/0)
|
||||
fold_same("x = 'abcde'.charCodeAt(-1)"); // or x = (0/0)
|
||||
fold_same("x = 'abcde'.charCodeAt(5)");
|
||||
fold("x = 'abcde'.charCodeAt(-1)", "x = NaN");
|
||||
fold_same("x = 'abcde'.charCodeAt(y)");
|
||||
// Seems that it does not handle this case
|
||||
// fold("x = 'abcde'.charCodeAt()", "x = 97");
|
||||
fold("x = 'abcde'.charCodeAt()", "x = 97");
|
||||
fold("x = 'abcde'.charCodeAt(0, ++z)", "x = 97");
|
||||
fold("x = 'abcde'.charCodeAt(null)", "x = 97");
|
||||
fold("x = 'abcde'.charCodeAt(true)", "x = 98");
|
||||
// fold("x = '\\ud834\udd1e'.charCodeAt(0)", "x = 55348");
|
||||
// fold("x = '\\ud834\udd1e'.charCodeAt(1)", "x = 56606");
|
||||
|
||||
// Template strings
|
||||
fold_same("x = `abcdef`.charCodeAt(0)");
|
||||
fold_same("x = `abcdef ${abc}`.charCodeAt(0)");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue