swc-project/swc

Problems with decorators when using class validator

Closed this issue · 19 comments

Describe the bug

I'm trying to use Nest.js with Class Validator for body validation. I've created an E2E test using ts-jest that passes, and I'm using the same test with @swc/jest but it's returning a 400 as if the fields weren't being assigned.

Input code

In this link is the repository with the tests that reproduces the error. Run yarn test:e2e for running the tests with ts-jest and test:e2e:swc for @swc/jest.

The problem is at src/app.controller.ts:17.

Another problem, is that the import * as request from 'supertest' is working with ts-jest but not with @swc/jest. The error is TypeError: request is not a function

Config

Check the .swcrc file at the repo.

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

Expected behavior

  • I would expect that import * as request from 'supertest'; works with @swc/jest (here).
  • I would expect that Nest.js assigns the attributes to the User class instance (here).

Version
The version of @swc/core: 1.2.46

kdy1 commented

Do you use tsconfig.json?
I'm not sure about the root cause, but I had seen something simillar.

Yes! There is a tsconfig.json file at the repo. Is this interfering with swc in some sense? We can have a call to deeper debug if you wish.

kdy1 commented

@DanielRamosAcosta There was an option that determines interop with es modules.
I googled it and it is esModuleInterop. Can you share your config?

Which config? Swc's one or TypeScript? You have the complete repo here with the tests: https://github.com/DanielRamosAcosta/nest-class-validator-swc

kdy1 commented

Oh, I see. Thanks!
(I meant tsconfig)

I've pushed some other tests. I thought the problem was caused with class-validator, you can run the tests yarn test for running with ts-node and yarn test:swc for running with swc.

The error is:

→ E:\Documentos/my-nest-project [main ≡]› yarn test
yarn run v1.22.5
$ jest
 PASS  src/dtos/CreateUserDto.spec.ts
  CreateUserDto
    √ accepts a valid user (5 ms)
    √ rejects a user with wrong email (3 ms)
    √ rejects a user without password
    √ rejects a user without password and email (1 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        0.974 s, estimated 1 s
Ran all test suites.
Done in 1.66s.
→ E:\Documentos/my-nest-project [main ≡]› yarn test:swc
> yarn run v1.22.5
> $ jest --config ./test/jest-swc.json
 FAIL  src/dtos/CreateUserDto.spec.ts
  ● Test suite failed to run

    TypeError: decorator is not a function

      at E:\Documentos/my-nest-project/src/dtos/CreateUserDto.ts:19:16
          at Array.reduce (<anonymous>)
      at _applyDecoratedDescriptor (E:\Documentos/my-nest-project/src/dtos/CreateUserDto.ts:18:41)
      at Object.<anonymous> (E:\Documentos/my-nest-project/src/dtos/CreateUserDto.ts:50:31)
      at Object.<anonymous> (E:\Documentos/my-nest-project/src/dtos/CreateUserDto.spec.ts:3:22)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.719 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. 

@DanielRamosAcosta can you check if it works with the latest version?

I've updated the dependencies of the example repo, but it still failing, don't know exactly why, but it still seems to be a problem with decorators.

Running Unitary tests

Without SWC

yarn test
yarn run v1.22.5
$ jest
 PASS  src/dtos/CreateUserDto.spec.ts
  CreateUserDto
    √ accepts a valid user (5 ms)
    √ rejects a user with wrong email (3 ms)
    √ rejects a user without password
    √ rejects a user without password and email (1 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        2.686 s
Ran all test suites.
Done in 3.54s.

With SWC

yarn test:swc
yarn run v1.22.5
$ jest --config ./test/jest-swc.json
 FAIL  src/dtos/CreateUserDto.spec.ts
  ● Test suite failed to run

    TypeError: decorator is not a function



      at dtos/CreateUserDto.ts:19:16
          at Array.reduce (<anonymous>)
      at _applyDecoratedDescriptor (dtos/CreateUserDto.ts:18:41)
      at Object.<anonymous> (dtos/CreateUserDto.ts:50:31)
      at Object.<anonymous> (dtos/CreateUserDto.spec.ts:3:22)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.716 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Running E2E tests

Without SWC

yarn test:e2e
yarn run v1.22.5
$ jest --config ./test/jest-e2e.json
 PASS  test/app.e2e-spec.ts
  AppController (e2e)
    √ / (GET) (327 ms)
    √ / (POST) (28 ms)

  console.log
    I have the user!

      at AppService.getHello (../src/app.service.ts:16:15)

  console.log
    User { name: 'Sam 1' }

      at AppService.getHello (../src/app.service.ts:17:15)

  console.log
    createUserDto { email: 'danielramosacosta@hotmail.com', password: 'hello' }        

      at AppController.create (../src/app.controller.ts:18:13)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.564 s, estimated 7 s
Ran all test suites.
Done in 4.43s.

With SWC

yarn test:e2e:swc
yarn run v1.22.5
$ jest --config ./test/jest-e2e-swc.json
 FAIL  test/app.e2e-spec.ts
  AppController (e2e)
    √ / (GET) (344 ms)
    × / (POST) (27 ms)

  ● AppController (e2e) › / (POST)

    expected 201 "Created", got 400 "Bad Request"

      26 |     return request(app.getHttpServer())
      27 |       .post('/')
    > 28 |       .send({
         |            ^
      29 |         email: "danielramosacosta@hotmail.com",
      30 |         password: "hello"
      31 |       })

      at Object.<anonymous> (app.e2e-spec.ts:28:12)
      ----
      at Test.Object.<anonymous>.Test._assertStatus (../node_modules/supertest/lib/test.js:296:12)
      at ../node_modules/supertest/lib/test.js:80:15
      at Test.Object.<anonymous>.Test._assertFunction (../node_modules/supertest/lib/test.js:311:11)
      at Test.Object.<anonymous>.Test.assert (../node_modules/supertest/lib/test.js:201:21)
      at Server.localAssert (../node_modules/supertest/lib/test.js:159:12)

  console.log
    I have the user!

      at AppService1.getHello (../src/app.service.ts:19:25)

  console.log
    User { name: 'Sam 1' }

      at AppService1.getHello (../src/app.service.ts:20:25)
kdy1 commented

I tried fixing this, but babel also emits error for

import * as request from 'supertest';

and it's logically correct.

You need to set jsc.module.noInterop to true lke

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

If so, swc works.

Hello @kdy1, thanks a lot for your comment and great work! The noModuleInterop option worked for me. The thing is, that the main issue, still persist. I think that decorators are still not working.

These are the outputs from the example repository I gave at the top:

Unitary tests:

› yarn test; yarn test:swc
yarn run v1.22.5
$ jest
 PASS  src/dtos/CreateUserDto.spec.ts
  CreateUserDto
    √ accepts a valid user (6 ms)
    √ rejects a user with wrong email (3 ms)
    √ rejects a user without password
    √ rejects a user without password and email (1 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        0.876 s, estimated 1 s
Ran all test suites.
Done in 1.49s.
yarn run v1.22.5
$ jest --config ./test/jest-swc.json
 FAIL  src/dtos/CreateUserDto.spec.ts
  ● Test suite failed to run

    TypeError: decorator is not a function



      at dtos/CreateUserDto.ts:19:16
          at Array.reduce (<anonymous>)
      at _applyDecoratedDescriptor (dtos/CreateUserDto.ts:18:41)
      at Object.<anonymous> (dtos/CreateUserDto.ts:50:31)
      at Object.<anonymous> (dtos/CreateUserDto.spec.ts:3:22)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.63 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

And also, the e2e tests are not working with swc:

› yarn test:e2e; yarn test:e2e:swc
yarn run v1.22.5
$ jest --config ./test/jest-e2e.json
 PASS  test/app.e2e-spec.ts
  AppController (e2e)
    √ / (GET) (459 ms)
    √ / (POST) (34 ms)

  console.log
    I have the user!

      at AppService.getHello (../src/app.service.ts:16:15)

  console.log
    User { name: 'Sam 1' }

      at AppService.getHello (../src/app.service.ts:17:15)

  console.log
    createUserDto { email: 'danielramosacosta@hotmail.com', password: 'hello' }

      at AppController.create (../src/app.controller.ts:18:13)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.119 s
Ran all test suites.
Done in 2.78s.
yarn run v1.22.5
$ jest --config ./test/jest-e2e-swc.json
 FAIL  test/app.e2e-spec.ts
  AppController (e2e)
    √ / (GET) (360 ms)
    × / (POST) (26 ms)

  ● AppController (e2e) › / (POST)

    expected 201 "Created", got 400 "Bad Request"

      26 |     return request(app.getHttpServer())
      27 |       .post('/')
    > 28 |       .send({
         |            ^
      29 |         email: "danielramosacosta@hotmail.com",
      30 |         password: "hello"
      31 |       })

      at Object.<anonymous> (app.e2e-spec.ts:28:12)
      ----
      at Test.Object.<anonymous>.Test._assertStatus (../node_modules/supertest/lib/test.js:296:12)
      at ../node_modules/supertest/lib/test.js:80:15
      at Test.Object.<anonymous>.Test._assertFunction (../node_modules/supertest/lib/test.js:311:11)
      at Test.Object.<anonymous>.Test.assert (../node_modules/supertest/lib/test.js:201:21)
      at Server.localAssert (../node_modules/supertest/lib/test.js:159:12)

  console.log
    I have the user!

      at AppService1.getHello (../src/app.service.ts:19:25)

  console.log
    User { name: 'Sam 1' }

      at AppService1.getHello (../src/app.service.ts:20:25)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        1.686 s, estimated 11 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I think there might be a problem with de decorators because when I remove the decorators from the CreateUserDto it works.

I've updated all the dependencies of the example repository so you can try it out.

Thanks a lot again!

RIP21 commented

Hey, I can confirm that class-transformer is not working well either.

I have following setup

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

But, unfortunately when I do plainToClass on such an entity

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

  @ValidateNested()
  public pagination: PaginationInput
}

export class PaginationInput {
  @IsInt()
  @IsOptional()
  public skip = 0

  @IsInt()
  @IsOptional()
  public limit = 39
}

Passing down { query: '', pagination: { skip:0, limit: 20 } } it fails to convert returning empty object of GetUsersOptionsInput input. E.g. logging this to console console.log(input) -> GetUsersOptionsInput {}

If decorators will start to work as expected SWC will be a way to go.
Ah, also unsure what's wrong but for IntelliJ debugging with node -r @swc/register ./src/index.ts half broke and doesn't stop in certain files. Mind that in most of the files there are lots of lots decorators, so that's maybe why.

kdy1 commented

Thanks, but seems like lots of investigation is still required to reduce the test case and spot the error.

@RIP21 I tried to reproduce your bug trying to research a little, but I cannot reproduce it.

You can try this repo and do yarn build; node dist/examples/main.js

I tried to narrow this down (from the example by @DanielRamosAcosta) to the simplest possible use case with a DTO having a single property and a single decorator, like this:

import { IsString } from 'class-validator';

export class CreateUserDto {
  @IsString()
  id!: string;
}

With TS it gets compiled to:

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateUserDto = void 0;
const class_validator_1 = require("class-validator");
class CreateUserDto {
}
__decorate([
    class_validator_1.IsString(),
    __metadata("design:type", String)
], CreateUserDto.prototype, "id", void 0);
exports.CreateUserDto = CreateUserDto;

With SWC the result is:

"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.CreateUserDto = 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(target, property, desc) || desc;
    }, desc);
    if (context && desc.initializer !== void 0) {
        desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
        desc.initializer = undefined;
    }
    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;
var _dec = (0, _classValidator).IsString(), _dec1 = typeof Reflect !== "undefined" && typeof Reflect.metadata === "function" && Reflect.metadata("design:type", String);
let CreateUserDto = ((_class = class CreateUserDto {
    constructor(){
        _initializerDefineProperty(this, "id", _descriptor, this);
    }
}) || _class, _descriptor = _applyDecoratedDescriptor(_class.prototype, "id", [
    _dec,
    _dec1
], {
    configurable: true,
    enumerable: true,
    writable: true,
    initializer: void 0
}), _class);
exports.CreateUserDto = CreateUserDto;

Trying to console.log(CreateUserDto.prototype) just before export TS outputs {} while with SWC it is { id: undefined }.

When logging the objectOrSchemaName here in the validator I get CreateUserDto { id: 'the-actual-test-value' } with TS while only CreateUserDto {} with SWC.

Seems like with SWC the validator doesn't receive the actual value of the id!: string; property. Not sure where to look further.

kdy1 commented

Oh, thanks for reducing! I'll try fixing it.

Thanks! Not sure if it's related to #879 in any way or if it's a completely different issue. The main thing I see on the class-validator side is not receiving the runtime data of the CreateUserDto instance properties.

kdy1 commented

I found that this is fixed by inserting require('reflect-metadata'); in the top.
Also improving helper worked, and I'm going to update helpers.

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.