//===- clang-modernize/IncludeDirectivesTest.cpp --------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "Core/IncludeDirectives.h"
#include "common/VirtualFileHelper.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "llvm/Support/Path.h"
#include "gtest/gtest.h"

using namespace llvm;
using namespace clang;

/// \brief A convenience method around \c tooling::runToolOnCodeWithArgs() that
/// adds the current directory to the include search paths.
static void applyActionOnCode(FrontendAction *ToolAction, StringRef Code) {
  SmallString<128> CurrentDir;
  ASSERT_FALSE(llvm::sys::fs::current_path(CurrentDir));

  // Add the current directory to the header search paths so angled includes can
  // find them.
  std::vector<std::string> Args;
  Args.push_back("-I");
  Args.push_back(CurrentDir.str().str());

  // mapVirtualFile() needs absolute path for the input file as well.
  SmallString<128> InputFile(CurrentDir);
  sys::path::append(InputFile, "input.cc");

  ASSERT_TRUE(
      tooling::runToolOnCodeWithArgs(ToolAction, Code, Args, InputFile.str()));
}

namespace {
class TestAddIncludeAction : public PreprocessOnlyAction {
public:
  TestAddIncludeAction(StringRef Include, tooling::Replacements &Replaces,
                       const char *HeaderToModify = nullptr)
      : Include(Include), Replaces(Replaces), HeaderToModify(HeaderToModify) {
    // some headers that the tests can include
    mapVirtualHeader("foo-inner.h", "#pragma once\n");
    mapVirtualHeader("foo.h", "#pragma once\n"
                              "#include <foo-inner.h>\n");
    mapVirtualHeader("bar-inner.h", "#pragma once\n");
    mapVirtualHeader("bar.h", "#pragma once\n"
                              "#include <bar-inner.h>\n");
    mapVirtualHeader("xmacro.def", "X(Val1)\n"
                                   "X(Val2)\n"
                                   "X(Val3)\n");
  }

  /// \brief Make \p FileName an absolute path.
  ///
  /// Header files are mapped in the current working directory. The current
  /// working directory is used because it's important to map files with
  /// absolute paths.
  ///
  /// When used in conjunction with \c applyActionOnCode() (which adds the
  /// current working directory to the header search paths) it is possible to
  /// refer to the headers by using '\<FileName\>'.
  std::string makeHeaderFileName(StringRef FileName) const {
    SmallString<128> Path;
    std::error_code EC = llvm::sys::fs::current_path(Path);
    assert(!EC);
    (void)EC;

    sys::path::append(Path, FileName);
    return Path.str().str();
  }

  /// \brief Map additional header files.
  ///
  /// \sa makeHeaderFileName()
  void mapVirtualHeader(StringRef FileName, StringRef Content) {
    VFHelper.mapFile(makeHeaderFileName(FileName), Content);
  }

private:
  virtual bool BeginSourceFileAction(CompilerInstance &CI,
                                     StringRef FileName) override {
    if (!PreprocessOnlyAction::BeginSourceFileAction(CI, FileName))
      return false;
    VFHelper.mapVirtualFiles(CI.getSourceManager());

    FileToModify =
        HeaderToModify ? makeHeaderFileName(HeaderToModify) : FileName.str();

    FileIncludes.reset(new IncludeDirectives(CI));
    return true;
  }

  virtual void EndSourceFileAction() override {
    const tooling::Replacement &Replace =
        FileIncludes->addAngledInclude(FileToModify, Include);
    if (Replace.isApplicable())
      Replaces.insert(Replace);
  }

  StringRef Include;
  VirtualFileHelper VFHelper;
  tooling::Replacements &Replaces;
  std::unique_ptr<IncludeDirectives> FileIncludes;
  std::string FileToModify;
  // if non-null, add the include directives in this file instead of the main
  // file.
  const char *HeaderToModify;
};

std::string addIncludeInCode(StringRef Include, StringRef Code) {
  tooling::Replacements Replaces;

  applyActionOnCode(new TestAddIncludeAction(Include, Replaces), Code);

  if (::testing::Test::HasFailure())
    return "<<unexpected error from applyActionOnCode()>>";

  return tooling::applyAllReplacements(Code, Replaces);
}
} // end anonymous namespace

TEST(IncludeDirectivesTest2, endOfLinesVariants) {
  EXPECT_EQ("#include <foo.h>\n"
            "#include <bar>\n",
            addIncludeInCode("bar", "#include <foo.h>\n"));
  EXPECT_EQ("#include <foo.h>\r\n"
            "#include <bar>\r\n",
            addIncludeInCode("bar", "#include <foo.h>\r\n"));
  EXPECT_EQ("#include <foo.h>\r"
            "#include <bar>\r",
            addIncludeInCode("bar", "#include <foo.h>\r"));
}

TEST(IncludeDirectivesTest, ppToken) {
  EXPECT_EQ("#define FOO <foo.h>\n"
            "#include FOO\n"
            "#include <bar>\n"
            "int i;\n",
            addIncludeInCode("bar", "#define FOO <foo.h>\n"
                                    "#include FOO\n"
                                    "int i;\n"));
}

TEST(IncludeDirectivesTest, noFileHeader) {
  EXPECT_EQ("#include <bar>\n"
            "\n"
            "int foo;\n",
            addIncludeInCode("bar", "int foo;\n"));
}

TEST(IncludeDirectivesTest, commentBeforeTopMostCode) {
  EXPECT_EQ("#include <bar>\n"
            "\n"
            "// Foo\n"
            "int foo;\n",
            addIncludeInCode("bar", "// Foo\n"
                                    "int foo;\n"));
}

TEST(IncludeDirectivesTest, multiLineComment) {
  EXPECT_EQ("#include <foo.h> /* \n */\n"
            "#include <bar>\n",
            addIncludeInCode("bar", "#include <foo.h> /* \n */\n"));
  EXPECT_EQ("#include <foo.h> /* \n */"
            "\n#include <bar>",
            addIncludeInCode("bar", "#include <foo.h> /* \n */"));
}

TEST(IncludeDirectivesTest, multilineCommentWithTrailingSpace) {
  EXPECT_EQ("#include <foo.h> /*\n*/ \n"
            "#include <bar>\n",
            addIncludeInCode("bar", "#include <foo.h> /*\n*/ \n"));
  EXPECT_EQ("#include <foo.h> /*\n*/ "
            "\n#include <bar>",
            addIncludeInCode("bar", "#include <foo.h> /*\n*/ "));
}

TEST(IncludeDirectivesTest, fileHeaders) {
  EXPECT_EQ("// this is a header\n"
            "// some license stuff here\n"
            "\n"
            "#include <bar>\n"
            "\n"
            "/// \\brief Foo\n"
            "int foo;\n",
            addIncludeInCode("bar", "// this is a header\n"
                                    "// some license stuff here\n"
                                    "\n"
                                    "/// \\brief Foo\n"
                                    "int foo;\n"));
}

TEST(IncludeDirectivesTest, preferablyAngledNextToAngled) {
  EXPECT_EQ("#include <foo.h>\n"
            "#include <bar>\n"
            "#include \"bar.h\"\n",
            addIncludeInCode("bar", "#include <foo.h>\n"
                                    "#include \"bar.h\"\n"));
  EXPECT_EQ("#include \"foo.h\"\n"
            "#include \"bar.h\"\n"
            "#include <bar>\n",
            addIncludeInCode("bar", "#include \"foo.h\"\n"
                                    "#include \"bar.h\"\n"));
}

TEST(IncludeDirectivesTest, avoidDuplicates) {
  EXPECT_EQ("#include <foo.h>\n",
            addIncludeInCode("foo.h", "#include <foo.h>\n"));
}

// Tests includes in the middle of the code are ignored.
TEST(IncludeDirectivesTest, ignoreHeadersMeantForMultipleInclusion) {
  std::string Expected = "#include \"foo.h\"\n"
                         "#include <bar>\n"
                         "\n"
                         "enum Kind {\n"
                         "#define X(A) K_##A,\n"
                         "#include \"xmacro.def\"\n"
                         "#undef X\n"
                         "  K_NUM_KINDS\n"
                         "};\n";
  std::string Result = addIncludeInCode("bar", "#include \"foo.h\"\n"
                                               "\n"
                                               "enum Kind {\n"
                                               "#define X(A) K_##A,\n"
                                               "#include \"xmacro.def\"\n"
                                               "#undef X\n"
                                               "  K_NUM_KINDS\n"
                                               "};\n");
  EXPECT_EQ(Expected, Result);
}

namespace {
TestAddIncludeAction *makeIndirectTestsAction(const char *HeaderToModify,
                                              tooling::Replacements &Replaces) {
  StringRef IncludeToAdd = "c.h";
  TestAddIncludeAction *TestAction =
      new TestAddIncludeAction(IncludeToAdd, Replaces, HeaderToModify);
  TestAction->mapVirtualHeader("c.h", "#pragma once\n");
  TestAction->mapVirtualHeader("a.h", "#pragma once\n"
                                      "#include <c.h>\n");
  TestAction->mapVirtualHeader("b.h", "#pragma once\n");
  return TestAction;
}
} // end anonymous namespace

TEST(IncludeDirectivesTest, indirectIncludes) {
  // In TestAddIncludeAction 'foo.h' includes 'foo-inner.h'. Check that we
  // aren't including foo-inner.h again.
  EXPECT_EQ("#include <foo.h>\n",
            addIncludeInCode("foo-inner.h", "#include <foo.h>\n"));

  tooling::Replacements Replaces;
  StringRef Code = "#include <a.h>\n"
                   "#include <b.h>\n";

  // a.h already includes c.h
  {
    FrontendAction *Action = makeIndirectTestsAction("a.h", Replaces);
    ASSERT_NO_FATAL_FAILURE(applyActionOnCode(Action, Code));
    EXPECT_EQ(unsigned(0), Replaces.size());
  }

  // c.h is included before b.h but b.h doesn't include c.h directly, so check
  // that it will be inserted.
  {
    FrontendAction *Action = makeIndirectTestsAction("b.h", Replaces);
    ASSERT_NO_FATAL_FAILURE(applyActionOnCode(Action, Code));
    EXPECT_EQ("#include <c.h>\n\n\n",
              tooling::applyAllReplacements("\n", Replaces));
  }
}

/// \brief Convenience method to test header guards detection implementation.
static std::string addIncludeInGuardedHeader(StringRef IncludeToAdd,
                                             StringRef GuardedHeaderCode) {
  const char *GuardedHeaderName = "guarded.h";
  tooling::Replacements Replaces;
  TestAddIncludeAction *TestAction =
      new TestAddIncludeAction(IncludeToAdd, Replaces, GuardedHeaderName);
  TestAction->mapVirtualHeader(GuardedHeaderName, GuardedHeaderCode);

  applyActionOnCode(TestAction, "#include <guarded.h>\n");
  if (::testing::Test::HasFailure())
    return "<<unexpected error from applyActionOnCode()>>";

  return tooling::applyAllReplacements(GuardedHeaderCode, Replaces);
}

TEST(IncludeDirectivesTest, insertInsideIncludeGuard) {
  EXPECT_EQ("#ifndef GUARD_H\n"
            "#define GUARD_H\n"
            "\n"
            "#include <foo>\n"
            "\n"
            "struct foo {};\n"
            "\n"
            "#endif // GUARD_H\n",
            addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
                                             "#define GUARD_H\n"
                                             "\n"
                                             "struct foo {};\n"
                                             "\n"
                                             "#endif // GUARD_H\n"));
}

TEST(IncludeDirectivesTest, guardAndHeader) {
  EXPECT_EQ("// File header\n"
            "\n"
            "#ifndef GUARD_H\n"
            "#define GUARD_H\n"
            "\n"
            "#include <foo>\n"
            "\n"
            "struct foo {};\n"
            "\n"
            "#endif // GUARD_H\n",
            addIncludeInGuardedHeader("foo", "// File header\n"
                                             "\n"
                                             "#ifndef GUARD_H\n"
                                             "#define GUARD_H\n"
                                             "\n"
                                             "struct foo {};\n"
                                             "\n"
                                             "#endif // GUARD_H\n"));
}

TEST(IncludeDirectivesTest, fullHeaderFitsAsAPreamble) {
  EXPECT_EQ("#ifndef GUARD_H\n"
            "#define GUARD_H\n"
            "\n"
            "#include <foo>\n"
            "\n"
            "#define FOO 1\n"
            "\n"
            "#endif // GUARD_H\n",
            addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
                                             "#define GUARD_H\n"
                                             "\n"
                                             "#define FOO 1\n"
                                             "\n"
                                             "#endif // GUARD_H\n"));
}

TEST(IncludeDirectivesTest, codeBeforeIfndef) {
  EXPECT_EQ("#include <foo>\n"
            "\n"
            "int bar;\n"
            "\n"
            "#ifndef GUARD_H\n"
            "#define GUARD_H\n"
            "\n"
            "struct foo;"
            "\n"
            "#endif // GUARD_H\n",
            addIncludeInGuardedHeader("foo", "int bar;\n"
                                             "\n"
                                             "#ifndef GUARD_H\n"
                                             "#define GUARD_H\n"
                                             "\n"
                                             "struct foo;"
                                             "\n"
                                             "#endif // GUARD_H\n"));
}

TEST(IncludeDirectivesTest, codeAfterEndif) {
  EXPECT_EQ("#include <foo>\n"
            "\n"
            "#ifndef GUARD_H\n"
            "#define GUARD_H\n"
            "\n"
            "struct foo;"
            "\n"
            "#endif // GUARD_H\n"
            "\n"
            "int bar;\n",
            addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
                                             "#define GUARD_H\n"
                                             "\n"
                                             "struct foo;"
                                             "\n"
                                             "#endif // GUARD_H\n"
                                             "\n"
                                             "int bar;\n"));
}

TEST(IncludeDirectivesTest, headerGuardWithInclude) {
  EXPECT_EQ("#ifndef GUARD_H\n"
            "#define GUARD_H\n"
            "\n"
            "#include <bar.h>\n"
            "#include <foo>\n"
            "\n"
            "struct foo;\n"
            "\n"
            "#endif // GUARD_H\n",
            addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
                                             "#define GUARD_H\n"
                                             "\n"
                                             "#include <bar.h>\n"
                                             "\n"
                                             "struct foo;\n"
                                             "\n"
                                             "#endif // GUARD_H\n"));
}
