Files
sdk/pkg/analyzer/lib/task/model.dart
T
Paul Berry 4cd9b28eb6 Create pkg/front_end/src/base and begin populating with general use classes.
In the long run, pkg/front_end/src/base is intended to contain code
that is useful to many components of the front end.

In this initial implementation, it contains:
- Source and the classes it references
- ErrorCode, ErrorSeverity, and ErrorType
- SyntacticEntity

Note that AnalysisTarget is included (even though strictly speaking it
is part of the analyzer task model) because it was too hard to
disentangle from Source.

Note that Token classes remain in pkg/front_end/src/scanner for now; I
plan to reorganize them at the time the parser is moved into the front
end.

R=brianwilkerson@google.com

Review URL: https://codereview.chromium.org/2486923007 .
2016-11-14 09:20:18 -08:00

787 lines
24 KiB
Dart

// 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.task.model;
import 'dart:collection';
import 'dart:developer';
import 'package:analyzer/error/error.dart' show AnalysisError;
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_general.dart';
import 'package:analyzer/src/task/driver.dart';
import 'package:analyzer/src/task/model.dart';
import 'package:front_end/src/base/analysis_target.dart';
export 'package:front_end/src/base/analysis_target.dart' show AnalysisTarget;
/**
* A function that converts the given [key] and [value] into a [TaskInput].
*/
typedef TaskInput<E> BinaryFunction<K, V, E>(K key, V value);
/**
* A function that takes an analysis [context] and an analysis [target] and
* returns an analysis task. Such functions are passed to a [TaskDescriptor] to
* be used to create the described task.
*/
typedef AnalysisTask BuildTask(AnalysisContext context, AnalysisTarget target);
/**
* A function that takes the target for which a task will produce results and
* returns a map from input names to descriptions of the analysis results needed
* by the task in order for the task to be performed. Such functions are passed
* to a [TaskDescriptor] to be used to determine the inputs needed by the task.
*/
typedef Map<String, TaskInput> CreateTaskInputs(AnalysisTarget target);
/**
* A function that takes the target for which a task will produce results and
* returns an indication of how suitable the task is for the target. Such
* functions are passed to a [TaskDescriptor] to be used to determine their
* suitability for computing results.
*/
typedef TaskSuitability SuitabilityFor(AnalysisTarget target);
/**
* A function that converts an object of the type [B] into a [TaskInput].
* This is used, for example, by a [ListTaskInput] to create task inputs
* for each value in a list of values.
*/
typedef TaskInput<E> UnaryFunction<B, E>(B object);
/**
* An [AnalysisTarget] wrapper for an [AnalysisContext].
*/
class AnalysisContextTarget implements AnalysisTarget {
static final AnalysisContextTarget request = new AnalysisContextTarget(null);
final AnalysisContext context;
AnalysisContextTarget(this.context);
@override
Source get librarySource => null;
@override
Source get source => null;
}
/**
* An object used to compute one or more analysis results associated with a
* single target.
*
* Clients must extend this class when creating new tasks.
*/
abstract class AnalysisTask {
/**
* A queue storing the last 10 task descriptions for diagnostic purposes.
*/
static final LimitedQueue<String> LAST_TASKS = new LimitedQueue<String>(10);
/**
* A table mapping the types of analysis tasks to the number of times each
* kind of task has been performed.
*/
static final Map<Type, int> countMap = new HashMap<Type, int>();
/**
* A table mapping the types of analysis tasks to user tags used to collect
* timing data for the Observatory.
*/
static Map<Type, UserTag> tagMap = new HashMap<Type, UserTag>();
/**
* A table mapping the types of analysis tasks to stopwatches used to compute
* how much time was spent executing each kind of task.
*/
static final Map<Type, Stopwatch> stopwatchMap =
new HashMap<Type, Stopwatch>();
/**
* The context in which the task is to be performed.
*/
final AnalysisContext context;
/**
* The target for which result values are being produced.
*/
final AnalysisTarget target;
/**
* A table mapping input names to input values.
*/
Map<String, dynamic> inputs;
/**
* A table mapping result descriptors whose values are produced by this task
* to the values that were produced.
*/
Map<ResultDescriptor, dynamic> outputs =
new HashMap<ResultDescriptor, dynamic>();
/**
* The exception that was thrown while performing this task, or `null` if the
* task completed successfully.
*/
CaughtException caughtException;
/**
* If a dependency cycle was found while computing the inputs for the task,
* the set of [WorkItem]s contained in the cycle (if there are overlapping
* cycles, this is the set of all [WorkItem]s in the entire strongly
* connected component). Otherwise, `null`.
*/
List<WorkItem> dependencyCycle;
/**
* Initialize a newly created task to perform analysis within the given
* [context] in order to produce results for the given [target].
*/
AnalysisTask(this.context, this.target);
/**
* Return a textual description of this task.
*/
String get description;
/**
* Return the descriptor that describes this task.
*/
TaskDescriptor get descriptor;
/**
* Indicates whether the task is capable of handling dependency cycles. A
* task that overrides this getter to return `true` must be prepared for the
* possibility that it will be invoked with a non-`null` value of
* [dependencyCycle], and with not all of its inputs computed.
*/
bool get handlesDependencyCycles => false;
/**
* Return the value of the input with the given [name], or `null` if the input
* value is not defined.
*/
Object getOptionalInput(String name) {
if (inputs == null || !inputs.containsKey(name)) {
return null;
}
return inputs[name];
}
/**
* Return the value of the input with the given [name]. Throw an exception if
* the input value is not defined.
*/
Object/*=E*/ getRequiredInput/*<E>*/(String name) {
if (inputs == null || !inputs.containsKey(name)) {
throw new AnalysisException("Could not $description: missing $name");
}
return inputs[name] as Object/*=E*/;
}
/**
* Return the source associated with the target. Throw an exception if
* the target is not associated with a source.
*/
Source getRequiredSource() {
Source source = target.source;
if (source == null) {
throw new AnalysisException("Could not $description: missing source");
}
return source;
}
/**
* Perform this analysis task, protected by an exception handler.
*
* This method should throw an [AnalysisException] if an exception occurs
* while performing the task. If other kinds of exceptions are thrown they
* will be wrapped in an [AnalysisException].
*
* If no exception is thrown, this method must fully populate the [outputs]
* map (have a key/value pair for each result that this task is expected to
* produce).
*/
void internalPerform();
/**
* Perform this analysis task. When this method returns, either the [outputs]
* map should be fully populated (have a key/value pair for each result that
* this task is expected to produce) or the [caughtException] should be set.
*
* Clients may not override this method.
*/
void perform() {
try {
_safelyPerform();
} on AnalysisException catch (exception, stackTrace) {
caughtException = new CaughtException(exception, stackTrace);
AnalysisEngine.instance.logger
.logError("Task failed: $description", caughtException);
}
}
@override
String toString() => description;
/**
* Given a strongly connected component, find and return a list of
* [TargetedResult]s that describes a cyclic path within the cycle. Returns
* null if no cyclic path is found.
*/
List<TargetedResult> _findCyclicPath(List<WorkItem> cycle) {
WorkItem findInCycle(AnalysisTarget target, ResultDescriptor descriptor) {
for (WorkItem item in cycle) {
if (target == item.target && descriptor == item.spawningResult) {
return item;
}
}
return null;
}
HashSet<WorkItem> active = new HashSet<WorkItem>();
List<TargetedResult> path = null;
bool traverse(WorkItem item) {
if (!active.add(item)) {
// We've found a cycle
path = <TargetedResult>[];
return true;
}
for (TargetedResult result in item.inputTargetedResults) {
WorkItem item = findInCycle(result.target, result.result);
// Ignore edges that leave the cycle.
if (item != null) {
if (traverse(item)) {
// This edge is in a cycle (or leads to a cycle) so add it to the
// path
path.add(result);
return true;
}
}
}
// There was no cycle.
return false;
}
if (cycle.length > 0) {
traverse(cycle[0]);
}
return path;
}
/**
* Perform this analysis task, ensuring that all exceptions are wrapped in an
* [AnalysisException].
*
* Clients may not override this method.
*/
void _safelyPerform() {
try {
//
// Store task description for diagnostics.
//
LAST_TASKS.add(description);
//
// Report that this task is being performed.
//
String contextName = context.name;
if (contextName == null) {
contextName = 'unnamed';
}
AnalysisEngine.instance.instrumentationService
.logAnalysisTask(contextName, this);
//
// Gather statistics on the performance of the task.
//
int count = countMap[runtimeType];
countMap[runtimeType] = count == null ? 1 : count + 1;
// UserTag tag = tagMap.putIfAbsent(
// runtimeType, () => new UserTag(runtimeType.toString()));
Stopwatch stopwatch = stopwatchMap[runtimeType];
if (stopwatch == null) {
stopwatch = new Stopwatch();
stopwatchMap[runtimeType] = stopwatch;
}
// UserTag previousTag = tag.makeCurrent();
// try {
stopwatch.start();
//
// Actually perform the task.
//
try {
if (dependencyCycle != null && !handlesDependencyCycles) {
throw new InfiniteTaskLoopException(
this, dependencyCycle, _findCyclicPath(dependencyCycle));
}
internalPerform();
} finally {
stopwatch.stop();
}
// } finally {
// previousTag.makeCurrent();
// }
} on AnalysisException {
rethrow;
} on ModificationTimeMismatchError {
rethrow;
} catch (exception, stackTrace) {
throw new AnalysisException(
'Unexpected exception while performing $description',
new CaughtException(exception, stackTrace));
}
}
}
/**
* A description of a [List]-based analysis result that can be computed by an
* [AnalysisTask].
*
* Clients may not extend, implement or mix-in this class.
*/
abstract class ListResultDescriptor<E> implements ResultDescriptor<List<E>> {
/**
* Initialize a newly created analysis result to have the given [name] and
* [defaultValue]. If a [cachingPolicy] is provided, it will control how long
* values associated with this result will remain in the cache.
*/
factory ListResultDescriptor(String name, List<E> defaultValue,
{ResultCachingPolicy<List<E>> cachingPolicy}) =
ListResultDescriptorImpl<E>;
@override
ListTaskInput<E> of(AnalysisTarget target, {bool flushOnAccess: false});
}
/**
* A description of an input to an [AnalysisTask] that can be used to compute
* that input.
*
* Clients may not extend, implement or mix-in this class.
*/
abstract class ListTaskInput<E> implements TaskInput<List<E>> {
/**
* Return a task input that can be used to compute a flatten list whose
* elements are combined [subListResult]'s associated with those elements.
*/
ListTaskInput/*<V>*/ toFlattenListOf/*<V>*/(
ListResultDescriptor/*<V>*/ subListResult);
/**
* Return a task input that can be used to compute a list whose elements are
* the result of passing the elements of this input to the [mapper] function.
*/
ListTaskInput/*<V>*/ toList/*<V>*/(UnaryFunction<E, dynamic/*=V*/ > mapper);
/**
* Return a task input that can be used to compute a list whose elements are
* [valueResult]'s associated with those elements.
*/
ListTaskInput/*<V>*/ toListOf/*<V>*/(ResultDescriptor/*<V>*/ valueResult);
/**
* Return a task input that can be used to compute a map whose keys are the
* elements of this input and whose values are the result of passing the
* corresponding key to the [mapper] function.
*/
MapTaskInput<E, dynamic/*=V*/ > toMap/*<V>*/(
UnaryFunction<E, dynamic/*=V*/ > mapper);
/**
* Return a task input that can be used to compute a map whose keys are the
* elements of this input and whose values are the [valueResult]'s associated
* with those elements.
*/
MapTaskInput<AnalysisTarget, dynamic/*=V*/ > toMapOf/*<V>*/(
ResultDescriptor/*<V>*/ valueResult);
}
/**
* A description of an input with a [Map] based values.
*
* Clients may not extend, implement or mix-in this class.
*/
abstract class MapTaskInput<K, V> implements TaskInput<Map<K, V>> {
/**
* [V] must be a [List].
* Return a task input that can be used to compute a list whose elements are
* the result of passing keys [K] and the corresponding elements of [V] to
* the [mapper] function.
*/
TaskInput<List/*<E>*/ > toFlattenList/*<E>*/(
BinaryFunction<K, dynamic /*element of V*/, dynamic/*=E*/ > mapper);
}
/**
* Instances of this class are thrown when a task detects that the modification
* time of a cache entry is not the same as the actual modification time. This
* means that any analysis results based on the content of the target cannot be
* used anymore and must be invalidated.
*/
class ModificationTimeMismatchError {
final Source source;
ModificationTimeMismatchError(this.source);
}
/**
* A policy object that can compute sizes of results and provide the maximum
* active and idle sizes that can be kept in the cache.
*
* All the [ResultDescriptor]s with the same [ResultCachingPolicy] instance
* share the same total size in a cache.
*
* Clients may implement this class when implementing plugins.
*/
abstract class ResultCachingPolicy<T> {
/**
* Return the maximum total size of results that can be kept in the cache
* while analysis is in progress.
*/
int get maxActiveSize;
/**
* Return the maximum total size of results that can be kept in the cache
* while analysis is idle.
*/
int get maxIdleSize;
/**
* Return the size of the given [object].
*/
int measure(T object);
}
/**
* A description of an analysis result that can be computed by an [AnalysisTask].
*
* Clients may not extend, implement or mix-in this class.
*/
abstract class ResultDescriptor<V> {
/**
* A comparator that can be used to sort result descriptors by their name.
*/
static final Comparator<ResultDescriptor> SORT_BY_NAME =
(ResultDescriptor first, ResultDescriptor second) =>
first.name.compareTo(second.name);
/**
* Initialize a newly created analysis result to have the given [name] and
* [defaultValue].
*
* The given [cachingPolicy] is used to limit the total size of results
* described by this descriptor. If no policy is specified, the results are
* never evicted from the cache, and removed only when they are invalidated.
*/
factory ResultDescriptor(String name, V defaultValue,
{ResultCachingPolicy<V> cachingPolicy}) = ResultDescriptorImpl<V>;
/**
* Return the caching policy for results described by this descriptor.
*/
ResultCachingPolicy<V> get cachingPolicy;
/**
* Return the default value for results described by this descriptor.
*/
V get defaultValue;
/**
* Return the name of this descriptor.
*/
String get name;
/**
* Return a task input that can be used to compute this result for the given
* [target]. If [flushOnAccess] is `true` then the value of this result that
* is associated with the [target] will be flushed when it is accessed.
*/
TaskInput<V> of(AnalysisTarget target, {bool flushOnAccess: false});
}
/**
* A specification of the given [result] for the given [target].
*
* Clients may not extend, implement or mix-in this class.
*/
class TargetedResult {
/**
* An empty list of results.
*/
static final List<TargetedResult> EMPTY_LIST = const <TargetedResult>[];
/**
* The target with which the result is associated.
*/
final AnalysisTarget target;
/**
* The result associated with the target.
*/
final ResultDescriptor result;
/**
* Initialize a new targeted result.
*/
TargetedResult(this.target, this.result);
@override
int get hashCode {
return JenkinsSmiHash.combine(target.hashCode, result.hashCode);
}
@override
bool operator ==(other) {
return other is TargetedResult &&
other.target == target &&
other.result == result;
}
@override
String toString() => '$result for $target';
}
/**
* A description of an [AnalysisTask].
*
* Clients may not extend, implement or mix-in this class.
*/
abstract class TaskDescriptor {
/**
* Initialize a newly created task descriptor to have the given [name] and to
* describe a task that takes the inputs built using the given [inputBuilder],
* and produces the given [results]. The [buildTask] function will be used to
* create the instance of [AnalysisTask] being described. If provided, the
* [isAppropriateFor] function will be used to determine whether the task can
* be used on a specific target.
*/
factory TaskDescriptor(String name, BuildTask buildTask,
CreateTaskInputs inputBuilder, List<ResultDescriptor> results,
{SuitabilityFor suitabilityFor}) = TaskDescriptorImpl;
/**
* Return the builder used to build the inputs to the task.
*/
CreateTaskInputs get createTaskInputs;
/**
* Return the name of the task being described.
*/
String get name;
/**
* Return a list of the analysis results that will be computed by this task.
*/
List<ResultDescriptor> get results;
/**
* Create and return a task that is described by this descriptor that can be
* used to compute results based on the given [inputs].
*/
AnalysisTask createTask(AnalysisContext context, AnalysisTarget target,
Map<String, dynamic> inputs);
/**
* Return an indication of how suitable this task is for the given [target].
*/
TaskSuitability suitabilityFor(AnalysisTarget target);
}
/**
* A description of an input to an [AnalysisTask] that can be used to compute
* that input.
*
* Clients may not extend, implement or mix-in this class.
*/
abstract class TaskInput<V> {
/**
* Create and return a builder that can be used to build this task input.
*/
TaskInputBuilder<V> createBuilder();
/**
* Return a task input that can be used to compute a list whose elements are
* the result of passing the result of this input to the [mapper] function.
*/
ListTaskInput/*<E>*/ mappedToList/*<E>*/(List/*<E>*/ mapper(V value));
}
/**
* An object used to build the value associated with a single [TaskInput].
*
* All builders work by requesting one or more results (each result being
* associated with a target). The interaction pattern is modeled after the class
* [Iterator], in which the method [moveNext] is invoked to move from one result
* request to the next. The getters [currentResult] and [currentTarget] are used
* to get the result and target of the current request. The value of the result
* must be supplied using the [currentValue] setter before [moveNext] can be
* invoked to move to the next request. When [moveNext] returns `false`,
* indicating that there are no more requests, the method [inputValue] can be
* used to access the value of the input that was built.
*
* Clients may not extend, implement or mix-in this class.
*/
abstract class TaskInputBuilder<V> {
/**
* Return the result that needs to be computed, or `null` if [moveNext] has
* not been invoked or if the last invocation of [moveNext] returned `false`.
*/
ResultDescriptor get currentResult;
/**
* Return the target for which the result needs to be computed, or `null` if
* [moveNext] has not been invoked or if the last invocation of [moveNext]
* returned `false`.
*/
AnalysisTarget get currentTarget;
/**
* Set the [value] that was computed for the current result.
*
* Throws a [StateError] if [moveNext] has not been invoked or if the last
* invocation of [moveNext] returned `false`.
*/
void set currentValue(Object value);
/**
* Return `true` if the value accessed by this input builder should be flushed
* from the cache at the time it is retrieved.
*/
bool get flushOnAccess;
/**
* Return the [value] that was computed by this builder.
*
* Throws a [StateError] if [moveNext] has not been invoked or if the last
* invocation of [moveNext] returned `true`.
*
* Returns `null` if no value could be computed due to a circular dependency.
*/
V get inputValue;
/**
* Record that no value is available for the current result, due to a
* circular dependency.
*
* Throws a [StateError] if [moveNext] has not been invoked or if the last
* invocation of [moveNext] returned `false`.
*/
void currentValueNotAvailable();
/**
* Move to the next result that needs to be computed in order to build the
* inputs for a task. Return `true` if there is another result that needs to
* be computed, or `false` if the inputs have been computed.
*
* It is safe to invoke [moveNext] after it has returned `false`. In this case
* [moveNext] has no effect and will again return `false`.
*
* Throws a [StateError] if the value of the current result has not been
* provided using [currentValue].
*/
bool moveNext();
}
/**
* An indication of how suitable a task is for a given target.
*/
enum TaskSuitability { NONE, LOWEST, HIGHEST }
/**
* [WorkManager]s are used to drive analysis.
*
* They know specific of the targets and results they care about,
* so they can request analysis results in optimal order.
*
* Clients may implement this class when implementing plugins.
*/
abstract class WorkManager {
/**
* Notifies the manager about changes in the explicit source list.
*/
void applyChange(List<Source> addedSources, List<Source> changedSources,
List<Source> removedSources);
/**
* Notifies the managers that the given set of priority [targets] was set.
*/
void applyPriorityTargets(List<AnalysisTarget> targets);
/**
* Return a list of all of the errors associated with the given [source].
* The list of errors will be empty if the source is not known to the context
* or if there are no errors in the source. The errors contained in the list
* can be incomplete.
*/
List<AnalysisError> getErrors(Source source);
/**
* Return the next [TargetedResult] that this work manager wants to be
* computed, or `null` if this manager doesn't need any new results.
*
* Note, that it is not guaranteed that this result will be computed, it is
* up to the work manager to check whether the result is already computed
* (for example during the next [getNextResult] invocation) or computation
* of the same result should be requested again.
*/
TargetedResult getNextResult();
/**
* Return the priority if the next work order this work manager want to be
* computed. The [AnalysisDriver] will perform the work order with
* the highest priority.
*
* Even if the returned value is [WorkOrderPriority.NONE], it still does not
* guarantee that [getNextResult] will return not `null`.
*/
WorkOrderPriority getNextResultPriority();
/**
* Notifies the manager about analysis options changes.
*/
void onAnalysisOptionsChanged();
/**
* Notifies the manager about [SourceFactory] changes.
*/
void onSourceFactoryChanged();
/**
* Notifies the manager that the given [outputs] were produced for
* the given [target].
*/
void resultsComputed(
AnalysisTarget target, Map<ResultDescriptor, dynamic> outputs);
}
/**
* The priorities of work orders returned by [WorkManager]s.
*
* New priorities may be added with time, clients need to tolerate this.
*/
enum WorkOrderPriority {
/**
* Responding to an user's action.
*/
INTERACTIVE,
/**
* Computing information for priority sources.
*/
PRIORITY,
/**
* A work should be done, but without any special urgency.
*/
NORMAL,
/**
* Nothing to do.
*/
NONE
}