| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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 | 4 |
| 5 library code_transformer.src.resolver_impl; | 5 library code_transformer.src.resolver_impl; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 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:analyzer/src/generated/engine.dart'; | 10 import 'package:analyzer/src/generated/engine.dart'; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 | 24 |
| 25 /// Resolves and updates an AST based on Barback-based assets. | 25 /// Resolves and updates an AST based on Barback-based assets. |
| 26 /// | 26 /// |
| 27 /// This also provides a handful of useful APIs for traversing and working | 27 /// This also provides a handful of useful APIs for traversing and working |
| 28 /// with the resolved AST. | 28 /// with the resolved AST. |
| 29 class ResolverImpl implements Resolver { | 29 class ResolverImpl implements Resolver { |
| 30 /// Cache of all asset sources currently referenced. | 30 /// Cache of all asset sources currently referenced. |
| 31 final Map<AssetId, _AssetBasedSource> sources = | 31 final Map<AssetId, _AssetBasedSource> sources = |
| 32 <AssetId, _AssetBasedSource>{}; | 32 <AssetId, _AssetBasedSource>{}; |
| 33 | 33 |
| 34 /// The Dart entry point file where parsing begins. | |
| 35 final AssetId entryPoint; | |
| 36 | |
| 37 final AnalysisContext _context = | 34 final AnalysisContext _context = |
| 38 AnalysisEngine.instance.createAnalysisContext(); | 35 AnalysisEngine.instance.createAnalysisContext(); |
| 39 | 36 |
| 40 /// Transform for which this is currently updating, or null when not updating. | 37 /// Transform for which this is currently updating, or null when not updating. |
| 41 Transform _currentTransform; | 38 Transform _currentTransform; |
| 42 | 39 |
| 43 /// The currently resolved library, or null if unresolved. | |
| 44 LibraryElement _entryLibrary; | |
| 45 | |
| 46 /// Future indicating when this resolver is done in the current phase. | 40 /// Future indicating when this resolver is done in the current phase. |
| 47 Future _lastPhaseComplete = new Future.value(); | 41 Future _lastPhaseComplete = new Future.value(); |
| 48 | 42 |
| 49 /// Completer for wrapping up the current phase. | 43 /// Completer for wrapping up the current phase. |
| 50 Completer _currentPhaseComplete; | 44 Completer _currentPhaseComplete; |
| 51 | 45 |
| 52 /// Handler for all Dart SDK (dart:) sources. | 46 /// Handler for all Dart SDK (dart:) sources. |
| 53 DirectoryBasedDartSdk _dartSdk; | 47 DirectoryBasedDartSdk _dartSdk; |
| 54 | 48 |
| 55 /// Creates a resolver that will resolve the Dart code starting at | 49 List<LibraryElement> _entryLibraries; |
| 56 /// [entryPoint]. | 50 |
| 57 /// | 51 /// Creates a resolver, where [sdkDir] is the root directory of the Dart SDK, |
| 58 /// [sdkDir] is the root directory of the Dart SDK, for resolving dart: | 52 /// for resolving `dart:*` imports. |
| 59 /// imports. | 53 ResolverImpl(String sdkDir, {AnalysisOptions options}) { |
| 60 ResolverImpl(this.entryPoint, String sdkDir, {AnalysisOptions options}) { | |
| 61 if (options == null) { | 54 if (options == null) { |
| 62 options = new AnalysisOptionsImpl() | 55 options = new AnalysisOptionsImpl() |
| 63 ..cacheSize = 256 // # of sources to cache ASTs for. | 56 ..cacheSize = 256 // # of sources to cache ASTs for. |
| 64 ..preserveComments = false | 57 ..preserveComments = false |
| 65 ..analyzeFunctionBodies = true; | 58 ..analyzeFunctionBodies = true; |
| 66 } | 59 } |
| 67 _context.analysisOptions = options; | 60 _context.analysisOptions = options; |
| 68 | 61 |
| 69 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); | 62 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); |
| 70 _dartSdk.context.analysisOptions = options; | 63 _dartSdk.context.analysisOptions = options; |
| 71 | 64 |
| 72 _context.sourceFactory = new SourceFactory([ | 65 _context.sourceFactory = new SourceFactory([ |
| 73 new DartUriResolverProxy(_dartSdk), | 66 new DartUriResolverProxy(_dartSdk), |
| 74 new _AssetUriResolver(this)]); | 67 new _AssetUriResolver(this)]); |
| 75 } | 68 } |
| 76 | 69 |
| 77 LibraryElement get entryLibrary => _entryLibrary; | 70 LibraryElement getLibrary(AssetId assetId) => sources[assetId].libraryElement; |
| 78 | 71 |
| 79 Future<Resolver> resolve(Transform transform) { | 72 Future<Resolver> resolve(Transform transform, [List<AssetId> entryPoints]) { |
| 80 // Can only have one resolve in progress at a time, so chain the current | 73 // Can only have one resolve in progress at a time, so chain the current |
| 81 // resolution to be after the last one. | 74 // resolution to be after the last one. |
| 82 var phaseComplete = new Completer(); | 75 var phaseComplete = new Completer(); |
| 83 var future = _lastPhaseComplete.then((_) { | 76 var future = _lastPhaseComplete.then((_) { |
| 84 _currentPhaseComplete = phaseComplete; | 77 _currentPhaseComplete = phaseComplete; |
| 85 | 78 return _performResolve(transform, |
| 86 return _performResolve(transform); | 79 entryPoints == null ? [transform.primaryInput.id] : entryPoints); |
| 87 }).then((_) => this); | 80 }).then((_) => this); |
| 88 // Advance the lastPhaseComplete to be done when this phase is all done. | 81 // Advance the lastPhaseComplete to be done when this phase is all done. |
| 89 _lastPhaseComplete = phaseComplete.future; | 82 _lastPhaseComplete = phaseComplete.future; |
| 90 return future; | 83 return future; |
| 91 } | 84 } |
| 92 | 85 |
| 93 void release() { | 86 void release() { |
| 94 if (_currentPhaseComplete == null) { | 87 if (_currentPhaseComplete == null) { |
| 95 throw new StateError('Releasing without current lock.'); | 88 throw new StateError('Releasing without current lock.'); |
| 96 } | 89 } |
| 97 _currentPhaseComplete.complete(null); | 90 _currentPhaseComplete.complete(null); |
| 98 _currentPhaseComplete = null; | 91 _currentPhaseComplete = null; |
| 99 | 92 |
| 100 // Clear out the entry lib since it should not be referenced after release. | 93 // Clear out libraries since they should not be referenced after release. |
| 101 _entryLibrary = null; | 94 sources.values.forEach((source) { source._libraryElement = null; }); |
| 102 } | 95 } |
| 103 | 96 |
| 104 Future _performResolve(Transform transform) { | 97 Future _performResolve(Transform transform, List<AssetId> entryPoints) { |
| 105 if (_currentTransform != null) { | 98 if (_currentTransform != null) { |
| 106 throw new StateError('Cannot be accessed by concurrent transforms'); | 99 throw new StateError('Cannot be accessed by concurrent transforms'); |
| 107 } | 100 } |
| 108 _currentTransform = transform; | 101 _currentTransform = transform; |
| 109 | 102 |
| 110 // Basic approach is to start at the first file, update it's contents | 103 // Basic approach is to start at the first file, update it's contents |
| 111 // and see if it changed, then walk all files accessed by it. | 104 // and see if it changed, then walk all files accessed by it. |
| 112 var visited = new Set<AssetId>(); | 105 var visited = new Set<AssetId>(); |
| 113 var visiting = new FutureGroup(); | 106 var visiting = new FutureGroup(); |
| 114 | 107 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 125 | 118 |
| 126 source.dependentAssets | 119 source.dependentAssets |
| 127 .where((id) => !visited.contains(id)) | 120 .where((id) => !visited.contains(id)) |
| 128 .forEach(processAsset); | 121 .forEach(processAsset); |
| 129 | 122 |
| 130 }, onError: (e) { | 123 }, onError: (e) { |
| 131 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); | 124 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); |
| 132 sources.remove(assetId); | 125 sources.remove(assetId); |
| 133 })); | 126 })); |
| 134 } | 127 } |
| 135 processAsset(entryPoint); | 128 entryPoints.forEach(processAsset); |
| 136 | 129 |
| 137 // Once we have all asset sources updated with the new contents then | 130 // Once we have all asset sources updated with the new contents then |
| 138 // resolve everything. | 131 // resolve everything. |
| 139 return visiting.future.then((_) { | 132 return visiting.future.then((_) { |
| 140 var changeSet = new ChangeSet(); | 133 var changeSet = new ChangeSet(); |
| 141 var unreachableAssets = new Set.from(sources.keys).difference(visited); | 134 var unreachableAssets = new Set.from(sources.keys).difference(visited); |
| 142 for (var unreachable in unreachableAssets) { | 135 for (var unreachable in unreachableAssets) { |
| 143 changeSet.removedSource(sources[unreachable]); | 136 changeSet.removedSource(sources[unreachable]); |
| 144 sources.remove(unreachable); | 137 sources.remove(unreachable); |
| 145 } | 138 } |
| 146 | 139 |
| 147 // Update the analyzer context with the latest sources | 140 // Update the analyzer context with the latest sources |
| 148 _context.applyChanges(changeSet); | 141 _context.applyChanges(changeSet); |
| 149 // Resolve the AST | 142 // Force resolve each entry point (the getter will ensure the library is |
| 150 _entryLibrary = _context.computeLibraryElement(sources[entryPoint]); | 143 // computed first). |
| 144 _entryLibraries = |
| 145 entryPoints.map((e) => sources[e].libraryElement).toList(); |
| 151 _currentTransform = null; | 146 _currentTransform = null; |
| 152 }); | 147 }); |
| 153 } | 148 } |
| 154 | 149 |
| 155 Iterable<LibraryElement> get libraries => entryLibrary.visibleLibraries; | 150 Iterable<LibraryElement> get libraries => |
| 151 _entryLibraries.expand((e) => e.visibleLibraries); |
| 156 | 152 |
| 157 LibraryElement getLibraryByName(String libraryName) => | 153 LibraryElement getLibraryByName(String libraryName) => |
| 158 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); | 154 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); |
| 159 | 155 |
| 160 LibraryElement getLibraryByUri(Uri uri) => | 156 LibraryElement getLibraryByUri(Uri uri) => |
| 161 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); | 157 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); |
| 162 | 158 |
| 163 ClassElement getType(String typeName) { | 159 ClassElement getType(String typeName) { |
| 164 var dotIndex = typeName.lastIndexOf('.'); | 160 var dotIndex = typeName.lastIndexOf('.'); |
| 165 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); | 161 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 219 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); | 215 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); |
| 220 } | 216 } |
| 221 | 217 |
| 222 AssetId getSourceAssetId(Element element) { | 218 AssetId getSourceAssetId(Element element) { |
| 223 var source = element.source; | 219 var source = element.source; |
| 224 if (source is _AssetBasedSource) return source.assetId; | 220 if (source is _AssetBasedSource) return source.assetId; |
| 225 return null; | 221 return null; |
| 226 } | 222 } |
| 227 | 223 |
| 228 Span getSourceSpan(Element element) { | 224 Span getSourceSpan(Element element) { |
| 229 var sourceFile = _getSourceFile(element); | 225 var sourceFile = getSourceFile(element); |
| 230 if (sourceFile == null) return null; | 226 if (sourceFile == null) return null; |
| 231 return sourceFile.span(element.node.offset, element.node.end); | 227 return sourceFile.span(element.node.offset, element.node.end); |
| 232 } | 228 } |
| 233 | 229 |
| 234 TextEditTransaction createTextEditTransaction(Element element) { | 230 TextEditTransaction createTextEditTransaction(Element element) { |
| 235 if (element.source is! _AssetBasedSource) return null; | 231 if (element.source is! _AssetBasedSource) return null; |
| 236 | 232 |
| 233 // Cannot edit unless there is an active transformer. |
| 234 if (_currentTransform == null) return null; |
| 235 |
| 237 _AssetBasedSource source = element.source; | 236 _AssetBasedSource source = element.source; |
| 238 // Cannot modify assets in other packages. | 237 // Cannot modify assets in other packages. |
| 239 if (source.assetId.package != entryPoint.package) return null; | 238 if (source.assetId.package != _currentTransform.primaryInput.id.package) { |
| 239 return null; |
| 240 } |
| 240 | 241 |
| 241 var sourceFile = _getSourceFile(element); | 242 var sourceFile = getSourceFile(element); |
| 242 if (sourceFile == null) return null; | 243 if (sourceFile == null) return null; |
| 243 | 244 |
| 244 return new TextEditTransaction(source.rawContents, sourceFile); | 245 return new TextEditTransaction(source.rawContents, sourceFile); |
| 245 } | 246 } |
| 246 | 247 |
| 247 /// Gets the SourceFile for the source of the element. | 248 /// Gets the SourceFile for the source of the element. |
| 248 SourceFile _getSourceFile(Element element) { | 249 SourceFile getSourceFile(Element element) { |
| 249 var assetId = getSourceAssetId(element); | 250 var assetId = getSourceAssetId(element); |
| 250 if (assetId == null) return null; | 251 if (assetId == null) return null; |
| 251 | 252 |
| 252 var importUri = _getSourceUri(element, from: entryPoint); | 253 var importUri = _getSourceUri(element); |
| 253 var spanPath = importUri != null ? importUri.toString() : assetId.path; | 254 var spanPath = importUri != null ? importUri.toString() : assetId.path; |
| 254 return new SourceFile.text(spanPath, sources[assetId].rawContents); | 255 return new SourceFile.text(spanPath, sources[assetId].rawContents); |
| 255 } | 256 } |
| 256 } | 257 } |
| 257 | 258 |
| 258 /// Implementation of Analyzer's Source for Barback based assets. | 259 /// Implementation of Analyzer's Source for Barback based assets. |
| 259 class _AssetBasedSource extends Source { | 260 class _AssetBasedSource extends Source { |
| 260 | 261 |
| 261 /// Asset ID where this source can be found. | 262 /// Asset ID where this source can be found. |
| 262 final AssetId assetId; | 263 final AssetId assetId; |
| 263 | 264 |
| 264 /// The resolver this is being used in. | 265 /// The resolver this is being used in. |
| 265 final ResolverImpl _resolver; | 266 final ResolverImpl _resolver; |
| 266 | 267 |
| 267 /// Cache of dependent asset IDs, to avoid re-parsing the AST. | 268 /// Cache of dependent asset IDs, to avoid re-parsing the AST. |
| 268 Iterable<AssetId> _dependentAssets; | 269 Iterable<AssetId> _dependentAssets; |
| 269 | 270 |
| 270 /// The current revision of the file, incremented only when file changes. | 271 /// The current revision of the file, incremented only when file changes. |
| 271 int _revision = 0; | 272 int _revision = 0; |
| 272 | 273 |
| 273 /// The file contents. | 274 /// The file contents. |
| 274 String _contents; | 275 String _contents; |
| 275 | 276 |
| 277 LibraryElement _libraryElement; |
| 278 |
| 279 LibraryElement get libraryElement { |
| 280 if (_libraryElement == null) { |
| 281 _libraryElement = _resolver._context.computeLibraryElement(this); |
| 282 } |
| 283 return _libraryElement; |
| 284 } |
| 285 |
| 276 _AssetBasedSource(this.assetId, this._resolver); | 286 _AssetBasedSource(this.assetId, this._resolver); |
| 277 | 287 |
| 278 /// Update the contents of this file with [contents]. | 288 /// Update the contents of this file with [contents]. |
| 279 /// | 289 /// |
| 280 /// Returns true if the contents of this asset have changed. | 290 /// Returns true if the contents of this asset have changed. |
| 281 bool updateContents(String contents) { | 291 bool updateContents(String contents) { |
| 282 if (contents == _contents) return false; | 292 if (contents == _contents) return false; |
| 283 var added = _contents == null; | 293 var added = _contents == null; |
| 284 _contents = contents; | 294 _contents = contents; |
| 285 ++_revision; | 295 ++_revision; |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 353 if (source == null) { | 363 if (source == null) { |
| 354 _logger.error('Could not load asset $id'); | 364 _logger.error('Could not load asset $id'); |
| 355 } | 365 } |
| 356 return source; | 366 return source; |
| 357 } | 367 } |
| 358 | 368 |
| 359 /// For logging errors. | 369 /// For logging errors. |
| 360 Span _getSpan(AstNode node) => _sourceFile.span(node.offset, node.end); | 370 Span _getSpan(AstNode node) => _sourceFile.span(node.offset, node.end); |
| 361 /// For logging errors. | 371 /// For logging errors. |
| 362 SourceFile get _sourceFile { | 372 SourceFile get _sourceFile { |
| 363 var uri = getSourceUri(_resolver.entryPoint); | 373 var uri = getSourceUri(); |
| 364 var path = uri != null ? uri.toString() : assetId.path; | 374 var path = uri != null ? uri.toString() : assetId.path; |
| 365 | 375 |
| 366 return new SourceFile.text(path, rawContents); | 376 return new SourceFile.text(path, rawContents); |
| 367 } | 377 } |
| 368 | 378 |
| 369 /// Gets a URI which would be appropriate for importing this file. | 379 /// Gets a URI which would be appropriate for importing this file. |
| 370 /// | 380 /// |
| 371 /// Note that this file may represent a non-importable file such as a part. | 381 /// Note that this file may represent a non-importable file such as a part. |
| 372 Uri getSourceUri([AssetId from]) { | 382 Uri getSourceUri([AssetId from]) { |
| 373 if (!assetId.path.startsWith('lib/')) { | 383 if (!assetId.path.startsWith('lib/')) { |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 567 | 577 |
| 568 /** | 578 /** |
| 569 * A Future that complets with a List of the values from all the added | 579 * A Future that complets with a List of the values from all the added |
| 570 * tasks, when they have all completed. | 580 * tasks, when they have all completed. |
| 571 * | 581 * |
| 572 * If any task fails, this Future will receive the error. Only the first | 582 * If any task fails, this Future will receive the error. Only the first |
| 573 * error will be sent to the Future. | 583 * error will be sent to the Future. |
| 574 */ | 584 */ |
| 575 Future<List<E>> get future => _completer.future; | 585 Future<List<E>> get future => _completer.future; |
| 576 } | 586 } |
| OLD | NEW |