SourceCoverageViewHTML.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. //===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
  2. //
  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
  6. //
  7. //===----------------------------------------------------------------------===//
  8. ///
  9. /// \file This file implements the html coverage renderer.
  10. ///
  11. //===----------------------------------------------------------------------===//
  12. #include "CoverageReport.h"
  13. #include "SourceCoverageViewHTML.h"
  14. #include "llvm/ADT/SmallString.h"
  15. #include "llvm/ADT/StringExtras.h"
  16. #include "llvm/Support/Format.h"
  17. #include "llvm/Support/Path.h"
  18. #include <optional>
  19. using namespace llvm;
  20. namespace {
  21. // Return a string with the special characters in \p Str escaped.
  22. std::string escape(StringRef Str, const CoverageViewOptions &Opts) {
  23. std::string TabExpandedResult;
  24. unsigned ColNum = 0; // Record the column number.
  25. for (char C : Str) {
  26. if (C == '\t') {
  27. // Replace '\t' with up to TabSize spaces.
  28. unsigned NumSpaces = Opts.TabSize - (ColNum % Opts.TabSize);
  29. TabExpandedResult.append(NumSpaces, ' ');
  30. ColNum += NumSpaces;
  31. } else {
  32. TabExpandedResult += C;
  33. if (C == '\n' || C == '\r')
  34. ColNum = 0;
  35. else
  36. ++ColNum;
  37. }
  38. }
  39. std::string EscapedHTML;
  40. {
  41. raw_string_ostream OS{EscapedHTML};
  42. printHTMLEscaped(TabExpandedResult, OS);
  43. }
  44. return EscapedHTML;
  45. }
  46. // Create a \p Name tag around \p Str, and optionally set its \p ClassName.
  47. std::string tag(const std::string &Name, const std::string &Str,
  48. const std::string &ClassName = "") {
  49. std::string Tag = "<" + Name;
  50. if (!ClassName.empty())
  51. Tag += " class='" + ClassName + "'";
  52. return Tag + ">" + Str + "</" + Name + ">";
  53. }
  54. // Create an anchor to \p Link with the label \p Str.
  55. std::string a(const std::string &Link, const std::string &Str,
  56. const std::string &TargetName = "") {
  57. std::string Name = TargetName.empty() ? "" : ("name='" + TargetName + "' ");
  58. return "<a " + Name + "href='" + Link + "'>" + Str + "</a>";
  59. }
  60. const char *BeginHeader =
  61. "<head>"
  62. "<meta name='viewport' content='width=device-width,initial-scale=1'>"
  63. "<meta charset='UTF-8'>";
  64. const char *CSSForCoverage =
  65. R"(.red {
  66. background-color: #ffd0d0;
  67. }
  68. .cyan {
  69. background-color: cyan;
  70. }
  71. body {
  72. font-family: -apple-system, sans-serif;
  73. }
  74. pre {
  75. margin-top: 0px !important;
  76. margin-bottom: 0px !important;
  77. }
  78. .source-name-title {
  79. padding: 5px 10px;
  80. border-bottom: 1px solid #dbdbdb;
  81. background-color: #eee;
  82. line-height: 35px;
  83. }
  84. .centered {
  85. display: table;
  86. margin-left: left;
  87. margin-right: auto;
  88. border: 1px solid #dbdbdb;
  89. border-radius: 3px;
  90. }
  91. .expansion-view {
  92. background-color: rgba(0, 0, 0, 0);
  93. margin-left: 0px;
  94. margin-top: 5px;
  95. margin-right: 5px;
  96. margin-bottom: 5px;
  97. border: 1px solid #dbdbdb;
  98. border-radius: 3px;
  99. }
  100. table {
  101. border-collapse: collapse;
  102. }
  103. .light-row {
  104. background: #ffffff;
  105. border: 1px solid #dbdbdb;
  106. }
  107. .light-row-bold {
  108. background: #ffffff;
  109. border: 1px solid #dbdbdb;
  110. font-weight: bold;
  111. }
  112. .column-entry {
  113. text-align: left;
  114. }
  115. .column-entry-bold {
  116. font-weight: bold;
  117. text-align: left;
  118. }
  119. .column-entry-yellow {
  120. text-align: left;
  121. background-color: #ffffd0;
  122. }
  123. .column-entry-yellow:hover {
  124. background-color: #fffff0;
  125. }
  126. .column-entry-red {
  127. text-align: left;
  128. background-color: #ffd0d0;
  129. }
  130. .column-entry-red:hover {
  131. background-color: #fff0f0;
  132. }
  133. .column-entry-green {
  134. text-align: left;
  135. background-color: #d0ffd0;
  136. }
  137. .column-entry-green:hover {
  138. background-color: #f0fff0;
  139. }
  140. .line-number {
  141. text-align: right;
  142. color: #aaa;
  143. }
  144. .covered-line {
  145. text-align: right;
  146. color: #0080ff;
  147. }
  148. .uncovered-line {
  149. text-align: right;
  150. color: #ff3300;
  151. }
  152. .tooltip {
  153. position: relative;
  154. display: inline;
  155. background-color: #b3e6ff;
  156. text-decoration: none;
  157. }
  158. .tooltip span.tooltip-content {
  159. position: absolute;
  160. width: 100px;
  161. margin-left: -50px;
  162. color: #FFFFFF;
  163. background: #000000;
  164. height: 30px;
  165. line-height: 30px;
  166. text-align: center;
  167. visibility: hidden;
  168. border-radius: 6px;
  169. }
  170. .tooltip span.tooltip-content:after {
  171. content: '';
  172. position: absolute;
  173. top: 100%;
  174. left: 50%;
  175. margin-left: -8px;
  176. width: 0; height: 0;
  177. border-top: 8px solid #000000;
  178. border-right: 8px solid transparent;
  179. border-left: 8px solid transparent;
  180. }
  181. :hover.tooltip span.tooltip-content {
  182. visibility: visible;
  183. opacity: 0.8;
  184. bottom: 30px;
  185. left: 50%;
  186. z-index: 999;
  187. }
  188. th, td {
  189. vertical-align: top;
  190. padding: 2px 8px;
  191. border-collapse: collapse;
  192. border-right: solid 1px #eee;
  193. border-left: solid 1px #eee;
  194. text-align: left;
  195. }
  196. td pre {
  197. display: inline-block;
  198. }
  199. td:first-child {
  200. border-left: none;
  201. }
  202. td:last-child {
  203. border-right: none;
  204. }
  205. tr:hover {
  206. background-color: #f0f0f0;
  207. }
  208. )";
  209. const char *EndHeader = "</head>";
  210. const char *BeginCenteredDiv = "<div class='centered'>";
  211. const char *EndCenteredDiv = "</div>";
  212. const char *BeginSourceNameDiv = "<div class='source-name-title'>";
  213. const char *EndSourceNameDiv = "</div>";
  214. const char *BeginCodeTD = "<td class='code'>";
  215. const char *EndCodeTD = "</td>";
  216. const char *BeginPre = "<pre>";
  217. const char *EndPre = "</pre>";
  218. const char *BeginExpansionDiv = "<div class='expansion-view'>";
  219. const char *EndExpansionDiv = "</div>";
  220. const char *BeginTable = "<table>";
  221. const char *EndTable = "</table>";
  222. const char *ProjectTitleTag = "h1";
  223. const char *ReportTitleTag = "h2";
  224. const char *CreatedTimeTag = "h4";
  225. std::string getPathToStyle(StringRef ViewPath) {
  226. std::string PathToStyle;
  227. std::string PathSep = std::string(sys::path::get_separator());
  228. unsigned NumSeps = ViewPath.count(PathSep);
  229. for (unsigned I = 0, E = NumSeps; I < E; ++I)
  230. PathToStyle += ".." + PathSep;
  231. return PathToStyle + "style.css";
  232. }
  233. void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
  234. const std::string &PathToStyle = "") {
  235. OS << "<!doctype html>"
  236. "<html>"
  237. << BeginHeader;
  238. // Link to a stylesheet if one is available. Otherwise, use the default style.
  239. if (PathToStyle.empty())
  240. OS << "<style>" << CSSForCoverage << "</style>";
  241. else
  242. OS << "<link rel='stylesheet' type='text/css' href='"
  243. << escape(PathToStyle, Opts) << "'>";
  244. OS << EndHeader << "<body>";
  245. }
  246. void emitEpilog(raw_ostream &OS) {
  247. OS << "</body>"
  248. << "</html>";
  249. }
  250. } // anonymous namespace
  251. Expected<CoveragePrinter::OwnedStream>
  252. CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
  253. auto OSOrErr = createOutputStream(Path, "html", InToplevel);
  254. if (!OSOrErr)
  255. return OSOrErr;
  256. OwnedStream OS = std::move(OSOrErr.get());
  257. if (!Opts.hasOutputDirectory()) {
  258. emitPrelude(*OS.get(), Opts);
  259. } else {
  260. std::string ViewPath = getOutputPath(Path, "html", InToplevel);
  261. emitPrelude(*OS.get(), Opts, getPathToStyle(ViewPath));
  262. }
  263. return std::move(OS);
  264. }
  265. void CoveragePrinterHTML::closeViewFile(OwnedStream OS) {
  266. emitEpilog(*OS.get());
  267. }
  268. /// Emit column labels for the table in the index.
  269. static void emitColumnLabelsForIndex(raw_ostream &OS,
  270. const CoverageViewOptions &Opts) {
  271. SmallVector<std::string, 4> Columns;
  272. Columns.emplace_back(tag("td", "Filename", "column-entry-bold"));
  273. Columns.emplace_back(tag("td", "Function Coverage", "column-entry-bold"));
  274. if (Opts.ShowInstantiationSummary)
  275. Columns.emplace_back(
  276. tag("td", "Instantiation Coverage", "column-entry-bold"));
  277. Columns.emplace_back(tag("td", "Line Coverage", "column-entry-bold"));
  278. if (Opts.ShowRegionSummary)
  279. Columns.emplace_back(tag("td", "Region Coverage", "column-entry-bold"));
  280. if (Opts.ShowBranchSummary)
  281. Columns.emplace_back(tag("td", "Branch Coverage", "column-entry-bold"));
  282. OS << tag("tr", join(Columns.begin(), Columns.end(), ""));
  283. }
  284. std::string
  285. CoveragePrinterHTML::buildLinkToFile(StringRef SF,
  286. const FileCoverageSummary &FCS) const {
  287. SmallString<128> LinkTextStr(sys::path::relative_path(FCS.Name));
  288. sys::path::remove_dots(LinkTextStr, /*remove_dot_dot=*/true);
  289. sys::path::native(LinkTextStr);
  290. std::string LinkText = escape(LinkTextStr, Opts);
  291. std::string LinkTarget =
  292. escape(getOutputPath(SF, "html", /*InToplevel=*/false), Opts);
  293. return a(LinkTarget, LinkText);
  294. }
  295. /// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is
  296. /// false, link the summary to \p SF.
  297. void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF,
  298. const FileCoverageSummary &FCS,
  299. bool IsTotals) const {
  300. SmallVector<std::string, 8> Columns;
  301. // Format a coverage triple and add the result to the list of columns.
  302. auto AddCoverageTripleToColumn =
  303. [&Columns, this](unsigned Hit, unsigned Total, float Pctg) {
  304. std::string S;
  305. {
  306. raw_string_ostream RSO{S};
  307. if (Total)
  308. RSO << format("%*.2f", 7, Pctg) << "% ";
  309. else
  310. RSO << "- ";
  311. RSO << '(' << Hit << '/' << Total << ')';
  312. }
  313. const char *CellClass = "column-entry-yellow";
  314. if (Pctg >= Opts.HighCovWatermark)
  315. CellClass = "column-entry-green";
  316. else if (Pctg < Opts.LowCovWatermark)
  317. CellClass = "column-entry-red";
  318. Columns.emplace_back(tag("td", tag("pre", S), CellClass));
  319. };
  320. // Simplify the display file path, and wrap it in a link if requested.
  321. std::string Filename;
  322. if (IsTotals) {
  323. Filename = std::string(SF);
  324. } else {
  325. Filename = buildLinkToFile(SF, FCS);
  326. }
  327. Columns.emplace_back(tag("td", tag("pre", Filename)));
  328. AddCoverageTripleToColumn(FCS.FunctionCoverage.getExecuted(),
  329. FCS.FunctionCoverage.getNumFunctions(),
  330. FCS.FunctionCoverage.getPercentCovered());
  331. if (Opts.ShowInstantiationSummary)
  332. AddCoverageTripleToColumn(FCS.InstantiationCoverage.getExecuted(),
  333. FCS.InstantiationCoverage.getNumFunctions(),
  334. FCS.InstantiationCoverage.getPercentCovered());
  335. AddCoverageTripleToColumn(FCS.LineCoverage.getCovered(),
  336. FCS.LineCoverage.getNumLines(),
  337. FCS.LineCoverage.getPercentCovered());
  338. if (Opts.ShowRegionSummary)
  339. AddCoverageTripleToColumn(FCS.RegionCoverage.getCovered(),
  340. FCS.RegionCoverage.getNumRegions(),
  341. FCS.RegionCoverage.getPercentCovered());
  342. if (Opts.ShowBranchSummary)
  343. AddCoverageTripleToColumn(FCS.BranchCoverage.getCovered(),
  344. FCS.BranchCoverage.getNumBranches(),
  345. FCS.BranchCoverage.getPercentCovered());
  346. if (IsTotals)
  347. OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold");
  348. else
  349. OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row");
  350. }
  351. Error CoveragePrinterHTML::createIndexFile(
  352. ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
  353. const CoverageFiltersMatchAll &Filters) {
  354. // Emit the default stylesheet.
  355. auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true);
  356. if (Error E = CSSOrErr.takeError())
  357. return E;
  358. OwnedStream CSS = std::move(CSSOrErr.get());
  359. CSS->operator<<(CSSForCoverage);
  360. // Emit a file index along with some coverage statistics.
  361. auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
  362. if (Error E = OSOrErr.takeError())
  363. return E;
  364. auto OS = std::move(OSOrErr.get());
  365. raw_ostream &OSRef = *OS.get();
  366. assert(Opts.hasOutputDirectory() && "No output directory for index file");
  367. emitPrelude(OSRef, Opts, getPathToStyle(""));
  368. // Emit some basic information about the coverage report.
  369. if (Opts.hasProjectTitle())
  370. OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts));
  371. OSRef << tag(ReportTitleTag, "Coverage Report");
  372. if (Opts.hasCreatedTime())
  373. OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts));
  374. // Emit a link to some documentation.
  375. OSRef << tag("p", "Click " +
  376. a("http://clang.llvm.org/docs/"
  377. "SourceBasedCodeCoverage.html#interpreting-reports",
  378. "here") +
  379. " for information about interpreting this report.");
  380. // Emit a table containing links to reports for each file in the covmapping.
  381. // Exclude files which don't contain any regions.
  382. OSRef << BeginCenteredDiv << BeginTable;
  383. emitColumnLabelsForIndex(OSRef, Opts);
  384. FileCoverageSummary Totals("TOTALS");
  385. auto FileReports = CoverageReport::prepareFileReports(
  386. Coverage, Totals, SourceFiles, Opts, Filters);
  387. bool EmptyFiles = false;
  388. for (unsigned I = 0, E = FileReports.size(); I < E; ++I) {
  389. if (FileReports[I].FunctionCoverage.getNumFunctions())
  390. emitFileSummary(OSRef, SourceFiles[I], FileReports[I]);
  391. else
  392. EmptyFiles = true;
  393. }
  394. emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true);
  395. OSRef << EndTable << EndCenteredDiv;
  396. // Emit links to files which don't contain any functions. These are normally
  397. // not very useful, but could be relevant for code which abuses the
  398. // preprocessor.
  399. if (EmptyFiles && Filters.empty()) {
  400. OSRef << tag("p", "Files which contain no functions. (These "
  401. "files contain code pulled into other files "
  402. "by the preprocessor.)\n");
  403. OSRef << BeginCenteredDiv << BeginTable;
  404. for (unsigned I = 0, E = FileReports.size(); I < E; ++I)
  405. if (!FileReports[I].FunctionCoverage.getNumFunctions()) {
  406. std::string Link = buildLinkToFile(SourceFiles[I], FileReports[I]);
  407. OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n';
  408. }
  409. OSRef << EndTable << EndCenteredDiv;
  410. }
  411. OSRef << tag("h5", escape(Opts.getLLVMVersionString(), Opts));
  412. emitEpilog(OSRef);
  413. return Error::success();
  414. }
  415. void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
  416. OS << BeginCenteredDiv << BeginTable;
  417. }
  418. void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) {
  419. OS << EndTable << EndCenteredDiv;
  420. }
  421. void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS, bool WholeFile) {
  422. OS << BeginSourceNameDiv << tag("pre", escape(getSourceName(), getOptions()))
  423. << EndSourceNameDiv;
  424. }
  425. void SourceCoverageViewHTML::renderLinePrefix(raw_ostream &OS, unsigned) {
  426. OS << "<tr>";
  427. }
  428. void SourceCoverageViewHTML::renderLineSuffix(raw_ostream &OS, unsigned) {
  429. // If this view has sub-views, renderLine() cannot close the view's cell.
  430. // Take care of it here, after all sub-views have been rendered.
  431. if (hasSubViews())
  432. OS << EndCodeTD;
  433. OS << "</tr>";
  434. }
  435. void SourceCoverageViewHTML::renderViewDivider(raw_ostream &, unsigned) {
  436. // The table-based output makes view dividers unnecessary.
  437. }
  438. void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L,
  439. const LineCoverageStats &LCS,
  440. unsigned ExpansionCol, unsigned) {
  441. StringRef Line = L.Line;
  442. unsigned LineNo = L.LineNo;
  443. // Steps for handling text-escaping, highlighting, and tooltip creation:
  444. //
  445. // 1. Split the line into N+1 snippets, where N = |Segments|. The first
  446. // snippet starts from Col=1 and ends at the start of the first segment.
  447. // The last snippet starts at the last mapped column in the line and ends
  448. // at the end of the line. Both are required but may be empty.
  449. SmallVector<std::string, 8> Snippets;
  450. CoverageSegmentArray Segments = LCS.getLineSegments();
  451. unsigned LCol = 1;
  452. auto Snip = [&](unsigned Start, unsigned Len) {
  453. Snippets.push_back(std::string(Line.substr(Start, Len)));
  454. LCol += Len;
  455. };
  456. Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1));
  457. for (unsigned I = 1, E = Segments.size(); I < E; ++I)
  458. Snip(LCol - 1, Segments[I]->Col - LCol);
  459. // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1.
  460. Snip(LCol - 1, Line.size() + 1 - LCol);
  461. // 2. Escape all of the snippets.
  462. for (unsigned I = 0, E = Snippets.size(); I < E; ++I)
  463. Snippets[I] = escape(Snippets[I], getOptions());
  464. // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment
  465. // 1 to set the highlight for snippet 2, segment 2 to set the highlight for
  466. // snippet 3, and so on.
  467. std::optional<StringRef> Color;
  468. SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
  469. auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) {
  470. if (getOptions().Debug)
  471. HighlightedRanges.emplace_back(LC, RC);
  472. return tag("span", Snippet, std::string(*Color));
  473. };
  474. auto CheckIfUncovered = [&](const CoverageSegment *S) {
  475. return S && (!S->IsGapRegion || (Color && *Color == "red")) &&
  476. S->HasCount && S->Count == 0;
  477. };
  478. if (CheckIfUncovered(LCS.getWrappedSegment())) {
  479. Color = "red";
  480. if (!Snippets[0].empty())
  481. Snippets[0] = Highlight(Snippets[0], 1, 1 + Snippets[0].size());
  482. }
  483. for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
  484. const auto *CurSeg = Segments[I];
  485. if (CheckIfUncovered(CurSeg))
  486. Color = "red";
  487. else if (CurSeg->Col == ExpansionCol)
  488. Color = "cyan";
  489. else
  490. Color = std::nullopt;
  491. if (Color)
  492. Snippets[I + 1] = Highlight(Snippets[I + 1], CurSeg->Col,
  493. CurSeg->Col + Snippets[I + 1].size());
  494. }
  495. if (Color && Segments.empty())
  496. Snippets.back() = Highlight(Snippets.back(), 1, 1 + Snippets.back().size());
  497. if (getOptions().Debug) {
  498. for (const auto &Range : HighlightedRanges) {
  499. errs() << "Highlighted line " << LineNo << ", " << Range.first << " -> ";
  500. if (Range.second == 0)
  501. errs() << "?";
  502. else
  503. errs() << Range.second;
  504. errs() << "\n";
  505. }
  506. }
  507. // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
  508. // sub-line region count tooltips if needed.
  509. if (shouldRenderRegionMarkers(LCS)) {
  510. // Just consider the segments which start *and* end on this line.
  511. for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {
  512. const auto *CurSeg = Segments[I];
  513. if (!CurSeg->IsRegionEntry)
  514. continue;
  515. if (CurSeg->Count == LCS.getExecutionCount())
  516. continue;
  517. Snippets[I + 1] =
  518. tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count),
  519. "tooltip-content"),
  520. "tooltip");
  521. if (getOptions().Debug)
  522. errs() << "Marker at " << CurSeg->Line << ":" << CurSeg->Col << " = "
  523. << formatCount(CurSeg->Count) << "\n";
  524. }
  525. }
  526. OS << BeginCodeTD;
  527. OS << BeginPre;
  528. for (const auto &Snippet : Snippets)
  529. OS << Snippet;
  530. OS << EndPre;
  531. // If there are no sub-views left to attach to this cell, end the cell.
  532. // Otherwise, end it after the sub-views are rendered (renderLineSuffix()).
  533. if (!hasSubViews())
  534. OS << EndCodeTD;
  535. }
  536. void SourceCoverageViewHTML::renderLineCoverageColumn(
  537. raw_ostream &OS, const LineCoverageStats &Line) {
  538. std::string Count;
  539. if (Line.isMapped())
  540. Count = tag("pre", formatCount(Line.getExecutionCount()));
  541. std::string CoverageClass =
  542. (Line.getExecutionCount() > 0) ? "covered-line" : "uncovered-line";
  543. OS << tag("td", Count, CoverageClass);
  544. }
  545. void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
  546. unsigned LineNo) {
  547. std::string LineNoStr = utostr(uint64_t(LineNo));
  548. std::string TargetName = "L" + LineNoStr;
  549. OS << tag("td", a("#" + TargetName, tag("pre", LineNoStr), TargetName),
  550. "line-number");
  551. }
  552. void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &,
  553. const LineCoverageStats &Line,
  554. unsigned) {
  555. // Region markers are rendered in-line using tooltips.
  556. }
  557. void SourceCoverageViewHTML::renderExpansionSite(raw_ostream &OS, LineRef L,
  558. const LineCoverageStats &LCS,
  559. unsigned ExpansionCol,
  560. unsigned ViewDepth) {
  561. // Render the line containing the expansion site. No extra formatting needed.
  562. renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
  563. }
  564. void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS,
  565. ExpansionView &ESV,
  566. unsigned ViewDepth) {
  567. OS << BeginExpansionDiv;
  568. ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
  569. /*ShowTitle=*/false, ViewDepth + 1);
  570. OS << EndExpansionDiv;
  571. }
  572. void SourceCoverageViewHTML::renderBranchView(raw_ostream &OS, BranchView &BRV,
  573. unsigned ViewDepth) {
  574. // Render the child subview.
  575. if (getOptions().Debug)
  576. errs() << "Branch at line " << BRV.getLine() << '\n';
  577. OS << BeginExpansionDiv;
  578. OS << BeginPre;
  579. for (const auto &R : BRV.Regions) {
  580. // Calculate TruePercent and False Percent.
  581. double TruePercent = 0.0;
  582. double FalsePercent = 0.0;
  583. unsigned Total = R.ExecutionCount + R.FalseExecutionCount;
  584. if (!getOptions().ShowBranchCounts && Total != 0) {
  585. TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0;
  586. FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0;
  587. }
  588. // Display Line + Column.
  589. std::string LineNoStr = utostr(uint64_t(R.LineStart));
  590. std::string ColNoStr = utostr(uint64_t(R.ColumnStart));
  591. std::string TargetName = "L" + LineNoStr;
  592. OS << " Branch (";
  593. OS << tag("span",
  594. a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr),
  595. TargetName),
  596. "line-number") +
  597. "): [";
  598. if (R.Folded) {
  599. OS << "Folded - Ignored]\n";
  600. continue;
  601. }
  602. // Display TrueCount or TruePercent.
  603. std::string TrueColor = R.ExecutionCount ? "None" : "red";
  604. std::string TrueCovClass =
  605. (R.ExecutionCount > 0) ? "covered-line" : "uncovered-line";
  606. OS << tag("span", "True", TrueColor);
  607. OS << ": ";
  608. if (getOptions().ShowBranchCounts)
  609. OS << tag("span", formatCount(R.ExecutionCount), TrueCovClass) << ", ";
  610. else
  611. OS << format("%0.2f", TruePercent) << "%, ";
  612. // Display FalseCount or FalsePercent.
  613. std::string FalseColor = R.FalseExecutionCount ? "None" : "red";
  614. std::string FalseCovClass =
  615. (R.FalseExecutionCount > 0) ? "covered-line" : "uncovered-line";
  616. OS << tag("span", "False", FalseColor);
  617. OS << ": ";
  618. if (getOptions().ShowBranchCounts)
  619. OS << tag("span", formatCount(R.FalseExecutionCount), FalseCovClass);
  620. else
  621. OS << format("%0.2f", FalsePercent) << "%";
  622. OS << "]\n";
  623. }
  624. OS << EndPre;
  625. OS << EndExpansionDiv;
  626. }
  627. void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS,
  628. InstantiationView &ISV,
  629. unsigned ViewDepth) {
  630. OS << BeginExpansionDiv;
  631. if (!ISV.View)
  632. OS << BeginSourceNameDiv
  633. << tag("pre",
  634. escape("Unexecuted instantiation: " + ISV.FunctionName.str(),
  635. getOptions()))
  636. << EndSourceNameDiv;
  637. else
  638. ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
  639. /*ShowTitle=*/false, ViewDepth);
  640. OS << EndExpansionDiv;
  641. }
  642. void SourceCoverageViewHTML::renderTitle(raw_ostream &OS, StringRef Title) {
  643. if (getOptions().hasProjectTitle())
  644. OS << tag(ProjectTitleTag, escape(getOptions().ProjectTitle, getOptions()));
  645. OS << tag(ReportTitleTag, escape(Title, getOptions()));
  646. if (getOptions().hasCreatedTime())
  647. OS << tag(CreatedTimeTag,
  648. escape(getOptions().CreatedTimeStr, getOptions()));
  649. }
  650. void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS,
  651. unsigned FirstUncoveredLineNo,
  652. unsigned ViewDepth) {
  653. std::string SourceLabel;
  654. if (FirstUncoveredLineNo == 0) {
  655. SourceLabel = tag("td", tag("pre", "Source"));
  656. } else {
  657. std::string LinkTarget = "#L" + utostr(uint64_t(FirstUncoveredLineNo));
  658. SourceLabel =
  659. tag("td", tag("pre", "Source (" +
  660. a(LinkTarget, "jump to first uncovered line") +
  661. ")"));
  662. }
  663. renderLinePrefix(OS, ViewDepth);
  664. OS << tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count"))
  665. << SourceLabel;
  666. renderLineSuffix(OS, ViewDepth);
  667. }