diff --git a/modules/angular2/src/core/decorators/decorators.es6 b/modules/angular2/src/core/decorators/decorators.es6 index a2cf220e34..e049c3e90c 100644 --- a/modules/angular2/src/core/decorators/decorators.es6 +++ b/modules/angular2/src/core/decorators/decorators.es6 @@ -5,12 +5,15 @@ import { import {ViewAnnotation} from '../annotations/view'; import {AncestorAnnotation, ParentAnnotation} from '../annotations/visibility'; import {AttributeAnnotation, QueryAnnotation} from '../annotations/di'; +import {global} from 'angular2/src/facade/lang'; function makeDecorator(annotationCls) { 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 Reflect = window.Reflect; return function(cls) { var annotations = Reflect.getMetadata('annotations', cls); 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 */ export var Component = makeDecorator(ComponentAnnotation); export var Decorator = makeDecorator(DirectiveAnnotation); -/* from di */ -export var Attribute = makeDecorator(AttributeAnnotation); -export var Query = makeDecorator(QueryAnnotation); - /* from view */ export var View = makeDecorator(ViewAnnotation); -/* from visiblity */ -export var Ancestor = makeDecorator(AncestorAnnotation); -export var Parent = makeDecorator(ParentAnnotation); +/* from visibility */ +export var Ancestor = makeParamDecorator(AncestorAnnotation); +export var Parent = makeParamDecorator(ParentAnnotation); + +/* from di */ +export var Attribute = makeParamDecorator(AttributeAnnotation); +export var Query = makeParamDecorator(QueryAnnotation); diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index 9526fecaca..6c1a3cbd3c 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -217,3 +217,6 @@ class DateWrapper { return date.toUtc().toIso8601String(); } } + +// needed to match the exports from lang.js +var global = null; diff --git a/modules/angular2/src/reflection/reflection_capabilities.es6 b/modules/angular2/src/reflection/reflection_capabilities.es6 index 26243a539d..3a20bd67cd 100644 --- a/modules/angular2/src/reflection/reflection_capabilities.es6 +++ b/modules/angular2/src/reflection/reflection_capabilities.es6 @@ -38,15 +38,35 @@ export class ReflectionCapabilities { return typeOfFunc.parameters; } if (isPresent(window.Reflect) && isPresent(window.Reflect.getMetadata)) { - var paramtypes = window.Reflect.getMetadata('design:paramtypes', typeOfFunc); - if (isPresent(paramtypes)) { - // TODO(rado): add parameter annotations here. - return paramtypes.map((p) => [p]); + var paramAnnotations = window.Reflect.getMetadata('parameters', typeOfFunc); + var paramTypes = window.Reflect.getMetadata('design:paramtypes', typeOfFunc); + if (isPresent(paramTypes) || isPresent(paramAnnotations)) { + return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations); } } 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 { // Prefer the direct API. if (isPresent(typeOfFunc.annotations)) { diff --git a/modules/angular2/src/reflection/reflection_capabilities.ts b/modules/angular2/src/reflection/reflection_capabilities.ts index 06a44060e7..f564797ee9 100644 --- a/modules/angular2/src/reflection/reflection_capabilities.ts +++ b/modules/angular2/src/reflection/reflection_capabilities.ts @@ -45,16 +45,34 @@ export class ReflectionCapabilities { throw new Error("Factory cannot take more than 10 arguments"); } + _zipTypesAndAnnotaions(paramTypes, paramAnnotations): List> { + 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> { // Prefer the direct API. if (isPresent(typeOfFunc.parameters)) { return typeOfFunc.parameters; } if (isPresent(global.Reflect) && isPresent(global.Reflect.getMetadata)) { - var paramtypes = global.Reflect.getMetadata('design:paramtypes', typeOfFunc); - if (isPresent(paramtypes)) { - // TODO(rado): add parameter annotations here. - return paramtypes.map((p) => [p]); + var paramAnnotations = global.Reflect.getMetadata('parameters', typeOfFunc); + var paramTypes = global.Reflect.getMetadata('design:paramtypes', typeOfFunc); + if (isPresent(paramTypes) || isPresent(paramAnnotations)) { + return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations); } } return ListWrapper.createFixedSize(typeOfFunc.length); diff --git a/modules/angular2/test/core/compiler/reflection_capabilities_spec.js b/modules/angular2/test/core/compiler/reflection_capabilities_spec.js new file mode 100644 index 0000000000..b4676cad72 --- /dev/null +++ b/modules/angular2/test/core/compiler/reflection_capabilities_spec.js @@ -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 {}