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(); |
+ } |
+} |