Skip to content

[clang-tidy] Add UnusedIncludes/MissingIncludes options to misc-include-cleaner #140600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 45 additions & 32 deletions clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand Down Expand Up @@ -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());
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
7 changes: 6 additions & 1 deletion clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// 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: }}' -- -I%S/Inputs -isystem%S/Inputs/system -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]

#include "bar.h"
// CHECK-FIXES-NOT: {{^}}#include "baz.h"{{$}}
58 changes: 58 additions & 0 deletions clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading