mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(minifier): dce conditional expression && or || (#4190)
This commit is contained in:
parent
e9ad03bb62
commit
c8184723f4
3 changed files with 61 additions and 37 deletions
|
|
@ -25,6 +25,7 @@ impl<'a> VisitMut<'a> for RemoveDeadCode<'a> {
|
|||
|
||||
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
||||
self.fold_conditional_expression(expr);
|
||||
self.fold_logical_expression(expr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,6 +125,15 @@ impl<'a> RemoveDeadCode<'a> {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_logical_expression(&mut self, expr: &mut Expression<'a>) {
|
||||
let Expression::LogicalExpression(logical_expr) = expr else {
|
||||
return;
|
||||
};
|
||||
if let Some(e) = self.folder.try_fold_logical_expression(logical_expr) {
|
||||
*expr = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeepVar<'a> {
|
||||
|
|
|
|||
|
|
@ -90,12 +90,9 @@ impl<'a> Folder<'a> {
|
|||
UnaryOperator::Void => self.try_reduce_void(unary_expr),
|
||||
_ => None,
|
||||
},
|
||||
Expression::LogicalExpression(logic_expr) => match logic_expr.operator {
|
||||
LogicalOperator::And | LogicalOperator::Or => {
|
||||
self.try_fold_and_or(logic_expr.operator, logic_expr)
|
||||
}
|
||||
LogicalOperator::Coalesce => None,
|
||||
},
|
||||
Expression::LogicalExpression(logic_expr) => {
|
||||
self.try_fold_logical_expression(logic_expr)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(folded_expr) = folded_expr {
|
||||
|
|
@ -672,39 +669,41 @@ impl<'a> Folder<'a> {
|
|||
None
|
||||
}
|
||||
|
||||
/// Try to fold a AND / OR node.
|
||||
///
|
||||
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587)
|
||||
/// Try to fold a AND/OR node.
|
||||
fn try_fold_and_or(
|
||||
pub fn try_fold_logical_expression(
|
||||
&mut self,
|
||||
op: LogicalOperator,
|
||||
logic_expr: &mut LogicalExpression<'a>,
|
||||
logical_expr: &mut LogicalExpression<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let boolean_value = get_boolean_value(&logic_expr.left);
|
||||
|
||||
if let Some(boolean_value) = boolean_value {
|
||||
let op = logical_expr.operator;
|
||||
if !matches!(op, LogicalOperator::And | LogicalOperator::Or) {
|
||||
return None;
|
||||
}
|
||||
if let Some(boolean_value) = get_boolean_value(&logical_expr.left) {
|
||||
// (TRUE || x) => TRUE (also, (3 || x) => 3)
|
||||
// (FALSE && x) => FALSE
|
||||
if (boolean_value && op == LogicalOperator::Or)
|
||||
|| (!boolean_value && op == LogicalOperator::And)
|
||||
{
|
||||
return Some(self.move_out_expression(&mut logic_expr.left));
|
||||
} else if !logic_expr.left.may_have_side_effects() {
|
||||
return Some(self.move_out_expression(&mut logical_expr.left));
|
||||
} else if !logical_expr.left.may_have_side_effects() {
|
||||
// (FALSE || x) => x
|
||||
// (TRUE && x) => x
|
||||
return Some(self.move_out_expression(&mut logic_expr.right));
|
||||
return Some(self.move_out_expression(&mut logical_expr.right));
|
||||
}
|
||||
// Left side may have side effects, but we know its boolean value.
|
||||
// e.g. true_with_sideeffects || foo() => true_with_sideeffects, foo()
|
||||
// or: false_with_sideeffects && foo() => false_with_sideeffects, foo()
|
||||
let left = self.move_out_expression(&mut logic_expr.left);
|
||||
let right = self.move_out_expression(&mut logic_expr.right);
|
||||
let left = self.move_out_expression(&mut logical_expr.left);
|
||||
let right = self.move_out_expression(&mut logical_expr.right);
|
||||
let mut vec = self.ast.vec_with_capacity(2);
|
||||
vec.push(left);
|
||||
vec.push(right);
|
||||
let sequence_expr = self.ast.expression_sequence(logic_expr.span, vec);
|
||||
let sequence_expr = self.ast.expression_sequence(logical_expr.span, vec);
|
||||
return Some(sequence_expr);
|
||||
} else if let Expression::LogicalExpression(left_child) = &mut logic_expr.left {
|
||||
if left_child.operator == logic_expr.operator {
|
||||
} else if let Expression::LogicalExpression(left_child) = &mut logical_expr.left {
|
||||
if left_child.operator == logical_expr.operator {
|
||||
let left_child_right_boolean = get_boolean_value(&left_child.right);
|
||||
let left_child_op = left_child.operator;
|
||||
if let Some(right_boolean) = left_child_right_boolean {
|
||||
|
|
@ -715,9 +714,9 @@ impl<'a> Folder<'a> {
|
|||
|| right_boolean && left_child_op == LogicalOperator::And
|
||||
{
|
||||
let left = self.move_out_expression(&mut left_child.left);
|
||||
let right = self.move_out_expression(&mut logic_expr.right);
|
||||
let right = self.move_out_expression(&mut logical_expr.right);
|
||||
let logic_expr = self.ast.expression_logical(
|
||||
logic_expr.span,
|
||||
logical_expr.span,
|
||||
left,
|
||||
left_child_op,
|
||||
right,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub(crate) fn test(source_text: &str, expected: &str) {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn remove_dead_code() {
|
||||
fn dce_if_statement() {
|
||||
test("if (true) { foo }", "{ foo }");
|
||||
test("if (true) { foo } else { bar }", "{ foo }");
|
||||
test("if (false) { foo } else { bar }", "{ bar }");
|
||||
|
|
@ -30,24 +30,15 @@ fn remove_dead_code() {
|
|||
test("if (!false) { foo }", "{ foo }");
|
||||
test("if (!true) { foo } else { bar }", "{ bar }");
|
||||
|
||||
test("if (!false && xxx) { foo }", "if (xxx) { foo; }");
|
||||
test("if (!true && yyy) { foo } else { bar }", "{ bar }");
|
||||
|
||||
test("if ('production' == 'production') { foo } else { bar }", "{ foo }");
|
||||
test("if ('development' == 'production') { foo } else { bar }", "{ bar }");
|
||||
|
||||
test("if ('production' === 'production') { foo } else { bar }", "{ foo }");
|
||||
test("if ('development' === 'production') { foo } else { bar }", "{ bar }");
|
||||
|
||||
test("false ? foo : bar;", "bar");
|
||||
test("true ? foo : bar;", "foo");
|
||||
|
||||
test("!true ? foo : bar;", "bar");
|
||||
test("!false ? foo : bar;", "foo");
|
||||
|
||||
test("!!false ? foo : bar;", "bar");
|
||||
test("!!true ? foo : bar;", "foo");
|
||||
|
||||
test("const foo = true ? A : B", "const foo = A");
|
||||
test("const foo = false ? A : B", "const foo = B");
|
||||
|
||||
// Shadowed `undefined` as a variable should not be erased.
|
||||
test(
|
||||
"function foo(undefined) { if (!undefined) { } }",
|
||||
|
|
@ -67,9 +58,33 @@ fn remove_dead_code() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dce_conditional_expression() {
|
||||
test("false ? foo : bar;", "bar");
|
||||
test("true ? foo : bar;", "foo");
|
||||
|
||||
test("!true ? foo : bar;", "bar");
|
||||
test("!false ? foo : bar;", "foo");
|
||||
|
||||
test("!!false ? foo : bar;", "bar");
|
||||
test("!!true ? foo : bar;", "foo");
|
||||
|
||||
test("const foo = true ? A : B", "const foo = A");
|
||||
test("const foo = false ? A : B", "const foo = B");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dce_logical_expression() {
|
||||
test("false && bar()", "false");
|
||||
test("true && bar()", "bar()");
|
||||
|
||||
test("const foo = false && bar()", "const foo = false");
|
||||
test("const foo = true && bar()", "const foo = bar()");
|
||||
}
|
||||
|
||||
// https://github.com/terser/terser/blob/master/test/compress/dead-code.js
|
||||
#[test]
|
||||
fn remove_dead_code_from_terser() {
|
||||
fn dce_from_terser() {
|
||||
test(
|
||||
"function f() {
|
||||
a();
|
||||
|
|
|
|||
Loading…
Reference in a new issue