| 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 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 106 /** | 106 /** |
| 107 * The combined unlinked and linked package for the SDK, extracted from | 107 * The combined unlinked and linked package for the SDK, extracted from |
| 108 * the given [_sourceFactory]. | 108 * the given [_sourceFactory]. |
| 109 */ | 109 */ |
| 110 PackageBundle _sdkBundle; | 110 PackageBundle _sdkBundle; |
| 111 | 111 |
| 112 /** | 112 /** |
| 113 * The mapping from the files for which analysis was requested using | 113 * The mapping from the files for which analysis was requested using |
| 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, 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 that are currently scheduled for analysis. | 124 * The set of files that are currently scheduled for analysis. |
| 125 */ | 125 */ |
| 126 final _filesToAnalyze = new LinkedHashSet<String>(); | 126 final _filesToAnalyze = new LinkedHashSet<String>(); |
| 127 | 127 |
| 128 /** | 128 /** |
| 129 * The mapping of [Uri]s to the mapping of textual URIs to the [Source] | 129 * The mapping of [Uri]s to the mapping of textual URIs to the [Source] |
| 130 * that correspond in the current [_sourceFactory]. | 130 * that correspond in the current [_sourceFactory]. |
| 131 */ | 131 */ |
| 132 final _uriResolutionCache = <Uri, Map<String, Source>>{}; | 132 final _uriResolutionCache = <Uri, Map<String, Source>>{}; |
| 133 | 133 |
| 134 /** | 134 /** |
| 135 * The current file state. | 135 * The current file state. |
| 136 * | 136 * |
| 137 * It maps file paths to the MD5 hash of the file content. | 137 * It maps file paths to the MD5 hash of the file content. |
| 138 */ | 138 */ |
| 139 final _fileContentHashMap = <String, String>{}; | 139 final _fileContentHashMap = <String, String>{}; |
| 140 | 140 |
| 141 /** | 141 /** |
| 142 * Mapping from library URIs to the linked hash of the library. | 142 * Mapping from library URIs to the dependency signature of the library. |
| 143 */ | 143 */ |
| 144 final _linkedHashMap = <Uri, String>{}; | 144 final _dependencySignatureMap = <Uri, String>{}; |
| 145 | 145 |
| 146 /** | 146 /** |
| 147 * TODO(scheglov) document and improve | 147 * TODO(scheglov) document and improve |
| 148 */ | 148 */ |
| 149 final _hasWorkStreamController = new StreamController<String>(); | 149 final _hasWorkStreamController = new StreamController<String>(); |
| 150 | 150 |
| 151 AnalysisDriver(this._logger, this._resourceProvider, this._byteStore, | 151 AnalysisDriver(this._logger, this._resourceProvider, this._byteStore, |
| 152 this._contentCache, this._sourceFactory, this._analysisOptions) { | 152 this._contentCache, this._sourceFactory, this._analysisOptions) { |
| 153 _sdkBundle = _sourceFactory.dartSdk.getLinkedBundle(); | 153 _sdkBundle = _sourceFactory.dartSdk.getLinkedBundle(); |
| 154 } | 154 } |
| (...skipping 30 matching lines...) Expand all Loading... |
| 185 * Results might be produced even for files that have never been added | 185 * Results might be produced even for files that have never been added |
| 186 * using [addFile], for example when [getResult] was called for a file. | 186 * using [addFile], for example when [getResult] was called for a file. |
| 187 */ | 187 */ |
| 188 Stream<AnalysisResult> get results async* { | 188 Stream<AnalysisResult> get results async* { |
| 189 try { | 189 try { |
| 190 while (true) { | 190 while (true) { |
| 191 // TODO(scheglov) implement state transitioning | 191 // TODO(scheglov) implement state transitioning |
| 192 await for (String why in _hasWorkStreamController.stream) { | 192 await for (String why in _hasWorkStreamController.stream) { |
| 193 // Analyze the first file in the general queue. | 193 // Analyze the first file in the general queue. |
| 194 if (_filesToAnalyze.isNotEmpty) { | 194 if (_filesToAnalyze.isNotEmpty) { |
| 195 _logger.runTimed('Analyzed ${_filesToAnalyze.length} files', () { | 195 _logger.run('Analyze ${_filesToAnalyze.length} files', () { |
| 196 while (_filesToAnalyze.isNotEmpty) { | 196 while (_filesToAnalyze.isNotEmpty) { |
| 197 String path = _filesToAnalyze.first; | 197 String path = _filesToAnalyze.first; |
| 198 _filesToAnalyze.remove(path); | 198 _filesToAnalyze.remove(path); |
| 199 _File file = _fileForPath(path); | 199 _File file = _fileForPath(path); |
| 200 _computeAndPrintErrors(file); | 200 _computeAndPrintErrors(file); |
| 201 // TODO(scheglov) yield the result | 201 // TODO(scheglov) yield the result |
| 202 } | 202 } |
| 203 }); | 203 }); |
| 204 } | 204 } |
| 205 } | 205 } |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 237 * transitions to "idle". | 237 * transitions to "idle". |
| 238 * | 238 * |
| 239 * Invocation of this method will not prevent a [Future] returned from | 239 * Invocation of this method will not prevent a [Future] returned from |
| 240 * [getResult] from completing with a result, but the result is not | 240 * [getResult] from completing with a result, but the result is not |
| 241 * guaranteed to be consistent with the new current file state after this | 241 * guaranteed to be consistent with the new current file state after this |
| 242 * [changeFile] invocation. | 242 * [changeFile] invocation. |
| 243 */ | 243 */ |
| 244 void changeFile(String path) { | 244 void changeFile(String path) { |
| 245 // TODO(scheglov) Don't clear, schedule API signature validation. | 245 // TODO(scheglov) Don't clear, schedule API signature validation. |
| 246 _fileContentHashMap.clear(); | 246 _fileContentHashMap.clear(); |
| 247 _linkedHashMap.clear(); | 247 _dependencySignatureMap.clear(); |
| 248 _filesToAnalyze.add(path); | 248 _filesToAnalyze.add(path); |
| 249 _filesToAnalyze.addAll(_explicitFiles); | 249 _filesToAnalyze.addAll(_explicitFiles); |
| 250 // TODO(scheglov) name?! | 250 // TODO(scheglov) name?! |
| 251 _hasWorkStreamController.add('do it!'); | 251 _hasWorkStreamController.add('do it!'); |
| 252 } | 252 } |
| 253 | 253 |
| 254 /** | 254 /** |
| 255 * Return the [Future] that completes with a [AnalysisResult] for the file | 255 * Return the [Future] that completes with a [AnalysisResult] for the file |
| 256 * with the given [path]. | 256 * with the given [path]. |
| 257 * | 257 * |
| 258 * The [path] must be absolute and normalized. | 258 * The [path] must be absolute and normalized. |
| 259 * | 259 * |
| 260 * The [path] can be any file - explicitly or implicitly analyzed, or neither. | 260 * The [path] can be any file - explicitly or implicitly analyzed, or neither. |
| 261 * | 261 * |
| 262 * Causes the analysis state to transition to "analyzing" (if it is not in | 262 * Causes the analysis state to transition to "analyzing" (if it is not in |
| 263 * that state already), the driver will read the file and produce the analysis | 263 * that state already), the driver will read the file and produce the analysis |
| 264 * result for it, which is consistent with the current file state (including | 264 * result for it, which is consistent with the current file state (including |
| 265 * the new state of the file), prior to the next time the analysis state | 265 * the new state of the file), prior to the next time the analysis state |
| 266 * transitions to "idle". | 266 * transitions to "idle". |
| 267 */ | 267 */ |
| 268 Future<AnalysisResult> getResult(String path) { | 268 Future<AnalysisResult> getResult(String path) { |
| 269 var completer = new Completer<AnalysisResult>(); | 269 var completer = new Completer<AnalysisResult>(); |
| 270 _requestedFiles[path] = completer; | 270 _requestedFiles |
| 271 .putIfAbsent(path, () => <Completer<AnalysisResult>>[]) |
| 272 .add(completer); |
| 273 _hasWorkStreamController.add(path); |
| 271 return completer.future; | 274 return completer.future; |
| 272 } | 275 } |
| 273 | 276 |
| 274 /** | 277 /** |
| 275 * Remove the file with the given [path] from the list of files to analyze. | 278 * Remove the file with the given [path] from the list of files to analyze. |
| 276 * | 279 * |
| 277 * The [path] must be absolute and normalized. | 280 * The [path] must be absolute and normalized. |
| 278 * | 281 * |
| 279 * The results of analysis of the file might still be produced by the | 282 * The results of analysis of the file might still be produced by the |
| 280 * [results] stream. The driver will try to stop producing these results, | 283 * [results] stream. The driver will try to stop producing these results, |
| (...skipping 21 matching lines...) Expand all Loading... |
| 302 file.path.endsWith('pkg/analyzer/lib/src/generated/error.dart') || | 305 file.path.endsWith('pkg/analyzer/lib/src/generated/error.dart') || |
| 303 file.path.endsWith('pkg/analyzer/lib/src/generated/scanner.dart') || | 306 file.path.endsWith('pkg/analyzer/lib/src/generated/scanner.dart') || |
| 304 file.path.endsWith('pkg/analyzer/lib/src/generated/sdk_io.dart') || | 307 file.path.endsWith('pkg/analyzer/lib/src/generated/sdk_io.dart') || |
| 305 file.path.endsWith('pkg/analyzer/lib/src/generated/visitors.dart') || | 308 file.path.endsWith('pkg/analyzer/lib/src/generated/visitors.dart') || |
| 306 file.path.endsWith('pkg/analyzer/test/generated/constant_test.dart') || | 309 file.path.endsWith('pkg/analyzer/test/generated/constant_test.dart') || |
| 307 file.path.endsWith('pkg/analyzer/test/source/embedder_test.dart')) { | 310 file.path.endsWith('pkg/analyzer/test/source/embedder_test.dart')) { |
| 308 return []; | 311 return []; |
| 309 } | 312 } |
| 310 | 313 |
| 311 List<String> errorStrings = _logger.run('Compute errors $file', () { | 314 List<String> errorStrings = _logger.run('Compute errors $file', () { |
| 312 LibraryContext libraryContext = _createLibraryContext(file); | 315 _LibraryContext libraryContext = _createLibraryContext(file); |
| 313 | 316 |
| 314 String errorsKey; | 317 String errorsKey; |
| 315 { | 318 { |
| 316 ApiSignature signature = new ApiSignature(); | 319 ApiSignature signature = new ApiSignature(); |
| 317 signature.addString(libraryContext.node.linkedHash); | 320 signature.addString(libraryContext.node.dependencySignature); |
| 318 signature.addString(file.contentHash); | 321 signature.addString(file.contentHash); |
| 319 errorsKey = '${signature.toHex()}.errors'; | 322 errorsKey = '${signature.toHex()}.errors'; |
| 320 } | 323 } |
| 321 | 324 |
| 322 { | 325 { |
| 323 List<int> bytes = _byteStore.get(errorsKey); | 326 List<int> bytes = _byteStore.get(errorsKey); |
| 324 if (bytes != null) { | 327 if (bytes != null) { |
| 325 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); | 328 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); |
| 326 int table = bp.derefObject(0); | 329 int table = bp.derefObject(0); |
| 327 return const fb.ListReader<String>(const fb.StringReader()) | 330 return const fb.ListReader<String>(const fb.StringReader()) |
| 328 .vTableGet(bp, table, 0); | 331 .vTableGet(bp, table, 0); |
| 329 } | 332 } |
| 330 } | 333 } |
| 331 | 334 |
| 332 AnalysisContext analysisContext = _createAnalysisContext(libraryContext); | 335 AnalysisContext analysisContext = _createAnalysisContext(libraryContext); |
| 333 analysisContext.setContents(file.source, file.content); | 336 analysisContext.setContents(file.source, file.content); |
| 334 try { | 337 try { |
| 335 // Compute resolved unit. | 338 // Compute resolved unit. |
| 336 // _logger.runTimed('Computed resolved unit', () { | 339 // _logger.runTimed('Computed resolved unit', () { |
| 337 // analysisContext.resolveCompilationUnit2( | 340 // analysisContext.resolveCompilationUnit2( |
| 338 // libraryContext.file.source, libraryContext.file.source); | 341 // libraryContext.file.source, libraryContext.file.source); |
| 339 // }); | 342 // }); |
| 340 // Compute errors. | 343 // Compute errors. |
| 341 List<AnalysisError> errors; | 344 List<AnalysisError> errors; |
| 342 try { | 345 try { |
| 343 errors = _logger.runTimed('Computed errors', () { | 346 errors = _logger.run('Compute errors', () { |
| 344 return analysisContext.computeErrors(file.source); | 347 return analysisContext.computeErrors(file.source); |
| 345 }); | 348 }); |
| 346 } catch (e, st) { | 349 } catch (e, st) { |
| 347 // TODO(scheglov) why does it fail? | 350 // TODO(scheglov) why does it fail? |
| 348 // Caused by Bad state: Unmatched TypeParameterElementImpl T | 351 // Caused by Bad state: Unmatched TypeParameterElementImpl T |
| 349 errors = []; | 352 errors = []; |
| 350 } | 353 } |
| 351 List<String> errorStrings = errors | 354 List<String> errorStrings = errors |
| 352 .where((error) => error.errorCode is! TodoCode) | 355 .where((error) => error.errorCode is! TodoCode) |
| 353 .map((error) => error.toString()) | 356 .map((error) => error.toString()) |
| (...skipping 17 matching lines...) Expand all Loading... |
| 371 }); | 374 }); |
| 372 | 375 |
| 373 if (errorStrings.isNotEmpty) { | 376 if (errorStrings.isNotEmpty) { |
| 374 errorStrings.forEach((errorString) => print('\t$errorString')); | 377 errorStrings.forEach((errorString) => print('\t$errorString')); |
| 375 } else { | 378 } else { |
| 376 print('\tNO ERRORS'); | 379 print('\tNO ERRORS'); |
| 377 } | 380 } |
| 378 return errorStrings; | 381 return errorStrings; |
| 379 } | 382 } |
| 380 | 383 |
| 381 AnalysisContext _createAnalysisContext(LibraryContext libraryContext) { | 384 AnalysisContext _createAnalysisContext(_LibraryContext libraryContext) { |
| 382 AnalysisContextImpl analysisContext = | 385 AnalysisContextImpl analysisContext = |
| 383 AnalysisEngine.instance.createAnalysisContext(); | 386 AnalysisEngine.instance.createAnalysisContext(); |
| 384 | 387 |
| 385 analysisContext.sourceFactory = | 388 analysisContext.sourceFactory = |
| 386 new SourceFactory((_sourceFactory as SourceFactoryImpl).resolvers); | 389 new SourceFactory((_sourceFactory as SourceFactoryImpl).resolvers); |
| 387 analysisContext.resultProvider = | 390 analysisContext.resultProvider = |
| 388 new InputPackagesResultProvider(analysisContext, libraryContext.store); | 391 new InputPackagesResultProvider(analysisContext, libraryContext.store); |
| 389 analysisContext | 392 analysisContext |
| 390 .applyChanges(new ChangeSet()..addedSource(libraryContext.file.source)); | 393 .applyChanges(new ChangeSet()..addedSource(libraryContext.file.source)); |
| 391 return analysisContext; | 394 return analysisContext; |
| 392 } | 395 } |
| 393 | 396 |
| 394 /** | 397 /** |
| 395 * Return the content in which the library represented by the given | 398 * Return the context in which the library represented by the given |
| 396 * [libraryFile] should be analyzed it. | 399 * [libraryFile] should be analyzed it. |
| 397 * | 400 * |
| 398 * TODO(scheglov) We often don't need [SummaryDataStore], only linked hash. | 401 * TODO(scheglov) We often don't need [SummaryDataStore], only dependency |
| 402 * signature. |
| 399 */ | 403 */ |
| 400 LibraryContext _createLibraryContext(_File libraryFile) { | 404 _LibraryContext _createLibraryContext(_File libraryFile) { |
| 401 Map<String, _LibraryNode> nodes = <String, _LibraryNode>{}; | |
| 402 | |
| 403 return _logger.run('Create library context', () { | 405 return _logger.run('Create library context', () { |
| 406 Map<String, _LibraryNode> nodes = <String, _LibraryNode>{}; |
| 404 SummaryDataStore store = new SummaryDataStore(const <String>[]); | 407 SummaryDataStore store = new SummaryDataStore(const <String>[]); |
| 405 store.addBundle(null, _sdkBundle); | 408 store.addBundle(null, _sdkBundle); |
| 406 | 409 |
| 407 void createLibraryNodes(_File libraryFile) { | 410 _LibraryNode createLibraryNodes(_File libraryFile) { |
| 408 Uri libraryUri = libraryFile.uri; | 411 Uri libraryUri = libraryFile.uri; |
| 412 |
| 413 // URIs with the 'dart:' scheme are served from the SDK bundle. |
| 409 if (libraryUri.scheme == 'dart') { | 414 if (libraryUri.scheme == 'dart') { |
| 410 return; | 415 return null; |
| 411 } | 416 } |
| 412 String uriStr = libraryUri.toString(); | 417 |
| 413 if (!nodes.containsKey(uriStr)) { | 418 String libraryUriStr = libraryUri.toString(); |
| 414 _LibraryNode node = new _LibraryNode(this, nodes, libraryUri); | 419 _LibraryNode node = nodes[libraryUriStr]; |
| 415 nodes[uriStr] = node; | 420 if (node == null) { |
| 416 ReferencedUris referenced = _getReferencedUris(libraryFile); | 421 node = new _LibraryNode(this, nodes, libraryUri); |
| 422 nodes[libraryUriStr] = node; |
| 423 _ReferencedUris referenced = _getReferencedUris(libraryFile); |
| 417 | 424 |
| 418 // Append unlinked bundles. | 425 // Append unlinked bundles. |
| 419 for (String uri in referenced.parted) { | 426 for (String uri in referenced.parted) { |
| 420 _File file = libraryFile.resolveUri(uri); | 427 _File file = libraryFile.resolveUri(uri); |
| 421 PackageBundle unlinked = _getUnlinked(file); | 428 PackageBundle unlinked = _getUnlinked(file); |
| 422 node.unlinkedBundles.add(unlinked); | 429 node.unlinkedBundles.add(unlinked); |
| 423 store.addBundle(null, unlinked); | 430 store.addBundle(null, unlinked); |
| 424 } | 431 } |
| 425 | 432 |
| 426 // Create nodes for referenced libraries. | 433 // Create nodes for referenced libraries. |
| 427 for (String uri in referenced.imported) { | 434 for (String uri in referenced.imported) { |
| 428 _File file = libraryFile.resolveUri(uri); | 435 _File file = libraryFile.resolveUri(uri); |
| 429 createLibraryNodes(file); | 436 createLibraryNodes(file); |
| 430 } | 437 } |
| 431 for (String uri in referenced.exported) { | 438 for (String uri in referenced.exported) { |
| 432 _File file = libraryFile.resolveUri(uri); | 439 _File file = libraryFile.resolveUri(uri); |
| 433 createLibraryNodes(file); | 440 createLibraryNodes(file); |
| 434 } | 441 } |
| 435 } | 442 } |
| 443 |
| 444 // Done with this node. |
| 445 return node; |
| 436 } | 446 } |
| 437 | 447 |
| 438 _logger.runTimed2(() { | 448 _LibraryNode libraryNode = _logger.run('Compute library nodes', () { |
| 439 createLibraryNodes(libraryFile); | 449 return createLibraryNodes(libraryFile); |
| 440 }, () => 'Computed ${nodes.length} nodes'); | 450 }); |
| 441 _LibraryNode libraryNode = nodes[libraryFile.uri.toString()]; | |
| 442 | 451 |
| 443 Set<String> libraryUrisToLink = new Set<String>(); | 452 Set<String> libraryUrisToLink = new Set<String>(); |
| 444 int numberOfNodesWithLinked = 0; | 453 _logger.run('Load linked bundles', () { |
| 445 _logger.runTimed2(() { | |
| 446 for (_LibraryNode node in nodes.values) { | 454 for (_LibraryNode node in nodes.values) { |
| 447 String key = '${node.linkedHash}.linked'; | 455 String key = '${node.dependencySignature}.linked'; |
| 448 List<int> bytes = _byteStore.get(key); | 456 List<int> bytes = _byteStore.get(key); |
| 449 if (bytes != null) { | 457 if (bytes != null) { |
| 450 PackageBundle linked = new PackageBundle.fromBuffer(bytes); | 458 PackageBundle linked = new PackageBundle.fromBuffer(bytes); |
| 451 store.addBundle(null, linked); | 459 store.addBundle(null, linked); |
| 452 numberOfNodesWithLinked++; | |
| 453 } else { | 460 } else { |
| 454 libraryUrisToLink.add(node.uri.toString()); | 461 libraryUrisToLink.add(node.uri.toString()); |
| 455 } | 462 } |
| 456 } | 463 } |
| 457 }, () => 'Loaded $numberOfNodesWithLinked linked bundles'); | 464 int numOfLoaded = nodes.length - libraryUrisToLink.length; |
| 465 _logger.writeln('Loaded $numOfLoaded linked bundles.'); |
| 466 }); |
| 458 | 467 |
| 459 Map<String, LinkedLibraryBuilder> linkedLibraries = {}; | 468 Map<String, LinkedLibraryBuilder> linkedLibraries = {}; |
| 460 _logger.runTimed2(() { | 469 _logger.run('Link bundles', () { |
| 461 linkedLibraries = link(libraryUrisToLink, (String uri) { | 470 linkedLibraries = link(libraryUrisToLink, (String uri) { |
| 462 LinkedLibrary linkedLibrary = store.linkedMap[uri]; | 471 LinkedLibrary linkedLibrary = store.linkedMap[uri]; |
| 463 if (linkedLibrary == null) { | 472 if (linkedLibrary == null) { |
| 464 throw new StateError('No linked library for: $uri'); | 473 throw new StateError('No linked library for: $uri'); |
| 465 } | 474 } |
| 466 return linkedLibrary; | 475 return linkedLibrary; |
| 467 }, (String uri) { | 476 }, (String uri) { |
| 468 UnlinkedUnit unlinkedUnit = store.unlinkedMap[uri]; | 477 UnlinkedUnit unlinkedUnit = store.unlinkedMap[uri]; |
| 469 if (unlinkedUnit == null) { | 478 if (unlinkedUnit == null) { |
| 470 throw new StateError('No unlinked unit for: $uri'); | 479 throw new StateError('No unlinked unit for: $uri'); |
| 471 } | 480 } |
| 472 return unlinkedUnit; | 481 return unlinkedUnit; |
| 473 }, (_) => null, _analysisOptions.strongMode); | 482 }, (_) => null, _analysisOptions.strongMode); |
| 474 }, () => 'Linked ${linkedLibraries.length} bundles'); | 483 _logger.writeln('Linked ${linkedLibraries.length} bundles.'); |
| 484 }); |
| 475 | 485 |
| 476 linkedLibraries.forEach((uri, linkedBuilder) { | 486 linkedLibraries.forEach((uri, linkedBuilder) { |
| 477 _LibraryNode node = nodes[uri]; | 487 _LibraryNode node = nodes[uri]; |
| 478 String key = '${node.linkedHash}.linked'; | 488 String key = '${node.dependencySignature}.linked'; |
| 479 List<int> bytes; | 489 List<int> bytes; |
| 480 { | 490 { |
| 481 PackageBundleAssembler assembler = new PackageBundleAssembler(); | 491 PackageBundleAssembler assembler = new PackageBundleAssembler(); |
| 482 assembler.addLinkedLibrary(uri, linkedBuilder); | 492 assembler.addLinkedLibrary(uri, linkedBuilder); |
| 483 bytes = assembler.assemble().toBuffer(); | 493 bytes = assembler.assemble().toBuffer(); |
| 484 } | 494 } |
| 485 PackageBundle linked = new PackageBundle.fromBuffer(bytes); | 495 PackageBundle linked = new PackageBundle.fromBuffer(bytes); |
| 486 store.addBundle(null, linked); | 496 store.addBundle(null, linked); |
| 487 _byteStore.put(key, bytes); | 497 _byteStore.put(key, bytes); |
| 488 }); | 498 }); |
| 489 | 499 |
| 490 return new LibraryContext(libraryFile, libraryNode, store); | 500 return new _LibraryContext(libraryFile, libraryNode, store); |
| 491 }); | 501 }); |
| 492 } | 502 } |
| 493 | 503 |
| 494 /** | 504 /** |
| 495 * Return the [_File] for the given [path] in [_sourceFactory]. | 505 * Return the [_File] for the given [path] in [_sourceFactory]. |
| 496 */ | 506 */ |
| 497 _File _fileForPath(String path) { | 507 _File _fileForPath(String path) { |
| 498 Source fileSource = _resourceProvider.getFile(path).createSource(); | 508 Source fileSource = _resourceProvider.getFile(path).createSource(); |
| 499 Uri uri = _sourceFactory.restoreUri(fileSource); | 509 Uri uri = _sourceFactory.restoreUri(fileSource); |
| 500 Source source = _resourceProvider.getFile(path).createSource(uri); | 510 Source source = _resourceProvider.getFile(path).createSource(uri); |
| 501 return new _File(this, source); | 511 return new _File(this, source); |
| 502 } | 512 } |
| 503 | 513 |
| 504 /** | 514 /** |
| 505 * TODO(scheglov) It would be nice to get URIs of "parts" from unlinked. | 515 * TODO(scheglov) It would be nice to get URIs of "parts" from unlinked. |
| 506 */ | 516 */ |
| 507 ReferencedUris _getReferencedUris(_File file) { | 517 _ReferencedUris _getReferencedUris(_File file) { |
| 508 // Try to get from the store. | 518 // Try to get from the store. |
| 509 { | 519 { |
| 510 String key = '${file.contentHash}.uris'; | 520 String key = '${file.contentHash}.uris'; |
| 511 List<int> bytes = _byteStore.get(key); | 521 List<int> bytes = _byteStore.get(key); |
| 512 if (bytes != null) { | 522 if (bytes != null) { |
| 513 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); | 523 fb.BufferContext bp = new fb.BufferContext.fromBytes(bytes); |
| 514 int table = bp.derefObject(0); | 524 int table = bp.derefObject(0); |
| 515 const fb.ListReader<String> stringListReader = | 525 const fb.ListReader<String> stringListReader = |
| 516 const fb.ListReader<String>(const fb.StringReader()); | 526 const fb.ListReader<String>(const fb.StringReader()); |
| 517 bool isLibrary = const fb.BoolReader().vTableGet(bp, table, 0); | 527 bool isLibrary = const fb.BoolReader().vTableGet(bp, table, 0); |
| 518 List<String> imported = stringListReader.vTableGet(bp, table, 1); | 528 List<String> imported = stringListReader.vTableGet(bp, table, 1); |
| 519 List<String> exported = stringListReader.vTableGet(bp, table, 2); | 529 List<String> exported = stringListReader.vTableGet(bp, table, 2); |
| 520 List<String> parted = stringListReader.vTableGet(bp, table, 3); | 530 List<String> parted = stringListReader.vTableGet(bp, table, 3); |
| 521 ReferencedUris referencedUris = new ReferencedUris(); | 531 _ReferencedUris referencedUris = new _ReferencedUris(); |
| 522 referencedUris.isLibrary = isLibrary; | 532 referencedUris.isLibrary = isLibrary; |
| 523 referencedUris.imported.addAll(imported); | 533 referencedUris.imported.addAll(imported); |
| 524 referencedUris.exported.addAll(exported); | 534 referencedUris.exported.addAll(exported); |
| 525 referencedUris.parted.addAll(parted); | 535 referencedUris.parted.addAll(parted); |
| 526 return referencedUris; | 536 return referencedUris; |
| 527 } | 537 } |
| 528 } | 538 } |
| 529 | 539 |
| 530 // Compute URIs. | 540 // Compute URIs. |
| 531 ReferencedUris referencedUris = new ReferencedUris(); | 541 _ReferencedUris referencedUris = new _ReferencedUris(); |
| 532 referencedUris.parted.add(file.uri.toString()); | 542 referencedUris.parted.add(file.uri.toString()); |
| 533 for (Directive directive in file.unit.directives) { | 543 for (Directive directive in file.unit.directives) { |
| 534 if (directive is PartOfDirective) { | 544 if (directive is PartOfDirective) { |
| 535 referencedUris.isLibrary = false; | 545 referencedUris.isLibrary = false; |
| 536 } else if (directive is UriBasedDirective) { | 546 } else if (directive is UriBasedDirective) { |
| 537 String uri = directive.uri.stringValue; | 547 String uri = directive.uri.stringValue; |
| 538 if (directive is ImportDirective) { | 548 if (directive is ImportDirective) { |
| 539 referencedUris.imported.add(uri); | 549 referencedUris.imported.add(uri); |
| 540 } else if (directive is ExportDirective) { | 550 } else if (directive is ExportDirective) { |
| 541 referencedUris.exported.add(uri); | 551 referencedUris.exported.add(uri); |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 587 */ | 597 */ |
| 588 PackageBundle _getUnlinked(_File file) { | 598 PackageBundle _getUnlinked(_File file) { |
| 589 // Try to get bytes for file's unlinked bundle. | 599 // Try to get bytes for file's unlinked bundle. |
| 590 List<int> bytes; | 600 List<int> bytes; |
| 591 { | 601 { |
| 592 String key = '${file.contentHash}.unlinked'; | 602 String key = '${file.contentHash}.unlinked'; |
| 593 bytes = _byteStore.get(key); | 603 bytes = _byteStore.get(key); |
| 594 } | 604 } |
| 595 // If no cached unlinked bundle, compute it. | 605 // If no cached unlinked bundle, compute it. |
| 596 if (bytes == null) { | 606 if (bytes == null) { |
| 597 _logger.runTimed('Create unlinked for $file', () { | 607 _logger.run('Create unlinked for $file', () { |
| 598 // We read the content and recomputed the hash. | 608 // We read the content and recomputed the hash. |
| 599 // So, we need to update the key. | 609 // So, we need to update the key. |
| 600 String key = '${file.contentHash}.unlinked'; | 610 String key = '${file.contentHash}.unlinked'; |
| 601 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(file.unit); | 611 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(file.unit); |
| 602 PackageBundleAssembler assembler = new PackageBundleAssembler(); | 612 PackageBundleAssembler assembler = new PackageBundleAssembler(); |
| 603 assembler.addUnlinkedUnitWithHash( | 613 assembler.addUnlinkedUnitWithHash( |
| 604 file.uri.toString(), unlinkedUnit, key); | 614 file.uri.toString(), unlinkedUnit, key); |
| 605 bytes = assembler.assemble().toBuffer(); | 615 bytes = assembler.assemble().toBuffer(); |
| 606 _byteStore.put(key, bytes); | 616 _byteStore.put(key, bytes); |
| 607 }); | 617 }); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 651 | 661 |
| 652 /** | 662 /** |
| 653 * The full list of computed analysis errors, both syntactic and semantic. | 663 * The full list of computed analysis errors, both syntactic and semantic. |
| 654 */ | 664 */ |
| 655 final List<AnalysisError> errors; | 665 final List<AnalysisError> errors; |
| 656 | 666 |
| 657 AnalysisResult(this.path, this.uri, this.content, this.contentHash, this.unit, | 667 AnalysisResult(this.path, this.uri, this.content, this.contentHash, this.unit, |
| 658 this.errors); | 668 this.errors); |
| 659 } | 669 } |
| 660 | 670 |
| 661 class LibraryContext { | 671 /** |
| 662 final _File file; | 672 * This class is used to gather and print performance information. |
| 663 final _LibraryNode node; | 673 */ |
| 664 final SummaryDataStore store; | |
| 665 LibraryContext(this.file, this.node, this.store); | |
| 666 } | |
| 667 | |
| 668 class PerformanceLog { | 674 class PerformanceLog { |
| 669 final StringSink sink; | 675 final StringSink sink; |
| 670 int _level = 0; | 676 int _level = 0; |
| 671 | 677 |
| 672 PerformanceLog(this.sink); | 678 PerformanceLog(this.sink); |
| 673 | 679 |
| 680 /** |
| 681 * Return the result of the function [f] invocation and log the elapsed time. |
| 682 * |
| 683 * Each invocation of [run] creates a new enclosed section in the log, |
| 684 * which begins with printing [msg], then any log output produced during |
| 685 * [f] invocation, and ends with printing [msg] with the elapsed time. |
| 686 */ |
| 674 /*=T*/ run/*<T>*/(String msg, /*=T*/ f()) { | 687 /*=T*/ run/*<T>*/(String msg, /*=T*/ f()) { |
| 675 Stopwatch timer = new Stopwatch()..start(); | 688 Stopwatch timer = new Stopwatch()..start(); |
| 676 try { | 689 try { |
| 677 writeln('+++ $msg.'); | 690 writeln('+++ $msg.'); |
| 678 _level++; | 691 _level++; |
| 679 return f(); | 692 return f(); |
| 680 } finally { | 693 } finally { |
| 681 _level--; | 694 _level--; |
| 682 int ms = timer.elapsedMilliseconds; | 695 int ms = timer.elapsedMilliseconds; |
| 683 writeln('--- $msg in $ms ms.'); | 696 writeln('--- $msg in $ms ms.'); |
| 684 } | 697 } |
| 685 } | 698 } |
| 686 | 699 |
| 687 /*=T*/ runTimed/*<T>*/(String msg, /*=T*/ f()) { | 700 /** |
| 688 _level++; | 701 * Write a new line into the log |
| 689 Stopwatch timer = new Stopwatch()..start(); | 702 */ |
| 690 try { | |
| 691 return f(); | |
| 692 } finally { | |
| 693 _level--; | |
| 694 int ms = timer.elapsedMilliseconds; | |
| 695 writeln('$msg in $ms ms.'); | |
| 696 } | |
| 697 } | |
| 698 | |
| 699 runTimed2(f(), String getMsg()) { | |
| 700 _level++; | |
| 701 Stopwatch timer = new Stopwatch()..start(); | |
| 702 try { | |
| 703 return f(); | |
| 704 } finally { | |
| 705 _level--; | |
| 706 int ms = timer.elapsedMilliseconds; | |
| 707 String msg = getMsg(); | |
| 708 writeln('$msg in $ms ms.'); | |
| 709 } | |
| 710 } | |
| 711 | |
| 712 void writeln(String msg) { | 703 void writeln(String msg) { |
| 713 String indent = '\t' * _level; | 704 String indent = '\t' * _level; |
| 714 sink.writeln('$indent$msg'); | 705 sink.writeln('$indent$msg'); |
| 715 } | 706 } |
| 716 } | 707 } |
| 717 | 708 |
| 718 class ReferencedUris { | |
| 719 bool isLibrary = true; | |
| 720 final List<String> imported = <String>[]; | |
| 721 final List<String> exported = <String>[]; | |
| 722 final List<String> parted = <String>[]; | |
| 723 } | |
| 724 | |
| 725 /** | 709 /** |
| 726 * Information about a file being analyzed, explicitly or implicitly. | 710 * Information about a file being analyzed, explicitly or implicitly. |
| 727 * | 711 * |
| 728 * It keeps a consistent view on its [content], [contentHash] and [unit]. | 712 * It keeps a consistent view on its [content], [contentHash] and [unit]. |
| 713 * |
| 714 * Instances of this class may only be used during computing a single analysis |
| 715 * result and should not be cached anywhere. We need this limitation to prevent |
| 716 * references from caches to the resolved [unit], so to element models, etc. |
| 717 * The data structures should be short lived - computed, returned to the client, |
| 718 * processed there and quickly thrown away. |
| 729 */ | 719 */ |
| 730 class _File { | 720 class _File { |
| 731 /** | 721 /** |
| 732 * The driver instance that is used to access [SourceFactory] and caches. | 722 * The driver instance that is used to access [SourceFactory] and caches. |
| 733 */ | 723 */ |
| 734 final AnalysisDriver driver; | 724 final AnalysisDriver driver; |
| 735 | 725 |
| 736 /** | 726 /** |
| 737 * The [Source] this [_File] instance represent. | 727 * The [Source] this [_File] instance represent. |
| 738 */ | 728 */ |
| (...skipping 22 matching lines...) Expand all Loading... |
| 761 return _content; | 751 return _content; |
| 762 } | 752 } |
| 763 | 753 |
| 764 /** | 754 /** |
| 765 * Ensure that the [contentHash] is filled. | 755 * Ensure that the [contentHash] is filled. |
| 766 * | 756 * |
| 767 * If the hash is already in the current file state, return the current | 757 * If the hash is already in the current file state, return the current |
| 768 * value. Otherwise, read the [content], compute the hash, put it into | 758 * value. Otherwise, read the [content], compute the hash, put it into |
| 769 * the current file state, and update the [contentHash] field. | 759 * the current file state, and update the [contentHash] field. |
| 770 * | 760 * |
| 771 * The client cannot remember values of this property, because its value | 761 * The client should not remember values of this property, because its value |
| 772 * might change when [content] is read and the hash is recomputed. | 762 * might change when [content] is read and the hash is recomputed. |
| 773 */ | 763 */ |
| 774 String get contentHash { | 764 String get contentHash { |
| 775 _contentHash ??= driver._fileContentHashMap[path]; | 765 _contentHash ??= driver._fileContentHashMap[path]; |
| 776 if (_contentHash == null) { | 766 if (_contentHash == null) { |
| 777 _readContentAndComputeHash(); | 767 _readContentAndComputeHash(); |
| 778 } | 768 } |
| 779 return _contentHash; | 769 return _contentHash; |
| 780 } | 770 } |
| 781 | 771 |
| 782 String get path => source.fullName; | 772 String get path => source.fullName; |
| 783 | 773 |
| 784 /** | 774 /** |
| 785 * Return the [CompilationUnit] of the file. | 775 * Return the unresolved [CompilationUnit] of the file. |
| 786 * | 776 * |
| 787 * Current this unit is resolved, it is used to compute unlinked summaries | 777 * Performing resolution and computing errors is done in a separate analysis |
| 788 * and and URIs. We use a separate analysis context to perform resolution | 778 * context. In the future we might push the existing unresolved unit into the |
| 789 * and computing errors. But this might change in the future. | 779 * analysis context, so at some point the unit might become resolved. |
| 790 */ | 780 */ |
| 791 CompilationUnit get unit { | 781 CompilationUnit get unit { |
| 792 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; | 782 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; |
| 793 | 783 |
| 794 CharSequenceReader reader = new CharSequenceReader(content); | 784 CharSequenceReader reader = new CharSequenceReader(content); |
| 795 Scanner scanner = new Scanner(source, reader, errorListener); | 785 Scanner scanner = new Scanner(source, reader, errorListener); |
| 796 scanner.scanGenericMethodComments = driver._analysisOptions.strongMode; | 786 scanner.scanGenericMethodComments = driver._analysisOptions.strongMode; |
| 797 Token token = scanner.tokenize(); | 787 Token token = scanner.tokenize(); |
| 798 LineInfo lineInfo = new LineInfo(scanner.lineStarts); | 788 LineInfo lineInfo = new LineInfo(scanner.lineStarts); |
| 799 | 789 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 816 .putIfAbsent(uri, () => driver._sourceFactory.resolveUri(source, uri)); | 806 .putIfAbsent(uri, () => driver._sourceFactory.resolveUri(source, uri)); |
| 817 return new _File(driver, uriSource); | 807 return new _File(driver, uriSource); |
| 818 } | 808 } |
| 819 | 809 |
| 820 @override | 810 @override |
| 821 String toString() => uri.toString(); | 811 String toString() => uri.toString(); |
| 822 | 812 |
| 823 /** | 813 /** |
| 824 * Fill the [_content] and [_contentHash] fields. | 814 * Fill the [_content] and [_contentHash] fields. |
| 825 * | 815 * |
| 826 * If the [_content] field if it is still `null`, get the content from the | 816 * If the [_content] field is still `null`, get the content from the |
| 827 * content cache or from the [source]. If the content cannot be accessed | 817 * content cache or from the [source]. If the content cannot be accessed |
| 828 * because of an exception, it considers to be an empty string. | 818 * because of an exception, it considers to be an empty string. |
| 829 * | 819 * |
| 830 * When a new content is read, the new [_contentHash] should be computed and | 820 * When a new content is read, the new [_contentHash] should be computed and |
| 831 * the current file state should be updated. | 821 * the current file state should be updated. |
| 832 */ | 822 */ |
| 833 void _readContentAndComputeHash() { | 823 void _readContentAndComputeHash() { |
| 834 try { | 824 try { |
| 835 _content = driver._contentCache.getContents(source); | 825 _content = driver._contentCache.getContents(source); |
| 836 _content ??= source.contents.data; | 826 _content ??= source.contents.data; |
| 837 } catch (_) { | 827 } catch (_) { |
| 828 // TODO(scheglov) Fix the bug with not existing sources. |
| 829 // We should not put "self URI" into cached _ReferencedUris. |
| 830 // Otherwise such not-existing/empty sources all have the same hash, |
| 831 // but their "self URIs" must be all different. |
| 838 _content = ''; | 832 _content = ''; |
| 839 } | 833 } |
| 840 // Compute the content hash. | 834 // Compute the content hash. |
| 841 List<int> textBytes = UTF8.encode(_content); | 835 List<int> textBytes = UTF8.encode(_content); |
| 842 List<int> hashBytes = md5.convert(textBytes).bytes; | 836 List<int> hashBytes = md5.convert(textBytes).bytes; |
| 843 _contentHash = hex.encode(hashBytes); | 837 _contentHash = hex.encode(hashBytes); |
| 844 // Update the current file state. | 838 // Update the current file state. |
| 845 driver._fileContentHashMap[path] = _contentHash; | 839 driver._fileContentHashMap[path] = _contentHash; |
| 846 } | 840 } |
| 847 } | 841 } |
| 848 | 842 |
| 843 /** |
| 844 * TODO(scheglov) document |
| 845 */ |
| 846 class _LibraryContext { |
| 847 final _File file; |
| 848 final _LibraryNode node; |
| 849 final SummaryDataStore store; |
| 850 _LibraryContext(this.file, this.node, this.store); |
| 851 } |
| 852 |
| 849 class _LibraryNode { | 853 class _LibraryNode { |
| 850 final AnalysisDriver driver; | 854 final AnalysisDriver driver; |
| 851 final Map<String, _LibraryNode> nodes; | 855 final Map<String, _LibraryNode> nodes; |
| 852 final Uri uri; | 856 final Uri uri; |
| 853 final List<PackageBundle> unlinkedBundles = <PackageBundle>[]; | 857 final List<PackageBundle> unlinkedBundles = <PackageBundle>[]; |
| 854 | 858 |
| 855 Set<_LibraryNode> transitiveDependencies; | 859 Set<_LibraryNode> transitiveDependencies; |
| 856 List<_LibraryNode> _dependencies; | 860 List<_LibraryNode> _dependencies; |
| 857 String _linkedHash; | 861 String _dependencySignature; |
| 858 | 862 |
| 859 _LibraryNode(this.driver, this.nodes, this.uri); | 863 _LibraryNode(this.driver, this.nodes, this.uri); |
| 860 | 864 |
| 861 /** | 865 /** |
| 862 * Retrieve the dependencies of this node. | 866 * Retrieve the dependencies of this node. |
| 863 */ | 867 */ |
| 864 List<_LibraryNode> get dependencies { | 868 List<_LibraryNode> get dependencies { |
| 865 if (_dependencies == null) { | 869 if (_dependencies == null) { |
| 866 Set<_LibraryNode> dependencies = new Set<_LibraryNode>(); | 870 Set<_LibraryNode> dependencies = new Set<_LibraryNode>(); |
| 867 | 871 |
| (...skipping 26 matching lines...) Expand all Loading... |
| 894 appendDependency(export.uri); | 898 appendDependency(export.uri); |
| 895 } | 899 } |
| 896 } | 900 } |
| 897 } | 901 } |
| 898 | 902 |
| 899 _dependencies = dependencies.toList(); | 903 _dependencies = dependencies.toList(); |
| 900 } | 904 } |
| 901 return _dependencies; | 905 return _dependencies; |
| 902 } | 906 } |
| 903 | 907 |
| 904 @override | 908 String get dependencySignature { |
| 905 int get hashCode => uri.hashCode; | 909 return _dependencySignature ??= |
| 906 | 910 driver._dependencySignatureMap.putIfAbsent(uri, () { |
| 907 String get linkedHash { | |
| 908 _linkedHash ??= driver._linkedHashMap.putIfAbsent(uri, () { | |
| 909 computeTransitiveDependencies(); | 911 computeTransitiveDependencies(); |
| 910 | 912 |
| 911 // Add all unlinked API signatures. | 913 // Add all unlinked API signatures. |
| 912 List<String> signatures = <String>[]; | 914 List<String> signatures = <String>[]; |
| 913 signatures.add(driver._sdkBundle.apiSignature); | 915 signatures.add(driver._sdkBundle.apiSignature); |
| 914 transitiveDependencies | 916 transitiveDependencies |
| 915 .map((node) => node.unlinkedBundles) | 917 .map((node) => node.unlinkedBundles) |
| 916 .expand((bundles) => bundles) | 918 .expand((bundles) => bundles) |
| 917 .map((bundle) => bundle.apiSignature) | 919 .map((bundle) => bundle.apiSignature) |
| 918 .forEach(signatures.add); | 920 .forEach(signatures.add); |
| 919 signatures.sort(); | 921 signatures.sort(); |
| 920 | 922 |
| 921 // Combine into a single hash. | 923 // Combine into a single hash. |
| 922 ApiSignature signature = new ApiSignature(); | 924 ApiSignature signature = new ApiSignature(); |
| 923 signature.addString(uri.toString()); | 925 signature.addString(uri.toString()); |
| 924 signatures.forEach(signature.addString); | 926 signatures.forEach(signature.addString); |
| 925 return signature.toHex(); | 927 return signature.toHex(); |
| 926 }); | 928 }); |
| 927 return _linkedHash; | |
| 928 } | 929 } |
| 929 | 930 |
| 931 @override |
| 932 int get hashCode => uri.hashCode; |
| 933 |
| 930 bool operator ==(other) { | 934 bool operator ==(other) { |
| 931 return other is _LibraryNode && other.uri == uri; | 935 return other is _LibraryNode && other.uri == uri; |
| 932 } | 936 } |
| 933 | 937 |
| 934 void computeTransitiveDependencies() { | 938 void computeTransitiveDependencies() { |
| 935 if (transitiveDependencies == null) { | 939 if (transitiveDependencies == null) { |
| 936 transitiveDependencies = new Set<_LibraryNode>(); | 940 transitiveDependencies = new Set<_LibraryNode>(); |
| 937 | 941 |
| 938 void appendDependencies(_LibraryNode node) { | 942 void appendDependencies(_LibraryNode node) { |
| 939 if (transitiveDependencies.add(node)) { | 943 if (transitiveDependencies.add(node)) { |
| 940 node.dependencies.forEach(appendDependencies); | 944 node.dependencies.forEach(appendDependencies); |
| 941 } | 945 } |
| 942 } | 946 } |
| 943 | 947 |
| 944 appendDependencies(this); | 948 appendDependencies(this); |
| 945 } | 949 } |
| 946 } | 950 } |
| 947 | 951 |
| 948 @override | 952 @override |
| 949 String toString() => uri.toString(); | 953 String toString() => uri.toString(); |
| 950 } | 954 } |
| 955 |
| 956 /** |
| 957 * TODO(scheglov) document |
| 958 */ |
| 959 class _ReferencedUris { |
| 960 bool isLibrary = true; |
| 961 final List<String> imported = <String>[]; |
| 962 final List<String> exported = <String>[]; |
| 963 final List<String> parted = <String>[]; |
| 964 } |
| OLD | NEW |