| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 library initialize.transformer; | 4 library initialize.transformer; |
| 5 | 5 |
| 6 import 'dart:async'; | 6 import 'dart:async'; |
| 7 import 'dart:collection' show Queue; | 7 import 'dart:collection' show Queue; |
| 8 import 'package:analyzer/src/generated/ast.dart'; | 8 import 'package:analyzer/src/generated/ast.dart'; |
| 9 import 'package:analyzer/src/generated/element.dart'; | 9 import 'package:analyzer/src/generated/element.dart'; |
| 10 import 'package:barback/barback.dart'; | 10 import 'package:barback/barback.dart'; |
| 11 import 'package:code_transformers/assets.dart'; | 11 import 'package:code_transformers/assets.dart'; |
| 12 import 'package:code_transformers/resolver.dart'; | 12 import 'package:code_transformers/resolver.dart'; |
| 13 import 'package:dart_style/dart_style.dart'; | 13 import 'package:dart_style/dart_style.dart'; |
| 14 import 'package:glob/glob.dart'; | 14 import 'package:glob/glob.dart'; |
| 15 import 'package:html5lib/dom.dart' as dom; | 15 import 'package:html5lib/dom.dart' as dom; |
| 16 import 'package:html5lib/parser.dart' show parse; | 16 import 'package:html5lib/parser.dart' show parse; |
| 17 import 'package:path/path.dart' as path; | 17 import 'package:path/path.dart' as path; |
| 18 | 18 |
| 19 /// Removes the mirror-based initialization logic and replaces it with static | 19 import 'build/initializer_plugin.dart'; |
| 20 /// logic. | 20 export 'build/initializer_plugin.dart'; |
| 21 |
| 22 /// Create a new [Asset] which inlines your [Initializer] annotations into |
| 23 /// a new file that bootstraps your application. |
| 24 Asset generateBootstrapFile(Resolver resolver, Transform transform, |
| 25 AssetId primaryAssetId, AssetId newEntryPointId, |
| 26 {bool errorIfNotFound: true, List<InitializerPlugin> plugins, |
| 27 bool appendDefaultPlugin: true}) { |
| 28 if (appendDefaultPlugin) { |
| 29 if (plugins == null) plugins = []; |
| 30 plugins.add(const DefaultInitializerPlugin()); |
| 31 } |
| 32 return new _BootstrapFileBuilder( |
| 33 resolver, transform, primaryAssetId, newEntryPointId, errorIfNotFound, |
| 34 plugins: plugins).run(); |
| 35 } |
| 36 |
| 37 /// Transformer which removes the mirror-based initialization logic and replaces |
| 38 /// it with static logic. |
| 21 class InitializeTransformer extends Transformer { | 39 class InitializeTransformer extends Transformer { |
| 22 final Resolvers _resolvers; | 40 final Resolvers _resolvers; |
| 23 final Iterable<Glob> _entryPointGlobs; | 41 final Iterable<Glob> _entryPointGlobs; |
| 24 final bool _errorIfNotFound; | 42 final bool _errorIfNotFound; |
| 43 final List<InitializerPlugin> plugins; |
| 25 | 44 |
| 26 InitializeTransformer(List<String> entryPoints, {bool errorIfNotFound: true}) | 45 InitializeTransformer(List<String> entryPoints, |
| 46 {bool errorIfNotFound: true, this.plugins}) |
| 27 : _entryPointGlobs = entryPoints.map((e) => new Glob(e)), | 47 : _entryPointGlobs = entryPoints.map((e) => new Glob(e)), |
| 28 _errorIfNotFound = errorIfNotFound, | 48 _errorIfNotFound = errorIfNotFound, |
| 29 _resolvers = new Resolvers.fromMock({ | 49 _resolvers = new Resolvers.fromMock({ |
| 30 // The list of types below is derived from: | 50 // The list of types below is derived from: |
| 31 // * types that are used internally by the resolver (see | 51 // * types that are used internally by the resolver (see |
| 32 // _initializeFrom in resolver.dart). | 52 // _initializeFrom in resolver.dart). |
| 33 // TODO(jakemac): Move this into code_transformers so it can be shared
. | 53 // TODO(jakemac): Move this into code_transformers so it can be shared
. |
| 34 'dart:core': ''' | 54 'dart:core': ''' |
| 35 library dart.core; | 55 library dart.core; |
| 36 class Object {} | 56 class Object {} |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 100 var newEntryPointId = new AssetId(primaryId.package, | 120 var newEntryPointId = new AssetId(primaryId.package, |
| 101 '${path.url.withoutExtension(primaryId.path)}.initialize.dart'); | 121 '${path.url.withoutExtension(primaryId.path)}.initialize.dart'); |
| 102 return transform.hasInput(newEntryPointId).then((exists) { | 122 return transform.hasInput(newEntryPointId).then((exists) { |
| 103 if (exists) { | 123 if (exists) { |
| 104 transform.logger | 124 transform.logger |
| 105 .error('New entry point file $newEntryPointId already exists.'); | 125 .error('New entry point file $newEntryPointId already exists.'); |
| 106 return null; | 126 return null; |
| 107 } | 127 } |
| 108 | 128 |
| 109 return _resolvers.get(transform, [primaryId]).then((resolver) { | 129 return _resolvers.get(transform, [primaryId]).then((resolver) { |
| 110 new _BootstrapFileBuilder(resolver, transform, primaryId, | 130 transform.addOutput(generateBootstrapFile( |
| 111 newEntryPointId, _errorIfNotFound).run(); | 131 resolver, transform, primaryId, newEntryPointId, |
| 132 errorIfNotFound: _errorIfNotFound, plugins: plugins)); |
| 112 resolver.release(); | 133 resolver.release(); |
| 113 return newEntryPointId; | 134 return newEntryPointId; |
| 114 }); | 135 }); |
| 115 }); | 136 }); |
| 116 } | 137 } |
| 138 // Finds the first (and only) dart script on an html page and returns the |
| 139 // [AssetId] that points to it |
| 140 AssetId _findMainScript( |
| 141 dom.Document document, AssetId entryPoint, Transform transform) { |
| 142 var scripts = document.querySelectorAll('script[type="application/dart"]'); |
| 143 if (scripts.length != 1) { |
| 144 transform.logger.error('Expected exactly one dart script in $entryPoint ' |
| 145 'but found ${scripts.length}.'); |
| 146 return null; |
| 147 } |
| 148 |
| 149 var src = scripts[0].attributes['src']; |
| 150 if (src == null) { |
| 151 // TODO(jakemac): Support inline scripts, |
| 152 transform.logger.error('Inline scripts are not supported at this time, ' |
| 153 'see https://github.com/dart-lang/initialize/issues/20.'); |
| 154 return null; |
| 155 } |
| 156 |
| 157 return uriToAssetId( |
| 158 entryPoint, src, transform.logger, scripts[0].sourceSpan); |
| 159 } |
| 117 | 160 |
| 118 // Replaces script tags pointing to [originalDartFile] with [newDartFile] in | 161 // Replaces script tags pointing to [originalDartFile] with [newDartFile] in |
| 119 // [entryPoint]. | 162 // [entryPoint]. |
| 120 void _replaceEntryWithBootstrap(Transform transform, dom.Document document, | 163 void _replaceEntryWithBootstrap(Transform transform, dom.Document document, |
| 121 AssetId entryPoint, AssetId originalDartFile, AssetId newDartFile) { | 164 AssetId entryPoint, AssetId originalDartFile, AssetId newDartFile) { |
| 122 var found = false; | 165 var found = false; |
| 166 |
| 123 var scripts = document | 167 var scripts = document |
| 124 .querySelectorAll('script[type="application/dart"]') | 168 .querySelectorAll('script[type="application/dart"]') |
| 125 .where((script) => uriToAssetId(entryPoint, script.attributes['src'], | 169 .where((script) { |
| 126 transform.logger, script.sourceSpan) == originalDartFile) | 170 var assetId = uriToAssetId(entryPoint, script.attributes['src'], |
| 127 .toList(); | 171 transform.logger, script.sourceSpan); |
| 172 return assetId == originalDartFile; |
| 173 }).toList(); |
| 128 | 174 |
| 129 if (scripts.length != 1) { | 175 if (scripts.length != 1) { |
| 130 transform.logger.error( | 176 transform.logger.error( |
| 131 'Expected exactly one script pointing to $originalDartFile in ' | 177 'Expected exactly one script pointing to $originalDartFile in ' |
| 132 '$entryPoint, but found ${scripts.length}.'); | 178 '$entryPoint, but found ${scripts.length}.'); |
| 133 return; | 179 return; |
| 134 } | 180 } |
| 135 scripts[0].attributes['src'] = path.url.relative(newDartFile.path, | 181 scripts[0].attributes['src'] = path.url.relative(newDartFile.path, |
| 136 from: path.dirname(entryPoint.path)); | 182 from: path.dirname(entryPoint.path)); |
| 137 transform.addOutput(new Asset.fromString(entryPoint, document.outerHtml)); | 183 transform.addOutput(new Asset.fromString(entryPoint, document.outerHtml)); |
| 138 } | 184 } |
| 139 | |
| 140 AssetId _findMainScript( | |
| 141 dom.Document document, AssetId entryPoint, Transform transform) { | |
| 142 var scripts = document.querySelectorAll('script[type="application/dart"]'); | |
| 143 if (scripts.length != 1) { | |
| 144 transform.logger.error('Expected exactly one dart script in $entryPoint ' | |
| 145 'but found ${scripts.length}.'); | |
| 146 return null; | |
| 147 } | |
| 148 | |
| 149 var src = scripts[0].attributes['src']; | |
| 150 // TODO(jakemac): Support inline scripts, | |
| 151 // https://github.com/dart-lang/initialize/issues/20 | |
| 152 if (src == null) { | |
| 153 transform.logger.error('Inline scripts are not supported at this time.'); | |
| 154 return null; | |
| 155 } | |
| 156 | |
| 157 return uriToAssetId( | |
| 158 entryPoint, src, transform.logger, scripts[0].sourceSpan); | |
| 159 } | |
| 160 } | 185 } |
| 161 | 186 |
| 187 // Class which builds a bootstrap file. |
| 162 class _BootstrapFileBuilder { | 188 class _BootstrapFileBuilder { |
| 163 final Resolver _resolver; | 189 final Resolver _resolver; |
| 164 final Transform _transform; | 190 final Transform _transform; |
| 165 final bool _errorIfNotFound; | 191 final bool _errorIfNotFound; |
| 166 AssetId _entryPoint; | 192 AssetId _entryPoint; |
| 167 AssetId _newEntryPoint; | 193 AssetId _newEntryPoint; |
| 168 | 194 |
| 169 /// The resolved initialize library. | 195 /// The resolved initialize library. |
| 170 LibraryElement _initializeLibrary; | 196 LibraryElement _initializeLibrary; |
| 171 /// The resolved Initializer class from the initialize library. | 197 /// The resolved Initializer class from the initialize library. |
| 172 ClassElement _initializer; | 198 ClassElement _initializer; |
| 173 | 199 |
| 174 /// Queue for intialization annotations. | 200 /// Queue for intialization annotations. |
| 175 final _initQueue = new Queue<_InitializerData>(); | 201 final _initQueue = new Queue<InitializerData>(); |
| 176 /// All the annotations we have seen for each element | 202 /// All the annotations we have seen for each element |
| 177 final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>(); | 203 final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>(); |
| 178 | 204 |
| 205 /// The list of [InitializerPlugin]s to apply. The first plugin which asks to |
| 206 /// be applied to a given initializer is the only one that will apply. |
| 207 List<InitializerPlugin> _plugins; |
| 208 |
| 179 TransformLogger _logger; | 209 TransformLogger _logger; |
| 180 | 210 |
| 181 _BootstrapFileBuilder(this._resolver, this._transform, this._entryPoint, | 211 _BootstrapFileBuilder(this._resolver, this._transform, this._entryPoint, |
| 182 this._newEntryPoint, this._errorIfNotFound) { | 212 this._newEntryPoint, this._errorIfNotFound, |
| 213 {List<InitializerPlugin> plugins}) { |
| 183 _logger = _transform.logger; | 214 _logger = _transform.logger; |
| 184 _initializeLibrary = | 215 _initializeLibrary = |
| 185 _resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart')); | 216 _resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart')); |
| 186 if (_initializeLibrary != null) { | 217 if (_initializeLibrary != null) { |
| 187 _initializer = _initializeLibrary.getType('Initializer'); | 218 _initializer = _initializeLibrary.getType('Initializer'); |
| 188 } else if (_errorIfNotFound) { | 219 } else if (_errorIfNotFound) { |
| 189 _logger.warning('Unable to read "package:initialize/initialize.dart". ' | 220 _logger.warning('Unable to read "package:initialize/initialize.dart". ' |
| 190 'This file must be imported via $_entryPoint or a transitive ' | 221 'This file must be imported via $_entryPoint or a transitive ' |
| 191 'dependency.'); | 222 'dependency.'); |
| 192 } | 223 } |
| 224 _plugins = plugins != null ? plugins : [const DefaultInitializerPlugin()]; |
| 193 } | 225 } |
| 194 | 226 |
| 195 /// Adds the new entry point file to the transform. Should only be ran once. | 227 /// Creates and returns the new bootstrap file. |
| 196 void run() { | 228 Asset run() { |
| 197 var entryLib = _resolver.getLibrary(_entryPoint); | 229 var entryLib = _resolver.getLibrary(_entryPoint); |
| 198 _readLibraries(entryLib); | 230 _readLibraries(entryLib); |
| 199 | 231 |
| 200 _transform.addOutput( | 232 return new Asset.fromString(_newEntryPoint, _buildNewEntryPoint(entryLib)); |
| 201 new Asset.fromString(_newEntryPoint, _buildNewEntryPoint(entryLib))); | |
| 202 } | 233 } |
| 203 | 234 |
| 204 /// Reads Initializer annotations on this library and all its dependencies in | 235 /// Reads Initializer annotations on this library and all its dependencies in |
| 205 /// post-order. | 236 /// post-order. |
| 206 void _readLibraries(LibraryElement library, [Set<LibraryElement> seen]) { | 237 void _readLibraries(LibraryElement library, [Set<LibraryElement> seen]) { |
| 207 if (seen == null) seen = new Set<LibraryElement>(); | 238 if (seen == null) seen = new Set<LibraryElement>(); |
| 208 seen.add(library); | 239 seen.add(library); |
| 209 | 240 |
| 210 // Visit all our dependencies. | 241 // Visit all our dependencies. |
| 211 for (var importedLibrary in _sortedLibraryImports(library)) { | 242 for (var importedLibrary in _sortedLibraryImports(library)) { |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 246 return _isInitializer(e.variable.evaluationResult.value.type); | 277 return _isInitializer(e.variable.evaluationResult.value.type); |
| 247 } else if (e is ConstructorElement) { | 278 } else if (e is ConstructorElement) { |
| 248 return _isInitializer(e.returnType); | 279 return _isInitializer(e.returnType); |
| 249 } | 280 } |
| 250 return false; | 281 return false; |
| 251 }).where((ElementAnnotation meta) { | 282 }).where((ElementAnnotation meta) { |
| 252 _seenAnnotations.putIfAbsent(element, () => new Set<ElementAnnotation>()); | 283 _seenAnnotations.putIfAbsent(element, () => new Set<ElementAnnotation>()); |
| 253 return !_seenAnnotations[element].contains(meta); | 284 return !_seenAnnotations[element].contains(meta); |
| 254 }).forEach((ElementAnnotation meta) { | 285 }).forEach((ElementAnnotation meta) { |
| 255 _seenAnnotations[element].add(meta); | 286 _seenAnnotations[element].add(meta); |
| 256 _initQueue.addLast(new _InitializerData(element, meta)); | 287 _initQueue.addLast(new InitializerData._(element, meta)); |
| 257 found = true; | 288 found = true; |
| 258 }); | 289 }); |
| 259 return found; | 290 return found; |
| 260 } | 291 } |
| 261 | 292 |
| 262 String _buildNewEntryPoint(LibraryElement entryLib) { | 293 String _buildNewEntryPoint(LibraryElement entryLib) { |
| 263 var importsBuffer = new StringBuffer(); | 294 var importsBuffer = new StringBuffer(); |
| 264 var initializersBuffer = new StringBuffer(); | 295 var initializersBuffer = new StringBuffer(); |
| 265 var libraryPrefixes = new Map<LibraryElement, String>(); | 296 var libraryPrefixes = new Map<LibraryElement, String>(); |
| 266 | 297 |
| 267 // Import the static_loader, initializer, and original entry point. | 298 // Import the static_loader, initializer, and original entry point. |
| 268 importsBuffer | 299 importsBuffer |
| 269 .writeln("import 'package:initialize/src/static_loader.dart';"); | 300 .writeln("import 'package:initialize/src/static_loader.dart';"); |
| 270 importsBuffer.writeln("import 'package:initialize/initialize.dart';"); | 301 importsBuffer.writeln("import 'package:initialize/initialize.dart';"); |
| 271 libraryPrefixes[entryLib] = 'i0'; | 302 libraryPrefixes[entryLib] = 'i0'; |
| 272 | 303 |
| 273 initializersBuffer.writeln(' initializers.addAll(['); | 304 initializersBuffer.writeln('initializers.addAll(['); |
| 274 while (_initQueue.isNotEmpty) { | 305 while (_initQueue.isNotEmpty) { |
| 275 var next = _initQueue.removeFirst(); | 306 var next = _initQueue.removeFirst(); |
| 276 | 307 |
| 277 libraryPrefixes.putIfAbsent( | 308 libraryPrefixes.putIfAbsent( |
| 278 next.element.library, () => 'i${libraryPrefixes.length}'); | 309 next.targetElement.library, () => 'i${libraryPrefixes.length}'); |
| 279 libraryPrefixes.putIfAbsent( | 310 libraryPrefixes.putIfAbsent(next.annotationElement.element.library, |
| 280 next.annotation.element.library, () => 'i${libraryPrefixes.length}'); | 311 () => 'i${libraryPrefixes.length}'); |
| 281 | 312 |
| 282 _writeInitializer(next, libraryPrefixes, initializersBuffer); | 313 // Run the first plugin which asks to be ran and then stop. |
| 314 var data = new InitializerPluginData( |
| 315 next, _newEntryPoint, libraryPrefixes, _resolver, _logger); |
| 316 var plugin = _plugins.firstWhere((p) => p.shouldApply(data), orElse: () { |
| 317 _logger.error('No InitializerPlugin handled the annotation: ' |
| 318 '${next.annotationElement} on: ${next.targetElement}.'); |
| 319 }); |
| 320 if (plugin == null) continue; |
| 321 |
| 322 var text = plugin.apply(data); |
| 323 if (text != null) initializersBuffer.writeln('$text,'); |
| 283 } | 324 } |
| 284 initializersBuffer.writeln(' ]);'); | 325 initializersBuffer.writeln(']);'); |
| 285 | 326 |
| 286 libraryPrefixes | 327 libraryPrefixes |
| 287 .forEach((lib, prefix) => _writeImport(lib, prefix, importsBuffer)); | 328 .forEach((lib, prefix) => _writeImport(lib, prefix, importsBuffer)); |
| 288 | 329 |
| 289 // TODO(jakemac): copyright and library declaration | 330 // TODO(jakemac): copyright and library declaration |
| 290 return new DartFormatter().format(''' | 331 return new DartFormatter().format(''' |
| 291 $importsBuffer | 332 $importsBuffer |
| 292 main() { | 333 main() { |
| 293 $initializersBuffer | 334 $initializersBuffer |
| 294 i0.main(); | 335 i0.main(); |
| (...skipping 13 matching lines...) Expand all Loading... |
| 308 path.url.split(_newEntryPoint.path)[0]) { | 349 path.url.split(_newEntryPoint.path)[0]) { |
| 309 var relativePath = | 350 var relativePath = |
| 310 path.relative(id.path, from: path.dirname(_newEntryPoint.path)); | 351 path.relative(id.path, from: path.dirname(_newEntryPoint.path)); |
| 311 buffer.write("import '${relativePath}'"); | 352 buffer.write("import '${relativePath}'"); |
| 312 } else { | 353 } else { |
| 313 _logger.error("Can't import `${id}` from `${_newEntryPoint}`"); | 354 _logger.error("Can't import `${id}` from `${_newEntryPoint}`"); |
| 314 } | 355 } |
| 315 buffer.writeln(' as $prefix;'); | 356 buffer.writeln(' as $prefix;'); |
| 316 } | 357 } |
| 317 | 358 |
| 318 _writeInitializer(_InitializerData data, | |
| 319 Map<LibraryElement, String> libraryPrefixes, StringBuffer buffer) { | |
| 320 final annotationElement = data.annotation.element; | |
| 321 final element = data.element; | |
| 322 | |
| 323 final metaPrefix = libraryPrefixes[annotationElement.library]; | |
| 324 var elementString; | |
| 325 if (element is LibraryElement) { | |
| 326 var segments = element.source.uri.pathSegments; | |
| 327 var package = segments[0]; | |
| 328 var libraryPath; | |
| 329 var packageString; | |
| 330 if (_newEntryPoint.package == package && | |
| 331 _newEntryPoint.path.startsWith('${segments[1]}/')) { | |
| 332 // reset `package` to null, we will do a relative path in this case. | |
| 333 packageString = 'null'; | |
| 334 libraryPath = path.url.relative( | |
| 335 path.url.joinAll(segments.getRange(1, segments.length)), | |
| 336 from: path.url.dirname(path.url.join(_newEntryPoint.path))); | |
| 337 } else if (segments[1] == 'lib') { | |
| 338 packageString = "'$package'"; | |
| 339 libraryPath = path.url.joinAll(segments.getRange(2, segments.length)); | |
| 340 } else { | |
| 341 _logger.error('Unable to import `${element.source.uri.path}` from ' | |
| 342 '${_newEntryPoint.path}.'); | |
| 343 } | |
| 344 | |
| 345 elementString = "const LibraryIdentifier(" | |
| 346 "#${element.name}, $packageString, '$libraryPath')"; | |
| 347 } else if (element is ClassElement || element is FunctionElement) { | |
| 348 elementString = | |
| 349 '${libraryPrefixes[data.element.library]}.${element.name}'; | |
| 350 } else { | |
| 351 _logger.error('Initializers can only be applied to top level functins, ' | |
| 352 'libraries, and classes.'); | |
| 353 } | |
| 354 | |
| 355 if (annotationElement is ConstructorElement) { | |
| 356 var node = data.element.node; | |
| 357 List<Annotation> astMeta; | |
| 358 if (node is SimpleIdentifier) { | |
| 359 astMeta = node.parent.parent.metadata; | |
| 360 } else if (node is ClassDeclaration || node is FunctionDeclaration) { | |
| 361 astMeta = node.metadata; | |
| 362 } else { | |
| 363 _logger.error( | |
| 364 'Initializer annotations are only supported on libraries, classes, ' | |
| 365 'and top level methods. Found $node.'); | |
| 366 } | |
| 367 final annotation = | |
| 368 astMeta.firstWhere((m) => m.elementAnnotation == data.annotation); | |
| 369 final clazz = annotation.name; | |
| 370 final constructor = annotation.constructorName == null | |
| 371 ? '' | |
| 372 : '.${annotation.constructorName}'; | |
| 373 // TODO(jakemac): Support more than raw values here | |
| 374 // https://github.com/dart-lang/static_init/issues/5 | |
| 375 final args = _buildArgsString(annotation.arguments, libraryPrefixes); | |
| 376 buffer.write(''' | |
| 377 new InitEntry(const $metaPrefix.${clazz}$constructor$args, $elementString), | |
| 378 '''); | |
| 379 } else if (annotationElement is PropertyAccessorElement) { | |
| 380 buffer.write(''' | |
| 381 new InitEntry($metaPrefix.${annotationElement.name}, $elementString), | |
| 382 '''); | |
| 383 } else { | |
| 384 _logger.error('Unsupported annotation type. Only constructors and ' | |
| 385 'properties are supported as initializers.'); | |
| 386 } | |
| 387 } | |
| 388 | |
| 389 String _buildArgsString( | |
| 390 ArgumentList args, Map<LibraryElement, String> libraryPrefixes) { | |
| 391 var buffer = new StringBuffer(); | |
| 392 buffer.write('('); | |
| 393 var first = true; | |
| 394 for (var arg in args.arguments) { | |
| 395 if (!first) buffer.write(', '); | |
| 396 first = false; | |
| 397 | |
| 398 Expression expression; | |
| 399 if (arg is NamedExpression) { | |
| 400 buffer.write('${arg.name.label.name}: '); | |
| 401 expression = arg.expression; | |
| 402 } else { | |
| 403 expression = arg; | |
| 404 } | |
| 405 | |
| 406 buffer.write(_expressionString(expression, libraryPrefixes)); | |
| 407 } | |
| 408 buffer.write(')'); | |
| 409 return buffer.toString(); | |
| 410 } | |
| 411 | |
| 412 String _expressionString( | |
| 413 Expression expression, Map<LibraryElement, String> libraryPrefixes) { | |
| 414 var buffer = new StringBuffer(); | |
| 415 if (expression is StringLiteral) { | |
| 416 var value = expression.stringValue; | |
| 417 if (value == null) { | |
| 418 _logger.error('Only const strings are allowed in initializer ' | |
| 419 'expressions, found $expression'); | |
| 420 } | |
| 421 value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'"); | |
| 422 buffer.write("'$value'"); | |
| 423 } else if (expression is BooleanLiteral || | |
| 424 expression is DoubleLiteral || | |
| 425 expression is IntegerLiteral || | |
| 426 expression is NullLiteral) { | |
| 427 buffer.write('${expression}'); | |
| 428 } else if (expression is ListLiteral) { | |
| 429 buffer.write('const ['); | |
| 430 var first = true; | |
| 431 for (Expression listExpression in expression.elements) { | |
| 432 if (!first) buffer.write(', '); | |
| 433 first = false; | |
| 434 buffer.write(_expressionString(listExpression, libraryPrefixes)); | |
| 435 } | |
| 436 buffer.write(']'); | |
| 437 } else if (expression is MapLiteral) { | |
| 438 buffer.write('const {'); | |
| 439 var first = true; | |
| 440 for (MapLiteralEntry entry in expression.entries) { | |
| 441 if (!first) buffer.write(', '); | |
| 442 first = false; | |
| 443 buffer.write(_expressionString(entry.key, libraryPrefixes)); | |
| 444 buffer.write(': '); | |
| 445 buffer.write(_expressionString(entry.value, libraryPrefixes)); | |
| 446 } | |
| 447 buffer.write('}'); | |
| 448 } else if (expression is Identifier) { | |
| 449 var element = expression.bestElement; | |
| 450 if (element == null || !element.isPublic) { | |
| 451 _logger.error('Private constants are not supported in intializer ' | |
| 452 'constructors, found $element.'); | |
| 453 } | |
| 454 libraryPrefixes.putIfAbsent( | |
| 455 element.library, () => 'i${libraryPrefixes.length}'); | |
| 456 | |
| 457 buffer.write('${libraryPrefixes[element.library]}.'); | |
| 458 if (element is ClassElement) { | |
| 459 buffer.write(element.name); | |
| 460 } else if (element is PropertyAccessorElement) { | |
| 461 var variable = element.variable; | |
| 462 if (variable is FieldElement) { | |
| 463 buffer.write('${variable.enclosingElement.name}.'); | |
| 464 } | |
| 465 buffer.write('${variable.name}'); | |
| 466 } else { | |
| 467 _logger.error('Unsupported argument to initializer constructor.'); | |
| 468 } | |
| 469 } else { | |
| 470 _logger.error('Only literals and identifiers are allowed for initializer ' | |
| 471 'expressions, found $expression.'); | |
| 472 } | |
| 473 return buffer.toString(); | |
| 474 } | |
| 475 | |
| 476 bool _isInitializer(InterfaceType type) { | 359 bool _isInitializer(InterfaceType type) { |
| 477 // If `_initializer` wasn't found then it was never loaded (even | 360 // If `_initializer` wasn't found then it was never loaded (even |
| 478 // transitively), and so no annotations can be initializers. | 361 // transitively), and so no annotations can be initializers. |
| 479 if (_initializer == null) return false; | 362 if (_initializer == null) return false; |
| 480 if (type == null) return false; | 363 if (type == null) return false; |
| 481 if (type.element.type == _initializer.type) return true; | 364 if (type.element.type == _initializer.type) return true; |
| 482 if (_isInitializer(type.superclass)) return true; | 365 if (_isInitializer(type.superclass)) return true; |
| 483 for (var interface in type.interfaces) { | 366 for (var interface in type.interfaces) { |
| 484 if (_isInitializer(interface)) return true; | 367 if (_isInitializer(interface)) return true; |
| 485 } | 368 } |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 555 | 438 |
| 556 // And finally compare based on the relative uri if both are file paths. | 439 // And finally compare based on the relative uri if both are file paths. |
| 557 var aUri = path.relative(a.source.uri.path, | 440 var aUri = path.relative(a.source.uri.path, |
| 558 from: path.dirname(library.source.uri.path)); | 441 from: path.dirname(library.source.uri.path)); |
| 559 var bUri = path.relative(b.source.uri.path, | 442 var bUri = path.relative(b.source.uri.path, |
| 560 from: path.dirname(library.source.uri.path)); | 443 from: path.dirname(library.source.uri.path)); |
| 561 return aUri.compareTo(bUri); | 444 return aUri.compareTo(bUri); |
| 562 })).map((import) => import.importedLibrary); | 445 })).map((import) => import.importedLibrary); |
| 563 } | 446 } |
| 564 | 447 |
| 565 // Element/ElementAnnotation pair. | 448 /// An [Initializer] annotation and the target of that annotation. |
| 566 class _InitializerData { | 449 class InitializerData { |
| 567 final Element element; | 450 /// The target [Element] of the annotation. |
| 568 final ElementAnnotation annotation; | 451 final Element targetElement; |
| 569 | 452 |
| 570 _InitializerData(this.element, this.annotation); | 453 /// The [ElementAnnotation] representing the annotation itself. |
| 454 final ElementAnnotation annotationElement; |
| 455 |
| 456 AstNode _targetNode; |
| 457 |
| 458 /// The target [AstNode] of the annotation. |
| 459 // TODO(jakemac): We at least cache this for now, but ideally `targetElement` |
| 460 // would actually be the getter, and `targetNode` would be the property. |
| 461 AstNode get targetNode { |
| 462 if (_targetNode == null) _targetNode = targetElement.node; |
| 463 return _targetNode; |
| 464 } |
| 465 |
| 466 /// The [Annotation] representing the annotation itself. |
| 467 Annotation get annotationNode { |
| 468 var annotatedNode; |
| 469 if (targetNode is SimpleIdentifier && |
| 470 targetNode.parent is LibraryIdentifier) { |
| 471 annotatedNode = targetNode.parent.parent; |
| 472 } else if (targetNode is ClassDeclaration || |
| 473 targetNode is FunctionDeclaration) { |
| 474 annotatedNode = targetNode; |
| 475 } else { |
| 476 return null; |
| 477 } |
| 478 if (annotatedNode is! AnnotatedNode) return null; |
| 479 var astMeta = annotatedNode.metadata; |
| 480 |
| 481 return astMeta.firstWhere((m) => m.elementAnnotation == annotationElement); |
| 482 } |
| 483 |
| 484 InitializerData._(this.targetElement, this.annotationElement); |
| 571 } | 485 } |
| 572 | 486 |
| 573 // Reads a file list from a barback settings configuration field. | 487 // Reads a file list from a barback settings configuration field. |
| 574 _readFileList(BarbackSettings settings, String field) { | 488 _readFileList(BarbackSettings settings, String field) { |
| 575 var value = settings.configuration[field]; | 489 var value = settings.configuration[field]; |
| 576 if (value == null) return null; | 490 if (value == null) return null; |
| 577 var files = []; | 491 var files = []; |
| 578 bool error; | 492 bool error; |
| 579 if (value is List) { | 493 if (value is List) { |
| 580 files = value; | 494 files = value; |
| 581 error = value.any((e) => e is! String); | 495 error = value.any((e) => e is! String); |
| 582 } else if (value is String) { | 496 } else if (value is String) { |
| 583 files = [value]; | 497 files = [value]; |
| 584 error = false; | 498 error = false; |
| 585 } else { | 499 } else { |
| 586 error = true; | 500 error = true; |
| 587 } | 501 } |
| 588 if (error) { | 502 if (error) { |
| 589 print('Bad value for "$field" in the initialize transformer. ' | 503 print('Bad value for "$field" in the initialize transformer. ' |
| 590 'Expected either one String or a list of Strings.'); | 504 'Expected either one String or a list of Strings.'); |
| 591 } | 505 } |
| 592 return files; | 506 return files; |
| 593 } | 507 } |
| OLD | NEW |