@@ -46,7 +46,7 @@ impl<'a> PeepholeOptimizations {
46
46
47
47
if let Some ( folded_stmt) = match stmt {
48
48
// If the condition is a literal, we'll let other optimizations try to remove useless code.
49
- Statement :: IfStatement ( _) => Self :: try_minimize_if ( stmt, ctx) ,
49
+ Statement :: IfStatement ( _) => self . try_minimize_if ( stmt, ctx) ,
50
50
_ => None ,
51
51
} {
52
52
* stmt = folded_stmt;
@@ -73,7 +73,7 @@ impl<'a> PeepholeOptimizations {
73
73
if Self :: try_fold_expr_in_boolean_context ( & mut logical_expr. test , ctx) {
74
74
local_change = true ;
75
75
}
76
- Self :: try_minimize_conditional ( logical_expr, ctx)
76
+ self . try_minimize_conditional ( logical_expr, ctx)
77
77
}
78
78
Expression :: AssignmentExpression ( e) => {
79
79
if self . try_compress_normal_assignment_to_combined_logical_assignment ( e, ctx) {
@@ -146,7 +146,7 @@ impl<'a> PeepholeOptimizations {
146
146
}
147
147
148
148
/// `MangleIf`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_parser.go#L9190>
149
- fn try_minimize_if ( stmt : & mut Statement < ' a > , ctx : Ctx < ' a , ' _ > ) -> Option < Statement < ' a > > {
149
+ fn try_minimize_if ( & self , stmt : & mut Statement < ' a > , ctx : Ctx < ' a , ' _ > ) -> Option < Statement < ' a > > {
150
150
let Statement :: IfStatement ( if_stmt) = stmt else { unreachable ! ( ) } ;
151
151
152
152
// `if (x) foo()` -> `x && foo()`
@@ -221,7 +221,7 @@ impl<'a> PeepholeOptimizations {
221
221
let consequent = Self :: get_block_expression ( & mut if_stmt. consequent , ctx) ;
222
222
let else_branch = if_stmt. alternate . as_mut ( ) . unwrap ( ) ;
223
223
let alternate = Self :: get_block_expression ( else_branch, ctx) ;
224
- let expr = Self :: minimize_conditional (
224
+ let expr = self . minimize_conditional (
225
225
if_stmt. span ,
226
226
test,
227
227
consequent,
@@ -268,19 +268,21 @@ impl<'a> PeepholeOptimizations {
268
268
}
269
269
270
270
pub fn minimize_conditional (
271
+ & self ,
271
272
span : Span ,
272
273
test : Expression < ' a > ,
273
274
consequent : Expression < ' a > ,
274
275
alternate : Expression < ' a > ,
275
276
ctx : Ctx < ' a , ' _ > ,
276
277
) -> Expression < ' a > {
277
278
let mut cond_expr = ctx. ast . conditional_expression ( span, test, consequent, alternate) ;
278
- Self :: try_minimize_conditional ( & mut cond_expr, ctx)
279
+ self . try_minimize_conditional ( & mut cond_expr, ctx)
279
280
. unwrap_or_else ( || Expression :: ConditionalExpression ( ctx. ast . alloc ( cond_expr) ) )
280
281
}
281
282
282
283
// `MangleIfExpr`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2745>
283
284
fn try_minimize_conditional (
285
+ & self ,
284
286
expr : & mut ConditionalExpression < ' a > ,
285
287
ctx : Ctx < ' a , ' _ > ,
286
288
) -> Option < Expression < ' a > > {
@@ -293,7 +295,7 @@ impl<'a> PeepholeOptimizations {
293
295
let Expression :: SequenceExpression ( sequence_expr) = & mut sequence else {
294
296
unreachable ! ( )
295
297
} ;
296
- let expr = Self :: minimize_conditional (
298
+ let expr = self . minimize_conditional (
297
299
span,
298
300
sequence_expr. expressions . pop ( ) . unwrap ( ) ,
299
301
ctx. ast . move_expression ( & mut expr. consequent ) ,
@@ -310,9 +312,9 @@ impl<'a> PeepholeOptimizations {
310
312
let test = ctx. ast . move_expression ( & mut test_expr. argument ) ;
311
313
let consequent = ctx. ast . move_expression ( & mut expr. alternate ) ;
312
314
let alternate = ctx. ast . move_expression ( & mut expr. consequent ) ;
313
- return Some ( Self :: minimize_conditional (
314
- expr. span , test, consequent, alternate, ctx,
315
- ) ) ;
315
+ return Some (
316
+ self . minimize_conditional ( expr. span , test, consequent, alternate, ctx) ,
317
+ ) ;
316
318
}
317
319
}
318
320
Expression :: Identifier ( id) => {
@@ -351,9 +353,9 @@ impl<'a> PeepholeOptimizations {
351
353
let test = ctx. ast . move_expression ( & mut expr. test ) ;
352
354
let consequent = ctx. ast . move_expression ( & mut expr. consequent ) ;
353
355
let alternate = ctx. ast . move_expression ( & mut expr. alternate ) ;
354
- return Some ( Self :: minimize_conditional (
355
- expr. span , test, alternate, consequent, ctx,
356
- ) ) ;
356
+ return Some (
357
+ self . minimize_conditional ( expr. span , test, alternate, consequent, ctx) ,
358
+ ) ;
357
359
}
358
360
}
359
361
_ => { }
@@ -555,7 +557,7 @@ impl<'a> PeepholeOptimizations {
555
557
let alternate_first_arg =
556
558
ctx. ast . move_expression ( alternate. arguments [ 0 ] . to_expression_mut ( ) ) ;
557
559
let mut args = std:: mem:: replace ( & mut consequent. arguments , ctx. ast . vec ( ) ) ;
558
- let cond_expr = Self :: minimize_conditional (
560
+ let cond_expr = self . minimize_conditional (
559
561
expr. test . span ( ) ,
560
562
ctx. ast . move_expression ( & mut expr. test ) ,
561
563
consequent_first_arg,
@@ -569,11 +571,52 @@ impl<'a> PeepholeOptimizations {
569
571
}
570
572
571
573
// Not part of esbuild
572
- if let Some ( e) = Self :: try_merge_conditional_expression_inside ( expr, ctx) {
574
+ if let Some ( e) = self . try_merge_conditional_expression_inside ( expr, ctx) {
573
575
return Some ( e) ;
574
576
}
575
577
576
- // TODO: Try using the "??" or "?." operators
578
+ // Try using the "??" or "?." operators
579
+ if self . target >= ESTarget :: ES2020 {
580
+ if let Expression :: BinaryExpression ( test_binary) = & expr. test {
581
+ if let Some ( is_negate) = match test_binary. operator {
582
+ BinaryOperator :: Inequality => Some ( true ) ,
583
+ BinaryOperator :: Equality => Some ( false ) ,
584
+ _ => None ,
585
+ } {
586
+ // a == null / a != null
587
+ if let Some ( ( id_expr, ( ) ) ) = Self :: commutative_pair (
588
+ ( & test_binary. left , & test_binary. right ) ,
589
+ |a| {
590
+ if let Expression :: Identifier ( id) = a {
591
+ ( !ctx. is_global_reference ( id) ) . then_some ( a)
592
+ } else {
593
+ None
594
+ }
595
+ } ,
596
+ |b| b. is_null ( ) . then_some ( ( ) ) ,
597
+ ) {
598
+ // `a == null ? b : a` -> `a ?? b`
599
+ // `a != null ? a : b` -> `a ?? b`
600
+ let target_expr =
601
+ if is_negate { & mut expr. consequent } else { & mut expr. alternate } ;
602
+ if id_expr. content_eq ( target_expr) {
603
+ return Some ( ctx. ast . expression_logical (
604
+ expr. span ,
605
+ ctx. ast . move_expression ( target_expr) ,
606
+ LogicalOperator :: Coalesce ,
607
+ ctx. ast . move_expression ( if is_negate {
608
+ & mut expr. alternate
609
+ } else {
610
+ & mut expr. consequent
611
+ } ) ,
612
+ ) ) ;
613
+ }
614
+
615
+ // TODO: try using optional chaining
616
+ }
617
+ }
618
+ }
619
+ }
577
620
578
621
if ctx. expr_eq ( & expr. alternate , & expr. consequent ) {
579
622
// TODO:
@@ -639,6 +682,7 @@ impl<'a> PeepholeOptimizations {
639
682
///
640
683
/// - `x ? a = 0 : a = 1` -> `a = x ? 0 : 1`
641
684
fn try_merge_conditional_expression_inside (
685
+ & self ,
642
686
expr : & mut ConditionalExpression < ' a > ,
643
687
ctx : Ctx < ' a , ' _ > ,
644
688
) -> Option < Expression < ' a > > {
@@ -661,7 +705,7 @@ impl<'a> PeepholeOptimizations {
661
705
{
662
706
return None ;
663
707
}
664
- let cond_expr = Self :: minimize_conditional (
708
+ let cond_expr = self . minimize_conditional (
665
709
expr. span ,
666
710
ctx. ast . move_expression ( & mut expr. test ) ,
667
711
ctx. ast . move_expression ( & mut consequent. right ) ,
@@ -1151,6 +1195,14 @@ mod test {
1151
1195
} ;
1152
1196
use oxc_syntax:: es_target:: ESTarget ;
1153
1197
1198
+ fn test_es2019 ( source_text : & str , expected : & str ) {
1199
+ let target = ESTarget :: ES2019 ;
1200
+ assert_eq ! (
1201
+ run( source_text, Some ( CompressOptions { target, ..CompressOptions :: default ( ) } ) ) ,
1202
+ run( expected, None )
1203
+ ) ;
1204
+ }
1205
+
1154
1206
/** Check that removing blocks with 1 child works */
1155
1207
#[ test]
1156
1208
fn test_fold_one_child_blocks ( ) {
@@ -2264,7 +2316,9 @@ mod test {
2264
2316
test ( "var a; a ? b(...c) : b(...e)" , "var a; b(...a ? c : e)" ) ;
2265
2317
test ( "var a; a ? b(c) : b(e)" , "var a; b(a ? c : e)" ) ;
2266
2318
test ( "a() != null ? a() : b" , "a() == null ? b : a()" ) ;
2267
- // test("a != null ? a : b", "a ?? b");
2319
+ test ( "var a; a != null ? a : b" , "var a; a ?? b" ) ;
2320
+ test ( "a != null ? a : b" , "a == null ? b : a" ) ; // accessing global `a` may have a getter with side effects
2321
+ test_es2019 ( "var a; a != null ? a : b" , "var a; a == null ? b : a" ) ;
2268
2322
// test("a != null ? a.b.c[d](e) : undefined", "a?.b.c[d](e)");
2269
2323
test ( "cmp !== 0 ? cmp : (bar, cmp);" , "cmp === 0 && bar, cmp;" ) ;
2270
2324
test ( "cmp === 0 ? cmp : (bar, cmp);" , "cmp === 0 || bar, cmp;" ) ;
0 commit comments