| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, 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 import 'dart:async'; | 5 import 'dart:async'; |
| 6 import 'dart:collection'; | 6 import 'dart:collection'; |
| 7 import 'dart:convert'; | 7 import 'dart:convert'; |
| 8 | 8 |
| 9 import 'package:analyzer/dart/ast/ast.dart'; | 9 import 'package:analyzer/dart/ast/ast.dart'; |
| 10 import 'package:analyzer/dart/ast/token.dart'; | 10 import 'package:analyzer/dart/ast/token.dart'; |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 228 continue; | 228 continue; |
| 229 } | 229 } |
| 230 | 230 |
| 231 // TODO(scheglov) analyze requested files | 231 // TODO(scheglov) analyze requested files |
| 232 | 232 |
| 233 // Analyze a priority file. | 233 // Analyze a priority file. |
| 234 if (_priorityFiles.isNotEmpty) { | 234 if (_priorityFiles.isNotEmpty) { |
| 235 bool analyzed = false; | 235 bool analyzed = false; |
| 236 for (String path in _priorityFiles) { | 236 for (String path in _priorityFiles) { |
| 237 if (_filesToAnalyze.remove(path)) { | 237 if (_filesToAnalyze.remove(path)) { |
| 238 _File file = _fileForPath(path); | |
| 239 AnalysisResult result = | 238 AnalysisResult result = |
| 240 _computeAnalysisResult(file, withUnit: true); | 239 _computeAnalysisResult(path, withUnit: true); |
| 241 yield result; | 240 yield result; |
| 242 break; | 241 break; |
| 243 } | 242 } |
| 244 } | 243 } |
| 245 // Repeat the processing loop. | 244 // Repeat the processing loop. |
| 246 if (analyzed) { | 245 if (analyzed) { |
| 247 _hasWork.notify(); | 246 _hasWork.notify(); |
| 248 continue; | 247 continue; |
| 249 } | 248 } |
| 250 } | 249 } |
| 251 | 250 |
| 252 // Analyze a general file. | 251 // Analyze a general file. |
| 253 if (_filesToAnalyze.isNotEmpty) { | 252 if (_filesToAnalyze.isNotEmpty) { |
| 254 String path = _removeFirst(_filesToAnalyze); | 253 String path = _removeFirst(_filesToAnalyze); |
| 255 _File file = _fileForPath(path); | 254 AnalysisResult result = _computeAnalysisResult(path, withUnit: false); |
| 256 AnalysisResult result = _computeAnalysisResult(file, withUnit: false); | |
| 257 yield result; | 255 yield result; |
| 258 // Repeat the processing loop. | 256 // Repeat the processing loop. |
| 259 _hasWork.notify(); | 257 _hasWork.notify(); |
| 260 continue; | 258 continue; |
| 261 } | 259 } |
| 262 | 260 |
| 263 // There is nothing to do. | 261 // There is nothing to do. |
| 264 analysisSection.exit(); | 262 analysisSection.exit(); |
| 265 analysisSection = null; | 263 analysisSection = null; |
| 266 } | 264 } |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 359 * add [UnlinkedUnit] with wrong URI. | 357 * add [UnlinkedUnit] with wrong URI. |
| 360 * | 358 * |
| 361 * We need to clean this up. | 359 * We need to clean this up. |
| 362 */ | 360 */ |
| 363 void _addToStoreUnlinked( | 361 void _addToStoreUnlinked( |
| 364 SummaryDataStore store, String uri, UnlinkedUnit unlinked) { | 362 SummaryDataStore store, String uri, UnlinkedUnit unlinked) { |
| 365 store.unlinkedMap[uri] = unlinked; | 363 store.unlinkedMap[uri] = unlinked; |
| 366 } | 364 } |
| 367 | 365 |
| 368 /** | 366 /** |
| 369 * Compute the [AnalysisResult] for the [file]. | 367 * Return the cached or newly computed analysis result of the file with the |
| 368 * given [path]. |
| 370 * | 369 * |
| 371 * The result will have the fully resolved unit only if [withUnit] is `true`. | 370 * The result will have the fully resolved unit and will always be newly |
| 371 * compute only if [withUnit] is `true`. |
| 372 */ | 372 */ |
| 373 AnalysisResult _computeAnalysisResult(_File file, {bool withUnit: false}) { | 373 AnalysisResult _computeAnalysisResult(String path, {bool withUnit: false}) { |
| 374 // If we don't need to the fully resolved unit, check for a cached result. | 374 Source source = _sourceForPath(path); |
| 375 |
| 376 // If we don't need the fully resolved unit, check for the cached result. |
| 375 if (!withUnit) { | 377 if (!withUnit) { |
| 376 AnalysisResult result = _getCachedAnalysisResult(file); | 378 _File file = new _File.forLinking(this, source); |
| 379 // Prepare the key for the cached result. |
| 380 String key = _getResolvedUnitKey(file); |
| 381 if (key == null) { |
| 382 _logger.run('Compute the dependency hash for $source', () { |
| 383 _createLibraryContext(file); |
| 384 key = _getResolvedUnitKey(file); |
| 385 }); |
| 386 } |
| 387 // Check for the cached result. |
| 388 AnalysisResult result = _getCachedAnalysisResult(file, key); |
| 377 if (result != null) { | 389 if (result != null) { |
| 378 return result; | 390 return result; |
| 379 } | 391 } |
| 380 } | 392 } |
| 381 | 393 |
| 382 // We need the fully resolved unit, or the result is not cached. | 394 // We need the fully resolved unit, or the result is not cached. |
| 383 return _logger.run('Compute analysis result for $file', () { | 395 return _logger.run('Compute analysis result for $source', () { |
| 396 // Still no result, compute and store it. |
| 397 _File file = new _File.forResolution(this, source); |
| 384 _LibraryContext libraryContext = _createLibraryContext(file); | 398 _LibraryContext libraryContext = _createLibraryContext(file); |
| 385 | |
| 386 // We recomputed the dependency hash, and we might have a cached result. | |
| 387 if (!withUnit) { | |
| 388 AnalysisResult result = _getCachedAnalysisResult(file); | |
| 389 if (result != null) { | |
| 390 _logger.writeln('Return the cached analysis result.'); | |
| 391 return result; | |
| 392 } | |
| 393 } | |
| 394 | |
| 395 // Still no result, compute and store it. | |
| 396 AnalysisContext analysisContext = _createAnalysisContext(libraryContext); | 399 AnalysisContext analysisContext = _createAnalysisContext(libraryContext); |
| 397 try { | 400 try { |
| 398 analysisContext.setContents(file.source, file.content); | 401 analysisContext.setContents(file.source, file.content); |
| 399 // TODO(scheglov) Add support for parts. | 402 // TODO(scheglov) Add support for parts. |
| 400 CompilationUnit resolvedUnit = | 403 CompilationUnit resolvedUnit = |
| 401 analysisContext.resolveCompilationUnit2(file.source, file.source); | 404 analysisContext.resolveCompilationUnit2(file.source, file.source); |
| 402 List<AnalysisError> errors = analysisContext.computeErrors(file.source); | 405 List<AnalysisError> errors = analysisContext.computeErrors(file.source); |
| 403 | 406 |
| 404 // Store the result into the cache. | 407 // Store the result into the cache. |
| 405 { | 408 { |
| (...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 552 PackageBundle linked = new PackageBundle.fromBuffer(bytes); | 555 PackageBundle linked = new PackageBundle.fromBuffer(bytes); |
| 553 _addToStoreLinked(store, uri, linked.linkedLibraries.single); | 556 _addToStoreLinked(store, uri, linked.linkedLibraries.single); |
| 554 _byteStore.put(key, bytes); | 557 _byteStore.put(key, bytes); |
| 555 }); | 558 }); |
| 556 | 559 |
| 557 return new _LibraryContext(libraryFile, libraryNode, store); | 560 return new _LibraryContext(libraryFile, libraryNode, store); |
| 558 }); | 561 }); |
| 559 } | 562 } |
| 560 | 563 |
| 561 /** | 564 /** |
| 562 * Return the [_File] for the given [path] in [_sourceFactory]. | 565 * If we know the result [key] for the [file], try to load the analysis |
| 566 * result from the cache. Return `null` if not found. |
| 563 */ | 567 */ |
| 564 _File _fileForPath(String path) { | 568 AnalysisResult _getCachedAnalysisResult(_File file, String key) { |
| 565 Source fileSource = _resourceProvider.getFile(path).createSource(); | 569 List<int> bytes = _byteStore.get(key); |
| 566 Uri uri = _sourceFactory.restoreUri(fileSource); | 570 if (bytes != null) { |
| 567 Source source = _resourceProvider.getFile(path).createSource(uri); | 571 var unit = new AnalysisDriverResolvedUnit.fromBuffer(bytes); |
| 568 return new _File.forResolution(this, source); | 572 List<AnalysisError> errors = unit.errors |
| 569 } | 573 .map((error) => new AnalysisError.forValues( |
| 570 | 574 file.source, |
| 571 /** | 575 error.offset, |
| 572 * If we know the dependency signature for the [file], try to load the | 576 error.length, |
| 573 * analysis result from the cache. Return `null` if not found. | 577 ErrorCode.byUniqueName(error.uniqueName), |
| 574 */ | 578 error.message, |
| 575 AnalysisResult _getCachedAnalysisResult(_File file) { | 579 error.correction)) |
| 576 String key = _getResolvedUnitKey(file); | 580 .toList(); |
| 577 if (key != null) { | 581 return new AnalysisResult( |
| 578 List<int> bytes = _byteStore.get(key); | 582 file.path, file.uri, null, file.contentHash, null, errors); |
| 579 if (bytes != null) { | |
| 580 var unit = new AnalysisDriverResolvedUnit.fromBuffer(bytes); | |
| 581 List<AnalysisError> errors = unit.errors | |
| 582 .map((error) => new AnalysisError.forValues( | |
| 583 file.source, | |
| 584 error.offset, | |
| 585 error.length, | |
| 586 ErrorCode.byUniqueName(error.uniqueName), | |
| 587 error.message, | |
| 588 error.correction)) | |
| 589 .toList(); | |
| 590 return new AnalysisResult( | |
| 591 file.path, file.uri, null, file.contentHash, null, errors); | |
| 592 } | |
| 593 } | 583 } |
| 594 return null; | 584 return null; |
| 595 } | 585 } |
| 596 | 586 |
| 597 /** | 587 /** |
| 598 * Return the key to store fully resolved results for the [file] into the | 588 * Return the key to store fully resolved results for the [file] into the |
| 599 * cache. Return `null` if the dependency signature is not known yet. | 589 * cache. Return `null` if the dependency signature is not known yet. |
| 600 */ | 590 */ |
| 601 String _getResolvedUnitKey(_File file) { | 591 String _getResolvedUnitKey(_File file) { |
| 602 String dependencyHash = _dependencySignatureMap[file.uri]; | 592 String dependencyHash = _dependencySignatureMap[file.uri]; |
| 603 if (dependencyHash != null) { | 593 if (dependencyHash != null) { |
| 604 ApiSignature signature = new ApiSignature(); | 594 ApiSignature signature = new ApiSignature(); |
| 605 signature.addString(dependencyHash); | 595 signature.addString(dependencyHash); |
| 606 signature.addString(file.contentHash); | 596 signature.addString(file.contentHash); |
| 607 return '${signature.toHex()}.resolved'; | 597 return '${signature.toHex()}.resolved'; |
| 608 } | 598 } |
| 609 return null; | 599 return null; |
| 610 } | 600 } |
| 611 | 601 |
| 612 /** | 602 /** |
| 603 * Return the [Source] for the given [path] in [_sourceFactory]. |
| 604 */ |
| 605 Source _sourceForPath(String path) { |
| 606 Source fileSource = _resourceProvider.getFile(path).createSource(); |
| 607 Uri uri = _sourceFactory.restoreUri(fileSource); |
| 608 return _resourceProvider.getFile(path).createSource(uri); |
| 609 } |
| 610 |
| 611 /** |
| 613 * Verify the API signature for the file with the given [path], and decide | 612 * Verify the API signature for the file with the given [path], and decide |
| 614 * which linked libraries should be invalidated, and files reanalyzed. | 613 * which linked libraries should be invalidated, and files reanalyzed. |
| 615 * | 614 * |
| 616 * TODO(scheglov) I see that adding a local var changes (full) API signature. | 615 * TODO(scheglov) I see that adding a local var changes (full) API signature. |
| 617 */ | 616 */ |
| 618 void _verifyApiSignatureOfChangedFile(String path) { | 617 void _verifyApiSignatureOfChangedFile(String path) { |
| 619 _logger.run('Verify API signature of $path', () { | 618 _logger.run('Verify API signature of $path', () { |
| 620 String oldSignature = _fileApiSignatureMap[path]; | 619 String oldSignature = _fileApiSignatureMap[path]; |
| 621 // Compute the new API signature. | 620 // Compute the new API signature. |
| 622 // _File.forResolution() also updates the content hash in the cache. | 621 // _File.forResolution() also updates the content hash in the cache. |
| 623 _File newFile = _fileForPath(path); | 622 Source source = _sourceForPath(path); |
| 623 _File newFile = new _File.forResolution(this, source); |
| 624 String newSignature = newFile.unlinked.apiSignature; | 624 String newSignature = newFile.unlinked.apiSignature; |
| 625 // If the old API signature is not null, then the file was used to | 625 // If the old API signature is not null, then the file was used to |
| 626 // compute at least one dependency signature. If the new API signature | 626 // compute at least one dependency signature. If the new API signature |
| 627 // is different, then potentially all dependency signatures and | 627 // is different, then potentially all dependency signatures and |
| 628 // resolution results are invalid. | 628 // resolution results are invalid. |
| 629 if (oldSignature != null && oldSignature != newSignature) { | 629 if (oldSignature != null && oldSignature != newSignature) { |
| 630 _logger.writeln('API signatures mismatch found for $newFile'); | 630 _logger.writeln('API signatures mismatch found for $newFile'); |
| 631 _dependencySignatureMap.clear(); | 631 _dependencySignatureMap.clear(); |
| 632 _filesToAnalyze.addAll(_explicitFiles); | 632 _filesToAnalyze.addAll(_explicitFiles); |
| 633 } | 633 } |
| (...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 809 /** | 809 /** |
| 810 * The unlinked bundle, not `null`. | 810 * The unlinked bundle, not `null`. |
| 811 */ | 811 */ |
| 812 final PackageBundle unlinked; | 812 final PackageBundle unlinked; |
| 813 | 813 |
| 814 /** | 814 /** |
| 815 * The unresolved unit, not `null` if this file is for resolution. | 815 * The unresolved unit, not `null` if this file is for resolution. |
| 816 */ | 816 */ |
| 817 final CompilationUnit unit; | 817 final CompilationUnit unit; |
| 818 | 818 |
| 819 factory _File.forLinking(AnalysisDriver driver, Source source) { | 819 /** |
| 820 // If we have enough cached information, use it. | 820 * Return the file with consistent [content] and [contentHash]. |
| 821 String contentHash = driver._fileContentHashMap[source.fullName]; | 821 */ |
| 822 if (contentHash != null) { | 822 factory _File.forContent(AnalysisDriver driver, Source source) { |
| 823 String key = '$contentHash.unlinked'; | |
| 824 List<int> bytes = driver._byteStore.get(key); | |
| 825 if (bytes != null) { | |
| 826 PackageBundle unlinked = new PackageBundle.fromBuffer(bytes); | |
| 827 return new _File._(driver, source, null, contentHash, unlinked, null); | |
| 828 } | |
| 829 } | |
| 830 // Otherwise, read the source, parse and build a new unlinked bundle. | |
| 831 return new _File.forResolution(driver, source); | |
| 832 } | |
| 833 | |
| 834 factory _File.forResolution(AnalysisDriver driver, Source source) { | |
| 835 String path = source.fullName; | 823 String path = source.fullName; |
| 836 // Read the content. | 824 // Read the content. |
| 837 String content; | 825 String content; |
| 838 try { | 826 try { |
| 839 content = driver._contentCache.getContents(source); | 827 content = driver._contentCache.getContents(source); |
| 840 content ??= source.contents.data; | 828 content ??= source.contents.data; |
| 841 } catch (_) { | 829 } catch (_) { |
| 842 content = ''; | 830 content = ''; |
| 843 // TODO(scheglov) We fail to report URI_DOES_NOT_EXIST. | 831 // TODO(scheglov) We fail to report URI_DOES_NOT_EXIST. |
| 844 // On one hand we need to provide an unlinked bundle to prevent | 832 // On one hand we need to provide an unlinked bundle to prevent |
| 845 // analysis context from reading the file (we want it to work | 833 // analysis context from reading the file (we want it to work |
| 846 // hermetically and handle one one file at a time). OTOH, | 834 // hermetically and handle one one file at a time). OTOH, |
| 847 // ResynthesizerResultProvider happily reports that any source in the | 835 // ResynthesizerResultProvider happily reports that any source in the |
| 848 // SummaryDataStore has MODIFICATION_TIME `0`. We need to return `-1` | 836 // SummaryDataStore has MODIFICATION_TIME `0`. We need to return `-1` |
| 849 // for missing files. Maybe add this feature to SummaryDataStore? | 837 // for missing files. Maybe add this feature to SummaryDataStore? |
| 850 } | 838 } |
| 851 // Compute the content hash. | 839 // Compute the content hash. |
| 852 List<int> textBytes = UTF8.encode(content); | 840 List<int> textBytes = UTF8.encode(content); |
| 853 List<int> hashBytes = md5.convert(textBytes).bytes; | 841 List<int> hashBytes = md5.convert(textBytes).bytes; |
| 854 String contentHash = hex.encode(hashBytes); | 842 String contentHash = hex.encode(hashBytes); |
| 855 driver._fileContentHashMap[path] = contentHash; | 843 driver._fileContentHashMap[path] = contentHash; |
| 844 // Return information about the file content. |
| 845 return new _File._(driver, source, content, contentHash, null, null); |
| 846 } |
| 847 |
| 848 factory _File.forLinking(AnalysisDriver driver, Source source) { |
| 849 String path = source.fullName; |
| 850 String contentHash = driver._fileContentHashMap[path]; |
| 851 // If we don't have the file content hash, compute it. |
| 852 if (contentHash == null) { |
| 853 _File file = new _File.forContent(driver, source); |
| 854 contentHash = file.contentHash; |
| 855 } |
| 856 // If we have the cached unlinked bundle, use it. |
| 857 { |
| 858 String key = '$contentHash.unlinked'; |
| 859 List<int> bytes = driver._byteStore.get(key); |
| 860 if (bytes != null) { |
| 861 PackageBundle unlinked = new PackageBundle.fromBuffer(bytes); |
| 862 driver._fileApiSignatureMap[path] = unlinked.apiSignature; |
| 863 return new _File._(driver, source, null, contentHash, unlinked, null); |
| 864 } |
| 865 } |
| 866 // Otherwise, read the source, parse and build a new unlinked bundle. |
| 867 return new _File.forResolution(driver, source); |
| 868 } |
| 869 |
| 870 factory _File.forResolution(AnalysisDriver driver, Source source) { |
| 871 _File file = new _File.forContent(driver, source); |
| 872 String path = file.path; |
| 873 String content = file.content; |
| 874 String contentHash = file.contentHash; |
| 856 // Parse the unit. | 875 // Parse the unit. |
| 857 CompilationUnit unit = _parse(driver, source, content); | 876 CompilationUnit unit = _parse(driver, source, content); |
| 858 // Prepare the unlinked bundle. | 877 // Prepare the unlinked bundle. |
| 859 PackageBundle unlinked; | 878 PackageBundle unlinked; |
| 860 { | 879 { |
| 861 String key = '$contentHash.unlinked'; | 880 String key = '$contentHash.unlinked'; |
| 862 List<int> bytes = driver._byteStore.get(key); | 881 List<int> bytes = driver._byteStore.get(key); |
| 863 if (bytes == null) { | 882 if (bytes == null) { |
| 864 driver._logger.run('Create unlinked for $path', () { | 883 driver._logger.run('Create unlinked for $path', () { |
| 865 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit); | 884 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit); |
| 866 PackageBundleAssembler assembler = new PackageBundleAssembler(); | 885 PackageBundleAssembler assembler = new PackageBundleAssembler(); |
| 867 assembler.addUnlinkedUnitWithHash( | 886 assembler.addUnlinkedUnitWithHash( |
| 868 source.uri.toString(), unlinkedUnit, contentHash); | 887 source.uri.toString(), unlinkedUnit, contentHash); |
| 869 bytes = assembler.assemble().toBuffer(); | 888 bytes = assembler.assemble().toBuffer(); |
| 870 driver._byteStore.put(key, bytes); | 889 driver._byteStore.put(key, bytes); |
| 871 }); | 890 }); |
| 872 } | 891 } |
| 873 unlinked = new PackageBundle.fromBuffer(bytes); | 892 unlinked = new PackageBundle.fromBuffer(bytes); |
| 874 driver._fileApiSignatureMap[path] = unlinked.apiSignature; | 893 driver._fileApiSignatureMap[path] = unlinked.apiSignature; |
| 875 } | 894 } |
| 876 // Update the current file state. | 895 // Return the full file. |
| 877 return new _File._(driver, source, content, contentHash, unlinked, unit); | 896 return new _File._(driver, source, content, contentHash, unlinked, unit); |
| 878 } | 897 } |
| 879 | 898 |
| 880 _File._(this.driver, this.source, this.content, this.contentHash, | 899 _File._(this.driver, this.source, this.content, this.contentHash, |
| 881 this.unlinked, this.unit); | 900 this.unlinked, this.unit); |
| 882 | 901 |
| 883 String get path => source.fullName; | 902 String get path => source.fullName; |
| 884 | 903 |
| 885 Uri get uri => source.uri; | 904 Uri get uri => source.uri; |
| 886 | 905 |
| (...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1082 } | 1101 } |
| 1083 } | 1102 } |
| 1084 for (UnlinkedExportPublic export in unit.publicNamespace.exports) { | 1103 for (UnlinkedExportPublic export in unit.publicNamespace.exports) { |
| 1085 referenced.exported.add(export.uri); | 1104 referenced.exported.add(export.uri); |
| 1086 } | 1105 } |
| 1087 return referenced; | 1106 return referenced; |
| 1088 } | 1107 } |
| 1089 | 1108 |
| 1090 _ReferencedUris._(); | 1109 _ReferencedUris._(); |
| 1091 } | 1110 } |
| OLD | NEW |