10b57cec5SDimitry Andric //===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric /// 90b57cec5SDimitry Andric /// \file This file implements the html coverage renderer. 100b57cec5SDimitry Andric /// 110b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 120b57cec5SDimitry Andric 130b57cec5SDimitry Andric #include "SourceCoverageViewHTML.h" 145f757f3fSDimitry Andric #include "CoverageReport.h" 150b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h" 160b57cec5SDimitry Andric #include "llvm/ADT/StringExtras.h" 170b57cec5SDimitry Andric #include "llvm/Support/Format.h" 180b57cec5SDimitry Andric #include "llvm/Support/Path.h" 195f757f3fSDimitry Andric #include "llvm/Support/ThreadPool.h" 20bdd1243dSDimitry Andric #include <optional> 210b57cec5SDimitry Andric 220b57cec5SDimitry Andric using namespace llvm; 230b57cec5SDimitry Andric 240b57cec5SDimitry Andric namespace { 250b57cec5SDimitry Andric 260b57cec5SDimitry Andric // Return a string with the special characters in \p Str escaped. 270b57cec5SDimitry Andric std::string escape(StringRef Str, const CoverageViewOptions &Opts) { 280b57cec5SDimitry Andric std::string TabExpandedResult; 290b57cec5SDimitry Andric unsigned ColNum = 0; // Record the column number. 300b57cec5SDimitry Andric for (char C : Str) { 310b57cec5SDimitry Andric if (C == '\t') { 320b57cec5SDimitry Andric // Replace '\t' with up to TabSize spaces. 330b57cec5SDimitry Andric unsigned NumSpaces = Opts.TabSize - (ColNum % Opts.TabSize); 345ffd83dbSDimitry Andric TabExpandedResult.append(NumSpaces, ' '); 350b57cec5SDimitry Andric ColNum += NumSpaces; 360b57cec5SDimitry Andric } else { 370b57cec5SDimitry Andric TabExpandedResult += C; 380b57cec5SDimitry Andric if (C == '\n' || C == '\r') 390b57cec5SDimitry Andric ColNum = 0; 400b57cec5SDimitry Andric else 410b57cec5SDimitry Andric ++ColNum; 420b57cec5SDimitry Andric } 430b57cec5SDimitry Andric } 440b57cec5SDimitry Andric std::string EscapedHTML; 450b57cec5SDimitry Andric { 460b57cec5SDimitry Andric raw_string_ostream OS{EscapedHTML}; 470b57cec5SDimitry Andric printHTMLEscaped(TabExpandedResult, OS); 480b57cec5SDimitry Andric } 490b57cec5SDimitry Andric return EscapedHTML; 500b57cec5SDimitry Andric } 510b57cec5SDimitry Andric 520b57cec5SDimitry Andric // Create a \p Name tag around \p Str, and optionally set its \p ClassName. 535f757f3fSDimitry Andric std::string tag(StringRef Name, StringRef Str, StringRef ClassName = "") { 545f757f3fSDimitry Andric std::string Tag = "<"; 555f757f3fSDimitry Andric Tag += Name; 565f757f3fSDimitry Andric if (!ClassName.empty()) { 575f757f3fSDimitry Andric Tag += " class='"; 585f757f3fSDimitry Andric Tag += ClassName; 595f757f3fSDimitry Andric Tag += "'"; 605f757f3fSDimitry Andric } 615f757f3fSDimitry Andric Tag += ">"; 625f757f3fSDimitry Andric Tag += Str; 635f757f3fSDimitry Andric Tag += "</"; 645f757f3fSDimitry Andric Tag += Name; 655f757f3fSDimitry Andric Tag += ">"; 665f757f3fSDimitry Andric return Tag; 670b57cec5SDimitry Andric } 680b57cec5SDimitry Andric 690b57cec5SDimitry Andric // Create an anchor to \p Link with the label \p Str. 705f757f3fSDimitry Andric std::string a(StringRef Link, StringRef Str, StringRef TargetName = "") { 715f757f3fSDimitry Andric std::string Tag; 725f757f3fSDimitry Andric Tag += "<a "; 735f757f3fSDimitry Andric if (!TargetName.empty()) { 745f757f3fSDimitry Andric Tag += "name='"; 755f757f3fSDimitry Andric Tag += TargetName; 765f757f3fSDimitry Andric Tag += "' "; 775f757f3fSDimitry Andric } 785f757f3fSDimitry Andric Tag += "href='"; 795f757f3fSDimitry Andric Tag += Link; 805f757f3fSDimitry Andric Tag += "'>"; 815f757f3fSDimitry Andric Tag += Str; 825f757f3fSDimitry Andric Tag += "</a>"; 835f757f3fSDimitry Andric return Tag; 840b57cec5SDimitry Andric } 850b57cec5SDimitry Andric 860b57cec5SDimitry Andric const char *BeginHeader = 870b57cec5SDimitry Andric "<head>" 880b57cec5SDimitry Andric "<meta name='viewport' content='width=device-width,initial-scale=1'>" 890b57cec5SDimitry Andric "<meta charset='UTF-8'>"; 900b57cec5SDimitry Andric 910b57cec5SDimitry Andric const char *CSSForCoverage = 920b57cec5SDimitry Andric R"(.red { 930b57cec5SDimitry Andric background-color: #ffd0d0; 940b57cec5SDimitry Andric } 950b57cec5SDimitry Andric .cyan { 960b57cec5SDimitry Andric background-color: cyan; 970b57cec5SDimitry Andric } 980b57cec5SDimitry Andric body { 990b57cec5SDimitry Andric font-family: -apple-system, sans-serif; 1000b57cec5SDimitry Andric } 1010b57cec5SDimitry Andric pre { 1020b57cec5SDimitry Andric margin-top: 0px !important; 1030b57cec5SDimitry Andric margin-bottom: 0px !important; 1040b57cec5SDimitry Andric } 1050b57cec5SDimitry Andric .source-name-title { 1060b57cec5SDimitry Andric padding: 5px 10px; 1070b57cec5SDimitry Andric border-bottom: 1px solid #dbdbdb; 1080b57cec5SDimitry Andric background-color: #eee; 1090b57cec5SDimitry Andric line-height: 35px; 1100b57cec5SDimitry Andric } 1110b57cec5SDimitry Andric .centered { 1120b57cec5SDimitry Andric display: table; 1130b57cec5SDimitry Andric margin-left: left; 1140b57cec5SDimitry Andric margin-right: auto; 1150b57cec5SDimitry Andric border: 1px solid #dbdbdb; 1160b57cec5SDimitry Andric border-radius: 3px; 1170b57cec5SDimitry Andric } 1180b57cec5SDimitry Andric .expansion-view { 1190b57cec5SDimitry Andric background-color: rgba(0, 0, 0, 0); 1200b57cec5SDimitry Andric margin-left: 0px; 1210b57cec5SDimitry Andric margin-top: 5px; 1220b57cec5SDimitry Andric margin-right: 5px; 1230b57cec5SDimitry Andric margin-bottom: 5px; 1240b57cec5SDimitry Andric border: 1px solid #dbdbdb; 1250b57cec5SDimitry Andric border-radius: 3px; 1260b57cec5SDimitry Andric } 1270b57cec5SDimitry Andric table { 1280b57cec5SDimitry Andric border-collapse: collapse; 1290b57cec5SDimitry Andric } 1300b57cec5SDimitry Andric .light-row { 1310b57cec5SDimitry Andric background: #ffffff; 1320b57cec5SDimitry Andric border: 1px solid #dbdbdb; 133*cb14a3feSDimitry Andric border-left: none; 134*cb14a3feSDimitry Andric border-right: none; 1350b57cec5SDimitry Andric } 1360b57cec5SDimitry Andric .light-row-bold { 1370b57cec5SDimitry Andric background: #ffffff; 1380b57cec5SDimitry Andric border: 1px solid #dbdbdb; 139*cb14a3feSDimitry Andric border-left: none; 140*cb14a3feSDimitry Andric border-right: none; 1410b57cec5SDimitry Andric font-weight: bold; 1420b57cec5SDimitry Andric } 1430b57cec5SDimitry Andric .column-entry { 1440b57cec5SDimitry Andric text-align: left; 1450b57cec5SDimitry Andric } 1460b57cec5SDimitry Andric .column-entry-bold { 1470b57cec5SDimitry Andric font-weight: bold; 1480b57cec5SDimitry Andric text-align: left; 1490b57cec5SDimitry Andric } 1500b57cec5SDimitry Andric .column-entry-yellow { 1510b57cec5SDimitry Andric text-align: left; 1520b57cec5SDimitry Andric background-color: #ffffd0; 1530b57cec5SDimitry Andric } 154*cb14a3feSDimitry Andric .column-entry-yellow:hover, tr:hover .column-entry-yellow { 1550b57cec5SDimitry Andric background-color: #fffff0; 1560b57cec5SDimitry Andric } 1570b57cec5SDimitry Andric .column-entry-red { 1580b57cec5SDimitry Andric text-align: left; 1590b57cec5SDimitry Andric background-color: #ffd0d0; 1600b57cec5SDimitry Andric } 161*cb14a3feSDimitry Andric .column-entry-red:hover, tr:hover .column-entry-red { 1620b57cec5SDimitry Andric background-color: #fff0f0; 1630b57cec5SDimitry Andric } 164*cb14a3feSDimitry Andric .column-entry-gray { 165*cb14a3feSDimitry Andric text-align: left; 166*cb14a3feSDimitry Andric background-color: #fbfbfb; 167*cb14a3feSDimitry Andric } 168*cb14a3feSDimitry Andric .column-entry-gray:hover, tr:hover .column-entry-gray { 169*cb14a3feSDimitry Andric background-color: #f0f0f0; 170*cb14a3feSDimitry Andric } 1710b57cec5SDimitry Andric .column-entry-green { 1720b57cec5SDimitry Andric text-align: left; 1730b57cec5SDimitry Andric background-color: #d0ffd0; 1740b57cec5SDimitry Andric } 175*cb14a3feSDimitry Andric .column-entry-green:hover, tr:hover .column-entry-green { 1760b57cec5SDimitry Andric background-color: #f0fff0; 1770b57cec5SDimitry Andric } 1780b57cec5SDimitry Andric .line-number { 1790b57cec5SDimitry Andric text-align: right; 1800b57cec5SDimitry Andric color: #aaa; 1810b57cec5SDimitry Andric } 1820b57cec5SDimitry Andric .covered-line { 1830b57cec5SDimitry Andric text-align: right; 1840b57cec5SDimitry Andric color: #0080ff; 1850b57cec5SDimitry Andric } 1860b57cec5SDimitry Andric .uncovered-line { 1870b57cec5SDimitry Andric text-align: right; 1880b57cec5SDimitry Andric color: #ff3300; 1890b57cec5SDimitry Andric } 1900b57cec5SDimitry Andric .tooltip { 1910b57cec5SDimitry Andric position: relative; 1920b57cec5SDimitry Andric display: inline; 1930b57cec5SDimitry Andric background-color: #b3e6ff; 1940b57cec5SDimitry Andric text-decoration: none; 1950b57cec5SDimitry Andric } 1960b57cec5SDimitry Andric .tooltip span.tooltip-content { 1970b57cec5SDimitry Andric position: absolute; 1980b57cec5SDimitry Andric width: 100px; 1990b57cec5SDimitry Andric margin-left: -50px; 2000b57cec5SDimitry Andric color: #FFFFFF; 2010b57cec5SDimitry Andric background: #000000; 2020b57cec5SDimitry Andric height: 30px; 2030b57cec5SDimitry Andric line-height: 30px; 2040b57cec5SDimitry Andric text-align: center; 2050b57cec5SDimitry Andric visibility: hidden; 2060b57cec5SDimitry Andric border-radius: 6px; 2070b57cec5SDimitry Andric } 2080b57cec5SDimitry Andric .tooltip span.tooltip-content:after { 2090b57cec5SDimitry Andric content: ''; 2100b57cec5SDimitry Andric position: absolute; 2110b57cec5SDimitry Andric top: 100%; 2120b57cec5SDimitry Andric left: 50%; 2130b57cec5SDimitry Andric margin-left: -8px; 2140b57cec5SDimitry Andric width: 0; height: 0; 2150b57cec5SDimitry Andric border-top: 8px solid #000000; 2160b57cec5SDimitry Andric border-right: 8px solid transparent; 2170b57cec5SDimitry Andric border-left: 8px solid transparent; 2180b57cec5SDimitry Andric } 2190b57cec5SDimitry Andric :hover.tooltip span.tooltip-content { 2200b57cec5SDimitry Andric visibility: visible; 2210b57cec5SDimitry Andric opacity: 0.8; 2220b57cec5SDimitry Andric bottom: 30px; 2230b57cec5SDimitry Andric left: 50%; 2240b57cec5SDimitry Andric z-index: 999; 2250b57cec5SDimitry Andric } 2260b57cec5SDimitry Andric th, td { 2270b57cec5SDimitry Andric vertical-align: top; 2280b57cec5SDimitry Andric padding: 2px 8px; 2290b57cec5SDimitry Andric border-collapse: collapse; 2300b57cec5SDimitry Andric border-right: solid 1px #eee; 2310b57cec5SDimitry Andric border-left: solid 1px #eee; 2320b57cec5SDimitry Andric text-align: left; 2330b57cec5SDimitry Andric } 2340b57cec5SDimitry Andric td pre { 2350b57cec5SDimitry Andric display: inline-block; 2360b57cec5SDimitry Andric } 2370b57cec5SDimitry Andric td:first-child { 2380b57cec5SDimitry Andric border-left: none; 2390b57cec5SDimitry Andric } 2400b57cec5SDimitry Andric td:last-child { 2410b57cec5SDimitry Andric border-right: none; 2420b57cec5SDimitry Andric } 2430b57cec5SDimitry Andric tr:hover { 2440b57cec5SDimitry Andric background-color: #f0f0f0; 2450b57cec5SDimitry Andric } 246*cb14a3feSDimitry Andric tr:last-child { 247*cb14a3feSDimitry Andric border-bottom: none; 248*cb14a3feSDimitry Andric } 2490b57cec5SDimitry Andric )"; 2500b57cec5SDimitry Andric 2510b57cec5SDimitry Andric const char *EndHeader = "</head>"; 2520b57cec5SDimitry Andric 2530b57cec5SDimitry Andric const char *BeginCenteredDiv = "<div class='centered'>"; 2540b57cec5SDimitry Andric 2550b57cec5SDimitry Andric const char *EndCenteredDiv = "</div>"; 2560b57cec5SDimitry Andric 2570b57cec5SDimitry Andric const char *BeginSourceNameDiv = "<div class='source-name-title'>"; 2580b57cec5SDimitry Andric 2590b57cec5SDimitry Andric const char *EndSourceNameDiv = "</div>"; 2600b57cec5SDimitry Andric 2610b57cec5SDimitry Andric const char *BeginCodeTD = "<td class='code'>"; 2620b57cec5SDimitry Andric 2630b57cec5SDimitry Andric const char *EndCodeTD = "</td>"; 2640b57cec5SDimitry Andric 2650b57cec5SDimitry Andric const char *BeginPre = "<pre>"; 2660b57cec5SDimitry Andric 2670b57cec5SDimitry Andric const char *EndPre = "</pre>"; 2680b57cec5SDimitry Andric 2690b57cec5SDimitry Andric const char *BeginExpansionDiv = "<div class='expansion-view'>"; 2700b57cec5SDimitry Andric 2710b57cec5SDimitry Andric const char *EndExpansionDiv = "</div>"; 2720b57cec5SDimitry Andric 2730b57cec5SDimitry Andric const char *BeginTable = "<table>"; 2740b57cec5SDimitry Andric 2750b57cec5SDimitry Andric const char *EndTable = "</table>"; 2760b57cec5SDimitry Andric 2770b57cec5SDimitry Andric const char *ProjectTitleTag = "h1"; 2780b57cec5SDimitry Andric 2790b57cec5SDimitry Andric const char *ReportTitleTag = "h2"; 2800b57cec5SDimitry Andric 2810b57cec5SDimitry Andric const char *CreatedTimeTag = "h4"; 2820b57cec5SDimitry Andric 2830b57cec5SDimitry Andric std::string getPathToStyle(StringRef ViewPath) { 284e8d8bef9SDimitry Andric std::string PathToStyle; 2855ffd83dbSDimitry Andric std::string PathSep = std::string(sys::path::get_separator()); 2860b57cec5SDimitry Andric unsigned NumSeps = ViewPath.count(PathSep); 2870b57cec5SDimitry Andric for (unsigned I = 0, E = NumSeps; I < E; ++I) 2880b57cec5SDimitry Andric PathToStyle += ".." + PathSep; 2890b57cec5SDimitry Andric return PathToStyle + "style.css"; 2900b57cec5SDimitry Andric } 2910b57cec5SDimitry Andric 2920b57cec5SDimitry Andric void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts, 2930b57cec5SDimitry Andric const std::string &PathToStyle = "") { 2940b57cec5SDimitry Andric OS << "<!doctype html>" 2950b57cec5SDimitry Andric "<html>" 2960b57cec5SDimitry Andric << BeginHeader; 2970b57cec5SDimitry Andric 2980b57cec5SDimitry Andric // Link to a stylesheet if one is available. Otherwise, use the default style. 2990b57cec5SDimitry Andric if (PathToStyle.empty()) 3000b57cec5SDimitry Andric OS << "<style>" << CSSForCoverage << "</style>"; 3010b57cec5SDimitry Andric else 3020b57cec5SDimitry Andric OS << "<link rel='stylesheet' type='text/css' href='" 3030b57cec5SDimitry Andric << escape(PathToStyle, Opts) << "'>"; 3040b57cec5SDimitry Andric 3050b57cec5SDimitry Andric OS << EndHeader << "<body>"; 3060b57cec5SDimitry Andric } 3070b57cec5SDimitry Andric 3085f757f3fSDimitry Andric void emitTableRow(raw_ostream &OS, const CoverageViewOptions &Opts, 3095f757f3fSDimitry Andric const std::string &FirstCol, const FileCoverageSummary &FCS, 3105f757f3fSDimitry Andric bool IsTotals) { 3115f757f3fSDimitry Andric SmallVector<std::string, 8> Columns; 3125f757f3fSDimitry Andric 3135f757f3fSDimitry Andric // Format a coverage triple and add the result to the list of columns. 3145f757f3fSDimitry Andric auto AddCoverageTripleToColumn = 3155f757f3fSDimitry Andric [&Columns, &Opts](unsigned Hit, unsigned Total, float Pctg) { 3165f757f3fSDimitry Andric std::string S; 3175f757f3fSDimitry Andric { 3185f757f3fSDimitry Andric raw_string_ostream RSO{S}; 3195f757f3fSDimitry Andric if (Total) 3205f757f3fSDimitry Andric RSO << format("%*.2f", 7, Pctg) << "% "; 3215f757f3fSDimitry Andric else 3225f757f3fSDimitry Andric RSO << "- "; 3235f757f3fSDimitry Andric RSO << '(' << Hit << '/' << Total << ')'; 3245f757f3fSDimitry Andric } 3255f757f3fSDimitry Andric const char *CellClass = "column-entry-yellow"; 326*cb14a3feSDimitry Andric if (!Total) 327*cb14a3feSDimitry Andric CellClass = "column-entry-gray"; 328*cb14a3feSDimitry Andric else if (Pctg >= Opts.HighCovWatermark) 3295f757f3fSDimitry Andric CellClass = "column-entry-green"; 3305f757f3fSDimitry Andric else if (Pctg < Opts.LowCovWatermark) 3315f757f3fSDimitry Andric CellClass = "column-entry-red"; 3325f757f3fSDimitry Andric Columns.emplace_back(tag("td", tag("pre", S), CellClass)); 3335f757f3fSDimitry Andric }; 3345f757f3fSDimitry Andric 3355f757f3fSDimitry Andric Columns.emplace_back(tag("td", tag("pre", FirstCol))); 3365f757f3fSDimitry Andric AddCoverageTripleToColumn(FCS.FunctionCoverage.getExecuted(), 3375f757f3fSDimitry Andric FCS.FunctionCoverage.getNumFunctions(), 3385f757f3fSDimitry Andric FCS.FunctionCoverage.getPercentCovered()); 3395f757f3fSDimitry Andric if (Opts.ShowInstantiationSummary) 3405f757f3fSDimitry Andric AddCoverageTripleToColumn(FCS.InstantiationCoverage.getExecuted(), 3415f757f3fSDimitry Andric FCS.InstantiationCoverage.getNumFunctions(), 3425f757f3fSDimitry Andric FCS.InstantiationCoverage.getPercentCovered()); 3435f757f3fSDimitry Andric AddCoverageTripleToColumn(FCS.LineCoverage.getCovered(), 3445f757f3fSDimitry Andric FCS.LineCoverage.getNumLines(), 3455f757f3fSDimitry Andric FCS.LineCoverage.getPercentCovered()); 3465f757f3fSDimitry Andric if (Opts.ShowRegionSummary) 3475f757f3fSDimitry Andric AddCoverageTripleToColumn(FCS.RegionCoverage.getCovered(), 3485f757f3fSDimitry Andric FCS.RegionCoverage.getNumRegions(), 3495f757f3fSDimitry Andric FCS.RegionCoverage.getPercentCovered()); 3505f757f3fSDimitry Andric if (Opts.ShowBranchSummary) 3515f757f3fSDimitry Andric AddCoverageTripleToColumn(FCS.BranchCoverage.getCovered(), 3525f757f3fSDimitry Andric FCS.BranchCoverage.getNumBranches(), 3535f757f3fSDimitry Andric FCS.BranchCoverage.getPercentCovered()); 3545f757f3fSDimitry Andric if (Opts.ShowMCDCSummary) 3555f757f3fSDimitry Andric AddCoverageTripleToColumn(FCS.MCDCCoverage.getCoveredPairs(), 3565f757f3fSDimitry Andric FCS.MCDCCoverage.getNumPairs(), 3575f757f3fSDimitry Andric FCS.MCDCCoverage.getPercentCovered()); 3585f757f3fSDimitry Andric 3595f757f3fSDimitry Andric if (IsTotals) 3605f757f3fSDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold"); 3615f757f3fSDimitry Andric else 3625f757f3fSDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row"); 3635f757f3fSDimitry Andric } 3645f757f3fSDimitry Andric 3650b57cec5SDimitry Andric void emitEpilog(raw_ostream &OS) { 3660b57cec5SDimitry Andric OS << "</body>" 3670b57cec5SDimitry Andric << "</html>"; 3680b57cec5SDimitry Andric } 3690b57cec5SDimitry Andric 3700b57cec5SDimitry Andric } // anonymous namespace 3710b57cec5SDimitry Andric 3720b57cec5SDimitry Andric Expected<CoveragePrinter::OwnedStream> 3730b57cec5SDimitry Andric CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) { 3740b57cec5SDimitry Andric auto OSOrErr = createOutputStream(Path, "html", InToplevel); 3750b57cec5SDimitry Andric if (!OSOrErr) 3760b57cec5SDimitry Andric return OSOrErr; 3770b57cec5SDimitry Andric 3780b57cec5SDimitry Andric OwnedStream OS = std::move(OSOrErr.get()); 3790b57cec5SDimitry Andric 3800b57cec5SDimitry Andric if (!Opts.hasOutputDirectory()) { 3810b57cec5SDimitry Andric emitPrelude(*OS.get(), Opts); 3820b57cec5SDimitry Andric } else { 3830b57cec5SDimitry Andric std::string ViewPath = getOutputPath(Path, "html", InToplevel); 3840b57cec5SDimitry Andric emitPrelude(*OS.get(), Opts, getPathToStyle(ViewPath)); 3850b57cec5SDimitry Andric } 3860b57cec5SDimitry Andric 3870b57cec5SDimitry Andric return std::move(OS); 3880b57cec5SDimitry Andric } 3890b57cec5SDimitry Andric 3900b57cec5SDimitry Andric void CoveragePrinterHTML::closeViewFile(OwnedStream OS) { 3910b57cec5SDimitry Andric emitEpilog(*OS.get()); 3920b57cec5SDimitry Andric } 3930b57cec5SDimitry Andric 3940b57cec5SDimitry Andric /// Emit column labels for the table in the index. 3950b57cec5SDimitry Andric static void emitColumnLabelsForIndex(raw_ostream &OS, 3960b57cec5SDimitry Andric const CoverageViewOptions &Opts) { 3970b57cec5SDimitry Andric SmallVector<std::string, 4> Columns; 3980b57cec5SDimitry Andric Columns.emplace_back(tag("td", "Filename", "column-entry-bold")); 3990b57cec5SDimitry Andric Columns.emplace_back(tag("td", "Function Coverage", "column-entry-bold")); 4000b57cec5SDimitry Andric if (Opts.ShowInstantiationSummary) 4010b57cec5SDimitry Andric Columns.emplace_back( 4020b57cec5SDimitry Andric tag("td", "Instantiation Coverage", "column-entry-bold")); 4030b57cec5SDimitry Andric Columns.emplace_back(tag("td", "Line Coverage", "column-entry-bold")); 4040b57cec5SDimitry Andric if (Opts.ShowRegionSummary) 4050b57cec5SDimitry Andric Columns.emplace_back(tag("td", "Region Coverage", "column-entry-bold")); 406e8d8bef9SDimitry Andric if (Opts.ShowBranchSummary) 407e8d8bef9SDimitry Andric Columns.emplace_back(tag("td", "Branch Coverage", "column-entry-bold")); 4085f757f3fSDimitry Andric if (Opts.ShowMCDCSummary) 4095f757f3fSDimitry Andric Columns.emplace_back(tag("td", "MC/DC", "column-entry-bold")); 4100b57cec5SDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), "")); 4110b57cec5SDimitry Andric } 4120b57cec5SDimitry Andric 4130b57cec5SDimitry Andric std::string 4140b57cec5SDimitry Andric CoveragePrinterHTML::buildLinkToFile(StringRef SF, 4150b57cec5SDimitry Andric const FileCoverageSummary &FCS) const { 4160b57cec5SDimitry Andric SmallString<128> LinkTextStr(sys::path::relative_path(FCS.Name)); 41704eeddc0SDimitry Andric sys::path::remove_dots(LinkTextStr, /*remove_dot_dot=*/true); 4180b57cec5SDimitry Andric sys::path::native(LinkTextStr); 4190b57cec5SDimitry Andric std::string LinkText = escape(LinkTextStr, Opts); 4200b57cec5SDimitry Andric std::string LinkTarget = 4210b57cec5SDimitry Andric escape(getOutputPath(SF, "html", /*InToplevel=*/false), Opts); 4220b57cec5SDimitry Andric return a(LinkTarget, LinkText); 4230b57cec5SDimitry Andric } 4240b57cec5SDimitry Andric 4255f757f3fSDimitry Andric Error CoveragePrinterHTML::emitStyleSheet() { 4260b57cec5SDimitry Andric auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true); 4270b57cec5SDimitry Andric if (Error E = CSSOrErr.takeError()) 4280b57cec5SDimitry Andric return E; 4290b57cec5SDimitry Andric 4300b57cec5SDimitry Andric OwnedStream CSS = std::move(CSSOrErr.get()); 4310b57cec5SDimitry Andric CSS->operator<<(CSSForCoverage); 4320b57cec5SDimitry Andric 4335f757f3fSDimitry Andric return Error::success(); 4345f757f3fSDimitry Andric } 4350b57cec5SDimitry Andric 4365f757f3fSDimitry Andric void CoveragePrinterHTML::emitReportHeader(raw_ostream &OSRef, 4375f757f3fSDimitry Andric const std::string &Title) { 4380b57cec5SDimitry Andric // Emit some basic information about the coverage report. 4390b57cec5SDimitry Andric if (Opts.hasProjectTitle()) 4400b57cec5SDimitry Andric OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts)); 4415f757f3fSDimitry Andric OSRef << tag(ReportTitleTag, Title); 4420b57cec5SDimitry Andric if (Opts.hasCreatedTime()) 4430b57cec5SDimitry Andric OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts)); 4440b57cec5SDimitry Andric 4450b57cec5SDimitry Andric // Emit a link to some documentation. 4460b57cec5SDimitry Andric OSRef << tag("p", "Click " + 4470b57cec5SDimitry Andric a("http://clang.llvm.org/docs/" 4480b57cec5SDimitry Andric "SourceBasedCodeCoverage.html#interpreting-reports", 4490b57cec5SDimitry Andric "here") + 4500b57cec5SDimitry Andric " for information about interpreting this report."); 4510b57cec5SDimitry Andric 4520b57cec5SDimitry Andric // Emit a table containing links to reports for each file in the covmapping. 4530b57cec5SDimitry Andric // Exclude files which don't contain any regions. 4540b57cec5SDimitry Andric OSRef << BeginCenteredDiv << BeginTable; 4550b57cec5SDimitry Andric emitColumnLabelsForIndex(OSRef, Opts); 4565f757f3fSDimitry Andric } 4575f757f3fSDimitry Andric 4585f757f3fSDimitry Andric /// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is 4595f757f3fSDimitry Andric /// false, link the summary to \p SF. 4605f757f3fSDimitry Andric void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF, 4615f757f3fSDimitry Andric const FileCoverageSummary &FCS, 4625f757f3fSDimitry Andric bool IsTotals) const { 4635f757f3fSDimitry Andric // Simplify the display file path, and wrap it in a link if requested. 4645f757f3fSDimitry Andric std::string Filename; 4655f757f3fSDimitry Andric if (IsTotals) { 4665f757f3fSDimitry Andric Filename = std::string(SF); 4675f757f3fSDimitry Andric } else { 4685f757f3fSDimitry Andric Filename = buildLinkToFile(SF, FCS); 4695f757f3fSDimitry Andric } 4705f757f3fSDimitry Andric 4715f757f3fSDimitry Andric emitTableRow(OS, Opts, Filename, FCS, IsTotals); 4725f757f3fSDimitry Andric } 4735f757f3fSDimitry Andric 4745f757f3fSDimitry Andric Error CoveragePrinterHTML::createIndexFile( 4755f757f3fSDimitry Andric ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 4765f757f3fSDimitry Andric const CoverageFiltersMatchAll &Filters) { 4775f757f3fSDimitry Andric // Emit the default stylesheet. 4785f757f3fSDimitry Andric if (Error E = emitStyleSheet()) 4795f757f3fSDimitry Andric return E; 4805f757f3fSDimitry Andric 4815f757f3fSDimitry Andric // Emit a file index along with some coverage statistics. 4825f757f3fSDimitry Andric auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true); 4835f757f3fSDimitry Andric if (Error E = OSOrErr.takeError()) 4845f757f3fSDimitry Andric return E; 4855f757f3fSDimitry Andric auto OS = std::move(OSOrErr.get()); 4865f757f3fSDimitry Andric raw_ostream &OSRef = *OS.get(); 4875f757f3fSDimitry Andric 4885f757f3fSDimitry Andric assert(Opts.hasOutputDirectory() && "No output directory for index file"); 4895f757f3fSDimitry Andric emitPrelude(OSRef, Opts, getPathToStyle("")); 4905f757f3fSDimitry Andric 4915f757f3fSDimitry Andric emitReportHeader(OSRef, "Coverage Report"); 4925f757f3fSDimitry Andric 4930b57cec5SDimitry Andric FileCoverageSummary Totals("TOTALS"); 4940b57cec5SDimitry Andric auto FileReports = CoverageReport::prepareFileReports( 4950b57cec5SDimitry Andric Coverage, Totals, SourceFiles, Opts, Filters); 4960b57cec5SDimitry Andric bool EmptyFiles = false; 4970b57cec5SDimitry Andric for (unsigned I = 0, E = FileReports.size(); I < E; ++I) { 4980b57cec5SDimitry Andric if (FileReports[I].FunctionCoverage.getNumFunctions()) 4990b57cec5SDimitry Andric emitFileSummary(OSRef, SourceFiles[I], FileReports[I]); 5000b57cec5SDimitry Andric else 5010b57cec5SDimitry Andric EmptyFiles = true; 5020b57cec5SDimitry Andric } 5030b57cec5SDimitry Andric emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true); 5040b57cec5SDimitry Andric OSRef << EndTable << EndCenteredDiv; 5050b57cec5SDimitry Andric 5060b57cec5SDimitry Andric // Emit links to files which don't contain any functions. These are normally 5070b57cec5SDimitry Andric // not very useful, but could be relevant for code which abuses the 5080b57cec5SDimitry Andric // preprocessor. 5090b57cec5SDimitry Andric if (EmptyFiles && Filters.empty()) { 5100b57cec5SDimitry Andric OSRef << tag("p", "Files which contain no functions. (These " 5110b57cec5SDimitry Andric "files contain code pulled into other files " 5120b57cec5SDimitry Andric "by the preprocessor.)\n"); 5130b57cec5SDimitry Andric OSRef << BeginCenteredDiv << BeginTable; 5140b57cec5SDimitry Andric for (unsigned I = 0, E = FileReports.size(); I < E; ++I) 5150b57cec5SDimitry Andric if (!FileReports[I].FunctionCoverage.getNumFunctions()) { 5160b57cec5SDimitry Andric std::string Link = buildLinkToFile(SourceFiles[I], FileReports[I]); 5170b57cec5SDimitry Andric OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n'; 5180b57cec5SDimitry Andric } 5190b57cec5SDimitry Andric OSRef << EndTable << EndCenteredDiv; 5200b57cec5SDimitry Andric } 5210b57cec5SDimitry Andric 5220b57cec5SDimitry Andric OSRef << tag("h5", escape(Opts.getLLVMVersionString(), Opts)); 5230b57cec5SDimitry Andric emitEpilog(OSRef); 5240b57cec5SDimitry Andric 5250b57cec5SDimitry Andric return Error::success(); 5260b57cec5SDimitry Andric } 5270b57cec5SDimitry Andric 5285f757f3fSDimitry Andric struct CoveragePrinterHTMLDirectory::Reporter : public DirectoryCoverageReport { 5295f757f3fSDimitry Andric CoveragePrinterHTMLDirectory &Printer; 5305f757f3fSDimitry Andric 5315f757f3fSDimitry Andric Reporter(CoveragePrinterHTMLDirectory &Printer, 5325f757f3fSDimitry Andric const coverage::CoverageMapping &Coverage, 5335f757f3fSDimitry Andric const CoverageFiltersMatchAll &Filters) 5345f757f3fSDimitry Andric : DirectoryCoverageReport(Printer.Opts, Coverage, Filters), 5355f757f3fSDimitry Andric Printer(Printer) {} 5365f757f3fSDimitry Andric 5375f757f3fSDimitry Andric Error generateSubDirectoryReport(SubFileReports &&SubFiles, 5385f757f3fSDimitry Andric SubDirReports &&SubDirs, 5395f757f3fSDimitry Andric FileCoverageSummary &&SubTotals) override { 5405f757f3fSDimitry Andric auto &LCPath = SubTotals.Name; 5415f757f3fSDimitry Andric assert(Options.hasOutputDirectory() && 5425f757f3fSDimitry Andric "No output directory for index file"); 5435f757f3fSDimitry Andric 5445f757f3fSDimitry Andric SmallString<128> OSPath = LCPath; 5455f757f3fSDimitry Andric sys::path::append(OSPath, "index"); 5465f757f3fSDimitry Andric auto OSOrErr = Printer.createOutputStream(OSPath, "html", 5475f757f3fSDimitry Andric /*InToplevel=*/false); 5485f757f3fSDimitry Andric if (auto E = OSOrErr.takeError()) 5495f757f3fSDimitry Andric return E; 5505f757f3fSDimitry Andric auto OS = std::move(OSOrErr.get()); 5515f757f3fSDimitry Andric raw_ostream &OSRef = *OS.get(); 5525f757f3fSDimitry Andric 5535f757f3fSDimitry Andric auto IndexHtmlPath = Printer.getOutputPath((LCPath + "index").str(), "html", 5545f757f3fSDimitry Andric /*InToplevel=*/false); 5555f757f3fSDimitry Andric emitPrelude(OSRef, Options, getPathToStyle(IndexHtmlPath)); 5565f757f3fSDimitry Andric 5575f757f3fSDimitry Andric auto NavLink = buildTitleLinks(LCPath); 5585f757f3fSDimitry Andric Printer.emitReportHeader(OSRef, "Coverage Report (" + NavLink + ")"); 5595f757f3fSDimitry Andric 5605f757f3fSDimitry Andric std::vector<const FileCoverageSummary *> EmptyFiles; 5615f757f3fSDimitry Andric 5625f757f3fSDimitry Andric // Make directories at the top of the table. 5635f757f3fSDimitry Andric for (auto &&SubDir : SubDirs) { 5645f757f3fSDimitry Andric auto &Report = SubDir.second.first; 5655f757f3fSDimitry Andric if (!Report.FunctionCoverage.getNumFunctions()) 5665f757f3fSDimitry Andric EmptyFiles.push_back(&Report); 5675f757f3fSDimitry Andric else 5685f757f3fSDimitry Andric emitTableRow(OSRef, Options, buildRelLinkToFile(Report.Name), Report, 5695f757f3fSDimitry Andric /*IsTotals=*/false); 5705f757f3fSDimitry Andric } 5715f757f3fSDimitry Andric 5725f757f3fSDimitry Andric for (auto &&SubFile : SubFiles) { 5735f757f3fSDimitry Andric auto &Report = SubFile.second; 5745f757f3fSDimitry Andric if (!Report.FunctionCoverage.getNumFunctions()) 5755f757f3fSDimitry Andric EmptyFiles.push_back(&Report); 5765f757f3fSDimitry Andric else 5775f757f3fSDimitry Andric emitTableRow(OSRef, Options, buildRelLinkToFile(Report.Name), Report, 5785f757f3fSDimitry Andric /*IsTotals=*/false); 5795f757f3fSDimitry Andric } 5805f757f3fSDimitry Andric 5815f757f3fSDimitry Andric // Emit the totals row. 5825f757f3fSDimitry Andric emitTableRow(OSRef, Options, "Totals", SubTotals, /*IsTotals=*/false); 5835f757f3fSDimitry Andric OSRef << EndTable << EndCenteredDiv; 5845f757f3fSDimitry Andric 5855f757f3fSDimitry Andric // Emit links to files which don't contain any functions. These are normally 5865f757f3fSDimitry Andric // not very useful, but could be relevant for code which abuses the 5875f757f3fSDimitry Andric // preprocessor. 5885f757f3fSDimitry Andric if (!EmptyFiles.empty()) { 5895f757f3fSDimitry Andric OSRef << tag("p", "Files which contain no functions. (These " 5905f757f3fSDimitry Andric "files contain code pulled into other files " 5915f757f3fSDimitry Andric "by the preprocessor.)\n"); 5925f757f3fSDimitry Andric OSRef << BeginCenteredDiv << BeginTable; 5935f757f3fSDimitry Andric for (auto FCS : EmptyFiles) { 5945f757f3fSDimitry Andric auto Link = buildRelLinkToFile(FCS->Name); 5955f757f3fSDimitry Andric OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n'; 5965f757f3fSDimitry Andric } 5975f757f3fSDimitry Andric OSRef << EndTable << EndCenteredDiv; 5985f757f3fSDimitry Andric } 5995f757f3fSDimitry Andric 6005f757f3fSDimitry Andric // Emit epilog. 6015f757f3fSDimitry Andric OSRef << tag("h5", escape(Options.getLLVMVersionString(), Options)); 6025f757f3fSDimitry Andric emitEpilog(OSRef); 6035f757f3fSDimitry Andric 6045f757f3fSDimitry Andric return Error::success(); 6055f757f3fSDimitry Andric } 6065f757f3fSDimitry Andric 6075f757f3fSDimitry Andric /// Make a title with hyperlinks to the index.html files of each hierarchy 6085f757f3fSDimitry Andric /// of the report. 6095f757f3fSDimitry Andric std::string buildTitleLinks(StringRef LCPath) const { 6105f757f3fSDimitry Andric // For each report level in LCPStack, extract the path component and 6115f757f3fSDimitry Andric // calculate the number of "../" relative to current LCPath. 6125f757f3fSDimitry Andric SmallVector<std::pair<SmallString<128>, unsigned>, 16> Components; 6135f757f3fSDimitry Andric 6145f757f3fSDimitry Andric auto Iter = LCPStack.begin(), IterE = LCPStack.end(); 6155f757f3fSDimitry Andric SmallString<128> RootPath; 6165f757f3fSDimitry Andric if (*Iter == 0) { 6175f757f3fSDimitry Andric // If llvm-cov works on relative coverage mapping data, the LCP of 6185f757f3fSDimitry Andric // all source file paths can be 0, which makes the title path empty. 6195f757f3fSDimitry Andric // As we like adding a slash at the back of the path to indicate a 6205f757f3fSDimitry Andric // directory, in this case, we use "." as the root path to make it 6215f757f3fSDimitry Andric // not be confused with the root path "/". 6225f757f3fSDimitry Andric RootPath = "."; 6235f757f3fSDimitry Andric } else { 6245f757f3fSDimitry Andric RootPath = LCPath.substr(0, *Iter); 6255f757f3fSDimitry Andric sys::path::native(RootPath); 6265f757f3fSDimitry Andric sys::path::remove_dots(RootPath, /*remove_dot_dot=*/true); 6275f757f3fSDimitry Andric } 6285f757f3fSDimitry Andric Components.emplace_back(std::move(RootPath), 0); 6295f757f3fSDimitry Andric 6305f757f3fSDimitry Andric for (auto Last = *Iter; ++Iter != IterE; Last = *Iter) { 6315f757f3fSDimitry Andric SmallString<128> SubPath = LCPath.substr(Last, *Iter - Last); 6325f757f3fSDimitry Andric sys::path::native(SubPath); 6335f757f3fSDimitry Andric sys::path::remove_dots(SubPath, /*remove_dot_dot=*/true); 6345f757f3fSDimitry Andric auto Level = unsigned(SubPath.count(sys::path::get_separator())) + 1; 6355f757f3fSDimitry Andric Components.back().second += Level; 6365f757f3fSDimitry Andric Components.emplace_back(std::move(SubPath), Level); 6375f757f3fSDimitry Andric } 6385f757f3fSDimitry Andric 6395f757f3fSDimitry Andric // Then we make the title accroding to Components. 6405f757f3fSDimitry Andric std::string S; 6415f757f3fSDimitry Andric for (auto I = Components.begin(), E = Components.end();;) { 6425f757f3fSDimitry Andric auto &Name = I->first; 6435f757f3fSDimitry Andric if (++I == E) { 6445f757f3fSDimitry Andric S += a("./index.html", Name); 6455f757f3fSDimitry Andric S += sys::path::get_separator(); 6465f757f3fSDimitry Andric break; 6475f757f3fSDimitry Andric } 6485f757f3fSDimitry Andric 6495f757f3fSDimitry Andric SmallString<128> Link; 6505f757f3fSDimitry Andric for (unsigned J = I->second; J > 0; --J) 6515f757f3fSDimitry Andric Link += "../"; 6525f757f3fSDimitry Andric Link += "index.html"; 6535f757f3fSDimitry Andric S += a(Link, Name); 6545f757f3fSDimitry Andric S += sys::path::get_separator(); 6555f757f3fSDimitry Andric } 6565f757f3fSDimitry Andric return S; 6575f757f3fSDimitry Andric } 6585f757f3fSDimitry Andric 6595f757f3fSDimitry Andric std::string buildRelLinkToFile(StringRef RelPath) const { 6605f757f3fSDimitry Andric SmallString<128> LinkTextStr(RelPath); 6615f757f3fSDimitry Andric sys::path::native(LinkTextStr); 6625f757f3fSDimitry Andric 6635f757f3fSDimitry Andric // remove_dots will remove trailing slash, so we need to check before it. 6645f757f3fSDimitry Andric auto IsDir = LinkTextStr.ends_with(sys::path::get_separator()); 6655f757f3fSDimitry Andric sys::path::remove_dots(LinkTextStr, /*remove_dot_dot=*/true); 6665f757f3fSDimitry Andric 6675f757f3fSDimitry Andric SmallString<128> LinkTargetStr(LinkTextStr); 6685f757f3fSDimitry Andric if (IsDir) { 6695f757f3fSDimitry Andric LinkTextStr += sys::path::get_separator(); 6705f757f3fSDimitry Andric sys::path::append(LinkTargetStr, "index.html"); 6715f757f3fSDimitry Andric } else { 6725f757f3fSDimitry Andric LinkTargetStr += ".html"; 6735f757f3fSDimitry Andric } 6745f757f3fSDimitry Andric 6755f757f3fSDimitry Andric auto LinkText = escape(LinkTextStr, Options); 6765f757f3fSDimitry Andric auto LinkTarget = escape(LinkTargetStr, Options); 6775f757f3fSDimitry Andric return a(LinkTarget, LinkText); 6785f757f3fSDimitry Andric } 6795f757f3fSDimitry Andric }; 6805f757f3fSDimitry Andric 6815f757f3fSDimitry Andric Error CoveragePrinterHTMLDirectory::createIndexFile( 6825f757f3fSDimitry Andric ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 6835f757f3fSDimitry Andric const CoverageFiltersMatchAll &Filters) { 6845f757f3fSDimitry Andric // The createSubIndexFile function only works when SourceFiles is 6855f757f3fSDimitry Andric // more than one. So we fallback to CoveragePrinterHTML when it is. 6865f757f3fSDimitry Andric if (SourceFiles.size() <= 1) 6875f757f3fSDimitry Andric return CoveragePrinterHTML::createIndexFile(SourceFiles, Coverage, Filters); 6885f757f3fSDimitry Andric 6895f757f3fSDimitry Andric // Emit the default stylesheet. 6905f757f3fSDimitry Andric if (Error E = emitStyleSheet()) 6915f757f3fSDimitry Andric return E; 6925f757f3fSDimitry Andric 6935f757f3fSDimitry Andric // Emit index files in every subdirectory. 6945f757f3fSDimitry Andric Reporter Report(*this, Coverage, Filters); 6955f757f3fSDimitry Andric auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles); 6965f757f3fSDimitry Andric if (auto E = TotalsOrErr.takeError()) 6975f757f3fSDimitry Andric return E; 6985f757f3fSDimitry Andric auto &LCPath = TotalsOrErr->Name; 6995f757f3fSDimitry Andric 7005f757f3fSDimitry Andric // Emit the top level index file. Top level index file is just a redirection 7015f757f3fSDimitry Andric // to the index file in the LCP directory. 7025f757f3fSDimitry Andric auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true); 7035f757f3fSDimitry Andric if (auto E = OSOrErr.takeError()) 7045f757f3fSDimitry Andric return E; 7055f757f3fSDimitry Andric auto OS = std::move(OSOrErr.get()); 7065f757f3fSDimitry Andric auto LCPIndexFilePath = 7075f757f3fSDimitry Andric getOutputPath((LCPath + "index").str(), "html", /*InToplevel=*/false); 7085f757f3fSDimitry Andric *OS.get() << R"(<!DOCTYPE html> 7095f757f3fSDimitry Andric <html> 7105f757f3fSDimitry Andric <head> 7115f757f3fSDimitry Andric <meta http-equiv="Refresh" content="0; url=')" 7125f757f3fSDimitry Andric << LCPIndexFilePath << R"('" /> 7135f757f3fSDimitry Andric </head> 7145f757f3fSDimitry Andric <body></body> 7155f757f3fSDimitry Andric </html> 7165f757f3fSDimitry Andric )"; 7175f757f3fSDimitry Andric 7185f757f3fSDimitry Andric return Error::success(); 7195f757f3fSDimitry Andric } 7205f757f3fSDimitry Andric 7210b57cec5SDimitry Andric void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) { 7220b57cec5SDimitry Andric OS << BeginCenteredDiv << BeginTable; 7230b57cec5SDimitry Andric } 7240b57cec5SDimitry Andric 7250b57cec5SDimitry Andric void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) { 7260b57cec5SDimitry Andric OS << EndTable << EndCenteredDiv; 7270b57cec5SDimitry Andric } 7280b57cec5SDimitry Andric 7290b57cec5SDimitry Andric void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS, bool WholeFile) { 7300b57cec5SDimitry Andric OS << BeginSourceNameDiv << tag("pre", escape(getSourceName(), getOptions())) 7310b57cec5SDimitry Andric << EndSourceNameDiv; 7320b57cec5SDimitry Andric } 7330b57cec5SDimitry Andric 7340b57cec5SDimitry Andric void SourceCoverageViewHTML::renderLinePrefix(raw_ostream &OS, unsigned) { 7350b57cec5SDimitry Andric OS << "<tr>"; 7360b57cec5SDimitry Andric } 7370b57cec5SDimitry Andric 7380b57cec5SDimitry Andric void SourceCoverageViewHTML::renderLineSuffix(raw_ostream &OS, unsigned) { 7390b57cec5SDimitry Andric // If this view has sub-views, renderLine() cannot close the view's cell. 7400b57cec5SDimitry Andric // Take care of it here, after all sub-views have been rendered. 7410b57cec5SDimitry Andric if (hasSubViews()) 7420b57cec5SDimitry Andric OS << EndCodeTD; 7430b57cec5SDimitry Andric OS << "</tr>"; 7440b57cec5SDimitry Andric } 7450b57cec5SDimitry Andric 7460b57cec5SDimitry Andric void SourceCoverageViewHTML::renderViewDivider(raw_ostream &, unsigned) { 7470b57cec5SDimitry Andric // The table-based output makes view dividers unnecessary. 7480b57cec5SDimitry Andric } 7490b57cec5SDimitry Andric 7500b57cec5SDimitry Andric void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L, 7510b57cec5SDimitry Andric const LineCoverageStats &LCS, 7520b57cec5SDimitry Andric unsigned ExpansionCol, unsigned) { 7530b57cec5SDimitry Andric StringRef Line = L.Line; 7540b57cec5SDimitry Andric unsigned LineNo = L.LineNo; 7550b57cec5SDimitry Andric 7560b57cec5SDimitry Andric // Steps for handling text-escaping, highlighting, and tooltip creation: 7570b57cec5SDimitry Andric // 7580b57cec5SDimitry Andric // 1. Split the line into N+1 snippets, where N = |Segments|. The first 7590b57cec5SDimitry Andric // snippet starts from Col=1 and ends at the start of the first segment. 7600b57cec5SDimitry Andric // The last snippet starts at the last mapped column in the line and ends 7610b57cec5SDimitry Andric // at the end of the line. Both are required but may be empty. 7620b57cec5SDimitry Andric 7630b57cec5SDimitry Andric SmallVector<std::string, 8> Snippets; 7640b57cec5SDimitry Andric CoverageSegmentArray Segments = LCS.getLineSegments(); 7650b57cec5SDimitry Andric 7660b57cec5SDimitry Andric unsigned LCol = 1; 7670b57cec5SDimitry Andric auto Snip = [&](unsigned Start, unsigned Len) { 7685ffd83dbSDimitry Andric Snippets.push_back(std::string(Line.substr(Start, Len))); 7690b57cec5SDimitry Andric LCol += Len; 7700b57cec5SDimitry Andric }; 7710b57cec5SDimitry Andric 7720b57cec5SDimitry Andric Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1)); 7730b57cec5SDimitry Andric 7740b57cec5SDimitry Andric for (unsigned I = 1, E = Segments.size(); I < E; ++I) 7750b57cec5SDimitry Andric Snip(LCol - 1, Segments[I]->Col - LCol); 7760b57cec5SDimitry Andric 7770b57cec5SDimitry Andric // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1. 7780b57cec5SDimitry Andric Snip(LCol - 1, Line.size() + 1 - LCol); 7790b57cec5SDimitry Andric 7800b57cec5SDimitry Andric // 2. Escape all of the snippets. 7810b57cec5SDimitry Andric 7820b57cec5SDimitry Andric for (unsigned I = 0, E = Snippets.size(); I < E; ++I) 7830b57cec5SDimitry Andric Snippets[I] = escape(Snippets[I], getOptions()); 7840b57cec5SDimitry Andric 7850b57cec5SDimitry Andric // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment 7860b57cec5SDimitry Andric // 1 to set the highlight for snippet 2, segment 2 to set the highlight for 7870b57cec5SDimitry Andric // snippet 3, and so on. 7880b57cec5SDimitry Andric 789bdd1243dSDimitry Andric std::optional<StringRef> Color; 7900b57cec5SDimitry Andric SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; 7910b57cec5SDimitry Andric auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) { 7920b57cec5SDimitry Andric if (getOptions().Debug) 7930b57cec5SDimitry Andric HighlightedRanges.emplace_back(LC, RC); 79481ad6265SDimitry Andric return tag("span", Snippet, std::string(*Color)); 7950b57cec5SDimitry Andric }; 7960b57cec5SDimitry Andric 7970b57cec5SDimitry Andric auto CheckIfUncovered = [&](const CoverageSegment *S) { 7980b57cec5SDimitry Andric return S && (!S->IsGapRegion || (Color && *Color == "red")) && 7990b57cec5SDimitry Andric S->HasCount && S->Count == 0; 8000b57cec5SDimitry Andric }; 8010b57cec5SDimitry Andric 8020b57cec5SDimitry Andric if (CheckIfUncovered(LCS.getWrappedSegment())) { 8030b57cec5SDimitry Andric Color = "red"; 8040b57cec5SDimitry Andric if (!Snippets[0].empty()) 8050b57cec5SDimitry Andric Snippets[0] = Highlight(Snippets[0], 1, 1 + Snippets[0].size()); 8060b57cec5SDimitry Andric } 8070b57cec5SDimitry Andric 8080b57cec5SDimitry Andric for (unsigned I = 0, E = Segments.size(); I < E; ++I) { 8090b57cec5SDimitry Andric const auto *CurSeg = Segments[I]; 8100b57cec5SDimitry Andric if (CheckIfUncovered(CurSeg)) 8110b57cec5SDimitry Andric Color = "red"; 8120b57cec5SDimitry Andric else if (CurSeg->Col == ExpansionCol) 8130b57cec5SDimitry Andric Color = "cyan"; 8140b57cec5SDimitry Andric else 815bdd1243dSDimitry Andric Color = std::nullopt; 8160b57cec5SDimitry Andric 81781ad6265SDimitry Andric if (Color) 8180b57cec5SDimitry Andric Snippets[I + 1] = Highlight(Snippets[I + 1], CurSeg->Col, 8190b57cec5SDimitry Andric CurSeg->Col + Snippets[I + 1].size()); 8200b57cec5SDimitry Andric } 8210b57cec5SDimitry Andric 82281ad6265SDimitry Andric if (Color && Segments.empty()) 8230b57cec5SDimitry Andric Snippets.back() = Highlight(Snippets.back(), 1, 1 + Snippets.back().size()); 8240b57cec5SDimitry Andric 8250b57cec5SDimitry Andric if (getOptions().Debug) { 8260b57cec5SDimitry Andric for (const auto &Range : HighlightedRanges) { 8270b57cec5SDimitry Andric errs() << "Highlighted line " << LineNo << ", " << Range.first << " -> "; 8280b57cec5SDimitry Andric if (Range.second == 0) 8290b57cec5SDimitry Andric errs() << "?"; 8300b57cec5SDimitry Andric else 8310b57cec5SDimitry Andric errs() << Range.second; 8320b57cec5SDimitry Andric errs() << "\n"; 8330b57cec5SDimitry Andric } 8340b57cec5SDimitry Andric } 8350b57cec5SDimitry Andric 8360b57cec5SDimitry Andric // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate 8370b57cec5SDimitry Andric // sub-line region count tooltips if needed. 8380b57cec5SDimitry Andric 8390b57cec5SDimitry Andric if (shouldRenderRegionMarkers(LCS)) { 8400b57cec5SDimitry Andric // Just consider the segments which start *and* end on this line. 8410b57cec5SDimitry Andric for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) { 8420b57cec5SDimitry Andric const auto *CurSeg = Segments[I]; 8430b57cec5SDimitry Andric if (!CurSeg->IsRegionEntry) 8440b57cec5SDimitry Andric continue; 8450b57cec5SDimitry Andric if (CurSeg->Count == LCS.getExecutionCount()) 8460b57cec5SDimitry Andric continue; 8470b57cec5SDimitry Andric 8480b57cec5SDimitry Andric Snippets[I + 1] = 8490b57cec5SDimitry Andric tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count), 8500b57cec5SDimitry Andric "tooltip-content"), 8510b57cec5SDimitry Andric "tooltip"); 8520b57cec5SDimitry Andric 8530b57cec5SDimitry Andric if (getOptions().Debug) 8540b57cec5SDimitry Andric errs() << "Marker at " << CurSeg->Line << ":" << CurSeg->Col << " = " 8550b57cec5SDimitry Andric << formatCount(CurSeg->Count) << "\n"; 8560b57cec5SDimitry Andric } 8570b57cec5SDimitry Andric } 8580b57cec5SDimitry Andric 8590b57cec5SDimitry Andric OS << BeginCodeTD; 8600b57cec5SDimitry Andric OS << BeginPre; 8610b57cec5SDimitry Andric for (const auto &Snippet : Snippets) 8620b57cec5SDimitry Andric OS << Snippet; 8630b57cec5SDimitry Andric OS << EndPre; 8640b57cec5SDimitry Andric 8650b57cec5SDimitry Andric // If there are no sub-views left to attach to this cell, end the cell. 8660b57cec5SDimitry Andric // Otherwise, end it after the sub-views are rendered (renderLineSuffix()). 8670b57cec5SDimitry Andric if (!hasSubViews()) 8680b57cec5SDimitry Andric OS << EndCodeTD; 8690b57cec5SDimitry Andric } 8700b57cec5SDimitry Andric 8710b57cec5SDimitry Andric void SourceCoverageViewHTML::renderLineCoverageColumn( 8720b57cec5SDimitry Andric raw_ostream &OS, const LineCoverageStats &Line) { 873e8d8bef9SDimitry Andric std::string Count; 8740b57cec5SDimitry Andric if (Line.isMapped()) 8750b57cec5SDimitry Andric Count = tag("pre", formatCount(Line.getExecutionCount())); 8760b57cec5SDimitry Andric std::string CoverageClass = 8770b57cec5SDimitry Andric (Line.getExecutionCount() > 0) ? "covered-line" : "uncovered-line"; 8780b57cec5SDimitry Andric OS << tag("td", Count, CoverageClass); 8790b57cec5SDimitry Andric } 8800b57cec5SDimitry Andric 8810b57cec5SDimitry Andric void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS, 8820b57cec5SDimitry Andric unsigned LineNo) { 8830b57cec5SDimitry Andric std::string LineNoStr = utostr(uint64_t(LineNo)); 8840b57cec5SDimitry Andric std::string TargetName = "L" + LineNoStr; 8850b57cec5SDimitry Andric OS << tag("td", a("#" + TargetName, tag("pre", LineNoStr), TargetName), 8860b57cec5SDimitry Andric "line-number"); 8870b57cec5SDimitry Andric } 8880b57cec5SDimitry Andric 8890b57cec5SDimitry Andric void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &, 8900b57cec5SDimitry Andric const LineCoverageStats &Line, 8910b57cec5SDimitry Andric unsigned) { 8920b57cec5SDimitry Andric // Region markers are rendered in-line using tooltips. 8930b57cec5SDimitry Andric } 8940b57cec5SDimitry Andric 8950b57cec5SDimitry Andric void SourceCoverageViewHTML::renderExpansionSite(raw_ostream &OS, LineRef L, 8960b57cec5SDimitry Andric const LineCoverageStats &LCS, 8970b57cec5SDimitry Andric unsigned ExpansionCol, 8980b57cec5SDimitry Andric unsigned ViewDepth) { 8990b57cec5SDimitry Andric // Render the line containing the expansion site. No extra formatting needed. 9000b57cec5SDimitry Andric renderLine(OS, L, LCS, ExpansionCol, ViewDepth); 9010b57cec5SDimitry Andric } 9020b57cec5SDimitry Andric 9030b57cec5SDimitry Andric void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS, 9040b57cec5SDimitry Andric ExpansionView &ESV, 9050b57cec5SDimitry Andric unsigned ViewDepth) { 9060b57cec5SDimitry Andric OS << BeginExpansionDiv; 9070b57cec5SDimitry Andric ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false, 9080b57cec5SDimitry Andric /*ShowTitle=*/false, ViewDepth + 1); 9090b57cec5SDimitry Andric OS << EndExpansionDiv; 9100b57cec5SDimitry Andric } 9110b57cec5SDimitry Andric 912e8d8bef9SDimitry Andric void SourceCoverageViewHTML::renderBranchView(raw_ostream &OS, BranchView &BRV, 913e8d8bef9SDimitry Andric unsigned ViewDepth) { 914e8d8bef9SDimitry Andric // Render the child subview. 915e8d8bef9SDimitry Andric if (getOptions().Debug) 916e8d8bef9SDimitry Andric errs() << "Branch at line " << BRV.getLine() << '\n'; 917e8d8bef9SDimitry Andric 918e8d8bef9SDimitry Andric OS << BeginExpansionDiv; 919e8d8bef9SDimitry Andric OS << BeginPre; 920e8d8bef9SDimitry Andric for (const auto &R : BRV.Regions) { 921e8d8bef9SDimitry Andric // Calculate TruePercent and False Percent. 922e8d8bef9SDimitry Andric double TruePercent = 0.0; 923e8d8bef9SDimitry Andric double FalsePercent = 0.0; 9245f757f3fSDimitry Andric // FIXME: It may overflow when the data is too large, but I have not 9255f757f3fSDimitry Andric // encountered it in actual use, and not sure whether to use __uint128_t. 9265f757f3fSDimitry Andric uint64_t Total = R.ExecutionCount + R.FalseExecutionCount; 927e8d8bef9SDimitry Andric 928e8d8bef9SDimitry Andric if (!getOptions().ShowBranchCounts && Total != 0) { 929e8d8bef9SDimitry Andric TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0; 930e8d8bef9SDimitry Andric FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0; 931e8d8bef9SDimitry Andric } 932e8d8bef9SDimitry Andric 933e8d8bef9SDimitry Andric // Display Line + Column. 934e8d8bef9SDimitry Andric std::string LineNoStr = utostr(uint64_t(R.LineStart)); 935e8d8bef9SDimitry Andric std::string ColNoStr = utostr(uint64_t(R.ColumnStart)); 936e8d8bef9SDimitry Andric std::string TargetName = "L" + LineNoStr; 937e8d8bef9SDimitry Andric 938e8d8bef9SDimitry Andric OS << " Branch ("; 939e8d8bef9SDimitry Andric OS << tag("span", 940e8d8bef9SDimitry Andric a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr), 941e8d8bef9SDimitry Andric TargetName), 942e8d8bef9SDimitry Andric "line-number") + 943e8d8bef9SDimitry Andric "): ["; 944e8d8bef9SDimitry Andric 945e8d8bef9SDimitry Andric if (R.Folded) { 946e8d8bef9SDimitry Andric OS << "Folded - Ignored]\n"; 947e8d8bef9SDimitry Andric continue; 948e8d8bef9SDimitry Andric } 949e8d8bef9SDimitry Andric 950e8d8bef9SDimitry Andric // Display TrueCount or TruePercent. 951e8d8bef9SDimitry Andric std::string TrueColor = R.ExecutionCount ? "None" : "red"; 952e8d8bef9SDimitry Andric std::string TrueCovClass = 953e8d8bef9SDimitry Andric (R.ExecutionCount > 0) ? "covered-line" : "uncovered-line"; 954e8d8bef9SDimitry Andric 955e8d8bef9SDimitry Andric OS << tag("span", "True", TrueColor); 956e8d8bef9SDimitry Andric OS << ": "; 957e8d8bef9SDimitry Andric if (getOptions().ShowBranchCounts) 958e8d8bef9SDimitry Andric OS << tag("span", formatCount(R.ExecutionCount), TrueCovClass) << ", "; 959e8d8bef9SDimitry Andric else 960e8d8bef9SDimitry Andric OS << format("%0.2f", TruePercent) << "%, "; 961e8d8bef9SDimitry Andric 962e8d8bef9SDimitry Andric // Display FalseCount or FalsePercent. 963e8d8bef9SDimitry Andric std::string FalseColor = R.FalseExecutionCount ? "None" : "red"; 964e8d8bef9SDimitry Andric std::string FalseCovClass = 965e8d8bef9SDimitry Andric (R.FalseExecutionCount > 0) ? "covered-line" : "uncovered-line"; 966e8d8bef9SDimitry Andric 967e8d8bef9SDimitry Andric OS << tag("span", "False", FalseColor); 968e8d8bef9SDimitry Andric OS << ": "; 969e8d8bef9SDimitry Andric if (getOptions().ShowBranchCounts) 970e8d8bef9SDimitry Andric OS << tag("span", formatCount(R.FalseExecutionCount), FalseCovClass); 971e8d8bef9SDimitry Andric else 972e8d8bef9SDimitry Andric OS << format("%0.2f", FalsePercent) << "%"; 973e8d8bef9SDimitry Andric 974e8d8bef9SDimitry Andric OS << "]\n"; 975e8d8bef9SDimitry Andric } 976e8d8bef9SDimitry Andric OS << EndPre; 977e8d8bef9SDimitry Andric OS << EndExpansionDiv; 978e8d8bef9SDimitry Andric } 979e8d8bef9SDimitry Andric 9805f757f3fSDimitry Andric void SourceCoverageViewHTML::renderMCDCView(raw_ostream &OS, MCDCView &MRV, 9815f757f3fSDimitry Andric unsigned ViewDepth) { 9825f757f3fSDimitry Andric for (auto &Record : MRV.Records) { 9835f757f3fSDimitry Andric OS << BeginExpansionDiv; 9845f757f3fSDimitry Andric OS << BeginPre; 9855f757f3fSDimitry Andric OS << " MC/DC Decision Region ("; 9865f757f3fSDimitry Andric 9875f757f3fSDimitry Andric // Display Line + Column information. 9885f757f3fSDimitry Andric const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion(); 9895f757f3fSDimitry Andric std::string LineNoStr = Twine(DecisionRegion.LineStart).str(); 9905f757f3fSDimitry Andric std::string ColNoStr = Twine(DecisionRegion.ColumnStart).str(); 9915f757f3fSDimitry Andric std::string TargetName = "L" + LineNoStr; 9925f757f3fSDimitry Andric OS << tag("span", 9935f757f3fSDimitry Andric a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr), 9945f757f3fSDimitry Andric TargetName), 9955f757f3fSDimitry Andric "line-number") + 9965f757f3fSDimitry Andric ") to ("; 9975f757f3fSDimitry Andric LineNoStr = utostr(uint64_t(DecisionRegion.LineEnd)); 9985f757f3fSDimitry Andric ColNoStr = utostr(uint64_t(DecisionRegion.ColumnEnd)); 9995f757f3fSDimitry Andric OS << tag("span", 10005f757f3fSDimitry Andric a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr), 10015f757f3fSDimitry Andric TargetName), 10025f757f3fSDimitry Andric "line-number") + 10035f757f3fSDimitry Andric ")\n\n"; 10045f757f3fSDimitry Andric 10055f757f3fSDimitry Andric // Display MC/DC Information. 10065f757f3fSDimitry Andric OS << " Number of Conditions: " << Record.getNumConditions() << "\n"; 10075f757f3fSDimitry Andric for (unsigned i = 0; i < Record.getNumConditions(); i++) { 10085f757f3fSDimitry Andric OS << " " << Record.getConditionHeaderString(i); 10095f757f3fSDimitry Andric } 10105f757f3fSDimitry Andric OS << "\n"; 10115f757f3fSDimitry Andric OS << " Executed MC/DC Test Vectors:\n\n "; 10125f757f3fSDimitry Andric OS << Record.getTestVectorHeaderString(); 10135f757f3fSDimitry Andric for (unsigned i = 0; i < Record.getNumTestVectors(); i++) 10145f757f3fSDimitry Andric OS << Record.getTestVectorString(i); 10155f757f3fSDimitry Andric OS << "\n"; 10165f757f3fSDimitry Andric for (unsigned i = 0; i < Record.getNumConditions(); i++) 10175f757f3fSDimitry Andric OS << Record.getConditionCoverageString(i); 10185f757f3fSDimitry Andric OS << " MC/DC Coverage for Expression: "; 10195f757f3fSDimitry Andric OS << format("%0.2f", Record.getPercentCovered()) << "%\n"; 10205f757f3fSDimitry Andric OS << EndPre; 10215f757f3fSDimitry Andric OS << EndExpansionDiv; 10225f757f3fSDimitry Andric } 10235f757f3fSDimitry Andric return; 10245f757f3fSDimitry Andric } 10255f757f3fSDimitry Andric 10260b57cec5SDimitry Andric void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS, 10270b57cec5SDimitry Andric InstantiationView &ISV, 10280b57cec5SDimitry Andric unsigned ViewDepth) { 10290b57cec5SDimitry Andric OS << BeginExpansionDiv; 10300b57cec5SDimitry Andric if (!ISV.View) 10310b57cec5SDimitry Andric OS << BeginSourceNameDiv 10320b57cec5SDimitry Andric << tag("pre", 10330b57cec5SDimitry Andric escape("Unexecuted instantiation: " + ISV.FunctionName.str(), 10340b57cec5SDimitry Andric getOptions())) 10350b57cec5SDimitry Andric << EndSourceNameDiv; 10360b57cec5SDimitry Andric else 10370b57cec5SDimitry Andric ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, 10380b57cec5SDimitry Andric /*ShowTitle=*/false, ViewDepth); 10390b57cec5SDimitry Andric OS << EndExpansionDiv; 10400b57cec5SDimitry Andric } 10410b57cec5SDimitry Andric 10420b57cec5SDimitry Andric void SourceCoverageViewHTML::renderTitle(raw_ostream &OS, StringRef Title) { 10430b57cec5SDimitry Andric if (getOptions().hasProjectTitle()) 10440b57cec5SDimitry Andric OS << tag(ProjectTitleTag, escape(getOptions().ProjectTitle, getOptions())); 10450b57cec5SDimitry Andric OS << tag(ReportTitleTag, escape(Title, getOptions())); 10460b57cec5SDimitry Andric if (getOptions().hasCreatedTime()) 10470b57cec5SDimitry Andric OS << tag(CreatedTimeTag, 10480b57cec5SDimitry Andric escape(getOptions().CreatedTimeStr, getOptions())); 10490b57cec5SDimitry Andric } 10500b57cec5SDimitry Andric 10510b57cec5SDimitry Andric void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS, 10520b57cec5SDimitry Andric unsigned FirstUncoveredLineNo, 10530b57cec5SDimitry Andric unsigned ViewDepth) { 10540b57cec5SDimitry Andric std::string SourceLabel; 10550b57cec5SDimitry Andric if (FirstUncoveredLineNo == 0) { 10560b57cec5SDimitry Andric SourceLabel = tag("td", tag("pre", "Source")); 10570b57cec5SDimitry Andric } else { 10580b57cec5SDimitry Andric std::string LinkTarget = "#L" + utostr(uint64_t(FirstUncoveredLineNo)); 10590b57cec5SDimitry Andric SourceLabel = 10600b57cec5SDimitry Andric tag("td", tag("pre", "Source (" + 10610b57cec5SDimitry Andric a(LinkTarget, "jump to first uncovered line") + 10620b57cec5SDimitry Andric ")")); 10630b57cec5SDimitry Andric } 10640b57cec5SDimitry Andric 10650b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth); 10660b57cec5SDimitry Andric OS << tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count")) 10670b57cec5SDimitry Andric << SourceLabel; 10680b57cec5SDimitry Andric renderLineSuffix(OS, ViewDepth); 10690b57cec5SDimitry Andric } 1070