SourceCoverageViewHTML.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. //===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
  2. //
  3. // The LLVM Compiler Infrastructure
  4. //
  5. // This file is distributed under the University of Illinois Open Source
  6. // License. See LICENSE.TXT for details.
  7. //
  8. //===----------------------------------------------------------------------===//
  9. ///
  10. /// \file This file implements the html coverage renderer.
  11. ///
  12. //===----------------------------------------------------------------------===//
  13. #include "SourceCoverageViewHTML.h"
  14. #include "llvm/ADT/Optional.h"
  15. #include "llvm/ADT/SmallString.h"
  16. #include "llvm/ADT/StringExtras.h"
  17. #include "llvm/Support/Path.h"
  18. using namespace llvm;
  19. namespace {
  20. const char *BeginHeader =
  21. "<head>"
  22. "<meta name='viewport' content='width=device-width,initial-scale=1'>"
  23. "<meta charset='UTF-8'>";
  24. const char *CSSForCoverage =
  25. "<style>"
  26. R"(
  27. .red {
  28. background-color: #FFD0D0;
  29. }
  30. .cyan {
  31. background-color: cyan;
  32. }
  33. .black {
  34. background-color: black;
  35. color: white;
  36. }
  37. .green {
  38. background-color: #98FFA6;
  39. color: white;
  40. }
  41. .magenta {
  42. background-color: #F998FF;
  43. color: white;
  44. }
  45. body {
  46. font-family: -apple-system, sans-serif;
  47. }
  48. pre {
  49. margin-top: 0px !important;
  50. margin-bottom: 0px !important;
  51. }
  52. .source-name-title {
  53. padding: 5px 10px;
  54. border-bottom: 1px solid #dbdbdb;
  55. background-color: #eee;
  56. }
  57. .centered {
  58. display: table;
  59. margin-left: auto;
  60. margin-right: auto;
  61. border: 1px solid #dbdbdb;
  62. border-radius: 3px;
  63. }
  64. .expansion-view {
  65. background-color: rgba(0, 0, 0, 0);
  66. margin-left: 0px;
  67. margin-top: 5px;
  68. margin-right: 5px;
  69. margin-bottom: 5px;
  70. border: 1px solid #dbdbdb;
  71. border-radius: 3px;
  72. }
  73. table {
  74. border-collapse: collapse;
  75. }
  76. .line-number {
  77. text-align: right;
  78. color: #aaa;
  79. }
  80. .covered-line {
  81. text-align: right;
  82. color: #0080ff;
  83. }
  84. .uncovered-line {
  85. text-align: right;
  86. color: #ff3300;
  87. }
  88. .tooltip {
  89. position: relative;
  90. display: inline;
  91. background-color: #b3e6ff;
  92. text-decoration: none;
  93. }
  94. .tooltip span.tooltip-content {
  95. position: absolute;
  96. width: 100px;
  97. margin-left: -50px;
  98. color: #FFFFFF;
  99. background: #000000;
  100. height: 30px;
  101. line-height: 30px;
  102. text-align: center;
  103. visibility: hidden;
  104. border-radius: 6px;
  105. }
  106. .tooltip span.tooltip-content:after {
  107. content: '';
  108. position: absolute;
  109. top: 100%;
  110. left: 50%;
  111. margin-left: -8px;
  112. width: 0; height: 0;
  113. border-top: 8px solid #000000;
  114. border-right: 8px solid transparent;
  115. border-left: 8px solid transparent;
  116. }
  117. :hover.tooltip span.tooltip-content {
  118. visibility: visible;
  119. opacity: 0.8;
  120. bottom: 30px;
  121. left: 50%;
  122. z-index: 999;
  123. }
  124. th, td {
  125. vertical-align: top;
  126. padding: 2px 5px;
  127. border-collapse: collapse;
  128. border-right: solid 1px #eee;
  129. border-left: solid 1px #eee;
  130. }
  131. td:first-child {
  132. border-left: none;
  133. }
  134. td:last-child {
  135. border-right: none;
  136. }
  137. )"
  138. "</style>";
  139. const char *EndHeader = "</head>";
  140. const char *BeginCenteredDiv = "<div class='centered'>";
  141. const char *EndCenteredDiv = "</div>";
  142. const char *BeginSourceNameDiv = "<div class='source-name-title'>";
  143. const char *EndSourceNameDiv = "</div>";
  144. const char *BeginCodeTD = "<td class='code'>";
  145. const char *EndCodeTD = "</td>";
  146. const char *BeginPre = "<pre>";
  147. const char *EndPre = "</pre>";
  148. const char *BeginExpansionDiv = "<div class='expansion-view'>";
  149. const char *EndExpansionDiv = "</div>";
  150. const char *BeginTable = "<table>";
  151. const char *EndTable = "</table>";
  152. void emitPrelude(raw_ostream &OS) {
  153. OS << "<!doctype html>"
  154. "<html>"
  155. << BeginHeader << CSSForCoverage << EndHeader << "<body>"
  156. << BeginCenteredDiv;
  157. }
  158. void emitEpilog(raw_ostream &OS) {
  159. OS << EndCenteredDiv << "</body>"
  160. "</html>";
  161. }
  162. // Return a string with the special characters in \p Str escaped.
  163. std::string escape(StringRef Str) {
  164. std::string Result;
  165. for (char C : Str) {
  166. if (C == '&')
  167. Result += "&amp;";
  168. else if (C == '<')
  169. Result += "&lt;";
  170. else if (C == '>')
  171. Result += "&gt;";
  172. else if (C == '\"')
  173. Result += "&quot;";
  174. else
  175. Result += C;
  176. }
  177. return Result;
  178. }
  179. // Create a \p Name tag around \p Str, and optionally set its \p ClassName.
  180. std::string tag(const std::string &Name, const std::string &Str,
  181. const std::string &ClassName = "") {
  182. std::string Tag = "<" + Name;
  183. if (ClassName != "")
  184. Tag += " class='" + ClassName + "'";
  185. return Tag + ">" + Str + "</" + Name + ">";
  186. }
  187. // Create an anchor to \p Link with the label \p Str.
  188. std::string a(const std::string &Link, const std::string &Str) {
  189. return "<a href='" + Link + "'>" + Str + "</a>";
  190. }
  191. } // anonymous namespace
  192. Expected<CoveragePrinter::OwnedStream>
  193. CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
  194. auto OSOrErr = createOutputStream(Path, "html", InToplevel);
  195. if (!OSOrErr)
  196. return OSOrErr;
  197. OwnedStream OS = std::move(OSOrErr.get());
  198. emitPrelude(*OS.get());
  199. return std::move(OS);
  200. }
  201. void CoveragePrinterHTML::closeViewFile(OwnedStream OS) {
  202. emitEpilog(*OS.get());
  203. }
  204. Error CoveragePrinterHTML::createIndexFile(ArrayRef<StringRef> SourceFiles) {
  205. auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
  206. if (Error E = OSOrErr.takeError())
  207. return E;
  208. auto OS = std::move(OSOrErr.get());
  209. raw_ostream &OSRef = *OS.get();
  210. // Emit a table containing links to reports for each file in the covmapping.
  211. emitPrelude(OSRef);
  212. OSRef << BeginSourceNameDiv << "Index" << EndSourceNameDiv;
  213. OSRef << BeginTable;
  214. for (StringRef SF : SourceFiles) {
  215. std::string LinkText = escape(sys::path::relative_path(SF));
  216. std::string LinkTarget =
  217. escape(getOutputPath(SF, "html", /*InToplevel=*/false));
  218. OSRef << tag("tr", tag("td", tag("pre", a(LinkTarget, LinkText), "code")));
  219. }
  220. OSRef << EndTable;
  221. emitEpilog(OSRef);
  222. return Error::success();
  223. }
  224. void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
  225. OS << BeginTable;
  226. }
  227. void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) {
  228. OS << EndTable;
  229. }
  230. void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS) {
  231. OS << BeginSourceNameDiv << tag("pre", escape(getSourceName()))
  232. << EndSourceNameDiv;
  233. }
  234. void SourceCoverageViewHTML::renderLinePrefix(raw_ostream &OS, unsigned) {
  235. OS << "<tr>";
  236. }
  237. void SourceCoverageViewHTML::renderLineSuffix(raw_ostream &OS, unsigned) {
  238. // If this view has sub-views, renderLine() cannot close the view's cell.
  239. // Take care of it here, after all sub-views have been rendered.
  240. if (hasSubViews())
  241. OS << EndCodeTD;
  242. OS << "</tr>";
  243. }
  244. void SourceCoverageViewHTML::renderViewDivider(raw_ostream &, unsigned) {
  245. // The table-based output makes view dividers unnecessary.
  246. }
  247. void SourceCoverageViewHTML::renderLine(
  248. raw_ostream &OS, LineRef L, const coverage::CoverageSegment *WrappedSegment,
  249. CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned) {
  250. StringRef Line = L.Line;
  251. // Steps for handling text-escaping, highlighting, and tooltip creation:
  252. //
  253. // 1. Split the line into N+1 snippets, where N = |Segments|. The first
  254. // snippet starts from Col=1 and ends at the start of the first segment.
  255. // The last snippet starts at the last mapped column in the line and ends
  256. // at the end of the line. Both are required but may be empty.
  257. SmallVector<std::string, 8> Snippets;
  258. unsigned LCol = 1;
  259. auto Snip = [&](unsigned Start, unsigned Len) {
  260. assert(Start + Len <= Line.size() && "Snippet extends past the EOL");
  261. Snippets.push_back(Line.substr(Start, Len));
  262. LCol += Len;
  263. };
  264. Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1));
  265. for (unsigned I = 1, E = Segments.size(); I < E; ++I) {
  266. assert(LCol == Segments[I - 1]->Col && "Snippet start position is wrong");
  267. Snip(LCol - 1, Segments[I]->Col - LCol);
  268. }
  269. // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1.
  270. Snip(LCol - 1, Line.size() + 1 - LCol);
  271. assert(LCol == Line.size() + 1 && "Final snippet doesn't reach the EOL");
  272. // 2. Escape all of the snippets.
  273. for (unsigned I = 0, E = Snippets.size(); I < E; ++I)
  274. Snippets[I] = escape(Snippets[I]);
  275. // 3. Use \p WrappedSegment to set the highlight for snippets 0 and 1. Use
  276. // segment 1 to set the highlight for snippet 2, segment 2 to set the
  277. // highlight for snippet 3, and so on.
  278. Optional<std::string> Color;
  279. auto Highlight = [&](const std::string &Snippet) {
  280. return tag("span", Snippet, Color.getValue());
  281. };
  282. auto CheckIfUncovered = [](const coverage::CoverageSegment *S) {
  283. return S && S->HasCount && S->Count == 0;
  284. };
  285. if (CheckIfUncovered(WrappedSegment) ||
  286. CheckIfUncovered(Segments.empty() ? nullptr : Segments.front())) {
  287. Color = "red";
  288. Snippets[0] = Highlight(Snippets[0]);
  289. Snippets[1] = Highlight(Snippets[1]);
  290. }
  291. for (unsigned I = 1, E = Segments.size(); I < E; ++I) {
  292. const auto *CurSeg = Segments[I];
  293. if (CurSeg->Col == ExpansionCol)
  294. Color = "cyan";
  295. else if (CheckIfUncovered(CurSeg))
  296. Color = "red";
  297. else
  298. Color = None;
  299. if (Color.hasValue())
  300. Snippets[I + 1] = Highlight(Snippets[I + 1]);
  301. }
  302. // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
  303. // sub-line region count tooltips if needed.
  304. bool HasMultipleRegions = [&] {
  305. unsigned RegionCount = 0;
  306. for (const auto *S : Segments)
  307. if (S->HasCount && S->IsRegionEntry)
  308. if (++RegionCount > 1)
  309. return true;
  310. return false;
  311. }();
  312. if (shouldRenderRegionMarkers(HasMultipleRegions)) {
  313. for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
  314. const auto *CurSeg = Segments[I];
  315. if (!CurSeg->IsRegionEntry || !CurSeg->HasCount)
  316. continue;
  317. Snippets[I + 1] =
  318. tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count),
  319. "tooltip-content"),
  320. "tooltip");
  321. }
  322. }
  323. OS << BeginCodeTD;
  324. OS << BeginPre;
  325. for (const auto &Snippet : Snippets)
  326. OS << Snippet;
  327. OS << EndPre;
  328. // If there are no sub-views left to attach to this cell, end the cell.
  329. // Otherwise, end it after the sub-views are rendered (renderLineSuffix()).
  330. if (!hasSubViews())
  331. OS << EndCodeTD;
  332. }
  333. void SourceCoverageViewHTML::renderLineCoverageColumn(
  334. raw_ostream &OS, const LineCoverageStats &Line) {
  335. std::string Count = "";
  336. if (Line.isMapped())
  337. Count = tag("pre", formatCount(Line.ExecutionCount));
  338. std::string CoverageClass =
  339. (Line.ExecutionCount > 0) ? "covered-line" : "uncovered-line";
  340. OS << tag("td", Count, CoverageClass);
  341. }
  342. void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
  343. unsigned LineNo) {
  344. OS << tag("td", tag("pre", utostr(uint64_t(LineNo))), "line-number");
  345. }
  346. void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &,
  347. CoverageSegmentArray,
  348. unsigned) {
  349. // Region markers are rendered in-line using tooltips.
  350. }
  351. void SourceCoverageViewHTML::renderExpansionSite(
  352. raw_ostream &OS, LineRef L, const coverage::CoverageSegment *WrappedSegment,
  353. CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned ViewDepth) {
  354. // Render the line containing the expansion site. No extra formatting needed.
  355. renderLine(OS, L, WrappedSegment, Segments, ExpansionCol, ViewDepth);
  356. }
  357. void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS,
  358. ExpansionView &ESV,
  359. unsigned ViewDepth) {
  360. OS << BeginExpansionDiv;
  361. ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
  362. ViewDepth + 1);
  363. OS << EndExpansionDiv;
  364. }
  365. void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS,
  366. InstantiationView &ISV,
  367. unsigned ViewDepth) {
  368. OS << BeginExpansionDiv;
  369. ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, ViewDepth);
  370. OS << EndExpansionDiv;
  371. }