Repository: swc-project/swc (rust)

Issue:
swc minifier strips the used property from the object.
### Describe the bug

https://github.com/vercel/next.js/issues/53723

### Input code

```typescript
export default function Home() {
  return (
    <div>{foo.a}</div>
  )
}

const foo = {
  get a() {
    return `a ${this.b}`;
  },
  get b() {
    return `b`;
  }
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": true
    },
    "target": "es2015",
    "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": {
        "toplevel": false,
        "keep_classnames": false,
        "keep_fnames": false,
        "keep_private_props": false,
        "ie8": false,
        "safari10": false
      }
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": true,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.3.74&code=H4sIAAAAAAAAA2WMMQ7CMBAEe79iC4pEQuEBCdQ8I3ZyBkvgQ84ZIVn3dxyIaOhub2eHXg9Ogpm8zTeBz3GSwBFnvlPTohggkeQU0dQTGObwPBXP3FkdDmuo79aoMRPHpQqYcfzMLiSwm%2BInGS12Ra5h6ZyOfW10v6HuD3VfwOgb3NC3f6QAAAA%3D&config=H4sIAAAAAAAAA42VzW7bMAyA732KwOce2gEbhj3AbnsGQbEoR5ksGiKVJijy7qUVO81q2tjFsPmJpPjr96fdrjlS2%2FzavcurfAw2E%2BT7t0joktieRdJA21tqcxi4eZ7pkUbEuUCVXG%2BgYZs74KpE315ev08KTUQkELG3kWCS9SEFf3l02WI%2FZCB6kIlUTJYeEtO%2F%2BhPL%2BEbTTR7ke8QINm0QY8mExNBB1gy3GKMdCMzJZsXKeFObA6HmYoSFwZkh46Dy5AIHTOJzSR1YZ1p0oKCQoeVwAk1NfIlaIglPiadiB%2FvSdbXMX7ThZGOxrPiEcy2J3FaxesBAbHxJWgpvcCUHNzgl96tm8CYDl5yWekcMaaUmfwEkA9ESJduDZree8NJPa9p%2BUzMkLy3LF4VLf2tRJugkqSYEr2R2zAxkDlo1M7jSwpjZVrvOhFfSR8GBAe%2BlVxTT9Ba4PWhO%2BTIAegVIfa3XuuoGzH0KV%2Fg4EBv4t0TJeoNNJ3rLh3VKl36PccNBD3xAt3FASsG4jrNsifOwzktyIK0BTj1SqILlEpABYDSx7stFb8h4iEXTRdx%2FronpwPW%2BhnubujrvD%2FuScYhwgrjWxv8xIptU7nYau3qe7MX0wk%2BtKa0MXnh9mdEcytP8rEE1PbryEFDtydvv5EfzeWj%2Bc8xZawL9mRVrnq4fPqQcz%2BIGAAA%3D

### Expected behavior

`.b` should not be stripped.

### Actual behavior

```js
export default function e(){return React.createElement("div",null,foo.a)}let foo={get a(){return`a ${this.b}`}};
```

### Version

1.3.74

### Additional context

_No response_

Pull request description:
fix(es/minifier): Do not drop properties used via `this`
**Description:**


**Related issue:**

 - Closes #7783.

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
index 1359603f91..75c5ca591b 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
@@ -1,8 +1,10 @@
+use rustc_hash::FxHashSet;
 use swc_atoms::{js_word, JsWord};
 use swc_common::{util::take::Take, DUMMY_SP};
 use swc_ecma_ast::*;
 use swc_ecma_usage_analyzer::util::is_global_var_with_pure_property_access;
 use swc_ecma_utils::{contains_ident_ref, ExprExt};
+use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
 
 use super::Optimizer;
 #[cfg(feature = "debug")]
@@ -785,11 +787,17 @@ impl Optimizer<'_> {
             return None;
         }
 
+        let properties_used_via_this = {
+            let mut v = ThisPropertyVisitor::default();
+            obj.visit_with(&mut v);
+            v.properties
+        };
+
         let should_preserve_property = |sym: &JsWord| {
             if let "toString" = &**sym {
                 return true;
             }
-            !usage.accessed_props.contains_key(sym)
+            !usage.accessed_props.contains_key(sym) && !properties_used_via_this.contains(sym)
         };
         let should_preserve = |key: &PropName| match key {
             PropName::Ident(k) => should_preserve_property(&k.sym),
@@ -824,3 +832,34 @@ impl Optimizer<'_> {
         None
     }
 }
+
+#[derive(Default)]
+struct ThisPropertyVisitor {
+    properties: FxHashSet<JsWord>,
+
+    should_abort: bool,
+}
+
+impl Visit for ThisPropertyVisitor {
+    noop_visit_type!();
+
+    fn visit_member_expr(&mut self, e: &MemberExpr) {
+        if self.should_abort {
+            return;
+        }
+
+        e.visit_children_with(self);
+
+        if let Expr::This(..) = &*e.obj {
+            match &e.prop {
+                MemberProp::Ident(p) => {
+                    self.properties.insert(p.sym.clone());
+                }
+                MemberProp::Computed(_) => {
+                    self.should_abort = true;
+                }
+                _ => {}
+            }
+        }
+    }
+}


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7783/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7783/input/.swcrc
new file mode 100644
index 0000000000..55c5f9dd71
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7783/input/.swcrc
@@ -0,0 +1,71 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": true
+        },
+        "target": "es2015",
+        "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": {
+                "toplevel": false,
+                "keep_classnames": false,
+                "keep_fnames": false,
+                "keep_private_props": false,
+                "ie8": false,
+                "safari10": false
+            }
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": true,
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7783/input/1.js b/crates/swc/tests/fixture/issues-7xxx/7783/input/1.js
new file mode 100644
index 0000000000..8fc302ca88
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7783/input/1.js
@@ -0,0 +1,14 @@
+export default function Home() {
+    return (
+        <div>{foo.a}</div>
+    )
+}
+
+const foo = {
+    get a() {
+        return `a ${this.b}`;
+    },
+    get b() {
+        return `b`;
+    }
+}
diff --git a/crates/swc/tests/fixture/issues-7xxx/7783/output/1.js b/crates/swc/tests/fixture/issues-7xxx/7783/output/1.js
new file mode 100644
index 0000000000..58d2bed8bc
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7783/output/1.js
@@ -0,0 +1,1 @@
+export default function e(){return React.createElement("div",null,foo.a)}let foo={get a(){return`a ${this.b}`},get b(){return"b"}};
diff --git a/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js b/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js
index 03cbbe791a..d6663a0135 100644
--- a/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js
+++ b/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js
@@ -28,6 +28,7 @@ function implicitThis(n) {
     return this.m + n + 12;
 }
 var impl = {
+    a: 12,
     explicitVoid2: function() {
         return _this.a;
     },
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/input.js
new file mode 100644
index 0000000000..551c23a119
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/input.js
@@ -0,0 +1,11 @@
+export default function Home() {
+    return React.createElement("div", null, foo.a);
+}
+const foo = {
+    get a() {
+        return `a ${this.b}`;
+    },
+    get b() {
+        return `b`;
+    }
+};
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/output.js
new file mode 100644
index 0000000000..4b9c79c6b5
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/output.js
@@ -0,0 +1,11 @@
+export default function Home() {
+    return React.createElement("div", null, foo.a);
+}
+const foo = {
+    get a () {
+        return `a ${this.b}`;
+    },
+    get b () {
+        return "b";
+    }
+};