brintos

brintos / llvm-project-archived public Read only

0
0
Text · 26.8 KiB · 16a2f94 Raw
903 lines · cpp
1//===--- PreambleTests.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 "Compiler.h"11#include "Config.h"12#include "Diagnostics.h"13#include "Headers.h"14#include "Hover.h"15#include "ParsedAST.h"16#include "Preamble.h"17#include "Protocol.h"18#include "SourceCode.h"19#include "TestFS.h"20#include "TestTU.h"21#include "XRefs.h"22#include "support/Context.h"23#include "clang/Basic/SourceManager.h"24#include "clang/Format/Format.h"25#include "clang/Frontend/FrontendActions.h"26#include "clang/Frontend/PrecompiledPreamble.h"27#include "llvm/ADT/StringMap.h"28#include "llvm/ADT/StringRef.h"29#include "llvm/Support/Error.h"30#include "llvm/Support/MemoryBuffer.h"31#include "llvm/Support/ScopedPrinter.h"32#include "llvm/Support/VirtualFileSystem.h"33#include "llvm/Testing/Annotations/Annotations.h"34#include "gmock/gmock.h"35#include "gtest/gtest-matchers.h"36#include "gtest/gtest.h"37#include <memory>38#include <optional>39#include <string>40#include <utility>41#include <vector>42 43using testing::AllOf;44using testing::Contains;45using testing::ElementsAre;46using testing::Field;47using testing::IsEmpty;48using testing::Matcher;49using testing::MatchesRegex;50using testing::UnorderedElementsAre;51using testing::UnorderedElementsAreArray;52 53namespace clang {54namespace clangd {55namespace {56 57MATCHER_P2(Distance, File, D, "") {58  return arg.first() == File && arg.second == D;59}60 61// Builds a preamble for BaselineContents, patches it for ModifiedContents and62// returns the includes in the patch.63IncludeStructure64collectPatchedIncludes(llvm::StringRef ModifiedContents,65                       llvm::StringRef BaselineContents,66                       llvm::StringRef MainFileName = "main.cpp") {67  MockFS FS;68  auto TU = TestTU::withCode(BaselineContents);69  TU.Filename = MainFileName.str();70  // ms-compatibility changes meaning of #import, make sure it is turned off.71  TU.ExtraArgs = {"-fno-ms-compatibility"};72  auto BaselinePreamble = TU.preamble();73  // Create the patch.74  TU.Code = ModifiedContents.str();75  auto PI = TU.inputs(FS);76  auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), PI,77                                           *BaselinePreamble);78  // Collect patch contents.79  IgnoreDiagnostics Diags;80  auto CI = buildCompilerInvocation(PI, Diags);81  PP.apply(*CI);82  // Run preprocessor over the modified contents with patched Invocation. We83  // provide a preamble and trim contents to ensure only the implicit header84  // introduced by the patch is parsed and nothing else.85  // We don't run PP directly over the patch cotents to test production86  // behaviour.87  auto Bounds = Lexer::ComputePreamble(ModifiedContents, CI->getLangOpts());88  auto Clang =89      prepareCompilerInstance(std::move(CI), &BaselinePreamble->Preamble,90                              llvm::MemoryBuffer::getMemBufferCopy(91                                  ModifiedContents.slice(0, Bounds.Size).str()),92                              PI.TFS->view(PI.CompileCommand.Directory), Diags);93  PreprocessOnlyAction Action;94  if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {95    ADD_FAILURE() << "failed begin source file";96    return {};97  }98  IncludeStructure Includes;99  Includes.collect(*Clang);100  if (llvm::Error Err = Action.Execute()) {101    ADD_FAILURE() << "failed to execute action: " << std::move(Err);102    return {};103  }104  Action.EndSourceFile();105  return Includes;106}107 108// Check preamble lexing logic by building an empty preamble and patching it109// with all the contents.110TEST(PreamblePatchTest, IncludeParsing) {111  // We expect any line with a point to show up in the patch.112  llvm::StringRef Cases[] = {113      // Only preamble114      R"cpp(^#include "a.h")cpp",115      // Both preamble and mainfile116      R"cpp(117        ^#include "a.h"118        garbage, finishes preamble119        #include "a.h")cpp",120      // Mixed directives121      R"cpp(122        ^#include "a.h"123        #pragma directive124        // some comments125        ^#include_next <a.h>126        #ifdef skipped127        ^#import "a.h"128        #endif)cpp",129      // Broken directives130      R"cpp(131        #include "a132        ^#include "a.h"133        #include <b134        ^#include <b.h>)cpp",135      // Directive is not part of preamble if it is not the token immediately136      // followed by the hash (#).137      R"cpp(138        ^#include "a.h"139        #/**/include <b.h>)cpp",140  };141 142  for (const auto &Case : Cases) {143    Annotations Test(Case);144    const auto Code = Test.code();145    SCOPED_TRACE(Code);146 147    auto Includes =148        collectPatchedIncludes(Code, /*BaselineContents=*/"").MainFileIncludes;149    auto Points = Test.points();150    ASSERT_EQ(Includes.size(), Points.size());151    for (size_t I = 0, E = Includes.size(); I != E; ++I)152      EXPECT_EQ(Includes[I].HashLine, Points[I].line);153  }154}155 156TEST(PreamblePatchTest, ContainsNewIncludes) {157  constexpr llvm::StringLiteral BaselineContents = R"cpp(158    #include <a.h>159    #include <b.h> // This will be removed160    #include <c.h>161  )cpp";162  constexpr llvm::StringLiteral ModifiedContents = R"cpp(163    #include <a.h>164    #include <c.h> // This has changed a line.165    #include <c.h> // This is a duplicate.166    #include <d.h> // This is newly introduced.167  )cpp";168  auto Includes = collectPatchedIncludes(ModifiedContents, BaselineContents)169                      .MainFileIncludes;170  EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<d.h>"),171                                          Field(&Inclusion::HashLine, 4))));172}173 174TEST(PreamblePatchTest, MainFileIsEscaped) {175  auto Includes = collectPatchedIncludes("#include <a.h>", "", "file\"name.cpp")176                      .MainFileIncludes;177  EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>"),178                                          Field(&Inclusion::HashLine, 0))));179}180 181TEST(PreamblePatchTest, PatchesPreambleIncludes) {182  MockFS FS;183  IgnoreDiagnostics Diags;184  auto TU = TestTU::withCode(R"cpp(185    #include "a.h" // IWYU pragma: keep186    #include "c.h"187    #ifdef FOO188    #include "d.h"189    #endif190  )cpp");191  TU.AdditionalFiles["a.h"] = "#include \"b.h\"";192  TU.AdditionalFiles["b.h"] = "";193  TU.AdditionalFiles["c.h"] = "";194  auto PI = TU.inputs(FS);195  auto BaselinePreamble = buildPreamble(196      TU.Filename, *buildCompilerInvocation(PI, Diags), PI, true, nullptr);197  // We drop c.h from modified and add a new header. Since the latter is patched198  // we should only get a.h in preamble includes. d.h shouldn't be part of the199  // preamble, as it's coming from a disabled region.200  TU.Code = R"cpp(201    #include "a.h"202    #include "b.h"203    #ifdef FOO204    #include "d.h"205    #endif206  )cpp";207  auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), TU.inputs(FS),208                                           *BaselinePreamble);209  // Only a.h should exists in the preamble, as c.h has been dropped and b.h was210  // newly introduced.211  EXPECT_THAT(212      PP.preambleIncludes(),213      ElementsAre(AllOf(214          Field(&Inclusion::Written, "\"a.h\""),215          Field(&Inclusion::Resolved, testPath("a.h")),216          Field(&Inclusion::HeaderID, testing::Not(testing::Eq(std::nullopt))),217          Field(&Inclusion::FileKind, SrcMgr::CharacteristicKind::C_User))));218}219 220std::optional<ParsedAST>221createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified,222                 llvm::StringMap<std::string> AdditionalFiles = {}) {223  auto TU = TestTU::withCode(Baseline);224  TU.AdditionalFiles = std::move(AdditionalFiles);225  auto BaselinePreamble = TU.preamble();226  if (!BaselinePreamble) {227    ADD_FAILURE() << "Failed to build baseline preamble";228    return std::nullopt;229  }230 231  IgnoreDiagnostics Diags;232  MockFS FS;233  TU.Code = Modified.str();234  auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);235  if (!CI) {236    ADD_FAILURE() << "Failed to build compiler invocation";237    return std::nullopt;238  }239  return ParsedAST::build(testPath(TU.Filename), TU.inputs(FS), std::move(CI),240                          {}, BaselinePreamble);241}242 243std::string getPreamblePatch(llvm::StringRef Baseline,244                             llvm::StringRef Modified) {245  auto BaselinePreamble = TestTU::withCode(Baseline).preamble();246  if (!BaselinePreamble) {247    ADD_FAILURE() << "Failed to build baseline preamble";248    return "";249  }250  MockFS FS;251  auto TU = TestTU::withCode(Modified);252  return PreamblePatch::createFullPatch(testPath("main.cpp"), TU.inputs(FS),253                                        *BaselinePreamble)254      .text()255      .str();256}257 258TEST(PreamblePatchTest, IncludesArePreserved) {259  llvm::StringLiteral Baseline = R"(//error-ok260#include <foo>261#include <bar>262)";263  llvm::StringLiteral Modified = R"(//error-ok264#include <foo>265#include <bar>266#define FOO)";267 268  auto Includes = createPatchedAST(Baseline, Modified.str())269                      ->getIncludeStructure()270                      .MainFileIncludes;271  EXPECT_TRUE(!Includes.empty());272  EXPECT_EQ(Includes, TestTU::withCode(Baseline)273                          .build()274                          .getIncludeStructure()275                          .MainFileIncludes);276}277 278TEST(PreamblePatchTest, Define) {279  // BAR should be defined while parsing the AST.280  struct {281    const char *const Contents;282    const char *const ExpectedPatch;283  } Cases[] = {284      {285          R"cpp(286        #define BAR287        [[BAR]])cpp",288          R"cpp(#line 0 ".*main.cpp"289#undef BAR290#line 2291#define         BAR292)cpp",293      },294      // multiline macro295      {296          R"cpp(297        #define BAR \298 299        [[BAR]])cpp",300          R"cpp(#line 0 ".*main.cpp"301#undef BAR302#line 2303#define         BAR304)cpp",305      },306      // multiline macro307      {308          R"cpp(309        #define \310                BAR311        [[BAR]])cpp",312          R"cpp(#line 0 ".*main.cpp"313#undef BAR314#line 3315#define         BAR316)cpp",317      },318  };319 320  for (const auto &Case : Cases) {321    SCOPED_TRACE(Case.Contents);322    llvm::Annotations Modified(Case.Contents);323    EXPECT_THAT(getPreamblePatch("", Modified.code()),324                MatchesRegex(Case.ExpectedPatch));325 326    auto AST = createPatchedAST("", Modified.code());327    ASSERT_TRUE(AST);328    std::vector<llvm::Annotations::Range> MacroRefRanges;329    for (auto &M : AST->getMacros().MacroRefs) {330      for (auto &O : M.getSecond())331        MacroRefRanges.push_back({O.StartOffset, O.EndOffset});332    }333    EXPECT_THAT(MacroRefRanges, Contains(Modified.range()));334  }335}336 337TEST(PreamblePatchTest, OrderingPreserved) {338  llvm::StringLiteral Baseline = "#define BAR(X) X";339  Annotations Modified(R"cpp(340    #define BAR(X, Y) X Y341    #define BAR(X) X342    [[BAR]](int y);343  )cpp");344 345  llvm::StringLiteral ExpectedPatch(R"cpp(#line 0 ".*main.cpp"346#undef BAR347#line 2348#define     BAR\(X, Y\) X Y349#undef BAR350#line 3351#define     BAR\(X\) X352)cpp");353  EXPECT_THAT(getPreamblePatch(Baseline, Modified.code()),354              MatchesRegex(ExpectedPatch.str()));355 356  auto AST = createPatchedAST(Baseline, Modified.code());357  ASSERT_TRUE(AST);358}359 360TEST(PreamblePatchTest, LocateMacroAtWorks) {361  struct {362    const char *const Baseline;363    const char *const Modified;364  } Cases[] = {365      // Addition of new directive366      {367          "",368          R"cpp(369            #define $def^FOO370            $use^FOO)cpp",371      },372      // Available inside preamble section373      {374          "",375          R"cpp(376            #define $def^FOO377            #undef $use^FOO)cpp",378      },379      // Available after undef, as we don't patch those380      {381          "",382          R"cpp(383            #define $def^FOO384            #undef FOO385            $use^FOO)cpp",386      },387      // Identifier on a different line388      {389          "",390          R"cpp(391            #define \392              $def^FOO393            $use^FOO)cpp",394      },395      // In presence of comment tokens396      {397          "",398          R"cpp(399            #\400              define /* FOO */\401              /* FOO */ $def^FOO402            $use^FOO)cpp",403      },404      // Moved around405      {406          "#define FOO",407          R"cpp(408            #define BAR409            #define $def^FOO410            $use^FOO)cpp",411      },412  };413  for (const auto &Case : Cases) {414    SCOPED_TRACE(Case.Modified);415    llvm::Annotations Modified(Case.Modified);416    auto AST = createPatchedAST(Case.Baseline, Modified.code());417    ASSERT_TRUE(AST);418 419    const auto &SM = AST->getSourceManager();420    auto *MacroTok = AST->getTokens().spelledTokenContaining(421        SM.getComposedLoc(SM.getMainFileID(), Modified.point("use")));422    ASSERT_TRUE(MacroTok);423 424    auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());425    ASSERT_TRUE(FoundMacro);426    EXPECT_THAT(FoundMacro->Name, "FOO");427 428    auto MacroLoc = FoundMacro->NameLoc;429    EXPECT_EQ(SM.getFileID(MacroLoc), SM.getMainFileID());430    EXPECT_EQ(SM.getFileOffset(MacroLoc), Modified.point("def"));431  }432}433 434TEST(PreamblePatchTest, LocateMacroAtDeletion) {435  {436    // We don't patch deleted define directives, make sure we don't crash.437    llvm::StringLiteral Baseline = "#define FOO";438    llvm::Annotations Modified("^FOO");439 440    auto AST = createPatchedAST(Baseline, Modified.code());441    ASSERT_TRUE(AST);442 443    const auto &SM = AST->getSourceManager();444    auto *MacroTok = AST->getTokens().spelledTokenContaining(445        SM.getComposedLoc(SM.getMainFileID(), Modified.point()));446    ASSERT_TRUE(MacroTok);447 448    auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());449    ASSERT_TRUE(FoundMacro);450    EXPECT_THAT(FoundMacro->Name, "FOO");451    auto HI =452        getHover(*AST, offsetToPosition(Modified.code(), Modified.point()),453                 format::getLLVMStyle(), nullptr);454    ASSERT_TRUE(HI);455    EXPECT_THAT(HI->Definition, testing::IsEmpty());456  }457 458  {459    // Offset is valid, but underlying text is different.460    llvm::StringLiteral Baseline = "#define FOO";461    Annotations Modified(R"cpp(#define BAR462    ^FOO")cpp");463 464    auto AST = createPatchedAST(Baseline, Modified.code());465    ASSERT_TRUE(AST);466 467    auto HI = getHover(*AST, Modified.point(), format::getLLVMStyle(), nullptr);468    ASSERT_TRUE(HI);469    EXPECT_THAT(HI->Definition, "#define BAR");470  }471}472 473MATCHER_P(referenceRangeIs, R, "") { return arg.Loc.range == R; }474 475TEST(PreamblePatchTest, RefsToMacros) {476  struct {477    const char *const Baseline;478    const char *const Modified;479  } Cases[] = {480      // Newly added481      {482          "",483          R"cpp(484            #define ^FOO485            ^[[FOO]])cpp",486      },487      // Moved around488      {489          "#define FOO",490          R"cpp(491            #define BAR492            #define ^FOO493            ^[[FOO]])cpp",494      },495      // Ref in preamble section496      {497          "",498          R"cpp(499            #define ^FOO500            #undef ^FOO)cpp",501      },502  };503 504  for (const auto &Case : Cases) {505    Annotations Modified(Case.Modified);506    auto AST = createPatchedAST("", Modified.code());507    ASSERT_TRUE(AST);508 509    const auto &SM = AST->getSourceManager();510    std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;511    for (const auto &R : Modified.ranges())512      ExpectedLocations.push_back(referenceRangeIs(R));513 514    for (const auto &P : Modified.points()) {515      auto *MacroTok =516          AST->getTokens().spelledTokenContaining(SM.getComposedLoc(517              SM.getMainFileID(),518              llvm::cantFail(positionToOffset(Modified.code(), P))));519      ASSERT_TRUE(MacroTok);520      EXPECT_THAT(findReferences(*AST, P, 0).References,521                  testing::ElementsAreArray(ExpectedLocations));522    }523  }524}525 526TEST(TranslatePreamblePatchLocation, Simple) {527  auto TU = TestTU::withHeaderCode(R"cpp(528    #line 3 "main.cpp"529    int foo();)cpp");530  // Presumed line/col needs to be valid in the main file.531  TU.Code = R"cpp(// line 1532    // line 2533    // line 3534    // line 4)cpp";535  TU.Filename = "main.cpp";536  TU.HeaderFilename = "__preamble_patch__.h";537  TU.ImplicitHeaderGuard = false;538 539  auto AST = TU.build();540  auto &SM = AST.getSourceManager();541  auto &ND = findDecl(AST, "foo");542  EXPECT_NE(SM.getFileID(ND.getLocation()), SM.getMainFileID());543 544  auto TranslatedLoc = translatePreamblePatchLocation(ND.getLocation(), SM);545  auto DecompLoc = SM.getDecomposedLoc(TranslatedLoc);546  EXPECT_EQ(DecompLoc.first, SM.getMainFileID());547  EXPECT_EQ(SM.getLineNumber(DecompLoc.first, DecompLoc.second), 3U);548}549 550TEST(PreamblePatch, ModifiedBounds) {551  struct {552    const char *const Baseline;553    const char *const Modified;554  } Cases[] = {555      // Size increased556      {557          "",558          R"cpp(559            #define FOO560            FOO)cpp",561      },562      // Stayed same563      {"#define FOO", "#define BAR"},564      // Got smaller565      {566          R"cpp(567            #define FOO568            #undef FOO)cpp",569          "#define FOO"},570  };571 572  for (const auto &Case : Cases) {573    auto TU = TestTU::withCode(Case.Baseline);574    auto BaselinePreamble = TU.preamble();575    ASSERT_TRUE(BaselinePreamble);576 577    Annotations Modified(Case.Modified);578    TU.Code = Modified.code().str();579    MockFS FS;580    auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename),581                                             TU.inputs(FS), *BaselinePreamble);582 583    IgnoreDiagnostics Diags;584    auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);585    ASSERT_TRUE(CI);586 587    const auto ExpectedBounds =588        Lexer::ComputePreamble(Case.Modified, CI->getLangOpts());589    EXPECT_EQ(PP.modifiedBounds().Size, ExpectedBounds.Size);590    EXPECT_EQ(PP.modifiedBounds().PreambleEndsAtStartOfLine,591              ExpectedBounds.PreambleEndsAtStartOfLine);592  }593}594 595TEST(PreamblePatch, MacroLoc) {596  llvm::StringLiteral Baseline = "\n#define MACRO 12\nint num = MACRO;";597  llvm::StringLiteral Modified = " \n#define MACRO 12\nint num = MACRO;";598  auto AST = createPatchedAST(Baseline, Modified);599  ASSERT_TRUE(AST);600}601 602TEST(PreamblePatch, NoopWhenNotRequested) {603  llvm::StringLiteral Baseline = "#define M\nint num = M;";604  llvm::StringLiteral Modified = "#define M\n#include <foo.h>\nint num = M;";605  auto TU = TestTU::withCode(Baseline);606  auto BaselinePreamble = TU.preamble();607  ASSERT_TRUE(BaselinePreamble);608 609  TU.Code = Modified.str();610  MockFS FS;611  auto PP = PreamblePatch::createMacroPatch(testPath(TU.Filename),612                                            TU.inputs(FS), *BaselinePreamble);613  EXPECT_TRUE(PP.text().empty());614}615 616::testing::Matcher<const Diag &>617withNote(::testing::Matcher<Note> NoteMatcher) {618  return Field(&Diag::Notes, ElementsAre(NoteMatcher));619}620MATCHER_P(Diag, Range, "Diag at " + llvm::to_string(Range)) {621  return arg.Range == Range;622}623MATCHER_P2(Diag, Range, Name,624           "Diag at " + llvm::to_string(Range) + " = [" + Name + "]") {625  return arg.Range == Range && arg.Name == Name;626}627 628TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) {629  {630    Annotations Code("#define FOO");631    // Check with removals from preamble.632    Annotations NewCode("[[x]];/* error-ok */");633    auto AST = createPatchedAST(Code.code(), NewCode.code());634    EXPECT_THAT(AST->getDiagnostics(),635                ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));636  }637  {638    // Check with additions to preamble.639    Annotations Code("#define FOO");640    Annotations NewCode(R"(641#define FOO642#define BAR643[[x]];/* error-ok */)");644    auto AST = createPatchedAST(Code.code(), NewCode.code());645    EXPECT_THAT(AST->getDiagnostics(),646                ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));647  }648}649 650TEST(PreamblePatch, DiagnosticsToPreamble) {651  Config Cfg;652  Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;653  Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict;654  WithContextValue WithCfg(Config::Key, std::move(Cfg));655 656  llvm::StringMap<std::string> AdditionalFiles;657  AdditionalFiles["foo.h"] = "#pragma once";658  AdditionalFiles["bar.h"] = "#pragma once";659  {660    Annotations Code(R"(661// Test comment662[[#include "foo.h"]])");663    // Check with removals from preamble.664    Annotations NewCode(R"([[#  include "foo.h"]])");665    auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);666    EXPECT_THAT(AST->getDiagnostics(),667                ElementsAre(Diag(NewCode.range(), "unused-includes")));668  }669  {670    // Check with additions to preamble.671    Annotations Code(R"(672// Test comment673[[#include "foo.h"]])");674    Annotations NewCode(R"(675$bar[[#include "bar.h"]]676// Test comment677$foo[[#include "foo.h"]])");678    auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);679    EXPECT_THAT(680        AST->getDiagnostics(),681        UnorderedElementsAre(Diag(NewCode.range("bar"), "unused-includes"),682                             Diag(NewCode.range("foo"), "unused-includes")));683  }684  {685    Annotations Code("#define [[FOO]] 1\n");686    // Check ranges for notes.687    // This also makes sure we don't generate missing-include diagnostics688    // because macros are redefined in preamble-patch.689    Annotations NewCode(R"(#define BARXYZ 1690#define $foo1[[FOO]] 1691void foo();692#define $foo2[[FOO]] 2)");693    auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);694    EXPECT_THAT(695        AST->getDiagnostics(),696        ElementsAre(AllOf(Diag(NewCode.range("foo2"), "-Wmacro-redefined"),697                          withNote(Diag(NewCode.range("foo1"))))));698  }699}700 701TEST(PreamblePatch, TranslatesDiagnosticsInPreamble) {702  {703    // Check with additions to preamble.704    Annotations Code("#include [[<foo>]]");705    Annotations NewCode(R"(706#define BAR707#include [[<foo>]])");708    auto AST = createPatchedAST(Code.code(), NewCode.code());709    EXPECT_THAT(AST->getDiagnostics(),710                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));711  }712  {713    // Check with removals from preamble.714    Annotations Code(R"(715#define BAR716#include [[<foo>]])");717    Annotations NewCode("#include [[<foo>]]");718    auto AST = createPatchedAST(Code.code(), NewCode.code());719    EXPECT_THAT(AST->getDiagnostics(),720                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));721  }722  {723    // Drop line with diags.724    Annotations Code("#include [[<foo>]]");725    Annotations NewCode("#define BAR\n#define BAZ\n");726    auto AST = createPatchedAST(Code.code(), NewCode.code());727    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());728  }729  {730    // Picks closest line in case of multiple alternatives.731    Annotations Code("#include [[<foo>]]");732    Annotations NewCode(R"(733#define BAR734#include [[<foo>]]735#define BAR736#include <foo>)");737    auto AST = createPatchedAST(Code.code(), NewCode.code());738    EXPECT_THAT(AST->getDiagnostics(),739                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));740  }741  {742    // Drop diag if line spelling has changed.743    Annotations Code("#include [[<foo>]]");744    Annotations NewCode(" # include <foo>");745    auto AST = createPatchedAST(Code.code(), NewCode.code());746    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());747  }748  {749    // Multiple lines.750    Annotations Code(R"(751#define BAR752#include [[<fo\753o>]])");754    Annotations NewCode(R"(#include [[<fo\755o>]])");756    auto AST = createPatchedAST(Code.code(), NewCode.code());757    EXPECT_THAT(AST->getDiagnostics(),758                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));759  }760  {761    // Multiple lines with change.762    Annotations Code(R"(763#define BAR764#include <fox>765#include [[<fo\766o>]])");767    Annotations NewCode(R"(#include <fo\768x>)");769    auto AST = createPatchedAST(Code.code(), NewCode.code());770    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());771  }772  {773    // Preserves notes.774    Annotations Code(R"(775#define $note[[BAR]] 1776#define $main[[BAR]] 2)");777    Annotations NewCode(R"(778#define BAZ 0779#define $note[[BAR]] 1780#define BAZ 0781#define $main[[BAR]] 2)");782    auto AST = createPatchedAST(Code.code(), NewCode.code());783    EXPECT_THAT(784        AST->getDiagnostics(),785        ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),786                          withNote(Diag(NewCode.range("note"))))));787  }788  {789    // Preserves diag without note.790    Annotations Code(R"(791#define $note[[BAR]] 1792#define $main[[BAR]] 2)");793    Annotations NewCode(R"(794#define $main[[BAR]] 2)");795    auto AST = createPatchedAST(Code.code(), NewCode.code());796    EXPECT_THAT(797        AST->getDiagnostics(),798        ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),799                          Field(&Diag::Notes, IsEmpty()))));800  }801  {802    // Make sure orphaned notes are not promoted to diags.803    Annotations Code(R"(804#define $note[[BAR]] 1805#define $main[[BAR]] 2)");806    Annotations NewCode(R"(807#define BAZ 0808#define BAR 1)");809    auto AST = createPatchedAST(Code.code(), NewCode.code());810    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());811  }812  {813    Annotations Code(R"(814#ifndef FOO815#define FOO816void foo();817#endif)");818    // This code will emit a diagnostic for unterminated #ifndef (as stale819    // preamble has the conditional but main file doesn't terminate it).820    // We shouldn't emit any diagnotiscs (and shouldn't crash).821    Annotations NewCode("");822    auto AST = createPatchedAST(Code.code(), NewCode.code());823    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());824  }825  {826    Annotations Code(R"(827#ifndef FOO828#define FOO829void foo();830#endif)");831    // This code will emit a diagnostic for unterminated #ifndef (as stale832    // preamble has the conditional but main file doesn't terminate it).833    // We shouldn't emit any diagnotiscs (and shouldn't crash).834    // FIXME: Patch/ignore diagnostics in such cases.835    Annotations NewCode(R"(836i[[nt]] xyz;837    )");838    auto AST = createPatchedAST(Code.code(), NewCode.code());839    EXPECT_THAT(840        AST->getDiagnostics(),841        ElementsAre(Diag(NewCode.range(), "pp_unterminated_conditional")));842  }843}844 845MATCHER_P2(Mark, Range, Text, "") {846  return std::tie(arg.Rng, arg.Trivia) == std::tie(Range, Text);847}848 849TEST(PreamblePatch, MacroAndMarkHandling) {850  {851    Annotations Code(R"cpp(852#ifndef FOO853#define FOO854// Some comments855#pragma mark XX856#define BAR857 858#endif)cpp");859    Annotations NewCode(R"cpp(860#ifndef FOO861#define FOO862#define BAR863#pragma $x[[mark XX864]]865#pragma $y[[mark YY866]]867#define BAZ868 869#endif)cpp");870    auto AST = createPatchedAST(Code.code(), NewCode.code());871    EXPECT_THAT(AST->getMacros().Names.keys(),872                UnorderedElementsAreArray({"FOO", "BAR", "BAZ"}));873    EXPECT_THAT(AST->getMarks(),874                UnorderedElementsAre(Mark(NewCode.range("x"), " XX"),875                                     Mark(NewCode.range("y"), " YY")));876  }877}878 879TEST(PreamblePatch, PatchFileEntry) {880  Annotations Code(R"cpp(#define FOO)cpp");881  Annotations NewCode(R"cpp(882#define BAR883#define FOO)cpp");884  {885    auto AST = createPatchedAST(Code.code(), Code.code());886    EXPECT_EQ(887        PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()),888        nullptr);889  }890  {891    auto AST = createPatchedAST(Code.code(), NewCode.code());892    auto FE =893        PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager());894    ASSERT_NE(FE, std::nullopt);895    EXPECT_THAT(FE->getName().str(),896                testing::EndsWith(PreamblePatch::HeaderName.str()));897  }898}899 900} // namespace901} // namespace clangd902} // namespace clang903