Chromium Code Reviews| 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 16 matching lines...) Expand all Loading... | |
| 27 | 27 |
| 28 /// Resolves and updates an AST based on Barback-based assets. | 28 /// Resolves and updates an AST based on Barback-based assets. |
| 29 /// | 29 /// |
| 30 /// This also provides a handful of useful APIs for traversing and working | 30 /// This also provides a handful of useful APIs for traversing and working |
| 31 /// with the resolved AST. | 31 /// with the resolved AST. |
| 32 class ResolverImpl implements Resolver { | 32 class ResolverImpl implements Resolver { |
| 33 /// Cache of all asset sources currently referenced. | 33 /// Cache of all asset sources currently referenced. |
| 34 final Map<AssetId, _AssetBasedSource> sources = | 34 final Map<AssetId, _AssetBasedSource> sources = |
| 35 <AssetId, _AssetBasedSource>{}; | 35 <AssetId, _AssetBasedSource>{}; |
| 36 | 36 |
| 37 /// The Dart entry point file where parsing begins. | |
| 38 final AssetId entryPoint; | |
| 39 | |
| 40 final AnalysisContext _context = | 37 final AnalysisContext _context = |
| 41 AnalysisEngine.instance.createAnalysisContext(); | 38 AnalysisEngine.instance.createAnalysisContext(); |
| 42 | 39 |
| 43 /// Transform for which this is currently updating, or null when not updating. | 40 /// Transform for which this is currently updating, or null when not updating. |
| 44 Transform _currentTransform; | 41 Transform _currentTransform; |
| 45 | 42 |
| 46 /// The currently resolved library, or null if unresolved. | 43 /// The currently resolved entry libraries, or null if nothing is resolved. |
| 47 LibraryElement _entryLibrary; | 44 List<LibraryElement> _entryLibraries; |
| 48 | 45 |
| 49 /// Future indicating when this resolver is done in the current phase. | 46 /// Future indicating when this resolver is done in the current phase. |
| 50 Future _lastPhaseComplete = new Future.value(); | 47 Future _lastPhaseComplete = new Future.value(); |
| 51 | 48 |
| 52 /// Completer for wrapping up the current phase. | 49 /// Completer for wrapping up the current phase. |
| 53 Completer _currentPhaseComplete; | 50 Completer _currentPhaseComplete; |
| 54 | 51 |
| 55 /// Handler for all Dart SDK (dart:) sources. | 52 /// Handler for all Dart SDK (dart:) sources. |
| 56 DirectoryBasedDartSdk _dartSdk; | 53 DirectoryBasedDartSdk _dartSdk; |
| 57 | 54 |
| 58 /// Creates a resolver that will resolve the Dart code starting at | 55 /// Creates a resolver, where [sdkDir] is the root directory of the Dart SDK, |
| 59 /// [entryPoint]. | 56 /// for resolving `dart:*` imports. |
| 60 /// | 57 ResolverImpl(String sdkDir, {AnalysisOptions options}) { |
| 61 /// [sdkDir] is the root directory of the Dart SDK, for resolving dart: | |
| 62 /// imports. | |
| 63 ResolverImpl(this.entryPoint, String sdkDir, {AnalysisOptions options}) { | |
| 64 if (options == null) { | 58 if (options == null) { |
| 65 options = new AnalysisOptionsImpl() | 59 options = new AnalysisOptionsImpl() |
| 66 ..cacheSize = 256 // # of sources to cache ASTs for. | 60 ..cacheSize = 256 // # of sources to cache ASTs for. |
| 67 ..preserveComments = false | 61 ..preserveComments = false |
| 68 ..analyzeFunctionBodies = true; | 62 ..analyzeFunctionBodies = true; |
| 69 } | 63 } |
| 70 _context.analysisOptions = options; | 64 _context.analysisOptions = options; |
| 71 | 65 |
| 72 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); | 66 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); |
| 73 _dartSdk.context.analysisOptions = options; | 67 _dartSdk.context.analysisOptions = options; |
| 74 | 68 |
| 75 _context.sourceFactory = new SourceFactory([ | 69 _context.sourceFactory = new SourceFactory([ |
| 76 new DartUriResolverProxy(_dartSdk), | 70 new DartUriResolverProxy(_dartSdk), |
| 77 new _AssetUriResolver(this)]); | 71 new _AssetUriResolver(this)]); |
| 78 } | 72 } |
| 79 | 73 |
| 80 LibraryElement get entryLibrary => _entryLibrary; | 74 LibraryElement getLibrary(AssetId assetId) { |
| 75 var source = sources[assetId]; | |
| 76 if (source._libraryElement == null) { | |
| 77 source._libraryElement = _context.computeLibraryElement(source); | |
|
blois
2014/03/17 16:55:45
computeLibraryElement is long-running the first ti
Siggi Cherem (dart-lang)
2014/03/17 20:13:39
Good to know, I had assumed that it didn't cache i
| |
| 78 } | |
| 79 return source == null ? null : source._libraryElement; | |
|
blois
2014/03/17 16:55:45
If source can be null then it will crash in the if
Siggi Cherem (dart-lang)
2014/03/17 20:13:39
good catch! this was a last minute refactor and I
| |
| 80 } | |
| 81 | 81 |
| 82 Future<Resolver> resolve(Transform transform) { | 82 Future<Resolver> resolve(Transform transform, [List<AssetId> entryPoints]) { |
| 83 // Can only have one resolve in progress at a time, so chain the current | 83 // Can only have one resolve in progress at a time, so chain the current |
| 84 // resolution to be after the last one. | 84 // resolution to be after the last one. |
| 85 var phaseComplete = new Completer(); | 85 var phaseComplete = new Completer(); |
| 86 var future = _lastPhaseComplete.then((_) { | 86 var future = _lastPhaseComplete.then((_) { |
| 87 _currentPhaseComplete = phaseComplete; | 87 _currentPhaseComplete = phaseComplete; |
| 88 | 88 return _performResolve(transform, |
| 89 return _performResolve(transform); | 89 entryPoints == null ? [transform.primaryInput.id] : entryPoints); |
| 90 }).then((_) => this); | 90 }).then((_) => this); |
| 91 // Advance the lastPhaseComplete to be done when this phase is all done. | 91 // Advance the lastPhaseComplete to be done when this phase is all done. |
| 92 _lastPhaseComplete = phaseComplete.future; | 92 _lastPhaseComplete = phaseComplete.future; |
| 93 return future; | 93 return future; |
| 94 } | 94 } |
| 95 | 95 |
| 96 void release() { | 96 void release() { |
| 97 if (_currentPhaseComplete == null) { | 97 if (_currentPhaseComplete == null) { |
| 98 throw new StateError('Releasing without current lock.'); | 98 throw new StateError('Releasing without current lock.'); |
| 99 } | 99 } |
| 100 _currentPhaseComplete.complete(null); | 100 _currentPhaseComplete.complete(null); |
| 101 _currentPhaseComplete = null; | 101 _currentPhaseComplete = null; |
| 102 | 102 |
| 103 // Clear out the entry lib since it should not be referenced after release. | 103 // Clear out libraries since they should not be referenced after release. |
| 104 _entryLibrary = null; | 104 sources.values.forEach((source) { source._libraryElement = null; }); |
| 105 _entryLibraries = null; | |
| 106 _currentTransform = null; | |
| 105 } | 107 } |
| 106 | 108 |
| 107 Future _performResolve(Transform transform) { | 109 Future _performResolve(Transform transform, List<AssetId> entryPoints) { |
| 108 if (_currentTransform != null) { | 110 if (_currentTransform != null) { |
| 109 throw new StateError('Cannot be accessed by concurrent transforms'); | 111 throw new StateError('Cannot be accessed by concurrent transforms'); |
| 110 } | 112 } |
| 111 _currentTransform = transform; | 113 _currentTransform = transform; |
| 112 | 114 |
| 113 // Basic approach is to start at the first file, update it's contents | 115 // Basic approach is to start at the first file, update it's contents |
| 114 // and see if it changed, then walk all files accessed by it. | 116 // and see if it changed, then walk all files accessed by it. |
| 115 var visited = new Set<AssetId>(); | 117 var visited = new Set<AssetId>(); |
| 116 var visiting = new FutureGroup(); | 118 var visiting = new FutureGroup(); |
| 117 var toUpdate = []; | 119 var toUpdate = []; |
| 118 | 120 |
| 119 void processAsset(AssetId assetId) { | 121 void processAsset(AssetId assetId) { |
| 120 visited.add(assetId); | 122 visited.add(assetId); |
| 121 | 123 |
| 122 visiting.add(transform.readInputAsString(assetId).then((contents) { | 124 visiting.add(transform.readInputAsString(assetId).then((contents) { |
| 123 var source = sources[assetId]; | 125 var source = sources[assetId]; |
| 124 if (source == null) { | 126 if (source == null) { |
| 125 source = new _AssetBasedSource(assetId, this); | 127 source = new _AssetBasedSource(assetId, this); |
| 126 sources[assetId] = source; | 128 sources[assetId] = source; |
| 127 } | 129 } |
| 128 source.updateDependencies(contents); | 130 source.updateDependencies(contents); |
| 129 toUpdate.add(new _PendingUpdate(source, contents)); | 131 toUpdate.add(new _PendingUpdate(source, contents)); |
| 130 source.dependentAssets.where((id) => !visited.contains(id)) | 132 source.dependentAssets.where((id) => !visited.contains(id)) |
| 131 .forEach(processAsset); | 133 .forEach(processAsset); |
| 132 }, onError: (e) { | 134 }, onError: (e) { |
| 133 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); | 135 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); |
| 134 sources.remove(assetId); | 136 sources.remove(assetId); |
| 135 })); | 137 })); |
| 136 } | 138 } |
| 137 processAsset(entryPoint); | 139 entryPoints.forEach(processAsset); |
| 138 | 140 |
| 139 // Once we have all asset sources updated with the new contents then | 141 // Once we have all asset sources updated with the new contents then |
| 140 // resolve everything. | 142 // resolve everything. |
| 141 return visiting.future.then((_) { | 143 return visiting.future.then((_) { |
| 142 var changeSet = new ChangeSet(); | 144 var changeSet = new ChangeSet(); |
| 143 toUpdate.forEach((pending) => pending.apply(changeSet)); | 145 toUpdate.forEach((pending) => pending.apply(changeSet)); |
| 144 var unreachableAssets = new Set.from(sources.keys).difference(visited); | 146 var unreachableAssets = new Set.from(sources.keys).difference(visited); |
| 145 for (var unreachable in unreachableAssets) { | 147 for (var unreachable in unreachableAssets) { |
| 146 changeSet.removedSource(sources[unreachable]); | 148 changeSet.removedSource(sources[unreachable]); |
| 147 sources.remove(unreachable); | 149 sources.remove(unreachable); |
| 148 } | 150 } |
| 149 | 151 |
| 150 // Update the analyzer context with the latest sources | 152 // Update the analyzer context with the latest sources |
| 151 _context.applyChanges(changeSet); | 153 _context.applyChanges(changeSet); |
| 152 // Resolve the AST | 154 // Force resolve each entry point (the getter will ensure the library is |
| 153 _entryLibrary = _context.computeLibraryElement(sources[entryPoint]); | 155 // computed first). |
| 154 _currentTransform = null; | 156 _entryLibraries = []; |
|
blois
2014/03/17 16:55:45
_entryLibraries = entryPoints.map((id) {
var sou
Siggi Cherem (dart-lang)
2014/03/17 20:13:39
Done.
| |
| 157 for (var id in entryPoints) { | |
| 158 var source = sources[id]; | |
| 159 source._libraryElement = _context.computeLibraryElement(source); | |
| 160 _entryLibraries.add(source._libraryElement); | |
| 161 } | |
| 155 }); | 162 }); |
| 156 } | 163 } |
| 157 | 164 |
| 158 Iterable<LibraryElement> get libraries => entryLibrary.visibleLibraries; | 165 Iterable<LibraryElement> get libraries { |
| 166 var all = new Set(); | |
|
blois
2014/03/17 16:55:45
=> _entryLibraries.expand((lib) => lib.visibleLibr
Siggi Cherem (dart-lang)
2014/03/17 20:13:39
Done.
| |
| 167 for (var lib in _entryLibraries) { | |
| 168 all.addAll(lib.visibleLibraries); | |
| 169 } | |
| 170 return all; | |
| 171 } | |
| 159 | 172 |
| 160 LibraryElement getLibraryByName(String libraryName) => | 173 LibraryElement getLibraryByName(String libraryName) => |
| 161 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); | 174 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); |
| 162 | 175 |
| 163 LibraryElement getLibraryByUri(Uri uri) => | 176 LibraryElement getLibraryByUri(Uri uri) => |
| 164 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); | 177 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); |
| 165 | 178 |
| 166 ClassElement getType(String typeName) { | 179 ClassElement getType(String typeName) { |
| 167 var dotIndex = typeName.lastIndexOf('.'); | 180 var dotIndex = typeName.lastIndexOf('.'); |
| 168 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); | 181 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 222 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); | 235 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); |
| 223 } | 236 } |
| 224 | 237 |
| 225 AssetId getSourceAssetId(Element element) { | 238 AssetId getSourceAssetId(Element element) { |
| 226 var source = element.source; | 239 var source = element.source; |
| 227 if (source is _AssetBasedSource) return source.assetId; | 240 if (source is _AssetBasedSource) return source.assetId; |
| 228 return null; | 241 return null; |
| 229 } | 242 } |
| 230 | 243 |
| 231 Span getSourceSpan(Element element) { | 244 Span getSourceSpan(Element element) { |
| 232 var sourceFile = _getSourceFile(element); | 245 var sourceFile = getSourceFile(element); |
| 233 if (sourceFile == null) return null; | 246 if (sourceFile == null) return null; |
| 234 return sourceFile.span(element.node.offset, element.node.end); | 247 return sourceFile.span(element.node.offset, element.node.end); |
| 235 } | 248 } |
| 236 | 249 |
| 237 TextEditTransaction createTextEditTransaction(Element element) { | 250 TextEditTransaction createTextEditTransaction(Element element) { |
| 238 if (element.source is! _AssetBasedSource) return null; | 251 if (element.source is! _AssetBasedSource) return null; |
| 239 | 252 |
| 253 // Cannot edit unless there is an active transformer. | |
| 254 if (_currentTransform == null) return null; | |
| 255 | |
| 240 _AssetBasedSource source = element.source; | 256 _AssetBasedSource source = element.source; |
| 241 // Cannot modify assets in other packages. | 257 // Cannot modify assets in other packages. |
| 242 if (source.assetId.package != entryPoint.package) return null; | 258 if (source.assetId.package != _currentTransform.primaryInput.id.package) { |
| 259 return null; | |
| 260 } | |
| 243 | 261 |
| 244 var sourceFile = _getSourceFile(element); | 262 var sourceFile = getSourceFile(element); |
| 245 if (sourceFile == null) return null; | 263 if (sourceFile == null) return null; |
| 246 | 264 |
| 247 return new TextEditTransaction(source.rawContents, sourceFile); | 265 return new TextEditTransaction(source.rawContents, sourceFile); |
| 248 } | 266 } |
| 249 | 267 |
| 250 /// Gets the SourceFile for the source of the element. | 268 /// Gets the SourceFile for the source of the element. |
| 251 SourceFile _getSourceFile(Element element) { | 269 SourceFile getSourceFile(Element element) { |
| 252 var assetId = getSourceAssetId(element); | 270 var assetId = getSourceAssetId(element); |
| 253 if (assetId == null) return null; | 271 if (assetId == null) return null; |
| 254 | 272 |
| 255 var importUri = _getSourceUri(element, from: entryPoint); | 273 var importUri = _getSourceUri(element); |
| 256 var spanPath = importUri != null ? importUri.toString() : assetId.path; | 274 var spanPath = importUri != null ? importUri.toString() : assetId.path; |
| 257 return new SourceFile.text(spanPath, sources[assetId].rawContents); | 275 return new SourceFile.text(spanPath, sources[assetId].rawContents); |
| 258 } | 276 } |
| 259 } | 277 } |
| 260 | 278 |
| 261 /// Implementation of Analyzer's Source for Barback based assets. | 279 /// Implementation of Analyzer's Source for Barback based assets. |
| 262 class _AssetBasedSource extends Source { | 280 class _AssetBasedSource extends Source { |
| 263 | 281 |
| 264 /// Asset ID where this source can be found. | 282 /// Asset ID where this source can be found. |
| 265 final AssetId assetId; | 283 final AssetId assetId; |
| 266 | 284 |
| 267 /// The resolver this is being used in. | 285 /// The resolver this is being used in. |
| 268 final ResolverImpl _resolver; | 286 final ResolverImpl _resolver; |
| 269 | 287 |
| 270 /// Cache of dependent asset IDs, to avoid re-parsing the AST. | 288 /// Cache of dependent asset IDs, to avoid re-parsing the AST. |
| 271 Iterable<AssetId> _dependentAssets; | 289 Iterable<AssetId> _dependentAssets; |
| 272 | 290 |
| 273 /// The current revision of the file, incremented only when file changes. | 291 /// The current revision of the file, incremented only when file changes. |
| 274 int _revision = 0; | 292 int _revision = 0; |
| 275 | 293 |
| 276 /// The file contents. | 294 /// The file contents. |
| 277 String _contents; | 295 String _contents; |
| 278 | 296 |
| 297 LibraryElement _libraryElement; | |
| 298 | |
| 279 _AssetBasedSource(this.assetId, this._resolver); | 299 _AssetBasedSource(this.assetId, this._resolver); |
| 280 | 300 |
| 281 /// Update the dependencies of this source. This parses [contents] but avoids | 301 /// Update the dependencies of this source. This parses [contents] but avoids |
| 282 /// any analyzer resolution. | 302 /// any analyzer resolution. |
| 283 void updateDependencies(String contents) { | 303 void updateDependencies(String contents) { |
| 284 if (contents == _contents) return; | 304 if (contents == _contents) return; |
| 285 var unit = _parseCompilationUnit(contents); | 305 var unit = _parseCompilationUnit(contents); |
| 286 _dependentAssets = unit.directives | 306 _dependentAssets = unit.directives |
| 287 .where((d) => (d is ImportDirective || d is PartDirective || | 307 .where((d) => (d is ImportDirective || d is PartDirective || |
| 288 d is ExportDirective)) | 308 d is ExportDirective)) |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 353 _logger.error('Could not load asset $id'); | 373 _logger.error('Could not load asset $id'); |
| 354 } | 374 } |
| 355 return source; | 375 return source; |
| 356 } | 376 } |
| 357 | 377 |
| 358 /// For logging errors. | 378 /// For logging errors. |
| 359 Span _getSpan(AstNode node, [String contents]) => | 379 Span _getSpan(AstNode node, [String contents]) => |
| 360 _getSourceFile(contents).span(node.offset, node.end); | 380 _getSourceFile(contents).span(node.offset, node.end); |
| 361 /// For logging errors. | 381 /// For logging errors. |
| 362 SourceFile _getSourceFile([String contents]) { | 382 SourceFile _getSourceFile([String contents]) { |
| 363 var uri = getSourceUri(_resolver.entryPoint); | 383 var uri = getSourceUri(); |
| 364 var path = uri != null ? uri.toString() : assetId.path; | 384 var path = uri != null ? uri.toString() : assetId.path; |
| 365 return new SourceFile.text(path, contents != null ? contents : rawContents); | 385 return new SourceFile.text(path, contents != null ? contents : rawContents); |
| 366 } | 386 } |
| 367 | 387 |
| 368 /// Gets a URI which would be appropriate for importing this file. | 388 /// Gets a URI which would be appropriate for importing this file. |
| 369 /// | 389 /// |
| 370 /// Note that this file may represent a non-importable file such as a part. | 390 /// Note that this file may represent a non-importable file such as a part. |
| 371 Uri getSourceUri([AssetId from]) { | 391 Uri getSourceUri([AssetId from]) { |
| 372 if (!assetId.path.startsWith('lib/')) { | 392 if (!assetId.path.startsWith('lib/')) { |
| 373 // Cannot do absolute imports of non lib-based assets. | 393 // Cannot do absolute imports of non lib-based assets. |
| (...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 602 | 622 |
| 603 void apply(ChangeSet changeSet) { | 623 void apply(ChangeSet changeSet) { |
| 604 if (!source.updateContents(content)) return; | 624 if (!source.updateContents(content)) return; |
| 605 if (source._revision == 1 && source._contents != null) { | 625 if (source._revision == 1 && source._contents != null) { |
| 606 changeSet.addedSource(source); | 626 changeSet.addedSource(source); |
| 607 } else { | 627 } else { |
| 608 changeSet.changedSource(source); | 628 changeSet.changedSource(source); |
| 609 } | 629 } |
| 610 } | 630 } |
| 611 } | 631 } |
| OLD | NEW |