Skip to content

Commit

Permalink
feat(minifier): eliminate unaffected ExpressionStatements
Browse files Browse the repository at this point in the history
  • Loading branch information
7086cmd committed Oct 11, 2024
1 parent 073b02a commit cef4351
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 1 deletion.
51 changes: 51 additions & 0 deletions crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use oxc_ast::{ast::*, Visit};
use oxc_span::SPAN;
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};

use crate::node_util::MayHaveSideEffects;
use crate::{keep_var::KeepVar, node_util::NodeUtil, tri::Tri, CompressorPass};

/// Remove Dead Code from the AST.
Expand Down Expand Up @@ -31,6 +32,9 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
if let Some(new_stmt) = match stmt {
Statement::IfStatement(if_stmt) => self.try_fold_if(if_stmt, ctx),
Statement::ForStatement(for_stmt) => self.try_fold_for(for_stmt, ctx),
Statement::ExpressionStatement(expr_stmt) => {
self.try_fold_expression_stmt(expr_stmt, ctx)
}
_ => None,
} {
*stmt = new_stmt;
Expand Down Expand Up @@ -192,6 +196,22 @@ impl<'a> PeepholeRemoveDeadCode {
}
}

fn try_fold_expression_stmt(
&mut self,
stmt: &mut ExpressionStatement<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
// We need to check if it is in arrow function with `expression: true`.
// This is the only scenario where we can't remove it even if `ExpressionStatement`.
// TODO find a better way to handle this.
if let Ancestor::ArrowFunctionExpressionBody(body) = ctx.ancestry.ancestor(1) {
if *body.expression() {
return None;
}
}
(!stmt.expression.may_have_side_effects()).then(|| ctx.ast.statement_empty(SPAN))
}

/// Try folding conditional expression (?:) if the condition results of the condition is known.
fn try_fold_conditional_expression(
expr: &mut ConditionalExpression<'a>,
Expand Down Expand Up @@ -290,4 +310,35 @@ mod test {
fold("for(;undefined;) foo()", "");
fold("for(;'';) foo()", "");
}

#[test]
fn test_object_literal() {
fold("({})", "");
fold("({a:1})", "");
// fold("({a:foo()})", "foo()");
// fold("({'a':foo()})", "foo()");
// Object-spread may trigger getters.
fold_same("({...a})");
fold_same("({...foo()})");
}

#[test]
fn test_array_literal() {
fold("([])", "");
fold("([1])", "");
// TODO we currently use `is_literal_value`, which is not proper.
// We need to use a `is_immutable_value` trait instead, which would return getter calls as true.
// fold("([a])", "");
// fold("([foo()])", "foo()");
}

#[test]
#[ignore]
fn test_array_literal_containing_spread() {
fold_same("([...c])");
fold("([4, ...c, a])", "([...c])");
fold("([foo(), ...c, bar()])", "(foo(), [...c], bar())");
fold("([...a, b, ...c])", "([...a], [...c])");
fold_same("([...b, ...c])"); // It would also be fine if the spreads were split apart.
}
}
3 changes: 2 additions & 1 deletion crates/oxc_minifier/src/node_util/is_literal_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ impl<'a, 'b> IsLiteralValue<'a, 'b> for ObjectProperty<'a> {
impl<'a, 'b> IsLiteralValue<'a, 'b> for PropertyKey<'a> {
fn is_literal_value(&self, include_functions: bool) -> bool {
match self {
Self::StaticIdentifier(_) | Self::PrivateIdentifier(_) => false,
Self::StaticIdentifier(_) => true,
Self::PrivateIdentifier(_) => false,
match_expression!(Self) => self.to_expression().is_literal_value(include_functions),
}
}
Expand Down

0 comments on commit cef4351

Please sign in to comment.