Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5cfea76

Browse files
committed
feat(minifier): compress (a = _a) != null ? a : b and (a = _a) != null ? a.b() : undefined (#8823)
Compresses `(a = _a) != null ? a : b` to `(a = _a) ?? b` and `(a = _a) != null ? a.b() : undefined` to `(a = _a).b()`. This commonly happens when lowered.
1 parent b253938 commit 5cfea76

File tree

2 files changed

+141
-77
lines changed

2 files changed

+141
-77
lines changed

crates/oxc_minifier/src/peephole/minimize_conditions.rs

Lines changed: 139 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -577,32 +577,43 @@ impl<'a> PeepholeOptimizations {
577577

578578
// Try using the "??" or "?." operators
579579
if self.target >= ESTarget::ES2020 {
580-
if let Expression::BinaryExpression(test_binary) = &expr.test {
580+
if let Expression::BinaryExpression(test_binary) = &mut expr.test {
581581
if let Some(is_negate) = match test_binary.operator {
582582
BinaryOperator::Inequality => Some(true),
583583
BinaryOperator::Equality => Some(false),
584584
_ => None,
585585
} {
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 {
598607
// `a == null ? b : a` -> `a ?? b`
599608
// `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 =
601612
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) {
603614
return Some(ctx.ast.expression_logical(
604615
expr.span,
605-
ctx.ast.move_expression(target_expr),
616+
ctx.ast.move_expression(value_expr),
606617
LogicalOperator::Coalesce,
607618
ctx.ast.move_expression(if is_negate {
608619
&mut expr.alternate
@@ -614,13 +625,16 @@ impl<'a> PeepholeOptimizations {
614625

615626
// "a == null ? undefined : a.b.c[d](e)" => "a?.b.c[d](e)"
616627
// "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 =
618631
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) {
620633
let expr_to_inject_optional_chaining =
621634
if is_negate { &mut expr.consequent } else { &mut expr.alternate };
622635
if Self::inject_optional_chaining_if_matched(
623-
id_expr,
636+
&target_id_name,
637+
value_expr,
624638
expr_to_inject_optional_chaining,
625639
ctx,
626640
) {
@@ -698,85 +712,133 @@ impl<'a> PeepholeOptimizations {
698712
///
699713
/// For `target_expr` = `a`, `expr` = `a.b`, this function changes `expr` to `a?.b` and returns true.
700714
fn inject_optional_chaining_if_matched(
701-
target_expr: &Expression<'a>,
715+
target_id_name: &str,
716+
expr_to_inject: &mut Expression<'a>,
702717
expr: &mut Expression<'a>,
703718
ctx: Ctx<'a, '_>,
704719
) -> 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) {
709794
e.optional = true;
795+
e.object = ctx.ast.move_expression(expr_to_inject);
710796
return true;
711797
}
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+
) {
713804
return true;
714805
}
715806
}
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) {
718809
e.optional = true;
810+
e.object = ctx.ast.move_expression(expr_to_inject);
719811
return true;
720812
}
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+
) {
722819
return true;
723820
}
724821
}
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) {
727824
e.optional = true;
825+
e.callee = ctx.ast.move_expression(expr_to_inject);
728826
return true;
729827
}
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+
) {
731834
return true;
732835
}
733836
}
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-
},
764837
_ => {}
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+
_ => {}
779840
}
841+
false
780842
}
781843

782844
/// Merge `consequent` and `alternate` of `ConditionalExpression` inside.
@@ -2426,9 +2488,11 @@ mod test {
24262488
test("var a; a ? b(c) : b(e)", "var a; b(a ? c : e)");
24272489
test("a() != null ? a() : b", "a() == null ? b : a()");
24282490
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");
24292492
test("a != null ? a : b", "a == null ? b : a"); // accessing global `a` may have a getter with side effects
24302493
test_es2019("var a; a != null ? a : b", "var a; a == null ? b : a");
24312494
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)");
24322496
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
24332497
test(
24342498
"var a, undefined = 1; a != null ? a.b.c[d](e) : undefined",

tasks/minsize/minsize.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Original | minified | minified | gzip | gzip | Fixture
2121

2222
3.20 MB | 1.01 MB | 1.01 MB | 324.31 kB | 331.56 kB | echarts.js
2323

24-
6.69 MB | 2.30 MB | 2.31 MB | 468.88 kB | 488.28 kB | antd.js
24+
6.69 MB | 2.30 MB | 2.31 MB | 468.52 kB | 488.28 kB | antd.js
2525

26-
10.95 MB | 3.37 MB | 3.49 MB | 863.74 kB | 915.50 kB | typescript.js
26+
10.95 MB | 3.36 MB | 3.49 MB | 862.11 kB | 915.50 kB | typescript.js
2727

0 commit comments

Comments
 (0)