@@ -612,7 +612,23 @@ impl<'a> PeepholeOptimizations {
612
612
) ) ;
613
613
}
614
614
615
- // TODO: try using optional chaining
615
+ // "a == null ? undefined : a.b.c[d](e)" => "a?.b.c[d](e)"
616
+ // "a != null ? a.b.c[d](e) : undefined" => "a?.b.c[d](e)"
617
+ let target_expr =
618
+ if is_negate { & expr. alternate } else { & expr. consequent } ;
619
+ if ctx. is_expression_undefined ( target_expr) {
620
+ let expr_to_inject_optional_chaining =
621
+ if is_negate { & mut expr. consequent } else { & mut expr. alternate } ;
622
+ if Self :: inject_optional_chaining_if_matched (
623
+ id_expr,
624
+ expr_to_inject_optional_chaining,
625
+ ctx,
626
+ ) {
627
+ return Some (
628
+ ctx. ast . move_expression ( expr_to_inject_optional_chaining) ,
629
+ ) ;
630
+ }
631
+ }
616
632
}
617
633
}
618
634
}
@@ -678,6 +694,91 @@ impl<'a> PeepholeOptimizations {
678
694
ctx. ast . expression_logical ( span, a, op, b)
679
695
}
680
696
697
+ /// Modify `expr` if that has `target_expr` as a parent, and returns true if modified.
698
+ ///
699
+ /// For `target_expr` = `a`, `expr` = `a.b`, this function changes `expr` to `a?.b` and returns true.
700
+ fn inject_optional_chaining_if_matched (
701
+ target_expr : & Expression < ' a > ,
702
+ expr : & mut Expression < ' a > ,
703
+ ctx : Ctx < ' a , ' _ > ,
704
+ ) -> bool {
705
+ fn inject ( target_expr : & Expression < ' _ > , expr : & mut Expression < ' _ > ) -> bool {
706
+ match expr {
707
+ Expression :: StaticMemberExpression ( e) => {
708
+ if e. object . content_eq ( target_expr) {
709
+ e. optional = true ;
710
+ return true ;
711
+ }
712
+ if inject ( target_expr, & mut e. object ) {
713
+ return true ;
714
+ }
715
+ }
716
+ Expression :: ComputedMemberExpression ( e) => {
717
+ if e. object . content_eq ( target_expr) {
718
+ e. optional = true ;
719
+ return true ;
720
+ }
721
+ if inject ( target_expr, & mut e. object ) {
722
+ return true ;
723
+ }
724
+ }
725
+ Expression :: CallExpression ( e) => {
726
+ if e. callee . content_eq ( target_expr) {
727
+ e. optional = true ;
728
+ return true ;
729
+ }
730
+ if inject ( target_expr, & mut e. callee ) {
731
+ return true ;
732
+ }
733
+ }
734
+ Expression :: ChainExpression ( e) => match & mut e. expression {
735
+ ChainElement :: StaticMemberExpression ( e) => {
736
+ if e. object . content_eq ( target_expr) {
737
+ e. optional = true ;
738
+ return true ;
739
+ }
740
+ if inject ( target_expr, & mut e. object ) {
741
+ return true ;
742
+ }
743
+ }
744
+ ChainElement :: ComputedMemberExpression ( e) => {
745
+ if e. object . content_eq ( target_expr) {
746
+ e. optional = true ;
747
+ return true ;
748
+ }
749
+ if inject ( target_expr, & mut e. object ) {
750
+ return true ;
751
+ }
752
+ }
753
+ ChainElement :: CallExpression ( e) => {
754
+ if e. callee . content_eq ( target_expr) {
755
+ e. optional = true ;
756
+ return true ;
757
+ }
758
+ if inject ( target_expr, & mut e. callee ) {
759
+ return true ;
760
+ }
761
+ }
762
+ _ => { }
763
+ } ,
764
+ _ => { }
765
+ }
766
+ false
767
+ }
768
+
769
+ if inject ( target_expr, expr) {
770
+ if !matches ! ( expr, Expression :: ChainExpression ( _) ) {
771
+ * expr = ctx. ast . expression_chain (
772
+ expr. span ( ) ,
773
+ ctx. ast . move_expression ( expr) . into_chain_element ( ) . unwrap ( ) ,
774
+ ) ;
775
+ }
776
+ true
777
+ } else {
778
+ false
779
+ }
780
+ }
781
+
681
782
/// Merge `consequent` and `alternate` of `ConditionalExpression` inside.
682
783
///
683
784
/// - `x ? a = 0 : a = 1` -> `a = x ? 0 : 1`
@@ -2319,7 +2420,16 @@ mod test {
2319
2420
test ( "var a; a != null ? a : b" , "var a; a ?? b" ) ;
2320
2421
test ( "a != null ? a : b" , "a == null ? b : a" ) ; // accessing global `a` may have a getter with side effects
2321
2422
test_es2019 ( "var a; a != null ? a : b" , "var a; a == null ? b : a" ) ;
2322
- // test("a != null ? a.b.c[d](e) : undefined", "a?.b.c[d](e)");
2423
+ test ( "var a; a != null ? a.b.c[d](e) : undefined" , "var a; a?.b.c[d](e)" ) ;
2424
+ test ( "a != null ? a.b.c[d](e) : undefined" , "a != null && a.b.c[d](e)" ) ; // accessing global `a` may have a getter with side effects
2425
+ test (
2426
+ "var a, undefined = 1; a != null ? a.b.c[d](e) : undefined" ,
2427
+ "var a, undefined = 1; a == null ? undefined : a.b.c[d](e)" ,
2428
+ ) ;
2429
+ test_es2019 (
2430
+ "var a; a != null ? a.b.c[d](e) : undefined" ,
2431
+ "var a; a != null && a.b.c[d](e)" ,
2432
+ ) ;
2323
2433
test ( "cmp !== 0 ? cmp : (bar, cmp);" , "cmp === 0 && bar, cmp;" ) ;
2324
2434
test ( "cmp === 0 ? cmp : (bar, cmp);" , "cmp === 0 || bar, cmp;" ) ;
2325
2435
test ( "cmp !== 0 ? (bar, cmp) : cmp;" , "cmp === 0 || bar, cmp;" ) ;
0 commit comments