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 |