16#include "clang-include-cleaner/Analysis.h"
17#include "clang-include-cleaner/Types.h"
18#include "clang/AST/DeclBase.h"
19#include "clang/Basic/SourceManager.h"
20#include "clang/Tooling/Syntax/Tokens.h"
21#include "llvm/ADT/ArrayRef.h"
22#include "llvm/ADT/ScopeExit.h"
23#include "llvm/ADT/StringMap.h"
24#include "llvm/ADT/StringRef.h"
25#include "llvm/Support/Casting.h"
26#include "llvm/Support/Error.h"
27#include "llvm/Support/ScopedPrinter.h"
28#include "gmock/gmock.h"
29#include "gtest/gtest.h"
39using ::testing::AllOf;
40using ::testing::ElementsAre;
41using ::testing::IsEmpty;
42using ::testing::Matcher;
43using ::testing::Pointee;
44using ::testing::UnorderedElementsAre;
47withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
48 return Field(&
Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
52 "Diag at " + llvm::to_string(Range) +
" = [" + Message +
"]") {
56MATCHER_P3(Fix, Range, Replacement, Message,
57 "Fix " + llvm::to_string(Range) +
" => " +
58 ::testing::PrintToString(Replacement) +
" = [" + Message +
"]") {
59 return arg.Message ==
Message && arg.Edits.size() == 1 &&
60 arg.Edits[0].range ==
Range && arg.Edits[0].newText == Replacement;
64std::string guard(llvm::StringRef
Code) {
65 return "#pragma once\n" +
Code.str();
69 if (arg.Written != Written)
70 *result_listener << arg.Written;
71 return arg.Written == Written;
74TEST(IncludeCleaner, StdlibUnused) {
78 #include <vector> // IWYU pragma: keep
79 #include <string> // IWYU pragma: export
83 TU.AdditionalFiles[
"bits"] = R
"cpp(
86 template <typename> class list {};
87 template <typename> class queue {};
88 template <typename> class vector {};
91 TU.AdditionalFiles["list"] = guard(
"#include <bits>");
92 TU.AdditionalFiles[
"queue"] = guard(
"#include <bits>");
93 TU.AdditionalFiles[
"vector"] = guard(
"#include <bits>");
94 TU.AdditionalFiles[
"string"] = guard(
"#include <bits>");
95 TU.ExtraArgs = {
"-isystem",
testRoot()};
96 auto AST = TU.build();
98 EXPECT_THAT(Findings.UnusedIncludes,
99 ElementsAre(
Pointee(writtenInclusion(
"<queue>"))));
102TEST(IncludeCleaner, GetUnusedHeaders) {
103 llvm::StringLiteral
MainFile = R
"cpp(
107 #include "dir/unused.h"
108 #include "unguarded.h"
110 #include <system_header.h>
118 TU.Filename =
"foo.cpp";
119 TU.AdditionalFiles[
"foo.h"] = guard(
"void foo();");
120 TU.AdditionalFiles[
"a.h"] = guard(
"void a();");
121 TU.AdditionalFiles[
"b.h"] = guard(
"void b();");
122 TU.AdditionalFiles[
"dir/c.h"] = guard(
"void c();");
123 TU.AdditionalFiles[
"unused.h"] = guard(
"void unused();");
124 TU.AdditionalFiles[
"dir/unused.h"] = guard(
"void dirUnused();");
125 TU.AdditionalFiles[
"system/system_header.h"] = guard(
"");
126 TU.AdditionalFiles[
"unguarded.h"] =
"";
127 TU.ExtraArgs.push_back(
"-I" +
testPath(
"dir"));
128 TU.ExtraArgs.push_back(
"-isystem" +
testPath(
"system"));
130 ParsedAST
AST = TU.build();
133 Findings.UnusedIncludes,
134 UnorderedElementsAre(
Pointee(writtenInclusion(
"\"unused.h\"")),
135 Pointee(writtenInclusion(
"\"dir/unused.h\""))));
138TEST(IncludeCleaner, ComputeMissingHeaders) {
146 TU.Filename = "foo.cpp";
147 TU.AdditionalFiles[
"a.h"] = guard(
"#include \"b.h\"");
148 TU.AdditionalFiles[
"b.h"] = guard(
"void b();");
151 ParsedAST
AST = TU.build();
154 const SourceManager &SM =
AST.getSourceManager();
155 const NamedDecl *BDecl =
nullptr;
156 for (
Decl *D :
AST.getASTContext().getTranslationUnitDecl()->decls()) {
157 const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D);
158 std::string
Name = CandidateDecl->getQualifiedNameAsString();
161 BDecl = CandidateDecl;
164 include_cleaner::Symbol
B{*BDecl};
168 syntax::FileRange BRange{SM.getMainFileID(),
static_cast<unsigned int>(Start),
169 static_cast<unsigned int>(End)};
170 include_cleaner::Header Header{
171 *SM.getFileManager().getOptionalFileRef(
"b.h")};
172 MissingIncludeDiagInfo BInfo{
B, BRange, {Header}};
173 EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
176TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
180$insert_b[[]]#include "baz.h"
182$insert_d[[]]$insert_foo[[]]#include "fuzz.h"
184$insert_foobar[[]]#include <e.h>
185$insert_f[[]]$insert_vector[[]]
187#define DEF(X) const Foo *X;
188#define BAZ(X) const X x
190// No missing include insertion for ambiguous macro refs.
201 // this should not be diagnosed, because it's ignored in the config
206 std::$vector[[vector]] v;
208 int var = $FOO[[FOO]];
218 TU.Filename = "main.cpp";
219 TU.AdditionalFiles[
"a.h"] = guard(
"#include \"b.h\"");
220 TU.AdditionalFiles[
"b.h"] = guard(
"void b();");
222 TU.AdditionalFiles[
"dir/c.h"] = guard(
"#include \"d.h\"");
223 TU.AdditionalFiles[
"dir/d.h"] =
224 guard(
"namespace ns { struct Bar { void d(); }; }");
226 TU.AdditionalFiles[
"system/e.h"] = guard(
"#include <f.h>");
227 TU.AdditionalFiles[
"system/f.h"] = guard(
"void f();");
228 TU.ExtraArgs.push_back(
"-isystem" +
testPath(
"system"));
230 TU.AdditionalFiles[
"fuzz.h"] = guard(
"#include \"buzz.h\"");
231 TU.AdditionalFiles[
"buzz.h"] = guard(
"void buzz();");
233 TU.AdditionalFiles[
"baz.h"] = guard(
"#include \"private.h\"");
234 TU.AdditionalFiles[
"private.h"] = guard(R
"cpp(
235 // IWYU pragma: private, include "public.h"
238 TU.AdditionalFiles["header.h"] = guard(R
"cpp(
239 namespace std { class vector {}; }
242 TU.AdditionalFiles["all.h"] = guard(
"#include \"foo.h\"");
243 TU.AdditionalFiles[
"foo.h"] = guard(R
"cpp(
244 #define BAR(x) Foo *x
250 ParsedAST AST = TU.build();
253 Findings.UnusedIncludes.clear();
255 AST, TU.Code, Findings, MockFS(),
256 {[](llvm::StringRef Header) {
return Header.ends_with(
"buzz.h"); }});
259 UnorderedElementsAre(
261 "No header providing \"b\" is directly included"),
262 withFix({Fix(
MainFile.range(
"insert_b"),
"#include \"b.h\"\n",
264 FixMessage(
"add all missing includes")})),
266 "No header providing \"ns::Bar\" is directly included"),
267 withFix({Fix(
MainFile.range(
"insert_d"),
268 "#include \"dir/d.h\"\n",
"#include \"dir/d.h\""),
269 FixMessage(
"add all missing includes")})),
271 "No header providing \"f\" is directly included"),
272 withFix({Fix(
MainFile.range(
"insert_f"),
"#include <f.h>\n",
274 FixMessage(
"add all missing includes")})),
277 "No header providing \"foobar\" is directly included"),
278 withFix({Fix(
MainFile.range(
"insert_foobar"),
279 "#include \"public.h\"\n",
"#include \"public.h\""),
280 FixMessage(
"add all missing includes")})),
283 "No header providing \"std::vector\" is directly included"),
285 Fix(
MainFile.range(
"insert_vector"),
"#include <vector>\n",
286 "#include <vector>"),
287 FixMessage(
"add all missing includes"),
290 "No header providing \"FOO\" is directly included"),
291 withFix({Fix(
MainFile.range(
"insert_foo"),
292 "#include \"foo.h\"\n",
"#include \"foo.h\""),
293 FixMessage(
"add all missing includes")})),
295 "No header providing \"Foo\" is directly included"),
296 withFix({Fix(
MainFile.range(
"insert_foo"),
297 "#include \"foo.h\"\n",
"#include \"foo.h\""),
298 FixMessage(
"add all missing includes")})),
300 "No header providing \"BAR\" is directly included"),
301 withFix({Fix(
MainFile.range(
"insert_foo"),
302 "#include \"foo.h\"\n",
"#include \"foo.h\""),
303 FixMessage(
"add all missing includes")})),
305 "No header providing \"Foo\" is directly included"),
306 withFix({Fix(
MainFile.range(
"insert_foo"),
307 "#include \"foo.h\"\n",
"#include \"foo.h\""),
308 FixMessage(
"add all missing includes")}))));
311TEST(IncludeCleaner, IWYUPragmas) {
314 #include "behind_keep.h" // IWYU pragma: keep
315 #include "exported.h" // IWYU pragma: export
318 void bar() { foo(); }
319 #include "keep_main_file.h" // IWYU pragma: keep
321 TU.AdditionalFiles["behind_keep.h"] = guard(
"");
322 TU.AdditionalFiles[
"keep_main_file.h"] = guard(
"");
323 TU.AdditionalFiles[
"exported.h"] = guard(
"");
324 TU.AdditionalFiles[
"public.h"] = guard(
"#include \"private.h\"");
325 TU.AdditionalFiles[
"private.h"] = guard(R
"cpp(
326 // IWYU pragma: private, include "public.h"
329 ParsedAST AST = TU.build();
331 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
334TEST(IncludeCleaner, IWYUPragmaExport) {
339 TU.AdditionalFiles["foo.h"] = R
"cpp(
343 #include "bar.h" // IWYU pragma: export
347 TU.AdditionalFiles["bar.h"] = guard(R
"cpp(
350 ParsedAST AST = TU.build();
353 EXPECT_THAT(Findings.UnusedIncludes,
354 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
357TEST(IncludeCleaner, NoDiagsForObjC) {
364 TU.AdditionalFiles["foo.h"] = R
"cpp(
370 TU.ExtraArgs.emplace_back("-xobjective-c");
372 ParsedAST
AST = TU.build();
374 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
375 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
378TEST(IncludeCleaner, UmbrellaUsesPrivate) {
383 TU.AdditionalFiles["private.h"] = guard(R
"cpp(
384 // IWYU pragma: private, include "public.h"
387 TU.Filename = "public.h";
388 ParsedAST
AST = TU.build();
390 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
393TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
396 #define FOO(X) const Foo *X
398 #include [["expander.inc"]]
403 TU.AdditionalFiles["expander.inc"] = guard(
"FOO(f1);FOO(f2);");
404 TU.AdditionalFiles[
"foo.h"] = guard(
"struct Foo {};");
405 TU.AdditionalFiles[
"all.h"] = guard(
"#include \"foo.h\"");
408 ParsedAST
AST = TU.build();
411 EXPECT_THAT(Findings, testing::SizeIs(1));
412 auto RefRange = Findings.front().SymRefRange;
413 auto &SM =
AST.getSourceManager();
414 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
419TEST(IncludeCleaner, MissingIncludesAreUnique) {
426 TU.AdditionalFiles["foo.h"] = guard(
"struct Foo {};");
427 TU.AdditionalFiles[
"all.h"] = guard(R
"cpp(
429 #define FOO(X) X y; X z
433 ParsedAST AST = TU.build();
436 EXPECT_THAT(Findings, testing::SizeIs(1));
437 auto RefRange = Findings.front().SymRefRange;
438 auto &SM =
AST.getSourceManager();
439 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
443TEST(IncludeCleaner, NoCrash) {
445 Annotations MainCode(R
"cpp(
451 TU.Code = MainCode.code();
452 TU.AdditionalFiles["foo.h"] =
453 guard(
"int operator\"\"s(unsigned long long) { return 0; }");
454 TU.AdditionalFiles[
"all.h"] = guard(
"#include \"foo.h\"");
455 ParsedAST
AST = TU.build();
456 const auto &MissingIncludes =
458 EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
459 auto &SM =
AST.getSourceManager();
461 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
465TEST(IncludeCleaner, IsPreferredProvider) {
471 TU.AdditionalFiles["decl.h"] =
"";
472 TU.AdditionalFiles[
"def.h"] =
"";
474 auto AST = TU.build();
475 auto &IncludeDecl =
AST.getIncludeStructure().MainFileIncludes[0];
476 auto &IncludeDef1 =
AST.getIncludeStructure().MainFileIncludes[1];
477 auto &IncludeDef2 =
AST.getIncludeStructure().MainFileIncludes[2];
479 auto &FM =
AST.getSourceManager().getFileManager();
480 auto DeclH = *FM.getOptionalFileRef(
"decl.h");
481 auto DefH = *FM.getOptionalFileRef(
"def.h");
484 std::vector<include_cleaner::Header> Providers = {
485 include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
491TEST(IncludeCleaner, BatchFix) {
493 TU.Filename =
"main.cpp";
494 TU.AdditionalFiles[
"foo.h"] = guard(
"class Foo;");
495 TU.AdditionalFiles[
"bar.h"] = guard(
"class Bar;");
496 TU.AdditionalFiles[
"all.h"] = guard(R
"cpp(
506 auto AST = TU.build();
510 UnorderedElementsAre(withFix({FixMessage(
"#include \"foo.h\""),
511 FixMessage(
"fix all includes")}),
512 withFix({FixMessage(
"remove #include directive"),
513 FixMessage(
"fix all includes")})));
525 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
526 FixMessage(
"fix all includes")}),
527 withFix({FixMessage(
"remove #include directive"),
528 FixMessage(
"remove all unused includes"),
529 FixMessage(
"fix all includes")}),
530 withFix({FixMessage(
"remove #include directive"),
531 FixMessage(
"remove all unused includes"),
532 FixMessage(
"fix all includes")})));
544 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
545 FixMessage(
"add all missing includes"),
546 FixMessage(
"fix all includes")}),
547 withFix({FixMessage(
"#include \"bar.h\""),
548 FixMessage(
"add all missing includes"),
549 FixMessage(
"fix all includes")}),
550 withFix({FixMessage(
"remove #include directive"),
551 FixMessage(
"fix all includes")})));
556TEST(IncludeCleaner, VerbatimEquivalence) {
558 #include "lib/rel/public.h"
561 TU.AdditionalFiles["repo/lib/rel/private.h"] = R
"cpp(
563 // IWYU pragma: private, include "rel/public.h"
566 TU.AdditionalFiles["repo/lib/rel/public.h"] = R
"cpp(
568 #include "rel/private.h"
571 TU.ExtraArgs.push_back("-Irepo");
572 TU.ExtraArgs.push_back(
"-Irepo/lib");
574 auto AST = TU.build();
576 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
577 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
580TEST(IncludeCleaner, ResourceDirIsIgnored) {
582 #include <amintrin.h>
583 #include <imintrin.h>
588 TU.ExtraArgs.push_back("-resource-dir");
589 TU.ExtraArgs.push_back(
testPath(
"resources"));
590 TU.AdditionalFiles[
"resources/include/amintrin.h"] = guard(
"");
591 TU.AdditionalFiles[
"resources/include/imintrin.h"] = guard(R
"cpp(
592 #include <emintrin.h>
594 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R
"cpp(
597 auto AST = TU.build();
599 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
600 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
Include Cleaner is clangd functionality for providing diagnostics for misuse of transitive headers an...
CharSourceRange Range
SourceRange for the file name.
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST)
MATCHER_P2(hasFlag, Flag, Path, "")
include_cleaner::Includes convertIncludes(const ParsedAST &AST)
Converts the clangd include representation to include-cleaner include representation.
std::string testPath(PathRef File, llvm::sys::path::Style Style)
std::vector< Diag > issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, const IncludeCleanerFindings &Findings, const ThreadsafeFS &TFS, HeaderFilter IgnoreHeaders)
TEST(BackgroundQueueTest, Priority)
bool isPreferredProvider(const Inclusion &Inc, const include_cleaner::Includes &Includes, llvm::ArrayRef< include_cleaner::Header > Providers)
Whether this #include is considered to provide a particular symbol.
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
constexpr llvm::StringLiteral Message
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
std::vector< MissingIncludeDiagInfo > MissingIncludes
static TestTU withCode(llvm::StringRef Code)