| Index: pkg/code_transformers/lib/messages/messages.dart
|
| diff --git a/pkg/code_transformers/lib/messages/messages.dart b/pkg/code_transformers/lib/messages/messages.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7045852c38c8b0bbdfaaa3e783742ced94e3c172
|
| --- /dev/null
|
| +++ b/pkg/code_transformers/lib/messages/messages.dart
|
| @@ -0,0 +1,230 @@
|
| +// 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.
|
| +
|
| +/// Defines messages templates and an adapter for TransformLogger to be able
|
| +/// report error messages from transformers and refer to them in a consistent
|
| +/// manner long term.
|
| +library code_transformers.messages;
|
| +
|
| +// Note: this library purposely doesn't depend on dart:io, dart:html, or barback
|
| +// so it can easily be used both in the transformers and in client-side apps
|
| +// (for example in the log_injector).
|
| +import 'dart:collection' show LinkedHashMap;
|
| +import 'package:source_span/source_span.dart';
|
| +
|
| +/// A globally unique identifier for an error message. This identifier should be
|
| +/// stable, that is, it should never change after it is asigned to a particular
|
| +/// message. That allows us to document our error messages and make them
|
| +/// searchable for prosperity.
|
| +class MessageId implements Comparable {
|
| + /// Name of the package that declares this message.
|
| + final String package;
|
| +
|
| + /// Message identifier number, unique within the package.
|
| + final int id;
|
| +
|
| + const MessageId(this.package, this.id);
|
| +
|
| + static const MessageId NOT_SPECIFIED = const MessageId('unknown', 0);
|
| +
|
| + /// Serialize this message. We use a string and not a map to encode ids so
|
| + /// they can be used as keys in JSON maps.
|
| + String toJson() => toString();
|
| +
|
| + toString() => '${package}:$id';
|
| +
|
| + int compareTo(MessageId other) {
|
| + var res = package.compareTo(other.package);
|
| + if (res != 0) return res;
|
| + return id.compareTo(other.id);
|
| + }
|
| +
|
| + /// Creates a new [MessageId] from an encoded value produced via [toJson].
|
| + factory MessageId.fromJson(data) {
|
| + var index = data.lastIndexOf(':');
|
| + if (index == -1) throw 'Invalid message id: $data';
|
| + return new MessageId(data.substring(0, index),
|
| + int.parse(data.substring(index + 1)));
|
| + }
|
| +
|
| + operator ==(MessageId other) => package == other.package && id == other.id;
|
| + int get hashCode => 31 * package.hashCode + id;
|
| +}
|
| +
|
| +/// An instance of an error message. These are typically produced from a
|
| +/// [MessageTemplate].
|
| +class Message {
|
| + /// A globally unique identifier for this message.
|
| + final MessageId id;
|
| +
|
| + /// A snippet message that is presented to the user.
|
| + final String snippet;
|
| +
|
| + const Message(this.id, this.snippet);
|
| +
|
| + const Message.unknown(this.snippet) : id = MessageId.NOT_SPECIFIED;
|
| +
|
| + /// Serializes this message to JSON.
|
| + Map toJson() => {'id': id.toJson(), 'snippet': snippet};
|
| + String toString() => 'id: $id, snippet: $snippet';
|
| +
|
| + /// Creates a new [Message] from an encoded value produced via [toJson].
|
| + factory Message.fromJson(data) => new Message(
|
| + new MessageId.fromJson(data['id']), data['snippet']);
|
| +}
|
| +
|
| +/// Template for a message. Templates can include placeholders to indicate
|
| +/// values that are different for each instance of the error. Calling [create]
|
| +/// will generate the actual message, with the placeholders replaced with
|
| +/// values. If there are no placeholders, an instance of [MessageTemplate] is a
|
| +/// valid instance of [Message] as well.
|
| +class MessageTemplate implements Message {
|
| + /// Unique and stable id for the message.
|
| + final MessageId id;
|
| +
|
| + /// Template message with placeholders of the form `%-name-%`.
|
| + final String snippetTemplate;
|
| +
|
| + /// This returns the message snippet, only if it the template has no
|
| + /// placeholders, otherwise this throws an exception. Most messages have no
|
| + /// placeholder arguments, in those cases, the snippet can be computed
|
| + /// without specifying any arguments (exactly like calling `create()` with no
|
| + /// arguments).
|
| + String get snippet => _createSnippet();
|
| +
|
| + /// Short description of the error message, typically used as a title of the
|
| + /// error message in autogenerated documentation. This should be a single
|
| + /// phrase, and cannot use placeholders.
|
| + final String description;
|
| +
|
| + /// Additional details about this error message. These are used to
|
| + /// automatically generate documentation.
|
| + final String details;
|
| +
|
| + const MessageTemplate(
|
| + this.id, this.snippetTemplate, this.description, this.details);
|
| +
|
| + static final _placeholderPattern = new RegExp(r"%-(\w*)-%");
|
| +
|
| + _createSnippet([Map args = const {}, bool fillUnknowns = false]) {
|
| + var snippet = snippetTemplate.replaceAllMapped(_placeholderPattern, (m) {
|
| + var arg = m.group(1);
|
| + var value = args[arg];
|
| + if (value != null) return '$value';
|
| + if (fillUnknowns) return '';
|
| + throw "missing argument $arg, for error message: $snippetTemplate";
|
| + });
|
| + return snippet;
|
| + }
|
| +
|
| + create([Map args = const {}, bool fillUnknowns = false]) =>
|
| + new Message(id, _createSnippet(args, fillUnknowns));
|
| +
|
| + /// Serializes this message to JSON.
|
| + Map toJson() => create().toJson();
|
| + String toString() => '${toJson()}';
|
| +}
|
| +
|
| +/// Represents an actual log entry for a build error message. Including the
|
| +/// actual message, its severity level (warning, error, etc), and a source span
|
| +/// for a code location that is revelant to the message.
|
| +class BuildLogEntry {
|
| + /// The actual message.
|
| + final Message message;
|
| +
|
| + /// Severity level.
|
| + final String level;
|
| +
|
| + /// Location associated with this message, if any.
|
| + final SourceSpan span;
|
| +
|
| + BuildLogEntry(this.message, this.span, this.level);
|
| +
|
| + /// Creates a new [BuildLogEntry] from an encoded value produced via [toJson].
|
| + factory BuildLogEntry.fromJson(Map data) {
|
| + var spanData = data['span'];
|
| + var span = null;
|
| + if (spanData != null) {
|
| + var locData = spanData['start'];
|
| + var start = new SourceLocation(locData['offset'],
|
| + sourceUrl: Uri.parse(locData['url']),
|
| + line: locData['line'],
|
| + column: locData['column']);
|
| + locData = spanData['end'];
|
| + var end = new SourceLocation(locData['offset'],
|
| + sourceUrl: Uri.parse(locData['url']),
|
| + line: locData['line'],
|
| + column: locData['column']);
|
| + span = new SourceSpan(start, end, spanData['text']);
|
| + }
|
| + return new BuildLogEntry(
|
| + new Message.fromJson(data['message']), span, data['level']);
|
| + }
|
| +
|
| + /// Serializes this log entry to JSON.
|
| + Map toJson() {
|
| + var data = {
|
| + 'level': level,
|
| + 'message': message.toJson(),
|
| + };
|
| + if (span != null) {
|
| + data['span'] = {
|
| + 'start': {
|
| + 'url': span.start.sourceUrl.toString(),
|
| + 'offset': span.start.offset,
|
| + 'line': span.start.line,
|
| + 'column': span.start.column,
|
| + },
|
| + 'end': {
|
| + 'url': span.end.sourceUrl.toString(),
|
| + 'offset': span.end.offset,
|
| + 'line': span.end.line,
|
| + 'column': span.end.column,
|
| + },
|
| + 'text': span.text,
|
| + };
|
| + }
|
| + return data;
|
| + }
|
| + String toString() => '${toJson()}';
|
| +}
|
| +
|
| +/// A table of entries, that clusters error messages by id.
|
| +class LogEntryTable {
|
| + final Map<MessageId, List<BuildLogEntry>> entries;
|
| +
|
| + LogEntryTable() : entries = new LinkedHashMap();
|
| +
|
| + /// Creates a new [LogEntryTable] from an encoded value produced via [toJson].
|
| + factory LogEntryTable.fromJson(Map json) {
|
| + var res = new LogEntryTable();
|
| + for (String key in json.keys) {
|
| + var id = new MessageId.fromJson(key);
|
| + res.entries[id] = json[key]
|
| + .map((v) => new BuildLogEntry.fromJson(v)).toList();
|
| + }
|
| + return res;
|
| + }
|
| +
|
| +
|
| + /// Serializes this entire table as JSON.
|
| + Map toJson() {
|
| + var res = {};
|
| + entries.forEach((key, value) {
|
| + res['$key'] = value.map((e) => e.toJson()).toList();
|
| + });
|
| + return res;
|
| + }
|
| + String toString() => '${toJson()}';
|
| +
|
| + void add(BuildLogEntry entry) {
|
| + entries.putIfAbsent(entry.message.id, () => []).add(entry);
|
| + }
|
| + void addAll(LogEntryTable other) {
|
| + for (var key in other.entries.keys) {
|
| + var values = entries.putIfAbsent(key, () => []);
|
| + values.addAll(other.entries[key]);
|
| + }
|
| + }
|
| +}
|
|
|