Skip to content

Commit

Permalink
feat(minifier): fold false['toString'] (#8447)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Jan 12, 2025
1 parent dd64340 commit 870a583
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 40 deletions.
18 changes: 10 additions & 8 deletions crates/oxc_minifier/src/ast_passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ pub struct PeepholeOptimizations {
x4_peephole_fold_constants: PeepholeFoldConstants,
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode,
x5_peephole_minimize_conditions: PeepholeMinimizeConditions,
x7_peephole_replace_known_methods: PeepholeReplaceKnownMethods,
x8_convert_to_dotted_properties: ConvertToDottedProperties,
x7_convert_to_dotted_properties: ConvertToDottedProperties,
x8_peephole_replace_known_methods: PeepholeReplaceKnownMethods,
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax,
}

Expand All @@ -63,8 +63,8 @@ impl PeepholeOptimizations {
x4_peephole_fold_constants: PeepholeFoldConstants::new(),
x5_peephole_minimize_conditions: PeepholeMinimizeConditions::new(target),
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(),
x7_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
x8_convert_to_dotted_properties: ConvertToDottedProperties::new(in_fixed_loop),
x7_convert_to_dotted_properties: ConvertToDottedProperties::new(in_fixed_loop),
x8_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new(
options.target,
in_fixed_loop,
Expand All @@ -80,7 +80,8 @@ impl PeepholeOptimizations {
self.x4_peephole_fold_constants.changed = false;
self.x5_peephole_minimize_conditions.changed = false;
self.x6_peephole_remove_dead_code.changed = false;
self.x7_peephole_replace_known_methods.changed = false;
self.x7_convert_to_dotted_properties.changed = false;
self.x8_peephole_replace_known_methods.changed = false;
self.x9_peephole_substitute_alternate_syntax.changed = false;
}

Expand All @@ -92,7 +93,8 @@ impl PeepholeOptimizations {
|| self.x4_peephole_fold_constants.changed
|| self.x5_peephole_minimize_conditions.changed
|| self.x6_peephole_remove_dead_code.changed
|| self.x7_peephole_replace_known_methods.changed
|| self.x7_convert_to_dotted_properties.changed
|| self.x8_peephole_replace_known_methods.changed
|| self.x9_peephole_substitute_alternate_syntax.changed
}

Expand Down Expand Up @@ -162,7 +164,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
self.x4_peephole_fold_constants.exit_expression(expr, ctx);
self.x5_peephole_minimize_conditions.exit_expression(expr, ctx);
self.x6_peephole_remove_dead_code.exit_expression(expr, ctx);
self.x7_peephole_replace_known_methods.exit_expression(expr, ctx);
self.x8_peephole_replace_known_methods.exit_expression(expr, ctx);
self.x9_peephole_substitute_alternate_syntax.exit_expression(expr, ctx);
}

Expand All @@ -179,7 +181,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
expr: &mut MemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x8_convert_to_dotted_properties.exit_member_expression(expr, ctx);
self.x7_convert_to_dotted_properties.exit_member_expression(expr, ctx);
}

fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,29 @@ impl<'a> PeepholeReplaceKnownMethods {
ctx: &mut TraverseCtx<'a>,
) {
let Expression::CallExpression(ce) = node else { return };
let Expression::StaticMemberExpression(member) = &ce.callee else { return };
if member.optional {
return;
}
let replacement = match member.property.name.as_str() {
"toLowerCase" | "toUpperCase" | "trim" => Self::try_fold_string_casing(ce, member, ctx),
"substring" | "slice" => Self::try_fold_string_substring_or_slice(ce, member, ctx),
"indexOf" | "lastIndexOf" => Self::try_fold_string_index_of(ce, member, ctx),
"charAt" => Self::try_fold_string_char_at(ce, member, ctx),
"charCodeAt" => Self::try_fold_string_char_code_at(ce, member, ctx),
"replace" | "replaceAll" => Self::try_fold_string_replace(ce, member, ctx),
"fromCharCode" => Self::try_fold_string_from_char_code(ce, member, ctx),
"toString" => Self::try_fold_to_string(ce, member, ctx),
let (name, object) = match &ce.callee {
Expression::StaticMemberExpression(member) if !member.optional => {
(member.property.name.as_str(), &member.object)
}
Expression::ComputedMemberExpression(member) if !member.optional => {
match &member.expression {
Expression::StringLiteral(s) => (s.value.as_str(), &member.object),
_ => return,
}
}
_ => return,
};
let replacement = match name {
"toLowerCase" | "toUpperCase" | "trim" => {
Self::try_fold_string_casing(ce, name, object, ctx)
}
"substring" | "slice" => Self::try_fold_string_substring_or_slice(ce, object, ctx),
"indexOf" | "lastIndexOf" => Self::try_fold_string_index_of(ce, name, object, ctx),
"charAt" => Self::try_fold_string_char_at(ce, object, ctx),
"charCodeAt" => Self::try_fold_string_char_code_at(ce, object, ctx),
"replace" | "replaceAll" => Self::try_fold_string_replace(ce, name, object, ctx),
"fromCharCode" => Self::try_fold_string_from_char_code(ce, object, ctx),
"toString" => Self::try_fold_to_string(ce, object, ctx),
_ => None,
};
if let Some(replacement) = replacement {
Expand All @@ -64,14 +74,15 @@ impl<'a> PeepholeReplaceKnownMethods {

fn try_fold_string_casing(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
name: &str,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if ce.arguments.len() >= 1 {
return None;
}
let Expression::StringLiteral(s) = &member.object else { return None };
let value = match member.property.name.as_str() {
let Expression::StringLiteral(s) = object else { return None };
let value = match name {
"toLowerCase" => s.value.cow_to_lowercase(),
"toUpperCase" => s.value.cow_to_uppercase(),
"trim" => Cow::Borrowed(s.value.trim()),
Expand All @@ -82,14 +93,15 @@ impl<'a> PeepholeReplaceKnownMethods {

fn try_fold_string_index_of(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
name: &str,
object: &Expression<'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 Expression::StringLiteral(s) = object else { return None };
let search_value = match args.first() {
Some(Argument::StringLiteral(string_lit)) => Some(string_lit.value.as_str()),
None => None,
Expand All @@ -100,7 +112,7 @@ impl<'a> PeepholeReplaceKnownMethods {
None => None,
_ => return None,
};
let result = match member.property.name.as_str() {
let result = match name {
"indexOf" => s.value.as_str().index_of(search_value, search_start_index),
"lastIndexOf" => s.value.as_str().last_index_of(search_value, search_start_index),
_ => unreachable!(),
Expand All @@ -111,14 +123,14 @@ impl<'a> PeepholeReplaceKnownMethods {

fn try_fold_string_substring_or_slice(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
if args.len() > 2 {
return None;
}
let Expression::StringLiteral(s) = &member.object else { return None };
let Expression::StringLiteral(s) = object else { return None };
let start_idx = args.first().and_then(|arg| match arg {
Argument::SpreadElement(_) => None,
_ => Ctx(ctx).get_side_free_number_value(arg.to_expression()),
Expand Down Expand Up @@ -147,14 +159,14 @@ impl<'a> PeepholeReplaceKnownMethods {

fn try_fold_string_char_at(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
if args.len() > 1 {
return None;
}
let Expression::StringLiteral(s) = &member.object else { return None };
let Expression::StringLiteral(s) = object else { return None };
let char_at_index: Option<f64> = match args.first() {
Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value),
Some(Argument::UnaryExpression(unary_expr))
Expand All @@ -175,10 +187,10 @@ impl<'a> PeepholeReplaceKnownMethods {

fn try_fold_string_char_code_at(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let Expression::StringLiteral(s) = &member.object else { return None };
let Expression::StringLiteral(s) = object else { return None };
let char_at_index = match ce.arguments.first() {
None => Some(0.0),
Some(Argument::SpreadElement(_)) => None,
Expand All @@ -192,13 +204,14 @@ impl<'a> PeepholeReplaceKnownMethods {

fn try_fold_string_replace(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
name: &str,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if ce.arguments.len() != 2 {
return None;
}
let Expression::StringLiteral(s) = &member.object else { return None };
let Expression::StringLiteral(s) = object else { return None };
let span = ce.span;
let search_value = ce.arguments.first().unwrap();
let search_value = match search_value {
Expand All @@ -217,7 +230,7 @@ impl<'a> PeepholeReplaceKnownMethods {
if replace_value.contains('$') {
return None;
}
let result = match member.property.name.as_str() {
let result = match name {
"replace" => s.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),
_ => unreachable!(),
Expand All @@ -228,10 +241,10 @@ impl<'a> PeepholeReplaceKnownMethods {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_lossless)]
fn try_fold_string_from_char_code(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let Expression::Identifier(ident) = &member.object else { return None };
let Expression::Identifier(ident) = object else { return None };
let ctx = Ctx(ctx);
if !ctx.is_global_reference(ident) {
return None;
Expand All @@ -256,11 +269,11 @@ impl<'a> PeepholeReplaceKnownMethods {
)]
fn try_fold_to_string(
ce: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
match &member.object {
match object {
// Number.prototype.toString()
// Number.prototype.toString(radix)
Expression::NumericLiteral(lit) if args.len() <= 1 => {
Expand Down Expand Up @@ -1188,6 +1201,7 @@ mod test {

#[test]
fn test_to_string() {
test("false['toString']()", "'false';");
test("false.toString()", "'false';");
test("true.toString()", "'true';");
test("'xy'.toString()", "'xy';");
Expand Down

0 comments on commit 870a583

Please sign in to comment.