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