Repository: swc-project/swc (rust)

Issue:
Decorated class .name is broken in class properties
Follow-up to #1279 
**Describe the bug**
When decorating class, property initalizers get wrong class name.
**Input code**
```ts
@Injectable()
export class TestClass {
	private readonly property = TestClass.name;
	private unitializedProperty;

	constructor() {
		const constructorVariable = TestClass.name;
		this.unitializedProperty = TestClass.name;
	}
}
```
**Output**
```js
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.TestClass = void 0;
var _class1;
var _dec = typeof Reflect !== "undefined" && typeof Reflect.metadata === "function" && Reflect.metadata("design:paramtypes", []), _dec1 = typeof Reflect !== "undefined" && typeof Reflect.metadata === "function" && Reflect.metadata("design:type", Function), _dec2 = Injectable();
let TestClass2 = _class1 = _dec2(_class1 = _dec1(_class1 = _dec((_class1 = class TestClass {
    constructor(){
        this.property = _class.name;
        const constructorVariable = TestClass.name;
        this.unitializedProperty = TestClass.name;
    }
}) || _class1) || _class1) || _class1) || _class1;
exports.TestClass = TestClass2;

```
**Config**

```json
{
	"jsc": {
		"parser": {
			"syntax": "typescript",
			"decorators": true
		},
		"transform": {
			"decoratorMetadata": true,
			"legacyDecorator": false
		},
		"target": "es2020",
		"keepClassNames": true,
	},
	"module": {
		"type": "commonjs",
		"strict": false,
		"strictMode": true,
		"lazy": false,
		"noInterop": false
	}
}
```

**Expected behavior**
I expect `TestClass.name` to stay as `TestClass.name`. Instead, in case of initialized class property, I get `_class.name`, which throws error at runtime. Initializing property in constructor works as expected. Note that this is happening only if the class is decorated.

**Version**
The version of @swc/core: 1.2.85

Pull request:
Fix: Issue 2224
# Description

In fixing issue https://github.com/swc-project/swc/issues/1869 in PR https://github.com/swc-project/swc/pull/1950, class-name identifiers of decorated
classes were renamed to the `_class` hard-coded identifier.

https://github.com/swc-project/swc/pull/1950/files#diff-aee7604a9d35c517e7e1637b34d66b7ce8de17cc407ba6236d314471d00d4b5eR265-R267

For decorated classes, the generated code is a class expression

This makes sense for static attributes of decorated classes, since the
generated code is a class expression, such that

```js
@dec
class TestClass {}
```

would produce, roughly:

```js
var _class;
let TestClass = _class = dec(_class = class TestClass {})
```

In function expressions, the right-hand class is only available within
the class block.

Adding static properties would show the issue:

```js
var _class;
var _class1;
let TestClass = _class1 = dec(
    _class1 = (
        _class = class TestClass {},
        _class.foo = _class.name,
        _class
    )
)
```

The `TestClass` from the class expression doesn't live long enough to be used
to assign `_class.foo`, and the `TestClass` from the let assignment
doesn't exist yet. Therefore, for static properties, it's imperative to
use the `_class` identifier.

We've seen why static properties identifiers must be renamed, but for
non-static properties, it's the opposite.

Non-static properties are assigned as part of the constructor function.

```js
@dec
class TestClass {
    readonly property = TestClass.name;
}
```

becomes roughly:

```js
var _class1;
let TestClass = _class1 = dec(_class1 =
    class TestClass {
        constructor(){
            this.property = TestClass.name;
        }
    }
);
```

We can see that within the constructor, the `TestClass` from the class
expression is available. Moreover, we can see that no `_class`
identifier has been generated.

Prior to this PR, the assignment would have been performed by the
invalid code:

```js
this.property = _class.name;
```

**Related issue (if exists):**

 - Closes #2224 


Patch:
diff --git a/crates/swc_ecma_transforms_typescript/src/strip.rs b/crates/swc_ecma_transforms_typescript/src/strip.rs
index 4219d4eec0..5c6e27530c 100644
--- a/crates/swc_ecma_transforms_typescript/src/strip.rs
+++ b/crates/swc_ecma_transforms_typescript/src/strip.rs
@@ -411,8 +411,10 @@ where
         } else {
             for m in class.body.iter_mut() {
                 if let ClassMember::ClassProp(m) = m {
-                    if let Some(orig_ident) = &orig_ident {
-                        replace_ident(&mut m.value, orig_ident.to_id(), &ident)
+                    if m.is_static {
+                        if let Some(orig_ident) = &orig_ident {
+                            replace_ident(&mut m.value, orig_ident.to_id(), &ident)
+                        }
                     }
                 }
             }


Test patch:
diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs
index 18c67d06e2..cf7bb1a16b 100644
--- a/crates/swc/tests/projects.rs
+++ b/crates/swc/tests/projects.rs
@@ -900,6 +900,37 @@ fn opt_source_file_name_1() {
     assert!(map.contains("entry-foo"));
 }
 
+#[test]
+fn issue_2224() {
+    let output = str_with_opt(
+        r#"
+        const Injectable = () => {};
+
+        @Injectable
+        export class TestClass {
+            private readonly property = TestClass.name;
+        }"#,
+        Options {
+            is_module: IsModule::Bool(true),
+            config: Config {
+                jsc: JscConfig {
+                    syntax: Some(Syntax::Typescript(TsConfig {
+                        decorators: true,
+                        ..Default::default()
+                    })),
+                    ..Default::default()
+                },
+                ..Default::default()
+            },
+            ..Default::default()
+        },
+    )
+    .unwrap();
+    println!("{}", output);
+
+    assert!(output.contains("this.property = TestClass.name"));
+}
+
 #[test]
 fn bom() {
     project("tests/projects/bom")