feat(core): add support for @Property and @Event decorators

Example:

@Directive({selector: 'my-selector'})
class MyDirective {
  @Property() prop;
  @Property('el-prop') prop2;
  @Event() event;
  @Event('el-event') event2;
}

Closes #3992
This commit is contained in:
vsavkin 2015-09-03 15:10:48 -07:00 committed by Victor Savkin
parent 337ce21149
commit 896add7d77
19 changed files with 511 additions and 89 deletions

View File

@ -34,7 +34,13 @@ export {
QueryFactory,
ViewQuery,
Pipe,
PipeFactory
PipeFactory,
Property,
PropertyFactory,
PropertyMetadata,
Event,
EventFactory,
EventMetadata
} from './src/core/metadata';
export {

View File

@ -1,6 +1,12 @@
import {resolveForwardRef, Injectable} from 'angular2/di';
import {Type, isPresent, BaseException, stringify} from 'angular2/src/core/facade/lang';
import {DirectiveMetadata} from 'angular2/metadata';
import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
import {
DirectiveMetadata,
ComponentMetadata,
PropertyMetadata,
EventMetadata
} from 'angular2/metadata';
import {reflector} from 'angular2/src/core/reflection/reflection';
/**
@ -16,15 +22,78 @@ export class DirectiveResolver {
* Return {@link DirectiveMetadata} for a given `Type`.
*/
resolve(type: Type): DirectiveMetadata {
var annotations = reflector.annotations(resolveForwardRef(type));
if (isPresent(annotations)) {
for (var i = 0; i < annotations.length; i++) {
var annotation = annotations[i];
if (annotation instanceof DirectiveMetadata) {
return annotation;
var typeMetadata = reflector.annotations(resolveForwardRef(type));
if (isPresent(typeMetadata)) {
for (var i = 0; i < typeMetadata.length; i++) {
var metadata = typeMetadata[i];
if (metadata instanceof DirectiveMetadata) {
var propertyMetadata = reflector.propMetadata(type);
return this._mergeWithPropertyMetadata(metadata, propertyMetadata);
}
}
}
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
}
private _mergeWithPropertyMetadata(dm: DirectiveMetadata,
propertyMetadata:
StringMap<string, any[]>): DirectiveMetadata {
var properties = [];
var events = [];
StringMapWrapper.forEach(propertyMetadata, (metadata: any[], propName: string) => {
metadata.forEach(a => {
if (a instanceof PropertyMetadata) {
if (isPresent(a.bindingPropertyName)) {
properties.push(`${propName}: ${a.bindingPropertyName}`);
} else {
properties.push(propName);
}
}
if (a instanceof EventMetadata) {
if (isPresent(a.bindingPropertyName)) {
events.push(`${propName}: ${a.bindingPropertyName}`);
} else {
events.push(propName);
}
}
});
});
return this._merge(dm, properties, events);
}
private _merge(dm: DirectiveMetadata, properties: string[], events: string[]): DirectiveMetadata {
var mergedProperties =
isPresent(dm.properties) ? ListWrapper.concat(dm.properties, properties) : properties;
var mergedEvents = isPresent(dm.events) ? ListWrapper.concat(dm.events, events) : events;
if (dm instanceof ComponentMetadata) {
return new ComponentMetadata({
selector: dm.selector,
properties: mergedProperties,
events: mergedEvents,
host: dm.host,
lifecycle: dm.lifecycle,
bindings: dm.bindings,
exportAs: dm.exportAs,
compileChildren: dm.compileChildren,
changeDetection: dm.changeDetection,
viewBindings: dm.viewBindings
});
} else {
return new DirectiveMetadata({
selector: dm.selector,
properties: mergedProperties,
events: mergedEvents,
host: dm.host,
lifecycle: dm.lifecycle,
bindings: dm.bindings,
exportAs: dm.exportAs,
compileChildren: dm.compileChildren
});
}
}
}

View File

@ -96,3 +96,19 @@ class ViewQuery extends ViewQueryMetadata {
const ViewQuery(dynamic /*Type | string*/ selector, {bool descendants: false})
: super(selector, descendants: descendants);
}
/**
* See: [PropertyMetadata] for docs.
*/
class Property extends PropertyMetadata {
const Property([String bindingPropertyName])
: super(bindingPropertyName);
}
/**
* See: [EventMetadata] for docs.
*/
class Event extends EventMetadata {
const Event([String bindingPropertyName])
: super(bindingPropertyName);
}

View File

@ -13,7 +13,9 @@ export {
ComponentMetadata,
DirectiveMetadata,
PipeMetadata,
LifecycleEvent
LifecycleEvent,
PropertyMetadata,
EventMetadata
} from './metadata/directives';
export {ViewMetadata, ViewEncapsulation} from './metadata/view';
@ -29,13 +31,21 @@ import {
ComponentMetadata,
DirectiveMetadata,
PipeMetadata,
LifecycleEvent
LifecycleEvent,
PropertyMetadata,
EventMetadata
} from './metadata/directives';
import {ViewMetadata, ViewEncapsulation} from './metadata/view';
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection/change_detection';
import {makeDecorator, makeParamDecorator, TypeDecorator, Class} from './util/decorators';
import {
makeDecorator,
makeParamDecorator,
makePropDecorator,
TypeDecorator,
Class
} from './util/decorators';
import {Type} from 'angular2/src/core/facade/lang';
/**
@ -397,6 +407,46 @@ export interface PipeFactory {
}): any;
}
/**
* {@link PropertyMetadata} factory for creating decorators.
*
* ## Example as TypeScript Decorator
*
* ```
* @Directive({
* selector: 'sample-dir'
* })
* class SampleDir {
* @Property() property; // Same as @Property('property') property;
* @Property("el-property") dirProperty;
* }
* ```
*/
export interface PropertyFactory {
(bindingPropertyName?: string): any;
new (bindingPropertyName?: string): any;
}
/**
* {@link EventMetadata} factory for creating decorators.
*
* ## Example as TypeScript Decorator
*
* ```
* @Directive({
* selector: 'sample-dir'
* })
* class SampleDir {
* @Event() event = new EventEmitter(); // Same as @Event('event') event = new EventEmitter();
* @Event("el-event") dirEvent = new EventEmitter();
* }
* ```
*/
export interface EventFactory {
(bindingPropertyName?: string): any;
new (bindingPropertyName?: string): any;
}
/**
* {@link ComponentMetadata} factory function.
*/
@ -433,3 +483,13 @@ export var ViewQuery: QueryFactory = makeParamDecorator(ViewQueryMetadata);
* {@link PipeMetadata} factory function.
*/
export var Pipe: PipeFactory = <PipeFactory>makeDecorator(PipeMetadata);
/**
* {@link PropertyMetadata} factory function.
*/
export var Property: PropertyFactory = makePropDecorator(PropertyMetadata);
/**
* {@link EventMetadata} factory function.
*/
export var Event: EventFactory = makePropDecorator(EventMetadata);

View File

@ -1070,3 +1070,43 @@ export class PipeMetadata extends InjectableMetadata {
this.name = name;
}
}
/**
* Declare a bound field.
*
* ## Example
*
* ```
* @Directive({
* selector: 'sample-dir'
* })
* class SampleDir {
* @Property() property; // Same as @Property('property') property;
* @Property("el-property") dirProperty;
* }
* ```
*/
@CONST()
export class PropertyMetadata {
constructor(public bindingPropertyName?: string) {}
}
/**
* Declare a bound event.
*
* ## Example
*
* ```
* @Directive({
* selector: 'sample-dir'
* })
* class SampleDir {
* @Event() event = new EventEmitter(); // Same as @Event('event') event = new EventEmitter();
* @Event("el-event") dirEvent = new EventEmitter();
* }
* ```
*/
@CONST()
export class EventMetadata {
constructor(public bindingPropertyName?: string) {}
}

View File

@ -41,6 +41,11 @@ class ReflectionCapabilities extends standard.ReflectionCapabilities {
return super.annotations(typeOrFunc);
}
Map propMetadata(typeOrFunc) {
_notify('propMetadata', typeOrFunc);
return super.propMetadata(typeOrFunc);
}
GetterFn getter(String name) {
_notify('getter', name);
return super.getter(name);

View File

@ -5,8 +5,9 @@ export interface PlatformReflectionCapabilities {
isReflectionEnabled(): boolean;
factory(type: Type): Function;
interfaces(type: Type): any[];
parameters(type: Type): any[][];
annotations(type: Type): any[];
parameters(type: any): any[][];
annotations(type: any): any[];
propMetadata(typeOrFunc: any): StringMap<string, any[]>;
getter(name: string): GetterFn;
setter(name: string): SetterFn;
method(name: string): MethodFn;

View File

@ -27,6 +27,10 @@ class NoReflectionCapabilities implements PlatformReflectionCapabilities {
throw "Cannot find reflection information on ${stringify(type)}";
}
Map propMetadata(Type type) {
throw "Cannot find reflection information on ${stringify(type)}";
}
GetterFn getter(String name) {
throw "Cannot find getter ${name}";
}

View File

@ -255,6 +255,19 @@ class ReflectionCapabilities implements PlatformReflectionCapabilities {
return meta.map((m) => m.reflectee).toList();
}
Map propMetadata(typeOrFunc) {
final res = {};
reflectClass(typeOrFunc).declarations.forEach((k,v) {
var name = _normalizeName(MirrorSystem.getName(k));
res[name] = v.metadata.map((fm) => fm.reflectee).toList();
});
return res;
}
String _normalizeName(String name) {
return name.endsWith("=") ? name.substring(0, name.length - 1) : name;
}
List interfaces(type) {
ClassMirror classMirror = reflectType(type);
return classMirror.superinterfaces.map((si) => si.reflectedType).toList();

View File

@ -109,37 +109,53 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
return result;
}
parameters(typeOfFunc: Type): any[][] {
parameters(typeOrFunc: Type): any[][] {
// Prefer the direct API.
if (isPresent((<any>typeOfFunc).parameters)) {
return (<any>typeOfFunc).parameters;
if (isPresent((<any>typeOrFunc).parameters)) {
return (<any>typeOrFunc).parameters;
}
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
var paramAnnotations = this._reflect.getMetadata('parameters', typeOfFunc);
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOfFunc);
var paramAnnotations = this._reflect.getMetadata('parameters', typeOrFunc);
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOrFunc);
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
}
}
return ListWrapper.createFixedSize((<any>typeOfFunc).length);
return ListWrapper.createFixedSize((<any>typeOrFunc).length);
}
annotations(typeOfFunc: Type): any[] {
annotations(typeOrFunc: Type): any[] {
// Prefer the direct API.
if (isPresent((<any>typeOfFunc).annotations)) {
var annotations = (<any>typeOfFunc).annotations;
if (isPresent((<any>typeOrFunc).annotations)) {
var annotations = (<any>typeOrFunc).annotations;
if (isFunction(annotations) && annotations.annotations) {
annotations = annotations.annotations;
}
return annotations;
}
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
var annotations = this._reflect.getMetadata('annotations', typeOfFunc);
var annotations = this._reflect.getMetadata('annotations', typeOrFunc);
if (isPresent(annotations)) return annotations;
}
return [];
}
propMetadata(typeOrFunc: any): StringMap<string, any[]> {
// Prefer the direct API.
if (isPresent((<any>typeOrFunc).propMetadata)) {
var propMetadata = (<any>typeOrFunc).propMetadata;
if (isFunction(propMetadata) && propMetadata.propMetadata) {
propMetadata = propMetadata.propMetadata;
}
return propMetadata;
}
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
var propMetadata = this._reflect.getMetadata('propMetadata', typeOrFunc);
if (isPresent(propMetadata)) return propMetadata;
}
return {};
}
interfaces(type: Type): any[] {
throw new BaseException("JavaScript does not support interfaces");
}

View File

@ -14,17 +14,8 @@ export {SetterFn, GetterFn, MethodFn} from './types';
export {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
export class ReflectionInfo {
_factory: Function;
_annotations: any[];
_parameters: any[][];
_interfaces: any[];
constructor(annotations?: any[], parameters?: any[][], factory?: Function, interfaces?: any[]) {
this._annotations = annotations;
this._parameters = parameters;
this._factory = factory;
this._interfaces = interfaces;
}
constructor(public annotations?: any[], public parameters?: any[][], public factory?: Function,
public interfaces?: any[], public propMetadata?: StringMap<string, any[]>) {}
}
export class Reflector {
@ -87,7 +78,7 @@ export class Reflector {
factory(type: Type): Function {
if (this._containsReflectionInfo(type)) {
var res = this._getReflectionInfo(type)._factory;
var res = this._getReflectionInfo(type).factory;
return isPresent(res) ? res : null;
} else {
return this.reflectionCapabilities.factory(type);
@ -96,7 +87,7 @@ export class Reflector {
parameters(typeOrFunc: /*Type*/ any): any[] {
if (this._injectableInfo.has(typeOrFunc)) {
var res = this._getReflectionInfo(typeOrFunc)._parameters;
var res = this._getReflectionInfo(typeOrFunc).parameters;
return isPresent(res) ? res : [];
} else {
return this.reflectionCapabilities.parameters(typeOrFunc);
@ -105,16 +96,25 @@ export class Reflector {
annotations(typeOrFunc: /*Type*/ any): any[] {
if (this._injectableInfo.has(typeOrFunc)) {
var res = this._getReflectionInfo(typeOrFunc)._annotations;
var res = this._getReflectionInfo(typeOrFunc).annotations;
return isPresent(res) ? res : [];
} else {
return this.reflectionCapabilities.annotations(typeOrFunc);
}
}
propMetadata(typeOrFunc: /*Type*/ any): StringMap<string, any[]> {
if (this._injectableInfo.has(typeOrFunc)) {
var res = this._getReflectionInfo(typeOrFunc).propMetadata;
return isPresent(res) ? res : {};
} else {
return this.reflectionCapabilities.propMetadata(typeOrFunc);
}
}
interfaces(type: Type): any[] {
if (this._injectableInfo.has(type)) {
var res = this._getReflectionInfo(type)._interfaces;
var res = this._getReflectionInfo(type).interfaces;
return isPresent(res) ? res : [];
} else {
return this.reflectionCapabilities.interfaces(type);

View File

@ -290,3 +290,24 @@ export function makeParamDecorator(annotationCls): any {
ParamDecoratorFactory.prototype = Object.create(annotationCls.prototype);
return ParamDecoratorFactory;
}
export function makePropDecorator(decoratorCls): any {
function PropDecoratorFactory(...args): any {
var decoratorInstance = Object.create(decoratorCls.prototype);
decoratorCls.apply(decoratorInstance, args);
if (this instanceof decoratorCls) {
return decoratorInstance;
} else {
return function PropDecorator(target: any, name: string) {
var meta = Reflect.getOwnMetadata('propMetadata', target.constructor);
meta = meta || {};
meta[name] = meta[name] || [];
meta[name].unshift(decoratorInstance);
Reflect.defineMetadata('propMetadata', meta, target.constructor);
};
}
}
PropDecoratorFactory.prototype = Object.create(decoratorCls.prototype);
return PropDecoratorFactory;
}

View File

@ -1,36 +0,0 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DirectiveMetadata, Directive} from 'angular2/metadata';
@Directive({selector: 'someDirective'})
class SomeDirective {
}
@Directive({selector: 'someChildDirective'})
class SomeChildDirective extends SomeDirective {
}
class SomeDirectiveWithoutAnnotation {}
export function main() {
describe("DirectiveResolver", () => {
var reader;
beforeEach(() => { reader = new DirectiveResolver(); });
it('should read out the Directive annotation', () => {
var directiveMetadata = reader.resolve(SomeDirective);
expect(directiveMetadata).toEqual(new Directive({selector: 'someDirective'}));
});
it('should throw if not matching annotation is found', () => {
expect(() => { reader.resolve(SomeDirectiveWithoutAnnotation); })
.toThrowError('No Directive annotation found on SomeDirectiveWithoutAnnotation');
});
it('should not read parent class Directive annotations', function() {
var directiveMetadata = reader.resolve(SomeChildDirective);
expect(directiveMetadata).toEqual(new Directive({selector: 'someChildDirective'}));
});
});
}

View File

@ -0,0 +1,102 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DirectiveMetadata, Directive, Property, Event} from 'angular2/metadata';
@Directive({selector: 'someDirective'})
class SomeDirective {
}
@Directive({selector: 'someChildDirective'})
class SomeChildDirective extends SomeDirective {
}
@Directive({selector: 'someDirective', properties: ['c']})
class SomeDirectiveWithProps {
@Property() a;
@Property("renamed") b;
c;
}
@Directive({selector: 'someDirective', events: ['c']})
class SomeDirectiveWithEvents {
@Event() a;
@Event("renamed") b;
c;
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithSetterProps {
@Property("renamed")
set a(value) {
}
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithGetterEvents {
@Event("renamed")
get a() {
return null;
}
}
class SomeDirectiveWithoutMetadata {}
export function main() {
describe("DirectiveResolver", () => {
var resolver;
beforeEach(() => { resolver = new DirectiveResolver(); });
it('should read out the Directive metadata', () => {
var directiveMetadata = resolver.resolve(SomeDirective);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata({selector: 'someDirective', properties: [], events: []}));
});
it('should throw if not matching metadata is found', () => {
expect(() => { resolver.resolve(SomeDirectiveWithoutMetadata); })
.toThrowError('No Directive annotation found on SomeDirectiveWithoutMetadata');
});
it('should not read parent class Directive metadata', function() {
var directiveMetadata = resolver.resolve(SomeChildDirective);
expect(directiveMetadata)
.toEqual(
new DirectiveMetadata({selector: 'someChildDirective', properties: [], events: []}));
});
describe('properties', () => {
it('should append directive properties', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithProps);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: ['c', 'a', 'b: renamed'], events: []}));
});
it('should work with getters and setters', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithSetterProps);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: ['a: renamed'], events: []}));
});
});
describe('events', () => {
it('should append directive events', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithEvents);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: [], events: ['c', 'a', 'b: renamed']}));
});
it('should work with getters and setters', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithGetterEvents);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: [], events: ['a: renamed']}));
});
});
});
}

View File

@ -64,7 +64,17 @@ import {
ChangeDetectorGenConfig
} from 'angular2/src/core/change_detection/change_detection';
import {Directive, Component, View, ViewMetadata, Attribute, Query, Pipe} from 'angular2/metadata';
import {
Directive,
Component,
View,
ViewMetadata,
Attribute,
Query,
Pipe,
Property,
Event
} from 'angular2/metadata';
import {QueryList} from 'angular2/src/core/compiler/query_list';
@ -1597,6 +1607,48 @@ export function main() {
}));
}
});
describe('property decorators', () => {
it('should support property decorators',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(
MyComp, new ViewMetadata({
template: '<with-prop-decorators el-prop="aaa"></with-prop-decorators>',
directives: [DirectiveWithPropDecorators]
}))
.createAsync(MyComp)
.then((rootTC) => {
rootTC.detectChanges();
var dir = rootTC.componentViewChildren[0].inject(DirectiveWithPropDecorators);
expect(dir.dirProp).toEqual("aaa");
async.done();
});
}));
if (DOM.supportsDOMEvents()) {
it('should support events decorators',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
tcb = tcb.overrideView(
MyComp, new ViewMetadata({
template: `<with-prop-decorators (el-event)="ctxProp='called'">`,
directives: [DirectiveWithPropDecorators]
}));
var rootTC;
tcb.createAsync(MyComp).then(root => { rootTC = root; });
tick();
var emitter =
rootTC.componentViewChildren[0].inject(DirectiveWithPropDecorators);
emitter.fireEvent('fired !');
tick();
expect(rootTC.componentInstance.ctxProp).toEqual("called");
})));
}
});
});
}
@ -2148,3 +2200,11 @@ class OtherDuplicateDir {
class DirectiveThrowingAnError {
constructor() { throw new BaseException("BOOM"); }
}
@Directive({selector: 'with-prop-decorators'})
class DirectiveWithPropDecorators {
@Property("elProp") dirProp: string;
@Event('elEvent') event = new EventEmitter();
fireEvent(msg) { ObservableWrapper.callNext(this.event, msg); }
}

View File

@ -10,6 +10,12 @@ class ParamDecorator {
const ParamDecorator(this.value);
}
class PropDecorator {
final dynamic value;
const PropDecorator(this.value);
}
ClassDecorator classDecorator(value) {
return new ClassDecorator(value);
}
@ -17,3 +23,7 @@ ClassDecorator classDecorator(value) {
ParamDecorator paramDecorator(value) {
return new ParamDecorator(value);
}
PropDecorator propDecorator(value) {
return new PropDecorator(value);
}

View File

@ -1,24 +1,33 @@
import {makeDecorator, makeParamDecorator} from 'angular2/src/core/util/decorators';
import {
makeDecorator,
makeParamDecorator,
makePropDecorator
} from 'angular2/src/core/util/decorators';
export class ClassDecoratorImpl {
value;
constructor(value) { this.value = value; }
export class ClassDecoratorMeta {
constructor(public value) {}
}
export class ParamDecoratorImpl {
value;
export class ParamDecoratorMeta {
constructor(public value) {}
}
constructor(value) { this.value = value; }
export class PropDecoratorMeta {
constructor(public value) {}
}
export function classDecorator(value) {
return new ClassDecoratorImpl(value);
return new ClassDecoratorMeta(value);
}
export function paramDecorator(value) {
return new ParamDecoratorImpl(value);
return new ParamDecoratorMeta(value);
}
export var ClassDecorator = makeDecorator(ClassDecoratorImpl);
export var ParamDecorator = makeParamDecorator(ParamDecoratorImpl);
export function propDecorator(value) {
return new PropDecoratorMeta(value);
}
export var ClassDecorator = makeDecorator(ClassDecoratorMeta);
export var ParamDecorator = makeParamDecorator(ParamDecoratorMeta);
export var PropDecorator = makePropDecorator(PropDecoratorMeta);

View File

@ -1,7 +1,14 @@
import {describe, it, iit, ddescribe, expect, beforeEach} from 'angular2/test_lib';
import {Reflector, ReflectionInfo} from 'angular2/src/core/reflection/reflection';
import {ReflectionCapabilities} from 'angular2/src/core/reflection/reflection_capabilities';
import {ClassDecorator, ParamDecorator, classDecorator, paramDecorator} from './reflector_common';
import {
ClassDecorator,
ParamDecorator,
PropDecorator,
classDecorator,
paramDecorator,
propDecorator
} from './reflector_common';
import {IS_DART} from '../../platform';
class AType {
@ -12,9 +19,13 @@ class AType {
@ClassDecorator('class')
class ClassWithDecorators {
a;
@PropDecorator("p1") @PropDecorator("p2") a;
b;
@PropDecorator("p3")
set c(value) {
}
constructor(@ParamDecorator("a") a: AType, @ParamDecorator("b") b: AType) {
this.a = a;
this.b = b;
@ -138,6 +149,19 @@ export function main() {
});
});
describe("propMetadata", () => {
it("should return a string map of prop metadata for the given class", () => {
var p = reflector.propMetadata(ClassWithDecorators);
expect(p["a"]).toEqual([propDecorator("p1"), propDecorator("p2")]);
expect(p["c"]).toEqual([propDecorator("p3")]);
});
it("should return registered meta if available", () => {
reflector.registerType(TestObj, new ReflectionInfo(null, null, null, null, {"a": [1, 2]}));
expect(reflector.propMetadata(TestObj)).toEqual({"a": [1, 2]});
});
});
describe("annotations", () => {
it("should return an array of annotations for a type", () => {
var p = reflector.annotations(ClassWithDecorators);

View File

@ -20,6 +20,8 @@ class NullReflectionCapabilities implements ReflectionCapabilities {
List annotations(typeOrFunc) => _notImplemented('annotations');
Map propMetadata(typeOrFunc) => _notImplemented('propMetadata');
GetterFn getter(String name) => _nullGetter;
SetterFn setter(String name) => _nullSetter;