//===- unittests/AST/NamedDeclPrinterTest.cpp --- NamedDecl printer tests -===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains tests for NamedDecl::printQualifiedName().
//
// These tests have a coding convention:
// * declaration to be printed is named 'A' unless it should have some special
// name (e.g., 'operator+');
// * additional helper declarations are 'Z', 'Y', 'X' and so on.
//
//===----------------------------------------------------------------------===//

#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"

using namespace clang;
using namespace ast_matchers;
using namespace tooling;

namespace {

class PrintMatch : public MatchFinder::MatchCallback {
  SmallString<1024> Printed;
  unsigned NumFoundDecls;
  std::function<void(llvm::raw_ostream &OS, const NamedDecl *)> Printer;

public:
  explicit PrintMatch(
      std::function<void(llvm::raw_ostream &OS, const NamedDecl *)> Printer)
      : NumFoundDecls(0), Printer(std::move(Printer)) {}

  void run(const MatchFinder::MatchResult &Result) override {
    const NamedDecl *ND = Result.Nodes.getNodeAs<NamedDecl>("id");
    if (!ND)
      return;
    NumFoundDecls++;
    if (NumFoundDecls > 1)
      return;

    llvm::raw_svector_ostream Out(Printed);
    Printer(Out, ND);
  }

  StringRef getPrinted() const {
    return Printed;
  }

  unsigned getNumFoundDecls() const {
    return NumFoundDecls;
  }
};

::testing::AssertionResult PrintedDeclMatches(
    StringRef Code, const std::vector<std::string> &Args,
    const DeclarationMatcher &NodeMatch, StringRef ExpectedPrinted,
    StringRef FileName,
    std::function<void(llvm::raw_ostream &, const NamedDecl *)> Print) {
  PrintMatch Printer(std::move(Print));
  MatchFinder Finder;
  Finder.addMatcher(NodeMatch, &Printer);
  std::unique_ptr<FrontendActionFactory> Factory =
      newFrontendActionFactory(&Finder);

  if (!runToolOnCodeWithArgs(Factory->create(), Code, Args, FileName))
    return testing::AssertionFailure()
        << "Parsing error in \"" << Code.str() << "\"";

  if (Printer.getNumFoundDecls() == 0)
    return testing::AssertionFailure()
        << "Matcher didn't find any named declarations";

  if (Printer.getNumFoundDecls() > 1)
    return testing::AssertionFailure()
        << "Matcher should match only one named declaration "
           "(found " << Printer.getNumFoundDecls() << ")";

  if (Printer.getPrinted() != ExpectedPrinted)
    return ::testing::AssertionFailure()
        << "Expected \"" << ExpectedPrinted.str() << "\", "
           "got \"" << Printer.getPrinted().str() << "\"";

  return ::testing::AssertionSuccess();
}

::testing::AssertionResult
PrintedNamedDeclMatches(StringRef Code, const std::vector<std::string> &Args,
                        bool SuppressUnwrittenScope,
                        const DeclarationMatcher &NodeMatch,
                        StringRef ExpectedPrinted, StringRef FileName) {
  return PrintedDeclMatches(Code, Args, NodeMatch, ExpectedPrinted, FileName,
                            [=](llvm::raw_ostream &Out, const NamedDecl *ND) {
                              auto Policy =
                                  ND->getASTContext().getPrintingPolicy();
                              Policy.SuppressUnwrittenScope =
                                  SuppressUnwrittenScope;
                              ND->printQualifiedName(Out, Policy);
                            });
}

::testing::AssertionResult
PrintedNamedDeclCXX98Matches(StringRef Code, StringRef DeclName,
                             StringRef ExpectedPrinted) {
  std::vector<std::string> Args(1, "-std=c++98");
  return PrintedNamedDeclMatches(Code, Args,
                                 /*SuppressUnwrittenScope*/ false,
                                 namedDecl(hasName(DeclName)).bind("id"),
                                 ExpectedPrinted, "input.cc");
}

::testing::AssertionResult
PrintedWrittenNamedDeclCXX11Matches(StringRef Code, StringRef DeclName,
                                    StringRef ExpectedPrinted) {
  std::vector<std::string> Args(1, "-std=c++11");
  return PrintedNamedDeclMatches(Code, Args,
                                 /*SuppressUnwrittenScope*/ true,
                                 namedDecl(hasName(DeclName)).bind("id"),
                                 ExpectedPrinted, "input.cc");
}

::testing::AssertionResult
PrintedWrittenPropertyDeclObjCMatches(StringRef Code, StringRef DeclName,
                                   StringRef ExpectedPrinted) {
  std::vector<std::string> Args{"-std=c++11", "-xobjective-c++"};
  return PrintedNamedDeclMatches(Code, Args,
                                 /*SuppressUnwrittenScope*/ true,
                                 objcPropertyDecl(hasName(DeclName)).bind("id"),
                                 ExpectedPrinted, "input.m");
}

::testing::AssertionResult
PrintedNestedNameSpecifierMatches(StringRef Code, StringRef DeclName,
                                  StringRef ExpectedPrinted) {
  std::vector<std::string> Args{"-std=c++11"};
  return PrintedDeclMatches(Code, Args, namedDecl(hasName(DeclName)).bind("id"),
                            ExpectedPrinted, "input.cc",
                            [](llvm::raw_ostream &Out, const NamedDecl *D) {
                              D->printNestedNameSpecifier(Out);
                            });
}

} // unnamed namespace

TEST(NamedDeclPrinter, TestNamespace1) {
  ASSERT_TRUE(PrintedNamedDeclCXX98Matches(
    "namespace { int A; }",
    "A",
    "(anonymous namespace)::A"));
}

TEST(NamedDeclPrinter, TestNamespace2) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "inline namespace Z { namespace { int A; } }",
    "A",
    "A"));
}

TEST(NamedDeclPrinter, TestUnscopedUnnamedEnum) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "enum { A };",
    "A",
    "A"));
}

TEST(NamedDeclPrinter, TestNamedEnum) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "enum X { A };",
    "A",
    "A"));
}

TEST(NamedDeclPrinter, TestScopedNamedEnum) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "enum class X { A };",
    "A",
    "X::A"));
}

TEST(NamedDeclPrinter, TestClassWithUnscopedUnnamedEnum) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "class X { enum { A }; };",
    "A",
    "X::A"));
}

TEST(NamedDeclPrinter, TestClassWithUnscopedNamedEnum) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "class X { enum Y { A }; };",
    "A",
    "X::A"));
}

TEST(NamedDeclPrinter, TestClassWithScopedNamedEnum) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "class X { enum class Y { A }; };",
    "A",
    "X::Y::A"));
}

TEST(NamedDeclPrinter, TestLinkageInNamespace) {
  ASSERT_TRUE(PrintedWrittenNamedDeclCXX11Matches(
    "namespace X { extern \"C\" { int A; } }",
    "A",
    "X::A"));
}

TEST(NamedDeclPrinter, TestObjCClassExtension) {
  const char *Code =
R"(
  @interface Obj
  @end

  @interface Obj ()
  @property(nonatomic) int property;
  @end
)";
  ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches(
    Code,
    "property",
    "Obj::property"));
}

TEST(NamedDeclPrinter, TestInstanceObjCClassExtension) {
  const char *Code =
R"(
@interface ObjC
@end
@interface ObjC () {
  char data; // legal with non-fragile ABI.
}
@end
)";

  std::vector<std::string> Args{
      "-std=c++11", "-xobjective-c++",
      "-fobjc-runtime=macosx" /*force to use non-fragile ABI*/};
  ASSERT_TRUE(PrintedNamedDeclMatches(Code, Args,
                                      /*SuppressUnwrittenScope*/ true,
                                      namedDecl(hasName("data")).bind("id"),
                                      // not "::data"
                                      "ObjC::data", "input.mm"));
}

TEST(NamedDeclPrinter, TestObjCClassExtensionWithGetter) {
  const char *Code =
R"(
  @interface Obj
  @end

  @interface Obj ()
  @property(nonatomic, getter=myPropertyGetter) int property;
  @end
)";
  ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches(
    Code,
    "property",
    "Obj::property"));
}

TEST(NamedDeclPrinter, NestedNameSpecifierSimple) {
  const char *Code =
      R"(
  namespace foo { namespace bar { void func(); }  }
)";
  ASSERT_TRUE(PrintedNestedNameSpecifierMatches(Code, "func", "foo::bar::"));
}

TEST(NamedDeclPrinter, NestedNameSpecifierTemplateArgs) {
  const char *Code =
      R"(
        template <class T> struct vector;
        template <> struct vector<int> { int method(); };
)";
  ASSERT_TRUE(
      PrintedNestedNameSpecifierMatches(Code, "method", "vector<int>::"));
}
