Chromium Code Reviews| 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..5c0787fdef8ab9c1575d103978ab420123dfa689 |
| --- /dev/null |
| +++ b/pkg/code_transformers/lib/messages/messages.dart |
| @@ -0,0 +1,225 @@ |
| +// 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 as a String |
| + String toJson() => toString(); |
| + |
| + toString() => '${package}_$id'; |
|
jakemac
2014/09/03 17:20:41
imo, a ':' would be better than an '_'
Siggi Cherem (dart-lang)
2014/09/04 02:32:13
yeah - just trying to use something that doesn't r
Siggi Cherem (dart-lang)
2014/09/04 19:55:11
All set - I went with # here, but do a separate se
|
| + |
| + 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); |
|
jakemac
2014/09/03 17:20:41
A named constructor or factory that takes no ID an
Siggi Cherem (dart-lang)
2014/09/04 02:32:13
Done.
|
| + |
| + /// Serializes this message to JSON. |
| + Map toJson() => {'id': id.toJson(), 'snippet': snippet}; |
| + String toString() => '${toJson()}'; |
|
jakemac
2014/09/03 17:20:41
I think something like '$id: $snippet' might be be
Siggi Cherem (dart-lang)
2014/09/04 02:32:13
Done.
|
| + |
| + /// 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 |
|
jakemac
2014/09/03 17:20:41
How does this differ from snippetTemplate and deta
Siggi Cherem (dart-lang)
2014/09/04 02:32:13
added details - basically it should be the title w
|
| + 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]); |
| + } |
| + } |
| +} |