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) 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: clang-move/ClangMove.h =================================================================== --- /dev/null +++ clang-move/ClangMove.h @@ -0,0 +1,118 @@ +//===-- 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 +#include +#include + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" + +namespace clang { +namespace move { + +// TODO(hokein): Make it support more types, e.g. function definitions. +// Currently only support moving class definition. +class ClangMoveTool : public clang::ast_matchers::MatchFinder::MatchCallback { +public: + // Information about the declaration being moved. + struct MovedDecl { + const clang::Decl *Decl; + clang::SourceManager *SM; + MovedDecl() : Decl(nullptr), SM(nullptr) {} + MovedDecl(const clang::Decl *Decl, clang::SourceManager *SM) + : Decl(Decl), SM(SM) {} + }; + + struct MoveDefinitionSpec { + std::string Name; + std::string OldHeader; + std::string OldCC; + std::string NewHeader; + std::string NewCC; + }; + + ClangMoveTool( + const MoveDefinitionSpec &Spec, + std::map &FileToReplacements) + : Spec(Spec), FileToReplacements(FileToReplacements), + FoundMovedDecl(false) {} + + void registerMatchers(clang::ast_matchers::MatchFinder *match_finder); + + void + run(const clang::ast_matchers::MatchFinder::MatchResult &result) override; + + void onEndOfTranslationUnit() override; + + void addIncludes(llvm::StringRef include_line, llvm::StringRef file_name); + +private: + void removeClassDefinitionInOldFiles(); + void moveClassDefinitionToNewFiles(); + + const MoveDefinitionSpec &Spec; + // The Key is file path, value is the replacements being applied to the file. + std::map &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 MovedDecls; + // Whether encounter the declaration/method of the class being renamed. + bool FoundMovedDecl; + // The declaration of the class being moved. + MovedDecl MovedClassDecl; + // The #includes in old_header.h. + std::vector HeaderIncludes; + // The #includes in old_cc.cc. + std::vector CCIncludes; +}; + +class ClangMoveAction : public clang::ASTFrontendAction { +public: + explicit ClangMoveAction( + const ClangMoveTool::MoveDefinitionSpec &spec, + std::map &FileToReplacements) + : MoveTool(spec, FileToReplacements) { + MoveTool.registerMatchers(&MatchFinder); + } + ~ClangMoveAction() override = default; + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef InFile) override; + +private: + clang::ast_matchers::MatchFinder MatchFinder; + ClangMoveTool MoveTool; +}; + +class ClangMoveActionFactory : public tooling::FrontendActionFactory { +public: + ClangMoveActionFactory( + const ClangMoveTool::MoveDefinitionSpec &Spec, + std::map &FileToReplacements) + : Spec(Spec), FileToReplacements(FileToReplacements) {} + clang::FrontendAction *create() override { + return new ClangMoveAction(Spec, FileToReplacements); + } + +private: + const ClangMoveTool::MoveDefinitionSpec &Spec; + std::map &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,294 @@ +//===-- 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 { + +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 GetNamespaces(const clang::Decl *D) { + std::vector Namespaces; + for (const auto *Context = D->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa(Context) || + llvm::isa(Context)) + break; + + if (const auto *ND = llvm::dyn_cast(Context)) + Namespaces.push_back(ND->getName().str()); + } + std::reverse(Namespaces.begin(), Namespaces.end()); + return Namespaces; +} + +std::string getDeclarationSourceText(const clang::Decl *D, + const clang::SourceManager *SM) { + // Gets the ending ";". + auto EndLoc = clang::Lexer::getLocForEndOfToken(D->getLocEnd(), 0, *SM, + clang::LangOptions()); + 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 &Includes, + const std::vector &Decls, + llvm::StringRef FileName) { + clang::tooling::Replacements InsertedReplacements; + + // Add #Includes. + // FIXME: Filter out the old_header.h. + for (const auto &Include : Includes) { + clang::tooling::Replacement InsertInclude(FileName, 0, 0, Include); + addOrMergeReplacement(InsertInclude, &InsertedReplacements); + } + + // Add moved class definition and its related declarations. All declarations + // in same namespace are grouped together. + std::vector CurrentNamespaces; + for (const auto &MovedDecl : Decls) { + std::vector 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 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; + } + + 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 +ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef /*InFile*/) { + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( + &Compiler.getSourceManager(), &MoveTool)); + return MatchFinder.newASTConsumer(); +} + +void ClangMoveTool::registerMatchers(clang::ast_matchers::MatchFinder *Finder) { + auto InOldHeader = isExpansionInFileMatching(Spec.OldHeader); + auto InOldCC = isExpansionInFileMatching(Spec.OldCC); + auto InOldFiles = anyOf(InOldHeader, InOldCC); + + // 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 which are defined outside class declaration. + Finder->addMatcher( + cxxMethodDecl(InOldFiles, ofClass(hasName(Spec.Name)), isDefinition()) + .bind("class_method"), + this); + + // Match functions defined in anonymous namespace. + Finder->addMatcher( + functionDecl(hasParent(namespaceDecl(isAnonymous())), InOldCC) + .bind("fun_decl"), + this); + + // Match forward declarations. + Finder->addMatcher( + cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), InOldHeader) + .bind("fwd_decl"), + this); +} + +void ClangMoveTool::run( + const clang::ast_matchers::MatchFinder::MatchResult &Result) { + if (const auto *CMD = + Result.Nodes.getNodeAs("class_method")) { + // Skip inline class methods. isInline() ast matcher doesn't ignore this + // case. + if (!CMD->isInlined()) { + MovedDecls.emplace_back(CMD, &Result.Context->getSourceManager()); + FoundMovedDecl = true; + } + } else if (const auto *FWD = + Result.Nodes.getNodeAs("fwd_decl")) { + // Skip all forwad declarations which appear after moved class declaration. + if (!MovedClassDecl.Decl) + MovedDecls.emplace_back(FWD, &Result.Context->getSourceManager()); + } else if (const auto *class_decl = + Result.Nodes.getNodeAs("moved_class")) { + FoundMovedDecl = true; + MovedDecls.emplace_back(class_decl, &Result.Context->getSourceManager()); + MovedClassDecl = MovedDecls.back(); + } else if (const auto *FD = + Result.Nodes.getNodeAs("fun_decl")) { + MovedDecls.emplace_back(FD, &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 : MovedDecls) { + // Find the moved class definition. + if (MovedDecl.Decl == MovedClassDecl.Decl || + clang::isa(MovedDecl.Decl)) { + auto EndLoc = clang::Lexer::getLocForEndOfToken( + MovedDecl.Decl->getLocEnd(), 0, *MovedDecl.SM, clang::LangOptions()); + clang::tooling::Replacement RemovedReplacement( + *MovedDecl.SM, clang::CharSourceRange::getTokenRange( + MovedDecl.Decl->getLocStart(), EndLoc), + ""); + std::string FilePath = RemovedReplacement.getFilePath().str(); + auto err = FileToReplacements[FilePath].add(RemovedReplacement); + if (err) { + llvm::errs() << "Failed to add replacement: " + << llvm::toString(std::move(err)); + } + } + } +} + +void ClangMoveTool::moveClassDefinitionToNewFiles() { + std::vector NewHeaderDecls; + std::vector 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 (!FoundMovedDecl) return; + removeClassDefinitionInOldFiles(); + moveClassDefinitionToNewFiles(); +} + +} // namespace move +} // namespace clang 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/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 +#include + +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 Name("name", cl::desc("The name of class being moved."), + cl::cat(ClangMoveCategory)); + +cl::opt OldHeader("old_header", cl::desc("Old header."), + cl::cat(ClangMoveCategory)); + +cl::opt OldCC("old_cc", cl::desc("Old CC file."), + cl::cat(ClangMoveCategory)); + +cl::opt NewHeader("new_header", cl::desc("New header."), + cl::cat(ClangMoveCategory)); + +cl::opt NewCC("new_cc", cl::desc("New CC file."), + cl::cat(ClangMoveCategory)); + +cl::opt + Style("style", + cl::desc("The style name used for reformatting. Default is \"llvm\""), + cl::init("llvm"), cl::cat(ClangMoveCategory)); + +cl::opt 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( + Spec, Tool.getReplacements()); + int CodeStatus = Tool.run(Factory.get()); + if (CodeStatus) + return CodeStatus; + + if (!NewCC.empty()) + CreateNewFile(NewCC); + if (!NewHeader.empty()) + CreateNewFile(NewHeader); + + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(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 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: 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: 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/clang-move/ClangMoveTests.cpp =================================================================== --- /dev/null +++ unittests/clang-move/ClangMoveTests.cpp @@ -0,0 +1,198 @@ +//===-- 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 +#include + +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" + "};\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" + "} // namespace\n" + "void Foo::f() { f1(); }\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" + "} // namespace\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" + "};\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" + "} // namespace\n" + "void Foo::f() { f1(); }\n" + "} // namespace b\n" + "} // namespace a\n"; + +std::map +runClangMoveOnCode(const move::ClangMoveTool::MoveDefinitionSpec &Spec) { + clang::RewriterTestContext Context; + + std::map FileToFileID; + std::vector> 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 FileToReplacements; + ClangMoveTool MoveTool(Spec, FileToReplacements); + auto Factory = llvm::make_unique( + Spec, FileToReplacements); + + tooling::runToolOnCodeWithArgs( + Factory->create(), TestCC, {"-std=c++11"}, TestCCName, "clang-move", + std::make_shared(), FileToSourceText); + formatAndApplyAllReplacements(FileToReplacements, Context.Rewrite, "llvm"); + // The Key is file name, value is the new code after moving the class. + std::map 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