| Index: lib/build/initializer_plugin.dart
 | 
| diff --git a/lib/build/initializer_plugin.dart b/lib/build/initializer_plugin.dart
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..035161ea427d3ceb138ddb4d127bb4103bd1cbc8
 | 
| --- /dev/null
 | 
| +++ b/lib/build/initializer_plugin.dart
 | 
| @@ -0,0 +1,265 @@
 | 
| +// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 | 
| +// for details. All rights reserved. Use of this source code is governed by a
 | 
| +// BSD-style license that can be found in the LICENSE file.
 | 
| +library initialize.build.initializer_plugin;
 | 
| +
 | 
| +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:initialize/transformer.dart';
 | 
| +import 'package:path/path.dart' as path;
 | 
| +
 | 
| +/// A plug which allows an initializer to write out an [InitEntry] given some
 | 
| +/// [InitializerData] from an annotation that was found.
 | 
| +abstract class InitializerPlugin {
 | 
| +  /// Whether or not this plugin should be applied to an [Initializer] given
 | 
| +  /// some [InitializerData]. If [true] is returned then this plugin will take
 | 
| +  /// ownership of this [InitializerData] and no subsequent plugins will have
 | 
| +  /// an opportunity to access it.
 | 
| +  bool shouldApply(InitializerPluginData data);
 | 
| +
 | 
| +  /// Returns a [String] or [null]. The [String] should represent dart code
 | 
| +  /// which creates a new [InitEntry] and that entry is added to the static
 | 
| +  /// initializers list. If [null] is returned then no entry is added at all for
 | 
| +  /// this [InitializerData].
 | 
| +  String apply(InitializerPluginData data);
 | 
| +}
 | 
| +
 | 
| +/// A class which wraps all the default data passed to an [InitializerPlugin]
 | 
| +/// for each annotation.
 | 
| +class InitializerPluginData {
 | 
| +  final InitializerData initializer;
 | 
| +  final AssetId bootstrapId;
 | 
| +  final Map<LibraryElement, String> libraryPrefixes;
 | 
| +  final TransformLogger logger;
 | 
| +  final Resolver resolver;
 | 
| +  InitializerPluginData(this.initializer, this.bootstrapId,
 | 
| +      this.libraryPrefixes, this.resolver, this.logger);
 | 
| +}
 | 
| +
 | 
| +/// The basic [InitializerPlugin]. This generates a new [InitEntry] to be added
 | 
| +/// to the static initializers list, and applies to every item it sees.
 | 
| +class DefaultInitializerPlugin implements InitializerPlugin {
 | 
| +  const DefaultInitializerPlugin();
 | 
| +
 | 
| +  /// Applies to everything. Put other plugins before this one to override this
 | 
| +  /// behaviour.
 | 
| +  bool shouldApply(InitializerPluginData data) => true;
 | 
| +
 | 
| +  /// Creates a normal [InitEntry] string.
 | 
| +  String apply(InitializerPluginData pluginData) {
 | 
| +    var target = buildTarget(pluginData);
 | 
| +    var meta = buildMeta(pluginData);
 | 
| +    return 'new InitEntry($meta, $target)';
 | 
| +  }
 | 
| +
 | 
| +  /// Builds a [String] representing the meta of an [InitEntry] given an
 | 
| +  /// [ElementAnnotation] that was found.
 | 
| +  String buildMeta(InitializerPluginData pluginData) {
 | 
| +    var logger = pluginData.logger;
 | 
| +    var element = pluginData.initializer.targetElement;
 | 
| +    var elementAnnotation = pluginData.initializer.annotationElement;
 | 
| +    var elementAnnotationElement = elementAnnotation.element;
 | 
| +    var libraryPrefixes = pluginData.libraryPrefixes;
 | 
| +    if (elementAnnotationElement is ConstructorElement) {
 | 
| +      return buildConstructorMeta(elementAnnotation, pluginData);
 | 
| +    } else if (elementAnnotationElement is PropertyAccessorElement) {
 | 
| +      return buildPropertyMeta(elementAnnotation, pluginData);
 | 
| +    } else {
 | 
| +      logger.error('Unsupported annotation type. Only constructors and '
 | 
| +          'properties are supported as initializers.');
 | 
| +    }
 | 
| +    return null;
 | 
| +  }
 | 
| +
 | 
| +  /// Builds a [String] representing the meta of an [InitEntry] given an
 | 
| +  /// [ElementAnnotation] whose element was a [ConstructorElement].
 | 
| +  String buildConstructorMeta(
 | 
| +      ElementAnnotation elementAnnotation, InitializerPluginData pluginData) {
 | 
| +    var logger = pluginData.logger;
 | 
| +    var node = pluginData.initializer.targetNode;
 | 
| +    var metaPrefix =
 | 
| +        pluginData.libraryPrefixes[elementAnnotation.element.library];
 | 
| +
 | 
| +    var annotation = pluginData.initializer.annotationNode;
 | 
| +    if (annotation == null) {
 | 
| +      logger.error(
 | 
| +          'Initializer annotations are only supported on libraries, classes, '
 | 
| +          'and top level methods. Found $node.');
 | 
| +    }
 | 
| +    var clazz = annotation.name;
 | 
| +    var constructor = annotation.constructorName == null
 | 
| +        ? ''
 | 
| +        : '.${annotation.constructorName}';
 | 
| +    // TODO(jakemac): Support more than raw values here
 | 
| +    // https://github.com/dart-lang/static_init/issues/5
 | 
| +    var args = buildArgumentList(annotation.arguments, pluginData);
 | 
| +    return 'const $metaPrefix.${clazz}$constructor$args';
 | 
| +  }
 | 
| +
 | 
| +  /// Builds a [String] representing the meta of an [InitEntry] given an
 | 
| +  /// [ElementAnnotation] whose element was a [PropertyAccessorElement].
 | 
| +  String buildPropertyMeta(
 | 
| +      ElementAnnotation annotation, InitializerPluginData pluginData) {
 | 
| +    var metaPrefix = pluginData.libraryPrefixes[annotation.element.library];
 | 
| +    return '$metaPrefix.${annotation.element.name}';
 | 
| +  }
 | 
| +
 | 
| +  /// Builds a [String] for the target of an [InitEntry] given an [Element] that
 | 
| +  /// was annotated.
 | 
| +  String buildTarget(InitializerPluginData pluginData) {
 | 
| +    var element = pluginData.initializer.targetElement;
 | 
| +    var logger = pluginData.logger;
 | 
| +    if (element is LibraryElement) {
 | 
| +      return buildLibraryTarget(element, pluginData);
 | 
| +    } else if (element is ClassElement) {
 | 
| +      return buildClassTarget(element, pluginData);
 | 
| +    } else if (element is FunctionElement) {
 | 
| +      return buildFunctionTarget(element, pluginData);
 | 
| +    } else {
 | 
| +      logger.error('Initializers can only be applied to top level functions, '
 | 
| +          'libraries, and classes.');
 | 
| +    }
 | 
| +    return null;
 | 
| +  }
 | 
| +
 | 
| +  /// Builds a [String] for the target of an [InitEntry] given [element] which
 | 
| +  /// is an annotated class.
 | 
| +  String buildClassTarget(
 | 
| +          ClassElement element, InitializerPluginData pluginData) =>
 | 
| +      buildSimpleTarget(element, pluginData);
 | 
| +
 | 
| +  /// Builds a [String] for the target of an [InitEntry] given [element] which
 | 
| +  /// is an annotated function.
 | 
| +  String buildFunctionTarget(
 | 
| +          FunctionElement element, InitializerPluginData pluginData) =>
 | 
| +      buildSimpleTarget(element, pluginData);
 | 
| +
 | 
| +  /// Builds a [String] for the target of an [InitEntry] for a simple [Element].
 | 
| +  /// This is just the library prefix followed by the element name.
 | 
| +  String buildSimpleTarget(Element element, InitializerPluginData pluginData) =>
 | 
| +      '${pluginData.libraryPrefixes[element.library]}.${element.name}';
 | 
| +
 | 
| +  /// Builds a [String] for the target of an [InitEntry] given [element] which
 | 
| +  /// is an annotated library.
 | 
| +  String buildLibraryTarget(
 | 
| +      LibraryElement element, InitializerPluginData pluginData) {
 | 
| +    var bootstrapId = pluginData.bootstrapId;
 | 
| +    var logger = pluginData.logger;
 | 
| +    var segments = element.source.uri.pathSegments;
 | 
| +    var package = segments[0];
 | 
| +    var libraryPath;
 | 
| +    var packageString;
 | 
| +    if (bootstrapId.package == package &&
 | 
| +        bootstrapId.path.startsWith('${segments[1]}/')) {
 | 
| +      // reset `package` to null, we will do a relative path in this case.
 | 
| +      packageString = 'null';
 | 
| +      libraryPath = path.url.relative(
 | 
| +          path.url.joinAll(segments.getRange(1, segments.length)),
 | 
| +          from: path.url.dirname(path.url.join(bootstrapId.path)));
 | 
| +    } else if (segments[1] == 'lib') {
 | 
| +      packageString = "'$package'";
 | 
| +      libraryPath = path.url.joinAll(segments.getRange(2, segments.length));
 | 
| +    } else {
 | 
| +      logger.error('Unable to import `${element.source.uri.path}` from '
 | 
| +          '${bootstrapId.path}.');
 | 
| +    }
 | 
| +
 | 
| +    return "const LibraryIdentifier"
 | 
| +        "(#${element.name}, $packageString, '$libraryPath')";
 | 
| +  }
 | 
| +
 | 
| +  /// Builds a [String] representing an [ArgumentList] taking into account the
 | 
| +  /// [libraryPrefixes] from [pluginData].
 | 
| +  String buildArgumentList(
 | 
| +      ArgumentList args, InitializerPluginData pluginData) {
 | 
| +    var buffer = new StringBuffer();
 | 
| +    buffer.write('(');
 | 
| +    var first = true;
 | 
| +    for (var arg in args.arguments) {
 | 
| +      if (!first) buffer.write(', ');
 | 
| +      first = false;
 | 
| +
 | 
| +      Expression expression;
 | 
| +      if (arg is NamedExpression) {
 | 
| +        buffer.write('${arg.name.label.name}: ');
 | 
| +        expression = arg.expression;
 | 
| +      } else {
 | 
| +        expression = arg;
 | 
| +      }
 | 
| +
 | 
| +      buffer.write(buildExpression(expression, pluginData));
 | 
| +    }
 | 
| +    buffer.write(')');
 | 
| +    return buffer.toString();
 | 
| +  }
 | 
| +
 | 
| +  /// Builds a [String] representing [expression] taking into account the
 | 
| +  /// [libraryPrefixes] from [pluginData].
 | 
| +  String buildExpression(
 | 
| +      Expression expression, InitializerPluginData pluginData) {
 | 
| +    var logger = pluginData.logger;
 | 
| +    var libraryPrefixes = pluginData.libraryPrefixes;
 | 
| +    var buffer = new StringBuffer();
 | 
| +    if (expression is StringLiteral) {
 | 
| +      var value = expression.stringValue;
 | 
| +      if (value == null) {
 | 
| +        logger.error('Only const strings are allowed in initializer '
 | 
| +            'expressions, found $expression');
 | 
| +      }
 | 
| +      value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'");
 | 
| +      buffer.write("'$value'");
 | 
| +    } else if (expression is BooleanLiteral ||
 | 
| +        expression is DoubleLiteral ||
 | 
| +        expression is IntegerLiteral ||
 | 
| +        expression is NullLiteral) {
 | 
| +      buffer.write('${expression}');
 | 
| +    } else if (expression is ListLiteral) {
 | 
| +      buffer.write('const [');
 | 
| +      var first = true;
 | 
| +      for (Expression listExpression in expression.elements) {
 | 
| +        if (!first) buffer.write(', ');
 | 
| +        first = false;
 | 
| +        buffer.write(buildExpression(listExpression, pluginData));
 | 
| +      }
 | 
| +      buffer.write(']');
 | 
| +    } else if (expression is MapLiteral) {
 | 
| +      buffer.write('const {');
 | 
| +      var first = true;
 | 
| +      for (MapLiteralEntry entry in expression.entries) {
 | 
| +        if (!first) buffer.write(', ');
 | 
| +        first = false;
 | 
| +        buffer.write(buildExpression(entry.key, pluginData));
 | 
| +        buffer.write(': ');
 | 
| +        buffer.write(buildExpression(entry.value, pluginData));
 | 
| +      }
 | 
| +      buffer.write('}');
 | 
| +    } else if (expression is Identifier) {
 | 
| +      var element = expression.bestElement;
 | 
| +      if (element == null || !element.isPublic) {
 | 
| +        logger.error('Private constants are not supported in intializer '
 | 
| +            'constructors, found $element.');
 | 
| +      }
 | 
| +      libraryPrefixes.putIfAbsent(
 | 
| +          element.library, () => 'i${libraryPrefixes.length}');
 | 
| +
 | 
| +      buffer.write('${libraryPrefixes[element.library]}.');
 | 
| +      if (element is ClassElement) {
 | 
| +        buffer.write(element.name);
 | 
| +      } else if (element is PropertyAccessorElement) {
 | 
| +        var variable = element.variable;
 | 
| +        if (variable is FieldElement) {
 | 
| +          buffer.write('${variable.enclosingElement.name}.');
 | 
| +        }
 | 
| +        buffer.write('${variable.name}');
 | 
| +      } else {
 | 
| +        logger.error('Unsupported argument to initializer constructor.');
 | 
| +      }
 | 
| +    } else {
 | 
| +      logger.error('Only literals and identifiers are allowed for initializer '
 | 
| +          'expressions, found $expression.');
 | 
| +    }
 | 
| +    return buffer.toString();
 | 
| +  }
 | 
| +}
 | 
| 
 |