https://github.com/DaanDeMeyer updated https://github.com/llvm/llvm-project/pull/140600
>From e68b81b128fd1c8a6fb7a462d0e610c4a7dcfa6a 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 | 78 +++++++++++-------- .../clang-tidy/misc/IncludeCleanerCheck.h | 4 + clang-tools-extra/docs/ReleaseNotes.rst | 5 ++ .../checks/misc/include-cleaner.rst | 13 +++- .../clang-tidy/IncludeCleanerTest.cpp | 58 ++++++++++++++ 5 files changed, 125 insertions(+), 33 deletions(-) diff --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp index 7638bbc103d16..b59b15e4617eb 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,20 @@ 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 +210,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..b63850c2eca40 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -225,6 +225,11 @@ Changes in existing checks tolerating fix-it breaking compilation when functions is used as pointers to avoid matching usage of functions within the current compilation unit. +- Improved :doc:`misc-include-cleaner + <clang-tidy/checks/misc/misc-include-cleaner>` check by adding the options + `UnusedIncludes` and `MissingIncludes`, which specify whether the check should + report unused or missing includes respectively. + Removed checks ^^^^^^^^^^^^^^ 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..4364610787058 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 @@ -10,7 +10,7 @@ Findings correspond to https://clangd.llvm.org/design/include-cleaner. Example: .. code-block:: c++ - + // foo.h class Foo{}; // bar.h @@ -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/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