| OLD | NEW |
| (Empty) |
| 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:di/transformer/options.dart'; | |
| 9 import 'package:path/path.dart' as path; | |
| 10 | |
| 11 import 'refactor.dart'; | |
| 12 | |
| 13 /** | |
| 14 * Pub transformer which generates type factories for all injectable types | |
| 15 * in the application. | |
| 16 */ | |
| 17 class InjectorGenerator extends Transformer with ResolverTransformer { | |
| 18 final TransformOptions options; | |
| 19 | |
| 20 InjectorGenerator(this.options, Resolvers resolvers) { | |
| 21 this.resolvers = resolvers; | |
| 22 } | |
| 23 | |
| 24 Future<bool> shouldApplyResolver(Asset asset) => options.isDartEntry(asset); | |
| 25 | |
| 26 applyResolver(Transform transform, Resolver resolver) => | |
| 27 new _Processor(transform, resolver, options).process(); | |
| 28 } | |
| 29 | |
| 30 /** Class for processing a single apply.*/ | |
| 31 class _Processor { | |
| 32 | |
| 33 /** Current transform. */ | |
| 34 final Transform transform; | |
| 35 | |
| 36 final Resolver resolver; | |
| 37 final TransformOptions options; | |
| 38 | |
| 39 /** Asset ID for the location of the generated file, for imports. */ | |
| 40 AssetId _generatedAssetId; | |
| 41 | |
| 42 /** Resolved injectable annotations of the form `@Injectable()`. */ | |
| 43 final List<TopLevelVariableElement> injectableMetaConsts = | |
| 44 <TopLevelVariableElement>[]; | |
| 45 | |
| 46 /** Resolved injectable annotations of the form `@injectable`. */ | |
| 47 final List<ConstructorElement> injectableMetaConstructors = | |
| 48 <ConstructorElement>[]; | |
| 49 | |
| 50 /** Default list of injectable consts */ | |
| 51 static const List<String> defaultInjectableMetaConsts = const [ | |
| 52 'inject.inject' | |
| 53 ]; | |
| 54 | |
| 55 _Processor(this.transform, this.resolver, this.options); | |
| 56 | |
| 57 TransformLogger get logger => transform.logger; | |
| 58 | |
| 59 process() { | |
| 60 _resolveInjectableMetadata(); | |
| 61 | |
| 62 var id = transform.primaryInput.id; | |
| 63 var outputFilename = '${path.url.basenameWithoutExtension(id.path)}' | |
| 64 '_static_injector.dart'; | |
| 65 var outputPath = path.url.join(path.url.dirname(id.path), outputFilename); | |
| 66 _generatedAssetId = new AssetId(id.package, outputPath); | |
| 67 | |
| 68 var constructors = _gatherConstructors(); | |
| 69 | |
| 70 var injectLibContents = _generateInjectLibrary(constructors); | |
| 71 transform.addOutput( | |
| 72 new Asset.fromString(_generatedAssetId, injectLibContents)); | |
| 73 | |
| 74 transformIdentifiers(transform, resolver, | |
| 75 identifier: 'di.auto_injector.defaultInjector', | |
| 76 replacement: 'createStaticInjector', | |
| 77 importPrefix: 'generated_static_injector', | |
| 78 importUrl: outputFilename); | |
| 79 } | |
| 80 | |
| 81 /** Resolves the classes for the injectable annotations in the current AST. */ | |
| 82 void _resolveInjectableMetadata() { | |
| 83 for (var constName in defaultInjectableMetaConsts) { | |
| 84 var variable = resolver.getLibraryVariable(constName); | |
| 85 if (variable != null) { | |
| 86 injectableMetaConsts.add(variable); | |
| 87 } | |
| 88 } | |
| 89 | |
| 90 // Resolve the user-specified annotations | |
| 91 // These may be either type names (constructors) or consts. | |
| 92 for (var metaName in options.injectableAnnotations) { | |
| 93 var variable = resolver.getLibraryVariable(metaName); | |
| 94 if (variable != null) { | |
| 95 injectableMetaConsts.add(variable); | |
| 96 continue; | |
| 97 } | |
| 98 var cls = resolver.getType(metaName); | |
| 99 if (cls != null && cls.unnamedConstructor != null) { | |
| 100 injectableMetaConstructors.add(cls.unnamedConstructor); | |
| 101 continue; | |
| 102 } | |
| 103 if (!DEFAULT_INJECTABLE_ANNOTATIONS.contains(metaName)) { | |
| 104 logger.warning('Unable to resolve injectable annotation $metaName'); | |
| 105 } | |
| 106 } | |
| 107 } | |
| 108 | |
| 109 /** Finds all annotated constructors or annotated classes in the program. */ | |
| 110 Iterable<ConstructorElement> _gatherConstructors() { | |
| 111 var constructors = resolver.libraries | |
| 112 .expand((lib) => lib.units) | |
| 113 .expand((compilationUnit) => compilationUnit.types) | |
| 114 .map(_findInjectedConstructor) | |
| 115 .where((ctor) => ctor != null).toList(); | |
| 116 | |
| 117 constructors.addAll(_gatherInjectablesContents()); | |
| 118 constructors.addAll(_gatherManuallyInjected()); | |
| 119 | |
| 120 return constructors.toSet(); | |
| 121 } | |
| 122 | |
| 123 /** | |
| 124 * Get the constructors for all elements in the library @Injectables | |
| 125 * statements. These are used to mark types as injectable which would | |
| 126 * otherwise not be injected. | |
| 127 * | |
| 128 * Syntax is: | |
| 129 * | |
| 130 * @Injectables(const[ElementName]) | |
| 131 * library my.library; | |
| 132 */ | |
| 133 Iterable<ConstructorElement> _gatherInjectablesContents() { | |
| 134 var injectablesClass = resolver.getType('di.annotations.Injectables'); | |
| 135 if (injectablesClass == null) return const []; | |
| 136 var injectablesCtor = injectablesClass.unnamedConstructor; | |
| 137 | |
| 138 var ctors = []; | |
| 139 | |
| 140 for (var lib in resolver.libraries) { | |
| 141 var annotationIdx = 0; | |
| 142 for (var annotation in lib.metadata) { | |
| 143 if (annotation.element == injectablesCtor) { | |
| 144 var libDirective = lib.definingCompilationUnit.node.directives | |
| 145 .where((d) => d is LibraryDirective).single; | |
| 146 var annotationDirective = libDirective.metadata[annotationIdx]; | |
| 147 var listLiteral = annotationDirective.arguments.arguments.first; | |
| 148 | |
| 149 for (var expr in listLiteral.elements) { | |
| 150 var element = (expr as SimpleIdentifier).bestElement; | |
| 151 if (element == null || element is! ClassElement) { | |
| 152 _warn('Unable to resolve class $expr', element); | |
| 153 continue; | |
| 154 } | |
| 155 var ctor = _findInjectedConstructor(element, true); | |
| 156 if (ctor != null) { | |
| 157 ctors.add(ctor); | |
| 158 } | |
| 159 } | |
| 160 } | |
| 161 } | |
| 162 } | |
| 163 return ctors; | |
| 164 } | |
| 165 | |
| 166 /** | |
| 167 * Finds all types which were manually specified as being injected in | |
| 168 * the options file. | |
| 169 */ | |
| 170 Iterable<ConstructorElement> _gatherManuallyInjected() { | |
| 171 var ctors = []; | |
| 172 for (var injectedName in options.injectedTypes) { | |
| 173 var injectedClass = resolver.getType(injectedName); | |
| 174 if (injectedClass == null) { | |
| 175 logger.warning('Unable to resolve injected type name $injectedName'); | |
| 176 continue; | |
| 177 } | |
| 178 var ctor = _findInjectedConstructor(injectedClass, true); | |
| 179 if (ctor != null) { | |
| 180 ctors.add(ctor); | |
| 181 } | |
| 182 } | |
| 183 return ctors; | |
| 184 } | |
| 185 | |
| 186 /** | |
| 187 * Checks if the element is annotated with one of the known injectablee | |
| 188 * annotations. | |
| 189 */ | |
| 190 bool _isElementAnnotated(Element e) { | |
| 191 for (var meta in e.metadata) { | |
| 192 if (meta.element is PropertyAccessorElement && | |
| 193 injectableMetaConsts.contains(meta.element.variable)) { | |
| 194 return true; | |
| 195 } else if (meta.element is ConstructorElement && | |
| 196 injectableMetaConstructors.contains(meta.element)) { | |
| 197 return true; | |
| 198 } | |
| 199 } | |
| 200 return false; | |
| 201 } | |
| 202 | |
| 203 /** | |
| 204 * Find an 'injected' constructor for the given class. | |
| 205 * If [noAnnotation] is true then this will assume that the class is marked | |
| 206 * for injection and will use the default constructor. | |
| 207 */ | |
| 208 ConstructorElement _findInjectedConstructor(ClassElement cls, | |
| 209 [bool noAnnotation = false]) { | |
| 210 var classInjectedConstructors = []; | |
| 211 if (_isElementAnnotated(cls) || noAnnotation) { | |
| 212 var defaultConstructor = cls.unnamedConstructor; | |
| 213 if (defaultConstructor == null) { | |
| 214 _warn('${cls.name} cannot be injected because ' | |
| 215 'it does not have a default constructor.', cls); | |
| 216 } else { | |
| 217 classInjectedConstructors.add(defaultConstructor); | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 classInjectedConstructors.addAll( | |
| 222 cls.constructors.where(_isElementAnnotated)); | |
| 223 | |
| 224 if (classInjectedConstructors.isEmpty) return null; | |
| 225 if (classInjectedConstructors.length > 1) { | |
| 226 _warn('${cls.name} has more than one constructor annotated for ' | |
| 227 'injection.', cls); | |
| 228 return null; | |
| 229 } | |
| 230 | |
| 231 var ctor = classInjectedConstructors.single; | |
| 232 if (!_validateConstructor(ctor)) return null; | |
| 233 | |
| 234 return ctor; | |
| 235 } | |
| 236 | |
| 237 /** | |
| 238 * Validates that the constructor is injectable and emits warnings for any | |
| 239 * errors. | |
| 240 */ | |
| 241 bool _validateConstructor(ConstructorElement ctor) { | |
| 242 var cls = ctor.enclosingElement; | |
| 243 if (cls.isAbstract && !ctor.isFactory) { | |
| 244 _warn('${cls.name} cannot be injected because ' | |
| 245 'it is an abstract type with no factory constructor.', cls); | |
| 246 return false; | |
| 247 } | |
| 248 if (cls.isPrivate) { | |
| 249 _warn('${cls.name} cannot be injected because it is a private type.', | |
| 250 cls); | |
| 251 return false; | |
| 252 } | |
| 253 if (resolver.getImportUri(cls.library, from: _generatedAssetId) == null) { | |
| 254 _warn('${cls.name} cannot be injected because ' | |
| 255 'the containing file cannot be imported.', cls); | |
| 256 return false; | |
| 257 } | |
| 258 if (!cls.typeParameters.isEmpty) { | |
| 259 _warn('${cls.name} is a parameterized type.', cls); | |
| 260 // Only warn. | |
| 261 } | |
| 262 if (ctor.name != '') { | |
| 263 _warn('Named constructors cannot be injected.', ctor); | |
| 264 return false; | |
| 265 } | |
| 266 for (var param in ctor.parameters) { | |
| 267 var type = param.type; | |
| 268 if (type is InterfaceType && | |
| 269 type.typeArguments.any((t) => !t.isDynamic)) { | |
| 270 _warn('${cls.name} cannot be injected because ' | |
| 271 '${param.type} is a parameterized type.', ctor); | |
| 272 return false; | |
| 273 } | |
| 274 if (type.isDynamic) { | |
| 275 _warn('${cls.name} cannot be injected because parameter type ' | |
| 276 '${param.name} cannot be resolved.', ctor); | |
| 277 return false; | |
| 278 } | |
| 279 } | |
| 280 return true; | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * Creates a library file for the specified constructors. | |
| 285 */ | |
| 286 String _generateInjectLibrary(Iterable<ConstructorElement> constructors) { | |
| 287 var prefixes = <LibraryElement, String>{}; | |
| 288 | |
| 289 var ctorTypes = constructors.map((ctor) => ctor.enclosingElement).toSet(); | |
| 290 var paramTypes = constructors.expand((ctor) => ctor.parameters) | |
| 291 .map((param) => param.type.element).toSet(); | |
| 292 | |
| 293 var usedLibs = new Set<LibraryElement>(); | |
| 294 String resolveClassName(ClassElement type) { | |
| 295 var library = type.library; | |
| 296 usedLibs.add(library); | |
| 297 | |
| 298 var prefix = prefixes[library]; | |
| 299 if (prefix == null) { | |
| 300 prefix = prefixes[library] = | |
| 301 library.isDartCore ? '' : 'import_${prefixes.length}'; | |
| 302 } | |
| 303 if (prefix.isNotEmpty) { | |
| 304 prefix = '$prefix.'; | |
| 305 } | |
| 306 return '$prefix${type.name}'; | |
| 307 } | |
| 308 | |
| 309 var factoriesBuffer = new StringBuffer(); | |
| 310 for (var ctor in constructors) { | |
| 311 var type = ctor.enclosingElement; | |
| 312 var typeName = resolveClassName(type); | |
| 313 factoriesBuffer.write(' $typeName: (f) => new $typeName('); | |
| 314 var params = ctor.parameters.map((param) { | |
| 315 var typeName = resolveClassName(param.type.element); | |
| 316 var annotations = []; | |
| 317 if (param.metadata.isNotEmpty) { | |
| 318 annotations = param.metadata.map( | |
| 319 (item) => resolveClassName(item.element.returnType.element)); | |
| 320 } | |
| 321 var annotationsSuffix = | |
| 322 annotations.isNotEmpty ? ', ${annotations.first}' : ''; | |
| 323 return 'f($typeName$annotationsSuffix)'; | |
| 324 }); | |
| 325 factoriesBuffer.write('${params.join(', ')}),\n'); | |
| 326 } | |
| 327 | |
| 328 var outputBuffer = new StringBuffer(); | |
| 329 | |
| 330 _writeStaticInjectorHeader(transform.primaryInput.id, outputBuffer); | |
| 331 usedLibs.forEach((lib) { | |
| 332 if (lib.isDartCore) return; | |
| 333 var uri = resolver.getImportUri(lib, from: _generatedAssetId); | |
| 334 outputBuffer.write('import \'$uri\' as ${prefixes[lib]};\n'); | |
| 335 }); | |
| 336 _writePreamble(outputBuffer); | |
| 337 outputBuffer.write(factoriesBuffer); | |
| 338 _writeFooter(outputBuffer); | |
| 339 | |
| 340 return outputBuffer.toString(); | |
| 341 } | |
| 342 | |
| 343 void _warn(String msg, Element element) { | |
| 344 logger.warning(msg, asset: resolver.getSourceAssetId(element), | |
| 345 span: resolver.getSourceSpan(element)); | |
| 346 } | |
| 347 } | |
| 348 | |
| 349 void _writeStaticInjectorHeader(AssetId id, StringSink sink) { | |
| 350 var libName = path.withoutExtension(id.path).replaceAll('/', '.'); | |
| 351 libName = libName.replaceAll('-', '_'); | |
| 352 sink.write(''' | |
| 353 library ${id.package}.$libName.generated_static_injector; | |
| 354 | |
| 355 import 'package:di/di.dart'; | |
| 356 import 'package:di/static_injector.dart'; | |
| 357 | |
| 358 '''); | |
| 359 } | |
| 360 | |
| 361 void _writePreamble(StringSink sink) { | |
| 362 sink.write(''' | |
| 363 Injector createStaticInjector({List<Module> modules, String name, | |
| 364 bool allowImplicitInjection: false}) => | |
| 365 new StaticInjector(modules: modules, name: name, | |
| 366 allowImplicitInjection: allowImplicitInjection, | |
| 367 typeFactories: factories); | |
| 368 | |
| 369 final Map<Type, TypeFactory> factories = <Type, TypeFactory>{ | |
| 370 '''); | |
| 371 } | |
| 372 | |
| 373 void _writeFooter(StringSink sink) { | |
| 374 sink.write(''' | |
| 375 }; | |
| 376 '''); | |
| 377 } | |
| OLD | NEW |