| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| //===-- 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 qualified name, e.g. "X", "a::X". | ||
| std::string Name; | ||
| std::string OldHeader; | ||
| std::string OldCC; | ||
| std::string NewHeader; | ||
| std::string NewCC; | ||
| }; | ||
|
|
||
| ClangMoveTool( | ||
| const MoveDefinitionSpec &MoveSpec, | ||
| std::map<std::string, tooling::Replacements> &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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) | ||
|
|
||
| add_clang_executable(clang-move | ||
| ClangMoveMain.cpp | ||
| ) | ||
|
|
||
| target_link_libraries(clang-move | ||
| clangAST | ||
| clangASTMatchers | ||
| clangBasic | ||
| clangFormat | ||
| clangFrontend | ||
| clangMove | ||
| clangRewrite | ||
| clangTooling | ||
| clangToolingCore | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 = "a::b::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 = "a::b::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 = "a::b::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 |