Repository: swc-project/swc (rust)

Issue:
Setting `loose` option in `env` results in incorrect code
### Describe the bug

While setting `loose` setting to true under `env` key, the input code below results in incorrect class output and non-working code.

```
// ... elided polyfill stuff

var B = function B() {
    "use strict";
};
var Thing = /*#__PURE__*/ function(B) {
    "use strict";
    _inherits(Thing, B);
    function Thing(n) {
        var _this;
        _this = B.call(this); // "_this" is always going to be undefined here
        _this.name = n; // so this line will always be an error
        return _this;
    }
    return Thing;
}(B);

var t = new Thing("name");
```

The difference between `jsc.loose` (https://swc.rs/docs/configuration/compilation#jscloose) and `env.loose` is also not quite clear.

### Input code

```typescript
class B {}

class Thing extends B {
  constructor(n) {
    super()
    this.name = n
  }
}

const t = new Thing("name")
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    }
  },
  "sourceMaps": true,
  "env": {
    "loose": true
  }
}
```


### Playground link

https://play.swc.rs/?version=1.2.151&code=H4sIAAAAAAAAAyWMwQmAMBAE%2F6liySv52IEfa7ABiYcKekruREHSu%2Bb8LbO7k9ZBBB2e4lyy3M8LT6BbiUdrHJB2Fs1n0j0HjoYAOQ%2FKIVrWeZGGh43Qgj9SXPXVF7Qiun5v8HXk4wsyGdDmdwAAAA%3D%3D&config=H4sIAAAAAAAAAyWL0QnAIAwFd3nfTuAOHSKEFFqsSqKlRbJ7lf4dx93AaYw4UElNdJG9udGDCOGLjPWoDWFmU%2B2UTNwDrHRl2agaYtMuAZLvNadSTH7n%2FgGSD1ntXQAAAA%3D%3D

### Expected behavior

Working polyfilled class code output by SWC compiler

### Actual behavior

_No response_

### Version

1.2.151

### Additional context

_No response_

Pull request:
fix(es/transforms/compat): refer this in constructor when set `super_is_callable_constructor`


<!-- 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 #3943

Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/es2015/classes/constructor.rs b/crates/swc_ecma_transforms_compat/src/es2015/classes/constructor.rs
index a4af2b2270..a78808127c 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/classes/constructor.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/classes/constructor.rs
@@ -162,6 +162,7 @@ pub(super) struct ConstructorFolder<'a> {
     pub super_var: Option<Ident>,
     /// True when recursing into other function or class.
     pub ignore_return: bool,
+    pub super_is_callable_constructor: bool,
 }
 
 /// `None`: `return _possibleConstructorReturn`
@@ -205,26 +206,39 @@ impl VisitMut for ConstructorFolder<'_> {
         }) = expr
         {
             let right = match self.super_var.clone() {
-                Some(super_var) => Box::new(Expr::Call(CallExpr {
-                    span: DUMMY_SP,
-                    callee: if self.is_constructor_default {
-                        super_var.make_member(quote_ident!("apply")).as_callee()
-                    } else {
-                        super_var.make_member(quote_ident!("call")).as_callee()
-                    },
-                    args: if self.is_constructor_default {
-                        vec![
-                            ThisExpr { span: DUMMY_SP }.as_arg(),
-                            quote_ident!("arguments").as_arg(),
-                        ]
-                    } else {
-                        let mut call_args = vec![ThisExpr { span: DUMMY_SP }.as_arg()];
-                        call_args.extend(args.take());
+                Some(super_var) => {
+                    let call = Box::new(Expr::Call(CallExpr {
+                        span: DUMMY_SP,
+                        callee: if self.is_constructor_default {
+                            super_var.make_member(quote_ident!("apply")).as_callee()
+                        } else {
+                            super_var.make_member(quote_ident!("call")).as_callee()
+                        },
+                        args: if self.is_constructor_default {
+                            vec![
+                                ThisExpr { span: DUMMY_SP }.as_arg(),
+                                quote_ident!("arguments").as_arg(),
+                            ]
+                        } else {
+                            let mut call_args = vec![ThisExpr { span: DUMMY_SP }.as_arg()];
+                            call_args.extend(args.take());
 
-                        call_args
-                    },
-                    type_args: Default::default(),
-                })),
+                            call_args
+                        },
+                        type_args: Default::default(),
+                    }));
+
+                    if self.super_is_callable_constructor {
+                        Box::new(Expr::Bin(BinExpr {
+                            span: DUMMY_SP,
+                            left: call,
+                            op: op!("||"),
+                            right: Box::new(Expr::This(ThisExpr { span: DUMMY_SP })),
+                        }))
+                    } else {
+                        call
+                    }
+                }
 
                 None => Box::new(make_possible_return_value(ReturningMode::Prototype {
                     class_name: self.class_name.clone(),
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 10005a21a0..480ea065f5 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs
@@ -578,6 +578,7 @@ where
                     is_constructor_default,
                     super_var,
                     ignore_return: false,
+                    super_is_callable_constructor: self.config.super_is_callable_constructor,
                 });
 
                 insert_this |= (mode == None && !is_always_initialized)


Test patch:
diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_classes.rs b/crates/swc_ecma_transforms_compat/tests/es2015_classes.rs
index 316f1481f7..71fa478cc0 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2015_classes.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2015_classes.rs
@@ -7318,3 +7318,37 @@ let Test = /*#__PURE__*/function (Foo) {
 }(Foo);
 "#
 );
+
+test!(
+    syntax(),
+    |t| classes(
+        Some(t.comments.clone()),
+        Config {
+            super_is_callable_constructor: true,
+            ..Default::default()
+        }
+    ),
+    issue_3943,
+    r#"
+class Thing extends B {
+  constructor(n) {
+    super()
+    this.name = n
+  }
+}
+"#,
+    r#"
+let Thing = function(B) {
+    "use strict";
+    _inherits(Thing, B);
+    function Thing(n) {
+        _classCallCheck(this, Thing);
+        var _this;
+        _this = B.call(this) || this;
+        _this.name = n;
+        return _this;
+    }
+    return Thing;
+}(B);
+"#
+);