From 870a583ae5cb9ddd2bc855626d95dc337075760e Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 12 Jan 2025 13:17:59 +0000 Subject: [PATCH] feat(minifier): fold `false['toString']` (#8447) --- crates/oxc_minifier/src/ast_passes/mod.rs | 18 +++-- .../peephole_replace_known_methods.rs | 78 +++++++++++-------- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index e656fe4514d25..6d8e86076c37b 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -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, } @@ -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, @@ -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; } @@ -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 } @@ -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); } @@ -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>) { 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 896d6c9128667..432cad046878c 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 @@ -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 { @@ -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> { 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()), @@ -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> { 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, @@ -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!(), @@ -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> { 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()), @@ -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> { 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 = match args.first() { Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value), Some(Argument::UnaryExpression(unary_expr)) @@ -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> { - 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, @@ -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> { 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 { @@ -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!(), @@ -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> { - 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; @@ -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> { let args = &ce.arguments; - match &member.object { + match object { // Number.prototype.toString() // Number.prototype.toString(radix) Expression::NumericLiteral(lit) if args.len() <= 1 => { @@ -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';");