add_subdirectory(ConfusableTable)
+include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../../include-cleaner/include")
add_custom_command(
OUTPUT Confusables.inc
ConstCorrectnessCheck.cpp
DefinitionsInHeadersCheck.cpp
ConfusableIdentifierCheck.cpp
+ IncludeCleanerCheck.cpp
MiscTidyModule.cpp
MisleadingBidirectional.cpp
MisleadingIdentifier.cpp
clangAST
clangASTMatchers
clangBasic
+ clangIncludeCleaner
clangLex
clangSerialization
clangTooling
--- /dev/null
+//===--- IncludeCleanerCheck.cpp - clang-tidy -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "IncludeCleanerCheck.h"
+#include "../ClangTidyCheck.h"
+#include "../ClangTidyDiagnosticConsumer.h"
+#include "../ClangTidyOptions.h"
+#include "../utils/OptionsUtils.h"
+#include "clang-include-cleaner/Analysis.h"
+#include "clang-include-cleaner/Record.h"
+#include "clang-include-cleaner/Types.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/FileEntry.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Format/Format.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Inclusions/HeaderIncludes.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/Regex.h"
+#include <optional>
+#include <string>
+#include <vector>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+namespace {
+struct MissingIncludeInfo {
+ SourceLocation SymRefLocation;
+ include_cleaner::Header Missing;
+};
+} // namespace
+
+IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ IgnoreHeaders(utils::options::parseStringList(
+ Options.getLocalOrGlobal("IgnoreHeaders", ""))) {
+ for (const auto &Header : IgnoreHeaders) {
+ if (!llvm::Regex{Header}.isValid())
+ configurationDiag("Invalid ignore headers regex '%0'") << Header;
+ std::string HeaderSuffix{Header.str()};
+ if (!Header.ends_with("$"))
+ HeaderSuffix += "$";
+ IgnoreHeadersRegex.emplace_back(HeaderSuffix);
+ }
+}
+
+void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IgnoreHeaders",
+ utils::options::serializeStringList(IgnoreHeaders));
+}
+
+bool IncludeCleanerCheck::isLanguageVersionSupported(
+ const LangOptions &LangOpts) const {
+ return !LangOpts.ObjC;
+}
+
+void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(translationUnitDecl().bind("top"), this);
+}
+
+void IncludeCleanerCheck::registerPPCallbacks(const SourceManager &SM,
+ Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) {
+ PP->addPPCallbacks(RecordedPreprocessor.record(*PP));
+ HS = &PP->getHeaderSearchInfo();
+ RecordedPI.record(*PP);
+}
+
+bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header &H) {
+ return llvm::any_of(IgnoreHeadersRegex, [&H](const llvm::Regex &R) {
+ switch (H.kind()) {
+ case include_cleaner::Header::Standard:
+ return R.match(H.standard().name());
+ case include_cleaner::Header::Verbatim:
+ return R.match(H.verbatim());
+ case include_cleaner::Header::Physical:
+ return R.match(H.physical()->tryGetRealPathName());
+ }
+ llvm_unreachable("Unknown Header kind.");
+ });
+}
+
+void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) {
+ const SourceManager *SM = Result.SourceManager;
+ const FileEntry *MainFile = SM->getFileEntryForID(SM->getMainFileID());
+ llvm::DenseSet<const include_cleaner::Include *> Used;
+ std::vector<MissingIncludeInfo> Missing;
+ llvm::SmallVector<Decl *> MainFileDecls;
+ for (Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>("top")->decls()) {
+ if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation())))
+ continue;
+ // FIXME: Filter out implicit template specializations.
+ MainFileDecls.push_back(D);
+ }
+ // FIXME: Find a way to have less code duplication between include-cleaner
+ // analysis implementation and the below code.
+ walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI,
+ *SM,
+ [&](const include_cleaner::SymbolReference &Ref,
+ llvm::ArrayRef<include_cleaner::Header> Providers) {
+ bool Satisfied = false;
+ for (const include_cleaner::Header &H : Providers) {
+ if (H.kind() == include_cleaner::Header::Physical &&
+ H.physical() == MainFile)
+ Satisfied = true;
+
+ for (const include_cleaner::Include *I :
+ RecordedPreprocessor.Includes.match(H)) {
+ Used.insert(I);
+ Satisfied = true;
+ }
+ }
+ if (!Satisfied && !Providers.empty() &&
+ Ref.RT == include_cleaner::RefType::Explicit &&
+ !shouldIgnore(Providers.front()))
+ Missing.push_back({Ref.RefLocation, Providers.front()});
+ });
+
+ std::vector<const include_cleaner::Include *> Unused;
+ for (const include_cleaner::Include &I :
+ RecordedPreprocessor.Includes.all()) {
+ if (Used.contains(&I) || !I.Resolved)
+ continue;
+ if (RecordedPI.shouldKeep(I.Line))
+ continue;
+ // Check if main file is the public interface for a private header. If so
+ // we shouldn't diagnose it as unused.
+ if (auto PHeader = RecordedPI.getPublic(I.Resolved); !PHeader.empty()) {
+ PHeader = PHeader.trim("<>\"");
+ // Since most private -> public mappings happen in a verbatim way, we
+ // check textually here. This might go wrong in presence of symlinks or
+ // header mappings. But that's not different than rest of the places.
+ if (getCurrentMainFile().endswith(PHeader))
+ continue;
+ }
+
+ if (llvm::none_of(IgnoreHeadersRegex,
+ [Resolved = I.Resolved->tryGetRealPathName()](
+ const llvm::Regex &R) { return R.match(Resolved); }))
+ Unused.push_back(&I);
+ }
+
+ llvm::StringRef Code = SM->getBufferData(SM->getMainFileID());
+ auto FileStyle =
+ format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(),
+ format::DefaultFallbackStyle, Code,
+ &SM->getFileManager().getVirtualFileSystem());
+ if (!FileStyle)
+ FileStyle = format::getLLVMStyle();
+
+ for (const auto *Inc : Unused) {
+ diag(Inc->HashLocation, "included header %0 is not used directly")
+ << Inc->quote()
+ << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
+ SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
+ SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
+ }
+
+ tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,
+ FileStyle->IncludeStyle);
+ for (const auto &Inc : Missing) {
+ std::string Spelling =
+ include_cleaner::spellHeader(Inc.Missing, *HS, MainFile);
+ bool Angled = llvm::StringRef{Spelling}.starts_with("<");
+ // We might suggest insertion of an existing include in edge cases, e.g.,
+ // include is present in a PP-disabled region, or spelling of the header
+ // turns out to be the same as one of the unresolved includes in the
+ // main file.
+ if (auto Replacement =
+ HeaderIncludes.insert(llvm::StringRef{Spelling}.trim("\"<>"),
+ Angled, tooling::IncludeDirective::Include))
+ diag(SM->getSpellingLoc(Inc.SymRefLocation),
+ "no header providing %0 is directly included")
+ << Spelling
+ << FixItHint::CreateInsertion(
+ SM->getComposedLoc(SM->getMainFileID(),
+ Replacement->getOffset()),
+ Replacement->getReplacementText());
+ }
+}
+
+} // namespace clang::tidy::misc
--- /dev/null
+//===--- IncludeCleanerCheck.h - clang-tidy ---------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCLUDECLEANER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCLUDECLEANER_H
+
+#include "../ClangTidyCheck.h"
+#include "../ClangTidyDiagnosticConsumer.h"
+#include "../ClangTidyOptions.h"
+#include "clang-include-cleaner/Record.h"
+#include "clang-include-cleaner/Types.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/HeaderSearch.h"
+#include "llvm/Support/Regex.h"
+#include <vector>
+
+namespace clang::tidy::misc {
+
+/// Checks for unused and missing includes. Generates findings only for
+/// the main file of a translation unit.
+/// Findings correspond to https://clangd.llvm.org/design/include-cleaner.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/misc/include-cleaner.html
+class IncludeCleanerCheck : public ClangTidyCheck {
+public:
+ IncludeCleanerCheck(StringRef Name, ClangTidyContext *Context);
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
+
+private:
+ include_cleaner::RecordedPP RecordedPreprocessor;
+ include_cleaner::PragmaIncludes RecordedPI;
+ HeaderSearch *HS;
+ std::vector<StringRef> IgnoreHeaders;
+ llvm::SmallVector<llvm::Regex> IgnoreHeadersRegex;
+ bool shouldIgnore(const include_cleaner::Header &H);
+};
+
+} // namespace clang::tidy::misc
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCLUDECLEANER_H
#include "ConfusableIdentifierCheck.h"
#include "ConstCorrectnessCheck.h"
#include "DefinitionsInHeadersCheck.h"
+#include "IncludeCleanerCheck.h"
#include "MisleadingBidirectional.h"
#include "MisleadingIdentifier.h"
#include "MisplacedConstCheck.h"
"misc-const-correctness");
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
"misc-definitions-in-headers");
+ CheckFactories.registerCheck<IncludeCleanerCheck>("misc-include-cleaner");
CheckFactories.registerCheck<MisleadingBidirectionalCheck>(
"misc-misleading-bidirectional");
CheckFactories.registerCheck<MisleadingIdentifierCheck>(
TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {
constexpr llvm::StringLiteral Seperator(",");
- static const std::string BadChecks =
- llvm::join_items(Seperator,
- // We want this list to start with a seperator to
- // simplify appending in the lambda. So including an
- // empty string here will force that.
- "",
- // ----- False Positives -----
-
- // Check relies on seeing ifndef/define/endif directives,
- // clangd doesn't replay those when using a preamble.
- "-llvm-header-guard", "-modernize-macro-to-enum",
-
- // ----- Crashing Checks -----
-
- // Check can choke on invalid (intermediate) c++
- // code, which is often the case when clangd
- // tries to build an AST.
- "-bugprone-use-after-move",
- // Alias for bugprone-use-after-move.
- "-hicpp-invalid-access-moved",
-
- // ----- Performance problems -----
-
- // This check runs expensive analysis for each variable.
- // It has been observed to increase reparse time by 10x.
- "-misc-const-correctness");
+ static const std::string BadChecks = llvm::join_items(
+ Seperator,
+ // We want this list to start with a seperator to
+ // simplify appending in the lambda. So including an
+ // empty string here will force that.
+ "",
+ // include-cleaner is directly integrated in IncludeCleaner.cpp
+ "-misc-include-cleaner",
+
+ // ----- False Positives -----
+
+ // Check relies on seeing ifndef/define/endif directives,
+ // clangd doesn't replay those when using a preamble.
+ "-llvm-header-guard", "-modernize-macro-to-enum",
+
+ // ----- Crashing Checks -----
+
+ // Check can choke on invalid (intermediate) c++
+ // code, which is often the case when clangd
+ // tries to build an AST.
+ "-bugprone-use-after-move",
+ // Alias for bugprone-use-after-move.
+ "-hicpp-invalid-access-moved",
+
+ // ----- Performance problems -----
+
+ // This check runs expensive analysis for each variable.
+ // It has been observed to increase reparse time by 10x.
+ "-misc-const-correctness");
size_t Size = BadChecks.size();
for (const std::string &Str : ExtraBadChecks) {
Checks that all implicit and explicit inline functions in header files are
tagged with the ``LIBC_INLINE`` macro.
+- New :doc:`misc-include-cleaner
+ <clang-tidy/checks/misc/include-cleaner>` check.
+
+ Checks for unused and missing includes.
+
- New :doc:`modernize-type-traits
<clang-tidy/checks/modernize/type-traits>` check.
`misc-confusable-identifiers <misc/confusable-identifiers.html>`_,
`misc-const-correctness <misc/const-correctness.html>`_, "Yes"
`misc-definitions-in-headers <misc/definitions-in-headers.html>`_, "Yes"
+ `misc-include-cleaner <misc/include-cleaner.html>`_, "Yes"
`misc-misleading-bidirectional <misc/misleading-bidirectional.html>`_,
`misc-misleading-identifier <misc/misleading-identifier.html>`_,
`misc-misplaced-const <misc/misplaced-const.html>`_,
--- /dev/null
+.. title:: clang-tidy - misc-include-cleaner
+
+misc-include-cleaner
+====================
+
+Checks for unused and missing includes. Generates findings only for
+the main file of a translation unit.
+Findings correspond to https://clangd.llvm.org/design/include-cleaner.
+
+Example:
+
+.. code-block:: c++
+ // foo.h
+ class Foo{};
+ // bar.h
+ #include "baz.h"
+ class Bar{};
+ // baz.h
+ class Baz{};
+ // main.cc
+ #include "bar.h" // OK: uses class Bar from bar.h
+ #include "foo.h" // warning: unused include "foo.h"
+ Bar bar;
+ Baz baz; // warning: missing include "baz.h"
+
+Options
+-------
+
+.. option:: IgnoreHeaders
+
+ A semicolon-separated list of regexes to disable insertion/removal of header
+ files that match this regex as a suffix. E.g., `foo/.*` disables
+ insertion/removal for all headers under the directory `foo`. By default, no
+ headers will be ignored.
#define CLANG_INCLUDE_CLEANER_RECORD_H
#include "clang-include-cleaner/Types.h"
+#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
/// to the structure.
void record(const CompilerInstance &CI);
+ /// Installs an analysing PPCallback and CommentHandler and populates results
+ /// to the structure.
+ void record(Preprocessor &P);
+
/// Returns true if the given #include of the main-file should never be
/// removed.
bool shouldKeep(unsigned HashLineNumber) const {
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/Inclusions/HeaderAnalysis.h"
#include "clang/Tooling/Inclusions/StandardLibrary.h"
+#include <memory>
+#include <utility>
namespace clang::include_cleaner {
namespace {
class PragmaIncludes::RecordPragma : public PPCallbacks, public CommentHandler {
public:
RecordPragma(const CompilerInstance &CI, PragmaIncludes *Out)
- : SM(CI.getSourceManager()),
- HeaderInfo(CI.getPreprocessor().getHeaderSearchInfo()), Out(Out),
+ : RecordPragma(CI.getPreprocessor(), Out) {}
+ RecordPragma(const Preprocessor &P, PragmaIncludes *Out)
+ : SM(P.getSourceManager()), HeaderInfo(P.getHeaderSearchInfo()), Out(Out),
UniqueStrings(Arena) {}
void FileChanged(SourceLocation Loc, FileChangeReason Reason,
CI.getPreprocessor().addPPCallbacks(std::move(Record));
}
+void PragmaIncludes::record(Preprocessor &P) {
+ auto Record = std::make_unique<RecordPragma>(P, this);
+ P.addCommentHandler(Record.get());
+ P.addPPCallbacks(std::move(Record));
+}
+
llvm::StringRef PragmaIncludes::getPublic(const FileEntry *F) const {
auto It = IWYUPublic.find(F->getUniqueID());
if (It == IWYUPublic.end())
}
static llvm::SmallVector<const FileEntry *>
-toFileEntries(llvm::ArrayRef<StringRef> FileNames, FileManager& FM) {
- llvm::SmallVector<const FileEntry *> Results;
+toFileEntries(llvm::ArrayRef<StringRef> FileNames, FileManager &FM) {
+ llvm::SmallVector<const FileEntry *> Results;
for (auto FName : FileNames) {
// FIMXE: log the failing cases?
--- /dev/null
+#pragma once
+#include "baz.h"
+#include "private.h"
+int bar();
--- /dev/null
+#pragma once
+int baz();
--- /dev/null
+#pragma once
+void foo();
--- /dev/null
+// IWYU pragma: private, include "public.h"
+int foobar();
--- /dev/null
+// RUN: %check_clang_tidy %s misc-include-cleaner %t -- -- -I%S/Inputs -isystem%S/system
+#include "bar.h"
+// CHECK-FIXES: {{^}}#include "baz.h"{{$}}
+#include "foo.h"
+// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: included header "foo.h" is not used directly [misc-include-cleaner]
+// CHECK-FIXES: {{^}}
+// CHECK-FIXES: {{^}}#include <string>{{$}}
+#include <vector.h>
+// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: included header <vector.h> is not used directly [misc-include-cleaner]
+// CHECK-FIXES: {{^}}
+int BarResult = bar();
+int BazResult = baz();
+// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: no header providing "baz.h" is directly included [misc-include-cleaner]
+std::string HelloString;
+// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: no header providing <string> is directly included [misc-include-cleaner]
+int FooBarResult = foobar();
+// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: no header providing "public.h" is directly included [misc-include-cleaner]
--- /dev/null
+#pragma once
+namespace std { class string {}; }
--- /dev/null
+#pragma once
+#include <string.h>
+
+namespace std { class vector {}; }
get_filename_component(CLANG_LINT_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/../../clang-tidy REALPATH)
include_directories(${CLANG_LINT_SOURCE_DIR})
+include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../../include-cleaner/include")
add_extra_unittest(ClangTidyTests
AddConstTest.cpp
ClangTidyDiagnosticConsumerTest.cpp
ClangTidyOptionsTest.cpp
DeclRefExprUtilsTest.cpp
+ IncludeCleanerTest.cpp
IncludeInserterTest.cpp
GlobListTest.cpp
GoogleModuleTest.cpp
clangTooling
clangToolingCore
clangTransformer
+ clangIncludeCleaner
)
target_link_libraries(ClangTidyTests
PRIVATE
clangTidy
clangTidyAndroidModule
clangTidyGoogleModule
+ clangTidyMiscModule
clangTidyLLVMModule
clangTidyModernizeModule
clangTidyObjCModule
--- /dev/null
+//===--- IncludeCleanerTest.cpp - clang-tidy -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangTidyDiagnosticConsumer.h"
+#include "ClangTidyOptions.h"
+#include "ClangTidyTest.h"
+#include "misc/IncludeCleanerCheck.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Regex.h"
+#include "gtest/gtest.h"
+#include <initializer_list>
+
+#include <optional>
+#include <vector>
+
+using namespace clang::tidy::misc;
+
+namespace clang {
+namespace tidy {
+namespace test {
+namespace {
+
+std::string
+appendPathFileSystemIndependent(std::initializer_list<std::string> Segments) {
+ llvm::SmallString<32> Result;
+ for (const auto &Segment : Segments)
+ llvm::sys::path::append(Result, llvm::sys::path::Style::native, Segment);
+ return std::string(Result.str());
+}
+
+TEST(IncludeCleanerCheckTest, BasicUnusedIncludes) {
+ const char *PreCode = R"(
+#include "bar.h"
+#include <vector>
+#include "bar.h"
+)";
+ const char *PostCode = "\n";
+
+ std::vector<ClangTidyError> Errors;
+ EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt,
+ ClangTidyOptions(), {{"bar.h", ""}, {"vector", ""}}));
+}
+
+TEST(IncludeCleanerCheckTest, SuppressUnusedIncludes) {
+ const char *PreCode = R"(
+#include "bar.h"
+#include "foo/qux.h"
+#include "baz/qux/qux.h"
+#include <vector>
+)";
+
+ const char *PostCode = R"(
+#include "bar.h"
+#include "foo/qux.h"
+#include <vector>
+)";
+
+ std::vector<ClangTidyError> Errors;
+ ClangTidyOptions Opts;
+ Opts.CheckOptions["IgnoreHeaders"] = llvm::StringRef{llvm::formatv(
+ "bar.h;{0};{1};vector",
+ llvm::Regex::escape(appendPathFileSystemIndependent({"foo", "qux.h"})),
+ llvm::Regex::escape(appendPathFileSystemIndependent({"baz", "qux"})))};
+ EXPECT_EQ(
+ PostCode,
+ runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt, Opts,
+ {{"bar.h", ""},
+ {"vector", ""},
+ {appendPathFileSystemIndependent({"foo", "qux.h"}), ""},
+ {appendPathFileSystemIndependent({"baz", "qux", "qux.h"}), ""}}));
+}
+
+TEST(IncludeCleanerCheckTest, BasicMissingIncludes) {
+ const char *PreCode = R"(
+#include "bar.h"
+
+int BarResult = bar();
+int BazResult = baz();
+)";
+ const char *PostCode = R"(
+#include "bar.h"
+#include "baz.h"
+
+int BarResult = bar();
+int BazResult = baz();
+)";
+
+ std::vector<ClangTidyError> Errors;
+ EXPECT_EQ(PostCode,
+ runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
+ {{"bar.h", R"(#pragma once
+ #include "baz.h"
+ int bar();
+ )"},
+ {"baz.h", R"(#pragma once
+ int baz();
+ )"}}));
+}
+
+TEST(IncludeCleanerCheckTest, SuppressMissingIncludes) {
+ const char *PreCode = R"(
+#include "bar.h"
+
+int BarResult = bar();
+int BazResult = baz();
+int QuxResult = qux();
+)";
+
+ ClangTidyOptions Opts;
+ Opts.CheckOptions["IgnoreHeaders"] = llvm::StringRef{
+ "baz.h;" +
+ llvm::Regex::escape(appendPathFileSystemIndependent({"foo", "qux.h"}))};
+ std::vector<ClangTidyError> Errors;
+ EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt, Opts,
+ {{"bar.h", R"(#pragma once
+ #include "baz.h"
+ #include "foo/qux.h"
+ int bar();
+ )"},
+ {"baz.h", R"(#pragma once
+ int baz();
+ )"},
+ {appendPathFileSystemIndependent({"foo", "qux.h"}),
+ R"(#pragma once
+ int qux();
+ )"}}));
+}
+
+TEST(IncludeCleanerCheckTest, SystemMissingIncludes) {
+ const char *PreCode = R"(
+#include <vector>
+
+std::string HelloString;
+std::vector Vec;
+)";
+ const char *PostCode = R"(
+#include <string>
+#include <vector>
+
+std::string HelloString;
+std::vector Vec;
+)";
+
+ std::vector<ClangTidyError> Errors;
+ EXPECT_EQ(PostCode,
+ runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
+ {{"string", R"(#pragma once
+ namespace std { class string {}; }
+ )"},
+ {"vector", R"(#pragma once
+ #include <string>
+ namespace std { class vector {}; }
+ )"}}));
+}
+
+TEST(IncludeCleanerCheckTest, PragmaMissingIncludes) {
+ const char *PreCode = R"(
+#include "bar.h"
+
+int BarResult = bar();
+int FooBarResult = foobar();
+)";
+ const char *PostCode = R"(
+#include "bar.h"
+#include "public.h"
+
+int BarResult = bar();
+int FooBarResult = foobar();
+)";
+
+ std::vector<ClangTidyError> Errors;
+ EXPECT_EQ(PostCode,
+ runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
+ {{"bar.h", R"(#pragma once
+ #include "private.h"
+ int bar();
+ )"},
+ {"private.h", R"(#pragma once
+ // IWYU pragma: private, include "public.h"
+ int foobar();
+ )"}}));
+}
+
+TEST(IncludeCleanerCheckTest, DeclFromMacroExpansion) {
+ const char *PreCode = R"(
+#include "foo.h"
+
+DECLARE(myfunc) {
+ int a;
+}
+)";
+
+ std::vector<ClangTidyError> Errors;
+ EXPECT_EQ(PreCode,
+ runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
+ {{"foo.h",
+ R"(#pragma once
+ #define DECLARE(X) void X()
+ )"}}));
+
+ PreCode = R"(
+#include "foo.h"
+
+DECLARE {
+ int a;
+}
+)";
+
+ EXPECT_EQ(PreCode,
+ runCheckOnCode<IncludeCleanerCheck>(
+ PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
+ {{"foo.h",
+ R"(#pragma once
+ #define DECLARE void myfunc()
+ )"}}));
+}
+
+} // namespace
+} // namespace test
+} // namespace tidy
+} // namespace clang