117 changes: 117 additions & 0 deletions clang-tools-extra/clang-move/ClangMove.h
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
17 changes: 17 additions & 0 deletions clang-tools-extra/clang-move/tool/CMakeLists.txt
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
)
126 changes: 126 additions & 0 deletions clang-tools-extra/clang-move/tool/ClangMoveMain.cpp
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();
}
1 change: 1 addition & 0 deletions clang-tools-extra/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ endfunction()

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)
28 changes: 28 additions & 0 deletions clang-tools-extra/unittests/clang-move/CMakeLists.txt
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
)
226 changes: 226 additions & 0 deletions clang-tools-extra/unittests/clang-move/ClangMoveTests.cpp
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