| 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 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 114 * [getResult] to the [Completer]s to report the result. | 114 * [getResult] to the [Completer]s to report the result. |
| 115 */ | 115 */ |
| 116 final _requestedFiles = <String, List<Completer<AnalysisResult>>>{}; | 116 final _requestedFiles = <String, List<Completer<AnalysisResult>>>{}; |
| 117 | 117 |
| 118 /** | 118 /** |
| 119 * The set of explicitly analyzed files. | 119 * The set of explicitly analyzed files. |
| 120 */ | 120 */ |
| 121 final _explicitFiles = new LinkedHashSet<String>(); | 121 final _explicitFiles = new LinkedHashSet<String>(); |
| 122 | 122 |
| 123 /** | 123 /** |
| 124 * The set of files were reported as changed through [changeFile] and for |
| 125 * which API signatures should be recomputed and compared before performing |
| 126 * any other analysis. |
| 127 */ |
| 128 final _filesToVerifyUnlinkedSignature = new Set<String>(); |
| 129 |
| 130 /** |
| 124 * The set of files that are currently scheduled for analysis. | 131 * The set of files that are currently scheduled for analysis. |
| 125 */ | 132 */ |
| 126 final _filesToAnalyze = new LinkedHashSet<String>(); | 133 final _filesToAnalyze = new LinkedHashSet<String>(); |
| 127 | 134 |
| 128 /** | 135 /** |
| 129 * Cache of URI resolution. The outer map key is the absolute URI of the | 136 * Cache of URI resolution. The outer map key is the absolute URI of the |
| 130 * containing file. The inner map key is the URI text of a directive | 137 * containing file. The inner map key is the URI text of a directive |
| 131 * contained in that file. The inner map value is the [Source] object which | 138 * contained in that file. The inner map value is the [Source] object which |
| 132 * that URI text resolves to. | 139 * that URI text resolves to. |
| 133 */ | 140 */ |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 185 * client does not change the state of the files. | 192 * client does not change the state of the files. |
| 186 * | 193 * |
| 187 * Results might be produced even for files that have never been added | 194 * Results might be produced even for files that have never been added |
| 188 * using [addFile], for example when [getResult] was called for a file. | 195 * using [addFile], for example when [getResult] was called for a file. |
| 189 */ | 196 */ |
| 190 Stream<AnalysisResult> get results async* { | 197 Stream<AnalysisResult> get results async* { |
| 191 try { | 198 try { |
| 192 while (true) { | 199 while (true) { |
| 193 // TODO(scheglov) implement state transitioning | 200 // TODO(scheglov) implement state transitioning |
| 194 await for (String why in _hasWorkStreamController.stream) { | 201 await for (String why in _hasWorkStreamController.stream) { |
| 202 _verifyUnlinkedSignatureOfChangedFiles(); |
| 203 |
| 195 // Analyze the first file in the general queue. | 204 // Analyze the first file in the general queue. |
| 196 if (_filesToAnalyze.isNotEmpty) { | 205 if (_filesToAnalyze.isNotEmpty) { |
| 197 _logger.run('Analyze ${_filesToAnalyze.length} files', () { | 206 _logger.run('Analyze ${_filesToAnalyze.length} files', () { |
| 198 while (_filesToAnalyze.isNotEmpty) { | 207 while (_filesToAnalyze.isNotEmpty) { |
| 199 String path = _filesToAnalyze.first; | 208 String path = _filesToAnalyze.first; |
| 200 _filesToAnalyze.remove(path); | 209 _filesToAnalyze.remove(path); |
| 201 _File file = _fileForPath(path); | 210 _File file = _fileForPath(path); |
| 202 _computeAndPrintErrors(file); | 211 _computeAndPrintErrors(file); |
| 203 // TODO(scheglov) yield the result | 212 // TODO(scheglov) yield the result |
| 204 } | 213 } |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 237 * that state already). Schedules the file contents for [path] to be read | 246 * that state already). Schedules the file contents for [path] to be read |
| 238 * into the current file state prior to the next time the analysis state | 247 * into the current file state prior to the next time the analysis state |
| 239 * transitions to "idle". | 248 * transitions to "idle". |
| 240 * | 249 * |
| 241 * Invocation of this method will not prevent a [Future] returned from | 250 * Invocation of this method will not prevent a [Future] returned from |
| 242 * [getResult] from completing with a result, but the result is not | 251 * [getResult] from completing with a result, but the result is not |
| 243 * guaranteed to be consistent with the new current file state after this | 252 * guaranteed to be consistent with the new current file state after this |
| 244 * [changeFile] invocation. | 253 * [changeFile] invocation. |
| 245 */ | 254 */ |
| 246 void changeFile(String path) { | 255 void changeFile(String path) { |
| 247 // TODO(scheglov) Don't clear, schedule API signature validation. | 256 _filesToVerifyUnlinkedSignature.add(path); |
| 248 _fileContentHashMap.clear(); | |
| 249 _dependencySignatureMap.clear(); | |
| 250 _filesToAnalyze.add(path); | 257 _filesToAnalyze.add(path); |
| 251 _filesToAnalyze.addAll(_explicitFiles); | |
| 252 // TODO(scheglov) name?! | |
| 253 _hasWorkStreamController.add('do it!'); | 258 _hasWorkStreamController.add('do it!'); |
| 254 } | 259 } |
| 255 | 260 |
| 256 /** | 261 /** |
| 257 * Return the [Future] that completes with a [AnalysisResult] for the file | 262 * Return the [Future] that completes with a [AnalysisResult] for the file |
| 258 * with the given [path]. | 263 * with the given [path]. |
| 259 * | 264 * |
| 260 * The [path] must be absolute and normalized. | 265 * The [path] must be absolute and normalized. |
| 261 * | 266 * |
| 262 * The [path] can be any file - explicitly or implicitly analyzed, or neither. | 267 * The [path] can be any file - explicitly or implicitly analyzed, or neither. |
| (...skipping 237 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 500 * Return the [_File] for the given [path] in [_sourceFactory]. | 505 * Return the [_File] for the given [path] in [_sourceFactory]. |
| 501 */ | 506 */ |
| 502 _File _fileForPath(String path) { | 507 _File _fileForPath(String path) { |
| 503 Source fileSource = _resourceProvider.getFile(path).createSource(); | 508 Source fileSource = _resourceProvider.getFile(path).createSource(); |
| 504 Uri uri = _sourceFactory.restoreUri(fileSource); | 509 Uri uri = _sourceFactory.restoreUri(fileSource); |
| 505 Source source = _resourceProvider.getFile(path).createSource(uri); | 510 Source source = _resourceProvider.getFile(path).createSource(uri); |
| 506 return new _File(this, source); | 511 return new _File(this, source); |
| 507 } | 512 } |
| 508 | 513 |
| 509 /** | 514 /** |
| 515 * Return the unlinked bundle of [file] for the current file state, or `null`. |
| 516 */ |
| 517 PackageBundle _getCurrentUnlinked(_File file) { |
| 518 String key = '${file.currentContentHash}.unlinked'; |
| 519 List<int> bytes = _byteStore.get(key); |
| 520 return bytes != null ? new PackageBundle.fromBuffer(bytes) : null; |
| 521 } |
| 522 |
| 523 /** |
| 510 * TODO(scheglov) It would be nice to get URIs of "parts" from unlinked. | 524 * TODO(scheglov) It would be nice to get URIs of "parts" from unlinked. |
| 511 */ | 525 */ |
| 512 _ReferencedUris _getReferencedUris(_File file) { | 526 _ReferencedUris _getReferencedUris(_File file) { |
| 513 // Try to get from the store. | 527 // Try to get from the store. |
| 514 { | 528 { |
| 515 String key = '${file.contentHash}.uris'; | 529 String key = '${file.contentHash}.uris'; |
| 516 List<int> bytes = _byteStore.get(key); | 530 List<int> bytes = _byteStore.get(key); |
| 517 if (bytes != null) { | 531 if (bytes != null) { |
| 518 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); | 532 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); |
| 519 int table = bp.derefObject(0); | 533 int table = bp.derefObject(0); |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 576 // So, we need to update the key. | 590 // So, we need to update the key. |
| 577 String key = '${file.contentHash}.uris'; | 591 String key = '${file.contentHash}.uris'; |
| 578 _byteStore.put(key, bytes); | 592 _byteStore.put(key, bytes); |
| 579 | 593 |
| 580 return referencedUris; | 594 return referencedUris; |
| 581 } | 595 } |
| 582 | 596 |
| 583 /** | 597 /** |
| 584 * Return the unlinked bundle of [file] for the current file state. | 598 * Return the unlinked bundle of [file] for the current file state. |
| 585 * | 599 * |
| 586 * That is, if there is an existing bundle for the current content hash | 600 * Return [_getCurrentUnlinked] or read the [file] content is read, compute |
| 587 * of the [file] in the [_byteStore], then it is returned. Otherwise, the | 601 * the content hash and update the current file state accordingly. Parse the |
| 588 * [file] content is read, the content hash is computed and the current file | 602 * content into the [CompilationUnit] and serialize into a new unlinked |
| 589 * state is updated accordingly. That the content is parsed into the | 603 * bundle. The bundle is then put into the [_byteStore] and returned. |
| 590 * [CompilationUnit] and serialized into a new unlinked bundle. The bundle | |
| 591 * is then put into the [_byteStore] and returned. | |
| 592 */ | 604 */ |
| 593 PackageBundle _getUnlinked(_File file) { | 605 PackageBundle _getUnlinked(_File file) { |
| 594 // Try to get bytes for file's unlinked bundle. | 606 return _getCurrentUnlinked(file) ?? |
| 595 List<int> bytes; | 607 _logger.run('Create unlinked for $file', () { |
| 596 { | 608 String key = '${file.contentHash}.unlinked'; |
| 597 String key = '${file.contentHash}.unlinked'; | 609 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(file.unit); |
| 598 bytes = _byteStore.get(key); | 610 PackageBundleAssembler assembler = new PackageBundleAssembler(); |
| 611 assembler.addUnlinkedUnitWithHash( |
| 612 file.uri.toString(), unlinkedUnit, key); |
| 613 List<int> bytes = assembler.assemble().toBuffer(); |
| 614 _byteStore.put(key, bytes); |
| 615 return new PackageBundle.fromBuffer(bytes); |
| 616 }); |
| 617 } |
| 618 |
| 619 /** |
| 620 * Verify the API signatures for the changed files, and decide which linked |
| 621 * libraries should be invalidated, and files reanalyzed. |
| 622 */ |
| 623 void _verifyUnlinkedSignatureOfChangedFiles() { |
| 624 if (_filesToVerifyUnlinkedSignature.isEmpty) { |
| 625 return; |
| 599 } | 626 } |
| 600 // If no cached unlinked bundle, compute it. | 627 int numOfFiles = _filesToVerifyUnlinkedSignature.length; |
| 601 if (bytes == null) { | 628 _logger.run('Verify API signatures of $numOfFiles files', () { |
| 602 _logger.run('Create unlinked for $file', () { | 629 for (String path in _filesToVerifyUnlinkedSignature) { |
| 603 // We read the content and recomputed the hash. | 630 _File file = _fileForPath(path); |
| 604 // So, we need to update the key. | 631 // Get the existing old API signature, maybe null. |
| 605 String key = '${file.contentHash}.unlinked'; | 632 String oldSignature = _getCurrentUnlinked(file)?.apiSignature; |
| 606 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(file.unit); | 633 // Clear the content hash cache, so force the file reading. |
| 607 PackageBundleAssembler assembler = new PackageBundleAssembler(); | 634 _fileContentHashMap.remove(path); |
| 608 assembler.addUnlinkedUnitWithHash( | 635 // Compute the new API signature. |
| 609 file.uri.toString(), unlinkedUnit, key); | 636 String newSignature = _getUnlinked(file).apiSignature; |
| 610 bytes = assembler.assemble().toBuffer(); | 637 // If the signatures are not the same, then potentially every linked |
| 611 _byteStore.put(key, bytes); | 638 // library is inconsistent and should be recomputed, and every explicit |
| 612 }); | 639 // file has inconsistent analysis results which also should be recompute
d. |
| 613 } | 640 if (oldSignature != newSignature) { |
| 614 return new PackageBundle.fromBuffer(bytes); | 641 _logger.writeln('API signature mismatch found for $file.'); |
| 642 _dependencySignatureMap.clear(); |
| 643 _filesToAnalyze.addAll(_explicitFiles); |
| 644 // Stop the verification, and restart analysis. |
| 645 break; |
| 646 } |
| 647 } |
| 648 _filesToVerifyUnlinkedSignature.clear(); |
| 649 }); |
| 615 } | 650 } |
| 616 } | 651 } |
| 617 | 652 |
| 618 /** | 653 /** |
| 619 * The result of analyzing of a single file. | 654 * The result of analyzing of a single file. |
| 620 * | 655 * |
| 621 * These results are self-consistent, i.e. [content], [contentHash], the | 656 * These results are self-consistent, i.e. [content], [contentHash], the |
| 622 * resolved [unit] correspond to each other. All referenced elements, even | 657 * resolved [unit] correspond to each other. All referenced elements, even |
| 623 * external ones, are also self-consistent. But none of the results is | 658 * external ones, are also self-consistent. But none of the results is |
| 624 * guaranteed to be consistent with the state of the files. | 659 * guaranteed to be consistent with the state of the files. |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 740 * current file state is updated. | 775 * current file state is updated. |
| 741 */ | 776 */ |
| 742 String get content { | 777 String get content { |
| 743 if (_content == null) { | 778 if (_content == null) { |
| 744 _readContentAndComputeHash(); | 779 _readContentAndComputeHash(); |
| 745 } | 780 } |
| 746 return _content; | 781 return _content; |
| 747 } | 782 } |
| 748 | 783 |
| 749 /** | 784 /** |
| 750 * Ensure that the [contentHash] is filled. | 785 * Ensure that the content hash is set for this [_File] instance, return it. |
| 751 * | 786 * |
| 752 * If the hash is already in the current file state, return the current | 787 * If the content hash has already been set for this [_File] instance, it is |
| 753 * value. Otherwise, read the [content], compute the hash, put it into | 788 * not updated here. But the hash value might be updated on [content] access. |
| 754 * the current file state, and update the [contentHash] field. | 789 * |
| 790 * If the content hash is known in the current file state, use it. |
| 791 * |
| 792 * Otherwise, read the [content], compute the hash, put it into the current |
| 793 * file state, and update the [contentHash] field. |
| 755 * | 794 * |
| 756 * The client should not remember values of this property, because its value | 795 * The client should not remember values of this property, because its value |
| 757 * might change when [content] is read and the hash is recomputed. | 796 * might change when [content] is read and the hash is recomputed. |
| 758 */ | 797 */ |
| 759 String get contentHash { | 798 String get contentHash { |
| 760 _contentHash ??= driver._fileContentHashMap[path]; | 799 _contentHash ??= currentContentHash; |
| 761 if (_contentHash == null) { | 800 if (_contentHash == null) { |
| 762 _readContentAndComputeHash(); | 801 _readContentAndComputeHash(); |
| 763 } | 802 } |
| 764 return _contentHash; | 803 return _contentHash; |
| 765 } | 804 } |
| 766 | 805 |
| 806 /** |
| 807 * Return the hash of the file content in the current file state, or `null` |
| 808 * if the current file state does not know the current file content hash. |
| 809 */ |
| 810 String get currentContentHash { |
| 811 return driver._fileContentHashMap[path]; |
| 812 } |
| 813 |
| 767 String get path => source.fullName; | 814 String get path => source.fullName; |
| 768 | 815 |
| 769 /** | 816 /** |
| 770 * Return the unresolved [CompilationUnit] of the file. | 817 * Return the unresolved [CompilationUnit] of the file. |
| 771 * | 818 * |
| 772 * Performing resolution and computing errors is done in a separate analysis | 819 * Performing resolution and computing errors is done in a separate analysis |
| 773 * context. In the future we might push the existing unresolved unit into the | 820 * context. In the future we might push the existing unresolved unit into the |
| 774 * analysis context, so at some point the unit might become resolved. | 821 * analysis context, so at some point the unit might become resolved. |
| 775 */ | 822 */ |
| 776 CompilationUnit get unit { | 823 CompilationUnit get unit { |
| (...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 952 | 999 |
| 953 /** | 1000 /** |
| 954 * TODO(scheglov) document | 1001 * TODO(scheglov) document |
| 955 */ | 1002 */ |
| 956 class _ReferencedUris { | 1003 class _ReferencedUris { |
| 957 bool isLibrary = true; | 1004 bool isLibrary = true; |
| 958 final List<String> imported = <String>[]; | 1005 final List<String> imported = <String>[]; |
| 959 final List<String> exported = <String>[]; | 1006 final List<String> exported = <String>[]; |
| 960 final List<String> parted = <String>[]; | 1007 final List<String> parted = <String>[]; |
| 961 } | 1008 } |
| OLD | NEW |