Repository: swc-project/swc (rust)

Issue:
Decorator design:type for union types
**Describe the bug**
When you have a union type on a decorated property the design:type is set to `void 0`.
TSC would set the type to `Object` instead.

**Input code**
```

class Xpto {
  @Decorator()
  prop!: string | number;
}

function Decorator() {
  return function (...args) {};
}

```
**Config**
```json
{
  "module": {
    "type": "commonjs"
  },
  "jsc": {
    "target": "es2018",
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "dynamicImport": false
    },
    "transform": {
      "legacyDecorator": false,
      "decoratorMetadata": true
    }
  }
}

```

**Current behavior**

TSC: 
```
__decorate([
    Decorator(),
   __metadata("design:type", Object)
], Xpto.prototype, "prop", void 0);
```

SWC: 
```
Reflect.metadata("design:type", void 0)
```

**Expected behavior**
SWC: 
```
Reflect.metadata("design:type", Object)
```


Pull request:
Fix #2461: Update decorator design:type so the outputted metadata for union types is consistent with Typescript
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

Fixes #2461.

Updates the decorator `design:type` so the outputted metadata for union types is consistent with Typescript

This is my first time using `rust` so 🤞 the code isn't too horrible 😬 

**BREAKING CHANGE:**

Nothing.

**Related issue (if exists):**

#2461

Patch:
diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs
index 07aec1e3b2..50835781a1 100644
--- a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs
+++ b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs
@@ -224,7 +224,6 @@ impl Fold for Metadata<'_> {
         if p.type_ann.is_none() {
             return p;
         }
-
         if let Some(name) = p
             .type_ann
             .as_ref()
@@ -259,7 +258,6 @@ impl Fold for Metadata<'_> {
             serialize_type(self.class_name, p.type_ann.as_ref()).as_arg(),
         );
         p.decorators.push(dec);
-
         p
     }
 }
@@ -401,14 +399,12 @@ fn serialize_type(class_name: Option<&Ident>, param: Option<&TsTypeAnn>) -> Expr
 
     fn serialize_type_list(class_name: &str, types: &[Box<TsType>]) -> Expr {
         let mut u = None;
-
         for ty in types {
             // Skip parens if need be
             let ty = match &**ty {
                 TsType::TsParenthesizedType(ty) => &ty.type_ann,
                 _ => ty,
             };
-
             match &**ty {
                 // Always elide `never` from the union/intersection if possible
                 TsType::TsKeywordType(TsKeywordType {
@@ -428,7 +424,7 @@ fn serialize_type(class_name: Option<&Ident>, param: Option<&TsTypeAnn>) -> Expr
                     kind: TsKeywordTypeKind::TsUndefinedKeyword,
                     ..
                 }) => {
-                    continue;
+                    return quote_ident!("Object").into();
                 }
 
                 _ => {}
@@ -469,7 +465,10 @@ fn serialize_type(class_name: Option<&Ident>, param: Option<&TsTypeAnn>) -> Expr
             }
         }
 
-        *undefined(DUMMY_SP)
+        match u {
+            Some(i) => i,
+            _ => quote_ident!("Object").into(),
+        }
     }
 
     fn serialize_type_node(class_name: &str, ty: &TsType) -> Expr {


Test patch:
diff --git a/crates/swc_ecma_transforms/tests/decorators.rs b/crates/swc_ecma_transforms/tests/decorators.rs
index aae166a74f..f49d4e51fb 100644
--- a/crates/swc_ecma_transforms/tests/decorators.rs
+++ b/crates/swc_ecma_transforms/tests/decorators.rs
@@ -5043,10 +5043,10 @@ let Sample = _class = _dec11(_class = _dec10(_class = _dec9(((_class = class Sam
 }, _dec1 = typeof Reflect !== "undefined" && typeof Reflect.metadata === "function" && Reflect.metadata("design:type", Function), _dec2 = typeof Reflect !== "undefined" && typeof Reflect.metadata === "function" && Reflect.metadata("design:paramtypes", [
     typeof Symbol === "undefined" ? Object : Symbol,
     Object,
+    Object,
     void 0,
-    void 0,
-    void 0,
-    void 0,
+    String,
+    Object,
     typeof Maybe === "undefined" ? Object : Maybe,
     Object,
     Object,
@@ -5054,7 +5054,7 @@ let Sample = _class = _dec11(_class = _dec10(_class = _dec9(((_class = class Sam
     Array,
     void 0,
     Boolean,
-    void 0,
+    Object,
     Object,
     typeof Object === "undefined" ? Object : Object,
     Object,
@@ -6002,3 +6002,35 @@ test_exec!(
     }
     "
 );
+
+test!(
+    ts(),
+    |_| decorators(Config {
+        legacy: true,
+        emit_metadata: true,
+    }),
+    issue_2461_decorator_design_type_for_union_types,
+    "const ThingDecorator: PropertyDecorator = () => {}
+
+  class Thing {
+    @ThingDecorator
+    thing?: string | null
+  }",
+    "var _class, _descriptor, _dec;
+  const ThingDecorator: PropertyDecorator = ()=>{ };
+  let Thing = ((_class = class Thing {
+    constructor(){
+        _initializerDefineProperty(this, \"thing\", _descriptor, this);
+    }
+  }) || _class, _dec = typeof Reflect !== \"undefined\" && typeof Reflect.metadata === \
+     \"function\" && Reflect.metadata(\"design:type\", Object), _descriptor = \
+     _applyDecoratedDescriptor(_class.prototype, \"thing\", [
+      ThingDecorator,
+    _dec
+  ], {
+      configurable: true,
+      enumerable: true,
+      writable: true,
+      initializer: void 0
+  }), _class);"
+);