mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(minifier): fold false['toString'] (#8447)
This commit is contained in:
parent
dd643407dd
commit
870a583ae5
2 changed files with 56 additions and 40 deletions
|
|
@ -46,8 +46,8 @@ pub struct PeepholeOptimizations {
|
||||||
x4_peephole_fold_constants: PeepholeFoldConstants,
|
x4_peephole_fold_constants: PeepholeFoldConstants,
|
||||||
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode,
|
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode,
|
||||||
x5_peephole_minimize_conditions: PeepholeMinimizeConditions,
|
x5_peephole_minimize_conditions: PeepholeMinimizeConditions,
|
||||||
x7_peephole_replace_known_methods: PeepholeReplaceKnownMethods,
|
x7_convert_to_dotted_properties: ConvertToDottedProperties,
|
||||||
x8_convert_to_dotted_properties: ConvertToDottedProperties,
|
x8_peephole_replace_known_methods: PeepholeReplaceKnownMethods,
|
||||||
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax,
|
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,8 +63,8 @@ impl PeepholeOptimizations {
|
||||||
x4_peephole_fold_constants: PeepholeFoldConstants::new(),
|
x4_peephole_fold_constants: PeepholeFoldConstants::new(),
|
||||||
x5_peephole_minimize_conditions: PeepholeMinimizeConditions::new(target),
|
x5_peephole_minimize_conditions: PeepholeMinimizeConditions::new(target),
|
||||||
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(),
|
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(),
|
||||||
x7_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
|
x7_convert_to_dotted_properties: ConvertToDottedProperties::new(in_fixed_loop),
|
||||||
x8_convert_to_dotted_properties: ConvertToDottedProperties::new(in_fixed_loop),
|
x8_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
|
||||||
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new(
|
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new(
|
||||||
options.target,
|
options.target,
|
||||||
in_fixed_loop,
|
in_fixed_loop,
|
||||||
|
|
@ -80,7 +80,8 @@ impl PeepholeOptimizations {
|
||||||
self.x4_peephole_fold_constants.changed = false;
|
self.x4_peephole_fold_constants.changed = false;
|
||||||
self.x5_peephole_minimize_conditions.changed = false;
|
self.x5_peephole_minimize_conditions.changed = false;
|
||||||
self.x6_peephole_remove_dead_code.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;
|
self.x9_peephole_substitute_alternate_syntax.changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +93,8 @@ impl PeepholeOptimizations {
|
||||||
|| self.x4_peephole_fold_constants.changed
|
|| self.x4_peephole_fold_constants.changed
|
||||||
|| self.x5_peephole_minimize_conditions.changed
|
|| self.x5_peephole_minimize_conditions.changed
|
||||||
|| self.x6_peephole_remove_dead_code.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
|
|| 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.x4_peephole_fold_constants.exit_expression(expr, ctx);
|
||||||
self.x5_peephole_minimize_conditions.exit_expression(expr, ctx);
|
self.x5_peephole_minimize_conditions.exit_expression(expr, ctx);
|
||||||
self.x6_peephole_remove_dead_code.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);
|
self.x9_peephole_substitute_alternate_syntax.exit_expression(expr, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +181,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
|
||||||
expr: &mut MemberExpression<'a>,
|
expr: &mut MemberExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'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>) {
|
fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
|
|
||||||
|
|
@ -41,19 +41,29 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) {
|
) {
|
||||||
let Expression::CallExpression(ce) = node else { return };
|
let Expression::CallExpression(ce) = node else { return };
|
||||||
let Expression::StaticMemberExpression(member) = &ce.callee else { return };
|
let (name, object) = match &ce.callee {
|
||||||
if member.optional {
|
Expression::StaticMemberExpression(member) if !member.optional => {
|
||||||
return;
|
(member.property.name.as_str(), &member.object)
|
||||||
}
|
}
|
||||||
let replacement = match member.property.name.as_str() {
|
Expression::ComputedMemberExpression(member) if !member.optional => {
|
||||||
"toLowerCase" | "toUpperCase" | "trim" => Self::try_fold_string_casing(ce, member, ctx),
|
match &member.expression {
|
||||||
"substring" | "slice" => Self::try_fold_string_substring_or_slice(ce, member, ctx),
|
Expression::StringLiteral(s) => (s.value.as_str(), &member.object),
|
||||||
"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),
|
}
|
||||||
"replace" | "replaceAll" => Self::try_fold_string_replace(ce, member, ctx),
|
_ => return,
|
||||||
"fromCharCode" => Self::try_fold_string_from_char_code(ce, member, ctx),
|
};
|
||||||
"toString" => Self::try_fold_to_string(ce, member, ctx),
|
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,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some(replacement) = replacement {
|
if let Some(replacement) = replacement {
|
||||||
|
|
@ -64,14 +74,15 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
|
|
||||||
fn try_fold_string_casing(
|
fn try_fold_string_casing(
|
||||||
ce: &CallExpression<'a>,
|
ce: &CallExpression<'a>,
|
||||||
member: &StaticMemberExpression<'a>,
|
name: &str,
|
||||||
|
object: &Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
if ce.arguments.len() >= 1 {
|
if ce.arguments.len() >= 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let Expression::StringLiteral(s) = &member.object else { return None };
|
let Expression::StringLiteral(s) = object else { return None };
|
||||||
let value = match member.property.name.as_str() {
|
let value = match name {
|
||||||
"toLowerCase" => s.value.cow_to_lowercase(),
|
"toLowerCase" => s.value.cow_to_lowercase(),
|
||||||
"toUpperCase" => s.value.cow_to_uppercase(),
|
"toUpperCase" => s.value.cow_to_uppercase(),
|
||||||
"trim" => Cow::Borrowed(s.value.trim()),
|
"trim" => Cow::Borrowed(s.value.trim()),
|
||||||
|
|
@ -82,14 +93,15 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
|
|
||||||
fn try_fold_string_index_of(
|
fn try_fold_string_index_of(
|
||||||
ce: &CallExpression<'a>,
|
ce: &CallExpression<'a>,
|
||||||
member: &StaticMemberExpression<'a>,
|
name: &str,
|
||||||
|
object: &Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let args = &ce.arguments;
|
let args = &ce.arguments;
|
||||||
if args.len() >= 3 {
|
if args.len() >= 3 {
|
||||||
return None;
|
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() {
|
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,
|
||||||
|
|
@ -100,7 +112,7 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
None => None,
|
None => None,
|
||||||
_ => return 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),
|
"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),
|
"lastIndexOf" => s.value.as_str().last_index_of(search_value, search_start_index),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
@ -111,14 +123,14 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
|
|
||||||
fn try_fold_string_substring_or_slice(
|
fn try_fold_string_substring_or_slice(
|
||||||
ce: &CallExpression<'a>,
|
ce: &CallExpression<'a>,
|
||||||
member: &StaticMemberExpression<'a>,
|
object: &Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let args = &ce.arguments;
|
let args = &ce.arguments;
|
||||||
if args.len() > 2 {
|
if args.len() > 2 {
|
||||||
return None;
|
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 {
|
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()),
|
||||||
|
|
@ -147,14 +159,14 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
|
|
||||||
fn try_fold_string_char_at(
|
fn try_fold_string_char_at(
|
||||||
ce: &CallExpression<'a>,
|
ce: &CallExpression<'a>,
|
||||||
member: &StaticMemberExpression<'a>,
|
object: &Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let args = &ce.arguments;
|
let args = &ce.arguments;
|
||||||
if args.len() > 1 {
|
if args.len() > 1 {
|
||||||
return None;
|
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() {
|
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))
|
||||||
|
|
@ -175,10 +187,10 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
|
|
||||||
fn try_fold_string_char_code_at(
|
fn try_fold_string_char_code_at(
|
||||||
ce: &CallExpression<'a>,
|
ce: &CallExpression<'a>,
|
||||||
member: &StaticMemberExpression<'a>,
|
object: &Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'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() {
|
let char_at_index = match ce.arguments.first() {
|
||||||
None => Some(0.0),
|
None => Some(0.0),
|
||||||
Some(Argument::SpreadElement(_)) => None,
|
Some(Argument::SpreadElement(_)) => None,
|
||||||
|
|
@ -192,13 +204,14 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
|
|
||||||
fn try_fold_string_replace(
|
fn try_fold_string_replace(
|
||||||
ce: &CallExpression<'a>,
|
ce: &CallExpression<'a>,
|
||||||
member: &StaticMemberExpression<'a>,
|
name: &str,
|
||||||
|
object: &Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
if ce.arguments.len() != 2 {
|
if ce.arguments.len() != 2 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let Expression::StringLiteral(s) = &member.object else { return None };
|
let Expression::StringLiteral(s) = object else { return None };
|
||||||
let span = ce.span;
|
let span = ce.span;
|
||||||
let search_value = ce.arguments.first().unwrap();
|
let search_value = ce.arguments.first().unwrap();
|
||||||
let search_value = match search_value {
|
let search_value = match search_value {
|
||||||
|
|
@ -217,7 +230,7 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
if replace_value.contains('$') {
|
if replace_value.contains('$') {
|
||||||
return None;
|
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),
|
"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),
|
"replaceAll" => s.value.as_str().cow_replace(search_value.as_ref(), &replace_value),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
@ -228,10 +241,10 @@ 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>,
|
object: &Expression<'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 Expression::Identifier(ident) = object else { return None };
|
||||||
let ctx = Ctx(ctx);
|
let ctx = Ctx(ctx);
|
||||||
if !ctx.is_global_reference(ident) {
|
if !ctx.is_global_reference(ident) {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -256,11 +269,11 @@ impl<'a> PeepholeReplaceKnownMethods {
|
||||||
)]
|
)]
|
||||||
fn try_fold_to_string(
|
fn try_fold_to_string(
|
||||||
ce: &CallExpression<'a>,
|
ce: &CallExpression<'a>,
|
||||||
member: &StaticMemberExpression<'a>,
|
object: &Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let args = &ce.arguments;
|
let args = &ce.arguments;
|
||||||
match &member.object {
|
match object {
|
||||||
// Number.prototype.toString()
|
// Number.prototype.toString()
|
||||||
// Number.prototype.toString(radix)
|
// Number.prototype.toString(radix)
|
||||||
Expression::NumericLiteral(lit) if args.len() <= 1 => {
|
Expression::NumericLiteral(lit) if args.len() <= 1 => {
|
||||||
|
|
@ -1188,6 +1201,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_to_string() {
|
fn test_to_string() {
|
||||||
|
test("false['toString']()", "'false';");
|
||||||
test("false.toString()", "'false';");
|
test("false.toString()", "'false';");
|
||||||
test("true.toString()", "'true';");
|
test("true.toString()", "'true';");
|
||||||
test("'xy'.toString()", "'xy';");
|
test("'xy'.toString()", "'xy';");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue