| 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/analyzer.dart' show parseCompilationUnit; | 8 import 'package:analyzer/analyzer.dart' show parseCompilationUnit; |
| 9 import 'package:analyzer/src/generated/ast.dart'; | 9 import 'package:analyzer/src/generated/ast.dart'; |
| 10 import 'package:analyzer/src/generated/element.dart'; | 10 import 'package:analyzer/src/generated/element.dart'; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 28 | 28 |
| 29 /// Resolves and updates an AST based on Barback-based assets. | 29 /// Resolves and updates an AST based on Barback-based assets. |
| 30 /// | 30 /// |
| 31 /// This also provides a handful of useful APIs for traversing and working | 31 /// This also provides a handful of useful APIs for traversing and working |
| 32 /// with the resolved AST. | 32 /// with the resolved AST. |
| 33 class ResolverImpl implements Resolver { | 33 class ResolverImpl implements Resolver { |
| 34 /// Cache of all asset sources currently referenced. | 34 /// Cache of all asset sources currently referenced. |
| 35 final Map<AssetId, _AssetBasedSource> sources = | 35 final Map<AssetId, _AssetBasedSource> sources = |
| 36 <AssetId, _AssetBasedSource>{}; | 36 <AssetId, _AssetBasedSource>{}; |
| 37 | 37 |
| 38 /// The Dart entry point file where parsing begins. | |
| 39 final AssetId entryPoint; | |
| 40 | |
| 41 final AnalysisContext _context = | 38 final AnalysisContext _context = |
| 42 AnalysisEngine.instance.createAnalysisContext(); | 39 AnalysisEngine.instance.createAnalysisContext(); |
| 43 | 40 |
| 44 /// Transform for which this is currently updating, or null when not updating. | 41 /// Transform for which this is currently updating, or null when not updating. |
| 45 Transform _currentTransform; | 42 Transform _currentTransform; |
| 46 | 43 |
| 47 /// The currently resolved library, or null if unresolved. | 44 /// The currently resolved entry libraries, or null if nothing is resolved. |
| 48 LibraryElement _entryLibrary; | 45 List<LibraryElement> _entryLibraries; |
| 49 | 46 |
| 50 /// Future indicating when this resolver is done in the current phase. | 47 /// Future indicating when this resolver is done in the current phase. |
| 51 Future _lastPhaseComplete = new Future.value(); | 48 Future _lastPhaseComplete = new Future.value(); |
| 52 | 49 |
| 53 /// Completer for wrapping up the current phase. | 50 /// Completer for wrapping up the current phase. |
| 54 Completer _currentPhaseComplete; | 51 Completer _currentPhaseComplete; |
| 55 | 52 |
| 56 /// Handler for all Dart SDK (dart:) sources. | 53 /// Handler for all Dart SDK (dart:) sources. |
| 57 DirectoryBasedDartSdk _dartSdk; | 54 DirectoryBasedDartSdk _dartSdk; |
| 58 | 55 |
| 59 /// Creates a resolver that will resolve the Dart code starting at | 56 /// Creates a resolver, where [sdkDir] is the root directory of the Dart SDK, |
| 60 /// [entryPoint]. | 57 /// for resolving `dart:*` imports. |
| 61 /// | 58 ResolverImpl(String sdkDir, {AnalysisOptions options}) { |
| 62 /// [sdkDir] is the root directory of the Dart SDK, for resolving dart: | |
| 63 /// imports. | |
| 64 ResolverImpl(this.entryPoint, String sdkDir, {AnalysisOptions options}) { | |
| 65 if (options == null) { | 59 if (options == null) { |
| 66 options = new AnalysisOptionsImpl() | 60 options = new AnalysisOptionsImpl() |
| 67 ..cacheSize = 256 // # of sources to cache ASTs for. | 61 ..cacheSize = 256 // # of sources to cache ASTs for. |
| 68 ..preserveComments = false | 62 ..preserveComments = false |
| 69 ..analyzeFunctionBodies = true; | 63 ..analyzeFunctionBodies = true; |
| 70 } | 64 } |
| 71 _context.analysisOptions = options; | 65 _context.analysisOptions = options; |
| 72 | 66 |
| 73 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); | 67 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); |
| 74 _dartSdk.context.analysisOptions = options; | 68 _dartSdk.context.analysisOptions = options; |
| 75 | 69 |
| 76 _context.sourceFactory = new SourceFactory([ | 70 _context.sourceFactory = new SourceFactory([ |
| 77 new DartUriResolverProxy(_dartSdk), | 71 new DartUriResolverProxy(_dartSdk), |
| 78 new _AssetUriResolver(this)]); | 72 new _AssetUriResolver(this)]); |
| 79 } | 73 } |
| 80 | 74 |
| 81 LibraryElement get entryLibrary => _entryLibrary; | 75 LibraryElement getLibrary(AssetId assetId) { |
| 76 var source = sources[assetId]; |
| 77 return source == null ? null : _context.computeLibraryElement(source); |
| 78 } |
| 82 | 79 |
| 83 Future<Resolver> resolve(Transform transform) { | 80 Future<Resolver> resolve(Transform transform, [List<AssetId> entryPoints]) { |
| 84 // Can only have one resolve in progress at a time, so chain the current | 81 // Can only have one resolve in progress at a time, so chain the current |
| 85 // resolution to be after the last one. | 82 // resolution to be after the last one. |
| 86 var phaseComplete = new Completer(); | 83 var phaseComplete = new Completer(); |
| 87 var future = _lastPhaseComplete.then((_) { | 84 var future = _lastPhaseComplete.then((_) { |
| 88 _currentPhaseComplete = phaseComplete; | 85 _currentPhaseComplete = phaseComplete; |
| 89 | 86 return _performResolve(transform, |
| 90 return _performResolve(transform); | 87 entryPoints == null ? [transform.primaryInput.id] : entryPoints); |
| 91 }).then((_) => this); | 88 }).then((_) => this); |
| 92 // Advance the lastPhaseComplete to be done when this phase is all done. | 89 // Advance the lastPhaseComplete to be done when this phase is all done. |
| 93 _lastPhaseComplete = phaseComplete.future; | 90 _lastPhaseComplete = phaseComplete.future; |
| 94 return future; | 91 return future; |
| 95 } | 92 } |
| 96 | 93 |
| 97 void release() { | 94 void release() { |
| 98 if (_currentPhaseComplete == null) { | 95 if (_currentPhaseComplete == null) { |
| 99 throw new StateError('Releasing without current lock.'); | 96 throw new StateError('Releasing without current lock.'); |
| 100 } | 97 } |
| 101 _currentPhaseComplete.complete(null); | 98 _currentPhaseComplete.complete(null); |
| 102 _currentPhaseComplete = null; | 99 _currentPhaseComplete = null; |
| 103 | 100 |
| 104 // Clear out the entry lib since it should not be referenced after release. | 101 // Clear out libraries since they should not be referenced after release. |
| 105 _entryLibrary = null; | 102 _entryLibraries = null; |
| 103 _currentTransform = null; |
| 106 } | 104 } |
| 107 | 105 |
| 108 Future _performResolve(Transform transform) { | 106 Future _performResolve(Transform transform, List<AssetId> entryPoints) { |
| 109 if (_currentTransform != null) { | 107 if (_currentTransform != null) { |
| 110 throw new StateError('Cannot be accessed by concurrent transforms'); | 108 throw new StateError('Cannot be accessed by concurrent transforms'); |
| 111 } | 109 } |
| 112 _currentTransform = transform; | 110 _currentTransform = transform; |
| 113 | 111 |
| 114 // Basic approach is to start at the first file, update it's contents | 112 // Basic approach is to start at the first file, update it's contents |
| 115 // and see if it changed, then walk all files accessed by it. | 113 // and see if it changed, then walk all files accessed by it. |
| 116 var visited = new Set<AssetId>(); | 114 var visited = new Set<AssetId>(); |
| 117 var visiting = new FutureGroup(); | 115 var visiting = new FutureGroup(); |
| 118 var toUpdate = []; | 116 var toUpdate = []; |
| 119 | 117 |
| 120 void processAsset(AssetId assetId) { | 118 void processAsset(AssetId assetId) { |
| 121 visited.add(assetId); | 119 visited.add(assetId); |
| 122 | 120 |
| 123 visiting.add(transform.readInputAsString(assetId).then((contents) { | 121 visiting.add(transform.readInputAsString(assetId).then((contents) { |
| 124 var source = sources[assetId]; | 122 var source = sources[assetId]; |
| 125 if (source == null) { | 123 if (source == null) { |
| 126 source = new _AssetBasedSource(assetId, this); | 124 source = new _AssetBasedSource(assetId, this); |
| 127 sources[assetId] = source; | 125 sources[assetId] = source; |
| 128 } | 126 } |
| 129 source.updateDependencies(contents); | 127 source.updateDependencies(contents); |
| 130 toUpdate.add(new _PendingUpdate(source, contents)); | 128 toUpdate.add(new _PendingUpdate(source, contents)); |
| 131 source.dependentAssets.where((id) => !visited.contains(id)) | 129 source.dependentAssets.where((id) => !visited.contains(id)) |
| 132 .forEach(processAsset); | 130 .forEach(processAsset); |
| 133 }, onError: (e) { | 131 }, onError: (e) { |
| 134 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); | 132 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); |
| 135 sources.remove(assetId); | 133 sources.remove(assetId); |
| 136 })); | 134 })); |
| 137 } | 135 } |
| 138 processAsset(entryPoint); | 136 entryPoints.forEach(processAsset); |
| 139 | 137 |
| 140 // Once we have all asset sources updated with the new contents then | 138 // Once we have all asset sources updated with the new contents then |
| 141 // resolve everything. | 139 // resolve everything. |
| 142 return visiting.future.then((_) { | 140 return visiting.future.then((_) { |
| 143 var changeSet = new ChangeSet(); | 141 var changeSet = new ChangeSet(); |
| 144 toUpdate.forEach((pending) => pending.apply(changeSet)); | 142 toUpdate.forEach((pending) => pending.apply(changeSet)); |
| 145 var unreachableAssets = new Set.from(sources.keys).difference(visited); | 143 var unreachableAssets = new Set.from(sources.keys).difference(visited); |
| 146 for (var unreachable in unreachableAssets) { | 144 for (var unreachable in unreachableAssets) { |
| 147 changeSet.removedSource(sources[unreachable]); | 145 changeSet.removedSource(sources[unreachable]); |
| 148 sources.remove(unreachable); | 146 sources.remove(unreachable); |
| 149 } | 147 } |
| 150 | 148 |
| 151 // Update the analyzer context with the latest sources | 149 // Update the analyzer context with the latest sources |
| 152 _context.applyChanges(changeSet); | 150 _context.applyChanges(changeSet); |
| 153 // Resolve the AST | 151 // Force resolve each entry point (the getter will ensure the library is |
| 154 _entryLibrary = _context.computeLibraryElement(sources[entryPoint]); | 152 // computed first). |
| 155 _currentTransform = null; | 153 _entryLibraries = entryPoints |
| 154 .map((id) => _context.computeLibraryElement(sources[id])).toList(); |
| 156 }); | 155 }); |
| 157 } | 156 } |
| 158 | 157 |
| 159 Iterable<LibraryElement> get libraries => entryLibrary.visibleLibraries; | 158 Iterable<LibraryElement> get libraries => |
| 159 _entryLibraries.expand((lib) => lib.visibleLibraries).toSet(); |
| 160 | 160 |
| 161 LibraryElement getLibraryByName(String libraryName) => | 161 LibraryElement getLibraryByName(String libraryName) => |
| 162 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); | 162 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); |
| 163 | 163 |
| 164 LibraryElement getLibraryByUri(Uri uri) => | 164 LibraryElement getLibraryByUri(Uri uri) => |
| 165 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); | 165 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); |
| 166 | 166 |
| 167 ClassElement getType(String typeName) { | 167 ClassElement getType(String typeName) { |
| 168 var dotIndex = typeName.lastIndexOf('.'); | 168 var dotIndex = typeName.lastIndexOf('.'); |
| 169 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); | 169 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 223 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); | 223 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); |
| 224 } | 224 } |
| 225 | 225 |
| 226 AssetId getSourceAssetId(Element element) { | 226 AssetId getSourceAssetId(Element element) { |
| 227 var source = element.source; | 227 var source = element.source; |
| 228 if (source is _AssetBasedSource) return source.assetId; | 228 if (source is _AssetBasedSource) return source.assetId; |
| 229 return null; | 229 return null; |
| 230 } | 230 } |
| 231 | 231 |
| 232 Span getSourceSpan(Element element) { | 232 Span getSourceSpan(Element element) { |
| 233 var sourceFile = _getSourceFile(element); | 233 var sourceFile = getSourceFile(element); |
| 234 if (sourceFile == null) return null; | 234 if (sourceFile == null) return null; |
| 235 return sourceFile.span(element.node.offset, element.node.end); | 235 return sourceFile.span(element.node.offset, element.node.end); |
| 236 } | 236 } |
| 237 | 237 |
| 238 TextEditTransaction createTextEditTransaction(Element element) { | 238 TextEditTransaction createTextEditTransaction(Element element) { |
| 239 if (element.source is! _AssetBasedSource) return null; | 239 if (element.source is! _AssetBasedSource) return null; |
| 240 | 240 |
| 241 // Cannot edit unless there is an active transformer. |
| 242 if (_currentTransform == null) return null; |
| 243 |
| 241 _AssetBasedSource source = element.source; | 244 _AssetBasedSource source = element.source; |
| 242 // Cannot modify assets in other packages. | 245 // Cannot modify assets in other packages. |
| 243 if (source.assetId.package != entryPoint.package) return null; | 246 if (source.assetId.package != _currentTransform.primaryInput.id.package) { |
| 247 return null; |
| 248 } |
| 244 | 249 |
| 245 var sourceFile = _getSourceFile(element); | 250 var sourceFile = getSourceFile(element); |
| 246 if (sourceFile == null) return null; | 251 if (sourceFile == null) return null; |
| 247 | 252 |
| 248 return new TextEditTransaction(source.rawContents, sourceFile); | 253 return new TextEditTransaction(source.rawContents, sourceFile); |
| 249 } | 254 } |
| 250 | 255 |
| 251 /// Gets the SourceFile for the source of the element. | 256 /// Gets the SourceFile for the source of the element. |
| 252 SourceFile _getSourceFile(Element element) { | 257 SourceFile getSourceFile(Element element) { |
| 253 var assetId = getSourceAssetId(element); | 258 var assetId = getSourceAssetId(element); |
| 254 if (assetId == null) return null; | 259 if (assetId == null) return null; |
| 255 | 260 |
| 256 var importUri = _getSourceUri(element, from: entryPoint); | 261 var importUri = _getSourceUri(element); |
| 257 var spanPath = importUri != null ? importUri.toString() : assetId.path; | 262 var spanPath = importUri != null ? importUri.toString() : assetId.path; |
| 258 return new SourceFile.text(spanPath, sources[assetId].rawContents); | 263 return new SourceFile.text(spanPath, sources[assetId].rawContents); |
| 259 } | 264 } |
| 260 } | 265 } |
| 261 | 266 |
| 262 /// Implementation of Analyzer's Source for Barback based assets. | 267 /// Implementation of Analyzer's Source for Barback based assets. |
| 263 class _AssetBasedSource extends Source { | 268 class _AssetBasedSource extends Source { |
| 264 | 269 |
| 265 /// Asset ID where this source can be found. | 270 /// Asset ID where this source can be found. |
| 266 final AssetId assetId; | 271 final AssetId assetId; |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 354 _logger.error('Could not load asset $id'); | 359 _logger.error('Could not load asset $id'); |
| 355 } | 360 } |
| 356 return source; | 361 return source; |
| 357 } | 362 } |
| 358 | 363 |
| 359 /// For logging errors. | 364 /// For logging errors. |
| 360 Span _getSpan(AstNode node, [String contents]) => | 365 Span _getSpan(AstNode node, [String contents]) => |
| 361 _getSourceFile(contents).span(node.offset, node.end); | 366 _getSourceFile(contents).span(node.offset, node.end); |
| 362 /// For logging errors. | 367 /// For logging errors. |
| 363 SourceFile _getSourceFile([String contents]) { | 368 SourceFile _getSourceFile([String contents]) { |
| 364 var uri = getSourceUri(_resolver.entryPoint); | 369 var uri = getSourceUri(); |
| 365 var path = uri != null ? uri.toString() : assetId.path; | 370 var path = uri != null ? uri.toString() : assetId.path; |
| 366 return new SourceFile.text(path, contents != null ? contents : rawContents); | 371 return new SourceFile.text(path, contents != null ? contents : rawContents); |
| 367 } | 372 } |
| 368 | 373 |
| 369 /// Gets a URI which would be appropriate for importing this file. | 374 /// Gets a URI which would be appropriate for importing this file. |
| 370 /// | 375 /// |
| 371 /// Note that this file may represent a non-importable file such as a part. | 376 /// Note that this file may represent a non-importable file such as a part. |
| 372 Uri getSourceUri([AssetId from]) { | 377 Uri getSourceUri([AssetId from]) { |
| 373 if (!assetId.path.startsWith('lib/')) { | 378 if (!assetId.path.startsWith('lib/')) { |
| 374 // Cannot do absolute imports of non lib-based assets. | 379 // Cannot do absolute imports of non lib-based assets. |
| (...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 587 | 592 |
| 588 void apply(ChangeSet changeSet) { | 593 void apply(ChangeSet changeSet) { |
| 589 if (!source.updateContents(content)) return; | 594 if (!source.updateContents(content)) return; |
| 590 if (source._revision == 1 && source._contents != null) { | 595 if (source._revision == 1 && source._contents != null) { |
| 591 changeSet.addedSource(source); | 596 changeSet.addedSource(source); |
| 592 } else { | 597 } else { |
| 593 changeSet.changedSource(source); | 598 changeSet.changedSource(source); |
| 594 } | 599 } |
| 595 } | 600 } |
| 596 } | 601 } |
| OLD | NEW |