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