Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// Summarizes the information produced by the checker. | 5 /// Summarizes the information produced by the checker. |
| 6 library dev_compiler.src.report; | 6 library dev_compiler.src.report; |
| 7 | 7 |
| 8 import 'dart:math' show max; | 8 import 'dart:math' show max; |
| 9 | 9 |
| 10 import 'package:analyzer/src/generated/source.dart' show Source; | |
| 11 import 'package:logging/logging.dart'; | |
| 10 import 'package:path/path.dart' as path; | 12 import 'package:path/path.dart' as path; |
| 11 import 'package:source_span/source_span.dart'; | 13 import 'package:source_span/source_span.dart'; |
| 12 import 'package:analyzer/src/generated/ast.dart'; | |
| 13 import 'package:analyzer/src/generated/source.dart' show Source; | |
| 14 import 'package:logging/logging.dart'; | |
| 15 | 14 |
| 16 import 'info.dart'; | 15 import 'info.dart'; |
| 17 import 'utils.dart'; | 16 import 'utils.dart'; |
| 17 import 'summary.dart'; | |
| 18 | |
| 19 /// A message (error or warning) produced by the dev_compiler and it's location | |
| 20 /// information. | |
| 21 /// | |
| 22 /// Currently the location information includes only the offsets within a file | |
| 23 /// where the error occurs. This is used in the context of a [CheckerReporter], | |
| 24 /// where the current file is being tracked. | |
| 25 abstract class Message { | |
| 26 // Message description. | |
| 27 final String message; | |
| 28 | |
| 29 /// Log level. This is a placeholder for severity. | |
| 30 final Level level; | |
| 31 | |
| 32 /// Offset where the error message begins in the tracked source file. | |
| 33 final int begin; | |
| 34 | |
| 35 /// Offset where the error message ends in the tracked source file. | |
| 36 final int end; | |
| 37 | |
| 38 const Message(this.message, this.level, this.begin, this.end); | |
| 39 } | |
| 40 | |
| 41 /// Like [Message], but with a precomputed source span. | |
| 42 abstract class MessageWithSpan implements Message { | |
| 43 final String message; | |
| 44 | |
| 45 final Level level; | |
| 46 | |
| 47 final SourceSpan span; | |
| 48 | |
| 49 int get begin => span.start.offset; | |
| 50 int get end => span.end.offset; | |
| 51 | |
| 52 const MessageWithSpan(this.message, this.level, this.span); | |
| 53 } | |
| 18 | 54 |
| 19 // Interface used to report error messages from the checker. | 55 // Interface used to report error messages from the checker. |
| 20 abstract class CheckerReporter { | 56 abstract class CheckerReporter { |
| 21 /// Called when starting to process a library. | 57 /// Called when starting to process a library. |
| 22 void enterLibrary(LibraryInfo info); | 58 void enterLibrary(Uri uri); |
| 23 void leaveLibrary(); | 59 void leaveLibrary(); |
| 24 | 60 |
| 61 /// Called when starting to process an HTML source file. | |
| 62 void enterHtml(Uri uri); | |
| 63 void leaveHtml(); | |
| 64 | |
| 25 /// Called when starting to process a source. All subsequent log entries must | 65 /// Called when starting to process a source. All subsequent log entries must |
| 26 /// belong to this source until the next call to enterSource. | 66 /// belong to this source until the next call to enterSource. |
| 27 void enterSource(Source source); | 67 void enterSource(Source source); |
| 28 void leaveSource(); | 68 void leaveSource(); |
| 69 void log(Message message); | |
| 29 | 70 |
| 30 void log(StaticInfo info); | 71 // Called in server-mode. |
| 31 | 72 void clearLibrary(Uri uri); |
| 32 // TODO(sigmund): merge this and [log] | 73 void clearHtml(Uri uri); |
| 33 void logAnalyzerError(String message, Level level, int begin, int end); | 74 void clearAll(); |
| 34 } | 75 } |
| 35 | 76 |
| 36 final _checkerLogger = new Logger('dev_compiler.checker'); | 77 final _checkerLogger = new Logger('dev_compiler.checker'); |
| 37 | 78 |
| 38 /// Simple reporter that logs checker messages as they are seen. | 79 /// Simple reporter that logs checker messages as they are seen. |
| 39 class LogReporter implements CheckerReporter { | 80 class LogReporter implements CheckerReporter { |
| 40 final bool useColors; | 81 final bool useColors; |
| 41 SourceFile _file; | 82 SourceFile _file; |
| 42 Source _current; | 83 Source _current; |
| 43 | 84 |
| 44 LogReporter([this.useColors = false]); | 85 LogReporter([this.useColors = false]); |
| 45 | 86 |
| 46 void enterLibrary(LibraryInfo info) {} | 87 void enterLibrary(Uri uri) {} |
| 47 void leaveLibrary() {} | 88 void leaveLibrary() {} |
| 48 | 89 |
| 90 void enterHtml(Uri uri) {} | |
| 91 void leaveHtml() {} | |
| 92 | |
| 49 void enterSource(Source source) { | 93 void enterSource(Source source) { |
| 50 _file = new SourceFile(source.contents.data, url: source.uri); | 94 _file = new SourceFile(source.contents.data, url: source.uri); |
| 51 _current = source; | 95 _current = source; |
| 52 } | 96 } |
| 53 | 97 |
| 54 void leaveSource() { | 98 void leaveSource() { |
| 55 _file = null; | 99 _file = null; |
| 56 _current = null; | 100 _current = null; |
| 57 } | 101 } |
| 58 | 102 |
| 59 void log(StaticInfo info) { | 103 void log(Message message) { |
| 60 assert((info.node as dynamic).root.element.source == _current); | 104 if (message is StaticInfo) { |
| 61 final span = _spanForNode(_file, info.node); | 105 assert((message.node as dynamic).root.element.source == _current); |
| 62 final color = useColors ? colorOf(info.level.name) : null; | 106 } |
| 63 _checkerLogger.log(info.level, span.message(info.message, color: color)); | 107 // TODO(sigmund): convert to use span information from AST (issue #73) |
| 108 final span = message is MessageWithSpan | |
| 109 ? message.span | |
| 110 : _file.span(message.begin, message.end); | |
| 111 final level = message.level; | |
| 112 final color = useColors ? colorOf(level.name) : null; | |
| 113 _checkerLogger.log(level, span.message(message.message, color: color)); | |
| 64 } | 114 } |
| 65 | 115 |
| 66 void logAnalyzerError(String message, Level level, int begin, int end) { | 116 void clearLibrary(Uri uri) {} |
| 67 var span = _file.span(begin, end); | 117 void clearHtml(Uri uri) {} |
| 68 final color = useColors ? colorOf(level.name) : null; | 118 void clearAll() {} |
| 69 _checkerLogger.log( | |
| 70 level, span.message('[from analyzer]: ${message}', color: color)); | |
| 71 } | |
| 72 } | 119 } |
| 73 | 120 |
| 74 /// A reporter that gathers all the information in a [GlobalSummary]. | 121 /// A reporter that gathers all the information in a [GlobalSummary]. |
| 75 class SummaryReporter implements CheckerReporter { | 122 class SummaryReporter implements CheckerReporter { |
| 76 GlobalSummary result = new GlobalSummary(); | 123 GlobalSummary result = new GlobalSummary(); |
| 77 LibrarySummary _currentLibrary; | 124 IndividualSummary _current; |
| 78 SourceFile _file; | 125 SourceFile _file; |
| 79 | 126 |
| 80 clear() { | 127 void enterLibrary(Uri uri) { |
| 81 result = new GlobalSummary(); | 128 var container; |
| 82 } | |
| 83 | |
| 84 void enterLibrary(LibraryInfo lib) { | |
| 85 var libKey = '${lib.library.source.uri}'; | |
| 86 var libSummary = _currentLibrary = new LibrarySummary(libKey); | |
| 87 | |
| 88 var uri = lib.library.source.uri; | |
| 89 if (uri.scheme == 'package') { | 129 if (uri.scheme == 'package') { |
| 90 var pname = path.split(uri.path)[0]; | 130 var pname = path.split(uri.path)[0]; |
| 91 result.packages.putIfAbsent(pname, () => new PackageSummary(pname)); | 131 result.packages.putIfAbsent(pname, () => new PackageSummary(pname)); |
| 92 if (result.packages[pname].libraries[libKey] != null) { | 132 container = result.packages[pname].libraries; |
| 93 print('ERROR: duplicate ${libKey}'); | |
| 94 } | |
| 95 result.packages[pname].libraries[libKey] = libSummary; | |
| 96 } else if (uri.scheme == 'dart') { | 133 } else if (uri.scheme == 'dart') { |
| 97 if (result.system[libKey] != null) { | 134 container = result.system; |
| 98 print('ERROR: duplicate ${libKey}'); | |
| 99 } | |
| 100 result.system[libKey] = libSummary; | |
| 101 } else { | 135 } else { |
| 102 if (result.loose[libKey] != null) { | 136 container = result.loose; |
| 103 print('ERROR: duplicate ${libKey}'); | |
| 104 } | |
| 105 result.loose[libKey] = libSummary; | |
| 106 } | 137 } |
| 138 _current = container.putIfAbsent('$uri', () => new LibrarySummary('$uri')); | |
| 107 } | 139 } |
| 108 | 140 |
| 109 void leaveLibrary() { | 141 void leaveLibrary() { |
| 110 _currentLibrary = null; | 142 _current = null; |
| 143 } | |
| 144 | |
| 145 void enterHtml(Uri uri) { | |
| 146 _current = result.loose.putIfAbsent('$uri', () => new HtmlSummary('$uri')); | |
| 147 } | |
| 148 | |
| 149 void leaveHtml() { | |
| 150 _current = null; | |
| 111 } | 151 } |
| 112 | 152 |
| 113 void enterSource(Source source) { | 153 void enterSource(Source source) { |
| 114 _file = new SourceFile(source.contents.data, url: source.uri); | 154 _file = new SourceFile(source.contents.data, url: source.uri); |
| 115 _currentLibrary.lines += _file.lines; | 155 if (_current is LibrarySummary) { |
| 156 (_current as LibrarySummary).lines += _file.lines; | |
| 157 } | |
| 116 } | 158 } |
| 117 | 159 |
| 118 void leaveSource() { | 160 void leaveSource() { |
| 119 _file = null; | 161 _file = null; |
| 120 } | 162 } |
| 121 | 163 |
| 122 void log(StaticInfo info) { | 164 void log(Message message) { |
| 123 assert(_file != null); | 165 assert(message is MessageWithSpan || _file != null); |
| 124 var span = _spanForNode(_file, info.node); | 166 // TODO(sigmund): convert to use span information from AST (issue #73) |
| 125 _currentLibrary.messages.add(new MessageSummary('${info.runtimeType}', | 167 final span = message is MessageWithSpan |
| 126 info.level.name.toLowerCase(), span, info.message)); | 168 ? message.span |
| 169 : _file.span(message.begin, message.end); | |
| 170 _current.messages.add(new MessageSummary('${message.runtimeType}', | |
| 171 message.level.name.toLowerCase(), span, message.message)); | |
| 127 } | 172 } |
| 128 | 173 |
| 129 void logAnalyzerError(String message, Level level, int begin, int end) { | 174 void clearLibrary(Uri uri) { |
| 130 var span = _file.span(begin, end); | 175 enterLibrary(uri); |
| 131 _currentLibrary.messages.add(new MessageSummary( | 176 _current.messages.clear(); |
| 132 'AnalyzerError', level.name.toLowerCase(), span, message)); | 177 (_current as LibrarySummary).lines = 0; |
| 178 leaveLibrary(); | |
| 179 } | |
| 180 | |
| 181 void clearHtml(Uri uri) { | |
| 182 HtmlSummary htmlSummary = result.loose['$uri']; | |
| 183 if (htmlSummary != null) htmlSummary.messages.clear(); | |
| 184 } | |
| 185 | |
| 186 clearAll() { | |
| 187 result = new GlobalSummary(); | |
| 133 } | 188 } |
| 134 } | 189 } |
| 135 | 190 |
| 136 /// Summary information computed by the DDC checker. | |
|
Siggi Cherem (dart-lang)
2015/03/07 02:43:01
note: these deletions are actually code that moved
| |
| 137 abstract class Summary { | |
| 138 Map toJsonMap(); | |
| 139 | |
| 140 void accept(SummaryVisitor visitor); | |
| 141 } | |
| 142 | |
| 143 /// Summary for the entire program. | |
| 144 class GlobalSummary implements Summary { | |
| 145 /// Summary from the system libaries. | |
| 146 final Map<String, LibrarySummary> system = <String, LibrarySummary>{}; | |
| 147 | |
| 148 /// Summary for libraries in packages. | |
| 149 final Map<String, PackageSummary> packages = <String, PackageSummary>{}; | |
| 150 | |
| 151 /// Summary for loose files | |
| 152 // TODO(sigmund): consider inferring the package from the pubspec instead? | |
| 153 final Map<String, LibrarySummary> loose = <String, LibrarySummary>{}; | |
| 154 | |
| 155 GlobalSummary(); | |
| 156 | |
| 157 Map toJsonMap() => { | |
| 158 'system': system.values.map((l) => l.toJsonMap()).toList(), | |
| 159 'packages': packages.values.map((p) => p.toJsonMap()).toList(), | |
| 160 'loose': loose.values.map((l) => l.toJsonMap()).toList(), | |
| 161 }; | |
| 162 | |
| 163 void accept(SummaryVisitor visitor) => visitor.visitGlobal(this); | |
| 164 | |
| 165 static GlobalSummary parse(Map json) { | |
| 166 var res = new GlobalSummary(); | |
| 167 json['system'].map(LibrarySummary.parse).forEach((l) { | |
| 168 res.system[l.name] = l; | |
| 169 }); | |
| 170 json['packages'].map(PackageSummary.parse).forEach((p) { | |
| 171 res.packages[p.name] = p; | |
| 172 }); | |
| 173 json['loose'].map(LibrarySummary.parse).forEach((l) { | |
| 174 res.loose[l.name] = l; | |
| 175 }); | |
| 176 return res; | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 /// A summary of a package. | |
| 181 class PackageSummary implements Summary { | |
| 182 final String name; | |
| 183 final Map<String, LibrarySummary> libraries = <String, LibrarySummary>{}; | |
| 184 | |
| 185 PackageSummary(this.name); | |
| 186 | |
| 187 Map toJsonMap() => { | |
| 188 'package_name': name, | |
| 189 'libraries': libraries.values.map((l) => l.toJsonMap()).toList(), | |
| 190 }; | |
| 191 | |
| 192 void accept(SummaryVisitor visitor) => visitor.visitPackage(this); | |
| 193 | |
| 194 static PackageSummary parse(Map json) { | |
| 195 var res = new PackageSummary(json['package_name']); | |
| 196 json['libraries'].map(LibrarySummary.parse).forEach((l) { | |
| 197 res.libraries[l.name] = l; | |
| 198 }); | |
| 199 return res; | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 /// A summary at the level of a library. | |
| 204 class LibrarySummary implements Summary { | |
| 205 /// Name of the library. | |
| 206 final String name; | |
| 207 | |
| 208 /// All messages collected for the library. | |
| 209 final List<MessageSummary> messages; | |
| 210 | |
| 211 /// Total lines of code (including all parts of the library). | |
| 212 int lines; | |
| 213 | |
| 214 LibrarySummary(this.name, [List<MessageSummary> messages, this.lines = 0]) | |
| 215 : messages = messages == null ? <MessageSummary>[] : messages; | |
| 216 | |
| 217 Map toJsonMap() => { | |
| 218 'library_name': name, | |
| 219 'messages': messages.map((m) => m.toJsonMap()).toList(), | |
| 220 'lines': lines, | |
| 221 }; | |
| 222 | |
| 223 void accept(SummaryVisitor visitor) => visitor.visitLibrary(this); | |
| 224 | |
| 225 static LibrarySummary parse(Map json) => new LibrarySummary( | |
| 226 json['library_name'], json['messages'].map(MessageSummary.parse).toList(), | |
| 227 json['lines']); | |
| 228 } | |
| 229 | |
| 230 /// A single message produced by the checker. | |
| 231 class MessageSummary implements Summary { | |
| 232 /// The kind of message, currently the name of the StaticInfo type. | |
| 233 final String kind; | |
| 234 | |
| 235 /// Level (error, warning, etc). | |
| 236 final String level; | |
| 237 | |
| 238 /// Location where the error is reported. | |
| 239 final SourceSpan span; | |
| 240 final String message; | |
| 241 | |
| 242 MessageSummary(this.kind, this.level, this.span, this.message); | |
| 243 | |
| 244 Map toJsonMap() => { | |
| 245 'kind': kind, | |
| 246 'level': level, | |
| 247 'message': message, | |
| 248 'url': '${span.sourceUrl}', | |
| 249 'start': span.start.offset, | |
| 250 'end': span.end.offset, | |
| 251 'text': span.text, | |
| 252 }; | |
| 253 | |
| 254 void accept(SummaryVisitor visitor) => visitor.visitMessage(this); | |
| 255 | |
| 256 static MessageSummary parse(Map json) { | |
| 257 var start = new SourceLocation(json['start'], sourceUrl: json['url']); | |
| 258 var end = new SourceLocation(json['end'], sourceUrl: json['url']); | |
| 259 var span = new SourceSpanBase(start, end, json['text']); | |
| 260 return new MessageSummary( | |
| 261 json['kind'], json['level'], span, json['message']); | |
| 262 } | |
| 263 } | |
| 264 | |
| 265 /// A visitor of the [Summary] hierarchy. | |
| 266 abstract class SummaryVisitor { | |
| 267 void visitGlobal(GlobalSummary global); | |
| 268 void visitPackage(PackageSummary package); | |
| 269 void visitLibrary(LibrarySummary lib); | |
| 270 void visitMessage(MessageSummary message); | |
| 271 } | |
| 272 | |
| 273 /// A recursive [SummaryVisitor] that visits summaries on a top-down fashion. | |
| 274 class RecursiveSummaryVisitor implements SummaryVisitor { | |
| 275 void visitGlobal(GlobalSummary global) { | |
| 276 for (var lib in global.system.values) { | |
| 277 lib.accept(this); | |
| 278 } | |
| 279 for (var package in global.packages.values) { | |
| 280 package.accept(this); | |
| 281 } | |
| 282 for (var lib in global.loose.values) { | |
| 283 lib.accept(this); | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 void visitPackage(PackageSummary package) { | |
| 288 for (var lib in package.libraries.values) { | |
| 289 lib.accept(this); | |
| 290 } | |
| 291 } | |
| 292 void visitLibrary(LibrarySummary lib) { | |
| 293 for (var msg in lib.messages) { | |
| 294 msg.accept(this); | |
| 295 } | |
| 296 } | |
| 297 void visitMessage(MessageSummary message) {} | |
| 298 } | |
| 299 | |
| 300 /// Produces a string representation of the summary. | 191 /// Produces a string representation of the summary. |
| 301 String summaryToString(GlobalSummary summary) { | 192 String summaryToString(GlobalSummary summary) { |
| 302 var counter = new _Counter(); | 193 var counter = new _Counter(); |
| 303 summary.accept(counter); | 194 summary.accept(counter); |
| 304 | 195 |
| 305 var table = new _Table(); | 196 var table = new _Table(); |
| 306 // Declare columns and add header | 197 // Declare columns and add header |
| 307 table.declareColumn('package'); | 198 table.declareColumn('package'); |
| 308 table.declareColumn('AnalyzerError', abbreviate: true); | 199 table.declareColumn('AnalyzerError', abbreviate: true); |
| 309 infoTypes.forEach((type) => table.declareColumn('$type', abbreviate: true)); | 200 infoTypes.forEach((type) => table.declareColumn('$type', abbreviate: true)); |
| (...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 484 | 375 |
| 485 visitMessage(MessageSummary message) { | 376 visitMessage(MessageSummary message) { |
| 486 var kind = message.kind; | 377 var kind = message.kind; |
| 487 errorCount.putIfAbsent(currentPackage, () => <String, int>{}); | 378 errorCount.putIfAbsent(currentPackage, () => <String, int>{}); |
| 488 errorCount[currentPackage].putIfAbsent(kind, () => 0); | 379 errorCount[currentPackage].putIfAbsent(kind, () => 0); |
| 489 errorCount[currentPackage][kind]++; | 380 errorCount[currentPackage][kind]++; |
| 490 totals.putIfAbsent(kind, () => 0); | 381 totals.putIfAbsent(kind, () => 0); |
| 491 totals[kind]++; | 382 totals[kind]++; |
| 492 } | 383 } |
| 493 } | 384 } |
| 494 | |
| 495 /// Returns a [SourceSpan] in [file] for the offsets of [node]. | |
| 496 // TODO(sigmund): convert to use span information from AST (issue #73) | |
| 497 SourceSpan _spanForNode(SourceFile file, AstNode node) { | |
| 498 final begin = node is AnnotatedNode | |
| 499 ? node.firstTokenAfterCommentAndMetadata.offset | |
| 500 : node.offset; | |
| 501 return file.span(begin, node.end); | |
| 502 } | |
| OLD | NEW |