diff --git a/crates/oxc_hir/src/span.rs b/crates/oxc_hir/src/span.rs index f74631ce9..4006b7c43 100644 --- a/crates/oxc_hir/src/span.rs +++ b/crates/oxc_hir/src/span.rs @@ -1,6 +1,6 @@ use oxc_span::{GetSpan, Span}; -use crate::hir::{Expression, MemberExpression}; +use crate::hir::{Declaration, Expression, MemberExpression, ModuleDeclaration, Statement}; impl<'a> GetSpan for Expression<'a> { fn span(&self) -> Span { @@ -52,3 +52,51 @@ impl<'a> GetSpan for MemberExpression<'a> { } } } + +impl<'a> GetSpan for Statement<'a> { + fn span(&self) -> Span { + match self { + Self::BlockStatement(stmt) => stmt.span, + Self::BreakStatement(stmt) => stmt.span, + Self::ContinueStatement(stmt) => stmt.span, + Self::DebuggerStatement(stmt) => stmt.span, + Self::Declaration(stmt) => stmt.span(), + Self::DoWhileStatement(stmt) => stmt.span, + Self::ExpressionStatement(stmt) => stmt.span, + Self::ForInStatement(stmt) => stmt.span, + Self::ForOfStatement(stmt) => stmt.span, + Self::ForStatement(stmt) => stmt.span, + Self::IfStatement(stmt) => stmt.span, + Self::LabeledStatement(stmt) => stmt.span, + Self::ModuleDeclaration(decl) => decl.span(), + Self::ReturnStatement(stmt) => stmt.span, + Self::SwitchStatement(stmt) => stmt.span, + Self::ThrowStatement(stmt) => stmt.span, + Self::TryStatement(stmt) => stmt.span, + Self::WhileStatement(stmt) => stmt.span, + Self::WithStatement(stmt) => stmt.span, + } + } +} + +impl<'a> GetSpan for Declaration<'a> { + fn span(&self) -> Span { + match self { + Self::ClassDeclaration(decl) => decl.span, + Self::FunctionDeclaration(decl) => decl.span, + Self::TSEnumDeclaration(decl) => decl.span, + Self::VariableDeclaration(decl) => decl.span, + } + } +} + +impl<'a> GetSpan for ModuleDeclaration<'a> { + fn span(&self) -> Span { + match self { + Self::ExportAllDeclaration(decl) => decl.span, + Self::ExportDefaultDeclaration(decl) => decl.span, + Self::ExportNamedDeclaration(decl) => decl.span, + Self::ImportDeclaration(decl) => decl.span, + } + } +} diff --git a/crates/oxc_minifier/src/compressor/fold.rs b/crates/oxc_minifier/src/compressor/fold.rs index a8d387d02..e49c37b60 100644 --- a/crates/oxc_minifier/src/compressor/fold.rs +++ b/crates/oxc_minifier/src/compressor/fold.rs @@ -783,29 +783,23 @@ impl<'a> Compressor<'a> { ) -> Option> { let boolean_value = get_boolean_value(&logic_expr.left); - let mut move_out = |dest: &mut Expression<'a>| { - let null_literal = self.hir.null_literal(dest.span()); - let null_expr = self.hir.literal_null_expression(null_literal); - mem::replace(dest, null_expr) - }; - if let Some(boolean_value) = boolean_value { // (TRUE || x) => TRUE (also, (3 || x) => 3) // (FALSE && x) => FALSE if (boolean_value && op == LogicalOperator::Or) || (!boolean_value && op == LogicalOperator::And) { - return Some(move_out(&mut logic_expr.left)); + return Some(self.move_out_expression(&mut logic_expr.left)); } else if !logic_expr.left.may_have_side_effects() { // (FALSE || x) => x // (TRUE && x) => x - return Some(move_out(&mut logic_expr.right)); + return Some(self.move_out_expression(&mut logic_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 = move_out(&mut logic_expr.left); - let right = move_out(&mut logic_expr.right); + let left = self.move_out_expression(&mut logic_expr.left); + let right = self.move_out_expression(&mut logic_expr.right); let mut vec = self.hir.new_vec_with_capacity(2); vec.push(left); vec.push(right); @@ -822,8 +816,8 @@ impl<'a> Compressor<'a> { if !right_boolean && left_child_op == LogicalOperator::Or || right_boolean && left_child_op == LogicalOperator::And { - let left = move_out(&mut left_child.left); - let right = move_out(&mut logic_expr.right); + let left = self.move_out_expression(&mut left_child.left); + let right = self.move_out_expression(&mut logic_expr.right); let logic_expr = self.hir.logical_expression( logic_expr.span, left, @@ -838,4 +832,79 @@ impl<'a> Compressor<'a> { } None } + + pub(crate) fn fold_condition<'b>(&mut self, stmt: &'b mut Statement<'a>) { + match stmt { + Statement::WhileStatement(while_stmt) => { + let minimized_expr = self.fold_expression_in_condition(&mut while_stmt.0.test); + + if let Some(min_expr) = minimized_expr { + while_stmt.0.test = min_expr; + } + } + Statement::ForStatement(for_stmt) => { + let test_expr = for_stmt.0.test.as_mut(); + + if let Some(test_expr) = test_expr { + let minimized_expr = self.fold_expression_in_condition(test_expr); + + if let Some(min_expr) = minimized_expr { + for_stmt.0.test = Some(min_expr); + } + } + } + _ => {} + }; + } + + fn fold_expression_in_condition( + &mut self, + expr: &mut Expression<'a>, + ) -> Option> { + let folded_expr = match expr { + Expression::UnaryExpression(unary_expr) => match unary_expr.operator { + UnaryOperator::LogicalNot => { + let should_fold = self.try_minimize_not(&mut unary_expr.0.argument); + + if should_fold { + Some(self.move_out_expression(&mut unary_expr.0.argument)) + } else { + None + } + } + _ => None, + }, + _ => None, + }; + + folded_expr + } + + fn move_out_expression(&mut self, expr: &mut Expression<'a>) -> Expression<'a> { + let null_literal = self.hir.null_literal(expr.span()); + let null_expr = self.hir.literal_null_expression(null_literal); + mem::replace(expr, null_expr) + } + + /// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java#L401-L435) + fn try_minimize_not(&mut self, expr: &mut Expression<'a>) -> bool { + let span = &mut expr.span(); + + match expr { + Expression::BinaryExpression(binary_expr) => { + let new_op = binary_expr.0.operator.equality_inverse_operator(); + + match new_op { + Some(new_op) => { + binary_expr.0.operator = new_op; + binary_expr.0.span = *span; + + true + } + _ => false, + } + } + _ => false, + } + } } diff --git a/crates/oxc_minifier/src/compressor/mod.rs b/crates/oxc_minifier/src/compressor/mod.rs index 6a38442ff..2f5b4227e 100644 --- a/crates/oxc_minifier/src/compressor/mod.rs +++ b/crates/oxc_minifier/src/compressor/mod.rs @@ -323,6 +323,7 @@ impl<'a, 'b> VisitMut<'a, 'b> for Compressor<'a> { fn visit_statement(&mut self, stmt: &'b mut Statement<'a>) { self.compress_block(stmt); self.compress_while(stmt); + self.fold_condition(stmt); self.visit_statement_match(stmt); } diff --git a/crates/oxc_minifier/tests/closure/fold_conditions.rs b/crates/oxc_minifier/tests/closure/fold_conditions.rs new file mode 100644 index 000000000..a6b90799c --- /dev/null +++ b/crates/oxc_minifier/tests/closure/fold_conditions.rs @@ -0,0 +1,21 @@ +use crate::test; + +#[test] +fn test_fold_not() { + test("while(!(x==y)){a=b;}", "for(;x!=y;)a=b"); + test("while(!(x!=y)){a=b;}", "for(;x==y;)a=b"); + test("while(!(x===y)){a=b;}", "for(;x!==y;)a=b"); + test("while(!(x!==y)){a=b;}", "for(;x===y;)a=b"); + + // Because !(x=NaN don't fold < and > cases. + test("while(!(x>y)){a=b;}", "for(;!(x>y);)a=b"); + test("while(!(x>=y)){a=b;}", "for(;!(x>=y);)a=b"); + test("while(!(x Option { + match self { + Self::Equality => Some(Self::Inequality), + Self::Inequality => Some(Self::Equality), + Self::StrictEquality => Some(Self::StrictInequality), + Self::StrictInequality => Some(Self::StrictEquality), + _ => None, + } + } + pub fn as_str(&self) -> &'static str { match self { Self::Equality => "==",