101095a5dSDimitry Andric //===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
201095a5dSDimitry Andric //
3e6d15924SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e6d15924SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5e6d15924SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
601095a5dSDimitry Andric //
701095a5dSDimitry Andric //===----------------------------------------------------------------------===//
801095a5dSDimitry Andric ///
901095a5dSDimitry Andric /// \file This file implements the html coverage renderer.
1001095a5dSDimitry Andric ///
1101095a5dSDimitry Andric //===----------------------------------------------------------------------===//
1201095a5dSDimitry Andric
1301095a5dSDimitry Andric #include "SourceCoverageViewHTML.h"
14b1c73532SDimitry Andric #include "CoverageReport.h"
1501095a5dSDimitry Andric #include "llvm/ADT/SmallString.h"
1601095a5dSDimitry Andric #include "llvm/ADT/StringExtras.h"
17b915e9e0SDimitry Andric #include "llvm/Support/Format.h"
1801095a5dSDimitry Andric #include "llvm/Support/Path.h"
19b1c73532SDimitry Andric #include "llvm/Support/ThreadPool.h"
20e3b55780SDimitry Andric #include <optional>
2101095a5dSDimitry Andric
2201095a5dSDimitry Andric using namespace llvm;
2301095a5dSDimitry Andric
2401095a5dSDimitry Andric namespace {
2501095a5dSDimitry Andric
26b915e9e0SDimitry Andric // Return a string with the special characters in \p Str escaped.
escape(StringRef Str,const CoverageViewOptions & Opts)27b915e9e0SDimitry Andric std::string escape(StringRef Str, const CoverageViewOptions &Opts) {
28eb11fae6SDimitry Andric std::string TabExpandedResult;
29b915e9e0SDimitry Andric unsigned ColNum = 0; // Record the column number.
30b915e9e0SDimitry Andric for (char C : Str) {
31eb11fae6SDimitry Andric if (C == '\t') {
32eb11fae6SDimitry Andric // Replace '\t' with up to TabSize spaces.
33eb11fae6SDimitry Andric unsigned NumSpaces = Opts.TabSize - (ColNum % Opts.TabSize);
34cfca06d7SDimitry Andric TabExpandedResult.append(NumSpaces, ' ');
35b915e9e0SDimitry Andric ColNum += NumSpaces;
36eb11fae6SDimitry Andric } else {
37eb11fae6SDimitry Andric TabExpandedResult += C;
38eb11fae6SDimitry Andric if (C == '\n' || C == '\r')
39eb11fae6SDimitry Andric ColNum = 0;
40eb11fae6SDimitry Andric else
41eb11fae6SDimitry Andric ++ColNum;
42b915e9e0SDimitry Andric }
43eb11fae6SDimitry Andric }
44eb11fae6SDimitry Andric std::string EscapedHTML;
45eb11fae6SDimitry Andric {
46eb11fae6SDimitry Andric raw_string_ostream OS{EscapedHTML};
47eb11fae6SDimitry Andric printHTMLEscaped(TabExpandedResult, OS);
48eb11fae6SDimitry Andric }
49eb11fae6SDimitry Andric return EscapedHTML;
50b915e9e0SDimitry Andric }
51b915e9e0SDimitry Andric
52b915e9e0SDimitry Andric // Create a \p Name tag around \p Str, and optionally set its \p ClassName.
tag(StringRef Name,StringRef Str,StringRef ClassName="")53b1c73532SDimitry Andric std::string tag(StringRef Name, StringRef Str, StringRef ClassName = "") {
54b1c73532SDimitry Andric std::string Tag = "<";
55b1c73532SDimitry Andric Tag += Name;
56b1c73532SDimitry Andric if (!ClassName.empty()) {
57b1c73532SDimitry Andric Tag += " class='";
58b1c73532SDimitry Andric Tag += ClassName;
59b1c73532SDimitry Andric Tag += "'";
60b1c73532SDimitry Andric }
61b1c73532SDimitry Andric Tag += ">";
62b1c73532SDimitry Andric Tag += Str;
63b1c73532SDimitry Andric Tag += "</";
64b1c73532SDimitry Andric Tag += Name;
65b1c73532SDimitry Andric Tag += ">";
66b1c73532SDimitry Andric return Tag;
67b915e9e0SDimitry Andric }
68b915e9e0SDimitry Andric
69b915e9e0SDimitry Andric // Create an anchor to \p Link with the label \p Str.
a(StringRef Link,StringRef Str,StringRef TargetName="")70b1c73532SDimitry Andric std::string a(StringRef Link, StringRef Str, StringRef TargetName = "") {
71b1c73532SDimitry Andric std::string Tag;
72b1c73532SDimitry Andric Tag += "<a ";
73b1c73532SDimitry Andric if (!TargetName.empty()) {
74b1c73532SDimitry Andric Tag += "name='";
75b1c73532SDimitry Andric Tag += TargetName;
76b1c73532SDimitry Andric Tag += "' ";
77b1c73532SDimitry Andric }
78b1c73532SDimitry Andric Tag += "href='";
79b1c73532SDimitry Andric Tag += Link;
80b1c73532SDimitry Andric Tag += "'>";
81b1c73532SDimitry Andric Tag += Str;
82b1c73532SDimitry Andric Tag += "</a>";
83b1c73532SDimitry Andric return Tag;
84b915e9e0SDimitry Andric }
85b915e9e0SDimitry Andric
8601095a5dSDimitry Andric const char *BeginHeader =
8701095a5dSDimitry Andric "<head>"
8801095a5dSDimitry Andric "<meta name='viewport' content='width=device-width,initial-scale=1'>"
8901095a5dSDimitry Andric "<meta charset='UTF-8'>";
9001095a5dSDimitry Andric
91ac9a064cSDimitry Andric const char *JSForCoverage =
92ac9a064cSDimitry Andric R"javascript(
93ac9a064cSDimitry Andric
94ac9a064cSDimitry Andric function next_uncovered(selector, reverse, scroll_selector) {
95ac9a064cSDimitry Andric function visit_element(element) {
96ac9a064cSDimitry Andric element.classList.add("seen");
97ac9a064cSDimitry Andric element.classList.add("selected");
98ac9a064cSDimitry Andric
99ac9a064cSDimitry Andric if (!scroll_selector) {
100ac9a064cSDimitry Andric scroll_selector = "tr:has(.selected) td.line-number"
101ac9a064cSDimitry Andric }
102ac9a064cSDimitry Andric
103ac9a064cSDimitry Andric const scroll_to = document.querySelector(scroll_selector);
104ac9a064cSDimitry Andric if (scroll_to) {
105ac9a064cSDimitry Andric scroll_to.scrollIntoView({behavior: "smooth", block: "center", inline: "end"});
106ac9a064cSDimitry Andric }
107ac9a064cSDimitry Andric
108ac9a064cSDimitry Andric }
109ac9a064cSDimitry Andric
110ac9a064cSDimitry Andric function select_one() {
111ac9a064cSDimitry Andric if (!reverse) {
112ac9a064cSDimitry Andric const previously_selected = document.querySelector(".selected");
113ac9a064cSDimitry Andric
114ac9a064cSDimitry Andric if (previously_selected) {
115ac9a064cSDimitry Andric previously_selected.classList.remove("selected");
116ac9a064cSDimitry Andric }
117ac9a064cSDimitry Andric
118ac9a064cSDimitry Andric return document.querySelector(selector + ":not(.seen)");
119ac9a064cSDimitry Andric } else {
120ac9a064cSDimitry Andric const previously_selected = document.querySelector(".selected");
121ac9a064cSDimitry Andric
122ac9a064cSDimitry Andric if (previously_selected) {
123ac9a064cSDimitry Andric previously_selected.classList.remove("selected");
124ac9a064cSDimitry Andric previously_selected.classList.remove("seen");
125ac9a064cSDimitry Andric }
126ac9a064cSDimitry Andric
127ac9a064cSDimitry Andric const nodes = document.querySelectorAll(selector + ".seen");
128ac9a064cSDimitry Andric if (nodes) {
129ac9a064cSDimitry Andric const last = nodes[nodes.length - 1]; // last
130ac9a064cSDimitry Andric return last;
131ac9a064cSDimitry Andric } else {
132ac9a064cSDimitry Andric return undefined;
133ac9a064cSDimitry Andric }
134ac9a064cSDimitry Andric }
135ac9a064cSDimitry Andric }
136ac9a064cSDimitry Andric
137ac9a064cSDimitry Andric function reset_all() {
138ac9a064cSDimitry Andric if (!reverse) {
139ac9a064cSDimitry Andric const all_seen = document.querySelectorAll(selector + ".seen");
140ac9a064cSDimitry Andric
141ac9a064cSDimitry Andric if (all_seen) {
142ac9a064cSDimitry Andric all_seen.forEach(e => e.classList.remove("seen"));
143ac9a064cSDimitry Andric }
144ac9a064cSDimitry Andric } else {
145ac9a064cSDimitry Andric const all_seen = document.querySelectorAll(selector + ":not(.seen)");
146ac9a064cSDimitry Andric
147ac9a064cSDimitry Andric if (all_seen) {
148ac9a064cSDimitry Andric all_seen.forEach(e => e.classList.add("seen"));
149ac9a064cSDimitry Andric }
150ac9a064cSDimitry Andric }
151ac9a064cSDimitry Andric
152ac9a064cSDimitry Andric }
153ac9a064cSDimitry Andric
154ac9a064cSDimitry Andric const uncovered = select_one();
155ac9a064cSDimitry Andric
156ac9a064cSDimitry Andric if (uncovered) {
157ac9a064cSDimitry Andric visit_element(uncovered);
158ac9a064cSDimitry Andric } else {
159ac9a064cSDimitry Andric reset_all();
160ac9a064cSDimitry Andric
161ac9a064cSDimitry Andric
162ac9a064cSDimitry Andric const uncovered = select_one();
163ac9a064cSDimitry Andric
164ac9a064cSDimitry Andric if (uncovered) {
165ac9a064cSDimitry Andric visit_element(uncovered);
166ac9a064cSDimitry Andric }
167ac9a064cSDimitry Andric }
168ac9a064cSDimitry Andric }
169ac9a064cSDimitry Andric
170ac9a064cSDimitry Andric function next_line(reverse) {
171ac9a064cSDimitry Andric next_uncovered("td.uncovered-line", reverse)
172ac9a064cSDimitry Andric }
173ac9a064cSDimitry Andric
174ac9a064cSDimitry Andric function next_region(reverse) {
175ac9a064cSDimitry Andric next_uncovered("span.red.region", reverse);
176ac9a064cSDimitry Andric }
177ac9a064cSDimitry Andric
178ac9a064cSDimitry Andric function next_branch(reverse) {
179ac9a064cSDimitry Andric next_uncovered("span.red.branch", reverse);
180ac9a064cSDimitry Andric }
181ac9a064cSDimitry Andric
182ac9a064cSDimitry Andric document.addEventListener("keypress", function(event) {
183ac9a064cSDimitry Andric console.log(event);
184ac9a064cSDimitry Andric const reverse = event.shiftKey;
185ac9a064cSDimitry Andric if (event.code == "KeyL") {
186ac9a064cSDimitry Andric next_line(reverse);
187ac9a064cSDimitry Andric }
188ac9a064cSDimitry Andric if (event.code == "KeyB") {
189ac9a064cSDimitry Andric next_branch(reverse);
190ac9a064cSDimitry Andric }
191ac9a064cSDimitry Andric if (event.code == "KeyR") {
192ac9a064cSDimitry Andric next_region(reverse);
193ac9a064cSDimitry Andric }
194ac9a064cSDimitry Andric
195ac9a064cSDimitry Andric });
196ac9a064cSDimitry Andric )javascript";
197ac9a064cSDimitry Andric
19801095a5dSDimitry Andric const char *CSSForCoverage =
199b915e9e0SDimitry Andric R"(.red {
200ac9a064cSDimitry Andric background-color: #f004;
20101095a5dSDimitry Andric }
20201095a5dSDimitry Andric .cyan {
20301095a5dSDimitry Andric background-color: cyan;
20401095a5dSDimitry Andric }
205ac9a064cSDimitry Andric html {
206ac9a064cSDimitry Andric scroll-behavior: smooth;
207ac9a064cSDimitry Andric }
20801095a5dSDimitry Andric body {
20901095a5dSDimitry Andric font-family: -apple-system, sans-serif;
21001095a5dSDimitry Andric }
21101095a5dSDimitry Andric pre {
21201095a5dSDimitry Andric margin-top: 0px !important;
21301095a5dSDimitry Andric margin-bottom: 0px !important;
21401095a5dSDimitry Andric }
21501095a5dSDimitry Andric .source-name-title {
21601095a5dSDimitry Andric padding: 5px 10px;
217ac9a064cSDimitry Andric border-bottom: 1px solid #8888;
218ac9a064cSDimitry Andric background-color: #0002;
219b915e9e0SDimitry Andric line-height: 35px;
22001095a5dSDimitry Andric }
22101095a5dSDimitry Andric .centered {
22201095a5dSDimitry Andric display: table;
223b915e9e0SDimitry Andric margin-left: left;
22401095a5dSDimitry Andric margin-right: auto;
225ac9a064cSDimitry Andric border: 1px solid #8888;
22601095a5dSDimitry Andric border-radius: 3px;
22701095a5dSDimitry Andric }
22801095a5dSDimitry Andric .expansion-view {
22901095a5dSDimitry Andric margin-left: 0px;
23001095a5dSDimitry Andric margin-top: 5px;
23101095a5dSDimitry Andric margin-right: 5px;
23201095a5dSDimitry Andric margin-bottom: 5px;
233ac9a064cSDimitry Andric border: 1px solid #8888;
23401095a5dSDimitry Andric border-radius: 3px;
23501095a5dSDimitry Andric }
23601095a5dSDimitry Andric table {
23701095a5dSDimitry Andric border-collapse: collapse;
23801095a5dSDimitry Andric }
239b915e9e0SDimitry Andric .light-row {
240ac9a064cSDimitry Andric border: 1px solid #8888;
24199aabd70SDimitry Andric border-left: none;
24299aabd70SDimitry Andric border-right: none;
243b915e9e0SDimitry Andric }
244eb11fae6SDimitry Andric .light-row-bold {
245ac9a064cSDimitry Andric border: 1px solid #8888;
24699aabd70SDimitry Andric border-left: none;
24799aabd70SDimitry Andric border-right: none;
248eb11fae6SDimitry Andric font-weight: bold;
249b915e9e0SDimitry Andric }
250eb11fae6SDimitry Andric .column-entry {
251eb11fae6SDimitry Andric text-align: left;
252eb11fae6SDimitry Andric }
253eb11fae6SDimitry Andric .column-entry-bold {
254eb11fae6SDimitry Andric font-weight: bold;
255b915e9e0SDimitry Andric text-align: left;
256b915e9e0SDimitry Andric }
257b915e9e0SDimitry Andric .column-entry-yellow {
258eb11fae6SDimitry Andric text-align: left;
259ac9a064cSDimitry Andric background-color: #ff06;
260eb11fae6SDimitry Andric }
261b915e9e0SDimitry Andric .column-entry-red {
262eb11fae6SDimitry Andric text-align: left;
263ac9a064cSDimitry Andric background-color: #f004;
264eb11fae6SDimitry Andric }
26599aabd70SDimitry Andric .column-entry-gray {
26699aabd70SDimitry Andric text-align: left;
267ac9a064cSDimitry Andric background-color: #fff4;
26899aabd70SDimitry Andric }
269b915e9e0SDimitry Andric .column-entry-green {
270eb11fae6SDimitry Andric text-align: left;
271ac9a064cSDimitry Andric background-color: #0f04;
272eb11fae6SDimitry Andric }
27301095a5dSDimitry Andric .line-number {
27401095a5dSDimitry Andric text-align: right;
27501095a5dSDimitry Andric }
27601095a5dSDimitry Andric .covered-line {
27701095a5dSDimitry Andric text-align: right;
278ac9a064cSDimitry Andric color: #06d;
27901095a5dSDimitry Andric }
28001095a5dSDimitry Andric .uncovered-line {
28101095a5dSDimitry Andric text-align: right;
282ac9a064cSDimitry Andric color: #d00;
283ac9a064cSDimitry Andric }
284ac9a064cSDimitry Andric .uncovered-line.selected {
285ac9a064cSDimitry Andric color: #f00;
286ac9a064cSDimitry Andric font-weight: bold;
287ac9a064cSDimitry Andric }
288ac9a064cSDimitry Andric .region.red.selected {
289ac9a064cSDimitry Andric background-color: #f008;
290ac9a064cSDimitry Andric font-weight: bold;
291ac9a064cSDimitry Andric }
292ac9a064cSDimitry Andric .branch.red.selected {
293ac9a064cSDimitry Andric background-color: #f008;
294ac9a064cSDimitry Andric font-weight: bold;
29501095a5dSDimitry Andric }
29601095a5dSDimitry Andric .tooltip {
29701095a5dSDimitry Andric position: relative;
29801095a5dSDimitry Andric display: inline;
299ac9a064cSDimitry Andric background-color: #bef;
30001095a5dSDimitry Andric text-decoration: none;
30101095a5dSDimitry Andric }
30201095a5dSDimitry Andric .tooltip span.tooltip-content {
30301095a5dSDimitry Andric position: absolute;
30401095a5dSDimitry Andric width: 100px;
30501095a5dSDimitry Andric margin-left: -50px;
30601095a5dSDimitry Andric color: #FFFFFF;
30701095a5dSDimitry Andric background: #000000;
30801095a5dSDimitry Andric height: 30px;
30901095a5dSDimitry Andric line-height: 30px;
31001095a5dSDimitry Andric text-align: center;
31101095a5dSDimitry Andric visibility: hidden;
31201095a5dSDimitry Andric border-radius: 6px;
31301095a5dSDimitry Andric }
31401095a5dSDimitry Andric .tooltip span.tooltip-content:after {
31501095a5dSDimitry Andric content: '';
31601095a5dSDimitry Andric position: absolute;
31701095a5dSDimitry Andric top: 100%;
31801095a5dSDimitry Andric left: 50%;
31901095a5dSDimitry Andric margin-left: -8px;
32001095a5dSDimitry Andric width: 0; height: 0;
32101095a5dSDimitry Andric border-top: 8px solid #000000;
32201095a5dSDimitry Andric border-right: 8px solid transparent;
32301095a5dSDimitry Andric border-left: 8px solid transparent;
32401095a5dSDimitry Andric }
32501095a5dSDimitry Andric :hover.tooltip span.tooltip-content {
32601095a5dSDimitry Andric visibility: visible;
32701095a5dSDimitry Andric opacity: 0.8;
32801095a5dSDimitry Andric bottom: 30px;
32901095a5dSDimitry Andric left: 50%;
33001095a5dSDimitry Andric z-index: 999;
33101095a5dSDimitry Andric }
33201095a5dSDimitry Andric th, td {
33301095a5dSDimitry Andric vertical-align: top;
334eb11fae6SDimitry Andric padding: 2px 8px;
33501095a5dSDimitry Andric border-collapse: collapse;
336ac9a064cSDimitry Andric border-right: 1px solid #8888;
337ac9a064cSDimitry Andric border-left: 1px solid #8888;
338eb11fae6SDimitry Andric text-align: left;
339eb11fae6SDimitry Andric }
340eb11fae6SDimitry Andric td pre {
341eb11fae6SDimitry Andric display: inline-block;
342ac9a064cSDimitry Andric text-decoration: inherit;
34301095a5dSDimitry Andric }
34401095a5dSDimitry Andric td:first-child {
34501095a5dSDimitry Andric border-left: none;
34601095a5dSDimitry Andric }
34701095a5dSDimitry Andric td:last-child {
34801095a5dSDimitry Andric border-right: none;
34901095a5dSDimitry Andric }
350eb11fae6SDimitry Andric tr:hover {
351ac9a064cSDimitry Andric background-color: #eee;
352eb11fae6SDimitry Andric }
35399aabd70SDimitry Andric tr:last-child {
35499aabd70SDimitry Andric border-bottom: none;
35599aabd70SDimitry Andric }
356ac9a064cSDimitry Andric tr:has(> td >a:target), tr:has(> td.uncovered-line.selected) {
357ac9a064cSDimitry Andric background-color: #8884;
358ac9a064cSDimitry Andric }
359ac9a064cSDimitry Andric a {
360ac9a064cSDimitry Andric color: inherit;
361ac9a064cSDimitry Andric }
362ac9a064cSDimitry Andric .control {
363ac9a064cSDimitry Andric position: fixed;
364ac9a064cSDimitry Andric top: 0em;
365ac9a064cSDimitry Andric right: 0em;
366ac9a064cSDimitry Andric padding: 1em;
367ac9a064cSDimitry Andric background: #FFF8;
368ac9a064cSDimitry Andric }
369ac9a064cSDimitry Andric @media (prefers-color-scheme: dark) {
370ac9a064cSDimitry Andric body {
371ac9a064cSDimitry Andric background-color: #222;
372ac9a064cSDimitry Andric color: whitesmoke;
373ac9a064cSDimitry Andric }
374ac9a064cSDimitry Andric tr:hover {
375ac9a064cSDimitry Andric background-color: #111;
376ac9a064cSDimitry Andric }
377ac9a064cSDimitry Andric .covered-line {
378ac9a064cSDimitry Andric color: #39f;
379ac9a064cSDimitry Andric }
380ac9a064cSDimitry Andric .uncovered-line {
381ac9a064cSDimitry Andric color: #f55;
382ac9a064cSDimitry Andric }
383ac9a064cSDimitry Andric .tooltip {
384ac9a064cSDimitry Andric background-color: #068;
385ac9a064cSDimitry Andric }
386ac9a064cSDimitry Andric .control {
387ac9a064cSDimitry Andric background: #2228;
388ac9a064cSDimitry Andric }
389ac9a064cSDimitry Andric tr:has(> td >a:target), tr:has(> td.uncovered-line.selected) {
390ac9a064cSDimitry Andric background-color: #8884;
391ac9a064cSDimitry Andric }
392ac9a064cSDimitry Andric }
393b915e9e0SDimitry Andric )";
39401095a5dSDimitry Andric
39501095a5dSDimitry Andric const char *EndHeader = "</head>";
39601095a5dSDimitry Andric
39701095a5dSDimitry Andric const char *BeginCenteredDiv = "<div class='centered'>";
39801095a5dSDimitry Andric
39901095a5dSDimitry Andric const char *EndCenteredDiv = "</div>";
40001095a5dSDimitry Andric
40101095a5dSDimitry Andric const char *BeginSourceNameDiv = "<div class='source-name-title'>";
40201095a5dSDimitry Andric
40301095a5dSDimitry Andric const char *EndSourceNameDiv = "</div>";
40401095a5dSDimitry Andric
40501095a5dSDimitry Andric const char *BeginCodeTD = "<td class='code'>";
40601095a5dSDimitry Andric
40701095a5dSDimitry Andric const char *EndCodeTD = "</td>";
40801095a5dSDimitry Andric
40901095a5dSDimitry Andric const char *BeginPre = "<pre>";
41001095a5dSDimitry Andric
41101095a5dSDimitry Andric const char *EndPre = "</pre>";
41201095a5dSDimitry Andric
41301095a5dSDimitry Andric const char *BeginExpansionDiv = "<div class='expansion-view'>";
41401095a5dSDimitry Andric
41501095a5dSDimitry Andric const char *EndExpansionDiv = "</div>";
41601095a5dSDimitry Andric
41701095a5dSDimitry Andric const char *BeginTable = "<table>";
41801095a5dSDimitry Andric
41901095a5dSDimitry Andric const char *EndTable = "</table>";
42001095a5dSDimitry Andric
421b915e9e0SDimitry Andric const char *ProjectTitleTag = "h1";
422b915e9e0SDimitry Andric
423b915e9e0SDimitry Andric const char *ReportTitleTag = "h2";
424b915e9e0SDimitry Andric
425b915e9e0SDimitry Andric const char *CreatedTimeTag = "h4";
426b915e9e0SDimitry Andric
getPathToStyle(StringRef ViewPath)427b915e9e0SDimitry Andric std::string getPathToStyle(StringRef ViewPath) {
428b60736ecSDimitry Andric std::string PathToStyle;
429cfca06d7SDimitry Andric std::string PathSep = std::string(sys::path::get_separator());
430b915e9e0SDimitry Andric unsigned NumSeps = ViewPath.count(PathSep);
431b915e9e0SDimitry Andric for (unsigned I = 0, E = NumSeps; I < E; ++I)
432b915e9e0SDimitry Andric PathToStyle += ".." + PathSep;
433b915e9e0SDimitry Andric return PathToStyle + "style.css";
434b915e9e0SDimitry Andric }
435b915e9e0SDimitry Andric
getPathToJavaScript(StringRef ViewPath)436ac9a064cSDimitry Andric std::string getPathToJavaScript(StringRef ViewPath) {
437ac9a064cSDimitry Andric std::string PathToJavaScript;
438ac9a064cSDimitry Andric std::string PathSep = std::string(sys::path::get_separator());
439ac9a064cSDimitry Andric unsigned NumSeps = ViewPath.count(PathSep);
440ac9a064cSDimitry Andric for (unsigned I = 0, E = NumSeps; I < E; ++I)
441ac9a064cSDimitry Andric PathToJavaScript += ".." + PathSep;
442ac9a064cSDimitry Andric return PathToJavaScript + "control.js";
443ac9a064cSDimitry Andric }
444ac9a064cSDimitry Andric
emitPrelude(raw_ostream & OS,const CoverageViewOptions & Opts,const std::string & PathToStyle="",const std::string & PathToJavaScript="")445b915e9e0SDimitry Andric void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
446ac9a064cSDimitry Andric const std::string &PathToStyle = "",
447ac9a064cSDimitry Andric const std::string &PathToJavaScript = "") {
44801095a5dSDimitry Andric OS << "<!doctype html>"
44901095a5dSDimitry Andric "<html>"
450b915e9e0SDimitry Andric << BeginHeader;
451b915e9e0SDimitry Andric
452b915e9e0SDimitry Andric // Link to a stylesheet if one is available. Otherwise, use the default style.
453b915e9e0SDimitry Andric if (PathToStyle.empty())
454b915e9e0SDimitry Andric OS << "<style>" << CSSForCoverage << "</style>";
455b915e9e0SDimitry Andric else
456b915e9e0SDimitry Andric OS << "<link rel='stylesheet' type='text/css' href='"
457b915e9e0SDimitry Andric << escape(PathToStyle, Opts) << "'>";
458b915e9e0SDimitry Andric
459ac9a064cSDimitry Andric // Link to a JavaScript if one is available
460ac9a064cSDimitry Andric if (PathToJavaScript.empty())
461ac9a064cSDimitry Andric OS << "<script>" << JSForCoverage << "</script>";
462ac9a064cSDimitry Andric else
463ac9a064cSDimitry Andric OS << "<script src='" << escape(PathToJavaScript, Opts) << "'></script>";
464ac9a064cSDimitry Andric
465b915e9e0SDimitry Andric OS << EndHeader << "<body>";
46601095a5dSDimitry Andric }
46701095a5dSDimitry Andric
emitTableRow(raw_ostream & OS,const CoverageViewOptions & Opts,const std::string & FirstCol,const FileCoverageSummary & FCS,bool IsTotals)468b1c73532SDimitry Andric void emitTableRow(raw_ostream &OS, const CoverageViewOptions &Opts,
469b1c73532SDimitry Andric const std::string &FirstCol, const FileCoverageSummary &FCS,
470b1c73532SDimitry Andric bool IsTotals) {
471b1c73532SDimitry Andric SmallVector<std::string, 8> Columns;
472b1c73532SDimitry Andric
473b1c73532SDimitry Andric // Format a coverage triple and add the result to the list of columns.
474b1c73532SDimitry Andric auto AddCoverageTripleToColumn =
475b1c73532SDimitry Andric [&Columns, &Opts](unsigned Hit, unsigned Total, float Pctg) {
476b1c73532SDimitry Andric std::string S;
477b1c73532SDimitry Andric {
478b1c73532SDimitry Andric raw_string_ostream RSO{S};
479b1c73532SDimitry Andric if (Total)
480b1c73532SDimitry Andric RSO << format("%*.2f", 7, Pctg) << "% ";
481b1c73532SDimitry Andric else
482b1c73532SDimitry Andric RSO << "- ";
483b1c73532SDimitry Andric RSO << '(' << Hit << '/' << Total << ')';
484b1c73532SDimitry Andric }
485b1c73532SDimitry Andric const char *CellClass = "column-entry-yellow";
48699aabd70SDimitry Andric if (!Total)
48799aabd70SDimitry Andric CellClass = "column-entry-gray";
48899aabd70SDimitry Andric else if (Pctg >= Opts.HighCovWatermark)
489b1c73532SDimitry Andric CellClass = "column-entry-green";
490b1c73532SDimitry Andric else if (Pctg < Opts.LowCovWatermark)
491b1c73532SDimitry Andric CellClass = "column-entry-red";
492b1c73532SDimitry Andric Columns.emplace_back(tag("td", tag("pre", S), CellClass));
493b1c73532SDimitry Andric };
494b1c73532SDimitry Andric
495b1c73532SDimitry Andric Columns.emplace_back(tag("td", tag("pre", FirstCol)));
496b1c73532SDimitry Andric AddCoverageTripleToColumn(FCS.FunctionCoverage.getExecuted(),
497b1c73532SDimitry Andric FCS.FunctionCoverage.getNumFunctions(),
498b1c73532SDimitry Andric FCS.FunctionCoverage.getPercentCovered());
499b1c73532SDimitry Andric if (Opts.ShowInstantiationSummary)
500b1c73532SDimitry Andric AddCoverageTripleToColumn(FCS.InstantiationCoverage.getExecuted(),
501b1c73532SDimitry Andric FCS.InstantiationCoverage.getNumFunctions(),
502b1c73532SDimitry Andric FCS.InstantiationCoverage.getPercentCovered());
503b1c73532SDimitry Andric AddCoverageTripleToColumn(FCS.LineCoverage.getCovered(),
504b1c73532SDimitry Andric FCS.LineCoverage.getNumLines(),
505b1c73532SDimitry Andric FCS.LineCoverage.getPercentCovered());
506b1c73532SDimitry Andric if (Opts.ShowRegionSummary)
507b1c73532SDimitry Andric AddCoverageTripleToColumn(FCS.RegionCoverage.getCovered(),
508b1c73532SDimitry Andric FCS.RegionCoverage.getNumRegions(),
509b1c73532SDimitry Andric FCS.RegionCoverage.getPercentCovered());
510b1c73532SDimitry Andric if (Opts.ShowBranchSummary)
511b1c73532SDimitry Andric AddCoverageTripleToColumn(FCS.BranchCoverage.getCovered(),
512b1c73532SDimitry Andric FCS.BranchCoverage.getNumBranches(),
513b1c73532SDimitry Andric FCS.BranchCoverage.getPercentCovered());
514312c0ed1SDimitry Andric if (Opts.ShowMCDCSummary)
515312c0ed1SDimitry Andric AddCoverageTripleToColumn(FCS.MCDCCoverage.getCoveredPairs(),
516312c0ed1SDimitry Andric FCS.MCDCCoverage.getNumPairs(),
517312c0ed1SDimitry Andric FCS.MCDCCoverage.getPercentCovered());
518b1c73532SDimitry Andric
519b1c73532SDimitry Andric if (IsTotals)
520b1c73532SDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold");
521b1c73532SDimitry Andric else
522b1c73532SDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row");
523b1c73532SDimitry Andric }
524b1c73532SDimitry Andric
emitEpilog(raw_ostream & OS)52501095a5dSDimitry Andric void emitEpilog(raw_ostream &OS) {
526b915e9e0SDimitry Andric OS << "</body>"
527b915e9e0SDimitry Andric << "</html>";
52801095a5dSDimitry Andric }
52901095a5dSDimitry Andric
53001095a5dSDimitry Andric } // anonymous namespace
53101095a5dSDimitry Andric
53201095a5dSDimitry Andric Expected<CoveragePrinter::OwnedStream>
createViewFile(StringRef Path,bool InToplevel)53301095a5dSDimitry Andric CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
53401095a5dSDimitry Andric auto OSOrErr = createOutputStream(Path, "html", InToplevel);
53501095a5dSDimitry Andric if (!OSOrErr)
53601095a5dSDimitry Andric return OSOrErr;
53701095a5dSDimitry Andric
53801095a5dSDimitry Andric OwnedStream OS = std::move(OSOrErr.get());
539b915e9e0SDimitry Andric
540b915e9e0SDimitry Andric if (!Opts.hasOutputDirectory()) {
541b915e9e0SDimitry Andric emitPrelude(*OS.get(), Opts);
542b915e9e0SDimitry Andric } else {
543b915e9e0SDimitry Andric std::string ViewPath = getOutputPath(Path, "html", InToplevel);
544ac9a064cSDimitry Andric emitPrelude(*OS.get(), Opts, getPathToStyle(ViewPath),
545ac9a064cSDimitry Andric getPathToJavaScript(ViewPath));
546b915e9e0SDimitry Andric }
547b915e9e0SDimitry Andric
54801095a5dSDimitry Andric return std::move(OS);
54901095a5dSDimitry Andric }
55001095a5dSDimitry Andric
closeViewFile(OwnedStream OS)55101095a5dSDimitry Andric void CoveragePrinterHTML::closeViewFile(OwnedStream OS) {
55201095a5dSDimitry Andric emitEpilog(*OS.get());
55301095a5dSDimitry Andric }
55401095a5dSDimitry Andric
555b915e9e0SDimitry Andric /// Emit column labels for the table in the index.
emitColumnLabelsForIndex(raw_ostream & OS,const CoverageViewOptions & Opts)556044eb2f6SDimitry Andric static void emitColumnLabelsForIndex(raw_ostream &OS,
557044eb2f6SDimitry Andric const CoverageViewOptions &Opts) {
558b915e9e0SDimitry Andric SmallVector<std::string, 4> Columns;
559eb11fae6SDimitry Andric Columns.emplace_back(tag("td", "Filename", "column-entry-bold"));
560eb11fae6SDimitry Andric Columns.emplace_back(tag("td", "Function Coverage", "column-entry-bold"));
561044eb2f6SDimitry Andric if (Opts.ShowInstantiationSummary)
562eb11fae6SDimitry Andric Columns.emplace_back(
563eb11fae6SDimitry Andric tag("td", "Instantiation Coverage", "column-entry-bold"));
564eb11fae6SDimitry Andric Columns.emplace_back(tag("td", "Line Coverage", "column-entry-bold"));
565044eb2f6SDimitry Andric if (Opts.ShowRegionSummary)
566eb11fae6SDimitry Andric Columns.emplace_back(tag("td", "Region Coverage", "column-entry-bold"));
567b60736ecSDimitry Andric if (Opts.ShowBranchSummary)
568b60736ecSDimitry Andric Columns.emplace_back(tag("td", "Branch Coverage", "column-entry-bold"));
569312c0ed1SDimitry Andric if (Opts.ShowMCDCSummary)
570312c0ed1SDimitry Andric Columns.emplace_back(tag("td", "MC/DC", "column-entry-bold"));
571b915e9e0SDimitry Andric OS << tag("tr", join(Columns.begin(), Columns.end(), ""));
572b915e9e0SDimitry Andric }
573b915e9e0SDimitry Andric
574044eb2f6SDimitry Andric std::string
buildLinkToFile(StringRef SF,const FileCoverageSummary & FCS) const575044eb2f6SDimitry Andric CoveragePrinterHTML::buildLinkToFile(StringRef SF,
576044eb2f6SDimitry Andric const FileCoverageSummary &FCS) const {
577044eb2f6SDimitry Andric SmallString<128> LinkTextStr(sys::path::relative_path(FCS.Name));
5786f8fc217SDimitry Andric sys::path::remove_dots(LinkTextStr, /*remove_dot_dot=*/true);
579044eb2f6SDimitry Andric sys::path::native(LinkTextStr);
580044eb2f6SDimitry Andric std::string LinkText = escape(LinkTextStr, Opts);
581044eb2f6SDimitry Andric std::string LinkTarget =
582044eb2f6SDimitry Andric escape(getOutputPath(SF, "html", /*InToplevel=*/false), Opts);
583044eb2f6SDimitry Andric return a(LinkTarget, LinkText);
584044eb2f6SDimitry Andric }
585044eb2f6SDimitry Andric
emitStyleSheet()586b1c73532SDimitry Andric Error CoveragePrinterHTML::emitStyleSheet() {
587b915e9e0SDimitry Andric auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true);
588b915e9e0SDimitry Andric if (Error E = CSSOrErr.takeError())
589b915e9e0SDimitry Andric return E;
590b915e9e0SDimitry Andric
591b915e9e0SDimitry Andric OwnedStream CSS = std::move(CSSOrErr.get());
592b915e9e0SDimitry Andric CSS->operator<<(CSSForCoverage);
593b915e9e0SDimitry Andric
594b1c73532SDimitry Andric return Error::success();
595b1c73532SDimitry Andric }
59601095a5dSDimitry Andric
emitJavaScript()597ac9a064cSDimitry Andric Error CoveragePrinterHTML::emitJavaScript() {
598ac9a064cSDimitry Andric auto JSOrErr = createOutputStream("control", "js", /*InToplevel=*/true);
599ac9a064cSDimitry Andric if (Error E = JSOrErr.takeError())
600ac9a064cSDimitry Andric return E;
601ac9a064cSDimitry Andric
602ac9a064cSDimitry Andric OwnedStream JS = std::move(JSOrErr.get());
603ac9a064cSDimitry Andric JS->operator<<(JSForCoverage);
604ac9a064cSDimitry Andric
605ac9a064cSDimitry Andric return Error::success();
606ac9a064cSDimitry Andric }
607ac9a064cSDimitry Andric
emitReportHeader(raw_ostream & OSRef,const std::string & Title)608b1c73532SDimitry Andric void CoveragePrinterHTML::emitReportHeader(raw_ostream &OSRef,
609b1c73532SDimitry Andric const std::string &Title) {
610b915e9e0SDimitry Andric // Emit some basic information about the coverage report.
611b915e9e0SDimitry Andric if (Opts.hasProjectTitle())
612b915e9e0SDimitry Andric OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts));
613b1c73532SDimitry Andric OSRef << tag(ReportTitleTag, Title);
614b915e9e0SDimitry Andric if (Opts.hasCreatedTime())
615b915e9e0SDimitry Andric OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts));
616b915e9e0SDimitry Andric
617b915e9e0SDimitry Andric // Emit a link to some documentation.
618b915e9e0SDimitry Andric OSRef << tag("p", "Click " +
619b915e9e0SDimitry Andric a("http://clang.llvm.org/docs/"
620b915e9e0SDimitry Andric "SourceBasedCodeCoverage.html#interpreting-reports",
621b915e9e0SDimitry Andric "here") +
622b915e9e0SDimitry Andric " for information about interpreting this report.");
623b915e9e0SDimitry Andric
62401095a5dSDimitry Andric // Emit a table containing links to reports for each file in the covmapping.
625044eb2f6SDimitry Andric // Exclude files which don't contain any regions.
626b915e9e0SDimitry Andric OSRef << BeginCenteredDiv << BeginTable;
627044eb2f6SDimitry Andric emitColumnLabelsForIndex(OSRef, Opts);
628b1c73532SDimitry Andric }
629b1c73532SDimitry Andric
630b1c73532SDimitry Andric /// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is
631b1c73532SDimitry Andric /// false, link the summary to \p SF.
emitFileSummary(raw_ostream & OS,StringRef SF,const FileCoverageSummary & FCS,bool IsTotals) const632b1c73532SDimitry Andric void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF,
633b1c73532SDimitry Andric const FileCoverageSummary &FCS,
634b1c73532SDimitry Andric bool IsTotals) const {
635b1c73532SDimitry Andric // Simplify the display file path, and wrap it in a link if requested.
636b1c73532SDimitry Andric std::string Filename;
637b1c73532SDimitry Andric if (IsTotals) {
638b1c73532SDimitry Andric Filename = std::string(SF);
639b1c73532SDimitry Andric } else {
640b1c73532SDimitry Andric Filename = buildLinkToFile(SF, FCS);
641b1c73532SDimitry Andric }
642b1c73532SDimitry Andric
643b1c73532SDimitry Andric emitTableRow(OS, Opts, Filename, FCS, IsTotals);
644b1c73532SDimitry Andric }
645b1c73532SDimitry Andric
createIndexFile(ArrayRef<std::string> SourceFiles,const CoverageMapping & Coverage,const CoverageFiltersMatchAll & Filters)646b1c73532SDimitry Andric Error CoveragePrinterHTML::createIndexFile(
647b1c73532SDimitry Andric ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
648b1c73532SDimitry Andric const CoverageFiltersMatchAll &Filters) {
649b1c73532SDimitry Andric // Emit the default stylesheet.
650b1c73532SDimitry Andric if (Error E = emitStyleSheet())
651b1c73532SDimitry Andric return E;
652b1c73532SDimitry Andric
653ac9a064cSDimitry Andric // Emit the JavaScript UI implementation
654ac9a064cSDimitry Andric if (Error E = emitJavaScript())
655ac9a064cSDimitry Andric return E;
656ac9a064cSDimitry Andric
657b1c73532SDimitry Andric // Emit a file index along with some coverage statistics.
658b1c73532SDimitry Andric auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
659b1c73532SDimitry Andric if (Error E = OSOrErr.takeError())
660b1c73532SDimitry Andric return E;
661b1c73532SDimitry Andric auto OS = std::move(OSOrErr.get());
662b1c73532SDimitry Andric raw_ostream &OSRef = *OS.get();
663b1c73532SDimitry Andric
664b1c73532SDimitry Andric assert(Opts.hasOutputDirectory() && "No output directory for index file");
665ac9a064cSDimitry Andric emitPrelude(OSRef, Opts, getPathToStyle(""), getPathToJavaScript(""));
666b1c73532SDimitry Andric
667b1c73532SDimitry Andric emitReportHeader(OSRef, "Coverage Report");
668b1c73532SDimitry Andric
669b915e9e0SDimitry Andric FileCoverageSummary Totals("TOTALS");
670044eb2f6SDimitry Andric auto FileReports = CoverageReport::prepareFileReports(
671044eb2f6SDimitry Andric Coverage, Totals, SourceFiles, Opts, Filters);
672044eb2f6SDimitry Andric bool EmptyFiles = false;
673044eb2f6SDimitry Andric for (unsigned I = 0, E = FileReports.size(); I < E; ++I) {
674044eb2f6SDimitry Andric if (FileReports[I].FunctionCoverage.getNumFunctions())
675b915e9e0SDimitry Andric emitFileSummary(OSRef, SourceFiles[I], FileReports[I]);
676044eb2f6SDimitry Andric else
677044eb2f6SDimitry Andric EmptyFiles = true;
678044eb2f6SDimitry Andric }
679b915e9e0SDimitry Andric emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true);
680044eb2f6SDimitry Andric OSRef << EndTable << EndCenteredDiv;
681044eb2f6SDimitry Andric
682044eb2f6SDimitry Andric // Emit links to files which don't contain any functions. These are normally
683044eb2f6SDimitry Andric // not very useful, but could be relevant for code which abuses the
684044eb2f6SDimitry Andric // preprocessor.
685044eb2f6SDimitry Andric if (EmptyFiles && Filters.empty()) {
686044eb2f6SDimitry Andric OSRef << tag("p", "Files which contain no functions. (These "
687044eb2f6SDimitry Andric "files contain code pulled into other files "
688044eb2f6SDimitry Andric "by the preprocessor.)\n");
689044eb2f6SDimitry Andric OSRef << BeginCenteredDiv << BeginTable;
690044eb2f6SDimitry Andric for (unsigned I = 0, E = FileReports.size(); I < E; ++I)
691044eb2f6SDimitry Andric if (!FileReports[I].FunctionCoverage.getNumFunctions()) {
692044eb2f6SDimitry Andric std::string Link = buildLinkToFile(SourceFiles[I], FileReports[I]);
693044eb2f6SDimitry Andric OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n';
694044eb2f6SDimitry Andric }
695044eb2f6SDimitry Andric OSRef << EndTable << EndCenteredDiv;
696044eb2f6SDimitry Andric }
697044eb2f6SDimitry Andric
698044eb2f6SDimitry Andric OSRef << tag("h5", escape(Opts.getLLVMVersionString(), Opts));
69901095a5dSDimitry Andric emitEpilog(OSRef);
70001095a5dSDimitry Andric
70101095a5dSDimitry Andric return Error::success();
70201095a5dSDimitry Andric }
70301095a5dSDimitry Andric
704b1c73532SDimitry Andric struct CoveragePrinterHTMLDirectory::Reporter : public DirectoryCoverageReport {
705b1c73532SDimitry Andric CoveragePrinterHTMLDirectory &Printer;
706b1c73532SDimitry Andric
ReporterCoveragePrinterHTMLDirectory::Reporter707b1c73532SDimitry Andric Reporter(CoveragePrinterHTMLDirectory &Printer,
708b1c73532SDimitry Andric const coverage::CoverageMapping &Coverage,
709b1c73532SDimitry Andric const CoverageFiltersMatchAll &Filters)
710b1c73532SDimitry Andric : DirectoryCoverageReport(Printer.Opts, Coverage, Filters),
711b1c73532SDimitry Andric Printer(Printer) {}
712b1c73532SDimitry Andric
generateSubDirectoryReportCoveragePrinterHTMLDirectory::Reporter713b1c73532SDimitry Andric Error generateSubDirectoryReport(SubFileReports &&SubFiles,
714b1c73532SDimitry Andric SubDirReports &&SubDirs,
715b1c73532SDimitry Andric FileCoverageSummary &&SubTotals) override {
716b1c73532SDimitry Andric auto &LCPath = SubTotals.Name;
717b1c73532SDimitry Andric assert(Options.hasOutputDirectory() &&
718b1c73532SDimitry Andric "No output directory for index file");
719b1c73532SDimitry Andric
720b1c73532SDimitry Andric SmallString<128> OSPath = LCPath;
721b1c73532SDimitry Andric sys::path::append(OSPath, "index");
722b1c73532SDimitry Andric auto OSOrErr = Printer.createOutputStream(OSPath, "html",
723b1c73532SDimitry Andric /*InToplevel=*/false);
724b1c73532SDimitry Andric if (auto E = OSOrErr.takeError())
725b1c73532SDimitry Andric return E;
726b1c73532SDimitry Andric auto OS = std::move(OSOrErr.get());
727b1c73532SDimitry Andric raw_ostream &OSRef = *OS.get();
728b1c73532SDimitry Andric
729b1c73532SDimitry Andric auto IndexHtmlPath = Printer.getOutputPath((LCPath + "index").str(), "html",
730b1c73532SDimitry Andric /*InToplevel=*/false);
731ac9a064cSDimitry Andric emitPrelude(OSRef, Options, getPathToStyle(IndexHtmlPath),
732ac9a064cSDimitry Andric getPathToJavaScript(IndexHtmlPath));
733b1c73532SDimitry Andric
734b1c73532SDimitry Andric auto NavLink = buildTitleLinks(LCPath);
735b1c73532SDimitry Andric Printer.emitReportHeader(OSRef, "Coverage Report (" + NavLink + ")");
736b1c73532SDimitry Andric
737b1c73532SDimitry Andric std::vector<const FileCoverageSummary *> EmptyFiles;
738b1c73532SDimitry Andric
739b1c73532SDimitry Andric // Make directories at the top of the table.
740b1c73532SDimitry Andric for (auto &&SubDir : SubDirs) {
741b1c73532SDimitry Andric auto &Report = SubDir.second.first;
742b1c73532SDimitry Andric if (!Report.FunctionCoverage.getNumFunctions())
743b1c73532SDimitry Andric EmptyFiles.push_back(&Report);
744b1c73532SDimitry Andric else
745b1c73532SDimitry Andric emitTableRow(OSRef, Options, buildRelLinkToFile(Report.Name), Report,
746b1c73532SDimitry Andric /*IsTotals=*/false);
747b1c73532SDimitry Andric }
748b1c73532SDimitry Andric
749b1c73532SDimitry Andric for (auto &&SubFile : SubFiles) {
750b1c73532SDimitry Andric auto &Report = SubFile.second;
751b1c73532SDimitry Andric if (!Report.FunctionCoverage.getNumFunctions())
752b1c73532SDimitry Andric EmptyFiles.push_back(&Report);
753b1c73532SDimitry Andric else
754b1c73532SDimitry Andric emitTableRow(OSRef, Options, buildRelLinkToFile(Report.Name), Report,
755b1c73532SDimitry Andric /*IsTotals=*/false);
756b1c73532SDimitry Andric }
757b1c73532SDimitry Andric
758b1c73532SDimitry Andric // Emit the totals row.
759b1c73532SDimitry Andric emitTableRow(OSRef, Options, "Totals", SubTotals, /*IsTotals=*/false);
760b1c73532SDimitry Andric OSRef << EndTable << EndCenteredDiv;
761b1c73532SDimitry Andric
762b1c73532SDimitry Andric // Emit links to files which don't contain any functions. These are normally
763b1c73532SDimitry Andric // not very useful, but could be relevant for code which abuses the
764b1c73532SDimitry Andric // preprocessor.
765b1c73532SDimitry Andric if (!EmptyFiles.empty()) {
766b1c73532SDimitry Andric OSRef << tag("p", "Files which contain no functions. (These "
767b1c73532SDimitry Andric "files contain code pulled into other files "
768b1c73532SDimitry Andric "by the preprocessor.)\n");
769b1c73532SDimitry Andric OSRef << BeginCenteredDiv << BeginTable;
770b1c73532SDimitry Andric for (auto FCS : EmptyFiles) {
771b1c73532SDimitry Andric auto Link = buildRelLinkToFile(FCS->Name);
772b1c73532SDimitry Andric OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n';
773b1c73532SDimitry Andric }
774b1c73532SDimitry Andric OSRef << EndTable << EndCenteredDiv;
775b1c73532SDimitry Andric }
776b1c73532SDimitry Andric
777b1c73532SDimitry Andric // Emit epilog.
778b1c73532SDimitry Andric OSRef << tag("h5", escape(Options.getLLVMVersionString(), Options));
779b1c73532SDimitry Andric emitEpilog(OSRef);
780b1c73532SDimitry Andric
781b1c73532SDimitry Andric return Error::success();
782b1c73532SDimitry Andric }
783b1c73532SDimitry Andric
784b1c73532SDimitry Andric /// Make a title with hyperlinks to the index.html files of each hierarchy
785b1c73532SDimitry Andric /// of the report.
buildTitleLinksCoveragePrinterHTMLDirectory::Reporter786b1c73532SDimitry Andric std::string buildTitleLinks(StringRef LCPath) const {
787b1c73532SDimitry Andric // For each report level in LCPStack, extract the path component and
788b1c73532SDimitry Andric // calculate the number of "../" relative to current LCPath.
789b1c73532SDimitry Andric SmallVector<std::pair<SmallString<128>, unsigned>, 16> Components;
790b1c73532SDimitry Andric
791b1c73532SDimitry Andric auto Iter = LCPStack.begin(), IterE = LCPStack.end();
792b1c73532SDimitry Andric SmallString<128> RootPath;
793b1c73532SDimitry Andric if (*Iter == 0) {
794b1c73532SDimitry Andric // If llvm-cov works on relative coverage mapping data, the LCP of
795b1c73532SDimitry Andric // all source file paths can be 0, which makes the title path empty.
796b1c73532SDimitry Andric // As we like adding a slash at the back of the path to indicate a
797b1c73532SDimitry Andric // directory, in this case, we use "." as the root path to make it
798b1c73532SDimitry Andric // not be confused with the root path "/".
799b1c73532SDimitry Andric RootPath = ".";
800b1c73532SDimitry Andric } else {
801b1c73532SDimitry Andric RootPath = LCPath.substr(0, *Iter);
802b1c73532SDimitry Andric sys::path::native(RootPath);
803b1c73532SDimitry Andric sys::path::remove_dots(RootPath, /*remove_dot_dot=*/true);
804b1c73532SDimitry Andric }
805b1c73532SDimitry Andric Components.emplace_back(std::move(RootPath), 0);
806b1c73532SDimitry Andric
807b1c73532SDimitry Andric for (auto Last = *Iter; ++Iter != IterE; Last = *Iter) {
808b1c73532SDimitry Andric SmallString<128> SubPath = LCPath.substr(Last, *Iter - Last);
809b1c73532SDimitry Andric sys::path::native(SubPath);
810b1c73532SDimitry Andric sys::path::remove_dots(SubPath, /*remove_dot_dot=*/true);
811b1c73532SDimitry Andric auto Level = unsigned(SubPath.count(sys::path::get_separator())) + 1;
812b1c73532SDimitry Andric Components.back().second += Level;
813b1c73532SDimitry Andric Components.emplace_back(std::move(SubPath), Level);
814b1c73532SDimitry Andric }
815b1c73532SDimitry Andric
816b1c73532SDimitry Andric // Then we make the title accroding to Components.
817b1c73532SDimitry Andric std::string S;
818b1c73532SDimitry Andric for (auto I = Components.begin(), E = Components.end();;) {
819b1c73532SDimitry Andric auto &Name = I->first;
820b1c73532SDimitry Andric if (++I == E) {
821b1c73532SDimitry Andric S += a("./index.html", Name);
822b1c73532SDimitry Andric S += sys::path::get_separator();
823b1c73532SDimitry Andric break;
824b1c73532SDimitry Andric }
825b1c73532SDimitry Andric
826b1c73532SDimitry Andric SmallString<128> Link;
827b1c73532SDimitry Andric for (unsigned J = I->second; J > 0; --J)
828b1c73532SDimitry Andric Link += "../";
829b1c73532SDimitry Andric Link += "index.html";
830b1c73532SDimitry Andric S += a(Link, Name);
831b1c73532SDimitry Andric S += sys::path::get_separator();
832b1c73532SDimitry Andric }
833b1c73532SDimitry Andric return S;
834b1c73532SDimitry Andric }
835b1c73532SDimitry Andric
buildRelLinkToFileCoveragePrinterHTMLDirectory::Reporter836b1c73532SDimitry Andric std::string buildRelLinkToFile(StringRef RelPath) const {
837b1c73532SDimitry Andric SmallString<128> LinkTextStr(RelPath);
838b1c73532SDimitry Andric sys::path::native(LinkTextStr);
839b1c73532SDimitry Andric
840b1c73532SDimitry Andric // remove_dots will remove trailing slash, so we need to check before it.
841312c0ed1SDimitry Andric auto IsDir = LinkTextStr.ends_with(sys::path::get_separator());
842b1c73532SDimitry Andric sys::path::remove_dots(LinkTextStr, /*remove_dot_dot=*/true);
843b1c73532SDimitry Andric
844b1c73532SDimitry Andric SmallString<128> LinkTargetStr(LinkTextStr);
845b1c73532SDimitry Andric if (IsDir) {
846b1c73532SDimitry Andric LinkTextStr += sys::path::get_separator();
847b1c73532SDimitry Andric sys::path::append(LinkTargetStr, "index.html");
848b1c73532SDimitry Andric } else {
849b1c73532SDimitry Andric LinkTargetStr += ".html";
850b1c73532SDimitry Andric }
851b1c73532SDimitry Andric
852b1c73532SDimitry Andric auto LinkText = escape(LinkTextStr, Options);
853b1c73532SDimitry Andric auto LinkTarget = escape(LinkTargetStr, Options);
854b1c73532SDimitry Andric return a(LinkTarget, LinkText);
855b1c73532SDimitry Andric }
856b1c73532SDimitry Andric };
857b1c73532SDimitry Andric
createIndexFile(ArrayRef<std::string> SourceFiles,const CoverageMapping & Coverage,const CoverageFiltersMatchAll & Filters)858b1c73532SDimitry Andric Error CoveragePrinterHTMLDirectory::createIndexFile(
859b1c73532SDimitry Andric ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
860b1c73532SDimitry Andric const CoverageFiltersMatchAll &Filters) {
861b1c73532SDimitry Andric // The createSubIndexFile function only works when SourceFiles is
862b1c73532SDimitry Andric // more than one. So we fallback to CoveragePrinterHTML when it is.
863b1c73532SDimitry Andric if (SourceFiles.size() <= 1)
864b1c73532SDimitry Andric return CoveragePrinterHTML::createIndexFile(SourceFiles, Coverage, Filters);
865b1c73532SDimitry Andric
866b1c73532SDimitry Andric // Emit the default stylesheet.
867b1c73532SDimitry Andric if (Error E = emitStyleSheet())
868b1c73532SDimitry Andric return E;
869b1c73532SDimitry Andric
870ac9a064cSDimitry Andric // Emit the JavaScript UI implementation
871ac9a064cSDimitry Andric if (Error E = emitJavaScript())
872ac9a064cSDimitry Andric return E;
873ac9a064cSDimitry Andric
874b1c73532SDimitry Andric // Emit index files in every subdirectory.
875b1c73532SDimitry Andric Reporter Report(*this, Coverage, Filters);
876b1c73532SDimitry Andric auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);
877b1c73532SDimitry Andric if (auto E = TotalsOrErr.takeError())
878b1c73532SDimitry Andric return E;
879b1c73532SDimitry Andric auto &LCPath = TotalsOrErr->Name;
880b1c73532SDimitry Andric
881b1c73532SDimitry Andric // Emit the top level index file. Top level index file is just a redirection
882b1c73532SDimitry Andric // to the index file in the LCP directory.
883b1c73532SDimitry Andric auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
884b1c73532SDimitry Andric if (auto E = OSOrErr.takeError())
885b1c73532SDimitry Andric return E;
886b1c73532SDimitry Andric auto OS = std::move(OSOrErr.get());
887b1c73532SDimitry Andric auto LCPIndexFilePath =
888b1c73532SDimitry Andric getOutputPath((LCPath + "index").str(), "html", /*InToplevel=*/false);
889b1c73532SDimitry Andric *OS.get() << R"(<!DOCTYPE html>
890b1c73532SDimitry Andric <html>
891b1c73532SDimitry Andric <head>
892b1c73532SDimitry Andric <meta http-equiv="Refresh" content="0; url=')"
893b1c73532SDimitry Andric << LCPIndexFilePath << R"('" />
894b1c73532SDimitry Andric </head>
895b1c73532SDimitry Andric <body></body>
896b1c73532SDimitry Andric </html>
897b1c73532SDimitry Andric )";
898b1c73532SDimitry Andric
899b1c73532SDimitry Andric return Error::success();
900b1c73532SDimitry Andric }
901b1c73532SDimitry Andric
renderViewHeader(raw_ostream & OS)90201095a5dSDimitry Andric void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
903b915e9e0SDimitry Andric OS << BeginCenteredDiv << BeginTable;
90401095a5dSDimitry Andric }
90501095a5dSDimitry Andric
renderViewFooter(raw_ostream & OS)90601095a5dSDimitry Andric void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) {
907b915e9e0SDimitry Andric OS << EndTable << EndCenteredDiv;
90801095a5dSDimitry Andric }
90901095a5dSDimitry Andric
renderSourceName(raw_ostream & OS,bool WholeFile)910b915e9e0SDimitry Andric void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS, bool WholeFile) {
911b915e9e0SDimitry Andric OS << BeginSourceNameDiv << tag("pre", escape(getSourceName(), getOptions()))
91201095a5dSDimitry Andric << EndSourceNameDiv;
91301095a5dSDimitry Andric }
91401095a5dSDimitry Andric
renderLinePrefix(raw_ostream & OS,unsigned)91501095a5dSDimitry Andric void SourceCoverageViewHTML::renderLinePrefix(raw_ostream &OS, unsigned) {
91601095a5dSDimitry Andric OS << "<tr>";
91701095a5dSDimitry Andric }
91801095a5dSDimitry Andric
renderLineSuffix(raw_ostream & OS,unsigned)91901095a5dSDimitry Andric void SourceCoverageViewHTML::renderLineSuffix(raw_ostream &OS, unsigned) {
92001095a5dSDimitry Andric // If this view has sub-views, renderLine() cannot close the view's cell.
92101095a5dSDimitry Andric // Take care of it here, after all sub-views have been rendered.
92201095a5dSDimitry Andric if (hasSubViews())
92301095a5dSDimitry Andric OS << EndCodeTD;
92401095a5dSDimitry Andric OS << "</tr>";
92501095a5dSDimitry Andric }
92601095a5dSDimitry Andric
renderViewDivider(raw_ostream &,unsigned)92701095a5dSDimitry Andric void SourceCoverageViewHTML::renderViewDivider(raw_ostream &, unsigned) {
92801095a5dSDimitry Andric // The table-based output makes view dividers unnecessary.
92901095a5dSDimitry Andric }
93001095a5dSDimitry Andric
renderLine(raw_ostream & OS,LineRef L,const LineCoverageStats & LCS,unsigned ExpansionCol,unsigned)931044eb2f6SDimitry Andric void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L,
932044eb2f6SDimitry Andric const LineCoverageStats &LCS,
933044eb2f6SDimitry Andric unsigned ExpansionCol, unsigned) {
93401095a5dSDimitry Andric StringRef Line = L.Line;
935b915e9e0SDimitry Andric unsigned LineNo = L.LineNo;
93601095a5dSDimitry Andric
93701095a5dSDimitry Andric // Steps for handling text-escaping, highlighting, and tooltip creation:
93801095a5dSDimitry Andric //
93901095a5dSDimitry Andric // 1. Split the line into N+1 snippets, where N = |Segments|. The first
94001095a5dSDimitry Andric // snippet starts from Col=1 and ends at the start of the first segment.
94101095a5dSDimitry Andric // The last snippet starts at the last mapped column in the line and ends
94201095a5dSDimitry Andric // at the end of the line. Both are required but may be empty.
94301095a5dSDimitry Andric
94401095a5dSDimitry Andric SmallVector<std::string, 8> Snippets;
945044eb2f6SDimitry Andric CoverageSegmentArray Segments = LCS.getLineSegments();
94601095a5dSDimitry Andric
94701095a5dSDimitry Andric unsigned LCol = 1;
94801095a5dSDimitry Andric auto Snip = [&](unsigned Start, unsigned Len) {
949cfca06d7SDimitry Andric Snippets.push_back(std::string(Line.substr(Start, Len)));
95001095a5dSDimitry Andric LCol += Len;
95101095a5dSDimitry Andric };
95201095a5dSDimitry Andric
95301095a5dSDimitry Andric Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1));
95401095a5dSDimitry Andric
955b915e9e0SDimitry Andric for (unsigned I = 1, E = Segments.size(); I < E; ++I)
95601095a5dSDimitry Andric Snip(LCol - 1, Segments[I]->Col - LCol);
95701095a5dSDimitry Andric
95801095a5dSDimitry Andric // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1.
95901095a5dSDimitry Andric Snip(LCol - 1, Line.size() + 1 - LCol);
96001095a5dSDimitry Andric
96101095a5dSDimitry Andric // 2. Escape all of the snippets.
96201095a5dSDimitry Andric
96301095a5dSDimitry Andric for (unsigned I = 0, E = Snippets.size(); I < E; ++I)
964b915e9e0SDimitry Andric Snippets[I] = escape(Snippets[I], getOptions());
96501095a5dSDimitry Andric
966b915e9e0SDimitry Andric // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment
967b915e9e0SDimitry Andric // 1 to set the highlight for snippet 2, segment 2 to set the highlight for
968b915e9e0SDimitry Andric // snippet 3, and so on.
96901095a5dSDimitry Andric
970e3b55780SDimitry Andric std::optional<StringRef> Color;
971b915e9e0SDimitry Andric SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
972b915e9e0SDimitry Andric auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) {
973b915e9e0SDimitry Andric if (getOptions().Debug)
974b915e9e0SDimitry Andric HighlightedRanges.emplace_back(LC, RC);
975ac9a064cSDimitry Andric if (Snippet.empty())
976145449b1SDimitry Andric return tag("span", Snippet, std::string(*Color));
977ac9a064cSDimitry Andric else
978ac9a064cSDimitry Andric return tag("span", Snippet, "region " + std::string(*Color));
97901095a5dSDimitry Andric };
98001095a5dSDimitry Andric
981044eb2f6SDimitry Andric auto CheckIfUncovered = [&](const CoverageSegment *S) {
982044eb2f6SDimitry Andric return S && (!S->IsGapRegion || (Color && *Color == "red")) &&
983044eb2f6SDimitry Andric S->HasCount && S->Count == 0;
98401095a5dSDimitry Andric };
98501095a5dSDimitry Andric
986044eb2f6SDimitry Andric if (CheckIfUncovered(LCS.getWrappedSegment())) {
98701095a5dSDimitry Andric Color = "red";
988b915e9e0SDimitry Andric if (!Snippets[0].empty())
989b915e9e0SDimitry Andric Snippets[0] = Highlight(Snippets[0], 1, 1 + Snippets[0].size());
99001095a5dSDimitry Andric }
99101095a5dSDimitry Andric
992b915e9e0SDimitry Andric for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
99301095a5dSDimitry Andric const auto *CurSeg = Segments[I];
994044eb2f6SDimitry Andric if (CheckIfUncovered(CurSeg))
99501095a5dSDimitry Andric Color = "red";
996044eb2f6SDimitry Andric else if (CurSeg->Col == ExpansionCol)
997044eb2f6SDimitry Andric Color = "cyan";
99801095a5dSDimitry Andric else
999e3b55780SDimitry Andric Color = std::nullopt;
100001095a5dSDimitry Andric
1001145449b1SDimitry Andric if (Color)
1002b915e9e0SDimitry Andric Snippets[I + 1] = Highlight(Snippets[I + 1], CurSeg->Col,
1003b915e9e0SDimitry Andric CurSeg->Col + Snippets[I + 1].size());
1004b915e9e0SDimitry Andric }
1005b915e9e0SDimitry Andric
1006145449b1SDimitry Andric if (Color && Segments.empty())
1007b915e9e0SDimitry Andric Snippets.back() = Highlight(Snippets.back(), 1, 1 + Snippets.back().size());
1008b915e9e0SDimitry Andric
1009b915e9e0SDimitry Andric if (getOptions().Debug) {
1010b915e9e0SDimitry Andric for (const auto &Range : HighlightedRanges) {
1011b915e9e0SDimitry Andric errs() << "Highlighted line " << LineNo << ", " << Range.first << " -> ";
1012b915e9e0SDimitry Andric if (Range.second == 0)
1013b915e9e0SDimitry Andric errs() << "?";
1014b915e9e0SDimitry Andric else
1015b915e9e0SDimitry Andric errs() << Range.second;
1016b915e9e0SDimitry Andric errs() << "\n";
1017b915e9e0SDimitry Andric }
101801095a5dSDimitry Andric }
101901095a5dSDimitry Andric
102001095a5dSDimitry Andric // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
102101095a5dSDimitry Andric // sub-line region count tooltips if needed.
102201095a5dSDimitry Andric
1023044eb2f6SDimitry Andric if (shouldRenderRegionMarkers(LCS)) {
1024044eb2f6SDimitry Andric // Just consider the segments which start *and* end on this line.
1025044eb2f6SDimitry Andric for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {
102601095a5dSDimitry Andric const auto *CurSeg = Segments[I];
1027044eb2f6SDimitry Andric if (!CurSeg->IsRegionEntry)
1028044eb2f6SDimitry Andric continue;
1029044eb2f6SDimitry Andric if (CurSeg->Count == LCS.getExecutionCount())
103001095a5dSDimitry Andric continue;
103101095a5dSDimitry Andric
103201095a5dSDimitry Andric Snippets[I + 1] =
103301095a5dSDimitry Andric tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count),
103401095a5dSDimitry Andric "tooltip-content"),
103501095a5dSDimitry Andric "tooltip");
1036044eb2f6SDimitry Andric
1037044eb2f6SDimitry Andric if (getOptions().Debug)
1038044eb2f6SDimitry Andric errs() << "Marker at " << CurSeg->Line << ":" << CurSeg->Col << " = "
1039044eb2f6SDimitry Andric << formatCount(CurSeg->Count) << "\n";
104001095a5dSDimitry Andric }
104101095a5dSDimitry Andric }
104201095a5dSDimitry Andric
104301095a5dSDimitry Andric OS << BeginCodeTD;
104401095a5dSDimitry Andric OS << BeginPre;
104501095a5dSDimitry Andric for (const auto &Snippet : Snippets)
104601095a5dSDimitry Andric OS << Snippet;
104701095a5dSDimitry Andric OS << EndPre;
104801095a5dSDimitry Andric
104901095a5dSDimitry Andric // If there are no sub-views left to attach to this cell, end the cell.
105001095a5dSDimitry Andric // Otherwise, end it after the sub-views are rendered (renderLineSuffix()).
105101095a5dSDimitry Andric if (!hasSubViews())
105201095a5dSDimitry Andric OS << EndCodeTD;
105301095a5dSDimitry Andric }
105401095a5dSDimitry Andric
renderLineCoverageColumn(raw_ostream & OS,const LineCoverageStats & Line)105501095a5dSDimitry Andric void SourceCoverageViewHTML::renderLineCoverageColumn(
105601095a5dSDimitry Andric raw_ostream &OS, const LineCoverageStats &Line) {
1057b60736ecSDimitry Andric std::string Count;
105801095a5dSDimitry Andric if (Line.isMapped())
1059044eb2f6SDimitry Andric Count = tag("pre", formatCount(Line.getExecutionCount()));
106001095a5dSDimitry Andric std::string CoverageClass =
1061ac9a064cSDimitry Andric (Line.getExecutionCount() > 0)
1062ac9a064cSDimitry Andric ? "covered-line"
1063ac9a064cSDimitry Andric : (Line.isMapped() ? "uncovered-line" : "skipped-line");
106401095a5dSDimitry Andric OS << tag("td", Count, CoverageClass);
106501095a5dSDimitry Andric }
106601095a5dSDimitry Andric
renderLineNumberColumn(raw_ostream & OS,unsigned LineNo)106701095a5dSDimitry Andric void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
106801095a5dSDimitry Andric unsigned LineNo) {
1069b915e9e0SDimitry Andric std::string LineNoStr = utostr(uint64_t(LineNo));
1070b915e9e0SDimitry Andric std::string TargetName = "L" + LineNoStr;
1071b915e9e0SDimitry Andric OS << tag("td", a("#" + TargetName, tag("pre", LineNoStr), TargetName),
1072b915e9e0SDimitry Andric "line-number");
107301095a5dSDimitry Andric }
107401095a5dSDimitry Andric
renderRegionMarkers(raw_ostream &,const LineCoverageStats & Line,unsigned)107501095a5dSDimitry Andric void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &,
1076044eb2f6SDimitry Andric const LineCoverageStats &Line,
107701095a5dSDimitry Andric unsigned) {
107801095a5dSDimitry Andric // Region markers are rendered in-line using tooltips.
107901095a5dSDimitry Andric }
108001095a5dSDimitry Andric
renderExpansionSite(raw_ostream & OS,LineRef L,const LineCoverageStats & LCS,unsigned ExpansionCol,unsigned ViewDepth)1081044eb2f6SDimitry Andric void SourceCoverageViewHTML::renderExpansionSite(raw_ostream &OS, LineRef L,
1082044eb2f6SDimitry Andric const LineCoverageStats &LCS,
1083044eb2f6SDimitry Andric unsigned ExpansionCol,
1084044eb2f6SDimitry Andric unsigned ViewDepth) {
108501095a5dSDimitry Andric // Render the line containing the expansion site. No extra formatting needed.
1086044eb2f6SDimitry Andric renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
108701095a5dSDimitry Andric }
108801095a5dSDimitry Andric
renderExpansionView(raw_ostream & OS,ExpansionView & ESV,unsigned ViewDepth)108901095a5dSDimitry Andric void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS,
109001095a5dSDimitry Andric ExpansionView &ESV,
109101095a5dSDimitry Andric unsigned ViewDepth) {
109201095a5dSDimitry Andric OS << BeginExpansionDiv;
109301095a5dSDimitry Andric ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
1094044eb2f6SDimitry Andric /*ShowTitle=*/false, ViewDepth + 1);
109501095a5dSDimitry Andric OS << EndExpansionDiv;
109601095a5dSDimitry Andric }
109701095a5dSDimitry Andric
renderBranchView(raw_ostream & OS,BranchView & BRV,unsigned ViewDepth)1098b60736ecSDimitry Andric void SourceCoverageViewHTML::renderBranchView(raw_ostream &OS, BranchView &BRV,
1099b60736ecSDimitry Andric unsigned ViewDepth) {
1100b60736ecSDimitry Andric // Render the child subview.
1101b60736ecSDimitry Andric if (getOptions().Debug)
1102b60736ecSDimitry Andric errs() << "Branch at line " << BRV.getLine() << '\n';
1103b60736ecSDimitry Andric
1104b60736ecSDimitry Andric OS << BeginExpansionDiv;
1105b60736ecSDimitry Andric OS << BeginPre;
1106b60736ecSDimitry Andric for (const auto &R : BRV.Regions) {
1107b60736ecSDimitry Andric // Calculate TruePercent and False Percent.
1108b60736ecSDimitry Andric double TruePercent = 0.0;
1109b60736ecSDimitry Andric double FalsePercent = 0.0;
1110b1c73532SDimitry Andric // FIXME: It may overflow when the data is too large, but I have not
1111b1c73532SDimitry Andric // encountered it in actual use, and not sure whether to use __uint128_t.
1112b1c73532SDimitry Andric uint64_t Total = R.ExecutionCount + R.FalseExecutionCount;
1113b60736ecSDimitry Andric
1114b60736ecSDimitry Andric if (!getOptions().ShowBranchCounts && Total != 0) {
1115b60736ecSDimitry Andric TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0;
1116b60736ecSDimitry Andric FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0;
1117b60736ecSDimitry Andric }
1118b60736ecSDimitry Andric
1119b60736ecSDimitry Andric // Display Line + Column.
1120b60736ecSDimitry Andric std::string LineNoStr = utostr(uint64_t(R.LineStart));
1121b60736ecSDimitry Andric std::string ColNoStr = utostr(uint64_t(R.ColumnStart));
1122b60736ecSDimitry Andric std::string TargetName = "L" + LineNoStr;
1123b60736ecSDimitry Andric
1124b60736ecSDimitry Andric OS << " Branch (";
1125b60736ecSDimitry Andric OS << tag("span",
1126b60736ecSDimitry Andric a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr),
1127b60736ecSDimitry Andric TargetName),
1128b60736ecSDimitry Andric "line-number") +
1129b60736ecSDimitry Andric "): [";
1130b60736ecSDimitry Andric
1131b60736ecSDimitry Andric if (R.Folded) {
1132b60736ecSDimitry Andric OS << "Folded - Ignored]\n";
1133b60736ecSDimitry Andric continue;
1134b60736ecSDimitry Andric }
1135b60736ecSDimitry Andric
1136b60736ecSDimitry Andric // Display TrueCount or TruePercent.
1137ac9a064cSDimitry Andric std::string TrueColor = R.ExecutionCount ? "None" : "red branch";
1138b60736ecSDimitry Andric std::string TrueCovClass =
1139b60736ecSDimitry Andric (R.ExecutionCount > 0) ? "covered-line" : "uncovered-line";
1140b60736ecSDimitry Andric
1141b60736ecSDimitry Andric OS << tag("span", "True", TrueColor);
1142b60736ecSDimitry Andric OS << ": ";
1143b60736ecSDimitry Andric if (getOptions().ShowBranchCounts)
1144b60736ecSDimitry Andric OS << tag("span", formatCount(R.ExecutionCount), TrueCovClass) << ", ";
1145b60736ecSDimitry Andric else
1146b60736ecSDimitry Andric OS << format("%0.2f", TruePercent) << "%, ";
1147b60736ecSDimitry Andric
1148b60736ecSDimitry Andric // Display FalseCount or FalsePercent.
1149ac9a064cSDimitry Andric std::string FalseColor = R.FalseExecutionCount ? "None" : "red branch";
1150b60736ecSDimitry Andric std::string FalseCovClass =
1151b60736ecSDimitry Andric (R.FalseExecutionCount > 0) ? "covered-line" : "uncovered-line";
1152b60736ecSDimitry Andric
1153b60736ecSDimitry Andric OS << tag("span", "False", FalseColor);
1154b60736ecSDimitry Andric OS << ": ";
1155b60736ecSDimitry Andric if (getOptions().ShowBranchCounts)
1156b60736ecSDimitry Andric OS << tag("span", formatCount(R.FalseExecutionCount), FalseCovClass);
1157b60736ecSDimitry Andric else
1158b60736ecSDimitry Andric OS << format("%0.2f", FalsePercent) << "%";
1159b60736ecSDimitry Andric
1160b60736ecSDimitry Andric OS << "]\n";
1161b60736ecSDimitry Andric }
1162b60736ecSDimitry Andric OS << EndPre;
1163b60736ecSDimitry Andric OS << EndExpansionDiv;
1164b60736ecSDimitry Andric }
1165b60736ecSDimitry Andric
renderMCDCView(raw_ostream & OS,MCDCView & MRV,unsigned ViewDepth)1166312c0ed1SDimitry Andric void SourceCoverageViewHTML::renderMCDCView(raw_ostream &OS, MCDCView &MRV,
1167312c0ed1SDimitry Andric unsigned ViewDepth) {
1168312c0ed1SDimitry Andric for (auto &Record : MRV.Records) {
1169312c0ed1SDimitry Andric OS << BeginExpansionDiv;
1170312c0ed1SDimitry Andric OS << BeginPre;
1171312c0ed1SDimitry Andric OS << " MC/DC Decision Region (";
1172312c0ed1SDimitry Andric
1173312c0ed1SDimitry Andric // Display Line + Column information.
1174312c0ed1SDimitry Andric const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion();
1175312c0ed1SDimitry Andric std::string LineNoStr = Twine(DecisionRegion.LineStart).str();
1176312c0ed1SDimitry Andric std::string ColNoStr = Twine(DecisionRegion.ColumnStart).str();
1177312c0ed1SDimitry Andric std::string TargetName = "L" + LineNoStr;
1178312c0ed1SDimitry Andric OS << tag("span",
1179ac9a064cSDimitry Andric a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr)),
1180312c0ed1SDimitry Andric "line-number") +
1181312c0ed1SDimitry Andric ") to (";
1182312c0ed1SDimitry Andric LineNoStr = utostr(uint64_t(DecisionRegion.LineEnd));
1183312c0ed1SDimitry Andric ColNoStr = utostr(uint64_t(DecisionRegion.ColumnEnd));
1184312c0ed1SDimitry Andric OS << tag("span",
1185ac9a064cSDimitry Andric a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr)),
1186312c0ed1SDimitry Andric "line-number") +
1187312c0ed1SDimitry Andric ")\n\n";
1188312c0ed1SDimitry Andric
1189312c0ed1SDimitry Andric // Display MC/DC Information.
1190312c0ed1SDimitry Andric OS << " Number of Conditions: " << Record.getNumConditions() << "\n";
1191312c0ed1SDimitry Andric for (unsigned i = 0; i < Record.getNumConditions(); i++) {
1192312c0ed1SDimitry Andric OS << " " << Record.getConditionHeaderString(i);
1193312c0ed1SDimitry Andric }
1194312c0ed1SDimitry Andric OS << "\n";
1195312c0ed1SDimitry Andric OS << " Executed MC/DC Test Vectors:\n\n ";
1196312c0ed1SDimitry Andric OS << Record.getTestVectorHeaderString();
1197312c0ed1SDimitry Andric for (unsigned i = 0; i < Record.getNumTestVectors(); i++)
1198312c0ed1SDimitry Andric OS << Record.getTestVectorString(i);
1199312c0ed1SDimitry Andric OS << "\n";
1200312c0ed1SDimitry Andric for (unsigned i = 0; i < Record.getNumConditions(); i++)
1201312c0ed1SDimitry Andric OS << Record.getConditionCoverageString(i);
1202312c0ed1SDimitry Andric OS << " MC/DC Coverage for Expression: ";
1203312c0ed1SDimitry Andric OS << format("%0.2f", Record.getPercentCovered()) << "%\n";
1204312c0ed1SDimitry Andric OS << EndPre;
1205312c0ed1SDimitry Andric OS << EndExpansionDiv;
1206312c0ed1SDimitry Andric }
1207312c0ed1SDimitry Andric return;
1208312c0ed1SDimitry Andric }
1209312c0ed1SDimitry Andric
renderInstantiationView(raw_ostream & OS,InstantiationView & ISV,unsigned ViewDepth)121001095a5dSDimitry Andric void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS,
121101095a5dSDimitry Andric InstantiationView &ISV,
121201095a5dSDimitry Andric unsigned ViewDepth) {
121301095a5dSDimitry Andric OS << BeginExpansionDiv;
1214b915e9e0SDimitry Andric if (!ISV.View)
1215b915e9e0SDimitry Andric OS << BeginSourceNameDiv
1216b915e9e0SDimitry Andric << tag("pre",
1217b915e9e0SDimitry Andric escape("Unexecuted instantiation: " + ISV.FunctionName.str(),
1218b915e9e0SDimitry Andric getOptions()))
1219b915e9e0SDimitry Andric << EndSourceNameDiv;
1220b915e9e0SDimitry Andric else
1221b915e9e0SDimitry Andric ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
1222044eb2f6SDimitry Andric /*ShowTitle=*/false, ViewDepth);
122301095a5dSDimitry Andric OS << EndExpansionDiv;
122401095a5dSDimitry Andric }
1225b915e9e0SDimitry Andric
renderTitle(raw_ostream & OS,StringRef Title)1226b915e9e0SDimitry Andric void SourceCoverageViewHTML::renderTitle(raw_ostream &OS, StringRef Title) {
1227b915e9e0SDimitry Andric if (getOptions().hasProjectTitle())
1228b915e9e0SDimitry Andric OS << tag(ProjectTitleTag, escape(getOptions().ProjectTitle, getOptions()));
1229b915e9e0SDimitry Andric OS << tag(ReportTitleTag, escape(Title, getOptions()));
1230b915e9e0SDimitry Andric if (getOptions().hasCreatedTime())
1231b915e9e0SDimitry Andric OS << tag(CreatedTimeTag,
1232b915e9e0SDimitry Andric escape(getOptions().CreatedTimeStr, getOptions()));
1233ac9a064cSDimitry Andric
1234ac9a064cSDimitry Andric OS << tag("span",
1235ac9a064cSDimitry Andric a("javascript:next_line()", "next uncovered line (L)") + ", " +
1236ac9a064cSDimitry Andric a("javascript:next_region()", "next uncovered region (R)") +
1237ac9a064cSDimitry Andric ", " +
1238ac9a064cSDimitry Andric a("javascript:next_branch()", "next uncovered branch (B)"),
1239ac9a064cSDimitry Andric "control");
1240b915e9e0SDimitry Andric }
1241b915e9e0SDimitry Andric
renderTableHeader(raw_ostream & OS,unsigned ViewDepth)1242b915e9e0SDimitry Andric void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS,
1243b915e9e0SDimitry Andric unsigned ViewDepth) {
1244ac9a064cSDimitry Andric std::string Links;
1245b915e9e0SDimitry Andric
1246b915e9e0SDimitry Andric renderLinePrefix(OS, ViewDepth);
1247ac9a064cSDimitry Andric OS << tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count"));
1248ac9a064cSDimitry Andric OS << tag("td", tag("pre", "Source" + Links));
1249b915e9e0SDimitry Andric renderLineSuffix(OS, ViewDepth);
1250b915e9e0SDimitry Andric }
1251