blob: f3dd0b66dbbe034df1acb3da04bbd43f8a98b183 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library analyzer.src.task.dart_work_manager;
import 'dart:collection';
import 'package:analyzer/src/context/cache.dart';
import 'package:analyzer/src/generated/engine.dart'
show AnalysisEngine, AnalysisErrorInfo, CacheState, InternalAnalysisContext;
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_collection.dart';
import 'package:analyzer/src/task/dart.dart';
import 'package:analyzer/src/task/html.dart';
import 'package:analyzer/task/dart.dart';
import 'package:analyzer/task/model.dart';
/**
* The manager for Dart specific analysis.
*/
class DartWorkManager implements WorkManager {
/**
* The list of errors that are reported for raw Dart [Source]s.
*/
static final List<ResultDescriptor<List<AnalysisError>>> _SOURCE_ERRORS =
<ResultDescriptor<List<AnalysisError>>>[
BUILD_DIRECTIVES_ERRORS,
BUILD_LIBRARY_ERRORS,
PARSE_ERRORS,
SCAN_ERRORS
];
/**
* The list of errors that are reported for raw Dart [LibrarySpecificUnit]s.
*/
static final List<ResultDescriptor<List<AnalysisError>>> _UNIT_ERRORS =
<ResultDescriptor<List<AnalysisError>>>[
HINTS,
LINTS,
LIBRARY_UNIT_ERRORS,
RESOLVE_TYPE_NAMES_ERRORS,
RESOLVE_TYPE_BOUNDS_ERRORS,
RESOLVE_UNIT_ERRORS,
STRONG_MODE_ERRORS,
VARIABLE_REFERENCE_ERRORS,
VERIFY_ERRORS
];
final InternalAnalysisContext context;
/**
* The [TargetedResult]s that should be computed with priority.
*/
final LinkedHashSet<TargetedResult> priorityResultQueue =
new LinkedHashSet<TargetedResult>();
/**
* The sources whose kind we don't know yet.
*/
final LinkedHashSet<Source> unknownSourceQueue = new LinkedHashSet<Source>();
/**
* The queue of library sources to process.
*/
final LinkedHashSet<Source> librarySourceQueue = new LinkedHashSet<Source>();
/**
* A table mapping library sources to the part sources they include.
*/
final HashMap<Source, List<Source>> libraryPartsMap =
new HashMap<Source, List<Source>>();
/**
* A table mapping part sources to the library sources that include them.
*/
final HashMap<Source, List<Source>> partLibrariesMap =
new HashMap<Source, List<Source>>();
/**
* Initialize a newly created manager.
*/
DartWorkManager(this.context) {
context.onResultInvalidated.listen((InvalidatedResult event) {
if (event.descriptor == LIBRARY_ERRORS_READY) {
CacheEntry entry = event.entry;
if (entry.explicitlyAdded &&
entry.getValue(SOURCE_KIND) == SourceKind.LIBRARY) {
librarySourceQueue.add(entry.target);
}
}
});
}
/**
* Returns the correctly typed result of `context.analysisCache`.
*/
AnalysisCache get analysisCache => context.analysisCache;
/**
* The partition that contains analysis results that are not shared with other
* contexts.
*/
CachePartition get privateAnalysisCachePartition =>
context.privateAnalysisCachePartition;
/**
* Specifies that the client want the given [result] of the given [target]
* to be computed with priority.
*/
void addPriorityResult(AnalysisTarget target, ResultDescriptor result) {
priorityResultQueue.add(new TargetedResult(target, result));
}
@override
void applyChange(List<Source> addedSources, List<Source> changedSources,
List<Source> removedSources) {
addedSources = addedSources.where(_isDartSource).toList();
changedSources = changedSources
.where(_isDartSource)
.where((source) => _needsComputing(source, SOURCE_KIND))
.toList();
removedSources = removedSources.where(_isDartSource).toList();
// unknown queue
unknownSourceQueue.addAll(addedSources);
unknownSourceQueue.addAll(changedSources);
unknownSourceQueue.removeAll(removedSources);
// library queue
librarySourceQueue.removeAll(changedSources);
librarySourceQueue.removeAll(removedSources);
// parts in libraries
for (Source changedSource in changedSources) {
_onLibrarySourceChangedOrRemoved(changedSource);
}
for (Source removedSource in removedSources) {
partLibrariesMap.remove(removedSource);
_onLibrarySourceChangedOrRemoved(removedSource);
}
}
@override
void applyPriorityTargets(List<AnalysisTarget> targets) {
// Unschedule the old targets.
List<TargetedResult> resultsToUnschedule = <TargetedResult>[];
for (TargetedResult result in priorityResultQueue) {
if (result.result == LIBRARY_ERRORS_READY) {
resultsToUnschedule.add(result);
}
}
priorityResultQueue.removeAll(resultsToUnschedule);
// Schedule new targets.
for (AnalysisTarget target in targets) {
if (_isDartSource(target)) {
SourceKind sourceKind = analysisCache.getValue(target, SOURCE_KIND);
if (sourceKind == SourceKind.UNKNOWN) {
addPriorityResult(target, SOURCE_KIND);
} else if (sourceKind == SourceKind.LIBRARY) {
_schedulePriorityLibrarySourceAnalysis(target);
} else if (sourceKind == SourceKind.PART) {
List<Source> libraries = context.getLibrariesContaining(target);
for (Source library in libraries) {
addPriorityResult(library, LIBRARY_ERRORS_READY);
}
}
}
}
}
@override
List<AnalysisError> getErrors(Source source) {
if (!_isDartSource(source) && source is! DartScript) {
return AnalysisError.NO_ERRORS;
}
// If analysis is finished, use all the errors.
if (analysisCache.getState(source, DART_ERRORS) == CacheState.VALID) {
return analysisCache.getValue(source, DART_ERRORS);
}
// If analysis is in progress, combine all known partial results.
List<AnalysisError> errors = <AnalysisError>[];
for (ResultDescriptor<List<AnalysisError>> descriptor in _SOURCE_ERRORS) {
errors.addAll(analysisCache.getValue(source, descriptor));
}
for (Source library in context.getLibrariesContaining(source)) {
LibrarySpecificUnit unit = new LibrarySpecificUnit(library, source);
for (ResultDescriptor<List<AnalysisError>> descriptor in _UNIT_ERRORS) {
errors.addAll(analysisCache.getValue(unit, descriptor));
}
}
return errors;
}
/**
* Returns libraries containing the given [part].
* Maybe empty, but not null.
*/
List<Source> getLibrariesContainingPart(Source part) {
if (part.isInSystemLibrary) {
DartWorkManager sdkDartWorkManager = _getSdkDartWorkManager();
if (sdkDartWorkManager != this) {
return sdkDartWorkManager.getLibrariesContainingPart(part);
}
}
List<Source> libraries = partLibrariesMap[part];
libraries ??= _getLibrariesContainingPartFromResultProvider(part);
return libraries?.toList() ?? Source.EMPTY_LIST;
}
@override
TargetedResult getNextResult() {
// Try to find a priority result to compute.
while (priorityResultQueue.isNotEmpty) {
TargetedResult result = priorityResultQueue.first;
if (!_needsComputing(result.target, result.result)) {
priorityResultQueue.remove(result);
continue;
}
return result;
}
// Try to find a new library to analyze.
while (librarySourceQueue.isNotEmpty) {
Source librarySource = librarySourceQueue.first;
// Maybe done with this library.
if (!_needsComputing(librarySource, LIBRARY_ERRORS_READY)) {
librarySourceQueue.remove(librarySource);
continue;
}
// Analyze this library.
return new TargetedResult(librarySource, LIBRARY_ERRORS_READY);
}
// No libraries in the queue, check whether there are sources to organize.
while (unknownSourceQueue.isNotEmpty) {
Source source = unknownSourceQueue.first;
// Maybe done with this source.
if (!_needsComputing(source, SOURCE_KIND)) {
unknownSourceQueue.remove(source);
continue;
}
// Compute the kind of this source.
return new TargetedResult(source, SOURCE_KIND);
}
// TODO(scheglov) Report errors for parts that remained in the queue after
// all libraries had been processed.
// No results to compute.
return null;
}
@override
WorkOrderPriority getNextResultPriority() {
if (priorityResultQueue.isNotEmpty) {
return WorkOrderPriority.PRIORITY;
}
if (unknownSourceQueue.isNotEmpty || librarySourceQueue.isNotEmpty) {
return WorkOrderPriority.NORMAL;
}
return WorkOrderPriority.NONE;
}
/**
* Notifies the manager about analysis options changes.
*/
void onAnalysisOptionsChanged() {
_invalidateAllLocalResolutionInformation(false);
}
/**
* Notifies the manager about [SourceFactory] changes.
*/
void onSourceFactoryChanged() {
_invalidateAllLocalResolutionInformation(true);
}
@override
void resultsComputed(
AnalysisTarget target, Map<ResultDescriptor, dynamic> outputs) {
bool isDartSource = _isDartSource(target);
// Route SDK outputs to the SDK WorkManager.
if (isDartSource && target.source.isInSystemLibrary) {
DartWorkManager sdkWorkManager = _getSdkDartWorkManager();
if (sdkWorkManager != this) {
sdkWorkManager.resultsComputed(target, outputs);
return;
}
}
// Organize sources.
bool isDartLibrarySource = false;
if (isDartSource) {
Source source = target;
SourceKind kind = outputs[SOURCE_KIND];
if (kind != null) {
unknownSourceQueue.remove(source);
if (kind == SourceKind.LIBRARY) {
isDartLibrarySource = true;
if (context.prioritySources.contains(source)) {
_schedulePriorityLibrarySourceAnalysis(source);
} else {
bool needErrors = _shouldErrorsBeComputed(source);
if (needErrors) {
librarySourceQueue.add(target);
}
}
}
}
}
// Update parts in libraries.
if (isDartLibrarySource) {
Source library = target;
List<Source> includedParts = outputs[INCLUDED_PARTS] as List<Source>;
if (includedParts != null) {
libraryPartsMap[library] = includedParts;
for (Source part in includedParts) {
List<Source> libraries =
partLibrariesMap.putIfAbsent(part, () => <Source>[]);
if (!libraries.contains(library)) {
libraries.add(library);
_invalidateContainingLibraries(part);
}
}
}
}
// Update notice.
if (isDartSource) {
bool shouldSetErrors = false;
outputs.forEach((ResultDescriptor descriptor, value) {
if (descriptor == PARSED_UNIT && value != null) {
context.getNotice(target).parsedDartUnit = value;
shouldSetErrors = true;
}
if (descriptor == DART_ERRORS) {
shouldSetErrors = true;
}
});
if (shouldSetErrors) {
AnalysisErrorInfo info = context.getErrors(target);
context.getNotice(target).setErrors(info.errors, info.lineInfo);
}
}
if (target is LibrarySpecificUnit) {
Source source = target.source;
bool shouldSetErrors = false;
outputs.forEach((ResultDescriptor descriptor, value) {
if (descriptor == RESOLVED_UNIT && value != null) {
context.getNotice(source).resolvedDartUnit = value;
shouldSetErrors = true;
}
});
if (shouldSetErrors) {
AnalysisErrorInfo info = context.getErrors(source);
context.getNotice(source).setErrors(info.errors, info.lineInfo);
}
}
}
/**
* The given unit was incrementally resolved. Some of its error results might
* have been invalidated, so we schedule it for computing errors.
*/
void unitIncrementallyResolved(Source librarySource, Source unitSource) {
librarySourceQueue.add(librarySource);
}
/**
* Ask the [context]'s result provider for [CONTAINING_LIBRARIES].
* Return the list of containing libraries, or `null` if unknown.
*/
List<Source> _getLibrariesContainingPartFromResultProvider(Source part) {
CacheEntry cacheEntry = context.getCacheEntry(part);
bool knows = context.aboutToComputeResult(cacheEntry, CONTAINING_LIBRARIES);
if (knows) {
return cacheEntry.getValue(CONTAINING_LIBRARIES);
}
return null;
}
/**
* Return the SDK [DartWorkManager] or this one.
*/
DartWorkManager _getSdkDartWorkManager() {
SourceFactory sourceFactory = context.sourceFactory;
InternalAnalysisContext sdkContext = sourceFactory.dartSdk.context;
if (sdkContext != context) {
for (WorkManager workManager in sdkContext.workManagers) {
if (workManager is DartWorkManager) {
return workManager;
}
}
}
return this;
}
/**
* Invalidate all of the resolution results computed by this context. The flag
* [invalidateUris] should be `true` if the cached results of converting URIs
* to source files should also be invalidated.
*/
void _invalidateAllLocalResolutionInformation(bool invalidateUris) {
CachePartition partition = privateAnalysisCachePartition;
// Prepare targets and values to invalidate.
List<Source> dartSources = <Source>[];
List<LibrarySpecificUnit> unitTargets = <LibrarySpecificUnit>[];
MapIterator<AnalysisTarget, CacheEntry> iterator = partition.iterator();
while (iterator.moveNext()) {
AnalysisTarget target = iterator.key;
// Optionally gather Dart sources to invalidate URIs resolution.
if (invalidateUris && _isDartSource(target)) {
dartSources.add(target);
}
// LibrarySpecificUnit(s) are roots of Dart resolution.
// When one is invalidated, invalidation is propagated to all resolution.
if (target is LibrarySpecificUnit) {
unitTargets.add(target);
Source library = target.library;
if (context.exists(library)) {
CacheEntry entry = iterator.value;
if (entry.explicitlyAdded) {
librarySourceQueue.add(library);
}
}
}
}
// Invalidate targets and values.
unitTargets.forEach(partition.remove);
for (Source dartSource in dartSources) {
CacheEntry entry = partition.get(dartSource);
if (entry != null) {
// TODO(scheglov) we invalidate too much.
// Would be nice to invalidate just URLs resolution.
entry.setState(PARSED_UNIT, CacheState.INVALID);
entry.setState(IMPORTED_LIBRARIES, CacheState.INVALID);
entry.setState(EXPLICITLY_IMPORTED_LIBRARIES, CacheState.INVALID);
entry.setState(EXPORTED_LIBRARIES, CacheState.INVALID);
entry.setState(INCLUDED_PARTS, CacheState.INVALID);
entry.setState(LIBRARY_SPECIFIC_UNITS, CacheState.INVALID);
entry.setState(UNITS, CacheState.INVALID);
}
}
}
/**
* Invalidate [CONTAINING_LIBRARIES] for the given [source].
* [CONTAINING_LIBRARIES] does not have dependencies, so we manage it here.
* The [source] may be a part, or a library whose contents is updated so
* will be a part.
*/
void _invalidateContainingLibraries(Source source) {
CacheEntry entry = analysisCache.get(source);
if (entry != null) {
entry.setState(CONTAINING_LIBRARIES, CacheState.INVALID);
}
}
/**
* Returns `true` if the given [result] of the given [target] needs
* computing, i.e. it is not in the valid and not in the error state.
*/
bool _needsComputing(AnalysisTarget target, ResultDescriptor result) {
CacheState state = analysisCache.getState(target, result);
return state != CacheState.VALID && state != CacheState.ERROR;
}
/**
* The given [library] source was changed or removed.
* Update [libraryPartsMap] and [partLibrariesMap].
*/
void _onLibrarySourceChangedOrRemoved(Source library) {
List<Source> parts = libraryPartsMap.remove(library);
if (parts != null) {
for (Source part in parts) {
List<Source> libraries = partLibrariesMap[part];
if (libraries != null) {
libraries.remove(library);
_invalidateContainingLibraries(part);
}
}
}
_invalidateContainingLibraries(library);
}
/**
* Schedule computing [RESOLVED_UNIT] for the given [librarySource].
* If errors should be computed, then schedule [LIBRARY_ERRORS_READY] instead,
* it also computes [RESOLVED_UNIT] in process.
*/
void _schedulePriorityLibrarySourceAnalysis(Source librarySource) {
bool needErrors = _shouldErrorsBeComputed(librarySource);
if (needErrors) {
addPriorityResult(librarySource, LIBRARY_ERRORS_READY);
} else {
var target = new LibrarySpecificUnit(librarySource, librarySource);
addPriorityResult(target, RESOLVED_UNIT);
}
}
bool _shouldErrorsBeComputed(Source source) =>
context.shouldErrorsBeAnalyzed(source);
static bool _isDartSource(AnalysisTarget target) {
return target is Source && AnalysisEngine.isDartFileName(target.fullName);
}
}