microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.08k stars 12.37k forks source link

Reflect Metadata not supported for TC39 decorators #55788

Open paulsmithkc opened 11 months ago

paulsmithkc commented 11 months ago

πŸ”Ž Search Terms

decorator, experimentalDecorators, emitDecoratorMetadata, TC39, reflect-metadata, Reflect.metadata(k, v)

πŸ•— Version & Regression Information

This breaks when setting disabling experimental decorators

"experimentalDecorators": false,
"emitDecoratorMetadata": false,

⏯ Playground Link

https://github.com/paulsmithkc/typescript-decorators

πŸ’» Code

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2021",
    "module": "commonjs",
    "outDir": "dist",
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

src/index.ts

import 'reflect-metadata';

function setting(defaultValue: string): any {
  function getType(target: unknown, property: string | symbol) {
    return Reflect.getMetadata('design:type', target, property);
  }
  function settingExperimental(target: unknown, property: string | symbol): void {
    console.log('settingExperimental', { defaultValue, target, property, type: getType(target, property) });
    target[property] = process.env[String(property)] || defaultValue;
    return;
  }
  function settingTC39(_target: unknown, context: ClassFieldDecoratorContext): () => string {
    return function (): string {
      console.log('settingTC39', { defaultValue, target: this, context, type: getType(this, context.name) });
      return process.env[String(context.name)] || defaultValue;
    };
  }
  return function (target: unknown, context: string | symbol | ClassFieldDecoratorContext) {
    if (typeof context !== 'object') {
      return settingExperimental(target, context);
    } else {
      return settingTC39(target, context);
    }
  };
}

class Config {
  @setting('default_1') SETTING_ONE: string;
}
const configInstance = new Config();

run with:

tsc --project tsconfig.json && node dist/index.js

πŸ™ Actual behavior

Reflect.getMetadata('design:type', target, property) returns undefined.

πŸ™‚ Expected behavior

Reflect.getMetadata('design:type', target, property) returns the type of the class field, when using Standard TC39 decorators.

Additional information about the issue

  1. When transpiling with:

    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    The decorator Reflect.metadata("design:type", type) is automatically applied to each class field.

  2. When transpiling with:

    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,

    The decorator Reflect.metadata("design:type", type) is not applied.

  3. When transpiling with:

    "experimentalDecorators": false,
    "emitDecoratorMetadata": true,

    Typescript produces the following error

    Option 'emitDecoratorMetadata' cannot be specified without specifying option 'experimentalDecorators'.
fatcerberus commented 11 months ago

AFAIU this is not implemented yet. See https://github.com/microsoft/TypeScript/issues/53461.

LabEG commented 8 months ago

Same problem. Lots of libraries use metadata about types, such as Angular, NestJs, class-validator, my library for di. Generating metadata for new decorators is very necessary.

Sayan751 commented 7 months ago

I am also seeing this issue. Here is the playground link.

I have set the emitDecoratorMetadata to true, but the emitted code does not contain the desgin:type metadata.

tsangste commented 7 months ago

I think the title of issue needs to be changed to something like TC39 Decorators does not emit metadata to Symbol.Metadata

Also reviewing the code it looks like it is not implemented here https://github.com/microsoft/TypeScript/blob/v5.3.3/src/compiler/transformers/ts.ts#L1064

artberri commented 6 months ago

I also thing that emitting type metadata is very necessary, but now that both Decorators and Decorator Metadata have achieved Stage 3 within TC39 and have been implemented in TS >=5.2, the API proposed in the reflect-metadata library is no longer being considered for standardization.

This is why I think that is better to expose design-time type information in the new TC39 decorator metadata when emitDecoratorMetadata: true.

bcheidemann commented 5 months ago

I also thing that emitting type metadata is very necessary, but now that both Decorators and Decorator Metadata have achieved Stage 3 within TC39 and have been implemented in TS >=5.2, the API proposed in the reflect-metadata library is no longer being considered for standardization.

This is why I think that is better to expose design-time type information in the new TC39 decorator metadata when emitDecoratorMetadata: true.

Inclined to agree @artberri. I've raised a PR to serve as a PoC / proposal for how this could be implemented. A lot of the foundations were already set, so this barely required any changes.

ianzone commented 4 months ago

Mark https://github.com/nestjs/nest/issues/11414