Repository: swc-project/swc (rust)

Issue:
Evaluation order of computed keys of a class are different
### Describe the bug

Found by https://github.com/swc-project/swc/pull/5578

### Input code

```typescript
var log = [];

class A {
  [log.push(1)]() {}
  static [log.push(2)]() {}
  [log.push(3)]() {}
  static [log.push(4)]() {}
}

expect(log).toEqual([1, 2, 3, 4]);
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript"
    },
    "target": "es5",
    "minify": {
      "compress": false,
      "mangle": false
    },
    "loose": false
  },
  "minify": false
}
```


### Playground link

https://play.swc.rs/?version=1.2.241&code=H4sIAAAAAAAAAytLLFLIyU9XsFWIjrXm4krOSSwuVnBUqOZSUIgGiusVlBZnaBhqxmpoKlTXAgWLSxJLMpOR5IwQcghBYzwaTGBytVxcqRUFqcklGkA5Tb2SfNfC0sQcjWhDHQUjHQVjHQWTWE2gkwBokVobogAAAA%3D%3D&config=H4sIAAAAAAAAA02LSwqAMBBD75K1Wze9zVCmUumPSReKeHctKrjLS%2FIOrPRwB5oY1UbiXrpscFCfhd5i6zgndLFF%2B6g5Y0KOJYZ9%2FH3NzZSEC5Ko9yRlSfribaZa%2BcPPfPgCbUYHSoEAAAA%3D

### Expected behavior

It should not be changed

### Actual behavior

_No response_

### Version

1.2.241

### Additional context

_No response_

Pull request description:
fix(es/compat): Handle evaluation order of class computed keys
- fix #5581 

Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs b/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs
index dddad9b3f8..eb0f502458 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs
@@ -22,7 +22,7 @@ use self::{
         constructor_fn, make_possible_return_value, replace_this_in_constructor, ConstructorFolder,
         ReturningMode, SuperCallFinder, SuperFoldingMode,
     },
-    prop_name::HashKey,
+    prop_name::{is_pure_prop_name, should_extract_class_prop_key, HashKey},
 };
 
 mod constructor;
@@ -37,6 +37,9 @@ where
         in_strict: false,
         comments,
         config,
+
+        params: Default::default(),
+        args: Default::default(),
     })
 }
 
@@ -73,7 +76,7 @@ type IndexMap<K, V> = indexmap::IndexMap<K, V, ahash::RandomState>;
 ///   return Test;
 /// }();
 /// ```
-#[derive(Default, Clone, Copy)]
+#[derive(Default, Clone)]
 struct Classes<C>
 where
     C: Comments,
@@ -81,6 +84,9 @@ where
     in_strict: bool,
     comments: Option<C>,
     config: Config,
+
+    params: Vec<Param>,
+    args: Vec<ExprOrSpread>,
 }
 
 #[derive(Debug, Clone, Copy, Default, Deserialize)]
@@ -409,7 +415,7 @@ where
             .as_ref()
             .map(|e| alias_if_required(e, "_superClass").0);
         let has_super = super_ident.is_some();
-        let (params, args, super_ident) = if let Some(ref super_ident) = super_ident {
+        let (mut params, mut args, super_ident) = if let Some(ref super_ident) = super_ident {
             // Param should have a separate syntax context from arg.
             let super_param = private_ident!(super_ident.sym.clone());
             let params = vec![Param {
@@ -443,6 +449,8 @@ where
         };
 
         let mut stmts = self.class_to_stmts(class_name, super_ident, class);
+        params.extend(self.params.take());
+        args.extend(self.args.take());
 
         let cnt_of_non_directive = stmts
             .iter()
@@ -991,12 +999,29 @@ where
 
         let (mut props, mut static_props) = (IndexMap::default(), IndexMap::default());
 
+        let should_extract = should_extract_class_prop_key(&methods);
+
         for mut m in methods {
             let key = HashKey::from(&m.key);
+            let key_is_pure = is_pure_prop_name(&m.key);
             let key_prop = Box::new(m.key.clone());
             let computed = matches!(m.key, PropName::Computed(..));
             let prop_name = prop_name_to_expr(m.key);
 
+            let key_prop = if should_extract && !key_is_pure {
+                let ident = private_ident!("_prop");
+
+                self.params.push(ident.clone().into());
+                self.args.push(prop_name.clone().into());
+
+                Box::new(PropName::Computed(ComputedPropName {
+                    span: DUMMY_SP,
+                    expr: Box::new(ident.into()),
+                }))
+            } else {
+                key_prop
+            };
+
             let append_to: &mut IndexMap<_, _> = if m.is_static {
                 &mut static_props
             } else {
diff --git a/crates/swc_ecma_transforms_compat/src/es2015/classes/prop_name.rs b/crates/swc_ecma_transforms_compat/src/es2015/classes/prop_name.rs
index af39090015..b84f14ed20 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/classes/prop_name.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/classes/prop_name.rs
@@ -21,3 +21,38 @@ impl From<&PropName> for HashKey {
         }
     }
 }
+
+pub fn is_pure_prop_name(p: &PropName) -> bool {
+    match p {
+        PropName::Ident(..) => true,
+        PropName::Str(..) => true,
+        PropName::Num(..) => true,
+        PropName::BigInt(..) => true,
+        PropName::Computed(ComputedPropName { expr, .. }) => match &**expr {
+            Expr::This(..) => true,
+            Expr::Lit(..) => true,
+            Expr::Ident(..) => true,
+            Expr::PrivateName(..) => true,
+            Expr::Tpl(tpl) => tpl.exprs.is_empty(),
+            _ => false,
+        },
+    }
+}
+
+pub fn should_extract_class_prop_key(methods: &[ClassMethod]) -> bool {
+    let mut has_static = false;
+
+    for m in methods {
+        if is_pure_prop_name(&m.key) {
+            continue;
+        }
+
+        if m.is_static {
+            has_static = true
+        } else if has_static {
+            return true;
+        }
+    }
+
+    false
+}


Test patch:
diff --git a/crates/swc/tests/babel-exec/packages/babel-plugin-transform-classes/test/fixtures/spec/.computed-keys-call-order/exec.js b/crates/swc/tests/babel-exec/packages/babel-plugin-transform-classes/test/fixtures/spec/computed-keys-call-order/exec.js
similarity index 100%
rename from crates/swc/tests/babel-exec/packages/babel-plugin-transform-classes/test/fixtures/spec/.computed-keys-call-order/exec.js
rename to crates/swc/tests/babel-exec/packages/babel-plugin-transform-classes/test/fixtures/spec/computed-keys-call-order/exec.js
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.1.normal.js b/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.1.normal.js
index bd0b983a4e..c4d98dd44e 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.1.normal.js
@@ -3,7 +3,7 @@ import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
 var s;
 var n;
 var a;
-var C = /*#__PURE__*/ function() {
+var C = /*#__PURE__*/ function(_prop, _prop1, _prop2, _prop3) {
     "use strict";
     function C() {
         _class_call_check(this, C);
@@ -11,14 +11,14 @@ var C = /*#__PURE__*/ function() {
     var _proto = C.prototype;
     _proto[s] = function() {};
     _proto[n] = function() {};
-    _proto[s + n] = function() {};
-    _proto[+s] = function() {};
+    _proto[_prop1] = function() {};
+    _proto[_prop2] = function() {};
     _proto[0] = function() {};
     _proto[a] = function() {};
     _proto["hello bye"] = function() {};
-    C[s + s] = function() {};
+    C[_prop] = function() {};
     C[""] = function() {};
     C[true] = function() {};
-    C["hello ".concat(a, " bye")] = function() {};
+    C[_prop3] = function() {};
     return C;
-}();
+}(s + s, s + n, +s, "hello ".concat(a, " bye"));
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.2.minified.js b/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.2.minified.js
index 1adbda0bc1..a16a108bc7 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames13_ES5_es5.2.minified.js
@@ -1,1 +1,3 @@
+var a;
 import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
+"hello ".concat(a, " bye");
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.1.normal.js b/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.1.normal.js
index 7a6860121a..b061d38539 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.1.normal.js
@@ -3,7 +3,7 @@ import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
 var s;
 var n;
 var a;
-var C = /*#__PURE__*/ function() {
+var C = /*#__PURE__*/ function(_prop, _prop1, _prop2, _prop3) {
     "use strict";
     function C() {
         _class_call_check(this, C);
@@ -11,14 +11,14 @@ var C = /*#__PURE__*/ function() {
     var _proto = C.prototype;
     _proto[s] = function() {};
     _proto[n] = function() {};
-    _proto[s + n] = function() {};
-    _proto[+s] = function() {};
+    _proto[_prop1] = function() {};
+    _proto[_prop2] = function() {};
     _proto[0] = function() {};
     _proto[a] = function() {};
     _proto["hello bye"] = function() {};
-    C[s + s] = function() {};
+    C[_prop] = function() {};
     C[""] = function() {};
     C[true] = function() {};
-    C["hello ".concat(a, " bye")] = function() {};
+    C[_prop3] = function() {};
     return C;
-}();
+}(s + s, s + n, +s, "hello ".concat(a, " bye"));
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.2.minified.js b/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.2.minified.js
index 1adbda0bc1..a16a108bc7 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames13_ES6_es5.2.minified.js
@@ -1,1 +1,3 @@
+var a;
 import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
+"hello ".concat(a, " bye");
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.1.normal.js b/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.1.normal.js
index c2663bd95a..3530ee93c6 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.1.normal.js
@@ -4,7 +4,7 @@ import _create_class from "@swc/helpers/src/_create_class.mjs";
 var s;
 var n;
 var a;
-var C = /*#__PURE__*/ function() {
+var C = /*#__PURE__*/ function(_prop, _prop1, _prop2, _prop3) {
     "use strict";
     function C() {
         _class_call_check(this, C);
@@ -21,11 +21,11 @@ var C = /*#__PURE__*/ function() {
             set: function set(v) {}
         },
         {
-            key: s + n,
+            key: _prop1,
             set: function set(v) {}
         },
         {
-            key: +s,
+            key: _prop2,
             get: function get() {
                 return 0;
             }
@@ -45,14 +45,14 @@ var C = /*#__PURE__*/ function() {
             set: function set(v) {}
         },
         {
-            key: "hello ".concat(a, " bye"),
+            key: _prop3,
             get: function get() {
                 return 0;
             }
         }
     ], [
         {
-            key: s + s,
+            key: _prop,
             get: function get() {
                 return 0;
             }
@@ -69,4 +69,4 @@ var C = /*#__PURE__*/ function() {
         }
     ]);
     return C;
-}();
+}(s + s, s + n, +s, "hello ".concat(a, " bye"));
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.2.minified.js b/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.2.minified.js
index 713ab45b37..c2e8e33caf 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames16_ES5_es5.2.minified.js
@@ -1,2 +1,4 @@
+var a;
 import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
 import _create_class from "@swc/helpers/src/_create_class.mjs";
+"hello ".concat(a, " bye");
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.1.normal.js b/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.1.normal.js
index 472cc4488d..a1415453b7 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.1.normal.js
@@ -4,7 +4,7 @@ import _create_class from "@swc/helpers/src/_create_class.mjs";
 var s;
 var n;
 var a;
-var C = /*#__PURE__*/ function() {
+var C = /*#__PURE__*/ function(_prop, _prop1, _prop2, _prop3) {
     "use strict";
     function C() {
         _class_call_check(this, C);
@@ -21,11 +21,11 @@ var C = /*#__PURE__*/ function() {
             set: function set(v) {}
         },
         {
-            key: s + n,
+            key: _prop1,
             set: function set(v) {}
         },
         {
-            key: +s,
+            key: _prop2,
             get: function get() {
                 return 0;
             }
@@ -45,14 +45,14 @@ var C = /*#__PURE__*/ function() {
             set: function set(v) {}
         },
         {
-            key: "hello ".concat(a, " bye"),
+            key: _prop3,
             get: function get() {
                 return 0;
             }
         }
     ], [
         {
-            key: s + s,
+            key: _prop,
             get: function get() {
                 return 0;
             }
@@ -69,4 +69,4 @@ var C = /*#__PURE__*/ function() {
         }
     ]);
     return C;
-}();
+}(s + s, s + n, +s, "hello ".concat(a, " bye"));
diff --git a/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.2.minified.js b/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.2.minified.js
index 713ab45b37..c2e8e33caf 100644
--- a/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/computedPropertyNames16_ES6_es5.2.minified.js
@@ -1,2 +1,4 @@
+var a;
 import _class_call_check from "@swc/helpers/src/_class_call_check.mjs";
 import _create_class from "@swc/helpers/src/_create_class.mjs";
+"hello ".concat(a, " bye");