xref: /llvm-project/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp (revision ec6c3448d31056db5d63d7aed3e9f207edb49321)
16fa0e026SSam McCall //===--- HTMLReport.cpp - Explain the analysis for humans -----------------===//
26fa0e026SSam McCall //
36fa0e026SSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
46fa0e026SSam McCall // See https://llvm.org/LICENSE.txt for license information.
56fa0e026SSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66fa0e026SSam McCall //
76fa0e026SSam McCall //===----------------------------------------------------------------------===//
86fa0e026SSam McCall //
96fa0e026SSam McCall // If we're debugging this tool or trying to explain its conclusions, we need to
106fa0e026SSam McCall // be able to identify specific facts about the code and the inferences made.
116fa0e026SSam McCall //
126fa0e026SSam McCall // This library prints an annotated version of the code
136fa0e026SSam McCall //
146fa0e026SSam McCall //===----------------------------------------------------------------------===//
156fa0e026SSam McCall 
166fa0e026SSam McCall #include "AnalysisInternal.h"
1764d97136Skadir çetinkaya #include "clang-include-cleaner/IncludeSpeller.h"
1826757732SSam McCall #include "clang-include-cleaner/Types.h"
196fa0e026SSam McCall #include "clang/AST/ASTContext.h"
206fa0e026SSam McCall #include "clang/AST/PrettyPrinter.h"
216fa0e026SSam McCall #include "clang/Basic/SourceManager.h"
226a95e673SSam McCall #include "clang/Lex/HeaderSearch.h"
236fa0e026SSam McCall #include "clang/Lex/Lexer.h"
24*ec6c3448Skadir çetinkaya #include "clang/Lex/Preprocessor.h"
2526757732SSam McCall #include "clang/Tooling/Inclusions/StandardLibrary.h"
2626757732SSam McCall #include "llvm/Support/ScopedPrinter.h"
276fa0e026SSam McCall #include "llvm/Support/raw_ostream.h"
289d5e82e7SSam McCall #include <numeric>
296fa0e026SSam McCall 
306fa0e026SSam McCall namespace clang::include_cleaner {
316fa0e026SSam McCall namespace {
326fa0e026SSam McCall 
336fa0e026SSam McCall constexpr llvm::StringLiteral CSS = R"css(
3425bac38aSSam McCall   body { margin: 0; }
3525bac38aSSam McCall   pre { line-height: 1.5em; counter-reset: line; margin: 0; }
366a95e673SSam McCall   pre .line:not(.added) { counter-increment: line; }
3725bac38aSSam McCall   pre .line::before {
3825bac38aSSam McCall     content: counter(line);
3925bac38aSSam McCall     display: inline-block;
4025bac38aSSam McCall     background-color: #eee; border-right: 1px solid #ccc;
4125bac38aSSam McCall     text-align: right;
4225bac38aSSam McCall     width: 3em; padding-right: 0.5em; margin-right: 0.5em;
4325bac38aSSam McCall   }
446a95e673SSam McCall   pre .line.added::before { content: '+' }
453e658abdSSam McCall   .ref, .inc { text-decoration: underline; color: #008; }
466fa0e026SSam McCall   .sel { position: relative; cursor: pointer; }
4726757732SSam McCall   .ref.implicit { background-color: #ff8; }
486fa0e026SSam McCall   #hover {
4926757732SSam McCall     color: black;
5026757732SSam McCall     background-color: #aaccff; border: 1px solid #444;
516fa0e026SSam McCall     z-index: 1;
526fa0e026SSam McCall     position: absolute; top: 100%; left: 0;
536fa0e026SSam McCall     font-family: sans-serif;
546fa0e026SSam McCall     padding: 0.5em;
556fa0e026SSam McCall   }
566fa0e026SSam McCall   #hover p, #hover pre { margin: 0; }
573e658abdSSam McCall   #hover .target.implicit, .provides .implicit { background-color: #bbb; }
583e658abdSSam McCall   #hover .target.ambiguous, .provides .ambiguous { background-color: #caf; }
5919ab2a67SSam McCall   .missing, .unused { background-color: #faa !important; }
606a95e673SSam McCall   .inserted { background-color: #bea !important; }
613e658abdSSam McCall   .semiused { background-color: #888 !important; }
6226757732SSam McCall   #hover th { color: #008; text-align: right; padding-right: 0.5em; }
6326757732SSam McCall   #hover .target:not(:first-child) {
6426757732SSam McCall     margin-top: 1em;
6526757732SSam McCall     padding-top: 1em;
6626757732SSam McCall     border-top: 1px solid #444;
6726757732SSam McCall   }
689d5e82e7SSam McCall   .ref.missing #hover .insert { background-color: #bea; }
696a95e673SSam McCall   .ref:not(.missing) #hover .insert { font-style: italic; }
706fa0e026SSam McCall )css";
716fa0e026SSam McCall 
726fa0e026SSam McCall constexpr llvm::StringLiteral JS = R"js(
736fa0e026SSam McCall   // Recreate the #hover div inside whichever target .sel element was clicked.
746fa0e026SSam McCall   function select(event) {
756fa0e026SSam McCall     var target = event.target.closest('.sel');
766fa0e026SSam McCall     var hover = document.getElementById('hover');
776fa0e026SSam McCall     if (hover) {
786fa0e026SSam McCall       if (hover.parentElement == target) return;
796fa0e026SSam McCall       hover.parentNode.removeChild(hover);
806fa0e026SSam McCall     }
816fa0e026SSam McCall     if (target == null) return;
826fa0e026SSam McCall     hover = document.createElement('div');
836fa0e026SSam McCall     hover.id = 'hover';
846fa0e026SSam McCall     fillHover(hover, target);
856fa0e026SSam McCall     target.appendChild(hover);
866fa0e026SSam McCall   }
876fa0e026SSam McCall   // Fill the #hover div with the templates named by data-hover in the target.
886fa0e026SSam McCall   function fillHover(hover, target) {
896fa0e026SSam McCall     target.dataset.hover?.split(',').forEach(function(id) {
906fa0e026SSam McCall       for (c of document.getElementById(id).content.childNodes)
916fa0e026SSam McCall         hover.appendChild(c.cloneNode(true));
926fa0e026SSam McCall     })
936fa0e026SSam McCall   }
946fa0e026SSam McCall )js";
956fa0e026SSam McCall 
9626757732SSam McCall // Categorize the symbol, like FunctionDecl or Macro
9726757732SSam McCall llvm::StringRef describeSymbol(const Symbol &Sym) {
9826757732SSam McCall   switch (Sym.kind()) {
9926757732SSam McCall   case Symbol::Declaration:
10026757732SSam McCall     return Sym.declaration().getDeclKindName();
10126757732SSam McCall   case Symbol::Macro:
10226757732SSam McCall     return "Macro";
10326757732SSam McCall   }
10426757732SSam McCall   llvm_unreachable("unhandled symbol kind");
10526757732SSam McCall }
10626757732SSam McCall 
1073e658abdSSam McCall // Return detailed symbol description (declaration), if we have any.
1083e658abdSSam McCall std::string printDetails(const Symbol &Sym) {
1093e658abdSSam McCall   std::string S;
1103e658abdSSam McCall   if (Sym.kind() == Symbol::Declaration) {
1113e658abdSSam McCall     // Print the declaration of the symbol, e.g. to disambiguate overloads.
1123e658abdSSam McCall     const auto &D = Sym.declaration();
1133e658abdSSam McCall     PrintingPolicy PP = D.getASTContext().getPrintingPolicy();
1143e658abdSSam McCall     PP.FullyQualifiedName = true;
1153e658abdSSam McCall     PP.TerseOutput = true;
1163e658abdSSam McCall     PP.SuppressInitializers = true;
1173e658abdSSam McCall     llvm::raw_string_ostream SS(S);
1183e658abdSSam McCall     D.print(SS, PP);
1193e658abdSSam McCall   }
1203e658abdSSam McCall   return S;
1213e658abdSSam McCall }
1223e658abdSSam McCall 
12326757732SSam McCall llvm::StringRef refType(RefType T) {
12426757732SSam McCall   switch (T) {
12526757732SSam McCall   case RefType::Explicit:
12626757732SSam McCall     return "explicit";
12726757732SSam McCall   case RefType::Implicit:
12826757732SSam McCall     return "implicit";
12926757732SSam McCall   case RefType::Ambiguous:
13026757732SSam McCall     return "ambiguous";
13126757732SSam McCall   }
1326faf5d72SSimon Pilgrim   llvm_unreachable("unhandled RefType enum");
1336fa0e026SSam McCall }
1346fa0e026SSam McCall 
1356fa0e026SSam McCall class Reporter {
1366fa0e026SSam McCall   llvm::raw_ostream &OS;
1376fa0e026SSam McCall   const ASTContext &Ctx;
1386fa0e026SSam McCall   const SourceManager &SM;
139*ec6c3448Skadir çetinkaya   const Preprocessor &PP;
140d3714c2bSSam McCall   const include_cleaner::Includes &Includes;
14126757732SSam McCall   const PragmaIncludes *PI;
14219ab2a67SSam McCall   FileID MainFile;
14319ab2a67SSam McCall   const FileEntry *MainFE;
1446fa0e026SSam McCall 
1459d5e82e7SSam McCall   // Points within the main file that reference a Symbol.
1469d5e82e7SSam McCall   // Implicit refs will be marked with a symbol just before the token.
1479d5e82e7SSam McCall   struct Ref {
1489d5e82e7SSam McCall     unsigned Offset;
14926757732SSam McCall     RefType Type;
1509d5e82e7SSam McCall     Symbol Sym;
1519c8a6a16SKazu Hirata     SmallVector<SymbolLocation> Locations = {};
1529c8a6a16SKazu Hirata     SmallVector<Header> Headers = {};
1539c8a6a16SKazu Hirata     SmallVector<const Include *> Includes = {};
15419ab2a67SSam McCall     bool Satisfied = false;  // Is the include present?
1559c8a6a16SKazu Hirata     std::string Insert = {}; // If we had no includes, what would we insert?
1566fa0e026SSam McCall   };
15726757732SSam McCall   std::vector<Ref> Refs;
1583e658abdSSam McCall   llvm::DenseMap<const Include *, std::vector<unsigned>> IncludeRefs;
1596a95e673SSam McCall   llvm::StringMap<std::vector</*RefIndex*/ unsigned>> Insertion;
1603e658abdSSam McCall 
1613e658abdSSam McCall   llvm::StringRef includeType(const Include *I) {
1623e658abdSSam McCall     auto &List = IncludeRefs[I];
1633e658abdSSam McCall     if (List.empty())
1643e658abdSSam McCall       return "unused";
1653e658abdSSam McCall     if (llvm::any_of(List, [&](unsigned I) {
1669d5e82e7SSam McCall           return Refs[I].Type == RefType::Explicit;
1673e658abdSSam McCall         }))
1683e658abdSSam McCall       return "used";
1693e658abdSSam McCall     return "semiused";
1703e658abdSSam McCall   }
17126757732SSam McCall 
1729d5e82e7SSam McCall   void fillTarget(Ref &R) {
17326757732SSam McCall     // Duplicates logic from walkUsed(), which doesn't expose SymbolLocations.
174*ec6c3448Skadir çetinkaya     for (auto &Loc : locateSymbol(R.Sym, Ctx.getLangOpts()))
17529a8eec1SKadir Cetinkaya       R.Locations.push_back(Loc);
176*ec6c3448Skadir çetinkaya     R.Headers = headersForSymbol(R.Sym, PP, PI);
17719ab2a67SSam McCall 
1789d5e82e7SSam McCall     for (const auto &H : R.Headers) {
1799d5e82e7SSam McCall       R.Includes.append(Includes.match(H));
18019ab2a67SSam McCall       // FIXME: library should signal main-file refs somehow.
18119ab2a67SSam McCall       // Non-physical refs to the main-file should be possible.
18219ab2a67SSam McCall       if (H.kind() == Header::Physical && H.physical() == MainFE)
1839d5e82e7SSam McCall         R.Satisfied = true;
18419ab2a67SSam McCall     }
1859d5e82e7SSam McCall     if (!R.Includes.empty())
1869d5e82e7SSam McCall       R.Satisfied = true;
18719ab2a67SSam McCall     // Include pointers are meaningfully ordered as they are backed by a vector.
1889d5e82e7SSam McCall     llvm::sort(R.Includes);
1899d5e82e7SSam McCall     R.Includes.erase(std::unique(R.Includes.begin(), R.Includes.end()),
1909d5e82e7SSam McCall                      R.Includes.end());
19126757732SSam McCall 
1929d5e82e7SSam McCall     if (!R.Headers.empty())
193*ec6c3448Skadir çetinkaya       R.Insert =
194*ec6c3448Skadir çetinkaya           spellHeader({R.Headers.front(), PP.getHeaderSearchInfo(), MainFE});
19526757732SSam McCall   }
1966fa0e026SSam McCall 
1976fa0e026SSam McCall public:
198*ec6c3448Skadir çetinkaya   Reporter(llvm::raw_ostream &OS, ASTContext &Ctx, const Preprocessor &PP,
19929a8eec1SKadir Cetinkaya            const include_cleaner::Includes &Includes, const PragmaIncludes *PI,
20029a8eec1SKadir Cetinkaya            FileID MainFile)
201*ec6c3448Skadir çetinkaya       : OS(OS), Ctx(Ctx), SM(Ctx.getSourceManager()), PP(PP),
2026a95e673SSam McCall         Includes(Includes), PI(PI), MainFile(MainFile),
2036a95e673SSam McCall         MainFE(SM.getFileEntryForID(MainFile)) {}
2046fa0e026SSam McCall 
20526757732SSam McCall   void addRef(const SymbolReference &SR) {
20626757732SSam McCall     auto [File, Offset] = SM.getDecomposedLoc(SM.getFileLoc(SR.RefLocation));
20719ab2a67SSam McCall     if (File != this->MainFile) {
20825bac38aSSam McCall       // Can get here e.g. if there's an #include inside a root Decl.
20925bac38aSSam McCall       // FIXME: do something more useful than this.
21026757732SSam McCall       llvm::errs() << "Ref location outside file! " << SR.Target << " at "
21126757732SSam McCall                    << SR.RefLocation.printToString(SM) << "\n";
21225bac38aSSam McCall       return;
21325bac38aSSam McCall     }
21426757732SSam McCall 
2159d5e82e7SSam McCall     int RefIndex = Refs.size();
2169d5e82e7SSam McCall     Refs.emplace_back(Ref{Offset, SR.RT, SR.Target});
2179d5e82e7SSam McCall     Ref &R = Refs.back();
2189d5e82e7SSam McCall     fillTarget(R);
2199d5e82e7SSam McCall     for (const auto *I : R.Includes)
2209d5e82e7SSam McCall       IncludeRefs[I].push_back(RefIndex);
2219d5e82e7SSam McCall     if (R.Type == RefType::Explicit && !R.Satisfied && !R.Insert.empty())
2229d5e82e7SSam McCall       Insertion[R.Insert].push_back(RefIndex);
2236fa0e026SSam McCall   }
2246fa0e026SSam McCall 
2256fa0e026SSam McCall   void write() {
2266fa0e026SSam McCall     OS << "<!doctype html>\n";
2276fa0e026SSam McCall     OS << "<html>\n";
2286fa0e026SSam McCall     OS << "<head>\n";
2296fa0e026SSam McCall     OS << "<style>" << CSS << "</style>\n";
2306fa0e026SSam McCall     OS << "<script>" << JS << "</script>\n";
2316a95e673SSam McCall     for (const auto &Ins : Insertion) {
2326a95e673SSam McCall       OS << "<template id='i";
2336a95e673SSam McCall       escapeString(Ins.first());
2346a95e673SSam McCall       OS << "'>";
2356a95e673SSam McCall       writeInsertion(Ins.first(), Ins.second);
2366a95e673SSam McCall       OS << "</template>\n";
2376a95e673SSam McCall     }
2383e658abdSSam McCall     for (auto &Inc : Includes.all()) {
2393e658abdSSam McCall       OS << "<template id='i" << Inc.Line << "'>";
2403e658abdSSam McCall       writeInclude(Inc);
2413e658abdSSam McCall       OS << "</template>\n";
2423e658abdSSam McCall     }
2439d5e82e7SSam McCall     for (unsigned I = 0; I < Refs.size(); ++I) {
24426757732SSam McCall       OS << "<template id='t" << I << "'>";
2459d5e82e7SSam McCall       writeTarget(Refs[I]);
24626757732SSam McCall       OS << "</template>\n";
2476fa0e026SSam McCall     }
2486fa0e026SSam McCall     OS << "</head>\n";
2496fa0e026SSam McCall     OS << "<body>\n";
2506fa0e026SSam McCall     writeCode();
2516fa0e026SSam McCall     OS << "</body>\n";
2526fa0e026SSam McCall     OS << "</html>\n";
2536fa0e026SSam McCall   }
2546fa0e026SSam McCall 
2556fa0e026SSam McCall private:
2566fa0e026SSam McCall   void escapeChar(char C) {
2576fa0e026SSam McCall     switch (C) {
2586fa0e026SSam McCall     case '<':
2596fa0e026SSam McCall       OS << "&lt;";
2606fa0e026SSam McCall       break;
2616fa0e026SSam McCall     case '&':
2626fa0e026SSam McCall       OS << "&amp;";
2636fa0e026SSam McCall       break;
2646fa0e026SSam McCall     default:
2656fa0e026SSam McCall       OS << C;
2666fa0e026SSam McCall     }
2676fa0e026SSam McCall   }
2686fa0e026SSam McCall 
2696fa0e026SSam McCall   void escapeString(llvm::StringRef S) {
2706fa0e026SSam McCall     for (char C : S)
2716fa0e026SSam McCall       escapeChar(C);
2726fa0e026SSam McCall   }
2736fa0e026SSam McCall 
27426757732SSam McCall   // Abbreviate a path ('path/to/Foo.h') to just the filename ('Foo.h').
27526757732SSam McCall   // The full path is available on hover.
27626757732SSam McCall   void printFilename(llvm::StringRef Path) {
27726757732SSam McCall     llvm::StringRef File = llvm::sys::path::filename(Path);
27826757732SSam McCall     if (File == Path)
27926757732SSam McCall       return escapeString(Path);
28026757732SSam McCall     OS << "<span title='";
28126757732SSam McCall     escapeString(Path);
28226757732SSam McCall     OS << "'>";
28326757732SSam McCall     escapeString(File);
28426757732SSam McCall     OS << "</span>";
28526757732SSam McCall   }
2866fa0e026SSam McCall 
28726757732SSam McCall   // Print a source location in compact style.
28826757732SSam McCall   void printSourceLocation(SourceLocation Loc) {
28926757732SSam McCall     if (Loc.isInvalid())
29026757732SSam McCall       return escapeString("<invalid>");
29126757732SSam McCall     if (!Loc.isMacroID())
29226757732SSam McCall       return printFilename(Loc.printToString(SM));
29326757732SSam McCall 
29426757732SSam McCall     // Replicating printToString() is a bit simpler than parsing/reformatting.
29526757732SSam McCall     printFilename(SM.getExpansionLoc(Loc).printToString(SM));
29626757732SSam McCall     OS << " &lt;Spelling=";
29726757732SSam McCall     printFilename(SM.getSpellingLoc(Loc).printToString(SM));
29826757732SSam McCall     OS << ">";
29926757732SSam McCall   }
30026757732SSam McCall 
3016a95e673SSam McCall   // Write "Provides: " rows of an include or include-insertion table.
3026a95e673SSam McCall   // These describe the symbols the header provides, referenced by RefIndices.
3036a95e673SSam McCall   void writeProvides(llvm::ArrayRef<unsigned> RefIndices) {
3043e658abdSSam McCall     // We show one ref for each symbol: first by (RefType != Explicit, Sequence)
3053e658abdSSam McCall     llvm::DenseMap<Symbol, /*RefIndex*/ unsigned> FirstRef;
3066a95e673SSam McCall     for (unsigned RefIndex : RefIndices) {
3079d5e82e7SSam McCall       const Ref &R = Refs[RefIndex];
3089d5e82e7SSam McCall       auto I = FirstRef.try_emplace(R.Sym, RefIndex);
3099d5e82e7SSam McCall       if (!I.second && R.Type == RefType::Explicit &&
3109d5e82e7SSam McCall           Refs[I.first->second].Type != RefType::Explicit)
3113e658abdSSam McCall         I.first->second = RefIndex;
3123e658abdSSam McCall     }
3133e658abdSSam McCall     std::vector<std::pair<Symbol, unsigned>> Sorted = {FirstRef.begin(),
3143e658abdSSam McCall                                                        FirstRef.end()};
3153e658abdSSam McCall     llvm::stable_sort(Sorted, llvm::less_second{});
3163e658abdSSam McCall     for (auto &[S, RefIndex] : Sorted) {
3179d5e82e7SSam McCall       auto &R = Refs[RefIndex];
3183e658abdSSam McCall       OS << "<tr class='provides'><th>Provides</td><td>";
3193e658abdSSam McCall       std::string Details = printDetails(S);
3203e658abdSSam McCall       if (!Details.empty()) {
3219d5e82e7SSam McCall         OS << "<span class='" << refType(R.Type) << "' title='";
3223e658abdSSam McCall         escapeString(Details);
3233e658abdSSam McCall         OS << "'>";
3243e658abdSSam McCall       }
3253e658abdSSam McCall       escapeString(llvm::to_string(S));
3263e658abdSSam McCall       if (!Details.empty())
3273e658abdSSam McCall         OS << "</span>";
3283e658abdSSam McCall 
3299d5e82e7SSam McCall       unsigned Line = SM.getLineNumber(MainFile, R.Offset);
3303e658abdSSam McCall       OS << ", <a href='#line" << Line << "'>line " << Line << "</a>";
3313e658abdSSam McCall       OS << "</td></tr>";
3323e658abdSSam McCall     }
3336a95e673SSam McCall   }
3346a95e673SSam McCall 
3356a95e673SSam McCall   void writeInclude(const Include &Inc) {
3366a95e673SSam McCall     OS << "<table class='include'>";
3376a95e673SSam McCall     if (Inc.Resolved) {
3386a95e673SSam McCall       OS << "<tr><th>Resolved</td><td>";
3396a95e673SSam McCall       escapeString(Inc.Resolved->getName());
3406a95e673SSam McCall       OS << "</td></tr>\n";
3416a95e673SSam McCall       writeProvides(IncludeRefs[&Inc]);
3426a95e673SSam McCall     }
3436a95e673SSam McCall     OS << "</table>";
3446a95e673SSam McCall   }
3456a95e673SSam McCall 
3466a95e673SSam McCall   void writeInsertion(llvm::StringRef Text, llvm::ArrayRef<unsigned> Refs) {
3476a95e673SSam McCall     OS << "<table class='insertion'>";
3486a95e673SSam McCall     writeProvides(Refs);
3493e658abdSSam McCall     OS << "</table>";
3503e658abdSSam McCall   }
3513e658abdSSam McCall 
3529d5e82e7SSam McCall   void writeTarget(const Ref &R) {
3539d5e82e7SSam McCall     OS << "<table class='target " << refType(R.Type) << "'>";
35426757732SSam McCall 
35526757732SSam McCall     OS << "<tr><th>Symbol</th><td>";
3569d5e82e7SSam McCall     OS << describeSymbol(R.Sym) << " <code>";
3579d5e82e7SSam McCall     escapeString(llvm::to_string(R.Sym));
35826757732SSam McCall     OS << "</code></td></tr>\n";
35926757732SSam McCall 
3609d5e82e7SSam McCall     std::string Details = printDetails(R.Sym);
3613e658abdSSam McCall     if (!Details.empty()) {
36226757732SSam McCall       OS << "<tr><td></td><td><code>";
3633e658abdSSam McCall       escapeString(Details);
36426757732SSam McCall       OS << "</code></td></tr>\n";
36526757732SSam McCall     }
36626757732SSam McCall 
3679d5e82e7SSam McCall     for (const auto &Loc : R.Locations) {
36826757732SSam McCall       OS << "<tr><th>Location</th><td>";
36926757732SSam McCall       if (Loc.kind() == SymbolLocation::Physical) // needs SM to print properly.
37026757732SSam McCall         printSourceLocation(Loc.physical());
37126757732SSam McCall       else
37226757732SSam McCall         escapeString(llvm::to_string(Loc));
37326757732SSam McCall       OS << "</td></tr>\n";
37426757732SSam McCall     }
37526757732SSam McCall 
3769d5e82e7SSam McCall     for (const auto &H : R.Headers) {
37726757732SSam McCall       OS << "<tr><th>Header</th><td>";
37826757732SSam McCall       switch (H.kind()) {
37926757732SSam McCall       case Header::Physical:
38098e6deb6SJan Svoboda         printFilename(H.physical().getName());
38126757732SSam McCall         break;
38226757732SSam McCall       case Header::Standard:
38326757732SSam McCall         OS << "stdlib " << H.standard().name();
38426757732SSam McCall         break;
38526757732SSam McCall       case Header::Verbatim:
38626757732SSam McCall         OS << "verbatim ";
38726757732SSam McCall         escapeString(H.verbatim());
38826757732SSam McCall         break;
38926757732SSam McCall       }
39026757732SSam McCall       OS << "</td></tr>\n";
39126757732SSam McCall     }
39226757732SSam McCall 
3939d5e82e7SSam McCall     for (const auto *I : R.Includes) {
39419ab2a67SSam McCall       OS << "<tr><th>Included</th><td>";
3959961fa16SSam McCall       escapeString(I->quote());
39619ab2a67SSam McCall       OS << ", <a href='#line" << I->Line << "'>line " << I->Line << "</a>";
39719ab2a67SSam McCall       OS << "</td></tr>";
39819ab2a67SSam McCall     }
39919ab2a67SSam McCall 
4009d5e82e7SSam McCall     if (!R.Insert.empty()) {
4016a95e673SSam McCall       OS << "<tr><th>Insert</th><td class='insert'>";
4029d5e82e7SSam McCall       escapeString(R.Insert);
4036a95e673SSam McCall       OS << "</td></tr>";
4046a95e673SSam McCall     }
4056a95e673SSam McCall 
40626757732SSam McCall     OS << "</table>";
4076fa0e026SSam McCall   }
4086fa0e026SSam McCall 
4096fa0e026SSam McCall   void writeCode() {
41019ab2a67SSam McCall     llvm::StringRef Code = SM.getBufferData(MainFile);
4116fa0e026SSam McCall 
41225bac38aSSam McCall     OS << "<pre onclick='select(event)' class='code'>";
4133e658abdSSam McCall 
4146a95e673SSam McCall     std::vector<llvm::StringRef> Insertions{Insertion.keys().begin(),
4156a95e673SSam McCall                                             Insertion.keys().end()};
4166a95e673SSam McCall     llvm::sort(Insertions);
4176a95e673SSam McCall     for (llvm::StringRef Insertion : Insertions) {
4186a95e673SSam McCall       OS << "<code class='line added'>"
4196a95e673SSam McCall          << "<span class='inc sel inserted' data-hover='i";
4206a95e673SSam McCall       escapeString(Insertion);
4216a95e673SSam McCall       OS << "'>#include ";
4226a95e673SSam McCall       escapeString(Insertion);
4236a95e673SSam McCall       OS << "</span></code>\n";
4246a95e673SSam McCall     }
4256a95e673SSam McCall 
4263e658abdSSam McCall     const Include *Inc = nullptr;
4273e658abdSSam McCall     unsigned LineNum = 0;
4283e658abdSSam McCall     // Lines are <code>, include lines have an inner <span>.
4293e658abdSSam McCall     auto StartLine = [&] {
4303e658abdSSam McCall       ++LineNum;
4313e658abdSSam McCall       OS << "<code class='line' id='line" << LineNum << "'>";
4323e658abdSSam McCall       if ((Inc = Includes.atLine(LineNum)))
4333e658abdSSam McCall         OS << "<span class='inc sel " << includeType(Inc) << "' data-hover='i"
4343e658abdSSam McCall            << Inc->Line << "'>";
4353e658abdSSam McCall     };
4363e658abdSSam McCall     auto EndLine = [&] {
4373e658abdSSam McCall       if (Inc)
4383e658abdSSam McCall         OS << "</span>";
4393e658abdSSam McCall       OS << "</code>\n";
4403e658abdSSam McCall     };
4413e658abdSSam McCall 
4429d5e82e7SSam McCall     std::vector<unsigned> RefOrder(Refs.size());
4439d5e82e7SSam McCall     std::iota(RefOrder.begin(), RefOrder.end(), 0);
4449d5e82e7SSam McCall     llvm::stable_sort(RefOrder, [&](unsigned A, unsigned B) {
4459d5e82e7SSam McCall       return std::make_pair(Refs[A].Offset, Refs[A].Type != RefType::Implicit) <
4469d5e82e7SSam McCall              std::make_pair(Refs[B].Offset, Refs[B].Type != RefType::Implicit);
4479d5e82e7SSam McCall     });
448984b800aSserge-sans-paille     auto Rest = llvm::ArrayRef(RefOrder);
4496fa0e026SSam McCall     unsigned End = 0;
4503e658abdSSam McCall     StartLine();
4516fa0e026SSam McCall     for (unsigned I = 0; I < Code.size(); ++I) {
45225bac38aSSam McCall       // Finish refs early at EOL to avoid dealing with splitting the span.
45325bac38aSSam McCall       if (End && (End == I || Code[I] == '\n')) {
4546fa0e026SSam McCall         OS << "</span>";
4556fa0e026SSam McCall         End = 0;
4566fa0e026SSam McCall       }
45726757732SSam McCall       // Handle implicit refs, which are rendered *before* the token.
4589d5e82e7SSam McCall       while (!Rest.empty() && Refs[Rest.front()].Offset == I &&
4599d5e82e7SSam McCall              Refs[Rest.front()].Type == RefType::Implicit) {
4609d5e82e7SSam McCall         const Ref &R = Refs[Rest.front()];
46119ab2a67SSam McCall         OS << "<span class='ref sel implicit "
4629d5e82e7SSam McCall            << (R.Satisfied ? "satisfied" : "missing") << "' data-hover='t"
4639d5e82e7SSam McCall            << Rest.front() << "'>&loz;</span>";
46426757732SSam McCall         Rest = Rest.drop_front();
46526757732SSam McCall       };
46626757732SSam McCall       // Accumulate all explicit refs that appear on the same token.
4676fa0e026SSam McCall       std::string TargetList;
46819ab2a67SSam McCall       bool Unsatisfied = false;
4699d5e82e7SSam McCall       Rest = Rest.drop_while([&](unsigned RefIndex) {
4709d5e82e7SSam McCall         const Ref &R = Refs[RefIndex];
47126757732SSam McCall         if (R.Offset != I)
4726fa0e026SSam McCall           return false;
4736fa0e026SSam McCall         if (!TargetList.empty())
4746fa0e026SSam McCall           TargetList.push_back(',');
4756fa0e026SSam McCall         TargetList.push_back('t');
4769d5e82e7SSam McCall         TargetList.append(std::to_string(RefIndex));
4779d5e82e7SSam McCall         Unsatisfied = Unsatisfied || !R.Satisfied;
4786fa0e026SSam McCall         return true;
4796fa0e026SSam McCall       });
4806fa0e026SSam McCall       if (!TargetList.empty()) {
4816fa0e026SSam McCall         assert(End == 0 && "Overlapping tokens!");
48219ab2a67SSam McCall         OS << "<span class='ref sel" << (Unsatisfied ? " missing" : "")
48319ab2a67SSam McCall            << "' data-hover='" << TargetList << "'>";
48419ab2a67SSam McCall         End = I + Lexer::MeasureTokenLength(SM.getComposedLoc(MainFile, I), SM,
4856fa0e026SSam McCall                                             Ctx.getLangOpts());
4866fa0e026SSam McCall       }
4873e658abdSSam McCall       if (Code[I] == '\n') {
4883e658abdSSam McCall         EndLine();
4893e658abdSSam McCall         StartLine();
4903e658abdSSam McCall       } else
4916fa0e026SSam McCall         escapeChar(Code[I]);
4926fa0e026SSam McCall     }
4933e658abdSSam McCall     EndLine();
4943e658abdSSam McCall     OS << "</pre>\n";
4956fa0e026SSam McCall   }
4966fa0e026SSam McCall };
4976fa0e026SSam McCall 
4986fa0e026SSam McCall } // namespace
4996fa0e026SSam McCall 
500d3714c2bSSam McCall void writeHTMLReport(FileID File, const include_cleaner::Includes &Includes,
50119ab2a67SSam McCall                      llvm::ArrayRef<Decl *> Roots,
50226757732SSam McCall                      llvm::ArrayRef<SymbolReference> MacroRefs, ASTContext &Ctx,
503*ec6c3448Skadir çetinkaya                      const Preprocessor &PP, PragmaIncludes *PI,
5046a95e673SSam McCall                      llvm::raw_ostream &OS) {
505*ec6c3448Skadir çetinkaya   Reporter R(OS, Ctx, PP, Includes, PI, File);
506bf6e6551SHaojian Wu   const auto& SM = Ctx.getSourceManager();
5076fa0e026SSam McCall   for (Decl *Root : Roots)
50826757732SSam McCall     walkAST(*Root, [&](SourceLocation Loc, const NamedDecl &D, RefType T) {
509bf6e6551SHaojian Wu       if(!SM.isWrittenInMainFile(SM.getSpellingLoc(Loc)))
510bf6e6551SHaojian Wu         return;
5113b59842aSHaojian Wu       R.addRef(SymbolReference{D, Loc, T});
512d19ba74dSKadir Cetinkaya     });
513bf6e6551SHaojian Wu   for (const SymbolReference &Ref : MacroRefs) {
514bf6e6551SHaojian Wu     if (!SM.isWrittenInMainFile(SM.getSpellingLoc(Ref.RefLocation)))
515bf6e6551SHaojian Wu       continue;
51626757732SSam McCall     R.addRef(Ref);
517bf6e6551SHaojian Wu   }
5186fa0e026SSam McCall   R.write();
5196fa0e026SSam McCall }
5206fa0e026SSam McCall 
5216fa0e026SSam McCall } // namespace clang::include_cleaner
522