xref: /llvm-project/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp (revision 4ef77d61b2ee3054344b50d5f4e3111ce69fffcf)
1 //===--- AnalysisTest.cpp -------------------------------------------------===//
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 "clang-include-cleaner/Analysis.h"
10 #include "AnalysisInternal.h"
11 #include "TypesInternal.h"
12 #include "clang-include-cleaner/Record.h"
13 #include "clang-include-cleaner/Types.h"
14 #include "clang/AST/ASTContext.h"
15 #include "clang/AST/DeclBase.h"
16 #include "clang/Basic/FileManager.h"
17 #include "clang/Basic/IdentifierTable.h"
18 #include "clang/Basic/SourceLocation.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Format/Format.h"
21 #include "clang/Frontend/FrontendActions.h"
22 #include "clang/Testing/TestAST.h"
23 #include "clang/Tooling/Inclusions/StandardLibrary.h"
24 #include "llvm/ADT/ArrayRef.h"
25 #include "llvm/ADT/IntrusiveRefCntPtr.h"
26 #include "llvm/ADT/SmallVector.h"
27 #include "llvm/ADT/StringRef.h"
28 #include "llvm/Support/Error.h"
29 #include "llvm/Support/MemoryBuffer.h"
30 #include "llvm/Support/ScopedPrinter.h"
31 #include "llvm/Support/VirtualFileSystem.h"
32 #include "llvm/Testing/Annotations/Annotations.h"
33 #include "gmock/gmock.h"
34 #include "gtest/gtest.h"
35 #include <cstddef>
36 #include <map>
37 #include <memory>
38 #include <string>
39 #include <vector>
40 
41 namespace clang::include_cleaner {
42 namespace {
43 using testing::_;
44 using testing::AllOf;
45 using testing::Contains;
46 using testing::ElementsAre;
47 using testing::Pair;
48 using testing::UnorderedElementsAre;
49 
50 std::string guard(llvm::StringRef Code) {
51   return "#pragma once\n" + Code.str();
52 }
53 
54 class WalkUsedTest : public testing::Test {
55 protected:
56   TestInputs Inputs;
57   PragmaIncludes PI;
58   WalkUsedTest() {
59     Inputs.MakeAction = [this] {
60       struct Hook : public SyntaxOnlyAction {
61       public:
62         Hook(PragmaIncludes *Out) : Out(Out) {}
63         bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
64           Out->record(CI);
65           return true;
66         }
67 
68         PragmaIncludes *Out;
69       };
70       return std::make_unique<Hook>(&PI);
71     };
72   }
73 
74   std::multimap<size_t, std::vector<Header>>
75   offsetToProviders(TestAST &AST,
76                     llvm::ArrayRef<SymbolReference> MacroRefs = {}) {
77     const auto &SM = AST.sourceManager();
78     llvm::SmallVector<Decl *> TopLevelDecls;
79     for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) {
80       if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation())))
81         continue;
82       TopLevelDecls.emplace_back(D);
83     }
84     std::multimap<size_t, std::vector<Header>> OffsetToProviders;
85     walkUsed(TopLevelDecls, MacroRefs, &PI, AST.preprocessor(),
86              [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
87                auto [FID, Offset] = SM.getDecomposedLoc(Ref.RefLocation);
88                if (FID != SM.getMainFileID())
89                  ADD_FAILURE() << "Reference outside of the main file!";
90                OffsetToProviders.emplace(Offset, Providers.vec());
91              });
92     return OffsetToProviders;
93   }
94 };
95 
96 TEST_F(WalkUsedTest, Basic) {
97   llvm::Annotations Code(R"cpp(
98   #include "header.h"
99   #include "private.h"
100 
101   // No reference reported for the Parameter "p".
102   void $bar^bar($private^Private p) {
103     $foo^foo();
104     std::$vector^vector $vconstructor^$v^v;
105     $builtin^__builtin_popcount(1);
106     std::$move^move(3);
107   }
108   )cpp");
109   Inputs.Code = Code.code();
110   Inputs.ExtraFiles["header.h"] = guard(R"cpp(
111   void foo();
112   namespace std { class vector {}; int&& move(int&&); }
113   )cpp");
114   Inputs.ExtraFiles["private.h"] = guard(R"cpp(
115     // IWYU pragma: private, include "path/public.h"
116     class Private {};
117   )cpp");
118 
119   TestAST AST(Inputs);
120   auto &SM = AST.sourceManager();
121   auto HeaderFile = Header(*AST.fileManager().getOptionalFileRef("header.h"));
122   auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef("private.h"));
123   auto PublicFile = Header("\"path/public.h\"");
124   auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
125   auto VectorSTL = Header(*tooling::stdlib::Header::named("<vector>"));
126   auto UtilitySTL = Header(*tooling::stdlib::Header::named("<utility>"));
127   EXPECT_THAT(
128       offsetToProviders(AST),
129       UnorderedElementsAre(
130           Pair(Code.point("bar"), UnorderedElementsAre(MainFile)),
131           Pair(Code.point("private"),
132                UnorderedElementsAre(PublicFile, PrivateFile)),
133           Pair(Code.point("foo"), UnorderedElementsAre(HeaderFile)),
134           Pair(Code.point("vector"), UnorderedElementsAre(VectorSTL)),
135           Pair(Code.point("vconstructor"), UnorderedElementsAre(VectorSTL)),
136           Pair(Code.point("v"), UnorderedElementsAre(MainFile)),
137           Pair(Code.point("builtin"), testing::IsEmpty()),
138           Pair(Code.point("move"), UnorderedElementsAre(UtilitySTL))));
139 }
140 
141 TEST_F(WalkUsedTest, MultipleProviders) {
142   llvm::Annotations Code(R"cpp(
143   #include "header1.h"
144   #include "header2.h"
145   void foo();
146 
147   void bar() {
148     $foo^foo();
149   }
150   )cpp");
151   Inputs.Code = Code.code();
152   Inputs.ExtraFiles["header1.h"] = guard(R"cpp(
153   void foo();
154   )cpp");
155   Inputs.ExtraFiles["header2.h"] = guard(R"cpp(
156   void foo();
157   )cpp");
158 
159   TestAST AST(Inputs);
160   auto &SM = AST.sourceManager();
161   auto HeaderFile1 = Header(*AST.fileManager().getOptionalFileRef("header1.h"));
162   auto HeaderFile2 = Header(*AST.fileManager().getOptionalFileRef("header2.h"));
163   auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
164   EXPECT_THAT(
165       offsetToProviders(AST),
166       Contains(Pair(Code.point("foo"),
167                     UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile))));
168 }
169 
170 TEST_F(WalkUsedTest, MacroRefs) {
171   llvm::Annotations Code(R"cpp(
172     #include "hdr.h"
173     int $3^x = $1^ANSWER;
174     int $4^y = $2^ANSWER;
175   )cpp");
176   llvm::Annotations Hdr(guard("#define ^ANSWER 42"));
177   Inputs.Code = Code.code();
178   Inputs.ExtraFiles["hdr.h"] = Hdr.code();
179   TestAST AST(Inputs);
180   auto &SM = AST.sourceManager();
181   auto &PP = AST.preprocessor();
182   auto HdrFile = *SM.getFileManager().getOptionalFileRef("hdr.h");
183   auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
184 
185   auto HdrID = SM.translateFile(HdrFile);
186 
187   Symbol Answer1 = Macro{PP.getIdentifierInfo("ANSWER"),
188                          SM.getComposedLoc(HdrID, Hdr.point())};
189   Symbol Answer2 = Macro{PP.getIdentifierInfo("ANSWER"),
190                          SM.getComposedLoc(HdrID, Hdr.point())};
191   EXPECT_THAT(
192       offsetToProviders(
193           AST,
194           {SymbolReference{
195                Answer1, SM.getComposedLoc(SM.getMainFileID(), Code.point("1")),
196                RefType::Explicit},
197            SymbolReference{
198                Answer2, SM.getComposedLoc(SM.getMainFileID(), Code.point("2")),
199                RefType::Explicit}}),
200       UnorderedElementsAre(
201           Pair(Code.point("1"), UnorderedElementsAre(HdrFile)),
202           Pair(Code.point("2"), UnorderedElementsAre(HdrFile)),
203           Pair(Code.point("3"), UnorderedElementsAre(MainFile)),
204           Pair(Code.point("4"), UnorderedElementsAre(MainFile))));
205 }
206 
207 class AnalyzeTest : public testing::Test {
208 protected:
209   TestInputs Inputs;
210   PragmaIncludes PI;
211   RecordedPP PP;
212   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS = nullptr;
213 
214   AnalyzeTest() {
215     Inputs.MakeAction = [this] {
216       struct Hook : public SyntaxOnlyAction {
217       public:
218         Hook(RecordedPP &PP, PragmaIncludes &PI,
219              llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS)
220             : PP(PP), PI(PI), ExtraFS(std::move(ExtraFS)) {}
221         bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
222           CI.getPreprocessor().addPPCallbacks(PP.record(CI.getPreprocessor()));
223           PI.record(CI);
224           return true;
225         }
226 
227         bool BeginInvocation(CompilerInstance &CI) override {
228           if (!ExtraFS)
229             return true;
230           auto OverlayFS =
231               llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(
232                   CI.getFileManager().getVirtualFileSystemPtr());
233           OverlayFS->pushOverlay(ExtraFS);
234           CI.getFileManager().setVirtualFileSystem(std::move(OverlayFS));
235           return true;
236         }
237 
238         RecordedPP &PP;
239         PragmaIncludes &PI;
240         llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS;
241       };
242       return std::make_unique<Hook>(PP, PI, ExtraFS);
243     };
244   }
245 };
246 
247 TEST_F(AnalyzeTest, Basic) {
248   Inputs.Code = R"cpp(
249 #include "a.h"
250 #include "b.h"
251 #include "keep.h" // IWYU pragma: keep
252 
253 int x = a + c;
254 )cpp";
255   Inputs.ExtraFiles["a.h"] = guard("int a;");
256   Inputs.ExtraFiles["b.h"] = guard(R"cpp(
257     #include "c.h"
258     int b;
259   )cpp");
260   Inputs.ExtraFiles["c.h"] = guard("int c;");
261   Inputs.ExtraFiles["keep.h"] = guard("");
262   TestAST AST(Inputs);
263   auto Decls = AST.context().getTranslationUnitDecl()->decls();
264   auto Results =
265       analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
266               PP.MacroReferences, PP.Includes, &PI, AST.preprocessor());
267   auto CHeader = llvm::cantFail(
268       AST.context().getSourceManager().getFileManager().getFileRef("c.h"));
269 
270   const Include *B = PP.Includes.atLine(3);
271   ASSERT_EQ(B->Spelled, "b.h");
272   EXPECT_THAT(Results.Missing, ElementsAre(Pair("\"c.h\"", Header(CHeader))));
273   EXPECT_THAT(Results.Unused, ElementsAre(B));
274 }
275 
276 TEST_F(AnalyzeTest, PrivateUsedInPublic) {
277   // Check that umbrella header uses private include.
278   Inputs.Code = R"cpp(#include "private.h")cpp";
279   Inputs.ExtraFiles["private.h"] =
280       guard("// IWYU pragma: private, include \"public.h\"");
281   Inputs.FileName = "public.h";
282   TestAST AST(Inputs);
283   EXPECT_FALSE(PP.Includes.all().empty());
284   auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
285   EXPECT_THAT(Results.Unused, testing::IsEmpty());
286 }
287 
288 TEST_F(AnalyzeTest, NoCrashWhenUnresolved) {
289   // Check that umbrella header uses private include.
290   Inputs.Code = R"cpp(#include "not_found.h")cpp";
291   Inputs.ErrorOK = true;
292   TestAST AST(Inputs);
293   EXPECT_FALSE(PP.Includes.all().empty());
294   auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
295   EXPECT_THAT(Results.Unused, testing::IsEmpty());
296 }
297 
298 TEST_F(AnalyzeTest, ResourceDirIsIgnored) {
299   Inputs.ExtraArgs.push_back("-resource-dir");
300   Inputs.ExtraArgs.push_back("resources");
301   Inputs.ExtraArgs.push_back("-internal-isystem");
302   Inputs.ExtraArgs.push_back("resources/include");
303   Inputs.Code = R"cpp(
304     #include <amintrin.h>
305     #include <imintrin.h>
306     void baz() {
307       bar();
308     }
309   )cpp";
310   Inputs.ExtraFiles["resources/include/amintrin.h"] = guard("");
311   Inputs.ExtraFiles["resources/include/emintrin.h"] = guard(R"cpp(
312     void bar();
313   )cpp");
314   Inputs.ExtraFiles["resources/include/imintrin.h"] = guard(R"cpp(
315     #include <emintrin.h>
316   )cpp");
317   TestAST AST(Inputs);
318   auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
319   EXPECT_THAT(Results.Unused, testing::IsEmpty());
320   EXPECT_THAT(Results.Missing, testing::IsEmpty());
321 }
322 
323 TEST_F(AnalyzeTest, DifferentHeaderSameSpelling) {
324   Inputs.ExtraArgs.push_back("-Ifoo");
325   Inputs.ExtraArgs.push_back("-Ifoo_inner");
326   // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
327   // directly. Make sure we don't generate unusued/missing include findings in
328   // such cases.
329   Inputs.Code = R"cpp(
330     #include <foo.h>
331     void baz() {
332       foo();
333     }
334   )cpp";
335   Inputs.ExtraFiles["foo/foo.h"] = guard("#include_next <foo.h>");
336   Inputs.ExtraFiles["foo_inner/foo.h"] = guard(R"cpp(
337     void foo();
338   )cpp");
339   TestAST AST(Inputs);
340   std::vector<Decl *> DeclsInTU;
341   for (auto *D : AST.context().getTranslationUnitDecl()->decls())
342     DeclsInTU.push_back(D);
343   auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());
344   EXPECT_THAT(Results.Unused, testing::IsEmpty());
345   EXPECT_THAT(Results.Missing, testing::IsEmpty());
346 }
347 
348 TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
349   llvm::Annotations Code(R"cpp(
350   #include "header.h"
351   void $bar^bar() {
352     $foo^foo();
353   }
354   )cpp");
355   Inputs.Code = Code.code();
356   ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
357   ExtraFS->addFile("content_for/0", /*ModificationTime=*/{},
358                    llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp(
359   #include "inner.h"
360   )cpp")));
361   ExtraFS->addSymbolicLink("header.h", "content_for/0",
362                            /*ModificationTime=*/{});
363   ExtraFS->addFile("content_for/1", /*ModificationTime=*/{},
364                    llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp(
365   void foo();
366   )cpp")));
367   ExtraFS->addSymbolicLink("inner.h", "content_for/1",
368                            /*ModificationTime=*/{});
369 
370   TestAST AST(Inputs);
371   std::vector<Decl *> DeclsInTU;
372   for (auto *D : AST.context().getTranslationUnitDecl()->decls())
373     DeclsInTU.push_back(D);
374   auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());
375   // Check that we're spelling header using the symlink, and not underlying
376   // path.
377   EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
378   // header.h should be unused.
379   EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
380 
381   {
382     // Make sure filtering is also applied to symlink, not underlying file.
383     auto HeaderFilter = [](llvm::StringRef Path) { return Path == "inner.h"; };
384     Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor(),
385                       HeaderFilter);
386     EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
387     // header.h should be unused.
388     EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
389   }
390   {
391     auto HeaderFilter = [](llvm::StringRef Path) { return Path == "header.h"; };
392     Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor(),
393                       HeaderFilter);
394     // header.h should be ignored now.
395     EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
396     EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
397   }
398 }
399 
400 TEST(FixIncludes, Basic) {
401   llvm::StringRef Code = R"cpp(#include "d.h"
402 #include "a.h"
403 #include "b.h"
404 #include <c.h>
405 )cpp";
406 
407   Includes Inc;
408   Include I;
409   I.Spelled = "a.h";
410   I.Line = 2;
411   Inc.add(I);
412   I.Spelled = "b.h";
413   I.Line = 3;
414   Inc.add(I);
415   I.Spelled = "c.h";
416   I.Line = 4;
417   I.Angled = true;
418   Inc.add(I);
419 
420   AnalysisResults Results;
421   Results.Missing.emplace_back("\"aa.h\"", Header(""));
422   Results.Missing.emplace_back("\"ab.h\"", Header(""));
423   Results.Missing.emplace_back("<e.h>", Header(""));
424   Results.Unused.push_back(Inc.atLine(3));
425   Results.Unused.push_back(Inc.atLine(4));
426 
427   EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
428 R"cpp(#include "d.h"
429 #include "a.h"
430 #include "aa.h"
431 #include "ab.h"
432 #include <e.h>
433 )cpp");
434 
435   Results = {};
436   Results.Missing.emplace_back("\"d.h\"", Header(""));
437   Code = R"cpp(#include "a.h")cpp";
438   EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
439 R"cpp(#include "d.h"
440 #include "a.h")cpp");
441 }
442 
443 MATCHER_P3(expandedAt, FileID, Offset, SM, "") {
444   auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);
445   return ExpanedFileID == FileID && ExpandedOffset == Offset;
446 }
447 MATCHER_P3(spelledAt, FileID, Offset, SM, "") {
448   auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);
449   return SpelledFileID == FileID && SpelledOffset == Offset;
450 }
451 TEST(WalkUsed, FilterRefsNotSpelledInMainFile) {
452   // Each test is expected to have a single expected ref of `target` symbol
453   // (or have none).
454   // The location in the reported ref is a macro location. $expand points to
455   // the macro location, and $spell points to the spelled location.
456   struct {
457     llvm::StringRef Header;
458     llvm::StringRef Main;
459   } TestCases[] = {
460       // Tests for decl references.
461       {
462           /*Header=*/"int target();",
463           R"cpp(
464             #define CALL_FUNC $spell^target()
465 
466             int b = $expand^CALL_FUNC;
467           )cpp",
468       },
469       {/*Header=*/R"cpp(
470            int target();
471            #define CALL_FUNC target()
472            )cpp",
473        // No ref of `target` being reported, as it is not spelled in main file.
474        "int a = CALL_FUNC;"},
475       {
476           /*Header=*/R"cpp(
477             int target();
478             #define PLUS_ONE(X) X() + 1
479           )cpp",
480           R"cpp(
481             int a = $expand^PLUS_ONE($spell^target);
482           )cpp",
483       },
484       {
485           /*Header=*/R"cpp(
486             int target();
487             #define PLUS_ONE(X) X() + 1
488           )cpp",
489           R"cpp(
490             int a = $expand^PLUS_ONE($spell^target);
491           )cpp",
492       },
493       // Tests for macro references
494       {/*Header=*/"#define target 1",
495        R"cpp(
496           #define USE_target $spell^target
497           int b = $expand^USE_target;
498         )cpp"},
499       {/*Header=*/R"cpp(
500           #define target 1
501           #define USE_target target
502         )cpp",
503        // No ref of `target` being reported, it is not spelled in main file.
504        R"cpp(
505           int a = USE_target;
506         )cpp"},
507   };
508 
509   for (const auto &T : TestCases) {
510     llvm::Annotations Main(T.Main);
511     TestInputs Inputs(Main.code());
512     Inputs.ExtraFiles["header.h"] = guard(T.Header);
513     RecordedPP Recorded;
514     Inputs.MakeAction = [&]() {
515       struct RecordAction : public SyntaxOnlyAction {
516         RecordedPP &Out;
517         RecordAction(RecordedPP &Out) : Out(Out) {}
518         bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
519           auto &PP = CI.getPreprocessor();
520           PP.addPPCallbacks(Out.record(PP));
521           return true;
522         }
523       };
524       return std::make_unique<RecordAction>(Recorded);
525     };
526     Inputs.ExtraArgs.push_back("-include");
527     Inputs.ExtraArgs.push_back("header.h");
528     TestAST AST(Inputs);
529     llvm::SmallVector<Decl *> TopLevelDecls;
530     for (Decl *D : AST.context().getTranslationUnitDecl()->decls())
531       TopLevelDecls.emplace_back(D);
532     auto &SM = AST.sourceManager();
533 
534     SourceLocation RefLoc;
535     walkUsed(TopLevelDecls, Recorded.MacroReferences,
536              /*PragmaIncludes=*/nullptr, AST.preprocessor(),
537              [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) {
538                if (!Ref.RefLocation.isMacroID())
539                  return;
540                if (llvm::to_string(Ref.Target) == "target") {
541                  ASSERT_TRUE(RefLoc.isInvalid())
542                      << "Expected only one 'target' ref loc per testcase";
543                  RefLoc = Ref.RefLocation;
544                }
545              });
546     FileID MainFID = SM.getMainFileID();
547     if (RefLoc.isValid()) {
548       EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand"), &SM),
549                                 spelledAt(MainFID, Main.point("spell"), &SM)))
550           << T.Main.str();
551     } else {
552       EXPECT_THAT(Main.points(), testing::IsEmpty());
553     }
554   }
555 }
556 
557 struct Tag {
558   friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {
559     return OS << "Anon Tag";
560   }
561 };
562 TEST(Hints, Ordering) {
563   auto Hinted = [](Hints Hints) {
564     return clang::include_cleaner::Hinted<Tag>({}, Hints);
565   };
566   EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol));
567   EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader));
568   EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader));
569   EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader),
570             Hinted(Hints::PublicHeader));
571 }
572 
573 // Test ast traversal & redecl selection end-to-end for templates, as explicit
574 // instantiations/specializations are not redecls of the primary template. We
575 // need to make sure we're selecting the right ones.
576 TEST_F(WalkUsedTest, TemplateDecls) {
577   llvm::Annotations Code(R"cpp(
578     #include "fwd.h"
579     #include "def.h"
580     #include "partial.h"
581     template <> struct $exp_spec^Foo<char> {};
582     template struct $exp^Foo<int>;
583     $full^Foo<int> x;
584     $implicit^Foo<bool> y;
585     $partial^Foo<int*> z;
586   )cpp");
587   Inputs.Code = Code.code();
588   Inputs.ExtraFiles["fwd.h"] = guard("template<typename> struct Foo;");
589   Inputs.ExtraFiles["def.h"] = guard("template<typename> struct Foo {};");
590   Inputs.ExtraFiles["partial.h"] =
591       guard("template<typename T> struct Foo<T*> {};");
592   TestAST AST(Inputs);
593   auto &SM = AST.sourceManager();
594   auto Fwd = *SM.getFileManager().getOptionalFileRef("fwd.h");
595   auto Def = *SM.getFileManager().getOptionalFileRef("def.h");
596   auto Partial = *SM.getFileManager().getOptionalFileRef("partial.h");
597 
598   EXPECT_THAT(
599       offsetToProviders(AST),
600       AllOf(Contains(
601                 Pair(Code.point("exp_spec"), UnorderedElementsAre(Fwd, Def))),
602             Contains(Pair(Code.point("exp"), UnorderedElementsAre(Fwd, Def))),
603             Contains(Pair(Code.point("full"), UnorderedElementsAre(Fwd, Def))),
604             Contains(
605                 Pair(Code.point("implicit"), UnorderedElementsAre(Fwd, Def))),
606             Contains(
607                 Pair(Code.point("partial"), UnorderedElementsAre(Partial)))));
608 }
609 
610 TEST_F(WalkUsedTest, IgnoresIdentityMacros) {
611   llvm::Annotations Code(R"cpp(
612   #include "header.h"
613   void $bar^bar() {
614     $stdin^stdin();
615   }
616   )cpp");
617   Inputs.Code = Code.code();
618   Inputs.ExtraFiles["header.h"] = guard(R"cpp(
619   #include "inner.h"
620   void stdin();
621   )cpp");
622   Inputs.ExtraFiles["inner.h"] = guard(R"cpp(
623   #define stdin stdin
624   )cpp");
625 
626   TestAST AST(Inputs);
627   auto &SM = AST.sourceManager();
628   auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
629   EXPECT_THAT(offsetToProviders(AST),
630               UnorderedElementsAre(
631                   // FIXME: we should have a reference from stdin to header.h
632                   Pair(Code.point("bar"), UnorderedElementsAre(MainFile))));
633 }
634 } // namespace
635 } // namespace clang::include_cleaner
636