//===-- SelectionTests.cpp - ----------------------------------------------===//
//
// 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 "Selection.h"
#include "SourceCode.h"
#include "TestTU.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {
namespace {
using ::testing::UnorderedElementsAreArray;

SelectionTree makeSelectionTree(const StringRef MarkedCode, ParsedAST &AST) {
  Annotations Test(MarkedCode);
  switch (Test.points().size()) {
  case 1: // Point selection.
    return SelectionTree(AST.getASTContext(),
                         cantFail(positionToOffset(Test.code(), Test.point())));
  case 2: // Range selection.
    return SelectionTree(
        AST.getASTContext(),
        cantFail(positionToOffset(Test.code(), Test.points()[0])),
        cantFail(positionToOffset(Test.code(), Test.points()[1])));
  default:
    ADD_FAILURE() << "Expected 1-2 points for selection.\n" << MarkedCode;
    return SelectionTree(AST.getASTContext(), 0u, 0u);
  }
}

Range nodeRange(const SelectionTree::Node *N, ParsedAST &AST) {
  if (!N)
    return Range{};
  SourceManager &SM = AST.getSourceManager();
  StringRef Buffer = SM.getBufferData(SM.getMainFileID());
  SourceRange SR = N->ASTNode.getSourceRange();
  SR.setBegin(SM.getFileLoc(SR.getBegin()));
  SR.setEnd(SM.getFileLoc(SR.getEnd()));
  CharSourceRange R =
      Lexer::getAsCharRange(SR, SM, AST.getASTContext().getLangOpts());
  return Range{offsetToPosition(Buffer, SM.getFileOffset(R.getBegin())),
               offsetToPosition(Buffer, SM.getFileOffset(R.getEnd()))};
}

std::string nodeKind(const SelectionTree::Node *N) {
  if (!N)
    return "<null>";
  return N->ASTNode.getNodeKind().asStringRef().str();
}

std::vector<const SelectionTree::Node *> allNodes(const SelectionTree &T) {
  std::vector<const SelectionTree::Node *> Result = {T.root()};
  for (unsigned I = 0; I < Result.size(); ++I) {
    const SelectionTree::Node *N = Result[I];
    Result.insert(Result.end(), N->Children.begin(), N->Children.end());
  }
  return Result;
}

// Returns true if Common is a descendent of Root.
// Verifies nothing is selected above Common.
bool verifyCommonAncestor(const SelectionTree::Node *Root,
                          const SelectionTree::Node *Common,
                          StringRef MarkedCode) {
  if (Root == Common)
    return true;
  if (Root->Selected)
    ADD_FAILURE() << "Selected nodes outside common ancestor\n" << MarkedCode;
  bool Seen = false;
  for (const SelectionTree::Node *Child : Root->Children)
    if (verifyCommonAncestor(Child, Common, MarkedCode)) {
      if (Seen)
        ADD_FAILURE() << "Saw common ancestor twice\n" << MarkedCode;
      Seen = true;
    }
  return Seen;
}

TEST(SelectionTest, CommonAncestor) {
  struct Case {
    // Selection is between ^marks^.
    // common ancestor marked with a [[range]].
    const char *Code;
    const char *CommonAncestorKind;
  };
  Case Cases[] = {
      {
          R"cpp(
            template <typename T>
            int x = [[T::^U::]]ccc();
          )cpp",
          "NestedNameSpecifierLoc",
      },
      {
          R"cpp(
            struct AAA { struct BBB { static int ccc(); };};
            int x = AAA::[[B^B^B]]::ccc();
          )cpp",
          "TypeLoc",
      },
      {
          R"cpp(
            struct AAA { struct BBB { static int ccc(); };};
            int x = AAA::[[B^BB^]]::ccc();
          )cpp",
          "TypeLoc",
      },
      {
          R"cpp(
            struct AAA { struct BBB { static int ccc(); };};
            int x = [[AAA::BBB::c^c^c]]();
          )cpp",
          "DeclRefExpr",
      },
      {
          R"cpp(
            struct AAA { struct BBB { static int ccc(); };};
            int x = [[AAA::BBB::cc^c(^)]];
          )cpp",
          "CallExpr",
      },

      {
          R"cpp(
            void foo() { [[if (1^11) { return; } else {^ }]] }
          )cpp",
          "IfStmt",
      },
      {
          R"cpp(
            void foo();
            #define CALL_FUNCTION(X) X()
            void bar() { CALL_FUNCTION([[f^o^o]]); }
          )cpp",
          "DeclRefExpr",
      },
      {
          R"cpp(
            void foo();
            #define CALL_FUNCTION(X) X()
            void bar() [[{ CALL_FUNC^TION(fo^o); }]]
          )cpp",
          "CompoundStmt",
      },
      {
          R"cpp(
            void foo();
            #define CALL_FUNCTION(X) X()
            void bar() [[{ C^ALL_FUNC^TION(foo); }]]
          )cpp",
          "CompoundStmt",
      },
      {
          R"cpp(
            void foo();
            #define CALL_FUNCTION(X) X^()^
            void bar() { CALL_FUNCTION(foo); }
          )cpp",
          nullptr,
      },
      {
          R"cpp(
            struct S { S(const char*); };
            S [[s ^= "foo"]];
          )cpp",
          "CXXConstructExpr",
      },
      {
          R"cpp(
            struct S { S(const char*); };
            [[S ^s = "foo"]];
          )cpp",
          "VarDecl",
      },
      {
          R"cpp(
            [[^void]] (*S)(int) = nullptr;
          )cpp",
          "TypeLoc",
      },
      {
          R"cpp(
            [[void (*S)^(int)]] = nullptr;
          )cpp",
          "TypeLoc",
      },
      {
          R"cpp(
            [[void (^*S)(int)]] = nullptr;
          )cpp",
          "TypeLoc",
      },
      {
          R"cpp(
            [[void (*^S)(int) = nullptr]];
          )cpp",
          "VarDecl",
      },
      {
          R"cpp(
            [[void ^(*S)(int)]] = nullptr;
          )cpp",
          "TypeLoc",
      },

      // Point selections.
      {"void foo() { [[^foo]](); }", "DeclRefExpr"},
      {"void foo() { [[f^oo]](); }", "DeclRefExpr"},
      {"void foo() { [[fo^o]](); }", "DeclRefExpr"},
      {"void foo() { [[foo^()]]; }", "CallExpr"},
      {"void foo() { [[foo^]] (); }", "DeclRefExpr"},
      {"int bar; void foo() [[{ foo (); }]]^", "CompoundStmt"},

      // Tricky case: FunctionTypeLoc in FunctionDecl has a hole in it.
      {"[[^void]] foo();", "TypeLoc"},
      {"[[void foo^()]];", "TypeLoc"},
      {"[[^void foo^()]];", "FunctionDecl"},
      {"[[void ^foo()]];", "FunctionDecl"},
      // Tricky case: two VarDecls share a specifier.
      {"[[int ^a]], b;", "VarDecl"},
      {"[[int a, ^b]];", "VarDecl"},
      // Tricky case: anonymous struct is a sibling of the VarDecl.
      {"[[st^ruct {int x;}]] y;", "CXXRecordDecl"},
      {"[[struct {int x;} ^y]];", "VarDecl"},
      {"struct {[[int ^x]];} y;", "FieldDecl"},

      {"^", nullptr},
      {"void foo() { [[foo^^]] (); }", "DeclRefExpr"},

      // FIXME: Ideally we'd get a declstmt or the VarDecl itself here.
      // This doesn't happen now; the RAV doesn't traverse a node containing ;.
      {"int x = 42;^", nullptr},
      {"int x = 42^;", nullptr},

      // Node types that have caused problems in the past.
      {"template <typename T> void foo() { [[^T]] t; }", "TypeLoc"},

      // No crash
      {
          R"cpp(
            template <class T> struct Foo {};
            template <[[template<class> class /*cursor here*/^U]]>
             struct Foo<U<int>*> {};
          )cpp",
          "TemplateTemplateParmDecl"},
  };
  for (const Case &C : Cases) {
    Annotations Test(C.Code);
    auto AST = TestTU::withCode(Test.code()).build();
    auto T = makeSelectionTree(C.Code, AST);

    if (Test.ranges().empty()) {
      // If no [[range]] is marked in the example, there should be no selection.
      EXPECT_FALSE(T.commonAncestor()) << C.Code << "\n" << T;
      EXPECT_FALSE(T.root()) << C.Code << "\n" << T;
    } else {
      // If there is an expected selection, both common ancestor and root
      // should exist with the appropriate node types in them.
      EXPECT_EQ(C.CommonAncestorKind, nodeKind(T.commonAncestor()))
          << C.Code << "\n"
          << T;
      EXPECT_EQ("TranslationUnitDecl", nodeKind(T.root())) << C.Code;
      // Convert the reported common ancestor to a range and verify it.
      EXPECT_EQ(nodeRange(T.commonAncestor(), AST), Test.range())
          << C.Code << "\n"
          << T;

      // Check that common ancestor is reachable on exactly one path from root,
      // and no nodes outside it are selected.
      EXPECT_TRUE(verifyCommonAncestor(T.root(), T.commonAncestor(), C.Code))
          << C.Code;
    }
  }
}

// Regression test: this used to match the injected X, not the outer X.
TEST(SelectionTest, InjectedClassName) {
  const char* Code = "struct ^X { int x; };";
  auto AST = TestTU::withCode(Annotations(Code).code()).build();
  auto T = makeSelectionTree(Code, AST);
  ASSERT_EQ("CXXRecordDecl", nodeKind(T.commonAncestor())) << T;
  auto *D = dyn_cast<CXXRecordDecl>(T.commonAncestor()->ASTNode.get<Decl>());
  EXPECT_FALSE(D->isInjectedClassName());
}

TEST(SelectionTest, Selected) {
  // Selection with ^marks^.
  // Partially selected nodes marked with a [[range]].
  // Completely selected nodes marked with a $C[[range]].
  const char *Cases[] = {
      R"cpp( int abc, xyz = [[^ab^c]]; )cpp",
      R"cpp( int abc, xyz = [[a^bc^]]; )cpp",
      R"cpp( int abc, xyz = $C[[^abc^]]; )cpp",
      R"cpp(
        void foo() {
          [[if ([[1^11]]) $C[[{
            $C[[return]];
          }]] else [[{^
          }]]]]
        }
      )cpp",
      R"cpp(
          template <class T>
          struct unique_ptr {};
          void foo(^$C[[unique_ptr<unique_ptr<$C[[int]]>>]]^ a) {}
      )cpp",
  };
  for (const char *C : Cases) {
    Annotations Test(C);
    auto AST = TestTU::withCode(Test.code()).build();
    auto T = makeSelectionTree(C, AST);

    std::vector<Range> Complete, Partial;
    for (const SelectionTree::Node *N : allNodes(T))
      if (N->Selected == SelectionTree::Complete)
        Complete.push_back(nodeRange(N, AST));
      else if (N->Selected == SelectionTree::Partial)
        Partial.push_back(nodeRange(N, AST));
    EXPECT_THAT(Complete, UnorderedElementsAreArray(Test.ranges("C"))) << C;
    EXPECT_THAT(Partial, UnorderedElementsAreArray(Test.ranges())) << C;
  }
}

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