xref: /llvm-project/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp (revision 2f5dc596b5719e5ed7f6978dafbce994f425a033)
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 
35 namespace clang {
36 namespace clangd {
37 namespace {
38 
39 using ::testing::AllOf;
40 using ::testing::ElementsAre;
41 using ::testing::IsEmpty;
42 using ::testing::Matcher;
43 using ::testing::Pointee;
44 using ::testing::UnorderedElementsAre;
45 
46 Matcher<const Diag &>
47 withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
48   return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
49 }
50 
51 MATCHER_P2(Diag, Range, Message,
52            "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
53   return arg.Range == Range && arg.Message == Message;
54 }
55 
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;
61 }
62 MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
63 
64 std::string guard(llvm::StringRef Code) {
65   return "#pragma once\n" + Code.str();
66 }
67 
68 MATCHER_P(writtenInclusion, Written, "") {
69   if (arg.Written != Written)
70     *result_listener << arg.Written;
71   return arg.Written == Written;
72 }
73 
74 TEST(IncludeCleaner, StdlibUnused) {
75   auto TU = TestTU::withCode(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("#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>"))));
100 }
101 
102 TEST(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("void foo();");
121   TU.AdditionalFiles["a.h"] = guard("void a();");
122   TU.AdditionalFiles["b.h"] = guard("void b();");
123   TU.AdditionalFiles["dir/c.h"] = guard("void c();");
124   TU.AdditionalFiles["unused.h"] = guard("void unused();");
125   TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();");
126   TU.AdditionalFiles["dir/non_system_angled_header.h"] = guard("");
127   TU.AdditionalFiles["system/system_header.h"] = guard("");
128   TU.AdditionalFiles["unguarded.h"] = "";
129   TU.ExtraArgs.push_back("-I" + testPath("dir"));
130   TU.ExtraArgs.push_back("-isystem" + testPath("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 
140 TEST(IncludeCleaner, IgnoredAngledHeaders) {
141   // Currently the default behavior is to ignore unused angled includes
142   auto TU = TestTU::withCode(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("class SystemClass {};");
149   TU.AdditionalFiles["system/system_unused.h"] = guard("");
150   TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard("");
151   TU.ExtraArgs = {
152       "-isystem" + testPath("system"),
153       "-I" + testPath("dir"),
154   };
155   auto AST = TU.build();
156   IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
157   EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
158 }
159 
160 TEST(IncludeCleaner, UnusedAngledHeaders) {
161   auto TU = TestTU::withCode(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("class SystemClass {};");
168   TU.AdditionalFiles["system/system_unused.h"] = guard("");
169   TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard("");
170   TU.ExtraArgs = {
171       "-isystem" + testPath("system"),
172       "-I" + testPath("dir"),
173   };
174   auto AST = TU.build();
175   IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST, true);
176   EXPECT_THAT(Findings.UnusedIncludes,
177               UnorderedElementsAre(
178                   Pointee(writtenInclusion("<system_unused.h>")),
179                   Pointee(writtenInclusion("<non_system_angled_unused.h>"))));
180 }
181 
182 TEST(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("#include \"b.h\"");
192   TU.AdditionalFiles["b.h"] = guard("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("b");
210   size_t Start = llvm::cantFail(positionToOffset(MainFile.code(), Range.start));
211   size_t End = llvm::cantFail(positionToOffset(MainFile.code(), 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("b.h")};
216   MissingIncludeDiagInfo BInfo{B, BRange, {Header}};
217   EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
218 }
219 
220 TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
221   Annotations MainFile(R"cpp(
222 #include "a.h"
223 #include "all.h"
224 $insert_b[[]]#include "baz.h"
225 #include "dir/c.h"
226 $insert_d[[]]$insert_foo[[]]#include "fuzz.h"
227 #include "header.h"
228 $insert_foobar[[]]#include <e.h>
229 $insert_f[[]]$insert_vector[[]]
230 
231 #define DEF(X) const Foo *X;
232 #define BAZ(X) const X x
233 
234 // No missing include insertion for ambiguous macro refs.
235 #if defined(FOO)
236 #endif
237 
238   void foo() {
239     $b[[b]]();
240 
241     ns::$bar[[Bar]] bar;
242     bar.d();
243     $f[[f]]();
244 
245     // this should not be diagnosed, because it's ignored in the config
246     buzz();
247 
248     $foobar[[foobar]]();
249 
250     std::$vector[[vector]] v;
251 
252     int var = $FOO[[FOO]];
253 
254     $DEF[[DEF]](a);
255 
256     $BAR[[BAR]](b);
257 
258     BAZ($Foo[[Foo]]);
259 })cpp");
260 
261   TestTU TU;
262   TU.Filename = "main.cpp";
263   TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
264   TU.AdditionalFiles["b.h"] = guard("void b();");
265 
266   TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\"");
267   TU.AdditionalFiles["dir/d.h"] =
268       guard("namespace ns { struct Bar { void d(); }; }");
269 
270   TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>");
271   TU.AdditionalFiles["system/f.h"] = guard("void f();");
272   TU.ExtraArgs.push_back("-isystem" + testPath("system"));
273 
274   TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\"");
275   TU.AdditionalFiles["buzz.h"] = guard("void buzz();");
276 
277   TU.AdditionalFiles["baz.h"] = guard("#include \"private.h\"");
278   TU.AdditionalFiles["private.h"] = guard(R"cpp(
279     // IWYU pragma: private, include "public.h"
280     void foobar();
281   )cpp");
282   TU.AdditionalFiles["header.h"] = guard(R"cpp(
283   namespace std { class vector {}; }
284   )cpp");
285 
286   TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
287   TU.AdditionalFiles["foo.h"] = guard(R"cpp(
288     #define BAR(x) Foo *x
289     #define FOO 1
290     struct Foo{};
291   )cpp");
292 
293   TU.Code = MainFile.code();
294   ParsedAST AST = TU.build();
295 
296   auto Findings = computeIncludeCleanerFindings(AST);
297   Findings.UnusedIncludes.clear();
298   std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics(
299       AST, TU.Code, Findings, MockFS(),
300       {[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }});
301   EXPECT_THAT(
302       Diags,
303       UnorderedElementsAre(
304           AllOf(Diag(MainFile.range("b"),
305                      "No header providing \"b\" is directly included"),
306                 withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
307                              "#include \"b.h\""),
308                          FixMessage("add all missing includes")})),
309           AllOf(Diag(MainFile.range("bar"),
310                      "No header providing \"ns::Bar\" is directly included"),
311                 withFix({Fix(MainFile.range("insert_d"),
312                              "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
313                          FixMessage("add all missing includes")})),
314           AllOf(Diag(MainFile.range("f"),
315                      "No header providing \"f\" is directly included"),
316                 withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
317                              "#include <f.h>"),
318                          FixMessage("add all missing includes")})),
319           AllOf(
320               Diag(MainFile.range("foobar"),
321                    "No header providing \"foobar\" is directly included"),
322               withFix({Fix(MainFile.range("insert_foobar"),
323                            "#include \"public.h\"\n", "#include \"public.h\""),
324                        FixMessage("add all missing includes")})),
325           AllOf(
326               Diag(MainFile.range("vector"),
327                    "No header providing \"std::vector\" is directly included"),
328               withFix({
329                   Fix(MainFile.range("insert_vector"), "#include <vector>\n",
330                       "#include <vector>"),
331                   FixMessage("add all missing includes"),
332               })),
333           AllOf(Diag(MainFile.range("FOO"),
334                      "No header providing \"FOO\" is directly included"),
335                 withFix({Fix(MainFile.range("insert_foo"),
336                              "#include \"foo.h\"\n", "#include \"foo.h\""),
337                          FixMessage("add all missing includes")})),
338           AllOf(Diag(MainFile.range("DEF"),
339                      "No header providing \"Foo\" is directly included"),
340                 withFix({Fix(MainFile.range("insert_foo"),
341                              "#include \"foo.h\"\n", "#include \"foo.h\""),
342                          FixMessage("add all missing includes")})),
343           AllOf(Diag(MainFile.range("BAR"),
344                      "No header providing \"BAR\" is directly included"),
345                 withFix({Fix(MainFile.range("insert_foo"),
346                              "#include \"foo.h\"\n", "#include \"foo.h\""),
347                          FixMessage("add all missing includes")})),
348           AllOf(Diag(MainFile.range("Foo"),
349                      "No header providing \"Foo\" is directly included"),
350                 withFix({Fix(MainFile.range("insert_foo"),
351                              "#include \"foo.h\"\n", "#include \"foo.h\""),
352                          FixMessage("add all missing includes")}))));
353 }
354 
355 TEST(IncludeCleaner, IWYUPragmas) {
356   TestTU TU;
357   TU.Code = R"cpp(
358     #include "behind_keep.h" // IWYU pragma: keep
359     #include "exported.h" // IWYU pragma: export
360     #include "public.h"
361 
362     void bar() { foo(); }
363     #include "keep_main_file.h" // IWYU pragma: keep
364     )cpp";
365   TU.AdditionalFiles["behind_keep.h"] = guard("");
366   TU.AdditionalFiles["keep_main_file.h"] = guard("");
367   TU.AdditionalFiles["exported.h"] = guard("");
368   TU.AdditionalFiles["public.h"] = guard("#include \"private.h\"");
369   TU.AdditionalFiles["private.h"] = guard(R"cpp(
370     // IWYU pragma: private, include "public.h"
371     void foo() {}
372   )cpp");
373   ParsedAST AST = TU.build();
374   IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
375   EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
376 }
377 
378 TEST(IncludeCleaner, IWYUPragmaExport) {
379   TestTU TU;
380   TU.Code = R"cpp(
381     #include "foo.h"
382     )cpp";
383   TU.AdditionalFiles["foo.h"] = R"cpp(
384     #ifndef FOO_H
385     #define FOO_H
386 
387     #include "bar.h" // IWYU pragma: export
388 
389     #endif
390   )cpp";
391   TU.AdditionalFiles["bar.h"] = guard(R"cpp(
392     void bar() {}
393   )cpp");
394   ParsedAST AST = TU.build();
395 
396   IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
397   EXPECT_THAT(Findings.UnusedIncludes,
398               ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
399 }
400 
401 TEST(IncludeCleaner, NoDiagsForObjC) {
402   TestTU TU;
403   TU.Code = R"cpp(
404     #include "foo.h"
405 
406     void bar() {}
407     )cpp";
408   TU.AdditionalFiles["foo.h"] = R"cpp(
409     #ifndef FOO_H
410     #define FOO_H
411 
412     #endif
413   )cpp";
414   TU.ExtraArgs.emplace_back("-xobjective-c");
415 
416   ParsedAST AST = TU.build();
417   IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
418   EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
419   EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
420 }
421 
422 TEST(IncludeCleaner, UmbrellaUsesPrivate) {
423   TestTU TU;
424   TU.Code = R"cpp(
425     #include "private.h"
426     )cpp";
427   TU.AdditionalFiles["private.h"] = guard(R"cpp(
428     // IWYU pragma: private, include "public.h"
429     void foo() {}
430   )cpp");
431   TU.Filename = "public.h";
432   ParsedAST AST = TU.build();
433   IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
434   EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
435 }
436 
437 TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
438   Annotations MainFile(R"cpp(
439   #include "all.h"
440   #define FOO(X) const Foo *X
441   void foo() {
442   #include [["expander.inc"]]
443   }
444 )cpp");
445 
446   TestTU TU;
447   TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);");
448   TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
449   TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
450 
451   TU.Code = MainFile.code();
452   ParsedAST AST = TU.build();
453 
454   auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
455   EXPECT_THAT(Findings, testing::SizeIs(1));
456   auto RefRange = Findings.front().SymRefRange;
457   auto &SM = AST.getSourceManager();
458   EXPECT_EQ(RefRange.file(), SM.getMainFileID());
459   // FIXME: Point at the spelling location, rather than the include.
460   EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
461 }
462 
463 TEST(IncludeCleaner, MissingIncludesAreUnique) {
464   Annotations MainFile(R"cpp(
465     #include "all.h"
466     FOO([[Foo]]);
467   )cpp");
468 
469   TestTU TU;
470   TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
471   TU.AdditionalFiles["all.h"] = guard(R"cpp(
472     #include "foo.h"
473     #define FOO(X) X y; X z
474   )cpp");
475 
476   TU.Code = MainFile.code();
477   ParsedAST AST = TU.build();
478 
479   auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
480   EXPECT_THAT(Findings, testing::SizeIs(1));
481   auto RefRange = Findings.front().SymRefRange;
482   auto &SM = AST.getSourceManager();
483   EXPECT_EQ(RefRange.file(), SM.getMainFileID());
484   EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
485 }
486 
487 TEST(IncludeCleaner, NoCrash) {
488   TestTU TU;
489   Annotations MainCode(R"cpp(
490     #include "all.h"
491     void test() {
492       [[1s]];
493     }
494     )cpp");
495   TU.Code = MainCode.code();
496   TU.AdditionalFiles["foo.h"] =
497       guard("int operator\"\"s(unsigned long long) { return 0; }");
498   TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
499   ParsedAST AST = TU.build();
500   const auto &MissingIncludes =
501       computeIncludeCleanerFindings(AST).MissingIncludes;
502   EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
503   auto &SM = AST.getSourceManager();
504   EXPECT_EQ(
505       halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
506       MainCode.range());
507 }
508 
509 TEST(IncludeCleaner, IsPreferredProvider) {
510   auto TU = TestTU::withCode(R"cpp(
511      #include "decl.h"
512      #include "def.h"
513      #include "def.h"
514   )cpp");
515   TU.AdditionalFiles["decl.h"] = "";
516   TU.AdditionalFiles["def.h"] = "";
517 
518   auto AST = TU.build();
519   auto &IncludeDecl = AST.getIncludeStructure().MainFileIncludes[0];
520   auto &IncludeDef1 = AST.getIncludeStructure().MainFileIncludes[1];
521   auto &IncludeDef2 = AST.getIncludeStructure().MainFileIncludes[2];
522 
523   auto &FM = AST.getSourceManager().getFileManager();
524   auto DeclH = *FM.getOptionalFileRef("decl.h");
525   auto DefH = *FM.getOptionalFileRef("def.h");
526 
527   auto Includes = convertIncludes(AST);
528   std::vector<include_cleaner::Header> Providers = {
529       include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
530   EXPECT_FALSE(isPreferredProvider(IncludeDecl, Includes, Providers));
531   EXPECT_TRUE(isPreferredProvider(IncludeDef1, Includes, Providers));
532   EXPECT_TRUE(isPreferredProvider(IncludeDef2, Includes, Providers));
533 }
534 
535 TEST(IncludeCleaner, BatchFix) {
536   TestTU TU;
537   TU.Filename = "main.cpp";
538   TU.AdditionalFiles["foo.h"] = guard("class Foo;");
539   TU.AdditionalFiles["bar.h"] = guard("class Bar;");
540   TU.AdditionalFiles["all.h"] = guard(R"cpp(
541     #include "foo.h"
542     #include "bar.h"
543   )cpp");
544 
545   TU.Code = R"cpp(
546   #include "all.h"
547 
548   Foo* foo;
549   )cpp";
550   auto AST = TU.build();
551   EXPECT_THAT(
552       issueIncludeCleanerDiagnostics(
553           AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
554       UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
555                                     FixMessage("fix all includes")}),
556                            withFix({FixMessage("remove #include directive"),
557                                     FixMessage("fix all includes")})));
558 
559   TU.Code = R"cpp(
560   #include "all.h"
561   #include "bar.h"
562 
563   Foo* foo;
564   )cpp";
565   AST = TU.build();
566   EXPECT_THAT(
567       issueIncludeCleanerDiagnostics(
568           AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
569       UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
570                                     FixMessage("fix all includes")}),
571                            withFix({FixMessage("remove #include directive"),
572                                     FixMessage("remove all unused includes"),
573                                     FixMessage("fix all includes")}),
574                            withFix({FixMessage("remove #include directive"),
575                                     FixMessage("remove all unused includes"),
576                                     FixMessage("fix all includes")})));
577 
578   TU.Code = R"cpp(
579   #include "all.h"
580 
581   Foo* foo;
582   Bar* bar;
583   )cpp";
584   AST = TU.build();
585   EXPECT_THAT(
586       issueIncludeCleanerDiagnostics(
587           AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
588       UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
589                                     FixMessage("add all missing includes"),
590                                     FixMessage("fix all includes")}),
591                            withFix({FixMessage("#include \"bar.h\""),
592                                     FixMessage("add all missing includes"),
593                                     FixMessage("fix all includes")}),
594                            withFix({FixMessage("remove #include directive"),
595                                     FixMessage("fix all includes")})));
596 }
597 
598 // In the presence of IWYU pragma private, we should accept spellings other
599 // than the recommended one if they appear to name the same public header.
600 TEST(IncludeCleaner, VerbatimEquivalence) {
601   auto TU = TestTU::withCode(R"cpp(
602     #include "lib/rel/public.h"
603     int x = Public;
604   )cpp");
605   TU.AdditionalFiles["repo/lib/rel/private.h"] = R"cpp(
606     #pragma once
607     // IWYU pragma: private, include "rel/public.h"
608     int Public;
609   )cpp";
610   TU.AdditionalFiles["repo/lib/rel/public.h"] = R"cpp(
611     #pragma once
612     #include "rel/private.h"
613   )cpp";
614 
615   TU.ExtraArgs.push_back("-Irepo");
616   TU.ExtraArgs.push_back("-Irepo/lib");
617 
618   auto AST = TU.build();
619   auto Findings = computeIncludeCleanerFindings(AST);
620   EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
621   EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
622 }
623 
624 TEST(IncludeCleaner, ResourceDirIsIgnored) {
625   auto TU = TestTU::withCode(R"cpp(
626     #include <amintrin.h>
627     #include <imintrin.h>
628     void baz() {
629       bar();
630     }
631   )cpp");
632   TU.ExtraArgs.push_back("-resource-dir");
633   TU.ExtraArgs.push_back(testPath("resources"));
634   TU.AdditionalFiles["resources/include/amintrin.h"] = guard("");
635   TU.AdditionalFiles["resources/include/imintrin.h"] = guard(R"cpp(
636     #include <emintrin.h>
637   )cpp");
638   TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R"cpp(
639     void bar();
640   )cpp");
641   auto AST = TU.build();
642   auto Findings = computeIncludeCleanerFindings(AST);
643   EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
644   EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
645 }
646 
647 TEST(IncludeCleaner, DifferentHeaderSameSpelling) {
648   // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
649   // directly. Make sure we don't generate unusued/missing include findings in
650   // such cases.
651   auto TU = TestTU::withCode(R"cpp(
652     #include <foo.h>
653     void baz() {
654       foo();
655     }
656   )cpp");
657   TU.AdditionalFiles["foo/foo.h"] = guard("#include_next <foo.h>");
658   TU.AdditionalFiles["foo_inner/foo.h"] = guard(R"cpp(
659     void foo();
660   )cpp");
661   TU.ExtraArgs.push_back("-Ifoo");
662   TU.ExtraArgs.push_back("-Ifoo_inner");
663 
664   auto AST = TU.build();
665   auto Findings = computeIncludeCleanerFindings(AST);
666   EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
667   EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
668 }
669 } // namespace
670 } // namespace clangd
671 } // namespace clang
672