Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(391)

Side by Side Diff: lib/src/report/html_reporter.dart

Issue 1419973003: generate an html report of the compilation results (Closed) Base URL: https://github.com/dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« lib/src/report/html_gen.dart ('K') | « lib/src/report/html_gen.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library dev_compiler.src.html_reporter;
6
7 import 'dart:collection' show LinkedHashSet;
8 import 'dart:convert' show HTML_ESCAPE;
9 import 'dart:io';
10
11 import 'package:analyzer/src/generated/engine.dart';
12 import 'package:analyzer/src/generated/error.dart';
13 import 'package:analyzer/src/generated/source.dart';
14 import 'package:source_span/source_span.dart';
15 import 'package:yaml/yaml.dart' as yaml;
16
17 import '../../devc.dart';
18 import '../options.dart';
19 import '../report.dart';
20 import '../summary.dart';
21 import 'html_gen.dart';
22
23 /// Generate a compilation summary using the [Primer](http://primercss.io) css.
24 class HtmlReporter implements AnalysisErrorListener {
25 final AnalysisContext context;
26 SummaryReporter reporter;
27 List<AnalysisError> errors = [];
28
29 HtmlReporter(this.context) {
30 reporter = new SummaryReporter(context);
31 }
32
33 void onError(AnalysisError error) {
34 try {
35 reporter.onError(error);
36 } catch (e, st) {
37 // TOOD: This can fail when extracting context spans.
vsm 2015/10/27 20:04:55 s/TOOD/TODO/
devoncarew 2015/10/27 20:32:35 Done.
38 print(e);
39 print(st);
40 }
41
42 errors.add(error);
43 }
44
45 void finish(CompilerOptions options) {
46 GlobalSummary result = reporter.result;
47
48 // Find all referenced packages - both those with and without issues.
49 List<String> allPackages = context.sources
50 .where((s) => s.uriKind == UriKind.PACKAGE_URI)
51 .map((s) => s.uri.pathSegments.first)
52 .toSet()
53 .toList();
54
55 String input = options.inputs.first;
56 List<SummaryInfo> summaries = [];
57
58 // Hoist the self-ref package to an `Application` category.
59 String packageName = _getPackageName();
60 if (result.packages.containsKey(packageName)) {
61 PackageSummary summary = result.packages[packageName];
62 List<MessageSummary> issues = summary.libraries.values
63 .expand((LibrarySummary l) => l.messages)
64 .toList();
65 summaries.add(new SummaryInfo(
66 'Application code', packageName, 'package:${packageName}', issues));
67 }
68
69 // package: code
70 List<String> keys = result.packages.keys.toList();
71 allPackages.forEach((name) {
72 if (!keys.contains(name)) keys.add(name);
73 });
74 keys.sort();
75
76 for (String name in keys) {
77 if (name == packageName) continue;
78
79 PackageSummary summary = result.packages[name];
80
81 if (summary == null) {
82 summaries.add(new SummaryInfo('Package: code', name));
83 } else {
84 List<MessageSummary> issues = summary.libraries.values
85 .expand((LibrarySummary summary) => summary.messages)
86 .toList();
87 summaries.add(
88 new SummaryInfo('Package: code', name, 'package:${name}', issues));
89 }
90 }
91
92 // dart: code
93 keys = result.system.keys.toList()..sort();
94 for (String name in keys) {
95 LibrarySummary summary = result.system[name];
96 summaries.add(new SummaryInfo(
97 'Dart: code', name, 'dart:${name}', summary.messages));
98 }
99
100 // Loose files
101 if (result.loose.isNotEmpty) {
102 List<MessageSummary> issues = result.loose.values
103 .expand((IndividualSummary summary) => summary.messages)
104 .toList();
105 summaries.add(new SummaryInfo('Files', 'files', 'files', issues));
106 }
107
108 // Write the html report.
109 Page page = new Page(input, input, summaries);
110 String path = '${input.replaceAll('.', '_')}_results.html';
111 new File(path).writeAsStringSync(page.create());
112 print('Compilation report available at ${path}; ${errors.length} issues.');
113 }
114
115 String _getPackageName() {
116 File file = new File('pubspec.yaml');
117 if (file.existsSync()) {
118 var doc = yaml.loadYaml(file.readAsStringSync());
119 return doc['name'];
120 } else {
121 return null;
122 }
123 }
124 }
125
126 class SummaryInfo {
127 static int _compareIssues(MessageSummary a, MessageSummary b) {
128 int result = _compareSeverity(a.level, b.level);
129 if (result != 0) return result;
130 result = a.span.sourceUrl.toString().compareTo(b.span.sourceUrl.toString());
131 if (result != 0) return result;
132 return a.span.start.compareTo(b.span.start);
133 }
134
135 static const _sevTable = const {'error': 0, 'warning': 1, 'info': 2};
136
137 static int _compareSeverity(String a, String b) =>
138 _sevTable[a] - _sevTable[b];
139
140 final String category;
141 final String shortTitle;
142 final String longTitle;
143 final List<MessageSummary> issues;
144
145 SummaryInfo(this.category, this.shortTitle, [this.longTitle, this.issues]) {
146 issues?.sort(_compareIssues);
147 }
148
149 String get ref => longTitle == null ? null : longTitle.replaceAll(':', '_');
150
151 int get errorCount =>
152 issues == null ? 0 : issues.where((i) => i.level == 'error').length;
153 int get warningCount =>
154 issues == null ? 0 : issues.where((i) => i.level == 'warning').length;
155 int get infoCount =>
156 issues == null ? 0 : issues.where((i) => i.level == 'info').length;
157
158 bool get hasIssues => issues == null ? false : issues.isNotEmpty;
159 }
160
161 class Page extends HtmlGen {
162 final String pageTitle;
163 final String inputFile;
164 final List<SummaryInfo> summaries;
165
166 Page(this.pageTitle, this.inputFile, this.summaries);
167
168 String get subTitle => 'DDC compilation report for ${inputFile}';
169
170 String create() {
171 start(
172 title: 'DDC ${pageTitle}',
173 theme: 'http://primercss.io/docs.css',
174 inlineStyle: _css);
175
176 header();
177 startTag('div', c: "container");
178 startTag('div', c: "columns docs-layout");
179
180 startTag('div', c: "column one-fourth");
181 nav();
182 endTag();
183
184 startTag('div', c: "column three-fourths");
185 subtitle();
186 contents();
187 endTag();
188
189 endTag();
190 footer();
191 endTag();
192 end();
193
194 return toString();
195 }
196
197 void header() {
198 startTag('header', c: "masthead");
199 startTag('div', c: "container");
200 title();
201 startTag('nav', c: "masthead-nav");
202 tag("a",
203 href:
204 "https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.m d",
205 text: "Strong Mode");
206 tag("a",
207 href: "https://github.com/dart-lang/dev_compiler", text: "DDC Repo");
208 endTag();
209 endTag();
210 endTag();
211 }
212
213 void title() {
214 tag("a", c: "masthead-logo", text: pageTitle);
215 }
216
217 void subtitle() {
218 tag("h1", text: subTitle, c: "page-title");
219 }
220
221 void contents() {
222 int errorCount = summaries.fold(
223 0, (int count, SummaryInfo info) => count + info.errorCount);
224 int warningCount = summaries.fold(
225 0, (int count, SummaryInfo info) => count + info.warningCount);
226 int infoCount = summaries.fold(
227 0, (int count, SummaryInfo info) => count + info.infoCount);
228
229 List<String> messages = [];
230
231 if (errorCount > 0) {
232 messages.add("${_comma(errorCount)} ${_pluralize(errorCount, 'error')}");
233 }
234 if (warningCount > 0) {
235 messages.add(
236 "${_comma(warningCount)} ${_pluralize(warningCount, 'warning')}");
237 }
238 if (infoCount > 0) {
239 messages.add("${_comma(infoCount)} ${_pluralize(infoCount, 'info')}");
240 }
241
242 String message;
243
244 if (messages.isEmpty) {
245 message = 'no issues';
246 } else if (messages.length == 2) {
247 message = messages.join(' and ');
248 } else {
249 message = messages.join(', ');
250 }
251
252 tag("p", text: 'Found ${message}.');
253
254 for (SummaryInfo info in summaries) {
255 if (!info.hasIssues) continue;
256
257 tag("h2", text: info.longTitle, attributes: "id=${info.ref}");
258 contentItem(info);
259 }
260 }
261
262 void nav() {
263 startTag("nav", c: "menu docs-menu");
264 Iterable<String> categories =
265 new LinkedHashSet.from(summaries.map((s) => s.category));
266 for (String category in categories) {
267 navItems(category, summaries.where((s) => s.category == category));
268 }
269 endTag();
270 }
271
272 void navItems(String category, List<SummaryInfo> infos) {
273 if (infos.isEmpty) return;
274
275 span(c: "menu-heading", text: category);
276
277 for (SummaryInfo info in infos) {
278 if (info.hasIssues) {
279 startTag("a", c: "menu-item", attributes: 'href="#${info.ref}"');
280
281 span(text: info.shortTitle);
282
283 int errorCount = info.errorCount;
284 int warningCount = info.warningCount;
285 int infoCount = info.infoCount;
286
287 if (infoCount > 0) {
288 span(c: "counter info", text: '${_comma(infoCount)}');
289 }
290 if (warningCount > 0) {
291 span(c: "counter warning", text: '${_comma(warningCount)}');
292 }
293 if (errorCount > 0) {
294 span(c: "counter error", text: '${_comma(errorCount)}');
295 }
296
297 endTag();
298 } else {
299 tag("div", c: "menu-item", text: info.shortTitle);
300 }
301 }
302 }
303
304 void footer() {
305 startTag('footer', c: "footer");
306 writeln("${inputFile} • DDC version ${devCompilerVersion}");
307 endTag();
308 }
309
310 void contentItem(SummaryInfo info) {
311 int errors = info.errorCount;
312 int warnings = info.warningCount;
313 int infos = info.infoCount;
314
315 if (errors > 0) {
316 span(
317 c: 'counter error',
318 text: '${_comma(errors)} ${_pluralize(errors, 'error')}');
319 }
320 if (warnings > 0) {
321 span(
322 c: 'counter warning',
323 text: '${_comma(warnings)} ${_pluralize(warnings, 'warning')}');
324 }
325 if (infos > 0) {
326 span(
327 c: 'counter info',
328 text: '${_comma(infos)} ${_pluralize(infos, 'info')}');
329 }
330
331 info.issues.forEach(emitMessage);
332 }
333
334 void emitMessage(MessageSummary issue) {
335 startTag('div', c: 'file');
336 startTag('div', c: 'file-header');
337 span(c: 'counter ${issue.level}', text: issue.kind);
338 span(c: 'file-info', text: issue.span.sourceUrl.toString());
339 endTag();
340
341 startTag('div', c: 'blob-wrapper');
342 startTag('table');
343 startTag('tbody');
344
345 // TODO: Widen the line extracts - +2 on either side.
346 // TODO: Highlight error ranges.
347 if (issue.span is SourceSpanWithContext) {
348 SourceSpanWithContext context = issue.span;
349 String text = context.context.trimRight();
350 int lineNum = context.start.line;
351
352 for (String line in text.split('\n')) {
353 lineNum++;
354 startTag('tr');
355 tag('td', c: 'blob-num', text: lineNum.toString());
356 tag('td',
357 c: 'blob-code blob-code-inner', text: HTML_ESCAPE.convert(line));
358 endTag();
359 }
360 }
361
362 startTag('tr', c: 'row-expandable');
363 tag('td', c: 'blob-num blob-num-expandable');
364 tag('td',
365 c: 'blob-code blob-code-expandable',
366 text: HTML_ESCAPE.convert(issue.message));
367 endTag();
368
369 endTag();
370 endTag();
371 endTag();
372
373 endTag();
374 }
375 }
376
377 String _pluralize(int count, String item) => count == 1 ? item : '${item}s';
378
379 String _comma(int count) {
380 String str = '${count}';
381 if (str.length <= 3) return str;
382 int pos = str.length - 3;
383 return str.substring(0, pos) + ',' + str.substring(pos);
384 }
385
386 /// Deltas from the baseline Primer css (http://primercss.io/docs.css).
387 const String _css = '''
388 h2 {
389 margin-top: 2em;
390 padding-bottom: 0.3em;
391 font-size: 1.75em;
392 line-height: 1.225;
393 border-bottom: 1px solid #eee;
394 }
395
396 .error {
397 background-color: #bf1515;
398 }
399
400 .menu-item .counter {
401 margin-bottom: 0;
402 }
403
404 .counter.error {
405 color: #eee;
406 text-shadow: none;
407 }
408
409 .warning {
410 background-color: #ffe5a7;
411 }
412
413 .counter.warning {
414 color: #777;
415 }
416
417 .counter.error,
418 .counter.warning,
419 .counter.info {
420 margin-bottom: 0;
421 }
422
423 nav.menu .menu-item {
424 overflow-x: auto;
425 }
426
427 .info {
428 background-color: #eee;
429 }
430
431 /* code snippets styles */
432
433 .file {
434 position: relative;
435 margin-top: 20px;
436 margin-bottom: 15px;
437 border: 1px solid #ddd;
438 border-radius: 3px;
439 }
440
441 .file-header {
442 padding: 5px 10px;
443 background-color: #f7f7f7;
444 border-bottom: 1px solid #d8d8d8;
445 border-top-left-radius: 2px;
446 border-top-right-radius: 2px;
447 }
448
449 .file-info {
450 font-size: 12px;
451 font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
452 }
453
454 table {
455 border-collapse: collapse;
456 border-spacing: 0;
457 margin-bottom: 0;
458 }
459
460 .blob-wrapper {
461 overflow-x: auto;
462 overflow-y: hidden;
463 }
464
465 .blob-num {
466 width: 1%;
467 min-width: 50px;
468 white-space: nowrap;
469 font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
470 font-size: 12px;
471 line-height: 18px;
472 color: rgba(0,0,0,0.3);
473 vertical-align: top;
474 text-align: right;
475 border: solid #eee;
476 border-width: 0 1px 0 0;
477 cursor: pointer;
478 -webkit-user-select: none;
479 -moz-user-select: none;
480 -ms-user-select: none;
481 user-select: none;
482 padding-left: 10px;
483 padding-right: 10px;
484 }
485
486 .blob-code {
487 padding-left: 10px;
488 padding-right: 10px;
489 vertical-align: top;
490 }
491
492 .blob-code-inner {
493 font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
494 font-size: 12px;
495 color: #333;
496 white-space: pre;
497 overflow: visible;
498 word-wrap: normal;
499 }
500
501 .row-expandable {
502 border-top: 1px solid #d8d8d8;
503 border-bottom-left-radius: 3px;
504 border-bottom-right-radius: 3px;
505 }
506
507 .blob-num-expandable,
508 .blob-code-expandable {
509 vertical-align: middle;
510 font-size: 14px;
511 border-color: #d2dff0;
512 }
513
514 .blob-num-expandable {
515 background-color: #edf2f9;
516 border-bottom-left-radius: 3px;
517 }
518
519 .blob-code-expandable {
520 padding-top: 4px;
521 padding-bottom: 4px;
522 background-color: #f4f7fb;
523 border-width: 1px 0;
524 border-bottom-right-radius: 3px;
525 }
526 ''';
OLDNEW
« lib/src/report/html_gen.dart ('K') | « lib/src/report/html_gen.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698