diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index 9852ed60348ba..f62ed6a008ab7 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -3,6 +3,7 @@ use oxc_ast::{ast::*, Visit}; use oxc_span::SPAN; use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; +use crate::node_util::IsLiteralValue; use crate::{keep_var::KeepVar, node_util::NodeUtil, tri::Tri, CompressorPass}; /// Remove Dead Code from the AST. @@ -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; @@ -192,6 +196,27 @@ impl<'a> PeepholeRemoveDeadCode { } } + fn try_fold_expression_stmt( + stmt: &mut ExpressionStatement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + // 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. + + stmt.expression + .is_literal_value(false) + .then(|| { + if let Ancestor::ArrowFunctionExpressionBody(body) = ctx.ancestry.ancestor(1) { + if *body.expression() { + return None; + } + } + Some(ctx.ast.statement_empty(SPAN)) + }) + .unwrap_or(None) + } + /// Try folding conditional expression (?:) if the condition results of the condition is known. fn try_fold_conditional_expression( expr: &mut ConditionalExpression<'a>, @@ -290,4 +315,33 @@ 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])", ""); + // fold("([a])", "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. + } } diff --git a/crates/oxc_minifier/src/node_util/is_literal_value.rs b/crates/oxc_minifier/src/node_util/is_literal_value.rs index 996a02ed4f31c..78a830e2d62de 100644 --- a/crates/oxc_minifier/src/node_util/is_literal_value.rs +++ b/crates/oxc_minifier/src/node_util/is_literal_value.rs @@ -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), } } diff --git a/crates/oxc_minifier/tests/ast_passes/dead_code_elimination.rs b/crates/oxc_minifier/tests/ast_passes/dead_code_elimination.rs index fc5e9975cfdac..e030cd6997cf8 100644 --- a/crates/oxc_minifier/tests/ast_passes/dead_code_elimination.rs +++ b/crates/oxc_minifier/tests/ast_passes/dead_code_elimination.rs @@ -112,7 +112,7 @@ fn dce_conditional_expression() { #[test] fn dce_logical_expression() { - test("false && bar()", "false"); + test("false && bar()", ""); test("true && bar()", "bar()"); test("const foo = false && bar()", "const foo = false");