Chromium Code Reviews| 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..dea78a574868b464dcc79bbef431a66c14a29756 |
| --- /dev/null |
| +++ b/lib/build/initializer_plugin.dart |
| @@ -0,0 +1,263 @@ |
| +// 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.element; |
| + var elementAnnotation = pluginData.initializer.elementAnnotation; |
| + 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.node; |
| + var metaPrefix = pluginData.libraryPrefixes[ |
| + elementAnnotation.element.library]; |
| + |
| + var annotation = pluginData.initializer.annotation; |
| + 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.element; |
| + 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 a [ClassElement] |
| + /// that was annotated. |
| + String buildClassTarget(Element element, InitializerPluginData pluginData) => |
|
Siggi Cherem (dart-lang)
2015/02/13 00:50:36
let's change the type here Element -> ClassElement
jakemac
2015/02/13 20:16:05
Done.
|
| + buildSimpleTarget(element, pluginData); |
| + |
| + /// Builds a [String] for the target of an [InitEntry] given a [FunctionElement] |
|
Siggi Cherem (dart-lang)
2015/02/13 00:50:36
nit - 80 col (here and below)
jakemac
2015/02/13 20:16:05
Done.
|
| + /// that was annotated. |
| + String buildFunctionTarget( |
| + Element element, InitializerPluginData pluginData) => |
|
Siggi Cherem (dart-lang)
2015/02/13 00:50:36
same here and with LibraryTarget below
jakemac
2015/02/13 20:16:05
Done.
|
| + 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 a [LibraryElement] |
| + /// that was annotated. |
| + String buildLibraryTarget(Element 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]. |
| + 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 an [Expression] taking into account the |
| + /// [libraryPrefixes]. |
| + 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(); |
| + } |
| +} |