[include-cleaner] Show details for #include directives (used/unused)
Differential Revision: https://reviews.llvm.org/D138649
This commit is contained in:
parent
99089b490d
commit
3e658abd41
|
@ -147,11 +147,15 @@ struct RecordedPP {
|
||||||
/// - for a logical file like <vector>, we check Spelled
|
/// - for a logical file like <vector>, we check Spelled
|
||||||
llvm::SmallVector<const Include *> match(Header H) const;
|
llvm::SmallVector<const Include *> match(Header H) const;
|
||||||
|
|
||||||
|
/// Finds the include written on the specified line.
|
||||||
|
const Include *atLine(unsigned OneBasedIndex) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Include> All;
|
std::vector<Include> All;
|
||||||
// Lookup structures for match(), values are index into All.
|
// Lookup structures for match(), values are index into All.
|
||||||
llvm::StringMap<llvm::SmallVector<unsigned>> BySpelling;
|
llvm::StringMap<llvm::SmallVector<unsigned>> BySpelling;
|
||||||
llvm::DenseMap<const FileEntry *, llvm::SmallVector<unsigned>> ByFile;
|
llvm::DenseMap<const FileEntry *, llvm::SmallVector<unsigned>> ByFile;
|
||||||
|
llvm::DenseMap<unsigned, unsigned> ByLine;
|
||||||
} Includes;
|
} Includes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ constexpr llvm::StringLiteral CSS = R"css(
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 3em; padding-right: 0.5em; margin-right: 0.5em;
|
width: 3em; padding-right: 0.5em; margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
.ref { text-decoration: underline; color: #008; }
|
.ref, .inc { text-decoration: underline; color: #008; }
|
||||||
.sel { position: relative; cursor: pointer; }
|
.sel { position: relative; cursor: pointer; }
|
||||||
.ref.implicit { background-color: #ff8; }
|
.ref.implicit { background-color: #ff8; }
|
||||||
#hover {
|
#hover {
|
||||||
|
@ -49,9 +49,10 @@ constexpr llvm::StringLiteral CSS = R"css(
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
#hover p, #hover pre { margin: 0; }
|
#hover p, #hover pre { margin: 0; }
|
||||||
#hover .target.implicit { background-color: #bbb; }
|
#hover .target.implicit, .provides .implicit { background-color: #bbb; }
|
||||||
#hover .target.ambiguous { background-color: #caf; }
|
#hover .target.ambiguous, .provides .ambiguous { background-color: #caf; }
|
||||||
.missing, .unused { background-color: #faa !important; }
|
.missing, .unused { background-color: #faa !important; }
|
||||||
|
.semiused { background-color: #888 !important; }
|
||||||
#hover th { color: #008; text-align: right; padding-right: 0.5em; }
|
#hover th { color: #008; text-align: right; padding-right: 0.5em; }
|
||||||
#hover .target:not(:first-child) {
|
#hover .target:not(:first-child) {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
@ -95,6 +96,22 @@ llvm::StringRef describeSymbol(const Symbol &Sym) {
|
||||||
llvm_unreachable("unhandled symbol kind");
|
llvm_unreachable("unhandled symbol kind");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return detailed symbol description (declaration), if we have any.
|
||||||
|
std::string printDetails(const Symbol &Sym) {
|
||||||
|
std::string S;
|
||||||
|
if (Sym.kind() == Symbol::Declaration) {
|
||||||
|
// Print the declaration of the symbol, e.g. to disambiguate overloads.
|
||||||
|
const auto &D = Sym.declaration();
|
||||||
|
PrintingPolicy PP = D.getASTContext().getPrintingPolicy();
|
||||||
|
PP.FullyQualifiedName = true;
|
||||||
|
PP.TerseOutput = true;
|
||||||
|
PP.SuppressInitializers = true;
|
||||||
|
llvm::raw_string_ostream SS(S);
|
||||||
|
D.print(SS, PP);
|
||||||
|
}
|
||||||
|
return S;
|
||||||
|
}
|
||||||
|
|
||||||
llvm::StringRef refType(RefType T) {
|
llvm::StringRef refType(RefType T) {
|
||||||
switch (T) {
|
switch (T) {
|
||||||
case RefType::Explicit:
|
case RefType::Explicit:
|
||||||
|
@ -139,6 +156,18 @@ class Reporter {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
std::vector<Ref> Refs;
|
std::vector<Ref> Refs;
|
||||||
|
llvm::DenseMap<const Include *, std::vector<unsigned>> IncludeRefs;
|
||||||
|
|
||||||
|
llvm::StringRef includeType(const Include *I) {
|
||||||
|
auto &List = IncludeRefs[I];
|
||||||
|
if (List.empty())
|
||||||
|
return "unused";
|
||||||
|
if (llvm::any_of(List, [&](unsigned I) {
|
||||||
|
return Targets[Refs[I].TargetIndex].Type == RefType::Explicit;
|
||||||
|
}))
|
||||||
|
return "used";
|
||||||
|
return "semiused";
|
||||||
|
}
|
||||||
|
|
||||||
Target makeTarget(const SymbolReference &SR) {
|
Target makeTarget(const SymbolReference &SR) {
|
||||||
Target T{SR.Target, SR.RT, {}, {}, {}};
|
Target T{SR.Target, SR.RT, {}, {}, {}};
|
||||||
|
@ -194,6 +223,8 @@ public:
|
||||||
|
|
||||||
Refs.push_back({Offset, SR.RT == RefType::Implicit, Targets.size()});
|
Refs.push_back({Offset, SR.RT == RefType::Implicit, Targets.size()});
|
||||||
Targets.push_back(makeTarget(SR));
|
Targets.push_back(makeTarget(SR));
|
||||||
|
for (const auto *I : Targets.back().Includes)
|
||||||
|
IncludeRefs[I].push_back(Targets.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void write() {
|
void write() {
|
||||||
|
@ -202,6 +233,11 @@ public:
|
||||||
OS << "<head>\n";
|
OS << "<head>\n";
|
||||||
OS << "<style>" << CSS << "</style>\n";
|
OS << "<style>" << CSS << "</style>\n";
|
||||||
OS << "<script>" << JS << "</script>\n";
|
OS << "<script>" << JS << "</script>\n";
|
||||||
|
for (auto &Inc : Includes.all()) {
|
||||||
|
OS << "<template id='i" << Inc.Line << "'>";
|
||||||
|
writeInclude(Inc);
|
||||||
|
OS << "</template>\n";
|
||||||
|
}
|
||||||
for (unsigned I = 0; I < Targets.size(); ++I) {
|
for (unsigned I = 0; I < Targets.size(); ++I) {
|
||||||
OS << "<template id='t" << I << "'>";
|
OS << "<template id='t" << I << "'>";
|
||||||
writeTarget(Targets[I]);
|
writeTarget(Targets[I]);
|
||||||
|
@ -260,6 +296,45 @@ private:
|
||||||
OS << ">";
|
OS << ">";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeInclude(const Include &Inc) {
|
||||||
|
OS << "<table class='include'>";
|
||||||
|
if (Inc.Resolved) {
|
||||||
|
OS << "<tr><th>Resolved</td><td>";
|
||||||
|
escapeString(Inc.Resolved->getName());
|
||||||
|
OS << "</td></tr>\n";
|
||||||
|
}
|
||||||
|
// We show one ref for each symbol: first by (RefType != Explicit, Sequence)
|
||||||
|
llvm::DenseMap<Symbol, /*RefIndex*/ unsigned> FirstRef;
|
||||||
|
for (unsigned RefIndex : IncludeRefs[&Inc]) {
|
||||||
|
const Target &T = Targets[Refs[RefIndex].TargetIndex];
|
||||||
|
auto I = FirstRef.try_emplace(T.Sym, RefIndex);
|
||||||
|
if (!I.second && T.Type == RefType::Explicit &&
|
||||||
|
Targets[Refs[I.first->second].TargetIndex].Type != RefType::Explicit)
|
||||||
|
I.first->second = RefIndex;
|
||||||
|
}
|
||||||
|
std::vector<std::pair<Symbol, unsigned>> Sorted = {FirstRef.begin(),
|
||||||
|
FirstRef.end()};
|
||||||
|
llvm::stable_sort(Sorted, llvm::less_second{});
|
||||||
|
for (auto &[S, RefIndex] : Sorted) {
|
||||||
|
auto &T = Targets[Refs[RefIndex].TargetIndex];
|
||||||
|
OS << "<tr class='provides'><th>Provides</td><td>";
|
||||||
|
std::string Details = printDetails(S);
|
||||||
|
if (!Details.empty()) {
|
||||||
|
OS << "<span class='" << refType(T.Type) << "' title='";
|
||||||
|
escapeString(Details);
|
||||||
|
OS << "'>";
|
||||||
|
}
|
||||||
|
escapeString(llvm::to_string(S));
|
||||||
|
if (!Details.empty())
|
||||||
|
OS << "</span>";
|
||||||
|
|
||||||
|
unsigned Line = SM.getLineNumber(MainFile, Refs[RefIndex].Offset);
|
||||||
|
OS << ", <a href='#line" << Line << "'>line " << Line << "</a>";
|
||||||
|
OS << "</td></tr>";
|
||||||
|
}
|
||||||
|
OS << "</table>";
|
||||||
|
}
|
||||||
|
|
||||||
void writeTarget(const Target &T) {
|
void writeTarget(const Target &T) {
|
||||||
OS << "<table class='target " << refType(T.Type) << "'>";
|
OS << "<table class='target " << refType(T.Type) << "'>";
|
||||||
|
|
||||||
|
@ -268,19 +343,10 @@ private:
|
||||||
escapeString(llvm::to_string(T.Sym));
|
escapeString(llvm::to_string(T.Sym));
|
||||||
OS << "</code></td></tr>\n";
|
OS << "</code></td></tr>\n";
|
||||||
|
|
||||||
if (T.Sym.kind() == Symbol::Declaration) {
|
std::string Details = printDetails(T.Sym);
|
||||||
// Print the declaration of the symbol, e.g. to disambiguate overloads.
|
if (!Details.empty()) {
|
||||||
const auto &D = T.Sym.declaration();
|
|
||||||
PrintingPolicy PP = D.getASTContext().getPrintingPolicy();
|
|
||||||
PP.FullyQualifiedName = true;
|
|
||||||
PP.TerseOutput = true;
|
|
||||||
PP.SuppressInitializers = true;
|
|
||||||
std::string S;
|
|
||||||
llvm::raw_string_ostream SS(S);
|
|
||||||
D.print(SS, PP);
|
|
||||||
|
|
||||||
OS << "<tr><td></td><td><code>";
|
OS << "<tr><td></td><td><code>";
|
||||||
escapeString(S);
|
escapeString(Details);
|
||||||
OS << "</code></td></tr>\n";
|
OS << "</code></td></tr>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,10 +391,26 @@ private:
|
||||||
llvm::StringRef Code = SM.getBufferData(MainFile);
|
llvm::StringRef Code = SM.getBufferData(MainFile);
|
||||||
|
|
||||||
OS << "<pre onclick='select(event)' class='code'>";
|
OS << "<pre onclick='select(event)' class='code'>";
|
||||||
OS << "<code class='line' id='line1'>";
|
|
||||||
unsigned LineNum = 1;
|
const Include *Inc = nullptr;
|
||||||
|
unsigned LineNum = 0;
|
||||||
|
// Lines are <code>, include lines have an inner <span>.
|
||||||
|
auto StartLine = [&] {
|
||||||
|
++LineNum;
|
||||||
|
OS << "<code class='line' id='line" << LineNum << "'>";
|
||||||
|
if ((Inc = Includes.atLine(LineNum)))
|
||||||
|
OS << "<span class='inc sel " << includeType(Inc) << "' data-hover='i"
|
||||||
|
<< Inc->Line << "'>";
|
||||||
|
};
|
||||||
|
auto EndLine = [&] {
|
||||||
|
if (Inc)
|
||||||
|
OS << "</span>";
|
||||||
|
OS << "</code>\n";
|
||||||
|
};
|
||||||
|
|
||||||
auto Rest = llvm::makeArrayRef(Refs);
|
auto Rest = llvm::makeArrayRef(Refs);
|
||||||
unsigned End = 0;
|
unsigned End = 0;
|
||||||
|
StartLine();
|
||||||
for (unsigned I = 0; I < Code.size(); ++I) {
|
for (unsigned I = 0; I < Code.size(); ++I) {
|
||||||
// Finish refs early at EOL to avoid dealing with splitting the span.
|
// Finish refs early at EOL to avoid dealing with splitting the span.
|
||||||
if (End && (End == I || Code[I] == '\n')) {
|
if (End && (End == I || Code[I] == '\n')) {
|
||||||
|
@ -364,12 +446,14 @@ private:
|
||||||
End = I + Lexer::MeasureTokenLength(SM.getComposedLoc(MainFile, I), SM,
|
End = I + Lexer::MeasureTokenLength(SM.getComposedLoc(MainFile, I), SM,
|
||||||
Ctx.getLangOpts());
|
Ctx.getLangOpts());
|
||||||
}
|
}
|
||||||
if (Code[I] == '\n')
|
if (Code[I] == '\n') {
|
||||||
OS << "</code>\n<code class='line' id='line" << (++LineNum) << "'>";
|
EndLine();
|
||||||
else
|
StartLine();
|
||||||
|
} else
|
||||||
escapeChar(Code[I]);
|
escapeChar(Code[I]);
|
||||||
}
|
}
|
||||||
OS << "</code></pre>\n";
|
EndLine();
|
||||||
|
OS << "</pre>\n";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -377,6 +377,13 @@ void RecordedPP::RecordedIncludes::add(const Include &I) {
|
||||||
BySpellingIt->second.push_back(Index);
|
BySpellingIt->second.push_back(Index);
|
||||||
if (I.Resolved)
|
if (I.Resolved)
|
||||||
ByFile[I.Resolved].push_back(Index);
|
ByFile[I.Resolved].push_back(Index);
|
||||||
|
ByLine[I.Line] = Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Include *
|
||||||
|
RecordedPP::RecordedIncludes::atLine(unsigned OneBasedIndex) const {
|
||||||
|
auto It = ByLine.find(OneBasedIndex);
|
||||||
|
return (It == ByLine.end()) ? nullptr : &All[It->second];
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm::SmallVector<const Include *>
|
llvm::SmallVector<const Include *>
|
||||||
|
|
Loading…
Reference in New Issue