14c01092aSVedant Kumar //===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
24c01092aSVedant Kumar //
34c01092aSVedant Kumar //                     The LLVM Compiler Infrastructure
44c01092aSVedant Kumar //
54c01092aSVedant Kumar // This file is distributed under the University of Illinois Open Source
64c01092aSVedant Kumar // License. See LICENSE.TXT for details.
74c01092aSVedant Kumar //
84c01092aSVedant Kumar //===----------------------------------------------------------------------===//
94c01092aSVedant Kumar ///
104c01092aSVedant Kumar /// \file This file implements the html coverage renderer.
114c01092aSVedant Kumar ///
124c01092aSVedant Kumar //===----------------------------------------------------------------------===//
134c01092aSVedant Kumar 
144c01092aSVedant Kumar #include "SourceCoverageViewHTML.h"
154c01092aSVedant Kumar #include "llvm/ADT/Optional.h"
164c01092aSVedant Kumar #include "llvm/ADT/SmallString.h"
174c01092aSVedant Kumar #include "llvm/ADT/StringExtras.h"
184c01092aSVedant Kumar #include "llvm/Support/Path.h"
194c01092aSVedant Kumar 
204c01092aSVedant Kumar using namespace llvm;
214c01092aSVedant Kumar 
224c01092aSVedant Kumar namespace {
234c01092aSVedant Kumar 
24c076c490SVedant Kumar // Return a string with the special characters in \p Str escaped.
25c076c490SVedant Kumar std::string escape(StringRef Str) {
26c076c490SVedant Kumar   std::string Result;
27c076c490SVedant Kumar   for (char C : Str) {
28c076c490SVedant Kumar     if (C == '&')
29c076c490SVedant Kumar       Result += "&";
30c076c490SVedant Kumar     else if (C == '<')
31c076c490SVedant Kumar       Result += "&lt;";
32c076c490SVedant Kumar     else if (C == '>')
33c076c490SVedant Kumar       Result += "&gt;";
34c076c490SVedant Kumar     else if (C == '\"')
35c076c490SVedant Kumar       Result += "&quot;";
36c076c490SVedant Kumar     else
37c076c490SVedant Kumar       Result += C;
38c076c490SVedant Kumar   }
39c076c490SVedant Kumar   return Result;
40c076c490SVedant Kumar }
41c076c490SVedant Kumar 
42c076c490SVedant Kumar // Create a \p Name tag around \p Str, and optionally set its \p ClassName.
43c076c490SVedant Kumar std::string tag(const std::string &Name, const std::string &Str,
44c076c490SVedant Kumar                 const std::string &ClassName = "") {
45c076c490SVedant Kumar   std::string Tag = "<" + Name;
46c076c490SVedant Kumar   if (ClassName != "")
47c076c490SVedant Kumar     Tag += " class='" + ClassName + "'";
48c076c490SVedant Kumar   return Tag + ">" + Str + "</" + Name + ">";
49c076c490SVedant Kumar }
50c076c490SVedant Kumar 
51c076c490SVedant Kumar // Create an anchor to \p Link with the label \p Str.
52c076c490SVedant Kumar std::string a(const std::string &Link, const std::string &Str,
53c076c490SVedant Kumar               const std::string &TargetType = "href") {
54c076c490SVedant Kumar   return "<a " + TargetType + "='" + Link + "'>" + Str + "</a>";
55c076c490SVedant Kumar }
56c076c490SVedant Kumar 
574c01092aSVedant Kumar const char *BeginHeader =
584c01092aSVedant Kumar   "<head>"
594c01092aSVedant Kumar     "<meta name='viewport' content='width=device-width,initial-scale=1'>"
604c01092aSVedant Kumar     "<meta charset='UTF-8'>";
614c01092aSVedant Kumar 
624c01092aSVedant Kumar const char *CSSForCoverage =
63c076c490SVedant Kumar     R"(.red {
644c01092aSVedant Kumar   background-color: #FFD0D0;
654c01092aSVedant Kumar }
664c01092aSVedant Kumar .cyan {
674c01092aSVedant Kumar   background-color: cyan;
684c01092aSVedant Kumar }
694c01092aSVedant Kumar body {
704c01092aSVedant Kumar   font-family: -apple-system, sans-serif;
714c01092aSVedant Kumar }
724c01092aSVedant Kumar pre {
734c01092aSVedant Kumar   margin-top: 0px !important;
744c01092aSVedant Kumar   margin-bottom: 0px !important;
754c01092aSVedant Kumar }
764c01092aSVedant Kumar .source-name-title {
774c01092aSVedant Kumar   padding: 5px 10px;
784c01092aSVedant Kumar   border-bottom: 1px solid #dbdbdb;
794c01092aSVedant Kumar   background-color: #eee;
804c01092aSVedant Kumar }
814c01092aSVedant Kumar .centered {
824c01092aSVedant Kumar   display: table;
834c01092aSVedant Kumar   margin-left: auto;
844c01092aSVedant Kumar   margin-right: auto;
854c01092aSVedant Kumar   border: 1px solid #dbdbdb;
864c01092aSVedant Kumar   border-radius: 3px;
874c01092aSVedant Kumar }
884c01092aSVedant Kumar .expansion-view {
894c01092aSVedant Kumar   background-color: rgba(0, 0, 0, 0);
904c01092aSVedant Kumar   margin-left: 0px;
914c01092aSVedant Kumar   margin-top: 5px;
924c01092aSVedant Kumar   margin-right: 5px;
934c01092aSVedant Kumar   margin-bottom: 5px;
944c01092aSVedant Kumar   border: 1px solid #dbdbdb;
954c01092aSVedant Kumar   border-radius: 3px;
964c01092aSVedant Kumar }
974c01092aSVedant Kumar table {
984c01092aSVedant Kumar   border-collapse: collapse;
994c01092aSVedant Kumar }
1004c01092aSVedant Kumar .line-number {
1014c01092aSVedant Kumar   text-align: right;
1024c01092aSVedant Kumar   color: #aaa;
1034c01092aSVedant Kumar }
1044c01092aSVedant Kumar .covered-line {
1054c01092aSVedant Kumar   text-align: right;
1064c01092aSVedant Kumar   color: #0080ff;
1074c01092aSVedant Kumar }
1084c01092aSVedant Kumar .uncovered-line {
1094c01092aSVedant Kumar   text-align: right;
1104c01092aSVedant Kumar   color: #ff3300;
1114c01092aSVedant Kumar }
1124c01092aSVedant Kumar .tooltip {
1134c01092aSVedant Kumar   position: relative;
1144c01092aSVedant Kumar   display: inline;
1154c01092aSVedant Kumar   background-color: #b3e6ff;
1164c01092aSVedant Kumar   text-decoration: none;
1174c01092aSVedant Kumar }
1184c01092aSVedant Kumar .tooltip span.tooltip-content {
1194c01092aSVedant Kumar   position: absolute;
1204c01092aSVedant Kumar   width: 100px;
1214c01092aSVedant Kumar   margin-left: -50px;
1224c01092aSVedant Kumar   color: #FFFFFF;
1234c01092aSVedant Kumar   background: #000000;
1244c01092aSVedant Kumar   height: 30px;
1254c01092aSVedant Kumar   line-height: 30px;
1264c01092aSVedant Kumar   text-align: center;
1274c01092aSVedant Kumar   visibility: hidden;
1284c01092aSVedant Kumar   border-radius: 6px;
1294c01092aSVedant Kumar }
1304c01092aSVedant Kumar .tooltip span.tooltip-content:after {
1314c01092aSVedant Kumar   content: '';
1324c01092aSVedant Kumar   position: absolute;
1334c01092aSVedant Kumar   top: 100%;
1344c01092aSVedant Kumar   left: 50%;
1354c01092aSVedant Kumar   margin-left: -8px;
1364c01092aSVedant Kumar   width: 0; height: 0;
1374c01092aSVedant Kumar   border-top: 8px solid #000000;
1384c01092aSVedant Kumar   border-right: 8px solid transparent;
1394c01092aSVedant Kumar   border-left: 8px solid transparent;
1404c01092aSVedant Kumar }
1414c01092aSVedant Kumar :hover.tooltip span.tooltip-content {
1424c01092aSVedant Kumar   visibility: visible;
1434c01092aSVedant Kumar   opacity: 0.8;
1444c01092aSVedant Kumar   bottom: 30px;
1454c01092aSVedant Kumar   left: 50%;
1464c01092aSVedant Kumar   z-index: 999;
1474c01092aSVedant Kumar }
1484c01092aSVedant Kumar th, td {
1494c01092aSVedant Kumar   vertical-align: top;
1504c01092aSVedant Kumar   padding: 2px 5px;
1514c01092aSVedant Kumar   border-collapse: collapse;
1524c01092aSVedant Kumar   border-right: solid 1px #eee;
1534c01092aSVedant Kumar   border-left: solid 1px #eee;
1544c01092aSVedant Kumar }
1554c01092aSVedant Kumar td:first-child {
1564c01092aSVedant Kumar   border-left: none;
1574c01092aSVedant Kumar }
1584c01092aSVedant Kumar td:last-child {
1594c01092aSVedant Kumar   border-right: none;
1604c01092aSVedant Kumar }
161c076c490SVedant Kumar )";
1624c01092aSVedant Kumar 
1634c01092aSVedant Kumar const char *EndHeader = "</head>";
1644c01092aSVedant Kumar 
1654c01092aSVedant Kumar const char *BeginCenteredDiv = "<div class='centered'>";
1664c01092aSVedant Kumar 
1674c01092aSVedant Kumar const char *EndCenteredDiv = "</div>";
1684c01092aSVedant Kumar 
1694c01092aSVedant Kumar const char *BeginSourceNameDiv = "<div class='source-name-title'>";
1704c01092aSVedant Kumar 
1714c01092aSVedant Kumar const char *EndSourceNameDiv = "</div>";
1724c01092aSVedant Kumar 
1734c01092aSVedant Kumar const char *BeginCodeTD = "<td class='code'>";
1744c01092aSVedant Kumar 
1754c01092aSVedant Kumar const char *EndCodeTD = "</td>";
1764c01092aSVedant Kumar 
1774c01092aSVedant Kumar const char *BeginPre = "<pre>";
1784c01092aSVedant Kumar 
1794c01092aSVedant Kumar const char *EndPre = "</pre>";
1804c01092aSVedant Kumar 
1814c01092aSVedant Kumar const char *BeginExpansionDiv = "<div class='expansion-view'>";
1824c01092aSVedant Kumar 
1834c01092aSVedant Kumar const char *EndExpansionDiv = "</div>";
1844c01092aSVedant Kumar 
1854c01092aSVedant Kumar const char *BeginTable = "<table>";
1864c01092aSVedant Kumar 
1874c01092aSVedant Kumar const char *EndTable = "</table>";
1884c01092aSVedant Kumar 
189c076c490SVedant Kumar std::string getPathToStyle(StringRef ViewPath) {
190c076c490SVedant Kumar   std::string PathToStyle = "";
191c076c490SVedant Kumar   std::string PathSep = sys::path::get_separator();
192c076c490SVedant Kumar   unsigned NumSeps = ViewPath.count(PathSep);
193c076c490SVedant Kumar   for (unsigned I = 0, E = NumSeps; I < E; ++I)
194c076c490SVedant Kumar     PathToStyle += ".." + PathSep;
195c076c490SVedant Kumar   return PathToStyle + "style.css";
196c076c490SVedant Kumar }
197c076c490SVedant Kumar 
198c076c490SVedant Kumar void emitPrelude(raw_ostream &OS, const std::string &PathToStyle = "") {
1994c01092aSVedant Kumar   OS << "<!doctype html>"
2004c01092aSVedant Kumar         "<html>"
201c076c490SVedant Kumar      << BeginHeader;
202c076c490SVedant Kumar 
203c076c490SVedant Kumar   // Link to a stylesheet if one is available. Otherwise, use the default style.
204c076c490SVedant Kumar   if (PathToStyle.empty())
205c076c490SVedant Kumar     OS << "<style>" << CSSForCoverage << "</style>";
206c076c490SVedant Kumar   else
207c076c490SVedant Kumar     OS << "<link rel='stylesheet' type='text/css' href='" << escape(PathToStyle)
208c076c490SVedant Kumar        << "'>";
209c076c490SVedant Kumar 
210c076c490SVedant Kumar   OS << EndHeader << "<body>" << BeginCenteredDiv;
2114c01092aSVedant Kumar }
2124c01092aSVedant Kumar 
2134c01092aSVedant Kumar void emitEpilog(raw_ostream &OS) {
2144c01092aSVedant Kumar   OS << EndCenteredDiv << "</body>"
2154c01092aSVedant Kumar                           "</html>";
2164c01092aSVedant Kumar }
2174c01092aSVedant Kumar 
2184c01092aSVedant Kumar } // anonymous namespace
2194c01092aSVedant Kumar 
2204c01092aSVedant Kumar Expected<CoveragePrinter::OwnedStream>
2214c01092aSVedant Kumar CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
2224c01092aSVedant Kumar   auto OSOrErr = createOutputStream(Path, "html", InToplevel);
2234c01092aSVedant Kumar   if (!OSOrErr)
2244c01092aSVedant Kumar     return OSOrErr;
2254c01092aSVedant Kumar 
2264c01092aSVedant Kumar   OwnedStream OS = std::move(OSOrErr.get());
227c076c490SVedant Kumar 
228c076c490SVedant Kumar   if (!Opts.hasOutputDirectory()) {
2294c01092aSVedant Kumar     emitPrelude(*OS.get());
230c076c490SVedant Kumar   } else {
231c076c490SVedant Kumar     std::string ViewPath = getOutputPath(Path, "html", InToplevel);
232c076c490SVedant Kumar     emitPrelude(*OS.get(), getPathToStyle(ViewPath));
233c076c490SVedant Kumar   }
234c076c490SVedant Kumar 
2354c01092aSVedant Kumar   return std::move(OS);
2364c01092aSVedant Kumar }
2374c01092aSVedant Kumar 
2384c01092aSVedant Kumar void CoveragePrinterHTML::closeViewFile(OwnedStream OS) {
2394c01092aSVedant Kumar   emitEpilog(*OS.get());
2404c01092aSVedant Kumar }
2414c01092aSVedant Kumar 
2424c01092aSVedant Kumar Error CoveragePrinterHTML::createIndexFile(ArrayRef<StringRef> SourceFiles) {
2434c01092aSVedant Kumar   auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
2444c01092aSVedant Kumar   if (Error E = OSOrErr.takeError())
2454c01092aSVedant Kumar     return E;
2464c01092aSVedant Kumar   auto OS = std::move(OSOrErr.get());
2474c01092aSVedant Kumar   raw_ostream &OSRef = *OS.get();
2484c01092aSVedant Kumar 
2494c01092aSVedant Kumar   // Emit a table containing links to reports for each file in the covmapping.
250127d0502SVedant Kumar   assert(Opts.hasOutputDirectory() && "No output directory for index file");
251127d0502SVedant Kumar   emitPrelude(OSRef, getPathToStyle(""));
2524c01092aSVedant Kumar   OSRef << BeginSourceNameDiv << "Index" << EndSourceNameDiv;
2534c01092aSVedant Kumar   OSRef << BeginTable;
2544c01092aSVedant Kumar   for (StringRef SF : SourceFiles) {
2554c01092aSVedant Kumar     std::string LinkText = escape(sys::path::relative_path(SF));
2564c01092aSVedant Kumar     std::string LinkTarget =
2574c01092aSVedant Kumar         escape(getOutputPath(SF, "html", /*InToplevel=*/false));
2584c01092aSVedant Kumar     OSRef << tag("tr", tag("td", tag("pre", a(LinkTarget, LinkText), "code")));
2594c01092aSVedant Kumar   }
2604c01092aSVedant Kumar   OSRef << EndTable;
2614c01092aSVedant Kumar   emitEpilog(OSRef);
2624c01092aSVedant Kumar 
263c076c490SVedant Kumar   // Emit the default stylesheet.
264c076c490SVedant Kumar   auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true);
265c076c490SVedant Kumar   if (Error E = CSSOrErr.takeError())
266c076c490SVedant Kumar     return E;
267c076c490SVedant Kumar 
268c076c490SVedant Kumar   OwnedStream CSS = std::move(CSSOrErr.get());
269c076c490SVedant Kumar   CSS->operator<<(CSSForCoverage);
270c076c490SVedant Kumar 
2714c01092aSVedant Kumar   return Error::success();
2724c01092aSVedant Kumar }
2734c01092aSVedant Kumar 
2744c01092aSVedant Kumar void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
2754c01092aSVedant Kumar   OS << BeginTable;
2764c01092aSVedant Kumar }
2774c01092aSVedant Kumar 
2784c01092aSVedant Kumar void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) {
2794c01092aSVedant Kumar   OS << EndTable;
2804c01092aSVedant Kumar }
2814c01092aSVedant Kumar 
2824c01092aSVedant Kumar void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS) {
2834c01092aSVedant Kumar   OS << BeginSourceNameDiv << tag("pre", escape(getSourceName()))
2844c01092aSVedant Kumar      << EndSourceNameDiv;
2854c01092aSVedant Kumar }
2864c01092aSVedant Kumar 
2874c01092aSVedant Kumar void SourceCoverageViewHTML::renderLinePrefix(raw_ostream &OS, unsigned) {
2884c01092aSVedant Kumar   OS << "<tr>";
2894c01092aSVedant Kumar }
2904c01092aSVedant Kumar 
2914c01092aSVedant Kumar void SourceCoverageViewHTML::renderLineSuffix(raw_ostream &OS, unsigned) {
2924c01092aSVedant Kumar   // If this view has sub-views, renderLine() cannot close the view's cell.
2934c01092aSVedant Kumar   // Take care of it here, after all sub-views have been rendered.
2944c01092aSVedant Kumar   if (hasSubViews())
2954c01092aSVedant Kumar     OS << EndCodeTD;
2964c01092aSVedant Kumar   OS << "</tr>";
2974c01092aSVedant Kumar }
2984c01092aSVedant Kumar 
2994c01092aSVedant Kumar void SourceCoverageViewHTML::renderViewDivider(raw_ostream &, unsigned) {
3004c01092aSVedant Kumar   // The table-based output makes view dividers unnecessary.
3014c01092aSVedant Kumar }
3024c01092aSVedant Kumar 
3034c01092aSVedant Kumar void SourceCoverageViewHTML::renderLine(
3044c01092aSVedant Kumar     raw_ostream &OS, LineRef L, const coverage::CoverageSegment *WrappedSegment,
3054c01092aSVedant Kumar     CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned) {
3064c01092aSVedant Kumar   StringRef Line = L.Line;
307*fc07e8b4SVedant Kumar   unsigned LineNo = L.LineNo;
3084c01092aSVedant Kumar 
3094c01092aSVedant Kumar   // Steps for handling text-escaping, highlighting, and tooltip creation:
3104c01092aSVedant Kumar   //
3114c01092aSVedant Kumar   // 1. Split the line into N+1 snippets, where N = |Segments|. The first
3124c01092aSVedant Kumar   //    snippet starts from Col=1 and ends at the start of the first segment.
3134c01092aSVedant Kumar   //    The last snippet starts at the last mapped column in the line and ends
3144c01092aSVedant Kumar   //    at the end of the line. Both are required but may be empty.
3154c01092aSVedant Kumar 
3164c01092aSVedant Kumar   SmallVector<std::string, 8> Snippets;
3174c01092aSVedant Kumar 
3184c01092aSVedant Kumar   unsigned LCol = 1;
3194c01092aSVedant Kumar   auto Snip = [&](unsigned Start, unsigned Len) {
3204c01092aSVedant Kumar     assert(Start + Len <= Line.size() && "Snippet extends past the EOL");
3214c01092aSVedant Kumar     Snippets.push_back(Line.substr(Start, Len));
3224c01092aSVedant Kumar     LCol += Len;
3234c01092aSVedant Kumar   };
3244c01092aSVedant Kumar 
3254c01092aSVedant Kumar   Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1));
3264c01092aSVedant Kumar 
3274c01092aSVedant Kumar   for (unsigned I = 1, E = Segments.size(); I < E; ++I) {
3284c01092aSVedant Kumar     assert(LCol == Segments[I - 1]->Col && "Snippet start position is wrong");
3294c01092aSVedant Kumar     Snip(LCol - 1, Segments[I]->Col - LCol);
3304c01092aSVedant Kumar   }
3314c01092aSVedant Kumar 
3324c01092aSVedant Kumar   // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1.
3334c01092aSVedant Kumar   Snip(LCol - 1, Line.size() + 1 - LCol);
3344c01092aSVedant Kumar   assert(LCol == Line.size() + 1 && "Final snippet doesn't reach the EOL");
3354c01092aSVedant Kumar 
3364c01092aSVedant Kumar   // 2. Escape all of the snippets.
3374c01092aSVedant Kumar 
3384c01092aSVedant Kumar   for (unsigned I = 0, E = Snippets.size(); I < E; ++I)
3394c01092aSVedant Kumar     Snippets[I] = escape(Snippets[I]);
3404c01092aSVedant Kumar 
341*fc07e8b4SVedant Kumar   // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment
342*fc07e8b4SVedant Kumar   //    1 to set the highlight for snippet 2, segment 2 to set the highlight for
343*fc07e8b4SVedant Kumar   //    snippet 3, and so on.
3444c01092aSVedant Kumar 
3454c01092aSVedant Kumar   Optional<std::string> Color;
346*fc07e8b4SVedant Kumar   SmallVector<std::pair<unsigned, unsigned>, 4> HighlightedRanges;
347*fc07e8b4SVedant Kumar   auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) {
348*fc07e8b4SVedant Kumar     if (getOptions().Debug) {
349*fc07e8b4SVedant Kumar       if (!HighlightedRanges.empty() &&
350*fc07e8b4SVedant Kumar           HighlightedRanges.back().second == LC - 1) {
351*fc07e8b4SVedant Kumar         HighlightedRanges.back().second = RC;
352*fc07e8b4SVedant Kumar       } else
353*fc07e8b4SVedant Kumar         HighlightedRanges.emplace_back(LC, RC);
354*fc07e8b4SVedant Kumar     }
3554c01092aSVedant Kumar     return tag("span", Snippet, Color.getValue());
3564c01092aSVedant Kumar   };
3574c01092aSVedant Kumar 
3584c01092aSVedant Kumar   auto CheckIfUncovered = [](const coverage::CoverageSegment *S) {
359*fc07e8b4SVedant Kumar     return S && (S->HasCount && S->Count == 0);
3604c01092aSVedant Kumar   };
3614c01092aSVedant Kumar 
3624c01092aSVedant Kumar   if (CheckIfUncovered(WrappedSegment) ||
3634c01092aSVedant Kumar       CheckIfUncovered(Segments.empty() ? nullptr : Segments.front())) {
3644c01092aSVedant Kumar     Color = "red";
365*fc07e8b4SVedant Kumar     Snippets[0] = Highlight(Snippets[0], 0, Snippets[0].size());
3664c01092aSVedant Kumar   }
3674c01092aSVedant Kumar 
368*fc07e8b4SVedant Kumar   for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
3694c01092aSVedant Kumar     const auto *CurSeg = Segments[I];
3704c01092aSVedant Kumar     if (CurSeg->Col == ExpansionCol)
3714c01092aSVedant Kumar       Color = "cyan";
3724c01092aSVedant Kumar     else if (CheckIfUncovered(CurSeg))
3734c01092aSVedant Kumar       Color = "red";
3744c01092aSVedant Kumar     else
3754c01092aSVedant Kumar       Color = None;
3764c01092aSVedant Kumar 
3774c01092aSVedant Kumar     if (Color.hasValue())
378*fc07e8b4SVedant Kumar       Snippets[I + 1] = Highlight(Snippets[I + 1], CurSeg->Col,
379*fc07e8b4SVedant Kumar                                   CurSeg->Col + Snippets[I + 1].size());
380*fc07e8b4SVedant Kumar   }
381*fc07e8b4SVedant Kumar 
382*fc07e8b4SVedant Kumar   if (Color.hasValue() && Segments.empty())
383*fc07e8b4SVedant Kumar     Snippets.back() = Highlight(Snippets.back(), Snippets[0].size(), 0);
384*fc07e8b4SVedant Kumar 
385*fc07e8b4SVedant Kumar   if (getOptions().Debug) {
386*fc07e8b4SVedant Kumar     for (const auto &Range : HighlightedRanges) {
387*fc07e8b4SVedant Kumar       errs() << "Highlighted line " << LineNo << ", " << Range.first + 1
388*fc07e8b4SVedant Kumar              << " -> ";
389*fc07e8b4SVedant Kumar       if (Range.second == 0)
390*fc07e8b4SVedant Kumar         errs() << "?";
391*fc07e8b4SVedant Kumar       else
392*fc07e8b4SVedant Kumar         errs() << Range.second + 1;
393*fc07e8b4SVedant Kumar       errs() << "\n";
394*fc07e8b4SVedant Kumar     }
3954c01092aSVedant Kumar   }
3964c01092aSVedant Kumar 
3974c01092aSVedant Kumar   // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
3984c01092aSVedant Kumar   //    sub-line region count tooltips if needed.
3994c01092aSVedant Kumar 
4004c01092aSVedant Kumar   bool HasMultipleRegions = [&] {
4014c01092aSVedant Kumar     unsigned RegionCount = 0;
4024c01092aSVedant Kumar     for (const auto *S : Segments)
4034c01092aSVedant Kumar       if (S->HasCount && S->IsRegionEntry)
4044c01092aSVedant Kumar         if (++RegionCount > 1)
4054c01092aSVedant Kumar           return true;
4064c01092aSVedant Kumar     return false;
4074c01092aSVedant Kumar   }();
4084c01092aSVedant Kumar 
4094c01092aSVedant Kumar   if (shouldRenderRegionMarkers(HasMultipleRegions)) {
4104c01092aSVedant Kumar     for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
4114c01092aSVedant Kumar       const auto *CurSeg = Segments[I];
4124c01092aSVedant Kumar       if (!CurSeg->IsRegionEntry || !CurSeg->HasCount)
4134c01092aSVedant Kumar         continue;
4144c01092aSVedant Kumar 
4154c01092aSVedant Kumar       Snippets[I + 1] =
4164c01092aSVedant Kumar           tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count),
4174c01092aSVedant Kumar                                            "tooltip-content"),
4184c01092aSVedant Kumar               "tooltip");
4194c01092aSVedant Kumar     }
4204c01092aSVedant Kumar   }
4214c01092aSVedant Kumar 
4224c01092aSVedant Kumar   OS << BeginCodeTD;
4234c01092aSVedant Kumar   OS << BeginPre;
4244c01092aSVedant Kumar   for (const auto &Snippet : Snippets)
4254c01092aSVedant Kumar     OS << Snippet;
4264c01092aSVedant Kumar   OS << EndPre;
4274c01092aSVedant Kumar 
4284c01092aSVedant Kumar   // If there are no sub-views left to attach to this cell, end the cell.
4294c01092aSVedant Kumar   // Otherwise, end it after the sub-views are rendered (renderLineSuffix()).
4304c01092aSVedant Kumar   if (!hasSubViews())
4314c01092aSVedant Kumar     OS << EndCodeTD;
4324c01092aSVedant Kumar }
4334c01092aSVedant Kumar 
4344c01092aSVedant Kumar void SourceCoverageViewHTML::renderLineCoverageColumn(
4354c01092aSVedant Kumar     raw_ostream &OS, const LineCoverageStats &Line) {
4364c01092aSVedant Kumar   std::string Count = "";
4374c01092aSVedant Kumar   if (Line.isMapped())
4384c01092aSVedant Kumar     Count = tag("pre", formatCount(Line.ExecutionCount));
4394c01092aSVedant Kumar   std::string CoverageClass =
4404c01092aSVedant Kumar       (Line.ExecutionCount > 0) ? "covered-line" : "uncovered-line";
4414c01092aSVedant Kumar   OS << tag("td", Count, CoverageClass);
4424c01092aSVedant Kumar }
4434c01092aSVedant Kumar 
4444c01092aSVedant Kumar void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
4454c01092aSVedant Kumar                                                     unsigned LineNo) {
4462e089362SVedant Kumar   std::string LineNoStr = utostr(uint64_t(LineNo));
4472e089362SVedant Kumar   OS << tag("td", a("L" + LineNoStr, tag("pre", LineNoStr), "name"),
4482e089362SVedant Kumar             "line-number");
4494c01092aSVedant Kumar }
4504c01092aSVedant Kumar 
4514c01092aSVedant Kumar void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &,
4524c01092aSVedant Kumar                                                  CoverageSegmentArray,
4534c01092aSVedant Kumar                                                  unsigned) {
4544c01092aSVedant Kumar   // Region markers are rendered in-line using tooltips.
4554c01092aSVedant Kumar }
4564c01092aSVedant Kumar 
4574c01092aSVedant Kumar void SourceCoverageViewHTML::renderExpansionSite(
4584c01092aSVedant Kumar     raw_ostream &OS, LineRef L, const coverage::CoverageSegment *WrappedSegment,
4594c01092aSVedant Kumar     CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned ViewDepth) {
4604c01092aSVedant Kumar   // Render the line containing the expansion site. No extra formatting needed.
4614c01092aSVedant Kumar   renderLine(OS, L, WrappedSegment, Segments, ExpansionCol, ViewDepth);
4624c01092aSVedant Kumar }
4634c01092aSVedant Kumar 
4644c01092aSVedant Kumar void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS,
4654c01092aSVedant Kumar                                                  ExpansionView &ESV,
4664c01092aSVedant Kumar                                                  unsigned ViewDepth) {
4674c01092aSVedant Kumar   OS << BeginExpansionDiv;
4684c01092aSVedant Kumar   ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
4694c01092aSVedant Kumar                   ViewDepth + 1);
4704c01092aSVedant Kumar   OS << EndExpansionDiv;
4714c01092aSVedant Kumar }
4724c01092aSVedant Kumar 
4734c01092aSVedant Kumar void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS,
4744c01092aSVedant Kumar                                                      InstantiationView &ISV,
4754c01092aSVedant Kumar                                                      unsigned ViewDepth) {
4764c01092aSVedant Kumar   OS << BeginExpansionDiv;
4774c01092aSVedant Kumar   ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, ViewDepth);
4784c01092aSVedant Kumar   OS << EndExpansionDiv;
4794c01092aSVedant Kumar }
480