├── .analysis_options ├── example ├── .analysis_options ├── pubspec.yaml ├── web │ ├── index.html │ └── main.dart └── pubspec.lock ├── test_assets ├── gen_test1 │ ├── c.dart │ ├── annotations.dart │ ├── common1.dart │ ├── common2.dart │ ├── a2.dart │ ├── b2.dart │ ├── a.dart │ ├── b.dart │ └── main.dart └── d8.js ├── scripts ├── travis │ ├── build.sh │ ├── benchmark.sh │ └── setup.sh ├── reflector_dynamic_script.dart ├── check_bind_args_script.dart ├── env.sh ├── class_gen.dart └── changelog.js ├── bin └── generator.dart ├── test ├── main_same_name.dart ├── test_annotations.dart ├── transformer_test.dart └── main.dart ├── lib ├── src │ ├── mirrors.dart │ ├── reflector_null.dart │ ├── reflector_static.dart │ ├── reflector.dart │ ├── errors.dart │ ├── module.dart │ ├── injector.dart │ └── reflector_dynamic.dart ├── type_literal.dart ├── di.dart ├── dynamic_injector.dart ├── annotations.dart ├── module_transformer.dart ├── transformer │ ├── options.dart │ └── injector_generator.dart ├── key.dart ├── check_bind_args.dart ├── transformer.dart └── generator.dart ├── .travis.yml ├── benchmark ├── static_injector_benchmark.dart ├── dynamic_injector_benchmark.dart ├── instance_benchmark.dart ├── module_benchmark.dart ├── injector_create_child_benchmark.dart ├── large_benchmark.dart ├── injector_benchmark_common.dart └── static_injector_baseline_benchmark.dart ├── .gitignore ├── test_tf_gen.sh ├── TODO ├── Gruntfile.coffee ├── package.json ├── pubspec.yaml ├── LICENSE ├── run-benchmarks.sh ├── run-tests.sh ├── README.md ├── pubspec.lock └── CHANGELOG.md /.analysis_options: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true -------------------------------------------------------------------------------- /example/.analysis_options: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true -------------------------------------------------------------------------------- /test_assets/gen_test1/c.dart: -------------------------------------------------------------------------------- 1 | library lib_c; 2 | 3 | cStuff() => print('cStuff'); -------------------------------------------------------------------------------- /scripts/travis/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | . ./scripts/env.sh 4 | 5 | ./run-tests.sh -------------------------------------------------------------------------------- /scripts/travis/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | . ./scripts/env.sh 4 | 5 | ./run-benchmarks.sh -------------------------------------------------------------------------------- /test_assets/gen_test1/annotations.dart: -------------------------------------------------------------------------------- 1 | library annotations; 2 | 3 | class InjectableTest { 4 | const InjectableTest(); 5 | } -------------------------------------------------------------------------------- /bin/generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:di/generator.dart' as generator; 2 | 3 | main(List args) { 4 | generator.main(args); 5 | } 6 | -------------------------------------------------------------------------------- /test/main_same_name.dart: -------------------------------------------------------------------------------- 1 | library di.tests_same_name; 2 | 3 | import 'package:di/annotations.dart'; 4 | 5 | @Injectable() 6 | class Engine {} 7 | -------------------------------------------------------------------------------- /lib/src/mirrors.dart: -------------------------------------------------------------------------------- 1 | library mirrors; 2 | 3 | import 'dart:mirrors'; 4 | export 'dart:mirrors'; 5 | 6 | String getSymbolName(Symbol symbol) => MirrorSystem.getName(symbol); 7 | -------------------------------------------------------------------------------- /lib/type_literal.dart: -------------------------------------------------------------------------------- 1 | library di.type_literal; 2 | 3 | /** 4 | * TypeLiteral is used to bind parameterized types. 5 | */ 6 | class TypeLiteral { 7 | Type get type => T; 8 | } -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: di_example 2 | version: 0.0.2 3 | dependencies: 4 | browser: any 5 | web_components: any 6 | di: 7 | path: ../ 8 | 9 | transformers: 10 | - di 11 | -------------------------------------------------------------------------------- /test_assets/gen_test1/common1.dart: -------------------------------------------------------------------------------- 1 | library lib_common1; 2 | 3 | import 'annotations.dart'; 4 | 5 | @InjectableTest() 6 | class ServiceCommon1 { 7 | sayHi() { 8 | print('Hi ServiceCommon1!'); 9 | } 10 | } -------------------------------------------------------------------------------- /test_assets/gen_test1/common2.dart: -------------------------------------------------------------------------------- 1 | library lib_common2; 2 | 3 | import 'annotations.dart'; 4 | 5 | @InjectableTest() 6 | class ServiceCommon2 { 7 | sayHi() { 8 | print('Hi ServiceCommon2!'); 9 | } 10 | } -------------------------------------------------------------------------------- /test/test_annotations.dart: -------------------------------------------------------------------------------- 1 | library test_annotations; 2 | 3 | class StringList { 4 | const StringList(); 5 | } 6 | 7 | class Old { 8 | const Old(); 9 | } 10 | 11 | class Turbo { 12 | const Turbo(); 13 | } 14 | -------------------------------------------------------------------------------- /test_assets/gen_test1/a2.dart: -------------------------------------------------------------------------------- 1 | library lib_a2; 2 | 3 | import 'annotations.dart'; 4 | import 'common2.dart'; 5 | 6 | @InjectableTest() 7 | class ServiceA2 { 8 | sayHi() { 9 | print('Hi ServiceA2!'); 10 | } 11 | } -------------------------------------------------------------------------------- /test_assets/gen_test1/b2.dart: -------------------------------------------------------------------------------- 1 | library lib_b2; 2 | 3 | import 'annotations.dart'; 4 | import 'common1.dart'; 5 | 6 | @InjectableTest() 7 | class ServiceB2 { 8 | sayHi() { 9 | print('Hi ServiceB2!'); 10 | } 11 | } -------------------------------------------------------------------------------- /test_assets/gen_test1/a.dart: -------------------------------------------------------------------------------- 1 | library lib_a; 2 | 3 | import 'annotations.dart'; 4 | import 'a2.dart'; 5 | import 'common1.dart'; 6 | 7 | @InjectableTest() 8 | class ServiceA { 9 | sayHi() { 10 | print('Hi ServiceA!'); 11 | } 12 | } -------------------------------------------------------------------------------- /test_assets/gen_test1/b.dart: -------------------------------------------------------------------------------- 1 | library lib_b; 2 | 3 | import 'annotations.dart'; 4 | import 'b2.dart'; 5 | import 'common2.dart'; 6 | 7 | @InjectableTest() 8 | class ServiceB { 9 | sayHi() { 10 | print('Hi ServiceB!'); 11 | } 12 | } -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | 8 | 9 | Check console output for build success. 10 | 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | 5 | env: 6 | matrix: 7 | - JOB=unit-stable 8 | - JOB=unit-dev 9 | 10 | before_script: 11 | - ./scripts/travis/setup.sh 12 | 13 | script: 14 | - ./scripts/travis/build.sh 15 | 16 | after_success: 17 | - ./scripts/travis/benchmark.sh 18 | -------------------------------------------------------------------------------- /benchmark/static_injector_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:di/di.dart'; 2 | import 'package:di/src/reflector_static.dart'; 3 | import 'injector_benchmark_common.dart'; 4 | 5 | main() { 6 | new InjectorBenchmark('StaticInjectorBenchmark', 7 | new GeneratedTypeFactories(typeFactories, paramKeys) 8 | ).report(); 9 | } 10 | -------------------------------------------------------------------------------- /benchmark/dynamic_injector_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:benchmark_harness/benchmark_harness.dart'; 2 | import 'package:di/src/reflector_dynamic.dart'; 3 | 4 | import 'injector_benchmark_common.dart'; 5 | 6 | main() { 7 | new InjectorBenchmark('DynamicInjectorBenchmark', 8 | new DynamicTypeFactories()).report(); 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | packages 3 | .packages 4 | .pub 5 | .idea 6 | test/type_factories_gen.dart 7 | test/main.dart.js 8 | test/main.dart.js.deps 9 | test/main.dart.js.map 10 | test/main.dart.precompiled.js 11 | test/transformer/build 12 | benchmark/generated_files/ 13 | examples/build 14 | out 15 | build 16 | benchmark/generated_files/* 17 | -------------------------------------------------------------------------------- /example/web/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:di/di.dart'; 2 | import 'package:di/annotations.dart'; 3 | 4 | @Injectable() 5 | class Application { 6 | run() { 7 | print('Success'); 8 | } 9 | } 10 | 11 | main() { 12 | Module module = new Module(); 13 | module.bind(Application); 14 | new ModuleInjector([module]).get(Application).run(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/di.dart: -------------------------------------------------------------------------------- 1 | library di; 2 | 3 | export 'key.dart' show Key, key; 4 | export 'src/injector.dart' show Injector, ModuleInjector; 5 | export 'src/module.dart' show Module, Binding, DEFAULT_VALUE; 6 | export 'src/reflector.dart' show TypeReflector; 7 | export 'src/errors.dart' hide BaseError, PRIMITIVE_TYPES; 8 | export 'annotations.dart' show Injectable, Injectables; 9 | -------------------------------------------------------------------------------- /test_tf_gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Runs type factories generator for test files. 3 | 4 | if [ -z "$DART_SDK" ]; then 5 | echo "ERROR: You need to set the DART_SDK environment variable to your dart sdk location" 6 | exit 1 7 | fi 8 | 9 | set -v 10 | 11 | dart --checked bin/generator.dart $DART_SDK test/main.dart di.tests.InjectableTest test/type_factories_gen.dart packages/ 12 | 13 | -------------------------------------------------------------------------------- /lib/dynamic_injector.dart: -------------------------------------------------------------------------------- 1 | library di.dynamic_injector; 2 | 3 | import 'di.dart'; 4 | export 'di.dart'; 5 | 6 | /** 7 | * A backwards-compatible shim to avoid breaking DI 1 with DI 2.0.0 8 | * TODO: Remove after all apps have been upgraded. 9 | */ 10 | @Deprecated("3.0") 11 | class DynamicInjector extends ModuleInjector { 12 | DynamicInjector({List modules}) : super(modules); 13 | } 14 | -------------------------------------------------------------------------------- /scripts/reflector_dynamic_script.dart: -------------------------------------------------------------------------------- 1 | main() { 2 | var args; 3 | for (var i = 0; i <= 25; i++) { 4 | print("case $i:"); 5 | args = new List.generate(i, (j) => "a${j+1}").join(', '); 6 | print("return ($args) {"); 7 | var buffer = new StringBuffer(); 8 | for (var j = 0; j < i; j++){ 9 | buffer.write("l[$j]=a${j+1};"); 10 | } 11 | print(buffer); 12 | print("return create(name, l).reflectee;"); 13 | print("};"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | transparency: 2 | - runtime check (all deps satisfied?) 3 | - logging 4 | - graph dump 5 | - runtime type check (warning: when overriding a provider with something that does not implement the provider) 6 | 7 | annotations: 8 | - injecting primitive types 9 | - injecting dynamic vars 10 | - overriding type annotations 11 | - support multiple constructors ??? 12 | - injecting a property (config.baseUrl, rather than whole config) ??? 13 | 14 | CONFIG: 15 | - allow implicit providers (Type) 16 | -------------------------------------------------------------------------------- /scripts/check_bind_args_script.dart: -------------------------------------------------------------------------------- 1 | main() { 2 | var s = new StringBuffer(); 3 | 4 | int max_arg_count = 15; 5 | 6 | for (var i = 0; i <= max_arg_count; i++) { 7 | s.write("typedef _$i("); 8 | s.write(new List.generate(i, (c) => "a${c+1}").join(", ")); 9 | s.write(");\n"); 10 | } 11 | 12 | s.write("switch (len) {\n"); 13 | for (var i = 0; i <= max_arg_count; i++) { 14 | s.write("case $i: argCountMatch = toFactory is _$i; break;\n"); 15 | } 16 | s.write('}'); 17 | 18 | print(s); 19 | } 20 | -------------------------------------------------------------------------------- /lib/annotations.dart: -------------------------------------------------------------------------------- 1 | library di.annotations; 2 | 3 | /** 4 | * Annotation that can be applied to the library declaration listing all 5 | * types for which type factories should be generated to be used 6 | * by StaticInjector. 7 | */ 8 | class Injectables { 9 | final List types; 10 | const Injectables(this.types); 11 | } 12 | 13 | /** 14 | * Annotation that can be applied to a class for which type factories 15 | * should be generated to be used by StaticInjector. 16 | */ 17 | class Injectable { 18 | const Injectable(); 19 | } 20 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | spawn = require('child_process').spawn 2 | 3 | DART_VM = process.env.DART_VM or '../dart-sdk/bin/dart' 4 | 5 | module.exports = (grunt) -> 6 | grunt.initConfig 7 | watch: 8 | tests: 9 | files: ['test/**/*.dart', 'lib/**/*.dart'] 10 | tasks: ['dart'] 11 | dart: 12 | tests: 13 | entry: 'test/main.dart' 14 | 15 | grunt.registerMultiTask 'dart', 'run dart program', -> 16 | spawn(DART_VM, [@data.entry], {stdio: 'inherit'}).on 'close', @async() 17 | 18 | grunt.loadNpmTasks 'grunt-contrib-watch' 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dart-di", 3 | "version": "0.0.0", 4 | "description": "A prototype of Dependency Injection framework.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "qq": "*" 8 | }, 9 | "devDependencies": { 10 | "grunt": "~0.4", 11 | "grunt-contrib-watch": "~0.3" 12 | }, 13 | "scripts": { 14 | "test": "grunt dart:test" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/angular/di.dart/issues" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/angular/di.dart.git" 22 | }, 23 | "author": "Vojta Jina ", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /benchmark/instance_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:di/di.dart'; 2 | import 'package:di/src/reflector_static.dart'; 3 | 4 | import 'injector_benchmark_common.dart'; 5 | 6 | // tests the speed of cached getInstanceByKey requests 7 | class InstanceBenchmark extends InjectorBenchmark{ 8 | InstanceBenchmark(name, typeReflector) : super(name, typeReflector); 9 | 10 | void run(){ 11 | Injector injector = new ModuleInjector([module]); 12 | for (var i = 0; i < 30; i++) { 13 | injector.get(A); 14 | } 15 | } 16 | } 17 | 18 | main() { 19 | new InstanceBenchmark('InstanceBenchmark', 20 | new GeneratedTypeFactories(typeFactories, paramKeys) 21 | ).report(); 22 | } 23 | -------------------------------------------------------------------------------- /scripts/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ -n "$DART_SDK" ]; then 5 | DARTSDK=$DART_SDK 6 | else 7 | echo "sdk=== $DARTSDK" 8 | DART=`which dart|cat` # pipe to cat to ignore the exit code 9 | DARTSDK=`which dart | sed -e 's/\/bin\/dart$/\//'` 10 | if [ -z "$DARTSDK" ]; then 11 | DARTSDK="`pwd`/dart-sdk" 12 | fi 13 | fi 14 | 15 | export DART_SDK="$DARTSDK" 16 | export DART=${DART:-"$DARTSDK/bin/dart"} 17 | export PUB=${PUB:-"$DARTSDK/bin/pub"} 18 | export DARTANALYZER=${DARTANALYZER:-"$DARTSDK/bin/dartanalyzer"} 19 | export DARTDOC=${DARTDOC:-"$DARTSDK/bin/dartdoc"} 20 | 21 | 22 | export DART_FLAGS='--enable_type_checks --enable_asserts' 23 | export PATH=$PATH:$DARTSDK/bin -------------------------------------------------------------------------------- /benchmark/module_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:benchmark_harness/benchmark_harness.dart'; 2 | import 'package:di/di.dart'; 3 | 4 | import 'injector_benchmark_common.dart'; 5 | 6 | /** 7 | * tests the speed of looking up typeFactories and binding them to 8 | * a module. Mirroring time is not counted because DynamicTypeFactories 9 | * caches the results. 10 | */ 11 | class ModuleBenchmark extends BenchmarkBase { 12 | var injectorFactory; 13 | 14 | ModuleBenchmark() : super('ModuleBenchmark'); 15 | 16 | void run() { 17 | var m = new Module() 18 | ..bind(A) 19 | ..bind(B) 20 | ..bind(C) 21 | ..bind(D) 22 | ..bind(E); 23 | } 24 | } 25 | 26 | main() { 27 | new ModuleBenchmark().report(); 28 | } 29 | -------------------------------------------------------------------------------- /test_assets/gen_test1/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'annotations.dart'; 4 | import 'common1.dart'; 5 | 6 | import 'a.dart' deferred as a; 7 | import 'b.dart' deferred as b; 8 | import 'c.dart' deferred as c; 9 | 10 | void main() { 11 | a.loadLibrary().then(onALoaded); 12 | b.loadLibrary().then(onBLoaded); 13 | c.loadLibrary().then(onCLoaded); 14 | } 15 | 16 | void onALoaded(_) { 17 | var serviceA = new a.ServiceA(); 18 | serviceA.sayHi(); 19 | } 20 | 21 | void onBLoaded(_) { 22 | var serviceB = new b.ServiceB(); 23 | serviceB.sayHi(); 24 | } 25 | 26 | void onCLoaded(_) { 27 | c.cStuff(); 28 | } 29 | 30 | @InjectableTest() 31 | class ServiceMain { 32 | sayHi() { 33 | print('Hi ServiceMain!'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: di 2 | version: 3.3.10 3 | authors: 4 | - Vojta Jina 5 | - Pavel Jbanov 6 | - Anting Shen 7 | - Misko Hevery 8 | - Victor Berchet 9 | description: Dependency Injection framework 10 | homepage: https://github.com/angular/di.dart 11 | environment: 12 | sdk: '>=1.13.0 <2.0.0' 13 | dependencies: 14 | analyzer: '0.27.2' 15 | barback: '>=0.15.0 <0.16.0' 16 | code_transformers: '>=0.3.1 <0.4.0' 17 | path: ">=1.3.0 <2.0.0" 18 | dev_dependencies: 19 | benchmark_harness: ">=1.0.4 <2.0.0" 20 | guinness2: '>=0.0.3 <1.0.0' 21 | matcher: '>=0.11.0 <0.13.0' 22 | test: '>=0.12.7 <0.13.0' 23 | transformers: 24 | - di/module_transformer 25 | -------------------------------------------------------------------------------- /scripts/travis/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | case $( uname -s ) in 6 | Linux) 7 | DART_SDK_ZIP=dartsdk-linux-x64-release.zip 8 | ;; 9 | Darwin) 10 | DART_SDK_ZIP=dartsdk-macos-x64-release.zip 11 | ;; 12 | esac 13 | 14 | CHANNEL=`echo $JOB | cut -f 2 -d -` 15 | echo Fetch Dart channel: $CHANNEL 16 | 17 | echo http://storage.googleapis.com/dart-archive/channels/$CHANNEL/release/latest/sdk/$DART_SDK_ZIP 18 | curl -L http://storage.googleapis.com/dart-archive/channels/$CHANNEL/release/latest/sdk/$DART_SDK_ZIP > $DART_SDK_ZIP 19 | echo Fetched new dart version $(unzip -p $DART_SDK_ZIP dart-sdk/version) 20 | rm -rf dart-sdk 21 | unzip $DART_SDK_ZIP > /dev/null 22 | rm $DART_SDK_ZIP 23 | 24 | echo ============================================================================= 25 | . ./scripts/env.sh 26 | $DART --version 27 | $PUB install -------------------------------------------------------------------------------- /lib/src/reflector_null.dart: -------------------------------------------------------------------------------- 1 | library di.reflector_null; 2 | 3 | import '../key.dart'; 4 | import 'reflector.dart'; 5 | import 'errors.dart'; 6 | 7 | TypeReflector getReflector() => new NullReflector(); 8 | 9 | class NullReflector extends TypeReflector { 10 | Function factoryFor(Type type) => throw new NullReflectorError(); 11 | 12 | List parameterKeysFor(Type type) => throw new NullReflectorError(); 13 | 14 | void addAll(Map factories, Map> parameterKeys) => 15 | throw new NullReflectorError(); 16 | 17 | void add(Type type, Function factory, List parameterKeys) => 18 | throw new NullReflectorError(); 19 | } 20 | 21 | class NullReflectorError extends BaseError { 22 | NullReflectorError() 23 | : super("Module.DEFAULT_REFLECTOR not initialized for dependency injection." 24 | "http://goo.gl/XFXx9G"); 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Google, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/src/reflector_static.dart: -------------------------------------------------------------------------------- 1 | library di.reflector_static; 2 | 3 | import '../di.dart'; 4 | 5 | class GeneratedTypeFactories extends TypeReflector { 6 | 7 | Map _factories; 8 | Map> _parameterKeys; 9 | 10 | /** 11 | * This constructor should be called by code generated by transformer in main 12 | * with generated factories and paramKeys to initialize it. 13 | */ 14 | GeneratedTypeFactories(Map this._factories, 15 | Map>this._parameterKeys); 16 | 17 | Function factoryFor(Type type) { 18 | var keys = _factories[type]; 19 | if (keys != null) return keys; 20 | throw new NoGeneratedTypeFactoryError(type); 21 | } 22 | 23 | List parameterKeysFor(Type type) { 24 | var keys = _parameterKeys[type]; 25 | if (keys != null) return keys; 26 | throw new NoGeneratedTypeFactoryError(type); 27 | } 28 | 29 | void addAll(Map factories, Map> parameterKeys) { 30 | _factories.addAll(factories); 31 | _parameterKeys.addAll(parameterKeys); 32 | } 33 | 34 | void add(Type type, Function factory, List paramKeys) { 35 | _factories[type] = factory; 36 | _parameterKeys[type] = paramKeys; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /benchmark/injector_create_child_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:di/static_injector.dart'; 2 | 3 | import 'injector_benchmark_common.dart'; 4 | 5 | class CreateChildBenchmark extends InjectorBenchmark{ 6 | CreateChildBenchmark(name, injectorFactory) : super(name, injectorFactory); 7 | 8 | StaticInjector injector; 9 | List moreModules; 10 | 11 | void setup() { 12 | getModule([_]) { 13 | var m = new Module() 14 | ..bind(A) 15 | ..bind(B) 16 | ..bind(C) 17 | ..bind(D) 18 | ..bind(E) 19 | ..bind(F) 20 | ..bind(G); 21 | return m; 22 | } 23 | module = getModule(); 24 | moreModules = new List.generate(30, getModule, growable:true); 25 | } 26 | 27 | void run(){ 28 | injector = injectorFactory([module]); 29 | for (var i = 0; i < 30; i++) { 30 | injector = injector.createChild(moreModules); 31 | } 32 | } 33 | } 34 | 35 | main() { 36 | var typeFactories = { 37 | A: (f) => new A(f(B), f(C)), 38 | B: (f) => new B(f(D), f(E)), 39 | C: (f) => new C(), 40 | D: (f) => new D(), 41 | E: (f) => new E(), 42 | }; 43 | 44 | new CreateChildBenchmark('CreateChildBenchmark', 45 | (m) => new StaticInjector(modules: m, typeFactories: typeFactories) 46 | ).report(); 47 | } 48 | -------------------------------------------------------------------------------- /lib/module_transformer.dart: -------------------------------------------------------------------------------- 1 | library di.transformer.module_transformer; 2 | 3 | import 'dart:async'; 4 | import 'package:barback/barback.dart'; 5 | 6 | /** 7 | * Pub transformer that changes reflector in Module to null instead of importing 8 | * the dynamic reflector which imports mirrors. Another di transformer run by the app 9 | * will import the static reflector. 10 | */ 11 | class ModuleTransformerGroup implements TransformerGroup { 12 | final Iterable phases; 13 | 14 | ModuleTransformerGroup.asPlugin(BarbackSettings settings) 15 | : phases = [[new ModuleTransformer()]]; 16 | } 17 | 18 | class ModuleTransformer extends Transformer { 19 | 20 | ModuleTransformer(); 21 | 22 | Future isPrimary(AssetId id) => 23 | new Future.value(id == new AssetId.parse("di|lib/src/module.dart")); 24 | 25 | Future apply(Transform transform) { 26 | var id = transform.primaryInput.id; 27 | return transform.primaryInput.readAsString().then((code) { 28 | // Note: this rewrite is coupled with how module.dart is 29 | // written. Make sure both are updated in sync. 30 | transform.addOutput(new Asset.fromString(id, code 31 | .replaceAll(new RegExp('import "reflector_dynamic.dart";'), 32 | 'import "reflector_null.dart";'))); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /run-benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Prepend text to a file in-place. 5 | function di::prepend_text { 6 | local file=$1 7 | local text=$2 8 | 9 | # NOTE: sed -i doesn't work on osx/bsd 10 | tmpfile=$(mktemp -t di_benchmark.XXXXXX) 11 | echo "$text" > $tmpfile 12 | cat "$file" >> $tmpfile 13 | cp -f "$tmpfile" "$file" 14 | rm -f "$tmpfile" 15 | } 16 | 17 | BENCHMARKS="module_benchmark.dart 18 | dynamic_injector_benchmark.dart 19 | static_injector_benchmark.dart 20 | instance_benchmark.dart 21 | large_benchmark.dart" 22 | 23 | mkdir -p benchmark/generated_files 24 | dart scripts/class_gen.dart 25 | 26 | # run tests in dart 27 | echo "Dart VM Benchmarks:" 28 | echo "-------------------" 29 | for b in $BENCHMARKS; do 30 | echo "Running: $b" 31 | dart benchmark/$b 32 | done 33 | 34 | # run dart2js on tests 35 | echo 36 | echo "Compiling with dart2js:" 37 | echo "-----------------------" 38 | mkdir -p out 39 | for b in $BENCHMARKS; do 40 | echo "$b" 41 | dart2js --minify benchmark/$b -o out/$b.js > /dev/null 42 | # HACK node.js doesn't understand self 43 | di::prepend_text "out/$b.js" "var self=this" 44 | done 45 | 46 | # run tests in node 47 | echo 48 | echo "JS Benchmarks:" 49 | echo "--------------" 50 | for b in $BENCHMARKS; do 51 | echo "Running: $b" 52 | node out/$b.js 53 | done 54 | -------------------------------------------------------------------------------- /lib/src/reflector.dart: -------------------------------------------------------------------------------- 1 | library di.reflector; 2 | 3 | import "../key.dart"; 4 | 5 | abstract class TypeReflector { 6 | /** 7 | * Returns a factory function that knows how to construct an instance of a type. 8 | * 9 | * This interface is type based because there is only one factory for each 10 | * type, no matter what the annotations are. However, the parameters returned 11 | * are keys because annotations matter in that case so the injector knows 12 | * what to inject. This leads to some performance loss from type comparison 13 | * and key creation in DynamicTypeFactories but TypeReflector should only be 14 | * used during module binding. 15 | */ 16 | Function factoryFor(Type type); 17 | 18 | /** 19 | * Returns keys of the items that must be injected into the corresponding 20 | * Factory that TypeReflector.factoryFor returns. 21 | */ 22 | List parameterKeysFor(Type type); 23 | 24 | /** 25 | * Adds these factories and parameterKeys to the reflector, so that future calls 26 | * to factoryFor and parameterKeysFor will return these new values. 27 | * 28 | * Overwrites in static implementation, no-op in dynamic implementation 29 | */ 30 | void addAll(Map factories, Map> parameterKeys); 31 | void add(Type type, Function factory, List parameterKeys); 32 | } 33 | -------------------------------------------------------------------------------- /scripts/class_gen.dart: -------------------------------------------------------------------------------- 1 | /** 2 | * This script generates a large number of classes and their corresponding factories 3 | * to benchmark/generated_files, to be imported by benchmarks that require many classes 4 | * and factories. Called from run-benchmarks.sh 5 | */ 6 | import 'dart:io'; 7 | 8 | main() { 9 | int numClasses = 1000; 10 | 11 | File file = new File('benchmark/generated_files/classes.dart'); 12 | var sink = file.openWrite(mode: WRITE); 13 | sink.write('int c=0;\n'); 14 | for (var i = 0; i < numClasses; i++) { 15 | sink.write('class Test$i{Test$i(){c++;}}\n'); 16 | } 17 | sink.close(); 18 | 19 | file = new File('benchmark/generated_files/factories.dart');; 20 | sink = file.openWrite(mode: WRITE); 21 | sink.write('import "package:di/key.dart";\n'); 22 | sink.write('import "package:di/di.dart";\n'); 23 | sink.write('import "classes.dart";\n'); 24 | sink.write('export "classes.dart";\n'); 25 | for (var i = 0; i< numClasses; i++) { 26 | sink.write('final Key key$i = new Key(Test$i);\n'); 27 | } 28 | sink.write('List allKeys = [\n'); 29 | for (var i = 0; i < numClasses; i++) { 30 | sink.write('key$i, '); 31 | } 32 | sink.write('];\n'); 33 | sink.write('Map typeFactories = {\n'); 34 | for (var i = 0; i < numClasses; i++) { 35 | sink.write('Test$i: () => new Test$i(),\n'); 36 | } 37 | sink.write('};\n'); 38 | sink.write('Map> parameterKeys = {\n'); 39 | for (var i = 0; i < numClasses; i++) { 40 | sink.write('Test$i: const[], '); 41 | } 42 | sink.write('\n};\n'); 43 | sink.close(); 44 | } 45 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Running generator..." 5 | ./test_tf_gen.sh 6 | 7 | echo "Running pub get..." 8 | pub get 9 | cd example 10 | pub get 11 | cd .. 12 | 13 | echo "Running tests in Dart..." 14 | dart --checked test/main.dart 15 | dart --checked test/transformer_test.dart 16 | 17 | echo "Running analyzer..." 18 | dartanalyzer --fatal-warnings lib/*.dart 19 | dartanalyzer --fatal-warnings example/web/*.dart 20 | 21 | # Example app test 22 | echo "Building example..." 23 | rm -rf example/build/ 24 | cd example 25 | pub_out=$(pub build 2>&1 | tee /dev/tty | grep -F "dart:mirrors" || : ) 26 | cd .. 27 | echo "--------" 28 | 29 | if [[ -n $pub_out ]] 30 | then 31 | echo "FAIL: Example transformer should not import dart:mirror" 32 | failed=1 33 | else 34 | echo "PASS: Example transformer should not import dart:mirror" 35 | fi 36 | 37 | output=$(node example/build/web/main.dart.js || : ) 38 | if [ $output == "Success" ] 39 | then 40 | echo "PASS: Example transformer should inject dependencies" 41 | else 42 | echo "FAIL: Example transformer should inject dependencies" 43 | failed=1 44 | fi 45 | 46 | if [[ -n $failed ]] 47 | then 48 | echo "Tests failed. Build /example with \`pub build example/ --mode debug\` to debug." 49 | exit 1 50 | fi 51 | echo "" 52 | 53 | echo "Compiling tests to JavaScript with dart2js..." 54 | mkdir -p out 55 | dart2js --minify -c test/main.dart -o out/main.dart.js 56 | 57 | # attach a preamble file to dart2js output to emulate browser 58 | # so node doesn't complain about lack of browser objects 59 | cp test_assets/d8.js out/main.js 60 | cat out/main.dart.js >> out/main.js 61 | 62 | echo "Running compiled tests in node..." 63 | node out/main.js 64 | 65 | echo "Testing complete." 66 | -------------------------------------------------------------------------------- /lib/transformer/options.dart: -------------------------------------------------------------------------------- 1 | library di.transformer.options; 2 | 3 | import 'dart:async'; 4 | import 'package:barback/barback.dart'; 5 | import 'package:code_transformers/resolver.dart'; 6 | 7 | const List DEFAULT_INJECTABLE_ANNOTATIONS = 8 | const ['di.annotations.Injectable']; 9 | 10 | /// Returns either a bool or a Future when complete. 11 | typedef EntryFilter(Asset asset); 12 | 13 | /** Options used by DI transformers */ 14 | class TransformOptions { 15 | 16 | /** 17 | * Filter to determine which assets should be modified. 18 | */ 19 | final EntryFilter entryFilter; 20 | 21 | /** 22 | * List of additional annotations which are used to indicate types as being 23 | * injectable. 24 | */ 25 | final List injectableAnnotations; 26 | 27 | /** 28 | * Set of additional types which should be injected. 29 | */ 30 | final Set injectedTypes; 31 | 32 | /** 33 | * Path to the Dart SDK directory, for resolving Dart libraries. 34 | */ 35 | final String sdkDirectory; 36 | 37 | TransformOptions({EntryFilter entryFilter, String sdkDirectory, 38 | List injectableAnnotations, List injectedTypes}) 39 | : entryFilter = entryFilter != null ? entryFilter : isPossibleDartEntry, 40 | sdkDirectory = sdkDirectory, 41 | injectableAnnotations = 42 | (injectableAnnotations != null ? injectableAnnotations : []) 43 | ..addAll(DEFAULT_INJECTABLE_ANNOTATIONS), 44 | injectedTypes = 45 | new Set.from(injectedTypes != null ? injectedTypes : []) { 46 | if (sdkDirectory == null) 47 | throw new ArgumentError('sdkDirectory must be provided.'); 48 | } 49 | 50 | Future isDartEntry(Asset asset) => new Future.value(entryFilter(asset)); 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/errors.dart: -------------------------------------------------------------------------------- 1 | library di.errors; 2 | 3 | import '../key.dart'; 4 | 5 | abstract class BaseError extends Error { 6 | final String message; 7 | BaseError(this.message); 8 | String toString() => message; 9 | } 10 | 11 | final List PRIMITIVE_TYPES = [ 12 | new Key(num), new Key(int), new Key(double), new Key(String), 13 | new Key(bool), new Key(dynamic) 14 | ]; 15 | 16 | class DynamicReflectorError extends BaseError { 17 | DynamicReflectorError(message) : super(message); 18 | } 19 | 20 | abstract class ResolvingError extends Error { 21 | 22 | List keys; 23 | ResolvingError(key): keys = [key]; 24 | 25 | String get resolveChain { 26 | StringBuffer buffer = new StringBuffer() 27 | ..write("(resolving ") 28 | ..write(keys.reversed.join(" -> ")) 29 | ..write(")"); 30 | return buffer.toString(); 31 | } 32 | 33 | void appendKey(Key key) { 34 | keys.add(key); 35 | } 36 | 37 | String toString(); 38 | } 39 | 40 | class NoProviderError extends ResolvingError { 41 | NoProviderError(key): super(key); 42 | 43 | String toString(){ 44 | var root = keys.first; 45 | if (PRIMITIVE_TYPES.contains(root)) { 46 | return "Cannot inject a primitive type of $root! $resolveChain"; 47 | } 48 | return "No provider found for $root! $resolveChain"; 49 | } 50 | } 51 | 52 | class CircularDependencyError extends ResolvingError { 53 | CircularDependencyError(key) : super(key); 54 | String toString() => "Cannot resolve a circular dependency! $resolveChain"; 55 | } 56 | 57 | class NoGeneratedTypeFactoryError extends BaseError { 58 | NoGeneratedTypeFactoryError(Type type): super(type.toString()); 59 | String toString() => 60 | "Type '$message' not found in generated typeFactory maps. Is the type's " 61 | "constructor injectable and annotated for injection?"; 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/angular/di.dart.svg?branch=master)](https://travis-ci.org/angular/di.dart) 2 | 3 | # Dependency Injection (DI) framework 4 | 5 | *Note:* This package does not work with Flutter 6 | (because this package uses dart:mirrors, which 7 | Flutter does not support). 8 | 9 | ## Installation 10 | 11 | Add dependency to your pubspec.yaml. 12 | 13 | dependencies: 14 | di: ">=3.3.4 <4.0.0" 15 | 16 | Then, run `pub install`. 17 | 18 | Import di. 19 | 20 | import 'package:di/di.dart'; 21 | 22 | ## Example 23 | 24 | ```dart 25 | import 'package:di/di.dart'; 26 | 27 | abstract class Engine { 28 | go(); 29 | } 30 | 31 | class Fuel {} 32 | 33 | class V8Engine implements Engine { 34 | Fuel fuel; 35 | V8Engine(this.fuel); 36 | 37 | go() { 38 | print('Vroom...'); 39 | } 40 | } 41 | 42 | class ElectricEngine implements Engine { 43 | go() { 44 | print('Hum...'); 45 | } 46 | } 47 | 48 | // Annotation 49 | class Electric { 50 | const Electric(); 51 | } 52 | 53 | class GenericCar { 54 | Engine engine; 55 | 56 | GenericCar(this.engine); 57 | 58 | drive() { 59 | engine.go(); 60 | } 61 | } 62 | 63 | class ElectricCar { 64 | Engine engine; 65 | 66 | ElectricCar(@Electric() this.engine); 67 | 68 | drive() { 69 | engine.go(); 70 | } 71 | } 72 | 73 | void main() { 74 | var injector = new ModuleInjector([new Module() 75 | ..bind(Fuel) 76 | ..bind(GenericCar) 77 | ..bind(ElectricCar) 78 | ..bind(Engine, toFactory: (fuel) => new V8Engine(fuel), inject: [Fuel]) 79 | ..bind(Engine, toImplementation: ElectricEngine, withAnnotation: const Electric()) 80 | ]); 81 | injector.get(GenericCar).drive(); // Vroom... 82 | injector.get(ElectricCar).drive(); // Hum... 83 | } 84 | ``` 85 | 86 | ## Contributing 87 | 88 | Refer to the guidelines for [contributing to AngularDart](http://goo.gl/nrXVgm). 89 | -------------------------------------------------------------------------------- /benchmark/large_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:benchmark_harness/benchmark_harness.dart'; 2 | import 'package:di/di.dart'; 3 | import 'package:di/src/reflector_static.dart'; 4 | import 'generated_files/factories.dart'; 5 | 6 | import 'dart:math'; 7 | 8 | class LargeBenchmark extends BenchmarkBase { 9 | var injectorFactory; 10 | Injector rootInjector; 11 | Injector leafInjector; 12 | var leafKey; 13 | var rng = new Random(); 14 | var numInjectors = 1; 15 | var allLeaves = []; 16 | 17 | LargeBenchmark(name, this.injectorFactory) : super("Large" + name); 18 | 19 | setup() { 20 | var rootModule = new Module() 21 | ..bindByKey(key999) 22 | ..bindByKey(key998) 23 | ..bindByKey(key997) 24 | ..bindByKey(key996) 25 | ..bindByKey(key995); 26 | rootInjector = injectorFactory([rootModule]); 27 | 28 | createChildren (injector, depth, width) { 29 | if (depth <= 0){ 30 | allLeaves.add(injector); 31 | return; 32 | } 33 | for (var i=0; i m) => new ModuleInjector(m)); 52 | 53 | run() { 54 | leafInjector.getByKey(key999); 55 | } 56 | } 57 | 58 | class GetFromLeaf extends LargeBenchmark { 59 | GetFromLeaf() : super('FromLeaf', (m) => new ModuleInjector(m)); 60 | 61 | run() { 62 | leafInjector.getByKey(leafKey); 63 | } 64 | } 65 | 66 | main() { 67 | Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys); 68 | new GetFromRoot().report(); 69 | new GetFromLeaf().report(); 70 | } 71 | -------------------------------------------------------------------------------- /lib/key.dart: -------------------------------------------------------------------------------- 1 | library di.key; 2 | 3 | /** 4 | * Key to which an [Injector] binds a [Provider]. This is a pair consisting of 5 | * a [type] and an optional [annotation]. 6 | */ 7 | class Key { 8 | // TODO: experiment with having a separate map for non-annotated types (perf) 9 | // While Map.identity is faster here, it's not supported in dart2js (dart issue 19622 wontfix) 10 | static Map> _typeToAnnotationToKey = {}; 11 | static int _numInstances = 0; 12 | /// The number of instances of [Key] created. 13 | static int get numInstances => _numInstances; 14 | 15 | final Type type; 16 | /// Optional. 17 | final Type annotation; 18 | /// Assigned via auto-increment. 19 | final int id; 20 | 21 | int _data; 22 | @deprecated 23 | int get uid => _data; 24 | @deprecated 25 | set uid(int d) { 26 | if (_data == null) { 27 | _data = d; 28 | return; 29 | } 30 | throw "Key($type).uid has already been set to $_data."; 31 | } 32 | 33 | int get hashCode => id; 34 | 35 | /** 36 | * Creates a new key or returns one from a cache if given the same inputs that 37 | * a previous call had. E.g. `identical(new Key(t, a), new Key(t, a))` holds. 38 | */ 39 | factory Key(Type type, [Object annotation]) { 40 | // Don't use Map.putIfAbsent -- too slow! 41 | var annotationToKey = _typeToAnnotationToKey[type]; 42 | if (annotationToKey == null) { 43 | _typeToAnnotationToKey[type] = annotationToKey = new Map(); 44 | } 45 | annotation = _toType(annotation); 46 | Key key = annotationToKey[annotation]; 47 | if (key == null) { 48 | annotationToKey[annotation] = 49 | key = new Key._(type, annotation, _numInstances++); 50 | } 51 | return key; 52 | } 53 | 54 | Key._(this.type, this.annotation, this.id); 55 | 56 | String toString() { 57 | String asString = type.toString(); 58 | if (annotation != null) { 59 | asString += ' annotated with: $annotation'; 60 | } 61 | return asString; 62 | } 63 | 64 | static Type _toType(obj) { 65 | if (obj == null) return null; 66 | if (obj is Type) return obj; 67 | return obj.runtimeType; 68 | } 69 | } 70 | 71 | /// shortcut function 72 | Key key(Type type, [annotation]) => new Key(type, annotation); 73 | -------------------------------------------------------------------------------- /lib/check_bind_args.dart: -------------------------------------------------------------------------------- 1 | library di.check_bind_args; 2 | 3 | import "src/module.dart"; 4 | export "src/module.dart" show DEFAULT_VALUE, IDENTITY, isSet, isNotSet; 5 | 6 | checkBindArgs(dynamic toValue, Function toFactory, 7 | Type toImplementation, List inject, toInstanceOf) { 8 | int count = 0; 9 | bool argCountMatch = true; 10 | if (isSet(toValue)) count++; 11 | if (isSet(toFactory)) { 12 | count++; 13 | var len = inject.length; 14 | switch (len) { 15 | case 0: argCountMatch = toFactory is _0; break; 16 | case 1: argCountMatch = toFactory is _1; break; 17 | case 2: argCountMatch = toFactory is _2; break; 18 | case 3: argCountMatch = toFactory is _3; break; 19 | case 4: argCountMatch = toFactory is _4; break; 20 | case 5: argCountMatch = toFactory is _5; break; 21 | case 6: argCountMatch = toFactory is _6; break; 22 | case 7: argCountMatch = toFactory is _7; break; 23 | case 8: argCountMatch = toFactory is _8; break; 24 | case 9: argCountMatch = toFactory is _9; break; 25 | case 10: argCountMatch = toFactory is _10; break; 26 | case 11: argCountMatch = toFactory is _11; break; 27 | case 12: argCountMatch = toFactory is _12; break; 28 | case 13: argCountMatch = toFactory is _13; break; 29 | case 14: argCountMatch = toFactory is _14; break; 30 | case 15: argCountMatch = toFactory is _15; break; 31 | } 32 | if (!argCountMatch) throw "toFactory's argument count does not match amount provided by inject"; 33 | } 34 | 35 | if (toImplementation != null) count++; 36 | if (toInstanceOf != null) count++; 37 | if (count > 1) { 38 | throw 'Only one of following parameters can be specified: ' 39 | 'toValue, toFactory, toImplementation, toInstanceOf'; 40 | } 41 | 42 | if (inject.isNotEmpty && isNotSet(toFactory)) { 43 | throw "Received inject list but toFactory is not set."; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | typedef _0(); 50 | typedef _1(a1); 51 | typedef _2(a1, a2); 52 | typedef _3(a1, a2, a3); 53 | typedef _4(a1, a2, a3, a4); 54 | typedef _5(a1, a2, a3, a4, a5); 55 | typedef _6(a1, a2, a3, a4, a5, a6); 56 | typedef _7(a1, a2, a3, a4, a5, a6, a7); 57 | typedef _8(a1, a2, a3, a4, a5, a6, a7, a8); 58 | typedef _9(a1, a2, a3, a4, a5, a6, a7, a8, a9); 59 | typedef _10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); 60 | typedef _11(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); 61 | typedef _12(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); 62 | typedef _13(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); 63 | typedef _14(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); 64 | typedef _15(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); 65 | 66 | // Generation script in scripts/check_bind_args_script.dart 67 | -------------------------------------------------------------------------------- /benchmark/injector_benchmark_common.dart: -------------------------------------------------------------------------------- 1 | library di.injector_benchmark_common; 2 | 3 | import 'package:benchmark_harness/benchmark_harness.dart'; 4 | import 'package:di/di.dart'; 5 | 6 | int count = 0; 7 | 8 | class InjectorBenchmark extends BenchmarkBase { 9 | var module; 10 | var typeReflector; 11 | Key KEY_A; 12 | Key KEY_B; 13 | Key KEY_C; 14 | Key KEY_D; 15 | Key KEY_E; 16 | 17 | InjectorBenchmark(name, this.typeReflector) : super(name); 18 | 19 | void run() { 20 | Injector injector = new ModuleInjector([module]); 21 | injector.getByKey(KEY_A); 22 | injector.getByKey(KEY_B); 23 | 24 | var childInjector = new ModuleInjector([module], injector); 25 | childInjector.getByKey(KEY_A); 26 | childInjector.getByKey(KEY_B); 27 | } 28 | 29 | setup() { 30 | module = new Module.withReflector(typeReflector) 31 | ..bind(A) 32 | ..bind(B) 33 | ..bind(C) 34 | ..bind(C, withAnnotation: AnnOne, toImplementation: COne ) 35 | ..bind(D) 36 | ..bind(E) 37 | ..bind(E, withAnnotation: AnnTwo, toImplementation: ETwo ) 38 | ..bind(F) 39 | ..bind(G); 40 | 41 | KEY_A = new Key(A); 42 | KEY_B = new Key(B); 43 | KEY_C = new Key(C); 44 | KEY_D = new Key(D); 45 | KEY_E = new Key(E); 46 | } 47 | 48 | teardown() { 49 | print(count); 50 | } 51 | } 52 | 53 | class AnnOne { 54 | const AnnOne(); 55 | } 56 | 57 | class AnnTwo { 58 | const AnnTwo(); 59 | } 60 | 61 | class A { 62 | A(B b, C c) { 63 | count++; 64 | } 65 | } 66 | 67 | class B { 68 | B(D b, E c) { 69 | count++; 70 | } 71 | } 72 | 73 | class C { 74 | C() { 75 | count++; 76 | } 77 | } 78 | 79 | class COne { 80 | COne() { 81 | count++; 82 | } 83 | } 84 | 85 | class D { 86 | D() { 87 | count++; 88 | } 89 | } 90 | 91 | class E { 92 | E() { 93 | count++; 94 | } 95 | } 96 | 97 | class ETwo { 98 | ETwo() { 99 | count++; 100 | } 101 | } 102 | 103 | class F { 104 | F(@AnnOne() C c, D d) { 105 | count++; 106 | } 107 | } 108 | 109 | class G { 110 | G(@AnnTwo() E e) { 111 | count++; 112 | } 113 | } 114 | 115 | var typeFactories = { 116 | A: (a1, a2) => new A(a1, a2), 117 | B: (a1, a2) => new B(a1, a2), 118 | C: () => new C(), 119 | D: () => new D(), 120 | E: () => new E(), 121 | COne: () => new COne(), 122 | ETwo: () => new ETwo(), 123 | F: (a1, a2) => new F(a1, a2), 124 | G: (a1) => new G(a1), 125 | }; 126 | 127 | var paramKeys = { 128 | A: [new Key(B), new Key(C)], 129 | B: [new Key(D), new Key(E)], 130 | C: const [], 131 | D: const [], 132 | E: const [], 133 | COne: const [], 134 | ETwo: const [], 135 | F: [new Key(C, AnnOne), new Key(D)], 136 | G: [new Key(G, AnnTwo)], 137 | }; 138 | -------------------------------------------------------------------------------- /benchmark/static_injector_baseline_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:di/di.dart'; 2 | import 'package:benchmark_harness/benchmark_harness.dart'; 3 | import 'package:di/src/reflector_static.dart'; 4 | 5 | import 'injector_benchmark_common.dart'; 6 | import 'static_injector_benchmark.dart'; 7 | 8 | import 'dart:developer'; 9 | 10 | /** 11 | * This benchmark creates the same objects as the StaticInjectorBenchmark 12 | * without using DI, to serve as a baseline for comparison. 13 | */ 14 | class CreateObjectsOnly extends BenchmarkBase { 15 | CreateObjectsOnly(name) : super(name); 16 | 17 | void run() { 18 | var b1 = new B(new D(), new E()); 19 | var c1 = new C(); 20 | var d1 = new D(); 21 | var e1 = new E(); 22 | 23 | var a = new A(b1, c1); 24 | var b = new B(d1, e1); 25 | 26 | var c = new A(b1, c1); 27 | var d = new B(d1, e1); 28 | } 29 | 30 | void teardown() { 31 | print(count); 32 | } 33 | } 34 | 35 | class CreateSingleInjector extends InjectorBenchmark { 36 | 37 | CreateSingleInjector(name, injectorFactory) : super(name, injectorFactory); 38 | 39 | void run() { 40 | Injector injector = new ModuleInjector([module]); 41 | 42 | var b1 = new B(new D(), new E()); 43 | var c1 = new C(); 44 | var d1 = new D(); 45 | var e1 = new E(); 46 | 47 | var a = new A(b1, c1); 48 | var b = new B(d1, e1); 49 | 50 | var c = new A(b1, c1); 51 | var d = new B(d1, e1); 52 | } 53 | } 54 | 55 | class CreateInjectorAndChild extends InjectorBenchmark { 56 | 57 | CreateInjectorAndChild(name, injectorFactory) : super(name, injectorFactory); 58 | 59 | void run() { 60 | Injector injector = new ModuleInjector([module]); 61 | var childInjector = injector.createChild([module]); 62 | 63 | var b1 = new B(new D(), new E()); 64 | var c1 = new C(); 65 | var d1 = new D(); 66 | var e1 = new E(); 67 | 68 | var a = new A(b1, c1); 69 | var b = new B(d1, e1); 70 | 71 | var c = new A(b1, c1); 72 | var d = new B(d1, e1); 73 | } 74 | } 75 | 76 | class InjectByKey extends InjectorBenchmark { 77 | 78 | InjectByKey(name, injectorFactory) 79 | : super(name, injectorFactory); 80 | 81 | void run() { 82 | var injector = new ModuleInjector([module]); 83 | var childInjector = new ModuleInjector([module], injector); 84 | 85 | injector.getByKey(KEY_A); 86 | injector.getByKey(KEY_B); 87 | 88 | childInjector.getByKey(KEY_A); 89 | childInjector.getByKey(KEY_B); 90 | } 91 | } 92 | 93 | main() { 94 | var oldTypeFactories = { 95 | A: (f) => new A(f(B), f(C)), 96 | B: (f) => new B(f(D), f(E)), 97 | C: (f) => new C(), 98 | D: (f) => new D(), 99 | E: (f) => new E(), 100 | }; 101 | 102 | const PAD_LENGTH = 35; 103 | GeneratedTypeFactories generatedTypeFactories = 104 | new GeneratedTypeFactories(typeFactories, paramKeys); 105 | 106 | new CreateObjectsOnly("Create objects manually without DI".padRight(PAD_LENGTH)).report(); 107 | new CreateSingleInjector('.. and create an injector'.padRight(PAD_LENGTH), 108 | generatedTypeFactories 109 | ).report(); 110 | new CreateInjectorAndChild('.. and a child injector'.padRight(PAD_LENGTH), 111 | generatedTypeFactories 112 | ).report(); 113 | new InjectorBenchmark('DI using ModuleInjector'.padRight(PAD_LENGTH), 114 | generatedTypeFactories 115 | ).report(); 116 | new InjectByKey('.. and precompute keys'.padRight(PAD_LENGTH), 117 | generatedTypeFactories 118 | ).report(); 119 | } 120 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See http://pub.dartlang.org/doc/glossary.html#lockfile 3 | packages: 4 | analyzer: 5 | description: analyzer 6 | source: hosted 7 | version: "0.27.2" 8 | args: 9 | description: args 10 | source: hosted 11 | version: "0.13.4" 12 | async: 13 | description: async 14 | source: hosted 15 | version: "1.10.0" 16 | barback: 17 | description: barback 18 | source: hosted 19 | version: "0.15.2+7" 20 | benchmark_harness: 21 | description: benchmark_harness 22 | source: hosted 23 | version: "1.0.4" 24 | boolean_selector: 25 | description: boolean_selector 26 | source: hosted 27 | version: "1.0.1" 28 | browser: 29 | description: browser 30 | source: hosted 31 | version: "0.10.0+2" 32 | charcode: 33 | description: charcode 34 | source: hosted 35 | version: "1.1.0" 36 | cli_util: 37 | description: cli_util 38 | source: hosted 39 | version: "0.0.1+2" 40 | code_transformers: 41 | description: code_transformers 42 | source: hosted 43 | version: "0.3.1" 44 | collection: 45 | description: collection 46 | source: hosted 47 | version: "1.5.1" 48 | convert: 49 | description: convert 50 | source: hosted 51 | version: "1.1.0" 52 | crypto: 53 | description: crypto 54 | source: hosted 55 | version: "0.9.2+1" 56 | csslib: 57 | description: csslib 58 | source: hosted 59 | version: "0.13.1" 60 | glob: 61 | description: glob 62 | source: hosted 63 | version: "1.1.2" 64 | guinness2: 65 | description: guinness2 66 | source: hosted 67 | version: "0.0.5" 68 | html: 69 | description: html 70 | source: hosted 71 | version: "0.12.2+2" 72 | http_multi_server: 73 | description: http_multi_server 74 | source: hosted 75 | version: "2.0.1" 76 | http_parser: 77 | description: http_parser 78 | source: hosted 79 | version: "2.2.1" 80 | logging: 81 | description: logging 82 | source: hosted 83 | version: "0.11.3" 84 | matcher: 85 | description: matcher 86 | source: hosted 87 | version: "0.12.0+2" 88 | mime: 89 | description: mime 90 | source: hosted 91 | version: "0.9.3" 92 | package_config: 93 | description: package_config 94 | source: hosted 95 | version: "0.1.3" 96 | path: 97 | description: path 98 | source: hosted 99 | version: "1.3.9" 100 | plugin: 101 | description: plugin 102 | source: hosted 103 | version: "0.1.0" 104 | pool: 105 | description: pool 106 | source: hosted 107 | version: "1.2.3" 108 | pub_semver: 109 | description: pub_semver 110 | source: hosted 111 | version: "1.2.4" 112 | shelf: 113 | description: shelf 114 | source: hosted 115 | version: "0.6.5" 116 | shelf_static: 117 | description: shelf_static 118 | source: hosted 119 | version: "0.2.3+3" 120 | shelf_web_socket: 121 | description: shelf_web_socket 122 | source: hosted 123 | version: "0.2.0" 124 | source_map_stack_trace: 125 | description: source_map_stack_trace 126 | source: hosted 127 | version: "1.0.4" 128 | source_maps: 129 | description: source_maps 130 | source: hosted 131 | version: "0.10.1+1" 132 | source_span: 133 | description: source_span 134 | source: hosted 135 | version: "1.2.2" 136 | stack_trace: 137 | description: stack_trace 138 | source: hosted 139 | version: "1.6.5" 140 | stream_channel: 141 | description: stream_channel 142 | source: hosted 143 | version: "1.3.1" 144 | string_scanner: 145 | description: string_scanner 146 | source: hosted 147 | version: "0.1.4+1" 148 | test: 149 | description: test 150 | source: hosted 151 | version: "0.12.13+1" 152 | typed_data: 153 | description: typed_data 154 | source: hosted 155 | version: "1.1.2" 156 | utf: 157 | description: utf 158 | source: hosted 159 | version: "0.9.0+3" 160 | watcher: 161 | description: watcher 162 | source: hosted 163 | version: "0.9.7" 164 | web_socket_channel: 165 | description: web_socket_channel 166 | source: hosted 167 | version: "1.0.2" 168 | when: 169 | description: when 170 | source: hosted 171 | version: "0.2.0" 172 | which: 173 | description: which 174 | source: hosted 175 | version: "0.1.3" 176 | yaml: 177 | description: yaml 178 | source: hosted 179 | version: "2.1.8" 180 | sdk: ">=1.14.0 <1.18.0" 181 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See http://pub.dartlang.org/doc/glossary.html#lockfile 3 | packages: 4 | analyzer: 5 | description: analyzer 6 | source: hosted 7 | version: "0.27.2" 8 | args: 9 | description: args 10 | source: hosted 11 | version: "0.13.4" 12 | async: 13 | description: async 14 | source: hosted 15 | version: "1.10.0" 16 | barback: 17 | description: barback 18 | source: hosted 19 | version: "0.15.2+7" 20 | boolean_selector: 21 | description: boolean_selector 22 | source: hosted 23 | version: "1.0.1" 24 | browser: 25 | description: browser 26 | source: hosted 27 | version: "0.10.0+2" 28 | charcode: 29 | description: charcode 30 | source: hosted 31 | version: "1.1.0" 32 | cli_util: 33 | description: cli_util 34 | source: hosted 35 | version: "0.0.1+2" 36 | code_transformers: 37 | description: code_transformers 38 | source: hosted 39 | version: "0.3.1" 40 | collection: 41 | description: collection 42 | source: hosted 43 | version: "1.5.1" 44 | convert: 45 | description: convert 46 | source: hosted 47 | version: "1.1.0" 48 | crypto: 49 | description: crypto 50 | source: hosted 51 | version: "1.0.0" 52 | csslib: 53 | description: csslib 54 | source: hosted 55 | version: "0.13.1" 56 | dart_style: 57 | description: dart_style 58 | source: hosted 59 | version: "0.2.4" 60 | di: 61 | description: 62 | path: ".." 63 | relative: true 64 | source: path 65 | version: "3.3.10" 66 | glob: 67 | description: glob 68 | source: hosted 69 | version: "1.1.2" 70 | html: 71 | description: html 72 | source: hosted 73 | version: "0.12.2+2" 74 | http_multi_server: 75 | description: http_multi_server 76 | source: hosted 77 | version: "2.0.1" 78 | http_parser: 79 | description: http_parser 80 | source: hosted 81 | version: "2.2.1" 82 | initialize: 83 | description: initialize 84 | source: hosted 85 | version: "0.6.2+2" 86 | logging: 87 | description: logging 88 | source: hosted 89 | version: "0.11.3" 90 | matcher: 91 | description: matcher 92 | source: hosted 93 | version: "0.12.0+2" 94 | mime: 95 | description: mime 96 | source: hosted 97 | version: "0.9.3" 98 | package_config: 99 | description: package_config 100 | source: hosted 101 | version: "0.1.3" 102 | path: 103 | description: path 104 | source: hosted 105 | version: "1.3.9" 106 | plugin: 107 | description: plugin 108 | source: hosted 109 | version: "0.1.0" 110 | pool: 111 | description: pool 112 | source: hosted 113 | version: "1.2.3" 114 | pub_semver: 115 | description: pub_semver 116 | source: hosted 117 | version: "1.2.4" 118 | shelf: 119 | description: shelf 120 | source: hosted 121 | version: "0.6.5" 122 | shelf_static: 123 | description: shelf_static 124 | source: hosted 125 | version: "0.2.3+3" 126 | shelf_web_socket: 127 | description: shelf_web_socket 128 | source: hosted 129 | version: "0.2.0" 130 | source_map_stack_trace: 131 | description: source_map_stack_trace 132 | source: hosted 133 | version: "1.0.4" 134 | source_maps: 135 | description: source_maps 136 | source: hosted 137 | version: "0.10.1+1" 138 | source_span: 139 | description: source_span 140 | source: hosted 141 | version: "1.2.2" 142 | stack_trace: 143 | description: stack_trace 144 | source: hosted 145 | version: "1.6.5" 146 | stream_channel: 147 | description: stream_channel 148 | source: hosted 149 | version: "1.3.1" 150 | string_scanner: 151 | description: string_scanner 152 | source: hosted 153 | version: "0.1.4+1" 154 | test: 155 | description: test 156 | source: hosted 157 | version: "0.12.13+1" 158 | typed_data: 159 | description: typed_data 160 | source: hosted 161 | version: "1.1.2" 162 | utf: 163 | description: utf 164 | source: hosted 165 | version: "0.9.0+3" 166 | watcher: 167 | description: watcher 168 | source: hosted 169 | version: "0.9.7" 170 | web_components: 171 | description: web_components 172 | source: hosted 173 | version: "0.12.3" 174 | web_socket_channel: 175 | description: web_socket_channel 176 | source: hosted 177 | version: "1.0.2" 178 | when: 179 | description: when 180 | source: hosted 181 | version: "0.2.0" 182 | which: 183 | description: which 184 | source: hosted 185 | version: "0.1.3" 186 | yaml: 187 | description: yaml 188 | source: hosted 189 | version: "2.1.8" 190 | sdk: ">=1.14.0 <1.18.0" 191 | -------------------------------------------------------------------------------- /lib/transformer.dart: -------------------------------------------------------------------------------- 1 | /** 2 | * Static injection transformer which generates, for each injectable type: 3 | * 4 | * - typeFactory: which is a closure (a1, a2...) => new Type(a1, a2...) where 5 | * args are injected dependency instances, as specified by 6 | * - paramKeys: List corresponding to the dependency needing to be injected 7 | * in the positional arguments of the typeFactory. 8 | * 9 | * These two give an injector the information needed to construct an instance of a 10 | * type without using mirrors. They are stored as a Map 11 | * and outputted to a file [entry_point_name]_generated_type_factory_maps.dart. Multiple 12 | * entry points (main functions) is not supported. 13 | * 14 | * The import in main is also modified to use import di_static.dart instead 15 | * of di.dart, and imports module_dynamic.dart. 16 | * 17 | * All of the above is taken care of by the transformer. The user needs to call 18 | * setupModuleTypeReflector in main, before any modules are initialized. User must also 19 | * annotate types for the transformer to add them to the generated type factories file, 20 | * in addition to enabling the transformer in pubspec.yaml. 21 | * 22 | * Types which are considered injectable can be annotated in the following ways: 23 | * 24 | * * Use the @inject annotation on a class from `package:inject/inject.dart` 25 | * @inject 26 | * class Engine {} 27 | * 28 | * or on the constructor: 29 | * 30 | * class Engine { 31 | * @inject 32 | * Engine(); 33 | * } 34 | * 35 | * * Define a custom annotation in pubspec.yaml 36 | * 37 | * transformers: 38 | * - di: 39 | * injectable_annotations: 40 | * - library_name.ClassName 41 | * - library_name.constInstance 42 | * 43 | * Annotate constructors or classes with those annotations 44 | * 45 | * @ClassName() 46 | * class Engine {} 47 | * 48 | * class Engine { 49 | * @constInstance 50 | * Engine(); 51 | * } 52 | * 53 | * * Use package:di's Injectables 54 | * 55 | * @Injectables(const [Engine]) 56 | * library my_lib; 57 | * 58 | * import 'package:di/annotations.dart'; 59 | * 60 | * class Engine {} 61 | * 62 | * * Specify injected types via pubspec.yaml 63 | * 64 | * transformers: 65 | * - di: 66 | * injected_types: 67 | * - library_name.Engine 68 | */ 69 | library di.transformer; 70 | 71 | import 'package:barback/barback.dart'; 72 | import 'package:code_transformers/resolver.dart'; 73 | import 'transformer/injector_generator.dart'; 74 | import 'transformer/options.dart'; 75 | 76 | export 'transformer/options.dart'; 77 | export 'transformer/injector_generator.dart'; 78 | 79 | /** 80 | * The transformer, which will extract all classes being dependency injected 81 | * into a static injector. 82 | */ 83 | class DependencyInjectorTransformerGroup implements TransformerGroup { 84 | final Iterable phases; 85 | 86 | DependencyInjectorTransformerGroup(TransformOptions options) 87 | : phases = _createPhases(options); 88 | 89 | DependencyInjectorTransformerGroup.asPlugin(BarbackSettings settings) 90 | : this(_parseSettings(settings.configuration)); 91 | } 92 | 93 | TransformOptions _parseSettings(Map args) { 94 | var annotations = _readStringListValue(args, 'injectable_annotations'); 95 | var injectedTypes = _readStringListValue(args, 'injected_types'); 96 | 97 | var sdkDir = _readStringValue(args, 'dart_sdk', required: false); 98 | if (sdkDir == null) sdkDir = dartSdkDirectory; 99 | 100 | return new TransformOptions( 101 | injectableAnnotations: annotations, 102 | injectedTypes: injectedTypes, 103 | sdkDirectory: sdkDir); 104 | } 105 | 106 | _readStringValue(Map args, String name, {bool required: true}) { 107 | var value = args[name]; 108 | if (value == null) { 109 | if (required) { 110 | print('di transformer "$name" has no value.'); 111 | } 112 | return null; 113 | } 114 | if (value is! String) { 115 | print('di transformer "$name" value is not a string.'); 116 | return null; 117 | } 118 | return value; 119 | } 120 | 121 | List _readStringListValue(Map args, String name) { 122 | var value = args[name]; 123 | if (value == null) return []; 124 | List results = []; 125 | bool error; 126 | if (value is List) { 127 | results = value as List; 128 | error = value.any((e) => e is! String); 129 | } else if (value is String) { 130 | results = [value]; 131 | error = false; 132 | } else { 133 | error = true; 134 | } 135 | if (error) { 136 | print('Invalid value for "$name" in di transformer .'); 137 | } 138 | return results; 139 | } 140 | 141 | List> _createPhases(TransformOptions options) { 142 | var resolvers = new Resolvers(options.sdkDirectory); 143 | return [[new InjectorGenerator(options, resolvers)]]; 144 | } 145 | -------------------------------------------------------------------------------- /scripts/changelog.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // TODO(vojta): pre-commit hook for validating messages 4 | // TODO(vojta): report errors, currently Q silence everything which really sucks 5 | 6 | var child = require('child_process'); 7 | var fs = require('fs'); 8 | var util = require('util'); 9 | var q = require('qq'); 10 | 11 | var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s'; 12 | var GIT_TAG_CMD = 'git describe --tags --abbrev=0'; 13 | 14 | var HEADER_TPL = '\n# %s (%s)\n\n'; 15 | var LINK_ISSUE = '[#%s](https://github.com/angular/di.dart/issues/%s)'; 16 | var LINK_COMMIT = '[%s](https://github.com/angular/di.dart/commit/%s)'; 17 | 18 | var EMPTY_COMPONENT = '$$'; 19 | 20 | 21 | var warn = function() { 22 | console.log('WARNING:', util.format.apply(null, arguments)); 23 | }; 24 | 25 | 26 | var parseRawCommit = function(raw) { 27 | if (!raw) return null; 28 | 29 | var lines = raw.split('\n'); 30 | var msg = {}, match; 31 | 32 | msg.hash = lines.shift(); 33 | msg.subject = lines.shift(); 34 | msg.closes = []; 35 | msg.breaks = []; 36 | 37 | lines.forEach(function(line) { 38 | match = line.match(/(?:Closes|Fixes)\s#(\d+)/); 39 | if (match) msg.closes.push(parseInt(match[1])); 40 | }); 41 | 42 | match = raw.match(/BREAKING CHANGE:([\s\S]*)/); 43 | if (match) { 44 | msg.breaking = match[1]; 45 | } 46 | 47 | 48 | msg.body = lines.join('\n'); 49 | match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/); 50 | 51 | if (!match || !match[1] || !match[3]) { 52 | warn('Incorrect message: %s %s', msg.hash, msg.subject); 53 | return null; 54 | } 55 | 56 | msg.type = match[1]; 57 | msg.component = match[2]; 58 | msg.subject = match[3]; 59 | 60 | return msg; 61 | }; 62 | 63 | 64 | var linkToIssue = function(issue) { 65 | return util.format(LINK_ISSUE, issue, issue); 66 | }; 67 | 68 | 69 | var linkToCommit = function(hash) { 70 | return util.format(LINK_COMMIT, hash.substr(0, 8), hash); 71 | }; 72 | 73 | 74 | var currentDate = function() { 75 | var now = new Date(); 76 | var pad = function(i) { 77 | return ('0' + i).substr(-2); 78 | }; 79 | 80 | return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate())); 81 | }; 82 | 83 | 84 | var printSection = function(stream, title, section, printCommitLinks) { 85 | printCommitLinks = printCommitLinks === undefined ? true : printCommitLinks; 86 | var components = Object.getOwnPropertyNames(section).sort(); 87 | 88 | if (!components.length) return; 89 | 90 | stream.write(util.format('\n## %s\n\n', title)); 91 | 92 | components.forEach(function(name) { 93 | var prefix = '-'; 94 | var nested = section[name].length > 1; 95 | 96 | if (name !== EMPTY_COMPONENT) { 97 | if (nested) { 98 | stream.write(util.format('- **%s:**\n', name)); 99 | prefix = ' -'; 100 | } else { 101 | prefix = util.format('- **%s:**', name); 102 | } 103 | } 104 | 105 | section[name].forEach(function(commit) { 106 | if (printCommitLinks) { 107 | stream.write(util.format('%s %s\n (%s', prefix, commit.subject, linkToCommit(commit.hash))); 108 | if (commit.closes.length) { 109 | stream.write(',\n ' + commit.closes.map(linkToIssue).join(', ')); 110 | } 111 | stream.write(')\n'); 112 | } else { 113 | stream.write(util.format('%s %s', prefix, commit.subject)); 114 | } 115 | }); 116 | }); 117 | 118 | stream.write('\n'); 119 | }; 120 | 121 | 122 | var readGitLog = function(grep, from) { 123 | var deferred = q.defer(); 124 | 125 | // TODO(vojta): if it's slow, use spawn and stream it instead 126 | console.log(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from)); 127 | child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) { 128 | var commits = []; 129 | 130 | stdout.split('\n==END==\n').forEach(function(rawCommit) { 131 | var commit = parseRawCommit(rawCommit); 132 | if (commit) commits.push(commit); 133 | }); 134 | 135 | deferred.resolve(commits); 136 | }); 137 | 138 | return deferred.promise; 139 | }; 140 | 141 | 142 | var writeChangelog = function(stream, commits, version) { 143 | var sections = { 144 | fix: {}, 145 | feat: {}, 146 | perf: {}, 147 | breaks: {} 148 | }; 149 | 150 | sections.breaks[EMPTY_COMPONENT] = []; 151 | 152 | commits.forEach(function(commit) { 153 | var section = sections[commit.type]; 154 | var component = commit.component || EMPTY_COMPONENT; 155 | 156 | if (section) { 157 | section[component] = section[component] || []; 158 | section[component].push(commit); 159 | } 160 | 161 | if (commit.breaking) { 162 | sections.breaks[component] = sections.breaks[component] || []; 163 | sections.breaks[component].push({ 164 | subject: util.format("due to %s,\n %s", linkToCommit(commit.hash), commit.breaking), 165 | hash: commit.hash, 166 | closes: [] 167 | }); 168 | }; 169 | }); 170 | 171 | stream.write(util.format(HEADER_TPL, version, version, currentDate())); 172 | printSection(stream, 'Bug Fixes', sections.fix); 173 | printSection(stream, 'Features', sections.feat); 174 | printSection(stream, 'Performance Improvements', sections.perf); 175 | printSection(stream, 'Breaking Changes', sections.breaks, false); 176 | } 177 | 178 | 179 | var generate = function(tagRange, file) { 180 | console.log('Reading git log for', tagRange); 181 | readGitLog('^fix|^feat|^perf|BREAKING', tagRange).then(function(commits) { 182 | console.log('Parsed', commits.length, 'commits'); 183 | console.log('Generating changelog to', file || 'stdout', '(', tagRange, ')'); 184 | writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, tagRange); 185 | }); 186 | }; 187 | 188 | 189 | // publish for testing 190 | exports.parseRawCommit = parseRawCommit; 191 | 192 | // hacky start if not run by jasmine :-D 193 | if (process.argv.join('').indexOf('jasmine-node') === -1) { 194 | generate(process.argv[2], process.argv[3]); 195 | } 196 | -------------------------------------------------------------------------------- /lib/src/module.dart: -------------------------------------------------------------------------------- 1 | library di.module; 2 | 3 | import "../key.dart"; 4 | import "../check_bind_args.dart" show checkBindArgs; 5 | import "../annotations.dart"; 6 | import "reflector.dart"; 7 | import "reflector_dynamic.dart"; 8 | import "errors.dart" show PRIMITIVE_TYPES; 9 | 10 | DEFAULT_VALUE(_) => null; 11 | IDENTITY(p) => p; 12 | 13 | class Binding { 14 | Key key; 15 | List parameterKeys; 16 | Function factory; 17 | static bool printInjectWarning = true; 18 | 19 | bool _checkPrimitive(Key key) { 20 | if (PRIMITIVE_TYPES.contains(key)) { 21 | throw "Cannot bind primitive type '${key.type}'."; 22 | } 23 | return true; 24 | } 25 | 26 | void bind(k, TypeReflector reflector, {toValue: DEFAULT_VALUE, 27 | Function toFactory: DEFAULT_VALUE, Type toImplementation, 28 | List inject: const[], toInstanceOf}) { 29 | key = k; 30 | assert(_checkPrimitive(k)); 31 | if (inject.length == 1 && isNotSet(toFactory)) { 32 | if (printInjectWarning) { 33 | try { 34 | throw []; 35 | } catch (e, stackTrace) { 36 | print("bind(${k.type}): Inject list without toFactory is deprecated. " 37 | "Use `toInstanceOf: Type|Key` instead. " 38 | "Called from:\n$stackTrace"); 39 | } 40 | printInjectWarning = false; 41 | } 42 | toFactory = IDENTITY; 43 | } 44 | assert(checkBindArgs(toValue, toFactory, toImplementation, inject, toInstanceOf)); 45 | 46 | if (toInstanceOf != null) { 47 | toFactory = IDENTITY; 48 | inject = [toInstanceOf]; 49 | } 50 | if (isSet(toValue)) { 51 | factory = () => toValue; 52 | parameterKeys = const []; 53 | } else if (isSet(toFactory)) { 54 | factory = toFactory; 55 | parameterKeys = inject.map((t) { 56 | if (t is Key) return t; 57 | if (t is Type) return new Key(t); 58 | throw "inject must be Keys or Types. '$t' is not an instance of Key or Type."; 59 | }).toList(growable: false); 60 | } else { 61 | var implementationType = toImplementation == null ? key.type : toImplementation; 62 | parameterKeys = reflector.parameterKeysFor(implementationType); 63 | factory = reflector.factoryFor(implementationType); 64 | } 65 | } 66 | } 67 | 68 | bool isSet(val) => !identical(val, DEFAULT_VALUE); 69 | bool isNotSet(val) => !isSet(val); 70 | 71 | /** 72 | * Module contributes configuration information to an [Injector] by providing 73 | * a collection of type bindings that specify how each type is created. 74 | * 75 | * When an injector is created, it copies its configuration information from a 76 | * module. Defining additional type bindings after an injector is created has 77 | * no effect on that injector. 78 | */ 79 | class Module { 80 | static TypeReflector DEFAULT_REFLECTOR = getReflector(); 81 | 82 | /** 83 | * A [TypeReflector] for the module to look up constructors for types when 84 | * toFactory and toValue are not specified. This is done with mirroring or 85 | * pre-generated typeFactories. 86 | */ 87 | final TypeReflector reflector; 88 | 89 | Map bindings = new Map(); 90 | 91 | /// Class level annotations 92 | static var classAnnotations = [Injectable]; 93 | 94 | /// Lib level annotations (Injectable types are available in the `types` member field) 95 | static var libAnnotations = [Injectables]; 96 | 97 | static bool assertAnnotations = false; 98 | 99 | Module(): reflector = DEFAULT_REFLECTOR; 100 | 101 | /** 102 | * Use a custom reflector instead of the default. Useful for testing purposes. 103 | */ 104 | Module.withReflector(this.reflector); 105 | 106 | /** 107 | * Copies all bindings of [module] into this one. Overwriting when conflicts are found. 108 | */ 109 | void install(Module module) => bindings.addAll(module.bindings); 110 | 111 | /** 112 | * Registers a binding for a given [type]. 113 | * 114 | * The default behavior is to simply instantiate the type. 115 | * 116 | * The following parameters can be specified: 117 | * 118 | * * [toImplementation]: The given type will be instantiated using the [new] 119 | * operator and the resulting instance will be injected. 120 | * * [toFactory]: The result of the factory function called with the types of [inject] as 121 | * arguments is the value that will be injected. 122 | * * [toValue]: The given value will be injected. 123 | * * [toInstanceOf]: An instance of the given type will be fetched with DI. This is shorthand for 124 | * toFactory: (x) => x, inject: [X]. 125 | * * [withAnnotation]: Type decorated with additional annotation. 126 | * 127 | * Up to one (0 or 1) of the following parameters can be specified at the 128 | * same time: [toImplementation], [toFactory], [toValue], [toInstanceOf]. 129 | */ 130 | void bind(Type type, {dynamic toValue: DEFAULT_VALUE, 131 | Function toFactory: DEFAULT_VALUE, Type toImplementation, 132 | List inject: const [], toInstanceOf, Object withAnnotation}) { 133 | bindByKey(new Key(type, _toType(withAnnotation)), toValue: toValue, toInstanceOf: toInstanceOf, 134 | toFactory: toFactory, toImplementation: toImplementation, inject: inject); 135 | } 136 | 137 | /** 138 | * Same as [bind] except it takes [Key] instead of 139 | * [Type] [withAnnotation] combination. Faster. 140 | */ 141 | void bindByKey(Key key, {dynamic toValue: DEFAULT_VALUE, toInstanceOf, 142 | Function toFactory: DEFAULT_VALUE, List inject: const [], Type toImplementation}) { 143 | 144 | var binding = new Binding(); 145 | binding.bind(key, reflector, toValue: toValue, toFactory: toFactory, toInstanceOf: toInstanceOf, 146 | toImplementation: toImplementation, inject: inject); 147 | bindings[key] = binding; 148 | } 149 | 150 | static Type _toType(obj) { 151 | if (obj == null) return null; 152 | if (obj is Type) { 153 | print("DEPRECATED: Use `withAnnotation: const $obj()` instead of `withAnnotation: $obj`."); 154 | return obj; 155 | } 156 | return obj.runtimeType; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lib/src/injector.dart: -------------------------------------------------------------------------------- 1 | library di.injector; 2 | 3 | import '../key.dart'; 4 | import 'module.dart'; 5 | import 'errors.dart'; 6 | 7 | final Key _INJECTOR_KEY = new Key(Injector); 8 | class _Instance { 9 | final String name; 10 | 11 | static const _Instance EMPTY = const _Instance("EMPTY"); 12 | static const _Instance CREATING = const _Instance("CREATING"); 13 | 14 | const _Instance(this.name); 15 | String toString() => name; 16 | } 17 | 18 | abstract class Injector { 19 | Injector get parent; 20 | 21 | /** 22 | * Returns the instance associated with the given key (i.e. [type] and 23 | * [annotation]) according to the following rules. 24 | * 25 | * Let I be the nearest ancestor injector (possibly this one) 26 | * that has either a cached instance or a binding for [key] 27 | * 28 | * If there is no such I, then throw 29 | * [NoProviderError]. 30 | * 31 | * Once I is found, if I already created an instance for the key, 32 | * it is returned. Otherwise, the typeFactory of the binding is 33 | * used to create an instance, using I as to resolve the dependencies. 34 | */ 35 | dynamic get(Type type, [Type annotation]) => 36 | getByKey(new Key(type, annotation)); 37 | 38 | /** 39 | * Faster version of [get] by saving key creation time. Should be used instead if 40 | * a key instance is already available. 41 | */ 42 | dynamic getByKey(Key key); 43 | 44 | /** 45 | * Creates a child injector. 46 | * 47 | * [modules] overrides bindings of the parent. 48 | */ 49 | @deprecated 50 | Injector createChild(List modules); 51 | } 52 | 53 | 54 | /** 55 | * The RootInjector serves as an alternative to having a null parent for 56 | * injectors that have no parent. This allows us to bypass checking for a null 57 | * parent, and instead RootInjector will start the exception chain that reports 58 | * the resolution context tree when no providers are found. 59 | */ 60 | class RootInjector extends Injector { 61 | Injector get parent => null; 62 | List get _instances => null; 63 | dynamic getByKey(key, [depth]) => throw new NoProviderError(key); 64 | Injector createChild(m) => null; 65 | } 66 | 67 | class ModuleInjector extends Injector { 68 | 69 | static final rootInjector = new RootInjector(); 70 | final Injector parent; 71 | 72 | List _bindings; 73 | List _instances; 74 | 75 | ModuleInjector(List modules, [Injector parent]) 76 | : parent = parent == null ? rootInjector : parent, 77 | _bindings = new List(Key.numInstances + 1), // + 1 for injector itself 78 | _instances = new List.filled(Key.numInstances + 1, _Instance.EMPTY) { 79 | 80 | if (modules != null) { 81 | modules.forEach((module) { 82 | module.bindings.forEach((Key key, Binding binding) => 83 | _bindings[key.id] = binding); 84 | }); 85 | } 86 | _instances[_INJECTOR_KEY.id] = this; 87 | } 88 | 89 | Iterable _typesCache; 90 | 91 | Iterable get _types { 92 | if (_bindings == null) return []; 93 | 94 | if (_typesCache == null) { 95 | _typesCache = _bindings 96 | .where((p) => p != null) 97 | .map((p) => p.key.type); 98 | } 99 | return _typesCache; 100 | } 101 | 102 | Set get types { 103 | var types = new Set(); 104 | for (var node = this; node.parent != null; node = node.parent) { 105 | types.addAll(node._types); 106 | } 107 | types.add(Injector); 108 | return types; 109 | } 110 | 111 | getByKey(Key key) { 112 | var id = key.id; 113 | if (id >= _instances.length) { 114 | throw new NoProviderError(key); 115 | } 116 | var instance = _instances[id]; 117 | if (identical(instance, _Instance.CREATING)) { 118 | _instances[id] = _Instance.EMPTY; 119 | throw new CircularDependencyError(key); 120 | } 121 | if (!identical(instance, _Instance.EMPTY)) return instance; 122 | 123 | Binding binding = _bindings[id]; 124 | // When binding is null, recurse instead of iterate because it: 125 | // 1. tracks key history on the stack for error reporting 126 | // 2. allows different types of ancestor injectors with alternative implementations. 127 | // An alternative could be to recurse only when parent is not a ModuleInjector 128 | if (binding == null) return _instances[id] = parent.getByKey(key); 129 | 130 | _instances[id] = _Instance.CREATING; 131 | try { 132 | var paramKeys = binding.parameterKeys; 133 | var length = paramKeys.length; 134 | var factory = binding.factory; 135 | 136 | if (length > 15) { 137 | var params = new List(length); 138 | for (var i = 0; i < length; i++) { 139 | params[i] = getByKey(paramKeys[i]); 140 | } 141 | return _instances[id] = Function.apply(factory, params); 142 | } 143 | 144 | var a1 = length >= 1 ? getByKey(paramKeys[0]) : null; 145 | var a2 = length >= 2 ? getByKey(paramKeys[1]) : null; 146 | var a3 = length >= 3 ? getByKey(paramKeys[2]) : null; 147 | var a4 = length >= 4 ? getByKey(paramKeys[3]) : null; 148 | var a5 = length >= 5 ? getByKey(paramKeys[4]) : null; 149 | var a6 = length >= 6 ? getByKey(paramKeys[5]) : null; 150 | var a7 = length >= 7 ? getByKey(paramKeys[6]) : null; 151 | var a8 = length >= 8 ? getByKey(paramKeys[7]) : null; 152 | var a9 = length >= 9 ? getByKey(paramKeys[8]) : null; 153 | var a10 = length >= 10 ? getByKey(paramKeys[9]) : null; 154 | var a11 = length >= 11 ? getByKey(paramKeys[10]) : null; 155 | var a12 = length >= 12 ? getByKey(paramKeys[11]) : null; 156 | var a13 = length >= 13 ? getByKey(paramKeys[12]) : null; 157 | var a14 = length >= 14 ? getByKey(paramKeys[13]) : null; 158 | var a15 = length >= 15 ? getByKey(paramKeys[14]) : null; 159 | 160 | switch (length) { 161 | case 0: return _instances[id] = factory(); 162 | case 1: return _instances[id] = factory(a1); 163 | case 2: return _instances[id] = factory(a1, a2); 164 | case 3: return _instances[id] = factory(a1, a2, a3); 165 | case 4: return _instances[id] = factory(a1, a2, a3, a4); 166 | case 5: return _instances[id] = factory(a1, a2, a3, a4, a5); 167 | case 6: return _instances[id] = factory(a1, a2, a3, a4, a5, a6); 168 | case 7: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7); 169 | case 8: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8); 170 | case 9: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9); 171 | case 10: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); 172 | case 11: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); 173 | case 12: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); 174 | case 13: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); 175 | case 14: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); 176 | case 15: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); 177 | } 178 | } on ResolvingError catch (e) { 179 | _instances[id] = _Instance.EMPTY; 180 | e.appendKey(key); 181 | rethrow; // to preserve stack trace 182 | } catch (e) { 183 | _instances[id] = _Instance.EMPTY; 184 | rethrow; 185 | } 186 | } 187 | 188 | @deprecated 189 | Injector createChild(List modules) { 190 | return new ModuleInjector(modules,this); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /test_assets/d8.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Javascript preamble, that lets the output of dart2js run on V8's d8 shell. 6 | 7 | // Node wraps files and provides them with a different `this`. The global 8 | // `this` can be accessed through `global`. 9 | 10 | var self = this; 11 | if (typeof global != "undefined") self = global; // Node.js. 12 | 13 | (function(self) { 14 | // Using strict mode to avoid accidentally defining global variables. 15 | "use strict"; // Should be first statement of this function. 16 | 17 | // Location (Uri.base) 18 | 19 | var workingDirectory; 20 | // TODO(sgjesse): This does not work on Windows. 21 | if (typeof os == "object" && "system" in os) { 22 | // V8. 23 | workingDirectory = os.system("pwd"); 24 | var length = workingDirectory.length; 25 | if (workingDirectory[length - 1] == '\n') { 26 | workingDirectory = workingDirectory.substring(0, length - 1); 27 | } 28 | } else if (typeof process != "undefined" && 29 | typeof process.cwd == "function") { 30 | // Node.js. 31 | workingDirectory = process.cwd(); 32 | } 33 | self.location = { href: "file://" + workingDirectory + "/" }; 34 | 35 | // Event loop. 36 | 37 | // Task queue as cyclic list queue. 38 | var taskQueue = new Array(8); // Length is power of 2. 39 | var head = 0; 40 | var tail = 0; 41 | var mask = taskQueue.length - 1; 42 | function addTask(elem) { 43 | taskQueue[head] = elem; 44 | head = (head + 1) & mask; 45 | if (head == tail) _growTaskQueue(); 46 | } 47 | function removeTask() { 48 | if (head == tail) return; 49 | var result = taskQueue[tail]; 50 | taskQueue[tail] = undefined; 51 | tail = (tail + 1) & mask; 52 | return result; 53 | } 54 | function _growTaskQueue() { 55 | // head == tail. 56 | var length = taskQueue.length; 57 | var split = head; 58 | taskQueue.length = length * 2; 59 | if (split * 2 < length) { // split < length / 2 60 | for (var i = 0; i < split; i++) { 61 | taskQueue[length + i] = taskQueue[i]; 62 | taskQueue[i] = undefined; 63 | } 64 | head += length; 65 | } else { 66 | for (var i = split; i < length; i++) { 67 | taskQueue[length + i] = taskQueue[i]; 68 | taskQueue[i] = undefined; 69 | } 70 | tail += length; 71 | } 72 | mask = taskQueue.length - 1; 73 | } 74 | 75 | // Mapping from timer id to timer function. 76 | // The timer id is written on the function as .$timerId. 77 | // That field is cleared when the timer is cancelled, but it is not returned 78 | // from the queue until its time comes. 79 | var timerIds = {}; 80 | var timerIdCounter = 1; // Counter used to assing ids. 81 | 82 | // Zero-timer queue as simple array queue using push/shift. 83 | var zeroTimerQueue = []; 84 | 85 | function addTimer(f, ms) { 86 | var id = timerIdCounter++; 87 | f.$timerId = id; 88 | timerIds[id] = f; 89 | if (ms == 0) { 90 | zeroTimerQueue.push(f); 91 | } else { 92 | addDelayedTimer(f, ms); 93 | } 94 | return id; 95 | } 96 | 97 | function nextZeroTimer() { 98 | while (zeroTimerQueue.length > 0) { 99 | var action = zeroTimerQueue.shift(); 100 | if (action.$timerId !== undefined) return action; 101 | } 102 | } 103 | 104 | function nextEvent() { 105 | var action = removeTask(); 106 | if (action) { 107 | return action; 108 | } 109 | do { 110 | action = nextZeroTimer(); 111 | if (action) break; 112 | var nextList = nextDelayedTimerQueue(); 113 | if (!nextList) { 114 | return; 115 | } 116 | var newTime = nextList.shift(); 117 | advanceTimeTo(newTime); 118 | zeroTimerQueue = nextList; 119 | } while (true) 120 | var id = action.$timerId; 121 | clearTimerId(action, id); 122 | return action; 123 | } 124 | 125 | // Mocking time. 126 | var timeOffset = 0; 127 | var now = function() { 128 | // Install the mock Date object only once. 129 | // Following calls to "now" will just use the new (mocked) Date.now 130 | // method directly. 131 | installMockDate(); 132 | now = Date.now; 133 | return Date.now(); 134 | }; 135 | var originalDate = Date; 136 | var originalNow = originalDate.now; 137 | function advanceTimeTo(time) { 138 | timeOffset = time - originalNow(); 139 | } 140 | function installMockDate() { 141 | var NewDate = function Date(Y, M, D, h, m, s, ms) { 142 | if (this instanceof Date) { 143 | // Assume a construct call. 144 | switch (arguments.length) { 145 | case 0: return new originalDate(originalNow() + timeOffset); 146 | case 1: return new originalDate(Y); 147 | case 2: return new originalDate(Y, M); 148 | case 3: return new originalDate(Y, M, D); 149 | case 4: return new originalDate(Y, M, D, h); 150 | case 5: return new originalDate(Y, M, D, h, m); 151 | case 6: return new originalDate(Y, M, D, h, m, s); 152 | default: return new originalDate(Y, M, D, h, m, s, ms); 153 | } 154 | } 155 | return new originalDate(originalNow() + timeOffset).toString(); 156 | }; 157 | NewDate.UTC = originalDate.UTC; 158 | NewDate.parse = originalDate.parse; 159 | NewDate.now = function now() { return originalNow() + timeOffset; }; 160 | NewDate.prototype = originalDate.prototype; 161 | originalDate.prototype.constructor = NewDate; 162 | Date = NewDate; 163 | } 164 | 165 | // Heap priority queue with key index. 166 | // Each entry is list of [timeout, callback1 ... callbackn]. 167 | var timerHeap = []; 168 | var timerIndex = {}; 169 | function addDelayedTimer(f, ms) { 170 | var timeout = now() + ms; 171 | var timerList = timerIndex[timeout]; 172 | if (timerList == null) { 173 | timerList = [timeout, f]; 174 | timerIndex[timeout] = timerList; 175 | var index = timerHeap.length; 176 | timerHeap.length += 1; 177 | bubbleUp(index, timeout, timerList); 178 | } else { 179 | timerList.push(f); 180 | } 181 | } 182 | 183 | function nextDelayedTimerQueue() { 184 | if (timerHeap.length == 0) return null; 185 | var result = timerHeap[0]; 186 | var last = timerHeap.pop(); 187 | if (timerHeap.length > 0) { 188 | bubbleDown(0, last[0], last); 189 | } 190 | return result; 191 | } 192 | 193 | function bubbleUp(index, key, value) { 194 | while (index != 0) { 195 | var parentIndex = (index - 1) >> 1; 196 | var parent = timerHeap[parentIndex]; 197 | var parentKey = parent[0]; 198 | if (key > parentKey) break; 199 | timerHeap[index] = parent; 200 | index = parentIndex; 201 | } 202 | timerHeap[index] = value; 203 | } 204 | 205 | function bubbleDown(index, key, value) { 206 | while (true) { 207 | var leftChildIndex = index * 2 + 1; 208 | if (leftChildIndex >= timerHeap.length) break; 209 | var minChildIndex = leftChildIndex; 210 | var minChild = timerHeap[leftChildIndex]; 211 | var minChildKey = minChild[0]; 212 | var rightChildIndex = leftChildIndex + 1; 213 | if (rightChildIndex < timerHeap.length) { 214 | var rightChild = timerHeap[rightChildIndex]; 215 | var rightKey = rightChild[0]; 216 | if (rightKey < minChildKey) { 217 | minChildIndex = rightChildIndex; 218 | minChild = rightChild; 219 | minChildKey = rightKey; 220 | } 221 | } 222 | if (minChildKey > key) break; 223 | timerHeap[index] = minChild; 224 | index = minChildIndex; 225 | } 226 | timerHeap[index] = value; 227 | } 228 | 229 | function addInterval(f, ms) { 230 | var id = timerIdCounter++; 231 | function repeat() { 232 | // Reactivate with the same id. 233 | repeat.$timerId = id; 234 | timerIds[id] = repeat; 235 | addDelayedTimer(repeat, ms); 236 | f(); 237 | } 238 | repeat.$timerId = id; 239 | timerIds[id] = repeat; 240 | addDelayedTimer(repeat, ms); 241 | return id; 242 | } 243 | 244 | function cancelTimer(id) { 245 | var f = timerIds[id]; 246 | if (f == null) return; 247 | clearTimerId(f, id); 248 | } 249 | 250 | function clearTimerId(f, id) { 251 | f.$timerId = undefined; 252 | delete timerIds[id]; 253 | } 254 | 255 | function eventLoop(action) { 256 | while (action) { 257 | try { 258 | action(); 259 | } catch (e) { 260 | if (typeof onerror == "function") { 261 | onerror(e, null, -1); 262 | } else { 263 | throw e; 264 | } 265 | } 266 | action = nextEvent(); 267 | } 268 | } 269 | 270 | // Global properties. "self" refers to the global object, so adding a 271 | // property to "self" defines a global variable. 272 | self.dartMainRunner = function(main, args) { 273 | // Initialize. 274 | var action = function() { main(args); } 275 | eventLoop(action); 276 | }; 277 | self.setTimeout = addTimer; 278 | self.clearTimeout = cancelTimer; 279 | self.setInterval = addInterval; 280 | self.clearInterval = cancelTimer; 281 | self.scheduleImmediate = addTask; 282 | self.self = self; 283 | })(self); 284 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.3.7 2 | 3 | - Update dependencies. Support dart 1.14 sdk. 4 | 5 | # 3.3.6 6 | 7 | - Move to analyzer 0.27.1 8 | - Move to guinness2 9 | 10 | # 3.3.5+1 11 | 12 | ## Bug fix 13 | 14 | - Properly transform an `async` main method in an entry point. 15 | 16 | # 3.3.5 17 | 18 | - Widen dependency on package:analyzer. 19 | 20 | # 3.3.4 21 | 22 | - Widen dependency on package:analyzer. 23 | 24 | # 3.3.3 25 | 26 | ## Dependencies 27 | 28 | - Upgraded pubspec dependencies to use newer versions. 29 | 30 | ## Clean-up 31 | 32 | - Eliminate dead code and analyzer warnings. 33 | 34 | # 3.3.2 35 | 36 | ## Bug fix 37 | 38 | - A class would only be marked as injectable if the first annotation was one of the injectable 39 | annotations (GH-191). The DI now checks all the annotations and no more the first one only. 40 | 41 | # 3.3.1 42 | 43 | ## Bug fix 44 | 45 | - Fix a bug in FF caused by using a stack trace in a warning message. 46 | 47 | # 3.3.0 48 | 49 | This release makes DI 3.0 backward compatible with DI 2.0. 50 | 51 | - Annotation assertions are now disabled by default. To enable them set 52 | `Module.assertAnnotations` to `true`. 53 | - The `di.dart` library now exports Injectable and Injectables. 54 | - Core types can be listed in a library level `@Injectables` annotation. 55 | 56 | # 3.2.0 57 | 58 | ## New feature 59 | 60 | - It is now possible to disable annotation assertion by setting `Module.assertAnnotations` to 61 | `false` (the feature is still enabled by default) 62 | 63 | # 3.1.1 64 | 65 | ## Bug fix 66 | 67 | - Revert "perf(Binding): saves a call to `reflector.parameterKeysFor()`" which is buggy 68 | 69 | # 3.1.0 70 | 71 | ## New features 72 | 73 | - Classes could be annotated with a child of `Injectable` to mark them as injectable. Before only 74 | `Injectable` was allowed. 75 | 76 | - An annotation instance must be passed when binding with an annotation. 77 | Before: `module.bind(MyType, withAnnotation: MyAnnotation);` 78 | After: `module.bind(MyType, withAnnotation: const MyAnnotation());` 79 | 80 | The former syntax is deprecated and the support will be dropped in the next major release. 81 | 82 | # 3.0.0 83 | 84 | ## Breaking Change 85 | 86 | - When assert mode is enabled, the dynamic version of the DI will make sure that the classes are 87 | set up for injection when they are bound. By default classes should either be annotated with 88 | `@Injectable` or listed in a library level `@Injectables` annotation. The class level and library 89 | levels annotations could be changed in `Module.classAnnotations` and `Module.libAnnotations`. 90 | 91 | # 2.0.2 92 | 93 | ## Features 94 | 95 | - It is now possible to inject parameterized types by using `TypeLiteral`: 96 | 97 | ```dart 98 | import 'package:di/type_literal.dart'; 99 | 100 | class DependencyWithParameterizedMap { 101 | Map map; 102 | DependencyWithParameterizedMap(this.map); 103 | } 104 | 105 | var injector = new ModuleInjector([moduleFactory() 106 | ..bind(new TypeLiteral>().type, toValue: {1 : 'first', 2: 'second'}) 107 | ..bind(DependencyWithParameterizedMap) 108 | ]); 109 | ``` 110 | ## Breaking Change 111 | 112 | - **annotations:** Users must now explicitly import `di/annotations.dart` to use `@Injectable` 113 | 114 | ## Fixes 115 | 116 | - Supports newer versions of barback, code transformers, and analyzer 117 | 118 | # 2.0.1 119 | 120 | ## Bug Fixes 121 | 122 | - **bind:** It must accept a Key as well as a Type 123 | ([e01bcda6](https://github.com/angular/di.dart/commit/e01bcda6a0bb2522af03d34f08469969baed1d98), 124 | [#154](https://github.com/angular/di.dart/issues/154)) 125 | 126 | ## Features 127 | 128 | - **Binding:** Display call stack when usign deprecated bind() form 129 | ([f20b3ba7](https://github.com/angular/di.dart/commit/f20b3ba7ec45edc5b2f519974454da0e0e0f87df)) 130 | 131 | # 2.0.0 132 | 133 | ## Breaking Changes 134 | 135 | ### Calls to `StaticInjector` and `DynamicInjector` should be replaced with `ModuleInjector` 136 | - There are no longer `StaticInjectors` and `DynamicInjectors`. They have been replaced 137 | by a new `ModuleInjector` class that acts as both types of injectors. 138 | 139 | ### ModuleInjectors have no visibility 140 | - All bindings and instances of parent injectors are now visible in child injectors. 141 | - The optional argument `forceNewInstances` of `Injector.createChild` has been removed 142 | Instead, create a new module with bindings of the types that require new instances 143 | and pass that to the child injector, and the child injector will create new 144 | instances instead of returning the instance of the parent injector. 145 | 146 | ### Use `new ModuleInjector(modules, parent)` instead of `Injector.createChild(modules)` 147 | - The latter is still available but deprecated. 148 | - Injectors with no parent now have a dummy RootInjector instance as the parent 149 | Instead of checking “parent == null”, check for “parent == rootInjector”. 150 | 151 | ### Injectors no longer have a name field 152 | 153 | ### typeFactories have changed 154 | - Old type factories had the form `(injector) => new Instance(injector.get(dep1), … )` 155 | - New factories have the form: 156 | - `toFactory(a0, a1, …) => new Instance(a0, a1, …)` 157 | - When calling `Module.bind(toFactory: factory)`, there is an additional argument `inject` 158 | of a list of types or keys (preferred for performance) whose instances should be 159 | passed to the factory. The arguments passed to the factory function will be instances 160 | of the types in `inject`. 161 | 162 | Example: 163 | - Old code `module.bind(Car, toFactory: (i) => new Car(i.get(Engine)));` 164 | - New code 165 | - `module.bind(Car, toFactory: (engine) => new Car(engine), inject: [Engine]);` 166 | 167 | There is also some syntactic sugar for this special case. 168 | - Old code `module.bind(V8Engine, toFactory: (i) => i.get(Engine));` 169 | - New code `module.bind(V8Engine, toFactory: (e) => e, inject: [Engine]);` 170 | - With sugar `module.bind(V8Engine, toInstanceOf: Engine);` 171 | 172 | ### Modules have a `TypeReflector` instance attached 173 | - The `TypeReflector` is how the module will find the `toFactory` and `inject` 174 | arguments when not explicitly specified. This is either done with mirroring or code 175 | generation via transformers. Transformers will set the default to use code gen. 176 | For testing and other special purposes where a specific reflector is needed, use 177 | `new Module.withReflector(reflector)`. 178 | 179 | ### The transformer has been updated 180 | - Running the transformer will do the necessary code generation and edits to switch the 181 | default `TypeReflector` from mirroring to static factories. Enable transformer to use 182 | static factories, disable to use mirrors. More docs on the transformer can be found in 183 | `transformer.dart` 184 | 185 | ### Deprecated module methods removed 186 | - `.value`, `.type`, `.factory`, `.factoryByKey` are gone. Use `..bind`. 187 | 188 | ## Deprecations 189 | 190 | - `module.bind()` calls specifying the `inject` parameter but no `toFactory` have been deprecated 191 | and will be removed in v3. Use the `toInstanceOf` parameter instead. 192 | - The dynamic injector shim (dynamic_injector.dart) has been added to ensure backward compatibility 193 | with v1 and will be removed in v3. 194 | 195 | # 1.2.3 196 | 197 | ## Features 198 | 199 | - **module:** Expose DEFAULT_VALUE temporarily 200 | ([6f5d88a1](https://github.com/angular/di.dart/commit/6f5d88a16fbc7bc6658722326c1ef35d7848963e)) 201 | 202 | # 1.2.2 203 | 204 | Reverted changes that tickled a Dart bug (to be fixed in 1.6) 205 | 206 | 207 | # 1.2.1 208 | 209 | Added missing library declaration to injector. 210 | 211 | # 1.2.0 212 | 213 | ## Features 214 | 215 | - **module:** allow v2 style toFactory binding with inject 216 | ([1ef6ba71](https://github.com/angular/di.dart/commit/1ef6ba7103e2aa81d30ad037666c1723834af203)) 217 | 218 | 219 | ## Performance Improvements 220 | 221 | - **injector:** inlined getProviderWithInjector 222 | ([d2a38b54](https://github.com/angular/di.dart/commit/d2a38b542fd773ec937ca8413fa388e30a58daa3)) 223 | 224 | 225 | # 1.1.0 226 | 227 | ## Performance Improvements 228 | 229 | - **injector:** optimized module to injector instantiation 230 | ([62f22f15](https://github.com/angular/di.dart/commit/62f22f1566642cecc1b9f980475c94a7a88e9362)) 231 | 232 | # 1.0.0 233 | 234 | Starting with this release DI is following [semver](http://semver.org). 235 | 236 | ## Bug Fixes 237 | 238 | - **Key:** fixed bugs caused by hashCode collisions, and docs cleanup 239 | ([f673267d](https://github.com/angular/di.dart/commit/f673267dd2eb3a3058ec8657e4f034057e377c47), 240 | [#94](https://github.com/angular/di.dart/issues/94)) 241 | - **circular deps:** Improve error messages 242 | ([4ccdb1f0](https://github.com/angular/di.dart/commit/4ccdb1f0723c140bceb332a317884770c02ad4a8)) 243 | 244 | 245 | ## Performance Improvements 246 | 247 | - **Key:** don't use Map.putIfAbsent -- too slow 248 | ([0930b377](https://github.com/angular/di.dart/commit/0930b37747ebfd483db71a2b333601d77a437c10)) 249 | - **injector:** use separate structures to allow compiler optimizations 250 | ([f7b8af92](https://github.com/angular/di.dart/commit/f7b8af92aa903621b0dc4d1001d7329d77d698c0)) 251 | 252 | 253 | # 0.0.40 254 | 255 | ## Bug Fixes 256 | 257 | - **module:** correctly handle null value binding 258 | ([ada47b36](https://github.com/angular/di.dart/commit/ada47b36f88ed4f31204baa647f957fe2547c355), 259 | [#93](https://github.com/angular/di.dart/issues/93)) 260 | 261 | 262 | # 0.0.39 263 | 264 | ## Bug Fixes 265 | 266 | - **transformer:** Exception on parameterized types with implicit constructors 267 | ([ed0a2b02](https://github.com/angular/di.dart/commit/ed0a2b0222bb4bb3a4bd83173a3101d4196e6005)) 268 | 269 | 270 | ## Features 271 | 272 | - **module:** new binding syntax 273 | ([36357b5c](https://github.com/angular/di.dart/commit/36357b5c3ea0c9c81da404169166bf0aa0e957b5), 274 | [#90](https://github.com/angular/di.dart/issues/90)) 275 | 276 | 277 | ## Breaking Changes 278 | 279 | Module has a new API: 280 | ```dart 281 | new Module() 282 | ..bind(Foo, toValue: new Foo()) 283 | ..bind(Foo, toFactory: (i) => new Foo()) 284 | ..bind(Foo, toImplementation: FooImpl); 285 | ``` 286 | 287 | Old methods `type`, `value` and `factory` were deprecated and will be removed in the next release. 288 | 289 | # 0.0.38 290 | 291 | ## Fixes 292 | 293 | - **key:** made Key part of di.dart again 294 | ([fe390ddf](https://github.com/angular/di.dart/commit/fe390ddf25c230e2c98cff0628297e42584f6945)) 295 | 296 | 297 | # 0.0.37 298 | 299 | Combined with previous release (0.0.36) injector is on average 2x faster. 300 | 301 | Before: 302 | ``` 303 | VM: 304 | DynamicInjectorBenchmark(RunTime): 231.93784065870346 us. 305 | StaticInjectorBenchmark(RunTime): 107.05491917353602 us. 306 | 307 | dart2js: 308 | DynamicInjectorBenchmark(RunTime): 2175 us. 309 | StaticInjectorBenchmark(RunTime): 765.1109410864575 us. 310 | ``` 311 | 312 | After: 313 | 314 | ``` 315 | VM: 316 | DynamicInjectorBenchmark(RunTime): 156.3721657544957 us. 317 | StaticInjectorBenchmark(RunTime): 54.246114622040196 us. 318 | 319 | dart2js: 320 | DynamicInjectorBenchmark(RunTime): 1454.5454545454545 us. 321 | StaticInjectorBenchmark(RunTime): 291.9281856663261 us. 322 | ``` 323 | 324 | ## Bug Fixes 325 | 326 | - **warnings:** refactored injector to fix analyzer warnings 327 | ([7d374b19](https://github.com/angular/di.dart/commit/7d374b196e795d9799c95a4e63cf497267604de9)) 328 | 329 | ## Performance Improvements 330 | 331 | - **injector:** 332 | - Make resolving a linked-list stored with the frame 333 | ([c588e662](https://github.com/angular/di.dart/commit/c588e662ab0f33dc645c8e170492c0c25c1085a5)) 334 | - Do not closurize methods. 335 | ([5f47cbd0](https://github.com/angular/di.dart/commit/5f47cbd0dc28cb16e497baf5cfda3c6499f56eb5)) 336 | - Do not check the circular dependency until we are 30 deep. 337 | ([1dedf6e3](https://github.com/angular/di.dart/commit/1dedf6e38fec4c3fc882ef59b4c4bf439d19ce0a)) 338 | - Track resolving keys with the frame. 339 | ([17aeb4df](https://github.com/angular/di.dart/commit/17aeb4df59465c22cd73ae5c601cb8d0f872c57b)) 340 | - **resolvedTypes:** minor performance inmprovement in resolvedTypes 341 | ([ba16bde5](https://github.com/angular/di.dart/commit/ba16bde5084eb3a2291ca3d2fb38de06ac734b03)) 342 | 343 | 344 | # 0.0.36 345 | 346 | ## Performance Improvements 347 | 348 | - **injector:** 349 | - skip _checkKeyConditions in dart2js 350 | ([6763552a](https://github.com/angular/di.dart/commit/6763552adccdc41ef1043930ea50e0425509e6c5)) 351 | - +29%. Use an array for type lookup instead of a map. 352 | -------------------------------------------------------------------------------- /lib/src/reflector_dynamic.dart: -------------------------------------------------------------------------------- 1 | library di.reflector_dynamic; 2 | 3 | import '../di.dart'; 4 | import 'mirrors.dart'; 5 | 6 | TypeReflector getReflector() => new DynamicTypeFactories(); 7 | 8 | class DynamicTypeFactories extends TypeReflector { 9 | /// caches of results calculated from mirroring 10 | final List _factories = new List(); 11 | final List> _parameterKeys = new List>(); 12 | final List lists = new List.generate(26, (i) => new List(i)); 13 | 14 | Iterable _injectableAnnotations; 15 | Set _injectableTypes; 16 | 17 | /** 18 | * Asserts that the injected classes are set up for static injection. While this is not required 19 | * for dynamic injection, asserting could help you catch error before switching to the static 20 | * version of the DI. 21 | * 22 | * The injected classes should either be annotated with one of the `Module.classAnnotations` or 23 | * listed in the `types` field of a `Module.libAnnotations`. 24 | */ 25 | DynamicTypeFactories() { 26 | assert(() { 27 | var typesSymbol = new Symbol('types'); 28 | if (Module.classAnnotations != null) { 29 | _injectableAnnotations = Module.classAnnotations.toSet().map((Type t) => reflectClass(t)); 30 | } 31 | if (Module.libAnnotations != null) { 32 | _injectableTypes = new Set(); 33 | currentMirrorSystem().libraries.forEach((uri, LibraryMirror lm) { 34 | lm.metadata.forEach((InstanceMirror im) { 35 | var cm = im.type; 36 | if (cm.hasReflectedType && 37 | Module.libAnnotations.contains(cm.reflectedType) && 38 | cm.declarations.containsKey(typesSymbol)) { 39 | _injectableTypes.addAll(im.getField(typesSymbol).reflectee as Iterable); 40 | } 41 | }); 42 | }); 43 | } 44 | return true; 45 | }); 46 | } 47 | 48 | Function factoryFor(Type type) { 49 | var key = new Key(type); 50 | _resize(key.id); 51 | Function factory = _factories[key.id]; 52 | if (factory == null) { 53 | factory = _factories[key.id] = _generateFactory(type); 54 | } 55 | return factory; 56 | } 57 | 58 | List parameterKeysFor(Type type) { 59 | var key = new Key(type); 60 | _resize(key.id); 61 | List parameterKeys = _parameterKeys[key.id]; 62 | if (parameterKeys == null) { 63 | parameterKeys = _parameterKeys[key.id] = _generateParameterKeys(type); 64 | } 65 | return parameterKeys; 66 | } 67 | 68 | void _resize(int maxId) { 69 | if (_factories.length <= maxId) { 70 | _factories.length = maxId + 1; 71 | _parameterKeys.length = maxId + 1; 72 | } 73 | } 74 | 75 | Function _generateFactory(Type type) { 76 | ClassMirror classMirror = _reflectClass(type); 77 | 78 | assert(() { 79 | // TODO(vicb): Skip the assertion in JS where `ClassMirror.isSubtypeOf()` is not implemented 80 | if (!Module.assertAnnotations || 1.0 is int) return true; 81 | // Assert than: 82 | // - either the class is annotated with a subtype of any `_injectableAnnotations`, 83 | // - or the class type is an `_injectableTypes`. 84 | var hasClassAnnotation = classMirror.metadata.any((InstanceMirror im) { 85 | var cm = im.type; 86 | return _injectableAnnotations.any((ClassMirror c) => cm.isSubtypeOf(c)); 87 | }); 88 | if (!hasClassAnnotation && !_injectableTypes.contains(type)) { 89 | throw "The class '$type' should be annotated with one of " 90 | "'${_injectableAnnotations.map((cm) => cm.reflectedType).join(', ')}'"; 91 | } 92 | return true; 93 | }); 94 | 95 | MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; 96 | int length = ctor.parameters.length; 97 | Function create = classMirror.newInstance; 98 | Symbol name = ctor.constructorName; 99 | if (length > 25) throw "Too many arguments in $name constructor for dynamic DI to handle :("; 100 | List l = lists[length]; 101 | // script for this is in scripts/reflector_dynamic_script.dart 102 | switch (length) { 103 | case 0: 104 | return () { 105 | return create(name, l).reflectee; 106 | }; 107 | case 1: 108 | return (a1) { 109 | l[0]=a1; 110 | return create(name, l).reflectee; 111 | }; 112 | case 2: 113 | return (a1, a2) { 114 | l[0]=a1;l[1]=a2; 115 | return create(name, l).reflectee; 116 | }; 117 | case 3: 118 | return (a1, a2, a3) { 119 | l[0]=a1;l[1]=a2;l[2]=a3; 120 | return create(name, l).reflectee; 121 | }; 122 | case 4: 123 | return (a1, a2, a3, a4) { 124 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4; 125 | return create(name, l).reflectee; 126 | }; 127 | case 5: 128 | return (a1, a2, a3, a4, a5) { 129 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5; 130 | return create(name, l).reflectee; 131 | }; 132 | case 6: 133 | return (a1, a2, a3, a4, a5, a6) { 134 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6; 135 | return create(name, l).reflectee; 136 | }; 137 | case 7: 138 | return (a1, a2, a3, a4, a5, a6, a7) { 139 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7; 140 | return create(name, l).reflectee; 141 | }; 142 | case 8: 143 | return (a1, a2, a3, a4, a5, a6, a7, a8) { 144 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8; 145 | return create(name, l).reflectee; 146 | }; 147 | case 9: 148 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9) { 149 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9; 150 | return create(name, l).reflectee; 151 | }; 152 | case 10: 153 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { 154 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10; 155 | return create(name, l).reflectee; 156 | }; 157 | case 11: 158 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) { 159 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11; 160 | return create(name, l).reflectee; 161 | }; 162 | case 12: 163 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) { 164 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12; 165 | return create(name, l).reflectee; 166 | }; 167 | case 13: 168 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) { 169 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13; 170 | return create(name, l).reflectee; 171 | }; 172 | case 14: 173 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) { 174 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14; 175 | return create(name, l).reflectee; 176 | }; 177 | case 15: 178 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) { 179 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15; 180 | return create(name, l).reflectee; 181 | }; 182 | case 16: 183 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) { 184 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16; 185 | return create(name, l).reflectee; 186 | }; 187 | case 17: 188 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) { 189 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17; 190 | return create(name, l).reflectee; 191 | }; 192 | case 18: 193 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) { 194 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18; 195 | return create(name, l).reflectee; 196 | }; 197 | case 19: 198 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) { 199 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19; 200 | return create(name, l).reflectee; 201 | }; 202 | case 20: 203 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) { 204 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20; 205 | return create(name, l).reflectee; 206 | }; 207 | case 21: 208 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) { 209 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21; 210 | return create(name, l).reflectee; 211 | }; 212 | case 22: 213 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) { 214 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22; 215 | return create(name, l).reflectee; 216 | }; 217 | case 23: 218 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) { 219 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23; 220 | return create(name, l).reflectee; 221 | }; 222 | case 24: 223 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) { 224 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23;l[23]=a24; 225 | return create(name, l).reflectee; 226 | }; 227 | case 25: 228 | return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) { 229 | l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23;l[23]=a24;l[24]=a25; 230 | return create(name, l).reflectee; 231 | }; 232 | default: 233 | return () { 234 | return null; 235 | }; 236 | } 237 | } 238 | 239 | List _generateParameterKeys(Type type) { 240 | ClassMirror classMirror = _reflectClass(type); 241 | MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; 242 | 243 | return new List.generate(ctor.parameters.length, (int pos) { 244 | ParameterMirror p = ctor.parameters[pos]; 245 | if (p.type.qualifiedName == #dynamic) { 246 | var name = MirrorSystem.getName(p.simpleName); 247 | throw new DynamicReflectorError("Error getting params for '$type': " 248 | "The '$name' parameter must be typed"); 249 | } 250 | if (p.type is TypedefMirror) { 251 | throw new DynamicReflectorError("Typedef '${p.type}' in constructor " 252 | "'${classMirror.simpleName}' is not supported."); 253 | } 254 | if (p.metadata.length > 1) { 255 | throw new DynamicReflectorError( 256 | "Constructor '${classMirror.simpleName}' parameter $pos of type " 257 | "'${p.type}' can have only zero on one annotation, but it has " 258 | "'${p.metadata}'."); 259 | } 260 | ClassMirror pTypeMirror = (p.type as ClassMirror); 261 | var pType = pTypeMirror.reflectedType; 262 | var annotationType = p.metadata.isNotEmpty ? p.metadata.first.type.reflectedType : null; 263 | return new Key(pType, annotationType); 264 | }, growable:false); 265 | } 266 | 267 | ClassMirror _reflectClass(Type type) { 268 | ClassMirror classMirror = reflectType(type); 269 | if (classMirror is TypedefMirror) { 270 | throw new DynamicReflectorError("No implementation provided for " 271 | "${getSymbolName(classMirror.qualifiedName)} typedef!"); 272 | } 273 | 274 | MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; 275 | 276 | if (ctor == null) { 277 | throw new DynamicReflectorError("Unable to find default constructor for $type. " 278 | "Make sure class has a default constructor." + (1.0 is int ? 279 | "Make sure you have correctly configured @MirrorsUsed." : "")); 280 | } 281 | return classMirror; 282 | } 283 | 284 | void addAll(Map factories, Map> parameterKeys) => null; 285 | void add(Type type, Function factory, List parameterKeys) => null; 286 | } 287 | -------------------------------------------------------------------------------- /lib/transformer/injector_generator.dart: -------------------------------------------------------------------------------- 1 | library di.transformer.injector_generator; 2 | 3 | import 'dart:async'; 4 | import 'package:analyzer/src/generated/ast.dart'; 5 | import 'package:analyzer/src/generated/element.dart'; 6 | import 'package:barback/barback.dart'; 7 | import 'package:code_transformers/resolver.dart'; 8 | import 'package:path/path.dart' as path; 9 | import 'options.dart'; 10 | 11 | /** 12 | * Pub transformer which generates type factories for all injectable types 13 | * in the application. 14 | */ 15 | class InjectorGenerator extends Transformer with ResolverTransformer { 16 | final TransformOptions options; 17 | 18 | InjectorGenerator(this.options, Resolvers resolvers) { 19 | this.resolvers = resolvers; 20 | } 21 | 22 | Future shouldApplyResolver(Asset asset) => options.isDartEntry(asset); 23 | 24 | applyResolver(Transform transform, Resolver resolver) => 25 | new _Processor(transform, resolver, options).process(); 26 | } 27 | 28 | /** Class for processing a single apply.*/ 29 | class _Processor { 30 | 31 | /** Current transform. */ 32 | final Transform transform; 33 | 34 | final Resolver resolver; 35 | final TransformOptions options; 36 | 37 | /** Asset ID for the location of the generated file, for imports. */ 38 | AssetId _generatedAssetId; 39 | 40 | /** Resolved injectable annotations of the form `@injectable`. */ 41 | final List injectableMetaConsts = 42 | []; 43 | 44 | /** Resolved injectable annotations of the form `@Injectable()`. */ 45 | final List injectableTypes = []; 46 | 47 | /** Default list of injectable consts */ 48 | static const List defaultInjectableMetaConsts = const [ 49 | 'inject.inject' 50 | ]; 51 | 52 | _Processor(this.transform, this.resolver, this.options); 53 | 54 | TransformLogger get logger => transform.logger; 55 | 56 | process() { 57 | _resolveInjectableMetadata(); 58 | 59 | var id = transform.primaryInput.id; 60 | var outputFilename = '${path.url.basenameWithoutExtension(id.path)}' 61 | '_generated_type_factory_maps.dart'; 62 | var outputPath = path.url.join(path.url.dirname(id.path), outputFilename); 63 | _generatedAssetId = new AssetId(id.package, outputPath); 64 | 65 | var constructors = _gatherConstructors(); 66 | 67 | // generates typeFactory file 68 | var injectLibContents = _generateInjectLibrary(constructors); 69 | transform.addOutput( 70 | new Asset.fromString(_generatedAssetId, injectLibContents)); 71 | 72 | _editMain(); 73 | } 74 | 75 | /** Resolves the classes for the injectable annotations in the current AST. */ 76 | void _resolveInjectableMetadata() { 77 | for (var constName in defaultInjectableMetaConsts) { 78 | var variable = resolver.getLibraryVariable(constName); 79 | if (variable != null) { 80 | injectableMetaConsts.add(variable); 81 | } 82 | } 83 | 84 | // Resolve the user-specified annotations 85 | // These may be either type names (constructors) or consts. 86 | for (var metaName in options.injectableAnnotations) { 87 | var variable = resolver.getLibraryVariable(metaName); 88 | if (variable != null) { 89 | injectableMetaConsts.add(variable); 90 | continue; 91 | } 92 | var cls = resolver.getType(metaName); 93 | if (cls != null && cls.unnamedConstructor != null) { 94 | injectableTypes.add(cls.type); 95 | continue; 96 | } 97 | if (!DEFAULT_INJECTABLE_ANNOTATIONS.contains(metaName)) { 98 | logger.warning('Unable to resolve injectable annotation $metaName'); 99 | } 100 | } 101 | } 102 | 103 | /** Finds all annotated constructors or annotated classes in the program. */ 104 | Iterable _gatherConstructors() { 105 | var constructors = resolver.libraries 106 | .expand((lib) => lib.units) 107 | .expand((compilationUnit) => compilationUnit.types) 108 | .map(_findInjectedConstructor) 109 | .where((ctor) => ctor != null).toList(); 110 | 111 | constructors.addAll(_gatherInjectablesContents()); 112 | constructors.addAll(_gatherManuallyInjected()); 113 | 114 | return constructors.toSet(); 115 | } 116 | 117 | /** 118 | * Get the constructors for all elements in the library @Injectables 119 | * statements. These are used to mark types as injectable which would 120 | * otherwise not be injected. 121 | * 122 | * Syntax is: 123 | * 124 | * @Injectables(const[ElementName]) 125 | * library my.library; 126 | */ 127 | List _gatherInjectablesContents() { 128 | var injectablesClass = resolver.getType('di.annotations.Injectables'); 129 | if (injectablesClass == null) return const []; 130 | var injectablesCtor = injectablesClass.unnamedConstructor; 131 | 132 | var ctors = new List(); 133 | 134 | for (var lib in resolver.libraries) { 135 | var annotationIdx = 0; 136 | for (var annotation in lib.metadata) { 137 | if (annotation.element == injectablesCtor) { 138 | var libDirective = lib.definingCompilationUnit.computeNode().directives 139 | .where((d) => d is LibraryDirective).single; 140 | var annotationDirective = libDirective.metadata[annotationIdx]; 141 | ListLiteral listLiteral = annotationDirective.arguments.arguments.first; 142 | 143 | for (var expr in listLiteral.elements) { 144 | var element = (expr as SimpleIdentifier).bestElement; 145 | if (element == null || element is! ClassElement) { 146 | _warn('Unable to resolve class $expr', element); 147 | continue; 148 | } 149 | var ctor = _findInjectedConstructor(element, true); 150 | if (ctor != null) { 151 | ctors.add(ctor); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | return ctors; 158 | } 159 | 160 | /** 161 | * Finds all types which were manually specified as being injected in 162 | * the options file. 163 | */ 164 | List _gatherManuallyInjected() { 165 | var ctors = new List(); 166 | for (var injectedName in options.injectedTypes) { 167 | var injectedClass = resolver.getType(injectedName); 168 | if (injectedClass == null) { 169 | logger.warning('Unable to resolve injected type name $injectedName'); 170 | continue; 171 | } 172 | var ctor = _findInjectedConstructor(injectedClass, true); 173 | if (ctor != null) { 174 | ctors.add(ctor); 175 | } 176 | } 177 | return ctors; 178 | } 179 | 180 | /** 181 | * Checks if the element is annotated with one of the known injectable 182 | * annotations. 183 | */ 184 | bool _isElementAnnotated(Element e) { 185 | for (var meta in e.metadata) { 186 | if (meta.element is PropertyAccessorElement && 187 | injectableMetaConsts.contains((meta.element as PropertyAccessorElement).variable)) { 188 | return true; 189 | } else if (meta.element is ConstructorElement) { 190 | DartType metaType = (meta.element.enclosingElement as TypeDefiningElement).type; 191 | if (injectableTypes.any((DartType t) => metaType.isSubtypeOf(t))) return true; 192 | } 193 | } 194 | return false; 195 | } 196 | 197 | /** 198 | * Find an 'injected' constructor for the given class. 199 | * If [noAnnotation] is true then this will assume that the class is marked 200 | * for injection and will use the default constructor. 201 | */ 202 | ConstructorElement _findInjectedConstructor(ClassElement cls, 203 | [bool noAnnotation = false]) { 204 | var classInjectedConstructors = []; 205 | if (_isElementAnnotated(cls) || noAnnotation) { 206 | var defaultConstructor = cls.unnamedConstructor; 207 | if (defaultConstructor == null) { 208 | _warn('${cls.name} cannot be injected because ' 209 | 'it does not have a default constructor.', cls); 210 | } else { 211 | classInjectedConstructors.add(defaultConstructor); 212 | } 213 | } 214 | 215 | classInjectedConstructors.addAll( 216 | cls.constructors.where(_isElementAnnotated)); 217 | 218 | if (classInjectedConstructors.isEmpty) return null; 219 | if (classInjectedConstructors.length > 1) { 220 | _warn('${cls.name} has more than one constructor annotated for ' 221 | 'injection.', cls); 222 | return null; 223 | } 224 | 225 | var ctor = classInjectedConstructors.single; 226 | if (!_validateConstructor(ctor)) return null; 227 | 228 | return ctor; 229 | } 230 | 231 | /** 232 | * Validates that the constructor is injectable and emits warnings for any 233 | * errors. 234 | */ 235 | bool _validateConstructor(ConstructorElement ctor) { 236 | var cls = ctor.enclosingElement; 237 | if (cls.isAbstract && !ctor.isFactory) { 238 | _warn('${cls.name} cannot be injected because ' 239 | 'it is an abstract type with no factory constructor.', cls); 240 | return false; 241 | } 242 | if (cls.isPrivate) { 243 | _warn('${cls.name} cannot be injected because it is a private type.', 244 | cls); 245 | return false; 246 | } 247 | if (resolver.getImportUri(cls.library, from: _generatedAssetId) == null) { 248 | _warn('${cls.name} cannot be injected because ' 249 | 'the containing file cannot be imported.', cls); 250 | return false; 251 | } 252 | if (!cls.typeParameters.isEmpty) { 253 | _warn('${cls.name} is a parameterized type.', cls); 254 | // Only warn. 255 | } 256 | if (ctor.name != '') { 257 | _warn('Named constructors cannot be injected.', ctor); 258 | return false; 259 | } 260 | for (var param in ctor.parameters) { 261 | var type = param.type; 262 | if (type.isDynamic) { 263 | _warn('${cls.name} cannot be injected because parameter type ' 264 | '${param.name} cannot be resolved.', ctor); 265 | return false; 266 | } 267 | } 268 | return true; 269 | } 270 | 271 | /** 272 | * Creates a library file for the specified constructors. 273 | */ 274 | String _generateInjectLibrary(Iterable constructors) { 275 | var prefixes = {}; 276 | var usedLibs = new Set(); 277 | String resolveClassName(ClassElement type, [List typeArgs]) { 278 | var library = type.library; 279 | usedLibs.add(library); 280 | 281 | var prefix = prefixes[library]; 282 | if (prefix == null) { 283 | prefix = prefixes[library] = 284 | library.isDartCore ? '' : 'import_${prefixes.length}'; 285 | } 286 | if (prefix.isNotEmpty) { 287 | prefix = '$prefix.'; 288 | } 289 | if (typeArgs == null || typeArgs.isEmpty || !typeArgs.any((arg) => arg is! DynamicTypeImpl)) { 290 | return '$prefix${type.name}'; 291 | } 292 | return 'new TypeLiteral<$prefix${type.name}<${typeArgs.join(', ')}>>().type'; 293 | } 294 | 295 | var keysBuffer = new StringBuffer(); 296 | var factoriesBuffer = new StringBuffer(); 297 | var paramsBuffer = new StringBuffer(); 298 | var addedKeys = new Set(); 299 | for (var ctor in constructors) { 300 | ClassElement type = ctor.enclosingElement; 301 | String typeName = resolveClassName(type); 302 | 303 | String args = new List.generate(ctor.parameters.length, (i) => 'a${i+1}').join(', '); 304 | factoriesBuffer.write(' $typeName: ($args) => new $typeName($args),\n'); 305 | 306 | paramsBuffer.write(' $typeName: '); 307 | paramsBuffer.write(ctor.parameters.isEmpty ? 'const[' : '['); 308 | var params = ctor.parameters.map((param) { 309 | String typeName = resolveClassName(param.type.element, (param.type as ParameterizedType).typeArguments); 310 | Iterable annotations = []; 311 | if (param.metadata.isNotEmpty) { 312 | annotations = param.metadata.map( 313 | (item) => (item.element as FunctionTypedElement).returnType.element as ClassElement); 314 | } 315 | 316 | var keyName = '_KEY_${param.type.name}' + (annotations.isNotEmpty ? '_${annotations.first.name}' : ''); 317 | var typeArgs = (param.type as ParameterizedType).typeArguments; 318 | if (typeArgs != null && typeArgs.isNotEmpty && typeArgs.any((arg) => arg is! DynamicTypeImpl)) { 319 | typeArgs.forEach((arg) => keyName = ('${keyName}_${arg.name}')); 320 | } 321 | if (addedKeys.add(keyName)) { 322 | keysBuffer.writeln('final Key $keyName = new Key($typeName' + 323 | (annotations.isNotEmpty ? ', ${resolveClassName(annotations.first)});' : ');')); 324 | } 325 | return keyName; 326 | }); 327 | paramsBuffer.write('${params.join(', ')}],\n'); 328 | } 329 | 330 | var outputBuffer = new StringBuffer(); 331 | 332 | _writeHeader(transform.primaryInput.id, outputBuffer); 333 | usedLibs.forEach((lib) { 334 | if (lib.isDartCore) return; 335 | var uri = resolver.getImportUri(lib, from: _generatedAssetId); 336 | outputBuffer.write('import \'$uri\' as ${prefixes[lib]};\n'); 337 | }); 338 | outputBuffer..write('\n') 339 | ..write(keysBuffer) 340 | ..write('final Map typeFactories = {\n') 341 | ..write(factoriesBuffer) 342 | ..write('};\nfinal Map> parameterKeys = {\n') 343 | ..write(paramsBuffer) 344 | ..write('};\n') 345 | ..write('setStaticReflectorAsDefault() => ' 346 | 'Module.DEFAULT_REFLECTOR = ' 347 | 'new GeneratedTypeFactories(typeFactories, parameterKeys);\n'); 348 | 349 | return outputBuffer.toString(); 350 | } 351 | 352 | void _warn(String msg, Element element) { 353 | logger.warning(msg, asset: resolver.getSourceAssetId(element), 354 | span: resolver.getSourceSpan(element)); 355 | } 356 | 357 | /// import generated_type_factory_maps and call initialize 358 | void _editMain() { 359 | AssetId id = transform.primaryInput.id; 360 | var lib = resolver.getLibrary(id); 361 | var unit = lib.definingCompilationUnit.computeNode(); 362 | var transaction = resolver.createTextEditTransaction(lib); 363 | 364 | var imports = unit.directives.where((d) => d is ImportDirective); 365 | transaction.edit(imports.last.end, imports.last.end, '\nimport ' 366 | "'${path.url.basenameWithoutExtension(id.path)}" 367 | "_generated_type_factory_maps.dart' show setStaticReflectorAsDefault;"); 368 | 369 | FunctionExpression main = (unit.declarations.where((d) => 370 | d is FunctionDeclaration && d.name.toString() == 'main') 371 | .first as FunctionDeclaration).functionExpression; 372 | var body = main.body; 373 | if (body is BlockFunctionBody) { 374 | var location = body.block.leftBracket.end; 375 | transaction.edit(location, location, '\n setStaticReflectorAsDefault();'); 376 | } else if (body is ExpressionFunctionBody) { 377 | transaction.edit(body.beginToken.offset, body.endToken.end, 378 | "{\n setStaticReflectorAsDefault();\n" 379 | " return ${body.expression};\n}"); 380 | } // EmptyFunctionBody can only appear as abstract methods and constructors. 381 | 382 | var printer = transaction.commit(); 383 | var url = id.path.startsWith('lib/') ? 384 | 'package:${id.package}/${id.path.substring(4)}' : id.path; 385 | printer.build(url); 386 | transform.addOutput(new Asset.fromString(id, printer.text)); 387 | } 388 | } 389 | 390 | void _writeHeader(AssetId id, StringSink sink) { 391 | var libName = path.withoutExtension(id.path).replaceAll('/', '.'); 392 | libName = libName.replaceAll('-', '_'); 393 | sink.write(''' 394 | library ${id.package}.$libName.generated_type_factory_maps; 395 | 396 | import 'package:di/di.dart'; 397 | import 'package:di/src/reflector_static.dart'; 398 | import 'package:di/type_literal.dart'; 399 | 400 | '''); 401 | } 402 | -------------------------------------------------------------------------------- /lib/generator.dart: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates Factory & paramKeys maps into a file by crawling source files and 3 | * finding all constructors that are annotated for injection. Does the same thing as 4 | * transformer.dart for when transformers are not available, except without modifications 5 | * to the main function and DI. As such, the user needs to import the generated file, and run 6 | * `Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys)` 7 | * imported, before any modules are initialized. Import 'di_static.dart' instead of 'di.dart' 8 | * to avoid mirrors, and import 'reflector_static.dart' for GeneratedTypeFactories. 9 | * See transformer.dart for more info. 10 | */ 11 | library di.generator; 12 | 13 | import 'package:analyzer/src/generated/java_io.dart'; 14 | import 'package:analyzer/src/generated/source_io.dart'; 15 | import 'package:analyzer/src/generated/ast.dart'; 16 | import 'package:analyzer/src/generated/sdk.dart' show DartSdk; 17 | import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; 18 | import 'package:analyzer/src/generated/element.dart'; 19 | import 'package:analyzer/src/generated/engine.dart'; 20 | import 'package:path/path.dart' as path; 21 | 22 | import 'dart:io'; 23 | 24 | const String PACKAGE_PREFIX = 'package:'; 25 | const String DART_PACKAGE_PREFIX = 'dart:'; 26 | const List _DEFAULT_INJECTABLE_ANNOTATIONS = const ['di.annotations.Injectable']; 27 | 28 | main(List args) { 29 | if (args.length < 4) { 30 | print('Usage: generator path_to_sdk file_to_resolve annotations output [package_roots+]'); 31 | exit(0); 32 | } 33 | 34 | var pathToSdk = args[0]; 35 | var entryPoint = args[1]; 36 | var classAnnotations = args[2].split(',')..addAll(_DEFAULT_INJECTABLE_ANNOTATIONS); 37 | var output = args[3]; 38 | var packageRoots = (args.length < 5) 39 | ? [path.fromUri(Platform.packageRoot ?? '')] 40 | : args.sublist(4); 41 | 42 | print('pathToSdk: $pathToSdk'); 43 | print('entryPoint: $entryPoint'); 44 | print('classAnnotations: ${classAnnotations.join(', ')}'); 45 | print('output: $output'); 46 | print('packageRoots: $packageRoots'); 47 | 48 | var code = generateCode(entryPoint, classAnnotations, pathToSdk, packageRoots, output); 49 | code.forEach((chunk, code) { 50 | String fileName = output; 51 | if (chunk.library != null) { 52 | var lastDot = fileName.lastIndexOf('.'); 53 | fileName = fileName.substring(0, lastDot) + '-' + chunk.library.name + fileName.substring(lastDot); 54 | } 55 | new File(fileName).writeAsStringSync(code); 56 | }); 57 | } 58 | 59 | Map generateCode(String entryPoint, List classAnnotations, 60 | String pathToSdk, List packageRoots, String outputFilename) { 61 | 62 | var c = new SourceCrawler(pathToSdk, packageRoots); 63 | List imports = []; 64 | Map> typeFactoryTypes = >{}; 65 | Map typeToImport = new Map(); 66 | Map typeMappings = {}; 67 | 68 | c.crawl(entryPoint, (CompilationUnitElement compilationUnit, SourceFile source) { 69 | new CompilationUnitVisitor(c.context, source, classAnnotations, imports, typeToImport, 70 | typeFactoryTypes, typeMappings, outputFilename).visit(compilationUnit, source); 71 | }); 72 | return printLibraryCode(typeToImport, imports, typeFactoryTypes, typeMappings); 73 | } 74 | 75 | Map printLibraryCode(Map typeToImport, List imports, 76 | Map> typeFactoryTypes, 77 | Map typeMapping) { 78 | 79 | Map factories = {}; 80 | Map keys = {}; 81 | Map paramLists = {}; 82 | Map result = {}; 83 | 84 | typeFactoryTypes.forEach((Chunk chunk, List classes) { 85 | List requiredImports = []; 86 | String resolveClassIdentifier(InterfaceType type, [List typeArgs, 87 | bool usedToGenerateConstructor = false]) { 88 | final TYPE_LITERAL = 'TypeLiteral'; 89 | if (type.element.library.isDartCore) { 90 | 91 | //workaround for https://github.com/angular/di.dart/issues/183 92 | final canUseTypeLiteral = typeMapping.containsKey(TYPE_LITERAL) && 93 | !usedToGenerateConstructor; 94 | 95 | if (type.typeParameters.isNotEmpty && canUseTypeLiteral) { 96 | return 'new ${resolveClassIdentifier(typeMapping[TYPE_LITERAL])}<$type>().type'; 97 | } 98 | return type.name; 99 | } 100 | String import = typeToImport[getCanonicalName(type)]; 101 | if (!requiredImports.contains(import)) { 102 | requiredImports.add(import); 103 | } 104 | String prefix = _calculateImportPrefix(import, imports); 105 | return '$prefix.${type.name}'; 106 | } 107 | 108 | factories[chunk] = new StringBuffer(); 109 | keys[chunk] = new StringBuffer(); 110 | paramLists[chunk] = new StringBuffer(); 111 | 112 | process_classes(classes, keys[chunk], factories[chunk], paramLists[chunk], 113 | resolveClassIdentifier); 114 | 115 | StringBuffer code = new StringBuffer(); 116 | String libSuffix = chunk.library == null ? '' : '.${chunk.library.name}'; 117 | code.write('library di.generated.type_factories$libSuffix;\n'); 118 | requiredImports.forEach((import) { 119 | String prefix = _calculateImportPrefix(import, imports); 120 | code.write ('import "$import" as $prefix;\n'); 121 | }); 122 | code..write('import "package:di/key.dart" show Key;\n') 123 | ..write(keys[chunk]) 124 | ..write('Map typeFactories = {\n${factories[chunk]}};\n') 125 | ..write('Map> parameterKeys = {\n${paramLists[chunk]}};\n') 126 | ..write('main() {}\n'); 127 | result[chunk] = code.toString(); 128 | }); 129 | 130 | return result; 131 | } 132 | 133 | typedef String IdentifierResolver(InterfaceType type, [List typeArgs, bool usedToGenerateConstructor]); 134 | /** 135 | * Takes classes and writes to StringBuffers the corresponding keys, factories, 136 | * and paramLists needed for static injection. 137 | * 138 | * resolveClassIdentifier is a function passed in to be called to resolve imports 139 | */ 140 | void process_classes(Iterable classes, StringBuffer keys, 141 | StringBuffer factories, StringBuffer paramLists, 142 | IdentifierResolver resolveClassIdentifier) { 143 | 144 | Map toBeAdded = new Map(); 145 | Set addedKeys = new Set(); 146 | 147 | classes.forEach((ClassElement clazz) { 148 | StringBuffer factory = new StringBuffer(); 149 | StringBuffer paramList = new StringBuffer(); 150 | List factoryKeys = new List(); 151 | bool skip = false; 152 | var className = getUniqueName(clazz.type); 153 | var classType = resolveClassIdentifier(clazz.type, [], true); 154 | if (addedKeys.add(className)) { 155 | toBeAdded[className] = 156 | 'final Key _KEY_$className = new Key($classType);\n'; 157 | } 158 | factoryKeys.add(className); 159 | 160 | ConstructorElement constr = clazz.constructors.firstWhere((c) => c.name.isEmpty, 161 | orElse: () { 162 | throw 'Unable to find default constructor for $clazz in ${clazz.source}'; 163 | }); 164 | 165 | var args = new List.generate(constr.parameters.length, (i) => 'a$i').join(', '); 166 | factory.write('$classType: ($args) => new $classType($args),\n'); 167 | 168 | paramList.write('$classType: '); 169 | if (constr.parameters.isEmpty){ 170 | paramList.write('const ['); 171 | } else { 172 | paramList.write('['); 173 | paramList.write(constr.parameters.map((param) { 174 | if (param.type.element is! ClassElement) { 175 | throw 'Unable to resolve type for constructor parameter ' 176 | '"${param.name}" for type "$clazz" in ${clazz.source}'; 177 | } 178 | var annotations = new Iterable.empty(); 179 | if (param.metadata.isNotEmpty) { 180 | annotations = param.metadata.map((item) => (item.element as FunctionTypedElement).returnType.name); 181 | } 182 | String keyName = annotations.isNotEmpty ? 183 | '${param.type.name}_${annotations.first}' : 184 | param.type.name; 185 | (param.type as ParameterizedType).typeArguments.forEach((arg) => keyName = '${keyName}_${arg.name}'); 186 | String output = '_KEY_${keyName}'; 187 | if (addedKeys.add(keyName)){ 188 | var annotationParam = ""; 189 | if (param.metadata.isNotEmpty) { 190 | var p = resolveClassIdentifier((param.metadata.first.element as FunctionTypedElement).returnType); 191 | annotationParam = ", $p"; 192 | } 193 | var clsId = resolveClassIdentifier(param.type, (param.type as ParameterizedType).typeArguments); 194 | toBeAdded[keyName]='final Key _KEY_${keyName} = new Key($clsId$annotationParam);\n'; 195 | } 196 | return output; 197 | }).join(', ')); 198 | } 199 | paramList.write('],\n'); 200 | if (!skip) { 201 | factoryKeys.forEach((key) { 202 | var keyString = toBeAdded.remove(key); 203 | if (keyString != null) { 204 | keys.write(keyString); 205 | } 206 | }); 207 | factories.write(factory); 208 | paramLists.write(paramList); 209 | } 210 | }); 211 | keys.writeAll(toBeAdded.values); 212 | toBeAdded.clear(); 213 | } 214 | 215 | String _calculateImportPrefix(String import, List imports) => 216 | 'import_${imports.indexOf(import)}'; 217 | 218 | class CompilationUnitVisitor { 219 | List imports; 220 | Map typeToImport; 221 | Map> typeFactoryTypes; 222 | List classAnnotations; 223 | SourceFile source; 224 | AnalysisContext context; 225 | String outputFilename; 226 | Map typeMappings; 227 | 228 | CompilationUnitVisitor(this.context, this.source, 229 | this.classAnnotations, this.imports, this.typeToImport, 230 | this.typeFactoryTypes, this.typeMappings, this.outputFilename); 231 | 232 | visit(CompilationUnitElement compilationUnit, SourceFile source) { 233 | if (typeFactoryTypes[source.chunk] == null) { 234 | typeFactoryTypes[source.chunk] = []; 235 | } 236 | visitLibrary(compilationUnit.enclosingElement, source); 237 | 238 | List types = []; 239 | types.addAll(compilationUnit.types); 240 | 241 | for (CompilationUnitElement part in compilationUnit.enclosingElement.parts) { 242 | types.addAll(part.types); 243 | } 244 | 245 | types.forEach((clazz) => visitClassElement(clazz, source)); 246 | } 247 | 248 | visitLibrary(LibraryElement libElement, SourceFile source) { 249 | CompilationUnit resolvedUnit = context.resolveCompilationUnit(libElement.source, libElement); 250 | 251 | resolvedUnit.directives.forEach((Directive directive) { 252 | if (directive is LibraryDirective) { 253 | LibraryDirective library = directive; 254 | int annotationIdx = 0; 255 | library.metadata.forEach((Annotation ann) { 256 | if (ann.element is ConstructorElement && 257 | getQualifiedName( 258 | (ann.element as ConstructorElement).enclosingElement.type) == 259 | 'di.annotations.Injectables') { 260 | ListLiteral listLiteral = library.metadata[annotationIdx].arguments.arguments.first; 261 | for (Expression expr in listLiteral.elements) { 262 | Element element = (expr as SimpleIdentifier).bestElement; 263 | if (element == null || element is! ClassElement) { 264 | throw 'Unable to resolve type "$expr" from @Injectables ' 265 | 'in ${library.element.source}'; 266 | } 267 | if (!typeFactoryTypes[source.chunk].contains(element)) { 268 | typeFactoryTypes[source.chunk].add(element as ClassElement); 269 | } 270 | } 271 | } 272 | annotationIdx++; 273 | }); 274 | } 275 | }); 276 | } 277 | 278 | visitClassElement(ClassElement classElement, SourceFile source) { 279 | if (classElement.isPrivate) return; // ignore private classes. 280 | var importUri = source.entryPointImport; 281 | if (Uri.parse(importUri).scheme == '') { 282 | importUri = path.relative(importUri, from: path.dirname(outputFilename)); 283 | } 284 | typeToImport[getCanonicalName(classElement.type)] = importUri; 285 | if (classElement.name == 'TypeLiteral' && classElement.library.name == 'di.type_literal') { 286 | typeMappings.putIfAbsent(classElement.name, () => classElement.type); 287 | } 288 | if (!imports.contains(importUri)) { 289 | imports.add(importUri); 290 | } 291 | for (ElementAnnotation ann in classElement.metadata) { 292 | if (ann.element is ConstructorElement) { 293 | ClassElement classEl = ann.element.enclosingElement; 294 | List types = [classEl.type]..addAll(classEl.allSupertypes); 295 | if (types.any((DartType t) => classAnnotations.contains(getQualifiedName(t)))) { 296 | // The class is injectable when the metadata is either: 297 | // - an instance of any `classAnnotations` types, 298 | // - a subtype of any `classAnnotations` types. 299 | if (typeFactoryTypes[source.chunk] == null) { 300 | typeFactoryTypes[source.chunk] = []; 301 | } 302 | if (!typeFactoryTypes[source.chunk].contains(classElement)) { 303 | typeFactoryTypes[source.chunk].add(classElement); 304 | } 305 | } 306 | } 307 | } 308 | } 309 | } 310 | 311 | String getQualifiedName(InterfaceType type) { 312 | var lib = type.element.library.displayName; 313 | var name = type.name; 314 | return lib == null ? name : '$lib.$name'; 315 | } 316 | 317 | String getUniqueName(InterfaceType type) { 318 | var lib = type.element.library.displayName; 319 | var name = type.name; 320 | String qualName = lib == null ? name : '$lib.$name'; 321 | return qualName.replaceAll('.', '_'); 322 | } 323 | 324 | String getCanonicalName(InterfaceType type) { 325 | var source = type.element.source.toString(); 326 | var name = type.name; 327 | return '$source:$name'; 328 | } 329 | 330 | typedef CompilationUnitCrawler(CompilationUnitElement compilationUnit, SourceFile source); 331 | 332 | class SourceCrawler { 333 | final List packageRoots; 334 | final String sdkPath; 335 | AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); 336 | 337 | SourceCrawler(this.sdkPath, this.packageRoots); 338 | 339 | void crawl(String entryPoint, CompilationUnitCrawler _visitor, 340 | {bool preserveComments : false}) { 341 | JavaSystemIO.setProperty("com.google.dart.sdk", sdkPath); 342 | DartSdk sdk = DirectoryBasedDartSdk.defaultSdk; 343 | 344 | AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); 345 | contextOptions.cacheSize = 256; 346 | contextOptions.preserveComments = preserveComments; 347 | contextOptions.analyzeFunctionBodies = false; 348 | context.analysisOptions = contextOptions; 349 | sdk.context.analysisOptions = contextOptions; 350 | 351 | var packageUriResolver = new PackageUriResolver(packageRoots.map( 352 | (pr) => new JavaFile.fromUri(new Uri.file(pr))).toList()); 353 | context.sourceFactory = new SourceFactory([ 354 | new DartUriResolver(sdk), 355 | new FileUriResolver(), 356 | packageUriResolver 357 | ]); 358 | 359 | var entryPointFile; 360 | var entryPointImport; 361 | if (entryPoint.startsWith(PACKAGE_PREFIX)) { 362 | entryPointFile = new JavaFile(packageUriResolver 363 | .resolveAbsolute(Uri.parse(entryPoint)).toString()); 364 | entryPointImport = entryPoint; 365 | } else { 366 | entryPointFile = new JavaFile(entryPoint); 367 | entryPointImport = entryPointFile.getAbsolutePath(); 368 | } 369 | 370 | Source source = new FileBasedSource(entryPointFile); 371 | ChangeSet changeSet = new ChangeSet(); 372 | changeSet.addedSource(source); 373 | context.applyChanges(changeSet); 374 | LibraryElement rootLib = context.computeLibraryElement(source); 375 | CompilationUnit resolvedUnit = 376 | context.resolveCompilationUnit(source, rootLib); 377 | 378 | var sourceFile = new SourceFile( 379 | entryPointFile.getAbsolutePath(), 380 | entryPointImport, 381 | resolvedUnit, 382 | resolvedUnit.element, 383 | new Chunk()); // root chunk 384 | List toVisit = [sourceFile]; 385 | List deferred = [sourceFile]; 386 | 387 | while (deferred.isNotEmpty) { 388 | toVisit.add(deferred.removeAt(0)); 389 | while (toVisit.isNotEmpty) { 390 | SourceFile currentFile = toVisit.removeAt(0); 391 | currentFile.chunk.addVisited(currentFile); 392 | _visitor(currentFile.compilationUnitElement, currentFile); 393 | var visitor = new CrawlerVisitor(currentFile, context); 394 | visitor.accept(currentFile.compilationUnit); 395 | visitor.toVisit.forEach((SourceFile todo) { 396 | if (!toVisit.contains(todo) && !currentFile.chunk.alreadyVisited(todo)) { 397 | toVisit.add(todo); 398 | } 399 | }); 400 | visitor.deferred.forEach((SourceFile todo) { 401 | if (!deferred.contains(todo) && !currentFile.chunk.alreadyVisited(todo)) { 402 | deferred.add(todo); 403 | } 404 | }); 405 | } 406 | } 407 | } 408 | } 409 | 410 | class CrawlerVisitor { 411 | List toVisit = []; 412 | List deferred = []; 413 | SourceFile currentFile; 414 | AnalysisContext context; 415 | String currentDir; 416 | 417 | CrawlerVisitor(this.currentFile, this.context); 418 | 419 | void accept(CompilationUnit cu) { 420 | cu.directives.forEach((Directive directive) { 421 | if (directive.element == null) return; // unresolvable, ignore 422 | if (directive is ImportDirective) { 423 | ImportElement import = directive.element; 424 | visitImportElement( 425 | new Library(import, import.uri, cu, import.importedLibrary.name), 426 | import.importedLibrary.source); 427 | } 428 | if (directive is ExportDirective) { 429 | ExportElement export = directive.element; 430 | visitImportElement( 431 | new Library(export, export.uri, cu, export.exportedLibrary.name), 432 | export.exportedLibrary.source); 433 | } 434 | }); 435 | } 436 | 437 | void visitImportElement(Library library, Source source) { 438 | String uri = library.uri; 439 | if (uri == null) return; // dart:core 440 | 441 | String systemImport; 442 | bool isSystem = false; 443 | if (uri.startsWith(DART_PACKAGE_PREFIX)) { 444 | isSystem = true; 445 | systemImport = uri; 446 | } else if (currentFile.entryPointImport.startsWith(DART_PACKAGE_PREFIX)) { 447 | isSystem = true; 448 | systemImport = currentFile.entryPointImport; 449 | } 450 | // check if it's some internal hidden library 451 | if (isSystem && systemImport.substring(DART_PACKAGE_PREFIX.length).startsWith('_')) { 452 | return; 453 | } 454 | 455 | var nextCompilationUnit = context 456 | .resolveCompilationUnit(source, context.computeLibraryElement(source)); 457 | 458 | SourceFile sourceFile; 459 | if (uri.startsWith(PACKAGE_PREFIX)) { 460 | sourceFile = new SourceFile(source.toString(), uri, 461 | nextCompilationUnit, nextCompilationUnit.element, currentFile.chunk); 462 | } else { // relative import. 463 | var newImport; 464 | if (isSystem) { 465 | newImport = systemImport; // original uri 466 | } else { 467 | // relative import 468 | String import = currentFile.entryPointImport; 469 | import = import.replaceAll('\\', '/'); // if at all needed, on Windows 470 | import = import.substring(0, import.lastIndexOf('/')); 471 | var currentDir = new File(currentFile.canonicalPath).parent.path; 472 | currentDir = currentDir.replaceAll('\\', '/'); // if at all needed, on Windows 473 | if (uri.startsWith('../')) { 474 | while (uri.startsWith('../')) { 475 | uri = uri.substring('../'.length); 476 | import = import.substring(0, import.lastIndexOf('/')); 477 | currentDir = currentDir.substring(0, currentDir.lastIndexOf('/')); 478 | } 479 | } 480 | newImport = '$import/$uri'; 481 | } 482 | sourceFile = new SourceFile( 483 | source.toString(), newImport, 484 | nextCompilationUnit, nextCompilationUnit.element, currentFile.chunk); 485 | } 486 | if (library.isDeferred || isDeferredImport(library)) { 487 | var childChunk = currentFile.chunk.createChild(library); 488 | deferred.add(new SourceFile(source.toString(), sourceFile.entryPointImport, 489 | nextCompilationUnit, nextCompilationUnit.element, childChunk)); 490 | } else { 491 | toVisit.add(sourceFile); 492 | } 493 | } 494 | } 495 | 496 | // TODO(cbracken) eliminate once support for old-style is removed in SDK 1.7.0 497 | bool isDeferredImport(Library library) { 498 | var isDeferred = false; 499 | library.element.metadata.forEach((ElementAnnotation annotation) { 500 | if (annotation.element is PropertyAccessorElement) { 501 | PropertyAccessorElement pa = annotation.element; 502 | library.compilationUnit.declarations.forEach((CompilationUnitMember member) { 503 | if (member is TopLevelVariableDeclaration && member.variables.isConst) { 504 | TopLevelVariableDeclaration topLevel = member; 505 | topLevel.variables.variables.forEach((VariableDeclaration varDecl) { 506 | if (varDecl.initializer is InstanceCreationExpression && 507 | (varDecl.initializer as InstanceCreationExpression).isConst && 508 | (varDecl.initializer as InstanceCreationExpression).staticElement is ConstructorElement && 509 | varDecl.name.name == pa.name) { 510 | ConstructorElement constr = (varDecl.initializer as InstanceCreationExpression).staticElement; 511 | if (constr.enclosingElement.library.name == 'dart.async' && 512 | constr.enclosingElement.type.name == 'DeferredLibrary') { 513 | isDeferred = true; 514 | } 515 | } 516 | }); 517 | } 518 | }); 519 | } 520 | }); 521 | return isDeferred; 522 | } 523 | 524 | class Library { 525 | final Element element; 526 | final String uri; 527 | final CompilationUnit compilationUnit; 528 | final String name; 529 | 530 | Library(this.element, this.uri, this.compilationUnit, this.name); 531 | 532 | bool get isDeferred => element is ImportElement && (element as ImportElement).isDeferred; 533 | 534 | String toString() => 'Library[$name]'; 535 | } 536 | 537 | class Chunk { 538 | final Chunk parent; 539 | Library library; 540 | List _visited = []; 541 | 542 | addVisited(SourceFile file) { 543 | _visited.add(file); 544 | } 545 | 546 | bool alreadyVisited(SourceFile file) { 547 | var cursor = this; 548 | while (cursor != null) { 549 | if (cursor._visited.contains(file)) { 550 | return true; 551 | } 552 | cursor = cursor.parent; 553 | } 554 | return false; 555 | } 556 | 557 | Chunk([this.parent, this.library]); 558 | 559 | Chunk createChild(Library library) => new Chunk(this, library); 560 | 561 | String toString() => 'Chunk[$library]'; 562 | } 563 | 564 | class SourceFile { 565 | String canonicalPath; 566 | String entryPointImport; 567 | CompilationUnit compilationUnit; 568 | CompilationUnitElement compilationUnitElement; 569 | Chunk chunk; 570 | 571 | SourceFile(this.canonicalPath, this.entryPointImport, this.compilationUnit, 572 | this.compilationUnitElement, this.chunk); 573 | 574 | operator ==(o) { 575 | if (o is String) return o == canonicalPath; 576 | if (o is! SourceFile) return false; 577 | return o.canonicalPath == canonicalPath; 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /test/transformer_test.dart: -------------------------------------------------------------------------------- 1 | library di.test.injector_generator_spec; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:barback/barback.dart'; 6 | import 'package:code_transformers/resolver.dart'; 7 | import 'package:code_transformers/tests.dart' as tests; 8 | import 'package:di/transformer.dart'; 9 | import 'package:di/transformer/options.dart'; 10 | import 'package:di/transformer/injector_generator.dart'; 11 | 12 | import 'package:guinness2/guinness2.dart'; 13 | 14 | main() { 15 | describe('transformer', () { 16 | var injectableAnnotations = []; 17 | var options = new TransformOptions( 18 | injectableAnnotations: injectableAnnotations, 19 | sdkDirectory: dartSdkDirectory); 20 | 21 | var resolvers = new Resolvers(dartSdkDirectory); 22 | 23 | var phases = [ 24 | [new InjectorGenerator(options, resolvers)] 25 | ]; 26 | 27 | it('transforms imports', () { 28 | return generates(phases, 29 | inputs: { 30 | 'a|web/main.dart': 'import "package:a/car.dart"; main() {}', 31 | 'a|lib/car.dart': ''' 32 | import 'package:inject/inject.dart'; 33 | import 'package:a/engine.dart'; 34 | import 'package:a/seat.dart' as seat; 35 | 36 | class Car { 37 | @inject 38 | Car(Engine e, seat.Seat s) {} 39 | } 40 | ''', 41 | 'a|lib/engine.dart': CLASS_ENGINE, 42 | 'a|lib/seat.dart': ''' 43 | import 'package:inject/inject.dart'; 44 | class Seat { 45 | @inject 46 | Seat(); 47 | } 48 | ''', 49 | }, 50 | imports: [ 51 | "import 'package:a/car.dart' as import_0;", 52 | "import 'package:a/engine.dart' as import_1;", 53 | "import 'package:a/seat.dart' as import_2;", 54 | ], 55 | keys: [ 56 | 'Engine = new Key(import_1.Engine);', 57 | 'Seat = new Key(import_2.Seat);', 58 | ], 59 | factories: [ 60 | 'import_0.Car: (a1, a2) => new import_0.Car(a1, a2),', 61 | 'import_1.Engine: () => new import_1.Engine(),', 62 | 'import_2.Seat: () => new import_2.Seat(),', 63 | ], 64 | paramKeys: [ 65 | 'import_0.Car: [_KEY_Engine, _KEY_Seat],', 66 | 'import_1.Engine: const[],', 67 | 'import_2.Seat: const[],' 68 | ]); 69 | }); 70 | 71 | it('should inject parameterized parameters into object', () { 72 | return generates(phases, 73 | inputs: { 74 | 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', 75 | 'di|lib/type_literal.dart': PACKAGE_TYPE_LITERAL, 76 | 'a|lib/a.dart': ''' 77 | import 'package:inject/inject.dart'; 78 | class Parameterized { 79 | List nums; 80 | 81 | @inject 82 | Parameterized(this.nums); 83 | } 84 | ''' 85 | }, 86 | imports: [ 87 | "import 'package:a/a.dart' as import_0;", 88 | ], 89 | keys: [ 90 | 'List_num = new Key(new TypeLiteral>().type);', 91 | ], 92 | factories: [ 93 | 'import_0.Parameterized: (a1) => new import_0.Parameterized(a1),', 94 | ], 95 | paramKeys: [ 96 | 'import_0.Parameterized: [_KEY_List_num],', 97 | ], 98 | messages: [ 99 | 'warning: Parameterized is a parameterized type. ' 100 | '(package:a/a.dart 1 16)', 101 | ]); 102 | }); 103 | 104 | it('injects parameterized constructor parameters', () { 105 | return generates(phases, 106 | inputs: { 107 | 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', 108 | 'a|lib/a.dart': ''' 109 | import 'package:inject/inject.dart'; 110 | class Foo {} 111 | class Bar { 112 | @inject 113 | Bar(Foo f); 114 | } 115 | ''' 116 | }, 117 | imports: [ 118 | "import 'package:a/a.dart' as import_0;", 119 | ], 120 | keys: [ 121 | 'Foo_bool = new Key(new TypeLiteral>().type);', 122 | ], 123 | factories: [ 124 | 'import_0.Bar: (a1) => new import_0.Bar(a1),', 125 | ], 126 | paramKeys: [ 127 | 'import_0.Bar: [_KEY_Foo_bool],', 128 | ]); 129 | }); 130 | 131 | it('allows un-parameterized parameters', () { 132 | return generates(phases, 133 | inputs: { 134 | 'a|web/main.dart': ''' 135 | import 'package:inject/inject.dart'; 136 | class Foo {} 137 | class Bar { 138 | @inject 139 | Bar(Foo f); 140 | } 141 | main() {} 142 | ''' 143 | }, 144 | imports: [ 145 | "import 'main.dart' as import_0;", 146 | ], 147 | keys: [ 148 | "Foo = new Key(import_0.Foo);" 149 | ], 150 | factories: [ 151 | 'import_0.Bar: (a1) => new import_0.Bar(a1),', 152 | ], 153 | paramKeys: [ 154 | 'import_0.Bar: [_KEY_Foo],' 155 | ]); 156 | }); 157 | 158 | it('allows partially-parameterized parameters', () { 159 | return generates(phases, 160 | inputs: { 161 | 'a|web/main.dart': ''' 162 | import 'package:inject/inject.dart'; 163 | class Foo {} 164 | class Bar { 165 | @inject 166 | Bar(Foo f); 167 | } 168 | main() {} 169 | ''' 170 | }, 171 | imports: [ 172 | "import 'main.dart' as import_0;", 173 | ], 174 | keys: [ 175 | 'Foo_bool_dynamic_num = new Key(new TypeLiteral>().type);', 176 | ], 177 | factories: [ 178 | 'import_0.Bar: (a1) => new import_0.Bar(a1),', 179 | ], 180 | paramKeys: [ 181 | 'import_0.Bar: [_KEY_Foo_bool_dynamic_num],' 182 | ]); 183 | }); 184 | 185 | it('should generate same method when there\'s no parameters and when all parameters are dynamic', () { 186 | return generates(phases, 187 | inputs: { 188 | 'a|web/main.dart': ''' 189 | import 'package:inject/inject.dart'; 190 | class Foo {} 191 | class Bar { 192 | @inject 193 | Bar(Foo f); 194 | } 195 | class Baz { 196 | @inject 197 | Baz(Foo f); 198 | } 199 | main() {} 200 | ''' 201 | }, 202 | imports: [ 203 | "import 'main.dart' as import_0;", 204 | ], 205 | keys: [ 206 | 'Foo = new Key(import_0.Foo);', 207 | ], 208 | factories: [ 209 | 'import_0.Bar: (a1) => new import_0.Bar(a1),', 210 | 'import_0.Baz: (a1) => new import_0.Baz(a1),', 211 | ], 212 | paramKeys: [ 213 | 'import_0.Bar: [_KEY_Foo],', 214 | 'import_0.Baz: [_KEY_Foo],', 215 | ]); 216 | }); 217 | 218 | it('follows exports', () { 219 | return generates(phases, 220 | inputs: { 221 | 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', 222 | 'a|lib/a.dart': 'export "package:a/b.dart";', 223 | 'a|lib/b.dart': CLASS_ENGINE 224 | }, 225 | imports: [ 226 | "import 'package:a/b.dart' as import_0;", 227 | ], 228 | factories: [ 229 | 'import_0.Engine: () => new import_0.Engine(),', 230 | ], 231 | paramKeys: [ 232 | 'import_0.Engine: const[],' 233 | ]); 234 | }); 235 | 236 | it('handles parts', () { 237 | return generates(phases, 238 | inputs: { 239 | 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', 240 | 'a|lib/a.dart': 241 | 'import "package:inject/inject.dart";\n' 242 | 'part "b.dart";', 243 | 'a|lib/b.dart': ''' 244 | part of a.a; 245 | class Engine { 246 | @inject 247 | Engine(); 248 | } 249 | ''' 250 | }, 251 | imports: [ 252 | "import 'package:a/a.dart' as import_0;", 253 | ], 254 | factories: [ 255 | 'import_0.Engine: () => new import_0.Engine(),', 256 | ], 257 | paramKeys: [ 258 | 'import_0.Engine: const[],' 259 | ]); 260 | }); 261 | 262 | it('follows relative imports', () { 263 | return generates(phases, 264 | inputs: { 265 | 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', 266 | 'a|lib/a.dart': 'import "b.dart";', 267 | 'a|lib/b.dart': CLASS_ENGINE 268 | }, 269 | imports: [ 270 | "import 'package:a/b.dart' as import_0;", 271 | ], 272 | factories: [ 273 | 'import_0.Engine: () => new import_0.Engine(),', 274 | ], 275 | paramKeys: [ 276 | 'import_0.Engine: const[],' 277 | ]); 278 | }); 279 | 280 | it('handles relative imports', () { 281 | return generates(phases, 282 | inputs: { 283 | 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', 284 | 'a|lib/a.dart': ''' 285 | import "package:inject/inject.dart"; 286 | import 'b.dart'; 287 | class Car { 288 | @inject 289 | Car(Engine engine); 290 | } 291 | ''', 292 | 'a|lib/b.dart': CLASS_ENGINE 293 | }, 294 | imports: [ 295 | "import 'package:a/a.dart' as import_0;", 296 | "import 'package:a/b.dart' as import_1;", 297 | ], 298 | keys: [ 299 | "Engine = new Key(import_1.Engine);" 300 | ], 301 | factories: [ 302 | 'import_0.Car: (a1) => new import_0.Car(a1),', 303 | 'import_1.Engine: () => new import_1.Engine(),', 304 | ], 305 | paramKeys: [ 306 | 'import_0.Car: [_KEY_Engine],', 307 | 'import_1.Engine: const[],', 308 | ]); 309 | }); 310 | 311 | it('handles web imports beside main', () { 312 | return generates(phases, 313 | inputs: { 314 | 'a|web/main.dart': 'import "a.dart"; main() {}', 315 | 'a|web/a.dart': CLASS_ENGINE 316 | }, 317 | imports: [ 318 | "import 'a.dart' as import_0;", 319 | ], 320 | factories: [ 321 | 'import_0.Engine: () => new import_0.Engine(),', 322 | ], 323 | paramKeys: [ 324 | 'import_0.Engine: const[],' 325 | ]); 326 | }); 327 | 328 | it('handles imports in main', () { 329 | return generates(phases, 330 | inputs: { 331 | 'a|web/main.dart': ''' 332 | $CLASS_ENGINE 333 | main() {} 334 | ''' 335 | }, 336 | imports: [ 337 | "import 'main.dart' as import_0;", 338 | ], 339 | factories: [ 340 | 'import_0.Engine: () => new import_0.Engine(),', 341 | ], 342 | paramKeys: [ 343 | 'import_0.Engine: const[],' 344 | ]); 345 | }); 346 | 347 | it('skips and warns on named constructors', () { 348 | return generates(phases, 349 | inputs: { 350 | 'a|web/main.dart': ''' 351 | import "package:inject/inject.dart"; 352 | class Engine { 353 | @inject 354 | Engine.foo(); 355 | } 356 | 357 | main() {} 358 | ''' 359 | }, 360 | messages: ['warning: Named constructors cannot be injected. ' 361 | '(web/main.dart 2 20)']); 362 | }); 363 | 364 | it('handles inject on classes', () { 365 | return generates(phases, 366 | inputs: { 367 | 'a|web/main.dart': ''' 368 | import "package:inject/inject.dart"; 369 | @inject 370 | class Engine {} 371 | 372 | main() {} 373 | ''' 374 | }, 375 | imports: [ 376 | "import 'main.dart' as import_0;", 377 | ], 378 | factories: [ 379 | 'import_0.Engine: () => new import_0.Engine(),', 380 | ], 381 | paramKeys: [ 382 | 'import_0.Engine: const[],' 383 | ]); 384 | }); 385 | 386 | it('skips and warns when no default constructor', () { 387 | return generates(phases, 388 | inputs: { 389 | 'a|web/main.dart': ''' 390 | import "package:inject/inject.dart"; 391 | @inject 392 | class Engine { 393 | Engine.foo(); 394 | } 395 | main() {} 396 | ''' 397 | }, 398 | messages: ['warning: Engine cannot be injected because it does not ' 399 | 'have a default constructor. (web/main.dart 1 18)']); 400 | }); 401 | 402 | it('skips and warns on abstract types with no factory constructor', () { 403 | return generates(phases, 404 | inputs: { 405 | 'a|web/main.dart': ''' 406 | import "package:inject/inject.dart"; 407 | @inject 408 | abstract class Engine { } 409 | 410 | main() {} 411 | ''' 412 | }, 413 | messages: ['warning: Engine cannot be injected because it is an ' 414 | 'abstract type with no factory constructor. ' 415 | '(web/main.dart 1 18)']); 416 | }); 417 | 418 | it('skips and warns on abstract types with implicit constructor', () { 419 | return generates(phases, 420 | inputs: { 421 | 'a|web/main.dart': ''' 422 | import "package:inject/inject.dart"; 423 | @inject 424 | abstract class Engine { 425 | Engine(); 426 | } 427 | main() {} 428 | ''' 429 | }, 430 | messages: ['warning: Engine cannot be injected because it is an ' 431 | 'abstract type with no factory constructor. ' 432 | '(web/main.dart 1 18)']); 433 | }); 434 | 435 | it('injects abstract types with factory constructors', () { 436 | return generates(phases, 437 | inputs: { 438 | 'a|web/main.dart': ''' 439 | import "package:inject/inject.dart"; 440 | @inject 441 | abstract class Engine { 442 | factory Engine() => new ConcreteEngine(); 443 | } 444 | 445 | class ConcreteEngine implements Engine {} 446 | 447 | main() {} 448 | ''' 449 | }, 450 | imports: [ 451 | "import 'main.dart' as import_0;", 452 | ], 453 | factories: [ 454 | 'import_0.Engine: () => new import_0.Engine(),', 455 | ], 456 | paramKeys: [ 457 | 'import_0.Engine: const[],' 458 | ]); 459 | }); 460 | 461 | it('injects this parameters', () { 462 | return generates(phases, 463 | inputs: { 464 | 'a|web/main.dart': ''' 465 | import "package:inject/inject.dart"; 466 | class Engine { 467 | final Fuel fuel; 468 | @inject 469 | Engine(this.fuel); 470 | } 471 | 472 | class Fuel {} 473 | 474 | main() {} 475 | ''' 476 | }, 477 | imports: [ 478 | "import 'main.dart' as import_0;", 479 | ], 480 | keys: [ 481 | "Fuel = new Key(import_0.Fuel);", 482 | ], 483 | factories: [ 484 | 'import_0.Engine: (a1) => new import_0.Engine(a1),', 485 | ], 486 | paramKeys: [ 487 | 'import_0.Engine: [_KEY_Fuel],' 488 | ]); 489 | }); 490 | 491 | it('narrows this parameters', () { 492 | return generates(phases, 493 | inputs: { 494 | 'a|web/main.dart': ''' 495 | import "package:inject/inject.dart"; 496 | class Engine { 497 | final Fuel fuel; 498 | @inject 499 | Engine(JetFuel this.fuel); 500 | } 501 | 502 | class Fuel {} 503 | class JetFuel implements Fuel {} 504 | 505 | main() {} 506 | ''' 507 | }, 508 | imports: [ 509 | "import 'main.dart' as import_0;", 510 | ], 511 | keys: [ 512 | "JetFuel = new Key(import_0.JetFuel);", 513 | ], 514 | factories: [ 515 | 'import_0.Engine: (a1) => new import_0.Engine(a1),', 516 | ], 517 | paramKeys: [ 518 | 'import_0.Engine: [_KEY_JetFuel],' 519 | ]); 520 | }); 521 | 522 | it('skips and warns on unresolved types', () { 523 | return generates(phases, 524 | inputs: { 525 | 'a|web/main.dart': ''' 526 | import "package:inject/inject.dart"; 527 | @inject 528 | class Engine { 529 | Engine(foo); 530 | } 531 | 532 | @inject 533 | class Car { 534 | var foo; 535 | Car(this.foo); 536 | } 537 | 538 | main() {} 539 | ''' 540 | }, 541 | messages: ['warning: Engine cannot be injected because parameter ' 542 | 'type foo cannot be resolved. (web/main.dart 3 20)', 543 | 'warning: Car cannot be injected because parameter type ' 544 | 'foo cannot be resolved. (web/main.dart 9 20)']); 545 | }); 546 | 547 | it('supports custom annotations', () { 548 | injectableAnnotations.add('angular.NgInjectableService'); 549 | return generates(phases, 550 | inputs: { 551 | 'angular|lib/angular.dart': PACKAGE_ANGULAR, 552 | 'a|web/main.dart': ''' 553 | import 'package:angular/angular.dart'; 554 | @NgInjectableService() 555 | class Engine { 556 | Engine(); 557 | } 558 | 559 | class Car { 560 | @NgInjectableService() 561 | Car(); 562 | } 563 | 564 | main() {} 565 | ''' 566 | }, 567 | imports: [ 568 | "import 'main.dart' as import_0;", 569 | ], 570 | factories: [ 571 | 'import_0.Engine: () => new import_0.Engine(),', 572 | 'import_0.Car: () => new import_0.Car(),', 573 | ], 574 | paramKeys: [ 575 | 'import_0.Engine: const[],', 576 | 'import_0.Car: const[],' 577 | ]).whenComplete(() { 578 | injectableAnnotations.clear(); 579 | }); 580 | }); 581 | 582 | it('supports default formal parameters', () { 583 | return generates(phases, 584 | inputs: { 585 | 'a|web/main.dart': ''' 586 | import "package:inject/inject.dart"; 587 | class Car { 588 | final Engine engine; 589 | 590 | @inject 591 | Car([Engine this.engine]); 592 | } 593 | 594 | class Engine { 595 | @inject 596 | Engine(); 597 | } 598 | 599 | main() {} 600 | ''' 601 | }, 602 | imports: [ 603 | "import 'main.dart' as import_0;", 604 | ], 605 | keys: [ 606 | "Engine = new Key(import_0.Engine);" 607 | ], 608 | factories: [ 609 | 'import_0.Car: (a1) => new import_0.Car(a1),', 610 | 'import_0.Engine: () => new import_0.Engine(),', 611 | ], 612 | paramKeys: [ 613 | 'import_0.Car: [_KEY_Engine],', 614 | 'import_0.Engine: const[],', 615 | ]); 616 | }); 617 | 618 | it('supports injectableTypes argument', () { 619 | return generates(phases, 620 | inputs: { 621 | 'di|lib/annotations.dart': PACKAGE_DI, 622 | 'a|web/main.dart': ''' 623 | @Injectables(const[Engine]) 624 | library a; 625 | 626 | import 'package:di/annotations.dart'; 627 | 628 | class Engine { 629 | Engine(); 630 | } 631 | 632 | main() {} 633 | ''' 634 | }, 635 | imports: [ 636 | "import 'main.dart' as import_0;", 637 | ], 638 | factories: [ 639 | 'import_0.Engine: () => new import_0.Engine(),', 640 | ], 641 | paramKeys: [ 642 | 'import_0.Engine: const[],' 643 | ]); 644 | }); 645 | 646 | it('does not generate dart:core imports', () { 647 | return generates(phases, 648 | inputs: { 649 | 'a|web/main.dart': ''' 650 | import 'package:inject/inject.dart'; 651 | 652 | class Engine { 653 | @inject 654 | Engine(int i); 655 | } 656 | main() {} 657 | ''' 658 | }, 659 | imports: [ 660 | "import 'main.dart' as import_0;", 661 | ], 662 | keys: [ 663 | "int = new Key(int);" 664 | ], 665 | factories: [ 666 | 'import_0.Engine: (a1) => new import_0.Engine(a1),', 667 | ], 668 | paramKeys: [ 669 | 'import_0.Engine: [_KEY_int],' 670 | ]); 671 | }); 672 | 673 | it('warns on private types', () { 674 | return generates(phases, 675 | inputs: { 676 | 'a|web/main.dart': ''' 677 | import "package:inject/inject.dart"; 678 | @inject 679 | class _Engine { 680 | _Engine(); 681 | } 682 | 683 | main() {} 684 | ''' 685 | }, 686 | messages: ['warning: _Engine cannot be injected because it is a ' 687 | 'private type. (web/main.dart 1 18)']); 688 | }); 689 | 690 | it('warns on multiple constructors', () { 691 | return generates(phases, 692 | inputs: { 693 | 'a|web/main.dart': ''' 694 | import "package:inject/inject.dart"; 695 | 696 | @inject 697 | class Engine { 698 | Engine(); 699 | 700 | @inject 701 | Engine.foo(); 702 | } 703 | 704 | main() {} 705 | ''' 706 | }, 707 | messages: ['warning: Engine has more than one constructor ' 708 | 'annotated for injection. (web/main.dart 2 18)']); 709 | }); 710 | 711 | it('handles annotated dependencies', () { 712 | return generates(phases, 713 | inputs: { 714 | 'a|web/main.dart': ''' 715 | import "package:inject/inject.dart"; 716 | 717 | class Turbo { 718 | const Turbo(); 719 | } 720 | 721 | @inject 722 | class Engine {} 723 | 724 | @inject 725 | class Car { 726 | Car(@Turbo() Engine engine); 727 | } 728 | 729 | main() {} 730 | ''' 731 | }, 732 | imports: [ 733 | "import 'main.dart' as import_0;", 734 | ], 735 | keys: [ 736 | "Engine_Turbo = new Key(import_0.Engine, import_0.Turbo);" 737 | ], 738 | factories: [ 739 | 'import_0.Engine: () => new import_0.Engine(),', 740 | 'import_0.Car: (a1) => new import_0.Car(a1),', 741 | ], 742 | paramKeys: [ 743 | 'import_0.Engine: const[],', 744 | 'import_0.Car: [_KEY_Engine_Turbo],' 745 | ]); 746 | }); 747 | 748 | it('transforms main', () { 749 | return tests.applyTransformers(phases, 750 | inputs: { 751 | 'a|web/main.dart': ''' 752 | library main; 753 | import 'package:di/di.dart'; 754 | 755 | main() { 756 | print('abc'); 757 | }''' 758 | }, 759 | results: { 760 | 'a|web/main.dart': ''' 761 | library main; 762 | import 'package:di/di.dart'; 763 | import 'main_generated_type_factory_maps.dart' show setStaticReflectorAsDefault; 764 | 765 | main() { 766 | setStaticReflectorAsDefault(); 767 | print('abc'); 768 | }''' 769 | }); 770 | }); 771 | 772 | it('transforms main async ', () { 773 | return tests.applyTransformers(phases, 774 | inputs: { 775 | 'a|web/main.dart': ''' 776 | library main; 777 | import 'package:di/di.dart'; 778 | 779 | main() async { 780 | print('abc'); 781 | }''' 782 | }, 783 | results: { 784 | 'a|web/main.dart': ''' 785 | library main; 786 | import 'package:di/di.dart'; 787 | import 'main_generated_type_factory_maps.dart' show setStaticReflectorAsDefault; 788 | 789 | main() async { 790 | setStaticReflectorAsDefault(); 791 | print('abc'); 792 | }''' 793 | }); 794 | }); 795 | 796 | 797 | it('supports using a child of an injectable annotations as an injection marker', () { 798 | injectableAnnotations.add('di.annotations.Injectable'); 799 | return generates(phases, 800 | inputs: { 801 | 'di|lib/annotations.dart': PACKAGE_DI, 802 | 'a|web/main.dart': ''' 803 | 804 | library a; 805 | 806 | import 'package:di/annotations.dart'; 807 | 808 | class Child implements Injectable { 809 | const Child(); 810 | } 811 | 812 | @Child() 813 | class Engine { 814 | Engine(); 815 | } 816 | 817 | main() {} 818 | ''' 819 | }, 820 | imports: [ 821 | "import 'main.dart' as import_0;", 822 | ], 823 | factories: [ 824 | 'import_0.Engine: () => new import_0.Engine(),', 825 | ], 826 | paramKeys: [ 827 | 'import_0.Engine: const[],' 828 | ]).whenComplete(() { 829 | injectableAnnotations.clear(); 830 | }); 831 | }); 832 | }); 833 | } 834 | 835 | Future generates(List> phases, 836 | {Map inputs, Iterable imports: const [], 837 | Iterable keys: const [], 838 | Iterable factories: const [], 839 | Iterable paramKeys: const [], 840 | Iterable messages: const []}) { 841 | 842 | inputs['inject|lib/inject.dart'] = PACKAGE_INJECT; 843 | 844 | imports = imports.map((i) => '$i\n'); 845 | keys = keys.map((t) => 'final Key _KEY_$t'); 846 | factories = factories.map((t) => ' $t\n'); 847 | paramKeys = paramKeys.map((t) => ' $t\n'); 848 | 849 | return tests.applyTransformers(phases, 850 | inputs: inputs, 851 | results: { 852 | 'a|web/main_generated_type_factory_maps.dart': ''' 853 | $IMPORTS 854 | ${imports.join('')}${(keys.length != 0 ? '\n' : '')}${keys.join('\n')} 855 | final Map typeFactories = { 856 | ${factories.join('')}}; 857 | final Map> parameterKeys = { 858 | ${paramKeys.join('')}}; 859 | setStaticReflectorAsDefault() => Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys); 860 | ''', 861 | }, 862 | messages: messages); 863 | } 864 | 865 | const String IMPORTS = ''' 866 | library a.web.main.generated_type_factory_maps; 867 | 868 | import 'package:di/di.dart'; 869 | import 'package:di/src/reflector_static.dart'; 870 | import 'package:di/type_literal.dart'; 871 | '''; 872 | 873 | const String CLASS_ENGINE = ''' 874 | import 'package:inject/inject.dart'; 875 | class Engine { 876 | @inject 877 | Engine(); 878 | }'''; 879 | 880 | const String PACKAGE_ANGULAR = ''' 881 | library angular; 882 | 883 | class NgInjectableService { 884 | const NgInjectableService(); 885 | } 886 | '''; 887 | 888 | const String PACKAGE_INJECT = ''' 889 | library inject; 890 | 891 | class InjectAnnotation { 892 | const InjectAnnotation._(); 893 | } 894 | const inject = const InjectAnnotation._(); 895 | '''; 896 | 897 | const String PACKAGE_DI = ''' 898 | library di.annotations; 899 | 900 | class Injectables { 901 | final List types; 902 | const Injectables(this.types); 903 | } 904 | 905 | class Injectable { 906 | const Injectable(); 907 | } 908 | '''; 909 | 910 | 911 | const String PACKAGE_TYPE_LITERAL = ''' 912 | library di.type_literal; 913 | 914 | class TypeLiteral { 915 | Type get type => T; 916 | } 917 | '''; 918 | -------------------------------------------------------------------------------- /test/main.dart: -------------------------------------------------------------------------------- 1 | @Injectables(const [ 2 | ClassOne, 3 | CircularA, 4 | CircularB, 5 | MultipleConstructors, 6 | NumDependency, 7 | IntDependency, 8 | DoubleDependency, 9 | BoolDependency, 10 | StringDependency, 11 | 12 | Expando // used to test that generator can handle core types in @Injectables 13 | ]) 14 | library di.tests; 15 | 16 | import 'package:guinness2/guinness2.dart'; 17 | import 'package:matcher/matcher.dart' as matcher; 18 | import 'package:di/di.dart'; 19 | import 'package:di/annotations.dart'; 20 | import 'package:di/type_literal.dart'; 21 | import 'package:di/src/reflector_static.dart'; 22 | import 'package:di/src/reflector_dynamic.dart'; 23 | import 'package:di/check_bind_args.dart'; 24 | import 'package:di/src/module.dart'; 25 | 26 | import 'test_annotations.dart'; 27 | // Generated file. Run ../test_tf_gen.sh. 28 | import 'type_factories_gen.dart' as type_factories_gen; 29 | import 'main_same_name.dart' as same_name; 30 | 31 | import 'dart:mirrors'; 32 | 33 | /** 34 | * Annotation used to mark classes for which static type factory must be 35 | * generated. For testing purposes not all classes are marked with this 36 | * annotation, some classes are included in @Injectables at the top. 37 | */ 38 | class InjectableTest { 39 | const InjectableTest(); 40 | } 41 | 42 | /// The class should be injectable if the annotation is a child of an injectable annotation 43 | class InjectableChild implements Injectable { 44 | const InjectableChild(); 45 | } 46 | 47 | // just some classes for testing 48 | @Deprecated("I'm here because of https://github.com/angular/di.dart/issues/191") 49 | @InjectableTest() 50 | class Engine { 51 | final String id = 'v8-id'; 52 | } 53 | 54 | @InjectableChild() 55 | class MockEngine implements Engine { 56 | final String id = 'mock-id'; 57 | } 58 | 59 | @InjectableTest() 60 | class MockEngine2 implements Engine { 61 | String id = 'mock-id-2'; 62 | } 63 | 64 | // this class should only be used in a single test (dynamic implicit injection) 65 | @InjectableTest() 66 | class SpecialEngine implements Engine { 67 | String id = 'special-id'; 68 | } 69 | 70 | class HiddenConstructor { 71 | HiddenConstructor._(); 72 | } 73 | 74 | @InjectableTest() 75 | class TurboEngine implements Engine { 76 | String id = 'turbo-engine-id'; 77 | } 78 | 79 | @InjectableTest() 80 | class BrokenOldEngine implements Engine { 81 | String id = 'broken-old-engine-id'; 82 | } 83 | 84 | @InjectableTest() 85 | class Car { 86 | Engine engine; 87 | Injector injector; 88 | 89 | Car(this.engine, this.injector); 90 | } 91 | 92 | class Lemon { 93 | final engine; 94 | final Injector injector; 95 | 96 | Lemon(this.engine, this.injector); 97 | } 98 | 99 | @InjectableTest() 100 | class Porsche { 101 | Engine engine; 102 | Injector injector; 103 | 104 | Porsche(@Turbo() this.engine, this.injector); 105 | } 106 | 107 | class NumDependency { 108 | NumDependency(num value) {} 109 | } 110 | 111 | class IntDependency { 112 | IntDependency(int value) {} 113 | } 114 | 115 | class DoubleDependency { 116 | DoubleDependency(double value) {} 117 | } 118 | 119 | class StringDependency { 120 | StringDependency(String value) {} 121 | } 122 | 123 | class BoolDependency { 124 | BoolDependency(bool value) {} 125 | } 126 | 127 | @InjectableTest() 128 | class DepedencyWithParameterizedList { 129 | List nums; 130 | List strs; 131 | 132 | DepedencyWithParameterizedList(this.nums, @StringList() this.strs); 133 | } 134 | 135 | @InjectableTest() 136 | class DependencyWithParameterizedMap { 137 | Map map; 138 | 139 | DependencyWithParameterizedMap(this.map); 140 | } 141 | 142 | @InjectableTest() 143 | class AnotherDependencyWithParameterizedMap { 144 | Map map; 145 | 146 | AnotherDependencyWithParameterizedMap(this.map); 147 | } 148 | 149 | @InjectableTest() 150 | class DependencyWithMap { 151 | Map map; 152 | 153 | DependencyWithMap(this.map); 154 | } 155 | 156 | 157 | class CircularA { 158 | CircularA(CircularB b) {} 159 | } 160 | 161 | class CircularB { 162 | CircularB(CircularA a) {} 163 | } 164 | 165 | typedef int CompareInt(int a, int b); 166 | 167 | int compareIntAsc(int a, int b) => b.compareTo(a); 168 | 169 | class WithTypeDefDependency { 170 | CompareInt compare; 171 | 172 | WithTypeDefDependency(this.compare); 173 | } 174 | 175 | class MultipleConstructors { 176 | String instantiatedVia; 177 | MultipleConstructors() : instantiatedVia = 'default'; 178 | MultipleConstructors.named() : instantiatedVia = 'named'; 179 | } 180 | 181 | class InterfaceOne { 182 | } 183 | 184 | class ClassOne implements InterfaceOne { 185 | ClassOne(Log log) { 186 | log.add('ClassOne'); 187 | } 188 | } 189 | 190 | @InjectableTest() 191 | class ParameterizedType { 192 | ParameterizedType(); 193 | } 194 | 195 | @InjectableTest() 196 | class ParameterizedDependency { 197 | final ParameterizedType _p; 198 | ParameterizedDependency(this._p); 199 | } 200 | 201 | @InjectableTest() 202 | class GenericParameterizedDependency { 203 | final ParameterizedType _p; 204 | GenericParameterizedDependency(this._p); 205 | } 206 | 207 | @InjectableTest() 208 | class Log { 209 | var log = []; 210 | 211 | add(String message) => log.add(message); 212 | } 213 | 214 | @InjectableTest() 215 | class AnnotatedPrimitiveDependency { 216 | String strValue; 217 | AnnotatedPrimitiveDependency(@Turbo() this.strValue); 218 | } 219 | 220 | class EmulatedMockEngineFactory { 221 | call() => new MockEngine(); 222 | } 223 | 224 | bool throwOnceShouldThrow = true; 225 | @InjectableTest() 226 | class ThrowOnce { 227 | ThrowOnce() { 228 | if (throwOnceShouldThrow) { 229 | throwOnceShouldThrow = false; 230 | throw ["ThrowOnce"]; 231 | } 232 | } 233 | } 234 | 235 | @Injectable() 236 | class SameEngine { 237 | same_name.Engine engine; 238 | SameEngine(this.engine); 239 | } 240 | 241 | 242 | const String STATIC_NAME = 'Static ModuleInjector'; 243 | const String DYNAMIC_NAME = 'Dynamic ModuleInjector'; 244 | void main() { 245 | testModule(); 246 | 247 | var static_factory = new GeneratedTypeFactories( 248 | type_factories_gen.typeFactories, type_factories_gen.parameterKeys); 249 | createInjectorSpec(STATIC_NAME, () => new Module.withReflector(static_factory)); 250 | 251 | Module.classAnnotations = [Injectable, InjectableTest]; 252 | Module.libAnnotations = [Injectables]; 253 | var reflector = new DynamicTypeFactories(); 254 | createInjectorSpec(DYNAMIC_NAME, () => new Module.withReflector(reflector)); 255 | 256 | testKey(); 257 | testCheckBindArgs(); 258 | } 259 | 260 | testModule() { 261 | 262 | describe('Module', () { 263 | 264 | const BIND_ERROR = 'Only one of following parameters can be specified: ' 265 | 'toValue, toFactory, toImplementation, toInstanceOf'; 266 | 267 | describe('bind', () { 268 | 269 | it('should throw if incorrect combination of parameters passed (1)', () { 270 | expect(() { 271 | new Module().bind(Engine, toValue: new Engine(), toImplementation: MockEngine); 272 | }).toThrowWith(message: BIND_ERROR); 273 | }); 274 | 275 | it('should throw if incorrect combination of parameters passed (2)', () { 276 | expect(() { 277 | new Module().bind(Engine, toValue: new Engine(), toFactory: () => null); 278 | }).toThrowWith(message: BIND_ERROR); 279 | }); 280 | 281 | it('should throw if incorrect combination of parameters passed (3)', () { 282 | expect(() { 283 | new Module().bind(Engine, toValue: new Engine(), toImplementation: MockEngine, toFactory: () => null); 284 | }).toThrowWith(message: BIND_ERROR); 285 | }); 286 | 287 | it('should throw if incorrect combination of parameters passed (4)', () { 288 | expect(() { 289 | new Module().bind(Engine, toImplementation: MockEngine, toFactory: () => null); 290 | }).toThrowWith(message: BIND_ERROR); 291 | }); 292 | 293 | it('should throw when trying to bind primitive type', () { 294 | expect(() { 295 | new Module().bind(int, toValue: 3); 296 | }).toThrowWith(message: "Cannot bind primitive type 'int'."); 297 | }); 298 | 299 | it('should accept a Type as toInstanceOf parameter', () { 300 | expect(() { 301 | new Module().bind(Engine, toInstanceOf: MockEngine); 302 | }).not.toThrow(); 303 | }); 304 | 305 | it('should accept a Key as toInstanceOf parameter', () { 306 | expect(() { 307 | new Module().bind(Engine, toInstanceOf: key(MockEngine)); 308 | }).not.toThrow(); 309 | }); 310 | }); 311 | 312 | describe('assert annotations', () { 313 | var classAnnotations, libAnnotations, assertAnnotations; 314 | 315 | // The assertion is not activated in JS 316 | if (1.0 is int) return; 317 | 318 | beforeEach(() { 319 | // Save the module configuration 320 | classAnnotations = Module.classAnnotations; 321 | libAnnotations = Module.libAnnotations; 322 | assertAnnotations = Module.assertAnnotations; 323 | Module.assertAnnotations = true; 324 | }); 325 | 326 | afterEach(() { 327 | // Restore the original module configuration 328 | Module.classAnnotations = classAnnotations; 329 | Module.libAnnotations = libAnnotations; 330 | Module.assertAnnotations = assertAnnotations; 331 | }); 332 | 333 | it('should assert class level annotations', () { 334 | // By default class annotated with `@Injectable` are detected 335 | var reflector = new DynamicTypeFactories(); 336 | var module = new Module.withReflector(reflector); 337 | expect(() =>module.bind(MockEngine)).not.toThrow(); 338 | 339 | // `EngineAnnotated` is not annotated with `@InjectableTest` 340 | Module.classAnnotations = [InjectableTest]; 341 | reflector = new DynamicTypeFactories(); 342 | module = new Module.withReflector(reflector); 343 | expect(() => module.bind(MockEngine)).toThrowWith( 344 | message: "The class 'MockEngine' should be annotated with one of 'InjectableTest'" 345 | ); 346 | }); 347 | 348 | it('should assert library level annotations', () { 349 | // By default class listed in `@Injectables` are detected 350 | var reflector = new DynamicTypeFactories(); 351 | var module = new Module.withReflector(reflector); 352 | expect(() => module.bind(ClassOne)).not.toThrow(); 353 | 354 | // `EngineAnnotated` is not listed in `@InjectableTest` 355 | Module.classAnnotations = [Injectable]; 356 | Module.libAnnotations = [InjectableTest]; 357 | reflector = new DynamicTypeFactories(); 358 | module = new Module.withReflector(reflector); 359 | expect(() => module.bind(ClassOne)).toThrowWith( 360 | message: "The class 'ClassOne' should be annotated with one of 'Injectable'" 361 | ); 362 | }); 363 | 364 | it('should not assert annotations when the check is disabled', () { 365 | Module.assertAnnotations = false; 366 | Module.classAnnotations = [InjectableTest]; 367 | var reflector = new DynamicTypeFactories(); 368 | var module = new Module.withReflector(reflector); 369 | expect(() => module.bind(MockEngine)).not.toThrow(); 370 | }); 371 | }); 372 | }); 373 | } 374 | 375 | typedef Module ModuleFactory(); 376 | 377 | createInjectorSpec(String injectorName, ModuleFactory moduleFactory) { 378 | 379 | describe(injectorName, () { 380 | 381 | it('should instantiate a type', () { 382 | var module = moduleFactory()..bind(Engine); 383 | var injector = new ModuleInjector([module]); 384 | var instance = injector.get(Engine); 385 | 386 | expect(instance).toBeAnInstanceOf(Engine); 387 | expect(instance.id).toEqual('v8-id'); 388 | }); 389 | 390 | it('should instantiate an annotated type', () { 391 | var injector = new ModuleInjector([moduleFactory() 392 | ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine) 393 | ..bind(Car, toValue: new Engine()) 394 | ]); 395 | var instance = injector.getByKey(new Key(Engine, Turbo)); 396 | 397 | expect(instance).toBeAnInstanceOf(TurboEngine); 398 | expect(instance.id).toEqual('turbo-engine-id'); 399 | }); 400 | 401 | it('should support passing annotations instead of to annotation types', () { 402 | var injector = new ModuleInjector([moduleFactory() 403 | ..bind(Engine, withAnnotation: const Turbo(), toImplementation: TurboEngine) 404 | ..bind(Car, toValue: new Engine()) 405 | ]); 406 | var instance = injector.getByKey(new Key(Engine, Turbo)); 407 | 408 | expect(instance).toBeAnInstanceOf(TurboEngine); 409 | expect(instance.id).toEqual('turbo-engine-id'); 410 | }); 411 | 412 | it('should fail if the type was not bound at injector creation', () { 413 | var module = moduleFactory(); 414 | var injector = new ModuleInjector([module]); 415 | module.bind(Engine); 416 | 417 | expect(() { 418 | injector.get(Engine); 419 | }).toThrowWith(message: 'No provider found for Engine! ' 420 | '(resolving Engine)'); 421 | }); 422 | 423 | it('should fail if no binding is found resolving dependencies', () { 424 | var injector = new ModuleInjector([moduleFactory()..bind(Car)]); 425 | expect(() { 426 | injector.get(Car); 427 | }).toThrowWith(message: 'No provider found for Engine! ' 428 | '(resolving Car -> Engine)'); 429 | }); 430 | 431 | it('should resolve basic dependencies', () { 432 | var injector = new ModuleInjector([ 433 | moduleFactory() 434 | ..bind(Car) 435 | ..bind(Engine) 436 | ]); 437 | var instance = injector.get(Car); 438 | 439 | expect(instance).toBeAnInstanceOf(Car); 440 | expect(instance.engine.id).toEqual('v8-id'); 441 | }); 442 | 443 | it('should resolve complex dependencies', () { 444 | var injector = new ModuleInjector([moduleFactory() 445 | ..bind(Porsche) 446 | ..bind(TurboEngine) 447 | ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine) 448 | ]); 449 | var instance = injector.get(Porsche); 450 | 451 | expect(instance).toBeAnInstanceOf(Porsche); 452 | expect(instance.engine.id).toEqual('turbo-engine-id'); 453 | }); 454 | 455 | it('should resolve dependencies with parameterized types (TypeLiteral)', () { 456 | var injector = new ModuleInjector([moduleFactory() 457 | ..bind(new TypeLiteral>().type, toValue: [1, 2]) 458 | ..bind(new TypeLiteral>().type, 459 | withAnnotation: const StringList(), 460 | toValue: ['1', '2']) 461 | ..bind(DepedencyWithParameterizedList) 462 | ]); 463 | var instance = injector.get(DepedencyWithParameterizedList); 464 | 465 | expect(instance).toBeAnInstanceOf(DepedencyWithParameterizedList); 466 | expect(instance.nums).toEqual([1, 2]); 467 | expect(instance.strs).toEqual(['1', '2']); 468 | }); 469 | 470 | it('should resolve dependencies with parameterized types (TypeLiteral)', () { 471 | var injector = new ModuleInjector([moduleFactory() 472 | ..bind(new TypeLiteral>().type, toValue: {1 : 'first', 2: 'second'}) 473 | ..bind(AnotherDependencyWithParameterizedMap) 474 | ..bind(DependencyWithParameterizedMap) 475 | ]); 476 | var instance = injector.get(AnotherDependencyWithParameterizedMap); 477 | 478 | expect(instance).toBeAnInstanceOf(AnotherDependencyWithParameterizedMap); 479 | expect(instance.map).toEqual({1 : 'first', 2: 'second'}); 480 | 481 | expect(() { 482 | var wrongInstance = injector.get(DependencyWithParameterizedMap); 483 | }).toThrow(); 484 | }); 485 | 486 | describe('dynamic parameter list', () { 487 | it('should treat all-dynamic parameter list same as no parameter list', () { 488 | var injector = new ModuleInjector([moduleFactory() 489 | ..bind(new TypeLiteral>().type, toValue: {1 : 'first', 2: 'second'}) 490 | ..bind(DependencyWithParameterizedMap) 491 | ..bind(DependencyWithMap) 492 | ]); 493 | var parameterizedMapInstance = injector.get(DependencyWithParameterizedMap); 494 | var mapInstance = injector.get(DependencyWithMap); 495 | 496 | expect(parameterizedMapInstance).toBeAnInstanceOf(DependencyWithParameterizedMap); 497 | expect(parameterizedMapInstance.map).toEqual({1 : 'first', 2: 'second'}); 498 | 499 | expect(mapInstance).toBeAnInstanceOf(DependencyWithMap); 500 | expect(mapInstance.map).toEqual({1 : 'first', 2: 'second'}); 501 | }); 502 | 503 | it('should treat parameter with no args same as parameter with all dynamic args', () { 504 | var injector = new ModuleInjector([moduleFactory() 505 | ..bind(Map, toValue: {1 : 'first', 2: 'second'}) 506 | ..bind(DependencyWithParameterizedMap) 507 | ..bind(DependencyWithMap) 508 | ]); 509 | var parameterizedMapInstance = injector.get(DependencyWithParameterizedMap); 510 | var mapInstance = injector.get(DependencyWithMap); 511 | 512 | expect(parameterizedMapInstance).toBeAnInstanceOf(DependencyWithParameterizedMap); 513 | expect(parameterizedMapInstance.map).toEqual({1 : 'first', 2: 'second'}); 514 | 515 | expect(mapInstance).toBeAnInstanceOf(DependencyWithMap); 516 | expect(mapInstance.map).toEqual({1 : 'first', 2: 'second'}); 517 | }); 518 | }); 519 | 520 | it('should resolve annotated primitive type', () { 521 | var injector = new ModuleInjector([moduleFactory() 522 | ..bind(AnnotatedPrimitiveDependency) 523 | ..bind(String, toValue: 'Worked!', withAnnotation: Turbo) 524 | ]); 525 | var instance = injector.get(AnnotatedPrimitiveDependency); 526 | 527 | expect(instance).toBeAnInstanceOf(AnnotatedPrimitiveDependency); 528 | expect(instance.strValue).toEqual('Worked!'); 529 | }); 530 | 531 | it('should instantiate parameterized types', () { 532 | var module = moduleFactory()..bind(ParameterizedType); 533 | var injector = new ModuleInjector([module]); 534 | expect(injector.get(ParameterizedType)).toBeAnInstanceOf(ParameterizedType); 535 | }); 536 | 537 | it('should inject generic parameterized types', () { 538 | var injector = new ModuleInjector([moduleFactory() 539 | ..bind(ParameterizedType) 540 | ..bind(GenericParameterizedDependency) 541 | ]); 542 | expect(injector.get(GenericParameterizedDependency)) 543 | .toBeAnInstanceOf(GenericParameterizedDependency); 544 | }); 545 | 546 | it('should allow modules and overriding providers', () { 547 | var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); 548 | 549 | // injector is immutable 550 | // you can't load more modules once it's instantiated 551 | // (you can create a child injector) 552 | var injector = new ModuleInjector([module]); 553 | var instance = injector.get(Engine); 554 | 555 | expect(instance.id).toEqual('mock-id'); 556 | }); 557 | 558 | it('should only create a single instance', () { 559 | var injector = new ModuleInjector([moduleFactory()..bind(Engine)]); 560 | var first = injector.get(Engine); 561 | var second = injector.get(Engine); 562 | 563 | expect(first).toBe(second); 564 | }); 565 | 566 | it('should allow providing values', () { 567 | var module = moduleFactory() 568 | ..bind(Engine, toValue: 'str value') 569 | ..bind(Car, toValue: 123); 570 | 571 | var injector = new ModuleInjector([module]); 572 | var abcInstance = injector.get(Engine); 573 | var complexInstance = injector.get(Car); 574 | 575 | expect(abcInstance).toEqual('str value'); 576 | expect(complexInstance).toEqual(123); 577 | }); 578 | 579 | it('should allow providing null values', () { 580 | var module = moduleFactory() 581 | ..bind(Engine, toValue: null); 582 | 583 | var injector = new ModuleInjector([module]); 584 | var engineInstance = injector.get(Engine); 585 | 586 | expect(engineInstance).toBeNull(); 587 | }); 588 | 589 | 590 | it('should cache null values', () { 591 | var count = 0; 592 | factory() { 593 | if (count++ == 0) return null; 594 | return new Engine(); 595 | } 596 | var module = moduleFactory()..bind(Engine, toFactory: factory); 597 | var injector = new ModuleInjector([module]); 598 | 599 | var engine = injector.get(Engine); 600 | engine = injector.get(Engine); 601 | 602 | expect(engine).toBeNull(); 603 | }); 604 | 605 | 606 | it('should only call factories once, even when circular', () { 607 | var count = 0; 608 | factory(injector) { 609 | count++; 610 | return injector.get(Engine); 611 | } 612 | var module = moduleFactory()..bind(Engine, toFactory: factory, inject: [Injector]); 613 | var injector = new ModuleInjector([module]); 614 | 615 | try { 616 | var engine = injector.get(Engine); 617 | } on CircularDependencyError catch (e) { 618 | expect(count).toEqual(1); 619 | } 620 | }); 621 | 622 | 623 | it('should allow providing factory functions', () { 624 | var module = moduleFactory()..bind(Engine, toFactory: () { 625 | return 'factory-product'; 626 | }, inject: []); 627 | 628 | var injector = new ModuleInjector([module]); 629 | var instance = injector.get(Engine); 630 | 631 | expect(instance).toEqual('factory-product'); 632 | }); 633 | 634 | it('should allow providing with emulated factory functions', () { 635 | var module = moduleFactory(); 636 | module.bind(Engine, toFactory: new EmulatedMockEngineFactory()); 637 | 638 | var injector = new ModuleInjector([module]); 639 | var instance = injector.get(Engine); 640 | 641 | expect(instance).toBeAnInstanceOf(MockEngine); 642 | }); 643 | 644 | it('should inject injector into factory function', () { 645 | var module = moduleFactory() 646 | ..bind(Engine) 647 | ..bind(Car, toFactory: (Engine engine, Injector injector) { 648 | return new Car(engine, injector); 649 | }, inject: [Engine, Injector]); 650 | 651 | var injector = new ModuleInjector([module]); 652 | var instance = injector.get(Car); 653 | 654 | expect(instance).toBeAnInstanceOf(Car); 655 | expect(instance.engine.id).toEqual('v8-id'); 656 | }); 657 | 658 | it('should throw an exception when injecting a primitive type', () { 659 | var injector = new ModuleInjector([ 660 | moduleFactory() 661 | ..bind(NumDependency) 662 | ..bind(IntDependency) 663 | ..bind(DoubleDependency) 664 | ..bind(BoolDependency) 665 | ..bind(StringDependency) 666 | ]); 667 | 668 | expect(() { 669 | injector.get(NumDependency); 670 | }).toThrowWith( 671 | anInstanceOf: NoProviderError, 672 | message: 'Cannot inject a primitive type of num! ' 673 | '(resolving NumDependency -> num)'); 674 | 675 | expect(() { 676 | injector.get(IntDependency); 677 | }).toThrowWith( 678 | anInstanceOf: NoProviderError, 679 | message: 'Cannot inject a primitive type of int! ' 680 | '(resolving IntDependency -> int)'); 681 | 682 | expect(() { 683 | injector.get(DoubleDependency); 684 | }).toThrowWith( 685 | anInstanceOf: NoProviderError, 686 | message: 'Cannot inject a primitive type of double! ' 687 | '(resolving DoubleDependency -> double)'); 688 | 689 | expect(() { 690 | injector.get(BoolDependency); 691 | }).toThrowWith( 692 | anInstanceOf: NoProviderError, 693 | message: 'Cannot inject a primitive type of bool! ' 694 | '(resolving BoolDependency -> bool)'); 695 | 696 | expect(() { 697 | injector.get(StringDependency); 698 | }).toThrowWith( 699 | anInstanceOf: NoProviderError, 700 | message: 'Cannot inject a primitive type of String! ' 701 | '(resolving StringDependency -> String)'); 702 | }); 703 | 704 | it('should throw an exception when circular dependency', () { 705 | var injector = new ModuleInjector([moduleFactory()..bind(CircularA) 706 | ..bind(CircularB)]); 707 | 708 | expect(() { 709 | injector.get(CircularA); 710 | }).toThrowWith( 711 | anInstanceOf: CircularDependencyError, 712 | message: 'Cannot resolve a circular dependency! ' 713 | '(resolving CircularA -> CircularB -> CircularA)'); 714 | }); 715 | 716 | it('should throw an exception when circular dependency in factory', () { 717 | var injector = new ModuleInjector([moduleFactory() 718 | ..bind(CircularA, toInstanceOf: CircularA) 719 | ]); 720 | 721 | expect(() { 722 | injector.get(CircularA); 723 | }).toThrowWith( 724 | anInstanceOf: CircularDependencyError, 725 | message: 'Cannot resolve a circular dependency! ' 726 | '(resolving CircularA -> CircularA)'); 727 | }); 728 | 729 | it('should recover from errors', () { 730 | var injector = new ModuleInjector([moduleFactory()..bind(ThrowOnce)]); 731 | throwOnceShouldThrow = true; 732 | 733 | var caught = false; 734 | try { 735 | injector.get(ThrowOnce); 736 | } catch (e, s) { 737 | caught = true; 738 | expect(injector.get(ThrowOnce)).toBeDefined(); 739 | } 740 | expect(caught).toEqual(true); 741 | }); 742 | 743 | it('should provide the injector as Injector', () { 744 | var injector = new ModuleInjector([]); 745 | 746 | expect(injector.get(Injector)).toBe(injector); 747 | }); 748 | 749 | 750 | it('should inject a typedef', () { 751 | var module = moduleFactory()..bind(CompareInt, toValue: compareIntAsc); 752 | 753 | var injector = new ModuleInjector([module]); 754 | var compare = injector.get(CompareInt); 755 | 756 | expect(compare(1, 2)).toEqual(1); 757 | expect(compare(5, 2)).toEqual(-1); 758 | }); 759 | 760 | it('should throw an exception when injecting typedef without providing it', () { 761 | expect(() { 762 | var injector = new ModuleInjector([moduleFactory()..bind(WithTypeDefDependency)]); 763 | injector.get(WithTypeDefDependency); 764 | }).toThrowWith(); 765 | }); 766 | 767 | it('should instantiate via the default/unnamed constructor', () { 768 | var injector = new ModuleInjector([moduleFactory()..bind(MultipleConstructors)]); 769 | MultipleConstructors instance = injector.get(MultipleConstructors); 770 | expect(instance.instantiatedVia).toEqual('default'); 771 | }); 772 | 773 | // CHILD INJECTORS 774 | it('should inject from child', () { 775 | var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); 776 | 777 | var parent = new ModuleInjector([moduleFactory()..bind(Engine)]); 778 | var child = new ModuleInjector([module], parent); 779 | 780 | var abcFromParent = parent.get(Engine); 781 | var abcFromChild = child.get(Engine); 782 | 783 | expect(abcFromParent.id).toEqual('v8-id'); 784 | expect(abcFromChild.id).toEqual('mock-id'); 785 | }); 786 | 787 | it('should enumerate across children', () { 788 | var parent = new ModuleInjector([moduleFactory()..bind(Engine)]); 789 | var child = new ModuleInjector([moduleFactory()..bind(MockEngine)], parent); 790 | 791 | expect(parent.types).to(matcher.unorderedEquals([Engine, Injector])); 792 | expect(child.types).to(matcher.unorderedEquals([Engine, MockEngine, Injector])); 793 | }); 794 | 795 | it('should inject instance from parent if not provided in child', () { 796 | var module = moduleFactory()..bind(Car); 797 | 798 | var parent = new ModuleInjector([moduleFactory()..bind(Car)..bind(Engine)]); 799 | var child = new ModuleInjector([module], parent); 800 | 801 | var complexFromParent = parent.get(Car); 802 | var complexFromChild = child.get(Car); 803 | var abcFromParent = parent.get(Engine); 804 | var abcFromChild = child.get(Engine); 805 | 806 | expect(complexFromChild).not.toBe(complexFromParent); 807 | expect(abcFromChild).toBe(abcFromParent); 808 | }); 809 | 810 | it('should inject instance from parent but never use dependency from child', () { 811 | var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); 812 | 813 | var parent = new ModuleInjector([moduleFactory()..bind(Car)..bind(Engine)]); 814 | var child = new ModuleInjector([module], parent); 815 | 816 | var complexFromParent = parent.get(Car); 817 | var complexFromChild = child.get(Car); 818 | var abcFromParent = parent.get(Engine); 819 | var abcFromChild = child.get(Engine); 820 | 821 | expect(complexFromChild).toBe(complexFromParent); 822 | expect(complexFromChild.engine).toBe(abcFromParent); 823 | expect(complexFromChild.engine).not.toBe(abcFromChild); 824 | }); 825 | 826 | it('should provide child injector as Injector', () { 827 | var injector = new ModuleInjector([]); 828 | var child = new ModuleInjector([], injector); 829 | 830 | expect(child.get(Injector)).toBe(child); 831 | }); 832 | 833 | it('should instantiate class only once (Issue #18)', () { 834 | var rootInjector = new ModuleInjector([]); 835 | var injector = new ModuleInjector([ 836 | moduleFactory() 837 | ..bind(Log) 838 | ..bind(ClassOne) 839 | ..bind(InterfaceOne, toInstanceOf: ClassOne) 840 | ], rootInjector); 841 | 842 | expect(injector.get(InterfaceOne)).toBe(injector.get(ClassOne)); 843 | expect(injector.get(Log).log.join(' ')).toEqual('ClassOne'); 844 | }); 845 | }); 846 | } 847 | 848 | 849 | testKey() { 850 | describe('Key', () { 851 | void expectEquals(x, y, bool truthValue) { 852 | expect(x == y).toEqual(truthValue); 853 | expect(identical(x, y)).toEqual(truthValue); 854 | if (truthValue == true) expect(x.hashCode).toEqual(y.hashCode); 855 | } 856 | 857 | it('should be equal to another key if type is the same', () { 858 | expectEquals(new Key(Car), new Key(Car), true); 859 | }); 860 | 861 | it('should be equal to another key if type and annotation are the same', () { 862 | expectEquals(new Key(Car, Turbo), new Key(Car, Turbo), true); 863 | }); 864 | 865 | it('should not be equal to another key where type and annotation are same ' 866 | 'but reversed', () { 867 | expectEquals(new Key(Car, Turbo), new Key(Turbo, Car), false); 868 | }); 869 | 870 | it('should not be equal to another key if types are different', () { 871 | expectEquals(new Key(Car), new Key(Porsche), false); 872 | }); 873 | 874 | it('should not be equal to another key if annotations are different', () { 875 | expectEquals(new Key(Car, Turbo), new Key(Car, Old), false); 876 | }); 877 | 878 | it('should not be equal to another key if type is different and annotation' 879 | ' is same', () { 880 | expectEquals(new Key(Engine, Old), new Key(Car, Old), false); 881 | }); 882 | 883 | it('should be equal to a mirrored key of the same type', () { 884 | ClassMirror classMirror = reflectType(Car); 885 | MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; 886 | 887 | ParameterMirror p = ctor.parameters[0]; 888 | var pType = (p.type as ClassMirror).reflectedType; 889 | 890 | expectEquals(new Key(Engine), new Key(pType), true); 891 | }); 892 | 893 | it('should support passing annotations instead of annotation types', () { 894 | expectEquals(new Key(Engine, Old), new Key(Engine, const Old()), true); 895 | }); 896 | }); 897 | } 898 | 899 | 900 | testCheckBindArgs() { 901 | describe('CheckBindArgs', () { 902 | var _ = DEFAULT_VALUE; 903 | it('should return true when args are well formed', () { 904 | expect(checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine, Car], null)).toBeTrue(); 905 | expect(checkBindArgs(_, () => 0, null, [], null)).toBeTrue(); 906 | expect(checkBindArgs(0, _, null, [], null)).toBeTrue(); 907 | expect(checkBindArgs(_, _, Car, [], null)).toBeTrue(); 908 | expect(checkBindArgs(_, _, null, [], Car)).toBeTrue(); 909 | expect(checkBindArgs(_, _, null, [], key(Car))).toBeTrue(); 910 | }); 911 | 912 | it('should error when wrong number of args have been set', () { 913 | expect(() => checkBindArgs(_, () => 0, Car, [], null)).toThrowWith(); 914 | expect(() => checkBindArgs(0, _, null, [Engine, Car], null)).toThrowWith(); 915 | expect(() => checkBindArgs(_, () => 0, null, [], Car)).toThrowWith(); 916 | }); 917 | 918 | it('should error when toFactory argument count does not match inject length', () { 919 | expect(() => checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine], null)).toThrowWith(); 920 | expect(() => checkBindArgs(_, () => 0, null, [Engine, Car], null)).toThrowWith(); 921 | }); 922 | }); 923 | } 924 | --------------------------------------------------------------------------------