[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:
Haojian Wu 2023-07-06 10:56:08 +02:00
parent a84e0b4bdc
commit 507d766d76
7 changed files with 118 additions and 27 deletions

View File

@ -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",

View File

@ -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.

View File

@ -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;

View File

@ -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))

View File

@ -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:

View File

@ -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"{{$}}

View File

@ -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;
}