Skip to content

Commit f863ea0

Browse files
committed
feat(decorators): adds support for parameter decorators.
Paramater decorators expect to be called as currently implemented by TS.
1 parent e434274 commit f863ea0

5 files changed

Lines changed: 182 additions & 17 deletions

File tree

modules/angular2/src/core/decorators/decorators.es6

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import {
55
import {ViewAnnotation} from '../annotations/view';
66
import {AncestorAnnotation, ParentAnnotation} from '../annotations/visibility';
77
import {AttributeAnnotation, QueryAnnotation} from '../annotations/di';
8+
import {global} from 'angular2/src/facade/lang';
89

910
function makeDecorator(annotationCls) {
1011
return function(...args) {
11-
if (!(window.Reflect && window.Reflect.getMetadata)) throw 'reflect-metadata shim is required';
12+
var Reflect = global.Reflect;
13+
if (!(Reflect && Reflect.getMetadata)) {
14+
throw 'reflect-metadata shim is required when using class decorators';
15+
}
1216
var annotationInstance = new annotationCls(...args);
13-
var Reflect = window.Reflect;
1417
return function(cls) {
1518
var annotations = Reflect.getMetadata('annotations', cls);
1619
annotations = annotations || [];
@@ -21,17 +24,39 @@ function makeDecorator(annotationCls) {
2124
}
2225
}
2326

27+
function makeParamDecorator(annotationCls) {
28+
return function(...args) {
29+
var Reflect = global.Reflect;
30+
if (!(Reflect && Reflect.getMetadata)) {
31+
throw 'reflect-metadata shim is required when using parameter decorators';
32+
}
33+
var annotationInstance = new annotationCls(...args);
34+
return function(cls, unusedKey, index) {
35+
var parameters = Reflect.getMetadata('parameters', cls);
36+
parameters = parameters || [];
37+
// there might be gaps if some in between parameters do not have annotations.
38+
// we pad with nulls.
39+
while (parameters.length <= index) {
40+
parameters.push(null);
41+
}
42+
parameters[index] = annotationInstance;
43+
Reflect.defineMetadata('parameters', parameters, cls);
44+
return cls;
45+
}
46+
}
47+
}
48+
2449
/* from annotations */
2550
export var Component = makeDecorator(ComponentAnnotation);
2651
export var Decorator = makeDecorator(DirectiveAnnotation);
2752

28-
/* from di */
29-
export var Attribute = makeDecorator(AttributeAnnotation);
30-
export var Query = makeDecorator(QueryAnnotation);
31-
3253
/* from view */
3354
export var View = makeDecorator(ViewAnnotation);
3455

35-
/* from visiblity */
36-
export var Ancestor = makeDecorator(AncestorAnnotation);
37-
export var Parent = makeDecorator(ParentAnnotation);
56+
/* from visibility */
57+
export var Ancestor = makeParamDecorator(AncestorAnnotation);
58+
export var Parent = makeParamDecorator(ParentAnnotation);
59+
60+
/* from di */
61+
export var Attribute = makeParamDecorator(AttributeAnnotation);
62+
export var Query = makeParamDecorator(QueryAnnotation);

modules/angular2/src/facade/lang.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,6 @@ class DateWrapper {
217217
return date.toUtc().toIso8601String();
218218
}
219219
}
220+
221+
// needed to match the exports from lang.js
222+
var global = null;

modules/angular2/src/reflection/reflection_capabilities.es6

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,35 @@ export class ReflectionCapabilities {
3838
return typeOfFunc.parameters;
3939
}
4040
if (isPresent(window.Reflect) && isPresent(window.Reflect.getMetadata)) {
41-
var paramtypes = window.Reflect.getMetadata('design:paramtypes', typeOfFunc);
42-
if (isPresent(paramtypes)) {
43-
// TODO(rado): add parameter annotations here.
44-
return paramtypes.map((p) => [p]);
41+
var paramAnnotations = window.Reflect.getMetadata('parameters', typeOfFunc);
42+
var paramTypes = window.Reflect.getMetadata('design:paramtypes', typeOfFunc);
43+
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
44+
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
4545
}
4646
}
4747
return ListWrapper.createFixedSize(typeOfFunc.length);
4848
}
4949

50+
51+
_zipTypesAndAnnotaions(paramTypes, paramAnnotations) {
52+
var result = ListWrapper.createFixedSize(paramTypes.length);
53+
for (var i = 0; i < result.length; i++) {
54+
// TS outputs Object for parameters without types, while Traceur omits
55+
// the annotations. For now we preserve the Traceur behavior to aid
56+
// migration, but this can be revisited.
57+
if (paramTypes[i] != Object) {
58+
result[i] = [paramTypes[i]];
59+
} else {
60+
result[i] = [];
61+
}
62+
if (isPresent(paramAnnotations[i])) {
63+
result[i] = result[i].concat(paramAnnotations[i]);
64+
}
65+
}
66+
return result;
67+
}
68+
69+
5070
annotations(typeOfFunc):List {
5171
// Prefer the direct API.
5272
if (isPresent(typeOfFunc.annotations)) {

modules/angular2/src/reflection/reflection_capabilities.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,34 @@ export class ReflectionCapabilities {
4545
throw new Error("Factory cannot take more than 10 arguments");
4646
}
4747

48+
_zipTypesAndAnnotaions(paramTypes, paramAnnotations): List<List<any>> {
49+
var result = ListWrapper.createFixedSize(paramTypes.length);
50+
for (var i = 0; i < result.length; i++) {
51+
// TS outputs Object for parameters without types, while Traceur omits
52+
// the annotations. For now we preserve the Traceur behavior to aid
53+
// migration, but this can be revisited.
54+
if (paramTypes[i] != Object) {
55+
result[i] = [paramTypes[i]];
56+
} else {
57+
result[i] = [];
58+
}
59+
if (isPresent(paramAnnotations[i])) {
60+
result[i] = result[i].concat(paramAnnotations[i]);
61+
}
62+
}
63+
return result;
64+
}
65+
4866
parameters(typeOfFunc): List<List<any>> {
4967
// Prefer the direct API.
5068
if (isPresent(typeOfFunc.parameters)) {
5169
return typeOfFunc.parameters;
5270
}
5371
if (isPresent(global.Reflect) && isPresent(global.Reflect.getMetadata)) {
54-
var paramtypes = global.Reflect.getMetadata('design:paramtypes', typeOfFunc);
55-
if (isPresent(paramtypes)) {
56-
// TODO(rado): add parameter annotations here.
57-
return paramtypes.map((p) => [p]);
72+
var paramAnnotations = global.Reflect.getMetadata('parameters', typeOfFunc);
73+
var paramTypes = global.Reflect.getMetadata('design:paramtypes', typeOfFunc);
74+
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
75+
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
5876
}
5977
}
6078
return ListWrapper.createFixedSize(typeOfFunc.length);
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {ddescribe, describe, it, iit, expect, beforeEach, IS_DARTIUM} from 'angular2/test_lib';
2+
import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities';
3+
import {isPresent, global, CONST} from 'angular2/src/facade/lang';
4+
5+
export function main() {
6+
var rc;
7+
beforeEach(() => {
8+
rc = new ReflectionCapabilities();
9+
});
10+
11+
function mockReflect(mockData, cls) {
12+
// This only makes sense for JS, but Dart passes trivially too.
13+
if (!IS_DARTIUM) {
14+
global.Reflect = {
15+
'getMetadata': (key, targetCls) => {
16+
return (targetCls == cls) ? mockData[key] : null;
17+
}
18+
}
19+
}
20+
}
21+
22+
function assertTestClassAnnotations(annotations) {
23+
expect(annotations[0]).toBeAnInstanceOf(ClassDec1);
24+
expect(annotations[1]).toBeAnInstanceOf(ClassDec2);
25+
}
26+
27+
function assertTestClassParameters(parameters) {
28+
expect(parameters[0].length).toBe(2);
29+
expect(parameters[0][0]).toEqual(P1);
30+
expect(parameters[0][1]).toBeAnInstanceOf(ParamDec);
31+
32+
expect(parameters[1].length).toBe(1);
33+
expect(parameters[1][0]).toEqual(P2);
34+
35+
36+
expect(parameters[2].length).toBe(1);
37+
expect(parameters[2][0]).toBeAnInstanceOf(ParamDec);
38+
39+
expect(parameters[3].length).toBe(0);
40+
}
41+
42+
describe('reflection capabilities', () => {
43+
it('can read out class annotations through annotations key', () => {
44+
assertTestClassAnnotations(rc.annotations(TestClass));
45+
});
46+
47+
it('can read out parameter annotations through parameters key', () => {
48+
assertTestClassParameters(rc.parameters(TestClass));
49+
});
50+
51+
// Mocking in the tests below is needed because the test runs through Traceur.
52+
// After the switch to TS the setup will have to change, where the direct key
53+
// access will be mocked, and the tests below will be direct.
54+
it('can read out class annotations though Reflect APIs', () => {
55+
if (IS_DARTIUM) return;
56+
mockReflect(mockDataForTestClassDec, TestClassDec);
57+
assertTestClassAnnotations(rc.annotations(TestClassDec));
58+
});
59+
60+
it('can read out parameter annotations though Reflect APIs', () => {
61+
if (IS_DARTIUM) return;
62+
mockReflect(mockDataForTestClassDec, TestClassDec);
63+
assertTestClassParameters(rc.parameters(TestClassDec));
64+
});
65+
});
66+
}
67+
68+
class ClassDec1 {
69+
@CONST()
70+
constructor() {}
71+
}
72+
73+
class ClassDec2 {
74+
@CONST()
75+
constructor() {}
76+
}
77+
78+
79+
class ParamDec {
80+
@CONST()
81+
constructor() {}
82+
}
83+
84+
class P1 {}
85+
class P2 {}
86+
87+
@ClassDec1()
88+
@ClassDec2()
89+
class TestClass {
90+
constructor(@ParamDec() a: P1, b: P2, @ParamDec() c, d) {}
91+
}
92+
93+
// Mocking the data stored in global.Reflect as if TS was compiling TestClass above.
94+
var mockDataForTestClassDec = {
95+
'annotations': [new ClassDec1(), new ClassDec2()],
96+
'parameters': [new ParamDec(), null, new ParamDec()],
97+
'design:paramtypes': [P1, P2, Object, Object]
98+
};
99+
class TestClassDec {}

0 commit comments

Comments
 (0)