13ca95b02SDimitry Andric //===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
23ca95b02SDimitry Andric //
33ca95b02SDimitry Andric // The LLVM Compiler Infrastructure
43ca95b02SDimitry Andric //
53ca95b02SDimitry Andric // This file is distributed under the University of Illinois Open Source
63ca95b02SDimitry Andric // License. See LICENSE.TXT for details.
73ca95b02SDimitry Andric //
83ca95b02SDimitry Andric //===----------------------------------------------------------------------===//
93ca95b02SDimitry Andric ///
103ca95b02SDimitry Andric /// \file This file implements the html coverage renderer.
113ca95b02SDimitry Andric ///
123ca95b02SDimitry Andric //===----------------------------------------------------------------------===//
133ca95b02SDimitry Andric
14d88c1a5aSDimitry Andric #include "CoverageReport.h"
153ca95b02SDimitry Andric #include "SourceCoverageViewHTML.h"
163ca95b02SDimitry Andric #include "llvm/ADT/Optional.h"
173ca95b02SDimitry Andric #include "llvm/ADT/SmallString.h"
183ca95b02SDimitry Andric #include "llvm/ADT/StringExtras.h"
19d88c1a5aSDimitry Andric #include "llvm/Support/Format.h"
203ca95b02SDimitry Andric #include "llvm/Support/Path.h"
213ca95b02SDimitry Andric
223ca95b02SDimitry Andric using namespace llvm;
233ca95b02SDimitry Andric
243ca95b02SDimitry Andric namespace {
253ca95b02SDimitry Andric
26d88c1a5aSDimitry Andric // Return a string with the special characters in \p Str escaped.
escape(StringRef Str,const CoverageViewOptions & Opts)27d88c1a5aSDimitry Andric std::string escape(StringRef Str, const CoverageViewOptions &Opts) {
284ba319b5SDimitry Andric std::string TabExpandedResult;
29d88c1a5aSDimitry Andric unsigned ColNum = 0; // Record the column number.
30d88c1a5aSDimitry Andric for (char C : Str) {
314ba319b5SDimitry Andric if (C == '\t') {
324ba319b5SDimitry Andric // Replace '\t' with up to TabSize spaces.
334ba319b5SDimitry Andric unsigned NumSpaces = Opts.TabSize - (ColNum % Opts.TabSize);
34d88c1a5aSDimitry Andric for (unsigned I = 0; I < NumSpaces; ++I)
354ba319b5SDimitry Andric TabExpandedResult += ' ';
36d88c1a5aSDimitry Andric ColNum += NumSpaces;
374ba319b5SDimitry Andric } else {
384ba319b5SDimitry Andric TabExpandedResult += C;
394ba319b5SDimitry Andric if (C == '\n' || C == '\r')
404ba319b5SDimitry Andric ColNum = 0;
414ba319b5SDimitry Andric else
424ba319b5SDimitry Andric ++ColNum;
43d88c1a5aSDimitry Andric }
444ba319b5SDimitry Andric }
454ba319b5SDimitry Andric std::string EscapedHTML;
464ba319b5SDimitry Andric {
474ba319b5SDimitry Andric raw_string_ostream OS{EscapedHTML};
484ba319b5SDimitry Andric printHTMLEscaped(TabExpandedResult, OS);
494ba319b5SDimitry Andric }
504ba319b5SDimitry Andric return EscapedHTML;
51d88c1a5aSDimitry Andric }
52d88c1a5aSDimitry Andric
53d88c1a5aSDimitry Andric // Create a \p Name tag around \p Str, and optionally set its \p ClassName.
tag(const std::string & Name,const std::string & Str,const std::string & ClassName="")54d88c1a5aSDimitry Andric std::string tag(const std::string &Name, const std::string &Str,
55d88c1a5aSDimitry Andric const std::string &ClassName = "") {
56d88c1a5aSDimitry Andric std::string Tag = "<" + Name;
57*b5893f02SDimitry Andric if (!ClassName.empty())
58d88c1a5aSDimitry Andric Tag += " class='" + ClassName + "'";
59d88c1a5aSDimitry Andric return Tag + ">" + Str + "</" + Name + ">";
60d88c1a5aSDimitry Andric }
61d88c1a5aSDimitry Andric
62d88c1a5aSDimitry Andric // Create an anchor to \p Link with the label \p Str.
a(const std::string & Link,const std::string & Str,const std::string & TargetName="")63d88c1a5aSDimitry Andric std::string a(const std::string &Link, const std::string &Str,
64d88c1a5aSDimitry Andric const std::string &TargetName = "") {
65d88c1a5aSDimitry Andric std::string Name = TargetName.empty() ? "" : ("name='" + TargetName + "' ");
66d88c1a5aSDimitry Andric return "<a " + Name + "href='" + Link + "'>" + Str + "</a>";
67d88c1a5aSDimitry Andric }
68d88c1a5aSDimitry Andric
693ca95b02SDimitry Andric const char *BeginHeader =
703ca95b02SDimitry Andric "<head>"
713ca95b02SDimitry Andric "<meta name='viewport' content='width=device-width,initial-scale=1'>"
723ca95b02SDimitry Andric "<meta charset='UTF-8'>";
733ca95b02SDimitry Andric
743ca95b02SDimitry Andric const char *CSSForCoverage =
75d88c1a5aSDimitry Andric R"(.red {
76d88c1a5aSDimitry Andric background-color: #ffd0d0;
773ca95b02SDimitry Andric }
783ca95b02SDimitry Andric .cyan {
793ca95b02SDimitry Andric background-color: cyan;
803ca95b02SDimitry Andric }
813ca95b02SDimitry Andric body {
823ca95b02SDimitry Andric font-family: -apple-system, sans-serif;
833ca95b02SDimitry Andric }
843ca95b02SDimitry Andric pre {
853ca95b02SDimitry Andric margin-top: 0px !important;
863ca95b02SDimitry Andric margin-bottom: 0px !important;
873ca95b02SDimitry Andric }
883ca95b02SDimitry Andric .source-name-title {
893ca95b02SDimitry Andric padding: 5px 10px;
903ca95b02SDimitry Andric border-bottom: 1px solid #dbdbdb;
913ca95b02SDimitry Andric background-color: #eee;
92d88c1a5aSDimitry Andric line-height: 35px;
933ca95b02SDimitry Andric }
943ca95b02SDimitry Andric .centered {
953ca95b02SDimitry Andric display: table;
96d88c1a5aSDimitry Andric margin-left: left;
973ca95b02SDimitry Andric margin-right: auto;
983ca95b02SDimitry Andric border: 1px solid #dbdbdb;
993ca95b02SDimitry Andric border-radius: 3px;
1003ca95b02SDimitry Andric }
1013ca95b02SDimitry Andric .expansion-view {
1023ca95b02SDimitry Andric background-color: rgba(0, 0, 0, 0);
1033ca95b02SDimitry Andric margin-left: 0px;
1043ca95b02SDimitry Andric margin-top: 5px;
1053ca95b02SDimitry Andric margin-right: 5px;
1063ca95b02SDimitry Andric margin-bottom: 5px;
1073ca95b02SDimitry Andric border: 1px solid #dbdbdb;
1083ca95b02SDimitry Andric border-radius: 3px;
1093ca95b02SDimitry Andric }
1103ca95b02SDimitry Andric table {
1113ca95b02SDimitry Andric border-collapse: collapse;
1123ca95b02SDimitry Andric }
113d88c1a5aSDimitry Andric .light-row {
114d88c1a5aSDimitry Andric background: #ffffff;
115d88c1a5aSDimitry Andric border: 1px solid #dbdbdb;
116d88c1a5aSDimitry Andric }
1174ba319b5SDimitry Andric .light-row-bold {
1184ba319b5SDimitry Andric background: #ffffff;
1194ba319b5SDimitry Andric border: 1px solid #dbdbdb;
1204ba319b5SDimitry Andric font-weight: bold;
121d88c1a5aSDimitry Andric }
1224ba319b5SDimitry Andric .column-entry {
1234ba319b5SDimitry Andric text-align: left;
1244ba319b5SDimitry Andric }
1254ba319b5SDimitry Andric .column-entry-bold {
1264ba319b5SDimitry Andric font-weight: bold;
127d88c1a5aSDimitry Andric text-align: left;
128d88c1a5aSDimitry Andric }
129d88c1a5aSDimitry Andric .column-entry-yellow {
1304ba319b5SDimitry Andric text-align: left;
131d88c1a5aSDimitry Andric background-color: #ffffd0;
132d88c1a5aSDimitry Andric }
1334ba319b5SDimitry Andric .column-entry-yellow:hover {
1344ba319b5SDimitry Andric background-color: #fffff0;
1354ba319b5SDimitry Andric }
136d88c1a5aSDimitry Andric .column-entry-red {
1374ba319b5SDimitry Andric text-align: left;
138d88c1a5aSDimitry Andric background-color: #ffd0d0;
139d88c1a5aSDimitry Andric }
1404ba319b5SDimitry Andric .column-entry-red:hover {
1414ba319b5SDimitry Andric background-color: #fff0f0;
1424ba319b5SDimitry Andric }
143d88c1a5aSDimitry Andric .column-entry-green {
1444ba319b5SDimitry Andric text-align: left;
145d88c1a5aSDimitry Andric background-color: #d0ffd0;
146d88c1a5aSDimitry Andric }
1474ba319b5SDimitry Andric .column-entry-green:hover {
1484ba319b5SDimitry Andric background-color: #f0fff0;
1494ba319b5SDimitry Andric }
1503ca95b02SDimitry Andric .line-number {
1513ca95b02SDimitry Andric text-align: right;
1523ca95b02SDimitry Andric color: #aaa;
1533ca95b02SDimitry Andric }
1543ca95b02SDimitry Andric .covered-line {
1553ca95b02SDimitry Andric text-align: right;
1563ca95b02SDimitry Andric color: #0080ff;
1573ca95b02SDimitry Andric }
1583ca95b02SDimitry Andric .uncovered-line {
1593ca95b02SDimitry Andric text-align: right;
1603ca95b02SDimitry Andric color: #ff3300;
1613ca95b02SDimitry Andric }
1623ca95b02SDimitry Andric .tooltip {
1633ca95b02SDimitry Andric position: relative;
1643ca95b02SDimitry Andric display: inline;
1653ca95b02SDimitry Andric background-color: #b3e6ff;
1663ca95b02SDimitry Andric text-decoration: none;
1673ca95b02SDimitry Andric }
1683ca95b02SDimitry Andric .tooltip span.tooltip-content {
1693ca95b02SDimitry Andric position: absolute;
1703ca95b02SDimitry Andric width: 100px;
1713ca95b02SDimitry Andric margin-left: -50px;
1723ca95b02SDimitry Andric color: #FFFFFF;
1733ca95b02SDimitry Andric background: #000000;
1743ca95b02SDimitry Andric height: 30px;
1753ca95b02SDimitry Andric line-height: 30px;
1763ca95b02SDimitry Andric text-align: center;
1773ca95b02SDimitry Andric visibility: hidden;
1783ca95b02SDimitry Andric border-radius: 6px;
1793ca95b02SDimitry Andric }
1803ca95b02SDimitry Andric .tooltip span.tooltip-content:after {
1813ca95b02SDimitry Andric content: '';
1823ca95b02SDimitry Andric position: absolute;
1833ca95b02SDimitry Andric top: 100%;
1843ca95b02SDimitry Andric left: 50%;
1853ca95b02SDimitry Andric margin-left: -8px;
1863ca95b02SDimitry Andric width: 0; height: 0;
1873ca95b02SDimitry Andric border-top: 8px solid #000000;
1883ca95b02SDimitry Andric border-right: 8px solid transparent;
1893ca95b02SDimitry Andric border-left: 8px solid transparent;
1903ca95b02SDimitry Andric }
1913ca95b02SDimitry Andric :hover.tooltip span.tooltip-content {
1923ca95b02SDimitry Andric visibility: visible;
1933ca95b02SDimitry Andric opacity: 0.8;
1943ca95b02SDimitry Andric bottom: 30px;
1953ca95b02SDimitry Andric left: 50%;
1963ca95b02SDimitry Andric z-index: 999;
1973ca95b02SDimitry Andric }
1983ca95b02SDimitry Andric th, td {
1993ca95b02SDimitry Andric vertical-align: top;
2004ba319b5SDimitry Andric padding: 2px 8px;
2013ca95b02SDimitry Andric border-collapse: collapse;
2023ca95b02SDimitry Andric border-right: solid 1px #eee;
2033ca95b02SDimitry Andric border-left: solid 1px #eee;
2044ba319b5SDimitry Andric text-align: left;
2054ba319b5SDimitry Andric }
2064ba319b5SDimitry Andric td pre {
2074ba319b5SDimitry Andric display: inline-block;
2083ca95b02SDimitry Andric }
2093ca95b02SDimitry Andric td:first-child {
2103ca95b02SDimitry Andric border-left: none;
2113ca95b02SDimitry Andric }
2123ca95b02SDimitry Andric td:last-child {
2133ca95b02SDimitry Andric border-right: none;
2143ca95b02SDimitry Andric }
2154ba319b5SDimitry Andric tr:hover {
2164ba319b5SDimitry Andric background-color: #f0f0f0;
2174ba319b5SDimitry Andric }
218d88c1a5aSDimitry Andric )";
2193ca95b02SDimitry Andric
2203ca95b02SDimitry Andric const char *EndHeader = "</head>";
2213ca95b02SDimitry Andric
2223ca95b02SDimitry Andric const char *BeginCenteredDiv = "<div class='centered'>";
2233ca95b02SDimitry Andric
2243ca95b02SDimitry Andric const char *EndCenteredDiv = "</div>";
2253ca95b02SDimitry Andric
2263ca95b02SDimitry Andric const char *BeginSourceNameDiv = "<div class='source-name-title'>";
2273ca95b02SDimitry Andric
2283ca95b02SDimitry Andric const char *EndSourceNameDiv = "</div>";
2293ca95b02SDimitry Andric
2303ca95b02SDimitry Andric const char *BeginCodeTD = "<td class='code'>";
2313ca95b02SDimitry Andric
2323ca95b02SDimitry Andric const char *EndCodeTD = "</td>";
2333ca95b02SDimitry Andric
2343ca95b02SDimitry Andric const char *BeginPre = "<pre>";
2353ca95b02SDimitry Andric
2363ca95b02SDimitry Andric const char *EndPre = "</pre>";
2373ca95b02SDimitry Andric
2383ca95b02SDimitry Andric const char *BeginExpansionDiv = "<div class='expansion-view'>";
2393ca95b02SDimitry Andric
2403ca95b02SDimitry Andric const char *EndExpansionDiv = "</div>";
2413ca95b02SDimitry Andric
2423ca95b02SDimitry Andric const char *BeginTable = "<table>";
2433ca95b02SDimitry Andric
2443ca95b02SDimitry Andric const char *EndTable = "</table>";
2453ca95b02SDimitry Andric
246d88c1a5aSDimitry Andric const char *ProjectTitleTag = "h1";
247d88c1a5aSDimitry Andric
248d88c1a5aSDimitry Andric const char *ReportTitleTag = "h2";
249d88c1a5aSDimitry Andric
250d88c1a5aSDimitry Andric const char *CreatedTimeTag = "h4";
251d88c1a5aSDimitry Andric
getPathToStyle(StringRef ViewPath)252d88c1a5aSDimitry Andric std::string getPathToStyle(StringRef ViewPath) {
253d88c1a5aSDimitry Andric std::string PathToStyle = "";
254d88c1a5aSDimitry Andric std::string PathSep = sys::path::get_separator();
255d88c1a5aSDimitry Andric unsigned NumSeps = ViewPath.count(PathSep);
256d88c1a5aSDimitry Andric for (unsigned I = 0, E = NumSeps; I < E; ++I)
257d88c1a5aSDimitry Andric PathToStyle += ".." + PathSep;
258d88c1a5aSDimitry Andric return PathToStyle + "style.css";
259d88c1a5aSDimitry Andric }
260d88c1a5aSDimitry Andric
emitPrelude(raw_ostream & OS,const CoverageViewOptions & Opts,const std::string & PathToStyle="")261d88c1a5aSDimitry Andric void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
262d88c1a5aSDimitry Andric const std::string &PathToStyle = "") {
2633ca95b02SDimitry Andric OS << "<!doctype html>"
2643ca95b02SDimitry Andric "<html>"
265d88c1a5aSDimitry Andric << BeginHeader;
266d88c1a5aSDimitry Andric
267d88c1a5aSDimitry Andric // Link to a stylesheet if one is available. Otherwise, use the default style.
268d88c1a5aSDimitry Andric if (PathToStyle.empty())
269d88c1a5aSDimitry Andric OS << "<style>" << CSSForCoverage << "</style>";
270d88c1a5aSDimitry Andric else
271d88c1a5aSDimitry Andric OS << "<link rel='stylesheet' type='text/css' href='"
272d88c1a5aSDimitry Andric << escape(PathToStyle, Opts) << "'>";
273d88c1a5aSDimitry Andric
274d88c1a5aSDimitry Andric OS << EndHeader << "<body>";
2753ca95b02SDimitry Andric }
2763ca95b02SDimitry Andric
emitEpilog(raw_ostream & OS)2773ca95b02SDimitry Andric void emitEpilog(raw_ostream &OS) {
278d88c1a5aSDimitry Andric OS << "</body>"
279d88c1a5aSDimitry Andric << "</html>";
2803ca95b02SDimitry Andric }
2813ca95b02SDimitry Andric
2823ca95b02SDimitry Andric } // anonymous namespace
2833ca95b02SDimitry Andric
2843ca95b02SDimitry Andric Expected<CoveragePrinter::OwnedStream>
createViewFile(StringRef Path,bool InToplevel)2853ca95b02SDimitry Andric CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
2863ca95b02SDimitry Andric auto OSOrErr = createOutputStream(Path, "html", InToplevel);
2873ca95b02SDimitry Andric if (!OSOrErr)
2883ca95b02SDimitry Andric return OSOrErr;
2893ca95b02SDimitry Andric
2903ca95b02SDimitry Andric OwnedStream OS = std::move(OSOrErr.get());
291d88c1a5aSDimitry Andric
292d88c1a5aSDimitry Andric if (!Opts.hasOutputDirectory()) {
293d88c1a5aSDimitry Andric emitPrelude(*OS.get(), Opts);
294d88c1a5aSDimitry Andric } else {
295d88c1a5aSDimitry Andric std::string ViewPath = getOutputPath(Path, "html", InToplevel);
296d88c1a5aSDimitry Andric emitPrelude(*OS.get(), Opts, getPathToStyle(ViewPath));
297d88c1a5aSDimitry Andric }
298d88c1a5aSDimitry Andric
2993ca95b02SDimitry Andric return std::move(OS);
3003ca95b02SDimitry Andric }
3013ca95b02SDimitry Andric
closeViewFile(OwnedStream OS)3023ca95b02SDimitry Andric void CoveragePrinterHTML::closeViewFile(OwnedStream OS) {
3033ca95b02SDimitry Andric emitEpilog(*OS.get());
3043ca95b02SDimitry Andric }
3053ca95b02SDimitry Andric
306d88c1a5aSDimitry Andric /// Emit column labels for the table in the index.
emitColumnLabelsForIndex(raw_ostream & OS,const CoverageViewOptions & Opts)3072cab237bSDimitry Andric static void emitColumnLabelsForIndex(raw_ostream &OS,
3082cab237bSDimitry Andric const CoverageViewOptions &Opts) {
309d88c1a5aSDimitry Andric SmallVector<std::string, 4> Columns;
3104ba319b5SDimitry Andric Columns.emplace_back(tag("td", "Filename", "column-entry-bold"));
3114ba319b5SDimitry Andric Columns.emplace_back(tag("td", "Function Coverage", "column-entry-bold"));
3122cab237bSDimitry Andric if (Opts.ShowInstantiationSummary)
3134ba319b5SDimitry Andric Columns.emplace_back(
3144ba319b5SDimitry Andric tag("td", "Instantiation Coverage", "column-entry-bold"));
3154ba319b5SDimitry Andric Columns.emplace_back(tag("td", "Line Coverage", "column-entry-bold"));
3162cab237bSDimitry Andric if (Opts.ShowRegionSummary)
3174ba319b5SDimitry Andric Columns.emplace_back(tag("td", "Region Coverage", "column-entry-bold"));
318d88c1a5aSDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""));
319d88c1a5aSDimitry Andric }
320d88c1a5aSDimitry Andric
3212cab237bSDimitry Andric std::string
buildLinkToFile(StringRef SF,const FileCoverageSummary & FCS) const3222cab237bSDimitry Andric CoveragePrinterHTML::buildLinkToFile(StringRef SF,
3232cab237bSDimitry Andric const FileCoverageSummary &FCS) const {
3242cab237bSDimitry Andric SmallString<128> LinkTextStr(sys::path::relative_path(FCS.Name));
3252cab237bSDimitry Andric sys::path::remove_dots(LinkTextStr, /*remove_dot_dots=*/true);
3262cab237bSDimitry Andric sys::path::native(LinkTextStr);
3272cab237bSDimitry Andric std::string LinkText = escape(LinkTextStr, Opts);
3282cab237bSDimitry Andric std::string LinkTarget =
3292cab237bSDimitry Andric escape(getOutputPath(SF, "html", /*InToplevel=*/false), Opts);
3302cab237bSDimitry Andric return a(LinkTarget, LinkText);
3312cab237bSDimitry Andric }
3322cab237bSDimitry Andric
333d88c1a5aSDimitry Andric /// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is
334d88c1a5aSDimitry Andric /// false, link the summary to \p SF.
emitFileSummary(raw_ostream & OS,StringRef SF,const FileCoverageSummary & FCS,bool IsTotals) const335d88c1a5aSDimitry Andric void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF,
336d88c1a5aSDimitry Andric const FileCoverageSummary &FCS,
337d88c1a5aSDimitry Andric bool IsTotals) const {
338d88c1a5aSDimitry Andric SmallVector<std::string, 8> Columns;
339d88c1a5aSDimitry Andric
340d88c1a5aSDimitry Andric // Format a coverage triple and add the result to the list of columns.
341d88c1a5aSDimitry Andric auto AddCoverageTripleToColumn = [&Columns](unsigned Hit, unsigned Total,
342d88c1a5aSDimitry Andric float Pctg) {
343d88c1a5aSDimitry Andric std::string S;
344d88c1a5aSDimitry Andric {
345d88c1a5aSDimitry Andric raw_string_ostream RSO{S};
346d88c1a5aSDimitry Andric if (Total)
347d88c1a5aSDimitry Andric RSO << format("%*.2f", 7, Pctg) << "% ";
348d88c1a5aSDimitry Andric else
349d88c1a5aSDimitry Andric RSO << "- ";
350d88c1a5aSDimitry Andric RSO << '(' << Hit << '/' << Total << ')';
351d88c1a5aSDimitry Andric }
352d88c1a5aSDimitry Andric const char *CellClass = "column-entry-yellow";
353d88c1a5aSDimitry Andric if (Hit == Total)
354d88c1a5aSDimitry Andric CellClass = "column-entry-green";
355d88c1a5aSDimitry Andric else if (Pctg < 80.0)
356d88c1a5aSDimitry Andric CellClass = "column-entry-red";
357d88c1a5aSDimitry Andric Columns.emplace_back(tag("td", tag("pre", S), CellClass));
358d88c1a5aSDimitry Andric };
359d88c1a5aSDimitry Andric
360d88c1a5aSDimitry Andric // Simplify the display file path, and wrap it in a link if requested.
361d88c1a5aSDimitry Andric std::string Filename;
362d88c1a5aSDimitry Andric if (IsTotals) {
3634ba319b5SDimitry Andric Filename = SF;
364d88c1a5aSDimitry Andric } else {
3652cab237bSDimitry Andric Filename = buildLinkToFile(SF, FCS);
366d88c1a5aSDimitry Andric }
367d88c1a5aSDimitry Andric
368d88c1a5aSDimitry Andric Columns.emplace_back(tag("td", tag("pre", Filename)));
3692cab237bSDimitry Andric AddCoverageTripleToColumn(FCS.FunctionCoverage.getExecuted(),
3702cab237bSDimitry Andric FCS.FunctionCoverage.getNumFunctions(),
371d88c1a5aSDimitry Andric FCS.FunctionCoverage.getPercentCovered());
3722cab237bSDimitry Andric if (Opts.ShowInstantiationSummary)
3732cab237bSDimitry Andric AddCoverageTripleToColumn(FCS.InstantiationCoverage.getExecuted(),
3742cab237bSDimitry Andric FCS.InstantiationCoverage.getNumFunctions(),
375d88c1a5aSDimitry Andric FCS.InstantiationCoverage.getPercentCovered());
3762cab237bSDimitry Andric AddCoverageTripleToColumn(FCS.LineCoverage.getCovered(),
3772cab237bSDimitry Andric FCS.LineCoverage.getNumLines(),
378d88c1a5aSDimitry Andric FCS.LineCoverage.getPercentCovered());
3792cab237bSDimitry Andric if (Opts.ShowRegionSummary)
3802cab237bSDimitry Andric AddCoverageTripleToColumn(FCS.RegionCoverage.getCovered(),
3812cab237bSDimitry Andric FCS.RegionCoverage.getNumRegions(),
382d88c1a5aSDimitry Andric FCS.RegionCoverage.getPercentCovered());
383d88c1a5aSDimitry Andric
3844ba319b5SDimitry Andric if (IsTotals)
3854ba319b5SDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold");
3864ba319b5SDimitry Andric else
387d88c1a5aSDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row");
388d88c1a5aSDimitry Andric }
389d88c1a5aSDimitry Andric
createIndexFile(ArrayRef<std::string> SourceFiles,const CoverageMapping & Coverage,const CoverageFiltersMatchAll & Filters)390d88c1a5aSDimitry Andric Error CoveragePrinterHTML::createIndexFile(
3912cab237bSDimitry Andric ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
3922cab237bSDimitry Andric const CoverageFiltersMatchAll &Filters) {
393d88c1a5aSDimitry Andric // Emit the default stylesheet.
394d88c1a5aSDimitry Andric auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true);
395d88c1a5aSDimitry Andric if (Error E = CSSOrErr.takeError())
396d88c1a5aSDimitry Andric return E;
397d88c1a5aSDimitry Andric
398d88c1a5aSDimitry Andric OwnedStream CSS = std::move(CSSOrErr.get());
399d88c1a5aSDimitry Andric CSS->operator<<(CSSForCoverage);
400d88c1a5aSDimitry Andric
401d88c1a5aSDimitry Andric // Emit a file index along with some coverage statistics.
4023ca95b02SDimitry Andric auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
4033ca95b02SDimitry Andric if (Error E = OSOrErr.takeError())
4043ca95b02SDimitry Andric return E;
4053ca95b02SDimitry Andric auto OS = std::move(OSOrErr.get());
4063ca95b02SDimitry Andric raw_ostream &OSRef = *OS.get();
4073ca95b02SDimitry Andric
408d88c1a5aSDimitry Andric assert(Opts.hasOutputDirectory() && "No output directory for index file");
409d88c1a5aSDimitry Andric emitPrelude(OSRef, Opts, getPathToStyle(""));
410d88c1a5aSDimitry Andric
411d88c1a5aSDimitry Andric // Emit some basic information about the coverage report.
412d88c1a5aSDimitry Andric if (Opts.hasProjectTitle())
413d88c1a5aSDimitry Andric OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts));
414d88c1a5aSDimitry Andric OSRef << tag(ReportTitleTag, "Coverage Report");
415d88c1a5aSDimitry Andric if (Opts.hasCreatedTime())
416d88c1a5aSDimitry Andric OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts));
417d88c1a5aSDimitry Andric
418d88c1a5aSDimitry Andric // Emit a link to some documentation.
419d88c1a5aSDimitry Andric OSRef << tag("p", "Click " +
420d88c1a5aSDimitry Andric a("http://clang.llvm.org/docs/"
421d88c1a5aSDimitry Andric "SourceBasedCodeCoverage.html#interpreting-reports",
422d88c1a5aSDimitry Andric "here") +
423d88c1a5aSDimitry Andric " for information about interpreting this report.");
424d88c1a5aSDimitry Andric
4253ca95b02SDimitry Andric // Emit a table containing links to reports for each file in the covmapping.
4262cab237bSDimitry Andric // Exclude files which don't contain any regions.
427d88c1a5aSDimitry Andric OSRef << BeginCenteredDiv << BeginTable;
4282cab237bSDimitry Andric emitColumnLabelsForIndex(OSRef, Opts);
429d88c1a5aSDimitry Andric FileCoverageSummary Totals("TOTALS");
4302cab237bSDimitry Andric auto FileReports = CoverageReport::prepareFileReports(
4312cab237bSDimitry Andric Coverage, Totals, SourceFiles, Opts, Filters);
4322cab237bSDimitry Andric bool EmptyFiles = false;
4332cab237bSDimitry Andric for (unsigned I = 0, E = FileReports.size(); I < E; ++I) {
4342cab237bSDimitry Andric if (FileReports[I].FunctionCoverage.getNumFunctions())
435d88c1a5aSDimitry Andric emitFileSummary(OSRef, SourceFiles[I], FileReports[I]);
4362cab237bSDimitry Andric else
4372cab237bSDimitry Andric EmptyFiles = true;
4382cab237bSDimitry Andric }
439d88c1a5aSDimitry Andric emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true);
4402cab237bSDimitry Andric OSRef << EndTable << EndCenteredDiv;
4412cab237bSDimitry Andric
4422cab237bSDimitry Andric // Emit links to files which don't contain any functions. These are normally
4432cab237bSDimitry Andric // not very useful, but could be relevant for code which abuses the
4442cab237bSDimitry Andric // preprocessor.
4452cab237bSDimitry Andric if (EmptyFiles && Filters.empty()) {
4462cab237bSDimitry Andric OSRef << tag("p", "Files which contain no functions. (These "
4472cab237bSDimitry Andric "files contain code pulled into other files "
4482cab237bSDimitry Andric "by the preprocessor.)\n");
4492cab237bSDimitry Andric OSRef << BeginCenteredDiv << BeginTable;
4502cab237bSDimitry Andric for (unsigned I = 0, E = FileReports.size(); I < E; ++I)
4512cab237bSDimitry Andric if (!FileReports[I].FunctionCoverage.getNumFunctions()) {
4522cab237bSDimitry Andric std::string Link = buildLinkToFile(SourceFiles[I], FileReports[I]);
4532cab237bSDimitry Andric OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n';
4542cab237bSDimitry Andric }
4552cab237bSDimitry Andric OSRef << EndTable << EndCenteredDiv;
4562cab237bSDimitry Andric }
4572cab237bSDimitry Andric
4582cab237bSDimitry Andric OSRef << tag("h5", escape(Opts.getLLVMVersionString(), Opts));
4593ca95b02SDimitry Andric emitEpilog(OSRef);
4603ca95b02SDimitry Andric
4613ca95b02SDimitry Andric return Error::success();
4623ca95b02SDimitry Andric }
4633ca95b02SDimitry Andric
renderViewHeader(raw_ostream & OS)4643ca95b02SDimitry Andric void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
465d88c1a5aSDimitry Andric OS << BeginCenteredDiv << BeginTable;
4663ca95b02SDimitry Andric }
4673ca95b02SDimitry Andric
renderViewFooter(raw_ostream & OS)4683ca95b02SDimitry Andric void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) {
469d88c1a5aSDimitry Andric OS << EndTable << EndCenteredDiv;
4703ca95b02SDimitry Andric }
4713ca95b02SDimitry Andric
renderSourceName(raw_ostream & OS,bool WholeFile)472d88c1a5aSDimitry Andric void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS, bool WholeFile) {
473d88c1a5aSDimitry Andric OS << BeginSourceNameDiv << tag("pre", escape(getSourceName(), getOptions()))
4743ca95b02SDimitry Andric << EndSourceNameDiv;
4753ca95b02SDimitry Andric }
4763ca95b02SDimitry Andric
renderLinePrefix(raw_ostream & OS,unsigned)4773ca95b02SDimitry Andric void SourceCoverageViewHTML::renderLinePrefix(raw_ostream &OS, unsigned) {
4783ca95b02SDimitry Andric OS << "<tr>";
4793ca95b02SDimitry Andric }
4803ca95b02SDimitry Andric
renderLineSuffix(raw_ostream & OS,unsigned)4813ca95b02SDimitry Andric void SourceCoverageViewHTML::renderLineSuffix(raw_ostream &OS, unsigned) {
4823ca95b02SDimitry Andric // If this view has sub-views, renderLine() cannot close the view's cell.
4833ca95b02SDimitry Andric // Take care of it here, after all sub-views have been rendered.
4843ca95b02SDimitry Andric if (hasSubViews())
4853ca95b02SDimitry Andric OS << EndCodeTD;
4863ca95b02SDimitry Andric OS << "</tr>";
4873ca95b02SDimitry Andric }
4883ca95b02SDimitry Andric
renderViewDivider(raw_ostream &,unsigned)4893ca95b02SDimitry Andric void SourceCoverageViewHTML::renderViewDivider(raw_ostream &, unsigned) {
4903ca95b02SDimitry Andric // The table-based output makes view dividers unnecessary.
4913ca95b02SDimitry Andric }
4923ca95b02SDimitry Andric
renderLine(raw_ostream & OS,LineRef L,const LineCoverageStats & LCS,unsigned ExpansionCol,unsigned)4932cab237bSDimitry Andric void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L,
4942cab237bSDimitry Andric const LineCoverageStats &LCS,
4952cab237bSDimitry Andric unsigned ExpansionCol, unsigned) {
4963ca95b02SDimitry Andric StringRef Line = L.Line;
497d88c1a5aSDimitry Andric unsigned LineNo = L.LineNo;
4983ca95b02SDimitry Andric
4993ca95b02SDimitry Andric // Steps for handling text-escaping, highlighting, and tooltip creation:
5003ca95b02SDimitry Andric //
5013ca95b02SDimitry Andric // 1. Split the line into N+1 snippets, where N = |Segments|. The first
5023ca95b02SDimitry Andric // snippet starts from Col=1 and ends at the start of the first segment.
5033ca95b02SDimitry Andric // The last snippet starts at the last mapped column in the line and ends
5043ca95b02SDimitry Andric // at the end of the line. Both are required but may be empty.
5053ca95b02SDimitry Andric
5063ca95b02SDimitry Andric SmallVector<std::string, 8> Snippets;
5072cab237bSDimitry Andric CoverageSegmentArray Segments = LCS.getLineSegments();
5083ca95b02SDimitry Andric
5093ca95b02SDimitry Andric unsigned LCol = 1;
5103ca95b02SDimitry Andric auto Snip = [&](unsigned Start, unsigned Len) {
5113ca95b02SDimitry Andric Snippets.push_back(Line.substr(Start, Len));
5123ca95b02SDimitry Andric LCol += Len;
5133ca95b02SDimitry Andric };
5143ca95b02SDimitry Andric
5153ca95b02SDimitry Andric Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1));
5163ca95b02SDimitry Andric
517d88c1a5aSDimitry Andric for (unsigned I = 1, E = Segments.size(); I < E; ++I)
5183ca95b02SDimitry Andric Snip(LCol - 1, Segments[I]->Col - LCol);
5193ca95b02SDimitry Andric
5203ca95b02SDimitry Andric // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1.
5213ca95b02SDimitry Andric Snip(LCol - 1, Line.size() + 1 - LCol);
5223ca95b02SDimitry Andric
5233ca95b02SDimitry Andric // 2. Escape all of the snippets.
5243ca95b02SDimitry Andric
5253ca95b02SDimitry Andric for (unsigned I = 0, E = Snippets.size(); I < E; ++I)
526d88c1a5aSDimitry Andric Snippets[I] = escape(Snippets[I], getOptions());
5273ca95b02SDimitry Andric
528d88c1a5aSDimitry Andric // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment
529d88c1a5aSDimitry Andric // 1 to set the highlight for snippet 2, segment 2 to set the highlight for
530d88c1a5aSDimitry Andric // snippet 3, and so on.
5313ca95b02SDimitry Andric
5322cab237bSDimitry Andric Optional<StringRef> Color;
533d88c1a5aSDimitry Andric SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
534d88c1a5aSDimitry Andric auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) {
535d88c1a5aSDimitry Andric if (getOptions().Debug)
536d88c1a5aSDimitry Andric HighlightedRanges.emplace_back(LC, RC);
5373ca95b02SDimitry Andric return tag("span", Snippet, Color.getValue());
5383ca95b02SDimitry Andric };
5393ca95b02SDimitry Andric
5402cab237bSDimitry Andric auto CheckIfUncovered = [&](const CoverageSegment *S) {
5412cab237bSDimitry Andric return S && (!S->IsGapRegion || (Color && *Color == "red")) &&
5422cab237bSDimitry Andric S->HasCount && S->Count == 0;
5433ca95b02SDimitry Andric };
5443ca95b02SDimitry Andric
5452cab237bSDimitry Andric if (CheckIfUncovered(LCS.getWrappedSegment())) {
5463ca95b02SDimitry Andric Color = "red";
547d88c1a5aSDimitry Andric if (!Snippets[0].empty())
548d88c1a5aSDimitry Andric Snippets[0] = Highlight(Snippets[0], 1, 1 + Snippets[0].size());
5493ca95b02SDimitry Andric }
5503ca95b02SDimitry Andric
551d88c1a5aSDimitry Andric for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
5523ca95b02SDimitry Andric const auto *CurSeg = Segments[I];
5532cab237bSDimitry Andric if (CheckIfUncovered(CurSeg))
5543ca95b02SDimitry Andric Color = "red";
5552cab237bSDimitry Andric else if (CurSeg->Col == ExpansionCol)
5562cab237bSDimitry Andric Color = "cyan";
5573ca95b02SDimitry Andric else
5583ca95b02SDimitry Andric Color = None;
5593ca95b02SDimitry Andric
5603ca95b02SDimitry Andric if (Color.hasValue())
561d88c1a5aSDimitry Andric Snippets[I + 1] = Highlight(Snippets[I + 1], CurSeg->Col,
562d88c1a5aSDimitry Andric CurSeg->Col + Snippets[I + 1].size());
563d88c1a5aSDimitry Andric }
564d88c1a5aSDimitry Andric
565d88c1a5aSDimitry Andric if (Color.hasValue() && Segments.empty())
566d88c1a5aSDimitry Andric Snippets.back() = Highlight(Snippets.back(), 1, 1 + Snippets.back().size());
567d88c1a5aSDimitry Andric
568d88c1a5aSDimitry Andric if (getOptions().Debug) {
569d88c1a5aSDimitry Andric for (const auto &Range : HighlightedRanges) {
570d88c1a5aSDimitry Andric errs() << "Highlighted line " << LineNo << ", " << Range.first << " -> ";
571d88c1a5aSDimitry Andric if (Range.second == 0)
572d88c1a5aSDimitry Andric errs() << "?";
573d88c1a5aSDimitry Andric else
574d88c1a5aSDimitry Andric errs() << Range.second;
575d88c1a5aSDimitry Andric errs() << "\n";
576d88c1a5aSDimitry Andric }
5773ca95b02SDimitry Andric }
5783ca95b02SDimitry Andric
5793ca95b02SDimitry Andric // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
5803ca95b02SDimitry Andric // sub-line region count tooltips if needed.
5813ca95b02SDimitry Andric
5822cab237bSDimitry Andric if (shouldRenderRegionMarkers(LCS)) {
5832cab237bSDimitry Andric // Just consider the segments which start *and* end on this line.
5842cab237bSDimitry Andric for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {
5853ca95b02SDimitry Andric const auto *CurSeg = Segments[I];
5862cab237bSDimitry Andric if (!CurSeg->IsRegionEntry)
5872cab237bSDimitry Andric continue;
5882cab237bSDimitry Andric if (CurSeg->Count == LCS.getExecutionCount())
5893ca95b02SDimitry Andric continue;
5903ca95b02SDimitry Andric
5913ca95b02SDimitry Andric Snippets[I + 1] =
5923ca95b02SDimitry Andric tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count),
5933ca95b02SDimitry Andric "tooltip-content"),
5943ca95b02SDimitry Andric "tooltip");
5952cab237bSDimitry Andric
5962cab237bSDimitry Andric if (getOptions().Debug)
5972cab237bSDimitry Andric errs() << "Marker at " << CurSeg->Line << ":" << CurSeg->Col << " = "
5982cab237bSDimitry Andric << formatCount(CurSeg->Count) << "\n";
5993ca95b02SDimitry Andric }
6003ca95b02SDimitry Andric }
6013ca95b02SDimitry Andric
6023ca95b02SDimitry Andric OS << BeginCodeTD;
6033ca95b02SDimitry Andric OS << BeginPre;
6043ca95b02SDimitry Andric for (const auto &Snippet : Snippets)
6053ca95b02SDimitry Andric OS << Snippet;
6063ca95b02SDimitry Andric OS << EndPre;
6073ca95b02SDimitry Andric
6083ca95b02SDimitry Andric // If there are no sub-views left to attach to this cell, end the cell.
6093ca95b02SDimitry Andric // Otherwise, end it after the sub-views are rendered (renderLineSuffix()).
6103ca95b02SDimitry Andric if (!hasSubViews())
6113ca95b02SDimitry Andric OS << EndCodeTD;
6123ca95b02SDimitry Andric }
6133ca95b02SDimitry Andric
renderLineCoverageColumn(raw_ostream & OS,const LineCoverageStats & Line)6143ca95b02SDimitry Andric void SourceCoverageViewHTML::renderLineCoverageColumn(
6153ca95b02SDimitry Andric raw_ostream &OS, const LineCoverageStats &Line) {
6163ca95b02SDimitry Andric std::string Count = "";
6173ca95b02SDimitry Andric if (Line.isMapped())
6182cab237bSDimitry Andric Count = tag("pre", formatCount(Line.getExecutionCount()));
6193ca95b02SDimitry Andric std::string CoverageClass =
6202cab237bSDimitry Andric (Line.getExecutionCount() > 0) ? "covered-line" : "uncovered-line";
6213ca95b02SDimitry Andric OS << tag("td", Count, CoverageClass);
6223ca95b02SDimitry Andric }
6233ca95b02SDimitry Andric
renderLineNumberColumn(raw_ostream & OS,unsigned LineNo)6243ca95b02SDimitry Andric void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
6253ca95b02SDimitry Andric unsigned LineNo) {
626d88c1a5aSDimitry Andric std::string LineNoStr = utostr(uint64_t(LineNo));
627d88c1a5aSDimitry Andric std::string TargetName = "L" + LineNoStr;
628d88c1a5aSDimitry Andric OS << tag("td", a("#" + TargetName, tag("pre", LineNoStr), TargetName),
629d88c1a5aSDimitry Andric "line-number");
6303ca95b02SDimitry Andric }
6313ca95b02SDimitry Andric
renderRegionMarkers(raw_ostream &,const LineCoverageStats & Line,unsigned)6323ca95b02SDimitry Andric void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &,
6332cab237bSDimitry Andric const LineCoverageStats &Line,
6343ca95b02SDimitry Andric unsigned) {
6353ca95b02SDimitry Andric // Region markers are rendered in-line using tooltips.
6363ca95b02SDimitry Andric }
6373ca95b02SDimitry Andric
renderExpansionSite(raw_ostream & OS,LineRef L,const LineCoverageStats & LCS,unsigned ExpansionCol,unsigned ViewDepth)6382cab237bSDimitry Andric void SourceCoverageViewHTML::renderExpansionSite(raw_ostream &OS, LineRef L,
6392cab237bSDimitry Andric const LineCoverageStats &LCS,
6402cab237bSDimitry Andric unsigned ExpansionCol,
6412cab237bSDimitry Andric unsigned ViewDepth) {
6423ca95b02SDimitry Andric // Render the line containing the expansion site. No extra formatting needed.
6432cab237bSDimitry Andric renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
6443ca95b02SDimitry Andric }
6453ca95b02SDimitry Andric
renderExpansionView(raw_ostream & OS,ExpansionView & ESV,unsigned ViewDepth)6463ca95b02SDimitry Andric void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS,
6473ca95b02SDimitry Andric ExpansionView &ESV,
6483ca95b02SDimitry Andric unsigned ViewDepth) {
6493ca95b02SDimitry Andric OS << BeginExpansionDiv;
6503ca95b02SDimitry Andric ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
6512cab237bSDimitry Andric /*ShowTitle=*/false, ViewDepth + 1);
6523ca95b02SDimitry Andric OS << EndExpansionDiv;
6533ca95b02SDimitry Andric }
6543ca95b02SDimitry Andric
renderInstantiationView(raw_ostream & OS,InstantiationView & ISV,unsigned ViewDepth)6553ca95b02SDimitry Andric void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS,
6563ca95b02SDimitry Andric InstantiationView &ISV,
6573ca95b02SDimitry Andric unsigned ViewDepth) {
6583ca95b02SDimitry Andric OS << BeginExpansionDiv;
659d88c1a5aSDimitry Andric if (!ISV.View)
660d88c1a5aSDimitry Andric OS << BeginSourceNameDiv
661d88c1a5aSDimitry Andric << tag("pre",
662d88c1a5aSDimitry Andric escape("Unexecuted instantiation: " + ISV.FunctionName.str(),
663d88c1a5aSDimitry Andric getOptions()))
664d88c1a5aSDimitry Andric << EndSourceNameDiv;
665d88c1a5aSDimitry Andric else
666d88c1a5aSDimitry Andric ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
6672cab237bSDimitry Andric /*ShowTitle=*/false, ViewDepth);
6683ca95b02SDimitry Andric OS << EndExpansionDiv;
6693ca95b02SDimitry Andric }
670d88c1a5aSDimitry Andric
renderTitle(raw_ostream & OS,StringRef Title)671d88c1a5aSDimitry Andric void SourceCoverageViewHTML::renderTitle(raw_ostream &OS, StringRef Title) {
672d88c1a5aSDimitry Andric if (getOptions().hasProjectTitle())
673d88c1a5aSDimitry Andric OS << tag(ProjectTitleTag, escape(getOptions().ProjectTitle, getOptions()));
674d88c1a5aSDimitry Andric OS << tag(ReportTitleTag, escape(Title, getOptions()));
675d88c1a5aSDimitry Andric if (getOptions().hasCreatedTime())
676d88c1a5aSDimitry Andric OS << tag(CreatedTimeTag,
677d88c1a5aSDimitry Andric escape(getOptions().CreatedTimeStr, getOptions()));
678d88c1a5aSDimitry Andric }
679d88c1a5aSDimitry Andric
renderTableHeader(raw_ostream & OS,unsigned FirstUncoveredLineNo,unsigned ViewDepth)680d88c1a5aSDimitry Andric void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS,
681d88c1a5aSDimitry Andric unsigned FirstUncoveredLineNo,
682d88c1a5aSDimitry Andric unsigned ViewDepth) {
683d88c1a5aSDimitry Andric std::string SourceLabel;
684d88c1a5aSDimitry Andric if (FirstUncoveredLineNo == 0) {
685d88c1a5aSDimitry Andric SourceLabel = tag("td", tag("pre", "Source"));
686d88c1a5aSDimitry Andric } else {
687d88c1a5aSDimitry Andric std::string LinkTarget = "#L" + utostr(uint64_t(FirstUncoveredLineNo));
688d88c1a5aSDimitry Andric SourceLabel =
689d88c1a5aSDimitry Andric tag("td", tag("pre", "Source (" +
690d88c1a5aSDimitry Andric a(LinkTarget, "jump to first uncovered line") +
691d88c1a5aSDimitry Andric ")"));
692d88c1a5aSDimitry Andric }
693d88c1a5aSDimitry Andric
694d88c1a5aSDimitry Andric renderLinePrefix(OS, ViewDepth);
695d88c1a5aSDimitry Andric OS << tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count"))
696d88c1a5aSDimitry Andric << SourceLabel;
697d88c1a5aSDimitry Andric renderLineSuffix(OS, ViewDepth);
698d88c1a5aSDimitry Andric }
699