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 << "<"; 2606fa0e026SSam McCall break; 2616fa0e026SSam McCall case '&': 2626fa0e026SSam McCall OS << "&"; 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 << " <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() << "'>◊</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