Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: pkg/analyzer/lib/src/dart/analysis/driver.dart

Issue 2439343002: Initial implementation of AnalysisDriver. (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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';
7 import 'dart:convert';
6 8
7 import 'package:analyzer/dart/ast/ast.dart'; 9 import 'package:analyzer/dart/ast/ast.dart';
10 import 'package:analyzer/dart/ast/token.dart';
8 import 'package:analyzer/error/error.dart'; 11 import 'package:analyzer/error/error.dart';
12 import 'package:analyzer/error/listener.dart';
13 import 'package:analyzer/file_system/file_system.dart';
14 import 'package:analyzer/src/context/context.dart';
15 import 'package:analyzer/src/context/source.dart';
9 import 'package:analyzer/src/dart/analysis/byte_store.dart'; 16 import 'package:analyzer/src/dart/analysis/byte_store.dart';
17 import 'package:analyzer/src/dart/error/todo_codes.dart';
18 import 'package:analyzer/src/dart/scanner/reader.dart';
19 import 'package:analyzer/src/dart/scanner/scanner.dart';
20 import 'package:analyzer/src/generated/engine.dart'
21 show AnalysisContext, AnalysisEngine, AnalysisOptions, ChangeSet;
22 import 'package:analyzer/src/generated/parser.dart';
10 import 'package:analyzer/src/generated/source.dart'; 23 import 'package:analyzer/src/generated/source.dart';
24 import 'package:analyzer/src/generated/utilities_dart.dart';
25 import 'package:analyzer/src/summary/api_signature.dart';
26 import 'package:analyzer/src/summary/flat_buffers.dart' as fb;
27 import 'package:analyzer/src/summary/format.dart';
28 import 'package:analyzer/src/summary/idl.dart';
29 import 'package:analyzer/src/summary/link.dart';
30 import 'package:analyzer/src/summary/package_bundle_reader.dart';
31 import 'package:analyzer/src/summary/summarize_ast.dart';
32 import 'package:analyzer/src/summary/summarize_elements.dart';
33 import 'package:analyzer/src/util/fast_uri.dart';
34 import 'package:convert/convert.dart';
35 import 'package:crypto/crypto.dart';
11 36
12 /** 37 /**
13 * This class computes [AnalysisResult]s for Dart files. 38 * This class computes [AnalysisResult]s for Dart files.
14 * 39 *
15 * Let the set of "explicitly analyzed files" denote the set of paths that have 40 * Let the set of "explicitly analyzed files" denote the set of paths that have
16 * been passed to [addFile] but not subsequently passed to [removeFile]. Let 41 * been passed to [addFile] but not subsequently passed to [removeFile]. Let
17 * the "current analysis results" denote the map from the set of explicitly 42 * the "current analysis results" denote the map from the set of explicitly
18 * analyzed files to the most recent [AnalysisResult] delivered to [results] 43 * analyzed files to the most recent [AnalysisResult] delivered to [results]
19 * for each file. Let the "current file state" represent a map from file path 44 * for each file. Let the "current file state" represent a map from file path
20 * to the file contents most recently read from that file, or fetched from the 45 * to the file contents most recently read from that file, or fetched from the
(...skipping 19 matching lines...) Expand all
40 * "idle" after a finite amount of processing. 65 * "idle" after a finite amount of processing.
41 * 66 *
42 * As a result of these guarantees, a client may ensure that the analysis 67 * As a result of these guarantees, a client may ensure that the analysis
43 * results are "eventually consistent" with the file system by simply calling 68 * results are "eventually consistent" with the file system by simply calling
44 * [changeFile] any time the contents of a file on the file system have changed. 69 * [changeFile] any time the contents of a file on the file system have changed.
45 * 70 *
46 * 71 *
47 * TODO(scheglov) Clean up the list of implicitly analyzed files. 72 * TODO(scheglov) Clean up the list of implicitly analyzed files.
48 */ 73 */
49 class AnalysisDriver { 74 class AnalysisDriver {
75 final PerformanceLog _logger;
76
77 /**
78 * The resource provider for working with files.
79 */
80 final ResourceProvider _resourceProvider;
81
50 /** 82 /**
51 * The byte storage to get and put serialized data. 83 * The byte storage to get and put serialized data.
52 * 84 *
53 * It can be shared with other [AnalysisDriver]s. 85 * It can be shared with other [AnalysisDriver]s.
54 */ 86 */
55 final ByteStore _byteStore; 87 final ByteStore _byteStore;
56 88
57 /** 89 /**
90 * This [ContentCache] is consulted for a file content before reading
91 * the content from the file.
92 */
93 final ContentCache _contentCache;
94
95 /**
58 * The [SourceFactory] is used to resolve URIs to paths and restore URIs 96 * The [SourceFactory] is used to resolve URIs to paths and restore URIs
59 * from file paths. 97 * from file paths.
60 */ 98 */
61 final SourceFactory _sourceFactory; 99 final SourceFactory _sourceFactory;
62 100
63 /** 101 /**
64 * This [ContentCache] is consulted for a file content before reading 102 * The analysis options to analyze with.
65 * the content from the file.
66 */ 103 */
67 final ContentCache _contentCache; 104 final AnalysisOptions _analysisOptions;
68 105
69 AnalysisDriver(this._byteStore, this._sourceFactory, this._contentCache); 106 /**
107 * The combined unlinked and linked package for the SDK, extracted from
108 * the given [_sourceFactory].
109 */
110 PackageBundle _sdkBundle;
111
112 /**
113 * The mapping from the files for which analysis was requested using
114 * [getResult] to the [Completer]s to report the result.
115 */
116 final _requestedFiles = <String, Completer<AnalysisResult>>{};
117
118 /**
119 * The set of explicitly analyzed files.
120 */
121 final _explicitFiles = new LinkedHashSet<String>();
122
123 /**
124 * The set of files that are currently scheduled for analysis.
125 */
126 final _filesToAnalyze = new LinkedHashSet<String>();
127
128 /**
129 * The current file state.
130 *
131 * It maps file paths to MD5 hash of the file content.
132 */
133 final _fileContentHashMap = <String, String>{};
134
135 /**
136 * TODO(scheglov) document and improve
137 */
138 final _hasWorkStreamController = new StreamController<String>();
139
140 AnalysisDriver(this._logger, this._resourceProvider, this._byteStore,
141 this._contentCache, this._sourceFactory, this._analysisOptions) {
142 _sdkBundle = _sourceFactory.dartSdk.getLinkedBundle();
143 }
70 144
71 /** 145 /**
72 * Set the list of files that the driver should try to analyze sooner. 146 * Set the list of files that the driver should try to analyze sooner.
73 * 147 *
74 * Every path in the list must be absolute and normalized. 148 * Every path in the list must be absolute and normalized.
75 * 149 *
76 * The driver will produce the results through the [results] stream. The 150 * The driver will produce the results through the [results] stream. The
77 * exact order in which results are produced is not defined, neither 151 * exact order in which results are produced is not defined, neither
78 * between priority files, nor between priority and non-priority files. 152 * between priority files, nor between priority and non-priority files.
79 */ 153 */
(...skipping 14 matching lines...) Expand all
94 * Invocation of [addFile] or [changeFile] might result in producing more 168 * Invocation of [addFile] or [changeFile] might result in producing more
95 * analysis results that reflect the new current file state. 169 * analysis results that reflect the new current file state.
96 * 170 *
97 * More than one result might be produced for the same file, even if the 171 * More than one result might be produced for the same file, even if the
98 * client does not change the state of the files. 172 * client does not change the state of the files.
99 * 173 *
100 * Results might be produced even for files that have never been added 174 * Results might be produced even for files that have never been added
101 * using [addFile], for example when [getResult] was called for a file. 175 * using [addFile], for example when [getResult] was called for a file.
102 */ 176 */
103 Stream<AnalysisResult> get results async* { 177 Stream<AnalysisResult> get results async* {
104 // TODO(scheglov) implement 178 try {
179 while (true) {
180 // TODO(scheglov) implement state transitioning
181 await for (String why in _hasWorkStreamController.stream) {
182 // Analyze the first file in the general queue.
183 if (_filesToAnalyze.isNotEmpty) {
184 _logger.runTimed('Analyzed ${_filesToAnalyze.length} files', () {
185 while (_filesToAnalyze.isNotEmpty) {
186 String path = _filesToAnalyze.first;
187 _filesToAnalyze.remove(path);
188 _File file = _fileForPath(path);
189 _computeAndPrintErrors(file);
190 // TODO(scheglov) yield the result
191 }
192 });
193 }
194 }
195 // TODO(scheglov) implement
196 }
197 } finally {
198 print('The stream was cancelled.');
199 }
105 } 200 }
106 201
107 /** 202 /**
108 * Add the file with the given [path] to the set of files to analyze. 203 * Add the file with the given [path] to the set of files to analyze.
109 * 204 *
110 * The [path] must be absolute and normalized. 205 * The [path] must be absolute and normalized.
111 * 206 *
112 * The results of analysis are eventually produced by the [results] stream. 207 * The results of analysis are eventually produced by the [results] stream.
113 */ 208 */
114 void addFile(String path) { 209 void addFile(String path) {
115 // TODO(scheglov) implement 210 _explicitFiles.add(path);
211 _filesToAnalyze.add(path);
212 _hasWorkStreamController.add('do it!');
116 } 213 }
117 214
118 /** 215 /**
119 * The file with the given [path] might have changed - updated, added or 216 * The file with the given [path] might have changed - updated, added or
120 * removed. Or not, we don't know. Or it might have, but then changed back. 217 * removed. Or not, we don't know. Or it might have, but then changed back.
121 * 218 *
122 * The [path] must be absolute and normalized. 219 * The [path] must be absolute and normalized.
123 * 220 *
124 * The [path] can be any file - explicitly or implicitly analyzed, or neither. 221 * The [path] can be any file - explicitly or implicitly analyzed, or neither.
125 * 222 *
126 * Causes the analysis state to transition to "analyzing" (if it is not in 223 * Causes the analysis state to transition to "analyzing" (if it is not in
127 * that state already). Schedules the file contents for [path] to be read 224 * that state already). Schedules the file contents for [path] to be read
128 * into the current file state prior to the next time the analysis state 225 * into the current file state prior to the next time the analysis state
129 * transitions to "idle". 226 * transitions to "idle".
130 * 227 *
131 * Invocation of this method will not prevent a [Future] returned from 228 * Invocation of this method will not prevent a [Future] returned from
132 * [getResult] from completing with a result, but the result is not 229 * [getResult] from completing with a result, but the result is not
133 * guaranteed to be consistent with the new current file state after this 230 * guaranteed to be consistent with the new current file state after this
134 * [changeFile] invocation. 231 * [changeFile] invocation.
135 */ 232 */
136 void changeFile(String path) { 233 void changeFile(String path) {
137 // TODO(scheglov) implement 234 // TODO(scheglov) Don't clear, schedule API signature validation.
235 _fileContentHashMap.clear();
236 _filesToAnalyze.add(path);
237 _filesToAnalyze.addAll(_explicitFiles);
238 // TODO(scheglov) name?!
239 _hasWorkStreamController.add('do it!');
138 } 240 }
139 241
140 /** 242 /**
141 * Return the [Future] that completes with a [AnalysisResult] for the file 243 * Return the [Future] that completes with a [AnalysisResult] for the file
142 * with the given [path]. 244 * with the given [path].
143 * 245 *
144 * The [path] must be absolute and normalized. 246 * The [path] must be absolute and normalized.
145 * 247 *
146 * The [path] can be any file - explicitly or implicitly analyzed, or neither. 248 * The [path] can be any file - explicitly or implicitly analyzed, or neither.
147 * 249 *
148 * Causes the analysis state to transition to "analyzing" (if it is not in 250 * Causes the analysis state to transition to "analyzing" (if it is not in
149 * that state already), the driver will read the file and produce the analysis 251 * that state already), the driver will read the file and produce the analysis
150 * result for it, which is consistent with the current file state (including 252 * result for it, which is consistent with the current file state (including
151 * the new state of the file), prior to the next time the analysis state 253 * the new state of the file), prior to the next time the analysis state
152 * transitions to "idle". 254 * transitions to "idle".
153 */ 255 */
154 Future<AnalysisResult> getResult(String path) { 256 Future<AnalysisResult> getResult(String path) {
155 // TODO(scheglov) implement 257 var completer = new Completer<AnalysisResult>();
156 throw new UnimplementedError(); 258 _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
259 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
157 } 260 }
158 261
159 /** 262 /**
160 * Remove the file with the given [path] from the list of files to analyze. 263 * Remove the file with the given [path] from the list of files to analyze.
161 * 264 *
162 * The [path] must be absolute and normalized. 265 * The [path] must be absolute and normalized.
163 * 266 *
164 * The results of analysis of the file might still be produced by the 267 * The results of analysis of the file might still be produced by the
165 * [results] stream. The driver will try to stop producing these results, 268 * [results] stream. The driver will try to stop producing these results,
166 * but does not guarantee this. 269 * but does not guarantee this.
167 */ 270 */
168 void removeFile(String path) { 271 void removeFile(String path) {
169 // TODO(scheglov) implement 272 _explicitFiles.remove(path);
273 _filesToAnalyze.remove(path);
274 }
275
276 /**
277 * TODO(scheglov) replace with actual [AnalysisResult] computing.
278 */
279 List<String> _computeAndPrintErrors(_File file) {
280 List<String> errorStrings = _logger.run('Compute errors $file', () {
281 LibraryContext libraryContext = _createLibraryContext(file);
282
283 String errorsKey;
284 {
285 ApiSignature signature = new ApiSignature();
286 signature.addString(libraryContext.node.linkedHash);
287 signature.addString(file.contentHash);
288 errorsKey = '${signature.toHex()}.errors';
289 }
290
291 {
292 List<int> bytes = _byteStore.get(errorsKey);
293 if (bytes != null) {
294 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes);
295 int table = bp.derefObject(0);
296 return const fb.ListReader<String>(const fb.StringReader())
297 .vTableGet(bp, table, 0);
298 }
299 }
300
301 AnalysisContext analysisContext = _createAnalysisContext(libraryContext);
302 analysisContext.resolveCompilationUnit2(
303 libraryContext.file.source, libraryContext.file.source);
304 try {
305 List<AnalysisError> errors;
306 try {
307 errors = _logger.runTimed('Computed errors', () {
308 return analysisContext.computeErrors(file.source);
309 });
310 } catch (e, st) {
311 // TODO(scheglov) why does it fail?
312 // 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
313 errors = [];
314 }
315 List<String> errorStrings = errors
316 .where((error) => error.errorCode is! TodoCode)
317 .map((error) => error.toString())
318 .toList();
319 {
320 fb.Builder fbBuilder = new fb.Builder();
321 var exportedOffset = fbBuilder.writeList(errorStrings
322 .map((errorStr) => fbBuilder.writeString(errorStr))
323 .toList());
324 fbBuilder.startTable();
325 fbBuilder.addOffset(0, exportedOffset);
326 var offset = fbBuilder.endTable();
327 List<int> bytes = fbBuilder.finish(offset, 'CErr');
328 _byteStore.put(errorsKey, bytes);
329 }
330
331 return errorStrings;
332 } finally {
333 analysisContext.dispose();
334 }
335 });
336
337 if (errorStrings.isNotEmpty) {
338 errorStrings.forEach((errorString) => print('\t$errorString'));
339 } else {
340 print('\tNO ERRORS');
341 }
342 return errorStrings;
343 }
344
345 AnalysisContext _createAnalysisContext(LibraryContext libraryContext) {
346 AnalysisContextImpl analysisContext =
347 AnalysisEngine.instance.createAnalysisContext();
348
349 analysisContext.sourceFactory =
350 new SourceFactory((_sourceFactory as SourceFactoryImpl).resolvers);
351 analysisContext.resultProvider =
352 new InputPackagesResultProvider(analysisContext, libraryContext.store);
353 analysisContext
354 .applyChanges(new ChangeSet()..addedSource(libraryContext.file.source));
355 return analysisContext;
356 }
357
358 /**
359 * 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.
360 * [libraryFile] should be analyzed it.
361 *
362 * TODO(scheglov) We often don't need [SummaryDataStore], only linked hash.
363 */
364 LibraryContext _createLibraryContext(_File libraryFile) {
365 Map<String, _LibraryNode> nodes = <String, _LibraryNode>{};
366
367 return _logger.run('Create library context', () {
368 SummaryDataStore store = new SummaryDataStore(const <String>[]);
369 store.addBundle(null, _sdkBundle);
370
371 void createLibraryNodes(_File libraryFile) {
372 Uri libraryUri = libraryFile.uri;
373 if (libraryUri.scheme == 'dart') {
374 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.
375 }
376 String uriStr = libraryUri.toString();
377 if (!nodes.containsKey(uriStr)) {
378 _LibraryNode node = new _LibraryNode(this, nodes, libraryUri);
379 nodes[uriStr] = node;
380 ReferencedUris referenced = _getReferencedUris(libraryFile);
381
382 // Append unlinked bundles.
383 for (String uri in referenced.parted) {
384 _File file = libraryFile.resolveUri(uri);
385 PackageBundle unlinked = _getUnlinked(file);
386 node.unlinkedBundles.add(unlinked);
387 store.addBundle(null, unlinked);
388 }
389
390 // Create nodes for referenced libraries.
391 for (String uri in referenced.imported) {
392 _File file = libraryFile.resolveUri(uri);
393 createLibraryNodes(file);
394 }
395 for (String uri in referenced.exported) {
396 _File file = libraryFile.resolveUri(uri);
397 createLibraryNodes(file);
398 }
399 }
400 }
401
402 _logger.runTimed2(() {
403 createLibraryNodes(libraryFile);
404 }, () => 'Computed ${nodes.length} nodes');
405 _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.
406
407 Set<String> libraryUrisToLink = new Set<String>();
408 int numberOfNodesWithLinked = 0;
409 _logger.runTimed2(() {
410 for (_LibraryNode node in nodes.values) {
411 String key = '${node.linkedHash}.linked';
412 List<int> bytes = _byteStore.get(key);
413 if (bytes != null) {
414 PackageBundle linked = new PackageBundle.fromBuffer(bytes);
415 node.linked = linked;
416 store.addBundle(null, linked);
417 numberOfNodesWithLinked++;
418 } else {
419 libraryUrisToLink.add(node.uri.toString());
420 }
421 }
422 }, () => 'Loaded $numberOfNodesWithLinked linked bundles');
423
424 Map<String, LinkedLibraryBuilder> linkedLibraries = {};
425 _logger.runTimed2(() {
426 linkedLibraries = link(libraryUrisToLink, (String uri) {
427 LinkedLibrary linkedLibrary = store.linkedMap[uri];
428 if (linkedLibrary == null) {
429 throw new StateError('No linked library for: $uri');
430 }
431 return linkedLibrary;
432 }, (String uri) {
433 UnlinkedUnit unlinkedUnit = store.unlinkedMap[uri];
434 if (unlinkedUnit == null) {
435 throw new StateError('No unlinked unit for: $uri');
436 }
437 return unlinkedUnit;
438 }, (_) => null, _analysisOptions.strongMode);
439 }, () => 'Linked ${linkedLibraries.length} bundles');
440
441 linkedLibraries.forEach((uri, linkedBuilder) {
442 _LibraryNode node = nodes[uri];
443 String key = '${node.linkedHash}.linked';
444 List<int> bytes;
445 {
446 PackageBundleAssembler assembler = new PackageBundleAssembler();
447 assembler.addLinkedLibrary(uri, linkedBuilder);
448 bytes = assembler.assemble().toBuffer();
449 }
450 PackageBundle linked = new PackageBundle.fromBuffer(bytes);
451 node.linked = linked;
452 store.addBundle(null, linked);
453 _byteStore.put(key, bytes);
454 });
455
456 return new LibraryContext(libraryFile, libraryNode, store);
457 });
458 }
459
460 /**
461 * Return the [_File] for the given [path] in [_sourceFactory].
462 */
463 _File _fileForPath(String path) {
464 Source fileSource = _resourceProvider.getFile(path).createSource();
465 Uri uri = _sourceFactory.restoreUri(fileSource);
466 Source source = _resourceProvider.getFile(path).createSource(uri);
467 return new _File(this, source);
468 }
469
470 /**
471 * TODO(scheglov) It would be nice to get URIs of "parts" from unlinked.
472 */
473 ReferencedUris _getReferencedUris(_File file) {
474 // Try to get from the store.
475 {
476 String key = '${file.contentHash}.uris';
477 List<int> bytes = _byteStore.get(key);
478 if (bytes != null) {
479 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes);
480 int table = bp.derefObject(0);
481 const fb.ListReader<String> stringListReader =
482 const fb.ListReader<String>(const fb.StringReader());
483 bool isLibrary = const fb.BoolReader().vTableGet(bp, table, 0);
484 List<String> imported = stringListReader.vTableGet(bp, table, 1);
485 List<String> exported = stringListReader.vTableGet(bp, table, 2);
486 List<String> parted = stringListReader.vTableGet(bp, table, 3);
487 ReferencedUris referencedUris = new ReferencedUris();
488 referencedUris.isLibrary = isLibrary;
489 referencedUris.imported.addAll(imported);
490 referencedUris.exported.addAll(exported);
491 referencedUris.parted.addAll(parted);
492 return referencedUris;
493 }
494 }
495
496 // Compute URIs.
497 ReferencedUris referencedUris = new ReferencedUris();
498 referencedUris.parted.add(file.uri.toString());
499 for (Directive directive in file.unit.directives) {
500 if (directive is PartOfDirective) {
501 referencedUris.isLibrary = false;
502 } else if (directive is UriBasedDirective) {
503 String uri = directive.uri.stringValue;
504 if (directive is ImportDirective) {
505 referencedUris.imported.add(uri);
506 } else if (directive is ExportDirective) {
507 referencedUris.exported.add(uri);
508 } else if (directive is PartDirective) {
509 referencedUris.parted.add(uri);
510 }
511 }
512 }
513
514 // Serialize into bytes.
515 List<int> bytes;
516 {
517 fb.Builder fbBuilder = new fb.Builder();
518 var importedOffset = fbBuilder.writeList(referencedUris.imported
519 .map((uri) => fbBuilder.writeString(uri))
520 .toList());
521 var exportedOffset = fbBuilder.writeList(referencedUris.exported
522 .map((uri) => fbBuilder.writeString(uri))
523 .toList());
524 var partedOffset = fbBuilder.writeList(referencedUris.parted
525 .map((uri) => fbBuilder.writeString(uri))
526 .toList());
527 fbBuilder.startTable();
528 fbBuilder.addBool(0, referencedUris.isLibrary);
529 fbBuilder.addOffset(1, importedOffset);
530 fbBuilder.addOffset(2, exportedOffset);
531 fbBuilder.addOffset(3, partedOffset);
532 var offset = fbBuilder.endTable();
533 bytes = fbBuilder.finish(offset, 'SoRU');
534 }
535
536 // We read the content and recomputed the hash.
537 // So, we need to update the key.
538 String key = '${file.contentHash}.uris';
539 _byteStore.put(key, bytes);
540
541 return referencedUris;
542 }
543
544 /**
545 * Return the unlinked bundle of [file] for the current file state.
546 *
547 * That is, if there is an existing bundle for the current content hash
548 * of the [file] in the [_byteStore], then it is returned. Otherwise, the
549 * [file] content is read, the content hash is computed and the current file
550 * state is updated accordingly. That the content is parsed into the
551 * [CompilationUnit] and serialized into a new unlinked bundle. The bundle
552 * is then put into the [_byteStore] and returned.
553 */
554 PackageBundle _getUnlinked(_File file) {
555 // Try to get bytes for file's unlinked bundle.
556 List<int> bytes;
557 {
558 String key = '${file.contentHash}.unlinked';
559 bytes = _byteStore.get(key);
560 }
561 // If no cached unlinked bundle, compute it.
562 if (bytes == null) {
563 _logger.runTimed('Create unlinked for $file', () {
564 // We read the content and recomputed the hash.
565 // So, we need to update the key.
566 String key = '${file.contentHash}.unlinked';
567 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(file.unit);
568 PackageBundleAssembler assembler = new PackageBundleAssembler();
569 assembler.addUnlinkedUnitWithHash(
570 file.uri.toString(), unlinkedUnit, key);
571 bytes = assembler.assemble().toBuffer();
572 _byteStore.put(key, bytes);
573 });
574 }
575 return new PackageBundle.fromBuffer(bytes);
170 } 576 }
171 } 577 }
172 578
173 /** 579 /**
174 * The result of analyzing of a single file. 580 * The result of analyzing of a single file.
175 * 581 *
176 * These results are self-consistent, i.e. [content], [contentHash], the 582 * These results are self-consistent, i.e. [content], [contentHash], the
177 * resolved [unit] correspond to each other. All referenced elements, even 583 * resolved [unit] correspond to each other. All referenced elements, even
178 * external ones, are also self-consistent. But none of the results is 584 * external ones, are also self-consistent. But none of the results is
179 * guaranteed to be consistent with the state of the files. 585 * guaranteed to be consistent with the state of the files.
(...skipping 30 matching lines...) Expand all
210 final CompilationUnit unit; 616 final CompilationUnit unit;
211 617
212 /** 618 /**
213 * The full list of computed analysis errors, both syntactic and semantic. 619 * The full list of computed analysis errors, both syntactic and semantic.
214 */ 620 */
215 final List<AnalysisError> errors; 621 final List<AnalysisError> errors;
216 622
217 AnalysisResult(this.path, this.uri, this.content, this.contentHash, this.unit, 623 AnalysisResult(this.path, this.uri, this.content, this.contentHash, this.unit,
218 this.errors); 624 this.errors);
219 } 625 }
626
627 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.
628 final _File file;
629 final _LibraryNode node;
630 final SummaryDataStore store;
631 LibraryContext(this.file, this.node, this.store);
632 }
633
634 class PerformanceLog {
635 final StringSink sink;
636 int _level = 0;
637
638 PerformanceLog(this.sink);
639
640 /*=T*/ run/*<T>*/(String msg, /*=T*/ f()) {
641 Stopwatch timer = new Stopwatch()..start();
642 try {
643 writeln('+++ $msg.');
644 _level++;
645 return f();
646 } finally {
647 _level--;
648 int ms = timer.elapsedMilliseconds;
649 writeln('--- $msg in $ms ms.');
650 }
651 }
652
653 /*=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.
654 _level++;
655 Stopwatch timer = new Stopwatch()..start();
656 try {
657 return f();
658 } finally {
659 _level--;
660 int ms = timer.elapsedMilliseconds;
661 writeln('$msg in $ms ms.');
662 }
663 }
664
665 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
666 _level++;
667 Stopwatch timer = new Stopwatch()..start();
668 try {
669 return f();
670 } finally {
671 _level--;
672 int ms = timer.elapsedMilliseconds;
673 String msg = getMsg();
674 writeln('$msg in $ms ms.');
675 }
676 }
677
678 void writeln(String msg) {
679 String indent = '\t' * _level;
680 sink.writeln('$indent$msg');
681 }
682 }
683
684 class ReferencedUris {
685 bool isLibrary = true;
686 final List<String> imported = <String>[];
687 final List<String> exported = <String>[];
688 final List<String> parted = <String>[];
689 }
690
691 /**
692 * Information about a file being analyzed, explicitly or implicitly.
693 *
694 * It keeps a consistent view on its [content], [contentHash] and [unit].
695 */
696 class _File {
697 /**
698 * The driver instance that is used to access [SourceFactory] and caches.
699 */
700 final AnalysisDriver driver;
701
702 /**
703 * The [Source] this [_File] instance represent.
Brian Wilkerson 2016/10/26 06:31:48 nit: "represent" --> "represents"
704 */
705 final Source source;
706
707 String _content;
708 String _contentHash;
709 CompilationUnit _unit;
710
711 _File(this.driver, this.source);
712
713 /**
714 * Return the current content of the file.
715 *
716 * If the [_content] field if it is still `null`, get the content from the
717 * content cache or from the [source]. If the content cannot be accessed
718 * because of an exception, it considers to be an empty string.
719 *
720 * When a new content is read, the new [_contentHash] is computed and the
721 * current file state is updated.
722 */
723 String get content {
724 if (_content == null) {
725 _readContentAndComputeHash();
726 }
727 return _content;
728 }
729
730 /**
731 * Ensure that the [contentHash] is filled.
732 *
733 * If the hash is already in the current file state, return the current
734 * value. Otherwise, read the [content], compute the hash, put it into
735 * the current file state, and update the [contentHash] field.
736 *
737 * 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.
738 * might change when [content] is read and the hash is recomputed.
739 */
740 String get contentHash {
741 _contentHash ??= driver._fileContentHashMap[path];
742 if (_contentHash == null) {
743 _readContentAndComputeHash();
744 }
745 return _contentHash;
746 }
747
748 String get path => source.fullName;
749
750 /**
751 * Return the [CompilationUnit] of the file.
752 *
753 * 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
754 * and and URIs. We use a separate analysis context to perform resolution
755 * and computing errors. But this might change in the future.
756 */
757 CompilationUnit get unit {
758 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER;
759
760 CharSequenceReader reader = new CharSequenceReader(content);
761 Scanner scanner = new Scanner(source, reader, errorListener);
762 scanner.scanGenericMethodComments = driver._analysisOptions.strongMode;
763 Token token = scanner.tokenize();
764 LineInfo lineInfo = new LineInfo(scanner.lineStarts);
765
766 Parser parser = new Parser(source, errorListener);
767 parser.parseGenericMethodComments = driver._analysisOptions.strongMode;
768 _unit = parser.parseCompilationUnit(token);
769 _unit.lineInfo = lineInfo;
770
771 return _unit;
772 }
773
774 Uri get uri => source.uri;
775
776 /**
777 * Return the [_File] for the [uri] referenced in this file.
778 */
779 _File resolveUri(String uri) {
780 Source uriSource = driver._sourceFactory.resolveUri(source, uri);
781 return new _File(driver, uriSource);
782 }
783
784 @override
785 String toString() => uri.toString();
786
787 /**
788 * Fill the [_content] and [_contentHash] fields.
789 *
790 * 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.
791 * content cache or from the [source]. If the content cannot be accessed
792 * because of an exception, it considers to be an empty string.
793 *
794 * When a new content is read, the new [_contentHash] should be computed and
795 * the current file state should be updated.
796 */
797 void _readContentAndComputeHash() {
798 try {
799 _content = driver._contentCache.getContents(source);
800 _content ??= source.contents.data;
801 } catch (_) {
802 _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
803 }
804 // Compute the content hash.
805 List<int> textBytes = UTF8.encode(_content);
806 List<int> hashBytes = md5.convert(textBytes).bytes;
807 _contentHash = hex.encode(hashBytes);
808 // Update the current file state.
809 driver._fileContentHashMap[path] = _contentHash;
810 }
811 }
812
813 class _LibraryNode {
814 final AnalysisDriver driver;
815 final Map<String, _LibraryNode> nodes;
816 final Uri uri;
817 final List<PackageBundle> unlinkedBundles = <PackageBundle>[];
818
819 Set<_LibraryNode> transitiveDependencies;
820 List<_LibraryNode> _dependencies;
821 String _linkedHash;
822
823 List<int> linkedNewBytes;
824 PackageBundle linked;
825
826 _LibraryNode(this.driver, this.nodes, this.uri);
827
828 /**
829 * Retrieve the dependencies of this node.
830 */
831 List<_LibraryNode> get dependencies {
832 if (_dependencies == null) {
833 Set<_LibraryNode> dependencies = new Set<_LibraryNode>();
834
835 void appendDependency(String uriStr) {
836 Uri uri = FastUri.parse(uriStr);
837 if (uri.scheme == 'dart') {
838 // Dependency on the SDK is implicit and always added.
839 // The SDK linked bundle is precomputed before linking packages.
840 } else {
841 if (!uri.isAbsolute) {
842 uri = resolveRelativeUri(this.uri, uri);
843 uriStr = uri.toString();
844 }
845 _LibraryNode node = nodes[uriStr];
846 if (node == null) {
847 throw new StateError('No node for: $uriStr');
848 }
849 dependencies.add(node);
850 }
851 }
852
853 for (PackageBundle unlinkedBundle in unlinkedBundles) {
854 for (UnlinkedUnit unit in unlinkedBundle.unlinkedUnits) {
855 for (UnlinkedImport import in unit.imports) {
856 if (!import.isImplicit) {
857 appendDependency(import.uri);
858 }
859 }
860 for (UnlinkedExportPublic export in unit.publicNamespace.exports) {
861 appendDependency(export.uri);
862 }
863 }
864 }
865
866 _dependencies = dependencies.toList();
867 }
868 return _dependencies;
869 }
870
871 @override
872 int get hashCode => uri.hashCode;
873
874 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.
875
876 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.
877 if (_linkedHash == null) {
878 if (transitiveDependencies == null) {
879 computeTransitiveDependencies();
880 }
881
882 // Add all unlinked API signatures.
883 List<String> signatures = <String>[];
884 signatures.add(driver._sdkBundle.apiSignature);
885 transitiveDependencies
886 .map((node) => node.unlinkedBundles)
887 .expand((bundles) => bundles)
888 .map((bundle) => bundle.apiSignature)
889 .forEach(signatures.add);
890 signatures.sort();
891
892 // Combine into a single hash.
893 ApiSignature signature = new ApiSignature();
894 signature.addString(uri.toString());
895 signatures.forEach(signature.addString);
896 _linkedHash = signature.toHex();
897 }
898 return _linkedHash;
899 }
900
901 bool operator ==(other) {
902 return other is _LibraryNode && other.uri == uri;
903 }
904
905 void computeTransitiveDependencies() {
906 if (transitiveDependencies == null) {
907 transitiveDependencies = new Set<_LibraryNode>();
908
909 void appendDependencies(_LibraryNode node) {
910 if (transitiveDependencies.add(node)) {
911 node.dependencies.forEach(appendDependencies);
912 }
913 }
914
915 appendDependencies(this);
916 }
917 }
918
919 @override
920 String toString() => uri.toString();
921 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698