@@ -577,32 +577,43 @@ impl<'a> PeepholeOptimizations {
577
577
578
578
// Try using the "??" or "?." operators
579
579
if self . target >= ESTarget :: ES2020 {
580
- if let Expression :: BinaryExpression ( test_binary) = & expr. test {
580
+ if let Expression :: BinaryExpression ( test_binary) = & mut expr. test {
581
581
if let Some ( is_negate) = match test_binary. operator {
582
582
BinaryOperator :: Inequality => Some ( true ) ,
583
583
BinaryOperator :: Equality => Some ( false ) ,
584
584
_ => None ,
585
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
- ) {
586
+ // a == null / a != null / (a = foo) == null / (a = foo) != null
587
+ let value_expr_with_id_name = if test_binary. left . is_null ( ) {
588
+ if let Some ( id) = Self :: extract_id_or_assign_to_id ( & test_binary. right )
589
+ . filter ( |id| !ctx. is_global_reference ( id) )
590
+ {
591
+ Some ( ( id. name , & mut test_binary. right ) )
592
+ } else {
593
+ None
594
+ }
595
+ } else if test_binary. right . is_null ( ) {
596
+ if let Some ( id) = Self :: extract_id_or_assign_to_id ( & test_binary. left )
597
+ . filter ( |id| !ctx. is_global_reference ( id) )
598
+ {
599
+ Some ( ( id. name , & mut test_binary. left ) )
600
+ } else {
601
+ None
602
+ }
603
+ } else {
604
+ None
605
+ } ;
606
+ if let Some ( ( target_id_name, value_expr) ) = value_expr_with_id_name {
598
607
// `a == null ? b : a` -> `a ?? b`
599
608
// `a != null ? a : b` -> `a ?? b`
600
- let target_expr =
609
+ // `(a = foo) == null ? b : a` -> `(a = foo) ?? b`
610
+ // `(a = foo) != null ? a : b` -> `(a = foo) ?? b`
611
+ let maybe_same_id_expr =
601
612
if is_negate { & mut expr. consequent } else { & mut expr. alternate } ;
602
- if id_expr . content_eq ( target_expr ) {
613
+ if maybe_same_id_expr . is_specific_id ( & target_id_name ) {
603
614
return Some ( ctx. ast . expression_logical (
604
615
expr. span ,
605
- ctx. ast . move_expression ( target_expr ) ,
616
+ ctx. ast . move_expression ( value_expr ) ,
606
617
LogicalOperator :: Coalesce ,
607
618
ctx. ast . move_expression ( if is_negate {
608
619
& mut expr. alternate
@@ -614,13 +625,16 @@ impl<'a> PeepholeOptimizations {
614
625
615
626
// "a == null ? undefined : a.b.c[d](e)" => "a?.b.c[d](e)"
616
627
// "a != null ? a.b.c[d](e) : undefined" => "a?.b.c[d](e)"
617
- let target_expr =
628
+ // "(a = foo) == null ? undefined : a.b.c[d](e)" => "(a = foo)?.b.c[d](e)"
629
+ // "(a = foo) != null ? a.b.c[d](e) : undefined" => "(a = foo)?.b.c[d](e)"
630
+ let maybe_undefined_expr =
618
631
if is_negate { & expr. alternate } else { & expr. consequent } ;
619
- if ctx. is_expression_undefined ( target_expr ) {
632
+ if ctx. is_expression_undefined ( maybe_undefined_expr ) {
620
633
let expr_to_inject_optional_chaining =
621
634
if is_negate { & mut expr. consequent } else { & mut expr. alternate } ;
622
635
if Self :: inject_optional_chaining_if_matched (
623
- id_expr,
636
+ & target_id_name,
637
+ value_expr,
624
638
expr_to_inject_optional_chaining,
625
639
ctx,
626
640
) {
@@ -698,85 +712,133 @@ impl<'a> PeepholeOptimizations {
698
712
///
699
713
/// For `target_expr` = `a`, `expr` = `a.b`, this function changes `expr` to `a?.b` and returns true.
700
714
fn inject_optional_chaining_if_matched (
701
- target_expr : & Expression < ' a > ,
715
+ target_id_name : & str ,
716
+ expr_to_inject : & mut Expression < ' a > ,
702
717
expr : & mut Expression < ' a > ,
703
718
ctx : Ctx < ' a , ' _ > ,
704
719
) -> 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) {
720
+ if Self :: inject_optional_chaining_if_matched_inner (
721
+ target_id_name,
722
+ expr_to_inject,
723
+ expr,
724
+ ctx,
725
+ ) {
726
+ if !matches ! ( expr, Expression :: ChainExpression ( _) ) {
727
+ * expr = ctx. ast . expression_chain (
728
+ expr. span ( ) ,
729
+ ctx. ast . move_expression ( expr) . into_chain_element ( ) . unwrap ( ) ,
730
+ ) ;
731
+ }
732
+ true
733
+ } else {
734
+ false
735
+ }
736
+ }
737
+
738
+ /// See [`Self::inject_optional_chaining_if_matched`]
739
+ fn inject_optional_chaining_if_matched_inner (
740
+ target_id_name : & str ,
741
+ expr_to_inject : & mut Expression < ' a > ,
742
+ expr : & mut Expression < ' a > ,
743
+ ctx : Ctx < ' a , ' _ > ,
744
+ ) -> bool {
745
+ match expr {
746
+ Expression :: StaticMemberExpression ( e) => {
747
+ if e. object . is_specific_id ( target_id_name) {
748
+ e. optional = true ;
749
+ e. object = ctx. ast . move_expression ( expr_to_inject) ;
750
+ return true ;
751
+ }
752
+ if Self :: inject_optional_chaining_if_matched_inner (
753
+ target_id_name,
754
+ expr_to_inject,
755
+ & mut e. object ,
756
+ ctx,
757
+ ) {
758
+ return true ;
759
+ }
760
+ }
761
+ Expression :: ComputedMemberExpression ( e) => {
762
+ if e. object . is_specific_id ( target_id_name) {
763
+ e. optional = true ;
764
+ e. object = ctx. ast . move_expression ( expr_to_inject) ;
765
+ return true ;
766
+ }
767
+ if Self :: inject_optional_chaining_if_matched_inner (
768
+ target_id_name,
769
+ expr_to_inject,
770
+ & mut e. object ,
771
+ ctx,
772
+ ) {
773
+ return true ;
774
+ }
775
+ }
776
+ Expression :: CallExpression ( e) => {
777
+ if e. callee . is_specific_id ( target_id_name) {
778
+ e. optional = true ;
779
+ e. callee = ctx. ast . move_expression ( expr_to_inject) ;
780
+ return true ;
781
+ }
782
+ if Self :: inject_optional_chaining_if_matched_inner (
783
+ target_id_name,
784
+ expr_to_inject,
785
+ & mut e. callee ,
786
+ ctx,
787
+ ) {
788
+ return true ;
789
+ }
790
+ }
791
+ Expression :: ChainExpression ( e) => match & mut e. expression {
792
+ ChainElement :: StaticMemberExpression ( e) => {
793
+ if e. object . is_specific_id ( target_id_name) {
709
794
e. optional = true ;
795
+ e. object = ctx. ast . move_expression ( expr_to_inject) ;
710
796
return true ;
711
797
}
712
- if inject ( target_expr, & mut e. object ) {
798
+ if Self :: inject_optional_chaining_if_matched_inner (
799
+ target_id_name,
800
+ expr_to_inject,
801
+ & mut e. object ,
802
+ ctx,
803
+ ) {
713
804
return true ;
714
805
}
715
806
}
716
- Expression :: ComputedMemberExpression ( e) => {
717
- if e. object . content_eq ( target_expr ) {
807
+ ChainElement :: ComputedMemberExpression ( e) => {
808
+ if e. object . is_specific_id ( target_id_name ) {
718
809
e. optional = true ;
810
+ e. object = ctx. ast . move_expression ( expr_to_inject) ;
719
811
return true ;
720
812
}
721
- if inject ( target_expr, & mut e. object ) {
813
+ if Self :: inject_optional_chaining_if_matched_inner (
814
+ target_id_name,
815
+ expr_to_inject,
816
+ & mut e. object ,
817
+ ctx,
818
+ ) {
722
819
return true ;
723
820
}
724
821
}
725
- Expression :: CallExpression ( e) => {
726
- if e. callee . content_eq ( target_expr ) {
822
+ ChainElement :: CallExpression ( e) => {
823
+ if e. callee . is_specific_id ( target_id_name ) {
727
824
e. optional = true ;
825
+ e. callee = ctx. ast . move_expression ( expr_to_inject) ;
728
826
return true ;
729
827
}
730
- if inject ( target_expr, & mut e. callee ) {
828
+ if Self :: inject_optional_chaining_if_matched_inner (
829
+ target_id_name,
830
+ expr_to_inject,
831
+ & mut e. callee ,
832
+ ctx,
833
+ ) {
731
834
return true ;
732
835
}
733
836
}
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
837
_ => { }
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
838
+ } ,
839
+ _ => { }
779
840
}
841
+ false
780
842
}
781
843
782
844
/// Merge `consequent` and `alternate` of `ConditionalExpression` inside.
@@ -2426,9 +2488,11 @@ mod test {
2426
2488
test ( "var a; a ? b(c) : b(e)" , "var a; b(a ? c : e)" ) ;
2427
2489
test ( "a() != null ? a() : b" , "a() == null ? b : a()" ) ;
2428
2490
test ( "var a; a != null ? a : b" , "var a; a ?? b" ) ;
2491
+ test ( "var a; (a = _a) != null ? a : b" , "var a; (a = _a) ?? b" ) ;
2429
2492
test ( "a != null ? a : b" , "a == null ? b : a" ) ; // accessing global `a` may have a getter with side effects
2430
2493
test_es2019 ( "var a; a != null ? a : b" , "var a; a == null ? b : a" ) ;
2431
2494
test ( "var a; a != null ? a.b.c[d](e) : undefined" , "var a; a?.b.c[d](e)" ) ;
2495
+ test ( "var a; (a = _a) != null ? a.b.c[d](e) : undefined" , "var a; (a = _a)?.b.c[d](e)" ) ;
2432
2496
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
2433
2497
test (
2434
2498
"var a, undefined = 1; a != null ? a.b.c[d](e) : undefined" ,
0 commit comments