feat(decorators): adds support for parameter decorators.
Paramater decorators expect to be called as currently implemented by TS.
This commit is contained in:
parent
e4342743c0
commit
f863ea0db5
@ -5,12 +5,15 @@ import {
|
|||||||
import {ViewAnnotation} from '../annotations/view';
|
import {ViewAnnotation} from '../annotations/view';
|
||||||
import {AncestorAnnotation, ParentAnnotation} from '../annotations/visibility';
|
import {AncestorAnnotation, ParentAnnotation} from '../annotations/visibility';
|
||||||
import {AttributeAnnotation, QueryAnnotation} from '../annotations/di';
|
import {AttributeAnnotation, QueryAnnotation} from '../annotations/di';
|
||||||
|
import {global} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
function makeDecorator(annotationCls) {
|
function makeDecorator(annotationCls) {
|
||||||
return function(...args) {
|
return function(...args) {
|
||||||
if (!(window.Reflect && window.Reflect.getMetadata)) throw 'reflect-metadata shim is required';
|
var Reflect = global.Reflect;
|
||||||
|
if (!(Reflect && Reflect.getMetadata)) {
|
||||||
|
throw 'reflect-metadata shim is required when using class decorators';
|
||||||
|
}
|
||||||
var annotationInstance = new annotationCls(...args);
|
var annotationInstance = new annotationCls(...args);
|
||||||
var Reflect = window.Reflect;
|
|
||||||
return function(cls) {
|
return function(cls) {
|
||||||
var annotations = Reflect.getMetadata('annotations', cls);
|
var annotations = Reflect.getMetadata('annotations', cls);
|
||||||
annotations = annotations || [];
|
annotations = annotations || [];
|
||||||
@ -21,17 +24,39 @@ function makeDecorator(annotationCls) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeParamDecorator(annotationCls) {
|
||||||
|
return function(...args) {
|
||||||
|
var Reflect = global.Reflect;
|
||||||
|
if (!(Reflect && Reflect.getMetadata)) {
|
||||||
|
throw 'reflect-metadata shim is required when using parameter decorators';
|
||||||
|
}
|
||||||
|
var annotationInstance = new annotationCls(...args);
|
||||||
|
return function(cls, unusedKey, index) {
|
||||||
|
var parameters = Reflect.getMetadata('parameters', cls);
|
||||||
|
parameters = parameters || [];
|
||||||
|
// there might be gaps if some in between parameters do not have annotations.
|
||||||
|
// we pad with nulls.
|
||||||
|
while (parameters.length <= index) {
|
||||||
|
parameters.push(null);
|
||||||
|
}
|
||||||
|
parameters[index] = annotationInstance;
|
||||||
|
Reflect.defineMetadata('parameters', parameters, cls);
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* from annotations */
|
/* from annotations */
|
||||||
export var Component = makeDecorator(ComponentAnnotation);
|
export var Component = makeDecorator(ComponentAnnotation);
|
||||||
export var Decorator = makeDecorator(DirectiveAnnotation);
|
export var Decorator = makeDecorator(DirectiveAnnotation);
|
||||||
|
|
||||||
/* from di */
|
|
||||||
export var Attribute = makeDecorator(AttributeAnnotation);
|
|
||||||
export var Query = makeDecorator(QueryAnnotation);
|
|
||||||
|
|
||||||
/* from view */
|
/* from view */
|
||||||
export var View = makeDecorator(ViewAnnotation);
|
export var View = makeDecorator(ViewAnnotation);
|
||||||
|
|
||||||
/* from visiblity */
|
/* from visibility */
|
||||||
export var Ancestor = makeDecorator(AncestorAnnotation);
|
export var Ancestor = makeParamDecorator(AncestorAnnotation);
|
||||||
export var Parent = makeDecorator(ParentAnnotation);
|
export var Parent = makeParamDecorator(ParentAnnotation);
|
||||||
|
|
||||||
|
/* from di */
|
||||||
|
export var Attribute = makeParamDecorator(AttributeAnnotation);
|
||||||
|
export var Query = makeParamDecorator(QueryAnnotation);
|
||||||
|
@ -217,3 +217,6 @@ class DateWrapper {
|
|||||||
return date.toUtc().toIso8601String();
|
return date.toUtc().toIso8601String();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// needed to match the exports from lang.js
|
||||||
|
var global = null;
|
||||||
|
@ -38,15 +38,35 @@ export class ReflectionCapabilities {
|
|||||||
return typeOfFunc.parameters;
|
return typeOfFunc.parameters;
|
||||||
}
|
}
|
||||||
if (isPresent(window.Reflect) && isPresent(window.Reflect.getMetadata)) {
|
if (isPresent(window.Reflect) && isPresent(window.Reflect.getMetadata)) {
|
||||||
var paramtypes = window.Reflect.getMetadata('design:paramtypes', typeOfFunc);
|
var paramAnnotations = window.Reflect.getMetadata('parameters', typeOfFunc);
|
||||||
if (isPresent(paramtypes)) {
|
var paramTypes = window.Reflect.getMetadata('design:paramtypes', typeOfFunc);
|
||||||
// TODO(rado): add parameter annotations here.
|
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
|
||||||
return paramtypes.map((p) => [p]);
|
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ListWrapper.createFixedSize(typeOfFunc.length);
|
return ListWrapper.createFixedSize(typeOfFunc.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_zipTypesAndAnnotaions(paramTypes, paramAnnotations) {
|
||||||
|
var result = ListWrapper.createFixedSize(paramTypes.length);
|
||||||
|
for (var i = 0; i < result.length; i++) {
|
||||||
|
// TS outputs Object for parameters without types, while Traceur omits
|
||||||
|
// the annotations. For now we preserve the Traceur behavior to aid
|
||||||
|
// migration, but this can be revisited.
|
||||||
|
if (paramTypes[i] != Object) {
|
||||||
|
result[i] = [paramTypes[i]];
|
||||||
|
} else {
|
||||||
|
result[i] = [];
|
||||||
|
}
|
||||||
|
if (isPresent(paramAnnotations[i])) {
|
||||||
|
result[i] = result[i].concat(paramAnnotations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
annotations(typeOfFunc):List {
|
annotations(typeOfFunc):List {
|
||||||
// Prefer the direct API.
|
// Prefer the direct API.
|
||||||
if (isPresent(typeOfFunc.annotations)) {
|
if (isPresent(typeOfFunc.annotations)) {
|
||||||
|
@ -45,16 +45,34 @@ export class ReflectionCapabilities {
|
|||||||
throw new Error("Factory cannot take more than 10 arguments");
|
throw new Error("Factory cannot take more than 10 arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_zipTypesAndAnnotaions(paramTypes, paramAnnotations): List<List<any>> {
|
||||||
|
var result = ListWrapper.createFixedSize(paramTypes.length);
|
||||||
|
for (var i = 0; i < result.length; i++) {
|
||||||
|
// TS outputs Object for parameters without types, while Traceur omits
|
||||||
|
// the annotations. For now we preserve the Traceur behavior to aid
|
||||||
|
// migration, but this can be revisited.
|
||||||
|
if (paramTypes[i] != Object) {
|
||||||
|
result[i] = [paramTypes[i]];
|
||||||
|
} else {
|
||||||
|
result[i] = [];
|
||||||
|
}
|
||||||
|
if (isPresent(paramAnnotations[i])) {
|
||||||
|
result[i] = result[i].concat(paramAnnotations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
parameters(typeOfFunc): List<List<any>> {
|
parameters(typeOfFunc): List<List<any>> {
|
||||||
// Prefer the direct API.
|
// Prefer the direct API.
|
||||||
if (isPresent(typeOfFunc.parameters)) {
|
if (isPresent(typeOfFunc.parameters)) {
|
||||||
return typeOfFunc.parameters;
|
return typeOfFunc.parameters;
|
||||||
}
|
}
|
||||||
if (isPresent(global.Reflect) && isPresent(global.Reflect.getMetadata)) {
|
if (isPresent(global.Reflect) && isPresent(global.Reflect.getMetadata)) {
|
||||||
var paramtypes = global.Reflect.getMetadata('design:paramtypes', typeOfFunc);
|
var paramAnnotations = global.Reflect.getMetadata('parameters', typeOfFunc);
|
||||||
if (isPresent(paramtypes)) {
|
var paramTypes = global.Reflect.getMetadata('design:paramtypes', typeOfFunc);
|
||||||
// TODO(rado): add parameter annotations here.
|
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
|
||||||
return paramtypes.map((p) => [p]);
|
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ListWrapper.createFixedSize(typeOfFunc.length);
|
return ListWrapper.createFixedSize(typeOfFunc.length);
|
||||||
|
99
modules/angular2/test/core/compiler/reflection_capabilities_spec.js
vendored
Normal file
99
modules/angular2/test/core/compiler/reflection_capabilities_spec.js
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import {ddescribe, describe, it, iit, expect, beforeEach, IS_DARTIUM} from 'angular2/test_lib';
|
||||||
|
import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities';
|
||||||
|
import {isPresent, global, CONST} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
var rc;
|
||||||
|
beforeEach(() => {
|
||||||
|
rc = new ReflectionCapabilities();
|
||||||
|
});
|
||||||
|
|
||||||
|
function mockReflect(mockData, cls) {
|
||||||
|
// This only makes sense for JS, but Dart passes trivially too.
|
||||||
|
if (!IS_DARTIUM) {
|
||||||
|
global.Reflect = {
|
||||||
|
'getMetadata': (key, targetCls) => {
|
||||||
|
return (targetCls == cls) ? mockData[key] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertTestClassAnnotations(annotations) {
|
||||||
|
expect(annotations[0]).toBeAnInstanceOf(ClassDec1);
|
||||||
|
expect(annotations[1]).toBeAnInstanceOf(ClassDec2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertTestClassParameters(parameters) {
|
||||||
|
expect(parameters[0].length).toBe(2);
|
||||||
|
expect(parameters[0][0]).toEqual(P1);
|
||||||
|
expect(parameters[0][1]).toBeAnInstanceOf(ParamDec);
|
||||||
|
|
||||||
|
expect(parameters[1].length).toBe(1);
|
||||||
|
expect(parameters[1][0]).toEqual(P2);
|
||||||
|
|
||||||
|
|
||||||
|
expect(parameters[2].length).toBe(1);
|
||||||
|
expect(parameters[2][0]).toBeAnInstanceOf(ParamDec);
|
||||||
|
|
||||||
|
expect(parameters[3].length).toBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('reflection capabilities', () => {
|
||||||
|
it('can read out class annotations through annotations key', () => {
|
||||||
|
assertTestClassAnnotations(rc.annotations(TestClass));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can read out parameter annotations through parameters key', () => {
|
||||||
|
assertTestClassParameters(rc.parameters(TestClass));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mocking in the tests below is needed because the test runs through Traceur.
|
||||||
|
// After the switch to TS the setup will have to change, where the direct key
|
||||||
|
// access will be mocked, and the tests below will be direct.
|
||||||
|
it('can read out class annotations though Reflect APIs', () => {
|
||||||
|
if (IS_DARTIUM) return;
|
||||||
|
mockReflect(mockDataForTestClassDec, TestClassDec);
|
||||||
|
assertTestClassAnnotations(rc.annotations(TestClassDec));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can read out parameter annotations though Reflect APIs', () => {
|
||||||
|
if (IS_DARTIUM) return;
|
||||||
|
mockReflect(mockDataForTestClassDec, TestClassDec);
|
||||||
|
assertTestClassParameters(rc.parameters(TestClassDec));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassDec1 {
|
||||||
|
@CONST()
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassDec2 {
|
||||||
|
@CONST()
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ParamDec {
|
||||||
|
@CONST()
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class P1 {}
|
||||||
|
class P2 {}
|
||||||
|
|
||||||
|
@ClassDec1()
|
||||||
|
@ClassDec2()
|
||||||
|
class TestClass {
|
||||||
|
constructor(@ParamDec() a: P1, b: P2, @ParamDec() c, d) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mocking the data stored in global.Reflect as if TS was compiling TestClass above.
|
||||||
|
var mockDataForTestClassDec = {
|
||||||
|
'annotations': [new ClassDec1(), new ClassDec2()],
|
||||||
|
'parameters': [new ParamDec(), null, new ParamDec()],
|
||||||
|
'design:paramtypes': [P1, P2, Object, Object]
|
||||||
|
};
|
||||||
|
class TestClassDec {}
|
Loading…
x
Reference in New Issue
Block a user