| 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. | |
| 6 | |
| 7 import 'dart:math' show max; | |
| 8 | |
| 9 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; | 5 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
| 10 import 'package:analyzer/src/generated/error.dart'; | 6 import 'package:analyzer/src/generated/error.dart'; |
| 11 import 'package:logging/logging.dart'; | 7 import 'package:logging/logging.dart'; |
| 12 import 'package:path/path.dart' as path; | 8 import 'package:path/path.dart' as path; |
| 13 import 'package:source_span/source_span.dart'; | |
| 14 | |
| 15 import 'utils.dart'; | 9 import 'utils.dart'; |
| 16 import 'summary.dart'; | |
| 17 | 10 |
| 18 final _checkerLogger = new Logger('dev_compiler.checker'); | 11 final _checkerLogger = new Logger('dev_compiler.checker'); |
| 19 | 12 |
| 20 /// Collects errors, and then sorts them and sends them | 13 /// Collects errors, and then sorts them and sends them |
| 21 class ErrorCollector implements AnalysisErrorListener { | 14 class ErrorCollector implements AnalysisErrorListener { |
| 22 final AnalysisErrorListener listener; | 15 final AnalysisErrorListener listener; |
| 23 final List<AnalysisError> _errors = []; | 16 final List<AnalysisError> _errors = []; |
| 24 | 17 |
| 25 ErrorCollector(this.listener); | 18 ErrorCollector(this.listener); |
| 26 | 19 |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 94 _checkerLogger.log(level, text); | 87 _checkerLogger.log(level, text); |
| 95 } | 88 } |
| 96 } | 89 } |
| 97 | 90 |
| 98 // TODO(jmesserly): remove log levels, instead just use severity. | 91 // TODO(jmesserly): remove log levels, instead just use severity. |
| 99 const _severityToLevel = const { | 92 const _severityToLevel = const { |
| 100 ErrorSeverity.ERROR: Level.SEVERE, | 93 ErrorSeverity.ERROR: Level.SEVERE, |
| 101 ErrorSeverity.WARNING: Level.WARNING, | 94 ErrorSeverity.WARNING: Level.WARNING, |
| 102 ErrorSeverity.INFO: Level.INFO | 95 ErrorSeverity.INFO: Level.INFO |
| 103 }; | 96 }; |
| 104 | |
| 105 /// A reporter that gathers all the information in a [GlobalSummary]. | |
| 106 class SummaryReporter implements AnalysisErrorListener { | |
| 107 GlobalSummary result = new GlobalSummary(); | |
| 108 final Level _level; | |
| 109 final AnalysisContext _context; | |
| 110 | |
| 111 SummaryReporter(this._context, [this._level = Level.ALL]); | |
| 112 | |
| 113 IndividualSummary _getIndividualSummary(Uri uri) { | |
| 114 if (uri.path.endsWith('.html')) { | |
| 115 return result.loose.putIfAbsent('$uri', () => new HtmlSummary('$uri')); | |
| 116 } | |
| 117 | |
| 118 var container; | |
| 119 if (uri.scheme == 'package') { | |
| 120 var pname = path.split(uri.path)[0]; | |
| 121 result.packages.putIfAbsent(pname, () => new PackageSummary(pname)); | |
| 122 container = result.packages[pname].libraries; | |
| 123 } else if (uri.scheme == 'dart') { | |
| 124 container = result.system; | |
| 125 } else { | |
| 126 container = result.loose; | |
| 127 } | |
| 128 return container.putIfAbsent('$uri', () => new LibrarySummary('$uri')); | |
| 129 } | |
| 130 | |
| 131 void onError(AnalysisError error) { | |
| 132 // Only summarize messages per configured logging level | |
| 133 var code = error.errorCode; | |
| 134 if (_severityToLevel[code.errorSeverity] < _level) return; | |
| 135 | |
| 136 var span = _toSpan(_context, error); | |
| 137 var summary = _getIndividualSummary(error.source.uri); | |
| 138 if (summary is LibrarySummary) { | |
| 139 summary.recordSourceLines(error.source.uri, () { | |
| 140 // TODO(jmesserly): parsing is serious overkill for this. | |
| 141 // Should be cached, but still. | |
| 142 // On the other hand, if we are going to parse, we could get a much | |
| 143 // better source lines of code estimate by excluding things like | |
| 144 // comments, blank lines, and closing braces. | |
| 145 var unit = _context.parseCompilationUnit(error.source); | |
| 146 return unit.lineInfo.getLocation(unit.endToken.end).lineNumber; | |
| 147 }); | |
| 148 } | |
| 149 summary.messages.add(new MessageSummary(errorCodeName(code), | |
| 150 code.errorSeverity.displayName, span, error.message)); | |
| 151 } | |
| 152 | |
| 153 // TODO(jmesserly): fix to not depend on SourceSpan. This will be really slow | |
| 154 // because it will reload source text from disk, for every single message... | |
| 155 SourceSpanWithContext _toSpan(AnalysisContext context, AnalysisError error) { | |
| 156 var source = error.source; | |
| 157 var lineInfo = context.computeLineInfo(source); | |
| 158 var content = context.getContents(source).data; | |
| 159 var start = error.offset; | |
| 160 var end = start + error.length; | |
| 161 return createSpanHelper(lineInfo, start, end, source, content); | |
| 162 } | |
| 163 | |
| 164 void clearLibrary(Uri uri) { | |
| 165 (_getIndividualSummary(uri) as LibrarySummary).clear(); | |
| 166 } | |
| 167 | |
| 168 void clearHtml(Uri uri) { | |
| 169 HtmlSummary htmlSummary = result.loose['$uri']; | |
| 170 if (htmlSummary != null) htmlSummary.messages.clear(); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 /// Produces a string representation of the summary. | |
| 175 String summaryToString(GlobalSummary summary) { | |
| 176 var counter = new _Counter(); | |
| 177 summary.accept(counter); | |
| 178 | |
| 179 var table = new _Table(); | |
| 180 // Declare columns and add header | |
| 181 table.declareColumn('package'); | |
| 182 table.declareColumn('AnalyzerError', abbreviate: true); | |
| 183 | |
| 184 var activeInfoTypes = counter.totals.keys; | |
| 185 activeInfoTypes.forEach((t) => table.declareColumn(t, abbreviate: true)); | |
| 186 table.declareColumn('LinesOfCode', abbreviate: true); | |
| 187 table.addHeader(); | |
| 188 | |
| 189 // Add entries for each package | |
| 190 appendCount(count) => table.addEntry(count == null ? 0 : count); | |
| 191 for (var package in counter.errorCount.keys) { | |
| 192 appendCount(package); | |
| 193 appendCount(counter.errorCount[package]['AnalyzerError']); | |
| 194 activeInfoTypes.forEach((t) => appendCount(counter.errorCount[package][t])); | |
| 195 appendCount(counter.linesOfCode[package]); | |
| 196 } | |
| 197 | |
| 198 // Add totals, percents and a new header for quick reference | |
| 199 table.addEmptyRow(); | |
| 200 table.addHeader(); | |
| 201 table.addEntry('total'); | |
| 202 appendCount(counter.totals['AnalyzerError']); | |
| 203 activeInfoTypes.forEach((t) => appendCount(counter.totals[t])); | |
| 204 appendCount(counter.totalLinesOfCode); | |
| 205 | |
| 206 appendPercent(count, total) { | |
| 207 if (count == null) count = 0; | |
| 208 var value = (count * 100 / total).toStringAsFixed(2); | |
| 209 table.addEntry(value); | |
| 210 } | |
| 211 | |
| 212 var totalLOC = counter.totalLinesOfCode; | |
| 213 table.addEntry('%'); | |
| 214 appendPercent(counter.totals['AnalyzerError'], totalLOC); | |
| 215 activeInfoTypes.forEach((t) => appendPercent(counter.totals[t], totalLOC)); | |
| 216 appendCount(100); | |
| 217 | |
| 218 return table.toString(); | |
| 219 } | |
| 220 | |
| 221 /// Helper class to combine all the information in table form. | |
| 222 class _Table { | |
| 223 int _totalColumns = 0; | |
| 224 int get totalColumns => _totalColumns; | |
| 225 | |
| 226 /// Abbreviations, used to make headers shorter. | |
| 227 Map<String, String> abbreviations = {}; | |
| 228 | |
| 229 /// Width of each column. | |
| 230 List<int> widths = <int>[]; | |
| 231 | |
| 232 /// The header for each column (`header.length == totalColumns`). | |
| 233 List header = []; | |
| 234 | |
| 235 /// Each row on the table. Note that all rows have the same size | |
| 236 /// (`rows[*].length == totalColumns`). | |
| 237 List<List> rows = []; | |
| 238 | |
| 239 /// Whether we started adding entries. Indicates that no more columns can be | |
| 240 /// added. | |
| 241 bool _sealed = false; | |
| 242 | |
| 243 /// Current row being built by [addEntry]. | |
| 244 List _currentRow; | |
| 245 | |
| 246 /// Add a column with the given [name]. | |
| 247 void declareColumn(String name, {bool abbreviate: false}) { | |
| 248 assert(!_sealed); | |
| 249 var headerName = name; | |
| 250 if (abbreviate) { | |
| 251 // abbreviate the header by using only the capital initials. | |
| 252 headerName = name.replaceAll(new RegExp('[a-z]'), ''); | |
| 253 while (abbreviations[headerName] != null) headerName = "$headerName'"; | |
| 254 abbreviations[headerName] = name; | |
| 255 } | |
| 256 widths.add(max(5, headerName.length + 1) as int); | |
| 257 header.add(headerName); | |
| 258 _totalColumns++; | |
| 259 } | |
| 260 | |
| 261 /// Add an entry in the table, creating a new row each time [totalColumns] | |
| 262 /// entries are added. | |
| 263 void addEntry(entry) { | |
| 264 if (_currentRow == null) { | |
| 265 _sealed = true; | |
| 266 _currentRow = []; | |
| 267 } | |
| 268 int pos = _currentRow.length; | |
| 269 assert(pos < _totalColumns); | |
| 270 | |
| 271 widths[pos] = max(widths[pos], '$entry'.length + 1); | |
| 272 _currentRow.add('$entry'); | |
| 273 | |
| 274 if (pos + 1 == _totalColumns) { | |
| 275 rows.add(_currentRow); | |
| 276 _currentRow = []; | |
| 277 } | |
| 278 } | |
| 279 | |
| 280 /// Add an empty row to divide sections of the table. | |
| 281 void addEmptyRow() { | |
| 282 var emptyRow = []; | |
| 283 for (int i = 0; i < _totalColumns; i++) { | |
| 284 emptyRow.add('-' * widths[i]); | |
| 285 } | |
| 286 rows.add(emptyRow); | |
| 287 } | |
| 288 | |
| 289 /// Enter the header titles. OK to do so more than once in long tables. | |
| 290 void addHeader() { | |
| 291 rows.add(header); | |
| 292 } | |
| 293 | |
| 294 /// Generates a string representation of the table to print on a terminal. | |
| 295 // TODO(sigmund): add also a .csv format | |
| 296 String toString() { | |
| 297 var sb = new StringBuffer(); | |
| 298 sb.write('\n'); | |
| 299 for (var row in rows) { | |
| 300 for (int i = 0; i < _totalColumns; i++) { | |
| 301 var entry = row[i]; | |
| 302 // Align first column to the left, everything else to the right. | |
| 303 sb.write( | |
| 304 i == 0 ? entry.padRight(widths[i]) : entry.padLeft(widths[i] + 1)); | |
| 305 } | |
| 306 sb.write('\n'); | |
| 307 } | |
| 308 sb.write('\nWhere:\n'); | |
| 309 for (var id in abbreviations.keys) { | |
| 310 sb.write(' $id:'.padRight(7)); | |
| 311 sb.write(' ${abbreviations[id]}\n'); | |
| 312 } | |
| 313 return sb.toString(); | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 /// An example visitor that counts the number of errors per package and total. | |
| 318 class _Counter extends RecursiveSummaryVisitor { | |
| 319 String _currentPackage; | |
| 320 String get currentPackage => | |
| 321 _currentPackage != null ? _currentPackage : "*other*"; | |
| 322 var sb = new StringBuffer(); | |
| 323 Map<String, Map<String, int>> errorCount = <String, Map<String, int>>{}; | |
| 324 Map<String, int> linesOfCode = <String, int>{}; | |
| 325 Map<String, int> totals = <String, int>{}; | |
| 326 int totalLinesOfCode = 0; | |
| 327 | |
| 328 void visitGlobal(GlobalSummary global) { | |
| 329 if (!global.system.isEmpty) { | |
| 330 for (var lib in global.system.values) { | |
| 331 lib.accept(this); | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 if (!global.packages.isEmpty) { | |
| 336 for (var lib in global.packages.values) { | |
| 337 lib.accept(this); | |
| 338 } | |
| 339 } | |
| 340 | |
| 341 if (!global.loose.isEmpty) { | |
| 342 for (var lib in global.loose.values) { | |
| 343 lib.accept(this); | |
| 344 } | |
| 345 } | |
| 346 } | |
| 347 | |
| 348 void visitPackage(PackageSummary package) { | |
| 349 _currentPackage = package.name; | |
| 350 super.visitPackage(package); | |
| 351 _currentPackage = null; | |
| 352 } | |
| 353 | |
| 354 void visitLibrary(LibrarySummary lib) { | |
| 355 super.visitLibrary(lib); | |
| 356 linesOfCode.putIfAbsent(currentPackage, () => 0); | |
| 357 linesOfCode[currentPackage] += lib.lines; | |
| 358 totalLinesOfCode += lib.lines; | |
| 359 } | |
| 360 | |
| 361 visitMessage(MessageSummary message) { | |
| 362 var kind = message.kind; | |
| 363 errorCount.putIfAbsent(currentPackage, () => <String, int>{}); | |
| 364 errorCount[currentPackage].putIfAbsent(kind, () => 0); | |
| 365 errorCount[currentPackage][kind]++; | |
| 366 totals.putIfAbsent(kind, () => 0); | |
| 367 totals[kind]++; | |
| 368 } | |
| 369 } | |
| OLD | NEW |