| Index: third_party/pkg/di/lib/transformer/injector_generator.dart
|
| diff --git a/third_party/pkg/di/lib/transformer/injector_generator.dart b/third_party/pkg/di/lib/transformer/injector_generator.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a6af1260476909948afc2cf77eecb0b94e9ab105
|
| --- /dev/null
|
| +++ b/third_party/pkg/di/lib/transformer/injector_generator.dart
|
| @@ -0,0 +1,377 @@
|
| +library di.transformer.injector_generator;
|
| +
|
| +import 'dart:async';
|
| +import 'package:analyzer/src/generated/ast.dart';
|
| +import 'package:analyzer/src/generated/element.dart';
|
| +import 'package:barback/barback.dart';
|
| +import 'package:code_transformers/resolver.dart';
|
| +import 'package:di/transformer/options.dart';
|
| +import 'package:path/path.dart' as path;
|
| +
|
| +import 'refactor.dart';
|
| +
|
| +/**
|
| + * Pub transformer which generates type factories for all injectable types
|
| + * in the application.
|
| + */
|
| +class InjectorGenerator extends Transformer with ResolverTransformer {
|
| + final TransformOptions options;
|
| +
|
| + InjectorGenerator(this.options, Resolvers resolvers) {
|
| + this.resolvers = resolvers;
|
| + }
|
| +
|
| + Future<bool> shouldApplyResolver(Asset asset) => options.isDartEntry(asset);
|
| +
|
| + applyResolver(Transform transform, Resolver resolver) =>
|
| + new _Processor(transform, resolver, options).process();
|
| +}
|
| +
|
| +/** Class for processing a single apply.*/
|
| +class _Processor {
|
| +
|
| + /** Current transform. */
|
| + final Transform transform;
|
| +
|
| + final Resolver resolver;
|
| + final TransformOptions options;
|
| +
|
| + /** Asset ID for the location of the generated file, for imports. */
|
| + AssetId _generatedAssetId;
|
| +
|
| + /** Resolved injectable annotations of the form `@Injectable()`. */
|
| + final List<TopLevelVariableElement> injectableMetaConsts =
|
| + <TopLevelVariableElement>[];
|
| +
|
| + /** Resolved injectable annotations of the form `@injectable`. */
|
| + final List<ConstructorElement> injectableMetaConstructors =
|
| + <ConstructorElement>[];
|
| +
|
| + /** Default list of injectable consts */
|
| + static const List<String> defaultInjectableMetaConsts = const [
|
| + 'inject.inject'
|
| + ];
|
| +
|
| + _Processor(this.transform, this.resolver, this.options);
|
| +
|
| + TransformLogger get logger => transform.logger;
|
| +
|
| + process() {
|
| + _resolveInjectableMetadata();
|
| +
|
| + var id = transform.primaryInput.id;
|
| + var outputFilename = '${path.url.basenameWithoutExtension(id.path)}'
|
| + '_static_injector.dart';
|
| + var outputPath = path.url.join(path.url.dirname(id.path), outputFilename);
|
| + _generatedAssetId = new AssetId(id.package, outputPath);
|
| +
|
| + var constructors = _gatherConstructors();
|
| +
|
| + var injectLibContents = _generateInjectLibrary(constructors);
|
| + transform.addOutput(
|
| + new Asset.fromString(_generatedAssetId, injectLibContents));
|
| +
|
| + transformIdentifiers(transform, resolver,
|
| + identifier: 'di.auto_injector.defaultInjector',
|
| + replacement: 'createStaticInjector',
|
| + importPrefix: 'generated_static_injector',
|
| + importUrl: outputFilename);
|
| + }
|
| +
|
| + /** Resolves the classes for the injectable annotations in the current AST. */
|
| + void _resolveInjectableMetadata() {
|
| + for (var constName in defaultInjectableMetaConsts) {
|
| + var variable = resolver.getLibraryVariable(constName);
|
| + if (variable != null) {
|
| + injectableMetaConsts.add(variable);
|
| + }
|
| + }
|
| +
|
| + // Resolve the user-specified annotations
|
| + // These may be either type names (constructors) or consts.
|
| + for (var metaName in options.injectableAnnotations) {
|
| + var variable = resolver.getLibraryVariable(metaName);
|
| + if (variable != null) {
|
| + injectableMetaConsts.add(variable);
|
| + continue;
|
| + }
|
| + var cls = resolver.getType(metaName);
|
| + if (cls != null && cls.unnamedConstructor != null) {
|
| + injectableMetaConstructors.add(cls.unnamedConstructor);
|
| + continue;
|
| + }
|
| + if (!DEFAULT_INJECTABLE_ANNOTATIONS.contains(metaName)) {
|
| + logger.warning('Unable to resolve injectable annotation $metaName');
|
| + }
|
| + }
|
| + }
|
| +
|
| + /** Finds all annotated constructors or annotated classes in the program. */
|
| + Iterable<ConstructorElement> _gatherConstructors() {
|
| + var constructors = resolver.libraries
|
| + .expand((lib) => lib.units)
|
| + .expand((compilationUnit) => compilationUnit.types)
|
| + .map(_findInjectedConstructor)
|
| + .where((ctor) => ctor != null).toList();
|
| +
|
| + constructors.addAll(_gatherInjectablesContents());
|
| + constructors.addAll(_gatherManuallyInjected());
|
| +
|
| + return constructors.toSet();
|
| + }
|
| +
|
| + /**
|
| + * Get the constructors for all elements in the library @Injectables
|
| + * statements. These are used to mark types as injectable which would
|
| + * otherwise not be injected.
|
| + *
|
| + * Syntax is:
|
| + *
|
| + * @Injectables(const[ElementName])
|
| + * library my.library;
|
| + */
|
| + Iterable<ConstructorElement> _gatherInjectablesContents() {
|
| + var injectablesClass = resolver.getType('di.annotations.Injectables');
|
| + if (injectablesClass == null) return const [];
|
| + var injectablesCtor = injectablesClass.unnamedConstructor;
|
| +
|
| + var ctors = [];
|
| +
|
| + for (var lib in resolver.libraries) {
|
| + var annotationIdx = 0;
|
| + for (var annotation in lib.metadata) {
|
| + if (annotation.element == injectablesCtor) {
|
| + var libDirective = lib.definingCompilationUnit.node.directives
|
| + .where((d) => d is LibraryDirective).single;
|
| + var annotationDirective = libDirective.metadata[annotationIdx];
|
| + var listLiteral = annotationDirective.arguments.arguments.first;
|
| +
|
| + for (var expr in listLiteral.elements) {
|
| + var element = (expr as SimpleIdentifier).bestElement;
|
| + if (element == null || element is! ClassElement) {
|
| + _warn('Unable to resolve class $expr', element);
|
| + continue;
|
| + }
|
| + var ctor = _findInjectedConstructor(element, true);
|
| + if (ctor != null) {
|
| + ctors.add(ctor);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return ctors;
|
| + }
|
| +
|
| + /**
|
| + * Finds all types which were manually specified as being injected in
|
| + * the options file.
|
| + */
|
| + Iterable<ConstructorElement> _gatherManuallyInjected() {
|
| + var ctors = [];
|
| + for (var injectedName in options.injectedTypes) {
|
| + var injectedClass = resolver.getType(injectedName);
|
| + if (injectedClass == null) {
|
| + logger.warning('Unable to resolve injected type name $injectedName');
|
| + continue;
|
| + }
|
| + var ctor = _findInjectedConstructor(injectedClass, true);
|
| + if (ctor != null) {
|
| + ctors.add(ctor);
|
| + }
|
| + }
|
| + return ctors;
|
| + }
|
| +
|
| + /**
|
| + * Checks if the element is annotated with one of the known injectablee
|
| + * annotations.
|
| + */
|
| + bool _isElementAnnotated(Element e) {
|
| + for (var meta in e.metadata) {
|
| + if (meta.element is PropertyAccessorElement &&
|
| + injectableMetaConsts.contains(meta.element.variable)) {
|
| + return true;
|
| + } else if (meta.element is ConstructorElement &&
|
| + injectableMetaConstructors.contains(meta.element)) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Find an 'injected' constructor for the given class.
|
| + * If [noAnnotation] is true then this will assume that the class is marked
|
| + * for injection and will use the default constructor.
|
| + */
|
| + ConstructorElement _findInjectedConstructor(ClassElement cls,
|
| + [bool noAnnotation = false]) {
|
| + var classInjectedConstructors = [];
|
| + if (_isElementAnnotated(cls) || noAnnotation) {
|
| + var defaultConstructor = cls.unnamedConstructor;
|
| + if (defaultConstructor == null) {
|
| + _warn('${cls.name} cannot be injected because '
|
| + 'it does not have a default constructor.', cls);
|
| + } else {
|
| + classInjectedConstructors.add(defaultConstructor);
|
| + }
|
| + }
|
| +
|
| + classInjectedConstructors.addAll(
|
| + cls.constructors.where(_isElementAnnotated));
|
| +
|
| + if (classInjectedConstructors.isEmpty) return null;
|
| + if (classInjectedConstructors.length > 1) {
|
| + _warn('${cls.name} has more than one constructor annotated for '
|
| + 'injection.', cls);
|
| + return null;
|
| + }
|
| +
|
| + var ctor = classInjectedConstructors.single;
|
| + if (!_validateConstructor(ctor)) return null;
|
| +
|
| + return ctor;
|
| + }
|
| +
|
| + /**
|
| + * Validates that the constructor is injectable and emits warnings for any
|
| + * errors.
|
| + */
|
| + bool _validateConstructor(ConstructorElement ctor) {
|
| + var cls = ctor.enclosingElement;
|
| + if (cls.isAbstract && !ctor.isFactory) {
|
| + _warn('${cls.name} cannot be injected because '
|
| + 'it is an abstract type with no factory constructor.', cls);
|
| + return false;
|
| + }
|
| + if (cls.isPrivate) {
|
| + _warn('${cls.name} cannot be injected because it is a private type.',
|
| + cls);
|
| + return false;
|
| + }
|
| + if (resolver.getImportUri(cls.library, from: _generatedAssetId) == null) {
|
| + _warn('${cls.name} cannot be injected because '
|
| + 'the containing file cannot be imported.', cls);
|
| + return false;
|
| + }
|
| + if (!cls.typeParameters.isEmpty) {
|
| + _warn('${cls.name} is a parameterized type.', cls);
|
| + // Only warn.
|
| + }
|
| + if (ctor.name != '') {
|
| + _warn('Named constructors cannot be injected.', ctor);
|
| + return false;
|
| + }
|
| + for (var param in ctor.parameters) {
|
| + var type = param.type;
|
| + if (type is InterfaceType &&
|
| + type.typeArguments.any((t) => !t.isDynamic)) {
|
| + _warn('${cls.name} cannot be injected because '
|
| + '${param.type} is a parameterized type.', ctor);
|
| + return false;
|
| + }
|
| + if (type.isDynamic) {
|
| + _warn('${cls.name} cannot be injected because parameter type '
|
| + '${param.name} cannot be resolved.', ctor);
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Creates a library file for the specified constructors.
|
| + */
|
| + String _generateInjectLibrary(Iterable<ConstructorElement> constructors) {
|
| + var prefixes = <LibraryElement, String>{};
|
| +
|
| + var ctorTypes = constructors.map((ctor) => ctor.enclosingElement).toSet();
|
| + var paramTypes = constructors.expand((ctor) => ctor.parameters)
|
| + .map((param) => param.type.element).toSet();
|
| +
|
| + var usedLibs = new Set<LibraryElement>();
|
| + String resolveClassName(ClassElement type) {
|
| + var library = type.library;
|
| + usedLibs.add(library);
|
| +
|
| + var prefix = prefixes[library];
|
| + if (prefix == null) {
|
| + prefix = prefixes[library] =
|
| + library.isDartCore ? '' : 'import_${prefixes.length}';
|
| + }
|
| + if (prefix.isNotEmpty) {
|
| + prefix = '$prefix.';
|
| + }
|
| + return '$prefix${type.name}';
|
| + }
|
| +
|
| + var factoriesBuffer = new StringBuffer();
|
| + for (var ctor in constructors) {
|
| + var type = ctor.enclosingElement;
|
| + var typeName = resolveClassName(type);
|
| + factoriesBuffer.write(' $typeName: (f) => new $typeName(');
|
| + var params = ctor.parameters.map((param) {
|
| + var typeName = resolveClassName(param.type.element);
|
| + var annotations = [];
|
| + if (param.metadata.isNotEmpty) {
|
| + annotations = param.metadata.map(
|
| + (item) => resolveClassName(item.element.returnType.element));
|
| + }
|
| + var annotationsSuffix =
|
| + annotations.isNotEmpty ? ', ${annotations.first}' : '';
|
| + return 'f($typeName$annotationsSuffix)';
|
| + });
|
| + factoriesBuffer.write('${params.join(', ')}),\n');
|
| + }
|
| +
|
| + var outputBuffer = new StringBuffer();
|
| +
|
| + _writeStaticInjectorHeader(transform.primaryInput.id, outputBuffer);
|
| + usedLibs.forEach((lib) {
|
| + if (lib.isDartCore) return;
|
| + var uri = resolver.getImportUri(lib, from: _generatedAssetId);
|
| + outputBuffer.write('import \'$uri\' as ${prefixes[lib]};\n');
|
| + });
|
| + _writePreamble(outputBuffer);
|
| + outputBuffer.write(factoriesBuffer);
|
| + _writeFooter(outputBuffer);
|
| +
|
| + return outputBuffer.toString();
|
| + }
|
| +
|
| + void _warn(String msg, Element element) {
|
| + logger.warning(msg, asset: resolver.getSourceAssetId(element),
|
| + span: resolver.getSourceSpan(element));
|
| + }
|
| +}
|
| +
|
| +void _writeStaticInjectorHeader(AssetId id, StringSink sink) {
|
| + var libName = path.withoutExtension(id.path).replaceAll('/', '.');
|
| + libName = libName.replaceAll('-', '_');
|
| + sink.write('''
|
| +library ${id.package}.$libName.generated_static_injector;
|
| +
|
| +import 'package:di/di.dart';
|
| +import 'package:di/static_injector.dart';
|
| +
|
| +''');
|
| +}
|
| +
|
| +void _writePreamble(StringSink sink) {
|
| + sink.write('''
|
| +Injector createStaticInjector({List<Module> modules, String name,
|
| + bool allowImplicitInjection: false}) =>
|
| + new StaticInjector(modules: modules, name: name,
|
| + allowImplicitInjection: allowImplicitInjection,
|
| + typeFactories: factories);
|
| +
|
| +final Map<Type, TypeFactory> factories = <Type, TypeFactory>{
|
| +''');
|
| +}
|
| +
|
| +void _writeFooter(StringSink sink) {
|
| + sink.write('''
|
| +};
|
| +''');
|
| +}
|
|
|