[include-cleaner] Add an IgnoreHeaders flag to the command-line tool.
Reviewed By: kadircet Differential Revision: https://reviews.llvm.org/D153340
This commit is contained in:
parent
a84e0b4bdc
commit
507d766d76
@ -125,18 +125,6 @@ bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm::StringRef getResolvedPath(const include_cleaner::Header &SymProvider) {
|
|
||||||
switch (SymProvider.kind()) {
|
|
||||||
case include_cleaner::Header::Physical:
|
|
||||||
return SymProvider.physical()->tryGetRealPathName();
|
|
||||||
case include_cleaner::Header::Standard:
|
|
||||||
return SymProvider.standard().name().trim("<>\"");
|
|
||||||
case include_cleaner::Header::Verbatim:
|
|
||||||
return SymProvider.verbatim().trim("<>\"");
|
|
||||||
}
|
|
||||||
llvm_unreachable("Unknown header kind");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Diag> generateMissingIncludeDiagnostics(
|
std::vector<Diag> generateMissingIncludeDiagnostics(
|
||||||
ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes,
|
ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes,
|
||||||
llvm::StringRef Code, HeaderFilter IgnoreHeaders) {
|
llvm::StringRef Code, HeaderFilter IgnoreHeaders) {
|
||||||
@ -156,7 +144,7 @@ std::vector<Diag> generateMissingIncludeDiagnostics(
|
|||||||
FileStyle->IncludeStyle);
|
FileStyle->IncludeStyle);
|
||||||
for (const auto &SymbolWithMissingInclude : MissingIncludes) {
|
for (const auto &SymbolWithMissingInclude : MissingIncludes) {
|
||||||
llvm::StringRef ResolvedPath =
|
llvm::StringRef ResolvedPath =
|
||||||
getResolvedPath(SymbolWithMissingInclude.Providers.front());
|
SymbolWithMissingInclude.Providers.front().resolvedPath();
|
||||||
if (isIgnored(ResolvedPath, IgnoreHeaders)) {
|
if (isIgnored(ResolvedPath, IgnoreHeaders)) {
|
||||||
dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by "
|
dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by "
|
||||||
"config",
|
"config",
|
||||||
|
@ -65,10 +65,16 @@ struct AnalysisResults {
|
|||||||
|
|
||||||
/// Determine which headers should be inserted or removed from the main file.
|
/// Determine which headers should be inserted or removed from the main file.
|
||||||
/// This exposes conclusions but not reasons: use lower-level walkUsed for that.
|
/// This exposes conclusions but not reasons: use lower-level walkUsed for that.
|
||||||
AnalysisResults analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
///
|
||||||
llvm::ArrayRef<SymbolReference> MacroRefs,
|
/// The HeaderFilter is a predicate that receives absolute path or spelling
|
||||||
const Includes &I, const PragmaIncludes *PI,
|
/// without quotes/brackets, when a phyiscal file doesn't exist.
|
||||||
const SourceManager &SM, const HeaderSearch &HS);
|
/// No analysis will be performed for headers that satisfy the predicate.
|
||||||
|
AnalysisResults
|
||||||
|
analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
||||||
|
llvm::ArrayRef<SymbolReference> MacroRefs, const Includes &I,
|
||||||
|
const PragmaIncludes *PI, const SourceManager &SM,
|
||||||
|
const HeaderSearch &HS,
|
||||||
|
llvm::function_ref<bool(llvm::StringRef)> HeaderFilter = nullptr);
|
||||||
|
|
||||||
/// Removes unused includes and inserts missing ones in the main file.
|
/// Removes unused includes and inserts missing ones in the main file.
|
||||||
/// Returns the modified main-file code.
|
/// Returns the modified main-file code.
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "llvm/ADT/DenseMapInfoVariant.h"
|
#include "llvm/ADT/DenseMapInfoVariant.h"
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/ADT/StringMap.h"
|
#include "llvm/ADT/StringMap.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
@ -133,6 +134,10 @@ struct Header {
|
|||||||
}
|
}
|
||||||
StringRef verbatim() const { return std::get<Verbatim>(Storage); }
|
StringRef verbatim() const { return std::get<Verbatim>(Storage); }
|
||||||
|
|
||||||
|
/// Absolute path for the header when it's a physical file. Otherwise just
|
||||||
|
/// the spelling without surrounding quotes/brackets.
|
||||||
|
llvm::StringRef resolvedPath() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Order must match Kind enum!
|
// Order must match Kind enum!
|
||||||
std::variant<const FileEntry *, tooling::stdlib::Header, StringRef> Storage;
|
std::variant<const FileEntry *, tooling::stdlib::Header, StringRef> Storage;
|
||||||
|
@ -57,13 +57,17 @@ void walkUsed(llvm::ArrayRef<Decl *> ASTRoots,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalysisResults analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
AnalysisResults
|
||||||
llvm::ArrayRef<SymbolReference> MacroRefs,
|
analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
||||||
const Includes &Inc, const PragmaIncludes *PI,
|
llvm::ArrayRef<SymbolReference> MacroRefs, const Includes &Inc,
|
||||||
const SourceManager &SM, const HeaderSearch &HS) {
|
const PragmaIncludes *PI, const SourceManager &SM,
|
||||||
|
const HeaderSearch &HS,
|
||||||
|
llvm::function_ref<bool(llvm::StringRef)> HeaderFilter) {
|
||||||
const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
|
const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
|
||||||
llvm::DenseSet<const Include *> Used;
|
llvm::DenseSet<const Include *> Used;
|
||||||
llvm::StringSet<> Missing;
|
llvm::StringSet<> Missing;
|
||||||
|
if (!HeaderFilter)
|
||||||
|
HeaderFilter = [](llvm::StringRef) { return false; };
|
||||||
walkUsed(ASTRoots, MacroRefs, PI, SM,
|
walkUsed(ASTRoots, MacroRefs, PI, SM,
|
||||||
[&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
|
[&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
|
||||||
bool Satisfied = false;
|
bool Satisfied = false;
|
||||||
@ -76,13 +80,15 @@ AnalysisResults analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!Satisfied && !Providers.empty() &&
|
if (!Satisfied && !Providers.empty() &&
|
||||||
Ref.RT == RefType::Explicit)
|
Ref.RT == RefType::Explicit &&
|
||||||
|
!HeaderFilter(Providers.front().resolvedPath()))
|
||||||
Missing.insert(spellHeader({Providers.front(), HS, MainFile}));
|
Missing.insert(spellHeader({Providers.front(), HS, MainFile}));
|
||||||
});
|
});
|
||||||
|
|
||||||
AnalysisResults Results;
|
AnalysisResults Results;
|
||||||
for (const Include &I : Inc.all()) {
|
for (const Include &I : Inc.all()) {
|
||||||
if (Used.contains(&I) || !I.Resolved)
|
if (Used.contains(&I) || !I.Resolved ||
|
||||||
|
HeaderFilter(I.Resolved->tryGetRealPathName()))
|
||||||
continue;
|
continue;
|
||||||
if (PI) {
|
if (PI) {
|
||||||
if (PI->shouldKeep(I.Line))
|
if (PI->shouldKeep(I.Line))
|
||||||
|
@ -38,6 +38,18 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Symbol &S) {
|
|||||||
llvm_unreachable("Unhandled Symbol kind");
|
llvm_unreachable("Unhandled Symbol kind");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
llvm::StringRef Header::resolvedPath() const {
|
||||||
|
switch (kind()) {
|
||||||
|
case include_cleaner::Header::Physical:
|
||||||
|
return physical()->tryGetRealPathName();
|
||||||
|
case include_cleaner::Header::Standard:
|
||||||
|
return standard().name().trim("<>\"");
|
||||||
|
case include_cleaner::Header::Verbatim:
|
||||||
|
return verbatim().trim("<>\"");
|
||||||
|
}
|
||||||
|
llvm_unreachable("Unknown header kind");
|
||||||
|
}
|
||||||
|
|
||||||
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Header &H) {
|
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Header &H) {
|
||||||
switch (H.kind()) {
|
switch (H.kind()) {
|
||||||
case Header::Physical:
|
case Header::Physical:
|
||||||
|
@ -14,6 +14,14 @@ int x = foo();
|
|||||||
// REMOVE: - "foobar.h"
|
// REMOVE: - "foobar.h"
|
||||||
// REMOVE-NOT: + "foo.h"
|
// REMOVE-NOT: + "foo.h"
|
||||||
|
|
||||||
|
// RUN: clang-include-cleaner -print=changes %s --ignore-headers="foobar\.h,foo\.h" -- -I%S/Inputs/ | FileCheck --match-full-lines --allow-empty --check-prefix=IGNORE %s
|
||||||
|
// IGNORE-NOT: - "foobar.h"
|
||||||
|
// IGNORE-NOT: + "foo.h"
|
||||||
|
|
||||||
|
// RUN: clang-include-cleaner -print=changes %s --ignore-headers="foobar.*\.h" -- -I%S/Inputs/ | FileCheck --match-full-lines --allow-empty --check-prefix=IGNORE2 %s
|
||||||
|
// IGNORE2-NOT: - "foobar.h"
|
||||||
|
// IGNORE2: + "foo.h"
|
||||||
|
|
||||||
// RUN: clang-include-cleaner -print %s -- -I%S/Inputs/ | FileCheck --match-full-lines --check-prefix=PRINT %s
|
// RUN: clang-include-cleaner -print %s -- -I%S/Inputs/ | FileCheck --match-full-lines --check-prefix=PRINT %s
|
||||||
// PRINT: #include "foo.h"
|
// PRINT: #include "foo.h"
|
||||||
// PRINT-NOT: {{^}}#include "foobar.h"{{$}}
|
// PRINT-NOT: {{^}}#include "foobar.h"{{$}}
|
||||||
@ -23,3 +31,8 @@ int x = foo();
|
|||||||
// RUN: FileCheck --match-full-lines --check-prefix=EDIT %s < %t.cpp
|
// RUN: FileCheck --match-full-lines --check-prefix=EDIT %s < %t.cpp
|
||||||
// EDIT: #include "foo.h"
|
// EDIT: #include "foo.h"
|
||||||
// EDIT-NOT: {{^}}#include "foobar.h"{{$}}
|
// EDIT-NOT: {{^}}#include "foobar.h"{{$}}
|
||||||
|
|
||||||
|
// RUN: cp %s %t.cpp
|
||||||
|
// RUN: clang-include-cleaner -edit --ignore-headers="foobar\.h,foo\.h" %t.cpp -- -I%S/Inputs/
|
||||||
|
// RUN: FileCheck --match-full-lines --check-prefix=EDIT2 %s < %t.cpp
|
||||||
|
// EDIT2-NOT: {{^}}#include "foo.h"{{$}}
|
||||||
|
@ -14,10 +14,19 @@
|
|||||||
#include "clang/Lex/Preprocessor.h"
|
#include "clang/Lex/Preprocessor.h"
|
||||||
#include "clang/Tooling/CommonOptionsParser.h"
|
#include "clang/Tooling/CommonOptionsParser.h"
|
||||||
#include "clang/Tooling/Tooling.h"
|
#include "clang/Tooling/Tooling.h"
|
||||||
|
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
#include "llvm/ADT/StringRef.h"
|
||||||
#include "llvm/Support/CommandLine.h"
|
#include "llvm/Support/CommandLine.h"
|
||||||
|
#include "llvm/Support/FormatVariadic.h"
|
||||||
|
#include "llvm/Support/Regex.h"
|
||||||
#include "llvm/Support/Signals.h"
|
#include "llvm/Support/Signals.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
namespace include_cleaner {
|
namespace include_cleaner {
|
||||||
@ -47,6 +56,14 @@ cl::opt<std::string> HTMLReportPath{
|
|||||||
cl::cat(IncludeCleaner),
|
cl::cat(IncludeCleaner),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cl::opt<std::string> IgnoreHeaders{
|
||||||
|
"ignore-headers",
|
||||||
|
cl::desc("A comma-separated list of regexes to match against suffix of a "
|
||||||
|
"header, and disable analysis if matched."),
|
||||||
|
cl::init(""),
|
||||||
|
cl::cat(IncludeCleaner),
|
||||||
|
};
|
||||||
|
|
||||||
enum class PrintStyle { Changes, Final };
|
enum class PrintStyle { Changes, Final };
|
||||||
cl::opt<PrintStyle> Print{
|
cl::opt<PrintStyle> Print{
|
||||||
"print",
|
"print",
|
||||||
@ -91,9 +108,15 @@ format::FormatStyle getStyle(llvm::StringRef Filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Action : public clang::ASTFrontendAction {
|
class Action : public clang::ASTFrontendAction {
|
||||||
|
public:
|
||||||
|
Action(llvm::function_ref<bool(llvm::StringRef)> HeaderFilter)
|
||||||
|
: HeaderFilter(HeaderFilter){};
|
||||||
|
|
||||||
|
private:
|
||||||
RecordedAST AST;
|
RecordedAST AST;
|
||||||
RecordedPP PP;
|
RecordedPP PP;
|
||||||
PragmaIncludes PI;
|
PragmaIncludes PI;
|
||||||
|
llvm::function_ref<bool(llvm::StringRef)> HeaderFilter;
|
||||||
|
|
||||||
bool BeginInvocation(CompilerInstance &CI) override {
|
bool BeginInvocation(CompilerInstance &CI) override {
|
||||||
// We only perform include-cleaner analysis. So we disable diagnostics that
|
// We only perform include-cleaner analysis. So we disable diagnostics that
|
||||||
@ -135,8 +158,8 @@ class Action : public clang::ASTFrontendAction {
|
|||||||
assert(!Path.empty() && "Main file path not known?");
|
assert(!Path.empty() && "Main file path not known?");
|
||||||
llvm::StringRef Code = SM.getBufferData(SM.getMainFileID());
|
llvm::StringRef Code = SM.getBufferData(SM.getMainFileID());
|
||||||
|
|
||||||
auto Results =
|
auto Results = analyze(AST.Roots, PP.MacroReferences, PP.Includes, &PI, SM,
|
||||||
analyze(AST.Roots, PP.MacroReferences, PP.Includes, &PI, SM, HS);
|
HS, HeaderFilter);
|
||||||
if (!Insert)
|
if (!Insert)
|
||||||
Results.Missing.clear();
|
Results.Missing.clear();
|
||||||
if (!Remove)
|
if (!Remove)
|
||||||
@ -185,6 +208,43 @@ class Action : public clang::ASTFrontendAction {
|
|||||||
getCompilerInstance().getPreprocessor().getHeaderSearchInfo(), &PI, OS);
|
getCompilerInstance().getPreprocessor().getHeaderSearchInfo(), &PI, OS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
class ActionFactory : public tooling::FrontendActionFactory {
|
||||||
|
public:
|
||||||
|
ActionFactory(llvm::function_ref<bool(llvm::StringRef)> HeaderFilter)
|
||||||
|
: HeaderFilter(HeaderFilter) {}
|
||||||
|
|
||||||
|
std::unique_ptr<clang::FrontendAction> create() override {
|
||||||
|
return std::make_unique<Action>(HeaderFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
llvm::function_ref<bool(llvm::StringRef)> HeaderFilter;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::function<bool(llvm::StringRef)> headerFilter() {
|
||||||
|
auto FilterRegs = std::make_shared<std::vector<llvm::Regex>>();
|
||||||
|
|
||||||
|
llvm::SmallVector<llvm::StringRef> Headers;
|
||||||
|
llvm::StringRef(IgnoreHeaders).split(Headers, ',', -1, /*KeepEmpty=*/false);
|
||||||
|
for (auto HeaderPattern : Headers) {
|
||||||
|
std::string AnchoredPattern = "(" + HeaderPattern.str() + ")$";
|
||||||
|
llvm::Regex CompiledRegex(AnchoredPattern);
|
||||||
|
std::string RegexError;
|
||||||
|
if (!CompiledRegex.isValid(RegexError)) {
|
||||||
|
llvm::errs() << llvm::formatv("Invalid regular expression '{0}': {1}\n",
|
||||||
|
HeaderPattern, RegexError);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
FilterRegs->push_back(std::move(CompiledRegex));
|
||||||
|
}
|
||||||
|
return [FilterRegs](llvm::StringRef Path) {
|
||||||
|
for (const auto &F : *FilterRegs) {
|
||||||
|
if (F.match(Path))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace include_cleaner
|
} // namespace include_cleaner
|
||||||
@ -210,9 +270,10 @@ int main(int argc, const char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto Factory = clang::tooling::newFrontendActionFactory<Action>();
|
auto HeaderFilter = headerFilter();
|
||||||
|
ActionFactory Factory(HeaderFilter);
|
||||||
return clang::tooling::ClangTool(OptionsParser->getCompilations(),
|
return clang::tooling::ClangTool(OptionsParser->getCompilations(),
|
||||||
OptionsParser->getSourcePathList())
|
OptionsParser->getSourcePathList())
|
||||||
.run(Factory.get()) ||
|
.run(&Factory) ||
|
||||||
Errors != 0;
|
Errors != 0;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user