Coverage Report

Created: 2017-10-03 07:32

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