Skip to content

Commit e68b81b

Browse files
committed
[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.
1 parent bca39f4 commit e68b81b

File tree

5 files changed

+125
-33
lines changed

5 files changed

+125
-33
lines changed

clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
5959
: ClangTidyCheck(Name, Context),
6060
IgnoreHeaders(
6161
utils::options::parseStringList(Options.get("IgnoreHeaders", ""))),
62-
DeduplicateFindings(Options.get("DeduplicateFindings", true)) {
62+
DeduplicateFindings(Options.get("DeduplicateFindings", true)),
63+
UnusedIncludes(Options.get("UnusedIncludes", true)),
64+
MissingIncludes(Options.get("MissingIncludes", true)) {
6365
for (const auto &Header : IgnoreHeaders) {
6466
if (!llvm::Regex{Header}.isValid())
6567
configurationDiag("Invalid ignore headers regex '%0'") << Header;
@@ -68,12 +70,20 @@ IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
6870
HeaderSuffix += "$";
6971
IgnoreHeadersRegex.emplace_back(HeaderSuffix);
7072
}
73+
74+
if (UnusedIncludes == false && MissingIncludes == false)
75+
this->configurationDiag(
76+
"The check 'misc-include-cleaner' will not "
77+
"perform any analysis because 'UnusedIncludes' and "
78+
"'MissingIncludes' are both false.");
7179
}
7280

7381
void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
7482
Options.store(Opts, "IgnoreHeaders",
7583
utils::options::serializeStringList(IgnoreHeaders));
7684
Options.store(Opts, "DeduplicateFindings", DeduplicateFindings);
85+
Options.store(Opts, "UnusedIncludes", UnusedIncludes);
86+
Options.store(Opts, "MissingIncludes", MissingIncludes);
7787
}
7888

7989
bool IncludeCleanerCheck::isLanguageVersionSupported(
@@ -200,39 +210,43 @@ void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) {
200210
if (!FileStyle)
201211
FileStyle = format::getLLVMStyle();
202212

203-
for (const auto *Inc : Unused) {
204-
diag(Inc->HashLocation, "included header %0 is not used directly")
205-
<< llvm::sys::path::filename(Inc->Spelled,
206-
llvm::sys::path::Style::posix)
207-
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(
208-
SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
209-
SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
213+
if (UnusedIncludes) {
214+
for (const auto *Inc : Unused) {
215+
diag(Inc->HashLocation, "included header %0 is not used directly")
216+
<< llvm::sys::path::filename(Inc->Spelled,
217+
llvm::sys::path::Style::posix)
218+
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(
219+
SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
220+
SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
221+
}
210222
}
211223

212-
tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,
213-
FileStyle->IncludeStyle);
214-
// Deduplicate insertions when running in bulk fix mode.
215-
llvm::StringSet<> InsertedHeaders{};
216-
for (const auto &Inc : Missing) {
217-
std::string Spelling = include_cleaner::spellHeader(
218-
{Inc.Missing, PP->getHeaderSearchInfo(), MainFile});
219-
bool Angled = llvm::StringRef{Spelling}.starts_with("<");
220-
// We might suggest insertion of an existing include in edge cases, e.g.,
221-
// include is present in a PP-disabled region, or spelling of the header
222-
// turns out to be the same as one of the unresolved includes in the
223-
// main file.
224-
if (auto Replacement =
225-
HeaderIncludes.insert(llvm::StringRef{Spelling}.trim("\"<>"),
226-
Angled, tooling::IncludeDirective::Include)) {
227-
DiagnosticBuilder DB =
228-
diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),
229-
"no header providing \"%0\" is directly included")
230-
<< Inc.SymRef.Target.name();
231-
if (areDiagsSelfContained() ||
232-
InsertedHeaders.insert(Replacement->getReplacementText()).second) {
233-
DB << FixItHint::CreateInsertion(
234-
SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()),
235-
Replacement->getReplacementText());
224+
if (MissingIncludes) {
225+
tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,
226+
FileStyle->IncludeStyle);
227+
// Deduplicate insertions when running in bulk fix mode.
228+
llvm::StringSet<> InsertedHeaders{};
229+
for (const auto &Inc : Missing) {
230+
std::string Spelling = include_cleaner::spellHeader(
231+
{Inc.Missing, PP->getHeaderSearchInfo(), MainFile});
232+
bool Angled = llvm::StringRef{Spelling}.starts_with("<");
233+
// We might suggest insertion of an existing include in edge cases, e.g.,
234+
// include is present in a PP-disabled region, or spelling of the header
235+
// turns out to be the same as one of the unresolved includes in the
236+
// main file.
237+
if (auto Replacement = HeaderIncludes.insert(
238+
llvm::StringRef{Spelling}.trim("\"<>"), Angled,
239+
tooling::IncludeDirective::Include)) {
240+
DiagnosticBuilder DB =
241+
diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),
242+
"no header providing \"%0\" is directly included")
243+
<< Inc.SymRef.Target.name();
244+
if (areDiagsSelfContained() ||
245+
InsertedHeaders.insert(Replacement->getReplacementText()).second) {
246+
DB << FixItHint::CreateInsertion(
247+
SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()),
248+
Replacement->getReplacementText());
249+
}
236250
}
237251
}
238252
}

clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class IncludeCleanerCheck : public ClangTidyCheck {
4747
std::vector<StringRef> IgnoreHeaders;
4848
// Whether emit only one finding per usage of a symbol.
4949
const bool DeduplicateFindings;
50+
// Whether to report unused includes.
51+
const bool UnusedIncludes;
52+
// Whether to report missing includes.
53+
const bool MissingIncludes;
5054
llvm::SmallVector<llvm::Regex> IgnoreHeadersRegex;
5155
bool shouldIgnore(const include_cleaner::Header &H);
5256
};

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ Changes in existing checks
225225
tolerating fix-it breaking compilation when functions is used as pointers
226226
to avoid matching usage of functions within the current compilation unit.
227227

228+
- Improved :doc:`misc-include-cleaner
229+
<clang-tidy/checks/misc/misc-include-cleaner>` check by adding the options
230+
`UnusedIncludes` and `MissingIncludes`, which specify whether the check should
231+
report unused or missing includes respectively.
232+
228233
Removed checks
229234
^^^^^^^^^^^^^^
230235

clang-tools-extra/docs/clang-tidy/checks/misc/include-cleaner.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Findings correspond to https://clangd.llvm.org/design/include-cleaner.
1010
Example:
1111

1212
.. code-block:: c++
13-
13+
1414
// foo.h
1515
class Foo{};
1616
// bar.h
@@ -38,3 +38,14 @@ Options
3838

3939
A boolean that controls whether the check should deduplicate findings for the
4040
same symbol. Defaults to `true`.
41+
42+
.. option:: UnusedIncludes
43+
44+
A boolean that controls whether the check should report unused includes
45+
(includes that are not used directly). Defaults to `true`.
46+
47+
.. option:: MissingIncludes
48+
49+
A boolean that controls whether the check should report missing includes
50+
(header files from which symbols are used but which are not directly included).
51+
Defaults to `true`.

clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,64 @@ DECLARE {
316316
)"}}));
317317
}
318318

319+
TEST(IncludeCleanerCheckTest, UnusedIncludes) {
320+
const char *PreCode = R"(
321+
#include "bar.h")";
322+
323+
{
324+
std::vector<ClangTidyError> Errors;
325+
runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {},
326+
ClangTidyOptions(),
327+
{{"bar.h", "#pragma once"}});
328+
ASSERT_THAT(Errors.size(), testing::Eq(1U));
329+
EXPECT_EQ(Errors.front().Message.Message,
330+
"included header bar.h is not used directly");
331+
}
332+
{
333+
std::vector<ClangTidyError> Errors;
334+
ClangTidyOptions Opts;
335+
Opts.CheckOptions["test-check-0.UnusedIncludes"] = "false";
336+
runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {}, Opts,
337+
{{"bar.h", "#pragma once"}});
338+
ASSERT_THAT(Errors.size(), testing::Eq(0U));
339+
}
340+
}
341+
342+
TEST(IncludeCleanerCheckTest, MissingIncludes) {
343+
const char *PreCode = R"(
344+
#include "baz.h" // IWYU pragma: keep
345+
346+
int BarResult1 = bar();)";
347+
348+
{
349+
std::vector<ClangTidyError> Errors;
350+
runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {},
351+
ClangTidyOptions(),
352+
{{"baz.h", R"(#pragma once
353+
#include "bar.h"
354+
)"},
355+
{"bar.h", R"(#pragma once
356+
int bar();
357+
)"}});
358+
ASSERT_THAT(Errors.size(), testing::Eq(1U));
359+
EXPECT_EQ(Errors.front().Message.Message,
360+
"no header providing \"bar\" is directly included");
361+
}
362+
{
363+
std::vector<ClangTidyError> Errors;
364+
ClangTidyOptions Opts;
365+
Opts.CheckOptions["test-check-0.MissingIncludes"] = "false";
366+
runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {}, Opts,
367+
{{"baz.h", R"(#pragma once
368+
#include "bar.h"
369+
)"},
370+
{"bar.h", R"(#pragma once
371+
int bar();
372+
)"}});
373+
ASSERT_THAT(Errors.size(), testing::Eq(0U));
374+
}
375+
}
376+
319377
} // namespace
320378
} // namespace test
321379
} // namespace tidy

0 commit comments

Comments
 (0)