diff --git a/src/core/reflection/reflection_capabilities.ts b/src/core/reflection/reflection_capabilities.ts index 4b901fe..5538679 100644 --- a/src/core/reflection/reflection_capabilities.ts +++ b/src/core/reflection/reflection_capabilities.ts @@ -181,6 +181,27 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { return result; } + /** @internal */ + _getParent( typeOrFunc: any ): any { + const parent = Object.getPrototypeOf(typeOrFunc.prototype); + + return parent && parent.constructor ? + parent.constructor : + null; + } + + /** @internal */ + _assign( target: T, source: U ): T & U { + const merged: any = target; + for (let key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + merged[key] = source[key]; + } + } + return merged; + } + + parameters( typeOrFunc: Type ): any[][] { // // Prefer the direct API. // if (isPresent((typeOrFunc).parameters)) { @@ -209,7 +230,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { } rawParameters( typeOrFunc: Type ): any[][] { - return this._reflect.getMetadata( PARAM_META_KEY, typeOrFunc ); + return this._reflect.getOwnMetadata( PARAM_META_KEY, typeOrFunc ); } registerParameters( parameters, type: Type ): void { @@ -250,7 +271,12 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { // return propMetadata; // } if ( isReflectMetadata( this._reflect ) ) { - const propMetadata = this._reflect.getMetadata( PROP_META_KEY, typeOrFunc ); + const propMetadata = {}; + // Loop type and type parents + while (typeOrFunc) { + this._assign(propMetadata, this._reflect.getMetadata( PROP_META_KEY, typeOrFunc )); + typeOrFunc = this._getParent(typeOrFunc); + } if ( isPresent( propMetadata ) ) return propMetadata; } return {}; diff --git a/test/core/reflection/reflection.spec.ts b/test/core/reflection/reflection.spec.ts index c7ab1e5..e8488a3 100644 --- a/test/core/reflection/reflection.spec.ts +++ b/test/core/reflection/reflection.spec.ts @@ -48,6 +48,36 @@ describe( `reflection/reflector`, ()=> { expect(actual).to.deep.equal(expected); } ); + it ( `should respect parent property metadata`, () => { + class FooMetadata{ + toString(): string { return `@Foo()`; } + } + const Foo = makePropDecorator(FooMetadata); + class BarMetadata{ + toString(): string { return `@Bar()`; } + } + const Bar = makePropDecorator(BarMetadata); + + class Test{ + @Foo() jedi: string; + + constructor(){ + this.jedi = 'Obi-wan Kenobi'; + } + } + class TestA extends Test{ + @Bar() luke: string; + + constructor(){ + super(); + this.luke = 'Obi-wan Kenobi'; + } + } + const actual = reflector.propMetadata(TestA); + const expected = { luke: [ BarMetadata.prototype ], jedi: [ FooMetadata.prototype ] }; + + expect(actual).to.deep.equal(expected); + }); it( `should extract constructor params metadata if present`, ()=> { function _createProto( Type, props ) { @@ -74,5 +104,96 @@ describe( `reflection/reflector`, ()=> { expect(actual).to.deep.equal(expected); } ); + it ( `should not duplicate constructor param metadata from the parent constructor to the child if the child has it's own params`, ()=> { + function _createProto( Type, props ) { + const instance = Object.create(Type.prototype); + return assign(instance,props); + } + + abstract class AbstractConstructorComponent { + private form: any; + constructor (@Inject('form') @Host() form: any) { + this.form = form; + } + } + + class AConstructorComponent extends AbstractConstructorComponent { + private $http: any; + constructor (@Inject('form') @Host() form: any, + @Inject('$http') $http: any) { + super(form); + this.$http = $http; + } + } + + class BConstructorComponent extends AbstractConstructorComponent { + private app: any; + constructor (@Inject('form') @Host() form: any, + @Inject('app') @Host() app: any) { + super(form); + this.app = app; + } + } + + class CConstructorComponent extends AbstractConstructorComponent { } + + class DConstructorComponent extends AbstractConstructorComponent { + constructor () { + super (null); + } + } + + class EConstructorComponent extends AbstractConstructorComponent { + constructor (form: any) { + super (form); + } + } + + class FConstructorComponent extends BConstructorComponent { + constructor (@Inject('$http') $http: any, + @Inject('form') @Host() form: any) { + super (form, $http); + } + } + + const actualA = reflector.parameters(AConstructorComponent); + const expectedA = [ + [_createProto(HostMetadata, null), _createProto(InjectMetadata,{token:'form'})], + [_createProto(InjectMetadata,{token:'$http'})] + ]; + expect(actualA).to.deep.equal(expectedA); + + const actualB = reflector.parameters(BConstructorComponent); + const expectedB = [ + [_createProto(HostMetadata, null), _createProto(InjectMetadata,{token:'form'})], + [_createProto(HostMetadata, null), _createProto(InjectMetadata,{token:'app'})] + ]; + expect(actualB).to.deep.equal(expectedB); + + const actualC = reflector.parameters(CConstructorComponent); + const expectedC = [ + [_createProto(HostMetadata, null), _createProto(InjectMetadata,{token:'form'})] + ]; + expect(actualC).to.deep.equal(expectedC); + + const actualD = reflector.parameters(DConstructorComponent); + const expectedD = [ + [_createProto(HostMetadata, null), _createProto(InjectMetadata,{token:'form'})] + ]; + expect(actualD).to.deep.equal(expectedD); + + const actualE = reflector.parameters(DConstructorComponent); + const expectedE = [ + [_createProto(HostMetadata, null), _createProto(InjectMetadata,{token:'form'})] + ]; + expect(actualE).to.deep.equal(expectedE); + + const actualF = reflector.parameters(FConstructorComponent); + const expectedF = [ + [_createProto(InjectMetadata,{token:'$http'})], + [_createProto(HostMetadata, null), _createProto(InjectMetadata,{token:'form'})] + ]; + expect(actualF).to.deep.equal(expectedF); + } ); } );