Repository: swc-project/swc (rust)

Issue:
Invalid code produced via Minify/Compress in 1.4.1+
### Describe the bug

Regression from 1.4.0, code yields a reference error. "dead_code" or "hoist_props" minifier options set to false mitigate the issue.

### Input code

```typescript
d(() => {
  var obj = {
    key: "some string",
  };
  
  var b = () => {
    switch (a) {
      default:
        break;
    }
    return obj.key;
  };
  return () => b;
});
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": {
        "arguments": false,
        "arrows": true,
        "booleans": true,
        "booleans_as_integers": false,
        "collapse_vars": true,
        "comparisons": true,
        "computed_props": true,
        "conditionals": true,
        "dead_code": true,
        "directives": true,
        "drop_console": false,
        "drop_debugger": true,
        "evaluate": true,
        "expression": false,
        "hoist_funs": false,
        "hoist_props": true,
        "hoist_vars": false,
        "if_return": true,
        "join_vars": true,
        "keep_classnames": false,
        "keep_fargs": true,
        "keep_fnames": false,
        "keep_infinity": false,
        "loops": true,
        "negate_iife": true,
        "properties": true,
        "reduce_funcs": false,
        "reduce_vars": false,
        "side_effects": true,
        "switches": true,
        "typeofs": true,
        "unsafe": false,
        "unsafe_arrows": false,
        "unsafe_comps": false,
        "unsafe_Function": false,
        "unsafe_math": false,
        "unsafe_symbols": false,
        "unsafe_methods": false,
        "unsafe_proto": false,
        "unsafe_regexp": false,
        "unsafe_undefined": false,
        "unused": true,
        "const_to_let": true,
        "pristine_globals": true
      },
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": true,
  "isModule": true
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.4.6&code=H4sIAAAAAAAAA0WNUQqDMBBE%2Fz3F4FcCxQO42LskdbXWVmETFZHc3Zim9GdgHrP7WqU0mjuOAliNYLYvNKkBI%2B81Sjd%2FGM7LMPXlLfJAMfLaxu3%2FHnDb4B9PKKMzAFruzPL2da6AFTYjpRpSCvtFpstcRSP9FBl%2F31sqgqYT2Fivw60AAAA%3D&config=H4sIAAAAAAAAA32US3LbMAyG9zmFR%2Bts20UPkF3PwKFJUKbDh4YAHWsyvnshSrLdGNJOwocfIAEQ32%2BHQ3dG0%2F05fPMn%2Fwy6IJT7P1twTKSvbOnARI2m%2BIG695WecUJOB4Rmus2kI116oKbCX4t7F3JGWN0XW%2FTJu%2FE5oclxKID4ZGMrx6sREuH%2F%2BoWV%2FDUBKvXZfsw5gE47RGlUPhH0UKTAJoegBwR10UWIMp1UF49ZSjHBSmDVUPIg8mQ9%2BZw45yu1oK0y2YKAfAFD%2FgKSjHOxLCFfT7hPwxaOte9bk3%2Bo4aJD1STkhGtrCZ9WiHrKHkm5mqQSznCjBjNcivtT6Z0qQLWkV905%2B7TRk08ArkDQiElHkOI2D8fztKV2u0qfHI8sjQLn%2BZZumaDnoirvnVDZqTJQyEvdLGCrgamyRjrOgjfKh96CAud4VoTQ%2BOXJnKSkNA6QnQC4v9pJUzUDdX%2BFG3x6EDv4g29J8oAtHlHTaZviGI857CSIQKdsdxy4FZS3ceEtcR22eU0WeDTAii4VG3hdAvwAKKvQluXLbPDz4IiqD%2Fn4WBOLw%2B2%2BhKNO%2FeO9z3v4bXHoYra1wWXDT%2F2d9%2FLv7uG0buH1BJ3Hv6uw5bz9A1EN%2Bh8sBgAA

### SWC Info output

_No response_

### Expected behavior

`d(function(){var b=function(){return a,"some string"};return function(){return b}});`

### Actual behavior

`d(function(){var b=function(){return a,obj.key};return function(){return b}});`

### Version

1.4.6

### Additional context

Modifying pass count reveals the intermediate error, with the key failure being in pass 2:
Pass 1: `d(function(){var obj={key:"some string"},b=function(){return a,obj.key};return function(){return b}});`
Pass 2: `d(function(){var obj_key="some string",b=function(){return a,obj.key};return function(){return b}});`
Pass 3: `d(function(){var b=function(){return a,obj.key};return function(){return b}});`

Pull request:
fix(es/minifier): Make `Finalizer` handle `hoisted_props` correctly
**Description:**


**Related issue:**

 - Closes #8737

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
index f9e1a71469..770d14ecf4 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
@@ -269,6 +269,7 @@ impl Vars {
             || !self.lits.is_empty()
             || !self.lits_for_cmp.is_empty()
             || !self.lits_for_array_access.is_empty()
+            || !self.hoisted_props.is_empty()
             || !self.removed.is_empty()
         {
             let mut v = Finalizer {
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/crates/swc_ecma_minifier/src/compress/optimize/util.rs
index ab671c14a5..7ae6087aea 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/util.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/util.rs
@@ -405,24 +405,35 @@ impl VisitMut for Finalizer<'_> {
     }
 
     fn visit_mut_expr(&mut self, n: &mut Expr) {
-        if let Expr::Ident(i) = n {
-            if let Some(expr) = self.lits.get(&i.to_id()) {
-                *n = *expr.clone();
+        match n {
+            Expr::Ident(i) => {
+                if let Some(expr) = self.lits.get(&i.to_id()) {
+                    *n = *expr.clone();
+                    return;
+                }
             }
-        } else {
-            n.visit_mut_children_with(self);
-        }
-
-        if let Expr::Member(e) = n {
-            if let Expr::Ident(obj) = &*e.obj {
-                if let MemberProp::Ident(prop) = &e.prop {
-                    if let Some(ident) = self.hoisted_props.get(&(obj.to_id(), prop.sym.clone())) {
+            Expr::Member(e) => {
+                if let Expr::Ident(obj) = &*e.obj {
+                    let sym = match &e.prop {
+                        MemberProp::Ident(i) => &i.sym,
+                        MemberProp::Computed(e) => match &*e.expr {
+                            Expr::Lit(Lit::Str(s)) => &s.value,
+                            _ => return,
+                        },
+                        _ => return,
+                    };
+
+                    if let Some(ident) = self.hoisted_props.get(&(obj.to_id(), sym.clone())) {
                         self.changed = true;
                         *n = ident.clone().into();
+                        return;
                     }
                 }
             }
+            _ => {}
         }
+
+        n.visit_mut_children_with(self);
     }
 
     fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {


Test patch:
diff --git a/crates/swc/tests/fixture/issues-8xxx/8737/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8737/input/.swcrc
new file mode 100644
index 0000000000..cd530500a1
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8737/input/.swcrc
@@ -0,0 +1,63 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": false
+        },
+        "target": "es5",
+        "loose": false,
+        "minify": {
+            "compress": {
+                "arguments": false,
+                "arrows": true,
+                "booleans": true,
+                "booleans_as_integers": false,
+                "collapse_vars": true,
+                "comparisons": true,
+                "computed_props": true,
+                "conditionals": true,
+                "dead_code": true,
+                "directives": true,
+                "drop_console": false,
+                "drop_debugger": true,
+                "evaluate": true,
+                "expression": false,
+                "hoist_funs": false,
+                "hoist_props": true,
+                "hoist_vars": false,
+                "if_return": true,
+                "join_vars": true,
+                "keep_classnames": false,
+                "keep_fargs": true,
+                "keep_fnames": false,
+                "keep_infinity": false,
+                "loops": true,
+                "negate_iife": true,
+                "properties": true,
+                "reduce_funcs": false,
+                "reduce_vars": false,
+                "side_effects": true,
+                "switches": true,
+                "typeofs": true,
+                "unsafe": false,
+                "unsafe_arrows": false,
+                "unsafe_comps": false,
+                "unsafe_Function": false,
+                "unsafe_math": false,
+                "unsafe_symbols": false,
+                "unsafe_methods": false,
+                "unsafe_proto": false,
+                "unsafe_regexp": false,
+                "unsafe_undefined": false,
+                "unused": true,
+                "const_to_let": true,
+                "pristine_globals": true
+            },
+            "mangle": false
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8737/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8737/input/1.js
new file mode 100644
index 0000000000..c778420503
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8737/input/1.js
@@ -0,0 +1,14 @@
+d(() => {
+    var obj = {
+        key: "some string",
+    };
+
+    var b = () => {
+        switch (a) {
+            default:
+                break;
+        }
+        return obj.key;
+    };
+    return () => b;
+});
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8737/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8737/output/1.js
new file mode 100644
index 0000000000..bb0fd3bbea
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8737/output/1.js
@@ -0,0 +1,8 @@
+d(function() {
+    var b = function() {
+        return a, "some string";
+    };
+    return function() {
+        return b;
+    };
+});
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8737/2/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8737/2/input.js
new file mode 100644
index 0000000000..bbca9b5721
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8737/2/input.js
@@ -0,0 +1,10 @@
+d(function () {
+    var obj = {
+        key: "some string"
+    }, b = function () {
+        return a, obj.key;
+    };
+    return function () {
+        return b;
+    };
+});
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8737/2/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8737/2/output.js
new file mode 100644
index 0000000000..bb0fd3bbea
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8737/2/output.js
@@ -0,0 +1,8 @@
+d(function() {
+    var b = function() {
+        return a, "some string";
+    };
+    return function() {
+        return b;
+    };
+});
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8737/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/8737/config.json
new file mode 100644
index 0000000000..7e144b291f
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8737/config.json
@@ -0,0 +1,47 @@
+{
+    "defaults": true,
+    "arguments": false,
+    "arrows": true,
+    "booleans": true,
+    "booleans_as_integers": false,
+    "collapse_vars": true,
+    "comparisons": true,
+    "computed_props": true,
+    "conditionals": true,
+    "dead_code": true,
+    "directives": true,
+    "drop_console": false,
+    "drop_debugger": true,
+    "evaluate": true,
+    "expression": false,
+    "hoist_funs": false,
+    "hoist_props": true,
+    "hoist_vars": false,
+    "if_return": true,
+    "join_vars": true,
+    "keep_classnames": false,
+    "keep_fargs": true,
+    "keep_fnames": false,
+    "keep_infinity": false,
+    "loops": true,
+    "negate_iife": true,
+    "properties": true,
+    "reduce_funcs": false,
+    "reduce_vars": false,
+    "side_effects": true,
+    "switches": true,
+    "typeofs": true,
+    "unsafe": false,
+    "unsafe_arrows": false,
+    "unsafe_comps": false,
+    "unsafe_Function": false,
+    "unsafe_math": false,
+    "unsafe_symbols": false,
+    "unsafe_methods": false,
+    "unsafe_proto": false,
+    "unsafe_regexp": false,
+    "unsafe_undefined": false,
+    "unused": true,
+    "const_to_let": true,
+    "pristine_globals": true
+}
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8737/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8737/input.js
new file mode 100644
index 0000000000..c778420503
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8737/input.js
@@ -0,0 +1,14 @@
+d(() => {
+    var obj = {
+        key: "some string",
+    };
+
+    var b = () => {
+        switch (a) {
+            default:
+                break;
+        }
+        return obj.key;
+    };
+    return () => b;
+});
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8737/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8737/output.js
new file mode 100644
index 0000000000..2d9886ffdc
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8737/output.js
@@ -0,0 +1,6 @@
+d(()=>{
+    var obj = {
+        key: "some string"
+    }, b = ()=>(a, obj.key);
+    return ()=>b;
+});