| 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.targetElement; | |
| 62 var elementAnnotation = pluginData.initializer.annotationElement; | |
| 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.targetNode; | |
| 82 var metaPrefix = | |
| 83 pluginData.libraryPrefixes[elementAnnotation.element.library]; | |
| 84 | |
| 85 var annotation = pluginData.initializer.annotationNode; | |
| 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 var args = buildArgumentList(annotation.arguments, pluginData); | |
| 96 return 'const $metaPrefix.${clazz}$constructor$args'; | |
| 97 } | |
| 98 | |
| 99 /// Builds a [String] representing the meta of an [InitEntry] given an | |
| 100 /// [ElementAnnotation] whose element was a [PropertyAccessorElement]. | |
| 101 String buildPropertyMeta( | |
| 102 ElementAnnotation annotation, InitializerPluginData pluginData) { | |
| 103 var metaPrefix = pluginData.libraryPrefixes[annotation.element.library]; | |
| 104 return '$metaPrefix.${annotation.element.name}'; | |
| 105 } | |
| 106 | |
| 107 /// Builds a [String] for the target of an [InitEntry] given an [Element] that | |
| 108 /// was annotated. | |
| 109 String buildTarget(InitializerPluginData pluginData) { | |
| 110 var element = pluginData.initializer.targetElement; | |
| 111 var logger = pluginData.logger; | |
| 112 if (element is LibraryElement) { | |
| 113 return buildLibraryTarget(element, pluginData); | |
| 114 } else if (element is ClassElement) { | |
| 115 return buildClassTarget(element, pluginData); | |
| 116 } else if (element is FunctionElement) { | |
| 117 return buildFunctionTarget(element, pluginData); | |
| 118 } else { | |
| 119 logger.error('Initializers can only be applied to top level functions, ' | |
| 120 'libraries, and classes.'); | |
| 121 } | |
| 122 return null; | |
| 123 } | |
| 124 | |
| 125 /// Builds a [String] for the target of an [InitEntry] given [element] which | |
| 126 /// is an annotated class. | |
| 127 String buildClassTarget( | |
| 128 ClassElement element, InitializerPluginData pluginData) => | |
| 129 buildSimpleTarget(element, pluginData); | |
| 130 | |
| 131 /// Builds a [String] for the target of an [InitEntry] given [element] which | |
| 132 /// is an annotated function. | |
| 133 String buildFunctionTarget( | |
| 134 FunctionElement element, InitializerPluginData pluginData) => | |
| 135 buildSimpleTarget(element, pluginData); | |
| 136 | |
| 137 /// Builds a [String] for the target of an [InitEntry] for a simple [Element]. | |
| 138 /// This is just the library prefix followed by the element name. | |
| 139 String buildSimpleTarget(Element element, InitializerPluginData pluginData) => | |
| 140 '${pluginData.libraryPrefixes[element.library]}.${element.name}'; | |
| 141 | |
| 142 /// Builds a [String] for the target of an [InitEntry] given [element] which | |
| 143 /// is an annotated library. | |
| 144 String buildLibraryTarget( | |
| 145 LibraryElement 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] from [pluginData]. | |
| 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 [expression] taking into account the | |
| 197 /// [libraryPrefixes] from [pluginData]. | |
| 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 && expression.stringValue != null) { | |
| 204 buffer.write(_stringValue(expression.stringValue)); | |
| 205 } else if (expression is BooleanLiteral || | |
| 206 expression is DoubleLiteral || | |
| 207 expression is IntegerLiteral || | |
| 208 expression is NullLiteral) { | |
| 209 buffer.write('${expression}'); | |
| 210 } else if (expression is ListLiteral) { | |
| 211 buffer.write('const ['); | |
| 212 var first = true; | |
| 213 for (Expression listExpression in expression.elements) { | |
| 214 if (!first) buffer.write(', '); | |
| 215 first = false; | |
| 216 buffer.write(buildExpression(listExpression, pluginData)); | |
| 217 } | |
| 218 buffer.write(']'); | |
| 219 } else if (expression is MapLiteral) { | |
| 220 buffer.write('const {'); | |
| 221 var first = true; | |
| 222 for (MapLiteralEntry entry in expression.entries) { | |
| 223 if (!first) buffer.write(', '); | |
| 224 first = false; | |
| 225 buffer.write(buildExpression(entry.key, pluginData)); | |
| 226 buffer.write(': '); | |
| 227 buffer.write(buildExpression(entry.value, pluginData)); | |
| 228 } | |
| 229 buffer.write('}'); | |
| 230 } else if (expression is Identifier) { | |
| 231 var element = expression.bestElement; | |
| 232 if (element == null) { | |
| 233 logger.error('Unable to get `bestElement` for expression: $expression'); | |
| 234 } else if (!element.isPublic) { | |
| 235 // Inline the evaluated value of private identifiers. | |
| 236 buffer.write(_evaluateExpression(expression, pluginData)); | |
| 237 } else { | |
| 238 libraryPrefixes.putIfAbsent( | |
| 239 element.library, () => 'i${libraryPrefixes.length}'); | |
| 240 | |
| 241 buffer.write('${libraryPrefixes[element.library]}.'); | |
| 242 if (element is ClassElement) { | |
| 243 buffer.write(element.name); | |
| 244 } else if (element is PropertyAccessorElement) { | |
| 245 var variable = element.variable; | |
| 246 if (variable is FieldElement) { | |
| 247 buffer.write('${variable.enclosingElement.name}.'); | |
| 248 } | |
| 249 buffer.write('${variable.name}'); | |
| 250 } else { | |
| 251 logger.error('Unsupported argument to initializer constructor.'); | |
| 252 } | |
| 253 } | |
| 254 } else if (expression is PropertyAccess) { | |
| 255 buffer.write(buildExpression(expression.target, pluginData)); | |
| 256 buffer.write('.${expression.propertyName}'); | |
| 257 } else if (expression is InstanceCreationExpression) { | |
| 258 logger.error('Unsupported expression in initializer, found $expression. ' | |
| 259 'Instance creation expressions are not supported (yet). Instead, ' | |
| 260 'please assign it to a const variable and use that instead.'); | |
| 261 } else { | |
| 262 buffer.write(_evaluateExpression(expression, pluginData)); | |
| 263 } | |
| 264 return buffer.toString(); | |
| 265 } | |
| 266 | |
| 267 _evaluateExpression(Expression expression, InitializerPluginData pluginData) { | |
| 268 var logger = pluginData.logger; | |
| 269 var result = pluginData.resolver.evaluateConstant( | |
| 270 pluginData.initializer.targetElement.library, expression); | |
| 271 if (!result.isValid) { | |
| 272 logger.error('Invalid expression in initializer, found $expression. ' | |
| 273 'And got the following errors: ${result.errors}.'); | |
| 274 return null; | |
| 275 } | |
| 276 var value = result.value.value; | |
| 277 if (value == null) { | |
| 278 logger.error('Unsupported expression in initializer, found ' | |
| 279 '$expression. Please file a bug at ' | |
| 280 'https://github.com/dart-lang/initialize/issues'); | |
| 281 return null; | |
| 282 } | |
| 283 | |
| 284 if (value is String) value = _stringValue(value); | |
| 285 return value; | |
| 286 } | |
| 287 | |
| 288 // Returns an expression for a string value. Wraps it in single quotes and | |
| 289 // escapes existing single quotes and escapes. | |
| 290 _stringValue(String value) { | |
| 291 value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'"); | |
| 292 return "'$value'"; | |
| 293 } | |
| 294 } | |
| OLD | NEW |