Skip to content

Commit

Permalink
✨ Put param dec to method decorators
Browse files Browse the repository at this point in the history
also:
* add design:type decorator to class methods (in metadataVisitor)
  • Loading branch information
wtho committed Dec 13, 2019
1 parent 11b04de commit 339b142
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 93 deletions.
59 changes: 39 additions & 20 deletions src/metadata/metadataVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@ import { NodePath } from '@babel/traverse';
import { types as t } from '@babel/core';
import { serializeType } from './serializeType';

function createMetadataDesignDecorator(
design: 'design:type' | 'design:paramtypes' | 'design:returntype' | 'design:typeinfo',
typeArg: t.Expression | t.SpreadElement | t.JSXNamespacedName | t.ArgumentPlaceholder
): t.Decorator {
return t.decorator(
t.callExpression(
t.memberExpression(
t.identifier('Reflect'),
t.identifier('metadata')
),
[
t.stringLiteral(design),
typeArg
]
)
)
}

export function metadataVisitor(
classPath: NodePath<t.ClassDeclaration>,
path: NodePath<t.ClassProperty | t.ClassMethod>
Expand All @@ -17,21 +35,27 @@ export function metadataVisitor(
if (!decorators || decorators.length === 0) return;

decorators!.push(
t.decorator(
t.callExpression(
t.memberExpression(
t.identifier('Reflect'),
t.identifier('metadata')
),
[
t.stringLiteral('design:paramtypes'),
t.arrayExpression(
field.params.map(param => serializeType(classPath, param))
)
]
createMetadataDesignDecorator(
'design:type',
t.identifier('Function')
)
);
decorators!.push(
createMetadataDesignDecorator(
'design:paramtypes',
t.arrayExpression(
field.params.map(param => serializeType(classPath, param))
)
)
);
// decorators!.push(
// createMetadataDesignDecorator(
// 'design:returntype',
// t.arrayExpression(
// field.params.map(param => serializeReturnType(classPath, param))
// )
// )
// );
break;

case 'ClassProperty':
Expand All @@ -44,14 +68,9 @@ export function metadataVisitor(
return;

field.decorators!.push(
t.decorator(
t.callExpression(
t.memberExpression(
t.identifier('Reflect'),
t.identifier('metadata')
),
[t.stringLiteral('design:type'), serializeType(classPath, field)]
)
createMetadataDesignDecorator(
'design:type',
serializeType(classPath, field)
)
);
break;
Expand Down
83 changes: 47 additions & 36 deletions src/parameter/parameterVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
import { NodePath } from '@babel/traverse';
import { types as t } from '@babel/core';

export function makeParamDecoratorExprAFunctionExpr(paramIdx: number, decExpr: t.Expression) {
// dec
// becomes
// function(target) { return dec(target, undefined, 0) }
const fnExpr = t.functionExpression(
null, // anonymous function
[t.identifier('target')],
t.blockStatement(
[
/**
* Helper function to create a field/class decorator from a parameter decorator.
* Field/class decorators get three arguments: the class, the name of the method
* (or 'undefined' in the case of the constructor) and the position index of the
* parameter in the argument list.
* Some of this information, the index, is only available at transform time, and
* has to be stored. The other arguments are part of the decorator signature and
* will be passed to the decorator anyway. But the decorator has to be called
* with all three arguments at runtime, so this creates a function wrapper, which
* takes the target and the key, and adds the index to it.
*
* Inject() becomes function(target, key) { return Inject()(target, key, 0) }
*
* @param paramIdx the index of the parameter inside the function call
* @param decExpr the decorator expression, the return object of SomeParameterDecorator()
* @param isConstructor indicates if the key should be set to 'undefined'
*/
function makeParamDecorator(
paramIdx: number,
decExpr: t.Expression,
isConstructor: boolean = false
) {
return t.decorator(
t.functionExpression(
null, // anonymous function
[t.identifier('target'), t.identifier('key')],
t.blockStatement([
t.returnStatement(
t.callExpression(
decExpr,
[
t.identifier('target'),
t.identifier('undefined'),
t.numericLiteral(paramIdx)
]
)
t.callExpression(decExpr, [
t.identifier('target'),
t.identifier(isConstructor ? 'undefined' : 'key'),
t.numericLiteral(paramIdx)
])
)
]
])
)
);
return fnExpr;
}

export function parameterVisitor(
Expand All @@ -35,7 +49,6 @@ export function parameterVisitor(
if (path.node.key.type !== 'Identifier') return;

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

params.slice().forEach(function(param) {
Expand All @@ -49,34 +62,32 @@ export function parameterVisitor(

if (identifier == null) return;

let resultantDecorator;
let resultantDecorator: t.Decorator | undefined;

((param.node as t.Identifier).decorators || [])
.slice()
.forEach(function(decorator) {
const className = classPath.node!.id!.name;

if (methodPath.node.kind === 'constructor') {
resultantDecorator = makeParamDecoratorExprAFunctionExpr(
resultantDecorator = makeParamDecorator(
param.key as number,
decorator.expression
decorator.expression,
true
);
if (!classPath.node.decorators) {
classPath.node.decorators = [];
}
const newDecorator = t.decorator(resultantDecorator);
classPath.node.decorators.push(newDecorator);

classPath.node.decorators.push(resultantDecorator);
} else {
resultantDecorator = t.callExpression(decorator.expression, [
t.identifier(`${className}.prototype`),
t.stringLiteral(methodName),
t.numericLiteral(param.key as number)
]);
const expression = t.expressionStatement(resultantDecorator);
classPath.insertAfter(expression);
resultantDecorator = makeParamDecorator(
param.key as number,
decorator.expression,
false
);
if (!methodPath.node.decorators) {
methodPath.node.decorators = [];
}
methodPath.node.decorators.push(resultantDecorator);
}

});

if (resultantDecorator) {
Expand Down
9 changes: 5 additions & 4 deletions test/__fixtures__/generics/output.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
var _dec, _dec2, _class, _class2;
var _dec, _dec2, _dec3, _dec4, _dec5, _class, _class2;

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

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 {
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) {
return Arg()(target, key, 1);
}, _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 {
constructor(generic, generic2) {
this.generic = generic;
}

method(generic, generic2) {}

}, (_applyDecoratedDescriptor(_class2.prototype, "method", [Run, _dec2], Object.getOwnPropertyDescriptor(_class2.prototype, "method"), _class2.prototype)), _class2)) || _class) || _class);
Arg()(MyClass.prototype, "method", 1);
}, (_applyDecoratedDescriptor(_class2.prototype, "method", [Run, _dec3, _dec4, _dec5], Object.getOwnPropertyDescriptor(_class2.prototype, "method"), _class2.prototype)), _class2)) || _class) || _class) || _class);
10 changes: 5 additions & 5 deletions test/__fixtures__/nest-injection/output.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _class, _class2, _descriptor, _descriptor2, _temp;
var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _class, _class2, _descriptor, _descriptor2, _temp;

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

Expand All @@ -7,7 +7,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
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.'); }

import { AppService } from './app.service';
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 {
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 {
constructor(appService) {
this.appService = appService;

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

}, _temp), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, "appService", [_dec3, _dec4], {
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, "appService", [_dec4, _dec5], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "appService2", [_dec5, _dec6], {
}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "appService2", [_dec6, _dec7], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _applyDecoratedDescriptor(_class2.prototype, "getHello", [_dec7, _dec8], Object.getOwnPropertyDescriptor(_class2.prototype, "getHello"), _class2.prototype)), _class2)) || _class) || _class);
}), _applyDecoratedDescriptor(_class2.prototype, "getHello", [_dec8, _dec9, _dec10], Object.getOwnPropertyDescriptor(_class2.prototype, "getHello"), _class2.prototype)), _class2)) || _class) || _class) || _class);
36 changes: 20 additions & 16 deletions test/__fixtures__/parameter-decorator-typed/output.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
var _dec, _dec2, _class, _dec3, _dec4, _dec5, _dec6, _dec7, _class2, _class3, _dec8, _dec9, _dec10, _dec11, _dec12, _class4, _class5;
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;

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

class Injected {}

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

}) || _class) || _class);
let MyOtherClass = (_dec3 = function (target) {
}) || _class) || _class) || _class);
let MyOtherClass = (_dec4 = function (target, key) {
return inject()(target, undefined, 0);
}, _dec4 = function (target) {
}, _dec5 = function (target, key) {
return inject('KIND')(target, undefined, 1);
}, _dec5 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Injected === "undefined" ? Object : Injected]), _dec6 = decorate('named'), _dec7 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Schema === "undefined" ? Object : Schema]), _dec3(_class2 = _dec4(_class2 = _dec5(_class2 = (_class3 = class MyOtherClass {
}, _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) {
return demo()(target, key, 0);
}, _dec9 = Reflect.metadata("design:type", Function), _dec10 = Reflect.metadata("design:paramtypes", [String, void 0]), _dec11 = decorate('named'), _dec12 = function (target, key) {
return inject()(target, key, 0);
}, _dec13 = function (target, key) {
return arg()(target, key, 1);
}, _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 {
constructor(parameter, otherParam) {
this.parameter = parameter;
}
Expand All @@ -23,20 +29,18 @@ let MyOtherClass = (_dec3 = function (target) {

method(param, schema) {}

}, (_applyDecoratedDescriptor(_class3.prototype, "method", [_dec6, _dec7], Object.getOwnPropertyDescriptor(_class3.prototype, "method"), _class3.prototype)), _class3)) || _class2) || _class2) || _class2);
arg()(MyOtherClass.prototype, "method", 1);
inject()(MyOtherClass.prototype, "method", 0);
demo()(MyOtherClass.prototype, "methodUndecorated", 0);
let DecoratedClass = (_dec8 = function (target) {
}, (_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);
let DecoratedClass = (_dec16 = function (target, key) {
return inject()(target, undefined, 0);
}, _dec9 = function (target) {
}, _dec17 = function (target, key) {
return inject()(target, undefined, 1);
}, _dec10 = Reflect.metadata("design:paramtypes", [typeof Injected === "undefined" ? Object : Injected, typeof Injected === "undefined" ? Object : Injected]), _dec11 = decorate('example'), _dec12 = Reflect.metadata("design:paramtypes", [String]), Decorate(_class4 = _dec8(_class4 = _dec9(_class4 = _dec10(_class4 = (_class5 = class DecoratedClass {
}, _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) {
return inject()(target, key, 0);
}, _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 {
constructor(module, otherModule) {
this.module = module;
}

method(param) {}

}, (_applyDecoratedDescriptor(_class5.prototype, "method", [_dec11, _dec12], Object.getOwnPropertyDescriptor(_class5.prototype, "method"), _class5.prototype)), _class5)) || _class4) || _class4) || _class4) || _class4);
inject()(DecoratedClass.prototype, "method", 0);
}, (_applyDecoratedDescriptor(_class5.prototype, "method", [_dec20, _dec21, _dec22, _dec23], Object.getOwnPropertyDescriptor(_class5.prototype, "method"), _class5.prototype)), _class5)) || _class4) || _class4) || _class4) || _class4) || _class4);
Loading

0 comments on commit 339b142

Please sign in to comment.