Repository: swc-project/swc (rust)

Issue:
ES2022 / Typescript / Decorators / Constructor Overloads Create Duplicate Constructors
### Describe the bug

When using Typescript, decorators, and constructor overloads, with a target of "ES2022", SWC generates duplicate constructors for the class.

### Input code

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any -- `any`s to match Promise interface. */
/* eslint-disable promise/prefer-await-to-then -- Can't use `await`s in the constructor */

import { action, computed, makeObservable, observable } from 'mobx';
import { Nullable } from './type-utils';

/** Corresponds to the TS type for the `resolve` function. */
type Resolve<T> = (value: T | PromiseLike<T>) => void;

/** Corresponds to the TS type for the `reject` function. */
type Reject = (reason?: any) => void;

/** Corresponds to the TS type for the `executor` function. */
type Executor<T> = (resolve: Resolve<T>, reject: Reject) => void;

/**
 * `StaleWhileRevalidate` is used to represent a value which we can get immediately,
 * but may require an update. It can be used just like a promise with `.then` or `await`.
 * The `value` property may represent the initial "stale" value or the "fresh" value
 * that was retrieved. The `value` property is observable and can be referenced in
 * React components directly.
 */
class StaleWhileRevalidate<T> extends Promise<T> implements PromiseLike<T> {
  @observable private _value: T;
  @observable private _pending: boolean;

  /** @returns The current value. May be the "stale" or "fresh" value. */
  @computed public get value(): T {
    return this._value;
  }

  /** @returns Whether the async operation to fetch the "fresh" data has completed. */
  @computed public get pending(): boolean {
    return this._pending;
  }
  
  public constructor(staleValue: Nullable<T>, futureValue?: Promise<T>);

  public constructor(staleValue: Nullable<T>, futureValue?: () => Promise<T>);

  public constructor(executor: Executor<T>);

  /**
   * @param staleValue - The known "stale" value, or `null` / `undefined`.
   * @param futureValue - Either a promise or a function which returns a promise
   * which resolves to the "fresh" updated value.
   * @inheritdoc
   */
  public constructor(
    staleValue: Nullable<T> | Executor<T>,
    futureValue?: (() => Promise<T>) | Promise<T>
  ) {
    // Get a promise from what was passed for the future value.
    const promise =
      futureValue instanceof Promise ? futureValue : futureValue?.();

    let res: Resolve<T>;
    let rej: Reject;

    // Here be dragons
    super(
      // An odd implementation detail to promises is that they might get
      // reconstructed by the JS engine. In this event, this class to needs
      // to secretly accept its first argument as a promise "executor"
      // function. If the first arg is a function and no second arg was passed
      // we know we should allow it to be the executor to the super class.
      // Further reading: https://tc39.es/ecma262/#sec-newpromisecapability
      staleValue instanceof Function && !promise
        ? (staleValue as Executor<T>)
        : // The normal executor
          (resolve, reject) => {
            res = resolve;
            rej = reject;
          }
    );

    promise
      ?.then(
        action((value) => {
          this._value = value;
          this._pending = false;
          res(value);
        })
      )
      .catch((e) => rej(e));

    this._pending = true;
    if (!(staleValue instanceof Function)) {
      this._value = staleValue as T;
    }

    makeObservable(this);
  }
}

export default StaleWhileRevalidate;
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": false,
      "react": {
        "importSource": "react",
        "runtime": "automatic",
        "pragma": "React.createElement",
        "pragmaFrag": "React.Fragment",
        "throwIfNamespace": true,
        "development": true,
        "useBuiltins": true,
        "refresh": false
      },
      "optimizer": {
        "simplify": false,
        "globals": {
          "typeofs": {
            "window": "undefined"
          },
          "envs": {
            "NODE_ENV": "\"development\""
          }
        }
      },
      "regenerator": {
        "importPath": "/Users/john.louderback/Documents/Git/headless-frontend/node_modules/next/dist/compiled/regenerator-runtime/runtime.js"
      }
    },
    "minify": {
      "mangle": false,
      "compress": false
    },
    "target": "es2022",
    "loose": false
  },
  "isModule": true,
  "minify": false,
  "module": {
    "type": "es6"
  },
  "env": {
    "targets": {
      "node": "14.17.3"
    },
    "include": [
      "proposal-optional-chaining",
      "proposal-nullish-coalescing-operator"
    ]
  }
}
```


### Playground link

https://play.swc.rs/?version=1.2.153&code=H4sIAAAAAAAAA51XbW%2FbRgz%2Bnl%2FBZkBrB7YFdMCAOWvToWi3DntDG6wf57NExZfKJ013imNk%2Be97eC%2BWlLjD1qKAbR1FPnxIPrxkZ8S20sbNC23VumJ65fYN27zVjZuHo8zUc75tKp1rN1dmT%2FM5rfC5suRq2iqXb%2Bj3tt5qywRzbkuV84LOspPskfcm2GVNyyW3c7VT8OnquduwEb%2BvlXnmqIOnlT9DDG0Ip5TXxrq2y13diusTvW3q1tEdqdzp2sxgsG06x8UMkD7xb2vL7Y3EnFF9%2BE73VAIBPdvW69tn572TX7uqGhksMuFh3jldWRgilTN6Xbct26Y2hc9cUF1%2BILGjEqjk9woGdXXDKyo744F5IrzN%2B3D03eVLekGTG1V1vKRL%2BjuR97P%2BJIdTevGSbmpd%2FK%2Bo15y740HlRAK2rGxtLpaE0n1JDL7lvAP7x6K8iWcxt0jCcpDyjALGZUT0AMEJndHqg1MVf9zoit8z6NGFciBSW%2BmHQnC1jL6xbBwp8vzRbqPRfTu0hzJ0xY70dsuFxovVfiZO151DP%2Bzx6l%2BdbhnJU9eI4wW9c%2F6tNQf%2F1511VKEGcB7blHbabWi1kO5cEYiITbkQz5dCikexEvuGW7ePoRJK4U0b7bSq6NRKdqcRdyT1tITlJj4Up26jHO2UhRPXar7hYnE8EFgZtLUyRcrFDxabHBlpIy7fMybET0dtAMpSAR5yV%2B0li%2Bwkr5S1dIx6KSbfOpamiC0qjzAzFW%2B9p3Hj0t0J0asBqqbVN%2FBDf6ZeP%2F%2BcQYMg2lwtaV3XFSuDpiCSxnwFHrrWWE9C3qFLQat3t6BfwDUS9jxGckHriFLfoYiZtIGabg0d853iDSZTGUEBThRCwZ%2B2iwBZ8N4%2FgvJxw4gZCqjs3uQkNVEyEdKkJYsgDqsLMhVtUFSBUbGTon4eV%2BRCkEU2juGLVgEhEf5HFwOdnHhW%2FgjkJ4Xzs1h2cBVOIAh9caee%2BC%2F3NPFj%2FR%2F8JTFZDqVjmsou6YLwRrVqS31omvs2%2BGTqnRnP08wPpwGwFWW06kzBpTZcyKQOXA3Awtcb7cvYT3stP5K4RWlJRT9YBYfp0MvbQTNTwYPCFLEHIwRtEE27os79g%2Bw4Mb7Sn2Ebq2LA1sybPuD%2FUQH69YJfeGMamynL6Ad2g%2BT92tsl%2BWkgCkggiX%2BIMsgnYD68%2FMI%2FG6GB%2BiAN6FBdHm4HFyOD5Qj8YhLKT4QJEWaH2%2BN8cHCdVkg0RyY%2FQvFECopWXQFX4LDDUE4iLth8b6guil69wrwW7JSupH4xFSvK6lUYeUPP9dXGyVj2jiCeqV5gaL33BP30gdhcoeWwVsKIEsTbuFn4HkQWUQxzYXtfeGI5R49Ve9xjcm6wwKCrpW5Brmqvuq1fdoP2o9M0Oqe9m34hvytDvZIDyWbQ07InjI%2BJVe%2FP%2B2L37nZhxuTTbuqugmVV4bd2AjhKboKRet%2FzHRJd9K7edq0fMlw%2BgsBvnGvsMstc%2FvW3C7YZ51v1%2FJvn2VfANDe8i2nmqlFrXWm3j74GKjBorLcpsadP6clgQP2%2FCxrIlpA4lJqD1VJgiqyYut1iT6e8DgZ0uM%2BkO4yfsbuBgWizxcUn2p0%2FOLr2R6Fl%2B%2Bf3%2Fntq%2BjH4C3%2FlmByswx13Ei6Nj8IPNhYiHTbX%2BDguDBiUqrIjA8COnvun94mh9LnI5aI%2FmYTwSAffEviHETAcMYAuafJk8u%2FFm04P2YwzGVfvMni8DyHHd%2FyJvDgNyxAG%2BGtFrvVYAqqr3NHLzfnJPyaLPjP7DAAA&config=H4sIAAAAAAAAA01SQW7bMBD8C8%2Bx1ThFC%2FhY2C1yiFu0aC9NETDkSqJLcgXuMo5r6O9dypTji0TNDIezQ53Unoxan9SgE0EqKzpG1q9qrfg4AJnkBlY3ikmgVnuCG2XBYNKMidSaU4ZR6KQjtZhCcfDQaXPczKqz6GrbA7C2mvXFMIE2XHa6MGDiH5iTAUlwxoXPkV0oiM6MQbMzgg5Jd0FM1PciWxpRM2w9BIh8oT%2FL8yIpH5XlPuHhvt3pADToctoc8gU8DpOqQpngU3aeXaQZStAmoL4OIPPjIAHdv9qgjOFde7zM13l8llXhSqnYTsuDixYPEi5HC62LYJU4QXyZ2N3XzfZpu%2Fsl%2FON1qkelxnEsETqIUAuem%2FumWUKp5qfcJTV77OPSY7aQnrX522zQ5GJBzRfHTQ%2FaeiBatAkjQ7RNRAtPAW0WuInwyo11xI3BMDgPtrk6clGvpKnv5Z5USRVcnAY%2FqaBj5%2BFSQTGRyuitMtapAylZAa3erVZyJx6R4E3g6GHKMpc%2Be1fHUMlzp5PPh1rgBE72U5VlLuFv3y9vPy7visZF43MBf8tvggOS9otyhRhlYXotJ8Vu%2BocqGbP3jvqFQS3lGGFFX9v%2FM47%2FAfFZW2JHAwAA

### Expected behavior

There should only ever be one constructor in a generated class.

### Actual behavior

There are two constructors in the generated class.

### Version

1.2.153

### Additional context

_No response_

Pull request:
fix(ecma/transform/decorator): insert initializer to constructor with body
<!-- 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 #3979

Patch:
diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs
index 103d475b65..218e80b948 100644
--- a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs
+++ b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs
@@ -702,41 +702,23 @@ impl Legacy {
         });
 
         if !constructor_stmts.is_empty() {
-            {
-                // Create constructors as required
-
-                let has = c
-                    .class
+            let constructor = if let Some(c) = c.class.body.iter_mut().find_map(|m| match m {
+                ClassMember::Constructor(c @ Constructor { body: Some(..), .. }) => Some(c),
+                _ => None,
+            }) {
+                c
+            } else {
+                c.class
                     .body
-                    .iter()
-                    .any(|m| matches!(m, ClassMember::Constructor(..)));
-
-                if !has {
-                    c.class
-                        .body
-                        .push(ClassMember::Constructor(default_constructor(
-                            c.class.super_class.is_some(),
-                        )))
+                    .push(ClassMember::Constructor(default_constructor(
+                        c.class.super_class.is_some(),
+                    )));
+                if let ClassMember::Constructor(c) = c.class.body.last_mut().unwrap() {
+                    c
+                } else {
+                    unreachable!()
                 }
-            }
-
-            let constructor = c
-                .class
-                .body
-                .iter_mut()
-                .filter_map(|m| match m {
-                    ClassMember::Constructor(c) => Some(c),
-                    _ => None,
-                })
-                .next()
-                .unwrap();
-
-            if constructor.body.is_none() {
-                constructor.body = Some(BlockStmt {
-                    span: DUMMY_SP,
-                    stmts: vec![],
-                });
-            }
+            };
 
             let decorate_stmts_insert_position = constructor
                 .body


Test patch:
diff --git a/crates/swc_ecma_transforms/tests/decorators.rs b/crates/swc_ecma_transforms/tests/decorators.rs
index 7ffe2454c8..433678e866 100644
--- a/crates/swc_ecma_transforms/tests/decorators.rs
+++ b/crates/swc_ecma_transforms/tests/decorators.rs
@@ -6061,6 +6061,43 @@ test!(
   }), _class);"
 );
 
+test!(
+    ts(),
+    |_| decorators(Config {
+        legacy: true,
+        emit_metadata: false,
+    }),
+    issue_3979,
+    "
+class A {
+  @foo x = 1;
+  constructor()
+  constructor() {
+    console.log(123)
+  }
+}
+",
+    r#"
+var _class, _descriptor;
+let A = ((_class = class A {
+    constructor();
+    constructor(){
+        _initializerDefineProperty(this, "x", _descriptor, this);
+        console.log(123);
+    }
+}) || _class, _descriptor = _applyDecoratedDescriptor(_class.prototype, "x", [
+    foo
+], {
+    configurable: true,
+    enumerable: true,
+    writable: true,
+    initializer: function() {
+        return 1;
+    }
+}), _class);
+"#
+);
+
 #[testing::fixture("tests/fixture/decorator/**/exec.ts")]
 fn fixture(input: PathBuf) {
     let code = fs::read_to_string(&input).expect("failed to read file");