@@ -481,18 +481,39 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
481
481
482
482
let new_op = expr. operator . to_assignment_operator ( ) ;
483
483
484
- match ( & assignment_expr. left , & expr. left ) {
485
- // `a || (a = b)` -> `a ||= b`
484
+ if !Self :: has_no_side_effect_for_evaluation_same_target (
485
+ & assignment_expr. left ,
486
+ & expr. left ,
487
+ ctx,
488
+ ) {
489
+ return None ;
490
+ }
491
+
492
+ assignment_expr. span = expr. span ;
493
+ assignment_expr. operator = new_op;
494
+ Some ( ctx. ast . move_expression ( & mut expr. right ) )
495
+ }
496
+
497
+ /// Returns `true` if the assignment target and expression have no side effect for *evaluation* and points to the same reference.
498
+ ///
499
+ /// Evaluation here means `Evaluation` in the spec.
500
+ /// <https://tc39.es/ecma262/multipage/syntax-directed-operations.html#sec-evaluation>
501
+ ///
502
+ /// Matches the following cases:
503
+ ///
504
+ /// - `a`, `a`
505
+ /// - `a.b`, `a.b`
506
+ /// - `a.#b`, `a.#b`
507
+ fn has_no_side_effect_for_evaluation_same_target (
508
+ assignment_target : & AssignmentTarget ,
509
+ expr : & Expression ,
510
+ ctx : Ctx < ' a , ' b > ,
511
+ ) -> bool {
512
+ match ( & assignment_target, & expr) {
486
513
(
487
514
AssignmentTarget :: AssignmentTargetIdentifier ( write_id_ref) ,
488
515
Expression :: Identifier ( read_id_ref) ,
489
- ) => {
490
- if write_id_ref. name != read_id_ref. name {
491
- return None ;
492
- }
493
- }
494
- // `a.b || (a.b = c)` -> `a.b ||= c`
495
- // `a.#b || (a.#b = c)` -> `a.#b ||= c`
516
+ ) => write_id_ref. name == read_id_ref. name ,
496
517
(
497
518
AssignmentTarget :: StaticMemberExpression ( _) ,
498
519
Expression :: StaticMemberExpression ( _) ,
@@ -501,24 +522,17 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
501
522
AssignmentTarget :: PrivateFieldExpression ( _) ,
502
523
Expression :: PrivateFieldExpression ( _) ,
503
524
) => {
504
- let write_expr = assignment_expr . left . to_member_expression ( ) ;
505
- let read_expr = expr. left . to_member_expression ( ) ;
525
+ let write_expr = assignment_target . to_member_expression ( ) ;
526
+ let read_expr = expr. to_member_expression ( ) ;
506
527
let Expression :: Identifier ( write_expr_object_id) = & write_expr. object ( ) else {
507
- return None ;
528
+ return false ;
508
529
} ;
509
- // It should also return None when the reference might refer to a reference value created by a with statement
530
+ // It should also return false when the reference might refer to a reference value created by a with statement
510
531
// when the minifier supports with statements
511
- if ctx. is_global_reference ( write_expr_object_id) || write_expr. content_ne ( read_expr)
512
- {
513
- return None ;
514
- }
532
+ !ctx. is_global_reference ( write_expr_object_id) && write_expr. content_eq ( read_expr)
515
533
}
516
- _ => return None ,
534
+ _ => false ,
517
535
}
518
-
519
- assignment_expr. span = expr. span ;
520
- assignment_expr. operator = new_op;
521
- Some ( ctx. ast . move_expression ( & mut expr. right ) )
522
536
}
523
537
524
538
fn commutative_pair < A , F , G , RetF : ' a , RetG : ' a > (
@@ -603,14 +617,12 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
603
617
if !matches ! ( expr. operator, AssignmentOperator :: Assign ) {
604
618
return ;
605
619
}
606
- let AssignmentTarget :: AssignmentTargetIdentifier ( write_id_ref) = & mut expr. left else {
607
- return ;
608
- } ;
609
620
610
621
let Expression :: BinaryExpression ( binary_expr) = & mut expr. right else { return } ;
611
622
let Some ( new_op) = binary_expr. operator . to_assignment_operator ( ) else { return } ;
612
- let Expression :: Identifier ( read_id_ref) = & mut binary_expr. left else { return } ;
613
- if write_id_ref. name != read_id_ref. name {
623
+
624
+ if !Self :: has_no_side_effect_for_evaluation_same_target ( & expr. left , & binary_expr. left , ctx)
625
+ {
614
626
return ;
615
627
}
616
628
@@ -1184,6 +1196,18 @@ mod test {
1184
1196
test_same ( "x = g() & x" ) ;
1185
1197
1186
1198
test_same ( "x = (x -= 2) ^ x" ) ;
1199
+
1200
+ // GetValue(x) has no sideeffect when x is a resolved identifier
1201
+ test ( "var x; x.y = x.y + 3" , "var x; x.y += 3" ) ;
1202
+ test ( "var x; x.#y = x.#y + 3" , "var x; x.#y += 3" ) ;
1203
+ test_same ( "x.y = x.y + 3" ) ;
1204
+ // this can be compressed if `y` does not have side effect
1205
+ test_same ( "var x; x[y] = x[y] + 3" ) ;
1206
+ // GetValue(x) has a side effect in this case
1207
+ // Example case: `var a = { get b() { console.log('b'); return { get c() { console.log('c') } } } }; a.b.c = a.b.c + 1`
1208
+ test_same ( "var x; x.y.z = x.y.z + 3" ) ;
1209
+ // This case is not supported, since the minifier does not support with statements
1210
+ // test_same("var x; with (z) { x.y || (x.y = 3) }");
1187
1211
}
1188
1212
1189
1213
#[ test]
0 commit comments