mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): implement folding charAt string fns (#6436)
This commit is contained in:
parent
e5a6f5de6a
commit
096e590483
3 changed files with 76 additions and 9 deletions
|
|
@ -7,6 +7,7 @@ mod private_bound_identifiers;
|
|||
mod prop_name;
|
||||
|
||||
// Abstract Operations
|
||||
mod string_char_at;
|
||||
mod string_index_of;
|
||||
mod string_last_index_of;
|
||||
mod to_int_32;
|
||||
|
|
@ -14,5 +15,6 @@ mod to_int_32;
|
|||
pub use self::{
|
||||
bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList,
|
||||
private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName,
|
||||
string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf, to_int_32::ToInt32,
|
||||
string_char_at::StringCharAt, string_index_of::StringIndexOf,
|
||||
string_last_index_of::StringLastIndexOf, to_int_32::ToInt32,
|
||||
};
|
||||
|
|
|
|||
34
crates/oxc_ecmascript/src/string_char_at.rs
Normal file
34
crates/oxc_ecmascript/src/string_char_at.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use crate::ToInt32;
|
||||
|
||||
pub trait StringCharAt {
|
||||
/// `String.prototype.charAt ( pos )`
|
||||
/// <https://tc39.es/ecma262/#sec-string.prototype.charat>
|
||||
fn char_at(&self, index: Option<f64>) -> Option<char>;
|
||||
}
|
||||
|
||||
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);
|
||||
if index < 0 {
|
||||
None
|
||||
} else {
|
||||
self.chars().nth(index as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use cow_utils::CowUtils;
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{StringIndexOf, StringLastIndexOf};
|
||||
use oxc_ecmascript::{StringCharAt, StringIndexOf, StringLastIndexOf};
|
||||
use oxc_traverse::{Traverse, TraverseCtx};
|
||||
|
||||
use crate::CompressorPass;
|
||||
|
|
@ -76,7 +75,9 @@ impl PeepholeReplaceKnownMethods {
|
|||
// TODO: Implement the rest of the string methods
|
||||
"substr" => None,
|
||||
"substring" | "slice" => None,
|
||||
"charAt" => None,
|
||||
"charAt" => {
|
||||
Self::try_fold_string_char_at(call_expr.span, call_expr, string_lit, ctx)
|
||||
}
|
||||
"charCodeAt" => None,
|
||||
"replace" => None,
|
||||
"replaceAll" => None,
|
||||
|
|
@ -125,6 +126,35 @@ impl PeepholeReplaceKnownMethods {
|
|||
NumberBase::Decimal,
|
||||
)));
|
||||
}
|
||||
|
||||
fn try_fold_string_char_at<'a>(
|
||||
span: Span,
|
||||
call_expr: &CallExpression<'a>,
|
||||
string_lit: &StringLiteral<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let char_at_index: Option<f64> = match call_expr.arguments.first() {
|
||||
Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value),
|
||||
Some(Argument::UnaryExpression(unary_expr))
|
||||
if unary_expr.operator == UnaryOperator::UnaryNegation =>
|
||||
{
|
||||
let Expression::NumericLiteral(numeric_lit) = &unary_expr.argument else {
|
||||
return None;
|
||||
};
|
||||
Some(-(numeric_lit.value))
|
||||
}
|
||||
None => None,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let result = &string_lit
|
||||
.value
|
||||
.as_str()
|
||||
.char_at(char_at_index)
|
||||
.map_or(String::new(), |v| v.to_string());
|
||||
|
||||
return Some(ctx.ast.expression_from_string_literal(ctx.ast.string_literal(span, result)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Port from: <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeReplaceKnownMethodsTest.java>
|
||||
|
|
@ -382,18 +412,19 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_fold_string_char_at() {
|
||||
fold("x = 'abcde'.charAt(0)", "x = 'a'");
|
||||
fold("x = 'abcde'.charAt(1)", "x = 'b'");
|
||||
fold("x = 'abcde'.charAt(2)", "x = 'c'");
|
||||
fold("x = 'abcde'.charAt(3)", "x = 'd'");
|
||||
fold("x = 'abcde'.charAt(4)", "x = 'e'");
|
||||
fold_same("x = 'abcde'.charAt(5)"); // or x = ''
|
||||
fold_same("x = 'abcde'.charAt(-1)"); // or x = ''
|
||||
// START: note, the following test cases outputs differ from Google's
|
||||
fold("x = 'abcde'.charAt(5)", "x = ''");
|
||||
fold("x = 'abcde'.charAt(-1)", "x = ''");
|
||||
fold("x = 'abcde'.charAt()", "x = 'a'");
|
||||
fold("x = 'abcde'.charAt(0, ++z)", "x = 'a'");
|
||||
// END
|
||||
fold_same("x = 'abcde'.charAt(y)");
|
||||
fold_same("x = 'abcde'.charAt()"); // or x = 'a'
|
||||
fold_same("x = 'abcde'.charAt(0, ++z)"); // or (++z, 'a')
|
||||
fold_same("x = 'abcde'.charAt(null)"); // or x = 'a'
|
||||
fold_same("x = 'abcde'.charAt(true)"); // or x = 'b'
|
||||
// fold("x = '\\ud834\udd1e'.charAt(0)", "x = '\\ud834'");
|
||||
|
|
|
|||
Loading…
Reference in a new issue