diff --git a/src/linker/directive_resolver.ts b/src/linker/directive_resolver.ts index f5efdef..5013c84 100644 --- a/src/linker/directive_resolver.ts +++ b/src/linker/directive_resolver.ts @@ -1,11 +1,11 @@ -import {Injectable} from '../di/decorators'; -import {Type, isPresent, isBlank, stringify} from '../facade/lang'; +import {Type, isPresent, isBlank, stringify, assign} from '../facade/lang'; import {StringMapWrapper, ListWrapper} from "../facade/collections"; - +import {reflector} from '../reflection/reflection'; import { DirectiveMetadata, ComponentMetadata, InputMetadata, + AttrMetadata, OutputMetadata, HostBindingMetadata, HostListenerMetadata @@ -16,17 +16,64 @@ import { ContentChildMetadata, ViewChildMetadata } from '../directives/metadata_di'; -import {reflector} from '../reflection/reflection'; -import {assign} from "../facade/lang"; -import {AttrMetadata} from "../directives/metadata_directives"; - +import {InjectMetadata,HostMetadata,SelfMetadata,SkipSelfMetadata,OptionalMetadata} from "../di/metadata"; type PropMetaInst = InputMetadata | OutputMetadata | HostBindingMetadata | HostListenerMetadata; +type ParamMetaInst = HostMetadata | InjectMetadata | SelfMetadata | SkipSelfMetadata; +type StringMap = {[key:string]:string}; -function _isDirectiveMetadata(type: any): boolean { +function _isDirectiveMetadata( type: any ): boolean { return type instanceof DirectiveMetadata; } +function _transformInjectedDirectivesMeta( paramsMeta: ParamMetaInst[] ): StringMap { + + if ( paramsMeta.length < 2 ) { + return; + } + + const injectInst = ListWrapper.find( paramsMeta, param=>param instanceof InjectMetadata ) as InjectMetadata; + const isHost = ListWrapper.find( paramsMeta, param=>param instanceof HostMetadata ) !== -1; + + if ( !(isHost || injectInst) ) { + return; + } + + if ( !injectInst.token ) { + throw new Error( 'no Directive instance name provided within @Inject()' ); + } + + const isOptional = ListWrapper.findIndex( paramsMeta, param=>param instanceof OptionalMetadata ) !== -1; + const isSelf = ListWrapper.findIndex( paramsMeta, param=>param instanceof SelfMetadata ) !== -1; + const isSkipSelf = ListWrapper.findIndex( paramsMeta, param=>param instanceof SkipSelfMetadata ) !== -1; + + if ( isSelf && isSkipSelf ) { + throw new Error( `you cannot provide both @Self() and @SkipSelf() for @Inject(${injectInst.token})` ); + } + + let locateType = ''; + let optionalType = isOptional + ? '?' + : ''; + if ( isHost ) { + locateType = '^'; + } + if ( isSelf ) { + locateType = ''; + } + if ( isSkipSelf ) { + locateType = '^^'; + } + + const requireExpressionPrefix = `${optionalType}${locateType}`; + const directiveName = injectInst.token; + + return { + [directiveName]: `${ requireExpressionPrefix }${ directiveName }` + }; + +} + /** * Resolve a `Type` for {@link DirectiveMetadata}. */ @@ -34,25 +81,78 @@ export class DirectiveResolver { /** * Return {@link DirectiveMetadata} for a given `Type`. */ - resolve(type: Type): DirectiveMetadata { + resolve( type: Type ): DirectiveMetadata { - const typeMetadata = reflector.annotations(type); + const metadata: DirectiveMetadata = this._getDirectiveMeta( type ); - if (isPresent(typeMetadata)) { + const propertyMetadata: {[key: string]: PropMetaInst[]} = reflector.propMetadata( type ); - const metadata: DirectiveMetadata = ListWrapper.find(typeMetadata, _isDirectiveMetadata); + return this._mergeWithPropertyMetadata( metadata, propertyMetadata ); - if (isPresent(metadata)) { + } - const propertyMetadata: {[key: string]: PropMetaInst[]} = reflector.propMetadata(type); + /** + * transform parameter annotations to required directives map so we can use it + * for DDO creation + * + * map consist of : + * - key == name of directive + * - value == Angular 1 require expression + * + * @param {Type} type + * @returns {StringMap} + */ + getRequiredDirectivesMap( type: Type ): StringMap { + + const metadata: DirectiveMetadata = this._getDirectiveMeta( type ); + + const paramMetadata = reflector.parameters( type ); + + if ( isPresent( paramMetadata ) ) { + + return paramMetadata + .reduce( ( acc, paramMetaArr )=> { + + const requireExp = _transformInjectedDirectivesMeta( paramMetaArr ); + if ( isPresent( requireExp ) ) { + assign( acc, requireExp ); + } + + return acc; + + }, {} as StringMap ); + + } - return this._mergeWithPropertyMetadata(metadata, propertyMetadata); + return {} as StringMap; + + } + + /** + * + * @param type + * @returns {DirectiveMetadata} + * @throws Error + * @private + */ + private _getDirectiveMeta( type: Type ): DirectiveMetadata { + + const typeMetadata = reflector.annotations( type ); + + if ( isPresent( typeMetadata ) ) { + + const metadata: DirectiveMetadata = ListWrapper.find( typeMetadata, _isDirectiveMetadata ); + + if ( isPresent( metadata ) ) { + + return metadata; } } - throw new Error(`No Directive annotation found on ${stringify(type)}`); + throw new Error( `No Directive annotation found on ${stringify( type )}` ); + } private _mergeWithPropertyMetadata( @@ -70,76 +170,76 @@ export class DirectiveResolver { metadata.forEach( propMetaInst => { - if (propMetaInst instanceof InputMetadata) { + if ( propMetaInst instanceof InputMetadata ) { - if (isPresent(propMetaInst.bindingPropertyName)) { - inputs.push(`${propName}: ${propMetaInst.bindingPropertyName}`); + if ( isPresent( propMetaInst.bindingPropertyName ) ) { + inputs.push( `${propName}: ${propMetaInst.bindingPropertyName}` ); } else { - inputs.push(propName); + inputs.push( propName ); } } - if (propMetaInst instanceof AttrMetadata) { + if ( propMetaInst instanceof AttrMetadata ) { - if (isPresent(propMetaInst.bindingPropertyName)) { - attrs.push(`${propName}: ${propMetaInst.bindingPropertyName}`); + if ( isPresent( propMetaInst.bindingPropertyName ) ) { + attrs.push( `${propName}: ${propMetaInst.bindingPropertyName}` ); } else { - attrs.push(propName); + attrs.push( propName ); } } - if (propMetaInst instanceof OutputMetadata) { + if ( propMetaInst instanceof OutputMetadata ) { - if (isPresent(propMetaInst.bindingPropertyName)) { - outputs.push(`${propName}: ${propMetaInst.bindingPropertyName}`); + if ( isPresent( propMetaInst.bindingPropertyName ) ) { + outputs.push( `${propName}: ${propMetaInst.bindingPropertyName}` ); } else { - outputs.push(propName); + outputs.push( propName ); } } - if (propMetaInst instanceof HostBindingMetadata) { + if ( propMetaInst instanceof HostBindingMetadata ) { - if (isPresent(propMetaInst.hostPropertyName)) { - host[`[${propMetaInst.hostPropertyName}]`] = propName; + if ( isPresent( propMetaInst.hostPropertyName ) ) { + host[ `[${propMetaInst.hostPropertyName}]` ] = propName; } else { - host[`[${propName}]`] = propName; + host[ `[${propName}]` ] = propName; } } - if (propMetaInst instanceof HostListenerMetadata) { + if ( propMetaInst instanceof HostListenerMetadata ) { const args = isPresent( propMetaInst.args ) ? propMetaInst.args.join( ', ' ) : ''; - host[`(${propMetaInst.eventName})`] = `${propName}(${args})`; + host[ `(${propMetaInst.eventName})` ] = `${propName}(${args})`; } - if (propMetaInst instanceof ContentChildrenMetadata) { - queries[propName] = propMetaInst; + if ( propMetaInst instanceof ContentChildrenMetadata ) { + queries[ propName ] = propMetaInst; } - if (propMetaInst instanceof ViewChildrenMetadata) { - queries[propName] = propMetaInst; + if ( propMetaInst instanceof ViewChildrenMetadata ) { + queries[ propName ] = propMetaInst; } - if (propMetaInst instanceof ContentChildMetadata) { - queries[propName] = propMetaInst; + if ( propMetaInst instanceof ContentChildMetadata ) { + queries[ propName ] = propMetaInst; } - if (propMetaInst instanceof ViewChildMetadata) { - queries[propName] = propMetaInst; + if ( propMetaInst instanceof ViewChildMetadata ) { + queries[ propName ] = propMetaInst; } - }); + } ); - }); + } ); - return this._merge(directiveMetadata, inputs, attrs, outputs, host, queries); + return this._merge( directiveMetadata, inputs, attrs, outputs, host, queries ); } @@ -178,7 +278,7 @@ export class DirectiveResolver { legacy: dm.legacy }; - if (dm instanceof ComponentMetadata) { + if ( dm instanceof ComponentMetadata ) { const componentSettings = assign( {}, @@ -188,11 +288,11 @@ export class DirectiveResolver { templateUrl: dm.templateUrl } ); - return new ComponentMetadata(componentSettings); + return new ComponentMetadata( componentSettings ); } else { - return new DirectiveMetadata(directiveSettings); + return new DirectiveMetadata( directiveSettings ); } diff --git a/test/linker/directive_resolver.spec.ts b/test/linker/directive_resolver.spec.ts index 3adc9d7..db1c274 100644 --- a/test/linker/directive_resolver.spec.ts +++ b/test/linker/directive_resolver.spec.ts @@ -11,169 +11,280 @@ import { Attr } from "../../src/directives/decorators"; import {DirectiveResolver} from "../../src/linker/directive_resolver"; +import {Inject} from "../../src/di/decorators"; +import {Host} from "../../src/di/decorators"; +import {Self} from "../../src/di/decorators"; +import {Optional} from "../../src/di/decorators"; +import {InjectMetadata} from "../../src/di/metadata"; +import {HostMetadata} from "../../src/di/metadata"; +import {SelfMetadata} from "../../src/di/metadata"; +import {OptionalMetadata} from "../../src/di/metadata"; +import {SkipSelf} from "../../src/di/decorators"; describe( `linker/directive_resolver`, ()=> { - it( `should return Directive metadata if exists on provided type`, ()=> { + describe( `#resolve`, ()=> { - @Directive({ - selector:'[myAttr]' - }) - class MyDirective{} + it( `should return Directive metadata if exists on provided type`, ()=> { - const resolver = new DirectiveResolver(); - const actual = resolver.resolve( MyDirective ); - const expected = true; + @Directive( { + selector: '[myAttr]' + } ) + class MyDirective { + } - expect( actual instanceof DirectiveMetadata ).to.equal( expected ); + const resolver = new DirectiveResolver(); + const actual = resolver.resolve( MyDirective ); + const expected = true; - } ); + expect( actual instanceof DirectiveMetadata ).to.equal( expected ); - it( `should return Component metadata if exists on provided type`, ()=> { + } ); - @Component({ - selector:'myComp', - template:'hello world' - }) - class MyComponent{} + it( `should return Component metadata if exists on provided type`, ()=> { - const resolver = new DirectiveResolver(); - const actual = resolver.resolve( MyComponent ); - const expected = true; + @Component( { + selector: 'myComp', + template: 'hello world' + } ) + class MyComponent { + } - expect( actual instanceof ComponentMetadata ).to.equal( expected ); + const resolver = new DirectiveResolver(); + const actual = resolver.resolve( MyComponent ); + const expected = true; - } ); + expect( actual instanceof ComponentMetadata ).to.equal( expected ); + } ); - it( `should throw error when provided type doesn't have Directive/Component metadata`, ()=> { - class NoDirective {} + it( `should throw error when provided type doesn't have Directive/Component metadata`, ()=> { - const resolver = new DirectiveResolver(); + class NoDirective {} - expect( ()=>resolver.resolve( NoDirective ) ).to.throw(`No Directive annotation found on ${stringify(NoDirective)}`); + const resolver = new DirectiveResolver(); - } ); + expect( ()=>resolver.resolve( NoDirective ) ) + .to + .throw( `No Directive annotation found on ${stringify( NoDirective )}` ); + + } ); + + it( `should update Class Metadata object accordingly with provided property Annotations`, ()=> { + + @Directive( { + selector: '[myClicker]' + } ) + class MyClicker { + + @Input() + one: string; + @Input( 'outsideAlias' ) + inside: string; + @Output() + onOne: Function; + @Output( 'onOutsideAlias' ) + onInside: Function; + + @HostBinding( 'class.disabled' ) + isDisabled: boolean; - it( `should update Class Metadata object accordingly with provided property Annotations`, ()=> { - - @Directive({ - selector:'[myClicker]' - }) - class MyClicker{ - - @Input() one: string; - @Input('outsideAlias') inside: string; - @Output() onOne: Function; - @Output('onOutsideAlias') onInside: Function; - - @HostBinding('class.disabled') isDisabled: boolean; - - @HostListener('mousemove',['$event.target']) - onMove(){} - - } - - const resolver = new DirectiveResolver(); - - const actual = resolver.resolve( MyClicker ); - const expected = new DirectiveMetadata({ - selector: '[myClicker]', - inputs: [ - 'one', - 'inside: outsideAlias' - ], - attrs: [], - outputs: [ - 'onOne', - 'onInside: onOutsideAlias' - ], - host:{ - '[class.disabled]':'isDisabled', - '(mousemove)':'onMove($event.target)', - }, - exportAs: undefined, - queries: {}, - providers: undefined - }); - - expect(actual).to.deep.equal(expected); + @HostListener( 'mousemove', [ '$event.target' ] ) + onMove() {} + + } + + const resolver = new DirectiveResolver(); + + const actual = resolver.resolve( MyClicker ); + const expected = new DirectiveMetadata( { + selector: '[myClicker]', + inputs: [ + 'one', + 'inside: outsideAlias' + ], + attrs: [], + outputs: [ + 'onOne', + 'onInside: onOutsideAlias' + ], + host: { + '[class.disabled]': 'isDisabled', + '(mousemove)': 'onMove($event.target)', + }, + exportAs: undefined, + queries: {}, + providers: undefined + } ); + + expect( actual ).to.deep.equal( expected ); + + } ); + + it( `should update merge Class Metadata object with provided property Annotations`, ()=> { + + @Component( { + selector: 'jedi', + template: '