509 changes: 509 additions & 0 deletions clang-tools-extra/change-namespace/ChangeNamespace.cpp

Large diffs are not rendered by default.

144 changes: 144 additions & 0 deletions clang-tools-extra/change-namespace/ChangeNamespace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//===-- ChangeNamespace.h -- Change namespace ------------------*- C++ -*-===//
//
// 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_CHANGE_NAMESPACE_CHANGENAMESPACE_H
#define LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H

#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Format/Format.h"
#include "clang/Tooling/Core/Replacement.h"
#include <string>

namespace clang {
namespace change_namespace {

// This tool can be used to change the surrounding namespaces of class/function
// definitions. Classes/functions in the moved namespace will have new
// namespaces while references to symbols (e.g. types, functions) which are not
// defined in the changed namespace will be correctly qualified by prepending
// namespace specifiers before them.
// For classes, only classes that are declared/defined in the given namespace in
// speficifed files will be moved: forward declarations will remain in the old
// namespace.
// For example, changing "a" to "x":
// Old code:
// namespace a {
// class FWD;
// class A { FWD *fwd; }
// } // a
// New code:
// namespace a {
// class FWD;
// } // a
// namespace x {
// class A { a::FWD *fwd; }
// } // x
// FIXME: support moving typedef, enums across namespaces.
class ChangeNamespaceTool : public ast_matchers::MatchFinder::MatchCallback {
public:
// Moves code in the old namespace `OldNs` to the new namespace `NewNs` in
// files matching `FilePattern`.
ChangeNamespaceTool(
llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern,
std::map<std::string, tooling::Replacements> *FileToReplacements,
llvm::StringRef FallbackStyle = "LLVM");

void registerMatchers(ast_matchers::MatchFinder *Finder);

void run(const ast_matchers::MatchFinder::MatchResult &Result) override;

// Moves the changed code in old namespaces but leaves class forward
// declarations behind.
void onEndOfTranslationUnit() override;

private:
void moveOldNamespace(const ast_matchers::MatchFinder::MatchResult &Result,
const NamespaceDecl *NsDecl);

void moveClassForwardDeclaration(
const ast_matchers::MatchFinder::MatchResult &Result,
const CXXRecordDecl *FwdDecl);

void replaceQualifiedSymbolInDeclContext(
const ast_matchers::MatchFinder::MatchResult &Result,
const Decl *DeclContext, SourceLocation Start, SourceLocation End,
llvm::StringRef DeclName);

void fixTypeLoc(const ast_matchers::MatchFinder::MatchResult &Result,
SourceLocation Start, SourceLocation End, TypeLoc Type);

// Information about moving an old namespace.
struct MoveNamespace {
// The start offset of the namespace block being moved in the original
// code.
unsigned Offset;
// The length of the namespace block in the original code.
unsigned Length;
// The offset at which the new namespace block will be inserted in the
// original code.
unsigned InsertionOffset;
// The file in which the namespace is declared.
FileID FileID;
SourceManager *SourceManager;
};

// Information about inserting a class forward declaration.
struct InsertForwardDeclaration {
// The offset at while the forward declaration will be inserted in the
// original code.
unsigned InsertionOffset;
// The code to be inserted.
std::string ForwardDeclText;
};

std::string FallbackStyle;
// In match callbacks, this contains replacements for replacing `typeLoc`s in
// and deleting forward declarations in the moved namespace blocks.
// In `onEndOfTranslationUnit` callback, the previous added replacements are
// applied (on the moved namespace blocks), and then changed code in old
// namespaces re moved to new namespaces, and previously deleted forward
// declarations are inserted back to old namespaces, from which they are
// deleted.
std::map<std::string, tooling::Replacements> &FileToReplacements;
// A fully qualified name of the old namespace without "::" prefix, e.g.
// "a::b::c".
std::string OldNamespace;
// A fully qualified name of the new namespace without "::" prefix, e.g.
// "x::y::z".
std::string NewNamespace;
// The longest suffix in the old namespace that does not overlap the new
// namespace.
// For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is
// "a::x::y", then `DiffOldNamespace` will be "b::c".
std::string DiffOldNamespace;
// The longest suffix in the new namespace that does not overlap the old
// namespace.
// For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is
// "a::x::y", then `DiffNewNamespace` will be "x::y".
std::string DiffNewNamespace;
// A regex pattern that matches files to be processed.
std::string FilePattern;
// Information about moved namespaces grouped by file.
// Since we are modifying code in old namespaces (e.g. add namespace
// spedifiers) as well as moving them, we store information about namespaces
// to be moved and only move them after all modifications are finished (i.e.
// in `onEndOfTranslationUnit`).
std::map<std::string, std::vector<MoveNamespace>> MoveNamespaces;
// Information about forward declaration insertions grouped by files.
// A class forward declaration is not moved, so it will be deleted from the
// moved code block and inserted back into the old namespace. The insertion
// will be done after removing the code from the old namespace and before
// inserting it to the new namespace.
std::map<std::string, std::vector<InsertForwardDeclaration>> InsertFwdDecls;
};

} // namespace change_namespace
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H
17 changes: 17 additions & 0 deletions clang-tools-extra/change-namespace/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-change-namespace
ClangChangeNamespace.cpp
)
target_link_libraries(clang-change-namespace
clangBasic
clangChangeNamespace
clangFormat
clangFrontend
clangRewrite
clangTooling
clangToolingCore
)

install(TARGETS clang-change-namespace
RUNTIME DESTINATION bin)
111 changes: 111 additions & 0 deletions clang-tools-extra/change-namespace/tool/ClangChangeNamespace.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//===-- ClangIncludeFixer.cpp - Standalone change namespace ---------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// This tool can be used to change the surrounding namespaces of class/function
// definitions.
//
// Example: test.cc
// namespace na {
// class X {};
// namespace nb {
// class Y { X x; };
// } // namespace nb
// } // namespace na
// To move the definition of class Y from namespace "na::nb" to "x::y", run:
// clang-change-namespace --old_namespace "na::nb" \
// --new_namespace "x::y" --file_pattern "test.cc" test.cc --
// Output:
// namespace na {
// class X {};
// } // namespace na
// namespace x {
// namespace y {
// class Y { na::X x; };
// } // namespace y
// } // namespace x

#include "ChangeNamespace.h"
#include "clang/Frontend/FrontendActions.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/Support/CommandLine.h"

using namespace clang;
using namespace llvm;

namespace {

cl::OptionCategory ChangeNamespaceCategory("Change namespace.");

cl::opt<std::string> OldNamespace("old_namespace", cl::Required,
cl::desc("Old namespace."),
cl::cat(ChangeNamespaceCategory));

cl::opt<std::string> NewNamespace("new_namespace", cl::Required,
cl::desc("New namespace."),
cl::cat(ChangeNamespaceCategory));

cl::opt<std::string> FilePattern(
"file_pattern", cl::Required,
cl::desc("Only rename namespaces in files that match the given pattern."),
cl::cat(ChangeNamespaceCategory));

cl::opt<bool> Inplace("i", cl::desc("Inplace edit <file>s, if specified."),
cl::cat(ChangeNamespaceCategory));

cl::opt<std::string> Style("style",
cl::desc("The style name used for reformatting."),
cl::init("LLVM"), cl::cat(ChangeNamespaceCategory));

} // anonymous namespace

int main(int argc, const char **argv) {
tooling::CommonOptionsParser OptionsParser(argc, argv,
ChangeNamespaceCategory);
const auto &Files = OptionsParser.getSourcePathList();
tooling::RefactoringTool Tool(OptionsParser.getCompilations(), Files);
change_namespace::ChangeNamespaceTool NamespaceTool(
OldNamespace, NewNamespace, FilePattern, &Tool.getReplacements());
ast_matchers::MatchFinder Finder;
NamespaceTool.registerMatchers(&Finder);
std::unique_ptr<tooling::FrontendActionFactory> Factory =
tooling::newFrontendActionFactory(&Finder);

if (int Result = Tool.run(Factory.get()))
return Result;
LangOptions DefaultLangOptions;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
&DiagnosticPrinter, false);
auto &FileMgr = Tool.getFiles();
SourceManager Sources(Diagnostics, FileMgr);
Rewriter Rewrite(Sources, DefaultLangOptions);

if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) {
llvm::errs() << "Failed applying all replacements.\n";
return 1;
}
if (Inplace)
return Rewrite.overwriteChangedFiles();

for (const auto &File : Files) {
const auto *Entry = FileMgr.getFile(File);

auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User);
// FIXME: print results in parsable format, e.g. JSON.
outs() << "============== " << File << " ==============\n";
Rewrite.getEditBuffer(ID).write(llvm::outs());
outs() << "\n============================================\n";
}
return 0;
}
1 change: 1 addition & 0 deletions clang-tools-extra/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ set(CLANG_TOOLS_TEST_DEPS

# Individual tools we test.
clang-apply-replacements
clang-change-namespace
clang-include-fixer
clang-query
clang-rename
Expand Down
10 changes: 10 additions & 0 deletions clang-tools-extra/test/change-namespace/simple-move.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: clang-change-namespace -old_namespace "na::nb" -new_namespace "x::y" --file_pattern ".*" %s -- | sed 's,// CHECK.*,,' | FileCheck %s
// CHECK: namespace x {
// CHECK-NEXT: namespace y {
namespace na {
namespace nb {
class A {};
// CHECK: } // namespace y
// CHECK-NEXT: } // namespace x
}
}
1 change: 1 addition & 0 deletions clang-tools-extra/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ function(add_extra_unittest test_dirname)
add_unittest(ExtraToolsUnitTests ${test_dirname} ${ARGN})
endfunction()

add_subdirectory(change-namespace)
add_subdirectory(clang-apply-replacements)
add_subdirectory(clang-query)
add_subdirectory(clang-tidy)
Expand Down
28 changes: 28 additions & 0 deletions clang-tools-extra/unittests/change-namespace/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
set(LLVM_LINK_COMPONENTS
support
)

get_filename_component(CHANGE_NAMESPACE_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/../../change-namespace REALPATH)
include_directories(
${CHANGE_NAMESPACE_SOURCE_DIR}
)

# We'd like clang/unittests/Tooling/RewriterTestContext.h in the test.
include_directories(${CLANG_SOURCE_DIR})

add_extra_unittest(ChangeNamespaceTests
ChangeNamespaceTests.cpp
)

target_link_libraries(ChangeNamespaceTests
clangAST
clangASTMatchers
clangBasic
clangChangeNamespace
clangFormat
clangFrontend
clangRewrite
clangTooling
clangToolingCore
)
234 changes: 234 additions & 0 deletions clang-tools-extra/unittests/change-namespace/ChangeNamespaceTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//===-- ChangeNamespaceTests.cpp - Change namespace unit tests ---*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "ChangeNamespace.h"
#include "unittests/Tooling/RewriterTestContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/FileSystemOptions.h"
#include "clang/Basic/VirtualFileSystem.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/PCHContainerOperations.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBuffer.h"
#include "gtest/gtest.h"
#include <memory>
#include <string>
#include <vector>

namespace clang {
namespace change_namespace {
namespace {

class ChangeNamespaceTest : public ::testing::Test {
public:
std::string runChangeNamespaceOnCode(llvm::StringRef Code) {
clang::RewriterTestContext Context;
clang::FileID ID = Context.createInMemoryFile(FileName, Code);

std::map<std::string, tooling::Replacements> FileToReplacements;
change_namespace::ChangeNamespaceTool NamespaceTool(
OldNamespace, NewNamespace, FilePattern, &FileToReplacements);
ast_matchers::MatchFinder Finder;
NamespaceTool.registerMatchers(&Finder);
std::unique_ptr<tooling::FrontendActionFactory> Factory =
tooling::newFrontendActionFactory(&Finder);
tooling::runToolOnCodeWithArgs(Factory->create(), Code, {"-std=c++11"},
FileName);
formatAndApplyAllReplacements(FileToReplacements, Context.Rewrite);
return format(Context.getRewrittenText(ID));
}

std::string format(llvm::StringRef Code) {
tooling::Replacements Replaces = format::reformat(
format::getLLVMStyle(), Code, {tooling::Range(0, Code.size())});
auto ChangedCode = tooling::applyAllReplacements(Code, Replaces);
EXPECT_TRUE(static_cast<bool>(ChangedCode));
if (!ChangedCode) {
llvm::errs() << llvm::toString(ChangedCode.takeError());
return "";
}
return *ChangedCode;
}

protected:
std::string FileName = "input.cc";
std::string OldNamespace = "na::nb";
std::string NewNamespace = "x::y";
std::string FilePattern = "input.cc";
};

TEST_F(ChangeNamespaceTest, NoMatchingNamespace) {
std::string Code = "namespace na {\n"
"namespace nx {\n"
"class A {};\n"
"} // namespace nx\n"
"} // namespace na\n";
std::string Expected = "namespace na {\n"
"namespace nx {\n"
"class A {};\n"
"} // namespace nx\n"
"} // namespace na\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
}

TEST_F(ChangeNamespaceTest, SimpleMoveWithoutTypeRefs) {
std::string Code = "namespace na {\n"
"namespace nb {\n"
"class A {};\n"
"} // namespace nb\n"
"} // namespace na\n";
std::string Expected = "\n\n"
"namespace x {\n"
"namespace y {\n"
"class A {};\n"
"} // namespace y\n"
"} // namespace x\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
}

TEST_F(ChangeNamespaceTest, SimpleMoveIntoAnotherNestedNamespace) {
NewNamespace = "na::nc";
std::string Code = "namespace na {\n"
"namespace nb {\n"
"class A {};\n"
"} // namespace nb\n"
"} // namespace na\n";
std::string Expected = "namespace na {\n"
"\n"
"namespace nc {\n"
"class A {};\n"
"} // namespace nc\n"
"} // namespace na\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
}

TEST_F(ChangeNamespaceTest, SimpleMoveNestedNamespace) {
NewNamespace = "na::x::y";
std::string Code = "namespace na {\n"
"class A {};\n"
"namespace nb {\n"
"class B {};\n"
"} // namespace nb\n"
"} // namespace na\n";
std::string Expected = "namespace na {\n"
"class A {};\n"
"\n"
"namespace x {\n"
"namespace y {\n"
"class B {};\n"
"} // namespace y\n"
"} // namespace x\n"
"} // namespace na\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
}

TEST_F(ChangeNamespaceTest, SimpleMoveWithTypeRefs) {
std::string Code = "namespace na {\n"
"class C_A {};\n"
"namespace nc {\n"
"class C_C {};"
"} // namespace nc\n"
"namespace nb {\n"
"class C_X {\n"
"public:\n"
" C_A a;\n"
" nc::C_C c;\n"
"};\n"
"class C_Y {\n"
" C_X x;\n"
"};\n"
"} // namespace nb\n"
"} // namespace na\n";
std::string Expected = "namespace na {\n"
"class C_A {};\n"
"namespace nc {\n"
"class C_C {};"
"} // namespace nc\n"
"\n"
"} // namespace na\n"
"namespace x {\n"
"namespace y {\n"
"class C_X {\n"
"public:\n"
" na::C_A a;\n"
" na::nc::C_C c;\n"
"};\n"
"class C_Y {\n"
" C_X x;\n"
"};\n"
"} // namespace y\n"
"} // namespace x\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
}

TEST_F(ChangeNamespaceTest, LeaveForwardDeclarationBehind) {
std::string Code = "namespace na {\n"
"namespace nb {\n"
"class FWD;\n"
"class A {\n"
" FWD *fwd;\n"
"};\n"
"} // namespace nb\n"
"} // namespace na\n";
std::string Expected = "namespace na {\n"
"namespace nb {\n"
"class FWD;\n"
"} // namespace nb\n"
"} // namespace na\n"
"namespace x {\n"
"namespace y {\n"
"\n"
"class A {\n"
" na::nb::FWD *fwd;\n"
"};\n"
"} // namespace y\n"
"} // namespace x\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
}

TEST_F(ChangeNamespaceTest, MoveFunctions) {
std::string Code = "namespace na {\n"
"class C_A {};\n"
"namespace nc {\n"
"class C_C {};"
"} // namespace nc\n"
"namespace nb {\n"
"void fwd();\n"
"void f(C_A ca, nc::C_C cc) {\n"
" C_A ca_1 = ca;\n"
"}\n"
"} // namespace nb\n"
"} // namespace na\n";

std::string Expected = "namespace na {\n"
"class C_A {};\n"
"namespace nc {\n"
"class C_C {};"
"} // namespace nc\n"
"\n"
"} // namespace na\n"
"namespace x {\n"
"namespace y {\n"
"void fwd();\n"
"void f(na::C_A ca, na::nc::C_C cc) {\n"
" na::C_A ca_1 = ca;\n"
"}\n"
"} // namespace y\n"
"} // namespace x\n";
EXPECT_EQ(format(Expected), runChangeNamespaceOnCode(Code));
}

} // anonymous namespace
} // namespace change_namespace
} // namespace clang