https://github.com/DaanDeMeyer updated 
https://github.com/llvm/llvm-project/pull/140600

>From 900e6b24fb244d9034cdf81f337dd457a451b915 Mon Sep 17 00:00:00 2001
From: Daan De Meyer <daan.j.deme...@gmail.com>
Date: Mon, 19 May 2025 21:39:32 +0200
Subject: [PATCH] [clang-tidy] Add UnusedIncludes/MissingIncludes options to
 misc-include-cleaner

These mimick the same options from clangd and allow using the check to
only check for unused includes or missing includes.
---
 .../clang-tidy/misc/IncludeCleanerCheck.cpp   | 77 +++++++++++--------
 .../clang-tidy/misc/IncludeCleanerCheck.h     |  4 +
 clang-tools-extra/docs/ReleaseNotes.rst       |  7 +-
 .../checks/misc/include-cleaner.rst           | 11 +++
 .../misc/include-cleaner-wrong-config.cpp     | 13 ++++
 .../clang-tidy/IncludeCleanerTest.cpp         | 58 ++++++++++++++
 6 files changed, 137 insertions(+), 33 deletions(-)
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner-wrong-config.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
index 7638bbc103d16..52a94f3c040aa 100644
--- a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
@@ -59,7 +59,9 @@ IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
     : ClangTidyCheck(Name, Context),
       IgnoreHeaders(
           utils::options::parseStringList(Options.get("IgnoreHeaders", ""))),
-      DeduplicateFindings(Options.get("DeduplicateFindings", true)) {
+      DeduplicateFindings(Options.get("DeduplicateFindings", true)),
+      UnusedIncludes(Options.get("UnusedIncludes", true)),
+      MissingIncludes(Options.get("MissingIncludes", true)) {
   for (const auto &Header : IgnoreHeaders) {
     if (!llvm::Regex{Header}.isValid())
       configurationDiag("Invalid ignore headers regex '%0'") << Header;
@@ -68,12 +70,19 @@ IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
       HeaderSuffix += "$";
     IgnoreHeadersRegex.emplace_back(HeaderSuffix);
   }
+
+  if (UnusedIncludes == false && MissingIncludes == false)
+    this->configurationDiag("The check 'misc-include-cleaner' will not "
+                            "perform any analysis because 'UnusedIncludes' and 
"
+                            "'MissingIncludes' are both false.");
 }
 
 void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
   Options.store(Opts, "IgnoreHeaders",
                 utils::options::serializeStringList(IgnoreHeaders));
   Options.store(Opts, "DeduplicateFindings", DeduplicateFindings);
+  Options.store(Opts, "UnusedIncludes", UnusedIncludes);
+  Options.store(Opts, "MissingIncludes", MissingIncludes);
 }
 
 bool IncludeCleanerCheck::isLanguageVersionSupported(
@@ -200,39 +209,43 @@ void IncludeCleanerCheck::check(const 
MatchFinder::MatchResult &Result) {
   if (!FileStyle)
     FileStyle = format::getLLVMStyle();
 
-  for (const auto *Inc : Unused) {
-    diag(Inc->HashLocation, "included header %0 is not used directly")
-        << llvm::sys::path::filename(Inc->Spelled,
-                                     llvm::sys::path::Style::posix)
-        << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
-               SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
-               SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
+  if (UnusedIncludes) {
+    for (const auto *Inc : Unused) {
+      diag(Inc->HashLocation, "included header %0 is not used directly")
+          << llvm::sys::path::filename(Inc->Spelled,
+                                       llvm::sys::path::Style::posix)
+          << 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);
-  // Deduplicate insertions when running in bulk fix mode.
-  llvm::StringSet<> InsertedHeaders{};
-  for (const auto &Inc : Missing) {
-    std::string Spelling = include_cleaner::spellHeader(
-        {Inc.Missing, PP->getHeaderSearchInfo(), 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)) 
{
-      DiagnosticBuilder DB =
-          diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),
-               "no header providing \"%0\" is directly included")
-          << Inc.SymRef.Target.name();
-      if (areDiagsSelfContained() ||
-          InsertedHeaders.insert(Replacement->getReplacementText()).second) {
-        DB << FixItHint::CreateInsertion(
-            SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()),
-            Replacement->getReplacementText());
+  if (MissingIncludes) {
+    tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,
+                                           FileStyle->IncludeStyle);
+    // Deduplicate insertions when running in bulk fix mode.
+    llvm::StringSet<> InsertedHeaders{};
+    for (const auto &Inc : Missing) {
+      std::string Spelling = include_cleaner::spellHeader(
+          {Inc.Missing, PP->getHeaderSearchInfo(), 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)) {
+        DiagnosticBuilder DB =
+            diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),
+                 "no header providing \"%0\" is directly included")
+            << Inc.SymRef.Target.name();
+        if (areDiagsSelfContained() ||
+            InsertedHeaders.insert(Replacement->getReplacementText()).second) {
+          DB << FixItHint::CreateInsertion(
+              SM->getComposedLoc(SM->getMainFileID(), 
Replacement->getOffset()),
+              Replacement->getReplacementText());
+        }
       }
     }
   }
diff --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h 
b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
index b46e409bd6f6a..8f05887efb776 100644
--- a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
@@ -47,6 +47,10 @@ class IncludeCleanerCheck : public ClangTidyCheck {
   std::vector<StringRef> IgnoreHeaders;
   // Whether emit only one finding per usage of a symbol.
   const bool DeduplicateFindings;
+  // Whether to report unused includes.
+  const bool UnusedIncludes;
+  // Whether to report missing includes.
+  const bool MissingIncludes;
   llvm::SmallVector<llvm::Regex> IgnoreHeadersRegex;
   bool shouldIgnore(const include_cleaner::Header &H);
 };
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 9b29ab12e1bfa..66dec0fc5eef4 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -180,6 +180,11 @@ Changes in existing checks
   `AnalyzePointers` option and fixing false positives when using const array
   type.
 
+- Improved :doc:`misc-include-cleaner
+  <clang-tidy/checks/misc/include-cleaner>` check by adding the options
+  `UnusedIncludes` and `MissingIncludes`, which specify whether the check 
should
+  report unused or missing includes respectively.
+
 - Improved :doc:`misc-redundant-expression
   <clang-tidy/checks/misc/redundant-expression>` check by providing additional
   examples and fixing some macro related false positives.
@@ -204,7 +209,7 @@ Changes in existing checks
   diagnosing designated initializers for ``std::array`` initializations.
 
 - Improved :doc:`modernize-use-ranges
-  <clang-tidy/checks/modernize/use-ranges>` check by updating suppress 
+  <clang-tidy/checks/modernize/use-ranges>` check by updating suppress
   warnings logic for ``nullptr`` in ``std::find``.
 
 - Improved :doc:`modernize-use-starts-ends-with
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
index d112f01cbc0b1..34833a3dd1aea 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst
@@ -38,3 +38,14 @@ Options
 
    A boolean that controls whether the check should deduplicate findings for 
the
    same symbol. Defaults to `true`.
+
+.. option:: UnusedIncludes
+
+   A boolean that controls whether the check should report unused includes
+   (includes that are not used directly). Defaults to `true`.
+
+.. option:: MissingIncludes
+
+   A boolean that controls whether the check should report missing includes
+   (header files from which symbols are used but which are not directly 
included).
+   Defaults to `true`.
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner-wrong-config.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner-wrong-config.cpp
new file mode 100644
index 0000000000000..8ca008a274007
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/misc/include-cleaner-wrong-config.cpp
@@ -0,0 +1,13 @@
+// RUN: %check_clang_tidy %s misc-include-cleaner %t \
+// RUN: -config='{CheckOptions: \
+// RUN:  {"misc-include-cleaner.UnusedIncludes": false,\
+// RUN:   "misc-include-cleaner.MissingIncludes": false,\
+// RUN:  }}' -- -fno-delayed-template-parsing
+
+// CHECK-MESSAGES: warning: The check 'misc-include-cleaner' will not perform 
any analysis because 'UnusedIncludes' and 'MissingIncludes' are both false. 
[clang-tidy-config]
+
+void g() {
+    int p_local0 = 42;
+    // CHECK-FIXES-NOT: int const p_local0 = 42;
+  }
+  
\ No newline at end of file
diff --git a/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp 
b/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp
index 3d6ec995e443d..00576916492e1 100644
--- a/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp
@@ -316,6 +316,64 @@ DECLARE {
                   )"}}));
 }
 
+TEST(IncludeCleanerCheckTest, UnusedIncludes) {
+  const char *PreCode = R"(
+#include "bar.h")";
+
+  {
+    std::vector<ClangTidyError> Errors;
+    runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {},
+                                        ClangTidyOptions(),
+                                        {{"bar.h", "#pragma once"}});
+    ASSERT_THAT(Errors.size(), testing::Eq(1U));
+    EXPECT_EQ(Errors.front().Message.Message,
+              "included header bar.h is not used directly");
+  }
+  {
+    std::vector<ClangTidyError> Errors;
+    ClangTidyOptions Opts;
+    Opts.CheckOptions["test-check-0.UnusedIncludes"] = "false";
+    runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {}, Opts,
+                                        {{"bar.h", "#pragma once"}});
+    ASSERT_THAT(Errors.size(), testing::Eq(0U));
+  }
+}
+
+TEST(IncludeCleanerCheckTest, MissingIncludes) {
+  const char *PreCode = R"(
+#include "baz.h" // IWYU pragma: keep
+
+int BarResult1 = bar();)";
+
+  {
+    std::vector<ClangTidyError> Errors;
+    runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {},
+                                        ClangTidyOptions(),
+                                        {{"baz.h", R"(#pragma once
+                                          #include "bar.h"
+                                       )"},
+                                         {"bar.h", R"(#pragma once
+                                          int bar();
+                                       )"}});
+    ASSERT_THAT(Errors.size(), testing::Eq(1U));
+    EXPECT_EQ(Errors.front().Message.Message,
+              "no header providing \"bar\" is directly included");
+  }
+  {
+    std::vector<ClangTidyError> Errors;
+    ClangTidyOptions Opts;
+    Opts.CheckOptions["test-check-0.MissingIncludes"] = "false";
+    runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {}, Opts,
+                                        {{"baz.h", R"(#pragma once
+                                          #include "bar.h"
+                                       )"},
+                                         {"bar.h", R"(#pragma once
+                                          int bar();
+                                       )"}});
+    ASSERT_THAT(Errors.size(), testing::Eq(0U));
+  }
+}
+
 } // namespace
 } // namespace test
 } // namespace tidy

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to