diff --git a/ng-metadata.test.ts b/ng-metadata.test.ts index 7343df1..a0c2716 100644 --- a/ng-metadata.test.ts +++ b/ng-metadata.test.ts @@ -11,6 +11,7 @@ import './test/providers.spec'; import './test/di/decorators.spec'; import './test/util/decorators.spec'; +import './test/reflection/reflection.spec'; describe( 'ng-metadata', ()=> { diff --git a/src/reflection/reflection.ts b/src/reflection/reflection.ts new file mode 100644 index 0000000..de8241e --- /dev/null +++ b/src/reflection/reflection.ts @@ -0,0 +1,7 @@ +import {Reflector} from './reflector'; + +/** + * The {@link Reflector} used internally in Angular to access metadata + * about symbols. + */ +export var reflector = new Reflector(); diff --git a/src/reflection/reflector.ts b/src/reflection/reflector.ts new file mode 100644 index 0000000..0e7df46 --- /dev/null +++ b/src/reflection/reflector.ts @@ -0,0 +1,96 @@ +import {Type,isPresent,isFunction} from "../facade/lang"; + +// This will be needed when we will used Reflect APIs +/*const Reflect = global.Reflect; + if (!(Reflect && Reflect.getMetadata)) { + throw 'reflect-metadata shim is required when using class decorators'; + }*/ + +/** + * @internal + * @private + * @type {string} + */ +export const CLASS_META_KEY = '__mAnnotations'; +/** + * @internal + * @private + * @type {string} + */ +export const PARAM_META_KEY = '__mParameters'; +/** + * @internal + * @private + * @type {string} + */ +export const PROP_META_KEY = '__mPropMetadata'; + + +/** + * Provides access to reflection data about symbols. Used internally by Angular + * to power dependency injection and compilation. + */ +export class Reflector { + + parameters( typeOrFunc: Type ): any[][] { + //return Reflect.getMetadata('parameters', cls); + return extractParameter( typeOrFunc ); + } + + registerParameters( parameters, type: Type ): void { + //Reflect.defineMetadata('parameters', parameters, cls); + type[ PARAM_META_KEY ] = parameters; + } + + annotations( typeOrFunc: Type ): any[] { + //return Reflect.getOwnMetadata('annotations', cls); + return extractAnnotation( typeOrFunc ); + } + + registerAnnotation( annotations, type: Type ): void { + //Reflect.defineMetadata('annotations', annotations, cls); + type[ CLASS_META_KEY ] = annotations; + } + + propMetadata( typeOrFunc: Type ): {[key: string]: any[]} { + //return Reflect.getOwnMetadata('propMetadata', target.constructor); + return extractProperty( typeOrFunc ); + } + + registerPropMetadata( propMetadata, type: Type ): void { + //Reflect.defineMetadata('propMetadata', meta, target.constructor); + type.constructor[ PROP_META_KEY ] = propMetadata; + } + +} + + +function extract( metaKey: string ) { + + return function ( cls: any ): any { + + if ( isFunction( cls ) && cls.hasOwnProperty( metaKey ) ) { + // it is a decorator, extract annotation + return cls[ metaKey ]; + } + + } + +} + +function extractAnnotation( cls: any ): any[] { + + return extract( CLASS_META_KEY )( cls ); + +} + +function extractParameter( cls: any ): any[][] { + + return extract( PARAM_META_KEY )( cls ); + +} +function extractProperty( cls: any ): {[name:string]:any[]} { + + return extract( PROP_META_KEY )( cls ); + +} diff --git a/src/util/decorators.ts b/src/util/decorators.ts index a85511a..115a093 100644 --- a/src/util/decorators.ts +++ b/src/util/decorators.ts @@ -1,24 +1,5 @@ import {global, Type, isFunction, isString, isPresent} from '../facade/lang'; - - -/** - * @internal - * @private - * @type {string} - */ -export const CLASS_META_KEY = '__mAnnotations'; -/** - * @internal - * @private - * @type {string} - */ -export const PARAM_META_KEY = '__mParameters'; -/** - * @internal - * @private - * @type {string} - */ -export const PROP_META_KEY = '__mPropMetadata'; +import {reflector} from '../reflection/reflection'; /** * An interface implemented by all Angular type decorators, @@ -45,41 +26,6 @@ export interface TypeDecorator { } -function extract( metaKey: string ) { - - return function ( cls: any ): any { - - if ( isFunction( cls ) && cls.hasOwnProperty( metaKey ) ) { - // it is a decorator, extract annotation - return cls[ metaKey ]; - } - - } - -} -export function extractAnnotation( cls: any ): any { - - return extract( CLASS_META_KEY )(cls); - -} - -export function extractParameter( cls: any ): any { - - return extract( PARAM_META_KEY )(cls); - -} -export function extractProperty( cls: any ): any { - - return extract( PROP_META_KEY )(cls); - -} - -// This will be needed when we will used Reflect APIs -/*const Reflect = global.Reflect; - if (!(Reflect && Reflect.getMetadata)) { - throw 'reflect-metadata shim is required when using class decorators'; - }*/ - export function makeDecorator( AnnotationCls: any, @@ -103,22 +49,16 @@ export function makeDecorator( function TypeDecorator( cls ): TypeDecorator { - //var annotations = Reflect.getOwnMetadata('annotations', cls); - var annotations = cls[ CLASS_META_KEY ]; + let annotations = reflector.annotations(cls); annotations = annotations || []; annotations.push( annotationInstance ); - - //Reflect.defineMetadata('annotations', annotations, cls); - cls[ CLASS_META_KEY ] = annotations; + reflector.registerAnnotation(annotations,cls); return cls; } - //TypeDecorator.annotations = chainAnnotation; - //TypeDecorator.Class = Class; - if ( chainFn ) { chainFn( TypeDecorator ); } @@ -168,8 +108,7 @@ export function makeParamDecorator( annotationCls, overrideParamDecorator: Funct } - //var parameters: any[][] = Reflect.getMetadata('parameters', cls); - var parameters: any[][] = cls[ PARAM_META_KEY ]; + let parameters: any[][] = reflector.parameters(cls); parameters = parameters || []; // there might be gaps if some in between parameters do not have annotations. @@ -180,11 +119,10 @@ export function makeParamDecorator( annotationCls, overrideParamDecorator: Funct parameters[ index ] = parameters[ index ] || []; - var annotationsForParam: any[] = parameters[ index ]; + const annotationsForParam: any[] = parameters[ index ]; annotationsForParam.push( annotationInstance ); - //Reflect.defineMetadata('parameters', parameters, cls); - cls[ PARAM_META_KEY ] = parameters; + reflector.registerParameters(parameters,cls); return cls; @@ -213,19 +151,15 @@ export function makePropDecorator( decoratorCls ): any { return function PropDecorator( target: any, name: string ) { - - //var meta = Reflect.getOwnMetadata('propMetadata', target.constructor); - var meta = target.constructor[ PROP_META_KEY ]; + let meta = reflector.propMetadata(target); meta = meta || {}; meta[ name ] = meta[ name ] || []; meta[ name ].unshift( decoratorInstance ); + reflector.registerPropMetadata(meta,target); - //Reflect.defineMetadata('propMetadata', meta, target.constructor); - target.constructor[ PROP_META_KEY ] = meta; - }; } diff --git a/test/di/decorators.spec.ts b/test/di/decorators.spec.ts index 2422001..9e4a0aa 100644 --- a/test/di/decorators.spec.ts +++ b/test/di/decorators.spec.ts @@ -15,10 +15,7 @@ import { SelfMetadata, SkipSelfMetadata } from '../../src/di/metadata'; -import { - CLASS_META_KEY, - PARAM_META_KEY -} from '../../src/util/decorators'; +import { reflector } from '../../src/reflection/reflection'; import {isArray,isBlank,isPresent} from '../../src/facade/lang' describe( 'di/decorators', () => { @@ -31,7 +28,7 @@ describe( 'di/decorators', () => { class Test { } - expect( Array.isArray( Test[ CLASS_META_KEY ] ) ).to.equal( true ); + expect( Array.isArray( reflector.annotations( Test ) ) ).to.equal( true ); } ); @@ -55,21 +52,21 @@ describe( 'di/decorators', () => { } ); it( 'should create param metadata on class', () => { - expect( Array.isArray( cls[ PARAM_META_KEY ] ) ).to.equal( true ); + expect( Array.isArray( reflector.parameters(cls) ) ).to.equal( true ); } ); it( 'should create 2 dimensional param metadata', () => { - const [paramOne,paramTwo] = cls[ PARAM_META_KEY ]; + const [paramOne,paramTwo] = reflector.parameters(cls); - expect( cls[ PARAM_META_KEY ].length ).to.equal( 2 ); + expect( reflector.parameters(cls).length ).to.equal( 2 ); expect( paramOne.length ).to.equal( 1 ); expect( paramTwo.length ).to.equal( 2 ); } ); it( 'should put to proper indexes proper paramDecorator instance', () => { - const [paramOne,paramTwo] = cls[ PARAM_META_KEY ]; + const [paramOne,paramTwo] = reflector.parameters(cls); expect( paramOne[ 0 ] instanceof InjectMetadata ).to.equal( true ); expect( paramTwo[ 0 ] instanceof InjectMetadata ).to.equal( true ); @@ -104,7 +101,7 @@ describe( 'di/decorators', () => { it( 'should not add instance to PARAM_META_KEY if used on non constructor', () => { - expect( isBlank( cls[ PARAM_META_KEY ] ) ).to.equal( true ); + expect( isBlank( reflector.parameters(cls) ) ).to.equal( true ); } ); @@ -143,8 +140,8 @@ describe( 'di/decorators', () => { const cls = TestBothInjectProvider; - expect( isBlank( cls[ PARAM_META_KEY ] ) ).to.equal( false ); - expect( cls[ PARAM_META_KEY ][0][0] instanceof InjectMetadata ).to.equal( true ); + expect( isBlank( reflector.parameters(cls) ) ).to.equal( false ); + expect( reflector.parameters(cls)[0][0] instanceof InjectMetadata ).to.equal( true ); expect( isBlank( cls.$inject ) ).to.equal( true ); expect( isArray( cls.prototype.$get.$inject ) ).to.equal( true ); diff --git a/test/reflection/reflection.spec.ts b/test/reflection/reflection.spec.ts new file mode 100644 index 0000000..bee13ee --- /dev/null +++ b/test/reflection/reflection.spec.ts @@ -0,0 +1,74 @@ +import {expect} from 'chai'; +import {makePropDecorator} from '../../src/util/decorators'; +import {Injectable} from "../../src/di/decorators"; +import {Inject} from "../../src/di/decorators"; +import {InjectableMetadata} from "../../src/di/metadata"; +import {Host} from "../../src/di/decorators"; +import {HostMetadata} from "../../src/di/metadata"; +import {assign} from "../../src/facade/lang"; +import {InjectMetadata} from "../../src/di/metadata"; +import {reflector} from "../../src/reflection/reflection"; + + +describe( `reflection/reflector`, ()=> { + + it( `should extract class annotation if present`, ()=> { + + @Injectable() + class Test{} + + const actual = reflector.annotations(Test); + const expected = [InjectableMetadata.prototype]; + + expect(actual).to.deep.equal(expected); + + } ); + it( `should extract class property metadata if present`, ()=> { + + class FooMetadata{ + toString(): string { return `@Foo()`; } + } + const Foo = makePropDecorator(FooMetadata); + + class Test{ + @Foo() jedi: string; + + constructor(){ + this.jedi = 'Obi-wan Kenobi'; + } + } + + + + const actual = reflector.propMetadata(Test); + const expected = { jedi: [ FooMetadata.prototype ] }; + + expect(actual).to.deep.equal(expected); + + } ); + it( `should extract constructor params metadata if present`, ()=> { + + function _createProto( Type, props ) { + const instance = Object.create(Type.prototype); + return assign(instance,props); + } + class Test{ + constructor( + @Inject('$http') private $http: ng.IHttpService, + @Inject('$log') private $log: ng.ILogService, + @Host() @Inject('ngModel') ngModel: ng.INgModelController + ){} + } + + const actual = reflector.parameters(Test); + const expected = [ + [_createProto(InjectMetadata,{token:'$http'})], + [_createProto(InjectMetadata,{token:'$log'})], + [_createProto(InjectMetadata,{token:'ngModel'}),_createProto(HostMetadata,null)] + ]; + + expect(actual).to.deep.equal(expected); + + } ); + +} ); diff --git a/test/util/decorators.spec.ts b/test/util/decorators.spec.ts index 0c1c9f1..d79e25b 100644 --- a/test/util/decorators.spec.ts +++ b/test/util/decorators.spec.ts @@ -1,74 +1,81 @@ import {expect} from 'chai'; -import {extractAnnotation,extractParameter,extractProperty,makePropDecorator} from '../../src/util/decorators'; -import {Injectable} from "../../src/di/decorators"; -import {Inject} from "../../src/di/decorators"; -import {InjectableMetadata} from "../../src/di/metadata"; -import {Host} from "../../src/di/decorators"; -import {HostMetadata} from "../../src/di/metadata"; -import {assign} from "../../src/facade/lang"; -import {InjectMetadata} from "../../src/di/metadata"; +import {makeDecorator,makeParamDecorator,makePropDecorator} from '../../src/util/decorators'; +import {isFunction} from "../../src/facade/lang"; describe( `util/decorators`, ()=> { - describe( `extraction helpers`, ()=> { - it( `should extract class annotation if present`, ()=> { + it( `should create class decorator `, ()=> { - @Injectable() - class Test{} + class TestMetadata {} + const ClsDecorator = makeDecorator( TestMetadata ); - const actual = extractAnnotation(Test); - const expected = [InjectableMetadata.prototype]; + expect( isFunction( ClsDecorator ) ).to.equal( true ); - expect(actual).to.deep.equal(expected); + @ClsDecorator() + class Test { + } - } ); - it( `should extract class property metadata if present`, ()=> { + expect( Object.keys( Test ).length ).to.equal( 1 ); - class FooMetadata{ - toString(): string { return `@Foo()`; } - } - const Foo = makePropDecorator(FooMetadata); + } ); + + it( `should create property decorator`, ()=> { - class Test{ - @Foo() jedi: string; + class TestMetadata {} + const PropDecorator = makePropDecorator( TestMetadata ); - constructor(){ - this.jedi = 'Obi-wan Kenobi'; - } - } + expect( isFunction( PropDecorator ) ).to.equal( true ); - const actual = extractProperty(Test); - const expected = { jedi: [ FooMetadata.prototype ] }; - expect(actual).to.deep.equal(expected); + class Test { + @PropDecorator() foo: any; + } + expect( Object.keys( Test ).length ).to.equal( 1 ); - } ); - it( `should extract constructor params metadata if present`, ()=> { + } ); - function _createProto( Type, props ) { - const instance = Object.create(Type.prototype); - return assign(instance,props); - } - class Test{ - constructor( - @Inject('$http') private $http: ng.IHttpService, - @Inject('$log') private $log: ng.ILogService, - @Host() @Inject('ngModel') ngModel: ng.INgModelController - ){} - } + it( `should create parameter decorator`, ()=> { - const actual = extractParameter(Test); - const expected = [ - [_createProto(InjectMetadata,{token:'$http'})], - [_createProto(InjectMetadata,{token:'$log'})], - [_createProto(InjectMetadata,{token:'ngModel'}),_createProto(HostMetadata,null)] - ]; + class TestMetadata {} + const ParamDecorator = makeParamDecorator( TestMetadata ); - expect(actual).to.deep.equal(expected); + expect( isFunction( ParamDecorator ) ).to.equal( true ); - } ); + class Test { + constructor( @ParamDecorator() moo: any ) {} + } + expect( Object.keys( Test ).length ).to.equal( 1 ); } ); + it( `should override the paramDecorator method when callback provided`, ()=> { + + let wasOverrideCalled = false; + + function override( target, key, idx ) { + wasOverrideCalled = true; + } + + class TestMetadata {} + const ParamDecorator = makeParamDecorator( TestMetadata, override ); + + expect( isFunction( ParamDecorator ) ).to.equal( true ); + + class Test { + constructor( @ParamDecorator() moo: any ) {} + } + expect( Object.keys( Test ).length ).to.equal( 1 ); + expect( wasOverrideCalled ).to.equal( false ); + + class TestOnMethod { + foo( @ParamDecorator() moo: any ) {} + } + // if override it doesn't create metadata on class + expect( Object.keys( TestOnMethod ).length ).to.equal( 0 ); + expect( wasOverrideCalled ).to.equal( true ); + + } ); + + } );