Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class transformer not working with swc #2117

Closed
DanielRamosAcosta opened this issue Aug 20, 2021 · 16 comments · Fixed by #3459
Closed

Class transformer not working with swc #2117

DanielRamosAcosta opened this issue Aug 20, 2021 · 16 comments · Fixed by #3459

Comments

@DanielRamosAcosta
Copy link

DanielRamosAcosta commented Aug 20, 2021

Describe the bug

When applying the plainToClass function to a class defined with class-validator, it returns the class with no attributes

Input code

Here is a repo with a test reproducing the error

const usersOptionsInput = plainToClass(GetUsersOptionsInput, {
  query: "Query",
  pagination: { skip: 0, limit: 20 },
});

expect(usersOptionsInput.query).toEqual("Query"); // error

Config

You can also find the config at the repo reproducing the error

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "target": "es2018"
  },
  "module": {
    "type": "commonjs",
    "noInterop": true
  }
}

Expected behavior
I would expect the instance of the class to have all the defined attributes.

Version
The version of @swc/core: 1.2.80

Additional context

Just one more time, I created this repo in order to reproduce the error

This error is related to #1362

@tonivj5
Copy link

tonivj5 commented Aug 26, 2021

I'm coming from #1362 and this doesn't work #1362 (comment)

Good repro btw @DanielRamosAcosta!

@tonivj5
Copy link

tonivj5 commented Aug 26, 2021

I've been checking the generated code, and there's a problematic part.

Minimal repro

  • Source
import { IsOptional, IsString } from 'class-validator';

export class GetUsersOptionsInput {
  @IsString()
  @IsOptional()
  public query?: string;
  // it works
  // public query?: string = undefined;
}
  • Generated
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.GetUsersOptionsInput = void 0;
var _classValidator = require("class-validator");
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
    var desc = {
    };
    Object.keys(descriptor).forEach(function(key) {
        desc[key] = descriptor[key];
    });
    desc.enumerable = !!desc.enumerable;
    desc.configurable = !!desc.configurable;
    if ("value" in desc || desc.initializer) {
        desc.writable = true;
    }
    desc = decorators.slice().reverse().reduce(function(desc, decorator) {
        return decorator ? decorator(target, property, desc) || desc : desc;
    }, desc);
    if (context && desc.initializer !== void 0) {
        desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
        desc.initializer = undefined;
    }
    // IF NO INITILIZAER, THE RETURNED DESCRIPTOR IS NULL
    if (desc.initializer === void 0) {
        Object.defineProperty(target, property, desc);
        desc = null;
    }
    return desc;
}
function _initializerDefineProperty(target, property, descriptor, context) {
    if (!descriptor) return;
    Object.defineProperty(target, property, {
        enumerable: descriptor.enumerable,
        configurable: descriptor.configurable,
        writable: descriptor.writable,
        value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
    });
}
var _class, _descriptor, _dec, _dec1, _dec2;
let GetUsersOptionsInput = ((_class = class GetUsersOptionsInput1 {
    constructor(){
        _initializerDefineProperty(this, "query", _descriptor/*THIS DESCRIPTOR IS NULL*/, this);
    }
}) || _class, _dec = (0, _classValidator).IsString(), _dec1 = (0, _classValidator).IsOptional(), _dec2 = typeof Reflect !== "undefined" && typeof Reflect.metadata === "function" && Reflect.metadata("design:type", String), _descriptor = _applyDecoratedDescriptor(_class.prototype, "query", [
    _dec,
    _dec1,
    _dec2
], {
    configurable: true,
    enumerable: true,
    writable: true,
    initializer: void 0 // THE INITIALIZER IS UNDEFINED
}), _class);
exports.GetUsersOptionsInput = GetUsersOptionsInput;

This is the problematic part from _applyDecoratedDescriptor, this branch is executed by initializer: void 0

    if (desc.initializer === void 0) {
        Object.defineProperty(target, property, desc);
        desc = null;
    }

When the property decorated has not initializer the descriptor decorator returned is null. If we remove that if, the repro code works and tests pass.

I don't know exactly why that check is neccesary, but there is the problem.

I hope it helps @kdy1! Excellent project btw 🆙

@DanielRamosAcosta
Copy link
Author

Great work @tonivj5! i tried diving into the generated code but I didn't reach any conclusions, thanks for your insight!

@kdy1 kdy1 modified the milestones: v1.2.85, v1.2.86, v1.2.87 Sep 1, 2021
@kdy1 kdy1 modified the milestones: v1.2.87, v1.2.88, v1.2.89, v1.2.90 Sep 11, 2021
@unlight
Copy link

unlight commented Sep 17, 2021

Issue on class-transformer side typestack/class-transformer#796 I suppose invalid, and it should be fixed in swc.

@kdy1 kdy1 modified the milestones: v1.2.90, v1.2.91, v1.2.92 Sep 21, 2021
@tonivj5
Copy link

tonivj5 commented Sep 24, 2021

@unlight if class-validator works perfect with tsc and fail with swc. I think it's a problem of swc handling decorators...

@kdy1
Copy link
Member

kdy1 commented Sep 24, 2021

@tonivj5 Did you try importing reflect-metadata?

@tonivj5
Copy link

tonivj5 commented Sep 24, 2021

@kdy1 yep! It doesn't work.

This code does not work:

import "reflect-metadata";
import { IsOptional, IsString } from "class-validator";

export class GetUsersOptionsInput {
  @IsString()
  @IsOptional()
  public query?: string;
}

This code does work:

import "reflect-metadata";
import { IsOptional, IsString } from "class-validator";

export class GetUsersOptionsInput {
  @IsString()
  @IsOptional()
  public query?: string = undefined;
}

I did a deeper research here #2117 (comment)

@kdy1 kdy1 removed this from the v1.2.92 milestone Sep 25, 2021
@kdy1 kdy1 modified the milestones: v1.2.125, v1.2.126, v1.2.127, v1.2.128 Jan 2, 2022
@minardimedia
Copy link

There is also a similar issue when upgrading to Next js 12, they use SWC and support legacy decorators but the @expose() and @exclude() decorators also seem to be falling. they return undefined attributes.

@kdy1 kdy1 modified the milestones: v1.2.128, v1.2.129, v1.2.130, v1.2.131 Jan 11, 2022
@kdy1 kdy1 modified the milestones: v1.2.135, v1.2.136 Jan 27, 2022
@ismoiliy98
Copy link

Coming from: https://stackoverflow.com/questions/70956406/plaintoclass-from-class-transform-converts-incorrecly
Any progress or possible workaround on this issue? Tried to use esbuild and plainToClass method works correctly.

@swc-bot
Copy link
Collaborator

swc-bot commented Oct 18, 2022

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@swc-project swc-project locked as resolved and limited conversation to collaborators Oct 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

Successfully merging a pull request may close this issue.