Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /// Defines messages templates and an adapter for TransformLogger to be able | |
| 6 /// report error messages from transformers and refer to them in a consistent | |
| 7 /// manner long term. | |
| 8 library code_transformers.messages; | |
| 9 | |
| 10 // Note: this library purposely doesn't depend on dart:io, dart:html, or barback | |
| 11 // so it can easily be used both in the transformers and in client-side apps | |
| 12 // (for example in the log_injector). | |
| 13 import 'dart:collection' show LinkedHashMap; | |
| 14 import 'package:source_span/source_span.dart'; | |
| 15 | |
| 16 /// A globally unique identifier for an error message. This identifier should be | |
| 17 /// stable, that is, it should never change after it is asigned to a particular | |
| 18 /// message. That allows us to document our error messages and make them | |
| 19 /// searchable for prosperity. | |
| 20 class MessageId implements Comparable { | |
| 21 /// Name of the package that declares this message. | |
| 22 final String package; | |
| 23 | |
| 24 /// Message identifier number, unique within the package. | |
| 25 final int id; | |
| 26 | |
| 27 const MessageId(this.package, this.id); | |
| 28 | |
| 29 static const MessageId NOT_SPECIFIED = const MessageId('unknown', 0); | |
| 30 | |
| 31 /// Serialize this message as a String | |
| 32 String toJson() => toString(); | |
| 33 | |
| 34 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
| |
| 35 | |
| 36 int compareTo(MessageId other) { | |
| 37 var res = package.compareTo(other.package); | |
| 38 if (res != 0) return res; | |
| 39 return id.compareTo(other.id); | |
| 40 } | |
| 41 | |
| 42 /// Creates a new [MessageId] from an encoded value produced via [toJson]. | |
| 43 factory MessageId.fromJson(data) { | |
| 44 var index = data.lastIndexOf('_'); | |
| 45 if (index == -1) throw 'Invalid message id: $data'; | |
| 46 return new MessageId(data.substring(0, index), | |
| 47 int.parse(data.substring(index + 1))); | |
| 48 } | |
| 49 | |
| 50 operator ==(MessageId other) => package == other.package && id == other.id; | |
| 51 int get hashCode => 31 * package.hashCode + id; | |
| 52 } | |
| 53 | |
| 54 /// An instance of an error message. These are typically produced from a | |
| 55 /// [MessageTemplate]. | |
| 56 class Message { | |
| 57 /// A globally unique identifier for this message. | |
| 58 final MessageId id; | |
| 59 | |
| 60 /// A snippet message that is presented to the user. | |
| 61 final String snippet; | |
| 62 | |
| 63 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.
| |
| 64 | |
| 65 /// Serializes this message to JSON. | |
| 66 Map toJson() => {'id': id.toJson(), 'snippet': snippet}; | |
| 67 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.
| |
| 68 | |
| 69 /// Creates a new [Message] from an encoded value produced via [toJson]. | |
| 70 factory Message.fromJson(data) => new Message( | |
| 71 new MessageId.fromJson(data['id']), data['snippet']); | |
| 72 } | |
| 73 | |
| 74 /// Template for a message. Templates can include placeholders to indicate | |
| 75 /// values that are different for each instance of the error. Calling [create] | |
| 76 /// will generate the actual message, with the placeholders replaced with | |
| 77 /// values. If there are no placeholders, an instance of [MessageTemplate] is a | |
| 78 /// valid instance of [Message] as well. | |
| 79 class MessageTemplate implements Message { | |
| 80 /// Unique and stable id for the message. | |
| 81 final MessageId id; | |
| 82 | |
| 83 /// Template message with placeholders of the form `%-name-%`. | |
| 84 final String snippetTemplate; | |
| 85 | |
| 86 /// This returns the message snippet, only if it the template has no | |
| 87 /// placeholders, otherwise this throws an exception. Most messages have no | |
| 88 /// placeholder arguments, in those cases, the snippet can be computed | |
| 89 /// without specifying any arguments (exactly like calling `create()` with no | |
| 90 /// arguments). | |
| 91 String get snippet => _createSnippet(); | |
| 92 | |
| 93 /// 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
| |
| 94 final String description; | |
| 95 | |
| 96 /// Additional details about this error message. These are used to | |
| 97 /// automatically generate documentation. | |
| 98 final String details; | |
| 99 | |
| 100 const MessageTemplate( | |
| 101 this.id, this.snippetTemplate, this.description, this.details); | |
| 102 | |
| 103 static final _placeholderPattern = new RegExp(r"%-(\w*)-%"); | |
| 104 | |
| 105 _createSnippet([Map args = const {}, bool fillUnknowns = false]) { | |
| 106 var snippet = snippetTemplate.replaceAllMapped(_placeholderPattern, (m) { | |
| 107 var arg = m.group(1); | |
| 108 var value = args[arg]; | |
| 109 if (value != null) return '$value'; | |
| 110 if (fillUnknowns) return ''; | |
| 111 throw "missing argument $arg, for error message: $snippetTemplate"; | |
| 112 }); | |
| 113 return snippet; | |
| 114 } | |
| 115 | |
| 116 create([Map args = const {}, bool fillUnknowns = false]) => | |
| 117 new Message(id, _createSnippet(args, fillUnknowns)); | |
| 118 | |
| 119 /// Serializes this message to JSON. | |
| 120 Map toJson() => create().toJson(); | |
| 121 String toString() => '${toJson()}'; | |
| 122 } | |
| 123 | |
| 124 /// Represents an actual log entry for a build error message. Including the | |
| 125 /// actual message, its severity level (warning, error, etc), and a source span | |
| 126 /// for a code location that is revelant to the message. | |
| 127 class BuildLogEntry { | |
| 128 /// The actual message. | |
| 129 final Message message; | |
| 130 | |
| 131 /// Severity level. | |
| 132 final String level; | |
| 133 | |
| 134 /// Location associated with this message, if any. | |
| 135 final SourceSpan span; | |
| 136 | |
| 137 BuildLogEntry(this.message, this.span, this.level); | |
| 138 | |
| 139 /// Creates a new [BuildLogEntry] from an encoded value produced via [toJson]. | |
| 140 factory BuildLogEntry.fromJson(Map data) { | |
| 141 var spanData = data['span']; | |
| 142 var span = null; | |
| 143 if (spanData != null) { | |
| 144 var locData = spanData['start']; | |
| 145 var start = new SourceLocation(locData['offset'], | |
| 146 sourceUrl: Uri.parse(locData['url']), | |
| 147 line: locData['line'], | |
| 148 column: locData['column']); | |
| 149 locData = spanData['end']; | |
| 150 var end = new SourceLocation(locData['offset'], | |
| 151 sourceUrl: Uri.parse(locData['url']), | |
| 152 line: locData['line'], | |
| 153 column: locData['column']); | |
| 154 span = new SourceSpan(start, end, spanData['text']); | |
| 155 } | |
| 156 return new BuildLogEntry( | |
| 157 new Message.fromJson(data['message']), span, data['level']); | |
| 158 } | |
| 159 | |
| 160 /// Serializes this log entry to JSON. | |
| 161 Map toJson() { | |
| 162 var data = { | |
| 163 'level': level, | |
| 164 'message': message.toJson(), | |
| 165 }; | |
| 166 if (span != null) { | |
| 167 data['span'] = { | |
| 168 'start': { | |
| 169 'url': span.start.sourceUrl.toString(), | |
| 170 'offset': span.start.offset, | |
| 171 'line': span.start.line, | |
| 172 'column': span.start.column, | |
| 173 }, | |
| 174 'end': { | |
| 175 'url': span.end.sourceUrl.toString(), | |
| 176 'offset': span.end.offset, | |
| 177 'line': span.end.line, | |
| 178 'column': span.end.column, | |
| 179 }, | |
| 180 'text': span.text, | |
| 181 }; | |
| 182 } | |
| 183 return data; | |
| 184 } | |
| 185 String toString() => '${toJson()}'; | |
| 186 } | |
| 187 | |
| 188 /// A table of entries, that clusters error messages by id. | |
| 189 class LogEntryTable { | |
| 190 final Map<MessageId, List<BuildLogEntry>> entries; | |
| 191 | |
| 192 LogEntryTable() : entries = new LinkedHashMap(); | |
| 193 | |
| 194 /// Creates a new [LogEntryTable] from an encoded value produced via [toJson]. | |
| 195 factory LogEntryTable.fromJson(Map json) { | |
| 196 var res = new LogEntryTable(); | |
| 197 for (String key in json.keys) { | |
| 198 var id = new MessageId.fromJson(key); | |
| 199 res.entries[id] = json[key] | |
| 200 .map((v) => new BuildLogEntry.fromJson(v)).toList(); | |
| 201 } | |
| 202 return res; | |
| 203 } | |
| 204 | |
| 205 | |
| 206 /// Serializes this entire table as JSON. | |
| 207 Map toJson() { | |
| 208 var res = {}; | |
| 209 entries.forEach((key, value) { | |
| 210 res['$key'] = value.map((e) => e.toJson()).toList(); | |
| 211 }); | |
| 212 return res; | |
| 213 } | |
| 214 String toString() => '${toJson()}'; | |
| 215 | |
| 216 void add(BuildLogEntry entry) { | |
| 217 entries.putIfAbsent(entry.message.id, () => []).add(entry); | |
| 218 } | |
| 219 void addAll(LogEntryTable other) { | |
| 220 for (var key in other.entries.keys) { | |
| 221 var values = entries.putIfAbsent(key, () => []); | |
| 222 values.addAll(other.entries[key]); | |
| 223 } | |
| 224 } | |
| 225 } | |
| OLD | NEW |