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 // 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.targetElement; |
| 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 [element] which |
| 128 /// is an annotated class. |
| 129 String buildClassTarget( |
| 130 ClassElement element, InitializerPluginData pluginData) => |
| 131 buildSimpleTarget(element, pluginData); |
| 132 |
| 133 /// Builds a [String] for the target of an [InitEntry] given [element] which |
| 134 /// is an annotated function. |
| 135 String buildFunctionTarget( |
| 136 FunctionElement element, InitializerPluginData pluginData) => |
| 137 buildSimpleTarget(element, pluginData); |
| 138 |
| 139 /// Builds a [String] for the target of an [InitEntry] for a simple [Element]. |
| 140 /// This is just the library prefix followed by the element name. |
| 141 String buildSimpleTarget(Element element, InitializerPluginData pluginData) => |
| 142 '${pluginData.libraryPrefixes[element.library]}.${element.name}'; |
| 143 |
| 144 /// Builds a [String] for the target of an [InitEntry] given [element] which |
| 145 /// is an annotated library. |
| 146 String buildLibraryTarget( |
| 147 LibraryElement element, InitializerPluginData pluginData) { |
| 148 var bootstrapId = pluginData.bootstrapId; |
| 149 var logger = pluginData.logger; |
| 150 var segments = element.source.uri.pathSegments; |
| 151 var package = segments[0]; |
| 152 var libraryPath; |
| 153 var packageString; |
| 154 if (bootstrapId.package == package && |
| 155 bootstrapId.path.startsWith('${segments[1]}/')) { |
| 156 // reset `package` to null, we will do a relative path in this case. |
| 157 packageString = 'null'; |
| 158 libraryPath = path.url.relative( |
| 159 path.url.joinAll(segments.getRange(1, segments.length)), |
| 160 from: path.url.dirname(path.url.join(bootstrapId.path))); |
| 161 } else if (segments[1] == 'lib') { |
| 162 packageString = "'$package'"; |
| 163 libraryPath = path.url.joinAll(segments.getRange(2, segments.length)); |
| 164 } else { |
| 165 logger.error('Unable to import `${element.source.uri.path}` from ' |
| 166 '${bootstrapId.path}.'); |
| 167 } |
| 168 |
| 169 return "const LibraryIdentifier" |
| 170 "(#${element.name}, $packageString, '$libraryPath')"; |
| 171 } |
| 172 |
| 173 /// Builds a [String] representing an [ArgumentList] taking into account the |
| 174 /// [libraryPrefixes] from [pluginData]. |
| 175 String buildArgumentList( |
| 176 ArgumentList args, InitializerPluginData pluginData) { |
| 177 var buffer = new StringBuffer(); |
| 178 buffer.write('('); |
| 179 var first = true; |
| 180 for (var arg in args.arguments) { |
| 181 if (!first) buffer.write(', '); |
| 182 first = false; |
| 183 |
| 184 Expression expression; |
| 185 if (arg is NamedExpression) { |
| 186 buffer.write('${arg.name.label.name}: '); |
| 187 expression = arg.expression; |
| 188 } else { |
| 189 expression = arg; |
| 190 } |
| 191 |
| 192 buffer.write(buildExpression(expression, pluginData)); |
| 193 } |
| 194 buffer.write(')'); |
| 195 return buffer.toString(); |
| 196 } |
| 197 |
| 198 /// Builds a [String] representing [expression] taking into account the |
| 199 /// [libraryPrefixes] from [pluginData]. |
| 200 String buildExpression( |
| 201 Expression expression, InitializerPluginData pluginData) { |
| 202 var logger = pluginData.logger; |
| 203 var libraryPrefixes = pluginData.libraryPrefixes; |
| 204 var buffer = new StringBuffer(); |
| 205 if (expression is StringLiteral) { |
| 206 var value = expression.stringValue; |
| 207 if (value == null) { |
| 208 logger.error('Only const strings are allowed in initializer ' |
| 209 'expressions, found $expression'); |
| 210 } |
| 211 value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'"); |
| 212 buffer.write("'$value'"); |
| 213 } else if (expression is BooleanLiteral || |
| 214 expression is DoubleLiteral || |
| 215 expression is IntegerLiteral || |
| 216 expression is NullLiteral) { |
| 217 buffer.write('${expression}'); |
| 218 } else if (expression is ListLiteral) { |
| 219 buffer.write('const ['); |
| 220 var first = true; |
| 221 for (Expression listExpression in expression.elements) { |
| 222 if (!first) buffer.write(', '); |
| 223 first = false; |
| 224 buffer.write(buildExpression(listExpression, pluginData)); |
| 225 } |
| 226 buffer.write(']'); |
| 227 } else if (expression is MapLiteral) { |
| 228 buffer.write('const {'); |
| 229 var first = true; |
| 230 for (MapLiteralEntry entry in expression.entries) { |
| 231 if (!first) buffer.write(', '); |
| 232 first = false; |
| 233 buffer.write(buildExpression(entry.key, pluginData)); |
| 234 buffer.write(': '); |
| 235 buffer.write(buildExpression(entry.value, pluginData)); |
| 236 } |
| 237 buffer.write('}'); |
| 238 } else if (expression is Identifier) { |
| 239 var element = expression.bestElement; |
| 240 if (element == null || !element.isPublic) { |
| 241 logger.error('Private constants are not supported in intializer ' |
| 242 'constructors, found $element.'); |
| 243 } |
| 244 libraryPrefixes.putIfAbsent( |
| 245 element.library, () => 'i${libraryPrefixes.length}'); |
| 246 |
| 247 buffer.write('${libraryPrefixes[element.library]}.'); |
| 248 if (element is ClassElement) { |
| 249 buffer.write(element.name); |
| 250 } else if (element is PropertyAccessorElement) { |
| 251 var variable = element.variable; |
| 252 if (variable is FieldElement) { |
| 253 buffer.write('${variable.enclosingElement.name}.'); |
| 254 } |
| 255 buffer.write('${variable.name}'); |
| 256 } else { |
| 257 logger.error('Unsupported argument to initializer constructor.'); |
| 258 } |
| 259 } else { |
| 260 logger.error('Only literals and identifiers are allowed for initializer ' |
| 261 'expressions, found $expression.'); |
| 262 } |
| 263 return buffer.toString(); |
| 264 } |
| 265 } |
OLD | NEW |