From d6b82704ba94f363990e6af3d401e4dd65df75da Mon Sep 17 00:00:00 2001 From: Mike Ryan Date: Wed, 4 Nov 2015 07:53:46 -0600 Subject: [PATCH] fix(decorators): Changing decorators to emit a call signature for TS --- lib/decorators/component.ts | 198 ++++++++++++++++++----------------- lib/decorators/directive.ts | 62 +++++------ lib/decorators/inject.ts | 92 ++++++++-------- lib/decorators/injectable.ts | 2 +- lib/decorators/pipe.ts | 2 +- lib/decorators/providers.ts | 20 ++-- 6 files changed, 193 insertions(+), 183 deletions(-) diff --git a/lib/decorators/component.ts b/lib/decorators/component.ts index f5f3807..87a6a36 100644 --- a/lib/decorators/component.ts +++ b/lib/decorators/component.ts @@ -55,7 +55,7 @@ import {inputsMap} from '../properties/inputs-builder'; const TYPE = 'component'; // ## Decorator Definition -export const Component = ( +export function Component( { selector, controllerAs, @@ -78,95 +78,97 @@ export const Component = ( pipes?: any[], directives?: any[] } - ) => (t: any) => { - // The only required config is a selector. If one wasn't passed, throw immediately - if( !selector ) { - throw new Error(`Component Decorator Error in "${t.name}": Component selector must be provided`); - } - - // Grab the provider name and selector type by parsing the selector - let {name, type: restrict} = parseSelector(selector); - - // Setup provider information using the parsed selector - providerStore.set('name', name, t); - providerStore.set('type', TYPE, t); - - // The appWriter needs the raw selector. This lets it bootstrap the root component - bundleStore.set('selector', selector, t); - - // Grab the providers from the config object, parse them, and write the metadata - // to the target. - Providers(...providers)(t); - - // Restrict type must be 'element' - componentStore.set('restrict', restrict, t); - - // Components should always create an isolate scope - componentStore.set('scope', {}, t); + ){ + return function(t: any){ + // The only required config is a selector. If one wasn't passed, throw immediately + if( !selector ) { + throw new Error(`Component Decorator Error in "${t.name}": Component selector must be provided`); + } - // Since components must have a template, set transclude to true - componentStore.set('transclude', true, t); - - // Inputs should always be bound to the controller instance, not - // to the scope - componentStore.set('bindToController', true, t); - - // Must perform some basic shape checking on the config object - [ - ['inputs', inputs], - ['providers', providers], - ['directives', directives], - ['outputs', outputs] - ].forEach(([propName, propVal]) => { - if(propVal !== undefined && !Array.isArray(propVal)){ - throw new TypeError(`Component Decorator Error in "${t.name}": Component ${propName} must be an array`); + // Grab the provider name and selector type by parsing the selector + let {name, type: restrict} = parseSelector(selector); + + // Setup provider information using the parsed selector + providerStore.set('name', name, t); + providerStore.set('type', TYPE, t); + + // The appWriter needs the raw selector. This lets it bootstrap the root component + bundleStore.set('selector', selector, t); + + // Grab the providers from the config object, parse them, and write the metadata + // to the target. + Providers(...providers)(t); + + // Restrict type must be 'element' + componentStore.set('restrict', restrict, t); + + // Components should always create an isolate scope + componentStore.set('scope', {}, t); + + // Since components must have a template, set transclude to true + componentStore.set('transclude', true, t); + + // Inputs should always be bound to the controller instance, not + // to the scope + componentStore.set('bindToController', true, t); + + // Must perform some basic shape checking on the config object + [ + ['inputs', inputs], + ['providers', providers], + ['directives', directives], + ['outputs', outputs] + ].forEach(([propName, propVal]) => { + if(propVal !== undefined && !Array.isArray(propVal)){ + throw new TypeError(`Component Decorator Error in "${t.name}": Component ${propName} must be an array`); + } + }); + + // Check for Angular 2 style inputs + let inputMap = parsePropertyMap(inputs); + let previousInputMap = componentStore.get('inputMap', t) || {}; + componentStore.set('inputMap', Object.assign({}, previousInputMap, inputMap), t); + + // outputs + if(outputs.length > 0){ + let outputMap = parsePropertyMap(outputs) || {}; + componentStore.set('outputMap', outputMap, t); + for(let key in outputMap){ + events.add(outputMap[key]); + } } - }); - - // Check for Angular 2 style inputs - let inputMap = parsePropertyMap(inputs); - let previousInputMap = componentStore.get('inputMap', t) || {}; - componentStore.set('inputMap', Object.assign({}, previousInputMap, inputMap), t); - // outputs - if(outputs.length > 0){ - let outputMap = parsePropertyMap(outputs) || {}; - componentStore.set('outputMap', outputMap, t); - for(let key in outputMap){ - events.add(outputMap[key]); + // Allow for renaming the controllerAs + if(controllerAs) { + componentStore.set('controllerAs', controllerAs, t); } + else { + // ControllerAs is the parsed selector. For example, `app` becomes `app` and + // `send-message` becomes `sendMessage` + componentStore.set('controllerAs', name, t); + } + + // Set a link function + if(t.link) { + componentStore.set('link', t.link, t); + } + + // Set a compile function + if(t.compile){ + componentStore.set('compile', t.compile, t); + } + + View({ + selector, + template, + templateUrl, + pipes, + directives + })(t); } +} - // Allow for renaming the controllerAs - if(controllerAs) { - componentStore.set('controllerAs', controllerAs, t); - } - else { - // ControllerAs is the parsed selector. For example, `app` becomes `app` and - // `send-message` becomes `sendMessage` - componentStore.set('controllerAs', name, t); - } - - // Set a link function - if(t.link) { - componentStore.set('link', t.link, t); - } - - // Set a compile function - if(t.compile){ - componentStore.set('compile', t.compile, t); - } - - View({ - selector, - template, - templateUrl, - pipes, - directives - })(t); -}; - -export const View = ( +export function View( { selector, template, @@ -181,20 +183,22 @@ export const View = ( pipes?: any[], directives?: any[] } -) => (t: any) => { - if(templateUrl) { - componentStore.set('templateUrl', templateUrl, t); - } - else if(template) { - componentStore.set('template', template, t); - } - else { - throw new Error(`@Component config must include either a template or a template url for component with selector ${selector} on ${t.name}`); +){ + return function(t: any){ + if(templateUrl) { + componentStore.set('templateUrl', templateUrl, t); + } + else if(template) { + componentStore.set('template', template, t); + } + else { + throw new Error(`@Component config must include either a template or a template url for component with selector ${selector} on ${t.name}`); + } + + Providers(...directives)(t); + Providers(...pipes)(t); } - - Providers(...directives)(t); - Providers(...pipes)(t); -}; +} // ## Component Provider Parser Module.addProvider(TYPE, (target: any, name: string, injects: string[], ngModule: ng.IModule) => { diff --git a/lib/decorators/directive.ts b/lib/decorators/directive.ts index 3933b62..f5055a0 100644 --- a/lib/decorators/directive.ts +++ b/lib/decorators/directive.ts @@ -24,7 +24,7 @@ import {inputsMap} from '../properties/inputs-builder'; const TYPE = 'directive'; // ## Decorator Definition -export const Directive = ( +export function Directive( { selector, providers = [] @@ -33,36 +33,38 @@ export const Directive = ( selector: string, providers?: any[] } - ) => (t: any) => { - // The only required config is a selector. If one wasn't passed, throw immediately - if( !selector ) { - throw new Error('Directive selector must be provided'); - } - - // Grab the provider name and selector type by parsing the selector - let {name, type: restrict} = parseSelector(selector); - - // If the selector type was not an element, throw an error. Components can only - // be elements in Angular 2, so we want to enforce that strictly here. - if(restrict !== 'A') { - throw new Error('@Directive selectors can only be attributes'); - } - - if(providers !== undefined && !Array.isArray(providers)){ - throw new TypeError(`Directive providers must be an array`); + ){ + return function(t: any){ + // The only required config is a selector. If one wasn't passed, throw immediately + if( !selector ) { + throw new Error('Directive selector must be provided'); + } + + // Grab the provider name and selector type by parsing the selector + let {name, type: restrict} = parseSelector(selector); + + // If the selector type was not an element, throw an error. Components can only + // be elements in Angular 2, so we want to enforce that strictly here. + if(restrict !== 'A') { + throw new Error('@Directive selectors can only be attributes'); + } + + if(providers !== undefined && !Array.isArray(providers)){ + throw new TypeError(`Directive providers must be an array`); + } + + // Setup provider information using the parsed selector + providerStore.set('name', name, t); + providerStore.set('type', TYPE, t); + + // Grab the providers from the config object, parse them, and write the metadata + // to the target. + Providers(...providers)(t); + + // Restrict type must be 'element' + componentStore.set('restrict', restrict, t); } - - // Setup provider information using the parsed selector - providerStore.set('name', name, t); - providerStore.set('type', TYPE, t); - - // Grab the providers from the config object, parse them, and write the metadata - // to the target. - Providers(...providers)(t); - - // Restrict type must be 'element' - componentStore.set('restrict', restrict, t); -}; +} // ## Component Provider Parser Module.addProvider(TYPE, (target: any, name: string, injects: string[], ngModule: ng.IModule) => { diff --git a/lib/decorators/inject.ts b/lib/decorators/inject.ts index 2ff69be..d91ddb3 100644 --- a/lib/decorators/inject.ts +++ b/lib/decorators/inject.ts @@ -20,50 +20,52 @@ import {OpaqueToken} from '../classes/opaque-token'; // ## @Inject // Takes an array of injects -export const Inject = ( ...injects: any[] ) => (t:any) => { - const notStringBased = (inj: any) => typeof inj !== 'string' && !(inj instanceof OpaqueToken); - const ensureInjectable = (inj: any) => { - if (!providerStore.get('name', inj) || !providerStore.get('type', inj)) { - throw new Error(`Processing "${t.name}" @Inject parameter: "${inj.name || inj.toString()}" is not a valid injectable. - Please ensure ${inj.name || inj.toString()} is injectable. Valid examples can be: - - a string representing an ng1 provider, e.g. '$q' - - an @Injectable ng-forward class - - a Provider, e.g. provide(SOME_CONFIG, {asValue: 100})`) +export function Inject( ...injects: any[] ){ + return function(t:any){ + const notStringBased = (inj: any) => typeof inj !== 'string' && !(inj instanceof OpaqueToken); + const ensureInjectable = (inj: any) => { + if (!providerStore.get('name', inj) || !providerStore.get('type', inj)) { + throw new Error(`Processing "${t.name}" @Inject parameter: "${inj.name || inj.toString()}" is not a valid injectable. + Please ensure ${inj.name || inj.toString()} is injectable. Valid examples can be: + - a string representing an ng1 provider, e.g. '$q' + - an @Injectable ng-forward class + - a Provider, e.g. provide(SOME_CONFIG, {asValue: 100})`) + } + return inj; + }; + // At the end of the day, Angular 1's DI requires the injection array to be + // an array of strings. Map over the injects to get the string provider name for + // each injectable + + var providers = injects + .filter(notStringBased) + .map(ensureInjectable); + + Providers(...providers)(t); + + let dependencies = injects.map(getInjectableName).filter(n => n !== undefined); + + // If there is already an $inject array, assume that it was set by a parent class. + // The resultant $inject array should be a concat of local dependencies and parent + // injects. + // ```js + // @Inject('$q', '$http', '$interval') + // class Parent{ ... } + // + // @Inject('$timeout') + // class Child extends Parent{ + // constructor($timeout, ...parentDependencies){ + // super(...parentDependencies); + // } + // } + // ``` + if (bundleStore.has('$inject', t)) { + let parentInjects = bundleStore.get('$inject', t); + bundleStore.set('$inject', [...dependencies, ...parentInjects], t); + } + // Otherwise just use the dependencies array as the $inject array. + else { + bundleStore.set('$inject', dependencies, t); } - return inj; - }; - // At the end of the day, Angular 1's DI requires the injection array to be - // an array of strings. Map over the injects to get the string provider name for - // each injectable - - var providers = injects - .filter(notStringBased) - .map(ensureInjectable); - - Providers(...providers)(t); - - let dependencies = injects.map(getInjectableName).filter(n => n !== undefined); - - // If there is already an $inject array, assume that it was set by a parent class. - // The resultant $inject array should be a concat of local dependencies and parent - // injects. - // ```js - // @Inject('$q', '$http', '$interval') - // class Parent{ ... } - // - // @Inject('$timeout') - // class Child extends Parent{ - // constructor($timeout, ...parentDependencies){ - // super(...parentDependencies); - // } - // } - // ``` - if (bundleStore.has('$inject', t)) { - let parentInjects = bundleStore.get('$inject', t); - bundleStore.set('$inject', [...dependencies, ...parentInjects], t); - } - // Otherwise just use the dependencies array as the $inject array. - else { - bundleStore.set('$inject', dependencies, t); } -}; +} diff --git a/lib/decorators/injectable.ts b/lib/decorators/injectable.ts index 942f08e..4567e2b 100644 --- a/lib/decorators/injectable.ts +++ b/lib/decorators/injectable.ts @@ -3,7 +3,7 @@ import decoratorFactory from '../util/decorator-factory'; export const INJECTABLE = 'injectable'; -export const Injectable: any = decoratorFactory(INJECTABLE); +export const Injectable: (any) => any = decoratorFactory(INJECTABLE); Module.addProvider(INJECTABLE, (provider: any, name: string, injects: string[], ngModule: ng.IModule) => { ngModule.service(name, [...injects, provider]); diff --git a/lib/decorators/pipe.ts b/lib/decorators/pipe.ts index a4190d6..9ee8594 100644 --- a/lib/decorators/pipe.ts +++ b/lib/decorators/pipe.ts @@ -34,7 +34,7 @@ const TYPE = 'pipe'; // The decorator itself. Note that while the name is technically optional, // with pipes you will almost _always_ wants to provide a name to use in your // templates that is different from the class name. This keeps your code uglify-proof. -export const Pipe: any = decoratorFactory(TYPE); +export const Pipe: (any) => any = decoratorFactory(TYPE); // ## Provider Parser Module.addProvider(TYPE, (provider: any, name: string, injects: string[], ngModule: ng.IModule) => { diff --git a/lib/decorators/providers.ts b/lib/decorators/providers.ts index 82fb047..5424df8 100644 --- a/lib/decorators/providers.ts +++ b/lib/decorators/providers.ts @@ -13,12 +13,14 @@ import {bundleStore} from '../writers'; import groupIntoModulesAndProviders from '../util/group-modules-providers'; -export const Providers = (...modulesAndProviders: any[]) => (t: any) => { - let { modules, providers } = groupIntoModulesAndProviders(modulesAndProviders); - - let parentModules = bundleStore.get('modules', t) || []; - bundleStore.set('modules', [...modules, ...parentModules], t); - - let parentProviders = bundleStore.get('providers', t) || []; - bundleStore.set('providers', [...providers, ...parentProviders], t); -}; +export function Providers(...modulesAndProviders: any[]){ + return function(t: any){ + let { modules, providers } = groupIntoModulesAndProviders(modulesAndProviders); + + let parentModules = bundleStore.get('modules', t) || []; + bundleStore.set('modules', [...modules, ...parentModules], t); + + let parentProviders = bundleStore.get('providers', t) || []; + bundleStore.set('providers', [...providers, ...parentProviders], t); + } +}