From 5ed439bcaff16a9023ba39afefa19e0fa5818835 Mon Sep 17 00:00:00 2001
From: camc314 <18101008+camc314@users.noreply.github.com>
Date: Mon, 6 Jan 2025 23:26:19 +0000
Subject: [PATCH] feat(minifier): minify typeof in binary expressions (#8302)

---
 .../src/ast_passes/peephole_fold_constants.rs | 63 +++++++++++++++++--
 tasks/minsize/minsize.snap                    |  2 +-
 2 files changed, 60 insertions(+), 5 deletions(-)

diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs
index 081d4826981f3..1827cb3cdeeb6 100644
--- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs
+++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs
@@ -31,7 +31,8 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants {
     fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
         let ctx = Ctx(ctx);
         if let Some(folded_expr) = match expr {
-            Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, ctx),
+            Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, ctx)
+                .or_else(|| Self::try_fold_binary_typeof_comparison(e, ctx)),
             Expression::UnaryExpression(e) => Self::try_fold_unary_expr(e, ctx),
             Expression::StaticMemberExpression(e) => Self::try_fold_static_member_expr(e, ctx),
             Expression::LogicalExpression(e) => Self::try_fold_logical_expr(e, ctx),
@@ -624,6 +625,55 @@ impl<'a, 'b> PeepholeFoldConstants {
             .to_js_string()
             .map(|value| ctx.ast.expression_string_literal(object.span(), value, None))
     }
+
+    // `typeof a === typeof b` -> `typeof a == typeof b`, `typeof a != typeof b` -> `typeof a != typeof b`,
+    // `typeof a == typeof a` -> `true`, `typeof a != typeof a` -> `false`
+    fn try_fold_binary_typeof_comparison(
+        bin_expr: &mut BinaryExpression<'a>,
+        ctx: Ctx<'a, 'b>,
+    ) -> Option<Expression<'a>> {
+        if bin_expr.operator.is_equality() {
+            if let (Expression::UnaryExpression(left), Expression::UnaryExpression(right)) =
+                (&bin_expr.left, &bin_expr.right)
+            {
+                if left.operator.is_typeof() && right.operator.is_typeof() {
+                    if let (
+                        Expression::Identifier(left_ident),
+                        Expression::Identifier(right_ident),
+                    ) = (&left.argument, &right.argument)
+                    {
+                        if left_ident.name == right_ident.name {
+                            return Some(ctx.ast.expression_boolean_literal(
+                                bin_expr.span,
+                                matches!(
+                                    bin_expr.operator,
+                                    BinaryOperator::StrictEquality | BinaryOperator::Equality
+                                ),
+                            ));
+                        }
+                    }
+
+                    if matches!(
+                        bin_expr.operator,
+                        BinaryOperator::StrictEquality | BinaryOperator::StrictInequality
+                    ) {
+                        return Some(ctx.ast.expression_binary(
+                            bin_expr.span,
+                            ctx.ast.move_expression(&mut bin_expr.left),
+                            if bin_expr.operator == BinaryOperator::StrictEquality {
+                                BinaryOperator::Equality
+                            } else {
+                                BinaryOperator::Inequality
+                            },
+                            ctx.ast.move_expression(&mut bin_expr.right),
+                        ));
+                    }
+                }
+            }
+        }
+
+        None
+    }
 }
 
 /// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeFoldConstantsTest.java>
@@ -931,11 +981,8 @@ mod test {
         test("'a' == 'a'", "true");
         test("'b' != 'a'", "true");
         test_same("typeof a != 'number'");
-        test_same("typeof a == typeof a");
         test("'a' === 'a'", "true");
         test("'b' !== 'a'", "true");
-        test_same("typeof a === typeof a");
-        test_same("typeof a !== typeof a");
         test_same("'' + x <= '' + y");
         test_same("'' + x != '' + y");
         test_same("'' + x === '' + y");
@@ -1797,4 +1844,12 @@ mod test {
         test("typeof foo + ''", "typeof foo");
         test_same("typeof foo - ''");
     }
+
+    #[test]
+    fn test_fold_same_typeof() {
+        test("typeof foo === typeof bar", "typeof foo == typeof bar");
+        test("typeof foo !== typeof bar", "typeof foo != typeof bar");
+        test("typeof foo.bar === typeof foo.bar", "typeof foo.bar == typeof foo.bar");
+        test("typeof foo.bar !== typeof foo.bar", "typeof foo.bar != typeof foo.bar");
+    }
 }
diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap
index f78847f3d6fca..084a421f0306e 100644
--- a/tasks/minsize/minsize.snap
+++ b/tasks/minsize/minsize.snap
@@ -13,7 +13,7 @@ Original   | minified   | minified   | gzip       | gzip       | Fixture
 
 555.77 kB  | 273.15 kB  | 270.13 kB  | 90.95 kB   | 90.80 kB   | d3.js     
 
-1.01 MB    | 460.32 kB  | 458.89 kB  | 126.84 kB  | 126.71 kB  | bundle.min.js
+1.01 MB    | 460.31 kB  | 458.89 kB  | 126.84 kB  | 126.71 kB  | bundle.min.js
 
 1.25 MB    | 652.68 kB  | 646.76 kB  | 163.53 kB  | 163.73 kB  | three.js