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