Skip to content

Commit 8b54814

Browse files
Merge pull request #24 from wtho/feat/transform-prop-dec-on-class
✨ Move param dec to class
2 parents 3b1ece6 + 5dfcc90 commit 8b54814

8 files changed

Lines changed: 145 additions & 86 deletions

File tree

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 runs after the decorators transform.'); }
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)