//===--- PreambleTests.cpp --------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "Annotations.h"
#include "Compiler.h"
#include "Config.h"
#include "Diagnostics.h"
#include "Headers.h"
#include "Hover.h"
#include "ParsedAST.h"
#include "Preamble.h"
#include "Protocol.h"
#include "SourceCode.h"
#include "TestFS.h"
#include "TestTU.h"
#include "XRefs.h"
#include "support/Context.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/PrecompiledPreamble.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Testing/Annotations/Annotations.h"
#include "gmock/gmock.h"
#include "gtest/gtest-matchers.h"
#include "gtest/gtest.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

using testing::AllOf;
using testing::Contains;
using testing::ElementsAre;
using testing::Field;
using testing::IsEmpty;
using testing::Matcher;
using testing::MatchesRegex;
using testing::UnorderedElementsAre;
using testing::UnorderedElementsAreArray;

namespace clang {
namespace clangd {
namespace {

MATCHER_P2(Distance, File, D, "") {
  return arg.first() == File && arg.second == D;
}

// Builds a preamble for BaselineContents, patches it for ModifiedContents and
// returns the includes in the patch.
IncludeStructure
collectPatchedIncludes(llvm::StringRef ModifiedContents,
                       llvm::StringRef BaselineContents,
                       llvm::StringRef MainFileName = "main.cpp") {
  MockFS FS;
  auto TU = TestTU::withCode(BaselineContents);
  TU.Filename = MainFileName.str();
  // ms-compatibility changes meaning of #import, make sure it is turned off.
  TU.ExtraArgs = {"-fno-ms-compatibility"};
  auto BaselinePreamble = TU.preamble();
  // Create the patch.
  TU.Code = ModifiedContents.str();
  auto PI = TU.inputs(FS);
  auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), PI,
                                           *BaselinePreamble);
  // Collect patch contents.
  IgnoreDiagnostics Diags;
  auto CI = buildCompilerInvocation(PI, Diags);
  PP.apply(*CI);
  // Run preprocessor over the modified contents with patched Invocation. We
  // provide a preamble and trim contents to ensure only the implicit header
  // introduced by the patch is parsed and nothing else.
  // We don't run PP directly over the patch cotents to test production
  // behaviour.
  auto Bounds = Lexer::ComputePreamble(ModifiedContents, CI->getLangOpts());
  auto Clang =
      prepareCompilerInstance(std::move(CI), &BaselinePreamble->Preamble,
                              llvm::MemoryBuffer::getMemBufferCopy(
                                  ModifiedContents.slice(0, Bounds.Size).str()),
                              PI.TFS->view(PI.CompileCommand.Directory), Diags);
  PreprocessOnlyAction Action;
  if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
    ADD_FAILURE() << "failed begin source file";
    return {};
  }
  IncludeStructure Includes;
  Includes.collect(*Clang);
  if (llvm::Error Err = Action.Execute()) {
    ADD_FAILURE() << "failed to execute action: " << std::move(Err);
    return {};
  }
  Action.EndSourceFile();
  return Includes;
}

// Check preamble lexing logic by building an empty preamble and patching it
// with all the contents.
TEST(PreamblePatchTest, IncludeParsing) {
  // We expect any line with a point to show up in the patch.
  llvm::StringRef Cases[] = {
      // Only preamble
      R"cpp(^#include "a.h")cpp",
      // Both preamble and mainfile
      R"cpp(
        ^#include "a.h"
        garbage, finishes preamble
        #include "a.h")cpp",
      // Mixed directives
      R"cpp(
        ^#include "a.h"
        #pragma directive
        // some comments
        ^#include_next <a.h>
        #ifdef skipped
        ^#import "a.h"
        #endif)cpp",
      // Broken directives
      R"cpp(
        #include "a
        ^#include "a.h"
        #include <b
        ^#include <b.h>)cpp",
      // Directive is not part of preamble if it is not the token immediately
      // followed by the hash (#).
      R"cpp(
        ^#include "a.h"
        #/**/include <b.h>)cpp",
  };

  for (const auto &Case : Cases) {
    Annotations Test(Case);
    const auto Code = Test.code();
    SCOPED_TRACE(Code);

    auto Includes =
        collectPatchedIncludes(Code, /*BaselineContents=*/"").MainFileIncludes;
    auto Points = Test.points();
    ASSERT_EQ(Includes.size(), Points.size());
    for (size_t I = 0, E = Includes.size(); I != E; ++I)
      EXPECT_EQ(Includes[I].HashLine, Points[I].line);
  }
}

TEST(PreamblePatchTest, ContainsNewIncludes) {
  constexpr llvm::StringLiteral BaselineContents = R"cpp(
    #include <a.h>
    #include <b.h> // This will be removed
    #include <c.h>
  )cpp";
  constexpr llvm::StringLiteral ModifiedContents = R"cpp(
    #include <a.h>
    #include <c.h> // This has changed a line.
    #include <c.h> // This is a duplicate.
    #include <d.h> // This is newly introduced.
  )cpp";
  auto Includes = collectPatchedIncludes(ModifiedContents, BaselineContents)
                      .MainFileIncludes;
  EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<d.h>"),
                                          Field(&Inclusion::HashLine, 4))));
}

TEST(PreamblePatchTest, MainFileIsEscaped) {
  auto Includes = collectPatchedIncludes("#include <a.h>", "", "file\"name.cpp")
                      .MainFileIncludes;
  EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>"),
                                          Field(&Inclusion::HashLine, 0))));
}

TEST(PreamblePatchTest, PatchesPreambleIncludes) {
  MockFS FS;
  IgnoreDiagnostics Diags;
  auto TU = TestTU::withCode(R"cpp(
    #include "a.h" // IWYU pragma: keep
    #include "c.h"
    #ifdef FOO
    #include "d.h"
    #endif
  )cpp");
  TU.AdditionalFiles["a.h"] = "#include \"b.h\"";
  TU.AdditionalFiles["b.h"] = "";
  TU.AdditionalFiles["c.h"] = "";
  auto PI = TU.inputs(FS);
  auto BaselinePreamble = buildPreamble(
      TU.Filename, *buildCompilerInvocation(PI, Diags), PI, true, nullptr);
  // We drop c.h from modified and add a new header. Since the latter is patched
  // we should only get a.h in preamble includes. d.h shouldn't be part of the
  // preamble, as it's coming from a disabled region.
  TU.Code = R"cpp(
    #include "a.h"
    #include "b.h"
    #ifdef FOO
    #include "d.h"
    #endif
  )cpp";
  auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), TU.inputs(FS),
                                           *BaselinePreamble);
  // Only a.h should exists in the preamble, as c.h has been dropped and b.h was
  // newly introduced.
  EXPECT_THAT(
      PP.preambleIncludes(),
      ElementsAre(AllOf(
          Field(&Inclusion::Written, "\"a.h\""),
          Field(&Inclusion::Resolved, testPath("a.h")),
          Field(&Inclusion::HeaderID, testing::Not(testing::Eq(std::nullopt))),
          Field(&Inclusion::FileKind, SrcMgr::CharacteristicKind::C_User))));
}

std::optional<ParsedAST>
createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified,
                 llvm::StringMap<std::string> AdditionalFiles = {}) {
  auto TU = TestTU::withCode(Baseline);
  TU.AdditionalFiles = std::move(AdditionalFiles);
  auto BaselinePreamble = TU.preamble();
  if (!BaselinePreamble) {
    ADD_FAILURE() << "Failed to build baseline preamble";
    return std::nullopt;
  }

  IgnoreDiagnostics Diags;
  MockFS FS;
  TU.Code = Modified.str();
  auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);
  if (!CI) {
    ADD_FAILURE() << "Failed to build compiler invocation";
    return std::nullopt;
  }
  return ParsedAST::build(testPath(TU.Filename), TU.inputs(FS), std::move(CI),
                          {}, BaselinePreamble);
}

std::string getPreamblePatch(llvm::StringRef Baseline,
                             llvm::StringRef Modified) {
  auto BaselinePreamble = TestTU::withCode(Baseline).preamble();
  if (!BaselinePreamble) {
    ADD_FAILURE() << "Failed to build baseline preamble";
    return "";
  }
  MockFS FS;
  auto TU = TestTU::withCode(Modified);
  return PreamblePatch::createFullPatch(testPath("main.cpp"), TU.inputs(FS),
                                        *BaselinePreamble)
      .text()
      .str();
}

TEST(PreamblePatchTest, IncludesArePreserved) {
  llvm::StringLiteral Baseline = R"(//error-ok
#include <foo>
#include <bar>
)";
  llvm::StringLiteral Modified = R"(//error-ok
#include <foo>
#include <bar>
#define FOO)";

  auto Includes = createPatchedAST(Baseline, Modified.str())
                      ->getIncludeStructure()
                      .MainFileIncludes;
  EXPECT_TRUE(!Includes.empty());
  EXPECT_EQ(Includes, TestTU::withCode(Baseline)
                          .build()
                          .getIncludeStructure()
                          .MainFileIncludes);
}

TEST(PreamblePatchTest, Define) {
  // BAR should be defined while parsing the AST.
  struct {
    const char *const Contents;
    const char *const ExpectedPatch;
  } Cases[] = {
      {
          R"cpp(
        #define BAR
        [[BAR]])cpp",
          R"cpp(#line 0 ".*main.cpp"
#undef BAR
#line 2
#define         BAR
)cpp",
      },
      // multiline macro
      {
          R"cpp(
        #define BAR \

        [[BAR]])cpp",
          R"cpp(#line 0 ".*main.cpp"
#undef BAR
#line 2
#define         BAR
)cpp",
      },
      // multiline macro
      {
          R"cpp(
        #define \
                BAR
        [[BAR]])cpp",
          R"cpp(#line 0 ".*main.cpp"
#undef BAR
#line 3
#define         BAR
)cpp",
      },
  };

  for (const auto &Case : Cases) {
    SCOPED_TRACE(Case.Contents);
    llvm::Annotations Modified(Case.Contents);
    EXPECT_THAT(getPreamblePatch("", Modified.code()),
                MatchesRegex(Case.ExpectedPatch));

    auto AST = createPatchedAST("", Modified.code());
    ASSERT_TRUE(AST);
    std::vector<llvm::Annotations::Range> MacroRefRanges;
    for (auto &M : AST->getMacros().MacroRefs) {
      for (auto &O : M.getSecond())
        MacroRefRanges.push_back({O.StartOffset, O.EndOffset});
    }
    EXPECT_THAT(MacroRefRanges, Contains(Modified.range()));
  }
}

TEST(PreamblePatchTest, OrderingPreserved) {
  llvm::StringLiteral Baseline = "#define BAR(X) X";
  Annotations Modified(R"cpp(
    #define BAR(X, Y) X Y
    #define BAR(X) X
    [[BAR]](int y);
  )cpp");

  llvm::StringLiteral ExpectedPatch(R"cpp(#line 0 ".*main.cpp"
#undef BAR
#line 2
#define     BAR\(X, Y\) X Y
#undef BAR
#line 3
#define     BAR\(X\) X
)cpp");
  EXPECT_THAT(getPreamblePatch(Baseline, Modified.code()),
              MatchesRegex(ExpectedPatch.str()));

  auto AST = createPatchedAST(Baseline, Modified.code());
  ASSERT_TRUE(AST);
}

TEST(PreamblePatchTest, LocateMacroAtWorks) {
  struct {
    const char *const Baseline;
    const char *const Modified;
  } Cases[] = {
      // Addition of new directive
      {
          "",
          R"cpp(
            #define $def^FOO
            $use^FOO)cpp",
      },
      // Available inside preamble section
      {
          "",
          R"cpp(
            #define $def^FOO
            #undef $use^FOO)cpp",
      },
      // Available after undef, as we don't patch those
      {
          "",
          R"cpp(
            #define $def^FOO
            #undef FOO
            $use^FOO)cpp",
      },
      // Identifier on a different line
      {
          "",
          R"cpp(
            #define \
              $def^FOO
            $use^FOO)cpp",
      },
      // In presence of comment tokens
      {
          "",
          R"cpp(
            #\
              define /* FOO */\
              /* FOO */ $def^FOO
            $use^FOO)cpp",
      },
      // Moved around
      {
          "#define FOO",
          R"cpp(
            #define BAR
            #define $def^FOO
            $use^FOO)cpp",
      },
  };
  for (const auto &Case : Cases) {
    SCOPED_TRACE(Case.Modified);
    llvm::Annotations Modified(Case.Modified);
    auto AST = createPatchedAST(Case.Baseline, Modified.code());
    ASSERT_TRUE(AST);

    const auto &SM = AST->getSourceManager();
    auto *MacroTok = AST->getTokens().spelledTokenContaining(
        SM.getComposedLoc(SM.getMainFileID(), Modified.point("use")));
    ASSERT_TRUE(MacroTok);

    auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());
    ASSERT_TRUE(FoundMacro);
    EXPECT_THAT(FoundMacro->Name, "FOO");

    auto MacroLoc = FoundMacro->NameLoc;
    EXPECT_EQ(SM.getFileID(MacroLoc), SM.getMainFileID());
    EXPECT_EQ(SM.getFileOffset(MacroLoc), Modified.point("def"));
  }
}

TEST(PreamblePatchTest, LocateMacroAtDeletion) {
  {
    // We don't patch deleted define directives, make sure we don't crash.
    llvm::StringLiteral Baseline = "#define FOO";
    llvm::Annotations Modified("^FOO");

    auto AST = createPatchedAST(Baseline, Modified.code());
    ASSERT_TRUE(AST);

    const auto &SM = AST->getSourceManager();
    auto *MacroTok = AST->getTokens().spelledTokenContaining(
        SM.getComposedLoc(SM.getMainFileID(), Modified.point()));
    ASSERT_TRUE(MacroTok);

    auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());
    ASSERT_TRUE(FoundMacro);
    EXPECT_THAT(FoundMacro->Name, "FOO");
    auto HI =
        getHover(*AST, offsetToPosition(Modified.code(), Modified.point()),
                 format::getLLVMStyle(), nullptr);
    ASSERT_TRUE(HI);
    EXPECT_THAT(HI->Definition, testing::IsEmpty());
  }

  {
    // Offset is valid, but underlying text is different.
    llvm::StringLiteral Baseline = "#define FOO";
    Annotations Modified(R"cpp(#define BAR
    ^FOO")cpp");

    auto AST = createPatchedAST(Baseline, Modified.code());
    ASSERT_TRUE(AST);

    auto HI = getHover(*AST, Modified.point(), format::getLLVMStyle(), nullptr);
    ASSERT_TRUE(HI);
    EXPECT_THAT(HI->Definition, "#define BAR");
  }
}

MATCHER_P(referenceRangeIs, R, "") { return arg.Loc.range == R; }

TEST(PreamblePatchTest, RefsToMacros) {
  struct {
    const char *const Baseline;
    const char *const Modified;
  } Cases[] = {
      // Newly added
      {
          "",
          R"cpp(
            #define ^FOO
            ^[[FOO]])cpp",
      },
      // Moved around
      {
          "#define FOO",
          R"cpp(
            #define BAR
            #define ^FOO
            ^[[FOO]])cpp",
      },
      // Ref in preamble section
      {
          "",
          R"cpp(
            #define ^FOO
            #undef ^FOO)cpp",
      },
  };

  for (const auto &Case : Cases) {
    Annotations Modified(Case.Modified);
    auto AST = createPatchedAST("", Modified.code());
    ASSERT_TRUE(AST);

    const auto &SM = AST->getSourceManager();
    std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
    for (const auto &R : Modified.ranges())
      ExpectedLocations.push_back(referenceRangeIs(R));

    for (const auto &P : Modified.points()) {
      auto *MacroTok =
          AST->getTokens().spelledTokenContaining(SM.getComposedLoc(
              SM.getMainFileID(),
              llvm::cantFail(positionToOffset(Modified.code(), P))));
      ASSERT_TRUE(MacroTok);
      EXPECT_THAT(findReferences(*AST, P, 0).References,
                  testing::ElementsAreArray(ExpectedLocations));
    }
  }
}

TEST(TranslatePreamblePatchLocation, Simple) {
  auto TU = TestTU::withHeaderCode(R"cpp(
    #line 3 "main.cpp"
    int foo();)cpp");
  // Presumed line/col needs to be valid in the main file.
  TU.Code = R"cpp(// line 1
    // line 2
    // line 3
    // line 4)cpp";
  TU.Filename = "main.cpp";
  TU.HeaderFilename = "__preamble_patch__.h";
  TU.ImplicitHeaderGuard = false;

  auto AST = TU.build();
  auto &SM = AST.getSourceManager();
  auto &ND = findDecl(AST, "foo");
  EXPECT_NE(SM.getFileID(ND.getLocation()), SM.getMainFileID());

  auto TranslatedLoc = translatePreamblePatchLocation(ND.getLocation(), SM);
  auto DecompLoc = SM.getDecomposedLoc(TranslatedLoc);
  EXPECT_EQ(DecompLoc.first, SM.getMainFileID());
  EXPECT_EQ(SM.getLineNumber(DecompLoc.first, DecompLoc.second), 3U);
}

TEST(PreamblePatch, ModifiedBounds) {
  struct {
    const char *const Baseline;
    const char *const Modified;
  } Cases[] = {
      // Size increased
      {
          "",
          R"cpp(
            #define FOO
            FOO)cpp",
      },
      // Stayed same
      {"#define FOO", "#define BAR"},
      // Got smaller
      {
          R"cpp(
            #define FOO
            #undef FOO)cpp",
          "#define FOO"},
  };

  for (const auto &Case : Cases) {
    auto TU = TestTU::withCode(Case.Baseline);
    auto BaselinePreamble = TU.preamble();
    ASSERT_TRUE(BaselinePreamble);

    Annotations Modified(Case.Modified);
    TU.Code = Modified.code().str();
    MockFS FS;
    auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename),
                                             TU.inputs(FS), *BaselinePreamble);

    IgnoreDiagnostics Diags;
    auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);
    ASSERT_TRUE(CI);

    const auto ExpectedBounds =
        Lexer::ComputePreamble(Case.Modified, CI->getLangOpts());
    EXPECT_EQ(PP.modifiedBounds().Size, ExpectedBounds.Size);
    EXPECT_EQ(PP.modifiedBounds().PreambleEndsAtStartOfLine,
              ExpectedBounds.PreambleEndsAtStartOfLine);
  }
}

TEST(PreamblePatch, MacroLoc) {
  llvm::StringLiteral Baseline = "\n#define MACRO 12\nint num = MACRO;";
  llvm::StringLiteral Modified = " \n#define MACRO 12\nint num = MACRO;";
  auto AST = createPatchedAST(Baseline, Modified);
  ASSERT_TRUE(AST);
}

TEST(PreamblePatch, NoopWhenNotRequested) {
  llvm::StringLiteral Baseline = "#define M\nint num = M;";
  llvm::StringLiteral Modified = "#define M\n#include <foo.h>\nint num = M;";
  auto TU = TestTU::withCode(Baseline);
  auto BaselinePreamble = TU.preamble();
  ASSERT_TRUE(BaselinePreamble);

  TU.Code = Modified.str();
  MockFS FS;
  auto PP = PreamblePatch::createMacroPatch(testPath(TU.Filename),
                                            TU.inputs(FS), *BaselinePreamble);
  EXPECT_TRUE(PP.text().empty());
}

::testing::Matcher<const Diag &>
withNote(::testing::Matcher<Note> NoteMatcher) {
  return Field(&Diag::Notes, ElementsAre(NoteMatcher));
}
MATCHER_P(Diag, Range, "Diag at " + llvm::to_string(Range)) {
  return arg.Range == Range;
}
MATCHER_P2(Diag, Range, Name,
           "Diag at " + llvm::to_string(Range) + " = [" + Name + "]") {
  return arg.Range == Range && arg.Name == Name;
}

TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) {
  {
    Annotations Code("#define FOO");
    // Check with removals from preamble.
    Annotations NewCode("[[x]];/* error-ok */");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(),
                ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));
  }
  {
    // Check with additions to preamble.
    Annotations Code("#define FOO");
    Annotations NewCode(R"(
#define FOO
#define BAR
[[x]];/* error-ok */)");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(),
                ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));
  }
}

TEST(PreamblePatch, DiagnosticsToPreamble) {
  Config Cfg;
  Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;
  Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict;
  WithContextValue WithCfg(Config::Key, std::move(Cfg));

  llvm::StringMap<std::string> AdditionalFiles;
  AdditionalFiles["foo.h"] = "#pragma once";
  AdditionalFiles["bar.h"] = "#pragma once";
  {
    Annotations Code(R"(
// Test comment
[[#include "foo.h"]])");
    // Check with removals from preamble.
    Annotations NewCode(R"([[#  include "foo.h"]])");
    auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);
    EXPECT_THAT(AST->getDiagnostics(),
                ElementsAre(Diag(NewCode.range(), "unused-includes")));
  }
  {
    // Check with additions to preamble.
    Annotations Code(R"(
// Test comment
[[#include "foo.h"]])");
    Annotations NewCode(R"(
$bar[[#include "bar.h"]]
// Test comment
$foo[[#include "foo.h"]])");
    auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);
    EXPECT_THAT(
        AST->getDiagnostics(),
        UnorderedElementsAre(Diag(NewCode.range("bar"), "unused-includes"),
                             Diag(NewCode.range("foo"), "unused-includes")));
  }
  {
    Annotations Code("#define [[FOO]] 1\n");
    // Check ranges for notes.
    // This also makes sure we don't generate missing-include diagnostics
    // because macros are redefined in preamble-patch.
    Annotations NewCode(R"(#define BARXYZ 1
#define $foo1[[FOO]] 1
void foo();
#define $foo2[[FOO]] 2)");
    auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);
    EXPECT_THAT(
        AST->getDiagnostics(),
        ElementsAre(AllOf(Diag(NewCode.range("foo2"), "-Wmacro-redefined"),
                          withNote(Diag(NewCode.range("foo1"))))));
  }
}

TEST(PreamblePatch, TranslatesDiagnosticsInPreamble) {
  {
    // Check with additions to preamble.
    Annotations Code("#include [[<foo>]]");
    Annotations NewCode(R"(
#define BAR
#include [[<foo>]])");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(),
                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
  }
  {
    // Check with removals from preamble.
    Annotations Code(R"(
#define BAR
#include [[<foo>]])");
    Annotations NewCode("#include [[<foo>]]");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(),
                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
  }
  {
    // Drop line with diags.
    Annotations Code("#include [[<foo>]]");
    Annotations NewCode("#define BAR\n#define BAZ\n");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
  }
  {
    // Picks closest line in case of multiple alternatives.
    Annotations Code("#include [[<foo>]]");
    Annotations NewCode(R"(
#define BAR
#include [[<foo>]]
#define BAR
#include <foo>)");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(),
                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
  }
  {
    // Drop diag if line spelling has changed.
    Annotations Code("#include [[<foo>]]");
    Annotations NewCode(" # include <foo>");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
  }
  {
    // Multiple lines.
    Annotations Code(R"(
#define BAR
#include [[<fo\
o>]])");
    Annotations NewCode(R"(#include [[<fo\
o>]])");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(),
                ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
  }
  {
    // Multiple lines with change.
    Annotations Code(R"(
#define BAR
#include <fox>
#include [[<fo\
o>]])");
    Annotations NewCode(R"(#include <fo\
x>)");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
  }
  {
    // Preserves notes.
    Annotations Code(R"(
#define $note[[BAR]] 1
#define $main[[BAR]] 2)");
    Annotations NewCode(R"(
#define BAZ 0
#define $note[[BAR]] 1
#define BAZ 0
#define $main[[BAR]] 2)");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(
        AST->getDiagnostics(),
        ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),
                          withNote(Diag(NewCode.range("note"))))));
  }
  {
    // Preserves diag without note.
    Annotations Code(R"(
#define $note[[BAR]] 1
#define $main[[BAR]] 2)");
    Annotations NewCode(R"(
#define $main[[BAR]] 2)");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(
        AST->getDiagnostics(),
        ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),
                          Field(&Diag::Notes, IsEmpty()))));
  }
  {
    // Make sure orphaned notes are not promoted to diags.
    Annotations Code(R"(
#define $note[[BAR]] 1
#define $main[[BAR]] 2)");
    Annotations NewCode(R"(
#define BAZ 0
#define BAR 1)");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
  }
  {
    Annotations Code(R"(
#ifndef FOO
#define FOO
void foo();
#endif)");
    // This code will emit a diagnostic for unterminated #ifndef (as stale
    // preamble has the conditional but main file doesn't terminate it).
    // We shouldn't emit any diagnotiscs (and shouldn't crash).
    Annotations NewCode("");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
  }
  {
    Annotations Code(R"(
#ifndef FOO
#define FOO
void foo();
#endif)");
    // This code will emit a diagnostic for unterminated #ifndef (as stale
    // preamble has the conditional but main file doesn't terminate it).
    // We shouldn't emit any diagnotiscs (and shouldn't crash).
    // FIXME: Patch/ignore diagnostics in such cases.
    Annotations NewCode(R"(
i[[nt]] xyz;
    )");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(
        AST->getDiagnostics(),
        ElementsAre(Diag(NewCode.range(), "pp_unterminated_conditional")));
  }
}

MATCHER_P2(Mark, Range, Text, "") {
  return std::tie(arg.Rng, arg.Trivia) == std::tie(Range, Text);
}

TEST(PreamblePatch, MacroAndMarkHandling) {
  {
    Annotations Code(R"cpp(
#ifndef FOO
#define FOO
// Some comments
#pragma mark XX
#define BAR

#endif)cpp");
    Annotations NewCode(R"cpp(
#ifndef FOO
#define FOO
#define BAR
#pragma $x[[mark XX
]]
#pragma $y[[mark YY
]]
#define BAZ

#endif)cpp");
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    EXPECT_THAT(AST->getMacros().Names.keys(),
                UnorderedElementsAreArray({"FOO", "BAR", "BAZ"}));
    EXPECT_THAT(AST->getMarks(),
                UnorderedElementsAre(Mark(NewCode.range("x"), " XX"),
                                     Mark(NewCode.range("y"), " YY")));
  }
}

TEST(PreamblePatch, PatchFileEntry) {
  Annotations Code(R"cpp(#define FOO)cpp");
  Annotations NewCode(R"cpp(
#define BAR
#define FOO)cpp");
  {
    auto AST = createPatchedAST(Code.code(), Code.code());
    EXPECT_EQ(
        PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()),
        nullptr);
  }
  {
    auto AST = createPatchedAST(Code.code(), NewCode.code());
    auto FE =
        PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager());
    ASSERT_NE(FE, std::nullopt);
    EXPECT_THAT(FE->getName().str(),
                testing::EndsWith(PreamblePatch::HeaderName.str()));
  }
}

} // namespace
} // namespace clangd
} // namespace clang
