Skip to content

Commit 3ef0be0

Browse files
committed
Bugfix: modern decorator support
1 parent e2f6800 commit 3ef0be0

14 files changed

Lines changed: 286 additions & 31 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [7.2.1] - 2024-02-18
9+
10+
### Fixed
11+
12+
- TypeScript 5 decorators do not work.
13+
- Circular loop when using multiple inheritance through the decorator type options.
14+
815
## [7.2.0] - 2023-10-18
916

1017
### Added

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ TypeScript needs to run with the `experimentalDecorators` and `emitDecoratorMeta
183183

184184
_If you want additional type-safety and reduced syntax you may wish to install [reflect-metadata](https://github.com/rbuckton/reflect-metadata). This step is on your choice and fully optional. When installed it must be available globally to work. This can usually be done with `import 'reflect-metadata';` in your main index file._
185185

186+
Starting from TypeScript 5 we also support modern decorator syntax. However because parameter decorations with the modern syntax at the time of this writing are not supported - you will not be able to use `Inject` decorator provided by the library as well as `reflect-metadata` package when decide to use modern syntax. We are going to add support as soon as it will be provided to us by the TypeScript. If you need `Inject` decorator and enabling legacy decorators support is not an option - you can use declarative configuration.
187+
186188
## How it works?
187189

188190
It defines configuration for each object which you are going to serialize or deserialize and uses this configuration to process data of your choice. There are two possible ways to define a configuration:

spec/property.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Property, TypeManager, TypeSerializer } from '../src';
1+
import { Property, Type, TypeManager, TypeSerializer } from '../src';
22

3+
@Type()
34
class User
45
{
56
@Property() public name?: string;

spec/use-cases/complex-polymorphic-types.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Message
77
@Property(() => Messageable) public reciever: Messageable
88
@Property(String) public content: string
99

10-
public constructor (content: string, sender: Messageable, reciever: Messageable)
10+
public constructor(content: string, sender: Messageable, reciever: Messageable)
1111
{
1212
this.content = content;
1313
this.sender = sender;
@@ -22,7 +22,7 @@ class Status
2222
{
2323
@Property(String) status: string;
2424

25-
public constructor (status: string)
25+
public constructor(status: string)
2626
{
2727
this.status = status;
2828

@@ -31,6 +31,7 @@ class Status
3131

3232
}
3333

34+
@Type()
3435
abstract class Messageable
3536
{
3637
@Property(Array, [Message]) messages!: Message[];
@@ -56,7 +57,7 @@ class Chat extends Messageable implements HasTitle
5657
{
5758
@Property(String) title: string;
5859

59-
public constructor (title: string)
60+
public constructor(title: string)
6061
{
6162
super();
6263

@@ -75,7 +76,7 @@ class User extends Statusable implements Messageable, HasTitle
7576
@Property(String) title: string;
7677
@Property(Array, [Message]) messages!: Message[];
7778

78-
constructor (title: string)
79+
public constructor(title: string)
7980
{
8081
super();
8182

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export * from './generic-argument';
2222
export * from './generic-metadata-resolver';
2323
export * from './generic-metadata';
2424
export * from './generic-structure';
25+
export * from './inject-decorator';
2526
export * from './inject-index';
2627
export * from './inject-internals';
2728
export * from './inject-metadata';
@@ -33,6 +34,7 @@ export * from './log-level';
3334
export * from './log';
3435
export * from './metadata';
3536
export * from './naming-convention';
37+
export * from './property-decorator';
3638
export * from './property-extension-metadata-ctor-set-key';
3739
export * from './property-extension-metadata-ctor';
3840
export * from './property-extension-metadata';
@@ -58,6 +60,7 @@ export * from './type-configuration';
5860
export * from './type-context-entry';
5961
export * from './type-context';
6062
export * from './type-ctor';
63+
export * from './type-decorator';
6164
export * from './type-extension-metadata-ctor-set-key';
6265
export * from './type-extension-metadata-ctor';
6366
export * from './type-extension-metadata';
@@ -74,5 +77,6 @@ export * from './type-name';
7477
export * from './type-options-base';
7578
export * from './type-options';
7679
export * from './type-resolver';
80+
export * from './type-scope';
7781
export * from './type';
7882
export * from './unknown';

src/inject-decorator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Inject decorator.
3+
*
4+
* @type {InjectDecorator}
5+
*/
6+
export type InjectDecorator = (target: any, propertyName: string | symbol | undefined, injectIndex: number) => void;

src/inject.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { isFunction, isNumber, isObject, isString, isUndefined } from 'lodash';
22
import { isCtorFunction } from './functions/is-ctor-function';
33
import { nameOf } from './functions/name-of';
4+
import { InjectDecorator } from './inject-decorator';
45
import { InjectOptions } from './inject-options';
56
import { TypeFn } from './type-fn';
67
import { TypeManager } from './type-manager';
@@ -10,9 +11,9 @@ import { TypeManager } from './type-manager';
1011
*
1112
* @param {TypeFn<TType>|InjectOptions<TType>|string} x Type function, inject options or parameter key from type context.
1213
*
13-
* @returns {ParameterDecorator} Parameter decorator.
14+
* @returns {InjectDecorator} Inject decorator.
1415
*/
15-
export function Inject<TType>(x: TypeFn<TType> | InjectOptions<TType> | string): ParameterDecorator
16+
export function Inject<TType>(x: TypeFn<TType> | InjectOptions<TType> | string): InjectDecorator
1617
{
1718
const injectOptions = (isObject(x) ? x : {}) as InjectOptions<TType>;
1819

@@ -25,8 +26,8 @@ export function Inject<TType>(x: TypeFn<TType> | InjectOptions<TType> | string):
2526
{
2627
injectOptions.typeFn = x as TypeFn<TType>;
2728
}
28-
29-
return function (target: any, propertyName: string | symbol, injectIndex: number): void
29+
30+
return function (target: any, propertyName: string | symbol | undefined, injectIndex: number): void
3031
{
3132
if (!isCtorFunction(target))
3233
{

src/property-decorator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Property decorator.
3+
*
4+
* @type {PropertyDecorator}
5+
*/
6+
export type PropertyDecorator = (target: any, context: any) => any;

src/property.ts

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { isArray, isFunction, isObject, isString, isSymbol, merge } from 'lodash';
1+
import { isArray, isFunction, isObject, isString, isSymbol, isUndefined, merge } from 'lodash';
22
import { isCtorFunction } from './functions/is-ctor-function';
33
import { nameOf } from './functions/name-of';
44
import { GenericArgument } from './generic-argument';
5+
import { PropertyDecorator } from './property-decorator';
56
import { PropertyOptions } from './property-options';
67
import { TypeArgument } from './type-argument';
78
import { TypeFn } from './type-fn';
@@ -54,27 +55,71 @@ export function Property<TType>(
5455
propertyOptions.typeArgument = x;
5556
}
5657

57-
return function (target: any, propertyName: string | symbol): void
58+
return function (target: any, context: any): any
5859
{
59-
if (isCtorFunction(target))
60+
// Modern decorator has a dynamic target which is dependent from where decorator
61+
// is applied (target), context as a second parameter (context) and optional
62+
// resolver like return type.
63+
if (isObject(context) && context.hasOwnProperty('kind'))
6064
{
61-
throw new Error(`${nameOf(target)}.${String(propertyName)}: property decorator cannot be applied to a static member.`);
62-
}
65+
const decoratorContext = context as any;
66+
const kind = decoratorContext.kind;
67+
const propertyName = decoratorContext.name;
6368

64-
if (isSymbol(propertyName))
65-
{
66-
throw new Error(`${nameOf(target.constructor)}.${String(propertyName)}: property decorator cannot be applied to a symbol.`);
69+
if (kind === 'method' || kind === 'class')
70+
{
71+
throw new Error(`${String(propertyName)}: property decorator cannot be applied to a method or a class.`);
72+
}
73+
74+
if (isUndefined(propertyName))
75+
{
76+
throw new Error(`${String(propertyName)}: property decorator cannot be applied to undefined values.`);
77+
}
78+
79+
if (isSymbol(propertyName))
80+
{
81+
throw new Error(`${String(propertyName)}: property decorator cannot be applied to a symbol.`);
82+
}
83+
84+
TypeManager.typeScope.addPropertyOptions(propertyName, propertyOptions);
85+
86+
return;
6787
}
6888

69-
if (isFunction(target[propertyName]))
89+
// Legacy decorator has class reference as a first parameter (target), property name
90+
// or symbol as a second parameter (context) and no return type.
91+
if (isObject(target) && (isString(context) || isSymbol(context)))
7092
{
71-
throw new Error(`${nameOf(target.constructor)}.${String(propertyName)}: property decorator cannot be applied to a method.`);
72-
}
93+
const legacyTarget = target as any;
94+
const propertyName = context as string | symbol | undefined;
95+
96+
if (isCtorFunction(legacyTarget))
97+
{
98+
throw new Error(`${nameOf(legacyTarget)}.${String(propertyName)}: property decorator cannot be applied to a static member.`);
99+
}
73100

74-
const typeFn = target.constructor as TypeFn<any>;
75-
76-
TypeManager.configureTypeMetadata(typeFn).configurePropertyMetadata(propertyName, propertyOptions);
101+
if (isUndefined(propertyName))
102+
{
103+
throw new Error(`${nameOf(legacyTarget)}.${String(propertyName)}: property decorator cannot be applied to undefined values.`);
104+
}
105+
106+
if (isSymbol(propertyName))
107+
{
108+
throw new Error(`${nameOf(legacyTarget.constructor)}.${String(propertyName)}: property decorator cannot be applied to a symbol.`);
109+
}
110+
111+
if (isFunction(legacyTarget[propertyName]))
112+
{
113+
throw new Error(`${nameOf(legacyTarget.constructor)}.${String(propertyName)}: property decorator cannot be applied to a method.`);
114+
}
115+
116+
const typeFn = legacyTarget.constructor as TypeFn<any>;
117+
118+
TypeManager.configureTypeMetadata(typeFn).configurePropertyMetadata(propertyName, propertyOptions);
119+
120+
return;
121+
}
77122

78-
return;
123+
throw new Error(`Property decorator was not able to detect correct resolver for the following target [${target}] and context [${context}].`);
79124
};
80125
}

src/type-decorator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Type decorator.
3+
*
4+
* @type {TypeDecorator}
5+
*/
6+
export type TypeDecorator = (target: any, context?: any) => any;

0 commit comments

Comments
 (0)