//===- unittest/Tooling/CommentHandlerTest.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 "TestVisitor.h"
#include "clang/Lex/Preprocessor.h"

namespace clang {

struct Comment {
  Comment(const std::string &Message, unsigned Line, unsigned Col)
    : Message(Message), Line(Line), Col(Col) { }

  std::string Message;
  unsigned Line, Col;
};

class CommentVerifier;
typedef std::vector<Comment> CommentList;

class CommentHandlerVisitor : public TestVisitor<CommentHandlerVisitor>,
                              public CommentHandler {
  typedef TestVisitor<CommentHandlerVisitor> base;

public:
  CommentHandlerVisitor() : base(), PP(nullptr), Verified(false) {}

  ~CommentHandlerVisitor() override {
    EXPECT_TRUE(Verified) << "CommentVerifier not accessed";
  }

  bool HandleComment(Preprocessor &PP, SourceRange Loc) override {
    assert(&PP == this->PP && "Preprocessor changed!");

    SourceLocation Start = Loc.getBegin();
    SourceManager &SM = PP.getSourceManager();
    std::string C(SM.getCharacterData(Start),
                  SM.getCharacterData(Loc.getEnd()));

    bool Invalid;
    unsigned CLine = SM.getSpellingLineNumber(Start, &Invalid);
    EXPECT_TRUE(!Invalid) << "Invalid line number on comment " << C;

    unsigned CCol = SM.getSpellingColumnNumber(Start, &Invalid);
    EXPECT_TRUE(!Invalid) << "Invalid column number on comment " << C;

    Comments.push_back(Comment(C, CLine, CCol));
    return false;
  }

  CommentVerifier GetVerifier();

protected:
  std::unique_ptr<ASTFrontendAction> CreateTestAction() override {
    return std::make_unique<CommentHandlerAction>(this);
  }

private:
  Preprocessor *PP;
  CommentList Comments;
  bool Verified;

  class CommentHandlerAction : public base::TestAction {
  public:
    CommentHandlerAction(CommentHandlerVisitor *Visitor)
        : TestAction(Visitor) { }

    bool BeginSourceFileAction(CompilerInstance &CI) override {
      CommentHandlerVisitor *V =
          static_cast<CommentHandlerVisitor*>(this->Visitor);
      V->PP = &CI.getPreprocessor();
      V->PP->addCommentHandler(V);
      return true;
    }

    void EndSourceFileAction() override {
      CommentHandlerVisitor *V =
          static_cast<CommentHandlerVisitor*>(this->Visitor);
      V->PP->removeCommentHandler(V);
    }
  };
};

class CommentVerifier {
  CommentList::const_iterator Current;
  CommentList::const_iterator End;
  Preprocessor *PP;

public:
  CommentVerifier(const CommentList &Comments, Preprocessor *PP)
      : Current(Comments.begin()), End(Comments.end()), PP(PP)
    { }

  CommentVerifier(CommentVerifier &&C) : Current(C.Current), End(C.End), PP(C.PP) {
    C.Current = C.End;
  }

  ~CommentVerifier() {
    if (Current != End) {
      EXPECT_TRUE(Current == End) << "Unexpected comment \""
        << Current->Message << "\" at line " << Current->Line << ", column "
        << Current->Col;
    }
  }

  void Match(const char *Message, unsigned Line, unsigned Col) {
    EXPECT_TRUE(Current != End) << "Comment " << Message << " not found";
    if (Current == End) return;

    const Comment &C = *Current;
    EXPECT_TRUE(C.Message == Message && C.Line == Line && C.Col == Col)
      <<   "Expected comment \"" << Message
      << "\" at line " << Line   << ", column " << Col
      << "\nActual comment   \"" << C.Message
      << "\" at line " << C.Line << ", column " << C.Col;

    ++Current;
  }
};

CommentVerifier CommentHandlerVisitor::GetVerifier() {
  Verified = true;
  return CommentVerifier(Comments, PP);
}


TEST(CommentHandlerTest, BasicTest1) {
  CommentHandlerVisitor Visitor;
  EXPECT_TRUE(Visitor.runOver("class X {}; int main() { return 0; }"));
  CommentVerifier Verifier = Visitor.GetVerifier();
}

TEST(CommentHandlerTest, BasicTest2) {
  CommentHandlerVisitor Visitor;
  EXPECT_TRUE(Visitor.runOver(
        "class X {}; int main() { /* comment */ return 0; }"));
  CommentVerifier Verifier = Visitor.GetVerifier();
  Verifier.Match("/* comment */", 1, 26);
}

TEST(CommentHandlerTest, BasicTest3) {
  CommentHandlerVisitor Visitor;
  EXPECT_TRUE(Visitor.runOver(
        "class X {}; // comment 1\n"
        "int main() {\n"
        "  // comment 2\n"
        "  return 0;\n"
        "}"));
  CommentVerifier Verifier = Visitor.GetVerifier();
  Verifier.Match("// comment 1", 1, 13);
  Verifier.Match("// comment 2", 3, 3);
}

TEST(CommentHandlerTest, IfBlock1) {
  CommentHandlerVisitor Visitor;
  EXPECT_TRUE(Visitor.runOver(
        "#if 0\n"
        "// ignored comment\n"
        "#endif\n"
        "// visible comment\n"));
  CommentVerifier Verifier = Visitor.GetVerifier();
  Verifier.Match("// visible comment", 4, 1);
}

TEST(CommentHandlerTest, IfBlock2) {
  CommentHandlerVisitor Visitor;
  EXPECT_TRUE(Visitor.runOver(
        "#define TEST        // visible_1\n"
        "#ifndef TEST        // visible_2\n"
        "                    // ignored_3\n"
        "# ifdef UNDEFINED   // ignored_4\n"
        "# endif             // ignored_5\n"
        "#elif defined(TEST) // visible_6\n"
        "# if 1              // visible_7\n"
        "                    // visible_8\n"
        "# else              // visible_9\n"
        "                    // ignored_10\n"
        "#  ifndef TEST      // ignored_11\n"
        "#  endif            // ignored_12\n"
        "# endif             // visible_13\n"
        "#endif              // visible_14\n"));

  CommentVerifier Verifier = Visitor.GetVerifier();
  Verifier.Match("// visible_1", 1, 21);
  Verifier.Match("// visible_2", 2, 21);
  Verifier.Match("// visible_6", 6, 21);
  Verifier.Match("// visible_7", 7, 21);
  Verifier.Match("// visible_8", 8, 21);
  Verifier.Match("// visible_9", 9, 21);
  Verifier.Match("// visible_13", 13, 21);
  Verifier.Match("// visible_14", 14, 21);
}

TEST(CommentHandlerTest, IfBlock3) {
  const char *Source =
        "/* commented out ...\n"
        "#if 0\n"
        "// enclosed\n"
        "#endif */";

  CommentHandlerVisitor Visitor;
  EXPECT_TRUE(Visitor.runOver(Source));
  CommentVerifier Verifier = Visitor.GetVerifier();
  Verifier.Match(Source, 1, 1);
}

TEST(CommentHandlerTest, PPDirectives) {
  CommentHandlerVisitor Visitor;
  EXPECT_TRUE(Visitor.runOver(
        "#warning Y   // ignored_1\n" // #warning takes whole line as message
        "#undef MACRO // visible_2\n"
        "#line 1      // visible_3\n"));

  CommentVerifier Verifier = Visitor.GetVerifier();
  Verifier.Match("// visible_2", 2, 14);
  Verifier.Match("// visible_3", 3, 14);
}

} // end namespace clang
