blob: a0d4624101a57149ecdecbaaff8dbc902d08ac3d [file]
// Copyright (c) 2025, 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.
/// @docImport 'package:analyzer/diagnostic/diagnostic.dart';
/// @docImport 'package:analyzer/error/listener.dart';
library;
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
import 'package:_fe_analyzer_shared/src/base/errors.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:source_span/source_span.dart';
part 'package:analyzer/src/diagnostic/diagnostic.g.dart';
/// Private subtype of [DiagnosticCode] that supports runtime checking of
/// parameter types.
class DiagnosticCodeWithExpectedTypes extends DiagnosticCodeImpl {
final List<ExpectedType>? expectedTypes;
const DiagnosticCodeWithExpectedTypes({
super.correctionMessage,
super.hasPublishedDocs = false,
super.isUnresolvedIdentifier = false,
required super.name,
required super.problemMessage,
required super.type,
required super.uniqueName,
this.expectedTypes,
});
}
/// A single message associated with a [Diagnostic], consisting of the text of
/// the message and the location associated with it.
///
/// Clients may not extend, implement or mix-in this class.
@AnalyzerPublicApi(
message: 'Exported by package:analyzer/diagnostic/diagnostic.dart',
)
abstract class DiagnosticMessage {
/// The absolute and normalized path of the file associated with this message.
String get filePath;
/// The length of the source range associated with this message.
int get length;
/// The zero-based offset from the start of the file to the beginning of the
/// source range associated with this message.
int get offset;
/// The URL containing documentation about this diagnostic message, if any.
///
/// Note: this should not be confused with the location in the user's code
/// where the error was reported; that information can be obtained from
/// [filePath], [length], and [offset].
String? get url;
/// Gets the text of the message.
///
/// If [includeUrl] is `true`, and this diagnostic message has an associated
/// URL, it is included in the returned value in a human-readable way.
/// Clients that wish to present URLs as simple text can do this. If
/// [includeUrl] is `false`, no URL is included in the returned value.
/// Clients that have a special mechanism for presenting URLs (e.g. as a
/// clickable link) should do this and then consult the [url] getter to access
/// the URL.
String messageText({required bool includeUrl});
}
/// A concrete implementation of a diagnostic message.
class DiagnosticMessageImpl implements DiagnosticMessage {
@override
final String filePath;
@override
final int length;
final String _message;
@override
final int offset;
@override
final String? url;
/// Initialize a newly created message to represent a [message] reported in
/// the file at the given [filePath] at the given [offset] and with the given
/// [length].
DiagnosticMessageImpl({
required this.filePath,
required this.length,
required String message,
required this.offset,
required this.url,
}) : _message = message;
@override
String messageText({required bool includeUrl}) {
if (includeUrl && url != null) {
var result = StringBuffer(_message);
if (!_message.endsWith('.')) {
result.write('.');
}
result.write(' See $url');
return result.toString();
}
return _message;
}
}
/// Common functionality for [DiagnosticCode]-derived classes that represent
/// errors that take arguments.
///
/// This class provides a [withArguments] getter, which can be used to supply
/// arguments and produce a [LocatableDiagnostic].
///
/// Note: the type argument `T` should be instantiated with a function type. But
/// it is typed as `extends Object` in order to reduce the risk of accidental
/// dynamic invocation of [withArguments].
class DiagnosticWithArguments<T extends Object>
extends DiagnosticCodeWithExpectedTypes {
/// Function accepting named arguments and returning [LocatableDiagnostic].
///
/// The value returned by this function can
/// be associated with a location in the source code using the
/// [LocatableDiagnostic.at] method, and then the result can be passed to
/// [DiagnosticReporter.reportError].
final T withArguments;
const DiagnosticWithArguments({
required super.name,
required super.problemMessage,
super.correctionMessage,
super.hasPublishedDocs = false,
super.isUnresolvedIdentifier = false,
required super.type,
required super.uniqueName,
required super.expectedTypes,
required this.withArguments,
});
}
/// Common functionality for [DiagnosticCode]-derived classes that represent
/// errors that do not take arguments.
///
/// This class implements [LocatableDiagnostic], which means that instances can
/// be associated with a location in the source code using the [at] method, and
/// then the result can be passed to [DiagnosticReporter.reportError].
base mixin DiagnosticWithoutArguments on DiagnosticCodeImpl
implements LocatableDiagnostic {
@override
List<Object> get arguments => const [];
@override
DiagnosticCode get code => this;
@override
Iterable<DiagnosticMessage> get contextMessages => const [];
@override
LocatedDiagnostic at(SyntacticEntity node) =>
atOffset(offset: node.offset, length: node.length);
@override
LocatedDiagnostic atOffset({required int offset, required int length}) =>
LocatedDiagnostic(this, offset, length);
@override
LocatedDiagnostic atSourceRange(SourceRange sourceRange) =>
atOffset(offset: sourceRange.offset, length: sourceRange.length);
@override
LocatedDiagnostic atSourceSpan(SourceSpan span) {
var trimmedSpan = span.withoutTrailingLineTerminators;
return atOffset(
offset: trimmedSpan.start.offset,
length: trimmedSpan.length,
);
}
@override
LocatableDiagnostic withContextMessages(
Iterable<DiagnosticMessage> messages,
) => LocatableDiagnosticImpl(code, arguments, contextMessages: [...messages]);
}
/// Concrete implementation of [DiagnosticWithoutArguments], used for diagnostic
/// messages that don't take any arguments.
///
/// This needs to be a separate class from [DiagnosticWithoutArguments] because
/// [DiagnosticWithoutArguments] is a mixin.
final class DiagnosticWithoutArgumentsImpl
extends DiagnosticCodeWithExpectedTypes
with DiagnosticWithoutArguments {
const DiagnosticWithoutArgumentsImpl({
required super.name,
required super.problemMessage,
super.correctionMessage,
super.hasPublishedDocs = false,
super.isUnresolvedIdentifier = false,
required super.type,
required super.uniqueName,
super.expectedTypes,
});
}
/// Expected type of a diagnostic code's parameter.
enum ExpectedType { element, int, name, object, string, token, type, uri }
/// Interface for a diagnostic that does not have any unfilled template
/// parameters, and hence is ready to be associated with a location in the
/// source code.
///
/// This could either be the result of calling `withArguments` on a diagnostic
/// code that requires arguments, or it could be a diagnostic code that doesn't
/// require arguments.
abstract final class LocatableDiagnostic {
/// The arguments that were applied to the diagnostic, or the empty list if
/// [code] doesn't accept any arguments.
List<Object> get arguments;
/// The [DiagnosticCode] associated with the diagnostic.
DiagnosticCode get code;
/// The context messages that were applied to the diagnostic.
Iterable<DiagnosticMessage> get contextMessages;
/// Converts this diagnostic to a [LocatedDiagnostic] by applying it to a
/// syntactic entity in the source code.
///
/// The result may be passed to [DiagnosticReporter.reportError].
LocatedDiagnostic at(SyntacticEntity node);
/// Converts this diagnostic to a [LocatedDiagnostic] by applying it to a
/// location in the source code.
///
/// The result may be passed to [DiagnosticReporter.reportError].
LocatedDiagnostic atOffset({required int offset, required int length});
/// Converts this diagnostic to a [LocatedDiagnostic] by applying it to a
/// location in the source code.
///
/// The result may be passed to [DiagnosticReporter.reportError].
LocatedDiagnostic atSourceRange(SourceRange sourceRange);
/// Converts this diagnostic to a [LocatedDiagnostic] by applying it to a
/// location in the source code.
///
/// The result may be passed to [DiagnosticReporter.reportError].
LocatedDiagnostic atSourceSpan(SourceSpan span);
/// Attaches context messages to this diagnostic.
///
/// The return value is a fresh instance of [LocatableDiagnostic]. This allows
/// for a literate style of error reporting, e.g.:
/// ```dart
/// // For an diagnostic code that doesn't take arguments:
/// diagnosticReporter.reportError(
/// diagnosticCode.withContextMessages(messages).at(astNode));
///
/// // For a diagnostic code that does take arguments:
/// diagnosticReporter.reportError(
/// diagnosticCode
/// .withArguments(...)
/// .withContextMessages(messages)
/// .at(astNode));
/// ```
LocatableDiagnostic withContextMessages(Iterable<DiagnosticMessage> messages);
}
/// Concrete implementation of [LocatableDiagnostic].
final class LocatableDiagnosticImpl implements LocatableDiagnostic {
@override
final DiagnosticCode code;
@override
final List<Object> arguments;
@override
final Iterable<DiagnosticMessage> contextMessages;
LocatableDiagnosticImpl(
this.code,
this.arguments, {
this.contextMessages = const [],
});
@override
LocatedDiagnostic at(SyntacticEntity node) =>
atOffset(offset: node.offset, length: node.length);
@override
LocatedDiagnostic atOffset({required int offset, required int length}) =>
LocatedDiagnostic(this, offset, length);
@override
LocatedDiagnostic atSourceRange(SourceRange sourceRange) =>
atOffset(offset: sourceRange.offset, length: sourceRange.length);
@override
LocatedDiagnostic atSourceSpan(SourceSpan span) {
var trimmedSpan = span.withoutTrailingLineTerminators;
return atOffset(
offset: trimmedSpan.start.offset,
length: trimmedSpan.length,
);
}
@override
LocatableDiagnostic withContextMessages(
Iterable<DiagnosticMessage> messages,
) => LocatableDiagnosticImpl(
code,
arguments,
contextMessages: [...contextMessages, ...messages],
);
}
/// A diagnostic that does not have any unfilled template parameters, and has
/// been associated with a location in the source code.
final class LocatedDiagnostic {
final LocatableDiagnostic locatableDiagnostic;
final int offset;
final int length;
LocatedDiagnostic(this.locatableDiagnostic, this.offset, this.length);
}
extension on SourceSpan {
SourceSpan get withoutTrailingLineTerminators {
var end = length;
while (end > 0) {
var codeUnit = text.codeUnitAt(end - 1);
if (codeUnit != 0x0A && codeUnit != 0x0D) {
break;
}
end--;
}
return subspan(0, end);
}
}