//===-- XRefsTests.cpp  ---------------------------*- C++ -*--------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Annotations.h"
#include "ClangdUnit.h"
#include "Matchers.h"
#include "XRefs.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/PCHContainerOperations.h"
#include "clang/Frontend/Utils.h"
#include "llvm/Support/Path.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {
using namespace llvm;

void PrintTo(const DocumentHighlight &V, std::ostream *O) {
  llvm::raw_os_ostream OS(*O);
  OS << V.range;
  if (V.kind == DocumentHighlightKind::Read)
    OS << "(r)";
  if (V.kind == DocumentHighlightKind::Write)
    OS << "(w)";
}

namespace {
using testing::ElementsAre;
using testing::Field;
using testing::Matcher;
using testing::UnorderedElementsAreArray;

// FIXME: this is duplicated with FileIndexTests. Share it.
ParsedAST build(StringRef Code) {
  auto CI = createInvocationFromCommandLine({"clang", "-xc++", "Foo.cpp"});
  auto Buf = MemoryBuffer::getMemBuffer(Code);
  auto AST = ParsedAST::Build(
      Context::empty(), std::move(CI), nullptr, std::move(Buf),
      std::make_shared<PCHContainerOperations>(), vfs::getRealFileSystem());
  assert(AST.hasValue());
  return std::move(*AST);
}

// Extracts ranges from an annotated example, and constructs a matcher for a
// highlight set. Ranges should be named $read/$write as appropriate.
Matcher<const std::vector<DocumentHighlight> &>
HighlightsFrom(const Annotations &Test) {
  std::vector<DocumentHighlight> Expected;
  auto Add = [&](const Range &R, DocumentHighlightKind K) {
    Expected.emplace_back();
    Expected.back().range = R;
    Expected.back().kind = K;
  };
  for (const auto &Range : Test.ranges())
    Add(Range, DocumentHighlightKind::Text);
  for (const auto &Range : Test.ranges("read"))
    Add(Range, DocumentHighlightKind::Read);
  for (const auto &Range : Test.ranges("write"))
    Add(Range, DocumentHighlightKind::Write);
  return UnorderedElementsAreArray(Expected);
}

TEST(HighlightsTest, All) {
  const char *Tests[] = {
      R"cpp(// Local variable
        int main() {
          int [[bonjour]];
          $write[[^bonjour]] = 2;
          int test1 = $read[[bonjour]];
        }
      )cpp",

      R"cpp(// Struct
        namespace ns1 {
        struct [[MyClass]] {
          static void foo([[MyClass]]*) {}
        };
        } // namespace ns1
        int main() {
          ns1::[[My^Class]]* Params;
        }
      )cpp",

      R"cpp(// Function
        int [[^foo]](int) {}
        int main() {
          [[foo]]([[foo]](42));
          auto *X = &[[foo]];
        }
      )cpp",
  };
  for (const char *Test : Tests) {
    Annotations T(Test);
    auto AST = build(T.code());
    EXPECT_THAT(findDocumentHighlights(Context::empty(), AST, T.point()),
                HighlightsFrom(T))
        << Test;
  }
}

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

TEST(GoToDefinition, All) {
  const char *Tests[] = {
      R"cpp(// Local variable
        int main() {
          [[int bonjour]];
          ^bonjour = 2;
          int test1 = bonjour;
        }
      )cpp",

      R"cpp(// Struct
        namespace ns1 {
        [[struct MyClass {}]];
        } // namespace ns1
        int main() {
          ns1::My^Class* Params;
        }
      )cpp",

      R"cpp(// Function definition via pointer
        [[int foo(int) {}]]
        int main() {
          auto *X = &^foo;
        }
      )cpp",

      R"cpp(// Function declaration via call
        [[int foo(int)]];
        int main() {
          return ^foo(42);
        }
      )cpp",

      R"cpp(// Field
        struct Foo { [[int x]]; };
        int main() {
          Foo bar;
          bar.^x;
        }
      )cpp",

      R"cpp(// Field, member initializer
        struct Foo {
          [[int x]];
          Foo() : ^x(0) {}
        };
      )cpp",

      R"cpp(// Field, GNU old-style field designator
        struct Foo { [[int x]]; };
        int main() {
          Foo bar = { ^x : 1 };
        }
      )cpp",

      R"cpp(// Field, field designator
        struct Foo { [[int x]]; };
        int main() {
          Foo bar = { .^x = 2 };
        }
      )cpp",

      R"cpp(// Method call
        struct Foo { [[int x()]]; };
        int main() {
          Foo bar;
          bar.^x();
        }
      )cpp",

      R"cpp(// Typedef
        [[typedef int Foo]];
        int main() {
          ^Foo bar;
        }
      )cpp",

      /* FIXME: clangIndex doesn't handle template type parameters
      R"cpp(// Template type parameter
        template <[[typename T]]>
        void foo() { ^T t; }
      )cpp", */

      R"cpp(// Namespace
        [[namespace ns {
        struct Foo { static void bar(); }
        }]] // namespace ns
        int main() { ^ns::Foo::bar(); }
      )cpp",

      R"cpp(// Macro
        #define MACRO 0
        #define [[MACRO 1]]
        int main() { return ^MACRO; }
        #define MACRO 2
        #undef macro
      )cpp",
  };
  for (const char *Test : Tests) {
    Annotations T(Test);
    auto AST = build(T.code());
    EXPECT_THAT(findDefinitions(Context::empty(), AST, T.point()),
                ElementsAre(RangeIs(T.range())))
        << Test;
  }
}

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