Index: test/tools/llvm-cov/showLineExecutionCounts.cpp =================================================================== --- test/tools/llvm-cov/showLineExecutionCounts.cpp +++ test/tools/llvm-cov/showLineExecutionCounts.cpp @@ -28,3 +28,7 @@ // RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE %s // RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence -name=main %s | FileCheck -check-prefix=CHECK -check-prefix=FILTER %s +// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence -output-dir coverage-output %s +// RUN: FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE -input-file coverage-output/tmp/showLineExecutionCounts.cpp.txt %s +// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence -name=main -output-dir coverage-output %s +// RUN: FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE -input-file coverage-output/tmp/showLineExecutionCounts.cpp.txt %s Index: test/tools/llvm-cov/showLineExecutionCountsHTML.cpp =================================================================== --- /dev/null +++ test/tools/llvm-cov/showLineExecutionCountsHTML.cpp @@ -0,0 +1,42 @@ +// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %S/Inputs/lineExecutionCountsHTML.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE %s +// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %S/Inputs/lineExecutionCountsHTML.profdata -filename-equivalence -name=main %s | FileCheck -check-prefix=CHECK %s + // WHOLE-FILE:
+ // WHOLE-FILE: showLineExecutionCounts.cpp + // CHECK:
1
+ // CHECK:
[[@LINE+1]]
+int main() { // CHECK:
+                                        // CHECK: int main() {
+                                        // CHECK: 
+ // CHECK:
1
+ // CHECK:
[[@LINE+1]]
+ int x = 0; // CHECK:
+                                        // CHECK:     int x = 0;
+                                        // CHECK: 
+ // CHECK:
101
+ // CHECK:
[[@LINE+1]]
+ for (int i = 0; i < 100; i++) { // CHECK:
+                                        // CHECK: for (int i = 0;
+                                        // CHECK: 
+ // CHECK:
100
+ // CHECK:
[[@LINE+1]]
+ x++; // CHECK:
+                                        // CHECK: x++;
+                                        // CHECK: 
+ } + + // CHECK:
1
+ // CHECK:
[[@LINE+1]]
+ if (0) { // CHECK:
+                                        // CHECK: if (0)
+                                        // CHECK: {
+                                        // CHECK: }
+                                        // CHECK: 
+                                        // CHECK: 
0
+ // CHECK:
[[@LINE+1]]
+ x += 10; // CHECK:
+                                        // CHECK: x += 10;
+    }
+    return 0;
+}
+// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %S/Inputs/lineExecutionCountsHTML.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE %s
+// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %S/Inputs/lineExecutionCountsHTML.profdata -filename-equivalence -name=main %s | FileCheck -check-prefix=CHECK  %s
Index: test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp
===================================================================
--- /dev/null
+++ test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp
@@ -0,0 +1,27 @@
+// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %S/Inputs/templateInstantiationsHTML.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK %s
+// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %S/Inputs/templateInstantiationsHTML.profdata -filename-equivalence -name=_Z4funcIbEiT_ %s | FileCheck -check-prefix=CHECK %s
+
+// before coverage
+                     // WHOLE-FILE: // before
+                     // FILTER-NOT: // before
+                     // CHECK: 
+template // WHOLE-FILE: 
+int func(T x) { // CHECK: + if(x) // CHECK: return 0 + return 0; // WHOLE-FILE:
+ else + return 1; + int j = 1; +} + +int main() { + func(0); + func(true); + return 0; +} +// after coverage + + // WHOLE-FILE: // after + // FILTER-NOT: // after +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %S/Inputs/templateInstantiationsHTML.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK %s +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %S/Inputs/templateInstantiationsHTML.profdata -filename-equivalence -name=_Z4funcIbEiT_ %s | FileCheck -check-prefix=CHECK %s Index: tools/llvm-cov/CodeCoverage.cpp =================================================================== --- tools/llvm-cov/CodeCoverage.cpp +++ tools/llvm-cov/CodeCoverage.cpp @@ -51,6 +51,10 @@ /// \brief Print the error message to the error output stream. void error(const Twine &Message, StringRef Whence = ""); + /// \brief Return a pointer to a stream for the given file. + /// Passing "" yields stdout. + std::unique_ptr getStreamForFile(StringRef Filename); + /// \brief Return a memory buffer for the given source file. ErrorOr getSourceFile(StringRef SourceFile); @@ -100,6 +104,36 @@ errs() << Message << "\n"; } +std::unique_ptr +CodeCoverageTool::getStreamForFile(StringRef Filename) { + if (Filename == "") { + return llvm::make_unique(fileno(stdout), false); + } + + std::error_code Error; + auto OS = llvm::make_unique(Filename, Error, sys::fs::F_RW); + if (Error) { + error(Error.message(), "could not get stream for " + std::string(Filename)); + return nullptr; + } + + return std::move(OS); +} + +llvm::SmallString<128> getOutputPathForFile(StringRef OutputDir, + StringRef SourceFile, + StringRef FileExt) { + auto Basename = sys::path::relative_path(sys::path::parent_path(SourceFile)); + auto Filename = (sys::path::filename(SourceFile) + "." + FileExt).str(); + llvm::SmallString<128> Path(OutputDir); + if (Path != "") { + sys::path::append(Path, Basename); + sys::fs::create_directories(Path); + sys::path::append(Path, Filename); + } + return Path; +} + ErrorOr CodeCoverageTool::getSourceFile(StringRef SourceFile) { // If we've remapped filenames, look up the real location for this file. @@ -135,8 +169,9 @@ continue; auto SubViewExpansions = ExpansionCoverage.getExpansions(); - auto SubView = llvm::make_unique( - SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage)); + auto SubView = SourceCoverageView::create(SourceBuffer.get(), ViewOpts, + Expansion.Function.Name, + std::move(ExpansionCoverage)); attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); View.addExpansion(Expansion.Region, std::move(SubView)); } @@ -153,8 +188,8 @@ return nullptr; auto Expansions = FunctionCoverage.getExpansions(); - auto View = llvm::make_unique( - SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage)); + auto View = SourceCoverageView::create( + SourceBuffer.get(), ViewOpts, Function.Name, std::move(FunctionCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); return View; @@ -171,15 +206,16 @@ return nullptr; auto Expansions = FileCoverage.getExpansions(); - auto View = llvm::make_unique( - SourceBuffer.get(), ViewOpts, std::move(FileCoverage)); + auto View = SourceCoverageView::create(SourceBuffer.get(), ViewOpts, + SourceFile, std::move(FileCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); for (auto Function : Coverage.getInstantiations(SourceFile)) { auto SubViewCoverage = Coverage.getCoverageForFunction(*Function); auto SubViewExpansions = SubViewCoverage.getExpansions(); - auto SubView = llvm::make_unique( - SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage)); + auto SubView = + SourceCoverageView::create(SourceBuffer.get(), ViewOpts, Function->Name, + std::move(SubViewCoverage)); attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); if (SubView) { @@ -315,9 +351,16 @@ ViewOpts.Debug = DebugDump; CompareFilenamesOnly = FilenameEquivalence; - ViewOpts.Colors = UseColor == cl::BOU_UNSET - ? sys::Process::StandardOutHasColors() - : UseColor == cl::BOU_TRUE; + switch (ViewOpts.Format) { + case CoverageViewOptions::OFText: + ViewOpts.Colors = UseColor == cl::BOU_UNSET + ? sys::Process::StandardOutHasColors() + : UseColor == cl::BOU_TRUE; + break; + case CoverageViewOptions::OFHTML: + ViewOpts.Colors = true; + break; + } // Create the function filters if (!NameFilters.empty() || !NameRegexFilters.empty()) { @@ -406,6 +449,18 @@ cl::desc("Show function instantiations"), cl::cat(ViewCategory)); + cl::opt OutputDirectory( + "output-dir", cl::Optional, + cl::desc("Directory to output individual files")); + + cl::opt Format( + "format", cl::desc("Format to output coverage"), + cl::values(clEnumValN(CoverageViewOptions::OFHTML, "html", "HTML output"), + clEnumValN(CoverageViewOptions::OFText, "text", + "Textual table output"), + clEnumValEnd), + cl::init(CoverageViewOptions::OFText)); + auto Err = commandLineParser(argc, argv); if (Err) return Err; @@ -417,12 +472,25 @@ ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts; ViewOpts.ShowExpandedRegions = ShowExpansions; ViewOpts.ShowFunctionInstantiations = ShowInstantiations; + ViewOpts.Format = Format; auto Coverage = load(); if (!Coverage) return 1; + bool isHTML = Format == CoverageViewOptions::OFHTML; + StringRef FileExt = isHTML ? "html" : "txt"; + if (!Filters.empty()) { + auto Path = getOutputPathForFile(OutputDirectory, "functions", FileExt); + auto OS = getStreamForFile(Path); + if (!OS) + return 1; + + if (isHTML) { + SourceCoverageViewHTML::renderFileHeader(*OS); + } + // Show functions for (const auto &Function : Coverage->getCoveredFunctions()) { if (!Filters.matches(Function)) @@ -435,39 +503,48 @@ outs() << "\n"; continue; } - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name - << ":"; - outs() << "\n"; - mainView->render(outs(), /*WholeFile=*/false); - outs() << "\n"; + mainView->render(*OS, /*WholeFile=*/false, /*RenderTitle=*/true); + *OS << "\n"; + } + + if (isHTML) { + SourceCoverageViewHTML::renderFileFooter(*OS); } return 0; } // Show files - bool ShowFilenames = SourceFiles.size() != 1; + bool ShowFilenames = isHTML || SourceFiles.size() != 1; if (SourceFiles.empty()) // Get the source files from the function coverage mapping for (StringRef Filename : Coverage->getUniqueSourceFiles()) SourceFiles.push_back(Filename); + if (isHTML && OutputDirectory != "") { + // Render an index with a list of files. + auto Path = getOutputPathForFile(OutputDirectory, "index", FileExt); + auto OS = getStreamForFile(Path); + if (!OS) + return 1; + SourceCoverageViewHTML::renderIndex(*OS, SourceFiles); + } + for (const auto &SourceFile : SourceFiles) { + auto Path = getOutputPathForFile(OutputDirectory, SourceFile, FileExt); + auto OS = getStreamForFile(Path); + if (!OS) + return 1; auto mainView = createSourceFileView(SourceFile, *Coverage); if (!mainView) { - ViewOpts.colored_ostream(outs(), raw_ostream::RED) + ViewOpts.colored_ostream(errs(), raw_ostream::RED) << "warning: The file '" << SourceFile << "' isn't covered."; - outs() << "\n"; + errs() << "\n"; continue; } - - if (ShowFilenames) { - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":"; - outs() << "\n"; - } - mainView->render(outs(), /*Wholefile=*/true); + mainView->render(*OS, /*Wholefile=*/true, /*RenderTitle=*/ShowFilenames); if (SourceFiles.size() > 1) - outs() << "\n"; + *OS << "\n"; } return 0; Index: tools/llvm-cov/CoverageCSS.inc =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageCSS.inc @@ -0,0 +1,121 @@ +//===-------- CoverageCSS.inc - css file for HTML coverage reports --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file has a raw string literal that is inserted as a CSS style into +// HTML documents generated by llvm-cov. +// +// This file includes the necessary styling for centered tables of code and +// tooltip generation. +// +//===----------------------------------------------------------------------===// + +StringRef CoverageCSS = R"(.red { + background-color: #FFD0D0; +} +.cyan { + background-color: cyan; +} +.black { + background-color: black; + color: white; +} +.green { + background-color: #98FFA6; + color: white; +} +.magenta { + background-color: #F998FF; + color: white; +} +body { + font-family: -apple-system, sans-serif; +} +pre { + margin-top: 0px !important; + margin-bottom: 0px !important; +} +.function-title { + padding: 5px 10px; + border-bottom: 1px solid #dbdbdb; + background-color: #eee; +} +.centered { + display: table; + margin-left: auto; + margin-right: auto; + border: 1px solid #dbdbdb; + border-radius: 3px; +} +.expansion-view { + background-color: rgba(0, 0, 0, 0); + margin-left: 0px; + margin-top: 5px; + margin-right: 5px; + margin-bottom: 5px; + border: 1px solid #dbdbdb; + border-radius: 3px; +} +table { + border-collapse: collapse; +} +.numeric { + text-align: right; + color: #aaa; +} +.tooltips { + position: relative; + display: inline; + background-color: #FFFBD2; + text-decoration: none; +} +.tooltips span.tooltip-content { + position: absolute; + width:140px; + color: #FFFFFF; + background: #000000; + height: 30px; + line-height: 30px; + text-align: center; + visibility: hidden; + border-radius: 6px; +} +.tooltips span.tooltip-content:after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + margin-left: -8px; + width: 0; height: 0; + border-top: 8px solid #000000; + border-right: 8px solid transparent; + border-left: 8px solid transparent; +} +:hover.tooltips span.tooltip-content { + visibility: visible; + opacity: 0.8; + bottom: 30px; + left: 50%; + margin-left: -76px; + z-index: 999; +} +th, td { + vertical-align: top; + padding: 2px 5px; + border-collapse: collapse; + border-right: solid 1px #eee; + border-left: solid 1px #eee; +} + +td:first-child { + border-left: none; +} +td:last-child { + border-right: none; +} +})"; Index: tools/llvm-cov/CoverageViewOptions.h =================================================================== --- tools/llvm-cov/CoverageViewOptions.h +++ tools/llvm-cov/CoverageViewOptions.h @@ -16,6 +16,12 @@ /// \brief The options for displaying the code coverage information. struct CoverageViewOptions { + enum OutputFormat { + /// \brief ASCII table output. + OFText, + /// \brief An HTML directory. + OFHTML + }; bool Debug; bool Colors; bool ShowLineNumbers; @@ -25,6 +31,7 @@ bool ShowExpandedRegions; bool ShowFunctionInstantiations; bool ShowFullFilenames; + OutputFormat Format; /// \brief Change the output's stream color if the colors are enabled. ColoredRawOstream colored_ostream(raw_ostream &OS, Index: tools/llvm-cov/SourceCoverageView.h =================================================================== --- tools/llvm-cov/SourceCoverageView.h +++ tools/llvm-cov/SourceCoverageView.h @@ -17,6 +17,9 @@ #include "CoverageViewOptions.h" #include "llvm/ProfileData/CoverageMapping.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include namespace llvm { @@ -76,7 +79,7 @@ /// \brief A code coverage view of a specific source file. /// It can have embedded coverage views. class SourceCoverageView { -private: +protected: /// \brief Coverage information for a single line. struct LineCoverageInfo { uint64_t ExecutionCount; @@ -103,39 +106,69 @@ const MemoryBuffer &File; const CoverageViewOptions &Options; + StringRef SourceName; coverage::CoverageData CoverageInfo; std::vector ExpansionSubViews; std::vector InstantiationSubViews; - /// \brief Render a source line with highlighting. - void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber, - const coverage::CoverageSegment *WrappedSegment, - ArrayRef Segments, - unsigned ExpansionCol); + virtual void output(llvm::raw_ostream &OS, StringRef Text, + llvm::Optional Highlight, + const coverage::CoverageSegment *Segment = nullptr) = 0; - void renderIndent(raw_ostream &OS, unsigned Level); - - void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS); + /// \brief Render a source line with highlighting. + virtual void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber, + const coverage::CoverageSegment *WrappedSegment, + ArrayRef Segments, + unsigned ExpansionCol); /// \brief Render the line's execution count column. - void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line); + virtual void renderLineCoverageColumn(raw_ostream &OS, + const LineCoverageInfo &Line) = 0; /// \brief Render the line number column. - void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo); + virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0; /// \brief Render all the region's execution counts on a line. - void + virtual void renderRegionMarkers(raw_ostream &OS, - ArrayRef Segments); + ArrayRef Segments, + unsigned IndentLevel, unsigned CombinedColumnWidth); + + /// \brief Render an expansion view inline, using format-specific logic. + virtual void renderExpansionView( + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView, + const coverage::CoverageSegment *WrappedSegment, + SmallVector &LineSegments, + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent, + unsigned CombinedColumnWidth, unsigned DividerWidth); + + /// \brief Render an instantiation view inline, using format-specific logic. + virtual void renderInstantiationView(raw_ostream &OS, + InstantiationView &InstantiationView, + unsigned IndentLevel, + unsigned NestedIndent, + unsigned DividerWidth); + + /// \brief Render all the region's execution counts on a line. + virtual void finishRenderingSubviews(raw_ostream &OS, unsigned IndentLevel, + unsigned DividerWidth) = 0; + + virtual void startLine(raw_ostream &OS, unsigned IndentLevel) {} + virtual void endLine(raw_ostream &OS) {} static const unsigned LineCoverageColumnWidth = 7; static const unsigned LineNumberColumnWidth = 5; public: SourceCoverageView(const MemoryBuffer &File, - const CoverageViewOptions &Options, + const CoverageViewOptions &Options, StringRef SourceName, coverage::CoverageData &&CoverageInfo) - : File(File), Options(Options), CoverageInfo(std::move(CoverageInfo)) {} + : File(File), Options(Options), SourceName(SourceName), + CoverageInfo(std::move(CoverageInfo)) {} + + static std::unique_ptr + create(const MemoryBuffer &File, const CoverageViewOptions &Options, + StringRef SourceName, coverage::CoverageData &&CoverageInfo); const CoverageViewOptions &getOptions() const { return Options; } @@ -153,7 +186,145 @@ /// \brief Print the code coverage information for a specific /// portion of a source file to the output stream. - void render(raw_ostream &OS, bool WholeFile, unsigned IndentLevel = 0); + virtual void render(raw_ostream &OS, bool WholeFile, bool RenderTitle = false, + unsigned IndentLevel = 0); + + /// \brief Render the title for a given source region (function or file name) + virtual void renderTitle(raw_ostream &OS) = 0; + + virtual ~SourceCoverageView() {} +}; + +class SourceCoverageViewHTML : public SourceCoverageView { + + /// \brief Render a source line with highlighting. + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber, + const coverage::CoverageSegment *WrappedSegment, + ArrayRef Segments, + unsigned ExpansionCol) override; + + void startLine(raw_ostream &OS, unsigned IndentLevel) override; + void endLine(raw_ostream &OS) override; + + /// \brief Render the line's execution count column. + void renderLineCoverageColumn(raw_ostream &OS, + const LineCoverageInfo &Line) override; + + void output(llvm::raw_ostream &OS, StringRef Text, + llvm::Optional Highlight, + const coverage::CoverageSegment *Segment = nullptr) override; + + /// \brief Render the line number column. + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override; + + /// \brief Render all the region's execution counts on a line. + void renderRegionMarkers(raw_ostream &OS, + ArrayRef Segments, + unsigned IndentLevel, + unsigned CombinedColumnWidth) override { + // empty implementation (region markers are tooltips). + } + + /// \brief Print the code coverage information for a specific + /// portion of a source file to the output stream. + void render(raw_ostream &OS, bool WholeFile, bool RenderTitle = false, + unsigned IndentLevel = 0) override; + + void finishRenderingSubviews(raw_ostream &OS, unsigned IndentLevel, + unsigned DividerWidth) override; + void renderExpansionView( + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView, + const coverage::CoverageSegment *WrappedSegment, + SmallVector &LineSegments, + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent, + unsigned CombinedColumnWidth, unsigned DividerWidth) override; + + void renderInstantiationView(raw_ostream &OS, + InstantiationView &InstantiationView, + unsigned IndentLevel, unsigned NestedIndent, + unsigned DividerWidth) override; + +public: + SourceCoverageViewHTML(const MemoryBuffer &File, + const CoverageViewOptions &Options, + StringRef SourceName, + coverage::CoverageData &&CoverageInfo) + : SourceCoverageView(File, Options, SourceName, std::move(CoverageInfo)) { + } + + static void renderIndex(raw_ostream &OS, + std::vector &SourceFiles); + + // \brief Render the standard boilerplate HTML header. + static void renderFileHeader(raw_ostream &OS); + + // \brief Render the standard boilerplate HTML footer. + static void renderFileFooter(raw_ostream &OS); + + /// \brief Render the title for a given source region (function or file name) + void renderTitle(raw_ostream &OS) override; +}; + +class SourceCoverageViewText : public SourceCoverageView { + + /// \brief Render a source line with highlighting. + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber, + const coverage::CoverageSegment *WrappedSegment, + ArrayRef Segments, + unsigned ExpansionCol) override; + + void startLine(raw_ostream &OS, unsigned IndentLevel) override; + + /// \brief Render the line's execution count column. + void renderLineCoverageColumn(raw_ostream &OS, + const LineCoverageInfo &Line) override; + + void output(raw_ostream &OS, StringRef Text, + Optional Highlight, + const coverage::CoverageSegment *Segment = nullptr) override; + + /// \brief Render the line number column. + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override; + + /// \brief Render all the region's execution counts on a line. + void renderRegionMarkers(raw_ostream &OS, + ArrayRef Segments, + unsigned IndentLevel, + unsigned CombinedColumnWidth) override; + + void renderIndent(raw_ostream &OS, unsigned Level); + + void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS); + + void finishRenderingSubviews(raw_ostream &OS, unsigned IndentLevel, + unsigned DividerWidth) override; + void renderExpansionView( + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView, + const coverage::CoverageSegment *WrappedSegment, + SmallVector &LineSegments, + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent, + unsigned CombinedColumnWidth, unsigned DividerWidth) override; + + void renderInstantiationView(raw_ostream &OS, + InstantiationView &InstantiationView, + unsigned IndentLevel, unsigned NestedIndent, + unsigned DividerWidth) override; + + /// \brief Print the code coverage information for a specific + /// portion of a source file to the output stream. + void render(raw_ostream &OS, bool WholeFile, bool RenderTitle = false, + unsigned IndentLevel = 0) override; + +public: + SourceCoverageViewText(const MemoryBuffer &File, + const CoverageViewOptions &Options, + StringRef SourceName, + coverage::CoverageData &&CoverageInfo) + : SourceCoverageView(File, Options, SourceName, std::move(CoverageInfo)) { + } + + /// \brief Render the title for a given source region (function or file name) + void renderTitle(raw_ostream &OS) override; }; } // namespace llvm Index: tools/llvm-cov/SourceCoverageView.cpp =================================================================== --- tools/llvm-cov/SourceCoverageView.cpp +++ tools/llvm-cov/SourceCoverageView.cpp @@ -12,13 +12,125 @@ //===----------------------------------------------------------------------===// #include "SourceCoverageView.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" -#include "llvm/Support/LineIterator.h" +#include "llvm/Support/Path.h" using namespace llvm; +namespace html { +/// An HTML-escaped string. +std::string escape(std::string Str) { + std::string Result; + for (size_t i = 0; i < Str.size(); ++i) { + std::string Token = Str.substr(i, 1); + if (Token == "&") + Token = "&"; + else if (Token == "<") + Token = "<"; + else if (Token == "\"") + Token = """; + else if (Token == ">") + Token = ">"; + Result += Token; + } + return Result; +} + +/// Creates a tag around the provided text +/// (expects the value to be properly escaped). +std::string tag(std::string Name, std::string Text, + std::string ClassName = "") { + std::string Tag = "<" + Name; + if (ClassName != "") { + Tag += " class='" + ClassName + "'"; + } + return Tag + ">" + Text + ""; +} + +std::string a(std::string Link, std::string Text) { + return "" + Text + ""; +} +} // end namespace html + +std::string classForColor(raw_ostream::Colors Color) { + switch (Color) { + case raw_ostream::RED: + return "red"; + case raw_ostream::CYAN: + return "cyan"; + case raw_ostream::BLUE: + return "blue"; + case raw_ostream::BLACK: + return "black"; + case raw_ostream::GREEN: + return "green"; + case raw_ostream::MAGENTA: + return "magenta"; + default: + return "clear"; + } +} + +/// Format a count using engineering notation with 3 significant digits. +std::string formatCount(uint64_t N) { + std::string Number = utostr(N); + int Len = Number.size(); + if (Len <= 3) + return Number; + int IntLen = Len % 3 == 0 ? 3 : Len % 3; + std::string Result(Number.data(), IntLen); + if (IntLen != 3) { + Result.push_back('.'); + Result += Number.substr(IntLen, 3 - IntLen); + } + Result.push_back(" kMGTPEZY"[(Len - 1) / 3]); + return Result; +} + +//===----------------------------------------------------------------------===// +// +// SourceCoverageView +// +//===----------------------------------------------------------------------===// + +std::unique_ptr SourceCoverageView::create( + const MemoryBuffer &File, const CoverageViewOptions &Options, + StringRef SourceName, coverage::CoverageData &&CoverageInfo) { + switch (Options.Format) { + case CoverageViewOptions::OFHTML: + return llvm::make_unique(File, Options, SourceName, + std::move(CoverageInfo)); + case CoverageViewOptions::OFText: + return llvm::make_unique(File, Options, SourceName, + std::move(CoverageInfo)); + default: + llvm_unreachable("unknown ouput format"); + } +} + +void SourceCoverageView::renderExpansionView( + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView, + const coverage::CoverageSegment *WrappedSegment, + SmallVector &LineSegments, + bool RenderedSubview, unsigned IndentLevel, unsigned NestedIndent, + unsigned CombinedColumnWidth, unsigned DividerWidth) { + + if (Options.Debug) + errs() << "Expansion at line " << ExpansionView.getLine() << ", " + << ExpansionView.getStartCol() << " -> " << ExpansionView.getEndCol() + << "\n"; + ExpansionView.View->render(OS, /*WholeFile=*/false, + /*RenderTitle*/ false, NestedIndent); +} + +void SourceCoverageView::renderInstantiationView( + raw_ostream &OS, InstantiationView &InstantiationView, unsigned IndentLevel, + unsigned NestedIndent, unsigned DividerWidth) { + InstantiationView.View->render(OS, /*WholeFile=*/false, + /*RenderTitle=*/true, NestedIndent); +} + void SourceCoverageView::renderLine( raw_ostream &OS, StringRef Line, int64_t LineNumber, const coverage::CoverageSegment *WrappedSegment, @@ -35,9 +147,8 @@ unsigned Col = 1; for (const auto *S : Segments) { unsigned End = std::min(S->Col, static_cast(Line.size()) + 1); - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true) - << Line.substr(Col - 1, End - Col); + auto Text = Line.substr(Col - 1, End - Col); + output(OS, Text, Highlight, S); if (Options.Debug && Highlight) HighlightedRanges.push_back(std::make_pair(Col, End)); Col = End; @@ -49,11 +160,8 @@ Highlight = None; } - // Show the rest of the line - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true) - << Line.substr(Col - 1, Line.size() - Col + 1); - OS << "\n"; + auto Rest = Line.substr(Col - 1, Line.size() - Col + 1); + output(OS, Rest, Highlight, nullptr); if (Options.Debug) { for (const auto &Range : HighlightedRanges) @@ -64,94 +172,12 @@ } } -void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) { - for (unsigned I = 0; I < Level; ++I) - OS << " |"; -} - -void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length, - raw_ostream &OS) { - assert(Level != 0 && "Cannot render divider at top level"); - renderIndent(OS, Level - 1); - OS.indent(2); - for (unsigned I = 0; I < Length; ++I) - OS << "-"; -} - -/// Format a count using engineering notation with 3 significant digits. -static std::string formatCount(uint64_t N) { - std::string Number = utostr(N); - int Len = Number.size(); - if (Len <= 3) - return Number; - int IntLen = Len % 3 == 0 ? 3 : Len % 3; - std::string Result(Number.data(), IntLen); - if (IntLen != 3) { - Result.push_back('.'); - Result += Number.substr(IntLen, 3 - IntLen); - } - Result.push_back(" kMGTPEZY"[(Len - 1) / 3]); - return Result; -} - -void -SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS, - const LineCoverageInfo &Line) { - if (!Line.isMapped()) { - OS.indent(LineCoverageColumnWidth) << '|'; - return; - } - std::string C = formatCount(Line.ExecutionCount); - OS.indent(LineCoverageColumnWidth - C.size()); - colored_ostream(OS, raw_ostream::MAGENTA, - Line.hasMultipleRegions() && Options.Colors) - << C; - OS << '|'; -} - -void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS, - unsigned LineNo) { - SmallString<32> Buffer; - raw_svector_ostream BufferOS(Buffer); - BufferOS << LineNo; - auto Str = BufferOS.str(); - // Trim and align to the right - Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); - OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; -} - -void SourceCoverageView::renderRegionMarkers( - raw_ostream &OS, ArrayRef Segments) { - unsigned PrevColumn = 1; - for (const auto *S : Segments) { - if (!S->IsRegionEntry) - continue; - // Skip to the new region - if (S->Col > PrevColumn) - OS.indent(S->Col - PrevColumn); - PrevColumn = S->Col + 1; - std::string C = formatCount(S->Count); - PrevColumn += C.size(); - OS << '^' << C; - } - OS << "\n"; - - if (Options.Debug) - for (const auto *S : Segments) - errs() << "Marker at " << S->Line << ":" << S->Col << " = " - << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n"); -} - void SourceCoverageView::render(raw_ostream &OS, bool WholeFile, - unsigned IndentLevel) { + bool RenderTitle, unsigned IndentLevel) { // The width of the leading columns unsigned CombinedColumnWidth = (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); - // The width of the line that is used to divide between the view and the - // subviews. - unsigned DividerWidth = CombinedColumnWidth + 4; - // We need the expansions and instantiations sorted so we can go through them // while we iterate lines. std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end()); @@ -191,10 +217,10 @@ LineCount.addRegionCount(WrappedSegment->Count); for (const auto *S : LineSegments) if (S->HasCount && S->IsRegionEntry) - LineCount.addRegionStartCount(S->Count); + LineCount.addRegionStartCount(S->Count); + + startLine(OS, IndentLevel); - // Render the line prefix. - renderIndent(OS, IndentLevel); if (Options.ShowLineStats) renderLineCoverageColumn(OS, LineCount); if (Options.ShowLineNumbers) @@ -202,8 +228,7 @@ // If there are expansion subviews, we want to highlight the first one. unsigned ExpansionColumn = 0; - if (NextESV != EndESV && NextESV->getLine() == LI.line_number() && - Options.Colors) + if (NextESV != EndESV && NextESV->getLine() == LI.line_number()) ExpansionColumn = NextESV->getStartCol(); // Display the source code for the current line. @@ -214,51 +239,357 @@ if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers || LineCount.hasMultipleRegions()) && !LineSegments.empty()) { - renderIndent(OS, IndentLevel); - OS.indent(CombinedColumnWidth); - renderRegionMarkers(OS, LineSegments); + renderRegionMarkers(OS, LineSegments, IndentLevel, CombinedColumnWidth); + + if (Options.Debug) + for (const auto *S : LineSegments) + errs() << "Marker at " << S->Line << ":" << S->Col << " = " + << formatCount(S->Count) + << (S->IsRegionEntry ? "\n" : " (pop)\n"); } + // The width of the line that is used to divide between the view and the + // subviews. + unsigned DividerWidth = CombinedColumnWidth + 4; // Show the expansions and instantiations for this line. unsigned NestedIndent = IndentLevel + 1; bool RenderedSubView = false; for (; NextESV != EndESV && NextESV->getLine() == LI.line_number(); ++NextESV) { - renderViewDivider(NestedIndent, DividerWidth, OS); - OS << "\n"; - if (RenderedSubView) { - // Re-render the current line and highlight the expansion range for - // this subview. - ExpansionColumn = NextESV->getStartCol(); - renderIndent(OS, IndentLevel); - OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1)); - renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments, - ExpansionColumn); - renderViewDivider(NestedIndent, DividerWidth, OS); - OS << "\n"; - } - // Render the child subview - if (Options.Debug) - errs() << "Expansion at line " << NextESV->getLine() << ", " - << NextESV->getStartCol() << " -> " << NextESV->getEndCol() - << "\n"; - NextESV->View->render(OS, false, NestedIndent); + renderExpansionView(OS, LI, *NextESV, WrappedSegment, LineSegments, + RenderedSubView, IndentLevel, NestedIndent, + CombinedColumnWidth, DividerWidth); RenderedSubView = true; } for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) { - renderViewDivider(NestedIndent, DividerWidth, OS); - OS << "\n"; - renderIndent(OS, NestedIndent); - OS << ' '; - Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName - << ":"; - OS << "\n"; - NextISV->View->render(OS, false, NestedIndent); + renderInstantiationView(OS, *NextISV, IndentLevel, NestedIndent, + DividerWidth); RenderedSubView = true; } if (RenderedSubView) { - renderViewDivider(NestedIndent, DividerWidth, OS); - OS << "\n"; + finishRenderingSubviews(OS, NestedIndent, DividerWidth); } + + endLine(OS); + } +} + +void SourceCoverageView::renderRegionMarkers( + raw_ostream &OS, ArrayRef Segments, + unsigned IndentLevel, unsigned CombinedColumnWidth) { + OS.indent(CombinedColumnWidth); + unsigned PrevColumn = 1; + for (const auto *S : Segments) { + if (!S->IsRegionEntry) + continue; + // Skip to the new region + if (S->Col > PrevColumn) + OS.indent(S->Col - PrevColumn); + PrevColumn = S->Col + 1; + std::string C = formatCount(S->Count); + PrevColumn += C.size(); + OS << '^' << C; + } + OS << "\n"; +} + +//===----------------------------------------------------------------------===// +// +// SourceCoverageViewHTML +// +//===----------------------------------------------------------------------===// + +void SourceCoverageViewHTML::output(raw_ostream &OS, StringRef Text, + Optional Highlight, + const coverage::CoverageSegment *Segment) { + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR; + std::string EscapedText = html::escape(Text); + if (Highlight) { + EscapedText = html::tag("span", EscapedText, classForColor(Color)); + } + if (Options.ShowRegionMarkers && Segment && Segment->IsRegionEntry) { + std::string C = formatCount(Segment->Count); + auto CountTag = html::tag("span", html::escape(C), "tooltip-content"); + EscapedText = html::tag("div", EscapedText + CountTag, "tooltips"); + } + OS << EscapedText; +} + +void SourceCoverageViewHTML::renderLine( + raw_ostream &OS, StringRef Line, int64_t LineNumber, + const coverage::CoverageSegment *WrappedSegment, + ArrayRef Segments, + unsigned int ExpansionCol) { + OS << "
\n";
+  SourceCoverageView::renderLine(OS, Line, LineNumber, WrappedSegment, Segments,
+                                 ExpansionCol);
+  OS << "
\n"; + if (ExpansionSubViews.empty() && InstantiationSubViews.empty()) { + // don't end the source line if we're going to embed sub-tables. + OS << "\n"; + } +} + +void SourceCoverageViewHTML::renderTitle(raw_ostream &OS) { + OS << "
\n" + << "
" << std::string(SourceName) << ":
" + << "
\n"; +} + +void SourceCoverageViewHTML::renderLineCoverageColumn( + raw_ostream &OS, const LineCoverageInfo &Line) { + if (Line.isMapped()) { + std::string C = formatCount(Line.ExecutionCount); + OS << html::tag("td", html::tag("pre", html::escape(C)), "numeric") << "\n"; + } else { + OS << html::tag("td", "", "numeric") << "\n"; + } +} + +void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS, + unsigned int LineNo) { + SmallString<32> Buffer; + raw_svector_ostream BufferOS(Buffer); + BufferOS << LineNo; + auto Str = BufferOS.str(); + OS << html::tag("td", html::tag("pre", Str), "numeric") << "\n"; +} + +void SourceCoverageViewHTML::renderExpansionView( + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView, + const coverage::CoverageSegment *WrappedSegment, + SmallVector &LineSegments, + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent, + unsigned CombinedColumnWidth, unsigned DividerWidth) { + // Render the child subview + + if (RenderedSubView) { + // Re-render the current line and highlight the expansion range for + // this subview. + unsigned ExpansionColumn = ExpansionView.getStartCol(); + OS << "
";
+    SourceCoverageView::renderLine(OS, *LI, LI.line_number(), WrappedSegment,
+                                   LineSegments, ExpansionColumn);
+    OS << "
\n"; + } + + OS << "
\n"; + SourceCoverageView::renderExpansionView( + OS, LI, ExpansionView, WrappedSegment, LineSegments, RenderedSubView, + IndentLevel, NestedIndent, CombinedColumnWidth, DividerWidth); + OS << "
\n"; +} + +void SourceCoverageViewHTML::renderInstantiationView( + llvm::raw_ostream &OS, llvm::InstantiationView &InstantiationView, + unsigned IndentLevel, unsigned NestedIndent, unsigned DividerWidth) { + OS << "
\n"; + SourceCoverageView::renderInstantiationView( + OS, InstantiationView, IndentLevel, NestedIndent, DividerWidth); + OS << "
\n"; +} + +void SourceCoverageViewHTML::finishRenderingSubviews(raw_ostream &OS, + unsigned IndentLevel, + unsigned DividerWidth) { + OS << "\n"; +} + +void SourceCoverageViewHTML::renderFileHeader(raw_ostream &OS) { +// bring in the `CoverageCSS` string declared in CoverageCSS.inc +#include "CoverageCSS.inc" + + OS << "\n" + "\n" + " \n" + " \n" + " " + " \n" + " \n" + " \n"; +} + +void SourceCoverageViewHTML::renderFileFooter(raw_ostream &OS) { + OS << " \n" + "\n"; +} + +void SourceCoverageViewHTML::render(raw_ostream &OS, bool WholeFile, + bool RenderTitle, unsigned IndentLevel) { + if (WholeFile) { + renderFileHeader(OS); + OS << "
\n"; + } + + if (RenderTitle) { + renderTitle(OS); + } + + OS << ""; + SourceCoverageView::render(OS, WholeFile, IndentLevel); + OS << "
\n"; + if (WholeFile) { + OS << "
\n"; + } + if (WholeFile) { + renderFileFooter(OS); + } +} + +void SourceCoverageViewHTML::startLine(raw_ostream &OS, unsigned IndentLevel) { + OS << "\n"; +} + +void SourceCoverageViewHTML::endLine(raw_ostream &OS) { OS << "\n"; } + +void SourceCoverageViewHTML::renderIndex( + raw_ostream &OS, std::vector &SourceFiles) { + SourceCoverageViewHTML::renderFileHeader(OS); + OS << "
\n" + "
Index
\n" + "\n"; + for (auto &SourceFile : SourceFiles) { + std::string Relative = sys::path::relative_path(SourceFile); + std::string Link = Relative + ".html"; + OS << html::tag( + "tr", + html::tag("td", html::tag("pre", html::a(Link, Relative)), "code")); + OS << "\n"; + } + OS << "
\n" + "
\n"; + SourceCoverageViewHTML::renderFileFooter(OS); +} + +//===----------------------------------------------------------------------===// +// +// SourceCoverageViewText +// +//===----------------------------------------------------------------------===// + +void SourceCoverageViewText::output(raw_ostream &OS, StringRef Text, + Optional Highlight, + const coverage::CoverageSegment *Segment) { + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR; + colored_ostream(OS, Color, Options.Colors && Highlight, /*Bold=*/false, + /*BG=*/true) + << Text; +} + +void SourceCoverageViewText::render(raw_ostream &OS, bool WholeFile, + bool RenderTitle, unsigned IndentLevel) { + if (RenderTitle) + renderTitle(OS); + SourceCoverageView::render(OS, WholeFile, RenderTitle, IndentLevel); +} + +void SourceCoverageViewText::renderTitle(raw_ostream &OS) { + Options.colored_ostream(OS, raw_ostream::CYAN) << SourceName << ":\n"; +} + +void SourceCoverageViewText::renderLine( + raw_ostream &OS, StringRef Line, int64_t LineNumber, + const coverage::CoverageSegment *WrappedSegment, + ArrayRef Segments, + unsigned int ExpansionCol) { + SourceCoverageView::renderLine(OS, Line, LineNumber, WrappedSegment, Segments, + ExpansionCol); + OS << "\n"; +} + +void SourceCoverageViewText::renderIndent(raw_ostream &OS, unsigned Level) { + for (unsigned I = 0; I < Level; ++I) + OS << " |"; +} + +void SourceCoverageViewText::renderViewDivider(unsigned Level, unsigned Length, + raw_ostream &OS) { + assert(Level != 0 && "Cannot render divider at top level"); + renderIndent(OS, Level - 1); + OS.indent(2); + for (unsigned I = 0; I < Length; ++I) + OS << "-"; +} + +void SourceCoverageViewText::renderLineCoverageColumn( + raw_ostream &OS, const LineCoverageInfo &Line) { + + std::string C = formatCount(Line.ExecutionCount); + if (!Line.isMapped()) { + OS.indent(LineCoverageColumnWidth) << '|'; + return; + } + OS.indent(LineCoverageColumnWidth - C.size()); + colored_ostream(OS, raw_ostream::MAGENTA, + Line.hasMultipleRegions() && Options.Colors) + << C; + OS << '|'; +} + +void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS, + unsigned LineNo) { + SmallString<32> Buffer; + raw_svector_ostream BufferOS(Buffer); + BufferOS << LineNo; + auto Str = BufferOS.str(); + // Trim and align to the right + Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); + OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; +} + +void SourceCoverageViewText::renderRegionMarkers( + raw_ostream &OS, ArrayRef Segments, + unsigned IndentLevel, unsigned CombinedColumnWidth) { + renderIndent(OS, IndentLevel); + SourceCoverageView::renderRegionMarkers(OS, Segments, IndentLevel, + CombinedColumnWidth); +} + +void SourceCoverageViewText::startLine(raw_ostream &OS, unsigned IndentLevel) { + renderIndent(OS, IndentLevel); +} + +void SourceCoverageViewText::renderInstantiationView( + raw_ostream &OS, InstantiationView &InstantiationView, unsigned IndentLevel, + unsigned NestedIndent, unsigned DividerWidth) { + renderViewDivider(NestedIndent, DividerWidth, OS); + OS << "\n"; + renderIndent(OS, NestedIndent); + OS << ' '; + SourceCoverageView::renderInstantiationView( + OS, InstantiationView, IndentLevel, NestedIndent, DividerWidth); +} + +void SourceCoverageViewText::finishRenderingSubviews(raw_ostream &OS, + unsigned IndentLevel, + unsigned DividerWidth) { + renderViewDivider(IndentLevel, DividerWidth, OS); + OS << "\n"; +} + +void SourceCoverageViewText::renderExpansionView( + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView, + const coverage::CoverageSegment *WrappedSegment, + SmallVector &LineSegments, + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent, + unsigned CombinedColumnWidth, unsigned DividerWidth) { + + renderViewDivider(NestedIndent, DividerWidth, OS); + OS << "\n"; + if (RenderedSubView) { + // Re-render the current line and highlight the expansion range for + // this subview. + unsigned ExpansionColumn = ExpansionView.getStartCol(); + renderIndent(OS, IndentLevel); + OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1)); + renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments, + ExpansionColumn); + renderViewDivider(NestedIndent, DividerWidth, OS); + OS << "\n"; } + SourceCoverageView::renderExpansionView( + OS, LI, ExpansionView, WrappedSegment, LineSegments, RenderedSubView, + IndentLevel, NestedIndent, CombinedColumnWidth, DividerWidth); }