blob: 7299a943cb20c67dee7ef34e41c628285894ca5b [file] [log] [blame]
// Copyright (c) 2017, 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 _fe_analyzer_shared.messages.codes;
import 'dart:convert' show JsonEncoder, json;
import 'diagnostic_message.dart' show DiagnosticMessage;
import '../scanner/token.dart' show Token;
import 'severity.dart' show Severity;
import '../util/relativize.dart' as util show isWindows, relativizeUri;
part 'codes_generated.dart';
const int noLength = 1;
class Code<T> {
final String name;
/// The unique positive integer associated with this code,
/// or `-1` if none. This index is used when translating
/// this error to its corresponding Analyzer error.
final int index;
final List<String>? analyzerCodes;
final Severity severity;
const Code(this.name,
{this.index: -1, this.analyzerCodes, this.severity: Severity.error});
String toString() => name;
}
class Message {
final Code<dynamic> code;
final String message;
final String? tip;
final Map<String, dynamic> arguments;
const Message(this.code,
{required this.message, this.tip, this.arguments = const {}});
LocatedMessage withLocation(Uri uri, int charOffset, int length) {
return new LocatedMessage(uri, charOffset, length, this);
}
LocatedMessage withoutLocation() {
return new LocatedMessage(null, -1, noLength, this);
}
String toString() {
return "Message[$code, $message, $tip, $arguments]";
}
}
class MessageCode extends Code<Null> implements Message {
final String message;
final String? tip;
const MessageCode(String name,
{int index: -1,
List<String>? analyzerCodes,
Severity severity: Severity.error,
required this.message,
this.tip})
: super(name,
index: index, analyzerCodes: analyzerCodes, severity: severity);
Map<String, dynamic> get arguments => const <String, dynamic>{};
Code<dynamic> get code => this;
@override
LocatedMessage withLocation(Uri uri, int charOffset, int length) {
return new LocatedMessage(uri, charOffset, length, this);
}
LocatedMessage withoutLocation() {
return new LocatedMessage(null, -1, noLength, this);
}
}
class Template<T> {
final String messageTemplate;
final String? tipTemplate;
final T withArguments;
const Template(
{required this.messageTemplate,
this.tipTemplate,
required this.withArguments});
}
class LocatedMessage implements Comparable<LocatedMessage> {
final Uri? uri;
final int charOffset;
final int length;
final Message messageObject;
const LocatedMessage(
this.uri, this.charOffset, this.length, this.messageObject);
Code<dynamic> get code => messageObject.code;
String get message => messageObject.message;
String? get tip => messageObject.tip;
Map<String, dynamic> get arguments => messageObject.arguments;
@override
int compareTo(LocatedMessage other) {
int result = "${uri}".compareTo("${other.uri}");
if (result != 0) return result;
result = charOffset.compareTo(other.charOffset);
if (result != 0) return result;
return message.compareTo(message);
}
FormattedMessage withFormatting(PlainAndColorizedString formatted, int line,
int column, Severity severity, List<FormattedMessage>? relatedInformation,
{List<Uri>? involvedFiles}) {
return new FormattedMessage(this, formatted.plain, formatted.colorized,
line, column, severity, relatedInformation,
involvedFiles: involvedFiles);
}
@override
int get hashCode =>
13 * uri.hashCode +
17 * charOffset.hashCode +
19 * length.hashCode +
23 * messageObject.hashCode;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is LocatedMessage &&
uri == other.uri &&
charOffset == other.charOffset &&
length == other.length &&
messageObject == other.messageObject;
}
@override
String toString() =>
'LocatedMessage(uri=$uri,charOffset=$charOffset,length=$length,'
'messageObject=$messageObject)';
}
class PlainAndColorizedString {
final String plain;
final String colorized;
@override
String toString() {
assert(false, "Called PlainAndColorizedString.toString: $plain");
return 'PlainAndColorizedString:$plain';
}
const PlainAndColorizedString(this.plain, this.colorized);
const PlainAndColorizedString.plainOnly(this.plain) : this.colorized = plain;
}
class FormattedMessage implements DiagnosticMessage {
final LocatedMessage locatedMessage;
final String formattedPlain;
final String formattedColorized;
final int line;
final int column;
@override
final Severity severity;
final List<FormattedMessage>? relatedInformation;
final List<Uri>? involvedFiles;
const FormattedMessage(
this.locatedMessage,
this.formattedPlain,
this.formattedColorized,
this.line,
this.column,
this.severity,
this.relatedInformation,
{this.involvedFiles});
Code<dynamic> get code => locatedMessage.code;
String get codeName => code.name;
String get message => locatedMessage.message;
String? get tip => locatedMessage.tip;
Map<String, dynamic> get arguments => locatedMessage.arguments;
Uri? get uri => locatedMessage.uri;
int get charOffset => locatedMessage.charOffset;
int get length => locatedMessage.length;
@override
Iterable<String> get ansiFormatted sync* {
yield formattedColorized;
if (relatedInformation != null) {
for (FormattedMessage m in relatedInformation!) {
yield m.formattedColorized;
}
}
}
@override
Iterable<String> get plainTextFormatted sync* {
yield formattedPlain;
if (relatedInformation != null) {
for (FormattedMessage m in relatedInformation!) {
yield m.formattedPlain;
}
}
}
Map<String, Object?> toJson() {
// This should be kept in sync with package:kernel/problems.md
return <String, Object?>{
"ansiFormatted": ansiFormatted.toList(),
"plainTextFormatted": plainTextFormatted.toList(),
"severity": severity.index,
"uri": uri?.toString(),
"involvedFiles": involvedFiles?.map((u) => u.toString()).toList(),
"codeName": code.name,
};
}
String toJsonString() {
JsonEncoder encoder = new JsonEncoder.withIndent(" ");
return encoder.convert(this);
}
}
class DiagnosticMessageFromJson implements DiagnosticMessage {
@override
final Iterable<String> ansiFormatted;
@override
final Iterable<String> plainTextFormatted;
@override
final Severity severity;
final Uri? uri;
final List<Uri>? involvedFiles;
final String codeName;
DiagnosticMessageFromJson(this.ansiFormatted, this.plainTextFormatted,
this.severity, this.uri, this.involvedFiles, this.codeName);
factory DiagnosticMessageFromJson.fromJson(String jsonString) {
Map<String, Object> decoded = json.decode(jsonString);
List<String> ansiFormatted =
new List<String>.from(_asListOfString(decoded["ansiFormatted"]));
List<String> plainTextFormatted =
_asListOfString(decoded["plainTextFormatted"]);
Severity severity = Severity.values[decoded["severity"] as int];
Uri? uri =
decoded["uri"] == null ? null : Uri.parse(decoded["uri"] as String);
List<Uri>? involvedFiles = decoded["involvedFiles"] == null
? null
: _asListOfString(decoded["involvedFiles"])
.map((e) => Uri.parse(e))
.toList();
String codeName = decoded["codeName"] as String;
return new DiagnosticMessageFromJson(ansiFormatted, plainTextFormatted,
severity, uri, involvedFiles, codeName);
}
Map<String, Object?> toJson() {
// This should be kept in sync with package:kernel/problems.md
return <String, Object?>{
"ansiFormatted": ansiFormatted.toList(),
"plainTextFormatted": plainTextFormatted.toList(),
"severity": severity.index,
"uri": uri?.toString(),
"involvedFiles": involvedFiles?.map((u) => u.toString()).toList(),
"codeName": codeName,
};
}
String toJsonString() {
JsonEncoder encoder = new JsonEncoder.withIndent(" ");
return encoder.convert(this);
}
static List<String> _asListOfString(Object? value) {
return (value as List<dynamic>).cast<String>();
}
}
String? relativizeUri(Uri? uri) {
// We have this method here for two reasons:
//
// 1. It allows us to implement #uri message argument without using it
// (otherwise, we might get an `UNUSED_IMPORT` warning).
//
// 2. We can change `base` argument here if needed.
return uri == null ? null : util.relativizeUri(Uri.base, uri, util.isWindows);
}
typedef SummaryTemplate = Message Function(int, int, num, num, num);
String itemizeNames(List<String> names) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < names.length - 1; i++) {
buffer.write(" - ");
buffer.writeln(names[i]);
}
buffer.write(" - ");
buffer.write(names.last);
return "$buffer";
}
/// Convert the synthetic name of an implicit mixin application class
/// into a name suitable for user-faced strings.
///
/// For example, when compiling "class A extends S with M1, M2", the
/// two synthetic classes will be named "_A&S&M1" and "_A&S&M1&M2".
/// This function will return "S with M1" and "S with M1, M2", respectively.
///
/// This method is copied from package:kernel/ast.dart.
// TODO(johnniwinther): Avoid the need for this method.
String demangleMixinApplicationName(String name) {
List<String> nameParts = name.split('&');
if (nameParts.length < 2 || name == "&") return name;
String demangledName = nameParts[1];
for (int i = 2; i < nameParts.length; i++) {
demangledName += (i == 2 ? " with " : ", ") + nameParts[i];
}
return demangledName;
}
final RegExp templateKey = new RegExp(r'#(\w+)');
/// Replaces occurrences of '#key' in [template], where 'key' is a key in
/// [arguments], with the corresponding values.
String applyArgumentsToTemplate(
String template, Map<String, dynamic> arguments) {
// TODO(johnniwinther): Remove `as dynamic` when unsound null safety is
// no longer supported.
if (arguments as dynamic == null || arguments.isEmpty) {
assert(!template.contains(templateKey),
'Message requires arguments, but none were provided.');
return template;
}
return template.replaceAllMapped(templateKey, (Match match) {
String? key = match.group(1);
Object? value = arguments[key];
assert(value != null, "No value for '$key' found in $arguments");
return value.toString();
});
}