blob: 4c1b8b81b511ee504b87d06e21dad68f4744dc89 [file]
// Copyright (c) 2019, 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:_fe_analyzer_shared/src/base/errors.dart';
import 'package:analyzer/source/source.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart'
show DiagnosticMessage, DiagnosticMessageImpl;
export 'package:analyzer/src/diagnostic/diagnostic.dart' show DiagnosticMessage;
/// A diagnostic, as defined by the [Diagnostic Design Guidelines][guidelines]:
///
/// > An indication of a specific problem at a specific location within the
/// > source code being processed by a development tool.
///
/// Clients may not extend, implement or mix-in this class.
///
/// [guidelines]: https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/doc/implementation/diagnostics.md
class Diagnostic {
/// The diagnostic code associated with the diagnostic.
final DiagnosticCode diagnosticCode;
/// A list of messages that provide context for understanding the problem
/// being reported. The list will be empty if there are no such messages.
final List<DiagnosticMessage> contextMessages;
/// A description of how to fix the problem, or `null` if there is no such
/// description.
final String? correctionMessage;
/// A message describing what is wrong and why.
final DiagnosticMessage problemMessage;
/// The source in which the diagnostic occurred, or `null` if unknown.
final Source source;
Diagnostic.forValues({
required this.source,
required int offset,
required int length,
required this.diagnosticCode,
required String message,
this.correctionMessage,
this.contextMessages = const [],
}) : problemMessage = DiagnosticMessageImpl(
filePath: source.fullName,
length: length,
message: message,
offset: offset,
url: null,
);
/// Initialize a newly created diagnostic.
///
/// The diagnostic is associated with the given [source] and is located at the
/// given [offset] with the given [length]. The diagnostic will have the given
/// [diagnosticCode] and the list of [arguments] will be used to complete the
/// message and correction. If any [contextMessages] are provided, they will
/// be recorded with the diagnostic.
factory Diagnostic.tmp({
required Source source,
required int offset,
required int length,
required DiagnosticCode diagnosticCode,
List<Object?> arguments = const [],
List<DiagnosticMessage> contextMessages = const [],
}) {
assert(
arguments.length == diagnosticCode.numParameters,
'Message $diagnosticCode requires ${diagnosticCode.numParameters} '
'argument${diagnosticCode.numParameters == 1 ? '' : 's'}, but '
'${arguments.length} '
'argument${arguments.length == 1 ? ' was' : 's were'} '
'provided',
);
String message = formatList(diagnosticCode.problemMessage, arguments);
String? correctionTemplate = diagnosticCode.correctionMessage;
String? correctionMessage;
if (correctionTemplate != null) {
correctionMessage = formatList(correctionTemplate, arguments);
}
return Diagnostic.forValues(
source: source,
offset: offset,
length: length,
diagnosticCode: diagnosticCode,
message: message,
correctionMessage: correctionMessage,
contextMessages: contextMessages,
);
}
/// The template used to create the correction to be displayed for this
/// diagnostic, or `null` if there is no correction information for this
/// error. The correction should indicate how the user can fix the error.
@Deprecated("Use 'correctionMessage' instead.")
String? get correction => correctionMessage;
@Deprecated("Use 'diagnosticCode' instead")
DiagnosticCode get errorCode => diagnosticCode;
@override
int get hashCode {
int hashCode = offset;
hashCode ^= message.hashCode;
hashCode ^= source.hashCode;
return hashCode;
}
/// The number of characters from the offset to the end of the source which
/// encompasses the compilation error.
int get length => problemMessage.length;
/// The message to be displayed for this diagnostic.
///
/// The message indicates what is wrong and why it is wrong.
String get message => problemMessage.messageText(includeUrl: true);
/// The character offset from the beginning of the source (zero based) where
/// the diagnostic occurred.
int get offset => problemMessage.offset;
Severity get severity {
switch (diagnosticCode.severity) {
case DiagnosticSeverity.ERROR:
return Severity.error;
case DiagnosticSeverity.WARNING:
return Severity.warning;
case DiagnosticSeverity.INFO:
return Severity.info;
default:
throw StateError('Invalid severity: ${diagnosticCode.severity}');
}
}
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
// prepare the other Diagnostic.
if (other is Diagnostic) {
// Quick checks.
if (!identical(diagnosticCode, other.diagnosticCode)) {
return false;
}
if (offset != other.offset || length != other.length) {
return false;
}
// Deep checks.
if (message != other.message) {
return false;
}
if (source != other.source) {
return false;
}
return true;
}
return false;
}
@override
String toString() {
StringBuffer buffer = StringBuffer();
buffer.write(source.fullName);
buffer.write("(");
buffer.write(offset);
buffer.write("..");
buffer.write(offset + length - 1);
buffer.write("): ");
buffer.write(message);
return buffer.toString();
}
}
/// An indication of the severity of a [Diagnostic].
enum Severity { error, warning, info }