//===- unittest/AST/MatchVerifier.h - AST unit test support ---------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
//  Provides MatchVerifier, a base class to implement gtest matchers that
//  verify things that can be matched on the AST.
//
//  Also implements matchers based on MatchVerifier:
//  LocationVerifier and RangeVerifier to verify whether a matched node has
//  the expected source location or source range.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_UNITTESTS_AST_MATCHVERIFIER_H
#define LLVM_CLANG_UNITTESTS_AST_MATCHVERIFIER_H

#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Testing/CommandLineArgs.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"

namespace clang {
namespace ast_matchers {

/// \brief Base class for verifying some property of nodes found by a matcher.
template <typename NodeType>
class MatchVerifier : public MatchFinder::MatchCallback {
public:
  template <typename MatcherType>
  testing::AssertionResult match(const std::string &Code,
                                 const MatcherType &AMatcher) {
    std::vector<std::string> Args;
    return match(Code, AMatcher, Args, Lang_CXX03);
  }

  template <typename MatcherType>
  testing::AssertionResult match(const std::string &Code,
                                 const MatcherType &AMatcher, TestLanguage L) {
    std::vector<std::string> Args;
    return match(Code, AMatcher, Args, L);
  }

  template <typename MatcherType>
  testing::AssertionResult
  match(const std::string &Code, const MatcherType &AMatcher,
        std::vector<std::string> &Args, TestLanguage L);

  template <typename MatcherType>
  testing::AssertionResult match(const Decl *D, const MatcherType &AMatcher);

protected:
  void run(const MatchFinder::MatchResult &Result) override;
  virtual void verify(const MatchFinder::MatchResult &Result,
                      const NodeType &Node) {}

  void setFailure(const Twine &Result) {
    Verified = false;
    VerifyResult = Result.str();
  }

  void setSuccess() {
    Verified = true;
  }

private:
  bool Verified;
  std::string VerifyResult;
};

/// \brief Runs a matcher over some code, and returns the result of the
/// verifier for the matched node.
template <typename NodeType>
template <typename MatcherType>
testing::AssertionResult
MatchVerifier<NodeType>::match(const std::string &Code,
                               const MatcherType &AMatcher,
                               std::vector<std::string> &Args, TestLanguage L) {
  MatchFinder Finder;
  Finder.addMatcher(AMatcher.bind(""), this);
  std::unique_ptr<tooling::FrontendActionFactory> Factory(
      tooling::newFrontendActionFactory(&Finder));

  StringRef FileName;
  switch (L) {
  case Lang_C89:
    Args.push_back("-std=c89");
    FileName = "input.c";
    break;
  case Lang_C99:
    Args.push_back("-std=c99");
    FileName = "input.c";
    break;
  case Lang_CXX03:
    Args.push_back("-std=c++03");
    FileName = "input.cc";
    break;
  case Lang_CXX11:
    Args.push_back("-std=c++11");
    FileName = "input.cc";
    break;
  case Lang_CXX14:
    Args.push_back("-std=c++14");
    FileName = "input.cc";
    break;
  case Lang_CXX17:
    Args.push_back("-std=c++17");
    FileName = "input.cc";
    break;
  case Lang_CXX20:
    Args.push_back("-std=c++20");
    FileName = "input.cc";
    break;
  case Lang_OpenCL:
    Args.push_back("-cl-no-stdinc");
    FileName = "input.cl";
    break;
  case Lang_OBJC:
    Args.push_back("-fobjc-nonfragile-abi");
    FileName = "input.m";
    break;
  case Lang_OBJCXX:
    FileName = "input.mm";
    break;
  }

  // Default to failure in case callback is never called
  setFailure("Could not find match");
  if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args, FileName))
    return testing::AssertionFailure() << "Parsing error";
  if (!Verified)
    return testing::AssertionFailure() << VerifyResult;
  return testing::AssertionSuccess();
}

/// \brief Runs a matcher over some AST, and returns the result of the
/// verifier for the matched node.
template <typename NodeType> template <typename MatcherType>
testing::AssertionResult MatchVerifier<NodeType>::match(
    const Decl *D, const MatcherType &AMatcher) {
  MatchFinder Finder;
  Finder.addMatcher(AMatcher.bind(""), this);

  setFailure("Could not find match");
  Finder.match(*D, D->getASTContext());

  if (!Verified)
    return testing::AssertionFailure() << VerifyResult;
  return testing::AssertionSuccess();
}

template <typename NodeType>
void MatchVerifier<NodeType>::run(const MatchFinder::MatchResult &Result) {
  const NodeType *Node = Result.Nodes.getNodeAs<NodeType>("");
  if (!Node) {
    setFailure("Matched node has wrong type");
  } else {
    // Callback has been called, default to success.
    setSuccess();
    verify(Result, *Node);
  }
}

template <>
inline void
MatchVerifier<DynTypedNode>::run(const MatchFinder::MatchResult &Result) {
  BoundNodes::IDToNodeMap M = Result.Nodes.getMap();
  BoundNodes::IDToNodeMap::const_iterator I = M.find("");
  if (I == M.end()) {
    setFailure("Node was not bound");
  } else {
    // Callback has been called, default to success.
    setSuccess();
    verify(Result, I->second);
  }
}

/// \brief Verify whether a node has the correct source location.
///
/// By default, Node.getSourceLocation() is checked. This can be changed
/// by overriding getLocation().
template <typename NodeType>
class LocationVerifier : public MatchVerifier<NodeType> {
public:
  void expectLocation(unsigned Line, unsigned Column) {
    ExpectLine = Line;
    ExpectColumn = Column;
  }

protected:
  void verify(const MatchFinder::MatchResult &Result,
              const NodeType &Node) override {
    SourceLocation Loc = getLocation(Node);
    unsigned Line = Result.SourceManager->getSpellingLineNumber(Loc);
    unsigned Column = Result.SourceManager->getSpellingColumnNumber(Loc);
    if (Line != ExpectLine || Column != ExpectColumn) {
      std::string MsgStr;
      llvm::raw_string_ostream Msg(MsgStr);
      Msg << "Expected location <" << ExpectLine << ":" << ExpectColumn
          << ">, found <";
      Loc.print(Msg, *Result.SourceManager);
      Msg << '>';
      this->setFailure(Msg.str());
    }
  }

  virtual SourceLocation getLocation(const NodeType &Node) {
    return Node.getLocation();
  }

private:
  unsigned ExpectLine, ExpectColumn;
};

/// \brief Verify whether a node has the correct source range.
///
/// By default, Node.getSourceRange() is checked. This can be changed
/// by overriding getRange().
template <typename NodeType>
class RangeVerifier : public MatchVerifier<NodeType> {
public:
  void expectRange(unsigned BeginLine, unsigned BeginColumn,
                   unsigned EndLine, unsigned EndColumn) {
    ExpectBeginLine = BeginLine;
    ExpectBeginColumn = BeginColumn;
    ExpectEndLine = EndLine;
    ExpectEndColumn = EndColumn;
  }

protected:
  void verify(const MatchFinder::MatchResult &Result,
              const NodeType &Node) override {
    SourceRange R = getRange(Node);
    SourceLocation Begin = R.getBegin();
    SourceLocation End = R.getEnd();
    unsigned BeginLine = Result.SourceManager->getSpellingLineNumber(Begin);
    unsigned BeginColumn = Result.SourceManager->getSpellingColumnNumber(Begin);
    unsigned EndLine = Result.SourceManager->getSpellingLineNumber(End);
    unsigned EndColumn = Result.SourceManager->getSpellingColumnNumber(End);
    if (BeginLine != ExpectBeginLine || BeginColumn != ExpectBeginColumn ||
        EndLine != ExpectEndLine || EndColumn != ExpectEndColumn) {
      std::string MsgStr;
      llvm::raw_string_ostream Msg(MsgStr);
      Msg << "Expected range <" << ExpectBeginLine << ":" << ExpectBeginColumn
          << '-' << ExpectEndLine << ":" << ExpectEndColumn << ">, found <";
      Begin.print(Msg, *Result.SourceManager);
      Msg << '-';
      End.print(Msg, *Result.SourceManager);
      Msg << '>';
      this->setFailure(Msg.str());
    }
  }

  virtual SourceRange getRange(const NodeType &Node) {
    return Node.getSourceRange();
  }

private:
  unsigned ExpectBeginLine, ExpectBeginColumn, ExpectEndLine, ExpectEndColumn;
};

/// \brief Verify whether a node's dump contains a given substring.
class DumpVerifier : public MatchVerifier<DynTypedNode> {
public:
  void expectSubstring(const std::string &Str) {
    ExpectSubstring = Str;
  }

protected:
  void verify(const MatchFinder::MatchResult &Result,
              const DynTypedNode &Node) override {
    std::string DumpStr;
    llvm::raw_string_ostream Dump(DumpStr);
    Node.dump(Dump, *Result.Context);

    if (Dump.str().find(ExpectSubstring) == std::string::npos) {
      std::string MsgStr;
      llvm::raw_string_ostream Msg(MsgStr);
      Msg << "Expected dump substring <" << ExpectSubstring << ">, found <"
          << Dump.str() << '>';
      this->setFailure(Msg.str());
    }
  }

private:
  std::string ExpectSubstring;
};

/// \brief Verify whether a node's pretty print matches a given string.
class PrintVerifier : public MatchVerifier<DynTypedNode> {
public:
  void expectString(const std::string &Str) {
    ExpectString = Str;
  }

protected:
  void verify(const MatchFinder::MatchResult &Result,
              const DynTypedNode &Node) override {
    std::string PrintStr;
    llvm::raw_string_ostream Print(PrintStr);
    Node.print(Print, Result.Context->getPrintingPolicy());

    if (Print.str() != ExpectString) {
      std::string MsgStr;
      llvm::raw_string_ostream Msg(MsgStr);
      Msg << "Expected pretty print <" << ExpectString << ">, found <"
          << Print.str() << '>';
      this->setFailure(Msg.str());
    }
  }

private:
  std::string ExpectString;
};

} // end namespace ast_matchers
} // end namespace clang

#endif
