brintos

brintos / llvm-project-archived public Read only

0
0
Text · 24.3 KiB · ec733cb Raw
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