1//===--- IncludeCleanerTests.cpp --------------------------------*- C++ -*-===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
9#include "Annotations.h"
10#include "Diagnostics.h"
11#include "IncludeCleaner.h"
12#include "ParsedAST.h"
13#include "SourceCode.h"
14#include "TestFS.h"
15#include "TestTU.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"
30#include <cstddef>
31#include <optional>
32#include <string>
33#include <vector>
34
35namespace clang {
36namespace clangd {
37namespace {
38
39using ::testing::AllOf;
40using ::testing::ElementsAre;
41using ::testing::IsEmpty;
42using ::testing::Matcher;
43using ::testing::Pointee;
44using ::testing::UnorderedElementsAre;
45
46Matcher<const Diag &>
47withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
48 return Field(field: &Diag::Fixes, matcher: testing::UnorderedElementsAreArray(container: FixMatcheres));
49}
50
51MATCHER_P2(Diag, Range, Message,
52 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
53 return arg.Range == Range && arg.Message == Message;
54}
55
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;
61}
62MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
63
64std::string guard(llvm::StringRef Code) {
65 return "#pragma once\n" + Code.str();
66}
67
68MATCHER_P(writtenInclusion, Written, "") {
69 if (arg.Written != Written)
70 *result_listener << arg.Written;
71 return arg.Written == Written;
72}
73
74TEST(IncludeCleaner, StdlibUnused) {
75 auto TU = TestTU::withCode(Code: R"cpp(
76 #include <list>
77 #include <queue>
78 #include <vector> // IWYU pragma: keep
79 #include <string> // IWYU pragma: export
80 std::list<int> x;
81 )cpp");
82 // Layout of std library impl is not relevant.
83 TU.AdditionalFiles["bits"] = R"cpp(
84 #pragma once
85 namespace std {
86 template <typename> class list {};
87 template <typename> class queue {};
88 template <typename> class vector {};
89 }
90 )cpp";
91 TU.AdditionalFiles["list"] = guard(Code: "#include <bits>");
92 TU.AdditionalFiles["queue"] = guard(Code: "#include <bits>");
93 TU.AdditionalFiles["vector"] = guard(Code: "#include <bits>");
94 TU.AdditionalFiles["string"] = guard(Code: "#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>"))));
100}
101
102TEST(IncludeCleaner, GetUnusedHeaders) {
103 llvm::StringLiteral MainFile = R"cpp(
104 #include "a.h"
105 #include "b.h"
106 #include "dir/c.h"
107 #include "dir/unused.h"
108 #include "unguarded.h"
109 #include "unused.h"
110 #include <system_header.h>
111 #include <non_system_angled_header.h>
112 void foo() {
113 a();
114 b();
115 c();
116 })cpp";
117 // Build expected ast with symbols coming from headers.
118 TestTU TU;
119 TU.Filename = "foo.cpp";
120 TU.AdditionalFiles["foo.h"] = guard(Code: "void foo();");
121 TU.AdditionalFiles["a.h"] = guard(Code: "void a();");
122 TU.AdditionalFiles["b.h"] = guard(Code: "void b();");
123 TU.AdditionalFiles["dir/c.h"] = guard(Code: "void c();");
124 TU.AdditionalFiles["unused.h"] = guard(Code: "void unused();");
125 TU.AdditionalFiles["dir/unused.h"] = guard(Code: "void dirUnused();");
126 TU.AdditionalFiles["dir/non_system_angled_header.h"] = guard(Code: "");
127 TU.AdditionalFiles["system/system_header.h"] = guard(Code: "");
128 TU.AdditionalFiles["unguarded.h"] = "";
129 TU.ExtraArgs.push_back(x: "-I" + testPath(File: "dir"));
130 TU.ExtraArgs.push_back(x: "-isystem" + testPath(File: "system"));
131 TU.Code = MainFile.str();
132 ParsedAST AST = TU.build();
133 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
134 EXPECT_THAT(
135 Findings.UnusedIncludes,
136 UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
137 Pointee(writtenInclusion("\"dir/unused.h\""))));
138}
139
140TEST(IncludeCleaner, IgnoredAngledHeaders) {
141 // Currently the default behavior is to ignore unused angled includes
142 auto TU = TestTU::withCode(Code: R"cpp(
143 #include <system_header.h>
144 #include <system_unused.h>
145 #include <non_system_angled_unused.h>
146 SystemClass x;
147 )cpp");
148 TU.AdditionalFiles["system/system_header.h"] = guard(Code: "class SystemClass {};");
149 TU.AdditionalFiles["system/system_unused.h"] = guard(Code: "");
150 TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard(Code: "");
151 TU.ExtraArgs = {
152 "-isystem" + testPath(File: "system"),
153 "-I" + testPath(File: "dir"),
154 };
155 auto AST = TU.build();
156 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
157 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
158}
159
160TEST(IncludeCleaner, UnusedAngledHeaders) {
161 auto TU = TestTU::withCode(Code: R"cpp(
162 #include <system_header.h>
163 #include <system_unused.h>
164 #include <non_system_angled_unused.h>
165 SystemClass x;
166 )cpp");
167 TU.AdditionalFiles["system/system_header.h"] = guard(Code: "class SystemClass {};");
168 TU.AdditionalFiles["system/system_unused.h"] = guard(Code: "");
169 TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard(Code: "");
170 TU.ExtraArgs = {
171 "-isystem" + testPath(File: "system"),
172 "-I" + testPath(File: "dir"),
173 };
174 auto AST = TU.build();
175 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST, AnalyzeAngledIncludes: true);
176 EXPECT_THAT(Findings.UnusedIncludes,
177 UnorderedElementsAre(
178 Pointee(writtenInclusion("<system_unused.h>")),
179 Pointee(writtenInclusion("<non_system_angled_unused.h>"))));
180}
181
182TEST(IncludeCleaner, ComputeMissingHeaders) {
183 Annotations MainFile(R"cpp(
184 #include "a.h"
185
186 void foo() {
187 $b[[b]]();
188 })cpp");
189 TestTU TU;
190 TU.Filename = "foo.cpp";
191 TU.AdditionalFiles["a.h"] = guard(Code: "#include \"b.h\"");
192 TU.AdditionalFiles["b.h"] = guard(Code: "void b();");
193
194 TU.Code = MainFile.code();
195 ParsedAST AST = TU.build();
196
197 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
198 const SourceManager &SM = AST.getSourceManager();
199 const NamedDecl *BDecl = nullptr;
200 for (Decl *D : AST.getASTContext().getTranslationUnitDecl()->decls()) {
201 const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D);
202 std::string Name = CandidateDecl->getQualifiedNameAsString();
203 if (Name != "b")
204 continue;
205 BDecl = CandidateDecl;
206 }
207 ASSERT_TRUE(BDecl);
208 include_cleaner::Symbol B{*BDecl};
209 auto Range = MainFile.range(Name: "b");
210 size_t Start = llvm::cantFail(ValOrErr: positionToOffset(Code: MainFile.code(), P: Range.start));
211 size_t End = llvm::cantFail(ValOrErr: positionToOffset(Code: MainFile.code(), P: Range.end));
212 syntax::FileRange BRange{SM.getMainFileID(), static_cast<unsigned int>(Start),
213 static_cast<unsigned int>(End)};
214 include_cleaner::Header Header{
215 *SM.getFileManager().getOptionalFileRef(Filename: "b.h")};
216 MissingIncludeDiagInfo BInfo{.Symbol: B, .SymRefRange: BRange, .Providers: {Header}};
217 EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
218}
219
220TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
221 Annotations MainFile(R"cpp(
222#include "a.h"
223#include "angled_wrapper.h"
224#include "all.h"
225$insert_b[[]]#include "baz.h"
226#include "dir/c.h"
227$insert_d[[]]$insert_foo[[]]#include "fuzz.h"
228#include "header.h"
229$insert_foobar[[]]$insert_quoted[[]]$insert_quoted2[[]]#include "quoted_wrapper.h"
230$insert_angled[[]]#include <e.h>
231$insert_f[[]]#include <quoted2_wrapper.h>
232$insert_vector[[]]
233
234#define DEF(X) const Foo *X;
235#define BAZ(X) const X x
236
237// No missing include insertion for ambiguous macro refs.
238#if defined(FOO)
239#endif
240
241 void foo() {
242 $b[[b]]();
243 $angled[[angled]]();
244 $quoted[[quoted]]();
245 $quoted2[[quoted2]]();
246
247 ns::$bar[[Bar]] bar;
248 bar.d();
249 $f[[f]]();
250
251 // this should not be diagnosed, because it's ignored in the config
252 buzz();
253
254 $foobar[[foobar]]();
255
256 std::$vector[[vector]] v;
257
258 int var = $FOO[[FOO]];
259
260 $DEF[[DEF]](a);
261
262 $BAR[[BAR]](b);
263
264 BAZ($Foo[[Foo]]);
265})cpp");
266
267 TestTU TU;
268 TU.Filename = "main.cpp";
269 TU.AdditionalFiles["a.h"] = guard(Code: "#include \"b.h\"");
270 TU.AdditionalFiles["b.h"] = guard(Code: "void b();");
271
272 TU.AdditionalFiles["angled_wrapper.h"] = guard(Code: "#include <angled.h>");
273 TU.AdditionalFiles["angled.h"] = guard(Code: "void angled();");
274 TU.ExtraArgs.push_back(x: "-I" + testPath(File: "."));
275
276 TU.AdditionalFiles["quoted_wrapper.h"] = guard(Code: "#include \"quoted.h\"");
277 TU.AdditionalFiles["quoted.h"] = guard(Code: "void quoted();");
278
279 TU.AdditionalFiles["dir/c.h"] = guard(Code: "#include \"d.h\"");
280 TU.AdditionalFiles["dir/d.h"] =
281 guard(Code: "namespace ns { struct Bar { void d(); }; }");
282
283 TU.AdditionalFiles["system/e.h"] = guard(Code: "#include <f.h>");
284 TU.AdditionalFiles["system/f.h"] = guard(Code: "void f();");
285 TU.AdditionalFiles["system/quoted2_wrapper.h"] =
286 guard(Code: "#include <system/quoted2.h>");
287 TU.AdditionalFiles["system/quoted2.h"] = guard(Code: "void quoted2();");
288 TU.ExtraArgs.push_back(x: "-isystem" + testPath(File: "system"));
289
290 TU.AdditionalFiles["fuzz.h"] = guard(Code: "#include \"buzz.h\"");
291 TU.AdditionalFiles["buzz.h"] = guard(Code: "void buzz();");
292
293 TU.AdditionalFiles["baz.h"] = guard(Code: "#include \"private.h\"");
294 TU.AdditionalFiles["private.h"] = guard(Code: R"cpp(
295 // IWYU pragma: private, include "public.h"
296 void foobar();
297 )cpp");
298 TU.AdditionalFiles["header.h"] = guard(Code: R"cpp(
299 namespace std { class vector {}; }
300 )cpp");
301
302 TU.AdditionalFiles["all.h"] = guard(Code: "#include \"foo.h\"");
303 TU.AdditionalFiles["foo.h"] = guard(Code: R"cpp(
304 #define BAR(x) Foo *x
305 #define FOO 1
306 struct Foo{};
307 )cpp");
308
309 TU.Code = MainFile.code();
310 ParsedAST AST = TU.build();
311
312 auto Findings = computeIncludeCleanerFindings(AST);
313 Findings.UnusedIncludes.clear();
314 std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics(
315 AST, Code: TU.Code, Findings, TFS: MockFS(),
316 /*IgnoreHeaders=*/IgnoreHeader: {[](llvm::StringRef Header) {
317 return Header.ends_with(Suffix: "buzz.h");
318 }},
319 /*AngledHeaders=*/{[](llvm::StringRef Header) {
320 return Header.contains(Other: "angled.h");
321 }},
322 /*QuotedHeaders=*/{[](llvm::StringRef Header) {
323 return Header.contains(Other: "quoted.h") || Header.contains(Other: "quoted2.h");
324 }});
325 EXPECT_THAT(
326 Diags,
327 UnorderedElementsAre(
328 AllOf(Diag(MainFile.range("b"),
329 "No header providing \"b\" is directly included"),
330 withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
331 "#include \"b.h\""),
332 FixMessage("add all missing includes")})),
333 AllOf(Diag(MainFile.range("angled"),
334 "No header providing \"angled\" is directly included"),
335 withFix({Fix(MainFile.range("insert_angled"),
336 "#include <angled.h>\n", "#include <angled.h>"),
337 FixMessage("add all missing includes")})),
338 AllOf(
339 Diag(MainFile.range("quoted"),
340 "No header providing \"quoted\" is directly included"),
341 withFix({Fix(MainFile.range("insert_quoted"),
342 "#include \"quoted.h\"\n", "#include \"quoted.h\""),
343 FixMessage("add all missing includes")})),
344 AllOf(Diag(MainFile.range("quoted2"),
345 "No header providing \"quoted2\" is directly included"),
346 withFix(
347 {Fix(MainFile.range("insert_quoted2"),
348 "#include \"quoted2.h\"\n", "#include \"quoted2.h\""),
349 FixMessage("add all missing includes")})),
350 AllOf(Diag(MainFile.range("bar"),
351 "No header providing \"ns::Bar\" is directly included"),
352 withFix({Fix(MainFile.range("insert_d"),
353 "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
354 FixMessage("add all missing includes")})),
355 AllOf(Diag(MainFile.range("f"),
356 "No header providing \"f\" is directly included"),
357 withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
358 "#include <f.h>"),
359 FixMessage("add all missing includes")})),
360 AllOf(
361 Diag(MainFile.range("foobar"),
362 "No header providing \"foobar\" is directly included"),
363 withFix({Fix(MainFile.range("insert_foobar"),
364 "#include \"public.h\"\n", "#include \"public.h\""),
365 FixMessage("add all missing includes")})),
366 AllOf(
367 Diag(MainFile.range("vector"),
368 "No header providing \"std::vector\" is directly included"),
369 withFix({
370 Fix(MainFile.range("insert_vector"), "#include <vector>\n",
371 "#include <vector>"),
372 FixMessage("add all missing includes"),
373 })),
374 AllOf(Diag(MainFile.range("FOO"),
375 "No header providing \"FOO\" is directly included"),
376 withFix({Fix(MainFile.range("insert_foo"),
377 "#include \"foo.h\"\n", "#include \"foo.h\""),
378 FixMessage("add all missing includes")})),
379 AllOf(Diag(MainFile.range("DEF"),
380 "No header providing \"Foo\" is directly included"),
381 withFix({Fix(MainFile.range("insert_foo"),
382 "#include \"foo.h\"\n", "#include \"foo.h\""),
383 FixMessage("add all missing includes")})),
384 AllOf(Diag(MainFile.range("BAR"),
385 "No header providing \"BAR\" is directly included"),
386 withFix({Fix(MainFile.range("insert_foo"),
387 "#include \"foo.h\"\n", "#include \"foo.h\""),
388 FixMessage("add all missing includes")})),
389 AllOf(Diag(MainFile.range("Foo"),
390 "No header providing \"Foo\" is directly included"),
391 withFix({Fix(MainFile.range("insert_foo"),
392 "#include \"foo.h\"\n", "#include \"foo.h\""),
393 FixMessage("add all missing includes")}))));
394}
395
396TEST(IncludeCleaner, IWYUPragmas) {
397 TestTU TU;
398 TU.Code = R"cpp(
399 #include "behind_keep.h" // IWYU pragma: keep
400 #include "exported.h" // IWYU pragma: export
401 #include "public.h"
402
403 void bar() { foo(); }
404 #include "keep_main_file.h" // IWYU pragma: keep
405 )cpp";
406 TU.AdditionalFiles["behind_keep.h"] = guard(Code: "");
407 TU.AdditionalFiles["keep_main_file.h"] = guard(Code: "");
408 TU.AdditionalFiles["exported.h"] = guard(Code: "");
409 TU.AdditionalFiles["public.h"] = guard(Code: "#include \"private.h\"");
410 TU.AdditionalFiles["private.h"] = guard(Code: R"cpp(
411 // IWYU pragma: private, include "public.h"
412 void foo() {}
413 )cpp");
414 ParsedAST AST = TU.build();
415 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
416 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
417}
418
419TEST(IncludeCleaner, IWYUPragmaExport) {
420 TestTU TU;
421 TU.Code = R"cpp(
422 #include "foo.h"
423 )cpp";
424 TU.AdditionalFiles["foo.h"] = R"cpp(
425 #ifndef FOO_H
426 #define FOO_H
427
428 #include "bar.h" // IWYU pragma: export
429
430 #endif
431 )cpp";
432 TU.AdditionalFiles["bar.h"] = guard(Code: R"cpp(
433 void bar() {}
434 )cpp");
435 ParsedAST AST = TU.build();
436
437 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
438 EXPECT_THAT(Findings.UnusedIncludes,
439 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
440}
441
442TEST(IncludeCleaner, NoDiagsForObjC) {
443 TestTU TU;
444 TU.Code = R"cpp(
445 #include "foo.h"
446
447 void bar() {}
448 )cpp";
449 TU.AdditionalFiles["foo.h"] = R"cpp(
450 #ifndef FOO_H
451 #define FOO_H
452
453 #endif
454 )cpp";
455 TU.ExtraArgs.emplace_back(args: "-xobjective-c");
456
457 ParsedAST AST = TU.build();
458 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
459 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
460 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
461}
462
463TEST(IncludeCleaner, UmbrellaUsesPrivate) {
464 TestTU TU;
465 TU.Code = R"cpp(
466 #include "private.h"
467 )cpp";
468 TU.AdditionalFiles["private.h"] = guard(Code: R"cpp(
469 // IWYU pragma: private, include "public.h"
470 void foo() {}
471 )cpp");
472 TU.Filename = "public.h";
473 ParsedAST AST = TU.build();
474 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
475 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
476}
477
478TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
479 Annotations MainFile(R"cpp(
480 #include "all.h"
481 #define FOO(X) const Foo *X
482 void foo() {
483 #include [["expander.inc"]]
484 }
485)cpp");
486
487 TestTU TU;
488 TU.AdditionalFiles["expander.inc"] = guard(Code: "FOO(f1);FOO(f2);");
489 TU.AdditionalFiles["foo.h"] = guard(Code: "struct Foo {};");
490 TU.AdditionalFiles["all.h"] = guard(Code: "#include \"foo.h\"");
491
492 TU.Code = MainFile.code();
493 ParsedAST AST = TU.build();
494
495 auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
496 EXPECT_THAT(Findings, testing::SizeIs(1));
497 auto RefRange = Findings.front().SymRefRange;
498 auto &SM = AST.getSourceManager();
499 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
500 // FIXME: Point at the spelling location, rather than the include.
501 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
502}
503
504TEST(IncludeCleaner, MissingIncludesAreUnique) {
505 Annotations MainFile(R"cpp(
506 #include "all.h"
507 FOO([[Foo]]);
508 )cpp");
509
510 TestTU TU;
511 TU.AdditionalFiles["foo.h"] = guard(Code: "struct Foo {};");
512 TU.AdditionalFiles["all.h"] = guard(Code: R"cpp(
513 #include "foo.h"
514 #define FOO(X) X y; X z
515 )cpp");
516
517 TU.Code = MainFile.code();
518 ParsedAST AST = TU.build();
519
520 auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
521 EXPECT_THAT(Findings, testing::SizeIs(1));
522 auto RefRange = Findings.front().SymRefRange;
523 auto &SM = AST.getSourceManager();
524 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
525 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
526}
527
528TEST(IncludeCleaner, NoCrash) {
529 TestTU TU;
530 Annotations MainCode(R"cpp(
531 #include "all.h"
532 void test() {
533 [[1s]];
534 }
535 )cpp");
536 TU.Code = MainCode.code();
537 TU.AdditionalFiles["foo.h"] =
538 guard(Code: "int operator\"\"s(unsigned long long) { return 0; }");
539 TU.AdditionalFiles["all.h"] = guard(Code: "#include \"foo.h\"");
540 ParsedAST AST = TU.build();
541 const auto &MissingIncludes =
542 computeIncludeCleanerFindings(AST).MissingIncludes;
543 EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
544 auto &SM = AST.getSourceManager();
545 EXPECT_EQ(
546 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
547 MainCode.range());
548}
549
550TEST(IncludeCleaner, IsPreferredProvider) {
551 auto TU = TestTU::withCode(Code: R"cpp(
552 #include "decl.h"
553 #include "def.h"
554 #include "def.h"
555 )cpp");
556 TU.AdditionalFiles["decl.h"] = "";
557 TU.AdditionalFiles["def.h"] = "";
558
559 auto AST = TU.build();
560 auto &IncludeDecl = AST.getIncludeStructure().MainFileIncludes[0];
561 auto &IncludeDef1 = AST.getIncludeStructure().MainFileIncludes[1];
562 auto &IncludeDef2 = AST.getIncludeStructure().MainFileIncludes[2];
563
564 auto &FM = AST.getSourceManager().getFileManager();
565 auto DeclH = *FM.getOptionalFileRef(Filename: "decl.h");
566 auto DefH = *FM.getOptionalFileRef(Filename: "def.h");
567
568 auto Includes = convertIncludes(AST);
569 std::vector<include_cleaner::Header> Providers = {
570 include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
571 EXPECT_FALSE(isPreferredProvider(IncludeDecl, Includes, Providers));
572 EXPECT_TRUE(isPreferredProvider(IncludeDef1, Includes, Providers));
573 EXPECT_TRUE(isPreferredProvider(IncludeDef2, Includes, Providers));
574}
575
576TEST(IncludeCleaner, BatchFix) {
577 TestTU TU;
578 TU.Filename = "main.cpp";
579 TU.AdditionalFiles["foo.h"] = guard(Code: "class Foo;");
580 TU.AdditionalFiles["bar.h"] = guard(Code: "class Bar;");
581 TU.AdditionalFiles["all.h"] = guard(Code: R"cpp(
582 #include "foo.h"
583 #include "bar.h"
584 )cpp");
585
586 TU.Code = R"cpp(
587 #include "all.h"
588
589 Foo* foo;
590 )cpp";
591 auto AST = TU.build();
592 EXPECT_THAT(
593 issueIncludeCleanerDiagnostics(
594 AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
595 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
596 FixMessage("fix all includes")}),
597 withFix({FixMessage("remove #include directive"),
598 FixMessage("fix all includes")})));
599
600 TU.Code = R"cpp(
601 #include "all.h"
602 #include "bar.h"
603
604 Foo* foo;
605 )cpp";
606 AST = TU.build();
607 EXPECT_THAT(
608 issueIncludeCleanerDiagnostics(
609 AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
610 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
611 FixMessage("fix all includes")}),
612 withFix({FixMessage("remove #include directive"),
613 FixMessage("remove all unused includes"),
614 FixMessage("fix all includes")}),
615 withFix({FixMessage("remove #include directive"),
616 FixMessage("remove all unused includes"),
617 FixMessage("fix all includes")})));
618
619 TU.Code = R"cpp(
620 #include "all.h"
621
622 Foo* foo;
623 Bar* bar;
624 )cpp";
625 AST = TU.build();
626 EXPECT_THAT(
627 issueIncludeCleanerDiagnostics(
628 AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
629 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
630 FixMessage("add all missing includes"),
631 FixMessage("fix all includes")}),
632 withFix({FixMessage("#include \"bar.h\""),
633 FixMessage("add all missing includes"),
634 FixMessage("fix all includes")}),
635 withFix({FixMessage("remove #include directive"),
636 FixMessage("fix all includes")})));
637}
638
639// In the presence of IWYU pragma private, we should accept spellings other
640// than the recommended one if they appear to name the same public header.
641TEST(IncludeCleaner, VerbatimEquivalence) {
642 auto TU = TestTU::withCode(Code: R"cpp(
643 #include "lib/rel/public.h"
644 int x = Public;
645 )cpp");
646 TU.AdditionalFiles["repo/lib/rel/private.h"] = R"cpp(
647 #pragma once
648 // IWYU pragma: private, include "rel/public.h"
649 int Public;
650 )cpp";
651 TU.AdditionalFiles["repo/lib/rel/public.h"] = R"cpp(
652 #pragma once
653 #include "rel/private.h"
654 )cpp";
655
656 TU.ExtraArgs.push_back(x: "-Irepo");
657 TU.ExtraArgs.push_back(x: "-Irepo/lib");
658
659 auto AST = TU.build();
660 auto Findings = computeIncludeCleanerFindings(AST);
661 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
662 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
663}
664
665TEST(IncludeCleaner, ResourceDirIsIgnored) {
666 auto TU = TestTU::withCode(Code: R"cpp(
667 #include <amintrin.h>
668 #include <imintrin.h>
669 void baz() {
670 bar();
671 }
672 )cpp");
673 TU.ExtraArgs.push_back(x: "-resource-dir");
674 TU.ExtraArgs.push_back(x: testPath(File: "resources"));
675 TU.AdditionalFiles["resources/include/amintrin.h"] = guard(Code: "");
676 TU.AdditionalFiles["resources/include/imintrin.h"] = guard(Code: R"cpp(
677 #include <emintrin.h>
678 )cpp");
679 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(Code: R"cpp(
680 void bar();
681 )cpp");
682 auto AST = TU.build();
683 auto Findings = computeIncludeCleanerFindings(AST);
684 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
685 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
686}
687
688TEST(IncludeCleaner, DifferentHeaderSameSpelling) {
689 // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
690 // directly. Make sure we don't generate unusued/missing include findings in
691 // such cases.
692 auto TU = TestTU::withCode(Code: R"cpp(
693 #include <foo.h>
694 void baz() {
695 foo();
696 }
697 )cpp");
698 TU.AdditionalFiles["foo/foo.h"] = guard(Code: "#include_next <foo.h>");
699 TU.AdditionalFiles["foo_inner/foo.h"] = guard(Code: R"cpp(
700 void foo();
701 )cpp");
702 TU.ExtraArgs.push_back(x: "-Ifoo");
703 TU.ExtraArgs.push_back(x: "-Ifoo_inner");
704
705 auto AST = TU.build();
706 auto Findings = computeIncludeCleanerFindings(AST);
707 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
708 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
709}
710} // namespace
711} // namespace clangd
712} // namespace clang
713

source code of clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp