Add an IncludeInserter to clang-tidy.

Will be used to allow checks to insert includes at the right position.

llvm-svn: 244586
diff --git a/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp b/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp
new file mode 100644
index 0000000..72b5fc3
--- /dev/null
+++ b/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp
@@ -0,0 +1,407 @@
+//===---- IncludeInserterTest.cpp - clang-tidy ----------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../clang-tidy/IncludeInserter.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "ClangTidyTest.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace {
+
+class IncludeInserterCheckBase : public ClangTidyCheck {
+public:
+  using ClangTidyCheck::ClangTidyCheck;
+  void registerPPCallbacks(CompilerInstance &Compiler) override {
+    Inserter.reset(new IncludeInserter(Compiler.getSourceManager(),
+                                       Compiler.getLangOpts(),
+                                       IncludeSorter::IS_Google));
+    Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks());
+  }
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override {
+    Finder->addMatcher(ast_matchers::declStmt().bind("stmt"), this);
+  }
+
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
+    auto Fixit =
+        Inserter->CreateIncludeInsertion(Result.SourceManager->getMainFileID(),
+                                         HeaderToInclude(), IsAngledInclude());
+    if (Fixit) {
+      diag(Result.Nodes.getStmtAs<DeclStmt>("stmt")->getLocStart(), "foo, bar")
+          << *Fixit;
+    }
+    // Second include should yield no Fixit.
+    Fixit =
+        Inserter->CreateIncludeInsertion(Result.SourceManager->getMainFileID(),
+                                         HeaderToInclude(), IsAngledInclude());
+    EXPECT_FALSE(Fixit);
+  }
+
+  virtual StringRef HeaderToInclude() const = 0;
+  virtual bool IsAngledInclude() const = 0;
+
+  std::unique_ptr<IncludeInserter> Inserter;
+};
+
+class NonSystemHeaderInserterCheck : public IncludeInserterCheckBase {
+public:
+  using IncludeInserterCheckBase::IncludeInserterCheckBase;
+  StringRef HeaderToInclude() const override { return "path/to/header.h"; }
+  bool IsAngledInclude() const override { return false; }
+};
+
+class CXXSystemIncludeInserterCheck : public IncludeInserterCheckBase {
+public:
+  using IncludeInserterCheckBase::IncludeInserterCheckBase;
+  StringRef HeaderToInclude() const override { return "set"; }
+  bool IsAngledInclude() const override { return true; }
+};
+
+template <typename Check>
+std::string runCheckOnCode(StringRef Code, StringRef Filename,
+                           size_t NumWarningsExpected) {
+  std::vector<ClangTidyError> Errors;
+  return test::runCheckOnCode<Check>(Code, &Errors, Filename, None,
+                                     ClangTidyOptions(),
+                                     {// Main file include
+                                      {"devtools/cymbal/clang_tidy/tests/"
+                                       "insert_includes_test_header.h",
+                                       "\n"},
+                                      // Non system headers
+                                      {"path/to/a/header.h", "\n"},
+                                      {"path/to/z/header.h", "\n"},
+                                      {"path/to/header.h", "\n"},
+                                      // Fake system headers.
+                                      {"stdlib.h", "\n"},
+                                      {"unistd.h", "\n"},
+                                      {"list", "\n"},
+                                      {"map", "\n"},
+                                      {"set", "\n"},
+                                      {"vector", "\n"}});
+  EXPECT_EQ(NumWarningsExpected, Errors.size());
+}
+
+TEST(IncludeInserterTest, InsertAfterLastNonSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_input2.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertBeforeFirstNonSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_input2.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertBetweenNonSystemIncludes) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_input2.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, NonSystemIncludeAlreadyIncluded) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  EXPECT_EQ(PreCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                         PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                  "insert_includes_test_input2.cc",
+                         0));
+}
+
+TEST(IncludeInserterTest, InsertNonSystemIncludeAfterLastCXXSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertNonSystemIncludeAfterMainFileInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include "path/to/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterLastCXXSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+#include <set>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeBeforeFirstCXXSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <set>
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeBetweenCXXSystemIncludes) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <map>
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterMainFileInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <set>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterCSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <stdlib.h>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <stdlib.h>
+
+#include <set>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+} // namespace
+} // namespace tidy
+} // namespace clang