| Index: lib/src/report/html_reporter.dart
|
| diff --git a/lib/src/report/html_reporter.dart b/lib/src/report/html_reporter.dart
|
| deleted file mode 100644
|
| index 13ba53611ecce59f634148318fa8730deba8d047..0000000000000000000000000000000000000000
|
| --- a/lib/src/report/html_reporter.dart
|
| +++ /dev/null
|
| @@ -1,532 +0,0 @@
|
| -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -import 'dart:collection' show LinkedHashSet;
|
| -import 'dart:convert' show HTML_ESCAPE;
|
| -import 'dart:io';
|
| -
|
| -import 'package:analyzer/src/generated/engine.dart';
|
| -import 'package:analyzer/src/generated/error.dart';
|
| -import 'package:analyzer/src/generated/source.dart';
|
| -import 'package:path/path.dart' as path;
|
| -import 'package:source_span/source_span.dart';
|
| -import 'package:yaml/yaml.dart' as yaml;
|
| -
|
| -import '../../devc.dart';
|
| -import '../options.dart';
|
| -import '../report.dart';
|
| -import '../summary.dart';
|
| -import 'html_gen.dart';
|
| -
|
| -/// Generate a compilation summary using the [Primer](http://primercss.io) css.
|
| -class HtmlReporter implements AnalysisErrorListener {
|
| - final AnalysisContext context;
|
| - SummaryReporter reporter;
|
| - List<AnalysisError> errors = [];
|
| -
|
| - HtmlReporter(this.context) {
|
| - reporter = new SummaryReporter(context);
|
| - }
|
| -
|
| - void onError(AnalysisError error) {
|
| - try {
|
| - reporter.onError(error);
|
| - } catch (e, st) {
|
| - // TODO: This can fail when extracting context spans.
|
| - print('${e}:${st}');
|
| - }
|
| -
|
| - errors.add(error);
|
| - }
|
| -
|
| - void finish(CompilerOptions options) {
|
| - GlobalSummary result = reporter.result;
|
| -
|
| - // Find all referenced packages - both those with and without issues.
|
| - List<String> allPackages = context.sources
|
| - .where((s) => s.uriKind == UriKind.PACKAGE_URI)
|
| - .map((s) => s.uri.pathSegments.first)
|
| - .toSet()
|
| - .toList();
|
| -
|
| - String input = options.inputs.first;
|
| - List<SummaryInfo> summaries = [];
|
| -
|
| - // Hoist the self-ref package to an `Application` category.
|
| - String packageName = _getPackageName();
|
| - if (result.packages.containsKey(packageName)) {
|
| - PackageSummary summary = result.packages[packageName];
|
| - List<MessageSummary> issues = summary.libraries.values
|
| - .expand((LibrarySummary l) => l.messages)
|
| - .toList();
|
| - summaries.add(new SummaryInfo(
|
| - 'Application code', packageName, 'package:${packageName}', issues));
|
| - }
|
| -
|
| - // package: code
|
| - List<String> keys = result.packages.keys.toList();
|
| - allPackages.forEach((name) {
|
| - if (!keys.contains(name)) keys.add(name);
|
| - });
|
| - keys.sort();
|
| -
|
| - for (String name in keys) {
|
| - if (name == packageName) continue;
|
| -
|
| - PackageSummary summary = result.packages[name];
|
| -
|
| - if (summary == null) {
|
| - summaries.add(new SummaryInfo('Package: code', name));
|
| - } else {
|
| - List<MessageSummary> issues = summary.libraries.values
|
| - .expand((LibrarySummary summary) => summary.messages)
|
| - .toList();
|
| - summaries.add(
|
| - new SummaryInfo('Package: code', name, 'package:${name}', issues));
|
| - }
|
| - }
|
| -
|
| - // dart: code
|
| - keys = result.system.keys.toList()..sort();
|
| - for (String name in keys) {
|
| - LibrarySummary summary = result.system[name];
|
| - if (summary.messages.isNotEmpty) {
|
| - summaries.add(new SummaryInfo(
|
| - 'Dart: code', name, 'dart:${name}', summary.messages));
|
| - }
|
| - }
|
| -
|
| - // Loose files
|
| - if (result.loose.isNotEmpty) {
|
| - List<MessageSummary> issues = result.loose.values
|
| - .expand((IndividualSummary summary) => summary.messages)
|
| - .toList();
|
| - summaries.add(new SummaryInfo('Files', 'files', 'files', issues));
|
| - }
|
| -
|
| - // Write the html report.
|
| - var page = new Page(input, input, summaries);
|
| - var outPath = '${input.replaceAll('.', '_')}_results.html';
|
| - var link = outPath;
|
| - if (options.serverMode) {
|
| - var base = path.basename(outPath);
|
| - outPath = path.join(options.codegenOptions.outputDir, base);
|
| - link = 'http://${options.host}:${options.port}/$base';
|
| - }
|
| - new File(outPath).writeAsStringSync(page.create());
|
| - print('Compilation report available at ${link}; ${errors.length} issues.');
|
| - }
|
| -
|
| - String _getPackageName() {
|
| - File file = new File('pubspec.yaml');
|
| - if (file.existsSync()) {
|
| - var doc = yaml.loadYaml(file.readAsStringSync());
|
| - return doc['name'];
|
| - } else {
|
| - return null;
|
| - }
|
| - }
|
| -}
|
| -
|
| -class SummaryInfo {
|
| - static int _compareIssues(MessageSummary a, MessageSummary b) {
|
| - int result = _compareSeverity(a.level, b.level);
|
| - if (result != 0) return result;
|
| - result = a.span.sourceUrl.toString().compareTo(b.span.sourceUrl.toString());
|
| - if (result != 0) return result;
|
| - return a.span.start.compareTo(b.span.start);
|
| - }
|
| -
|
| - static const _sevTable = const {'error': 0, 'warning': 1, 'info': 2};
|
| -
|
| - static int _compareSeverity(String a, String b) =>
|
| - _sevTable[a] - _sevTable[b];
|
| -
|
| - final String category;
|
| - final String shortTitle;
|
| - final String longTitle;
|
| - final List<MessageSummary> issues;
|
| -
|
| - SummaryInfo(this.category, this.shortTitle, [this.longTitle, this.issues]) {
|
| - issues?.sort(_compareIssues);
|
| - }
|
| -
|
| - String get ref => longTitle == null ? null : longTitle.replaceAll(':', '_');
|
| -
|
| - int get errorCount =>
|
| - issues == null ? 0 : issues.where((i) => i.level == 'error').length;
|
| - int get warningCount =>
|
| - issues == null ? 0 : issues.where((i) => i.level == 'warning').length;
|
| - int get infoCount =>
|
| - issues == null ? 0 : issues.where((i) => i.level == 'info').length;
|
| -
|
| - bool get hasIssues => issues == null ? false : issues.isNotEmpty;
|
| -}
|
| -
|
| -class Page extends HtmlGen {
|
| - final String pageTitle;
|
| - final String inputFile;
|
| - final List<SummaryInfo> summaries;
|
| -
|
| - Page(this.pageTitle, this.inputFile, this.summaries);
|
| -
|
| - String get subTitle => 'DDC compilation report for ${inputFile}';
|
| -
|
| - String create() {
|
| - start(
|
| - title: 'DDC ${pageTitle}',
|
| - theme: 'http://primercss.io/docs.css',
|
| - inlineStyle: _css);
|
| -
|
| - header();
|
| - startTag('div', c: "container");
|
| - startTag('div', c: "columns docs-layout");
|
| -
|
| - startTag('div', c: "column one-fourth");
|
| - nav();
|
| - endTag();
|
| -
|
| - startTag('div', c: "column three-fourths");
|
| - subtitle();
|
| - contents();
|
| - endTag();
|
| -
|
| - endTag();
|
| - footer();
|
| - endTag();
|
| - end();
|
| -
|
| - return toString();
|
| - }
|
| -
|
| - void header() {
|
| - startTag('header', c: "masthead");
|
| - startTag('div', c: "container");
|
| - title();
|
| - startTag('nav', c: "masthead-nav");
|
| - tag("a",
|
| - href:
|
| - "https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md",
|
| - text: "Strong Mode");
|
| - tag("a",
|
| - href: "https://github.com/dart-lang/dev_compiler", text: "DDC Repo");
|
| - endTag();
|
| - endTag();
|
| - endTag();
|
| - }
|
| -
|
| - void title() {
|
| - tag("a", c: "masthead-logo", text: pageTitle);
|
| - }
|
| -
|
| - void subtitle() {
|
| - tag("h1", text: subTitle, c: "page-title");
|
| - }
|
| -
|
| - void contents() {
|
| - int errorCount = summaries.fold(
|
| - 0, (int count, SummaryInfo info) => count + info.errorCount);
|
| - int warningCount = summaries.fold(
|
| - 0, (int count, SummaryInfo info) => count + info.warningCount);
|
| - int infoCount = summaries.fold(
|
| - 0, (int count, SummaryInfo info) => count + info.infoCount);
|
| -
|
| - List<String> messages = [];
|
| -
|
| - if (errorCount > 0) {
|
| - messages.add("${_comma(errorCount)} ${_pluralize(errorCount, 'error')}");
|
| - }
|
| - if (warningCount > 0) {
|
| - messages.add(
|
| - "${_comma(warningCount)} ${_pluralize(warningCount, 'warning')}");
|
| - }
|
| - if (infoCount > 0) {
|
| - messages.add("${_comma(infoCount)} ${_pluralize(infoCount, 'info')}");
|
| - }
|
| -
|
| - String message;
|
| -
|
| - if (messages.isEmpty) {
|
| - message = 'no issues';
|
| - } else if (messages.length == 2) {
|
| - message = messages.join(' and ');
|
| - } else {
|
| - message = messages.join(', ');
|
| - }
|
| -
|
| - tag("p", text: 'Found ${message}.');
|
| -
|
| - for (SummaryInfo info in summaries) {
|
| - if (!info.hasIssues) continue;
|
| -
|
| - tag("h2", text: info.longTitle, attributes: "id=${info.ref}");
|
| - contentItem(info);
|
| - }
|
| - }
|
| -
|
| - void nav() {
|
| - startTag("nav", c: "menu docs-menu");
|
| - Iterable<String> categories =
|
| - new LinkedHashSet.from(summaries.map((s) => s.category));
|
| - for (String category in categories) {
|
| - navItems(category, summaries.where((s) => s.category == category));
|
| - }
|
| - endTag();
|
| - }
|
| -
|
| - void navItems(String category, Iterable<SummaryInfo> infos) {
|
| - if (infos.isEmpty) return;
|
| -
|
| - span(c: "menu-heading", text: category);
|
| -
|
| - for (SummaryInfo info in infos) {
|
| - if (info.hasIssues) {
|
| - startTag("a", c: "menu-item", attributes: 'href="#${info.ref}"');
|
| -
|
| - span(text: info.shortTitle);
|
| -
|
| - int errorCount = info.errorCount;
|
| - int warningCount = info.warningCount;
|
| - int infoCount = info.infoCount;
|
| -
|
| - if (infoCount > 0) {
|
| - span(c: "counter info", text: '${_comma(infoCount)}');
|
| - }
|
| - if (warningCount > 0) {
|
| - span(c: "counter warning", text: '${_comma(warningCount)}');
|
| - }
|
| - if (errorCount > 0) {
|
| - span(c: "counter error", text: '${_comma(errorCount)}');
|
| - }
|
| -
|
| - endTag();
|
| - } else {
|
| - tag("div", c: "menu-item", text: info.shortTitle);
|
| - }
|
| - }
|
| - }
|
| -
|
| - void footer() {
|
| - startTag('footer', c: "footer");
|
| - writeln("${inputFile} • DDC version ${devCompilerVersion}");
|
| - endTag();
|
| - }
|
| -
|
| - void contentItem(SummaryInfo info) {
|
| - int errors = info.errorCount;
|
| - int warnings = info.warningCount;
|
| - int infos = info.infoCount;
|
| -
|
| - if (errors > 0) {
|
| - span(
|
| - c: 'counter error',
|
| - text: '${_comma(errors)} ${_pluralize(errors, 'error')}');
|
| - }
|
| - if (warnings > 0) {
|
| - span(
|
| - c: 'counter warning',
|
| - text: '${_comma(warnings)} ${_pluralize(warnings, 'warning')}');
|
| - }
|
| - if (infos > 0) {
|
| - span(
|
| - c: 'counter info',
|
| - text: '${_comma(infos)} ${_pluralize(infos, 'info')}');
|
| - }
|
| -
|
| - info.issues.forEach(emitMessage);
|
| - }
|
| -
|
| - void emitMessage(MessageSummary issue) {
|
| - startTag('div', c: 'file');
|
| - startTag('div', c: 'file-header');
|
| - span(c: 'counter ${issue.level}', text: issue.kind);
|
| - span(c: 'file-info', text: issue.span.sourceUrl.toString());
|
| - endTag();
|
| -
|
| - startTag('div', c: 'blob-wrapper');
|
| - startTag('table');
|
| - startTag('tbody');
|
| -
|
| - // TODO: Widen the line extracts - +2 on either side.
|
| - // TODO: Highlight error ranges.
|
| - if (issue.span is SourceSpanWithContext) {
|
| - SourceSpanWithContext context = issue.span;
|
| - String text = context.context.trimRight();
|
| - int lineNum = context.start.line;
|
| -
|
| - for (String line in text.split('\n')) {
|
| - lineNum++;
|
| - startTag('tr');
|
| - tag('td', c: 'blob-num', text: lineNum.toString());
|
| - tag('td',
|
| - c: 'blob-code blob-code-inner', text: HTML_ESCAPE.convert(line));
|
| - endTag();
|
| - }
|
| - }
|
| -
|
| - startTag('tr', c: 'row-expandable');
|
| - tag('td', c: 'blob-num blob-num-expandable');
|
| - tag('td',
|
| - c: 'blob-code blob-code-expandable',
|
| - text: HTML_ESCAPE.convert(issue.message));
|
| - endTag();
|
| -
|
| - endTag();
|
| - endTag();
|
| - endTag();
|
| -
|
| - endTag();
|
| - }
|
| -}
|
| -
|
| -String _pluralize(int count, String item) => count == 1 ? item : '${item}s';
|
| -
|
| -String _comma(int count) {
|
| - String str = '${count}';
|
| - if (str.length <= 3) return str;
|
| - int pos = str.length - 3;
|
| - return str.substring(0, pos) + ',' + str.substring(pos);
|
| -}
|
| -
|
| -/// Deltas from the baseline Primer css (http://primercss.io/docs.css).
|
| -const String _css = '''
|
| -h2 {
|
| - margin-top: 2em;
|
| - padding-bottom: 0.3em;
|
| - font-size: 1.75em;
|
| - line-height: 1.225;
|
| - border-bottom: 1px solid #eee;
|
| -}
|
| -
|
| -.error {
|
| - background-color: #bf1515;
|
| -}
|
| -
|
| -.menu-item .counter {
|
| - margin-bottom: 0;
|
| -}
|
| -
|
| -.counter.error {
|
| - color: #eee;
|
| - text-shadow: none;
|
| -}
|
| -
|
| -.warning {
|
| - background-color: #ffe5a7;
|
| -}
|
| -
|
| -.counter.warning {
|
| - color: #777;
|
| -}
|
| -
|
| -.counter.error,
|
| -.counter.warning,
|
| -.counter.info {
|
| - margin-bottom: 0;
|
| -}
|
| -
|
| -nav.menu .menu-item {
|
| - overflow-x: auto;
|
| -}
|
| -
|
| -.info {
|
| - background-color: #eee;
|
| -}
|
| -
|
| -/* code snippets styles */
|
| -
|
| -.file {
|
| - position: relative;
|
| - margin-top: 20px;
|
| - margin-bottom: 15px;
|
| - border: 1px solid #ddd;
|
| - border-radius: 3px;
|
| -}
|
| -
|
| -.file-header {
|
| - padding: 5px 10px;
|
| - background-color: #f7f7f7;
|
| - border-bottom: 1px solid #d8d8d8;
|
| - border-top-left-radius: 2px;
|
| - border-top-right-radius: 2px;
|
| -}
|
| -
|
| -.file-info {
|
| - font-size: 12px;
|
| - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
| -}
|
| -
|
| -table {
|
| - border-collapse: collapse;
|
| - border-spacing: 0;
|
| - margin-bottom: 0;
|
| -}
|
| -
|
| -.blob-wrapper {
|
| - overflow-x: auto;
|
| - overflow-y: hidden;
|
| -}
|
| -
|
| -.blob-num {
|
| - width: 1%;
|
| - min-width: 50px;
|
| - white-space: nowrap;
|
| - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
| - font-size: 12px;
|
| - line-height: 18px;
|
| - color: rgba(0,0,0,0.3);
|
| - vertical-align: top;
|
| - text-align: right;
|
| - border: solid #eee;
|
| - border-width: 0 1px 0 0;
|
| - cursor: pointer;
|
| - -webkit-user-select: none;
|
| - -moz-user-select: none;
|
| - -ms-user-select: none;
|
| - user-select: none;
|
| - padding-left: 10px;
|
| - padding-right: 10px;
|
| -}
|
| -
|
| -.blob-code {
|
| - padding-left: 10px;
|
| - padding-right: 10px;
|
| - vertical-align: top;
|
| -}
|
| -
|
| -.blob-code-inner {
|
| - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
| - font-size: 12px;
|
| - color: #333;
|
| - white-space: pre;
|
| - overflow: visible;
|
| - word-wrap: normal;
|
| -}
|
| -
|
| -.row-expandable {
|
| - border-top: 1px solid #d8d8d8;
|
| - border-bottom-left-radius: 3px;
|
| - border-bottom-right-radius: 3px;
|
| -}
|
| -
|
| -.blob-num-expandable,
|
| -.blob-code-expandable {
|
| - vertical-align: middle;
|
| - font-size: 14px;
|
| - border-color: #d2dff0;
|
| -}
|
| -
|
| -.blob-num-expandable {
|
| - background-color: #edf2f9;
|
| - border-bottom-left-radius: 3px;
|
| -}
|
| -
|
| -.blob-code-expandable {
|
| - padding-top: 4px;
|
| - padding-bottom: 4px;
|
| - background-color: #f4f7fb;
|
| - border-width: 1px 0;
|
| - border-bottom-right-radius: 3px;
|
| -}
|
| -''';
|
|
|