Repository: swc-project/swc (rust)

Issue:
[Bug]: Re-assignment to class during static field initialization
### Describe the bug

Mutable assignment to class name should throw `TypeError` during an initialization of static fields.
However, the transpiled code terminates without any error.

### Input code

```typescript
class x { static y = x = 0 ; }
```


### Config

_No response_

### Playground link

https://play.swc.rs/?version=1.3.11&code=H4sIAAAAAAAAA0vOSSwuVqhQqFYoLkksyUxWqFSwBXJtFQwUrBVqFQB8P0%2FWHwAAAA%3D%3D&config=H4sIAAAAAAAAA0WMTQrEIAxG75K1286id5hDBCctFv9IUhgR714tlu7C915ehUMsrBUyshCPS0pU%2FMMKZAOKZZcVTNf6tKEXagYUeScdiiyd%2BZSEJjUQXHRbGSWbQmYSeRHG3T9m66GQfucYKmjJdAc%2F0N7G%2FHPynaLySe0Cj9ke9LUAAAA%3D

### Expected behavior

Mutable assignment to class name should throw `TypeError` during an initialization of static fields:
```console
$ node input.js
input.js:1
class x { static y = x = 0 ; }
                       ^

TypeError: Assignment to constant variable.
    at <static_initializer> (/Users/p51lee/dev/esmeta-plrg/input.js:1:24)
    at Object.<anonymous> (/Users/p51lee/dev/esmeta-plrg/input.js:1:1)
    at Module._compile (node:internal/modules/cjs/loader:1159:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
    at Module.load (node:internal/modules/cjs/loader:1037:32)
    at Module._load (node:internal/modules/cjs/loader:878:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47

Node.js v18.11.0
```

### Actual behavior

The output code terminates without any error.
```console
$ node output.js
// terminates without error
```

### Version

1.3.11 (default SWC playground)

### Additional context

_No response_

Pull request:
fix(es/lints): Disallow assign to class
<!-- 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:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
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):**
closes #6307 

Patch:
diff --git a/crates/swc_ecma_lints/src/rules/const_assign.rs b/crates/swc_ecma_lints/src/rules/const_assign.rs
index 22a36fea90..96a87b7720 100644
--- a/crates/swc_ecma_lints/src/rules/const_assign.rs
+++ b/crates/swc_ecma_lints/src/rules/const_assign.rs
@@ -11,7 +11,8 @@ pub fn const_assign() -> Box<dyn Rule> {
 #[derive(Debug, Default)]
 struct ConstAssign {
     const_vars: AHashMap<Id, Span>,
-    import_binding: AHashMap<Id, Span>,
+    import_bindings: AHashMap<Id, Span>,
+    classes: AHashMap<Id, Span>,
 
     is_pat_decl: bool,
 }
@@ -40,7 +41,7 @@ impl ConstAssign {
             });
         }
 
-        if let Some(&binding_span) = self.import_binding.get(&id.to_id()) {
+        if let Some(&binding_span) = self.import_bindings.get(&id.to_id()) {
             HANDLER.with(|handler| {
                 handler
                     .struct_span_err(id.span, "cannot reassign to an imported binding")
@@ -48,6 +49,15 @@ impl ConstAssign {
                     .emit();
             });
         }
+
+        if let Some(&binding_span) = self.classes.get(&id.to_id()) {
+            HANDLER.with(|handler| {
+                handler
+                    .struct_span_err(id.span, "cannot reassign to a class")
+                    .span_label(binding_span, "class name")
+                    .emit();
+            });
+        }
     }
 }
 
@@ -63,7 +73,8 @@ impl Visit for ConstAssign {
     fn visit_module(&mut self, program: &Module) {
         program.visit_children_with(&mut Collector {
             const_vars: &mut self.const_vars,
-            import_binding: &mut self.import_binding,
+            import_bindings: &mut self.import_bindings,
+            classes: &mut self.classes,
             var_decl_kind: None,
         });
 
@@ -101,7 +112,8 @@ impl Visit for ConstAssign {
             const_vars: &mut self.const_vars,
             // I don't believe that import stmt exists in Script
             // But it's ok. Let's pass it in.
-            import_binding: &mut self.import_binding,
+            import_bindings: &mut self.import_bindings,
+            classes: &mut self.classes,
             var_decl_kind: None,
         });
 
@@ -120,7 +132,8 @@ impl Visit for ConstAssign {
 
 struct Collector<'a> {
     const_vars: &'a mut AHashMap<Id, Span>,
-    import_binding: &'a mut AHashMap<Id, Span>,
+    import_bindings: &'a mut AHashMap<Id, Span>,
+    classes: &'a mut AHashMap<Id, Span>,
 
     var_decl_kind: Option<VarDeclKind>,
 }
@@ -133,7 +146,7 @@ impl Visit for Collector<'_> {
             ImportSpecifier::Named(ImportNamedSpecifier { local, .. })
             | ImportSpecifier::Default(ImportDefaultSpecifier { local, .. })
             | ImportSpecifier::Namespace(ImportStarAsSpecifier { local, .. }) => {
-                self.import_binding.insert(local.to_id(), local.span);
+                self.import_bindings.insert(local.to_id(), local.span);
             }
         }
     }
@@ -173,4 +186,17 @@ impl Visit for Collector<'_> {
 
         self.var_decl_kind = old_var_decl_kind;
     }
+
+    fn visit_class_decl(&mut self, class: &ClassDecl) {
+        *self.classes.entry(class.ident.to_id()).or_default() = class.ident.span;
+
+        class.visit_children_with(self);
+    }
+
+    fn visit_class_expr(&mut self, class: &ClassExpr) {
+        if let Some(ident) = &class.ident {
+            *self.classes.entry(ident.to_id()).or_default() = ident.span;
+        }
+        class.visit_children_with(self);
+    }
 }


Test patch:
diff --git a/crates/swc/tests/errors/lints/const-assign/3/input.js b/crates/swc/tests/errors/lints/const-assign/3/input.js
new file mode 100644
index 0000000000..869749c794
--- /dev/null
+++ b/crates/swc/tests/errors/lints/const-assign/3/input.js
@@ -0,0 +1,1 @@
+class X { static y = X = 0 }
\ No newline at end of file
diff --git a/crates/swc/tests/errors/lints/const-assign/3/output.swc-stderr b/crates/swc/tests/errors/lints/const-assign/3/output.swc-stderr
new file mode 100644
index 0000000000..a0c19b8562
--- /dev/null
+++ b/crates/swc/tests/errors/lints/const-assign/3/output.swc-stderr
@@ -0,0 +1,7 @@
+
+  x cannot reassign to a class
+   ,----
+ 1 | class X { static y = X = 0 }
+   :       |              ^
+   :       `-- class name
+   `----
diff --git a/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.1.normal.js b/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.1.normal.js
index fb7d4baba9..c2e64fe145 100644
--- a/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.1.normal.js
+++ b/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.1.normal.js
@@ -1,88 +1,13 @@
 //// [assignmentToParenthesizedIdentifiers.ts]
-import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
-var x;
-x = 3; // OK
-x = 3; // OK
-x = ""; // Error
-x = ""; // Error
-var M;
-(function(M) {
-    var y;
-    M.y = y;
-})(M || (M = {}));
-M.y = 3; // OK
-M.y = 3; // OK
-M.y = 3; // OK
-M.y = ""; // Error
-M.y = ""; // Error
-M.y = ""; // Error
-M = {
-    y: 3
-}; // Error
-M = {
-    y: 3
-}; // Error
-var M2;
-(function(M2) {
-    var M3;
-    (function(M3) {
-        var x;
-        M3.x = x;
-    })(M3 = M2.M3 || (M2.M3 = {}));
-    M3 = {
-        x: 3
-    }; // Error
-})(M2 || (M2 = {}));
-M2.M3 = {
-    x: 3
-}; // OK
-M2.M3 = {
-    x: 3
-}; // OK
-M2.M3 = {
-    x: 3
-}; // OK
-M2.M3 = {
-    x: ""
-}; // Error
-M2.M3 = {
-    x: ""
-}; // Error
-M2.M3 = {
-    x: ""
-}; // Error
-function fn() {}
-fn = function() {
-    return 3;
-}; // Bug 823548: Should be error (fn is not a reference)
-fn = function() {
-    return 3;
-}; // Should be error
-function fn2(x, y) {
-    x = 3;
-    x = 3; // OK
-    x = ""; // Error
-    x = ""; // Error
-    y.t = 3; // OK
-    y.t = 3; // OK
-    y.t = ""; // Error
-    y.t = ""; // Error
-    y["t"] = 3; // OK
-    y["t"] = 3; // OK
-    y["t"] = 3; // OK
-    y["t"] = ""; // Error
-    y["t"] = ""; // Error
-    y["t"] = ""; // Error
-}
-var E;
-(function(E) {
-    E[E["A"] = 0] = "A";
-})(E || (E = {}));
-E = undefined; // Error
-E = undefined; // Error
-var C = function C() {
-    "use strict";
-    _class_call_check(this, C);
-};
-C = undefined; // Error
-C = undefined; // Error
+//! 
+//!   x cannot reassign to a class
+//!     ,-[65:1]
+//!  65 | class C {
+//!     :       |
+//!     :       `-- class name
+//!  66 | 
+//!  67 | }
+//!  68 | 
+//!  69 | C = undefined; // Error
+//!     : ^
+//!     `----
diff --git a/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.2.minified.js b/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.2.minified.js
index d870783232..c2e64fe145 100644
--- a/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.2.minified.js
+++ b/crates/swc/tests/tsc-references/assignmentToParenthesizedIdentifiers.2.minified.js
@@ -1,27 +1,13 @@
 //// [assignmentToParenthesizedIdentifiers.ts]
-var M, M2, E;
-!function(M) {
-    var y;
-    M.y = y;
-}(M || (M = {})), M.y = 3, M.y = 3, M.y = 3, M.y = "", M.y = "", M.y = "", M = {
-    y: 3
-}, M = {
-    y: 3
-}, function(M2) {
-    var x;
-    (M2.M3 || (M2.M3 = {})).x = x;
-}(M2 || (M2 = {})), M2.M3 = {
-    x: 3
-}, M2.M3 = {
-    x: 3
-}, M2.M3 = {
-    x: 3
-}, M2.M3 = {
-    x: ""
-}, M2.M3 = {
-    x: ""
-}, M2.M3 = {
-    x: ""
-}, function(E) {
-    E[E.A = 0] = "A";
-}(E || (E = {})), E = void 0, E = void 0;
+//! 
+//!   x cannot reassign to a class
+//!     ,-[65:1]
+//!  65 | class C {
+//!     :       |
+//!     :       `-- class name
+//!  66 | 
+//!  67 | }
+//!  68 | 
+//!  69 | C = undefined; // Error
+//!     : ^
+//!     `----
diff --git a/crates/swc/tests/tsc-references/assignments.1.normal.js b/crates/swc/tests/tsc-references/assignments.1.normal.js
index 741db84407..312285c896 100644
--- a/crates/swc/tests/tsc-references/assignments.1.normal.js
+++ b/crates/swc/tests/tsc-references/assignments.1.normal.js
@@ -1,30 +1,10 @@
 //// [assignments.ts]
-// In this file:
-//  Assign to a module
-//  Assign to a class
-//  Assign to an enum
-//  Assign to a function
-//  Assign to a variable
-//  Assign to a parameter
-//  Assign to an interface
-import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
-M = null; // Error
-var C = function C() {
-    "use strict";
-    _class_call_check(this, C);
-};
-C = null; // Error
-var E;
-(function(E) {
-    E[E["A"] = 0] = "A";
-})(E || (E = {}));
-E = null; // Error
-E.A = null; // OK per spec, Error per implementation (509581)
-function fn() {}
-fn = null; // Should be error
-var v;
-v = null; // OK
-function fn2(p) {
-    p = null; // OK
-}
-I = null; // Error
+//! 
+//!   x cannot reassign to a class
+//!     ,-[13:1]
+//!  13 | class C { }
+//!     :       |
+//!     :       `-- class name
+//!  14 | C = null; // Error
+//!     : ^
+//!     `----
diff --git a/crates/swc/tests/tsc-references/assignments.2.minified.js b/crates/swc/tests/tsc-references/assignments.2.minified.js
index 75f1ceb0f0..312285c896 100644
--- a/crates/swc/tests/tsc-references/assignments.2.minified.js
+++ b/crates/swc/tests/tsc-references/assignments.2.minified.js
@@ -1,5 +1,10 @@
 //// [assignments.ts]
-var E;
-M = null, function(E) {
-    E[E.A = 0] = "A";
-}(E || (E = {})), (E = null).A = null, I = null;
+//! 
+//!   x cannot reassign to a class
+//!     ,-[13:1]
+//!  13 | class C { }
+//!     :       |
+//!     :       `-- class name
+//!  14 | C = null; // Error
+//!     : ^
+//!     `----
diff --git a/crates/swc/tests/tsc-references/invalidUndefinedAssignments.1.normal.js b/crates/swc/tests/tsc-references/invalidUndefinedAssignments.1.normal.js
index f26b5449cb..3c358f371f 100644
--- a/crates/swc/tests/tsc-references/invalidUndefinedAssignments.1.normal.js
+++ b/crates/swc/tests/tsc-references/invalidUndefinedAssignments.1.normal.js
@@ -1,26 +1,11 @@
 //// [invalidUndefinedAssignments.ts]
-import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
-var x;
-var E;
-(function(E) {
-    E[E["A"] = 0] = "A";
-})(E || (E = {}));
-E = x;
-E.A = x;
-var C = function C() {
-    "use strict";
-    _class_call_check(this, C);
-};
-var f;
-C = x;
-var g;
-g = x;
-I = x;
-var M;
-(function(M) {
-    var x = M.x = 1;
-})(M || (M = {}));
-M = x;
-function i(a) {}
-// BUG 767030
-i = x;
+//! 
+//!   x cannot reassign to a class
+//!    ,-[7:1]
+//!  7 | class C { foo: string }
+//!    :       |
+//!    :       `-- class name
+//!  8 | var f: C;
+//!  9 | C = x;
+//!    : ^
+//!    `----
diff --git a/crates/swc/tests/tsc-references/invalidUndefinedAssignments.2.minified.js b/crates/swc/tests/tsc-references/invalidUndefinedAssignments.2.minified.js
index ce97b1983f..3c358f371f 100644
--- a/crates/swc/tests/tsc-references/invalidUndefinedAssignments.2.minified.js
+++ b/crates/swc/tests/tsc-references/invalidUndefinedAssignments.2.minified.js
@@ -1,5 +1,11 @@
 //// [invalidUndefinedAssignments.ts]
-var x, E, M;
-!function(E) {
-    E[E.A = 0] = "A";
-}(E || (E = {})), (E = x).A = x, I = x, (M || (M = {})).x = 1, M = x;
+//! 
+//!   x cannot reassign to a class
+//!    ,-[7:1]
+//!  7 | class C { foo: string }
+//!    :       |
+//!    :       `-- class name
+//!  8 | var f: C;
+//!  9 | C = x;
+//!    : ^
+//!    `----
diff --git a/crates/swc/tests/tsc-references/validNullAssignments.1.normal.js b/crates/swc/tests/tsc-references/validNullAssignments.1.normal.js
index d863c45fc8..7a83c03fb5 100644
--- a/crates/swc/tests/tsc-references/validNullAssignments.1.normal.js
+++ b/crates/swc/tests/tsc-references/validNullAssignments.1.normal.js
@@ -1,33 +1,12 @@
 //// [validNullAssignments.ts]
-import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
-var a = null;
-var b = null;
-var c = null;
-var d = null;
-var e = null;
-e = null; // ok
-var E;
-(function(E) {
-    E[E["A"] = 0] = "A";
-})(E || (E = {}));
-E.A = null; // error
-var C = function C() {
-    "use strict";
-    _class_call_check(this, C);
-};
-var f;
-f = null; // ok
-C = null; // error
-var g;
-g = null; // ok
-I = null; // error
-var M;
-(function(M) {
-    var x = M.x = 1;
-})(M || (M = {}));
-M = null; // error
-var h = null;
-function i(a) {
-    a = null;
-}
-i = null; // error
+//! 
+//!   x cannot reassign to a class
+//!     ,-[12:1]
+//!  12 | class C { foo: string }
+//!     :       |
+//!     :       `-- class name
+//!  13 | var f: C;
+//!  14 | f = null; // ok
+//!  15 | C = null; // error
+//!     : ^
+//!     `----
diff --git a/crates/swc/tests/tsc-references/validNullAssignments.2.minified.js b/crates/swc/tests/tsc-references/validNullAssignments.2.minified.js
index ec4e4d75e3..7a83c03fb5 100644
--- a/crates/swc/tests/tsc-references/validNullAssignments.2.minified.js
+++ b/crates/swc/tests/tsc-references/validNullAssignments.2.minified.js
@@ -1,5 +1,12 @@
 //// [validNullAssignments.ts]
-var E, M;
-!function(E) {
-    E[E.A = 0] = "A";
-}(E || (E = {})), E.A = null, I = null, (M || (M = {})).x = 1, M = null;
+//! 
+//!   x cannot reassign to a class
+//!     ,-[12:1]
+//!  12 | class C { foo: string }
+//!     :       |
+//!     :       `-- class name
+//!  13 | var f: C;
+//!  14 | f = null; // ok
+//!  15 | C = null; // error
+//!     : ^
+//!     `----