| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2014, 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 |
| 5 library code_transformer.src.resolver_impl; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'package:analyzer/src/generated/ast.dart'; |
| 9 import 'package:analyzer/src/generated/element.dart'; |
| 10 import 'package:analyzer/src/generated/engine.dart'; |
| 11 import 'package:analyzer/src/generated/error.dart'; |
| 12 import 'package:analyzer/src/generated/java_io.dart'; |
| 13 import 'package:analyzer/src/generated/parser.dart' show Parser; |
| 14 import 'package:analyzer/src/generated/scanner.dart'; |
| 15 import 'package:analyzer/src/generated/sdk.dart' show DartSdk; |
| 16 import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; |
| 17 import 'package:analyzer/src/generated/source.dart'; |
| 18 import 'package:barback/barback.dart'; |
| 19 import 'package:path/path.dart' as path; |
| 20 import 'package:source_maps/refactor.dart'; |
| 21 import 'package:source_maps/span.dart' show SourceFile, Span; |
| 22 |
| 23 import 'resolver.dart'; |
| 24 |
| 25 /// Resolves and updates an AST based on Barback-based assets. |
| 26 /// |
| 27 /// This also provides a handful of useful APIs for traversing and working |
| 28 /// with the resolved AST. |
| 29 class ResolverImpl implements Resolver { |
| 30 /// Cache of all asset sources currently referenced. |
| 31 final Map<AssetId, _AssetBasedSource> sources = |
| 32 <AssetId, _AssetBasedSource>{}; |
| 33 |
| 34 /// The Dart entry point file where parsing begins. |
| 35 final AssetId entryPoint; |
| 36 |
| 37 final AnalysisContext _context = |
| 38 AnalysisEngine.instance.createAnalysisContext(); |
| 39 |
| 40 /// Transform for which this is currently updating, or null when not updating. |
| 41 Transform _currentTransform; |
| 42 |
| 43 /// The currently resolved library, or null if unresolved. |
| 44 LibraryElement _entryLibrary; |
| 45 |
| 46 /// Handler for all Dart SDK (dart:) sources. |
| 47 DirectoryBasedDartSdk _dartSdk; |
| 48 |
| 49 /// Creates a resolver that will resolve the Dart code starting at |
| 50 /// [entryPoint]. |
| 51 /// |
| 52 /// [sdkDir] is the root directory of the Dart SDK, for resolving dart: |
| 53 /// imports. |
| 54 ResolverImpl(this.entryPoint, String sdkDir, {AnalysisOptions options}) { |
| 55 if (options == null) { |
| 56 options = new AnalysisOptionsImpl() |
| 57 ..cacheSize = 256 // # of sources to cache ASTs for. |
| 58 ..preserveComments = false |
| 59 ..analyzeFunctionBodies = true; |
| 60 } |
| 61 _context.analysisOptions = options; |
| 62 |
| 63 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); |
| 64 _dartSdk.context.analysisOptions = options; |
| 65 |
| 66 _context.sourceFactory = new SourceFactory.con2([ |
| 67 new DartUriResolverProxy(_dartSdk), |
| 68 new _AssetUriResolver(this)]); |
| 69 } |
| 70 |
| 71 LibraryElement get entryLibrary => _entryLibrary; |
| 72 |
| 73 |
| 74 /// Update the status of all the sources referenced by the entryPoint and |
| 75 /// update the resolved library. |
| 76 /// |
| 77 /// This will be invoked automatically by [ResolverTransformer]. Only one |
| 78 /// transformer may update this at a time. |
| 79 Future updateSources(Transform transform) { |
| 80 if (_currentTransform != null) { |
| 81 throw new StateError('Cannot be accessed by concurrent transforms'); |
| 82 } |
| 83 _currentTransform = transform; |
| 84 // Clear this out and update once all asset changes have been processed. |
| 85 _entryLibrary = null; |
| 86 |
| 87 // Basic approach is to start at the first file, update it's contents |
| 88 // and see if it changed, then walk all files accessed by it. |
| 89 var visited = new Set<AssetId>(); |
| 90 var visiting = new FutureGroup(); |
| 91 |
| 92 void processAsset(AssetId assetId) { |
| 93 visited.add(assetId); |
| 94 |
| 95 visiting.add(transform.readInputAsString(assetId).then((contents) { |
| 96 var source = sources[assetId]; |
| 97 if (source == null) { |
| 98 source = new _AssetBasedSource(assetId, this); |
| 99 sources[assetId] = source; |
| 100 } |
| 101 source.updateContents(contents); |
| 102 |
| 103 source.dependentAssets |
| 104 .where((id) => !visited.contains(id)) |
| 105 .forEach(processAsset); |
| 106 |
| 107 }, onError: (e) { |
| 108 _context.applyChanges(new ChangeSet()..removed(sources[assetId])); |
| 109 sources.remove(assetId); |
| 110 })); |
| 111 } |
| 112 processAsset(entryPoint); |
| 113 |
| 114 // Once we have all asset sources updated with the new contents then |
| 115 // resolve everything. |
| 116 return visiting.future.then((_) { |
| 117 var changeSet = new ChangeSet(); |
| 118 var unreachableAssets = new Set.from(sources.keys).difference(visited); |
| 119 for (var unreachable in unreachableAssets) { |
| 120 changeSet.removed(sources[unreachable]); |
| 121 sources.remove(unreachable); |
| 122 } |
| 123 |
| 124 // Update the analyzer context with the latest sources |
| 125 _context.applyChanges(changeSet); |
| 126 // Resolve the AST |
| 127 _entryLibrary = _context.computeLibraryElement(sources[entryPoint]); |
| 128 _currentTransform = null; |
| 129 }); |
| 130 } |
| 131 |
| 132 Iterable<LibraryElement> get libraries => entryLibrary.visibleLibraries; |
| 133 |
| 134 LibraryElement getLibraryByName(String libraryName) => |
| 135 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); |
| 136 |
| 137 LibraryElement getLibraryByUri(Uri uri) => |
| 138 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); |
| 139 |
| 140 ClassElement getType(String typeName) { |
| 141 var dotIndex = typeName.lastIndexOf('.'); |
| 142 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); |
| 143 |
| 144 var className = dotIndex == -1 ? |
| 145 typeName : typeName.substring(dotIndex + 1); |
| 146 |
| 147 for (var lib in libraries.where((l) => l.name == libraryName)) { |
| 148 var type = lib.getType(className); |
| 149 if (type != null) return type; |
| 150 } |
| 151 return null; |
| 152 } |
| 153 |
| 154 Element getLibraryVariable(String variableName) { |
| 155 var dotIndex = variableName.lastIndexOf('.'); |
| 156 var libraryName = dotIndex == -1 ? '' : variableName.substring(0, dotIndex); |
| 157 |
| 158 var name = dotIndex == -1 ? |
| 159 variableName : variableName.substring(dotIndex + 1); |
| 160 |
| 161 return libraries.where((lib) => lib.name == libraryName) |
| 162 .expand((lib) => lib.units) |
| 163 .expand((unit) => unit.topLevelVariables) |
| 164 .firstWhere((variable) => variable.name == name, |
| 165 orElse: () => null); |
| 166 } |
| 167 |
| 168 Element getLibraryFunction(String fnName) { |
| 169 var dotIndex = fnName.lastIndexOf('.'); |
| 170 var libraryName = dotIndex == -1 ? '' : fnName.substring(0, dotIndex); |
| 171 |
| 172 var name = dotIndex == -1 ? |
| 173 fnName : fnName.substring(dotIndex + 1); |
| 174 |
| 175 return libraries.where((lib) => lib.name == libraryName) |
| 176 .expand((lib) => lib.units) |
| 177 .expand((unit) => unit.functions) |
| 178 .firstWhere((fn) => fn.name == name, |
| 179 orElse: () => null); |
| 180 } |
| 181 |
| 182 Uri getImportUri(LibraryElement lib, {AssetId from}) => |
| 183 _getSourceUri(lib, from: from); |
| 184 |
| 185 |
| 186 /// Similar to getImportUri but will get the part URI for parts rather than |
| 187 /// the library URI. |
| 188 Uri _getSourceUri(Element element, {AssetId from}) { |
| 189 var source = element.source; |
| 190 if (source is _AssetBasedSource) { |
| 191 return source.getSourceUri(from); |
| 192 } else if (source is _DartSourceProxy) { |
| 193 return source.uri; |
| 194 } |
| 195 // Should not be able to encounter any other source types. |
| 196 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); |
| 197 } |
| 198 |
| 199 AssetId getSourceAssetId(Element element) { |
| 200 var source = element.source; |
| 201 if (source is _AssetBasedSource) return source.assetId; |
| 202 return null; |
| 203 } |
| 204 |
| 205 Span getSourceSpan(Element element) { |
| 206 var sourceFile = _getSourceFile(element); |
| 207 if (sourceFile == null) return null; |
| 208 return sourceFile.span(element.node.offset, element.node.end); |
| 209 } |
| 210 |
| 211 TextEditTransaction createTextEditTransaction(Element element) { |
| 212 if (element.source is! _AssetBasedSource) return null; |
| 213 |
| 214 _AssetBasedSource source = element.source; |
| 215 // Cannot modify assets in other packages. |
| 216 if (source.assetId.package != entryPoint.package) return null; |
| 217 |
| 218 var sourceFile = _getSourceFile(element); |
| 219 if (sourceFile == null) return null; |
| 220 |
| 221 return new TextEditTransaction(source.contents, sourceFile); |
| 222 } |
| 223 |
| 224 /// Gets the SourceFile for the source of the element. |
| 225 SourceFile _getSourceFile(Element element) { |
| 226 var assetId = getSourceAssetId(element); |
| 227 if (assetId == null) return null; |
| 228 |
| 229 var importUri = _getSourceUri(element, from: entryPoint); |
| 230 var spanPath = importUri != null ? importUri.toString() : assetId.path; |
| 231 return new SourceFile.text(spanPath, sources[assetId].contents); |
| 232 } |
| 233 } |
| 234 |
| 235 /// Implementation of Analyzer's Source for Barback based assets. |
| 236 class _AssetBasedSource extends Source { |
| 237 |
| 238 /// Asset ID where this source can be found. |
| 239 final AssetId assetId; |
| 240 |
| 241 /// The resolver this is being used in. |
| 242 final ResolverImpl _resolver; |
| 243 |
| 244 /// Cache of dependent asset IDs, to avoid re-parsing the AST. |
| 245 Iterable<AssetId> _dependentAssets; |
| 246 |
| 247 /// The current revision of the file, incremented only when file changes. |
| 248 int _revision = 0; |
| 249 |
| 250 /// The file contents. |
| 251 String _contents; |
| 252 |
| 253 _AssetBasedSource(this.assetId, this._resolver); |
| 254 |
| 255 /// Update the contents of this file with [contents]. |
| 256 /// |
| 257 /// Returns true if the contents of this asset have changed. |
| 258 bool updateContents(String contents) { |
| 259 if (contents == _contents) return false; |
| 260 var added = _contents == null; |
| 261 _contents = contents; |
| 262 ++_revision; |
| 263 // Invalidate the imports so we only parse the AST when needed. |
| 264 _dependentAssets = null; |
| 265 |
| 266 if (added) { |
| 267 _resolver._context.applyChanges(new ChangeSet()..added(this)); |
| 268 } else { |
| 269 _resolver._context.applyChanges(new ChangeSet()..changed(this)); |
| 270 } |
| 271 |
| 272 var compilationUnit = _resolver._context.parseCompilationUnit(this); |
| 273 _dependentAssets = compilationUnit.directives |
| 274 .where((d) => (d is ImportDirective || d is PartDirective || |
| 275 d is ExportDirective)) |
| 276 .map((d) => _resolve(assetId, d.uri.stringValue, |
| 277 _logger, _getSpan(d))) |
| 278 .where((id) => id != null).toSet(); |
| 279 return true; |
| 280 } |
| 281 |
| 282 /// Contents of the file. |
| 283 String get contents => _contents; |
| 284 |
| 285 /// Logger for the current transform. |
| 286 /// |
| 287 /// Only valid while the resolver is updating assets. |
| 288 TransformLogger get _logger => _resolver._currentTransform.logger; |
| 289 |
| 290 /// Gets all imports/parts/exports which resolve to assets (non-Dart files). |
| 291 Iterable<AssetId> get dependentAssets => _dependentAssets; |
| 292 |
| 293 bool exists() => true; |
| 294 |
| 295 bool operator ==(Object other) => |
| 296 other is _AssetBasedSource && assetId == other.assetId; |
| 297 |
| 298 int get hashCode => assetId.hashCode; |
| 299 |
| 300 void getContents(Source_ContentReceiver receiver) { |
| 301 receiver.accept(contents, modificationStamp); |
| 302 } |
| 303 |
| 304 String get encoding => |
| 305 "${uriKind.encoding}${assetId.package}/${assetId.path}"; |
| 306 |
| 307 String get fullName => assetId.toString(); |
| 308 |
| 309 int get modificationStamp => _revision; |
| 310 |
| 311 String get shortName => path.basename(assetId.path); |
| 312 |
| 313 UriKind get uriKind { |
| 314 if (assetId.path.startsWith('lib/')) return UriKind.PACKAGE_URI; |
| 315 return UriKind.FILE_URI; |
| 316 } |
| 317 |
| 318 bool get isInSystemLibrary => false; |
| 319 |
| 320 Source resolveRelative(Uri relativeUri) { |
| 321 var id = _resolve(assetId, relativeUri.toString(), _logger, null); |
| 322 if (id == null) return null; |
| 323 |
| 324 // The entire AST should have been parsed and loaded at this point. |
| 325 var source = _resolver.sources[id]; |
| 326 if (source == null) { |
| 327 _logger.error('Could not load asset $id'); |
| 328 } |
| 329 return source; |
| 330 } |
| 331 |
| 332 /// For logging errors. |
| 333 Span _getSpan(ASTNode node) => _sourceFile.span(node.offset, node.end); |
| 334 /// For logging errors. |
| 335 SourceFile get _sourceFile { |
| 336 var uri = getSourceUri(_resolver.entryPoint); |
| 337 var path = uri != null ? uri.toString() : assetId.path; |
| 338 |
| 339 return new SourceFile.text(path, contents); |
| 340 } |
| 341 |
| 342 /// Gets a URI which would be appropriate for importing this file. |
| 343 /// |
| 344 /// Note that this file may represent a non-importable file such as a part. |
| 345 Uri getSourceUri([AssetId from]) { |
| 346 if (!assetId.path.startsWith('lib/')) { |
| 347 // Cannot do absolute imports of non lib-based assets. |
| 348 if (from == null) return null; |
| 349 |
| 350 if (assetId.package != from.package) return null; |
| 351 return new Uri( |
| 352 path: path.relative(assetId.path, from: path.dirname(from.path))); |
| 353 } |
| 354 |
| 355 return Uri.parse('package:${assetId.package}/${assetId.path.substring(4)}'); |
| 356 } |
| 357 } |
| 358 |
| 359 /// Implementation of Analyzer's UriResolver for Barback based assets. |
| 360 class _AssetUriResolver implements UriResolver { |
| 361 final ResolverImpl _resolver; |
| 362 _AssetUriResolver(this._resolver); |
| 363 |
| 364 Source resolveAbsolute(ContentCache contentCache, Uri uri) { |
| 365 var assetId = _resolve(null, uri.toString(), logger, null); |
| 366 var source = _resolver.sources[assetId]; |
| 367 /// All resolved assets should be available by this point. |
| 368 if (source == null) { |
| 369 logger.error('Unable to find asset for "$uri"'); |
| 370 } |
| 371 return source; |
| 372 } |
| 373 |
| 374 Source fromEncoding(ContentCache contentCache, UriKind kind, Uri uri) => |
| 375 throw new UnsupportedError('fromEncoding is not supported'); |
| 376 |
| 377 Uri restoreAbsolute(Source source) => |
| 378 throw new UnsupportedError('restoreAbsolute is not supported'); |
| 379 |
| 380 TransformLogger get logger => _resolver._currentTransform.logger; |
| 381 } |
| 382 |
| 383 |
| 384 /// Dart SDK which wraps all Dart sources to ensure they are tracked with Uris. |
| 385 /// |
| 386 /// Just a simple wrapper to make it easy to make sure that all sources we |
| 387 /// encounter are either _AssetBasedSource or _DartSourceProxy. |
| 388 class _DirectoryBasedDartSdkProxy extends DirectoryBasedDartSdk { |
| 389 _DirectoryBasedDartSdkProxy(JavaFile sdkDirectory) : super(sdkDirectory); |
| 390 |
| 391 Source mapDartUri(String dartUri) => |
| 392 _DartSourceProxy.wrap(super.mapDartUri(dartUri), Uri.parse(dartUri)); |
| 393 } |
| 394 |
| 395 |
| 396 /// Dart SDK resolver which wraps all Dart sources to ensure they are tracked |
| 397 /// with URIs. |
| 398 class DartUriResolverProxy implements DartUriResolver { |
| 399 final DartUriResolver _proxy; |
| 400 DartUriResolverProxy(DirectoryBasedDartSdk sdk) : |
| 401 _proxy = new DartUriResolver(sdk); |
| 402 |
| 403 Source resolveAbsolute(ContentCache contentCache, Uri uri) => |
| 404 _DartSourceProxy.wrap(_proxy.resolveAbsolute(contentCache, uri), uri); |
| 405 |
| 406 DartSdk get dartSdk => _proxy.dartSdk; |
| 407 |
| 408 Source fromEncoding(ContentCache contentCache, UriKind kind, Uri uri) => |
| 409 throw new UnsupportedError('fromEncoding is not supported'); |
| 410 |
| 411 Uri restoreAbsolute(Source source) => |
| 412 throw new UnsupportedError('restoreAbsolute is not supported'); |
| 413 } |
| 414 |
| 415 /// Source file for dart: sources which track the sources with dart: URIs. |
| 416 /// |
| 417 /// This is primarily to support [Resolver.getImportUri] for Dart SDK (dart:) |
| 418 /// based libraries. |
| 419 class _DartSourceProxy implements Source { |
| 420 |
| 421 /// Absolute URI which this source can be imported from |
| 422 final Uri uri; |
| 423 |
| 424 /// Underlying source object. |
| 425 final Source _proxy; |
| 426 |
| 427 _DartSourceProxy(this._proxy, this.uri); |
| 428 |
| 429 /// Ensures that [source] is a _DartSourceProxy. |
| 430 static _DartSourceProxy wrap(Source source, Uri uri) { |
| 431 if (source == null || source is _DartSourceProxy) return source; |
| 432 return new _DartSourceProxy(source, uri); |
| 433 } |
| 434 |
| 435 Source resolveRelative(Uri relativeUri) { |
| 436 // Assume that the type can be accessed via this URI, since these |
| 437 // should only be parts for dart core files. |
| 438 return wrap(_proxy.resolveRelative(relativeUri), uri); |
| 439 } |
| 440 |
| 441 bool exists() => _proxy.exists(); |
| 442 |
| 443 bool operator ==(Object other) => |
| 444 (other is _DartSourceProxy && _proxy == other._proxy); |
| 445 |
| 446 int get hashCode => _proxy.hashCode; |
| 447 |
| 448 void getContents(Source_ContentReceiver receiver) { |
| 449 _proxy.getContents(receiver); |
| 450 } |
| 451 |
| 452 String get encoding => _proxy.encoding; |
| 453 |
| 454 String get fullName => _proxy.fullName; |
| 455 |
| 456 int get modificationStamp => _proxy.modificationStamp; |
| 457 |
| 458 String get shortName => _proxy.shortName; |
| 459 |
| 460 UriKind get uriKind => _proxy.uriKind; |
| 461 |
| 462 bool get isInSystemLibrary => _proxy.isInSystemLibrary; |
| 463 } |
| 464 |
| 465 /// Get an asset ID for a URL relative to another source asset. |
| 466 AssetId _resolve(AssetId source, String url, TransformLogger logger, |
| 467 Span span) { |
| 468 if (url == null || url == '') return null; |
| 469 var urlBuilder = path.url; |
| 470 var uri = Uri.parse(url); |
| 471 |
| 472 if (uri.scheme == 'package') { |
| 473 var segments = new List.from(uri.pathSegments); |
| 474 var package = segments[0]; |
| 475 segments[0] = 'lib'; |
| 476 return new AssetId(package, segments.join(urlBuilder.separator)); |
| 477 } |
| 478 // Dart SDK libraries do not have assets. |
| 479 if (uri.scheme == 'dart') return null; |
| 480 |
| 481 if (uri.host != '' || uri.scheme != '' || urlBuilder.isAbsolute(url)) { |
| 482 logger.error('absolute paths not allowed: "$url"', span: span); |
| 483 return null; |
| 484 } |
| 485 |
| 486 var targetPath = urlBuilder.normalize( |
| 487 urlBuilder.join(urlBuilder.dirname(source.path), url)); |
| 488 return new AssetId(source.package, targetPath); |
| 489 } |
| 490 |
| 491 |
| 492 /// A completer that waits until all added [Future]s complete. |
| 493 // TODO(blois): Copied from quiver. Remove from here when it gets |
| 494 // added to dart:core. (See #6626.) |
| 495 class FutureGroup<E> { |
| 496 static const _FINISHED = -1; |
| 497 |
| 498 int _pending = 0; |
| 499 Future _failedTask; |
| 500 final Completer<List> _completer = new Completer<List>(); |
| 501 final List results = []; |
| 502 |
| 503 /** Gets the task that failed, if any. */ |
| 504 Future get failedTask => _failedTask; |
| 505 |
| 506 /** |
| 507 * Wait for [task] to complete. |
| 508 * |
| 509 * If this group has already been marked as completed, a [StateError] will be |
| 510 * thrown. |
| 511 * |
| 512 * If this group has a [failedTask], new tasks will be ignored, because the |
| 513 * error has already been signaled. |
| 514 */ |
| 515 void add(Future task) { |
| 516 if (_failedTask != null) return; |
| 517 if (_pending == _FINISHED) throw new StateError("Future already completed"); |
| 518 |
| 519 _pending++; |
| 520 var i = results.length; |
| 521 results.add(null); |
| 522 task.then((res) { |
| 523 results[i] = res; |
| 524 if (_failedTask != null) return; |
| 525 _pending--; |
| 526 if (_pending == 0) { |
| 527 _pending = _FINISHED; |
| 528 _completer.complete(results); |
| 529 } |
| 530 }, onError: (e, s) { |
| 531 if (_failedTask != null) return; |
| 532 _failedTask = task; |
| 533 _completer.completeError(e, s); |
| 534 }); |
| 535 } |
| 536 |
| 537 /** |
| 538 * A Future that complets with a List of the values from all the added |
| 539 * tasks, when they have all completed. |
| 540 * |
| 541 * If any task fails, this Future will receive the error. Only the first |
| 542 * error will be sent to the Future. |
| 543 */ |
| 544 Future<List<E>> get future => _completer.future; |
| 545 } |
| OLD | NEW |