Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(clang-apply-replacements) add_subdirectory(clang-rename) add_subdirectory(clang-reorder-fields) +add_subdirectory(migrate-tool) add_subdirectory(modularize) if(CLANG_ENABLE_STATIC_ANALYZER) add_subdirectory(clang-tidy) Index: migrate-tool/AffectedFilesFinder.h =================================================================== --- /dev/null +++ migrate-tool/AffectedFilesFinder.h @@ -0,0 +1,33 @@ +//===-- AffectedFilesFinder.h - Finds files affected by a refactor.-*- 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_MIGRATE_TOOL_AFFECTEDFILESINGFINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_AFFECTEDFILESINGFINDER_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace migrate_tool { + +// This defines interface for finding files that are affected by a refactoring +// action, e.g. renaming a symbol. +class AffectedFilesFinder { +public: + // Get all files that need to be updated when a symbol is renamed and/or + // moved. + virtual llvm::Expected> + getAffectedFiles(llvm::StringRef Symbol) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_AFFECTEDFILESINGFINDER_H Index: migrate-tool/BuildManager.h =================================================================== --- /dev/null +++ migrate-tool/BuildManager.h @@ -0,0 +1,39 @@ +//===-- BuildManager.h - Manages build targets ------------------*- 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_MIGRATE_TOOL_BUILDMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_BUILDMANAGER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace migrate_tool { + +// This defines interfaces for a codebase-dependent build manager. +class BuildManager { +public: + // Adds a new library build target with \p Sources as source files. The name + // of the new library will be \p Name if it is provided; otherwise, the name + // will be auto-generated. + virtual bool addLibrary(llvm::ArrayRef Sources, + llvm::StringRef Name = llvm::StringRef()) = 0; + + // Adds a new dependency to the build target. + virtual bool addDependency(llvm::StringRef BuildTarget, + llvm::StringRef Dependency) = 0; + + // Returns the name of the build target containing \p File. + virtual std::string getBuildTargetForFile(llvm::StringRef File) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_BUILDMANAGER_H Index: migrate-tool/CMakeLists.txt =================================================================== --- /dev/null +++ migrate-tool/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(migrateTool + HeaderGenerator.cpp + MigrateTool.cpp + + LINK_LIBS + clangAST + clangBasic + clangFormat + clangTooling + clangToolingCore + ) Index: migrate-tool/HeaderGenerator.h =================================================================== --- /dev/null +++ migrate-tool/HeaderGenerator.h @@ -0,0 +1,58 @@ +//===-- HeaderGenerator.h - Generate new headers. ----------------*- 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_MIGRATE_TOOL_HEADERGENERATOR_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_HEADERGENERATOR_H + +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace migrate_tool { + +// This constructs a C++ header containing type aliases. +class HeaderGenerator { +public: + HeaderGenerator(llvm::StringRef HeaderName); + + // Returns the name of the header. + llvm::StringRef getName() const { return HeaderName; } + + /// \brief Adds an `using` shadow declaration from \p TypeName to \p NewName. + /// Both names should be fully-qualified. + /// For example, if \p NewName is `a::b::New` and TypeName is `x::y::Old`. + /// Then, the following code will be added into the header: + /// \code + /// namespace a { + /// namespace b { + /// using New = ::x::y::Old; + /// } // namespace b + /// } // namespace a + /// \endcode + void addAlias(llvm::StringRef NewName, llvm::StringRef TypeName); + + // Adds an #include into the header. + void addInclude(llvm::StringRef IncludeHeader); + + // Generates the header content containing given aliases and #include's. + // FIXME: consider code style. + std::string generateContent() const; + +private: + std::string HeaderName; + std::string HeaderGuard; + // Old symbol name, New symbol name pairs. + std::vector> Aliases; + std::vector Includes; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_HEADERGENERATOR_H Index: migrate-tool/HeaderGenerator.cpp =================================================================== --- /dev/null +++ migrate-tool/HeaderGenerator.cpp @@ -0,0 +1,112 @@ +//===-- HeaderGenerator.cpp - Generate new headers. -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGenerator.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +namespace clang { +namespace migrate_tool { + +namespace { + +class Namespace { +public: + Namespace(llvm::StringRef Name, bool IsTopLevel = false) + : Name(Name), IsTopLevel(IsTopLevel) { + assert(!(!Name.empty() && IsTopLevel) && + "A namespace must be either a top-level namespace with no name or " + "not a top-level namespace with name."); + } + + void addAlias(llvm::StringRef NewName, llvm::StringRef TypeName) { + std::string OS; + llvm::raw_string_ostream SS(OS); + SS << "using " << NewName.str() << " = ::" << TypeName.str() << ";"; + SS.flush(); + CodeBlocks.push_back(OS); + } + + Namespace *addNestedNamespace(llvm::StringRef Name) { + auto Pair = NestedNamespaces.try_emplace(Name, Name); + return &Pair.first->getValue(); + } + + std::string GenerateNamespaceCodeBlock() const { + std::vector Lines; + if (!IsTopLevel) + Lines.push_back("namespace " + Name + " {"); + // Add code blocks. + Lines.insert(Lines.end(), CodeBlocks.begin(), CodeBlocks.end()); + // Add nested namespaces. + // If there are multiple nested namespaces, add newlines around each + // namespace. + for (const auto &Entry : NestedNamespaces) + Lines.push_back(Entry.second.GenerateNamespaceCodeBlock()); + if (!IsTopLevel) + Lines.push_back("} // namespace " + Name); + return llvm::join(Lines.begin(), Lines.end(), "\n"); + } + +private: + std::string Name; + bool IsTopLevel; + std::vector CodeBlocks; + llvm::StringMap NestedNamespaces; +}; + +} // namespace + +HeaderGenerator::HeaderGenerator(llvm::StringRef HeaderName) + : HeaderName(HeaderName), HeaderGuard(HeaderName) { + // FIXME: use clang-tidy to generate the header guard. + std::replace(HeaderGuard.begin(), HeaderGuard.end(), '/', '_'); + std::replace(HeaderGuard.begin(), HeaderGuard.end(), '.', '_'); + HeaderGuard = llvm::StringRef(HeaderGuard).upper(); +} + +void HeaderGenerator::addAlias(llvm::StringRef NewName, llvm::StringRef TypeName) { + Aliases.emplace_back(NewName, TypeName); +} + +void HeaderGenerator::addInclude(llvm::StringRef IncludeHeader) { + Includes.push_back(IncludeHeader); +} + +// FIXME: format generated code. +std::string HeaderGenerator::generateContent() const { + std::vector Lines; + Lines.push_back("#ifndef " + HeaderGuard); + Lines.push_back("#define " + HeaderGuard); + for (const auto &Include : Includes) + Lines.push_back("#include \"" + Include + "\""); + + Namespace TopNs("", /*IsTopLevel=*/true); + // Generate namespces containing aliases. + for (const auto &Entry : Aliases) { + llvm::StringRef NewName = Entry.first; + llvm::SmallVector NewNameSplitted; + NewName.split(NewNameSplitted, "::"); + auto *CurNs = &TopNs; + for (auto I = NewNameSplitted.begin(), E = NewNameSplitted.end() - 1; + I != E; ++I) + CurNs = CurNs->addNestedNamespace(*I); + CurNs->addAlias(NewNameSplitted.back(), Entry.second); + } + + Lines.push_back(TopNs.GenerateNamespaceCodeBlock()); + Lines.push_back("#endif // " + HeaderGuard); + return llvm::join(Lines.begin(), Lines.end(), "\n"); +} + +} // namespace migrate_tool +} // namespace clang Index: migrate-tool/MigrateTool.h =================================================================== --- /dev/null +++ migrate-tool/MigrateTool.h @@ -0,0 +1,100 @@ +//===-- MigrateTool.h - Migrate class, function etc. ------------*- 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_MIGRATE_TOOL_MIGRATETOOL_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATETOOL_H + +#include "AffectedFilesFinder.h" +#include "BuildManager.h" +#include "RefactoringManager.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace migrate_tool { + +// This tool automates the entire process of migrating a C++ symbol (class, +// function, enum etc.) including renaming a symbol and/or moving a symbol +// across namespace and/or files. Besides moving the symbol definition, it also +// takes care of users of the symbol (i.e. update all references of old symbol +// to new symbol) as well as build rules. +// The migration path is divided into 3 phases: +// 1. Preparation phase. +// Create an alias from the old symbol name to to new symbol name. If the +// symbol is to be moved acrossed files, a new temporary header containing +// type alias is created. +// 2. Renaming phase. +// Update all affected projects (i.e. users of the old symbol) to use the +// new symbol name via the alias above. +// 3. Migration phase. +// Actual migration happens at this phase after all affected projects are +// updated. +// Move the symbol definition to the new file if the symbol is to be moved +// to a different file. +// Rename the symbol (i.e. the definition) if the symbol is to be renamed. +// Change the namespace in which the symbol is defined if the symbol is to +// be moved to a different namespace. +// With this migration path, we can divide the three phases into three separate +// patches. For large scale changes where many projects are affected, the +// renaming phase can be splitted into multiple smaller patches to make the +// both refactoring and review process easier. +class MigrateTool { +public: + class MigrateSpec { + public: + enum class MigrateType { + Class, + Unknown, + }; + + llvm::StringRef getOldHeader() const { return OldHeader; } + llvm::StringRef getNewHeader() const { return NewHeader; } + llvm::StringRef getOldName() const { return OldName; } + llvm::StringRef getNewName() const { return NewName; } + + private: + std::string OldHeader; + std::string NewHeader; + std::string OldName; + std::string NewName; + }; + + // FIXME: support handling multiple `MigrateSpec`s in one run. + MigrateTool(const MigrateSpec &Spec, BuildManager *BuildMgr, + RefactoringManager *Refactor, AffectedFilesFinder *Finder); + + llvm::Error run(); + +private: + // Preparation phase. See comment for the class above. + llvm::Error prepare(); + + // Renaming phase. See comment for the class above. + llvm::Error rename(); + + // Migration phase. See comment for the class above. + llvm::Error migrate(); + + // Create a new file with the given `Content`. + llvm::Error createFile(llvm::StringRef FilePath, llvm::StringRef Content); + + MigrateSpec Spec; + // A codebase-dependent `BuildManager` that takes care of build rules during + // the migration. + BuildManager *BuildMgr; + // A codebase-dependent `RefactoringManager` that performs some refactoring + // operations. + RefactoringManager *RefactorMgr; + // Finds files that are affected by refactoring actions. + AffectedFilesFinder *Finder; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATETOOL_H Index: migrate-tool/MigrateTool.cpp =================================================================== --- /dev/null +++ migrate-tool/MigrateTool.cpp @@ -0,0 +1,134 @@ +//===-- MigrateTool.cpp - Migrate class, function etc. ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MigrateTool.h" +#include "HeaderGenerator.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Process.h" +#include +#include + +namespace clang { +namespace migrate_tool { + +MigrateTool::MigrateTool(const MigrateSpec &Spec, BuildManager *BuildMgr, + RefactoringManager *Refactor, + AffectedFilesFinder *Finder) + : Spec(Spec), BuildMgr(BuildMgr), RefactorMgr(Refactor), Finder(Finder) {} + +llvm::Error MigrateTool::createFile(llvm::StringRef FilePath, + llvm::StringRef Content) { + std::error_code EC; + llvm::raw_fd_ostream File(FilePath.str(), EC, + llvm::sys::fs::OpenFlags::F_None); + if (EC) + return llvm::errorCodeToError(EC); + File << Content.str(); + File.close(); + return llvm::Error::success(); +} + +llvm::Error MigrateTool::prepare() { + llvm::outs() << "Preparation phase.\n"; + HeaderGenerator NewHeader(Spec.getNewHeader()); + if (Spec.getNewHeader() == Spec.getOldHeader()) { + // FIXME: rename class within the same file. No need for new header. Just + // insert the alias in the old header. + return llvm::make_error( + "Inplace renaming is not supported yet.", + llvm::inconvertibleErrorCode()); + } + // Construct a new header for the symbol alias. + NewHeader.addInclude(Spec.getOldHeader()); + if (Spec.getNewName() != Spec.getOldName()) + NewHeader.addAlias(Spec.getNewName(), Spec.getOldName()); + + // Create new header and build target. + std::string Code = NewHeader.generateContent(); + if (auto Err = createFile(NewHeader.getName(), Code)) + return Err; + BuildMgr->addLibrary({NewHeader.getName()}); + + return llvm::Error::success(); +} + +llvm::Error MigrateTool::rename() { + llvm::outs() << "Rename phase.\n"; + // Get all files that are affected by the migration, i.e. users of the symbol. + auto Files = Finder->getAffectedFiles(Spec.getOldName()); + if (!Files) + return Files.takeError(); + llvm::SmallVector, 4> Renames; + std::set NewIncludes; + if (Spec.getOldName() != Spec.getNewName()) + Renames.emplace_back(Spec.getOldName(), Spec.getNewName()); + if (Spec.getOldHeader() != Spec.getNewHeader()) + NewIncludes.insert(Spec.getNewHeader()); + if (auto Err = RefactorMgr->renameSymbolsInFiles(Renames, *Files)) + return Err; + if (auto Err = RefactorMgr->addIncludesToFiles(NewIncludes, *Files)) + return Err; + // Add dependency to affected target. + std::string NewTarget = BuildMgr->getBuildTargetForFile(Spec.getNewHeader()); + for (llvm::StringRef File : *Files) + BuildMgr->addDependency(BuildMgr->getBuildTargetForFile(File), NewTarget); + return llvm::Error::success(); +} + +static inline llvm::StringRef +extractNamespaceFromQualifiedName(llvm::StringRef QualifiedName) { + return QualifiedName.rsplit(':').second; +} + +llvm::Error MigrateTool::migrate() { + llvm::outs() << "Migrate phase.\n"; + // Move symbol definitions, rename symbol definitions, change + // surroudning namespaces, and update build rules if necessary. + llvm::SmallVector, 4> Renames; + if (Spec.getOldName() != Spec.getNewName()) + Renames.emplace_back(Spec.getOldName(), Spec.getNewName()); + llvm::SmallVector MovedSymbols; + MovedSymbols.push_back(Spec.getOldName()); + // FIXME: consider source files. Need to determine whether source files send + // with ".cpp" or ".cc" etc. + // FIXME: copy dependencies from old target to new target. + if (auto Err = RefactorMgr->moveSymbolsAcrossFiles( + MovedSymbols, Spec.getOldHeader(), Spec.getNewHeader())) + return Err; + if (auto Err = + RefactorMgr->renameSymbolsInFiles(Renames, {Spec.getNewHeader()})) + return Err; + + llvm::StringRef OldNamespace = + extractNamespaceFromQualifiedName(Spec.getOldName()); + llvm::StringRef NewNamespace = + extractNamespaceFromQualifiedName(Spec.getNewName()); + + llvm::SmallVector Files; + Files.push_back(Spec.getNewHeader()); + if (auto Err = RefactorMgr->changeNamespaceInFiles( + OldNamespace, NewNamespace, Spec.getNewHeader(), Files)) + return Err; + + return llvm::Error::success(); +} + +llvm::Error MigrateTool::run() { + if (auto Err = prepare()) + return Err; + if (auto Err = rename()) + return Err; + if (auto Err = migrate()) + return Err; + return llvm::Error::success(); +} + +} // namespace migrate_tool +} // namespace clang Index: migrate-tool/RefactoringManager.h =================================================================== --- /dev/null +++ migrate-tool/RefactoringManager.h @@ -0,0 +1,54 @@ +//===-- RefactoringManager.h - Perform refactoring on files. ------*- 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_MIGRATE_TOOL_REFACTORINGMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_REFACTORINGMANAGER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace migrate_tool { + +// This defines interfaces for a codebase-dependent `RefactoringManager` that +// performs some refactoring operations. +class RefactoringManager { +public: + // Adds #include directives in \p Includes to each file in \p Files. + virtual llvm::Error + addIncludesToFiles(const std::set &Includes, + llvm::ArrayRef Files) = 0; + + // Applies all rename rules in \p Renames on all \p Files. + // Rename rules are a set of pair. + virtual llvm::Error renameSymbolsInFiles( + llvm::ArrayRef> Renames, + llvm::ArrayRef Files) = 0; + + // Changes namespace \p OldNamespace to \p NewNamespace in \p Files and + // the corresponding files in translation units that match the given \p + // FilePattern. + virtual llvm::Error changeNamespaceInFiles( + llvm::StringRef OldNamespace, llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, llvm::ArrayRef Files) = 0; + + // Moves definitions of \p Symbols from \p OldHeader and the corresponding + // source file to \p NewHeader and the new source file. + virtual llvm::Error + moveSymbolsAcrossFiles(llvm::ArrayRef Symbols, + llvm::StringRef OldHeader, + llvm::StringRef NewHeader) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_REFACTORINGMANAGER_H Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory(clang-query) add_subdirectory(clang-tidy) add_subdirectory(include-fixer) +add_subdirectory(migrate-tool) Index: unittests/migrate-tool/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/migrate-tool/CMakeLists.txt @@ -0,0 +1,25 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +get_filename_component(MIGRATE_TOOL_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../../migrate-tool REALPATH) +include_directories( + ${MIGRATE_TOOL_SOURCE_DIR} + ) + +# We'd like to clang/unittests/Tooling/RewriterTestContext.h in the test. +include_directories(${CLANG_SOURCE_DIR}) + +add_extra_unittest(MigrateToolTests + HeaderBuildTest.cpp + ) + +target_link_libraries(MigrateToolTests + migrateTool + clangBasic + clangFormat + clangRewrite + clangTooling + clangToolingCore + ) Index: unittests/migrate-tool/HeaderBuildTest.cpp =================================================================== --- /dev/null +++ unittests/migrate-tool/HeaderBuildTest.cpp @@ -0,0 +1,100 @@ +//===-- HeaderGeneratorTest.cpp - HeaderGenerator unit tests ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGenerator.h" +#include "gtest/gtest.h" + +namespace clang { +namespace migrate_tool { + +TEST(HeaderGenerator, Empty) { + HeaderGenerator Hdr("a.h"); + std::string Expected = "#ifndef A_H\n" + "#define A_H\n" + "\n" + "#endif // A_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, SingleAlias) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "} // namespace nb\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, SingleAliasWithIncludes) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addInclude("x/y/z.h"); + Hdr.addInclude("x/y/zz.h"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "#include \"x/y/z.h\"\n" + "#include \"x/y/zz.h\"\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "} // namespace nb\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, MultipleAliasInOneNamespace) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + Hdr.addAlias("na::nb::D", "x::y::D"); + Hdr.addAlias("na::nb::Q", "x::y::Q"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "using D = ::x::y::D;\n" + "using Q = ::x::y::Q;\n" + "} // namespace nb\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, AliasesInMultipleNamespace) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addAlias("nb::Q", "x::Q"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + Hdr.addAlias("na::nc::D", "x::y::D"); + Hdr.addAlias("na::nb::Q", "x::y::Q"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "namespace nb {\n" + "using Q = ::x::Q;\n" + "} // namespace nb\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "using Q = ::x::y::Q;\n" + "} // namespace nb\n" + "namespace nc {\n" + "using D = ::x::y::D;\n" + "} // namespace nc\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +} // namespace migrate_tool +} // namespace clang