blob: 5f7e1499a3c3509ae88dd943a9bdaf8480745d9f [file] [log] [blame]
// Copyright (c) 2014, 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.
import 'dart:collection';
import 'package:analyzer/dart/ast/ast.dart' show AstNode;
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:front_end/src/fasta/fasta_codes.dart' show Message;
import 'package:source_span/source_span.dart';
/**
* An object that listen for [AnalysisError]s being produced by the analysis
* engine.
*/
abstract class AnalysisErrorListener {
/**
* An error listener that ignores errors that are reported to it.
*/
static final AnalysisErrorListener NULL_LISTENER = new _NullErrorListener();
/**
* This method is invoked when an [error] has been found by the analysis
* engine.
*/
void onError(AnalysisError error);
}
/**
* An [AnalysisErrorListener] that keeps track of whether any error has been
* reported to it.
*/
class BooleanErrorListener implements AnalysisErrorListener {
/**
* A flag indicating whether an error has been reported to this listener.
*/
bool _errorReported = false;
/**
* Return `true` if an error has been reported to this listener.
*/
bool get errorReported => _errorReported;
@override
void onError(AnalysisError error) {
_errorReported = true;
}
}
/**
* An object used to create analysis errors and report then to an error
* listener.
*/
class ErrorReporter {
/**
* The error listener to which errors will be reported.
*/
final AnalysisErrorListener _errorListener;
/**
* The default source to be used when reporting errors.
*/
final Source _defaultSource;
/**
* The source to be used when reporting errors.
*/
Source _source;
/**
* Initialize a newly created error reporter that will report errors to the
* given [_errorListener]. Errors will be reported against the
* [_defaultSource] unless another source is provided later.
*/
ErrorReporter(this._errorListener, this._defaultSource) {
if (_errorListener == null) {
throw new ArgumentError("An error listener must be provided");
} else if (_defaultSource == null) {
throw new ArgumentError("A default source must be provided");
}
this._source = _defaultSource;
}
Source get source => _source;
/**
* Set the source to be used when reporting errors to the given [source].
* Setting the source to `null` will cause the default source to be used.
*/
void set source(Source source) {
this._source = source ?? _defaultSource;
}
/**
* Report the given [error].
*/
void reportError(AnalysisError error) {
_errorListener.onError(error);
}
/**
* Report an error with the given [errorCode] and [arguments]. The [element]
* is used to compute the location of the error.
*/
void reportErrorForElement(ErrorCode errorCode, Element element,
[List<Object> arguments]) {
int length = 0;
if (element is ImportElement) {
length = 6; // 'import'.length
} else if (element is ExportElement) {
length = 6; // 'export'.length
} else {
length = element.nameLength;
}
reportErrorForOffset(errorCode, element.nameOffset, length, arguments);
}
/**
* Report an error with the given [errorCode] and [arguments].
* The [node] is used to compute the location of the error.
*
* If the arguments contain the names of two or more types, the method
* [reportTypeErrorForNode] should be used and the types
* themselves (rather than their names) should be passed as arguments.
*/
void reportErrorForNode(ErrorCode errorCode, AstNode node,
[List<Object> arguments]) {
reportErrorForOffset(errorCode, node.offset, node.length, arguments);
}
/**
* Report an error with the given [errorCode] and [arguments]. The location of
* the error is specified by the given [offset] and [length].
*/
void reportErrorForOffset(ErrorCode errorCode, int offset, int length,
[List<Object> arguments]) {
_errorListener.onError(
new AnalysisError(_source, offset, length, errorCode, arguments));
}
/**
* Report an error with the given [errorCode] and [arguments]. The location of
* the error is specified by the given [span].
*/
void reportErrorForSpan(ErrorCode errorCode, SourceSpan span,
[List<Object> arguments]) {
reportErrorForOffset(errorCode, span.start.offset, span.length, arguments);
}
/**
* Report an error with the given [errorCode] and [arguments]. The [token] is
* used to compute the location of the error.
*/
void reportErrorForToken(ErrorCode errorCode, Token token,
[List<Object> arguments]) {
reportErrorForOffset(errorCode, token.offset, token.length, arguments);
}
/**
* Report an error with the given [errorCode] and [message]. The location of
* the error is specified by the given [offset] and [length].
*/
void reportErrorMessage(
ErrorCode errorCode, int offset, int length, Message message) {
_errorListener.onError(new AnalysisError.forValues(
_source, offset, length, errorCode, message.message, message.tip));
}
/**
* Report an error with the given [errorCode] and [arguments]. The [node] is
* used to compute the location of the error. The arguments are expected to
* contain two or more types. Convert the types into strings by using the
* display names of the types, unless there are two or more types with the
* same names, in which case the extended display names of the types will be
* used in order to clarify the message.
*
* If there are not two or more types in the argument list, the method
* [reportErrorForNode] should be used instead.
*/
void reportTypeErrorForNode(
ErrorCode errorCode, AstNode node, List<Object> arguments) {
_convertTypeNames(arguments);
reportErrorForOffset(errorCode, node.offset, node.length, arguments);
}
/**
* Given an array of [arguments] that is expected to contain two or more
* types, convert the types into strings by using the display names of the
* types, unless there are two or more types with the same names, in which
* case the extended display names of the types will be used in order to
* clarify the message.
*/
void _convertTypeNames(List<Object> arguments) {
String computeDisplayName(DartType type) {
if (type is FunctionType) {
String name = type.name;
if (name != null && name.isNotEmpty) {
StringBuffer buffer = new StringBuffer();
buffer.write(name);
(type as TypeImpl).appendTo(buffer, new Set.identity());
return buffer.toString();
}
}
return type.displayName;
}
Map<String, List<_TypeToConvert>> typeGroups = {};
for (int i = 0; i < arguments.length; i++) {
Object argument = arguments[i];
if (argument is DartType) {
String displayName = computeDisplayName(argument);
List<_TypeToConvert> types =
typeGroups.putIfAbsent(displayName, () => <_TypeToConvert>[]);
types.add(new _TypeToConvert(i, argument, displayName));
}
}
for (List<_TypeToConvert> typeGroup in typeGroups.values) {
if (typeGroup.length == 1) {
_TypeToConvert typeToConvert = typeGroup[0];
if (typeToConvert.type is DartType) {
arguments[typeToConvert.index] = typeToConvert.displayName;
}
} else {
Map<String, Set<Element>> nameToElementMap = {};
for (_TypeToConvert typeToConvert in typeGroup) {
for (Element element in typeToConvert.allElements()) {
Set<Element> elements = nameToElementMap.putIfAbsent(
element.name, () => new Set<Element>());
elements.add(element);
}
}
for (_TypeToConvert typeToConvert in typeGroup) {
Element element = typeToConvert.type.element;
if (element == null) {
arguments[typeToConvert.index] = typeToConvert.displayName;
} else {
// TODO(brianwilkerson) When analyzer supports info or context
// messages, expose the additional information that way (rather
// than being poorly inserted into the problem message).
StringBuffer buffer = new StringBuffer();
for (Element element in typeToConvert.allElements()) {
String name = element.name;
if (nameToElementMap[name].length > 1) {
buffer.write(buffer.isEmpty ? 'where ' : ', ');
buffer.write('$name is defined in ${element.source.fullName}');
}
}
arguments[typeToConvert.index] =
'${typeToConvert.displayName} ($buffer)';
}
}
}
}
}
}
/**
* An error listener that will record the errors that are reported to it in a
* way that is appropriate for caching those errors within an analysis context.
*/
class RecordingErrorListener implements AnalysisErrorListener {
Set<AnalysisError> _errors;
/**
* Return the errors collected by the listener.
*/
List<AnalysisError> get errors {
if (_errors == null) {
return const <AnalysisError>[];
}
return _errors.toList();
}
/**
* Return the errors collected by the listener for the given [source].
*/
List<AnalysisError> getErrorsForSource(Source source) {
if (_errors == null) {
return const <AnalysisError>[];
}
return _errors.where((error) => error.source == source).toList();
}
@override
void onError(AnalysisError error) {
_errors ??= new HashSet<AnalysisError>();
_errors.add(error);
}
}
/**
* An [AnalysisErrorListener] that ignores error.
*/
class _NullErrorListener implements AnalysisErrorListener {
@override
void onError(AnalysisError event) {
// Ignore errors
}
}
/**
* Used by `ErrorReporter._convertTypeNames` to keep track of a type that is
* being converted.
*/
class _TypeToConvert {
final int index;
final DartType type;
final String displayName;
List<Element> _allElements;
_TypeToConvert(this.index, this.type, this.displayName);
List<Element> allElements() {
if (_allElements == null) {
Set<Element> elements = new Set<Element>();
void addElementsFrom(DartType type) {
Element element = type?.element;
if (element != null) {
if (type is InterfaceType && elements.add(element)) {
for (DartType typeArgument in type.typeArguments) {
addElementsFrom(typeArgument);
}
} else if (type is FunctionType && elements.add(element)) {
addElementsFrom(type.returnType);
for (DartType typeArgument in type.typeArguments) {
addElementsFrom(typeArgument);
}
for (ParameterElement parameter in type.parameters) {
addElementsFrom(parameter.type);
}
}
}
}
addElementsFrom(type);
_allElements = elements
.where((element) => element.name != null && element.name.isNotEmpty)
.toList();
}
return _allElements;
}
}