Index: pkg/analyzer/lib/src/dart/analysis/driver.dart |
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart |
index 2a86589a75ede9649358534072c2c6ad302cfbdb..0b2bbf18912ec8ac14a1f565e04103e92ef3b57f 100644 |
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart |
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart |
@@ -3,11 +3,36 @@ |
// BSD-style license that can be found in the LICENSE file. |
import 'dart:async'; |
+import 'dart:collection'; |
+import 'dart:convert'; |
import 'package:analyzer/dart/ast/ast.dart'; |
+import 'package:analyzer/dart/ast/token.dart'; |
import 'package:analyzer/error/error.dart'; |
+import 'package:analyzer/error/listener.dart'; |
+import 'package:analyzer/file_system/file_system.dart'; |
+import 'package:analyzer/src/context/context.dart'; |
+import 'package:analyzer/src/context/source.dart'; |
import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
+import 'package:analyzer/src/dart/error/todo_codes.dart'; |
+import 'package:analyzer/src/dart/scanner/reader.dart'; |
+import 'package:analyzer/src/dart/scanner/scanner.dart'; |
+import 'package:analyzer/src/generated/engine.dart' |
+ show AnalysisContext, AnalysisEngine, AnalysisOptions, ChangeSet; |
+import 'package:analyzer/src/generated/parser.dart'; |
import 'package:analyzer/src/generated/source.dart'; |
+import 'package:analyzer/src/generated/utilities_dart.dart'; |
+import 'package:analyzer/src/summary/api_signature.dart'; |
+import 'package:analyzer/src/summary/flat_buffers.dart' as fb; |
+import 'package:analyzer/src/summary/format.dart'; |
+import 'package:analyzer/src/summary/idl.dart'; |
+import 'package:analyzer/src/summary/link.dart'; |
+import 'package:analyzer/src/summary/package_bundle_reader.dart'; |
+import 'package:analyzer/src/summary/summarize_ast.dart'; |
+import 'package:analyzer/src/summary/summarize_elements.dart'; |
+import 'package:analyzer/src/util/fast_uri.dart'; |
+import 'package:convert/convert.dart'; |
+import 'package:crypto/crypto.dart'; |
/** |
* This class computes [AnalysisResult]s for Dart files. |
@@ -47,6 +72,13 @@ import 'package:analyzer/src/generated/source.dart'; |
* TODO(scheglov) Clean up the list of implicitly analyzed files. |
*/ |
class AnalysisDriver { |
+ final PerformanceLog _logger; |
+ |
+ /** |
+ * The resource provider for working with files. |
+ */ |
+ final ResourceProvider _resourceProvider; |
+ |
/** |
* The byte storage to get and put serialized data. |
* |
@@ -55,18 +87,60 @@ class AnalysisDriver { |
final ByteStore _byteStore; |
/** |
+ * This [ContentCache] is consulted for a file content before reading |
+ * the content from the file. |
+ */ |
+ final ContentCache _contentCache; |
+ |
+ /** |
* The [SourceFactory] is used to resolve URIs to paths and restore URIs |
* from file paths. |
*/ |
final SourceFactory _sourceFactory; |
/** |
- * This [ContentCache] is consulted for a file content before reading |
- * the content from the file. |
+ * The analysis options to analyze with. |
*/ |
- final ContentCache _contentCache; |
+ final AnalysisOptions _analysisOptions; |
+ |
+ /** |
+ * The combined unlinked and linked package for the SDK, extracted from |
+ * the given [_sourceFactory]. |
+ */ |
+ PackageBundle _sdkBundle; |
+ |
+ /** |
+ * The mapping from the files for which analysis was requested using |
+ * [getResult] to the [Completer]s to report the result. |
+ */ |
+ final _requestedFiles = <String, Completer<AnalysisResult>>{}; |
+ |
+ /** |
+ * The set of explicitly analyzed files. |
+ */ |
+ final _explicitFiles = new LinkedHashSet<String>(); |
- AnalysisDriver(this._byteStore, this._sourceFactory, this._contentCache); |
+ /** |
+ * The set of files that are currently scheduled for analysis. |
+ */ |
+ final _filesToAnalyze = new LinkedHashSet<String>(); |
+ |
+ /** |
+ * The current file state. |
+ * |
+ * It maps file paths to MD5 hash of the file content. |
+ */ |
+ final _fileContentHashMap = <String, String>{}; |
+ |
+ /** |
+ * TODO(scheglov) document and improve |
+ */ |
+ final _hasWorkStreamController = new StreamController<String>(); |
+ |
+ AnalysisDriver(this._logger, this._resourceProvider, this._byteStore, |
+ this._contentCache, this._sourceFactory, this._analysisOptions) { |
+ _sdkBundle = _sourceFactory.dartSdk.getLinkedBundle(); |
+ } |
/** |
* Set the list of files that the driver should try to analyze sooner. |
@@ -101,7 +175,28 @@ class AnalysisDriver { |
* using [addFile], for example when [getResult] was called for a file. |
*/ |
Stream<AnalysisResult> get results async* { |
- // TODO(scheglov) implement |
+ try { |
+ while (true) { |
+ // TODO(scheglov) implement state transitioning |
+ await for (String why in _hasWorkStreamController.stream) { |
+ // Analyze the first file in the general queue. |
+ if (_filesToAnalyze.isNotEmpty) { |
+ _logger.runTimed('Analyzed ${_filesToAnalyze.length} files', () { |
+ while (_filesToAnalyze.isNotEmpty) { |
+ String path = _filesToAnalyze.first; |
+ _filesToAnalyze.remove(path); |
+ _File file = _fileForPath(path); |
+ _computeAndPrintErrors(file); |
+ // TODO(scheglov) yield the result |
+ } |
+ }); |
+ } |
+ } |
+ // TODO(scheglov) implement |
+ } |
+ } finally { |
+ print('The stream was cancelled.'); |
+ } |
} |
/** |
@@ -112,7 +207,9 @@ class AnalysisDriver { |
* The results of analysis are eventually produced by the [results] stream. |
*/ |
void addFile(String path) { |
- // TODO(scheglov) implement |
+ _explicitFiles.add(path); |
+ _filesToAnalyze.add(path); |
+ _hasWorkStreamController.add('do it!'); |
} |
/** |
@@ -134,7 +231,12 @@ class AnalysisDriver { |
* [changeFile] invocation. |
*/ |
void changeFile(String path) { |
- // TODO(scheglov) implement |
+ // TODO(scheglov) Don't clear, schedule API signature validation. |
+ _fileContentHashMap.clear(); |
+ _filesToAnalyze.add(path); |
+ _filesToAnalyze.addAll(_explicitFiles); |
+ // TODO(scheglov) name?! |
+ _hasWorkStreamController.add('do it!'); |
} |
/** |
@@ -152,8 +254,9 @@ class AnalysisDriver { |
* transitions to "idle". |
*/ |
Future<AnalysisResult> getResult(String path) { |
- // TODO(scheglov) implement |
- throw new UnimplementedError(); |
+ var completer = new Completer<AnalysisResult>(); |
+ _requestedFiles[path] = completer; |
Paul Berry
2016/10/24 11:46:21
There's a bug here. If the client calls getResult
scheglov
2016/10/24 17:23:48
Fixed.
We need a list of completers here.
Every of
|
+ return completer.future; |
Paul Berry
2016/10/24 11:46:21
I'm surprised not to see a call to `_hasWorkStream
scheglov
2016/10/24 17:23:48
Done.
Yes, we need to add a value to the "has wor
|
} |
/** |
@@ -166,7 +269,310 @@ class AnalysisDriver { |
* but does not guarantee this. |
*/ |
void removeFile(String path) { |
- // TODO(scheglov) implement |
+ _explicitFiles.remove(path); |
+ _filesToAnalyze.remove(path); |
+ } |
+ |
+ /** |
+ * TODO(scheglov) replace with actual [AnalysisResult] computing. |
+ */ |
+ List<String> _computeAndPrintErrors(_File file) { |
+ List<String> errorStrings = _logger.run('Compute errors $file', () { |
+ LibraryContext libraryContext = _createLibraryContext(file); |
+ |
+ String errorsKey; |
+ { |
+ ApiSignature signature = new ApiSignature(); |
+ signature.addString(libraryContext.node.linkedHash); |
+ signature.addString(file.contentHash); |
+ errorsKey = '${signature.toHex()}.errors'; |
+ } |
+ |
+ { |
+ List<int> bytes = _byteStore.get(errorsKey); |
+ if (bytes != null) { |
+ fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); |
+ int table = bp.derefObject(0); |
+ return const fb.ListReader<String>(const fb.StringReader()) |
+ .vTableGet(bp, table, 0); |
+ } |
+ } |
+ |
+ AnalysisContext analysisContext = _createAnalysisContext(libraryContext); |
+ analysisContext.resolveCompilationUnit2( |
+ libraryContext.file.source, libraryContext.file.source); |
+ try { |
+ List<AnalysisError> errors; |
+ try { |
+ errors = _logger.runTimed('Computed errors', () { |
+ return analysisContext.computeErrors(file.source); |
+ }); |
+ } catch (e, st) { |
+ // TODO(scheglov) why does it fail? |
+ // Caused by Bad state: Unmatched TypeParameterElementImpl T |
Paul Berry
2016/10/24 11:46:21
This exception is usually caused by a bug in type
scheglov
2016/10/24 17:23:47
I will try to come up with a repro today.
scheglov
2016/10/24 17:29:31
Hm...
Cannot reproduce now.
I will remove try/catc
|
+ errors = []; |
+ } |
+ List<String> errorStrings = errors |
+ .where((error) => error.errorCode is! TodoCode) |
+ .map((error) => error.toString()) |
+ .toList(); |
+ { |
+ fb.Builder fbBuilder = new fb.Builder(); |
+ var exportedOffset = fbBuilder.writeList(errorStrings |
+ .map((errorStr) => fbBuilder.writeString(errorStr)) |
+ .toList()); |
+ fbBuilder.startTable(); |
+ fbBuilder.addOffset(0, exportedOffset); |
+ var offset = fbBuilder.endTable(); |
+ List<int> bytes = fbBuilder.finish(offset, 'CErr'); |
+ _byteStore.put(errorsKey, bytes); |
+ } |
+ |
+ return errorStrings; |
+ } finally { |
+ analysisContext.dispose(); |
+ } |
+ }); |
+ |
+ if (errorStrings.isNotEmpty) { |
+ errorStrings.forEach((errorString) => print('\t$errorString')); |
+ } else { |
+ print('\tNO ERRORS'); |
+ } |
+ return errorStrings; |
+ } |
+ |
+ AnalysisContext _createAnalysisContext(LibraryContext libraryContext) { |
+ AnalysisContextImpl analysisContext = |
+ AnalysisEngine.instance.createAnalysisContext(); |
+ |
+ analysisContext.sourceFactory = |
+ new SourceFactory((_sourceFactory as SourceFactoryImpl).resolvers); |
+ analysisContext.resultProvider = |
+ new InputPackagesResultProvider(analysisContext, libraryContext.store); |
+ analysisContext |
+ .applyChanges(new ChangeSet()..addedSource(libraryContext.file.source)); |
+ return analysisContext; |
+ } |
+ |
+ /** |
+ * Return the content in which the library represented by the given |
Paul Berry
2016/10/24 11:46:21
s/content/context/
scheglov
2016/10/24 17:23:48
Done.
|
+ * [libraryFile] should be analyzed it. |
+ * |
+ * TODO(scheglov) We often don't need [SummaryDataStore], only linked hash. |
+ */ |
+ LibraryContext _createLibraryContext(_File libraryFile) { |
+ Map<String, _LibraryNode> nodes = <String, _LibraryNode>{}; |
+ |
+ return _logger.run('Create library context', () { |
+ SummaryDataStore store = new SummaryDataStore(const <String>[]); |
+ store.addBundle(null, _sdkBundle); |
+ |
+ void createLibraryNodes(_File libraryFile) { |
+ Uri libraryUri = libraryFile.uri; |
+ if (libraryUri.scheme == 'dart') { |
+ return; |
Paul Berry
2016/10/24 11:46:21
Why? Is this because we always get "dart:" stuff
scheglov
2016/10/24 17:23:48
Done.
|
+ } |
+ String uriStr = libraryUri.toString(); |
+ if (!nodes.containsKey(uriStr)) { |
+ _LibraryNode node = new _LibraryNode(this, nodes, libraryUri); |
+ nodes[uriStr] = node; |
+ ReferencedUris referenced = _getReferencedUris(libraryFile); |
+ |
+ // Append unlinked bundles. |
+ for (String uri in referenced.parted) { |
+ _File file = libraryFile.resolveUri(uri); |
+ PackageBundle unlinked = _getUnlinked(file); |
+ node.unlinkedBundles.add(unlinked); |
+ store.addBundle(null, unlinked); |
+ } |
+ |
+ // Create nodes for referenced libraries. |
+ for (String uri in referenced.imported) { |
+ _File file = libraryFile.resolveUri(uri); |
+ createLibraryNodes(file); |
+ } |
+ for (String uri in referenced.exported) { |
+ _File file = libraryFile.resolveUri(uri); |
+ createLibraryNodes(file); |
+ } |
+ } |
+ } |
+ |
+ _logger.runTimed2(() { |
+ createLibraryNodes(libraryFile); |
+ }, () => 'Computed ${nodes.length} nodes'); |
+ _LibraryNode libraryNode = nodes[libraryFile.uri.toString()]; |
Paul Berry
2016/10/24 11:46:21
Nit: how about if we change createLibraryNodes() s
scheglov
2016/10/24 17:23:48
Done.
|
+ |
+ Set<String> libraryUrisToLink = new Set<String>(); |
+ int numberOfNodesWithLinked = 0; |
+ _logger.runTimed2(() { |
+ for (_LibraryNode node in nodes.values) { |
+ String key = '${node.linkedHash}.linked'; |
+ List<int> bytes = _byteStore.get(key); |
+ if (bytes != null) { |
+ PackageBundle linked = new PackageBundle.fromBuffer(bytes); |
+ node.linked = linked; |
+ store.addBundle(null, linked); |
+ numberOfNodesWithLinked++; |
+ } else { |
+ libraryUrisToLink.add(node.uri.toString()); |
+ } |
+ } |
+ }, () => 'Loaded $numberOfNodesWithLinked linked bundles'); |
+ |
+ Map<String, LinkedLibraryBuilder> linkedLibraries = {}; |
+ _logger.runTimed2(() { |
+ linkedLibraries = link(libraryUrisToLink, (String uri) { |
+ LinkedLibrary linkedLibrary = store.linkedMap[uri]; |
+ if (linkedLibrary == null) { |
+ throw new StateError('No linked library for: $uri'); |
+ } |
+ return linkedLibrary; |
+ }, (String uri) { |
+ UnlinkedUnit unlinkedUnit = store.unlinkedMap[uri]; |
+ if (unlinkedUnit == null) { |
+ throw new StateError('No unlinked unit for: $uri'); |
+ } |
+ return unlinkedUnit; |
+ }, (_) => null, _analysisOptions.strongMode); |
+ }, () => 'Linked ${linkedLibraries.length} bundles'); |
+ |
+ linkedLibraries.forEach((uri, linkedBuilder) { |
+ _LibraryNode node = nodes[uri]; |
+ String key = '${node.linkedHash}.linked'; |
+ List<int> bytes; |
+ { |
+ PackageBundleAssembler assembler = new PackageBundleAssembler(); |
+ assembler.addLinkedLibrary(uri, linkedBuilder); |
+ bytes = assembler.assemble().toBuffer(); |
+ } |
+ PackageBundle linked = new PackageBundle.fromBuffer(bytes); |
+ node.linked = linked; |
+ store.addBundle(null, linked); |
+ _byteStore.put(key, bytes); |
+ }); |
+ |
+ return new LibraryContext(libraryFile, libraryNode, store); |
+ }); |
+ } |
+ |
+ /** |
+ * Return the [_File] for the given [path] in [_sourceFactory]. |
+ */ |
+ _File _fileForPath(String path) { |
+ Source fileSource = _resourceProvider.getFile(path).createSource(); |
+ Uri uri = _sourceFactory.restoreUri(fileSource); |
+ Source source = _resourceProvider.getFile(path).createSource(uri); |
+ return new _File(this, source); |
+ } |
+ |
+ /** |
+ * TODO(scheglov) It would be nice to get URIs of "parts" from unlinked. |
+ */ |
+ ReferencedUris _getReferencedUris(_File file) { |
+ // Try to get from the store. |
+ { |
+ String key = '${file.contentHash}.uris'; |
+ List<int> bytes = _byteStore.get(key); |
+ if (bytes != null) { |
+ fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); |
+ int table = bp.derefObject(0); |
+ const fb.ListReader<String> stringListReader = |
+ const fb.ListReader<String>(const fb.StringReader()); |
+ bool isLibrary = const fb.BoolReader().vTableGet(bp, table, 0); |
+ List<String> imported = stringListReader.vTableGet(bp, table, 1); |
+ List<String> exported = stringListReader.vTableGet(bp, table, 2); |
+ List<String> parted = stringListReader.vTableGet(bp, table, 3); |
+ ReferencedUris referencedUris = new ReferencedUris(); |
+ referencedUris.isLibrary = isLibrary; |
+ referencedUris.imported.addAll(imported); |
+ referencedUris.exported.addAll(exported); |
+ referencedUris.parted.addAll(parted); |
+ return referencedUris; |
+ } |
+ } |
+ |
+ // Compute URIs. |
+ ReferencedUris referencedUris = new ReferencedUris(); |
+ referencedUris.parted.add(file.uri.toString()); |
+ for (Directive directive in file.unit.directives) { |
+ if (directive is PartOfDirective) { |
+ referencedUris.isLibrary = false; |
+ } else if (directive is UriBasedDirective) { |
+ String uri = directive.uri.stringValue; |
+ if (directive is ImportDirective) { |
+ referencedUris.imported.add(uri); |
+ } else if (directive is ExportDirective) { |
+ referencedUris.exported.add(uri); |
+ } else if (directive is PartDirective) { |
+ referencedUris.parted.add(uri); |
+ } |
+ } |
+ } |
+ |
+ // Serialize into bytes. |
+ List<int> bytes; |
+ { |
+ fb.Builder fbBuilder = new fb.Builder(); |
+ var importedOffset = fbBuilder.writeList(referencedUris.imported |
+ .map((uri) => fbBuilder.writeString(uri)) |
+ .toList()); |
+ var exportedOffset = fbBuilder.writeList(referencedUris.exported |
+ .map((uri) => fbBuilder.writeString(uri)) |
+ .toList()); |
+ var partedOffset = fbBuilder.writeList(referencedUris.parted |
+ .map((uri) => fbBuilder.writeString(uri)) |
+ .toList()); |
+ fbBuilder.startTable(); |
+ fbBuilder.addBool(0, referencedUris.isLibrary); |
+ fbBuilder.addOffset(1, importedOffset); |
+ fbBuilder.addOffset(2, exportedOffset); |
+ fbBuilder.addOffset(3, partedOffset); |
+ var offset = fbBuilder.endTable(); |
+ bytes = fbBuilder.finish(offset, 'SoRU'); |
+ } |
+ |
+ // We read the content and recomputed the hash. |
+ // So, we need to update the key. |
+ String key = '${file.contentHash}.uris'; |
+ _byteStore.put(key, bytes); |
+ |
+ return referencedUris; |
+ } |
+ |
+ /** |
+ * Return the unlinked bundle of [file] for the current file state. |
+ * |
+ * That is, if there is an existing bundle for the current content hash |
+ * of the [file] in the [_byteStore], then it is returned. Otherwise, the |
+ * [file] content is read, the content hash is computed and the current file |
+ * state is updated accordingly. That the content is parsed into the |
+ * [CompilationUnit] and serialized into a new unlinked bundle. The bundle |
+ * is then put into the [_byteStore] and returned. |
+ */ |
+ PackageBundle _getUnlinked(_File file) { |
+ // Try to get bytes for file's unlinked bundle. |
+ List<int> bytes; |
+ { |
+ String key = '${file.contentHash}.unlinked'; |
+ bytes = _byteStore.get(key); |
+ } |
+ // If no cached unlinked bundle, compute it. |
+ if (bytes == null) { |
+ _logger.runTimed('Create unlinked for $file', () { |
+ // We read the content and recomputed the hash. |
+ // So, we need to update the key. |
+ String key = '${file.contentHash}.unlinked'; |
+ UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(file.unit); |
+ PackageBundleAssembler assembler = new PackageBundleAssembler(); |
+ assembler.addUnlinkedUnitWithHash( |
+ file.uri.toString(), unlinkedUnit, key); |
+ bytes = assembler.assemble().toBuffer(); |
+ _byteStore.put(key, bytes); |
+ }); |
+ } |
+ return new PackageBundle.fromBuffer(bytes); |
} |
} |
@@ -217,3 +623,299 @@ class AnalysisResult { |
AnalysisResult(this.path, this.uri, this.content, this.contentHash, this.unit, |
this.errors); |
} |
+ |
+class LibraryContext { |
Paul Berry
2016/10/24 11:46:21
Please add doc comments to this class and its publ
scheglov
2016/10/24 17:23:48
Done.
|
+ final _File file; |
+ final _LibraryNode node; |
+ final SummaryDataStore store; |
+ LibraryContext(this.file, this.node, this.store); |
+} |
+ |
+class PerformanceLog { |
+ final StringSink sink; |
+ int _level = 0; |
+ |
+ PerformanceLog(this.sink); |
+ |
+ /*=T*/ run/*<T>*/(String msg, /*=T*/ f()) { |
+ Stopwatch timer = new Stopwatch()..start(); |
+ try { |
+ writeln('+++ $msg.'); |
+ _level++; |
+ return f(); |
+ } finally { |
+ _level--; |
+ int ms = timer.elapsedMilliseconds; |
+ writeln('--- $msg in $ms ms.'); |
+ } |
+ } |
+ |
+ /*=T*/ runTimed/*<T>*/(String msg, /*=T*/ f()) { |
Paul Berry
2016/10/24 11:46:21
I'm not comfortable with the duplication between r
scheglov
2016/10/24 17:23:48
Acknowledged.
|
+ _level++; |
+ Stopwatch timer = new Stopwatch()..start(); |
+ try { |
+ return f(); |
+ } finally { |
+ _level--; |
+ int ms = timer.elapsedMilliseconds; |
+ writeln('$msg in $ms ms.'); |
+ } |
+ } |
+ |
+ runTimed2(f(), String getMsg()) { |
Paul Berry
2016/10/24 11:46:21
Similar concern here. I would recommend getting r
scheglov
2016/10/24 17:23:48
Acknowledged.
I completely agree.
I changed the lo
|
+ _level++; |
+ Stopwatch timer = new Stopwatch()..start(); |
+ try { |
+ return f(); |
+ } finally { |
+ _level--; |
+ int ms = timer.elapsedMilliseconds; |
+ String msg = getMsg(); |
+ writeln('$msg in $ms ms.'); |
+ } |
+ } |
+ |
+ void writeln(String msg) { |
+ String indent = '\t' * _level; |
+ sink.writeln('$indent$msg'); |
+ } |
+} |
+ |
+class ReferencedUris { |
+ bool isLibrary = true; |
+ final List<String> imported = <String>[]; |
+ final List<String> exported = <String>[]; |
+ final List<String> parted = <String>[]; |
+} |
+ |
+/** |
+ * Information about a file being analyzed, explicitly or implicitly. |
+ * |
+ * It keeps a consistent view on its [content], [contentHash] and [unit]. |
+ */ |
+class _File { |
+ /** |
+ * The driver instance that is used to access [SourceFactory] and caches. |
+ */ |
+ final AnalysisDriver driver; |
+ |
+ /** |
+ * The [Source] this [_File] instance represent. |
Brian Wilkerson
2016/10/26 06:31:48
nit: "represent" --> "represents"
|
+ */ |
+ final Source source; |
+ |
+ String _content; |
+ String _contentHash; |
+ CompilationUnit _unit; |
+ |
+ _File(this.driver, this.source); |
+ |
+ /** |
+ * Return the current content of the file. |
+ * |
+ * If the [_content] field if it is still `null`, get the content from the |
+ * content cache or from the [source]. If the content cannot be accessed |
+ * because of an exception, it considers to be an empty string. |
+ * |
+ * When a new content is read, the new [_contentHash] is computed and the |
+ * current file state is updated. |
+ */ |
+ String get content { |
+ if (_content == null) { |
+ _readContentAndComputeHash(); |
+ } |
+ return _content; |
+ } |
+ |
+ /** |
+ * Ensure that the [contentHash] is filled. |
+ * |
+ * If the hash is already in the current file state, return the current |
+ * value. Otherwise, read the [content], compute the hash, put it into |
+ * the current file state, and update the [contentHash] field. |
+ * |
+ * The client cannot remember values of this property, because its value |
Paul Berry
2016/10/24 11:46:21
Change "cannot" to either "should not" or "cannot
scheglov
2016/10/24 17:23:48
Done.
|
+ * might change when [content] is read and the hash is recomputed. |
+ */ |
+ String get contentHash { |
+ _contentHash ??= driver._fileContentHashMap[path]; |
+ if (_contentHash == null) { |
+ _readContentAndComputeHash(); |
+ } |
+ return _contentHash; |
+ } |
+ |
+ String get path => source.fullName; |
+ |
+ /** |
+ * Return the [CompilationUnit] of the file. |
+ * |
+ * Current this unit is resolved, it is used to compute unlinked summaries |
Paul Berry
2016/10/24 11:46:21
I had trouble understanding this paragraph. Do yo
scheglov
2016/10/24 17:23:48
I changed the documentation comments for this gett
|
+ * and and URIs. We use a separate analysis context to perform resolution |
+ * and computing errors. But this might change in the future. |
+ */ |
+ CompilationUnit get unit { |
+ AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; |
+ |
+ CharSequenceReader reader = new CharSequenceReader(content); |
+ Scanner scanner = new Scanner(source, reader, errorListener); |
+ scanner.scanGenericMethodComments = driver._analysisOptions.strongMode; |
+ Token token = scanner.tokenize(); |
+ LineInfo lineInfo = new LineInfo(scanner.lineStarts); |
+ |
+ Parser parser = new Parser(source, errorListener); |
+ parser.parseGenericMethodComments = driver._analysisOptions.strongMode; |
+ _unit = parser.parseCompilationUnit(token); |
+ _unit.lineInfo = lineInfo; |
+ |
+ return _unit; |
+ } |
+ |
+ Uri get uri => source.uri; |
+ |
+ /** |
+ * Return the [_File] for the [uri] referenced in this file. |
+ */ |
+ _File resolveUri(String uri) { |
+ Source uriSource = driver._sourceFactory.resolveUri(source, uri); |
+ return new _File(driver, uriSource); |
+ } |
+ |
+ @override |
+ String toString() => uri.toString(); |
+ |
+ /** |
+ * Fill the [_content] and [_contentHash] fields. |
+ * |
+ * If the [_content] field if it is still `null`, get the content from the |
Paul Berry
2016/10/24 11:46:21
Drop the words "if it".
scheglov
2016/10/24 17:23:48
Done.
|
+ * content cache or from the [source]. If the content cannot be accessed |
+ * because of an exception, it considers to be an empty string. |
+ * |
+ * When a new content is read, the new [_contentHash] should be computed and |
+ * the current file state should be updated. |
+ */ |
+ void _readContentAndComputeHash() { |
+ try { |
+ _content = driver._contentCache.getContents(source); |
+ _content ??= source.contents.data; |
+ } catch (_) { |
+ _content = ''; |
Paul Berry
2016/10/24 11:46:21
Do we need to record the fact that we couldn't rea
scheglov
2016/10/24 17:23:48
I don't know yet.
There is a bug with not existing
|
+ } |
+ // Compute the content hash. |
+ List<int> textBytes = UTF8.encode(_content); |
+ List<int> hashBytes = md5.convert(textBytes).bytes; |
+ _contentHash = hex.encode(hashBytes); |
+ // Update the current file state. |
+ driver._fileContentHashMap[path] = _contentHash; |
+ } |
+} |
+ |
+class _LibraryNode { |
+ final AnalysisDriver driver; |
+ final Map<String, _LibraryNode> nodes; |
+ final Uri uri; |
+ final List<PackageBundle> unlinkedBundles = <PackageBundle>[]; |
+ |
+ Set<_LibraryNode> transitiveDependencies; |
+ List<_LibraryNode> _dependencies; |
+ String _linkedHash; |
+ |
+ List<int> linkedNewBytes; |
+ PackageBundle linked; |
+ |
+ _LibraryNode(this.driver, this.nodes, this.uri); |
+ |
+ /** |
+ * Retrieve the dependencies of this node. |
+ */ |
+ List<_LibraryNode> get dependencies { |
+ if (_dependencies == null) { |
+ Set<_LibraryNode> dependencies = new Set<_LibraryNode>(); |
+ |
+ void appendDependency(String uriStr) { |
+ Uri uri = FastUri.parse(uriStr); |
+ if (uri.scheme == 'dart') { |
+ // Dependency on the SDK is implicit and always added. |
+ // The SDK linked bundle is precomputed before linking packages. |
+ } else { |
+ if (!uri.isAbsolute) { |
+ uri = resolveRelativeUri(this.uri, uri); |
+ uriStr = uri.toString(); |
+ } |
+ _LibraryNode node = nodes[uriStr]; |
+ if (node == null) { |
+ throw new StateError('No node for: $uriStr'); |
+ } |
+ dependencies.add(node); |
+ } |
+ } |
+ |
+ for (PackageBundle unlinkedBundle in unlinkedBundles) { |
+ for (UnlinkedUnit unit in unlinkedBundle.unlinkedUnits) { |
+ for (UnlinkedImport import in unit.imports) { |
+ if (!import.isImplicit) { |
+ appendDependency(import.uri); |
+ } |
+ } |
+ for (UnlinkedExportPublic export in unit.publicNamespace.exports) { |
+ appendDependency(export.uri); |
+ } |
+ } |
+ } |
+ |
+ _dependencies = dependencies.toList(); |
+ } |
+ return _dependencies; |
+ } |
+ |
+ @override |
+ int get hashCode => uri.hashCode; |
+ |
+ bool get isReady => linked != null; |
Paul Berry
2016/10/24 11:46:21
It's not obvious to me why "ready" means "linked".
scheglov
2016/10/24 17:23:48
I removed this and couple other fields later.
|
+ |
+ String get linkedHash { |
Paul Berry
2016/10/24 11:46:21
The name "linkedHash" sounds like it means a hash
scheglov
2016/10/24 17:23:48
Done.
|
+ if (_linkedHash == null) { |
+ if (transitiveDependencies == null) { |
+ computeTransitiveDependencies(); |
+ } |
+ |
+ // Add all unlinked API signatures. |
+ List<String> signatures = <String>[]; |
+ signatures.add(driver._sdkBundle.apiSignature); |
+ transitiveDependencies |
+ .map((node) => node.unlinkedBundles) |
+ .expand((bundles) => bundles) |
+ .map((bundle) => bundle.apiSignature) |
+ .forEach(signatures.add); |
+ signatures.sort(); |
+ |
+ // Combine into a single hash. |
+ ApiSignature signature = new ApiSignature(); |
+ signature.addString(uri.toString()); |
+ signatures.forEach(signature.addString); |
+ _linkedHash = signature.toHex(); |
+ } |
+ return _linkedHash; |
+ } |
+ |
+ bool operator ==(other) { |
+ return other is _LibraryNode && other.uri == uri; |
+ } |
+ |
+ void computeTransitiveDependencies() { |
+ if (transitiveDependencies == null) { |
+ transitiveDependencies = new Set<_LibraryNode>(); |
+ |
+ void appendDependencies(_LibraryNode node) { |
+ if (transitiveDependencies.add(node)) { |
+ node.dependencies.forEach(appendDependencies); |
+ } |
+ } |
+ |
+ appendDependencies(this); |
+ } |
+ } |
+ |
+ @override |
+ String toString() => uri.toString(); |
+} |