Repository: swc-project/swc (rust)

Issue:
mangle keep_classnames fails when class is not exported in es6 module
### Describe the bug

When using module type `es6`, the keepClassNames only seem to take effect if the class is exported.

### Input code

```typescript
@decorator
export class ImportantName {} 

console.log(ImportantName.name)
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true
    },
    "target": "es2019",
    "minify": {
      "compress": {
        "keep_classnames": true
      },
      "mangle": {
        "keep_classnames": true
      }
    },
    "keepClassNames": true
  },
  "module": {
    "type": "es6"
  },
  "minify": true,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.2.177&code=H4sIAAAAAAAAA1WOvQ7CMBCD9zyFR1iaV2BlYeEJrtdTiUhy1V34qSrenVZ0YbFs2dLnGGFS9CmQ96TWQHWAi4AzuaNSESTfNwP6GYXqmCXECH%2BlxrdUR7g%2BjAVtnlZRXNnS1NDb2jnS6ojv4TQIq1FTCzvrxziXLVBtlw22fBACa3XN0mUdD391tx06hi%2Blgw9ztgAAAA%3D%3D&config=H4sIAAAAAAAAA3WOywrCQAxF%2FyXrWagLwW5d6y9ImMYy2nmQpOBQ%2Bu9OqlBcuLsk5yR3hod46GYoyEJsSWpSfEEHWguJ51AUHPTkM6NmFuiUJ1ocKPJA2kCSw25%2FalAMKdyrHfE5FiYRy0%2BicvMjiiSMtPkR0zDSf6Ihtjjb%2FPpr5n76mNZxbXCEZftvmIMgly%2B3am%2FmE1rn6wAAAA%3D%3D

### Expected behavior

The class name should not be mangled when using es6 and the class is not exported.  It should respect the keepClassName settings

### Actual behavior

Class name is mangled

### Version

1.2.177

### Additional context

We are attempting to use SWC to minify code containing Catalyst Components, which use a `@controller` decorator to register the class as a web component.  The decorator is responsible for all of the work, so the class is not actually reference anywhere and is not typically exported from the file.  Catalyst uses the class name to determine what the custom element name should be, which is why we need to persist it during minification.

Pull request:
Preserve class expression identifiers if keepClassNames is set to true
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

Hello! This is our attempt to fix #4529. I think this is the correct fix but having to update `crates/swc_ecma_minifier/tests/terser/compress/harmony/class_name_can_be_preserved/output.js` made me think that maybe we need to guard the code better?

My thought is that `keepClassNames` should always keep any class name but maybe there are nuances that I'm missing.

I'm happy to be mentored to get this PR mergable if it's not already. I like Rust but I haven't had many chances to actually work on projects using rust.

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

I don't think this is a breaking change unless we consider the changes described in the diff of `crates/swc_ecma_minifier/tests/terser/compress/harmony/class_name_can_be_preserved/output.js` to be breaking.

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**

Fixes #4529 


Patch:
diff --git a/crates/swc_ecma_minifier/src/pass/mangle_names/preserver.rs b/crates/swc_ecma_minifier/src/pass/mangle_names/preserver.rs
index c5784658ca..9c9f69bf58 100644
--- a/crates/swc_ecma_minifier/src/pass/mangle_names/preserver.rs
+++ b/crates/swc_ecma_minifier/src/pass/mangle_names/preserver.rs
@@ -57,6 +57,16 @@ impl Visit for Preserver {
         }
     }
 
+    fn visit_class_expr(&mut self, n: &ClassExpr) {
+        n.visit_children_with(self);
+
+        if self.options.keep_class_names {
+            if let Some(i) = &n.ident {
+                self.preserved.insert(i.to_id());
+            }
+        }
+    }
+
     fn visit_export_decl(&mut self, n: &ExportDecl) {
         n.visit_children_with(self);
 


Test patch:
diff --git a/crates/swc/tests/fixture/issues-4xxx/4529/1/input/.swcrc b/crates/swc/tests/fixture/issues-4xxx/4529/1/input/.swcrc
new file mode 100644
index 0000000000..a1db2c97f0
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-4xxx/4529/1/input/.swcrc
@@ -0,0 +1,26 @@
+{
+  "jsc": {
+    "parser": {
+      "syntax": "typescript",
+      "tsx": false,
+      "decorators": true
+    },
+    "target": "es2019",
+    "loose": false,
+    "minify": {
+      "compress": false,
+      "mangle": {
+        "toplevel": false,
+        "keep_classnames": true,
+        "keep_fnames": false,
+        "keep_private_props": false,
+        "ie8": false,
+        "safari10": false
+      }
+    }
+  },
+  "module": {
+    "type": "es6"
+  },
+  "minify": false
+}
diff --git a/crates/swc/tests/fixture/issues-4xxx/4529/1/input/index.ts b/crates/swc/tests/fixture/issues-4xxx/4529/1/input/index.ts
new file mode 100644
index 0000000000..9d9394fdc3
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-4xxx/4529/1/input/index.ts
@@ -0,0 +1,1 @@
+decorator(class ImportantName {});
diff --git a/crates/swc/tests/fixture/issues-4xxx/4529/1/output/index.ts b/crates/swc/tests/fixture/issues-4xxx/4529/1/output/index.ts
new file mode 100644
index 0000000000..be74ec8b47
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-4xxx/4529/1/output/index.ts
@@ -0,0 +1,2 @@
+decorator(class ImportantName {
+});
diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/class_name_can_be_preserved/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/class_name_can_be_preserved/output.js
index c4d419a457..0c6a49cfe5 100644
--- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/class_name_can_be_preserved/output.js
+++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/class_name_can_be_preserved/output.js
@@ -1,5 +1,5 @@
 function x() {
-    (class a {
+    (class Baz {
     });
     class Foo {
     }