Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(change-namespace) 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,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 +#include +#include + +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 &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 &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; + // The declarations that needs to be removed in old.cc/h. + std::vector RemovedDecls; + // The #includes in old_header.h. + std::vector HeaderIncludes; + // The #includes in old_cc.cc. + std::vector CCIncludes; +}; + +class ClangMoveAction : public clang::ASTFrontendAction { +public: + 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: + 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,339 @@ +//===-- 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, + llvm::StringRef HeaderFile) { + if (HeaderFile.empty()) + return false; + 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(HeaderFile); + + 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; +} + +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 &Includes, + const std::vector &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 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; + } + + // 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 +ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef /*InFile*/) { + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( + &Compiler.getSourceManager(), &MoveTool)); + return MatchFinder.newASTConsumer(); +} + + +ClangMoveTool::ClangMoveTool( + const MoveDefinitionSpec &MoveSpec, + std::map &FileToReplacements) + : Spec(MoveSpec), FileToReplacements(FileToReplacements) { + Spec.Name = llvm::StringRef(Spec.Name).ltrim(':'); +} + +void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) { + std::string FullyQualifiedName = "::" + Spec.Name; + auto InOldHeader = isExpansionInFileMatching(Spec.OldHeader); + auto InOldCC = isExpansionInFileMatching(Spec.OldCC); + auto InOldFiles = anyOf(InOldHeader, InOldCC); + auto InMovedClass = + hasDeclContext(cxxRecordDecl(hasName(FullyQualifiedName))); + + // Match moved class declarations. + auto MovedClass = cxxRecordDecl( + InOldFiles, hasName(FullyQualifiedName), 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(FullyQualifiedName)), + 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 definitions 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("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( + "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("moved_class")) { + MovedDecls.emplace_back(class_decl, &Result.Context->getSourceManager()); + RemovedDecls.push_back(MovedDecls.back()); + } else if (const auto *FWD = + Result.Nodes.getNodeAs("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( + "decls_in_anonymous_ns")) { + MovedDecls.emplace_back(FD, &Result.Context->getSourceManager()); + } else if (const auto *ND = + Result.Nodes.getNodeAs("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 NewHeaderDecls; + std::vector NewCCDecls; + for (const auto &MovedDecl : MovedDecls) { + if (IsInHeaderFile(*MovedDecl.SM, MovedDecl.Decl, Spec.OldHeader)) + 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/tool/CMakeLists.txt =================================================================== --- /dev/null +++ clang-move/tool/CMakeLists.txt @@ -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 + ) 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 @@ -7,6 +7,7 @@ add_subdirectory(change-namespace) add_subdirectory(clang-apply-replacements) +add_subdirectory(clang-move) add_subdirectory(clang-query) 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,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 +#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" + " 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 +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 = "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