diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(ConfusableTable) +include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../../include-cleaner/include") add_custom_command( OUTPUT Confusables.inc @@ -19,6 +20,7 @@ ConstCorrectnessCheck.cpp DefinitionsInHeadersCheck.cpp ConfusableIdentifierCheck.cpp + IncludeCleanerCheck.cpp MiscTidyModule.cpp MisleadingBidirectional.cpp MisleadingIdentifier.cpp @@ -53,6 +55,7 @@ clangAST clangASTMatchers clangBasic + clangIncludeCleaner clangLex clangSerialization clangTooling diff --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h @@ -0,0 +1,66 @@ +//===--- 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 "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 "clang/Tooling/Syntax/Tokens.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Regex.h" +#include +#include + +namespace clang::tidy::misc { + +/// Compute unused and missing includes and suggest fixes. +/// 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) + : ClangTidyCheck(Name, Context) { + std::optional IgnoreHeaders = + Options.getLocalOrGlobal("IgnoreHeaders"); + if (!IgnoreHeaders || IgnoreHeaders->empty()) + return; + llvm::SmallVector SplitIgnoreHeaders; + llvm::StringRef{*IgnoreHeaders}.split(SplitIgnoreHeaders, ","); + for (const auto &Header : SplitIgnoreHeaders) { + std::string HeaderSuffix{Header.str() + "$"}; + if (!llvm::Regex{HeaderSuffix}.isValid()) + configurationDiag("Invalid ignore headers regex '%0'") << Header; + IgnoreHeadersRegex.push_back(llvm::Regex{HeaderSuffix}); + } + } + 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; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + +private: + include_cleaner::RecordedPP RecordedPreprocessor; + include_cleaner::PragmaIncludes RecordedPI; + HeaderSearch *HS; + llvm::SmallVector IgnoreHeadersRegex; + bool shouldIgnore(const include_cleaner::Header &H); +}; + +} // namespace clang::tidy::misc + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCLUDECLEANER_H diff --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp @@ -0,0 +1,179 @@ +//===--- 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 "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/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/ArrayRef.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 +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::misc { + +namespace { +struct MissingIncludeInfo { + SourceLocation SymRefLocation; + std::string MissingHeaderSpelling; +}; +} // namespace + +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 Used; + std::vector Missing; + llvm::SmallVector MainFileDecls; + for (auto *D : Result.Nodes.getNodeAs("top")->decls()) { + SourceLocation Loc = D->getLocation(); + if (!SM->isWrittenInMainFile(SM->getSpellingLoc(Loc))) + continue; + MainFileDecls.push_back(D); + } + walkUsed( + MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI, *SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef 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, + include_cleaner::spellHeader(Providers.front(), *HS, MainFile)}); + }); + + std::vector 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 (ClangTidyCheck::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, ClangTidyCheck::getCurrentMainFile(), + format::DefaultFallbackStyle, Code, + &SM->getFileManager().getVirtualFileSystem()); + if (!FileStyle) + FileStyle = format::getLLVMStyle(); + + for (const auto *Inc : Unused) { + diag(Inc->HashLocation, "unused include %0") + << Inc->quote() + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1), + SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1))); + } + + tooling::HeaderIncludes HeaderIncludes(ClangTidyCheck::getCurrentMainFile(), + Code, FileStyle->IncludeStyle); + for (const auto &Inc : Missing) { + bool Angled = llvm::StringRef{Inc.MissingHeaderSpelling}.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. + std::optional Replacement = HeaderIncludes.insert( + llvm::StringRef{Inc.MissingHeaderSpelling}.trim("\"<>"), Angled, + tooling::IncludeDirective::Include); + if (!Replacement.has_value()) + continue; + + diag(Inc.SymRefLocation, "missing include %0") + << Inc.MissingHeaderSpelling + << FixItHint::CreateInsertion( + SM->getComposedLoc(SM->getMainFileID(), + Replacement->getOffset()), + Replacement->getReplacementText()); + } +} + +} // namespace clang::tidy::misc diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp --- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp @@ -12,6 +12,7 @@ #include "ConfusableIdentifierCheck.h" #include "ConstCorrectnessCheck.h" #include "DefinitionsInHeadersCheck.h" +#include "IncludeCleanerCheck.h" #include "MisleadingBidirectional.h" #include "MisleadingIdentifier.h" #include "MisplacedConstCheck.h" @@ -41,6 +42,7 @@ "misc-const-correctness"); CheckFactories.registerCheck( "misc-definitions-in-headers"); + CheckFactories.registerCheck("misc-include-cleaner"); CheckFactories.registerCheck( "misc-misleading-bidirectional"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp --- a/clang-tools-extra/clangd/TidyProvider.cpp +++ b/clang-tools-extra/clangd/TidyProvider.cpp @@ -196,32 +196,34 @@ TidyProvider disableUnusableChecks(llvm::ArrayRef 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. + "", + // ----- 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", + // include-cleaner is directly integrated in IncludeCleaner.cpp + "-misc-include-cleaner", + + // ----- 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) { diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -183,6 +183,11 @@ Enforces consistent token representation for invoked binary, unary and overloaded operators in C++ code. +- New :doc:`misc-include-cleaner + ` check. + + Enforces include-what-you-use on headers using include-cleaner. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -254,6 +254,7 @@ `misc-confusable-identifiers `_, `misc-const-correctness `_, "Yes" `misc-definitions-in-headers `_, "Yes" + `misc-include-cleaner `_, "Yes" `misc-misleading-bidirectional `_, `misc-misleading-identifier `_, `misc-misplaced-const `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - misc-include-cleaner + +misc-include-cleaner +====================== + +Checks for unused and missing includes. The check only generates findings for +the main file of a translation unit. +Findings correspond to https://clangd.llvm.org/design/include-cleaner. + +Options +------- + +.. option:: IgnoreHeader + + A comma-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`. diff --git a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h --- a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h +++ b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h @@ -18,6 +18,7 @@ #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" @@ -52,6 +53,10 @@ /// 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 { diff --git a/clang-tools-extra/include-cleaner/lib/Record.cpp b/clang-tools-extra/include-cleaner/lib/Record.cpp --- a/clang-tools-extra/include-cleaner/lib/Record.cpp +++ b/clang-tools-extra/include-cleaner/lib/Record.cpp @@ -19,6 +19,8 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/Inclusions/HeaderAnalysis.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" +#include +#include namespace clang::include_cleaner { namespace { @@ -148,8 +150,10 @@ 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, @@ -342,6 +346,12 @@ CI.getPreprocessor().addPPCallbacks(std::move(Record)); } +void PragmaIncludes::record(Preprocessor &P) { + auto Record = std::make_unique(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()) diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/bar.h b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/bar.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/bar.h @@ -0,0 +1,4 @@ +#pragma once +#include "baz.h" +#include "private.h" +int bar(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/baz.h b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/baz.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/baz.h @@ -0,0 +1,2 @@ +#pragma once +int baz(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/foo.h b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/foo.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/foo.h @@ -0,0 +1,2 @@ +#pragma once +void foo(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/private.h b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/private.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/private.h @@ -0,0 +1,2 @@ +// IWYU pragma: private, include "public.h" +int foobar(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner.cpp @@ -0,0 +1,17 @@ +// 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: unused include "foo.h" [misc-include-cleaner] +// CHECK-FIXES: {{^}} +// CHECK-FIXES: {{^}}#include {{$}} +#include +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: unused include [misc-include-cleaner] +// CHECK-FIXES: {{^}} +int BarResult = bar(); +int BazResult = baz(); +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: missing include "baz.h" [misc-include-cleaner] +std::string HelloString; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: missing include [misc-include-cleaner] +int FooBarResult = foobar(); +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: missing include "public.h" [misc-include-cleaner] diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/system/string.h b/clang-tools-extra/test/clang-tidy/checkers/misc/system/string.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/system/string.h @@ -0,0 +1,2 @@ +#pragma once +namespace std { class string {}; } diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/system/vector.h b/clang-tools-extra/test/clang-tidy/checkers/misc/system/vector.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/system/vector.h @@ -0,0 +1,4 @@ +#pragma once +#include + +namespace std { class vector {}; } diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -15,12 +15,14 @@ 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 @@ -46,12 +48,14 @@ clangTooling clangToolingCore clangTransformer + clangIncludeCleaner ) target_link_libraries(ClangTidyTests PRIVATE clangTidy clangTidyAndroidModule clangTidyGoogleModule + clangTidyMiscModule clangTidyLLVMModule clangTidyModernizeModule clangTidyObjCModule diff --git a/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp b/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp @@ -0,0 +1,169 @@ +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyOptions.h" +#include "ClangTidyTest.h" +#include "misc/IncludeCleanerCheck.h" +#include "gtest/gtest.h" + +#include +#include + +using namespace clang::tidy::misc; + +namespace clang { +namespace tidy { +namespace test { + +TEST(IncludeCleanerCheckTest, BasicUnusedIncludes) { + const char *PreCode = R"( +#include "bar.h" +#include +#include "bar.h" +)"; + const char *PostCode = "\n"; + + std::vector Errors; + EXPECT_EQ(PostCode, runCheckOnCode( + 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 +)"; + + const char *PostCode = R"( +#include "bar.h" +#include "foo/qux.h" +#include +)"; + + std::vector Errors; + ClangTidyOptions Opts; + Opts.CheckOptions["IgnoreHeaders"] = "bar.h,foo/.*,baz/qux,vector"; + EXPECT_EQ(PostCode, runCheckOnCode( + PreCode, &Errors, "file.cpp", std::nullopt, Opts, + {{"bar.h", ""}, + {"vector", ""}, + {"foo/qux.h", ""}, + {"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 Errors; + EXPECT_EQ(PostCode, + runCheckOnCode( + 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"] = "baz.h,foo/.*"; + std::vector Errors; + EXPECT_EQ(PreCode, runCheckOnCode( + 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(); + )"}, + {"foo/qux.h", R"(#pragma once + int qux(); + )"}})); +} + +TEST(IncludeCleanerCheckTest, SystemMissingIncludes) { + const char *PreCode = R"( +#include + +std::string HelloString; +std::vector Vec; +)"; + const char *PostCode = R"( +#include +#include + +std::string HelloString; +std::vector Vec; +)"; + + std::vector Errors; + EXPECT_EQ(PostCode, + runCheckOnCode( + PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(), + {{"string", R"(#pragma once + namespace std { class string {}; } + )"}, + {"vector", R"(#pragma once + #include + 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 Errors; + EXPECT_EQ(PostCode, + runCheckOnCode( + 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(); + )"}})); +} + +} // namespace test +} // namespace tidy +} // namespace clang