blob: 08963223861768a069f30217eb0412045bb2869c [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 'package:analyzer/dart/ast/ast.dart'
show AstNode, ConstructorDeclaration;
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/source/source.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';
/// An object that listens 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 = _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 source to be used when reporting errors.
final Source _source;
/// The lock level, if greater than zero, no errors will be reported.
/// This is used to prevent reporting errors inside comments.
@internal
int lockLevel = 0;
/// 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._source, {
@Deprecated('Will be removed') bool isNonNullableByDefault = true,
});
Source get source => _source;
/// Report a diagnostic with the given [errorCode] and [arguments].
/// The location of the diagnostic will be the name of the [node].
void atConstructorDeclaration(
ConstructorDeclaration node,
ErrorCode errorCode, {
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
}) {
// TODO(brianwilkerson): Consider extending this method to take any
// declaration and compute the correct range for the name of that
// declaration. This might make it easier to be consistent.
if (node.name case var nameToken?) {
var offset = node.returnType.offset;
atOffset(
offset: offset,
length: nameToken.end - offset,
errorCode: errorCode,
arguments: arguments,
);
} else {
atNode(
node.returnType,
errorCode,
arguments: arguments,
);
}
}
/// Report an error with the given [errorCode] and [arguments].
/// The [element] is used to compute the location of the error.
void atElement(
Element element,
ErrorCode errorCode, {
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
}) {
var nonSynthetic = element.nonSynthetic;
atOffset(
errorCode: errorCode,
offset: nonSynthetic.nameOffset,
length: nonSynthetic.nameLength,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// Report an error with the given [errorCode] and [arguments].
/// The [entity] is used to compute the location of the error.
void atEntity(
SyntacticEntity entity,
ErrorCode errorCode, {
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
}) {
atOffset(
errorCode: errorCode,
offset: entity.offset,
length: entity.length,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// Report an error with the given [errorCode] and [arguments].
/// The [node] is used to compute the location of the error.
void atNode(
AstNode node,
ErrorCode errorCode, {
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
}) {
atOffset(
errorCode: errorCode,
offset: node.offset,
length: node.length,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// Report an error with the given [errorCode] and [arguments]. The location
/// of the error is specified by the given [offset] and [length].
void atOffset({
required int offset,
required int length,
required ErrorCode errorCode,
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
}) {
if (lockLevel != 0) {
return;
}
_convertElements(arguments);
contextMessages ??= [];
contextMessages.addAll(_convertTypeNames(arguments));
_errorListener.onError(
AnalysisError.tmp(
source: _source,
offset: offset,
length: length,
errorCode: errorCode,
arguments: arguments ?? const [],
contextMessages: contextMessages,
data: data,
),
);
}
/// Report an error with the given [errorCode] and [arguments].
/// The [span] is used to compute the location of the error.
void atSourceSpan(
SourceSpan span,
ErrorCode errorCode, {
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
}) {
atOffset(
errorCode: errorCode,
offset: span.start.offset,
length: span.length,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// Report an error with the given [errorCode] and [arguments]. The [token] is
/// used to compute the location of the error.
void atToken(
Token token,
ErrorCode errorCode, {
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
}) {
atOffset(
errorCode: errorCode,
offset: token.offset,
length: token.length,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// 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.
@Deprecated('Use atElement() instead')
void reportErrorForElement(ErrorCode errorCode, Element element,
[List<Object>? arguments, List<DiagnosticMessage>? messages]) {
atElement(
element,
errorCode,
arguments: arguments,
contextMessages: messages,
);
}
/// Report an error with the given [errorCode] and [arguments].
/// The [node] is used to compute the location of the error.
@Deprecated('Use atNode() instead')
void reportErrorForNode(
ErrorCode errorCode,
AstNode node, [
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
]) {
atNode(
node,
errorCode,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// Report an error with the given [errorCode] and [arguments]. The location
/// of the error is specified by the given [offset] and [length].
@Deprecated('Use atOffset() instead')
void reportErrorForOffset(
ErrorCode errorCode,
int offset,
int length, [
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
]) {
atOffset(
offset: offset,
length: length,
errorCode: errorCode,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// Report an error with the given [errorCode] and [arguments]. The location
/// of the error is specified by the given [span].
@Deprecated('Use atSourceSpan() instead')
void reportErrorForSpan(
ErrorCode errorCode,
SourceSpan span, [
List<Object>? arguments,
]) {
atSourceSpan(
span,
errorCode,
arguments: arguments,
);
}
/// Report an error with the given [errorCode] and [arguments]. The [token] is
/// used to compute the location of the error.
@Deprecated('Use atToken() instead')
void reportErrorForToken(
ErrorCode errorCode,
Token token, [
List<Object>? arguments,
List<DiagnosticMessage>? contextMessages,
Object? data,
]) {
atToken(
token,
errorCode,
arguments: arguments,
contextMessages: contextMessages,
data: data,
);
}
/// 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
/// [atNode] should be used instead.
@Deprecated('Use reportErrorForNode(), it will convert types as well')
void reportTypeErrorForNode(
ErrorCode errorCode, AstNode node, List<Object> arguments) {
atOffset(
offset: node.offset,
length: node.length,
errorCode: errorCode,
arguments: arguments,
);
}
/// Convert all [Element]s in the [arguments] into their display strings.
void _convertElements(List<Object>? arguments) {
if (arguments == null) {
return;
}
for (var i = 0; i < arguments.length; i++) {
var argument = arguments[i];
if (argument is Element) {
arguments[i] = argument.getDisplayString();
} else if (!(argument is String ||
argument is DartType ||
argument is int ||
argument is Uri)) {
throw ArgumentError(
'Tried to format an error using ${argument.runtimeType}');
}
}
}
/// 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.
List<DiagnosticMessage> _convertTypeNames(List<Object?>? arguments) {
var messages = <DiagnosticMessage>[];
if (arguments == null) {
return messages;
}
Map<String, List<_TypeToConvert>> typeGroups = {};
for (int i = 0; i < arguments.length; i++) {
var argument = arguments[i];
if (argument is TypeImpl) {
String displayName = argument.getDisplayString(
preferTypeAlias: true,
);
List<_TypeToConvert> types =
typeGroups.putIfAbsent(displayName, () => <_TypeToConvert>[]);
types.add(_TypeToConvert(i, argument, displayName));
}
}
for (List<_TypeToConvert> typeGroup in typeGroups.values) {
if (typeGroup.length == 1) {
_TypeToConvert typeToConvert = typeGroup[0];
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!, () => <Element>{});
elements.add(element);
}
}
for (_TypeToConvert typeToConvert in typeGroup) {
// TODO(brianwilkerson): When clients do a better job of displaying
// context messages, remove the extra text added to the buffer.
StringBuffer? buffer;
for (Element element in typeToConvert.allElements()) {
String name = element.name!;
if (nameToElementMap[name]!.length > 1) {
if (buffer == null) {
buffer = StringBuffer();
buffer.write('where ');
} else {
buffer.write(', ');
}
buffer.write('$name is defined in ${element.source!.fullName}');
}
messages.add(DiagnosticMessageImpl(
filePath: element.source!.fullName,
length: element.nameLength,
message: '$name is defined in ${element.source!.fullName}',
offset: element.nameOffset,
url: null));
}
if (buffer != null) {
arguments[typeToConvert.index] =
'${typeToConvert.displayName} ($buffer)';
} else {
arguments[typeToConvert.index] = typeToConvert.displayName;
}
}
}
}
return messages;
}
}
/// 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 [];
}
return _errors!.toList();
}
/// Return the errors collected by the listener for the given [source].
List<AnalysisError> getErrorsForSource(Source source) {
if (_errors == null) {
return const [];
}
return _errors!.where((error) => error.source == source).toList();
}
@override
void onError(AnalysisError error) {
(_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 = <Element>{};
void addElementsFrom(DartType type) {
if (type is FunctionType) {
addElementsFrom(type.returnType);
for (ParameterElement parameter in type.parameters) {
addElementsFrom(parameter.type);
}
} else if (type is InterfaceType) {
if (elements.add(type.element)) {
for (DartType typeArgument in type.typeArguments) {
addElementsFrom(typeArgument);
}
}
}
}
addElementsFrom(type);
_allElements = elements.where((element) {
var name = element.name;
return name != null && name.isNotEmpty;
}).toList();
}
return _allElements!;
}
}