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 {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
//TODO: vsavkin: uncomment once const constructor are supported
|
||||||
|
//export class Inject {
|
||||||
|
// @CONST
|
||||||
|
// constructor(token){
|
||||||
|
// this.token = token;
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {}
|
|
||||||
}
|
|
|
@ -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)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -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)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -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'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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…
Reference in New Issue