Skip to content

Commit 746f7b3

Browse files
committed
refactor(minifier): align code with closure compiler (#5717)
also fixes #4341
1 parent 4bdc202 commit 746f7b3

File tree

6 files changed

+128
-80
lines changed

6 files changed

+128
-80
lines changed

crates/oxc_minifier/src/ast_passes/fold_constants.rs

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ impl<'a> FoldConstants<'a> {
5353
pub fn fold_expression(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
5454
if let Some(folded_expr) = match expr {
5555
Expression::BinaryExpression(e) => self.try_fold_binary_operator(e, ctx),
56-
Expression::LogicalExpression(e) => self.try_fold_logical_expression(e, ctx),
56+
Expression::LogicalExpression(e)
57+
if matches!(e.operator, LogicalOperator::And | LogicalOperator::Or) =>
58+
{
59+
self.try_fold_and_or(e, ctx)
60+
}
5761
// TODO: move to `PeepholeMinimizeConditions`
5862
Expression::ConditionalExpression(e) => self.try_fold_conditional_expression(e, ctx),
5963
Expression::UnaryExpression(e) => self.try_fold_unary_expression(e, ctx),
@@ -111,7 +115,7 @@ impl<'a> FoldConstants<'a> {
111115
ctx: &mut TraverseCtx<'a>,
112116
) -> Option<bool> {
113117
self.fold_expression(expr, ctx);
114-
ctx.get_boolean_value(expr)
118+
ctx.get_boolean_value(expr).to_option()
115119
}
116120

117121
fn fold_if_statement(&self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
@@ -150,11 +154,15 @@ impl<'a> FoldConstants<'a> {
150154
expr: &mut ConditionalExpression<'a>,
151155
ctx: &mut TraverseCtx<'a>,
152156
) -> Option<Expression<'a>> {
153-
if ctx.ancestry.parent().is_tagged_template_expression() {
154-
return None;
155-
}
156157
match self.fold_expression_and_get_boolean_value(&mut expr.test, ctx) {
157-
Some(true) => Some(self.ast.move_expression(&mut expr.consequent)),
158+
Some(true) => {
159+
// Bail `let o = { f() { assert.ok(this !== o); } }; (true ? o.f : false)(); (true ? o.f : false)``;`
160+
let parent = ctx.ancestry.parent();
161+
if parent.is_tagged_template_expression() || parent.is_call_expression() {
162+
return None;
163+
}
164+
Some(self.ast.move_expression(&mut expr.consequent))
165+
}
158166
Some(false) => Some(self.ast.move_expression(&mut expr.alternate)),
159167
_ => None,
160168
}
@@ -384,7 +392,7 @@ impl<'a> FoldConstants<'a> {
384392
let right_bigint = ctx.get_side_free_bigint_value(right_expr);
385393

386394
if let (Some(l_big), Some(r_big)) = (left_bigint, right_bigint) {
387-
return Tri::for_boolean(l_big.eq(&r_big));
395+
return Tri::from(l_big.eq(&r_big));
388396
}
389397
}
390398

@@ -421,7 +429,7 @@ impl<'a> FoldConstants<'a> {
421429
return Tri::Unknown;
422430
}
423431

424-
return Tri::for_boolean(left_string.cmp(&right_string) == Ordering::Less);
432+
return Tri::from(left_string.cmp(&right_string) == Ordering::Less);
425433
}
426434

427435
if let (Expression::UnaryExpression(left), Expression::UnaryExpression(right)) =
@@ -450,14 +458,14 @@ impl<'a> FoldConstants<'a> {
450458
match (left_bigint, right_bigint, left_num, right_num) {
451459
// Next, try to evaluate based on the value of the node. Try comparing as BigInts first.
452460
(Some(l_big), Some(r_big), _, _) => {
453-
return Tri::for_boolean(l_big < r_big);
461+
return Tri::from(l_big < r_big);
454462
}
455463
// try comparing as Numbers.
456464
(_, _, Some(l_num), Some(r_num)) => match (l_num, r_num) {
457465
(NumberValue::NaN, _) | (_, NumberValue::NaN) => {
458-
return Tri::for_boolean(will_negative);
466+
return Tri::from(will_negative);
459467
}
460-
(NumberValue::Number(l), NumberValue::Number(r)) => return Tri::for_boolean(l < r),
468+
(NumberValue::Number(l), NumberValue::Number(r)) => return Tri::from(l < r),
461469
_ => {}
462470
},
463471
// Finally, try comparisons between BigInt and Number.
@@ -484,7 +492,7 @@ impl<'a> FoldConstants<'a> {
484492
// if invert is false, then the number is on the right in tryAbstractRelationalComparison
485493
// if it's true, then the number is on the left
486494
match number_value {
487-
NumberValue::NaN => Tri::for_boolean(will_negative),
495+
NumberValue::NaN => Tri::from(will_negative),
488496
NumberValue::PositiveInfinity => Tri::True.xor(invert),
489497
NumberValue::NegativeInfinity => Tri::False.xor(invert),
490498
NumberValue::Number(num) => {
@@ -502,7 +510,7 @@ impl<'a> FoldConstants<'a> {
502510
if is_exact_int64(*num) {
503511
Tri::False
504512
} else {
505-
Tri::for_boolean(num.is_sign_positive()).xor(invert)
513+
Tri::from(num.is_sign_positive()).xor(invert)
506514
}
507515
}
508516
}
@@ -534,7 +542,7 @@ impl<'a> FoldConstants<'a> {
534542
return Tri::False;
535543
}
536544

537-
return Tri::for_boolean(l_num == r_num);
545+
return Tri::from(l_num == r_num);
538546
}
539547

540548
Tri::Unknown
@@ -548,7 +556,7 @@ impl<'a> FoldConstants<'a> {
548556
return Tri::Unknown;
549557
}
550558

551-
return Tri::for_boolean(left_string == right_string);
559+
return Tri::from(left_string == right_string);
552560
}
553561

554562
if let (Expression::UnaryExpression(left), Expression::UnaryExpression(right)) =
@@ -640,22 +648,20 @@ impl<'a> FoldConstants<'a> {
640648
/// Try to fold a AND / OR node.
641649
///
642650
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587)
643-
pub fn try_fold_logical_expression(
651+
pub fn try_fold_and_or(
644652
&self,
645653
logical_expr: &mut LogicalExpression<'a>,
646654
ctx: &mut TraverseCtx<'a>,
647655
) -> Option<Expression<'a>> {
648-
if ctx.ancestry.parent().is_tagged_template_expression() {
649-
return None;
650-
}
651656
let op = logical_expr.operator;
652-
if !matches!(op, LogicalOperator::And | LogicalOperator::Or) {
653-
return None;
654-
}
657+
debug_assert!(matches!(op, LogicalOperator::And | LogicalOperator::Or));
655658

656-
if let Some(boolean_value) = ctx.get_boolean_value(&logical_expr.left) {
659+
let left = &logical_expr.left;
660+
let left_val = ctx.get_boolean_value(left).to_option();
661+
662+
if let Some(lval) = left_val {
657663
// Bail `0 && (module.exports = {})` for `cjs-module-lexer`.
658-
if !boolean_value {
664+
if !lval {
659665
if let Expression::AssignmentExpression(assign_expr) = &logical_expr.right {
660666
if let Some(member_expr) = assign_expr.left.as_member_expression() {
661667
if member_expr.is_specific_member_access("module", "exports") {
@@ -667,11 +673,14 @@ impl<'a> FoldConstants<'a> {
667673

668674
// (TRUE || x) => TRUE (also, (3 || x) => 3)
669675
// (FALSE && x) => FALSE
670-
if (boolean_value && op == LogicalOperator::Or)
671-
|| (!boolean_value && op == LogicalOperator::And)
672-
{
676+
if if lval { op == LogicalOperator::Or } else { op == LogicalOperator::And } {
673677
return Some(self.ast.move_expression(&mut logical_expr.left));
674-
} else if !logical_expr.left.may_have_side_effects() {
678+
} else if !left.may_have_side_effects() {
679+
let parent = ctx.ancestry.parent();
680+
// Bail `let o = { f() { assert.ok(this !== o); } }; (true && o.f)(); (true && o.f)``;`
681+
if parent.is_tagged_template_expression() || parent.is_call_expression() {
682+
return None;
683+
}
675684
// (FALSE || x) => x
676685
// (TRUE && x) => x
677686
return Some(self.ast.move_expression(&mut logical_expr.right));
@@ -688,7 +697,7 @@ impl<'a> FoldConstants<'a> {
688697
return Some(sequence_expr);
689698
} else if let Expression::LogicalExpression(left_child) = &mut logical_expr.left {
690699
if left_child.operator == logical_expr.operator {
691-
let left_child_right_boolean = ctx.get_boolean_value(&left_child.right);
700+
let left_child_right_boolean = ctx.get_boolean_value(&left_child.right).to_option();
692701
let left_child_op = left_child.operator;
693702
if let Some(right_boolean) = left_child_right_boolean {
694703
if !left_child.right.may_have_side_effects() {

crates/oxc_minifier/src/node_util/check_for_state_change.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ fn is_simple_unary_operator(operator: UnaryOperator) -> bool {
66
operator != UnaryOperator::Delete
77
}
88

9-
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/AstAnalyzer.java#L241)
109
/// Returns true if some node in n's subtree changes application state. If
1110
/// `check_for_new_objects` is true, we assume that newly created mutable objects (like object
1211
/// literals) change state. Otherwise, we assume that they have no side effects.
12+
///
13+
/// Ported from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/AstAnalyzer.java#L241)
1314
pub trait CheckForStateChange<'a, 'b> {
1415
fn check_for_state_change(&self, check_for_new_objects: bool) -> bool;
1516
}

crates/oxc_minifier/src/node_util/may_have_side_effects.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ use oxc_ast::ast::*;
22

33
use super::check_for_state_change::CheckForStateChange;
44

5-
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/AstAnalyzer.java#L94)
65
/// Returns true if the node which may have side effects when executed.
76
/// This version default to the "safe" assumptions when the compiler object
87
/// is not provided (RegExp have side-effects, etc).
8+
///
9+
/// Ported from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/AstAnalyzer.java#L94)
910
pub trait MayHaveSideEffects<'a, 'b>
1011
where
1112
Self: CheckForStateChange<'a, 'b>,
1213
{
1314
fn may_have_side_effects(&self) -> bool {
14-
self.check_for_state_change(false)
15+
self.check_for_state_change(/* check_for_new_objects */ false)
1516
}
1617
}
1718

crates/oxc_minifier/src/node_util/mod.rs

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use oxc_ast::ast::*;
1111
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
1212
use oxc_syntax::operator::{AssignmentOperator, LogicalOperator, UnaryOperator};
1313

14+
use crate::tri::Tri;
15+
1416
pub use self::{
1517
is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects,
1618
number_value::NumberValue,
@@ -89,20 +91,22 @@ pub trait NodeUtil {
8991
/// Gets the boolean value of a node that represents an expression, or `None` if no
9092
/// such value can be determined by static analysis.
9193
/// This method does not consider whether the node may have side-effects.
92-
fn get_boolean_value(&self, expr: &Expression) -> Option<bool> {
94+
fn get_boolean_value(&self, expr: &Expression) -> Tri {
9395
match expr {
9496
Expression::RegExpLiteral(_)
9597
| Expression::ArrayExpression(_)
9698
| Expression::ArrowFunctionExpression(_)
9799
| Expression::ClassExpression(_)
98100
| Expression::FunctionExpression(_)
99101
| Expression::NewExpression(_)
100-
| Expression::ObjectExpression(_) => Some(true),
101-
Expression::NullLiteral(_) => Some(false),
102-
Expression::BooleanLiteral(boolean_literal) => Some(boolean_literal.value),
103-
Expression::NumericLiteral(number_literal) => Some(number_literal.value != 0.0),
104-
Expression::BigIntLiteral(big_int_literal) => Some(!big_int_literal.is_zero()),
105-
Expression::StringLiteral(string_literal) => Some(!string_literal.value.is_empty()),
102+
| Expression::ObjectExpression(_) => Tri::True,
103+
Expression::NullLiteral(_) => Tri::False,
104+
Expression::BooleanLiteral(boolean_literal) => Tri::from(boolean_literal.value),
105+
Expression::NumericLiteral(number_literal) => Tri::from(number_literal.value != 0.0),
106+
Expression::BigIntLiteral(big_int_literal) => Tri::from(!big_int_literal.is_zero()),
107+
Expression::StringLiteral(string_literal) => {
108+
Tri::from(!string_literal.value.is_empty())
109+
}
106110
Expression::TemplateLiteral(template_literal) => {
107111
// only for ``
108112
template_literal
@@ -111,16 +115,17 @@ pub trait NodeUtil {
111115
.filter(|quasi| quasi.tail)
112116
.and_then(|quasi| quasi.value.cooked.as_ref())
113117
.map(|cooked| !cooked.is_empty())
118+
.into()
114119
}
115120
Expression::Identifier(ident) => match ident.name.as_str() {
116-
"NaN" => Some(false),
117-
"Infinity" => Some(true),
118-
"undefined" if self.is_identifier_undefined(ident) => Some(false),
119-
_ => None,
121+
"NaN" => Tri::False,
122+
"Infinity" => Tri::True,
123+
"undefined" if self.is_identifier_undefined(ident) => Tri::False,
124+
_ => Tri::Unknown,
120125
},
121126
Expression::AssignmentExpression(assign_expr) => {
122127
match assign_expr.operator {
123-
AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => None,
128+
AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => Tri::Unknown,
124129
// For ASSIGN, the value is the value of the RHS.
125130
_ => self.get_boolean_value(&assign_expr.right),
126131
}
@@ -133,36 +138,35 @@ pub trait NodeUtil {
133138
LogicalOperator::And => {
134139
let left = self.get_boolean_value(&logical_expr.left);
135140
let right = self.get_boolean_value(&logical_expr.right);
136-
137141
match (left, right) {
138-
(Some(true), Some(true)) => Some(true),
139-
(Some(false), _) | (_, Some(false)) => Some(false),
140-
(None, _) | (_, None) => None,
142+
(Tri::True, Tri::True) => Tri::True,
143+
(Tri::False, _) | (_, Tri::False) => Tri::False,
144+
(Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
141145
}
142146
}
143147
// true || false -> true
144148
// false || false -> false
145-
// a || b -> None
149+
// a || b -> Tri::Unknown
146150
LogicalOperator::Or => {
147151
let left = self.get_boolean_value(&logical_expr.left);
148152
let right = self.get_boolean_value(&logical_expr.right);
149153

150154
match (left, right) {
151-
(Some(true), _) | (_, Some(true)) => Some(true),
152-
(Some(false), Some(false)) => Some(false),
153-
(None, _) | (_, None) => None,
155+
(Tri::True, _) | (_, Tri::True) => Tri::True,
156+
(Tri::False, Tri::False) => Tri::False,
157+
(Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
154158
}
155159
}
156-
LogicalOperator::Coalesce => None,
160+
LogicalOperator::Coalesce => Tri::Unknown,
157161
}
158162
}
159163
Expression::SequenceExpression(sequence_expr) => {
160164
// For sequence expression, the value is the value of the RHS.
161-
sequence_expr.expressions.last().and_then(|e| self.get_boolean_value(e))
165+
sequence_expr.expressions.last().map_or(Tri::Unknown, |e| self.get_boolean_value(e))
162166
}
163167
Expression::UnaryExpression(unary_expr) => {
164168
if unary_expr.operator == UnaryOperator::Void {
165-
Some(false)
169+
Tri::False
166170
} else if matches!(
167171
unary_expr.operator,
168172
UnaryOperator::BitwiseNot
@@ -173,15 +177,17 @@ pub trait NodeUtil {
173177
// +1 -> true
174178
// +0 -> false
175179
// -0 -> false
176-
self.get_number_value(expr).map(|value| value != NumberValue::Number(0_f64))
180+
self.get_number_value(expr)
181+
.map(|value| value != NumberValue::Number(0_f64))
182+
.into()
177183
} else if unary_expr.operator == UnaryOperator::LogicalNot {
178184
// !true -> false
179-
self.get_boolean_value(&unary_expr.argument).map(|boolean| !boolean)
185+
self.get_boolean_value(&unary_expr.argument).not()
180186
} else {
181-
None
187+
Tri::Unknown
182188
}
183189
}
184-
_ => None,
190+
_ => Tri::Unknown,
185191
}
186192
}
187193

@@ -213,7 +219,7 @@ pub trait NodeUtil {
213219
}
214220
UnaryOperator::LogicalNot => self
215221
.get_boolean_value(expr)
216-
.map(|boolean| if boolean { 1_f64 } else { 0_f64 })
222+
.map(|tri| if tri.is_true() { 1_f64 } else { 0_f64 })
217223
.map(NumberValue::Number),
218224
UnaryOperator::Void => Some(NumberValue::NaN),
219225
_ => None,
@@ -264,7 +270,7 @@ pub trait NodeUtil {
264270
}
265271
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
266272
UnaryOperator::LogicalNot => self.get_boolean_value(expr).map(|boolean| {
267-
if boolean {
273+
if boolean.is_true() {
268274
BigInt::one()
269275
} else {
270276
BigInt::zero()
@@ -336,7 +342,7 @@ pub trait NodeUtil {
336342
UnaryOperator::LogicalNot => {
337343
self.get_boolean_value(&unary_expr.argument).map(|boolean| {
338344
// need reversed.
339-
if boolean {
345+
if boolean.is_true() {
340346
Cow::Borrowed("false")
341347
} else {
342348
Cow::Borrowed("true")

0 commit comments

Comments
 (0)