1303231Sdim//===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
2303231Sdim//
3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4353358Sdim// See https://llvm.org/LICENSE.txt for license information.
5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6303231Sdim//
7303231Sdim//===----------------------------------------------------------------------===//
8303231Sdim///
9303231Sdim/// \file This file implements the text-based coverage renderer.
10303231Sdim///
11303231Sdim//===----------------------------------------------------------------------===//
12303231Sdim
13314564Sdim#include "CoverageReport.h"
14303231Sdim#include "SourceCoverageViewText.h"
15303231Sdim#include "llvm/ADT/Optional.h"
16303231Sdim#include "llvm/ADT/SmallString.h"
17303231Sdim#include "llvm/ADT/StringExtras.h"
18303231Sdim
19303231Sdimusing namespace llvm;
20303231Sdim
21303231SdimExpected<CoveragePrinter::OwnedStream>
22303231SdimCoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {
23303231Sdim  return createOutputStream(Path, "txt", InToplevel);
24303231Sdim}
25303231Sdim
26303231Sdimvoid CoveragePrinterText::closeViewFile(OwnedStream OS) {
27303231Sdim  OS->operator<<('\n');
28303231Sdim}
29303231Sdim
30314564SdimError CoveragePrinterText::createIndexFile(
31327952Sdim    ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
32327952Sdim    const CoverageFiltersMatchAll &Filters) {
33303231Sdim  auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true);
34303231Sdim  if (Error E = OSOrErr.takeError())
35303231Sdim    return E;
36303231Sdim  auto OS = std::move(OSOrErr.get());
37303231Sdim  raw_ostream &OSRef = *OS.get();
38303231Sdim
39314564Sdim  CoverageReport Report(Opts, Coverage);
40327952Sdim  Report.renderFileReports(OSRef, SourceFiles, Filters);
41303231Sdim
42314564Sdim  Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"
43314564Sdim                                                 << Opts.getLLVMVersionString();
44314564Sdim
45303231Sdim  return Error::success();
46303231Sdim}
47303231Sdim
48303231Sdimnamespace {
49303231Sdim
50303231Sdimstatic const unsigned LineCoverageColumnWidth = 7;
51303231Sdimstatic const unsigned LineNumberColumnWidth = 5;
52303231Sdim
53341825Sdim/// Get the width of the leading columns.
54303231Sdimunsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {
55303231Sdim  return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
56303231Sdim         (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
57303231Sdim}
58303231Sdim
59341825Sdim/// The width of the line that is used to divide between the view and
60303231Sdim/// the subviews.
61303231Sdimunsigned getDividerWidth(const CoverageViewOptions &Opts) {
62303231Sdim  return getCombinedColumnWidth(Opts) + 4;
63303231Sdim}
64303231Sdim
65303231Sdim} // anonymous namespace
66303231Sdim
67303231Sdimvoid SourceCoverageViewText::renderViewHeader(raw_ostream &) {}
68303231Sdim
69303231Sdimvoid SourceCoverageViewText::renderViewFooter(raw_ostream &) {}
70303231Sdim
71314564Sdimvoid SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {
72303231Sdim  getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()
73303231Sdim                                                      << ":\n";
74303231Sdim}
75303231Sdim
76303231Sdimvoid SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,
77303231Sdim                                              unsigned ViewDepth) {
78303231Sdim  for (unsigned I = 0; I < ViewDepth; ++I)
79303231Sdim    OS << "  |";
80303231Sdim}
81303231Sdim
82303231Sdimvoid SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}
83303231Sdim
84303231Sdimvoid SourceCoverageViewText::renderViewDivider(raw_ostream &OS,
85303231Sdim                                               unsigned ViewDepth) {
86303231Sdim  assert(ViewDepth != 0 && "Cannot render divider at top level");
87303231Sdim  renderLinePrefix(OS, ViewDepth - 1);
88303231Sdim  OS.indent(2);
89303231Sdim  unsigned Length = getDividerWidth(getOptions());
90303231Sdim  for (unsigned I = 0; I < Length; ++I)
91303231Sdim    OS << '-';
92303231Sdim  OS << '\n';
93303231Sdim}
94303231Sdim
95327952Sdimvoid SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L,
96327952Sdim                                        const LineCoverageStats &LCS,
97327952Sdim                                        unsigned ExpansionCol,
98327952Sdim                                        unsigned ViewDepth) {
99303231Sdim  StringRef Line = L.Line;
100303231Sdim  unsigned LineNumber = L.LineNo;
101327952Sdim  auto *WrappedSegment = LCS.getWrappedSegment();
102327952Sdim  CoverageSegmentArray Segments = LCS.getLineSegments();
103303231Sdim
104303231Sdim  Optional<raw_ostream::Colors> Highlight;
105303231Sdim  SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
106303231Sdim
107303231Sdim  // The first segment overlaps from a previous line, so we treat it specially.
108327952Sdim  if (WrappedSegment && !WrappedSegment->IsGapRegion &&
109327952Sdim      WrappedSegment->HasCount && WrappedSegment->Count == 0)
110303231Sdim    Highlight = raw_ostream::RED;
111303231Sdim
112303231Sdim  // Output each segment of the line, possibly highlighted.
113303231Sdim  unsigned Col = 1;
114303231Sdim  for (const auto *S : Segments) {
115303231Sdim    unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
116303231Sdim    colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
117303231Sdim                    getOptions().Colors && Highlight, /*Bold=*/false,
118303231Sdim                    /*BG=*/true)
119303231Sdim        << Line.substr(Col - 1, End - Col);
120303231Sdim    if (getOptions().Debug && Highlight)
121303231Sdim      HighlightedRanges.push_back(std::make_pair(Col, End));
122303231Sdim    Col = End;
123327952Sdim    if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) &&
124327952Sdim        S->HasCount && S->Count == 0)
125327952Sdim      Highlight = raw_ostream::RED;
126327952Sdim    else if (Col == ExpansionCol)
127303231Sdim      Highlight = raw_ostream::CYAN;
128303231Sdim    else
129303231Sdim      Highlight = None;
130303231Sdim  }
131303231Sdim
132303231Sdim  // Show the rest of the line.
133303231Sdim  colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
134303231Sdim                  getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true)
135303231Sdim      << Line.substr(Col - 1, Line.size() - Col + 1);
136303231Sdim  OS << '\n';
137303231Sdim
138303231Sdim  if (getOptions().Debug) {
139303231Sdim    for (const auto &Range : HighlightedRanges)
140303231Sdim      errs() << "Highlighted line " << LineNumber << ", " << Range.first
141303231Sdim             << " -> " << Range.second << '\n';
142303231Sdim    if (Highlight)
143303231Sdim      errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
144303231Sdim  }
145303231Sdim}
146303231Sdim
147303231Sdimvoid SourceCoverageViewText::renderLineCoverageColumn(
148303231Sdim    raw_ostream &OS, const LineCoverageStats &Line) {
149303231Sdim  if (!Line.isMapped()) {
150303231Sdim    OS.indent(LineCoverageColumnWidth) << '|';
151303231Sdim    return;
152303231Sdim  }
153327952Sdim  std::string C = formatCount(Line.getExecutionCount());
154303231Sdim  OS.indent(LineCoverageColumnWidth - C.size());
155303231Sdim  colored_ostream(OS, raw_ostream::MAGENTA,
156303231Sdim                  Line.hasMultipleRegions() && getOptions().Colors)
157303231Sdim      << C;
158303231Sdim  OS << '|';
159303231Sdim}
160303231Sdim
161303231Sdimvoid SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS,
162303231Sdim                                                    unsigned LineNo) {
163303231Sdim  SmallString<32> Buffer;
164303231Sdim  raw_svector_ostream BufferOS(Buffer);
165303231Sdim  BufferOS << LineNo;
166303231Sdim  auto Str = BufferOS.str();
167303231Sdim  // Trim and align to the right.
168303231Sdim  Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
169303231Sdim  OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
170303231Sdim}
171303231Sdim
172327952Sdimvoid SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS,
173327952Sdim                                                 const LineCoverageStats &Line,
174327952Sdim                                                 unsigned ViewDepth) {
175303231Sdim  renderLinePrefix(OS, ViewDepth);
176303231Sdim  OS.indent(getCombinedColumnWidth(getOptions()));
177303231Sdim
178327952Sdim  CoverageSegmentArray Segments = Line.getLineSegments();
179327952Sdim
180327952Sdim  // Just consider the segments which start *and* end on this line.
181327952Sdim  if (Segments.size() > 1)
182327952Sdim    Segments = Segments.drop_back();
183327952Sdim
184303231Sdim  unsigned PrevColumn = 1;
185303231Sdim  for (const auto *S : Segments) {
186303231Sdim    if (!S->IsRegionEntry)
187303231Sdim      continue;
188327952Sdim    if (S->Count == Line.getExecutionCount())
189327952Sdim      continue;
190303231Sdim    // Skip to the new region.
191303231Sdim    if (S->Col > PrevColumn)
192303231Sdim      OS.indent(S->Col - PrevColumn);
193303231Sdim    PrevColumn = S->Col + 1;
194303231Sdim    std::string C = formatCount(S->Count);
195303231Sdim    PrevColumn += C.size();
196303231Sdim    OS << '^' << C;
197327952Sdim
198327952Sdim    if (getOptions().Debug)
199327952Sdim      errs() << "Marker at " << S->Line << ":" << S->Col << " = "
200327952Sdim            << formatCount(S->Count) << "\n";
201303231Sdim  }
202303231Sdim  OS << '\n';
203303231Sdim}
204303231Sdim
205327952Sdimvoid SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L,
206327952Sdim                                                 const LineCoverageStats &LCS,
207327952Sdim                                                 unsigned ExpansionCol,
208327952Sdim                                                 unsigned ViewDepth) {
209303231Sdim  renderLinePrefix(OS, ViewDepth);
210303231Sdim  OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));
211327952Sdim  renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
212303231Sdim}
213303231Sdim
214303231Sdimvoid SourceCoverageViewText::renderExpansionView(raw_ostream &OS,
215303231Sdim                                                 ExpansionView &ESV,
216303231Sdim                                                 unsigned ViewDepth) {
217303231Sdim  // Render the child subview.
218303231Sdim  if (getOptions().Debug)
219303231Sdim    errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol()
220303231Sdim           << " -> " << ESV.getEndCol() << '\n';
221303231Sdim  ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
222327952Sdim                  /*ShowTitle=*/false, ViewDepth + 1);
223303231Sdim}
224303231Sdim
225303231Sdimvoid SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,
226303231Sdim                                                     InstantiationView &ISV,
227303231Sdim                                                     unsigned ViewDepth) {
228303231Sdim  renderLinePrefix(OS, ViewDepth);
229303231Sdim  OS << ' ';
230314564Sdim  if (!ISV.View)
231314564Sdim    getOptions().colored_ostream(OS, raw_ostream::RED)
232314564Sdim        << "Unexecuted instantiation: " << ISV.FunctionName << "\n";
233314564Sdim  else
234314564Sdim    ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
235327952Sdim                    /*ShowTitle=*/false, ViewDepth);
236303231Sdim}
237314564Sdim
238314564Sdimvoid SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {
239314564Sdim  if (getOptions().hasProjectTitle())
240314564Sdim    getOptions().colored_ostream(OS, raw_ostream::CYAN)
241314564Sdim        << getOptions().ProjectTitle << "\n";
242314564Sdim
243314564Sdim  getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";
244314564Sdim
245314564Sdim  if (getOptions().hasCreatedTime())
246314564Sdim    getOptions().colored_ostream(OS, raw_ostream::CYAN)
247314564Sdim        << getOptions().CreatedTimeStr << "\n";
248314564Sdim}
249314564Sdim
250314564Sdimvoid SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned,
251314564Sdim                                               unsigned) {}
252