Repository: swc-project/swc (rust)

Issue:
assert_this_initialized check fired before super()
### Describe the bug

In some cases _assert_this_initialized fires before super() call which causes  `ReferenceError: this hasn't been initialised - super() hasn't been called`

### Input code

```typescript
class Test0 {}

class Test extends Test0 {
  constructor() {
    super(),
      console.log(async (e) => {
        await this.test();
      });
  }
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "dynamicImport": true,
      "tsx": true,
      "jsx": true
    },
    "loose": false,
    "externalHelpers": true
  },
  "env": {
    "targets": "> 0.25%, not dead, Safari >=8",
    "mode": "entry",
    "coreJs": "3.27",
    "loose": false,
    "bugfixes": true
  }
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.3.100&code=H4sIAAAAAAAAAz2MwQqDMBBE7%2FsVc0ygiHfRr%2BgPhHVphZAUd6UVyb9rozineTPDcAyqeIpai60Q8c2Qn0ka75IAzkltXtjy7HxNAF0%2BctCjwjnJUZqYXy7omhhOPPrhWv8VvmEy2HvSxo5r57urKtUVKrQDPuJyYpYAAAA%3D&config=H4sIAAAAAAAAA32PzQrCQAyE732KJeCtFFFEEexZvfoEcZuWlna3ZKO0lL67a%2B2PePCW%2BWYSJl2gFBROw1F1fvSiRnbEs%2FbEtUaw8QRIV%2Bg057VAOLlJa7DK9aWqLYsPCT9oNsU1v6iY0UD6jwGltY68kWLpxjBQI8QGyzOVNbFb1oYlIPNcagtyRvLOQKzW0Wa3CpWxohLCJFQ3TJFzFZ8OY3GobELDS0a4naC2TNfhxjba7OFPtfsjS%2FOGvjsF%2FQv6%2BI68TQEAAA%3D%3D

### SWC Info output

_No response_

### Expected behavior

```javascript
var _this;
_this = _super.call(this);
var _this1 = _assert_this_initialized(_this);
```

Compiles well if i use `super();` instead of `super(),`

### Actual behavior

```javascript
var _this;
var _this1 = _assert_this_initialized(_this);
_this = _super.call(this),
```

### Version

1.3.101

### Additional context

_No response_

Pull request:
fix(es/compat): Init this in sub class constructor for async
**Related issue:**

 - Closes https://github.com/swc-project/swc/issues/8452
 - Closes https://github.com/swc-project/swc/issues/9432

Patch:
diff --git a/.changeset/grumpy-cars-move.md b/.changeset/grumpy-cars-move.md
new file mode 100644
index 0000000000..72ae36128f
--- /dev/null
+++ b/.changeset/grumpy-cars-move.md
@@ -0,0 +1,7 @@
+---
+swc_core: patch
+swc_ecma_compat_es2017: patch
+swc_ecma_compat_es2015: patch
+---
+
+fix(es/compat): Init this in sub class constructor for async
diff --git a/crates/swc_ecma_compat_es2015/src/arrow.rs b/crates/swc_ecma_compat_es2015/src/arrow.rs
index 1a8a7596e8..9fa82b2bd0 100644
--- a/crates/swc_ecma_compat_es2015/src/arrow.rs
+++ b/crates/swc_ecma_compat_es2015/src/arrow.rs
@@ -75,11 +75,13 @@ impl VisitMut for Arrow {
     noop_visit_mut_type!(fail);
 
     fn visit_mut_class(&mut self, c: &mut Class) {
+        let old = self.in_subclass;
+
         if c.super_class.is_some() {
             self.in_subclass = true;
         }
         c.visit_mut_children_with(self);
-        self.in_subclass = false;
+        self.in_subclass = old;
     }
 
     fn visit_mut_constructor(&mut self, c: &mut Constructor) {
diff --git a/crates/swc_ecma_compat_es2017/src/async_to_generator.rs b/crates/swc_ecma_compat_es2017/src/async_to_generator.rs
index be6b1c04f5..23436794f3 100644
--- a/crates/swc_ecma_compat_es2017/src/async_to_generator.rs
+++ b/crates/swc_ecma_compat_es2017/src/async_to_generator.rs
@@ -1,4 +1,4 @@
-use std::iter;
+use std::{iter, mem};
 
 use serde::Deserialize;
 use swc_common::{
@@ -9,8 +9,8 @@ use swc_ecma_transforms_base::{helper, helper_expr, perf::Check};
 use swc_ecma_transforms_macros::fast_path;
 use swc_ecma_utils::{
     contains_this_expr, find_pat_ids,
-    function::{FnEnvHoister, FnWrapperResult, FunctionWrapper},
-    private_ident, quote_ident, ExprFactory, Remapper, StmtLike,
+    function::{init_this, FnEnvHoister, FnWrapperResult, FunctionWrapper},
+    prepend_stmt, private_ident, quote_ident, ExprFactory, Remapper, StmtLike,
 };
 use swc_ecma_visit::{
     as_folder, noop_visit_mut_type, noop_visit_type, Fold, Visit, VisitMut, VisitMutWith, VisitWith,
@@ -45,6 +45,7 @@ pub fn async_to_generator<C: Comments + Clone>(
     as_folder(AsyncToGenerator {
         c,
         comments,
+        in_subclass: false,
         unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
     })
 }
@@ -62,6 +63,7 @@ pub struct Config {
 struct AsyncToGenerator<C: Comments + Clone> {
     c: Config,
     comments: Option<C>,
+    in_subclass: bool,
     unresolved_ctxt: SyntaxContext,
 }
 
@@ -69,9 +71,11 @@ struct Actual<C: Comments> {
     c: Config,
     comments: Option<C>,
 
+    in_subclass: bool,
+    hoister: FnEnvHoister,
+
     unresolved_ctxt: SyntaxContext,
     extra_stmts: Vec<Stmt>,
-    hoist_stmts: Vec<Stmt>,
 }
 
 #[swc_trace]
@@ -79,6 +83,14 @@ struct Actual<C: Comments> {
 impl<C: Comments + Clone> VisitMut for AsyncToGenerator<C> {
     noop_visit_mut_type!(fail);
 
+    fn visit_mut_class(&mut self, c: &mut Class) {
+        if c.super_class.is_some() {
+            self.in_subclass = true;
+        }
+        c.visit_mut_children_with(self);
+        self.in_subclass = false;
+    }
+
     fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
         self.visit_mut_stmt_like(n);
     }
@@ -98,16 +110,19 @@ impl<C: Comments + Clone> AsyncToGenerator<C> {
         let mut stmts_updated = Vec::with_capacity(stmts.len());
 
         for mut stmt in stmts.drain(..) {
+            let hoister = FnEnvHoister::new(self.unresolved_ctxt);
+
             let mut actual = Actual {
                 c: self.c,
                 comments: self.comments.clone(),
+                in_subclass: self.in_subclass,
+                hoister,
                 unresolved_ctxt: self.unresolved_ctxt,
                 extra_stmts: Vec::new(),
-                hoist_stmts: Vec::new(),
             };
 
             stmt.visit_mut_with(&mut actual);
-            stmts_updated.extend(actual.hoist_stmts.into_iter().map(T::from));
+            stmts_updated.extend(actual.hoister.to_stmt().into_iter().map(T::from));
             stmts_updated.push(stmt);
             stmts_updated.extend(actual.extra_stmts.into_iter().map(T::from));
         }
@@ -122,6 +137,45 @@ impl<C: Comments + Clone> AsyncToGenerator<C> {
 impl<C: Comments> VisitMut for Actual<C> {
     noop_visit_mut_type!(fail);
 
+    fn visit_mut_class(&mut self, c: &mut Class) {
+        let old = self.in_subclass;
+
+        if c.super_class.is_some() {
+            self.in_subclass = true;
+        }
+        c.visit_mut_children_with(self);
+        self.in_subclass = old;
+    }
+
+    fn visit_mut_constructor(&mut self, c: &mut Constructor) {
+        c.params.visit_mut_children_with(self);
+
+        if let Some(BlockStmt { span: _, stmts, .. }) = &mut c.body {
+            let old_rep = self.hoister.take();
+
+            stmts.visit_mut_children_with(self);
+
+            if self.in_subclass {
+                let (decl, this_id) =
+                    mem::replace(&mut self.hoister, old_rep).to_stmt_in_subclass();
+
+                if let Some(this_id) = this_id {
+                    init_this(stmts, &this_id)
+                }
+
+                if let Some(decl) = decl {
+                    prepend_stmt(stmts, decl)
+                }
+            } else {
+                let decl = mem::replace(&mut self.hoister, old_rep).to_stmt();
+
+                if let Some(decl) = decl {
+                    prepend_stmt(stmts, decl)
+                }
+            }
+        }
+    }
+
     fn visit_mut_class_method(&mut self, m: &mut ClassMethod) {
         if m.function.body.is_none() {
             return;
@@ -192,6 +246,51 @@ impl<C: Comments> VisitMut for Actual<C> {
         self.visit_mut_expr_with_binding(expr, None, false);
     }
 
+    fn visit_mut_function(&mut self, f: &mut Function) {
+        let old_rep = self.hoister.take();
+
+        f.visit_mut_children_with(self);
+
+        let decl = mem::replace(&mut self.hoister, old_rep).to_stmt();
+
+        if let (Some(body), Some(decl)) = (&mut f.body, decl) {
+            prepend_stmt(&mut body.stmts, decl);
+        }
+    }
+
+    fn visit_mut_getter_prop(&mut self, f: &mut GetterProp) {
+        f.key.visit_mut_with(self);
+
+        if let Some(body) = &mut f.body {
+            let old_rep = self.hoister.take();
+
+            body.visit_mut_with(self);
+
+            let decl = mem::replace(&mut self.hoister, old_rep).to_stmt();
+
+            if let Some(stmt) = decl {
+                prepend_stmt(&mut body.stmts, stmt);
+            }
+        }
+    }
+
+    fn visit_mut_setter_prop(&mut self, f: &mut SetterProp) {
+        f.key.visit_mut_with(self);
+        f.param.visit_mut_with(self);
+
+        if let Some(body) = &mut f.body {
+            let old_rep = self.hoister.take();
+
+            body.visit_mut_with(self);
+
+            let decl = mem::replace(&mut self.hoister, old_rep).to_stmt();
+
+            if let Some(stmt) = decl {
+                prepend_stmt(&mut body.stmts, stmt);
+            }
+        }
+    }
+
     fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
         f.visit_mut_children_with(self);
         if !f.function.is_async {
@@ -342,11 +441,7 @@ impl<C: Comments> Actual<C> {
 
         match expr {
             Expr::Arrow(arrow_expr @ ArrowExpr { is_async: true, .. }) => {
-                let mut state = FnEnvHoister::new(self.unresolved_ctxt);
-
-                arrow_expr.visit_mut_with(&mut state);
-
-                self.hoist_stmts.extend(state.to_stmt());
+                arrow_expr.visit_mut_with(&mut self.hoister);
 
                 let mut wrapper = FunctionWrapper::from(arrow_expr.take());
                 wrapper.ignore_function_name = self.c.ignore_function_name;


Test patch:
diff --git a/crates/swc/tests/fixture/issues-4xxx/4208/1/output/index.ts b/crates/swc/tests/fixture/issues-4xxx/4208/1/output/index.ts
index 455c67e8f6..caf5e0b3f7 100644
--- a/crates/swc/tests/fixture/issues-4xxx/4208/1/output/index.ts
+++ b/crates/swc/tests/fixture/issues-4xxx/4208/1/output/index.ts
@@ -5,8 +5,8 @@ import { _ as _ts_generator } from "@swc/helpers/_/_ts_generator";
 export var CompanyBgStore = function CompanyBgStore() {
     "use strict";
     _class_call_check(this, CompanyBgStore);
-    _define_property(this, "corpName", 123);
     var _this = this;
+    _define_property(this, "corpName", 123);
     _define_property(this, "getBusinessInfo", _async_to_generator(function() {
         var corpName;
         var _arguments = arguments;
diff --git a/crates/swc/tests/fixture/issues-4xxx/4224/1/output/index.js b/crates/swc/tests/fixture/issues-4xxx/4224/1/output/index.js
index fe46e73ac8..8e1269e8f1 100644
--- a/crates/swc/tests/fixture/issues-4xxx/4224/1/output/index.js
+++ b/crates/swc/tests/fixture/issues-4xxx/4224/1/output/index.js
@@ -13,10 +13,9 @@ var A = function A() {
             ];
         });
     });
-    var _this1 = this;
     this.bar = /*#__PURE__*/ _async_to_generator._(function() {
         return _ts_generator._(this, function(_state) {
-            _this1.x();
+            _this.x();
             return [
                 2
             ];
diff --git a/crates/swc/tests/fixture/issues-4xxx/4224/2/output/index.js b/crates/swc/tests/fixture/issues-4xxx/4224/2/output/index.js
index 17264ad2b5..ed046d51c0 100644
--- a/crates/swc/tests/fixture/issues-4xxx/4224/2/output/index.js
+++ b/crates/swc/tests/fixture/issues-4xxx/4224/2/output/index.js
@@ -5,9 +5,8 @@ class A {
         this.foo = /*#__PURE__*/ _async_to_generator._(function*() {
             _this.x();
         });
-        var _this1 = this;
         this.bar = /*#__PURE__*/ _async_to_generator._(function*() {
-            _this1.x();
+            _this.x();
         });
     }
 }
diff --git a/crates/swc_ecma_transforms_compat/tests/__swc_snapshots__/tests/es2017_async_to_generator.rs/issue_8452.js b/crates/swc_ecma_transforms_compat/tests/__swc_snapshots__/tests/es2017_async_to_generator.rs/issue_8452.js
new file mode 100644
index 0000000000..0ec40d0250
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/__swc_snapshots__/tests/es2017_async_to_generator.rs/issue_8452.js
@@ -0,0 +1,15 @@
+class Test0 {
+}
+class Test extends Test0 {
+    constructor(){
+        var _this;
+        super(), _this = this, console.log(function() {
+            var _ref = _async_to_generator(function*(e) {
+                yield _this.test();
+            });
+            return function(e) {
+                return _ref.apply(this, arguments);
+            };
+        }());
+    }
+}
diff --git a/crates/swc_ecma_transforms_compat/tests/__swc_snapshots__/tests/es2017_async_to_generator.rs/issue_9432.js b/crates/swc_ecma_transforms_compat/tests/__swc_snapshots__/tests/es2017_async_to_generator.rs/issue_9432.js
new file mode 100644
index 0000000000..e0085be24c
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/__swc_snapshots__/tests/es2017_async_to_generator.rs/issue_9432.js
@@ -0,0 +1,10 @@
+class Foo extends Bar {
+    constructor(options){
+        var _this;
+        super({
+            callA: _async_to_generator(function*() {
+                _this.callA();
+            })
+        }), _this = this;
+    }
+}
diff --git a/crates/swc_ecma_transforms_compat/tests/es2017_async_to_generator.rs b/crates/swc_ecma_transforms_compat/tests/es2017_async_to_generator.rs
index 17782426c2..7ac0cc6e50 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2017_async_to_generator.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2017_async_to_generator.rs
@@ -2077,6 +2077,44 @@ test!(
     "
 );
 
+test!(
+    Syntax::default(),
+    |_| async_to_generator::<SingleThreadedComments>(Default::default(), None, Mark::new()),
+    issue_8452,
+    r#"
+class Test0 {}
+
+class Test extends Test0 {
+    constructor() {
+        super(),
+            console.log(async (e) => {
+                await this.test();
+            });
+    }
+}
+
+"#
+);
+
+test!(
+    Syntax::default(),
+    |_| with_resolver(),
+    issue_9432,
+    r#"
+class Foo extends Bar {
+  constructor(options) {
+    super(
+      {
+        callA: async () => {
+          this.callA();
+        },
+      }
+    );
+  }
+}
+"#
+);
+
 #[testing::fixture("tests/async-to-generator/**/exec.js")]
 fn exec(input: PathBuf) {
     let input = read_to_string(input).unwrap();