diff --git a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs index df7fc7733..476464b0c 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs @@ -1,4 +1,7 @@ +use std::borrow::Cow; + use cow_utils::CowUtils; + use oxc_ast::ast::*; use oxc_ecmascript::{ constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf, @@ -37,55 +40,24 @@ impl PeepholeReplaceKnownMethods { node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>, ) { - let Expression::CallExpression(call_expr) = node else { return }; - - let Expression::StaticMemberExpression(member) = &call_expr.callee else { return }; - if let Expression::StringLiteral(string_lit) = &member.object { + let Expression::CallExpression(ce) = node else { return }; + let Expression::StaticMemberExpression(member) = &ce.callee else { return }; + if let Expression::StringLiteral(s) = &member.object { let replacement = match member.property.name.as_str() { - "toLowerCase" | "toUpperCase" | "trim" => match member.property.name.as_str() { - "toLowerCase" => Some(ctx.ast.expression_string_literal( - call_expr.span, - string_lit.value.cow_to_lowercase(), - None, - )), - "toUpperCase" => Some(ctx.ast.expression_string_literal( - call_expr.span, - string_lit.value.cow_to_uppercase(), - None, - )), - "trim" => Some(ctx.ast.expression_string_literal( - call_expr.span, - string_lit.value.trim(), - None, - )), - _ => None, - }, - "indexOf" | "lastIndexOf" => Self::try_fold_string_index_of( - call_expr.span, - call_expr, - member, - string_lit, - ctx, - ), - "substring" | "slice" => Self::try_fold_string_substring_or_slice( - call_expr.span, - call_expr, - string_lit, - ctx, - ), - "charAt" => { - Self::try_fold_string_char_at(call_expr.span, call_expr, string_lit, ctx) + "toLowerCase" | "toUpperCase" | "trim" => { + 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, + }; + Some(ctx.ast.expression_string_literal(ce.span, value, None)) } - "charCodeAt" => { - Self::try_fold_string_char_code_at(call_expr.span, call_expr, string_lit, ctx) - } - "replace" | "replaceAll" => Self::try_fold_string_replace_or_string_replace_all( - call_expr.span, - call_expr, - member, - string_lit, - 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, }; @@ -97,7 +69,6 @@ impl PeepholeReplaceKnownMethods { } fn try_fold_string_index_of<'a>( - span: Span, call_expr: &CallExpression<'a>, member: &StaticMemberExpression<'a>, string_lit: &StringLiteral<'a>, @@ -108,13 +79,11 @@ impl PeepholeReplaceKnownMethods { None => None, _ => return None, }; - let search_start_index = match call_expr.arguments.get(1) { Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value), None => None, _ => return None, }; - let result = match member.property.name.as_str() { "indexOf" => string_lit.value.as_str().index_of(search_value, search_start_index), "lastIndexOf" => { @@ -122,13 +91,12 @@ impl PeepholeReplaceKnownMethods { } _ => unreachable!(), }; - + let span = call_expr.span; #[expect(clippy::cast_precision_loss)] Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal)) } fn try_fold_string_substring_or_slice<'a>( - span: Span, call_expr: &CallExpression<'a>, string_lit: &StringLiteral<'a>, ctx: &mut TraverseCtx<'a>, @@ -136,7 +104,7 @@ impl PeepholeReplaceKnownMethods { if call_expr.arguments.len() > 2 { return None; } - + let span = call_expr.span; let start_idx = call_expr.arguments.first().and_then(|arg| match arg { Argument::SpreadElement(_) => None, _ => Ctx(ctx).get_side_free_number_value(arg.to_expression()), @@ -145,20 +113,17 @@ impl PeepholeReplaceKnownMethods { Argument::SpreadElement(_) => None, _ => Ctx(ctx).get_side_free_number_value(arg.to_expression()), }); - #[expect(clippy::cast_precision_loss)] if start_idx.is_some_and(|start| start > string_lit.value.len() as f64 || start < 0.0) || end_idx.is_some_and(|end| end > string_lit.value.len() as f64 || end < 0.0) { return None; } - if let (Some(start), Some(end)) = (start_idx, end_idx) { if start > end { return None; } }; - Some(ctx.ast.expression_string_literal( span, string_lit.value.as_str().substring(start_idx, end_idx), @@ -167,7 +132,6 @@ impl PeepholeReplaceKnownMethods { } fn try_fold_string_char_at<'a>( - span: Span, call_expr: &CallExpression<'a>, string_lit: &StringLiteral<'a>, ctx: &mut TraverseCtx<'a>, @@ -175,7 +139,7 @@ impl PeepholeReplaceKnownMethods { if call_expr.arguments.len() > 1 { return None; } - + let span = call_expr.span; let char_at_index: Option = match call_expr.arguments.first() { Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value), Some(Argument::UnaryExpression(unary_expr)) @@ -200,7 +164,6 @@ impl PeepholeReplaceKnownMethods { } fn try_fold_string_char_code_at<'a>( - span: Span, call_expr: &CallExpression<'a>, string_lit: &StringLiteral<'a>, ctx: &mut TraverseCtx<'a>, @@ -209,16 +172,14 @@ impl PeepholeReplaceKnownMethods { Argument::SpreadElement(_) => None, _ => Ctx(ctx).get_side_free_number_value(arg.to_expression()), })?; - + let span = call_expr.span; // 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))?; - #[expect(clippy::cast_lossless)] Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal)) } - fn try_fold_string_replace_or_string_replace_all<'a>( - span: Span, + fn try_fold_string_replace<'a>( call_expr: &CallExpression<'a>, member: &StaticMemberExpression<'a>, string_lit: &StringLiteral<'a>, @@ -227,7 +188,7 @@ impl PeepholeReplaceKnownMethods { if call_expr.arguments.len() != 2 { return None; } - + let span = call_expr.span; let search_value = call_expr.arguments.first().unwrap(); let search_value = match search_value { Argument::SpreadElement(_) => return None, @@ -242,11 +203,9 @@ impl PeepholeReplaceKnownMethods { Ctx(ctx).get_side_free_string_value(replace_value.to_expression())? } }; - if replace_value.contains('$') { return None; } - let result = match member.property.name.as_str() { "replace" => { string_lit.value.as_str().cow_replacen(search_value.as_ref(), &replace_value, 1) @@ -256,7 +215,6 @@ impl PeepholeReplaceKnownMethods { } _ => unreachable!(), }; - Some(ctx.ast.expression_string_literal(span, result, None)) } }