refactor(minifier): clean up peephole_replace_known_methods (#8306)

This commit is contained in:
Boshen 2025-01-07 02:29:49 +00:00
parent 66a24437ac
commit d16e598678

View file

@ -42,88 +42,93 @@ impl<'a> PeepholeReplaceKnownMethods {
) { ) {
let Expression::CallExpression(ce) = node else { return }; let Expression::CallExpression(ce) = node else { return };
let Expression::StaticMemberExpression(member) = &ce.callee else { return }; let Expression::StaticMemberExpression(member) = &ce.callee else { return };
let replacement = match &member.object { if member.optional {
Expression::StringLiteral(s) => match member.property.name.as_str() { return;
"toLowerCase" | "toUpperCase" | "trim" => { }
let value = match member.property.name.as_str() { let replacement = match member.property.name.as_str() {
"toLowerCase" => s.value.cow_to_lowercase(), "toLowerCase" | "toUpperCase" | "trim" => Self::try_fold_string_casing(ce, member, ctx),
"toUpperCase" => s.value.cow_to_uppercase(), "substring" | "slice" => Self::try_fold_string_substring_or_slice(ce, member, ctx),
"trim" => Cow::Borrowed(s.value.trim()), "indexOf" | "lastIndexOf" => Self::try_fold_string_index_of(ce, member, ctx),
_ => return, "charAt" => Self::try_fold_string_char_at(ce, member, ctx),
}; "charCodeAt" => Self::try_fold_string_char_code_at(ce, member, ctx),
Some(ctx.ast.expression_string_literal(ce.span, value, None)) "replace" | "replaceAll" => Self::try_fold_string_replace(ce, member, ctx),
} "fromCharCode" => Self::try_fold_string_from_char_code(ce, member, ctx),
"indexOf" | "lastIndexOf" => Self::try_fold_string_index_of(ce, member, s, ctx),
"substring" | "slice" => Self::try_fold_string_substring_or_slice(ce, s, ctx),
"charAt" => Self::try_fold_string_char_at(ce, s, ctx),
"charCodeAt" => Self::try_fold_string_char_code_at(ce, s, ctx),
"replace" | "replaceAll" => Self::try_fold_string_replace(ce, member, s, ctx),
_ => None,
},
Expression::Identifier(ident)
if ident.name == "String"
&& member.property.name == "fromCharCode"
&& Ctx(ctx).is_global_reference(ident) =>
{
Self::try_fold_string_from_char_code(ce, ctx)
}
_ => None, _ => None,
}; };
if let Some(replacement) = replacement { if let Some(replacement) = replacement {
self.changed = true; self.changed = true;
*node = replacement; *node = replacement;
} }
} }
fn try_fold_string_index_of( fn try_fold_string_casing(
call_expr: &CallExpression<'a>, ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>, member: &StaticMemberExpression<'a>,
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
let search_value = match call_expr.arguments.first() { if ce.arguments.len() >= 1 {
return None;
}
let Expression::StringLiteral(s) = &member.object else { return None };
let value = match member.property.name.as_str() {
"toLowerCase" => s.value.cow_to_lowercase(),
"toUpperCase" => s.value.cow_to_uppercase(),
"trim" => Cow::Borrowed(s.value.trim()),
_ => return None,
};
Some(ctx.ast.expression_string_literal(ce.span, value, None))
}
fn try_fold_string_index_of(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
if args.len() >= 3 {
return None;
}
let Expression::StringLiteral(s) = &member.object else { return None };
let search_value = match args.first() {
Some(Argument::StringLiteral(string_lit)) => Some(string_lit.value.as_str()), Some(Argument::StringLiteral(string_lit)) => Some(string_lit.value.as_str()),
None => None, None => None,
_ => return None, _ => return None,
}; };
let search_start_index = match call_expr.arguments.get(1) { let search_start_index = match args.get(1) {
Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value), Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value),
None => None, None => None,
_ => return None, _ => return None,
}; };
let result = match member.property.name.as_str() { let result = match member.property.name.as_str() {
"indexOf" => string_lit.value.as_str().index_of(search_value, search_start_index), "indexOf" => s.value.as_str().index_of(search_value, search_start_index),
"lastIndexOf" => { "lastIndexOf" => s.value.as_str().last_index_of(search_value, search_start_index),
string_lit.value.as_str().last_index_of(search_value, search_start_index)
}
_ => unreachable!(), _ => unreachable!(),
}; };
let span = call_expr.span;
#[expect(clippy::cast_precision_loss)] #[expect(clippy::cast_precision_loss)]
Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal)) Some(ctx.ast.expression_numeric_literal(ce.span, result as f64, None, NumberBase::Decimal))
} }
fn try_fold_string_substring_or_slice( fn try_fold_string_substring_or_slice(
call_expr: &CallExpression<'a>, ce: &CallExpression<'a>,
string_lit: &StringLiteral<'a>, member: &StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
if call_expr.arguments.len() > 2 { let args = &ce.arguments;
if args.len() > 2 {
return None; return None;
} }
let span = call_expr.span; let Expression::StringLiteral(s) = &member.object else { return None };
let start_idx = call_expr.arguments.first().and_then(|arg| match arg { let start_idx = args.first().and_then(|arg| match arg {
Argument::SpreadElement(_) => None, Argument::SpreadElement(_) => None,
_ => Ctx(ctx).get_side_free_number_value(arg.to_expression()), _ => Ctx(ctx).get_side_free_number_value(arg.to_expression()),
}); });
let end_idx = call_expr.arguments.get(1).and_then(|arg| match arg { let end_idx = args.get(1).and_then(|arg| match arg {
Argument::SpreadElement(_) => None, Argument::SpreadElement(_) => None,
_ => Ctx(ctx).get_side_free_number_value(arg.to_expression()), _ => Ctx(ctx).get_side_free_number_value(arg.to_expression()),
}); });
#[expect(clippy::cast_precision_loss)] #[expect(clippy::cast_precision_loss)]
if start_idx.is_some_and(|start| start > string_lit.value.len() as f64 || start < 0.0) if start_idx.is_some_and(|start| start > s.value.len() as f64 || start < 0.0)
|| end_idx.is_some_and(|end| end > string_lit.value.len() as f64 || end < 0.0) || end_idx.is_some_and(|end| end > s.value.len() as f64 || end < 0.0)
{ {
return None; return None;
} }
@ -133,22 +138,23 @@ impl<'a> PeepholeReplaceKnownMethods {
} }
}; };
Some(ctx.ast.expression_string_literal( Some(ctx.ast.expression_string_literal(
span, ce.span,
string_lit.value.as_str().substring(start_idx, end_idx), s.value.as_str().substring(start_idx, end_idx),
None, None,
)) ))
} }
fn try_fold_string_char_at( fn try_fold_string_char_at(
call_expr: &CallExpression<'a>, ce: &CallExpression<'a>,
string_lit: &StringLiteral<'a>, member: &StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
if call_expr.arguments.len() > 1 { let args = &ce.arguments;
if args.len() > 1 {
return None; return None;
} }
let span = call_expr.span; let Expression::StringLiteral(s) = &member.object else { return None };
let char_at_index: Option<f64> = match call_expr.arguments.first() { let char_at_index: Option<f64> = match args.first() {
Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value), Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value),
Some(Argument::UnaryExpression(unary_expr)) Some(Argument::UnaryExpression(unary_expr))
if unary_expr.operator == UnaryOperator::UnaryNegation => if unary_expr.operator == UnaryOperator::UnaryNegation =>
@ -161,51 +167,46 @@ impl<'a> PeepholeReplaceKnownMethods {
None => None, None => None,
_ => return None, _ => return None,
}; };
let result =
let result = &string_lit &s.value.as_str().char_at(char_at_index).map_or(String::new(), |v| v.to_string());
.value Some(ctx.ast.expression_string_literal(ce.span, result, None))
.as_str()
.char_at(char_at_index)
.map_or(String::new(), |v| v.to_string());
Some(ctx.ast.expression_string_literal(span, result, None))
} }
fn try_fold_string_char_code_at( fn try_fold_string_char_code_at(
call_expr: &CallExpression<'a>, ce: &CallExpression<'a>,
string_lit: &StringLiteral<'a>, member: &StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
let char_at_index = match call_expr.arguments.first() { let Expression::StringLiteral(s) = &member.object else { return None };
let char_at_index = match ce.arguments.first() {
None => Some(0.0), None => Some(0.0),
Some(Argument::SpreadElement(_)) => None, Some(Argument::SpreadElement(_)) => None,
Some(e) => Ctx(ctx).get_side_free_number_value(e.to_expression()), Some(e) => Ctx(ctx).get_side_free_number_value(e.to_expression()),
}?; }?;
let span = call_expr.span;
// TODO: if `result` is `None`, return `NaN` instead of skipping the optimization // TODO: if `result` is `None`, return `NaN` instead of skipping the optimization
let result = string_lit.value.as_str().char_code_at(Some(char_at_index))?; let result = s.value.as_str().char_code_at(Some(char_at_index))?;
#[expect(clippy::cast_lossless)] #[expect(clippy::cast_lossless)]
Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal)) Some(ctx.ast.expression_numeric_literal(ce.span, result as f64, None, NumberBase::Decimal))
} }
fn try_fold_string_replace( fn try_fold_string_replace(
call_expr: &CallExpression<'a>, ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>, member: &StaticMemberExpression<'a>,
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
if call_expr.arguments.len() != 2 { if ce.arguments.len() != 2 {
return None; return None;
} }
let span = call_expr.span; let Expression::StringLiteral(s) = &member.object else { return None };
let search_value = call_expr.arguments.first().unwrap(); let span = ce.span;
let search_value = ce.arguments.first().unwrap();
let search_value = match search_value { let search_value = match search_value {
Argument::SpreadElement(_) => return None, Argument::SpreadElement(_) => return None,
match_expression!(Argument) => { match_expression!(Argument) => {
Ctx(ctx).get_side_free_string_value(search_value.to_expression())? Ctx(ctx).get_side_free_string_value(search_value.to_expression())?
} }
}; };
let replace_value = call_expr.arguments.get(1).unwrap(); let replace_value = ce.arguments.get(1).unwrap();
let replace_value = match replace_value { let replace_value = match replace_value {
Argument::SpreadElement(_) => return None, Argument::SpreadElement(_) => return None,
match_expression!(Argument) => { match_expression!(Argument) => {
@ -216,12 +217,8 @@ impl<'a> PeepholeReplaceKnownMethods {
return None; return None;
} }
let result = match member.property.name.as_str() { let result = match member.property.name.as_str() {
"replace" => { "replace" => s.value.as_str().cow_replacen(search_value.as_ref(), &replace_value, 1),
string_lit.value.as_str().cow_replacen(search_value.as_ref(), &replace_value, 1) "replaceAll" => s.value.as_str().cow_replace(search_value.as_ref(), &replace_value),
}
"replaceAll" => {
string_lit.value.as_str().cow_replace(search_value.as_ref(), &replace_value)
}
_ => unreachable!(), _ => unreachable!(),
}; };
Some(ctx.ast.expression_string_literal(span, result, None)) Some(ctx.ast.expression_string_literal(span, result, None))
@ -230,9 +227,14 @@ impl<'a> PeepholeReplaceKnownMethods {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_lossless)] #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_lossless)]
fn try_fold_string_from_char_code( fn try_fold_string_from_char_code(
ce: &CallExpression<'a>, ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
let Expression::Identifier(ident) = &member.object else { return None };
let ctx = Ctx(ctx); let ctx = Ctx(ctx);
if !ctx.is_global_reference(ident) {
return None;
}
let args = &ce.arguments; let args = &ce.arguments;
let mut s = String::with_capacity(args.len()); let mut s = String::with_capacity(args.len());
for arg in args { for arg in args {