685 lines · cpp
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-exception6//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 41namespace clang::include_cleaner {42namespace {43using testing::_;44using testing::AllOf;45using testing::Contains;46using testing::ElementsAre;47using testing::Pair;48using testing::UnorderedElementsAre;49 50std::string guard(llvm::StringRef Code) {51 return "#pragma once\n" + Code.str();52}53 54class WalkUsedTest : public testing::Test {55protected: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 96TEST_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 141TEST_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 170TEST_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 207class AnalyzeTest : public testing::Test {208protected: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 247TEST_F(AnalyzeTest, Basic) {248 Inputs.Code = R"cpp(249#include "a.h"250#include "b.h"251#include "keep.h" // IWYU pragma: keep252 253int 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 276TEST_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 288TEST_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 298TEST_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 323TEST_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 it327 // directly. Make sure we don't generate unusued/missing include findings in328 // 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 348TEST_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 underlying376 // 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// Make sure that the references to implicit operator new/delete are reported as401// ambigious.402TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotMissing) {403 ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();404 ExtraFS->addFile("header.h",405 /*ModificationTime=*/{},406 llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp(407 void* operator new(decltype(sizeof(int)));408 )cpp")));409 ExtraFS->addFile("wrapper.h",410 /*ModificationTime=*/{},411 llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp(412 #include "header.h"413 )cpp")));414 415 Inputs.Code = R"cpp(416 #include "wrapper.h"417 void bar() {418 operator new(3);419 })cpp";420 TestAST AST(Inputs);421 std::vector<Decl *> DeclsInTU;422 for (auto *D : AST.context().getTranslationUnitDecl()->decls())423 DeclsInTU.push_back(D);424 auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());425 EXPECT_THAT(Results.Missing, testing::IsEmpty());426}427 428TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotUnused) {429 ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();430 ExtraFS->addFile("header.h",431 /*ModificationTime=*/{},432 llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp(433 void* operator new(decltype(sizeof(int)));434 )cpp")));435 436 Inputs.Code = R"cpp(437 #include "header.h"438 void bar() {439 operator new(3);440 })cpp";441 TestAST AST(Inputs);442 std::vector<Decl *> DeclsInTU;443 for (auto *D : AST.context().getTranslationUnitDecl()->decls())444 DeclsInTU.push_back(D);445 auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());446 EXPECT_THAT(Results.Unused, testing::IsEmpty());447}448 449TEST(FixIncludes, Basic) {450 llvm::StringRef Code = R"cpp(#include "d.h"451#include "a.h"452#include "b.h"453#include <c.h>454)cpp";455 456 Includes Inc;457 Include I;458 I.Spelled = "a.h";459 I.Line = 2;460 Inc.add(I);461 I.Spelled = "b.h";462 I.Line = 3;463 Inc.add(I);464 I.Spelled = "c.h";465 I.Line = 4;466 I.Angled = true;467 Inc.add(I);468 469 AnalysisResults Results;470 Results.Missing.emplace_back("\"aa.h\"", Header(""));471 Results.Missing.emplace_back("\"ab.h\"", Header(""));472 Results.Missing.emplace_back("<e.h>", Header(""));473 Results.Unused.push_back(Inc.atLine(3));474 Results.Unused.push_back(Inc.atLine(4));475 476 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),477R"cpp(#include "d.h"478#include "a.h"479#include "aa.h"480#include "ab.h"481#include <e.h>482)cpp");483 484 Results = {};485 Results.Missing.emplace_back("\"d.h\"", Header(""));486 Code = R"cpp(#include "a.h")cpp";487 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),488R"cpp(#include "d.h"489#include "a.h")cpp");490}491 492MATCHER_P3(expandedAt, FileID, Offset, SM, "") {493 auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);494 return ExpanedFileID == FileID && ExpandedOffset == Offset;495}496MATCHER_P3(spelledAt, FileID, Offset, SM, "") {497 auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);498 return SpelledFileID == FileID && SpelledOffset == Offset;499}500TEST(WalkUsed, FilterRefsNotSpelledInMainFile) {501 // Each test is expected to have a single expected ref of `target` symbol502 // (or have none).503 // The location in the reported ref is a macro location. $expand points to504 // the macro location, and $spell points to the spelled location.505 struct {506 llvm::StringRef Header;507 llvm::StringRef Main;508 } TestCases[] = {509 // Tests for decl references.510 {511 /*Header=*/"int target();",512 R"cpp(513 #define CALL_FUNC $spell^target()514 515 int b = $expand^CALL_FUNC;516 )cpp",517 },518 {/*Header=*/R"cpp(519 int target();520 #define CALL_FUNC target()521 )cpp",522 // No ref of `target` being reported, as it is not spelled in main file.523 "int a = CALL_FUNC;"},524 {525 /*Header=*/R"cpp(526 int target();527 #define PLUS_ONE(X) X() + 1528 )cpp",529 R"cpp(530 int a = $expand^PLUS_ONE($spell^target);531 )cpp",532 },533 {534 /*Header=*/R"cpp(535 int target();536 #define PLUS_ONE(X) X() + 1537 )cpp",538 R"cpp(539 int a = $expand^PLUS_ONE($spell^target);540 )cpp",541 },542 // Tests for macro references543 {/*Header=*/"#define target 1",544 R"cpp(545 #define USE_target $spell^target546 int b = $expand^USE_target;547 )cpp"},548 {/*Header=*/R"cpp(549 #define target 1550 #define USE_target target551 )cpp",552 // No ref of `target` being reported, it is not spelled in main file.553 R"cpp(554 int a = USE_target;555 )cpp"},556 };557 558 for (const auto &T : TestCases) {559 llvm::Annotations Main(T.Main);560 TestInputs Inputs(Main.code());561 Inputs.ExtraFiles["header.h"] = guard(T.Header);562 RecordedPP Recorded;563 Inputs.MakeAction = [&]() {564 struct RecordAction : public SyntaxOnlyAction {565 RecordedPP &Out;566 RecordAction(RecordedPP &Out) : Out(Out) {}567 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {568 auto &PP = CI.getPreprocessor();569 PP.addPPCallbacks(Out.record(PP));570 return true;571 }572 };573 return std::make_unique<RecordAction>(Recorded);574 };575 Inputs.ExtraArgs.push_back("-include");576 Inputs.ExtraArgs.push_back("header.h");577 TestAST AST(Inputs);578 llvm::SmallVector<Decl *> TopLevelDecls;579 for (Decl *D : AST.context().getTranslationUnitDecl()->decls())580 TopLevelDecls.emplace_back(D);581 auto &SM = AST.sourceManager();582 583 SourceLocation RefLoc;584 walkUsed(TopLevelDecls, Recorded.MacroReferences,585 /*PragmaIncludes=*/nullptr, AST.preprocessor(),586 [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) {587 if (!Ref.RefLocation.isMacroID())588 return;589 if (llvm::to_string(Ref.Target) == "target") {590 ASSERT_TRUE(RefLoc.isInvalid())591 << "Expected only one 'target' ref loc per testcase";592 RefLoc = Ref.RefLocation;593 }594 });595 FileID MainFID = SM.getMainFileID();596 if (RefLoc.isValid()) {597 EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand"), &SM),598 spelledAt(MainFID, Main.point("spell"), &SM)))599 << T.Main.str();600 } else {601 EXPECT_THAT(Main.points(), testing::IsEmpty());602 }603 }604}605 606struct Tag {607 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {608 return OS << "Anon Tag";609 }610};611TEST(Hints, Ordering) {612 auto Hinted = [](Hints Hints) {613 return clang::include_cleaner::Hinted<Tag>({}, Hints);614 };615 EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol));616 EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader));617 EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader));618 EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader),619 Hinted(Hints::PublicHeader));620}621 622// Test ast traversal & redecl selection end-to-end for templates, as explicit623// instantiations/specializations are not redecls of the primary template. We624// need to make sure we're selecting the right ones.625TEST_F(WalkUsedTest, TemplateDecls) {626 llvm::Annotations Code(R"cpp(627 #include "fwd.h"628 #include "def.h"629 #include "partial.h"630 template <> struct $exp_spec^Foo<char> {};631 template struct $exp^Foo<int>;632 $full^Foo<int> x;633 $implicit^Foo<bool> y;634 $partial^Foo<int*> z;635 )cpp");636 Inputs.Code = Code.code();637 Inputs.ExtraFiles["fwd.h"] = guard("template<typename> struct Foo;");638 Inputs.ExtraFiles["def.h"] = guard("template<typename> struct Foo {};");639 Inputs.ExtraFiles["partial.h"] =640 guard("template<typename T> struct Foo<T*> {};");641 TestAST AST(Inputs);642 auto &SM = AST.sourceManager();643 auto Fwd = *SM.getFileManager().getOptionalFileRef("fwd.h");644 auto Def = *SM.getFileManager().getOptionalFileRef("def.h");645 auto Partial = *SM.getFileManager().getOptionalFileRef("partial.h");646 647 EXPECT_THAT(648 offsetToProviders(AST),649 AllOf(Contains(650 Pair(Code.point("exp_spec"), UnorderedElementsAre(Fwd, Def))),651 Contains(Pair(Code.point("exp"), UnorderedElementsAre(Fwd, Def))),652 Contains(Pair(Code.point("full"), UnorderedElementsAre(Fwd, Def))),653 Contains(654 Pair(Code.point("implicit"), UnorderedElementsAre(Fwd, Def))),655 Contains(656 Pair(Code.point("partial"), UnorderedElementsAre(Partial)))));657}658 659TEST_F(WalkUsedTest, IgnoresIdentityMacros) {660 llvm::Annotations Code(R"cpp(661 #include "header.h"662 void $bar^bar() {663 $stdin^stdin();664 }665 )cpp");666 Inputs.Code = Code.code();667 Inputs.ExtraFiles["header.h"] = guard(R"cpp(668 #include "inner.h"669 void stdin();670 )cpp");671 Inputs.ExtraFiles["inner.h"] = guard(R"cpp(672 #define stdin stdin673 )cpp");674 675 TestAST AST(Inputs);676 auto &SM = AST.sourceManager();677 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));678 EXPECT_THAT(offsetToProviders(AST),679 UnorderedElementsAre(680 // FIXME: we should have a reference from stdin to header.h681 Pair(Code.point("bar"), UnorderedElementsAre(MainFile))));682}683} // namespace684} // namespace clang::include_cleaner685