1 //===--- IncludeCleanerTests.cpp --------------------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "Annotations.h"
10 #include "Diagnostics.h"
11 #include "IncludeCleaner.h"
12 #include "ParsedAST.h"
13 #include "SourceCode.h"
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"
39 using ::testing::AllOf
;
40 using ::testing::ElementsAre
;
41 using ::testing::IsEmpty
;
42 using ::testing::Matcher
;
43 using ::testing::Pointee
;
44 using ::testing::UnorderedElementsAre
;
47 withFix(std::vector
<::testing::Matcher
<Fix
>> FixMatcheres
) {
48 return Field(&Diag::Fixes
, testing::UnorderedElementsAreArray(FixMatcheres
));
51 MATCHER_P2(Diag
, Range
, Message
,
52 "Diag at " + llvm::to_string(Range
) + " = [" + Message
+ "]") {
53 return arg
.Range
== Range
&& arg
.Message
== Message
;
56 MATCHER_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
;
62 MATCHER_P(FixMessage
, Message
, "") { return arg
.Message
== Message
; }
64 std::string
guard(llvm::StringRef Code
) {
65 return "#pragma once\n" + Code
.str();
68 MATCHER_P(writtenInclusion
, Written
, "") {
69 if (arg
.Written
!= Written
)
70 *result_listener
<< arg
.Written
;
71 return arg
.Written
== Written
;
74 TEST(IncludeCleaner
, StdlibUnused
) {
75 auto TU
= TestTU::withCode(R
"cpp(
78 #include <vector> // IWYU pragma: keep
79 #include <string> // IWYU pragma: export
82 // Layout of std library impl is not relevant.
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();
97 IncludeCleanerFindings Findings
= computeIncludeCleanerFindings(AST
);
98 EXPECT_THAT(Findings
.UnusedIncludes
,
99 ElementsAre(Pointee(writtenInclusion("<queue>"))));
102 TEST(IncludeCleaner
, GetUnusedHeaders
) {
103 llvm::StringLiteral MainFile
= R
"cpp(
107 #include "dir
/unused
.h
"
108 #include "unguarded
.h
"
110 #include <system_header.h>
116 // Build expected ast with symbols coming from headers.
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"));
129 TU
.Code
= MainFile
.str();
130 ParsedAST AST
= TU
.build();
131 IncludeCleanerFindings Findings
= computeIncludeCleanerFindings(AST
);
133 Findings
.UnusedIncludes
,
134 UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
135 Pointee(writtenInclusion("\"dir/unused.h\""))));
138 TEST(IncludeCleaner
, ComputeMissingHeaders
) {
139 Annotations
MainFile(R
"cpp(
146 TU
.Filename
= "foo.cpp";
147 TU
.AdditionalFiles
["a.h"] = guard("#include \"b.h\"");
148 TU
.AdditionalFiles
["b.h"] = guard("void b();");
150 TU
.Code
= MainFile
.code();
151 ParsedAST AST
= TU
.build();
153 IncludeCleanerFindings Findings
= computeIncludeCleanerFindings(AST
);
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
};
165 auto Range
= MainFile
.range("b");
166 size_t Start
= llvm::cantFail(positionToOffset(MainFile
.code(), Range
.start
));
167 size_t End
= llvm::cantFail(positionToOffset(MainFile
.code(), Range
.end
));
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
));
176 TEST(IncludeCleaner
, GenerateMissingHeaderDiags
) {
177 Annotations
MainFile(R
"cpp(
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
249 TU
.Code
= MainFile
.code();
250 ParsedAST AST
= TU
.build();
252 auto Findings
= computeIncludeCleanerFindings(AST
);
253 Findings
.UnusedIncludes
.clear();
254 std::vector
<clangd::Diag
> Diags
= issueIncludeCleanerDiagnostics(
255 AST
, TU
.Code
, Findings
,
256 {[](llvm::StringRef Header
) { return Header
.ends_with("buzz.h"); }});
259 UnorderedElementsAre(
260 AllOf(Diag(MainFile
.range("b"),
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")})),
265 AllOf(Diag(MainFile
.range("bar"),
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")})),
270 AllOf(Diag(MainFile
.range("f"),
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")})),
276 Diag(MainFile
.range("foobar"),
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")})),
282 Diag(MainFile
.range("vector"),
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"),
289 AllOf(Diag(MainFile
.range("FOO"),
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")})),
294 AllOf(Diag(MainFile
.range("DEF"),
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")})),
299 AllOf(Diag(MainFile
.range("BAR"),
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")})),
304 AllOf(Diag(MainFile
.range("Foo"),
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")}))));
311 TEST(IncludeCleaner
, IWYUPragmas
) {
314 #include "behind_keep
.h
" // IWYU pragma: keep
315 #include "exported
.h
" // IWYU pragma: export
318 void bar() { foo(); }
320 TU
.AdditionalFiles
["behind_keep.h"] = guard("");
321 TU
.AdditionalFiles
["exported.h"] = guard("");
322 TU
.AdditionalFiles
["public.h"] = guard("#include \"private.h\"");
323 TU
.AdditionalFiles
["private.h"] = guard(R
"cpp(
324 // IWYU pragma: private, include "public.h
"
327 ParsedAST AST
= TU
.build();
328 IncludeCleanerFindings Findings
= computeIncludeCleanerFindings(AST
);
329 EXPECT_THAT(Findings
.UnusedIncludes
, IsEmpty());
332 TEST(IncludeCleaner
, IWYUPragmaExport
) {
337 TU
.AdditionalFiles
["foo.h"] = R
"cpp(
341 #include "bar
.h
" // IWYU pragma: export
345 TU
.AdditionalFiles
["bar.h"] = guard(R
"cpp(
348 ParsedAST AST
= TU
.build();
350 IncludeCleanerFindings Findings
= computeIncludeCleanerFindings(AST
);
351 EXPECT_THAT(Findings
.UnusedIncludes
,
352 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
355 TEST(IncludeCleaner
, NoDiagsForObjC
) {
362 TU
.AdditionalFiles
["foo.h"] = R
"cpp(
368 TU
.ExtraArgs
.emplace_back("-xobjective-c");
370 ParsedAST AST
= TU
.build();
371 IncludeCleanerFindings Findings
= computeIncludeCleanerFindings(AST
);
372 EXPECT_THAT(Findings
.MissingIncludes
, IsEmpty());
373 EXPECT_THAT(Findings
.UnusedIncludes
, IsEmpty());
376 TEST(IncludeCleaner
, UmbrellaUsesPrivate
) {
381 TU
.AdditionalFiles
["private.h"] = guard(R
"cpp(
382 // IWYU pragma: private, include "public.h
"
385 TU
.Filename
= "public.h";
386 ParsedAST AST
= TU
.build();
387 IncludeCleanerFindings Findings
= computeIncludeCleanerFindings(AST
);
388 EXPECT_THAT(Findings
.UnusedIncludes
, IsEmpty());
391 TEST(IncludeCleaner
, MacroExpandedThroughIncludes
) {
392 Annotations
MainFile(R
"cpp(
394 #define FOO(X) const Foo *X
396 #include [["expander
.inc
"]]
401 TU
.AdditionalFiles
["expander.inc"] = guard("FOO(f1);FOO(f2);");
402 TU
.AdditionalFiles
["foo.h"] = guard("struct Foo {};");
403 TU
.AdditionalFiles
["all.h"] = guard("#include \"foo.h\"");
405 TU
.Code
= MainFile
.code();
406 ParsedAST AST
= TU
.build();
408 auto Findings
= computeIncludeCleanerFindings(AST
).MissingIncludes
;
409 EXPECT_THAT(Findings
, testing::SizeIs(1));
410 auto RefRange
= Findings
.front().SymRefRange
;
411 auto &SM
= AST
.getSourceManager();
412 EXPECT_EQ(RefRange
.file(), SM
.getMainFileID());
413 // FIXME: Point at the spelling location, rather than the include.
414 EXPECT_EQ(halfOpenToRange(SM
, RefRange
.toCharRange(SM
)), MainFile
.range());
417 TEST(IncludeCleaner
, MissingIncludesAreUnique
) {
418 Annotations
MainFile(R
"cpp(
424 TU
.AdditionalFiles
["foo.h"] = guard("struct Foo {};");
425 TU
.AdditionalFiles
["all.h"] = guard(R
"cpp(
427 #define FOO(X) X y; X z
430 TU
.Code
= MainFile
.code();
431 ParsedAST AST
= TU
.build();
433 auto Findings
= computeIncludeCleanerFindings(AST
).MissingIncludes
;
434 EXPECT_THAT(Findings
, testing::SizeIs(1));
435 auto RefRange
= Findings
.front().SymRefRange
;
436 auto &SM
= AST
.getSourceManager();
437 EXPECT_EQ(RefRange
.file(), SM
.getMainFileID());
438 EXPECT_EQ(halfOpenToRange(SM
, RefRange
.toCharRange(SM
)), MainFile
.range());
441 TEST(IncludeCleaner
, NoCrash
) {
443 Annotations
MainCode(R
"cpp(
449 TU
.Code
= MainCode
.code();
450 TU
.AdditionalFiles
["foo.h"] =
451 guard("int operator\"\"s(unsigned long long) { return 0; }");
452 TU
.AdditionalFiles
["all.h"] = guard("#include \"foo.h\"");
453 ParsedAST AST
= TU
.build();
454 const auto &MissingIncludes
=
455 computeIncludeCleanerFindings(AST
).MissingIncludes
;
456 EXPECT_THAT(MissingIncludes
, testing::SizeIs(1));
457 auto &SM
= AST
.getSourceManager();
459 halfOpenToRange(SM
, MissingIncludes
.front().SymRefRange
.toCharRange(SM
)),
463 TEST(IncludeCleaner
, IsPreferredProvider
) {
464 auto TU
= TestTU::withCode(R
"cpp(
469 TU
.AdditionalFiles
["decl.h"] = "";
470 TU
.AdditionalFiles
["def.h"] = "";
472 auto AST
= TU
.build();
473 auto &IncludeDecl
= AST
.getIncludeStructure().MainFileIncludes
[0];
474 auto &IncludeDef1
= AST
.getIncludeStructure().MainFileIncludes
[1];
475 auto &IncludeDef2
= AST
.getIncludeStructure().MainFileIncludes
[2];
477 auto &FM
= AST
.getSourceManager().getFileManager();
478 auto DeclH
= *FM
.getOptionalFileRef("decl.h");
479 auto DefH
= *FM
.getOptionalFileRef("def.h");
481 auto Includes
= convertIncludes(AST
);
482 std::vector
<include_cleaner::Header
> Providers
= {
483 include_cleaner::Header(DefH
), include_cleaner::Header(DeclH
)};
484 EXPECT_FALSE(isPreferredProvider(IncludeDecl
, Includes
, Providers
));
485 EXPECT_TRUE(isPreferredProvider(IncludeDef1
, Includes
, Providers
));
486 EXPECT_TRUE(isPreferredProvider(IncludeDef2
, Includes
, Providers
));
489 TEST(IncludeCleaner
, BatchFix
) {
491 TU
.Filename
= "main.cpp";
492 TU
.AdditionalFiles
["foo.h"] = guard("class Foo;");
493 TU
.AdditionalFiles
["bar.h"] = guard("class Bar;");
494 TU
.AdditionalFiles
["all.h"] = guard(R
"cpp(
504 auto AST
= TU
.build();
506 issueIncludeCleanerDiagnostics(AST
, TU
.Code
,
507 computeIncludeCleanerFindings(AST
)),
508 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
509 FixMessage("fix all includes")}),
510 withFix({FixMessage("remove #include directive"),
511 FixMessage("fix all includes")})));
521 issueIncludeCleanerDiagnostics(AST
, TU
.Code
,
522 computeIncludeCleanerFindings(AST
)),
523 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
524 FixMessage("fix all includes")}),
525 withFix({FixMessage("remove #include directive"),
526 FixMessage("remove all unused includes"),
527 FixMessage("fix all includes")}),
528 withFix({FixMessage("remove #include directive"),
529 FixMessage("remove all unused includes"),
530 FixMessage("fix all includes")})));
540 issueIncludeCleanerDiagnostics(AST
, TU
.Code
,
541 computeIncludeCleanerFindings(AST
)),
542 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
543 FixMessage("add all missing includes"),
544 FixMessage("fix all includes")}),
545 withFix({FixMessage("#include \"bar.h\""),
546 FixMessage("add all missing includes"),
547 FixMessage("fix all includes")}),
548 withFix({FixMessage("remove #include directive"),
549 FixMessage("fix all includes")})));
552 // In the presence of IWYU pragma private, we should accept spellings other
553 // than the recommended one if they appear to name the same public header.
554 TEST(IncludeCleaner
, VerbatimEquivalence
) {
555 auto TU
= TestTU::withCode(R
"cpp(
556 #include "lib
/rel
/public.h
"
559 TU
.AdditionalFiles
["repo/lib/rel/private.h"] = R
"cpp(
561 // IWYU pragma: private, include "rel
/public.h
"
564 TU
.AdditionalFiles
["repo/lib/rel/public.h"] = R
"cpp(
566 #include "rel
/private.h
"
569 TU
.ExtraArgs
.push_back("-Irepo");
570 TU
.ExtraArgs
.push_back("-Irepo/lib");
572 auto AST
= TU
.build();
573 auto Findings
= computeIncludeCleanerFindings(AST
);
574 EXPECT_THAT(Findings
.MissingIncludes
, IsEmpty());
575 EXPECT_THAT(Findings
.UnusedIncludes
, IsEmpty());
578 TEST(IncludeCleaner
, ResourceDirIsIgnored
) {
579 auto TU
= TestTU::withCode(R
"cpp(
580 #include <amintrin.h>
581 #include <imintrin.h>
586 TU
.ExtraArgs
.push_back("-resource-dir");
587 TU
.ExtraArgs
.push_back(testPath("resources"));
588 TU
.AdditionalFiles
["resources/include/amintrin.h"] = guard("");
589 TU
.AdditionalFiles
["resources/include/imintrin.h"] = guard(R
"cpp(
590 #include <emintrin.h>
592 TU
.AdditionalFiles
["resources/include/emintrin.h"] = guard(R
"cpp(
595 auto AST
= TU
.build();
596 auto Findings
= computeIncludeCleanerFindings(AST
);
597 EXPECT_THAT(Findings
.UnusedIncludes
, IsEmpty());
598 EXPECT_THAT(Findings
.MissingIncludes
, IsEmpty());
602 } // namespace clangd