Repository: swc-project/swc (rust)

Issue:
Better dead code elimination for swc minify.
### Describe the feature

Given code:

```js
const a = 'a' === 'a';
if (a) {
  console.log('Hey!');
}
console.log(a);
```

Terser can output:

```js
console.log("Hey!"),console.log(true);
// You can try it out at https://try.terser.org/
```

While swc can only do:

```js
var a=!0;a&&console.log("Hey!"),console.log(a)
```

### Babel plugin or link to the feature description

_No response_

### Additional context

_No response_

Pull request:
feat(es/minifier): Treat `!0` as a literal while inlining
**Description:**



**Related issue (if exists):**

 - Closes #3629

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs
index 2bee463bcd..f6eaa66eba 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs
@@ -165,6 +165,9 @@ where
                             Lit::Regex(_) => self.options.unsafe_regexp,
                             _ => false,
                         },
+                        Expr::Unary(UnaryExpr {
+                            op: op!("!"), arg, ..
+                        }) => arg.is_lit(),
                         Expr::This(..) => usage.is_fn_local,
                         Expr::Arrow(arr) => is_arrow_simple_enough(arr),
                         _ => false,


Test patch:
diff --git a/crates/swc/tests/tsc-references/booleanPropertyAccess_es2015.2.minified.js b/crates/swc/tests/tsc-references/booleanPropertyAccess_es2015.2.minified.js
index 24dbae9cda..43c806762a 100644
--- a/crates/swc/tests/tsc-references/booleanPropertyAccess_es2015.2.minified.js
+++ b/crates/swc/tests/tsc-references/booleanPropertyAccess_es2015.2.minified.js
@@ -1,2 +1,1 @@
-var x = !0;
-x.toString(), x.toString();
+(!0).toString(), (!0).toString();
diff --git a/crates/swc/tests/tsc-references/booleanPropertyAccess_es5.2.minified.js b/crates/swc/tests/tsc-references/booleanPropertyAccess_es5.2.minified.js
index 24dbae9cda..43c806762a 100644
--- a/crates/swc/tests/tsc-references/booleanPropertyAccess_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/booleanPropertyAccess_es5.2.minified.js
@@ -1,2 +1,1 @@
-var x = !0;
-x.toString(), x.toString();
+(!0).toString(), (!0).toString();
diff --git a/crates/swc/tests/tsc-references/extendBooleanInterface_es2015.2.minified.js b/crates/swc/tests/tsc-references/extendBooleanInterface_es2015.2.minified.js
index 96eb5db951..ddde18aa3f 100644
--- a/crates/swc/tests/tsc-references/extendBooleanInterface_es2015.2.minified.js
+++ b/crates/swc/tests/tsc-references/extendBooleanInterface_es2015.2.minified.js
@@ -1,2 +1,1 @@
-var x = !0;
-x.doStuff(), x.doOtherStuff("hm"), x.doStuff(), x.doOtherStuff("hm");
+(!0).doStuff(), (!0).doOtherStuff("hm"), (!0).doStuff(), (!0).doOtherStuff("hm");
diff --git a/crates/swc/tests/tsc-references/extendBooleanInterface_es5.2.minified.js b/crates/swc/tests/tsc-references/extendBooleanInterface_es5.2.minified.js
index 96eb5db951..ddde18aa3f 100644
--- a/crates/swc/tests/tsc-references/extendBooleanInterface_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/extendBooleanInterface_es5.2.minified.js
@@ -1,2 +1,1 @@
-var x = !0;
-x.doStuff(), x.doOtherStuff("hm"), x.doStuff(), x.doOtherStuff("hm");
+(!0).doStuff(), (!0).doOtherStuff("hm"), (!0).doStuff(), (!0).doOtherStuff("hm");
diff --git a/crates/swc/tests/tsc-references/ifDoWhileStatements_es2015.2.minified.js b/crates/swc/tests/tsc-references/ifDoWhileStatements_es2015.2.minified.js
index 83c18035c9..ea8a0d3ac5 100644
--- a/crates/swc/tests/tsc-references/ifDoWhileStatements_es2015.2.minified.js
+++ b/crates/swc/tests/tsc-references/ifDoWhileStatements_es2015.2.minified.js
@@ -46,12 +46,11 @@ while (new C())
 for(new D(); new D(););
 do ;
 while (new D())
-for(var M, N, a = !0; a;);
-do ;
-while (a)
+for(;;);
+for(;;);
 do ;
 while (null)
-for(var c = void 0; c;);
+for(var M, N, c = void 0; c;);
 do ;
 while (c)
 do ;
diff --git a/crates/swc/tests/tsc-references/ifDoWhileStatements_es5.2.minified.js b/crates/swc/tests/tsc-references/ifDoWhileStatements_es5.2.minified.js
index 81541fb9c0..ec37459a02 100644
--- a/crates/swc/tests/tsc-references/ifDoWhileStatements_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/ifDoWhileStatements_es5.2.minified.js
@@ -100,9 +100,8 @@ while (new C());
 for(new D(); new D(););
 do ;
 while (new D());
-for(var a = !0; a;);
-do ;
-while (a);
+for(;;);
+for(;;);
 do ;
 while (null);
 for(var c = void 0; c;);
diff --git a/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es2015.2.minified.js b/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es2015.2.minified.js
index 0ccc1fed53..f73282a770 100644
--- a/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es2015.2.minified.js
+++ b/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es2015.2.minified.js
@@ -1,2 +1,1 @@
-const maybeBool = !1;
-null == maybeBool || maybeBool || foo(), foo(), foo();
+foo(), foo(), foo();
diff --git a/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es5.2.minified.js b/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es5.2.minified.js
index ac05f7ab20..f73282a770 100644
--- a/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/nullishCoalescingOperator1_es5.2.minified.js
@@ -1,2 +1,1 @@
-var maybeBool = !1;
-null == maybeBool || maybeBool || foo(), foo(), foo();
+foo(), foo(), foo();
diff --git a/crates/swc/tests/tsc-references/typeOfThisInstanceMemberNarrowedWithLoopAntecedent_es5.2.minified.js b/crates/swc/tests/tsc-references/typeOfThisInstanceMemberNarrowedWithLoopAntecedent_es5.2.minified.js
index 9222eba74a..111d3898a4 100644
--- a/crates/swc/tests/tsc-references/typeOfThisInstanceMemberNarrowedWithLoopAntecedent_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/typeOfThisInstanceMemberNarrowedWithLoopAntecedent_es5.2.minified.js
@@ -32,7 +32,7 @@ var SomeClass = function() {
         {
             key: "method",
             value: function() {
-                for(var ref, c = !1; c;);
+                var ref;
                 "numberVariant" === this.state.type && this.state.data, null === (ref = this.state) || void 0 === ref || ref.data;
             }
         }
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/analysis-snapshot.rust-debug b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/analysis-snapshot.rust-debug
new file mode 100644
index 0000000000..10ef815f29
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/analysis-snapshot.rust-debug
@@ -0,0 +1,80 @@
+TestSnapshot {
+    vars: [
+        (
+            (
+                Atom('a' type=inline),
+                #1,
+            ),
+            VarUsageInfo {
+                inline_prevented: false,
+                ref_count: 2,
+                cond_init: false,
+                declared: true,
+                declared_count: 1,
+                declared_as_fn_param: false,
+                declared_as_fn_expr: false,
+                assign_count: 0,
+                mutation_by_call_count: 1,
+                usage_count: 2,
+                reassigned_with_assignment: false,
+                reassigned_with_var_decl: false,
+                mutated: true,
+                has_property_access: false,
+                has_property_mutation: false,
+                accessed_props: {},
+                exported: false,
+                used_above_decl: false,
+                is_fn_local: true,
+                used_by_nested_fn: false,
+                executed_multiple_time: false,
+                used_in_cond: false,
+                var_kind: Some(
+                    "const",
+                ),
+                var_initialized: true,
+                declared_as_catch_param: false,
+                no_side_effect_for_member_access: false,
+                used_as_callee: false,
+                used_as_arg: true,
+                infects: [],
+            },
+        ),
+        (
+            (
+                Atom('console' type=inline),
+                #1,
+            ),
+            VarUsageInfo {
+                inline_prevented: false,
+                ref_count: 2,
+                cond_init: false,
+                declared: false,
+                declared_count: 0,
+                declared_as_fn_param: false,
+                declared_as_fn_expr: false,
+                assign_count: 0,
+                mutation_by_call_count: 0,
+                usage_count: 2,
+                reassigned_with_assignment: false,
+                reassigned_with_var_decl: false,
+                mutated: false,
+                has_property_access: true,
+                has_property_mutation: false,
+                accessed_props: {},
+                exported: false,
+                used_above_decl: true,
+                is_fn_local: true,
+                used_by_nested_fn: false,
+                executed_multiple_time: false,
+                used_in_cond: true,
+                var_kind: None,
+                var_initialized: false,
+                declared_as_catch_param: false,
+                no_side_effect_for_member_access: false,
+                used_as_callee: false,
+                used_as_arg: false,
+                infects: [],
+            },
+        ),
+    ],
+}
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/input.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/input.js
new file mode 100644
index 0000000000..35b7c7ff7d
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/input.js
@@ -0,0 +1,5 @@
+const a = 'a' === 'a';
+if (a) {
+    console.log('Hey!');
+}
+console.log(a);
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/output.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/output.js
new file mode 100644
index 0000000000..c018b74dd4
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3629/1/output.js
@@ -0,0 +1,1 @@
+console.log("Hey!"), console.log(!0);
diff --git a/crates/swc_ecma_minifier/tests/projects/output/react-17.0.2.js b/crates/swc_ecma_minifier/tests/projects/output/react-17.0.2.js
index ec0ba5d82f..6659334cd6 100644
--- a/crates/swc_ecma_minifier/tests/projects/output/react-17.0.2.js
+++ b/crates/swc_ecma_minifier/tests/projects/output/react-17.0.2.js
@@ -623,7 +623,7 @@
             null !== fragment.ref && (setCurrentlyValidatingElement$1(fragment), error1("Invalid attribute `ref` supplied to `React.Fragment`."), setCurrentlyValidatingElement$1(null));
         })(element) : validatePropTypes(element), element;
     }
-    var didWarnAboutDeprecatedCreateFactory = !1, enableSchedulerDebugging = !1;
+    var didWarnAboutDeprecatedCreateFactory = !1;
     if ("object" == typeof performance && "function" == typeof performance.now) {
         var localPerformance = performance;
         getCurrentTime = function() {
@@ -755,7 +755,7 @@
     }
     function workLoop(hasTimeRemaining, initialTime) {
         var currentTime = initialTime;
-        for(advanceTimers(currentTime), currentTask = peek(taskQueue); null !== currentTask && !enableSchedulerDebugging;){
+        for(advanceTimers(currentTime), currentTask = peek(taskQueue); null !== currentTask;){
             if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())) break;
             var callback = currentTask.callback;
             if ("function" == typeof callback) {
diff --git a/crates/swc_ecma_minifier/tests/projects/output/react-dom-17.0.2.js b/crates/swc_ecma_minifier/tests/projects/output/react-dom-17.0.2.js
index c2b2da0114..d43bba03ab 100644
--- a/crates/swc_ecma_minifier/tests/projects/output/react-dom-17.0.2.js
+++ b/crates/swc_ecma_minifier/tests/projects/output/react-dom-17.0.2.js
@@ -25,7 +25,7 @@
         argsWithFormat.unshift("Warning: " + format), Function.prototype.apply.call(console[level], console, argsWithFormat);
     }
     if (!React) throw Error("ReactDOM was loaded before React. Make sure you load the React package before loading ReactDOM.");
-    var enableProfilerTimer = !0, enableFundamentalAPI = !1, enableNewReconciler = !1, allNativeEvents = new Set(), registrationNameDependencies1 = {}, possibleRegistrationNames1 = {};
+    var allNativeEvents = new Set(), registrationNameDependencies1 = {}, possibleRegistrationNames1 = {};
     function registerTwoPhaseEvent(registrationName, dependencies) {
         registerDirectEvent(registrationName, dependencies), registerDirectEvent(registrationName + "Capture", dependencies);
     }
@@ -3245,8 +3245,8 @@
         }();
     }
     function listenToNonDelegatedEvent(domEventName, targetElement) {
-        var isCapturePhaseListener = !1, listenerSet = getEventListenerSet(targetElement), listenerSetKey = getListenerSetKey(domEventName, isCapturePhaseListener);
-        listenerSet.has(listenerSetKey) || (addTrappedEventListener(targetElement, domEventName, 2, isCapturePhaseListener), listenerSet.add(listenerSetKey));
+        var listenerSet = getEventListenerSet(targetElement), listenerSetKey = getListenerSetKey(domEventName, !1);
+        listenerSet.has(listenerSetKey) || (addTrappedEventListener(targetElement, domEventName, 2, !1), listenerSet.add(listenerSetKey));
     }
     var listeningMarker = "_reactListening" + Math.random().toString(36).slice(2);
     function listenToAllSupportedEvents(rootContainerElement) {
@@ -5821,7 +5821,7 @@
         useTransition: throwInvalidHookError,
         useMutableSource: throwInvalidHookError,
         useOpaqueIdentifier: throwInvalidHookError,
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     }, HooksDispatcherOnMountInDEV = null, HooksDispatcherOnMountWithHookTypesInDEV = null, HooksDispatcherOnUpdateInDEV = null, HooksDispatcherOnRerenderInDEV = null, InvalidNestedHooksDispatcherOnMountInDEV = null, InvalidNestedHooksDispatcherOnUpdateInDEV = null, InvalidNestedHooksDispatcherOnRerenderInDEV = null, warnInvalidContextAccess = function() {
         error1("Context can only be read while React is rendering. In classes, you can read it in the render method or getDerivedStateFromProps. In function components, you can read it directly in the function body, but not inside Hooks like useReducer() or useMemo().");
     }, warnInvalidHookAccess = function() {
@@ -5894,7 +5894,7 @@
         useOpaqueIdentifier: function() {
             return currentHookNameInDev = "useOpaqueIdentifier", mountHookTypesDev(), mountOpaqueIdentifier();
         },
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     }, HooksDispatcherOnMountWithHookTypesInDEV = {
         readContext: function(context, observedBits) {
             return readContext(context, observedBits);
@@ -5962,7 +5962,7 @@
         useOpaqueIdentifier: function() {
             return currentHookNameInDev = "useOpaqueIdentifier", updateHookTypesDev(), mountOpaqueIdentifier();
         },
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     }, HooksDispatcherOnUpdateInDEV = {
         readContext: function(context, observedBits) {
             return readContext(context, observedBits);
@@ -6030,7 +6030,7 @@
         useOpaqueIdentifier: function() {
             return currentHookNameInDev = "useOpaqueIdentifier", updateHookTypesDev(), updateOpaqueIdentifier();
         },
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     }, HooksDispatcherOnRerenderInDEV = {
         readContext: function(context, observedBits) {
             return readContext(context, observedBits);
@@ -6098,7 +6098,7 @@
         useOpaqueIdentifier: function() {
             return currentHookNameInDev = "useOpaqueIdentifier", updateHookTypesDev(), rerenderOpaqueIdentifier();
         },
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     }, InvalidNestedHooksDispatcherOnMountInDEV = {
         readContext: function(context, observedBits) {
             return warnInvalidContextAccess(), readContext(context, observedBits);
@@ -6166,7 +6166,7 @@
         useOpaqueIdentifier: function() {
             return currentHookNameInDev = "useOpaqueIdentifier", warnInvalidHookAccess(), mountHookTypesDev(), mountOpaqueIdentifier();
         },
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     }, InvalidNestedHooksDispatcherOnUpdateInDEV = {
         readContext: function(context, observedBits) {
             return warnInvalidContextAccess(), readContext(context, observedBits);
@@ -6234,7 +6234,7 @@
         useOpaqueIdentifier: function() {
             return currentHookNameInDev = "useOpaqueIdentifier", warnInvalidHookAccess(), updateHookTypesDev(), updateOpaqueIdentifier();
         },
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     }, InvalidNestedHooksDispatcherOnRerenderInDEV = {
         readContext: function(context, observedBits) {
             return warnInvalidContextAccess(), readContext(context, observedBits);
@@ -6302,7 +6302,7 @@
         useOpaqueIdentifier: function() {
             return currentHookNameInDev = "useOpaqueIdentifier", warnInvalidHookAccess(), updateHookTypesDev(), rerenderOpaqueIdentifier();
         },
-        unstable_isNewReconciler: enableNewReconciler
+        unstable_isNewReconciler: !1
     };
     var now$1 = unstable_now, commitTime = 0, profilerStartTime = -1;
     function recordCommitTime() {
@@ -7840,7 +7840,7 @@
     }
     function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
         var tag = node.tag, isHost = 5 === tag || 6 === tag;
-        if (isHost || enableFundamentalAPI) {
+        if (isHost || 0) {
             var container, child, beforeChild, container1, child1, parentNode, stateNode = isHost ? node.stateNode : node.stateNode.instance;
             before ? (container = parent, child = stateNode, beforeChild = before, 8 === container.nodeType ? container.parentNode.insertBefore(child, beforeChild) : container.insertBefore(child, beforeChild)) : (container1 = parent, child1 = stateNode, 8 === container1.nodeType ? (parentNode = container1.parentNode).insertBefore(child1, container1) : (parentNode = container1).appendChild(child1), null == container1._reactRootContainer && null === parentNode.onclick && trapClickOnNonInteractiveElement(parentNode));
         } else if (4 === tag) ;
@@ -7854,7 +7854,7 @@
     }
     function insertOrAppendPlacementNode(node, before, parent) {
         var tag = node.tag, isHost = 5 === tag || 6 === tag;
-        if (isHost || enableFundamentalAPI) {
+        if (isHost || 0) {
             var stateNode = isHost ? node.stateNode : node.stateNode.instance;
             before ? (function(parentInstance, child, beforeChild) {
                 parentInstance.insertBefore(child, beforeChild);
@@ -8271,7 +8271,7 @@
                     workInProgressRootExitStatus = 1, workInProgressRootFatalError = thrownValue, workInProgress1 = null;
                     return;
                 }
-                enableProfilerTimer && 8 & erroredWork.mode && stopProfilerTimerIfRunningAndRecordDelta(erroredWork, !0), throwException(root, erroredWork.return, erroredWork, thrownValue, workInProgressRootRenderLanes), completeUnitOfWork(erroredWork);
+                8 & erroredWork.mode && stopProfilerTimerIfRunningAndRecordDelta(erroredWork, !0), throwException(root, erroredWork.return, erroredWork, thrownValue, workInProgressRootRenderLanes), completeUnitOfWork(erroredWork);
             } catch (yetAnotherThrownValue) {
                 thrownValue = yetAnotherThrownValue, workInProgress1 === erroredWork && null !== erroredWork ? workInProgress1 = erroredWork = erroredWork.return : erroredWork = workInProgress1;
                 continue;
@@ -8524,7 +8524,7 @@
         if (rootDidHavePassiveEffects || finishPendingInteractions(root6, lanes5), remainingLanes1 === SyncLane ? root6 === rootWithNestedUpdates ? nestedUpdateCount++ : (nestedUpdateCount = 0, rootWithNestedUpdates = root6) : nestedUpdateCount = 0, !function(root, priorityLevel) {
             if (injectedHook && "function" == typeof injectedHook.onCommitFiberRoot) try {
                 var didError = (root.current.flags & DidCapture) === DidCapture;
-                enableProfilerTimer ? injectedHook.onCommitFiberRoot(rendererID, root, priorityLevel, didError) : injectedHook.onCommitFiberRoot(rendererID, root, void 0, didError);
+                injectedHook.onCommitFiberRoot(rendererID, root, priorityLevel, didError);
             } catch (err) {
                 hasLoggedError || (hasLoggedError = !0, error1("React instrumentation encountered an error: %s", err));
             }
@@ -9268,7 +9268,7 @@
             var currentParent = findCurrentFiberUsingSlowPath(parent);
             if (!currentParent) return null;
             for(var node = currentParent;;){
-                if (5 === node.tag || 6 === node.tag || enableFundamentalAPI) return node;
+                if (5 === node.tag || 6 === node.tag) return node;
                 if (node.child && 4 !== node.tag) {
                     node.child.return = node, node = node.child;
                     continue;