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 {ProtoView} from './view';
 | ||||
| import {TemplateLoader} from './template_loader'; | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import {Future} from 'facade/lang'; | ||||
| import {Future} from 'facade/async'; | ||||
| //import {Document} from 'facade/dom';
 | ||||
| 
 | ||||
| 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 {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'; | ||||
| 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) {} | ||||
| } | ||||
| export class 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 void set(m, k, v){ m[k] = v; } | ||||
|   static contains(m, k) => m.containsKey(k); | ||||
|   static forEach(m, fn) { | ||||
|     m.forEach(fn); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class ListWrapper { | ||||
|   static List clone(List l) => new List.from(l); | ||||
|   static List create() => new List(); | ||||
|   static List createFixedSize(int size) => new List(size); | ||||
|   static get(m, k) => m[k]; | ||||
|   static void set(m, k, v) { m[k] = v; } | ||||
|   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); } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -6,18 +6,38 @@ export class MapWrapper { | ||||
|   static create():HashMap { return new HashMap(); } | ||||
|   static get(m, k) { return m[k]; } | ||||
|   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 { | ||||
|   static create():List { return new List(); } | ||||
|   static createFixedSize(size):List { return new List(); } | ||||
|   static get(m, k) { return m[k]; } | ||||
|   static set(m, k, v) { m[k] = v; } | ||||
|   static clone(array) { | ||||
|     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 { | ||||
|  | ||||
							
								
								
									
										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, int; | ||||
| export 'dart:core' show Type; | ||||
| 
 | ||||
| class FIELD { | ||||
|   final String definition; | ||||
| @ -19,6 +18,10 @@ class IMPLEMENTS { | ||||
|   const IMPLEMENTS(this.interfaceClass); | ||||
| } | ||||
| 
 | ||||
| bool isPresent(obj) => obj != null; | ||||
| bool isBlank(obj) => obj == null; | ||||
| 
 | ||||
| String humanize(obj) => obj.toString(); | ||||
| 
 | ||||
| class StringWrapper { | ||||
|   static String fromCharCode(int code) { | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| export var Future = Promise; | ||||
| export var Type = Function; | ||||
| 
 | ||||
| export class FIELD { | ||||
| @ -12,6 +11,30 @@ export class ABSTRACT {} | ||||
| 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 { | ||||
|   static fromCharCode(code:int) { | ||||
|     return String.fromCharCode(code); | ||||
|  | ||||
| @ -1,5 +1,45 @@ | ||||
| library test_lib.test_lib; | ||||
| export 'package:guinness/guinness.dart' show | ||||
|     describe, ddescribe, xdescribe, | ||||
|     it, xit, iit, | ||||
|     beforeEach, afterEach, expect; | ||||
| 
 | ||||
| import 'package:guinness/guinness_html.dart' as gns; | ||||
| export 'package:guinness/guinness_html.dart'; | ||||
| 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.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.
 | ||||
|         elementTree.body.statements.forEach(function(statement) { | ||||
|           var exp = statement.expression; | ||||
|           if (exp.type === BINARY_EXPRESSION && | ||||
|           if (exp && | ||||
|               exp.type === BINARY_EXPRESSION && | ||||
|               exp.operator.type === EQUAL && | ||||
|               exp.left.type === MEMBER_EXPRESSION && | ||||
|               exp.left.operand.type === THIS_EXPRESSION) { | ||||
| @ -170,7 +171,8 @@ export class ClassTransformer extends ParseTreeTransformer { | ||||
|     var superCall = null; | ||||
| 
 | ||||
|     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) { | ||||
|         superCall = statement.expression; | ||||
|       } else { | ||||
|  | ||||
| @ -11,7 +11,8 @@ import { | ||||
|   OPEN_CURLY, | ||||
|   OPEN_PAREN, | ||||
|   SEMI_COLON, | ||||
|   STAR | ||||
|   STAR, | ||||
|   STATIC | ||||
| } from 'traceur/src/syntax/TokenType'; | ||||
| 
 | ||||
| import {ParseTreeWriter as JavaScriptParseTreeWriter, ObjectLiteralExpression} from 'traceur/src/outputgeneration/ParseTreeWriter'; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user