Repository: swc-project/swc (rust)

Issue:
Minifier removes property in object incorrectly when circular reference exists
### Describe the bug

Currently minifier cannot track unused properties correctly when circular reference exists.
before v1.4.1, minifier converts this correctly.

### Input code

```typescript
const foo = {
    x: 1,
    y: () => foo
};
console.log(foo.y().x); // should log: 1
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es2022",
    "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": false,
        "const_to_let": true,
        "pristine_globals": true,
        "passes": 3
      },
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.4.1&code=H4sIAAAAAAAAA0vOzysuUUjLz1ewVajmUgCCCisFQx0wq9JKQUNTwdYOJM1Va82VDFSbn5Oql5OfrgEU0qvU0NSr0LRW0NdXKM7IL81JUQDKAHUDAMHeFN5UAAAA&config=H4sIAAAAAAAAA32US47bMAyG9zlF4HUXRQp00QPMrmcQFIlylNHDEKlMjEHuXsp2MumE9s7mx5%2BUSIqfu%2F2%2BO6Pp%2Fuw%2F%2BZN%2FBl0QyuOfLTgm0le2dGCiRlP8QN2POz1jQ04HhMl0m0lHuvRAkwoPPw%2BHRdGFnBHuisUWffJufM5pchwKID7Z2Moha4RE%2BL9%2BYSV%2FNEClPtuPOQfQaYMojcongh6KFNjkEPSAoC66CFHaSXXxmKUUDVYCq4aSB5En68nnxDlfqQVtlckWBOQLGPIXkGSci2UJ%2BXrCfSZs4Vj7furzNzVcdKiahJxwnVrCpxWinrJHUq4mqYQzXKnBDJfifld6pwpQLelVd84%2BrfTkHYArEDRi0hGkuJOH43laU7tNpU%2BOR5ZGgfN8S7dM0HNRlfdOqGyrDBTyUjcL2GqgVdZIx1nwSvnQW1DgHM%2BKEBo%2FPJmTlJTGAbITAPdXO2mqZqAer3CFtwexgd%2F4liQP2OIRNZ3WKY7xmMNGggh0ynbDgVtBeR0X3hLXYZ3XZIFHA6zoUlEE7a2SoqzCtDBfhoPfB4dUfchHcU8MPOhTF38ttttjO0ed%2Bq8tMC%2Fo3eLQxWzrBJfV37o%2BL%2Bzf3ZfTfTc%2FTt15%2FHtXtpPsbv8AJMu9REYGAAA%3D

### SWC Info output

_No response_

### Expected behavior

```javascript
const foo = {
    x: 1,
    y: () => foo
};
console.log(foo.y().x);
```

### Actual behavior

```javascript
const foo = {
    y: ()=>foo
};
console.log(foo.y().x);
```

### Version

1.4.1

### Additional context

_No response_

Pull request description:
fix(es/minifier): Handle cyclic references while dropping unused properties
**Description:**


**Related issue:**

 - Closes #8714

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
index 431316f6b2..14b810be9d 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
@@ -793,7 +793,7 @@ impl Optimizer<'_> {
 
         let usage = self.data.vars.get(&name.to_id())?;
 
-        if usage.indexed_with_dynamic_key || usage.used_as_ref {
+        if usage.indexed_with_dynamic_key || usage.used_as_ref || usage.used_recursively {
             return None;
         }
 


Test patch:
diff --git a/crates/swc/tests/fixture/issues-8xxx/8714/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8714/input/.swcrc
new file mode 100644
index 0000000000..b4f4091fcd
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8714/input/.swcrc
@@ -0,0 +1,65 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": false
+        },
+        "target": "es2022",
+        "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": false,
+                "const_to_let": true,
+                "pristine_globals": true,
+                "passes": 3
+            },
+            "mangle": false
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": false,
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8714/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8714/input/1.js
new file mode 100644
index 0000000000..88dbc6ebb6
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8714/input/1.js
@@ -0,0 +1,5 @@
+const foo = {
+    x: 1,
+    y: () => foo
+};
+console.log(foo.y().x); // should log: 1
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8714/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8714/output/1.js
new file mode 100644
index 0000000000..613ae0f867
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8714/output/1.js
@@ -0,0 +1,5 @@
+const foo = {
+    x: 1,
+    y: ()=>foo
+};
+console.log(foo.y().x);
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8714/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/8714/config.json
new file mode 100644
index 0000000000..ee65e9fdc6
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8714/config.json
@@ -0,0 +1,3 @@
+{
+    "defaults": true
+}
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8714/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8714/input.js
new file mode 100644
index 0000000000..c9d684029e
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8714/input.js
@@ -0,0 +1,5 @@
+const foo = {
+    x: 1,
+    y: () => foo
+};
+console.log(foo.y().x); 
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8714/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8714/output.js
new file mode 100644
index 0000000000..613ae0f867
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8714/output.js
@@ -0,0 +1,5 @@
+const foo = {
+    x: 1,
+    y: ()=>foo
+};
+console.log(foo.y().x);