[clang-tools-extra] c28506b - [clang-tidy] Implement an include-cleaner check.

Viktoriia Bakalova via cfe-commits cfe-commits at lists.llvm.org
Fri Jun 2 08:21:33 PDT 2023


Author: Viktoriia Bakalova
Date: 2023-06-02T15:21:20Z
New Revision: c28506ba4b6961950849f8fdecd0cf7e503a14f9

URL: https://github.com/llvm/llvm-project/commit/c28506ba4b6961950849f8fdecd0cf7e503a14f9
DIFF: https://github.com/llvm/llvm-project/commit/c28506ba4b6961950849f8fdecd0cf7e503a14f9.diff

LOG: [clang-tidy] Implement an include-cleaner check.

Differential Revision: https://reviews.llvm.org/D148793

Added: 
    clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
    clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
    clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/bar.h
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/baz.h
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/foo.h
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/private.h
    clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner.cpp
    clang-tools-extra/test/clang-tidy/checkers/misc/system/string.h
    clang-tools-extra/test/clang-tidy/checkers/misc/system/vector.h
    clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp

Modified: 
    clang-tools-extra/clang-tidy/misc/CMakeLists.txt
    clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
    clang-tools-extra/clangd/TidyProvider.cpp
    clang-tools-extra/docs/ReleaseNotes.rst
    clang-tools-extra/docs/clang-tidy/checks/list.rst
    clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h
    clang-tools-extra/include-cleaner/lib/Record.cpp
    clang-tools-extra/unittests/clang-tidy/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index a72362906e0b8..1703ff82b942d 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -7,6 +7,7 @@ setup_host_tool(clang-tidy-confusable-chars-gen CLANG_TIDY_CONFUSABLE_CHARS_GEN
 
 add_subdirectory(ConfusableTable)
 
+include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../../include-cleaner/include")
 
 add_custom_command(
     OUTPUT Confusables.inc
@@ -19,6 +20,7 @@ add_clang_library(clangTidyMiscModule
   ConstCorrectnessCheck.cpp
   DefinitionsInHeadersCheck.cpp
   ConfusableIdentifierCheck.cpp
+  IncludeCleanerCheck.cpp
   MiscTidyModule.cpp
   MisleadingBidirectional.cpp
   MisleadingIdentifier.cpp
@@ -53,6 +55,7 @@ clang_target_link_libraries(clangTidyMiscModule
   clangAST
   clangASTMatchers
   clangBasic
+  clangIncludeCleaner
   clangLex
   clangSerialization
   clangTooling

diff  --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
new file mode 100644
index 0000000000000..c7aca83f2ca8c
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
@@ -0,0 +1,202 @@
+//===--- 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 
diff erent 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

diff  --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
new file mode 100644
index 0000000000000..d5f75f2b1c7fa
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
@@ -0,0 +1,53 @@
+//===--- 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

diff  --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 2ec61f8912632..f63c5ab543feb 100644
--- 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 @@ class MiscModule : public ClangTidyModule {
         "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>(

diff  --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp
index e3a6d5af20ae2..f101199a20ceb 100644
--- a/clang-tools-extra/clangd/TidyProvider.cpp
+++ b/clang-tools-extra/clangd/TidyProvider.cpp
@@ -196,32 +196,35 @@ TidyProvider addTidyChecks(llvm::StringRef Checks,
 
 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) {

diff  --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 5e3c46cadde9f..077be93d8510f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -165,6 +165,11 @@ New checks
   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.
 

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index a13f841105f45..2b5f8e8291e0a 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -255,6 +255,7 @@ Clang-Tidy Checks
    `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>`_,

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
index 0000000000000..30865680ac023
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst
@@ -0,0 +1,34 @@
+.. 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.

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
index 27a2270f7e362..ae11f49f83709 100644
--- 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 @@ class PragmaIncludes {
   /// 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
index 113a69c7fbee7..e36753123392a 100644
--- 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 <memory>
+#include <utility>
 
 namespace clang::include_cleaner {
 namespace {
@@ -148,8 +150,9 @@ class PPRecorder : public PPCallbacks {
 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 +345,12 @@ void PragmaIncludes::record(const CompilerInstance &CI) {
   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())
@@ -350,8 +359,8 @@ llvm::StringRef PragmaIncludes::getPublic(const FileEntry *F) const {
 }
 
 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?

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
index 0000000000000..06e7156ea1254
--- /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
index 0000000000000..042e9ca404012
--- /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
index 0000000000000..a158d915f0a1e
--- /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
index 0000000000000..318c9f06c0e2b
--- /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
index 0000000000000..0f4c7a8f4ad1c
--- /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: 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]

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
index 0000000000000..9b23b425350ee
--- /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
index 0000000000000..2d7bb696bfd91
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/system/vector.h
@@ -0,0 +1,4 @@
+#pragma once
+#include <string.h>
+
+namespace std { class vector {}; }

diff  --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
index 336b773206832..3304924d39757 100644
--- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
@@ -15,12 +15,14 @@ endif()
 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 @@ clang_target_link_libraries(ClangTidyTests
   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
index 0000000000000..db048f10a0c15
--- /dev/null
+++ b/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp
@@ -0,0 +1,236 @@
+//===--- 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


        


More information about the cfe-commits mailing list