text: //===--- DiagnosticsTests.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 "Diagnostics.h"
  #include "ParsedAST.h"
  #include "Path.h"
  #include "Protocol.h"
  #include "SourceCode.h"
  #include "TestFS.h"
  #include "TestIndex.h"
  #include "TestTU.h"
  #include "index/MemIndex.h"
  #include "clang/Basic/Diagnostic.h"
  #include "clang/Basic/DiagnosticSema.h"
  #include "llvm/Support/ScopedPrinter.h"
  #include "llvm/Support/TargetSelect.h"
  #include "gmock/gmock.h"
  #include "gtest/gtest.h"
  #include <algorithm>
  
  namespace clang {
  namespace clangd {
  namespace {
  
  using ::testing::_;
  using ::testing::ElementsAre;
  using ::testing::Field;
  using ::testing::IsEmpty;
  using ::testing::Pair;
  using ::testing::UnorderedElementsAre;
  
  ::testing::Matcher<const Diag &> WithFix(::testing::Matcher<Fix> FixMatcher) {
    return Field(&Diag::Fixes, ElementsAre(FixMatcher));
  }
  
  ::testing::Matcher<const Diag &> WithFix(::testing::Matcher<Fix> FixMatcher1,
                                           ::testing::Matcher<Fix> FixMatcher2) {
    return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2));
  }
  
  ::testing::Matcher<const Diag &>
  WithNote(::testing::Matcher<Note> NoteMatcher) {
    return Field(&Diag::Notes, ElementsAre(NoteMatcher));
  }
  
  MATCHER_P2(Diag, Range, Message,
             "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
    return arg.Range == Range && arg.Message == Message;
  }
  
  MATCHER_P3(Fix, Range, Replacement, Message,
             "Fix " + llvm::to_string(Range) + " => " +
                 ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
    return arg.Message == Message && arg.Edits.size() == 1 &&
           arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
  }
  
  MATCHER_P(FixMessage, Message, "") {
    return arg.Message == Message;
  }
  
  MATCHER_P(EqualToLSPDiag, LSPDiag,
            "LSP diagnostic " + llvm::to_string(LSPDiag)) {
    if (toJSON(arg) != toJSON(LSPDiag)) {
      *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
                                        toJSON(LSPDiag), toJSON(arg))
                              .str();
      return false;
    }
    return true;
  }
  
  MATCHER_P(DiagSource, S, "") { return arg.Source == S; }
  MATCHER_P(DiagName, N, "") { return arg.Name == N; }
  MATCHER_P(DiagSeverity, S, "") { return arg.Severity == S; }
  
  MATCHER_P(EqualToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {
    if (arg.Message != Fix.Message)
      return false;
    if (arg.Edits.size() != Fix.Edits.size())
      return false;
    for (std::size_t I = 0; I < arg.Edits.size(); ++I) {
      if (arg.Edits[I].range != Fix.Edits[I].range ||
          arg.Edits[I].newText != Fix.Edits[I].newText)
        return false;
    }
    return true;
  }
  
  // Helper function to make tests shorter.
  Position pos(int line, int character) {
    Position Res;
    Res.line = line;
    Res.character = character;
    return Res;
  }
  
  TEST(DiagnosticsTest, DiagnosticRanges) {
    // Check we report correct ranges, including various edge-cases.
    Annotations Test(R"cpp(
      // error-ok
      namespace test{};
      void $decl[[foo]]();
      class T{$explicit[[]]$constructor[[T]](int a);};
      int main() {
        $typo[[go\
  o]]();
        foo()$semicolon[[]]//with comments
        $unk[[unknown]]();
        double $type[[bar]] = "foo";
        struct Foo { int x; }; Foo a;
        a.$nomember[[y]];
        test::$nomembernamespace[[test]];
      }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    TU.ClangTidyChecks = "-*,google-explicit-constructor";
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        ElementsAre(
            // This range spans lines.
            AllOf(Diag(Test.range("typo"),
                       "use of undeclared identifier 'goo'; did you mean 'foo'?"),
                  DiagSource(Diag::Clang), DiagName("undeclared_var_use_suggest"),
                  WithFix(
                      Fix(Test.range("typo"), "foo", "change 'go\\…' to 'foo'")),
                  // This is a pretty normal range.
                  WithNote(Diag(Test.range("decl"), "'foo' declared here"))),
            // This range is zero-width and insertion. Therefore make sure we are
            // not expanding it into other tokens. Since we are not going to
            // replace those.
            AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"),
                  WithFix(Fix(Test.range("semicolon"), ";", "insert ';'"))),
            // This range isn't provided by clang, we expand to the token.
            Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"),
            Diag(Test.range("type"),
                 "cannot initialize a variable of type 'double' with an lvalue "
                 "of type 'const char [4]'"),
            Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"),
            Diag(Test.range("nomembernamespace"),
                 "no member named 'test' in namespace 'test'"),
            // We make sure here that the entire token is highlighted
            AllOf(Diag(Test.range("constructor"),
                       "single-argument constructors must be marked explicit to "
                       "avoid unintentional implicit conversions"),
                  WithFix(Fix(Test.range("explicit"), "explicit ",
                              "insert 'explicit '")))));
  }
  
  TEST(DiagnosticsTest, FlagsMatter) {
    Annotations Test("[[void]] main() {} // error-ok");
    auto TU = TestTU::withCode(Test.code());
    EXPECT_THAT(TU.build().getDiagnostics(),
                ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
                                  WithFix(Fix(Test.range(), "int",
                                              "change 'void' to 'int'")))));
    // Same code built as C gets different diagnostics.
    TU.Filename = "Plain.c";
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        ElementsAre(AllOf(
            Diag(Test.range(), "return type of 'main' is not 'int'"),
            WithFix(Fix(Test.range(), "int", "change return type to 'int'")))));
  }
  
  TEST(DiagnosticsTest, DiagnosticPreamble) {
    Annotations Test(R"cpp(
      #include $[["not-found.h"]] // error-ok
    )cpp");
  
    auto TU = TestTU::withCode(Test.code());
    EXPECT_THAT(TU.build().getDiagnostics(),
                ElementsAre(::testing::AllOf(
                    Diag(Test.range(), "'not-found.h' file not found"),
                    DiagSource(Diag::Clang), DiagName("pp_file_not_found"))));
  }
  
  TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
    Annotations Test(R"cpp(
      float foo = [[0.1f]];
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    // Enable alias clang-tidy checks, these check emits the same diagnostics
    // (except the check name).
    TU.ClangTidyChecks = "-*, readability-uppercase-literal-suffix, "
                         "hicpp-uppercase-literal-suffix";
    // Verify that we filter out the duplicated diagnostic message.
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(::testing::AllOf(
            Diag(Test.range(),
                 "floating point literal has suffix 'f', which is not uppercase"),
            DiagSource(Diag::ClangTidy))));
  
    Test = Annotations(R"cpp(
      template<typename T>
      void func(T) {
        float f = [[0.3f]];
      }
      void k() {
        func(123);
        func(2.0);
      }
    )cpp");
    TU.Code = std::string(Test.code());
    // The check doesn't handle template instantiations which ends up emitting
    // duplicated messages, verify that we deduplicate them.
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(::testing::AllOf(
            Diag(Test.range(),
                 "floating point literal has suffix 'f', which is not uppercase"),
            DiagSource(Diag::ClangTidy))));
  }
  
  TEST(DiagnosticsTest, ClangTidy) {
    Annotations Test(R"cpp(
      #include $deprecated[["assert.h"]]
  
      #define $macrodef[[SQUARE]](X) (X)*(X)
      int $main[[main]]() {
        int y = 4;
        return SQUARE($macroarg[[++]]y);
        return $doubled[[sizeof]](sizeof(int));
      }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    TU.HeaderFilename = "assert.h"; // Suppress "not found" error.
    TU.ClangTidyChecks =
        "-*, bugprone-sizeof-expression, bugprone-macro-repeated-side-effects, "
        "modernize-deprecated-headers, modernize-use-trailing-return-type";
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(
            AllOf(Diag(Test.range("deprecated"),
                       "inclusion of deprecated C++ header 'assert.h'; consider "
                       "using 'cassert' instead"),
                  DiagSource(Diag::ClangTidy),
                  DiagName("modernize-deprecated-headers"),
                  WithFix(Fix(Test.range("deprecated"), "<cassert>",
                              "change '\"assert.h\"' to '<cassert>'"))),
            Diag(Test.range("doubled"),
                 "suspicious usage of 'sizeof(sizeof(...))'"),
            AllOf(
                Diag(Test.range("macroarg"),
                     "side effects in the 1st macro argument 'X' are repeated in "
                     "macro expansion"),
                DiagSource(Diag::ClangTidy),
                DiagName("bugprone-macro-repeated-side-effects"),
                WithNote(
                    Diag(Test.range("macrodef"), "macro 'SQUARE' defined here"))),
            Diag(Test.range("macroarg"),
                 "multiple unsequenced modifications to 'y'"),
            AllOf(
              Diag(Test.range("main"),
                   "use a trailing return type for this function"),
              DiagSource(Diag::ClangTidy),
              DiagName("modernize-use-trailing-return-type"),
              // Verify that we don't have "[check-name]" suffix in the message.
              WithFix(FixMessage("use a trailing return type for this function")))
            ));
  }
  
  TEST(DiagnosticTest, ClangTidySuppressionComment) {
    Annotations Main(R"cpp(
      int main() {
        int i = 3;
        double d = 8 / i;  // NOLINT
        // NOLINTNEXTLINE
        double e = 8 / i;
        double f = [[8]] / i;
      }
    )cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.ClangTidyChecks = "bugprone-integer-division";
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(::testing::AllOf(
            Diag(Main.range(), "result of integer division used in a floating "
                               "point context; possible loss of precision"),
            DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"))));
  }
  
  TEST(DiagnosticTest, ClangTidyWarningAsError) {
    Annotations Main(R"cpp(
      int main() {
        int i = 3;
        double f = [[8]] / i; // error-ok
      }
    )cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.ClangTidyChecks = "bugprone-integer-division";
    TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(::testing::AllOf(
            Diag(Main.range(), "result of integer division used in a floating "
                               "point context; possible loss of precision"),
            DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"),
            DiagSeverity(DiagnosticsEngine::Error))));
  }
  
  TEST(DiagnosticTest, LongFixMessages) {
    // We limit the size of printed code.
    Annotations Source(R"cpp(
      int main() {
        // error-ok
        int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
        [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
      }
    )cpp");
    TestTU TU = TestTU::withCode(Source.code());
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        ElementsAre(WithFix(Fix(
            Source.range(),
            "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
            "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
            "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
    // Only show changes up to a first newline.
    Source = Annotations(R"cpp(
      // error-ok
      int main() {
        int ident;
        [[ide\
  n]] = 10; // error-ok
      }
    )cpp");
    TU.Code = std::string(Source.code());
    EXPECT_THAT(TU.build().getDiagnostics(),
                ElementsAre(WithFix(
                    Fix(Source.range(), "ident", "change 'ide\\…' to 'ident'"))));
  }
  
  TEST(DiagnosticTest, ClangTidyWarningAsErrorTrumpsSuppressionComment) {
    Annotations Main(R"cpp(
      int main() {
        int i = 3;
        double f = [[8]] / i;  // NOLINT // error-ok
      }
    )cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.ClangTidyChecks = "bugprone-integer-division";
    TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(::testing::AllOf(
            Diag(Main.range(), "result of integer division used in a floating "
                               "point context; possible loss of precision"),
            DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"),
            DiagSeverity(DiagnosticsEngine::Error))));
  }
  
  TEST(DiagnosticsTest, Preprocessor) {
    // This looks like a preamble, but there's an #else in the middle!
    // Check that:
    //  - the #else doesn't generate diagnostics (we had this bug)
    //  - we get diagnostics from the taken branch
    //  - we get no diagnostics from the not taken branch
    Annotations Test(R"cpp(
      #ifndef FOO
      #define FOO
        int a = [[b]]; // error-ok
      #else
        int x = y;
      #endif
      )cpp");
    EXPECT_THAT(
        TestTU::withCode(Test.code()).build().getDiagnostics(),
        ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
  }
  
  TEST(DiagnosticsTest, InsideMacros) {
    Annotations Test(R"cpp(
      #define TEN 10
      #define RET(x) return x + 10
  
      int* foo() {
        RET($foo[[0]]); // error-ok
      }
      int* bar() {
        return $bar[[TEN]];
      }
      )cpp");
    EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(),
                ElementsAre(Diag(Test.range("foo"),
                                 "cannot initialize return object of type "
                                 "'int *' with an rvalue of type 'int'"),
                            Diag(Test.range("bar"),
                                 "cannot initialize return object of type "
                                 "'int *' with an rvalue of type 'int'")));
  }
  
  TEST(DiagnosticsTest, NoFixItInMacro) {
    Annotations Test(R"cpp(
      #define Define(name) void name() {}
  
      [[Define]](main) // error-ok
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    EXPECT_THAT(TU.build().getDiagnostics(),
                ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
                                  Not(WithFix(_)))));
  }
  
  TEST(ClangdTest, MSAsm) {
    // Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.
    // We used to crash here. Now clang emits a diagnostic, which we filter out.
    llvm::InitializeAllTargetInfos(); // As in ClangdMain
    auto TU = TestTU::withCode("void fn() { __asm { cmp cl,64 } }");
    TU.ExtraArgs = {"-fms-extensions"};
    EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
  }
  
  TEST(DiagnosticsTest, ToLSP) {
    URIForFile MainFile =
        URIForFile::canonicalize(testPath("foo/bar/main.cpp"), "");
    URIForFile HeaderFile =
        URIForFile::canonicalize(testPath("foo/bar/header.h"), "");
  
    clangd::Diag D;
    D.ID = clang::diag::err_enum_class_reference;
    D.Name = "enum_class_reference";
    D.Source = clangd::Diag::Clang;
    D.Message = "something terrible happened";
    D.Range = {pos(1, 2), pos(3, 4)};
    D.InsideMainFile = true;
    D.Severity = DiagnosticsEngine::Error;
    D.File = "foo/bar/main.cpp";
    D.AbsFile = std::string(MainFile.file());
  
    clangd::Note NoteInMain;
    NoteInMain.Message = "declared somewhere in the main file";
    NoteInMain.Range = {pos(5, 6), pos(7, 8)};
    NoteInMain.Severity = DiagnosticsEngine::Remark;
    NoteInMain.File = "../foo/bar/main.cpp";
    NoteInMain.InsideMainFile = true;
    NoteInMain.AbsFile = std::string(MainFile.file());
  
    D.Notes.push_back(NoteInMain);
  
    clangd::Note NoteInHeader;
    NoteInHeader.Message = "declared somewhere in the header file";
    NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
    NoteInHeader.Severity = DiagnosticsEngine::Note;
    NoteInHeader.File = "../foo/baz/header.h";
    NoteInHeader.InsideMainFile = false;
    NoteInHeader.AbsFile = std::string(HeaderFile.file());
    D.Notes.push_back(NoteInHeader);
  
    clangd::Fix F;
    F.Message = "do something";
    D.Fixes.push_back(F);
  
    // Diagnostics should turn into these:
    clangd::Diagnostic MainLSP;
    MainLSP.range = D.Range;
    MainLSP.severity = getSeverity(DiagnosticsEngine::Error);
    MainLSP.code = "enum_class_reference";
    MainLSP.source = "clang";
    MainLSP.message =
        R"(Something terrible happened (fix available)
  
  main.cpp:6:7: remark: declared somewhere in the main file
  
  ../foo/baz/header.h:10:11:
  note: declared somewhere in the header file)";
  
    clangd::Diagnostic NoteInMainLSP;
    NoteInMainLSP.range = NoteInMain.Range;
    NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);
    NoteInMainLSP.message = R"(Declared somewhere in the main file
  
  main.cpp:2:3: error: something terrible happened)";
  
    ClangdDiagnosticOptions Opts;
    // Transform diagnostics and check the results.
    std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
    toLSPDiags(D, MainFile, Opts,
               [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
                 LSPDiags.push_back(
                     {std::move(LSPDiag),
                      std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
               });
  
    EXPECT_THAT(
        LSPDiags,
        ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))),
                    Pair(EqualToLSPDiag(NoteInMainLSP), IsEmpty())));
    EXPECT_EQ(LSPDiags[0].first.code, "enum_class_reference");
    EXPECT_EQ(LSPDiags[0].first.source, "clang");
    EXPECT_EQ(LSPDiags[1].first.code, "");
    EXPECT_EQ(LSPDiags[1].first.source, "");
  
    // Same thing, but don't flatten notes into the main list.
    LSPDiags.clear();
    Opts.EmitRelatedLocations = true;
    toLSPDiags(D, MainFile, Opts,
               [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
                 LSPDiags.push_back(
                     {std::move(LSPDiag),
                      std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
               });
    MainLSP.message = "Something terrible happened (fix available)";
    DiagnosticRelatedInformation NoteInMainDRI;
    NoteInMainDRI.message = "Declared somewhere in the main file";
    NoteInMainDRI.location.range = NoteInMain.Range;
    NoteInMainDRI.location.uri = MainFile;
    MainLSP.relatedInformation = {NoteInMainDRI};
    DiagnosticRelatedInformation NoteInHeaderDRI;
    NoteInHeaderDRI.message = "Declared somewhere in the header file";
    NoteInHeaderDRI.location.range = NoteInHeader.Range;
    NoteInHeaderDRI.location.uri = HeaderFile;
    MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
    EXPECT_THAT(LSPDiags, ElementsAre(Pair(EqualToLSPDiag(MainLSP),
                                           ElementsAre(EqualToFix(F)))));
  }
  
  struct SymbolWithHeader {
    std::string QName;
    std::string DeclaringFile;
    std::string IncludeHeader;
  };
  
  std::unique_ptr<SymbolIndex>
  buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
    SymbolSlab::Builder Slab;
    for (const auto &S : Syms) {
      Symbol Sym = cls(S.QName);
      Sym.Flags |= Symbol::IndexedForCodeCompletion;
      Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
      Sym.Definition.FileURI = S.DeclaringFile.c_str();
      Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1);
      Slab.insert(Sym);
    }
    return MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
  }
  
  TEST(IncludeFixerTest, IncompleteType) {
    Annotations Test(R"cpp(// error-ok
  $insert[[]]namespace ns {
    class X;
    $nested[[X::]]Nested n;
  }
  class Y : $base[[public ns::X]] {};
  int main() {
    ns::X *x;
    x$access[[->]]f();
  }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    auto Index = buildIndexWithSymbol(
        {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(
            AllOf(Diag(Test.range("nested"),
                       "incomplete type 'ns::X' named in nested name specifier"),
                  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                              "Add include \"x.h\" for symbol ns::X"))),
            AllOf(Diag(Test.range("base"), "base class has incomplete type"),
                  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                              "Add include \"x.h\" for symbol ns::X"))),
            AllOf(Diag(Test.range("access"),
                       "member access into incomplete type 'ns::X'"),
                  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                              "Add include \"x.h\" for symbol ns::X")))));
  }
  
  TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
    Annotations Test(R"cpp(// error-ok
  $insert[[]]namespace ns {
    class X;
  }
  class Y : $base[[public ns::X]] {};
  int main() {
    ns::X *x;
    x$access[[->]]f();
  }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    Symbol Sym = cls("ns::X");
    Sym.Flags |= Symbol::IndexedForCodeCompletion;
    Sym.CanonicalDeclaration.FileURI = "unittest:///x.h";
    Sym.Definition.FileURI = "unittest:///x.cc";
    Sym.IncludeHeaders.emplace_back("\"x.h\"", 1);
  
    SymbolSlab::Builder Slab;
    Slab.insert(Sym);
    auto Index =
        MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(
                    Diag(Test.range("base"), "base class has incomplete type"),
                    Diag(Test.range("access"),
                         "member access into incomplete type 'ns::X'")));
  }
  
  TEST(IncludeFixerTest, Typo) {
    Annotations Test(R"cpp(// error-ok
  $insert[[]]namespace ns {
  void foo() {
    $unqualified1[[X]] x;
    // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
    // considered the unresolved type.
    $unqualified2[[X]]::Nested n;
  }
  }
  void bar() {
    ns::$qualified1[[X]] x; // ns:: is valid.
    ns::$qualified2[[X]](); // Error: no member in namespace
  
    ::$global[[Global]] glob;
  }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    auto Index = buildIndexWithSymbol(
        {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""},
         SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""}});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(
            AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
                  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                              "Add include \"x.h\" for symbol ns::X"))),
            Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
            AllOf(Diag(Test.range("qualified1"),
                       "no type named 'X' in namespace 'ns'"),
                  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                              "Add include \"x.h\" for symbol ns::X"))),
            AllOf(Diag(Test.range("qualified2"),
                       "no member named 'X' in namespace 'ns'"),
                  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                              "Add include \"x.h\" for symbol ns::X"))),
            AllOf(Diag(Test.range("global"),
                       "no type named 'Global' in the global namespace"),
                  WithFix(Fix(Test.range("insert"), "#include \"global.h\"\n",
                              "Add include \"global.h\" for symbol Global")))));
  }
  
  TEST(IncludeFixerTest, MultipleMatchedSymbols) {
    Annotations Test(R"cpp(// error-ok
  $insert[[]]namespace na {
  namespace nb {
  void foo() {
    $unqualified[[X]] x;
  }
  }
  }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    auto Index = buildIndexWithSymbol(
        {SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""},
         SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(AllOf(
                    Diag(Test.range("unqualified"), "unknown type name 'X'"),
                    WithFix(Fix(Test.range("insert"), "#include \"a.h\"\n",
                                "Add include \"a.h\" for symbol na::X"),
                            Fix(Test.range("insert"), "#include \"b.h\"\n",
                                "Add include \"b.h\" for symbol na::nb::X")))));
  }
  
  TEST(IncludeFixerTest, NoCrashMemebrAccess) {
    Annotations Test(R"cpp(// error-ok
      struct X { int  xyz; };
      void g() { X x; x.$[[xy]] }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    auto Index = buildIndexWithSymbol(
        SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
  }
  
  TEST(IncludeFixerTest, UseCachedIndexResults) {
    // As index results for the identical request are cached, more than 5 fixes
    // are generated.
    Annotations Test(R"cpp(// error-ok
  $insert[[]]void foo() {
    $x1[[X]] x;
    $x2[[X]] x;
    $x3[[X]] x;
    $x4[[X]] x;
    $x5[[X]] x;
    $x6[[X]] x;
    $x7[[X]] x;
  }
  
  class X;
  void bar(X *x) {
    x$a1[[->]]f();
    x$a2[[->]]f();
    x$a3[[->]]f();
    x$a4[[->]]f();
    x$a5[[->]]f();
    x$a6[[->]]f();
    x$a7[[->]]f();
  }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    auto Index =
        buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""});
    TU.ExternalIndex = Index.get();
  
    auto Parsed = TU.build();
    for (const auto &D : Parsed.getDiagnostics()) {
      if (D.Fixes.size() != 1) {
        ADD_FAILURE() << "D.Fixes.size() != 1";
        continue;
      }
      EXPECT_EQ(D.Fixes[0].Message,
                std::string("Add include \"a.h\" for symbol X"));
    }
  }
  
  TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
    Annotations Test(R"cpp(// error-ok
  $insert[[]]namespace ns {
  }
  void g() {  ns::$[[scope]]::X_Y();  }
    )cpp");
    TestTU TU;
    TU.Code = std::string(Test.code());
    // FIXME: Figure out why this is needed and remove it, PR43662.
    TU.ExtraArgs.push_back("-fno-ms-compatibility");
    auto Index = buildIndexWithSymbol(
        SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(AllOf(
            Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
            WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                        "Add include \"x.h\" for symbol ns::scope::X_Y")))));
  }
  
  TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
    Annotations Test(R"cpp(// error-ok
  $insert[[]]namespace clang {
  void f() {
    // "clangd::" will be corrected to "clang::" by Sema.
    $q1[[clangd]]::$x[[X]] x;
    $q2[[clangd]]::$ns[[ns]]::Y y;
  }
  }
    )cpp");
    TestTU TU;
    TU.Code = std::string(Test.code());
    // FIXME: Figure out why this is needed and remove it, PR43662.
    TU.ExtraArgs.push_back("-fno-ms-compatibility");
    auto Index = buildIndexWithSymbol(
        {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
         SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        UnorderedElementsAre(
            AllOf(
                Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
                                       "did you mean 'clang'?"),
                WithFix(_, // change clangd to clang
                        Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Add include \"x.h\" for symbol clang::clangd::X"))),
            AllOf(
                Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
                WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Add include \"x.h\" for symbol clang::clangd::X"))),
            AllOf(
                Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
                                       "did you mean 'clang'?"),
                WithFix(
                    _, // change clangd to clangd
                    Fix(Test.range("insert"), "#include \"y.h\"\n",
                        "Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
            AllOf(Diag(Test.range("ns"),
                       "no member named 'ns' in namespace 'clang'"),
                  WithFix(Fix(
                      Test.range("insert"), "#include \"y.h\"\n",
                      "Add include \"y.h\" for symbol clang::clangd::ns::Y")))));
  }
  
  TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
    Annotations Test(R"cpp(// error-ok
  $insert[[]]namespace a {}
  namespace b = a;
  namespace c {
    b::$[[X]] x;
  }
    )cpp");
    auto TU = TestTU::withCode(Test.code());
    auto Index = buildIndexWithSymbol(
        SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(AllOf(
                    Diag(Test.range(), "no type named 'X' in namespace 'a'"),
                    WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                                "Add include \"x.h\" for symbol a::X")))));
  }
  
  TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
    Annotations Test(R"cpp(
      template <typename T> struct Templ {
        template <typename U>
        typename U::type operator=(const U &);
      };
  
      struct A {
        Templ<char> s;
        A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
      };
    )cpp");
  
    auto TU = TestTU::withCode(Test.code());
    auto Index = buildIndexWithSymbol({});
    TU.ExternalIndex = Index.get();
  
    EXPECT_THAT(TU.build().getDiagnostics(),
                ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));
  }
  
  TEST(DiagsInHeaders, DiagInsideHeader) {
    Annotations Main(R"cpp(
      #include [["a.h"]]
      void foo() {})cpp");
    Annotations Header("[[no_type_spec]]; // error-ok");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(AllOf(
                    Diag(Main.range(), "in included file: C++ requires a "
                                       "type specifier for all declarations"),
                    WithNote(Diag(Header.range(), "error occurred here")))));
  }
  
  TEST(DiagsInHeaders, DiagInTransitiveInclude) {
    Annotations Main(R"cpp(
      #include [["a.h"]]
      void foo() {})cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", "#include \"b.h\""},
                          {"b.h", "no_type_spec; // error-ok"}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(
                    Diag(Main.range(), "in included file: C++ requires a "
                                       "type specifier for all declarations")));
  }
  
  TEST(DiagsInHeaders, DiagInMultipleHeaders) {
    Annotations Main(R"cpp(
      #include $a[["a.h"]]
      #include $b[["b.h"]]
      void foo() {})cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", "no_type_spec; // error-ok"},
                          {"b.h", "no_type_spec; // error-ok"}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(
                    Diag(Main.range("a"), "in included file: C++ requires a type "
                                          "specifier for all declarations"),
                    Diag(Main.range("b"), "in included file: C++ requires a type "
                                          "specifier for all declarations")));
  }
  
  TEST(DiagsInHeaders, PreferExpansionLocation) {
    Annotations Main(R"cpp(
      #include [["a.h"]]
      #include "b.h"
      void foo() {})cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {
        {"a.h", "#include \"b.h\"\n"},
        {"b.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(Diag(Main.range(),
                                          "in included file: C++ requires a type "
                                          "specifier for all declarations")));
  }
  
  TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
    Annotations Main(R"cpp(
      #define X
      #include "a.h"
      #undef X
      #include [["b.h"]]
      void foo() {})cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {
        {"a.h", "#include \"c.h\"\n"},
        {"b.h", "#include \"c.h\"\n"},
        {"c.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(
                    Diag(Main.range(), "in included file: C++ requires a "
                                       "type specifier for all declarations")));
  }
  
  TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
    Annotations Main(R"cpp(
      #include [["a.h"]]
      #include "b.h"
      void foo() {})cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},
                          {"b.h", "#include \"c.h\"\n"},
                          {"c.h", R"cpp(
        #ifndef X
        #define X
        no_type_spec_0; // error-ok
        no_type_spec_1;
        no_type_spec_2;
        no_type_spec_3;
        no_type_spec_4;
        no_type_spec_5;
        no_type_spec_6;
        no_type_spec_7;
        no_type_spec_8;
        no_type_spec_9;
        no_type_spec_10;
        #endif)cpp"}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(
                    Diag(Main.range(), "in included file: C++ requires a "
                                       "type specifier for all declarations")));
  }
  
  TEST(DiagsInHeaders, OnlyErrorOrFatal) {
    Annotations Main(R"cpp(
      #include [["a.h"]]
      void foo() {})cpp");
    Annotations Header(R"cpp(
      [[no_type_spec]]; // error-ok
      int x = 5/0;)cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(AllOf(
                    Diag(Main.range(), "in included file: C++ requires "
                                       "a type specifier for all declarations"),
                    WithNote(Diag(Header.range(), "error occurred here")))));
  }
  
  TEST(DiagsInHeaders, FromNonWrittenSources) {
    Annotations Main(R"cpp(
      #include [["a.h"]]
      void foo() {})cpp");
    Annotations Header(R"cpp(
      int x = 5/0;
      int b = [[FOO]]; // error-ok)cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
    TU.ExtraArgs = {"-DFOO=NOOO"};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(AllOf(
                    Diag(Main.range(),
                         "in included file: use of undeclared identifier 'NOOO'"),
                    WithNote(Diag(Header.range(), "error occurred here")))));
  }
  
  TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
    Annotations Main(R"cpp(
    void bar() {
      int fo; // error-ok
      #include [["a.h"]]
    })cpp");
    Annotations Header(R"cpp(
    #define X foo
    X;)cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(
                    Diag(Main.range(), "in included file: use of undeclared "
                                       "identifier 'foo'; did you mean 'fo'?")));
  }
  
  TEST(DiagsInHeaders, ErrorFromMacroArgument) {
    Annotations Main(R"cpp(
    void bar() {
      int fo; // error-ok
      #include [["a.h"]]
    })cpp");
    Annotations Header(R"cpp(
    #define X(arg) arg
    X(foo);)cpp");
    TestTU TU = TestTU::withCode(Main.code());
    TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
    EXPECT_THAT(TU.build().getDiagnostics(),
                UnorderedElementsAre(
                    Diag(Main.range(), "in included file: use of undeclared "
                                       "identifier 'foo'; did you mean 'fo'?")));
  }
  
  TEST(IgnoreDiags, FromNonWrittenInclude) {
    TestTU TU;
    TU.ExtraArgs.push_back("--include=a.h");
    TU.AdditionalFiles = {{"a.h", "void main();"}};
    // The diagnostic "main must return int" is from the header, we don't attempt
    // to render it in the main file as there is no written location there.
    EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
  }
  
  TEST(ToLSPDiag, RangeIsInMain) {
    ClangdDiagnosticOptions Opts;
    clangd::Diag D;
    D.Range = {pos(1, 2), pos(3, 4)};
    D.Notes.emplace_back();
    Note &N = D.Notes.back();
    N.Range = {pos(2, 3), pos(3, 4)};
  
    D.InsideMainFile = true;
    N.InsideMainFile = false;
    toLSPDiags(D, {}, Opts,
               [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
                 EXPECT_EQ(LSPDiag.range, D.Range);
               });
  
    D.InsideMainFile = false;
    N.InsideMainFile = true;
    toLSPDiags(D, {}, Opts,
               [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
                 EXPECT_EQ(LSPDiag.range, N.Range);
               });
  }
  
  } // namespace
  } // namespace clangd
  } // namespace clang
  