1 //===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 /// \file This file implements the html coverage renderer.
11 //===----------------------------------------------------------------------===//
13 #include "SourceCoverageViewHTML.h"
14 #include "CoverageReport.h"
15 #include "llvm/ADT/SmallString.h"
16 #include "llvm/ADT/StringExtras.h"
17 #include "llvm/Support/Format.h"
18 #include "llvm/Support/Path.h"
19 #include "llvm/Support/ThreadPool.h"
26 // Return a string with the special characters in \p Str escaped.
27 std::string
escape(StringRef Str
, const CoverageViewOptions
&Opts
) {
28 std::string TabExpandedResult
;
29 unsigned ColNum
= 0; // Record the column number.
32 // Replace '\t' with up to TabSize spaces.
33 unsigned NumSpaces
= Opts
.TabSize
- (ColNum
% Opts
.TabSize
);
34 TabExpandedResult
.append(NumSpaces
, ' ');
37 TabExpandedResult
+= C
;
38 if (C
== '\n' || C
== '\r')
44 std::string EscapedHTML
;
46 raw_string_ostream OS
{EscapedHTML
};
47 printHTMLEscaped(TabExpandedResult
, OS
);
52 // Create a \p Name tag around \p Str, and optionally set its \p ClassName.
53 std::string
tag(StringRef Name
, StringRef Str
, StringRef ClassName
= "") {
54 std::string Tag
= "<";
56 if (!ClassName
.empty()) {
69 // Create an anchor to \p Link with the label \p Str.
70 std::string
a(StringRef Link
, StringRef Str
, StringRef TargetName
= "") {
73 if (!TargetName
.empty()) {
86 const char *BeginHeader
=
88 "<meta name='viewport' content='width=device-width,initial-scale=1'>"
89 "<meta charset='UTF-8'>";
91 const char *CSSForCoverage
=
93 background-color: #ffd0d0;
96 background-color: cyan;
99 font-family: -apple-system, sans-serif;
102 margin-top: 0px !important;
103 margin-bottom: 0px !important;
107 border-bottom: 1px solid #dbdbdb;
108 background-color: #eee;
115 border: 1px solid #dbdbdb;
119 background-color: rgba(0, 0, 0, 0);
124 border: 1px solid #dbdbdb;
128 border-collapse: collapse;
132 border: 1px solid #dbdbdb;
136 border: 1px solid #dbdbdb;
146 .column-entry-yellow {
148 background-color: #ffffd0;
150 .column-entry-yellow:hover {
151 background-color: #fffff0;
155 background-color: #ffd0d0;
157 .column-entry-red:hover {
158 background-color: #fff0f0;
160 .column-entry-green {
162 background-color: #d0ffd0;
164 .column-entry-green:hover {
165 background-color: #f0fff0;
182 background-color: #b3e6ff;
183 text-decoration: none;
185 .tooltip span.tooltip-content {
197 .tooltip span.tooltip-content:after {
204 border-top: 8px solid #000000;
205 border-right: 8px solid transparent;
206 border-left: 8px solid transparent;
208 :hover.tooltip span.tooltip-content {
218 border-collapse: collapse;
219 border-right: solid 1px #eee;
220 border-left: solid 1px #eee;
224 display: inline-block;
233 background-color: #f0f0f0;
237 const char *EndHeader
= "</head>";
239 const char *BeginCenteredDiv
= "<div class='centered'>";
241 const char *EndCenteredDiv
= "</div>";
243 const char *BeginSourceNameDiv
= "<div class='source-name-title'>";
245 const char *EndSourceNameDiv
= "</div>";
247 const char *BeginCodeTD
= "<td class='code'>";
249 const char *EndCodeTD
= "</td>";
251 const char *BeginPre
= "<pre>";
253 const char *EndPre
= "</pre>";
255 const char *BeginExpansionDiv
= "<div class='expansion-view'>";
257 const char *EndExpansionDiv
= "</div>";
259 const char *BeginTable
= "<table>";
261 const char *EndTable
= "</table>";
263 const char *ProjectTitleTag
= "h1";
265 const char *ReportTitleTag
= "h2";
267 const char *CreatedTimeTag
= "h4";
269 std::string
getPathToStyle(StringRef ViewPath
) {
270 std::string PathToStyle
;
271 std::string PathSep
= std::string(sys::path::get_separator());
272 unsigned NumSeps
= ViewPath
.count(PathSep
);
273 for (unsigned I
= 0, E
= NumSeps
; I
< E
; ++I
)
274 PathToStyle
+= ".." + PathSep
;
275 return PathToStyle
+ "style.css";
278 void emitPrelude(raw_ostream
&OS
, const CoverageViewOptions
&Opts
,
279 const std::string
&PathToStyle
= "") {
280 OS
<< "<!doctype html>"
284 // Link to a stylesheet if one is available. Otherwise, use the default style.
285 if (PathToStyle
.empty())
286 OS
<< "<style>" << CSSForCoverage
<< "</style>";
288 OS
<< "<link rel='stylesheet' type='text/css' href='"
289 << escape(PathToStyle
, Opts
) << "'>";
291 OS
<< EndHeader
<< "<body>";
294 void emitTableRow(raw_ostream
&OS
, const CoverageViewOptions
&Opts
,
295 const std::string
&FirstCol
, const FileCoverageSummary
&FCS
,
297 SmallVector
<std::string
, 8> Columns
;
299 // Format a coverage triple and add the result to the list of columns.
300 auto AddCoverageTripleToColumn
=
301 [&Columns
, &Opts
](unsigned Hit
, unsigned Total
, float Pctg
) {
304 raw_string_ostream RSO
{S
};
306 RSO
<< format("%*.2f", 7, Pctg
) << "% ";
309 RSO
<< '(' << Hit
<< '/' << Total
<< ')';
311 const char *CellClass
= "column-entry-yellow";
312 if (Pctg
>= Opts
.HighCovWatermark
)
313 CellClass
= "column-entry-green";
314 else if (Pctg
< Opts
.LowCovWatermark
)
315 CellClass
= "column-entry-red";
316 Columns
.emplace_back(tag("td", tag("pre", S
), CellClass
));
319 Columns
.emplace_back(tag("td", tag("pre", FirstCol
)));
320 AddCoverageTripleToColumn(FCS
.FunctionCoverage
.getExecuted(),
321 FCS
.FunctionCoverage
.getNumFunctions(),
322 FCS
.FunctionCoverage
.getPercentCovered());
323 if (Opts
.ShowInstantiationSummary
)
324 AddCoverageTripleToColumn(FCS
.InstantiationCoverage
.getExecuted(),
325 FCS
.InstantiationCoverage
.getNumFunctions(),
326 FCS
.InstantiationCoverage
.getPercentCovered());
327 AddCoverageTripleToColumn(FCS
.LineCoverage
.getCovered(),
328 FCS
.LineCoverage
.getNumLines(),
329 FCS
.LineCoverage
.getPercentCovered());
330 if (Opts
.ShowRegionSummary
)
331 AddCoverageTripleToColumn(FCS
.RegionCoverage
.getCovered(),
332 FCS
.RegionCoverage
.getNumRegions(),
333 FCS
.RegionCoverage
.getPercentCovered());
334 if (Opts
.ShowBranchSummary
)
335 AddCoverageTripleToColumn(FCS
.BranchCoverage
.getCovered(),
336 FCS
.BranchCoverage
.getNumBranches(),
337 FCS
.BranchCoverage
.getPercentCovered());
340 OS
<< tag("tr", join(Columns
.begin(), Columns
.end(), ""), "light-row-bold");
342 OS
<< tag("tr", join(Columns
.begin(), Columns
.end(), ""), "light-row");
345 void emitEpilog(raw_ostream
&OS
) {
350 } // anonymous namespace
352 Expected
<CoveragePrinter::OwnedStream
>
353 CoveragePrinterHTML::createViewFile(StringRef Path
, bool InToplevel
) {
354 auto OSOrErr
= createOutputStream(Path
, "html", InToplevel
);
358 OwnedStream OS
= std::move(OSOrErr
.get());
360 if (!Opts
.hasOutputDirectory()) {
361 emitPrelude(*OS
.get(), Opts
);
363 std::string ViewPath
= getOutputPath(Path
, "html", InToplevel
);
364 emitPrelude(*OS
.get(), Opts
, getPathToStyle(ViewPath
));
367 return std::move(OS
);
370 void CoveragePrinterHTML::closeViewFile(OwnedStream OS
) {
371 emitEpilog(*OS
.get());
374 /// Emit column labels for the table in the index.
375 static void emitColumnLabelsForIndex(raw_ostream
&OS
,
376 const CoverageViewOptions
&Opts
) {
377 SmallVector
<std::string
, 4> Columns
;
378 Columns
.emplace_back(tag("td", "Filename", "column-entry-bold"));
379 Columns
.emplace_back(tag("td", "Function Coverage", "column-entry-bold"));
380 if (Opts
.ShowInstantiationSummary
)
381 Columns
.emplace_back(
382 tag("td", "Instantiation Coverage", "column-entry-bold"));
383 Columns
.emplace_back(tag("td", "Line Coverage", "column-entry-bold"));
384 if (Opts
.ShowRegionSummary
)
385 Columns
.emplace_back(tag("td", "Region Coverage", "column-entry-bold"));
386 if (Opts
.ShowBranchSummary
)
387 Columns
.emplace_back(tag("td", "Branch Coverage", "column-entry-bold"));
388 OS
<< tag("tr", join(Columns
.begin(), Columns
.end(), ""));
392 CoveragePrinterHTML::buildLinkToFile(StringRef SF
,
393 const FileCoverageSummary
&FCS
) const {
394 SmallString
<128> LinkTextStr(sys::path::relative_path(FCS
.Name
));
395 sys::path::remove_dots(LinkTextStr
, /*remove_dot_dot=*/true);
396 sys::path::native(LinkTextStr
);
397 std::string LinkText
= escape(LinkTextStr
, Opts
);
398 std::string LinkTarget
=
399 escape(getOutputPath(SF
, "html", /*InToplevel=*/false), Opts
);
400 return a(LinkTarget
, LinkText
);
403 Error
CoveragePrinterHTML::emitStyleSheet() {
404 auto CSSOrErr
= createOutputStream("style", "css", /*InToplevel=*/true);
405 if (Error E
= CSSOrErr
.takeError())
408 OwnedStream CSS
= std::move(CSSOrErr
.get());
409 CSS
->operator<<(CSSForCoverage
);
411 return Error::success();
414 void CoveragePrinterHTML::emitReportHeader(raw_ostream
&OSRef
,
415 const std::string
&Title
) {
416 // Emit some basic information about the coverage report.
417 if (Opts
.hasProjectTitle())
418 OSRef
<< tag(ProjectTitleTag
, escape(Opts
.ProjectTitle
, Opts
));
419 OSRef
<< tag(ReportTitleTag
, Title
);
420 if (Opts
.hasCreatedTime())
421 OSRef
<< tag(CreatedTimeTag
, escape(Opts
.CreatedTimeStr
, Opts
));
423 // Emit a link to some documentation.
424 OSRef
<< tag("p", "Click " +
425 a("http://clang.llvm.org/docs/"
426 "SourceBasedCodeCoverage.html#interpreting-reports",
428 " for information about interpreting this report.");
430 // Emit a table containing links to reports for each file in the covmapping.
431 // Exclude files which don't contain any regions.
432 OSRef
<< BeginCenteredDiv
<< BeginTable
;
433 emitColumnLabelsForIndex(OSRef
, Opts
);
436 /// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is
437 /// false, link the summary to \p SF.
438 void CoveragePrinterHTML::emitFileSummary(raw_ostream
&OS
, StringRef SF
,
439 const FileCoverageSummary
&FCS
,
440 bool IsTotals
) const {
441 // Simplify the display file path, and wrap it in a link if requested.
442 std::string Filename
;
444 Filename
= std::string(SF
);
446 Filename
= buildLinkToFile(SF
, FCS
);
449 emitTableRow(OS
, Opts
, Filename
, FCS
, IsTotals
);
452 Error
CoveragePrinterHTML::createIndexFile(
453 ArrayRef
<std::string
> SourceFiles
, const CoverageMapping
&Coverage
,
454 const CoverageFiltersMatchAll
&Filters
) {
455 // Emit the default stylesheet.
456 if (Error E
= emitStyleSheet())
459 // Emit a file index along with some coverage statistics.
460 auto OSOrErr
= createOutputStream("index", "html", /*InToplevel=*/true);
461 if (Error E
= OSOrErr
.takeError())
463 auto OS
= std::move(OSOrErr
.get());
464 raw_ostream
&OSRef
= *OS
.get();
466 assert(Opts
.hasOutputDirectory() && "No output directory for index file");
467 emitPrelude(OSRef
, Opts
, getPathToStyle(""));
469 emitReportHeader(OSRef
, "Coverage Report");
471 FileCoverageSummary
Totals("TOTALS");
472 auto FileReports
= CoverageReport::prepareFileReports(
473 Coverage
, Totals
, SourceFiles
, Opts
, Filters
);
474 bool EmptyFiles
= false;
475 for (unsigned I
= 0, E
= FileReports
.size(); I
< E
; ++I
) {
476 if (FileReports
[I
].FunctionCoverage
.getNumFunctions())
477 emitFileSummary(OSRef
, SourceFiles
[I
], FileReports
[I
]);
481 emitFileSummary(OSRef
, "Totals", Totals
, /*IsTotals=*/true);
482 OSRef
<< EndTable
<< EndCenteredDiv
;
484 // Emit links to files which don't contain any functions. These are normally
485 // not very useful, but could be relevant for code which abuses the
487 if (EmptyFiles
&& Filters
.empty()) {
488 OSRef
<< tag("p", "Files which contain no functions. (These "
489 "files contain code pulled into other files "
490 "by the preprocessor.)\n");
491 OSRef
<< BeginCenteredDiv
<< BeginTable
;
492 for (unsigned I
= 0, E
= FileReports
.size(); I
< E
; ++I
)
493 if (!FileReports
[I
].FunctionCoverage
.getNumFunctions()) {
494 std::string Link
= buildLinkToFile(SourceFiles
[I
], FileReports
[I
]);
495 OSRef
<< tag("tr", tag("td", tag("pre", Link
)), "light-row") << '\n';
497 OSRef
<< EndTable
<< EndCenteredDiv
;
500 OSRef
<< tag("h5", escape(Opts
.getLLVMVersionString(), Opts
));
503 return Error::success();
506 struct CoveragePrinterHTMLDirectory::Reporter
: public DirectoryCoverageReport
{
507 CoveragePrinterHTMLDirectory
&Printer
;
509 Reporter(CoveragePrinterHTMLDirectory
&Printer
,
510 const coverage::CoverageMapping
&Coverage
,
511 const CoverageFiltersMatchAll
&Filters
)
512 : DirectoryCoverageReport(Printer
.Opts
, Coverage
, Filters
),
515 Error
generateSubDirectoryReport(SubFileReports
&&SubFiles
,
516 SubDirReports
&&SubDirs
,
517 FileCoverageSummary
&&SubTotals
) override
{
518 auto &LCPath
= SubTotals
.Name
;
519 assert(Options
.hasOutputDirectory() &&
520 "No output directory for index file");
522 SmallString
<128> OSPath
= LCPath
;
523 sys::path::append(OSPath
, "index");
524 auto OSOrErr
= Printer
.createOutputStream(OSPath
, "html",
525 /*InToplevel=*/false);
526 if (auto E
= OSOrErr
.takeError())
528 auto OS
= std::move(OSOrErr
.get());
529 raw_ostream
&OSRef
= *OS
.get();
531 auto IndexHtmlPath
= Printer
.getOutputPath((LCPath
+ "index").str(), "html",
532 /*InToplevel=*/false);
533 emitPrelude(OSRef
, Options
, getPathToStyle(IndexHtmlPath
));
535 auto NavLink
= buildTitleLinks(LCPath
);
536 Printer
.emitReportHeader(OSRef
, "Coverage Report (" + NavLink
+ ")");
538 std::vector
<const FileCoverageSummary
*> EmptyFiles
;
540 // Make directories at the top of the table.
541 for (auto &&SubDir
: SubDirs
) {
542 auto &Report
= SubDir
.second
.first
;
543 if (!Report
.FunctionCoverage
.getNumFunctions())
544 EmptyFiles
.push_back(&Report
);
546 emitTableRow(OSRef
, Options
, buildRelLinkToFile(Report
.Name
), Report
,
550 for (auto &&SubFile
: SubFiles
) {
551 auto &Report
= SubFile
.second
;
552 if (!Report
.FunctionCoverage
.getNumFunctions())
553 EmptyFiles
.push_back(&Report
);
555 emitTableRow(OSRef
, Options
, buildRelLinkToFile(Report
.Name
), Report
,
559 // Emit the totals row.
560 emitTableRow(OSRef
, Options
, "Totals", SubTotals
, /*IsTotals=*/false);
561 OSRef
<< EndTable
<< EndCenteredDiv
;
563 // Emit links to files which don't contain any functions. These are normally
564 // not very useful, but could be relevant for code which abuses the
566 if (!EmptyFiles
.empty()) {
567 OSRef
<< tag("p", "Files which contain no functions. (These "
568 "files contain code pulled into other files "
569 "by the preprocessor.)\n");
570 OSRef
<< BeginCenteredDiv
<< BeginTable
;
571 for (auto FCS
: EmptyFiles
) {
572 auto Link
= buildRelLinkToFile(FCS
->Name
);
573 OSRef
<< tag("tr", tag("td", tag("pre", Link
)), "light-row") << '\n';
575 OSRef
<< EndTable
<< EndCenteredDiv
;
579 OSRef
<< tag("h5", escape(Options
.getLLVMVersionString(), Options
));
582 return Error::success();
585 /// Make a title with hyperlinks to the index.html files of each hierarchy
587 std::string
buildTitleLinks(StringRef LCPath
) const {
588 // For each report level in LCPStack, extract the path component and
589 // calculate the number of "../" relative to current LCPath.
590 SmallVector
<std::pair
<SmallString
<128>, unsigned>, 16> Components
;
592 auto Iter
= LCPStack
.begin(), IterE
= LCPStack
.end();
593 SmallString
<128> RootPath
;
595 // If llvm-cov works on relative coverage mapping data, the LCP of
596 // all source file paths can be 0, which makes the title path empty.
597 // As we like adding a slash at the back of the path to indicate a
598 // directory, in this case, we use "." as the root path to make it
599 // not be confused with the root path "/".
602 RootPath
= LCPath
.substr(0, *Iter
);
603 sys::path::native(RootPath
);
604 sys::path::remove_dots(RootPath
, /*remove_dot_dot=*/true);
606 Components
.emplace_back(std::move(RootPath
), 0);
608 for (auto Last
= *Iter
; ++Iter
!= IterE
; Last
= *Iter
) {
609 SmallString
<128> SubPath
= LCPath
.substr(Last
, *Iter
- Last
);
610 sys::path::native(SubPath
);
611 sys::path::remove_dots(SubPath
, /*remove_dot_dot=*/true);
612 auto Level
= unsigned(SubPath
.count(sys::path::get_separator())) + 1;
613 Components
.back().second
+= Level
;
614 Components
.emplace_back(std::move(SubPath
), Level
);
617 // Then we make the title accroding to Components.
619 for (auto I
= Components
.begin(), E
= Components
.end();;) {
620 auto &Name
= I
->first
;
622 S
+= a("./index.html", Name
);
623 S
+= sys::path::get_separator();
627 SmallString
<128> Link
;
628 for (unsigned J
= I
->second
; J
> 0; --J
)
630 Link
+= "index.html";
632 S
+= sys::path::get_separator();
637 std::string
buildRelLinkToFile(StringRef RelPath
) const {
638 SmallString
<128> LinkTextStr(RelPath
);
639 sys::path::native(LinkTextStr
);
641 // remove_dots will remove trailing slash, so we need to check before it.
642 auto IsDir
= LinkTextStr
.endswith(sys::path::get_separator());
643 sys::path::remove_dots(LinkTextStr
, /*remove_dot_dot=*/true);
645 SmallString
<128> LinkTargetStr(LinkTextStr
);
647 LinkTextStr
+= sys::path::get_separator();
648 sys::path::append(LinkTargetStr
, "index.html");
650 LinkTargetStr
+= ".html";
653 auto LinkText
= escape(LinkTextStr
, Options
);
654 auto LinkTarget
= escape(LinkTargetStr
, Options
);
655 return a(LinkTarget
, LinkText
);
659 Error
CoveragePrinterHTMLDirectory::createIndexFile(
660 ArrayRef
<std::string
> SourceFiles
, const CoverageMapping
&Coverage
,
661 const CoverageFiltersMatchAll
&Filters
) {
662 // The createSubIndexFile function only works when SourceFiles is
663 // more than one. So we fallback to CoveragePrinterHTML when it is.
664 if (SourceFiles
.size() <= 1)
665 return CoveragePrinterHTML::createIndexFile(SourceFiles
, Coverage
, Filters
);
667 // Emit the default stylesheet.
668 if (Error E
= emitStyleSheet())
671 // Emit index files in every subdirectory.
672 Reporter
Report(*this, Coverage
, Filters
);
673 auto TotalsOrErr
= Report
.prepareDirectoryReports(SourceFiles
);
674 if (auto E
= TotalsOrErr
.takeError())
676 auto &LCPath
= TotalsOrErr
->Name
;
678 // Emit the top level index file. Top level index file is just a redirection
679 // to the index file in the LCP directory.
680 auto OSOrErr
= createOutputStream("index", "html", /*InToplevel=*/true);
681 if (auto E
= OSOrErr
.takeError())
683 auto OS
= std::move(OSOrErr
.get());
684 auto LCPIndexFilePath
=
685 getOutputPath((LCPath
+ "index").str(), "html", /*InToplevel=*/false);
686 *OS
.get() << R
"(<!DOCTYPE html>
689 <meta http-equiv="Refresh
" content="0; url
=')"
690 << LCPIndexFilePath << R"('" />
696 return Error::success();
699 void SourceCoverageViewHTML::renderViewHeader(raw_ostream
&OS
) {
700 OS
<< BeginCenteredDiv
<< BeginTable
;
703 void SourceCoverageViewHTML::renderViewFooter(raw_ostream
&OS
) {
704 OS
<< EndTable
<< EndCenteredDiv
;
707 void SourceCoverageViewHTML::renderSourceName(raw_ostream
&OS
, bool WholeFile
) {
708 OS
<< BeginSourceNameDiv
<< tag("pre", escape(getSourceName(), getOptions()))
712 void SourceCoverageViewHTML::renderLinePrefix(raw_ostream
&OS
, unsigned) {
716 void SourceCoverageViewHTML::renderLineSuffix(raw_ostream
&OS
, unsigned) {
717 // If this view has sub-views, renderLine() cannot close the view's cell.
718 // Take care of it here, after all sub-views have been rendered.
724 void SourceCoverageViewHTML::renderViewDivider(raw_ostream
&, unsigned) {
725 // The table-based output makes view dividers unnecessary.
728 void SourceCoverageViewHTML::renderLine(raw_ostream
&OS
, LineRef L
,
729 const LineCoverageStats
&LCS
,
730 unsigned ExpansionCol
, unsigned) {
731 StringRef Line
= L
.Line
;
732 unsigned LineNo
= L
.LineNo
;
734 // Steps for handling text-escaping, highlighting, and tooltip creation:
736 // 1. Split the line into N+1 snippets, where N = |Segments|. The first
737 // snippet starts from Col=1 and ends at the start of the first segment.
738 // The last snippet starts at the last mapped column in the line and ends
739 // at the end of the line. Both are required but may be empty.
741 SmallVector
<std::string
, 8> Snippets
;
742 CoverageSegmentArray Segments
= LCS
.getLineSegments();
745 auto Snip
= [&](unsigned Start
, unsigned Len
) {
746 Snippets
.push_back(std::string(Line
.substr(Start
, Len
)));
750 Snip(LCol
- 1, Segments
.empty() ? 0 : (Segments
.front()->Col
- 1));
752 for (unsigned I
= 1, E
= Segments
.size(); I
< E
; ++I
)
753 Snip(LCol
- 1, Segments
[I
]->Col
- LCol
);
755 // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1.
756 Snip(LCol
- 1, Line
.size() + 1 - LCol
);
758 // 2. Escape all of the snippets.
760 for (unsigned I
= 0, E
= Snippets
.size(); I
< E
; ++I
)
761 Snippets
[I
] = escape(Snippets
[I
], getOptions());
763 // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment
764 // 1 to set the highlight for snippet 2, segment 2 to set the highlight for
765 // snippet 3, and so on.
767 std::optional
<StringRef
> Color
;
768 SmallVector
<std::pair
<unsigned, unsigned>, 2> HighlightedRanges
;
769 auto Highlight
= [&](const std::string
&Snippet
, unsigned LC
, unsigned RC
) {
770 if (getOptions().Debug
)
771 HighlightedRanges
.emplace_back(LC
, RC
);
772 return tag("span", Snippet
, std::string(*Color
));
775 auto CheckIfUncovered
= [&](const CoverageSegment
*S
) {
776 return S
&& (!S
->IsGapRegion
|| (Color
&& *Color
== "red")) &&
777 S
->HasCount
&& S
->Count
== 0;
780 if (CheckIfUncovered(LCS
.getWrappedSegment())) {
782 if (!Snippets
[0].empty())
783 Snippets
[0] = Highlight(Snippets
[0], 1, 1 + Snippets
[0].size());
786 for (unsigned I
= 0, E
= Segments
.size(); I
< E
; ++I
) {
787 const auto *CurSeg
= Segments
[I
];
788 if (CheckIfUncovered(CurSeg
))
790 else if (CurSeg
->Col
== ExpansionCol
)
793 Color
= std::nullopt
;
796 Snippets
[I
+ 1] = Highlight(Snippets
[I
+ 1], CurSeg
->Col
,
797 CurSeg
->Col
+ Snippets
[I
+ 1].size());
800 if (Color
&& Segments
.empty())
801 Snippets
.back() = Highlight(Snippets
.back(), 1, 1 + Snippets
.back().size());
803 if (getOptions().Debug
) {
804 for (const auto &Range
: HighlightedRanges
) {
805 errs() << "Highlighted line " << LineNo
<< ", " << Range
.first
<< " -> ";
806 if (Range
.second
== 0)
809 errs() << Range
.second
;
814 // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
815 // sub-line region count tooltips if needed.
817 if (shouldRenderRegionMarkers(LCS
)) {
818 // Just consider the segments which start *and* end on this line.
819 for (unsigned I
= 0, E
= Segments
.size() - 1; I
< E
; ++I
) {
820 const auto *CurSeg
= Segments
[I
];
821 if (!CurSeg
->IsRegionEntry
)
823 if (CurSeg
->Count
== LCS
.getExecutionCount())
827 tag("div", Snippets
[I
+ 1] + tag("span", formatCount(CurSeg
->Count
),
831 if (getOptions().Debug
)
832 errs() << "Marker at " << CurSeg
->Line
<< ":" << CurSeg
->Col
<< " = "
833 << formatCount(CurSeg
->Count
) << "\n";
839 for (const auto &Snippet
: Snippets
)
843 // If there are no sub-views left to attach to this cell, end the cell.
844 // Otherwise, end it after the sub-views are rendered (renderLineSuffix()).
849 void SourceCoverageViewHTML::renderLineCoverageColumn(
850 raw_ostream
&OS
, const LineCoverageStats
&Line
) {
853 Count
= tag("pre", formatCount(Line
.getExecutionCount()));
854 std::string CoverageClass
=
855 (Line
.getExecutionCount() > 0) ? "covered-line" : "uncovered-line";
856 OS
<< tag("td", Count
, CoverageClass
);
859 void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream
&OS
,
861 std::string LineNoStr
= utostr(uint64_t(LineNo
));
862 std::string TargetName
= "L" + LineNoStr
;
863 OS
<< tag("td", a("#" + TargetName
, tag("pre", LineNoStr
), TargetName
),
867 void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream
&,
868 const LineCoverageStats
&Line
,
870 // Region markers are rendered in-line using tooltips.
873 void SourceCoverageViewHTML::renderExpansionSite(raw_ostream
&OS
, LineRef L
,
874 const LineCoverageStats
&LCS
,
875 unsigned ExpansionCol
,
876 unsigned ViewDepth
) {
877 // Render the line containing the expansion site. No extra formatting needed.
878 renderLine(OS
, L
, LCS
, ExpansionCol
, ViewDepth
);
881 void SourceCoverageViewHTML::renderExpansionView(raw_ostream
&OS
,
883 unsigned ViewDepth
) {
884 OS
<< BeginExpansionDiv
;
885 ESV
.View
->print(OS
, /*WholeFile=*/false, /*ShowSourceName=*/false,
886 /*ShowTitle=*/false, ViewDepth
+ 1);
887 OS
<< EndExpansionDiv
;
890 void SourceCoverageViewHTML::renderBranchView(raw_ostream
&OS
, BranchView
&BRV
,
891 unsigned ViewDepth
) {
892 // Render the child subview.
893 if (getOptions().Debug
)
894 errs() << "Branch at line " << BRV
.getLine() << '\n';
896 OS
<< BeginExpansionDiv
;
898 for (const auto &R
: BRV
.Regions
) {
899 // Calculate TruePercent and False Percent.
900 double TruePercent
= 0.0;
901 double FalsePercent
= 0.0;
902 // FIXME: It may overflow when the data is too large, but I have not
903 // encountered it in actual use, and not sure whether to use __uint128_t.
904 uint64_t Total
= R
.ExecutionCount
+ R
.FalseExecutionCount
;
906 if (!getOptions().ShowBranchCounts
&& Total
!= 0) {
907 TruePercent
= ((double)(R
.ExecutionCount
) / (double)Total
) * 100.0;
908 FalsePercent
= ((double)(R
.FalseExecutionCount
) / (double)Total
) * 100.0;
911 // Display Line + Column.
912 std::string LineNoStr
= utostr(uint64_t(R
.LineStart
));
913 std::string ColNoStr
= utostr(uint64_t(R
.ColumnStart
));
914 std::string TargetName
= "L" + LineNoStr
;
918 a("#" + TargetName
, tag("span", LineNoStr
+ ":" + ColNoStr
),
924 OS
<< "Folded - Ignored]\n";
928 // Display TrueCount or TruePercent.
929 std::string TrueColor
= R
.ExecutionCount
? "None" : "red";
930 std::string TrueCovClass
=
931 (R
.ExecutionCount
> 0) ? "covered-line" : "uncovered-line";
933 OS
<< tag("span", "True", TrueColor
);
935 if (getOptions().ShowBranchCounts
)
936 OS
<< tag("span", formatCount(R
.ExecutionCount
), TrueCovClass
) << ", ";
938 OS
<< format("%0.2f", TruePercent
) << "%, ";
940 // Display FalseCount or FalsePercent.
941 std::string FalseColor
= R
.FalseExecutionCount
? "None" : "red";
942 std::string FalseCovClass
=
943 (R
.FalseExecutionCount
> 0) ? "covered-line" : "uncovered-line";
945 OS
<< tag("span", "False", FalseColor
);
947 if (getOptions().ShowBranchCounts
)
948 OS
<< tag("span", formatCount(R
.FalseExecutionCount
), FalseCovClass
);
950 OS
<< format("%0.2f", FalsePercent
) << "%";
955 OS
<< EndExpansionDiv
;
958 void SourceCoverageViewHTML::renderInstantiationView(raw_ostream
&OS
,
959 InstantiationView
&ISV
,
960 unsigned ViewDepth
) {
961 OS
<< BeginExpansionDiv
;
963 OS
<< BeginSourceNameDiv
965 escape("Unexecuted instantiation: " + ISV
.FunctionName
.str(),
969 ISV
.View
->print(OS
, /*WholeFile=*/false, /*ShowSourceName=*/true,
970 /*ShowTitle=*/false, ViewDepth
);
971 OS
<< EndExpansionDiv
;
974 void SourceCoverageViewHTML::renderTitle(raw_ostream
&OS
, StringRef Title
) {
975 if (getOptions().hasProjectTitle())
976 OS
<< tag(ProjectTitleTag
, escape(getOptions().ProjectTitle
, getOptions()));
977 OS
<< tag(ReportTitleTag
, escape(Title
, getOptions()));
978 if (getOptions().hasCreatedTime())
979 OS
<< tag(CreatedTimeTag
,
980 escape(getOptions().CreatedTimeStr
, getOptions()));
983 void SourceCoverageViewHTML::renderTableHeader(raw_ostream
&OS
,
984 unsigned FirstUncoveredLineNo
,
985 unsigned ViewDepth
) {
986 std::string SourceLabel
;
987 if (FirstUncoveredLineNo
== 0) {
988 SourceLabel
= tag("td", tag("pre", "Source"));
990 std::string LinkTarget
= "#L" + utostr(uint64_t(FirstUncoveredLineNo
));
992 tag("td", tag("pre", "Source (" +
993 a(LinkTarget
, "jump to first uncovered line") +
997 renderLinePrefix(OS
, ViewDepth
);
998 OS
<< tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count"))
1000 renderLineSuffix(OS
, ViewDepth
);