feat(injector): initial implementaion of dynamic injector
This commit is contained in:
		
							parent
							
								
									6c8da62c1b
								
							
						
					
					
						commit
						b2199632c7
					
				| @ -1,4 +1,5 @@ | |||||||
| import {Future, Type} from 'facade/lang'; | import {Type} from 'facade/lang'; | ||||||
|  | import {Future} from 'facade/async'; | ||||||
| import {Element} from 'facade/dom'; | import {Element} from 'facade/dom'; | ||||||
| //import {ProtoView} from './view';
 | //import {ProtoView} from './view';
 | ||||||
| import {TemplateLoader} from './template_loader'; | import {TemplateLoader} from './template_loader'; | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import {Future} from 'facade/lang'; | import {Future} from 'facade/async'; | ||||||
| //import {Document} from 'facade/dom';
 | //import {Document} from 'facade/dom';
 | ||||||
| 
 | 
 | ||||||
| export class TemplateLoader { | export class TemplateLoader { | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								modules/di/src/annotations.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								modules/di/src/annotations.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | //TODO: vsavkin: uncomment once const constructor are supported
 | ||||||
|  | //export class Inject {
 | ||||||
|  | //  @CONST
 | ||||||
|  | //  constructor(token){
 | ||||||
|  | //    this.token = token;
 | ||||||
|  | //  }
 | ||||||
|  | //}
 | ||||||
							
								
								
									
										64
									
								
								modules/di/src/binding.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								modules/di/src/binding.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | import {Type} from 'facade/lang'; | ||||||
|  | import {List, MapWrapper, ListWrapper} from 'facade/collection'; | ||||||
|  | import {Reflector} from 'facade/di/reflector'; | ||||||
|  | import {Key} from './key'; | ||||||
|  | 
 | ||||||
|  | export class Binding { | ||||||
|  |   constructor(key:Key, factory:Function, dependencies:List, async) { | ||||||
|  |     this.key = key; | ||||||
|  |     this.factory = factory; | ||||||
|  |     this.dependencies = dependencies; | ||||||
|  |     this.async = async; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function bind(token):BindingBuilder { | ||||||
|  |   return new BindingBuilder(token); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class BindingBuilder { | ||||||
|  |   constructor(token) { | ||||||
|  |     this.token = token; | ||||||
|  |     this.reflector = new Reflector(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toClass(type:Type):Binding { | ||||||
|  |     return new Binding( | ||||||
|  |       Key.get(this.token), | ||||||
|  |       this.reflector.factoryFor(type), | ||||||
|  |       this._wrapKeys(this.reflector.dependencies(type)), | ||||||
|  |       false | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toValue(value):Binding { | ||||||
|  |     return new Binding( | ||||||
|  |       Key.get(this.token), | ||||||
|  |       (_) => value, | ||||||
|  |       [], | ||||||
|  |       false | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toFactory(dependencies:List, factoryFunction:Function):Binding { | ||||||
|  |     return new Binding( | ||||||
|  |       Key.get(this.token), | ||||||
|  |       this.reflector.convertToFactory(factoryFunction), | ||||||
|  |       this._wrapKeys(dependencies), | ||||||
|  |       false | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toAsyncFactory(dependencies:List, factoryFunction:Function):Binding { | ||||||
|  |     return new Binding( | ||||||
|  |       Key.get(this.token), | ||||||
|  |       this.reflector.convertToFactory(factoryFunction), | ||||||
|  |       this._wrapKeys(dependencies), | ||||||
|  |       true | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _wrapKeys(deps:List) { | ||||||
|  |     return ListWrapper.map(deps, (t) => Key.get(t)); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1 +1,5 @@ | |||||||
|  | export * from './injector'; | ||||||
|  | export * from './binding'; | ||||||
|  | export * from './key'; | ||||||
| export * from './module'; | export * from './module'; | ||||||
|  | export {Inject} from 'facade/di/reflector'; | ||||||
|  | |||||||
							
								
								
									
										52
									
								
								modules/di/src/exceptions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								modules/di/src/exceptions.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | import {ListWrapper, List} from 'facade/collection'; | ||||||
|  | import {humanize} from 'facade/lang'; | ||||||
|  | 
 | ||||||
|  | function constructResolvingPath(keys: List) { | ||||||
|  |   if (keys.length > 1) { | ||||||
|  |     var tokenStrs = ListWrapper.map(keys, (k) => humanize(k.token)); | ||||||
|  |     return " (" + tokenStrs.join(' -> ') + ")"; | ||||||
|  |   } else { | ||||||
|  |     return ""; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class NoProviderError extends Error { | ||||||
|  |   constructor(keys:List){ | ||||||
|  |     this.message = this._constructResolvingMessage(keys); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _constructResolvingMessage(keys:List) { | ||||||
|  |     var last = humanize(ListWrapper.last(keys).token); | ||||||
|  |     return `No provider for ${last}!${constructResolvingPath(keys)}`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toString() { | ||||||
|  |     return this.message; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AsyncProviderError extends Error { | ||||||
|  |   constructor(keys:List){ | ||||||
|  |     this.message = this._constructResolvingMessage(keys); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _constructResolvingMessage(keys:List) { | ||||||
|  |     var last = humanize(ListWrapper.last(keys).token); | ||||||
|  |     return `Cannot instantiate ${last} synchronously. ` + | ||||||
|  |       `It is provided as a future!${constructResolvingPath(keys)}`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toString() { | ||||||
|  |     return this.message; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class InvalidBindingError extends Error { | ||||||
|  |   constructor(binding){ | ||||||
|  |     this.message = `Invalid binding ${binding}`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toString() { | ||||||
|  |     return this.message; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										133
									
								
								modules/di/src/injector.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								modules/di/src/injector.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | |||||||
|  | import {Map, List, MapWrapper, ListWrapper} from 'facade/collection'; | ||||||
|  | import {Binding, BindingBuilder, bind} from './binding'; | ||||||
|  | import {NoProviderError, InvalidBindingError, AsyncProviderError} from './exceptions'; | ||||||
|  | import {Type, isPresent, isBlank} from 'facade/lang'; | ||||||
|  | import {Future, FutureWrapper} from 'facade/async'; | ||||||
|  | import {Key} from './key'; | ||||||
|  | 
 | ||||||
|  | export class Injector { | ||||||
|  |   constructor(bindings:List) { | ||||||
|  |     var flatten = _flattenBindings(bindings); | ||||||
|  |     this._bindings = this._createListOfBindings(flatten); | ||||||
|  |     this._instances = this._createInstances(); | ||||||
|  |     this._parent = null; //TODO: vsavkin make a parameter
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _createListOfBindings(flattenBindings):List { | ||||||
|  |     var bindings = ListWrapper.createFixedSize(Key.numberOfKeys() + 1); | ||||||
|  |     MapWrapper.forEach(flattenBindings, (keyId, v) => bindings[keyId] = v); | ||||||
|  |     return bindings; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _createInstances():List { | ||||||
|  |     return ListWrapper.createFixedSize(Key.numberOfKeys() + 1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get(token) { | ||||||
|  |     return this.getByKey(Key.get(token)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   asyncGet(token) { | ||||||
|  |     return this.asyncGetByKey(Key.get(token)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getByKey(key:Key) { | ||||||
|  |     return this._getByKey(key, [], false); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   asyncGetByKey(key:Key) { | ||||||
|  |     return this._getByKey(key, [], true); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _getByKey(key:Key, resolving:List, async) { | ||||||
|  |     var keyId = key.id; | ||||||
|  |     //TODO: vsavkin: use LinkedList to remove clone
 | ||||||
|  |     resolving = ListWrapper.clone(resolving) | ||||||
|  |     ListWrapper.push(resolving, key); | ||||||
|  | 
 | ||||||
|  |     if (key.token === Injector) return this._injector(async); | ||||||
|  | 
 | ||||||
|  |     var instance = this._get(this._instances, keyId); | ||||||
|  |     if (isPresent(instance)) return instance; | ||||||
|  | 
 | ||||||
|  |     var binding = this._get(this._bindings, keyId); | ||||||
|  | 
 | ||||||
|  |     if (isPresent(binding)) { | ||||||
|  |       return this._instantiate(key, binding, resolving, async); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (isPresent(this._parent)) { | ||||||
|  |       return this._parent._getByKey(key, resolving, async); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     throw new NoProviderError(resolving); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createChild(bindings:List):Injector { | ||||||
|  |     var inj = new Injector(bindings); | ||||||
|  |     inj._parent = this; //TODO: vsavkin: change it when optional parameters are working
 | ||||||
|  |     return inj; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _injector(async){ | ||||||
|  |     return async ? FutureWrapper.value(this) : this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _get(list:List, index){ | ||||||
|  |     if (list.length <= index) return null; | ||||||
|  |     return ListWrapper.get(list, index); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _instantiate(key:Key, binding:Binding, resolving:List, async) { | ||||||
|  |     if (binding.async && !async) { | ||||||
|  |       throw new AsyncProviderError(resolving); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (async) { | ||||||
|  |       return this._instantiateAsync(key, binding, resolving, async); | ||||||
|  |     } else { | ||||||
|  |       return this._instantiateSync(key, binding, resolving, async); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _instantiateSync(key:Key, binding:Binding, resolving:List, async) { | ||||||
|  |     var deps = ListWrapper.map(binding.dependencies, d => this._getByKey(d, resolving, false)); | ||||||
|  |     var instance = binding.factory(deps); | ||||||
|  |     ListWrapper.set(this._instances, key.id, instance); | ||||||
|  |     if (!binding.async && async) { | ||||||
|  |       return FutureWrapper.value(instance); | ||||||
|  |     } | ||||||
|  |     return instance; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _instantiateAsync(key:Key, binding:Binding, resolving:List, async):Future { | ||||||
|  |     var instances = this._createInstances(); | ||||||
|  |     var futures = ListWrapper.map(binding.dependencies, d => this._getByKey(d, resolving, true)); | ||||||
|  |     return FutureWrapper.wait(futures). | ||||||
|  |       then(binding.factory). | ||||||
|  |       then(function(instance) { | ||||||
|  |         ListWrapper.set(instances, key.id, instance); | ||||||
|  |         return instance | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _flattenBindings(bindings:List) { | ||||||
|  |   var res = {}; | ||||||
|  |   ListWrapper.forEach(bindings, function (b){ | ||||||
|  |     if (b instanceof Binding) { | ||||||
|  |       MapWrapper.set(res, b.key.id, b); | ||||||
|  | 
 | ||||||
|  |     } else if (b instanceof Type) { | ||||||
|  |       var s = bind(b).toClass(b); | ||||||
|  |       MapWrapper.set(res, s.key.id, s); | ||||||
|  | 
 | ||||||
|  |     } else if (b instanceof BindingBuilder) { | ||||||
|  |       throw new InvalidBindingError(b.token); | ||||||
|  | 
 | ||||||
|  |     } else { | ||||||
|  |       throw new InvalidBindingError(b); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   return res; | ||||||
|  | } | ||||||
| @ -1,3 +1,25 @@ | |||||||
| export class Key { | import {MapWrapper} from 'facade/collection'; | ||||||
| 
 | 
 | ||||||
|  | var _allKeys = {}; | ||||||
|  | var _id = 0; | ||||||
|  | 
 | ||||||
|  | export class Key { | ||||||
|  |   constructor(token, id) { | ||||||
|  |     this.token = token; | ||||||
|  |     this.id = id; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static get(token) { | ||||||
|  |     if (MapWrapper.contains(_allKeys, token)) { | ||||||
|  |       return MapWrapper.get(_allKeys, token) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var newKey = new Key(token, ++_id); | ||||||
|  |     MapWrapper.set(_allKeys, token, newKey); | ||||||
|  |     return newKey; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static numberOfKeys() { | ||||||
|  |     return _id; | ||||||
|  |   } | ||||||
| } | } | ||||||
| @ -1,26 +1 @@ | |||||||
| import {FIELD} from 'facade/lang'; | export class Module {} | ||||||
| import {Type} from 'facade/lang'; |  | ||||||
| import {Map, MapWrapper} from 'facade/collection'; |  | ||||||
| import {Key} from './key'; |  | ||||||
| 
 |  | ||||||
| /// becouse we need to know when toValue was not set.
 |  | ||||||
| /// (it could be that toValue is set to null or undefined in js)
 |  | ||||||
| var _UNDEFINED = {} |  | ||||||
| 
 |  | ||||||
| export class Module { |  | ||||||
| 
 |  | ||||||
|   @FIELD('final bindings:Map<Key, Binding>') |  | ||||||
|   constructor(){ |  | ||||||
|     this.bindings = new MapWrapper(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   bind(type:Type, |  | ||||||
|       {toValue/*=_UNDEFINED*/, toFactory, toImplementation, inject, toInstanceOf, withAnnotation}/*: |  | ||||||
|         {toFactory:Function, toImplementation: Type, inject: Array, toInstanceOf:Type}*/) {} |  | ||||||
| 
 |  | ||||||
|   bindByKey(key:Key, |  | ||||||
|       {toValue/*=_UNDEFINED*/, toFactory, toImplementation, inject, toInstanceOf}/*: |  | ||||||
|         {toFactory:Function, toImplementation: Type, inject: Array, toInstanceOf:Type}*/) {} |  | ||||||
| 
 |  | ||||||
|   install(module:Module) {} |  | ||||||
| } |  | ||||||
							
								
								
									
										76
									
								
								modules/di/test/di/async_spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								modules/di/test/di/async_spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | |||||||
|  | import {ddescribe, describe, it, iit, xit, expect, beforeEach} from 'test_lib/test_lib'; | ||||||
|  | import {Injector, Inject, bind, Key} from 'di/di'; | ||||||
|  | import {Future, FutureWrapper} from 'facade/async'; | ||||||
|  | 
 | ||||||
|  | class UserList {} | ||||||
|  | 
 | ||||||
|  | function fetchUsers() { | ||||||
|  |   return FutureWrapper.value(new UserList()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class SynchronousUserList {} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class UserController { | ||||||
|  |   constructor(list:UserList) { | ||||||
|  |     this.list = list; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function main () { | ||||||
|  |   describe("async injection", function () { | ||||||
|  |     it('should return a future', function() { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         bind(UserList).toAsyncFactory([], fetchUsers) | ||||||
|  |       ]); | ||||||
|  |       var p = injector.asyncGet(UserList); | ||||||
|  |       expect(p).toBeFuture(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should throw when instantiating async provider synchronously', function() { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         bind(UserList).toAsyncFactory([], fetchUsers) | ||||||
|  |       ]); | ||||||
|  | 
 | ||||||
|  |       expect(() => injector.get(UserList)) | ||||||
|  |         .toThrowError('Cannot instantiate UserList synchronously. It is provided as a future!'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should return a future even if the provider is sync', function() { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         SynchronousUserList | ||||||
|  |       ]); | ||||||
|  |       var p = injector.asyncGet(SynchronousUserList); | ||||||
|  |       expect(p).toBeFuture(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should provide itself', function() { | ||||||
|  |       var injector = new Injector([]); | ||||||
|  |       var p = injector.asyncGet(Injector); | ||||||
|  |       expect(p).toBeFuture(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should return a future when a dependency is async', function(done) { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         bind(UserList).toAsyncFactory([], fetchUsers), | ||||||
|  |         UserController | ||||||
|  |       ]); | ||||||
|  | 
 | ||||||
|  |       injector.asyncGet(UserController).then(function(userController) { | ||||||
|  |         expect(userController).toBeAnInstanceOf(UserController); | ||||||
|  |         expect(userController.list).toBeAnInstanceOf(UserList); | ||||||
|  |         done(); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should throw when a dependency is async', function() { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         bind(UserList).toAsyncFactory([], fetchUsers), | ||||||
|  |         UserController | ||||||
|  |       ]); | ||||||
|  | 
 | ||||||
|  |       expect(() => injector.get(UserController)) | ||||||
|  |         .toThrowError('Cannot instantiate UserList synchronously. It is provided as a future! (UserController -> UserList)'); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										144
									
								
								modules/di/test/di/injector_spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								modules/di/test/di/injector_spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,144 @@ | |||||||
|  | import {describe, it, expect, beforeEach} from 'test_lib/test_lib'; | ||||||
|  | import {Injector, Inject, bind} from 'di/di'; | ||||||
|  | 
 | ||||||
|  | class Engine {} | ||||||
|  | class Dashboard {} | ||||||
|  | class TurboEngine extends Engine{} | ||||||
|  | 
 | ||||||
|  | class Car { | ||||||
|  |   constructor(engine:Engine) { | ||||||
|  |     this.engine = engine; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class CarWithDashboard { | ||||||
|  |   constructor(engine:Engine, dashboard:Dashboard) { | ||||||
|  |     this.engine = engine; | ||||||
|  |     this.dashboard = dashboard; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class SportsCar extends Car { | ||||||
|  |   constructor(engine:Engine) { | ||||||
|  |     super(engine); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class CarWithInject { | ||||||
|  |   constructor(@Inject(TurboEngine) engine:Engine) { | ||||||
|  |     this.engine = engine; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function main() { | ||||||
|  |   describe('injector', function() { | ||||||
|  |     it('should instantiate a class without dependencies', function() { | ||||||
|  |       var injector = new Injector([Engine]); | ||||||
|  |       var engine = injector.get(Engine); | ||||||
|  | 
 | ||||||
|  |       expect(engine).toBeAnInstanceOf(Engine); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should resolve dependencies based on type information', function() { | ||||||
|  |       var injector = new Injector([Engine, Car]); | ||||||
|  |       var car = injector.get(Car); | ||||||
|  | 
 | ||||||
|  |       expect(car).toBeAnInstanceOf(Car); | ||||||
|  |       expect(car.engine).toBeAnInstanceOf(Engine); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should resolve dependencies based on @Inject annotation', function() { | ||||||
|  |       var injector = new Injector([TurboEngine, Engine, CarWithInject]); | ||||||
|  |       var car = injector.get(CarWithInject); | ||||||
|  | 
 | ||||||
|  |       expect(car).toBeAnInstanceOf(CarWithInject); | ||||||
|  |       expect(car.engine).toBeAnInstanceOf(TurboEngine); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should cache instances', function() { | ||||||
|  |       var injector = new Injector([Engine]); | ||||||
|  | 
 | ||||||
|  |       var e1 = injector.get(Engine); | ||||||
|  |       var e2 = injector.get(Engine); | ||||||
|  | 
 | ||||||
|  |       expect(e1).toBe(e2); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should bind to a value', function() { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         bind(Engine).toValue("fake engine") | ||||||
|  |       ]); | ||||||
|  | 
 | ||||||
|  |       var engine = injector.get(Engine); | ||||||
|  |       expect(engine).toEqual("fake engine"); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should bind to a factory', function() { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         Engine, | ||||||
|  |         bind(Car).toFactory([Engine], (e) => new SportsCar(e)) | ||||||
|  |       ]); | ||||||
|  | 
 | ||||||
|  |       var car = injector.get(Car); | ||||||
|  |       expect(car).toBeAnInstanceOf(SportsCar); | ||||||
|  |       expect(car.engine).toBeAnInstanceOf(Engine); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should use non-type tokens', function() { | ||||||
|  |       var injector = new Injector([ | ||||||
|  |         bind('token').toValue('value') | ||||||
|  |       ]); | ||||||
|  | 
 | ||||||
|  |       expect(injector.get('token')).toEqual('value'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should throw when given invalid bindings', function() { | ||||||
|  |       expect(() => new Injector(["blah"])).toThrowError('Invalid binding blah'); | ||||||
|  |       expect(() => new Injector([bind("blah")])).toThrowError('Invalid binding blah'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe("child", function () { | ||||||
|  |       it('should load instances from parent injector', function() { | ||||||
|  |         var parent = new Injector([Engine]); | ||||||
|  |         var child = parent.createChild([]); | ||||||
|  | 
 | ||||||
|  |         var engineFromParent = parent.get(Engine); | ||||||
|  |         var engineFromChild = child.get(Engine); | ||||||
|  | 
 | ||||||
|  |         expect(engineFromChild).toBe(engineFromParent); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should create new instance in a child injector', function() { | ||||||
|  |         var parent = new Injector([Engine]); | ||||||
|  |         var child = parent.createChild([ | ||||||
|  |           bind(Engine).toClass(TurboEngine) | ||||||
|  |         ]); | ||||||
|  | 
 | ||||||
|  |         var engineFromParent = parent.get(Engine); | ||||||
|  |         var engineFromChild = child.get(Engine); | ||||||
|  | 
 | ||||||
|  |         expect(engineFromParent).not.toBe(engineFromChild); | ||||||
|  |         expect(engineFromChild).toBeAnInstanceOf(TurboEngine); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should provide itself', function() { | ||||||
|  |       var parent = new Injector([]); | ||||||
|  |       var child = parent.createChild([]); | ||||||
|  | 
 | ||||||
|  |       expect(child.get(Injector)).toBe(child); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should throw when no provider defined', function() { | ||||||
|  |       var injector = new Injector([]); | ||||||
|  |       expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should show the full path when no provider', function() { | ||||||
|  |       var injector = new Injector([CarWithDashboard, Engine]); | ||||||
|  | 
 | ||||||
|  |       expect(() => injector.get(CarWithDashboard)). | ||||||
|  |         toThrowError('No provider for Dashboard! (CarWithDashboard -> Dashboard)'); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								modules/di/test/di/key_spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								modules/di/test/di/key_spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | import {describe, it, expect} from 'test_lib/test_lib'; | ||||||
|  | import {Key} from 'di/di'; | ||||||
|  | 
 | ||||||
|  | export function main () { | ||||||
|  |   describe("key", function () { | ||||||
|  |     it('should be equal to another key if type is the same', function () { | ||||||
|  |       expect(Key.get('car')).toBe(Key.get('car')); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should not be equal to another key if types are different', function () { | ||||||
|  |       expect(Key.get('car')).not.toBe(Key.get('porsche')); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								modules/facade/src/async.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								modules/facade/src/async.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | library angular.core.facade.async; | ||||||
|  | 
 | ||||||
|  | import 'dart:async'; | ||||||
|  | export 'dart:async' show Future; | ||||||
|  | 
 | ||||||
|  | class FutureWrapper { | ||||||
|  |   static Future value(obj) { | ||||||
|  |     return new Future.value(obj); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static Future wait(List<Future> futures){ | ||||||
|  |     return Future.wait(futures); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								modules/facade/src/async.es6
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								modules/facade/src/async.es6
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | export var Future = Promise; | ||||||
|  | 
 | ||||||
|  | export class FutureWrapper { | ||||||
|  |   static value(obj):Future { | ||||||
|  |     return Future.resolve(obj); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static wait(futures):Future { | ||||||
|  |     if (futures.length == 0) return Future.resolve([]); | ||||||
|  |     return Future.all(futures); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -8,14 +8,25 @@ class MapWrapper { | |||||||
|   static get(m, k) => m[k]; |   static get(m, k) => m[k]; | ||||||
|   static void set(m, k, v){ m[k] = v; } |   static void set(m, k, v){ m[k] = v; } | ||||||
|   static contains(m, k) => m.containsKey(k); |   static contains(m, k) => m.containsKey(k); | ||||||
|  |   static forEach(m, fn) { | ||||||
|  |     m.forEach(fn); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class ListWrapper { | class ListWrapper { | ||||||
|   static List clone(List l) => new List.from(l); |   static List clone(List l) => new List.from(l); | ||||||
|   static List create() => new List(); |   static List create() => new List(); | ||||||
|  |   static List createFixedSize(int size) => new List(size); | ||||||
|   static get(m, k) => m[k]; |   static get(m, k) => m[k]; | ||||||
|   static void set(m, k, v) { m[k] = v; } |   static void set(m, k, v) { m[k] = v; } | ||||||
|   static contains(m, k) => m.containsKey(k); |   static contains(m, k) => m.containsKey(k); | ||||||
|  |   static map(list, fn) => list.map(fn).toList(); | ||||||
|  |   static forEach(list, fn) { | ||||||
|  |     list.forEach(fn); | ||||||
|  |   } | ||||||
|  |   static last(list) { | ||||||
|  |     return list.last; | ||||||
|  |   } | ||||||
|   static void push(List l, e) { l.add(e); } |   static void push(List l, e) { l.add(e); } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,18 +6,38 @@ export class MapWrapper { | |||||||
|   static create():HashMap { return new HashMap(); } |   static create():HashMap { return new HashMap(); } | ||||||
|   static get(m, k) { return m[k]; } |   static get(m, k) { return m[k]; } | ||||||
|   static set(m, k, v) { m[k] = v; } |   static set(m, k, v) { m[k] = v; } | ||||||
|   static contains(m, k) { return  m.containsKey(k); } |   static contains(m, k) { return  m[k] != undefined; } | ||||||
|  |   static forEach(m, fn) { | ||||||
|  |     for(var k in m) { | ||||||
|  |       fn(k, m[k]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export class ListWrapper { | export class ListWrapper { | ||||||
|   static create():List { return new List(); } |   static create():List { return new List(); } | ||||||
|  |   static createFixedSize(size):List { return new List(); } | ||||||
|   static get(m, k) { return m[k]; } |   static get(m, k) { return m[k]; } | ||||||
|   static set(m, k, v) { m[k] = v; } |   static set(m, k, v) { m[k] = v; } | ||||||
|   static clone(array) { |   static clone(array) { | ||||||
|     return Array.prototype.slice.call(array, 0); |     return Array.prototype.slice.call(array, 0); | ||||||
|   } |   } | ||||||
|   static push(l, e) { l.push(e); } |   static map(array, fn) { | ||||||
|  |     return array.map(fn); | ||||||
|  |   } | ||||||
|  |   static forEach(array, fn) { | ||||||
|  |     for(var p of array) { | ||||||
|  |       fn(p); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   static push(array, el) { | ||||||
|  |     array.push(el); | ||||||
|  |   } | ||||||
|  |   static last(array) { | ||||||
|  |     if (!array || array.length == 0) return null; | ||||||
|  |     return array[array.length - 1]; | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class SetWrapper { | export class SetWrapper { | ||||||
|  | |||||||
							
								
								
									
										57
									
								
								modules/facade/src/di/reflector.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								modules/facade/src/di/reflector.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | library facade.di.reflector; | ||||||
|  | 
 | ||||||
|  | import 'dart:mirrors'; | ||||||
|  | 
 | ||||||
|  | class Inject { | ||||||
|  |   final Object token; | ||||||
|  |   const Inject(this.token); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Reflector { | ||||||
|  |   factoryFor(Type type) { | ||||||
|  |     return _generateFactory(type); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   convertToFactory(Function factory) { | ||||||
|  |     return (args) => Function.apply(factory, args); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   Function _generateFactory(Type type) { | ||||||
|  |     ClassMirror classMirror = reflectType(type); | ||||||
|  |     MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; | ||||||
|  |     Function create = classMirror.newInstance; | ||||||
|  |     Symbol name = ctor.constructorName; | ||||||
|  |     return (args) => create(name, args).reflectee; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   dependencies(Type type) { | ||||||
|  |     ClassMirror classMirror = reflectType(type); | ||||||
|  |     MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; | ||||||
|  | 
 | ||||||
|  |     return new List.generate(ctor.parameters.length, (int pos) { | ||||||
|  |       ParameterMirror p = ctor.parameters[pos]; | ||||||
|  | 
 | ||||||
|  |       if (p.type.qualifiedName == #dynamic) { | ||||||
|  |         var name = MirrorSystem.getName(p.simpleName); | ||||||
|  |         throw "Error getting params for '$type': " | ||||||
|  |           "The '$name' parameter must be typed"; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (p.type is TypedefMirror) { | ||||||
|  |         throw "Typedef '${p.type}' in constructor " | ||||||
|  |         "'${classMirror.simpleName}' is not supported."; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       ClassMirror pTypeMirror = (p.type as ClassMirror); | ||||||
|  |       var pType = pTypeMirror.reflectedType; | ||||||
|  | 
 | ||||||
|  |       final inject = p.metadata.map((m) => m.reflectee).where((m) => m is Inject); | ||||||
|  | 
 | ||||||
|  |       if (inject.isNotEmpty) { | ||||||
|  |         return inject.first.token; | ||||||
|  |       } else { | ||||||
|  |         return pType; | ||||||
|  |       } | ||||||
|  |     }, growable:false); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								modules/facade/src/di/reflector.es6
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								modules/facade/src/di/reflector.es6
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | import {Type} from 'facade/lang'; | ||||||
|  | 
 | ||||||
|  | //TODO: vsvakin: remove when const constructors are implemented | ||||||
|  | export class Inject { | ||||||
|  |   constructor(token){ | ||||||
|  |     this.token = token; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class Reflector { | ||||||
|  |   factoryFor(type:Type) { | ||||||
|  |     return (args) => new type(...args); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   convertToFactory(factoryFunction:Function) { | ||||||
|  |     return (args) => factoryFunction(...args); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   dependencies(type:Type) { | ||||||
|  |     var p = type.parameters; | ||||||
|  |     if (p == undefined) return []; | ||||||
|  |     return type.parameters.map((p) => this._extractToken(p)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _extractToken(annotations) { | ||||||
|  |     var type, inject; | ||||||
|  |     for (var paramAnnotation of annotations) { | ||||||
|  |       if (isFunction(paramAnnotation)) { | ||||||
|  |         type = paramAnnotation; | ||||||
|  | 
 | ||||||
|  |       } else if (paramAnnotation instanceof Inject) { | ||||||
|  |         inject = paramAnnotation.token; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return inject != undefined ? inject : type; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isFunction(value) { | ||||||
|  |   return typeof value === 'function'; | ||||||
|  | } | ||||||
| @ -1,7 +1,6 @@ | |||||||
| library angular.core.facade.async; | library angular.core.facade.lang; | ||||||
| 
 | 
 | ||||||
| export 'dart:async' show Future; | export 'dart:core' show Type; | ||||||
| export 'dart:core' show Type, int; |  | ||||||
| 
 | 
 | ||||||
| class FIELD { | class FIELD { | ||||||
|   final String definition; |   final String definition; | ||||||
| @ -19,6 +18,10 @@ class IMPLEMENTS { | |||||||
|   const IMPLEMENTS(this.interfaceClass); |   const IMPLEMENTS(this.interfaceClass); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool isPresent(obj) => obj != null; | ||||||
|  | bool isBlank(obj) => obj == null; | ||||||
|  | 
 | ||||||
|  | String humanize(obj) => obj.toString(); | ||||||
| 
 | 
 | ||||||
| class StringWrapper { | class StringWrapper { | ||||||
|   static String fromCharCode(int code) { |   static String fromCharCode(int code) { | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| export var Future = Promise; |  | ||||||
| export var Type = Function; | export var Type = Function; | ||||||
| 
 | 
 | ||||||
| export class FIELD { | export class FIELD { | ||||||
| @ -12,6 +11,30 @@ export class ABSTRACT {} | |||||||
| export class IMPLEMENTS {} | export class IMPLEMENTS {} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | export function isPresent(obj){ | ||||||
|  |   return obj != undefined && obj != null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function isBlank(obj){ | ||||||
|  |   return obj == undefined || obj == null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function humanize(token) { | ||||||
|  |   if (typeof token === 'string') { | ||||||
|  |     return token; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (token === undefined || token === null) { | ||||||
|  |     return '' + token; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (token.name) { | ||||||
|  |     return token.name; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return token.toString(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export class StringWrapper { | export class StringWrapper { | ||||||
|   static fromCharCode(code:int) { |   static fromCharCode(code:int) { | ||||||
|     return String.fromCharCode(code); |     return String.fromCharCode(code); | ||||||
|  | |||||||
| @ -1,5 +1,45 @@ | |||||||
| library test_lib.test_lib; | library test_lib.test_lib; | ||||||
| export 'package:guinness/guinness.dart' show | 
 | ||||||
|     describe, ddescribe, xdescribe, | import 'package:guinness/guinness_html.dart' as gns; | ||||||
|     it, xit, iit, | export 'package:guinness/guinness_html.dart'; | ||||||
|     beforeEach, afterEach, expect; | import 'package:unittest/unittest.dart' hide expect; | ||||||
|  | import 'dart:mirrors'; | ||||||
|  | import 'dart:async'; | ||||||
|  | 
 | ||||||
|  | Expect expect(actual, [matcher]) { | ||||||
|  |   final expect = new Expect(actual); | ||||||
|  |   if (matcher != null) expect.to(matcher); | ||||||
|  |   return expect; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Expect extends gns.Expect { | ||||||
|  |   Expect(actual) : super(actual); | ||||||
|  | 
 | ||||||
|  |   void toThrowError(message) => this.toThrowWith(message: message); | ||||||
|  |   void toBeFuture() => _expect(actual is Future, equals(true)); | ||||||
|  |   Function get _expect => gns.guinness.matchers.expect; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | it(name, fn) { | ||||||
|  |   gns.it(name, _handleAsync(fn)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | iit(name, fn) { | ||||||
|  |   gns.iit(name, _handleAsync(fn)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _handleAsync(fn) { | ||||||
|  |   ClosureMirror cm = reflect(fn); | ||||||
|  |   MethodMirror mm = cm.function; | ||||||
|  | 
 | ||||||
|  |   var completer = new Completer(); | ||||||
|  | 
 | ||||||
|  |   if (mm.parameters.length == 1) { | ||||||
|  |     return () { | ||||||
|  |       cm.apply([completer.complete]); | ||||||
|  |       return completer.future; | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return fn; | ||||||
|  | } | ||||||
| @ -16,3 +16,35 @@ window.print = function(msg) { | |||||||
|     window.console.log(msg); |     window.console.log(msg); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | window.beforeEach(function() { | ||||||
|  |   jasmine.addMatchers({ | ||||||
|  |     toBeFuture: function() { | ||||||
|  |       return { | ||||||
|  |         compare: function (actual, expectedClass) { | ||||||
|  |           var pass = typeof actual === 'object' && typeof actual.then === 'function'; | ||||||
|  |           return { | ||||||
|  |             pass: pass, | ||||||
|  |             get message() { | ||||||
|  |               return 'Expected ' + actual + ' to be a future'; | ||||||
|  |             } | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     toBeAnInstanceOf: function() { | ||||||
|  |       return { | ||||||
|  |         compare: function(actual, expectedClass) { | ||||||
|  |           var pass = typeof actual === 'object' && actual instanceof expectedClass; | ||||||
|  |           return { | ||||||
|  |             pass: pass, | ||||||
|  |             get message() { | ||||||
|  |               return 'Expected ' + actual + ' to be an instance of ' + expectedClass; | ||||||
|  |             } | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | |||||||
| @ -73,7 +73,8 @@ export class ClassTransformer extends ParseTreeTransformer { | |||||||
|         // Collect all fields, defined in the constructor.
 |         // Collect all fields, defined in the constructor.
 | ||||||
|         elementTree.body.statements.forEach(function(statement) { |         elementTree.body.statements.forEach(function(statement) { | ||||||
|           var exp = statement.expression; |           var exp = statement.expression; | ||||||
|           if (exp.type === BINARY_EXPRESSION && |           if (exp && | ||||||
|  |               exp.type === BINARY_EXPRESSION && | ||||||
|               exp.operator.type === EQUAL && |               exp.operator.type === EQUAL && | ||||||
|               exp.left.type === MEMBER_EXPRESSION && |               exp.left.type === MEMBER_EXPRESSION && | ||||||
|               exp.left.operand.type === THIS_EXPRESSION) { |               exp.left.operand.type === THIS_EXPRESSION) { | ||||||
| @ -170,7 +171,8 @@ export class ClassTransformer extends ParseTreeTransformer { | |||||||
|     var superCall = null; |     var superCall = null; | ||||||
| 
 | 
 | ||||||
|     body.statements.forEach(function (statement) { |     body.statements.forEach(function (statement) { | ||||||
|       if (statement.expression.type === CALL_EXPRESSION && |       if (statement.expression && | ||||||
|  |           statement.expression.type === CALL_EXPRESSION && | ||||||
|           statement.expression.operand.type === SUPER_EXPRESSION) { |           statement.expression.operand.type === SUPER_EXPRESSION) { | ||||||
|         superCall = statement.expression; |         superCall = statement.expression; | ||||||
|       } else { |       } else { | ||||||
|  | |||||||
| @ -11,7 +11,8 @@ import { | |||||||
|   OPEN_CURLY, |   OPEN_CURLY, | ||||||
|   OPEN_PAREN, |   OPEN_PAREN, | ||||||
|   SEMI_COLON, |   SEMI_COLON, | ||||||
|   STAR |   STAR, | ||||||
|  |   STATIC | ||||||
| } from 'traceur/src/syntax/TokenType'; | } from 'traceur/src/syntax/TokenType'; | ||||||
| 
 | 
 | ||||||
| import {ParseTreeWriter as JavaScriptParseTreeWriter, ObjectLiteralExpression} from 'traceur/src/outputgeneration/ParseTreeWriter'; | import {ParseTreeWriter as JavaScriptParseTreeWriter, ObjectLiteralExpression} from 'traceur/src/outputgeneration/ParseTreeWriter'; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user