Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 library initialize.build.initializer_plugin; | |
| 5 | |
| 6 import 'package:analyzer/src/generated/ast.dart'; | |
| 7 import 'package:analyzer/src/generated/element.dart'; | |
| 8 import 'package:barback/barback.dart'; | |
| 9 import 'package:code_transformers/resolver.dart'; | |
| 10 import 'package:initialize/transformer.dart'; | |
| 11 import 'package:path/path.dart' as path; | |
| 12 | |
| 13 /// A plug which allows an initializer to write out an [InitEntry] given some | |
| 14 /// [InitializerData] from an annotation that was found. | |
| 15 abstract class InitializerPlugin { | |
| 16 /// Whether or not this plugin should be applied to an [Initializer] given | |
| 17 /// some [InitializerData]. If [true] is returned then this plugin will take | |
| 18 /// ownership of this [InitializerData] and no subsequent plugins will have | |
| 19 /// an opportunity to access it. | |
| 20 bool shouldApply(InitializerPluginData data); | |
| 21 | |
| 22 /// Returns a [String] or [null]. The [String] should represent dart code | |
| 23 /// which creates a new [InitEntry] and that entry is added to the static | |
| 24 /// initializers list. If [null] is returned then no entry is added at all for | |
| 25 /// this [InitializerData]. | |
| 26 String apply(InitializerPluginData data); | |
| 27 } | |
| 28 | |
| 29 /// A class which wraps all the default data passed to an [InitializerPlugin] | |
| 30 /// for each annotation. | |
| 31 class InitializerPluginData { | |
| 32 final InitializerData initializer; | |
| 33 final AssetId bootstrapId; | |
| 34 final Map<LibraryElement, String> libraryPrefixes; | |
| 35 final TransformLogger logger; | |
| 36 final Resolver resolver; | |
| 37 InitializerPluginData(this.initializer, this.bootstrapId, | |
| 38 this.libraryPrefixes, this.resolver, this.logger); | |
| 39 } | |
| 40 | |
| 41 /// The basic [InitializerPlugin]. This generates a new [InitEntry] to be added | |
| 42 /// to the static initializers list, and applies to every item it sees. | |
| 43 class DefaultInitializerPlugin implements InitializerPlugin { | |
| 44 const DefaultInitializerPlugin(); | |
| 45 | |
| 46 /// Applies to everything. Put other plugins before this one to override this | |
| 47 /// behaviour. | |
| 48 bool shouldApply(InitializerPluginData data) => true; | |
| 49 | |
| 50 /// Creates a normal [InitEntry] string. | |
| 51 String apply(InitializerPluginData pluginData) { | |
| 52 var target = buildTarget(pluginData); | |
| 53 var meta = buildMeta(pluginData); | |
| 54 return ' new InitEntry($meta, $target)'; | |
| 55 } | |
| 56 | |
| 57 /// Builds a [String] representing the meta of an [InitEntry] given an | |
| 58 /// [ElementAnnotation] that was found. | |
| 59 String buildMeta(InitializerPluginData pluginData) { | |
| 60 var logger = pluginData.logger; | |
| 61 var element = pluginData.initializer.element; | |
| 62 var elementAnnotation = pluginData.initializer.elementAnnotation; | |
| 63 var elementAnnotationElement = elementAnnotation.element; | |
| 64 var libraryPrefixes = pluginData.libraryPrefixes; | |
| 65 if (elementAnnotationElement is ConstructorElement) { | |
| 66 return buildConstructorMeta(elementAnnotation, pluginData); | |
| 67 } else if (elementAnnotationElement is PropertyAccessorElement) { | |
| 68 return buildPropertyMeta(elementAnnotation, pluginData); | |
| 69 } else { | |
| 70 logger.error('Unsupported annotation type. Only constructors and ' | |
| 71 'properties are supported as initializers.'); | |
| 72 } | |
| 73 return null; | |
| 74 } | |
| 75 | |
| 76 /// Builds a [String] representing the meta of an [InitEntry] given an | |
| 77 /// [ElementAnnotation] whose element was a [ConstructorElement]. | |
| 78 String buildConstructorMeta( | |
| 79 ElementAnnotation elementAnnotation, InitializerPluginData pluginData) { | |
| 80 var logger = pluginData.logger; | |
| 81 var node = pluginData.initializer.node; | |
| 82 var metaPrefix = pluginData.libraryPrefixes[ | |
| 83 elementAnnotation.element.library]; | |
| 84 | |
| 85 var annotation = pluginData.initializer.annotation; | |
| 86 if (annotation == null) { | |
| 87 logger.error( | |
| 88 'Initializer annotations are only supported on libraries, classes, ' | |
| 89 'and top level methods. Found $node.'); | |
| 90 } | |
| 91 var clazz = annotation.name; | |
| 92 var constructor = annotation.constructorName == null | |
| 93 ? '' | |
| 94 : '.${annotation.constructorName}'; | |
| 95 // TODO(jakemac): Support more than raw values here | |
| 96 // https://github.com/dart-lang/static_init/issues/5 | |
| 97 var args = buildArgumentList(annotation.arguments, pluginData); | |
| 98 return 'const $metaPrefix.${clazz}$constructor$args'; | |
| 99 } | |
| 100 | |
| 101 /// Builds a [String] representing the meta of an [InitEntry] given an | |
| 102 /// [ElementAnnotation] whose element was a [PropertyAccessorElement]. | |
| 103 String buildPropertyMeta( | |
| 104 ElementAnnotation annotation, InitializerPluginData pluginData) { | |
| 105 var metaPrefix = pluginData.libraryPrefixes[annotation.element.library]; | |
| 106 return '$metaPrefix.${annotation.element.name}'; | |
| 107 } | |
| 108 | |
| 109 /// Builds a [String] for the target of an [InitEntry] given an [Element] that | |
| 110 /// was annotated. | |
| 111 String buildTarget(InitializerPluginData pluginData) { | |
| 112 var element = pluginData.initializer.element; | |
| 113 var logger = pluginData.logger; | |
| 114 if (element is LibraryElement) { | |
| 115 return buildLibraryTarget(element, pluginData); | |
| 116 } else if (element is ClassElement) { | |
| 117 return buildClassTarget(element, pluginData); | |
| 118 } else if (element is FunctionElement) { | |
| 119 return buildFunctionTarget(element, pluginData); | |
| 120 } else { | |
| 121 logger.error('Initializers can only be applied to top level functions, ' | |
| 122 'libraries, and classes.'); | |
| 123 } | |
| 124 return null; | |
| 125 } | |
| 126 | |
| 127 /// Builds a [String] for the target of an [InitEntry] given a [ClassElement] | |
| 128 /// that was annotated. | |
| 129 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.
| |
| 130 buildSimpleTarget(element, pluginData); | |
| 131 | |
| 132 /// Builds a [String] for the target of an [InitEntry] given a [FunctionElemen t] | |
|
Siggi Cherem (dart-lang)
2015/02/13 00:50:36
nit - 80 col (here and below)
jakemac
2015/02/13 20:16:05
Done.
| |
| 133 /// that was annotated. | |
| 134 String buildFunctionTarget( | |
| 135 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.
| |
| 136 buildSimpleTarget(element, pluginData); | |
| 137 | |
| 138 /// Builds a [String] for the target of an [InitEntry] for a simple [Element]. | |
| 139 /// This is just the library prefix followed by the element name. | |
| 140 String buildSimpleTarget(Element element, InitializerPluginData pluginData) => | |
| 141 '${pluginData.libraryPrefixes[element.library]}.${element.name}'; | |
| 142 | |
| 143 /// Builds a [String] for the target of an [InitEntry] given a [LibraryElement ] | |
| 144 /// that was annotated. | |
| 145 String buildLibraryTarget(Element element, InitializerPluginData pluginData) { | |
| 146 var bootstrapId = pluginData.bootstrapId; | |
| 147 var logger = pluginData.logger; | |
| 148 var segments = element.source.uri.pathSegments; | |
| 149 var package = segments[0]; | |
| 150 var libraryPath; | |
| 151 var packageString; | |
| 152 if (bootstrapId.package == package && | |
| 153 bootstrapId.path.startsWith('${segments[1]}/')) { | |
| 154 // reset `package` to null, we will do a relative path in this case. | |
| 155 packageString = 'null'; | |
| 156 libraryPath = path.url.relative( | |
| 157 path.url.joinAll(segments.getRange(1, segments.length)), | |
| 158 from: path.url.dirname(path.url.join(bootstrapId.path))); | |
| 159 } else if (segments[1] == 'lib') { | |
| 160 packageString = "'$package'"; | |
| 161 libraryPath = path.url.joinAll(segments.getRange(2, segments.length)); | |
| 162 } else { | |
| 163 logger.error('Unable to import `${element.source.uri.path}` from ' | |
| 164 '${bootstrapId.path}.'); | |
| 165 } | |
| 166 | |
| 167 return "const LibraryIdentifier" | |
| 168 "(#${element.name}, $packageString, '$libraryPath')"; | |
| 169 } | |
| 170 | |
| 171 /// Builds a [String] representing an [ArgumentList] taking into account the | |
| 172 /// [libraryPrefixes]. | |
| 173 String buildArgumentList( | |
| 174 ArgumentList args, InitializerPluginData pluginData) { | |
| 175 var buffer = new StringBuffer(); | |
| 176 buffer.write('('); | |
| 177 var first = true; | |
| 178 for (var arg in args.arguments) { | |
| 179 if (!first) buffer.write(', '); | |
| 180 first = false; | |
| 181 | |
| 182 Expression expression; | |
| 183 if (arg is NamedExpression) { | |
| 184 buffer.write('${arg.name.label.name}: '); | |
| 185 expression = arg.expression; | |
| 186 } else { | |
| 187 expression = arg; | |
| 188 } | |
| 189 | |
| 190 buffer.write(buildExpression(expression, pluginData)); | |
| 191 } | |
| 192 buffer.write(')'); | |
| 193 return buffer.toString(); | |
| 194 } | |
| 195 | |
| 196 /// Builds a [String] representing an [Expression] taking into account the | |
| 197 /// [libraryPrefixes]. | |
| 198 String buildExpression( | |
| 199 Expression expression, InitializerPluginData pluginData) { | |
| 200 var logger = pluginData.logger; | |
| 201 var libraryPrefixes = pluginData.libraryPrefixes; | |
| 202 var buffer = new StringBuffer(); | |
| 203 if (expression is StringLiteral) { | |
| 204 var value = expression.stringValue; | |
| 205 if (value == null) { | |
| 206 logger.error('Only const strings are allowed in initializer ' | |
| 207 'expressions, found $expression'); | |
| 208 } | |
| 209 value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'"); | |
| 210 buffer.write("'$value'"); | |
| 211 } else if (expression is BooleanLiteral || | |
| 212 expression is DoubleLiteral || | |
| 213 expression is IntegerLiteral || | |
| 214 expression is NullLiteral) { | |
| 215 buffer.write('${expression}'); | |
| 216 } else if (expression is ListLiteral) { | |
| 217 buffer.write('const ['); | |
| 218 var first = true; | |
| 219 for (Expression listExpression in expression.elements) { | |
| 220 if (!first) buffer.write(', '); | |
| 221 first = false; | |
| 222 buffer.write(buildExpression(listExpression, pluginData)); | |
| 223 } | |
| 224 buffer.write(']'); | |
| 225 } else if (expression is MapLiteral) { | |
| 226 buffer.write('const {'); | |
| 227 var first = true; | |
| 228 for (MapLiteralEntry entry in expression.entries) { | |
| 229 if (!first) buffer.write(', '); | |
| 230 first = false; | |
| 231 buffer.write(buildExpression(entry.key, pluginData)); | |
| 232 buffer.write(': '); | |
| 233 buffer.write(buildExpression(entry.value, pluginData)); | |
| 234 } | |
| 235 buffer.write('}'); | |
| 236 } else if (expression is Identifier) { | |
| 237 var element = expression.bestElement; | |
| 238 if (element == null || !element.isPublic) { | |
| 239 logger.error('Private constants are not supported in intializer ' | |
| 240 'constructors, found $element.'); | |
| 241 } | |
| 242 libraryPrefixes.putIfAbsent( | |
| 243 element.library, () => 'i${libraryPrefixes.length}'); | |
| 244 | |
| 245 buffer.write('${libraryPrefixes[element.library]}.'); | |
| 246 if (element is ClassElement) { | |
| 247 buffer.write(element.name); | |
| 248 } else if (element is PropertyAccessorElement) { | |
| 249 var variable = element.variable; | |
| 250 if (variable is FieldElement) { | |
| 251 buffer.write('${variable.enclosingElement.name}.'); | |
| 252 } | |
| 253 buffer.write('${variable.name}'); | |
| 254 } else { | |
| 255 logger.error('Unsupported argument to initializer constructor.'); | |
| 256 } | |
| 257 } else { | |
| 258 logger.error('Only literals and identifiers are allowed for initializer ' | |
| 259 'expressions, found $expression.'); | |
| 260 } | |
| 261 return buffer.toString(); | |
| 262 } | |
| 263 } | |
| OLD | NEW |