[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;
|
||||
}
|
||||
|
||||
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(
|
||||
ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes,
|
||||
llvm::StringRef Code, HeaderFilter IgnoreHeaders) {
|
||||
@ -156,7 +144,7 @@ std::vector<Diag> generateMissingIncludeDiagnostics(
|
||||
FileStyle->IncludeStyle);
|
||||
for (const auto &SymbolWithMissingInclude : MissingIncludes) {
|
||||
llvm::StringRef ResolvedPath =
|
||||
getResolvedPath(SymbolWithMissingInclude.Providers.front());
|
||||
SymbolWithMissingInclude.Providers.front().resolvedPath();
|
||||
if (isIgnored(ResolvedPath, IgnoreHeaders)) {
|
||||
dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by "
|
||||
"config",
|
||||
|
@ -65,10 +65,16 @@ struct AnalysisResults {
|
||||
|
||||
/// Determine which headers should be inserted or removed from the main file.
|
||||
/// This exposes conclusions but not reasons: use lower-level walkUsed for that.
|
||||
AnalysisResults analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
||||
llvm::ArrayRef<SymbolReference> MacroRefs,
|
||||
const Includes &I, const PragmaIncludes *PI,
|
||||
const SourceManager &SM, const HeaderSearch &HS);
|
||||
///
|
||||
/// The HeaderFilter is a predicate that receives absolute path or spelling
|
||||
/// without quotes/brackets, when a phyiscal file doesn't exist.
|
||||
/// 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.
|
||||
/// Returns the modified main-file code.
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "llvm/ADT/DenseMapInfoVariant.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
@ -133,6 +134,10 @@ struct Header {
|
||||
}
|
||||
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:
|
||||
// Order must match Kind enum!
|
||||
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,
|
||||
llvm::ArrayRef<SymbolReference> MacroRefs,
|
||||
const Includes &Inc, const PragmaIncludes *PI,
|
||||
const SourceManager &SM, const HeaderSearch &HS) {
|
||||
AnalysisResults
|
||||
analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
||||
llvm::ArrayRef<SymbolReference> MacroRefs, const Includes &Inc,
|
||||
const PragmaIncludes *PI, const SourceManager &SM,
|
||||
const HeaderSearch &HS,
|
||||
llvm::function_ref<bool(llvm::StringRef)> HeaderFilter) {
|
||||
const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
|
||||
llvm::DenseSet<const Include *> Used;
|
||||
llvm::StringSet<> Missing;
|
||||
if (!HeaderFilter)
|
||||
HeaderFilter = [](llvm::StringRef) { return false; };
|
||||
walkUsed(ASTRoots, MacroRefs, PI, SM,
|
||||
[&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
|
||||
bool Satisfied = false;
|
||||
@ -76,13 +80,15 @@ AnalysisResults analyze(llvm::ArrayRef<Decl *> ASTRoots,
|
||||
}
|
||||
}
|
||||
if (!Satisfied && !Providers.empty() &&
|
||||
Ref.RT == RefType::Explicit)
|
||||
Ref.RT == RefType::Explicit &&
|
||||
!HeaderFilter(Providers.front().resolvedPath()))
|
||||
Missing.insert(spellHeader({Providers.front(), HS, MainFile}));
|
||||
});
|
||||
|
||||
AnalysisResults Results;
|
||||
for (const Include &I : Inc.all()) {
|
||||
if (Used.contains(&I) || !I.Resolved)
|
||||
if (Used.contains(&I) || !I.Resolved ||
|
||||
HeaderFilter(I.Resolved->tryGetRealPathName()))
|
||||
continue;
|
||||
if (PI) {
|
||||
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::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) {
|
||||
switch (H.kind()) {
|
||||
case Header::Physical:
|
||||
|
@ -14,6 +14,14 @@ int x = foo();
|
||||
// REMOVE: - "foobar.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
|
||||
// PRINT: #include "foo.h"
|
||||
// PRINT-NOT: {{^}}#include "foobar.h"{{$}}
|
||||
@ -23,3 +31,8 @@ int x = foo();
|
||||
// RUN: FileCheck --match-full-lines --check-prefix=EDIT %s < %t.cpp
|
||||
// EDIT: #include "foo.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/Tooling/CommonOptionsParser.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "llvm/Support/Regex.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace clang {
|
||||
namespace include_cleaner {
|
||||
@ -47,6 +56,14 @@ cl::opt<std::string> HTMLReportPath{
|
||||
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 };
|
||||
cl::opt<PrintStyle> Print{
|
||||
"print",
|
||||
@ -91,9 +108,15 @@ format::FormatStyle getStyle(llvm::StringRef Filename) {
|
||||
}
|
||||
|
||||
class Action : public clang::ASTFrontendAction {
|
||||
public:
|
||||
Action(llvm::function_ref<bool(llvm::StringRef)> HeaderFilter)
|
||||
: HeaderFilter(HeaderFilter){};
|
||||
|
||||
private:
|
||||
RecordedAST AST;
|
||||
RecordedPP PP;
|
||||
PragmaIncludes PI;
|
||||
llvm::function_ref<bool(llvm::StringRef)> HeaderFilter;
|
||||
|
||||
bool BeginInvocation(CompilerInstance &CI) override {
|
||||
// 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?");
|
||||
llvm::StringRef Code = SM.getBufferData(SM.getMainFileID());
|
||||
|
||||
auto Results =
|
||||
analyze(AST.Roots, PP.MacroReferences, PP.Includes, &PI, SM, HS);
|
||||
auto Results = analyze(AST.Roots, PP.MacroReferences, PP.Includes, &PI, SM,
|
||||
HS, HeaderFilter);
|
||||
if (!Insert)
|
||||
Results.Missing.clear();
|
||||
if (!Remove)
|
||||
@ -185,6 +208,43 @@ class Action : public clang::ASTFrontendAction {
|
||||
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 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(),
|
||||
OptionsParser->getSourcePathList())
|
||||
.run(Factory.get()) ||
|
||||
.run(&Factory) ||
|
||||
Errors != 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user