feat(minifier): minify sequence expressions (#8305)

This commit is contained in:
camc314 2025-01-06 23:42:30 +00:00
parent af65c3670f
commit 66a24437ac
2 changed files with 101 additions and 2 deletions

View file

@ -26,8 +26,13 @@ impl<'a> CheckForStateChange<'a, '_> for Expression<'a> {
| Self::RegExpLiteral(_)
| Self::MetaProperty(_)
| Self::ThisExpression(_)
| Self::ClassExpression(_)
| Self::ArrowFunctionExpression(_)
| Self::FunctionExpression(_) => false,
Self::ClassExpression(class_expr) => class_expr
.body
.body
.iter()
.any(|method| method.check_for_state_change(check_for_new_objects)),
Self::TemplateLiteral(template) => template
.expressions
.iter()
@ -73,6 +78,7 @@ impl<'a> CheckForStateChange<'a, '_> for Expression<'a> {
.iter()
.any(|element| element.check_for_state_change(check_for_new_objects))
}
_ => true,
}
}
@ -319,3 +325,20 @@ impl CheckForStateChange<'_, '_> for AssignmentTargetMaybeDefault<'_> {
}
}
}
impl<'a> CheckForStateChange<'a, '_> for ClassElement<'a> {
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool {
match self {
ClassElement::TSIndexSignature(_) | ClassElement::StaticBlock(_) => false,
ClassElement::MethodDefinition(method_definition) => {
method_definition.key.check_for_state_change(check_for_new_objects)
}
ClassElement::PropertyDefinition(property_definition) => {
property_definition.key.check_for_state_change(check_for_new_objects)
}
ClassElement::AccessorProperty(accessor_property) => {
accessor_property.key.check_for_state_change(check_for_new_objects)
}
}
}
}

View file

@ -1,6 +1,9 @@
use oxc_allocator::Vec;
use oxc_ast::{ast::*, Visit};
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, IsLiteralValue};
use oxc_ecmascript::{
constant_evaluation::{ConstantEvaluation, IsLiteralValue},
side_effects::MayHaveSideEffects,
};
use oxc_span::SPAN;
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
@ -52,6 +55,9 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
let ctx = Ctx(ctx);
if let Some(folded_expr) = match expr {
Expression::ConditionalExpression(e) => Self::try_fold_conditional_expression(e, ctx),
Expression::SequenceExpression(sequence_expression) => {
Self::try_fold_sequence_expression(sequence_expression, ctx)
}
_ => None,
} {
*expr = folded_expr;
@ -423,6 +429,65 @@ impl<'a, 'b> PeepholeRemoveDeadCode {
None => None,
}
}
fn try_fold_sequence_expression(
sequence_expr: &mut SequenceExpression<'a>,
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
let should_include_ret_val =
!matches!(ctx.parent(), Ancestor::ExpressionStatementExpression(_));
let should_keep_as_sequence_expr = matches!(
ctx.parent(),
Ancestor::CallExpressionCallee(_) | Ancestor::TaggedTemplateExpressionTag(_)
);
if should_keep_as_sequence_expr && sequence_expr.expressions.len() == 2 {
return None;
}
let (should_fold, new_len) = sequence_expr.expressions.iter().enumerate().fold(
(false, 0),
|(mut should_fold, mut new_len), (i, expr)| {
if expr.may_have_side_effects()
|| (should_include_ret_val && i == sequence_expr.expressions.len() - 1)
{
new_len += 1;
} else {
should_fold = true;
}
(should_fold, new_len)
},
);
if new_len == 0 {
return Some(ctx.ast.expression_null_literal(sequence_expr.span));
}
if should_fold {
let mut new_exprs = ctx.ast.vec_with_capacity(new_len);
let len = sequence_expr.expressions.len();
for (i, expr) in sequence_expr.expressions.iter_mut().enumerate() {
if expr.may_have_side_effects() || (should_include_ret_val && i == len - 1) {
new_exprs.push(ctx.ast.move_expression(expr));
}
}
if should_keep_as_sequence_expr && new_exprs.len() == 1 {
new_exprs.insert(
0,
ctx.ast.expression_numeric_literal(SPAN, 1.0, None, NumberBase::Decimal),
);
}
if new_exprs.len() == 1 {
return Some(new_exprs.pop().unwrap());
}
return Some(ctx.ast.expression_sequence(sequence_expr.span, new_exprs));
}
None
}
}
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeRemoveDeadCodeTest.java>
@ -594,4 +659,15 @@ mod test {
fold_same("(() => {})()");
fold_same("(function () {})()");
}
#[test]
fn test_fold_sequence_expr() {
fold("('foo', 'bar', 'baz')", "");
fold("('foo', 'bar', baz())", "baz()");
fold("('foo', bar(), baz())", "bar(), baz()");
fold("(() => {}, bar(), baz())", "bar(), baz()");
fold("(function k() {}, k(), baz())", "k(), baz()");
fold_same("(0, o.f)();");
fold("var obj = Object((null, 2, 3), 1, 2);", "var obj = Object(3, 1, 2);");
}
}