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'; |
| 11 import 'package:analyzer/src/generated/error.dart'; | |
| 11 import 'package:analyzer/src/generated/java_io.dart'; | 12 import 'package:analyzer/src/generated/java_io.dart'; |
| 13 import 'package:analyzer/src/generated/parser.dart'; | |
| 14 import 'package:analyzer/src/generated/scanner.dart'; | |
| 12 import 'package:analyzer/src/generated/sdk.dart' show DartSdk; | 15 import 'package:analyzer/src/generated/sdk.dart' show DartSdk; |
| 13 import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; | 16 import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; |
| 14 import 'package:analyzer/src/generated/source.dart'; | 17 import 'package:analyzer/src/generated/source.dart'; |
| 15 import 'package:barback/barback.dart'; | 18 import 'package:barback/barback.dart'; |
| 16 import 'package:path/path.dart' as native_path; | 19 import 'package:path/path.dart' as native_path; |
| 17 import 'package:source_maps/refactor.dart'; | 20 import 'package:source_maps/refactor.dart'; |
| 18 import 'package:source_maps/span.dart' show SourceFile, Span; | 21 import 'package:source_maps/span.dart' show SourceFile, Span; |
| 19 | 22 |
| 20 import 'resolver.dart'; | 23 import 'resolver.dart'; |
| 21 | 24 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 104 Future _performResolve(Transform transform) { | 107 Future _performResolve(Transform transform) { |
| 105 if (_currentTransform != null) { | 108 if (_currentTransform != null) { |
| 106 throw new StateError('Cannot be accessed by concurrent transforms'); | 109 throw new StateError('Cannot be accessed by concurrent transforms'); |
| 107 } | 110 } |
| 108 _currentTransform = transform; | 111 _currentTransform = transform; |
| 109 | 112 |
| 110 // Basic approach is to start at the first file, update it's contents | 113 // 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. | 114 // and see if it changed, then walk all files accessed by it. |
| 112 var visited = new Set<AssetId>(); | 115 var visited = new Set<AssetId>(); |
| 113 var visiting = new FutureGroup(); | 116 var visiting = new FutureGroup(); |
| 117 var toUpdate = []; | |
| 114 | 118 |
| 115 void processAsset(AssetId assetId) { | 119 void processAsset(AssetId assetId) { |
| 116 visited.add(assetId); | 120 visited.add(assetId); |
| 117 | 121 |
| 118 visiting.add(transform.readInputAsString(assetId).then((contents) { | 122 visiting.add(transform.readInputAsString(assetId).then((contents) { |
| 119 var source = sources[assetId]; | 123 var source = sources[assetId]; |
| 120 if (source == null) { | 124 if (source == null) { |
| 121 source = new _AssetBasedSource(assetId, this); | 125 source = new _AssetBasedSource(assetId, this); |
| 122 sources[assetId] = source; | 126 sources[assetId] = source; |
| 123 } | 127 } |
| 124 source.updateContents(contents); | 128 source.updateDependencies(contents); |
| 125 | 129 toUpdate.add(new _PendingUpdate(source, contents)); |
| 126 source.dependentAssets | 130 source.dependentAssets.where((id) => !visited.contains(id)) |
| 127 .where((id) => !visited.contains(id)) | |
| 128 .forEach(processAsset); | 131 .forEach(processAsset); |
| 129 | |
| 130 }, onError: (e) { | 132 }, onError: (e) { |
| 131 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); | 133 _context.applyChanges(new ChangeSet()..removedSource(sources[assetId])); |
| 132 sources.remove(assetId); | 134 sources.remove(assetId); |
| 133 })); | 135 })); |
| 134 } | 136 } |
| 135 processAsset(entryPoint); | 137 processAsset(entryPoint); |
| 136 | 138 |
| 137 // Once we have all asset sources updated with the new contents then | 139 // Once we have all asset sources updated with the new contents then |
| 138 // resolve everything. | 140 // resolve everything. |
| 139 return visiting.future.then((_) { | 141 return visiting.future.then((_) { |
| 140 var changeSet = new ChangeSet(); | 142 var changeSet = new ChangeSet(); |
| 143 toUpdate.forEach((pending) => pending.apply(changeSet)); | |
| 141 var unreachableAssets = new Set.from(sources.keys).difference(visited); | 144 var unreachableAssets = new Set.from(sources.keys).difference(visited); |
| 142 for (var unreachable in unreachableAssets) { | 145 for (var unreachable in unreachableAssets) { |
| 143 changeSet.removedSource(sources[unreachable]); | 146 changeSet.removedSource(sources[unreachable]); |
| 144 sources.remove(unreachable); | 147 sources.remove(unreachable); |
| 145 } | 148 } |
| 146 | 149 |
| 147 // Update the analyzer context with the latest sources | 150 // Update the analyzer context with the latest sources |
| 148 _context.applyChanges(changeSet); | 151 _context.applyChanges(changeSet); |
| 149 // Resolve the AST | 152 // Resolve the AST |
| 150 _entryLibrary = _context.computeLibraryElement(sources[entryPoint]); | 153 _entryLibrary = _context.computeLibraryElement(sources[entryPoint]); |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 268 Iterable<AssetId> _dependentAssets; | 271 Iterable<AssetId> _dependentAssets; |
| 269 | 272 |
| 270 /// The current revision of the file, incremented only when file changes. | 273 /// The current revision of the file, incremented only when file changes. |
| 271 int _revision = 0; | 274 int _revision = 0; |
| 272 | 275 |
| 273 /// The file contents. | 276 /// The file contents. |
| 274 String _contents; | 277 String _contents; |
| 275 | 278 |
| 276 _AssetBasedSource(this.assetId, this._resolver); | 279 _AssetBasedSource(this.assetId, this._resolver); |
| 277 | 280 |
| 281 /// Update the dependencies of this source. This parses [contents] but avoids | |
| 282 /// any analyzer resolution. | |
| 283 void updateDependencies(String contents) { | |
| 284 if (contents == _contents) return; | |
| 285 var unit = _parseCompilationUnit(contents); | |
| 286 _dependentAssets = unit.directives | |
| 287 .where((d) => (d is ImportDirective || d is PartDirective || | |
| 288 d is ExportDirective)) | |
| 289 .map((d) => _resolve(assetId, d.uri.stringValue, _logger, | |
| 290 _getSpan(d, contents))) | |
| 291 .where((id) => id != null).toSet(); | |
| 292 } | |
| 293 | |
| 278 /// Update the contents of this file with [contents]. | 294 /// Update the contents of this file with [contents]. |
| 279 /// | 295 /// |
| 280 /// Returns true if the contents of this asset have changed. | 296 /// Returns true if the contents of this asset have changed. |
| 281 bool updateContents(String contents) { | 297 bool updateContents(String contents) { |
| 282 if (contents == _contents) return false; | 298 if (contents == _contents) return false; |
| 283 var added = _contents == null; | |
| 284 _contents = contents; | 299 _contents = contents; |
| 285 ++_revision; | 300 ++_revision; |
| 286 // Invalidate the imports so we only parse the AST when needed. | |
| 287 _dependentAssets = null; | |
| 288 | |
| 289 if (added) { | |
| 290 _resolver._context.applyChanges(new ChangeSet()..addedSource(this)); | |
| 291 } else { | |
| 292 _resolver._context.applyChanges(new ChangeSet()..changedSource(this)); | |
| 293 } | |
| 294 | |
| 295 var compilationUnit = _resolver._context.parseCompilationUnit(this); | |
| 296 _dependentAssets = compilationUnit.directives | |
| 297 .where((d) => (d is ImportDirective || d is PartDirective || | |
| 298 d is ExportDirective)) | |
| 299 .map((d) => _resolve(assetId, d.uri.stringValue, | |
| 300 _logger, _getSpan(d))) | |
| 301 .where((id) => id != null).toSet(); | |
| 302 return true; | 301 return true; |
| 303 } | 302 } |
| 304 | 303 |
| 305 /// Contents of the file. | 304 /// Contents of the file. |
| 306 TimestampedData<String> get contents => | 305 TimestampedData<String> get contents => |
| 307 new TimestampedData<String>(modificationStamp, _contents); | 306 new TimestampedData<String>(modificationStamp, _contents); |
| 308 | 307 |
| 309 /// Contents of the file. | 308 /// Contents of the file. |
| 310 String get rawContents => _contents; | 309 String get rawContents => _contents; |
| 311 | 310 |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 350 | 349 |
| 351 // The entire AST should have been parsed and loaded at this point. | 350 // The entire AST should have been parsed and loaded at this point. |
| 352 var source = _resolver.sources[id]; | 351 var source = _resolver.sources[id]; |
| 353 if (source == null) { | 352 if (source == null) { |
| 354 _logger.error('Could not load asset $id'); | 353 _logger.error('Could not load asset $id'); |
| 355 } | 354 } |
| 356 return source; | 355 return source; |
| 357 } | 356 } |
| 358 | 357 |
| 359 /// For logging errors. | 358 /// For logging errors. |
| 360 Span _getSpan(AstNode node) => _sourceFile.span(node.offset, node.end); | 359 Span _getSpan(AstNode node, [String contents]) => |
| 360 _getSourceFile(contents).span(node.offset, node.end); | |
| 361 /// For logging errors. | 361 /// For logging errors. |
| 362 SourceFile get _sourceFile { | 362 SourceFile _getSourceFile([String contents]) { |
| 363 var uri = getSourceUri(_resolver.entryPoint); | 363 var uri = getSourceUri(_resolver.entryPoint); |
| 364 var path = uri != null ? uri.toString() : assetId.path; | 364 var path = uri != null ? uri.toString() : assetId.path; |
| 365 | 365 return new SourceFile.text(path, contents != null ? contents : rawContents); |
| 366 return new SourceFile.text(path, rawContents); | |
| 367 } | 366 } |
| 368 | 367 |
| 369 /// Gets a URI which would be appropriate for importing this file. | 368 /// Gets a URI which would be appropriate for importing this file. |
| 370 /// | 369 /// |
| 371 /// Note that this file may represent a non-importable file such as a part. | 370 /// Note that this file may represent a non-importable file such as a part. |
| 372 Uri getSourceUri([AssetId from]) { | 371 Uri getSourceUri([AssetId from]) { |
| 373 if (!assetId.path.startsWith('lib/')) { | 372 if (!assetId.path.startsWith('lib/')) { |
| 374 // Cannot do absolute imports of non lib-based assets. | 373 // Cannot do absolute imports of non lib-based assets. |
| 375 if (from == null) return null; | 374 if (from == null) return null; |
| 376 | 375 |
| (...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 567 | 566 |
| 568 /** | 567 /** |
| 569 * A Future that complets with a List of the values from all the added | 568 * A Future that complets with a List of the values from all the added |
| 570 * tasks, when they have all completed. | 569 * tasks, when they have all completed. |
| 571 * | 570 * |
| 572 * If any task fails, this Future will receive the error. Only the first | 571 * If any task fails, this Future will receive the error. Only the first |
| 573 * error will be sent to the Future. | 572 * error will be sent to the Future. |
| 574 */ | 573 */ |
| 575 Future<List<E>> get future => _completer.future; | 574 Future<List<E>> get future => _completer.future; |
| 576 } | 575 } |
| 576 | |
| 577 /// Parse [code] using just the analyzer's parser, without actually getting the | |
| 578 /// resolver to run. | |
| 579 CompilationUnit _parseCompilationUnit(String code) { | |
|
blois
2014/03/17 16:29:46
Should be able to use package:analyzer/analyzer.da
Siggi Cherem (dart-lang)
2014/03/17 17:58:09
Done.
| |
| 580 var errorListener = new _ErrorCollector(); | |
| 581 var reader = new CharSequenceReader(code); | |
| 582 var scanner = new Scanner(null, reader, errorListener); | |
| 583 var token = scanner.tokenize(); | |
| 584 var parser = new Parser(null, errorListener); | |
| 585 return parser.parseCompilationUnit(token); | |
| 586 } | |
| 587 | |
| 588 class _ErrorCollector extends AnalysisErrorListener { | |
| 589 final errors = <AnalysisError>[]; | |
| 590 onError(error) => errors.add(error); | |
| 591 } | |
| 592 | |
| 593 /// A pending update to notify the resolver that a [Source] has been added or | |
| 594 /// changed. This is used by the `_performResolve` algorithm above to apply all | |
| 595 /// changes after it first discovers the transitive closure of files that are | |
| 596 /// reachable from the sources. | |
| 597 class _PendingUpdate { | |
| 598 _AssetBasedSource source; | |
| 599 String content; | |
| 600 | |
| 601 _PendingUpdate(this.source, this.content); | |
| 602 | |
| 603 void apply(ChangeSet changeSet) { | |
| 604 if (!source.updateContents(content)) return; | |
| 605 if (source._revision == 1 && source._contents != null) { | |
| 606 changeSet.addedSource(source); | |
| 607 } else { | |
| 608 changeSet.changedSource(source); | |
| 609 } | |
| 610 } | |
| 611 } | |
| OLD | NEW |