Skip to content

Commit 1371f6b

Browse files
committed
✨ Move param dec to class
The parameter decorator is now not applied on the class after the class declaration, but instead added as class decorator itself. This is imitated from TypeScript compilation. It leads to more consistency between compilers and increases compatibility of libraries that depend on this behavior. Furthermore the `design:type` decorator is also added to each decorated class (with value `Function`, as TSC does as well).
1 parent 8ab68d7 commit 1371f6b

File tree

8 files changed

+145
-86
lines changed

8 files changed

+145
-86
lines changed

README.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,3 @@ export const lazyInject = fixPropertyDecorator(originalLazyInject);
143143
concrete values (like classes, etc.). In order to resolve this, we emit the
144144
following: `typeof Type === 'undefined' ? Object : Type`. The code has the
145145
advantage of not throwing. If you know a better way to do this, let me know!
146-
- Parameter decorators are emitted right _after_ the `ClassDeclaration` node,
147-
like:
148-
149-
```js
150-
let A = (/* ... */)
151-
Inject()(A.prototype, 'methodName', 1);
152-
```
153-
154-
I'm not sure if this can cause issue with scoping, if you get in troubles with
155-
this kind of decorators, please open an issue.

src/metadata/metadataVisitor.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@ import { NodePath } from '@babel/traverse';
22
import { types as t } from '@babel/core';
33
import { serializeType } from './serializeType';
44

5+
function createMetadataDesignDecorator(
6+
design: 'design:type' | 'design:paramtypes' | 'design:returntype' | 'design:typeinfo',
7+
typeArg: t.Expression | t.SpreadElement | t.JSXNamespacedName | t.ArgumentPlaceholder
8+
): t.Decorator {
9+
return t.decorator(
10+
t.callExpression(
11+
t.memberExpression(
12+
t.identifier('Reflect'),
13+
t.identifier('metadata')
14+
),
15+
[
16+
t.stringLiteral(design),
17+
typeArg
18+
]
19+
)
20+
)
21+
}
22+
523
export function metadataVisitor(
624
classPath: NodePath<t.ClassDeclaration>,
725
path: NodePath<t.ClassProperty | t.ClassMethod>
@@ -17,21 +35,22 @@ export function metadataVisitor(
1735
if (!decorators || decorators.length === 0) return;
1836

1937
decorators!.push(
20-
t.decorator(
21-
t.callExpression(
22-
t.memberExpression(
23-
t.identifier('Reflect'),
24-
t.identifier('metadata')
25-
),
26-
[
27-
t.stringLiteral('design:paramtypes'),
28-
t.arrayExpression(
29-
field.params.map(param => serializeType(classPath, param))
30-
)
31-
]
38+
createMetadataDesignDecorator(
39+
'design:type',
40+
t.identifier('Function')
41+
)
42+
);
43+
decorators!.push(
44+
createMetadataDesignDecorator(
45+
'design:paramtypes',
46+
t.arrayExpression(
47+
field.params.map(param => serializeType(classPath, param))
3248
)
3349
)
3450
);
51+
// Hint: `design:returntype` could also be implemented here, although this seems
52+
// quite complicated to achieve without the TypeScript compiler.
53+
// See https://github.com/microsoft/TypeScript/blob/f807b57356a8c7e476fedc11ad98c9b02a9a0e81/src/compiler/transformers/ts.ts#L1315
3554
break;
3655

3756
case 'ClassProperty':
@@ -44,14 +63,9 @@ export function metadataVisitor(
4463
return;
4564

4665
field.decorators!.push(
47-
t.decorator(
48-
t.callExpression(
49-
t.memberExpression(
50-
t.identifier('Reflect'),
51-
t.identifier('metadata')
52-
),
53-
[t.stringLiteral('design:type'), serializeType(classPath, field)]
54-
)
66+
createMetadataDesignDecorator(
67+
'design:type',
68+
serializeType(classPath, field)
5569
)
5670
);
5771
break;

src/parameter/parameterVisitor.ts

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
11
import { NodePath } from '@babel/traverse';
22
import { types as t } from '@babel/core';
33

4+
/**
5+
* Helper function to create a field/class decorator from a parameter decorator.
6+
* Field/class decorators get three arguments: the class, the name of the method
7+
* (or 'undefined' in the case of the constructor) and the position index of the
8+
* parameter in the argument list.
9+
* Some of this information, the index, is only available at transform time, and
10+
* has to be stored. The other arguments are part of the decorator signature and
11+
* will be passed to the decorator anyway. But the decorator has to be called
12+
* with all three arguments at runtime, so this creates a function wrapper, which
13+
* takes the target and the key, and adds the index to it.
14+
*
15+
* Inject() becomes function(target, key) { return Inject()(target, key, 0) }
16+
*
17+
* @param paramIndex the index of the parameter inside the function call
18+
* @param decoratorExpression the decorator expression, the return object of SomeParameterDecorator()
19+
* @param isConstructor indicates if the key should be set to 'undefined'
20+
*/
21+
function createParamDecorator(
22+
paramIndex: number,
23+
decoratorExpression: t.Expression,
24+
isConstructor: boolean = false
25+
) {
26+
return t.decorator(
27+
t.functionExpression(
28+
null, // anonymous function
29+
[t.identifier('target'), t.identifier('key')],
30+
t.blockStatement([
31+
t.returnStatement(
32+
t.callExpression(decoratorExpression, [
33+
t.identifier('target'),
34+
t.identifier(isConstructor ? 'undefined' : 'key'),
35+
t.numericLiteral(paramIndex)
36+
])
37+
)
38+
])
39+
)
40+
);
41+
}
42+
443
export function parameterVisitor(
544
classPath: NodePath<t.ClassDeclaration>,
645
path: NodePath<t.ClassMethod> | NodePath<t.ClassProperty>
@@ -10,7 +49,6 @@ export function parameterVisitor(
1049
if (path.node.key.type !== 'Identifier') return;
1150

1251
const methodPath = path as NodePath<t.ClassMethod>;
13-
const methodName = path.node.key.name;
1452
const params = methodPath.get('params') || [];
1553

1654
params.slice().forEach(function(param) {
@@ -24,30 +62,32 @@ export function parameterVisitor(
2462

2563
if (identifier == null) return;
2664

27-
let resultantDecorator;
65+
let resultantDecorator: t.Decorator | undefined;
2866

2967
((param.node as t.Identifier).decorators || [])
3068
.slice()
3169
.forEach(function(decorator) {
32-
const className = classPath.node!.id!.name;
33-
3470
if (methodPath.node.kind === 'constructor') {
35-
resultantDecorator = t.callExpression(decorator.expression, [
36-
t.identifier(className),
37-
t.identifier('undefined'),
38-
t.numericLiteral(param.key as number)
39-
]);
71+
resultantDecorator = createParamDecorator(
72+
param.key as number,
73+
decorator.expression,
74+
true
75+
);
76+
if (!classPath.node.decorators) {
77+
classPath.node.decorators = [];
78+
}
79+
classPath.node.decorators.push(resultantDecorator);
4080
} else {
41-
resultantDecorator = t.callExpression(decorator.expression, [
42-
t.identifier(`${className}.prototype`),
43-
t.stringLiteral(methodName),
44-
t.numericLiteral(param.key as number)
45-
]);
81+
resultantDecorator = createParamDecorator(
82+
param.key as number,
83+
decorator.expression,
84+
false
85+
);
86+
if (!methodPath.node.decorators) {
87+
methodPath.node.decorators = [];
88+
}
89+
methodPath.node.decorators.push(resultantDecorator);
4690
}
47-
48-
const expression = t.expressionStatement(resultantDecorator);
49-
50-
classPath.insertAfter(expression);
5191
});
5292

5393
if (resultantDecorator) {
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
var _dec, _dec2, _class, _class2;
1+
var _dec, _dec2, _dec3, _dec4, _dec5, _class, _class2;
22

33
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; }
44

5-
let MyClass = (_dec = Reflect.metadata("design:paramtypes", [typeof Generic === "undefined" ? Object : Generic, typeof Generic === "undefined" ? Object : Generic]), _dec2 = Reflect.metadata("design:paramtypes", [typeof Inter === "undefined" ? Object : Inter, typeof InterGen === "undefined" ? Object : InterGen]), Decorate(_class = _dec(_class = (_class2 = class MyClass {
5+
let MyClass = (_dec = Reflect.metadata("design:type", Function), _dec2 = Reflect.metadata("design:paramtypes", [typeof Generic === "undefined" ? Object : Generic, typeof Generic === "undefined" ? Object : Generic]), _dec3 = function (target, key) {
6+
return Arg()(target, key, 1);
7+
}, _dec4 = Reflect.metadata("design:type", Function), _dec5 = Reflect.metadata("design:paramtypes", [typeof Inter === "undefined" ? Object : Inter, typeof InterGen === "undefined" ? Object : InterGen]), Decorate(_class = _dec(_class = _dec2(_class = (_class2 = class MyClass {
68
constructor(generic, generic2) {
79
this.generic = generic;
810
}
911

1012
method(generic, generic2) {}
1113

12-
}, (_applyDecoratedDescriptor(_class2.prototype, "method", [Run, _dec2], Object.getOwnPropertyDescriptor(_class2.prototype, "method"), _class2.prototype)), _class2)) || _class) || _class);
13-
Arg()(MyClass.prototype, "method", 1);
14+
}, (_applyDecoratedDescriptor(_class2.prototype, "method", [Run, _dec3, _dec4, _dec5], Object.getOwnPropertyDescriptor(_class2.prototype, "method"), _class2.prototype)), _class2)) || _class) || _class) || _class);

test/__fixtures__/nest-injection/output.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _class, _class2, _descriptor, _descriptor2, _temp;
1+
var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _class, _class2, _descriptor, _descriptor2, _temp;
22

33
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 }); }
44

@@ -7,7 +7,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
77
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); }
88

99
import { AppService } from './app.service';
10-
export let AppController = (_dec = Controller(), _dec2 = Reflect.metadata("design:paramtypes", [typeof AppService === "undefined" ? Object : AppService]), _dec3 = Inject(), _dec4 = Reflect.metadata("design:type", typeof AppService === "undefined" ? Object : AppService), _dec5 = Inject(), _dec6 = Reflect.metadata("design:type", typeof AppService === "undefined" ? Object : AppService), _dec7 = Get(), _dec8 = Reflect.metadata("design:paramtypes", []), _dec(_class = _dec2(_class = (_class2 = (_temp = class AppController {
10+
export let AppController = (_dec = Controller(), _dec2 = Reflect.metadata("design:type", Function), _dec3 = Reflect.metadata("design:paramtypes", [typeof AppService === "undefined" ? Object : AppService]), _dec4 = Inject(), _dec5 = Reflect.metadata("design:type", typeof AppService === "undefined" ? Object : AppService), _dec6 = Inject(), _dec7 = Reflect.metadata("design:type", typeof AppService === "undefined" ? Object : AppService), _dec8 = Get(), _dec9 = Reflect.metadata("design:type", Function), _dec10 = Reflect.metadata("design:paramtypes", []), _dec(_class = _dec2(_class = _dec3(_class = (_class2 = (_temp = class AppController {
1111
constructor(appService) {
1212
this.appService = appService;
1313

@@ -20,14 +20,14 @@ export let AppController = (_dec = Controller(), _dec2 = Reflect.metadata("desig
2020
return this.appService.getHello();
2121
}
2222

23-
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, "appService", [_dec3, _dec4], {
23+
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, "appService", [_dec4, _dec5], {
2424
configurable: true,
2525
enumerable: true,
2626
writable: true,
2727
initializer: null
28-
}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "appService2", [_dec5, _dec6], {
28+
}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "appService2", [_dec6, _dec7], {
2929
configurable: true,
3030
enumerable: true,
3131
writable: true,
3232
initializer: null
33-
}), _applyDecoratedDescriptor(_class2.prototype, "getHello", [_dec7, _dec8], Object.getOwnPropertyDescriptor(_class2.prototype, "getHello"), _class2.prototype)), _class2)) || _class) || _class);
33+
}), _applyDecoratedDescriptor(_class2.prototype, "getHello", [_dec8, _dec9, _dec10], Object.getOwnPropertyDescriptor(_class2.prototype, "getHello"), _class2.prototype)), _class2)) || _class) || _class) || _class);
Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
var _dec, _dec2, _class, _dec3, _dec4, _dec5, _class2, _class3;
1+
var _dec, _dec2, _dec3, _class, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _class2, _class3, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _class4, _class5;
22

33
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; }
44

55
class Injected {}
66

7-
class MyClass {
7+
let MyClass = (_dec = function (target, key) {
8+
return inject()(target, undefined, 0);
9+
}, _dec2 = Reflect.metadata("design:type", Function), _dec3 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected]), _dec(_class = _dec2(_class = _dec3(_class = class MyClass {
810
constructor(parameter) {}
911

10-
}
11-
12-
inject()(MyClass, undefined, 0);
13-
let MyOtherClass = (_dec = decorate('named'), _dec2 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Schema === "undefined" ? Object : Schema]), (_class = class MyOtherClass {
12+
}) || _class) || _class) || _class);
13+
let MyOtherClass = (_dec4 = function (target, key) {
14+
return inject()(target, undefined, 0);
15+
}, _dec5 = function (target, key) {
16+
return inject('KIND')(target, undefined, 1);
17+
}, _dec6 = Reflect.metadata("design:type", Function), _dec7 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Injected === "undefined" ? Object : Injected]), _dec8 = function (target, key) {
18+
return demo()(target, key, 0);
19+
}, _dec9 = Reflect.metadata("design:type", Function), _dec10 = Reflect.metadata("design:paramtypes", [String, void 0]), _dec11 = decorate('named'), _dec12 = function (target, key) {
20+
return inject()(target, key, 0);
21+
}, _dec13 = function (target, key) {
22+
return arg()(target, key, 1);
23+
}, _dec14 = Reflect.metadata("design:type", Function), _dec15 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Schema === "undefined" ? Object : Schema]), _dec4(_class2 = _dec5(_class2 = _dec6(_class2 = _dec7(_class2 = (_class3 = class MyOtherClass {
1424
constructor(parameter, otherParam) {
1525
this.parameter = parameter;
1626
}
@@ -19,20 +29,18 @@ let MyOtherClass = (_dec = decorate('named'), _dec2 = Reflect.metadata("design:p
1929

2030
method(param, schema) {}
2131

22-
}, (_applyDecoratedDescriptor(_class.prototype, "method", [_dec, _dec2], Object.getOwnPropertyDescriptor(_class.prototype, "method"), _class.prototype)), _class));
23-
arg()(MyOtherClass.prototype, "method", 1);
24-
inject()(MyOtherClass.prototype, "method", 0);
25-
demo()(MyOtherClass.prototype, "methodUndecorated", 0);
26-
inject('KIND')(MyOtherClass, undefined, 1);
27-
inject()(MyOtherClass, undefined, 0);
28-
let DecoratedClass = (_dec3 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Injected === "undefined" ? Object : Injected]), _dec4 = decorate('example'), _dec5 = Reflect.metadata("design:paramtypes", [String]), Decorate(_class2 = _dec3(_class2 = (_class3 = class DecoratedClass {
32+
}, (_applyDecoratedDescriptor(_class3.prototype, "methodUndecorated", [_dec8, _dec9, _dec10], Object.getOwnPropertyDescriptor(_class3.prototype, "methodUndecorated"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "method", [_dec11, _dec12, _dec13, _dec14, _dec15], Object.getOwnPropertyDescriptor(_class3.prototype, "method"), _class3.prototype)), _class3)) || _class2) || _class2) || _class2) || _class2);
33+
let DecoratedClass = (_dec16 = function (target, key) {
34+
return inject()(target, undefined, 0);
35+
}, _dec17 = function (target, key) {
36+
return inject()(target, undefined, 1);
37+
}, _dec18 = Reflect.metadata("design:type", Function), _dec19 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Injected === "undefined" ? Object : Injected]), _dec20 = decorate('example'), _dec21 = function (target, key) {
38+
return inject()(target, key, 0);
39+
}, _dec22 = Reflect.metadata("design:type", Function), _dec23 = Reflect.metadata("design:paramtypes", [String]), Decorate(_class4 = _dec16(_class4 = _dec17(_class4 = _dec18(_class4 = _dec19(_class4 = (_class5 = class DecoratedClass {
2940
constructor(module, otherModule) {
3041
this.module = module;
3142
}
3243

3344
method(param) {}
3445

35-
}, (_applyDecoratedDescriptor(_class3.prototype, "method", [_dec4, _dec5], Object.getOwnPropertyDescriptor(_class3.prototype, "method"), _class3.prototype)), _class3)) || _class2) || _class2);
36-
inject()(DecoratedClass.prototype, "method", 0);
37-
inject()(DecoratedClass, undefined, 1);
38-
inject()(DecoratedClass, undefined, 0);
46+
}, (_applyDecoratedDescriptor(_class5.prototype, "method", [_dec20, _dec21, _dec22, _dec23], Object.getOwnPropertyDescriptor(_class5.prototype, "method"), _class5.prototype)), _class5)) || _class4) || _class4) || _class4) || _class4) || _class4);

0 commit comments

Comments
 (0)