blob: cc4d4e6d336cd361d860ab80035aa8b88a6cd148 [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,
AnalysisErrorInfoImpl,
AnalysisOptions,
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> _SOURCE_ERRORS = <ResultDescriptor>[
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> _UNIT_ERRORS = <ResultDescriptor>[
HINTS,
LINTS,
INFER_STATIC_VARIABLE_TYPES_ERRORS,
LIBRARY_UNIT_ERRORS,
PARTIALLY_RESOLVE_REFERENCES_ERRORS,
RESOLVE_FUNCTION_BODIES_ERRORS,
RESOLVE_TYPE_NAMES_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) {
analysisCache.onResultInvalidated.listen((InvalidatedResult event) {
if (event.descriptor == LIBRARY_ERRORS_READY) {
CacheEntry entry = event.entry;
if (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).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 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 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) {
List<Source> libraries = partLibrariesMap[part];
return libraries != null ? libraries : 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);
// 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];
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);
}
}
}
void unitIncrementallyResolved(Source librarySource, Source unitSource) {
librarySourceQueue.add(librarySource);
}
/**
* 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)) {
librarySourceQueue.add(library);
}
}
}
// Invalidate targets and values.
unitTargets.forEach(partition.remove);
for (Source dartSource in dartSources) {
CacheEntry entry = partition.get(dartSource);
if (dartSource != 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);
}
}
}
/**
* 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, null);
static bool _isDartSource(AnalysisTarget target) {
return target is Source && AnalysisEngine.isDartFileName(target.fullName);
}
}