| // 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 Template<T> template; |
| |
| final List<String> analyzerCodes; |
| |
| final Severity severity; |
| |
| const Code(this.name, this.template, |
| {int index, this.analyzerCodes, this.severity: Severity.error}) |
| : this.index = index ?? -1; |
| |
| String toString() => name; |
| } |
| |
| class Message { |
| final Code<dynamic> code; |
| |
| final String message; |
| |
| final String tip; |
| |
| final Map<String, dynamic> arguments; |
| |
| const Message(this.code, {this.message, this.tip, this.arguments}); |
| |
| 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, |
| List<String> analyzerCodes, |
| Severity severity: Severity.error, |
| this.message, |
| this.tip}) |
| : super(name, null, |
| 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({this.messageTemplate, this.tipTemplate, 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(String formatted, int line, int column, |
| Severity severity, List<FormattedMessage> relatedInformation, |
| {List<Uri> involvedFiles}) { |
| return new FormattedMessage( |
| this, formatted, 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 FormattedMessage implements DiagnosticMessage { |
| final LocatedMessage locatedMessage; |
| |
| final String formatted; |
| |
| final int line; |
| |
| final int column; |
| |
| @override |
| final Severity severity; |
| |
| final List<FormattedMessage> relatedInformation; |
| |
| final List<Uri> involvedFiles; |
| |
| const FormattedMessage(this.locatedMessage, this.formatted, 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 formatted; |
| if (relatedInformation != null) { |
| for (FormattedMessage m in relatedInformation) { |
| yield m.formatted; |
| } |
| } |
| } |
| |
| @override |
| Iterable<String> get plainTextFormatted { |
| // TODO(ahe): Implement this correctly. |
| return ansiFormatted; |
| } |
| |
| 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(decoded["ansiFormatted"]); |
| List<String> plainTextFormatted = |
| new List<String>.from(decoded["plainTextFormatted"]); |
| Severity severity = Severity.values[decoded["severity"]]; |
| Uri uri = decoded["uri"] == null ? null : Uri.parse(decoded["uri"]); |
| List<Uri> involvedFiles = decoded["involvedFiles"] == null |
| ? null |
| : new List<String>.from(decoded["involvedFiles"]) |
| .map((e) => Uri.parse(e)) |
| .toList(); |
| String codeName = decoded["codeName"]; |
| |
| 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); |
| } |
| } |
| |
| 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; |
| } |