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

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

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

Powered by Google App Engine
This is Rietveld 408576698