713 lines · cpp
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-exception6//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(&Diag::Fixes, testing::UnorderedElementsAreArray(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(R"cpp(76 #include <list>77 #include <queue>78 #include <vector> // IWYU pragma: keep79 #include <string> // IWYU pragma: export80 std::list<int> x;81 )cpp");82 // Layout of std library impl is not relevant.83 TU.AdditionalFiles["bits"] = R"cpp(84 #pragma once85 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 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("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 140TEST(IncludeCleaner, IgnoredAngledHeaders) {141 // Currently the default behavior is to ignore unused angled includes142 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 160TEST(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 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("#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 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 x236 237// No missing include insertion for ambiguous macro refs.238#if defined(FOO)239#endif240 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 config252 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("#include \"b.h\"");270 TU.AdditionalFiles["b.h"] = guard("void b();");271 272 TU.AdditionalFiles["angled_wrapper.h"] = guard("#include <angled.h>");273 TU.AdditionalFiles["angled.h"] = guard("void angled();");274 TU.ExtraArgs.push_back("-I" + testPath("."));275 276 TU.AdditionalFiles["quoted_wrapper.h"] = guard("#include \"quoted.h\"");277 TU.AdditionalFiles["quoted.h"] = guard("void quoted();");278 279 TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\"");280 TU.AdditionalFiles["dir/d.h"] =281 guard("namespace ns { struct Bar { void d(); }; }");282 283 TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>");284 TU.AdditionalFiles["system/f.h"] = guard("void f();");285 TU.AdditionalFiles["system/quoted2_wrapper.h"] =286 guard("#include <system/quoted2.h>");287 TU.AdditionalFiles["system/quoted2.h"] = guard("void quoted2();");288 TU.ExtraArgs.push_back("-isystem" + testPath("system"));289 290 TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\"");291 TU.AdditionalFiles["buzz.h"] = guard("void buzz();");292 293 TU.AdditionalFiles["baz.h"] = guard("#include \"private.h\"");294 TU.AdditionalFiles["private.h"] = guard(R"cpp(295 // IWYU pragma: private, include "public.h"296 void foobar();297 )cpp");298 TU.AdditionalFiles["header.h"] = guard(R"cpp(299 namespace std { class vector {}; }300 )cpp");301 302 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");303 TU.AdditionalFiles["foo.h"] = guard(R"cpp(304 #define BAR(x) Foo *x305 #define FOO 1306 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, TU.Code, Findings, MockFS(),316 /*IgnoreHeaders=*/{[](llvm::StringRef Header) {317 return Header.ends_with("buzz.h");318 }},319 /*AngledHeaders=*/{[](llvm::StringRef Header) {320 return Header.contains("angled.h");321 }},322 /*QuotedHeaders=*/{[](llvm::StringRef Header) {323 return Header.contains("quoted.h") || Header.contains("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: keep400 #include "exported.h" // IWYU pragma: export401 #include "public.h"402 403 void bar() { foo(); }404 #include "keep_main_file.h" // IWYU pragma: keep405 )cpp";406 TU.AdditionalFiles["behind_keep.h"] = guard("");407 TU.AdditionalFiles["keep_main_file.h"] = guard("");408 TU.AdditionalFiles["exported.h"] = guard("");409 TU.AdditionalFiles["public.h"] = guard("#include \"private.h\"");410 TU.AdditionalFiles["private.h"] = guard(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_H426 #define FOO_H427 428 #include "bar.h" // IWYU pragma: export429 430 #endif431 )cpp";432 TU.AdditionalFiles["bar.h"] = guard(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_H451 #define FOO_H452 453 #endif454 )cpp";455 TU.ExtraArgs.emplace_back("-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(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 *X482 void foo() {483 #include [["expander.inc"]]484 }485)cpp");486 487 TestTU TU;488 TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);");489 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");490 TU.AdditionalFiles["all.h"] = guard("#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("struct Foo {};");512 TU.AdditionalFiles["all.h"] = guard(R"cpp(513 #include "foo.h"514 #define FOO(X) X y; X z515 )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("int operator\"\"s(unsigned long long) { return 0; }");539 TU.AdditionalFiles["all.h"] = guard("#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(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("decl.h");566 auto DefH = *FM.getOptionalFileRef("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("class Foo;");580 TU.AdditionalFiles["bar.h"] = guard("class Bar;");581 TU.AdditionalFiles["all.h"] = guard(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 other640// than the recommended one if they appear to name the same public header.641TEST(IncludeCleaner, VerbatimEquivalence) {642 auto TU = TestTU::withCode(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 once648 // IWYU pragma: private, include "rel/public.h"649 int Public;650 )cpp";651 TU.AdditionalFiles["repo/lib/rel/public.h"] = R"cpp(652 #pragma once653 #include "rel/private.h"654 )cpp";655 656 TU.ExtraArgs.push_back("-Irepo");657 TU.ExtraArgs.push_back("-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(R"cpp(667 #include <amintrin.h>668 #include <imintrin.h>669 void baz() {670 bar();671 }672 )cpp");673 TU.ExtraArgs.push_back("-resource-dir");674 TU.ExtraArgs.push_back(testPath("resources"));675 TU.AdditionalFiles["resources/include/amintrin.h"] = guard("");676 TU.AdditionalFiles["resources/include/imintrin.h"] = guard(R"cpp(677 #include <emintrin.h>678 )cpp");679 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(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 it690 // directly. Make sure we don't generate unusued/missing include findings in691 // such cases.692 auto TU = TestTU::withCode(R"cpp(693 #include <foo.h>694 void baz() {695 foo();696 }697 )cpp");698 TU.AdditionalFiles["foo/foo.h"] = guard("#include_next <foo.h>");699 TU.AdditionalFiles["foo_inner/foo.h"] = guard(R"cpp(700 void foo();701 )cpp");702 TU.ExtraArgs.push_back("-Ifoo");703 TU.ExtraArgs.push_back("-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} // namespace711} // namespace clangd712} // namespace clang713