order of applied class/constr-param decorators different than in tsc
wtho opened this issue · comments
When I compile
@ClassDec()
export class Decoratee {
constructor(@ParamDec() param: string) {}
}
with tsc I end up with (I replaced some tslib
calls with what they will be replaced with):
let Decoratee = class Decoratee {
constructor(param) { }
};
Decoratee = tslib_1.__decorate([
ClassDec(),
ParamDec(),
Reflect.metadata("design:paramtypes", [String])
], TokenService);
tslib then will call these three decorators on Decoratee
, starting with the last one (Reflect.metadata
), then alls ParamDec, and finally
ClassDec`.
Using this plugin, I get something like this, with the order being differently:
var _dec, _dec2, _class;
var Decoratee = (
_dec = (0, ClassDec)(),
_dec2 = Reflect.metadata("design:paramtypes", [String]),
_dec(
_class = _dec2(
_class = function Decoratee(param) {}
) || _class
) || _class
);
(0, ParamDec)()(Decoratee, undefined, 0);
Compiling this with babel will in some cases have different results, if libraries/frameworks rely on a consistent order. I do not know, if the order is defined in the metadata proposal, did not find anything so far. I think you mentioned this issue also at the bottom of README.md
. Specifically in the Angular DI, the @Injectable
decorator expects all @Inject
s inside the constructor parameter to already have been applied.
You're right here. I added the parameter decorator emitter (even if this is not strictly related to metadata emission) since current babel decorator plugin does not support the syntax the way we need.
The only solution I can think of, is to operate like we are actually doing and enhance the paramterVisitor
function on order to emit something that can be processed by the babel-decorator
plugin, but I'm not sure this is possibile due to plugin constraints.
I found a solution. So what ts in combination tslib does it this:
let Decoratee = class Decoratee {
constructor(token) { }
};
Decoratee = tslib_1.__decorate([
Injectable(),
tslib_1.__param(0, Inject(TOKEN)),
tslib_1.__metadata("design:paramtypes", [String])
], Decoratee);
And tslib_1.__param(0, Inject(TOKEN))
expands to:
(function (target, key) { return (0, Inject)(TOKEN)(target, undefined, 0); })
So I tried to build the same structure and succeeded, but it bloats the code:
// parameterVisitor
if (methodPath.node.kind === 'constructor') {
// function(target, key) { return decorator(Decoratee, undefined, 0) }
const resultantDecorator = _core.types.decorator(
_core.types.functionExpression(
null, // anonymous function
[_core.types.identifier('target')],
_core.types.blockStatement(
[
_core.types.returnStatement(
_core.types.callExpression(
decorator.expression,
[
_core.types.identifier('target'),
_core.types.identifier('undefined'),
_core.types.numericLiteral(param.key)
]
)
)
]
)
)
)
classPath.node.decorators.push(resultantDecorator)
} else {
I also moved the classPath.insertAfter
to the else
block.
Reflect.metadata
is added to the constructors later, so the error with angular DI is resolved.
Might this create issues with other DI frameworks? Do you want me to open a PR?
By the way, this also resolves #22, I guess because we now put the new decorator on the class itself and this in return leads to the Reflect.metadata
decorator being created.
Awesome! I'd like to see the output code, and we should add some tests.
Anyway, if you can build up something with a PR it would be cool 👍
Going to publish today 🚀