hokein created this revision. hokein added a subscriber: cfe-commits. This patch introduces a new tool which moves a specific class definition from files (.h, .cc) to new files (.h, .cc), which mostly acts like "Extract class defintion". In the long term, this tool should be merged in to clang-refactoring as a subtool.
clang-move not only moves class definition, but also moves all the forward declarations, functions defined in anonymous namespace and #include headers to new files, to make sure the new files are compliable as much as possible. To move `Foo` from old.[h/cc] to new.[h/cc], use: ``` clang-move -name=Foo -old_header=old.h -old_cc=old.cc -new_header=new.h -new_cc=new.cc old.cc ``` To move `Foo` from old.h to new.h, use: ``` clang-move -name=Foo -old_header=old.h -new_header=new.h old.cc ``` 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,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 <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" + "};\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<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; + } + std::set<llvm::StringRef> Files; + for (const auto &it : Tool.getReplacements()) + Files.insert(it.first); + + if (Dump) { + 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,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 <map> +#include <string> +#include <vector> + +#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<std::string, tooling::Replacements> &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; + // Key is file path, value is the replacements being applied to the file. + std::map<std::string, tooling::Replacements> &FileToReplacements; + // All declarations (the class declbeing moved, forward decls) that need to + // be moved/copy to the new files, saving in an AST-visited order. + std::vector<MovedDecl> 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<std::string> HeaderIncludes; + // The #includes in old_cc.cc. + std::vector<std::string> CCIncludes; +}; + +class ClangMoveAction : public clang::ASTFrontendAction { +public: + explicit 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: + clang::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,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<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; +} + +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<std::string> &Includes, + const std::vector<ClangMoveTool::MovedDecl> &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<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; + } + + 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(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<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()); + FoundMovedDecl = true; + } + } else if (const auto *FWD = + Result.Nodes.getNodeAs<clang::CXXRecordDecl>("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<clang::CXXRecordDecl>("moved_class")) { + FoundMovedDecl = true; + MovedDecls.emplace_back(class_decl, &Result.Context->getSourceManager()); + MovedClassDecl = MovedDecls.back(); + } else if (const auto *FD = + Result.Nodes.getNodeAs<clang::FunctionDecl>("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<clang::CXXMethodDecl>(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<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 (!FoundMovedDecl) 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