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 |