hokein updated this revision to Diff 71506.
hokein marked 2 inline comments as done.
hokein added a comment.
Herald added a subscriber: mgorny.

Address review comments.


https://reviews.llvm.org/D24243

Files:
  CMakeLists.txt
  clang-move/CMakeLists.txt
  clang-move/ClangMove.cpp
  clang-move/ClangMove.h
  clang-move/tool/CMakeLists.txt
  clang-move/tool/ClangMoveMain.cpp
  unittests/CMakeLists.txt
  unittests/clang-move/CMakeLists.txt
  unittests/clang-move/ClangMoveTests.cpp

Index: unittests/clang-move/ClangMoveTests.cpp
===================================================================
--- /dev/null
+++ unittests/clang-move/ClangMoveTests.cpp
@@ -0,0 +1,226 @@
+//===-- ClangMoveTest.cpp - clang-move unit tests -------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangMove.h"
+#include "unittests/Tooling/RewriterTestContext.h"
+#include "clang/Format/Format.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Frontend/TextDiagnosticPrinter.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/StringRef.h"
+#include "gtest/gtest.h"
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace move {
+namespace {
+
+const char TestHeaderName[] = "foo.h";
+
+const char TestCCName[] = "foo.cc";
+
+const char TestHeader[] = "namespace a {\n"
+                          "class C1;\n"
+                          "namespace b {\n"
+                          "class Foo {\n"
+                          "public:\n"
+                          "  void f();\n"
+                          "\n"
+                          "private:\n"
+                          "  C1 *c1;\n"
+                          "  static int b;\n"
+                          "};\n"
+                          "\n"
+                          "class Foo2 {\n"
+                          "public:\n"
+                          "  int f();\n"
+                          "};\n"
+                          "} // namespace b\n"
+                          "} // namespace a\n";
+
+const char TestCC[] = "#include \"foo.h\"\n"
+                      "namespace a {\n"
+                      "namespace b {\n"
+                      "namespace {\n"
+                      "void f1() {}\n"
+                      "int kConstInt1 = 0;\n"
+                      "} // namespace\n"
+                      "\n"
+                      "static int kConstInt2 = 1;\n"
+                      "\n"
+                      "static int help() {\n"
+                      "  int a = 0;\n"
+                      "  return a;\n"
+                      "}\n"
+                      "\n"
+                      "void Foo::f() { f1(); }\n"
+                      "\n"
+                      "int Foo::b = 2;\n"
+                      "int Foo2::f() {\n"
+                      "  f1();\n"
+                      "  return 1;\n"
+                      "}\n"
+                      "} // namespace b\n"
+                      "} // namespace a\n";
+
+const char ExpectedTestHeader[] = "namespace a {\n"
+                                  "class C1;\n"
+                                  "namespace b {\n"
+                                  "\n"
+                                  "class Foo2 {\n"
+                                  "public:\n"
+                                  "  int f();\n"
+                                  "};\n"
+                                  "} // namespace b\n"
+                                  "} // namespace a\n";
+
+const char ExpectedTestCC[] = "#include \"foo.h\"\n"
+                              "namespace a {\n"
+                              "namespace b {\n"
+                              "namespace {\n"
+                              "void f1() {}\n"
+                              "int kConstInt1 = 0;\n"
+                              "} // namespace\n"
+                              "\n"
+                              "static int kConstInt2 = 1;\n"
+                              "\n"
+                              "static int help() {\n"
+                              "  int a = 0;\n"
+                              "  return a;\n"
+                              "}\n"
+                              "\n"
+                              "int Foo2::f() {\n"
+                              "  f1();\n"
+                              "  return 1;\n"
+                              "}\n"
+                              "} // namespace b\n"
+                              "} // namespace a\n";
+
+const char ExpectedNewHeader[] = "namespace a {\n"
+                                 "class C1;\n"
+                                 "namespace b {\n"
+                                 "class Foo {\n"
+                                 "public:\n"
+                                 "  void f();\n"
+                                 "\n"
+                                 "private:\n"
+                                 "  C1 *c1;\n"
+                                 "  static int b;\n"
+                                 "};\n"
+                                 "} // namespace b\n"
+                                 "} // namespace a\n";
+
+const char ExpectedNewCC[] = "#include \"foo.h\"\n"
+                             "namespace a {\n"
+                             "namespace b {\n"
+                             "namespace {\n"
+                             "void f1() {}\n"
+                             "int kConstInt1 = 0;\n"
+                             "} // namespace\n"
+                             "static int kConstInt2 = 1;\n"
+                             "static int help() {\n"
+                             "  int a = 0;\n"
+                             "  return a;\n"
+                             "}\n"
+                             "void Foo::f() { f1(); }\n"
+                             "int Foo::b = 2;\n"
+                             "} // namespace b\n"
+                             "} // namespace a\n";
+
+std::map<std::string, std::string>
+runClangMoveOnCode(const move::ClangMoveTool::MoveDefinitionSpec &Spec) {
+  clang::RewriterTestContext Context;
+
+  std::map<llvm::StringRef, clang::FileID> FileToFileID;
+  std::vector<std::pair<std::string, std::string>> FileToSourceText = {
+      {TestHeaderName, TestHeader}, {TestCCName, TestCC}};
+
+  auto CreateFiles = [&FileToSourceText, &Context, &FileToFileID](
+      llvm::StringRef Name, llvm::StringRef Code) {
+    if (!Name.empty()) {
+      FileToSourceText.emplace_back(Name, Code);
+      FileToFileID[Name] = Context.createInMemoryFile(Name, Code);
+    }
+  };
+  CreateFiles(Spec.NewCC, "");
+  CreateFiles(Spec.NewHeader, "");
+  CreateFiles(Spec.OldHeader, TestHeader);
+  CreateFiles(Spec.OldCC, TestCC);
+
+  std::map<std::string, tooling::Replacements> FileToReplacements;
+  ClangMoveTool MoveTool(Spec, FileToReplacements);
+  auto Factory = llvm::make_unique<clang::move::ClangMoveActionFactory>(
+      Spec, FileToReplacements);
+
+  tooling::runToolOnCodeWithArgs(
+      Factory->create(), TestCC, {"-std=c++11"}, TestCCName, "clang-move",
+      std::make_shared<PCHContainerOperations>(), FileToSourceText);
+  formatAndApplyAllReplacements(FileToReplacements, Context.Rewrite, "llvm");
+  // The Key is file name, value is the new code after moving the class.
+  std::map<std::string, std::string> Results;
+  for (const auto &It : FileToReplacements) {
+    StringRef FilePath = It.first;
+    Results[FilePath] = Context.getRewrittenText(FileToFileID[FilePath]);
+  }
+  return Results;
+}
+
+TEST(ClangMove, MoveHeaderAndCC) {
+  move::ClangMoveTool::MoveDefinitionSpec Spec;
+  Spec.Name = "Foo";
+  Spec.OldHeader = "foo.h";
+  Spec.OldCC = "foo.cc";
+  Spec.NewHeader = "new_foo.h";
+  Spec.NewCC = "new_foo.cc";
+  auto Results = runClangMoveOnCode(Spec);
+  EXPECT_EQ(ExpectedTestHeader, Results[Spec.OldHeader]);
+  EXPECT_EQ(ExpectedTestCC, Results[Spec.OldCC]);
+  EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]);
+  EXPECT_EQ(ExpectedNewCC, Results[Spec.NewCC]);
+}
+
+TEST(ClangMove, MoveHeaderOnly) {
+  move::ClangMoveTool::MoveDefinitionSpec Spec;
+  Spec.Name = "Foo";
+  Spec.OldHeader = "foo.h";
+  Spec.NewHeader = "new_foo.h";
+  auto Results = runClangMoveOnCode(Spec);
+  EXPECT_EQ(2, Results.size());
+  EXPECT_EQ(ExpectedTestHeader, Results[Spec.OldHeader]);
+  EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]);
+}
+
+TEST(ClangMove, MoveCCOnly) {
+  move::ClangMoveTool::MoveDefinitionSpec Spec;
+  Spec.Name = "Foo";
+  Spec.OldCC = "foo.cc";
+  Spec.NewCC = "new_foo.cc";
+  auto Results = runClangMoveOnCode(Spec);
+  EXPECT_EQ(2, Results.size());
+  EXPECT_EQ(ExpectedTestCC, Results[Spec.OldCC]);
+  EXPECT_EQ(ExpectedNewCC, Results[Spec.NewCC]);
+}
+
+TEST(ClangMove, MoveNonExistClass) {
+  move::ClangMoveTool::MoveDefinitionSpec Spec;
+  Spec.Name = "NonExistFoo";
+  Spec.OldHeader = "foo.h";
+  Spec.OldCC = "foo.cc";
+  Spec.NewHeader = "new_foo.h";
+  Spec.NewCC = "new_foo.cc";
+  auto Results = runClangMoveOnCode(Spec);
+  EXPECT_EQ(0, Results.size());
+}
+
+} // namespace
+} // namespce move
+} // namespace clang
Index: unittests/clang-move/CMakeLists.txt
===================================================================
--- /dev/null
+++ unittests/clang-move/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(LLVM_LINK_COMPONENTS
+  support
+  )
+
+get_filename_component(INCLUDE_FIXER_SOURCE_DIR
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-move REALPATH)
+include_directories(
+  ${INCLUDE_FIXER_SOURCE_DIR}
+  )
+
+# We'd like to clang/unittests/Tooling/RewriterTestContext.h in the test.
+include_directories(${CLANG_SOURCE_DIR})
+
+add_extra_unittest(ClangMoveTests
+  ClangMoveTests.cpp
+  )
+
+target_link_libraries(ClangMoveTests
+  clangAST
+  clangASTMatchers
+  clangBasic
+  clangFormat
+  clangFrontend
+  clangMove
+  clangRewrite
+  clangTooling
+  clangToolingCore
+  )
Index: unittests/CMakeLists.txt
===================================================================
--- unittests/CMakeLists.txt
+++ unittests/CMakeLists.txt
@@ -6,7 +6,8 @@
 endfunction()
 
 add_subdirectory(clang-apply-replacements)
-add_subdirectory(clang-rename)
+add_subdirectory(clang-move)
 add_subdirectory(clang-query)
+add_subdirectory(clang-rename)
 add_subdirectory(clang-tidy)
 add_subdirectory(include-fixer)
Index: clang-move/tool/ClangMoveMain.cpp
===================================================================
--- /dev/null
+++ clang-move/tool/ClangMoveMain.cpp
@@ -0,0 +1,126 @@
+//===-- ClangMoveMain.cpp - move defintion to new file ----------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangMove.h"
+#include "clang/Frontend/TextDiagnosticPrinter.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/YAMLTraits.h"
+#include <set>
+#include <string>
+
+using namespace clang;
+using namespace llvm;
+
+namespace {
+std::error_code CreateNewFile(const llvm::Twine &path) {
+  int fd = 0;
+  if (std::error_code ec =
+          llvm::sys::fs::openFileForWrite(path, fd, llvm::sys::fs::F_Text))
+    return ec;
+
+  return llvm::sys::Process::SafelyCloseFileDescriptor(fd);
+}
+
+cl::OptionCategory ClangMoveCategory("clang-move options");
+
+cl::opt<std::string> Name("name", cl::desc("The name of class being moved."),
+                          cl::cat(ClangMoveCategory));
+
+cl::opt<std::string> OldHeader("old_header", cl::desc("Old header."),
+                               cl::cat(ClangMoveCategory));
+
+cl::opt<std::string> OldCC("old_cc", cl::desc("Old CC file."),
+                           cl::cat(ClangMoveCategory));
+
+cl::opt<std::string> NewHeader("new_header", cl::desc("New header."),
+                               cl::cat(ClangMoveCategory));
+
+cl::opt<std::string> NewCC("new_cc", cl::desc("New CC file."),
+                           cl::cat(ClangMoveCategory));
+
+cl::opt<std::string>
+    Style("style",
+          cl::desc("The style name used for reformatting. Default is \"llvm\""),
+          cl::init("llvm"), cl::cat(ClangMoveCategory));
+
+cl::opt<bool> Dump("dump_result",
+                   cl::desc("Dump results in JSON format to stdout."),
+                   cl::cat(ClangMoveCategory));
+
+} // namespace
+
+int main(int argc, const char **argv) {
+  tooling::CommonOptionsParser OptionsParser(argc, argv, ClangMoveCategory);
+  tooling::RefactoringTool Tool(OptionsParser.getCompilations(),
+                                OptionsParser.getSourcePathList());
+  move::ClangMoveTool::MoveDefinitionSpec Spec;
+  Spec.Name = Name;
+  Spec.OldHeader = OldHeader;
+  Spec.NewHeader = NewHeader;
+  Spec.OldCC = OldCC;
+  Spec.NewCC = NewCC;
+  auto Factory = llvm::make_unique<clang::move::ClangMoveActionFactory>(
+      Spec, Tool.getReplacements());
+  int CodeStatus = Tool.run(Factory.get());
+  if (CodeStatus)
+    return CodeStatus;
+
+  if (!NewCC.empty())
+    CreateNewFile(NewCC);
+  if (!NewHeader.empty())
+    CreateNewFile(NewHeader);
+
+  IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
+  clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
+  DiagnosticsEngine Diagnostics(
+      IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
+      &DiagnosticPrinter, false);
+  auto &FileMgr = Tool.getFiles();
+  SourceManager SM(Diagnostics, FileMgr);
+  Rewriter Rewrite(SM, LangOptions());
+
+  if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) {
+    llvm::errs() << "Failed applying all replacements.\n";
+    return 1;
+  }
+
+  if (Dump) {
+    std::set<llvm::StringRef> Files;
+    for (const auto &it : Tool.getReplacements())
+      Files.insert(it.first);
+    auto WriteToJson = [&](llvm::raw_ostream &OS) {
+      OS << "[\n";
+      for (auto File : Files) {
+        OS << "  {\n";
+        OS << "    \"FilePath\": \"" << File << "\",\n";
+        const auto *Entry = FileMgr.getFile(File);
+        auto ID = SM.translateFile(Entry);
+        std::string Content;
+        llvm::raw_string_ostream ContentStream(Content);
+        Rewrite.getEditBuffer(ID).write(ContentStream);
+        OS << "    \"SourceText\": \""
+           << llvm::yaml::escape(ContentStream.str()) << "\"\n";
+        OS << "  }";
+        if (File != *(--Files.end()))
+          OS << ",\n";
+      }
+      OS << "\n]\n";
+    };
+    WriteToJson(llvm::outs());
+    return 0;
+  }
+
+  return Rewrite.overwriteChangedFiles();
+}
Index: clang-move/tool/CMakeLists.txt
===================================================================
--- /dev/null
+++ clang-move/tool/CMakeLists.txt
@@ -0,0 +1,15 @@
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
+
+add_clang_executable(clang-move
+  ClangMoveMain.cpp
+  )
+
+target_link_libraries(clang-move
+  clangBasic
+  clangFormat
+  clangFrontend
+  clangMove
+  clangRewrite
+  clangTooling
+  clangToolingCore
+  )
Index: clang-move/ClangMove.h
===================================================================
--- /dev/null
+++ clang-move/ClangMove.h
@@ -0,0 +1,120 @@
+//===-- ClangMove.h - Clang move  -----------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Tooling.h"
+#include <map>
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace move {
+
+// FIXME: Make it support more types, e.g. function definitions.
+// Currently only support moving class definition.
+class ClangMoveTool : public ast_matchers::MatchFinder::MatchCallback {
+public:
+  // Information about the declaration being moved.
+  struct MovedDecl {
+    const clang::NamedDecl *Decl = nullptr;
+    clang::SourceManager *SM = nullptr;
+    MovedDecl() = default;
+    MovedDecl(const clang::NamedDecl *Decl, clang::SourceManager *SM)
+        : Decl(Decl), SM(SM) {}
+  };
+
+  struct MoveDefinitionSpec {
+    // A fully/partially qualified name, e.g. "X", "::a::X", "a::X".
+    // For partially qualified name, all the class whose name ends with `Name`
+    // will be moved.
+    std::string Name;
+    std::string OldHeader;
+    std::string OldCC;
+    std::string NewHeader;
+    std::string NewCC;
+  };
+
+  ClangMoveTool(
+      const MoveDefinitionSpec &Spec,
+      std::map<std::string, tooling::Replacements> &FileToReplacements)
+      : Spec(Spec), FileToReplacements(FileToReplacements) {}
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder);
+
+  void run(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+  void onEndOfTranslationUnit() override;
+
+  // Add #includes from old.h/cc files. The FileName is where the #include
+  // comes from.
+  void addIncludes(llvm::StringRef IncludeLine, llvm::StringRef FileName);
+
+private:
+  void removeClassDefinitionInOldFiles();
+  void moveClassDefinitionToNewFiles();
+
+  MoveDefinitionSpec Spec;
+  // The Key is file path, value is the replacements being applied to the file.
+  std::map<std::string, tooling::Replacements> &FileToReplacements;
+  // All declarations (the class decl being moved, forward decls) that need to
+  // be moved/copy to the new files, saving in an AST-visited order.
+  std::vector<MovedDecl> MovedDecls;
+  // The declarations that needs to be removed in old.cc/h.
+  std::vector<MovedDecl> RemovedDecls;
+  // The #includes in old_header.h.
+  std::vector<std::string> HeaderIncludes;
+  // The #includes in old_cc.cc.
+  std::vector<std::string> CCIncludes;
+};
+
+class ClangMoveAction : public clang::ASTFrontendAction {
+public:
+  ClangMoveAction(
+      const ClangMoveTool::MoveDefinitionSpec &spec,
+      std::map<std::string, tooling::Replacements> &FileToReplacements)
+      : MoveTool(spec, FileToReplacements) {
+    MoveTool.registerMatchers(&MatchFinder);
+  }
+
+  ~ClangMoveAction() override = default;
+
+  std::unique_ptr<clang::ASTConsumer>
+  CreateASTConsumer(clang::CompilerInstance &Compiler,
+                    llvm::StringRef InFile) override;
+
+private:
+  ast_matchers::MatchFinder MatchFinder;
+  ClangMoveTool MoveTool;
+};
+
+class ClangMoveActionFactory : public tooling::FrontendActionFactory {
+public:
+  ClangMoveActionFactory(
+      const ClangMoveTool::MoveDefinitionSpec &Spec,
+      std::map<std::string, tooling::Replacements> &FileToReplacements)
+      : Spec(Spec), FileToReplacements(FileToReplacements) {}
+
+  clang::FrontendAction *create() override {
+    return new ClangMoveAction(Spec, FileToReplacements);
+  }
+
+private:
+  const ClangMoveTool::MoveDefinitionSpec &Spec;
+  std::map<std::string, tooling::Replacements> &FileToReplacements;
+};
+
+} // namespace move
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H
Index: clang-move/ClangMove.cpp
===================================================================
--- /dev/null
+++ clang-move/ClangMove.cpp
@@ -0,0 +1,325 @@
+//===-- ClangMove.cpp - Implement ClangMove functationalities ---*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangMove.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Format/Format.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Core/Replacement.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace move {
+namespace {
+
+// FIXME: Move to ASTMatcher.
+AST_POLYMORPHIC_MATCHER(isStatic, AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl,
+                                                                  VarDecl)) {
+  return Node.getStorageClass() == SC_Static;
+}
+
+class FindAllIncludes : public clang::PPCallbacks {
+public:
+  explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool)
+      : SM(*SM), MoveTool(MoveTool) {}
+
+  void InclusionDirective(clang::SourceLocation HashLoc,
+                          const clang::Token & /*IncludeTok*/,
+                          StringRef FileName, bool IsAngled,
+                          clang::CharSourceRange /*FilenameRange*/,
+                          const clang::FileEntry * /*File*/,
+                          StringRef /*SearchPath*/, StringRef /*RelativePath*/,
+                          const clang::Module * /*Imported*/) override {
+    if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc))) {
+      if (IsAngled) {
+        MoveTool->addIncludes("#include <" + FileName.str() + ">\n",
+                              FileEntry->getName());
+      } else {
+        MoveTool->addIncludes("#include \"" + FileName.str() + "\"\n",
+                              FileEntry->getName());
+      }
+    }
+  }
+
+private:
+  const SourceManager &SM;
+  ClangMoveTool *const MoveTool;
+};
+
+clang::tooling::Replacement
+getReplacementInChangedCode(const clang::tooling::Replacements &Replacements,
+                            const clang::tooling::Replacement &Replacement) {
+  unsigned Start = Replacements.getShiftedCodePosition(Replacement.getOffset());
+  unsigned End = Replacements.getShiftedCodePosition(Replacement.getOffset() +
+                                                     Replacement.getLength());
+  return clang::tooling::Replacement(Replacement.getFilePath(), Start,
+                                     End - Start,
+                                     Replacement.getReplacementText());
+}
+
+void addOrMergeReplacement(const clang::tooling::Replacement &Replacement,
+                           clang::tooling::Replacements *Replacements) {
+  auto Err = Replacements->add(Replacement);
+  if (Err) {
+    llvm::consumeError(std::move(Err));
+    auto Replace = getReplacementInChangedCode(*Replacements, Replacement);
+    *Replacements = Replacements->merge(clang::tooling::Replacements(Replace));
+  }
+}
+
+bool IsInHeaderFile(const clang::SourceManager &SM, const clang::Decl *D) {
+  auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart());
+  if (ExpansionLoc.isInvalid())
+    return false;
+
+  if (const auto *FE = SM.getFileEntryForID(SM.getFileID(ExpansionLoc)))
+    return llvm::StringRef(FE->getName()).endswith(".h");
+
+  return false;
+}
+
+std::vector<std::string> GetNamespaces(const clang::Decl *D) {
+  std::vector<std::string> Namespaces;
+  for (const auto *Context = D->getDeclContext(); Context;
+       Context = Context->getParent()) {
+    if (llvm::isa<clang::TranslationUnitDecl>(Context) ||
+        llvm::isa<clang::LinkageSpecDecl>(Context))
+      break;
+
+    if (const auto *ND = llvm::dyn_cast<clang::NamespaceDecl>(Context))
+      Namespaces.push_back(ND->getName().str());
+  }
+  std::reverse(Namespaces.begin(), Namespaces.end());
+  return Namespaces;
+}
+
+SourceLocation getLocForEndOfDecl(const clang::Decl *D,
+                                  const clang::SourceManager *SM) {
+  auto End = D->getLocEnd();
+  clang::SourceLocation AfterSemi = clang::Lexer::findLocationAfterToken(
+      End, clang::tok::semi, *SM, clang::LangOptions(),
+      /*SkipTrailingWhitespaceAndNewLine=*/true);
+  if (AfterSemi.isValid())
+    End = AfterSemi.getLocWithOffset(-1);
+  return End;
+}
+
+std::string getDeclarationSourceText(const clang::Decl *D,
+                                     const clang::SourceManager *SM) {
+  auto EndLoc = getLocForEndOfDecl(D, SM);
+  llvm::StringRef SourceText = clang::Lexer::getSourceText(
+      clang::CharSourceRange::getTokenRange(D->getLocStart(), EndLoc), *SM,
+      clang::LangOptions());
+  return SourceText.str() + "\n";
+}
+
+clang::tooling::Replacements
+createInsertedReplacements(const std::vector<std::string> &Includes,
+                           const std::vector<ClangMoveTool::MovedDecl> &Decls,
+                           llvm::StringRef FileName) {
+  clang::tooling::Replacements InsertedReplacements;
+
+  // Add #Includes.
+  std::string AllIncludesString;
+  // FIXME: Filter out the old_header.h and add header guard.
+  for (const auto &Include : Includes)
+    AllIncludesString += Include;
+  clang::tooling::Replacement InsertInclude(FileName, 0, 0, AllIncludesString);
+  addOrMergeReplacement(InsertInclude, &InsertedReplacements);
+
+  // Add moved class definition and its related declarations. All declarations
+  // in same namespace are grouped together.
+  std::vector<std::string> CurrentNamespaces;
+  for (const auto &MovedDecl : Decls) {
+    std::vector<std::string> DeclNamespaces = GetNamespaces(MovedDecl.Decl);
+    auto CurrentIt = CurrentNamespaces.begin();
+    auto DeclIt = DeclNamespaces.begin();
+    while (CurrentIt != CurrentNamespaces.end() &&
+           DeclIt != DeclNamespaces.end()) {
+      if (*CurrentIt != *DeclIt)
+        break;
+      ++CurrentIt;
+      ++DeclIt;
+    }
+    std::vector<std::string> NextNamespaces(CurrentNamespaces.begin(),
+                                            CurrentIt);
+    NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end());
+    auto RemainingSize = CurrentNamespaces.end() - CurrentIt;
+    for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0;
+         --RemainingSize, ++It) {
+      assert(It < CurrentNamespaces.rend());
+      auto code = "} // namespace " + *It + "\n";
+      clang::tooling::Replacement InsertedReplacement(FileName, 0, 0, code);
+      addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
+    }
+    while (DeclIt != DeclNamespaces.end()) {
+      clang::tooling::Replacement InsertedReplacement(
+          FileName, 0, 0, "namespace " + *DeclIt + " {\n");
+      addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
+      ++DeclIt;
+    }
+
+    // FIXME: consider moving comments of the moved declaration.
+    clang::tooling::Replacement InsertedReplacement(
+        FileName, 0, 0, getDeclarationSourceText(MovedDecl.Decl, MovedDecl.SM));
+    addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
+
+    CurrentNamespaces = std::move(NextNamespaces);
+  }
+  std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end());
+  for (const auto &NS : CurrentNamespaces) {
+    clang::tooling::Replacement InsertedReplacement(
+        FileName, 0, 0, "} // namespace " + NS + "\n");
+    addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
+  }
+  return InsertedReplacements;
+}
+
+} // namespace
+
+std::unique_ptr<clang::ASTConsumer>
+ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler,
+                                   StringRef /*InFile*/) {
+  Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique<FindAllIncludes>(
+      &Compiler.getSourceManager(), &MoveTool));
+  return MatchFinder.newASTConsumer();
+}
+
+void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
+  auto InOldHeader = isExpansionInFileMatching(Spec.OldHeader);
+  auto InOldCC = isExpansionInFileMatching(Spec.OldCC);
+  auto InOldFiles = anyOf(InOldHeader, InOldCC);
+  auto InMovedClass = hasDeclContext(cxxRecordDecl(hasName(Spec.Name)));
+
+  // Match moved class declarations.
+  auto MovedClass = cxxRecordDecl(
+      InOldFiles, hasName(Spec.Name), isDefinition(),
+      hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())));
+  Finder->addMatcher(MovedClass.bind("moved_class"), this);
+
+  // Match moved class methods (static methods included) which are defined
+  // outside moved class declaration.
+  Finder->addMatcher(
+      cxxMethodDecl(InOldFiles, ofClass(hasName(Spec.Name)), isDefinition())
+          .bind("class_method"),
+      this);
+
+  // Match static member variable definition of the moved class.
+  Finder->addMatcher(varDecl(InMovedClass, InOldCC, isDefinition())
+                         .bind("class_static_var_decl"),
+                     this);
+
+  auto inAnonymousNamespace = hasParent(namespaceDecl(isAnonymous()));
+  // Match functions/variables defintions which are defined in anonymous
+  // namespace in old cc.
+  Finder->addMatcher(
+      namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
+                inAnonymousNamespace)
+          .bind("decls_in_anonymous_ns"),
+      this);
+
+  // Match static functions/variabale definitions in old cc.
+  Finder->addMatcher(
+      namedDecl(anyOf(functionDecl(isDefinition(), unless(InMovedClass),
+                                   isStatic(), InOldCC),
+                      varDecl(isDefinition(), unless(InMovedClass), isStatic(),
+                              InOldCC)))
+          .bind("static_decls"),
+      this);
+
+  // Match forward declarations in old header.
+  Finder->addMatcher(
+      cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), InOldHeader)
+          .bind("fwd_decl"),
+      this);
+}
+
+void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) {
+  if (const auto *CMD =
+          Result.Nodes.getNodeAs<clang::CXXMethodDecl>("class_method")) {
+    // Skip inline class methods. isInline() ast matcher doesn't ignore this
+    // case.
+    if (!CMD->isInlined()) {
+      MovedDecls.emplace_back(CMD, &Result.Context->getSourceManager());
+      RemovedDecls.push_back(MovedDecls.back());
+    }
+  } else if (const auto *VD = Result.Nodes.getNodeAs<clang::VarDecl>(
+                 "class_static_var_decl")) {
+    MovedDecls.emplace_back(VD, &Result.Context->getSourceManager());
+    RemovedDecls.push_back(MovedDecls.back());
+  } else if (const auto *class_decl =
+                 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("moved_class")) {
+    MovedDecls.emplace_back(class_decl, &Result.Context->getSourceManager());
+    RemovedDecls.push_back(MovedDecls.back());
+  } else if (const auto *FWD =
+                 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("fwd_decl")) {
+    // Skip all forwad declarations which appear after moved class declaration.
+    if (RemovedDecls.empty())
+      MovedDecls.emplace_back(FWD, &Result.Context->getSourceManager());
+  } else if (const auto *FD = Result.Nodes.getNodeAs<clang::NamedDecl>(
+                 "decls_in_anonymous_ns")) {
+    MovedDecls.emplace_back(FD, &Result.Context->getSourceManager());
+  } else if (const auto *ND =
+                 Result.Nodes.getNodeAs<clang::NamedDecl>("static_decls")) {
+    MovedDecls.emplace_back(ND, &Result.Context->getSourceManager());
+  }
+}
+
+void ClangMoveTool::addIncludes(llvm::StringRef IncludeLine,
+                                llvm::StringRef FileName) {
+  if (!Spec.OldHeader.empty() && FileName.endswith(Spec.OldHeader))
+    HeaderIncludes.push_back(IncludeLine.str());
+  else if (!Spec.OldCC.empty() && FileName.endswith(Spec.OldCC))
+    CCIncludes.push_back(IncludeLine.str());
+}
+
+void ClangMoveTool::removeClassDefinitionInOldFiles() {
+  for (const auto &MovedDecl : RemovedDecls) {
+    auto EndLoc = getLocForEndOfDecl(MovedDecl.Decl, MovedDecl.SM);
+    clang::tooling::Replacement RemoveReplacement(
+        *MovedDecl.SM, clang::CharSourceRange::getTokenRange(
+                           MovedDecl.Decl->getLocStart(), EndLoc),
+        "");
+    std::string FilePath = RemoveReplacement.getFilePath().str();
+    addOrMergeReplacement(RemoveReplacement, &FileToReplacements[FilePath]);
+  }
+}
+
+void ClangMoveTool::moveClassDefinitionToNewFiles() {
+  std::vector<MovedDecl> NewHeaderDecls;
+  std::vector<MovedDecl> NewCCDecls;
+  for (const auto &MovedDecl : MovedDecls) {
+    if (IsInHeaderFile(*MovedDecl.SM, MovedDecl.Decl))
+      NewHeaderDecls.push_back(MovedDecl);
+    else
+      NewCCDecls.push_back(MovedDecl);
+  }
+
+  if (!Spec.NewHeader.empty())
+    FileToReplacements[Spec.NewHeader] = createInsertedReplacements(
+        HeaderIncludes, NewHeaderDecls, Spec.NewHeader);
+  if (!Spec.NewCC.empty())
+    FileToReplacements[Spec.NewCC] =
+        createInsertedReplacements(CCIncludes, NewCCDecls, Spec.NewCC);
+}
+
+void ClangMoveTool::onEndOfTranslationUnit() {
+  if (RemovedDecls.empty())
+    return;
+  removeClassDefinitionInOldFiles();
+  moveClassDefinitionToNewFiles();
+}
+
+} // namespace move
+} // namespace clang
Index: clang-move/CMakeLists.txt
===================================================================
--- /dev/null
+++ clang-move/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(LLVM_LINK_COMPONENTS
+  support
+  )
+
+add_clang_library(clangMove
+  ClangMove.cpp
+
+  LINK_LIBS
+  clangAST
+  clangASTMatchers
+  clangBasic
+  clangFormat
+  clangFrontend
+  clangLex
+  clangTooling
+  clangToolingCore
+  )
+
+add_subdirectory(tool)
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -7,6 +7,7 @@
 endif()
 
 add_subdirectory(clang-query)
+add_subdirectory(clang-move)
 add_subdirectory(include-fixer)
 add_subdirectory(pp-trace)
 add_subdirectory(tool-template)
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to