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,53 @@ +//===-- 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/ArrayRef.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: + virtual ~AffectedFilesFinder() {} + + // Get all files that need to be updated when a symbol is renamed and/or + // moved. + // \param Symbol A fully qualified symbol name. + // \returns Absolute paths of all affected files if the symbol is found; + // otherwise, this returns an llvm::StringError. + virtual llvm::Expected> + getAffectedFiles(llvm::StringRef Symbol) = 0; +}; + +// This simply returns a given set of files as the affected files. +class IdentityFilesFinder : public AffectedFilesFinder { +public: + IdentityFilesFinder(llvm::ArrayRef Files) : Files(Files) {} + + virtual llvm::Expected> + getAffectedFiles(llvm::StringRef Symbol) { + return Files; + } + +private: + std::vector Files; +}; + +} // 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,51 @@ +//===-- 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, which +// provides interfaces to create/modify build rules in the codebase and query +// existing build rules. +// Build rules are rules for generating build targets, which can be programs, +// libraries, and tests. +// Each build rule/target in the codebase is uniquely identified a string name. +class BuildManager { +public: + virtual ~BuildManager() {} + + // Adds build rule with \p Sources as source files for a new library build + // target. The name of the new library will be \p Name if it is provided; + // otherwise, the name will be auto-generated. + // If there is existing target with name \p Name, the old rule will be + // overridden. + // \returns True if the rule is successfully added. + virtual bool addLibrary(llvm::ArrayRef Sources, + llvm::StringRef Name = llvm::StringRef()) = 0; + + // Adds a new dependency to the build target. + // A dependency is a build target that \p BuildTarget depends on and needs in + // order to be genertaed. + 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/FileSystemManager.h =================================================================== --- /dev/null +++ migrate-tool/FileSystemManager.h @@ -0,0 +1,41 @@ +//===-- FileSystemManager.h - Reading/writing file systems. --------*- 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_FILESYSTEMMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_FILESYSTEMMANAGER_H + +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace migrate_tool { + +// This provides interfaces for reading and writing files in a specific file +// system. +class FileSystemManager { +public: + virtual ~FileSystemManager() {} + + // Returns true a file with \p FilePath exists. + virtual bool exists(llvm::StringRef FilePath) = 0; + + // Reads content of a file from the underlying file system. + virtual llvm::Expected readFile(llvm::StringRef FilePath) = 0; + + // Creates a file at \p FilePath with \p Content. If the file already exists, + // it will be overwritten. + virtual llvm::Error createFile(llvm::StringRef FilePath, + llvm::StringRef Code) = 0; + + virtual void deleteFile(llvm::StringRef FilePath) = 0; +}; + +} // namespace migrate_tool +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_FILESYSTEMMANAGER_H 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: + explicit 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,113 @@ +//===-- 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,88 @@ +//===-- 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 "MigrationEnvironment.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: + MigrateSpec(llvm::StringRef OldHeader, llvm::StringRef NewHeader, + llvm::StringRef OldName, llvm::StringRef NewName) + : OldHeader(OldHeader), NewHeader(NewHeader), OldName(OldName), + NewName(NewName) {} + + 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, MigrationEnvironment &Env); + + // 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(); + +private: + MigrateSpec Spec; + + // The environment contains codebase-dependent components like BuildManager, + // RefactoringManager, AffectedFilesFinder etc. + MigrationEnvironment &Env; +}; + +} // 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,114 @@ +//===-- 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, MigrationEnvironment &Env) + : Spec(Spec), Env(Env) {} + +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 = + Env.getFileSystemManager().createFile(NewHeader.getName(), Code)) + return Err; + Env.getBuildManager().addLibrary({NewHeader.getName()}); + + return llvm::Error::success(); +} + +llvm::Error MigrateTool::rename() { + llvm::outs() << "Rename phase...\n"; + RefactoringManager::SymbolRenameSpec RenameSpec(Spec.getOldName(), + Spec.getNewName()); + if (Spec.getOldHeader() != Spec.getNewHeader()) { + // FIXME: if all symbols in the old header has been moved to the new header, + // we should also delete all #include's of the old header. + RenameSpec.NewHeader = Spec.getNewHeader(); + RenameSpec.NewDependency = + Env.getBuildManager().getBuildTargetForFile(Spec.getNewHeader()); + } + + return Env.getRefactoringManager().renameSymbolsInAffectedFiles( + Env.getAffectedFilesFinder(), Env.getBuildManager(), RenameSpec); +} + +// Splits a qualified symbol name into a pair of namespace and unqialified name. +static std::pair +splitQualifiedName(llvm::StringRef QualifiedName) { + auto splitted = QualifiedName.rsplit(':'); + return splitted.second.empty() + ? std::make_pair("", QualifiedName) + : std::make_pair(splitted.first.drop_back(1), splitted.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 MovedSymbols; + MovedSymbols.push_back(Spec.getOldName()); + // FIXME: consider source files. Need to determine whether source files end + // with ".cpp" or ".cc" etc. + // FIXME: copy dependencies from old target to new target. + // Delete the alias header. + Env.getFileSystemManager().deleteFile(Spec.getNewHeader()); + if (auto Err = Env.getRefactoringManager().moveSymbolsAcrossFiles( + MovedSymbols, Spec.getOldHeader(), Spec.getNewHeader())) + return Err; + + auto SplittedOldName = splitQualifiedName(Spec.getOldName()); + auto SplittedNewName = splitQualifiedName(Spec.getNewName()); + std::string NewNameInOldNamespace = + (SplittedOldName.first + "::" + SplittedNewName.second).str(); + RefactoringManager::SymbolRenameSpec RenameSpec(Spec.getOldName(), + NewNameInOldNamespace); + IdentityFilesFinder IdentityFiles({Spec.getNewHeader()}); + // FIXME: if the moved symbol are referenced in old header/sources, we also + // need to rename references of OldSymbol in those files after the definition + // of OldSymol is moved to new files. + if (auto Err = Env.getRefactoringManager().renameSymbolsInAffectedFiles( + IdentityFiles, Env.getBuildManager(), RenameSpec)) + return Err; + + llvm::SmallVector Files; + Files.push_back(Spec.getNewHeader()); + if (auto Err = Env.getRefactoringManager().changeNamespaceInFiles( + SplittedOldName.first, SplittedNewName.first, Spec.getNewHeader(), + Files)) + return Err; + + return llvm::Error::success(); +} + +} // namespace migrate_tool +} // namespace clang Index: migrate-tool/MigrationEnvironment.h =================================================================== --- /dev/null +++ migrate-tool/MigrationEnvironment.h @@ -0,0 +1,63 @@ +//===-- MigrationEnvironment.h - Codebase-dependent environment. --*- 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_MIGRATIONENVIRONMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATIONENVIRONMENT_H + +#include "AffectedFilesFinder.h" +#include "BuildManager.h" +#include "FileSystemManager.h" +#include "RefactoringManager.h" +#include "clang/Basic/VirtualFileSystem.h" + +namespace clang { +namespace migrate_tool { + +// This contains codebase-dependent components including BuildManager, +// RefactoringManager, and AffectedFilesFinder. +class MigrationEnvironment { +public: + MigrationEnvironment() = delete; + + // Creates a migration environement containing codebase-dependent component. + // This also takes ownership of all objects passed in. + MigrationEnvironment(BuildManager *BuildMgr, RefactoringManager *RefactorMgr, + AffectedFilesFinder *Finder, FileSystemManager *Files) + : BuildMgr(BuildMgr), RefactorMgr(RefactorMgr), Finder(Finder), + Files(Files) {} + + BuildManager &getBuildManager() { return *BuildMgr; } + + RefactoringManager &getRefactoringManager() { return *RefactorMgr; } + + AffectedFilesFinder &getAffectedFilesFinder() { return *Finder; } + + FileSystemManager &getFileSystemManager() { return *Files; } + +private: + // A codebase-dependent `BuildManager` that takes care of build rules during + // the migration. + std::unique_ptr BuildMgr; + + // A codebase-dependent `RefactoringManager` that performs some refactoring + // operations. + std::unique_ptr RefactorMgr; + + // Finds files that are affected by refactoring actions. + std::unique_ptr Finder; + + // A file system dependent `FileSystemManager` that provides interfaces for + // accessing and modifying files in a file system. + std::unique_ptr Files; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATIONENVIRONMENT_H Index: migrate-tool/RefactoringManager.h =================================================================== --- /dev/null +++ migrate-tool/RefactoringManager.h @@ -0,0 +1,67 @@ +//===-- 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 "AffectedFilesFinder.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: + virtual ~RefactoringManager() {} + + // FIXME: when we support removing old header and old target, add OldHeader + // and OldDependency so that they can be removed from users. + struct SymbolRenameSpec { + SymbolRenameSpec(llvm::StringRef OldSymbol, llvm::StringRef NewSymbol) + : OldSymbol(OldSymbol), NewSymbol(NewSymbol) {} + + std::string OldSymbol; + std::string NewSymbol; + + // If specified, NewHeader #include is added to files that use OldSymbol. + std::string NewHeader = ""; + + // If specified, add the following dependencies to targets that reference + // the OldSymol. + std::string NewDependency = ""; + }; + + virtual llvm::Error + renameSymbolsInAffectedFiles(AffectedFilesFinder &Finder, + BuildManager &BuildManager, + const SymbolRenameSpec &RenameSpec) = 0; + + // Changes namespace \p OldNamespace to \p NewNamespace in files that matches + // \p FilePattern in translation units \p TUs . + virtual llvm::Error changeNamespaceInFiles( + llvm::StringRef OldNamespace, llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, llvm::ArrayRef TUs) = 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 @@ -11,3 +11,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,27 @@ +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 + DummyMigrateToolTest.cpp + DummyMigrationEnvironment.cpp + HeaderBuildTest.cpp + ) + +target_link_libraries(MigrateToolTests + migrateTool + clangBasic + clangFormat + clangRewrite + clangTooling + clangToolingCore + ) Index: unittests/migrate-tool/DummyMigrateToolTest.cpp =================================================================== --- /dev/null +++ unittests/migrate-tool/DummyMigrateToolTest.cpp @@ -0,0 +1,216 @@ +//===-- DummyMigrateToolTest.cpp - Dummy migrate tool unit tests ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DummyMigrationEnvironment.h" +#include "HeaderGenerator.h" +#include "MigrateTool.h" +#include "gtest/gtest.h" + +namespace clang { +namespace migrate_tool { +namespace { + +class DummyMigrateToolTest : public ::testing::Test { + protected: + void setupMigrateTool(const MigrateTool::MigrateSpec &Spec, + llvm::ArrayRef AffectedFiles) { + Env = createDummyMigrationEnvironment(&DummyFS, &BuildRules, AffectedFiles); + Tool = llvm::make_unique(Spec, *Env); + EXPECT_NE(nullptr, Tool.get()); + } + + bool prepare() { + if (auto Err = Tool->prepare()) { + llvm::errs() << "Failed preparation phase: " + << llvm::toString(std::move(Err)) << "\n"; + return false; + } + return true; + } + + bool rename() { + if (auto Err = Tool->rename()) { + llvm::errs() << "Failed preparation phase: " + << llvm::toString(std::move(Err)) << "\n"; + return false; + } + return true; + } + + bool migrate() { + if (auto Err = Tool->migrate()) { + llvm::errs() << "Failed migration phase: " + << llvm::toString(std::move(Err)) << "\n"; + return false; + } + return true; + } + + llvm::StringRef readFile(llvm::StringRef FileName) { + return DummyFS[FileName]; + } + + std::string createSingleAliasHeader(llvm::StringRef HeaderName, + llvm::StringRef OldSymbol, + llvm::StringRef NewSymbol, + llvm::StringRef Include) { + HeaderGenerator Hdr(HeaderName); + Hdr.addAlias(NewSymbol, OldSymbol); + Hdr.addInclude(Include); + return Hdr.generateContent(); + } + + llvm::StringMap DummyFS; + llvm::StringMap BuildRules; + +private: + std::unique_ptr Env; + std::unique_ptr Tool; +}; + +TEST_F(DummyMigrateToolTest, MoveWithoutAffectedFiles) { + llvm::StringRef OldSymbol = "x::y::Old"; + llvm::StringRef NewSymbol = "a::b::New"; + llvm::StringRef OldHeader = "old.h"; + llvm::StringRef NewHeader = "new.h"; + llvm::StringRef OldTarget = "old"; + llvm::StringRef NewTarget = "new"; + + std::string OldHeaderContent = + DummyFile(/*Includes=*/{}, /*SymbolDefinitions=*/{OldSymbol}, + /*SymbolReferences=*/{}) + .toYamlString(); + DummyFS[OldHeader] = OldHeaderContent; + + std::string OldHeaderContentAfterMigration = DummyFile().toYamlString(); + + std::string NewHeaderContentAfterMigration = + DummyFile(/*Includes=*/{}, /*SymbolDefinitions=*/{NewSymbol}, + /*SymbolReferences=*/{}) + .toYamlString(); + + auto OldRule = + DummyBuildManager::BuildRule(OldTarget, /*Sources=*/{OldHeader}, + /*Dependencies=*/{}); + auto NewRule = + DummyBuildManager::BuildRule(NewTarget, /*Sources=*/{NewHeader}, + /*Dependencies=*/{}); + BuildRules[OldTarget] = OldRule; + + setupMigrateTool( + MigrateTool::MigrateSpec(OldHeader, NewHeader, OldSymbol, NewSymbol), + /*AffectedFiles=*/{}); + + EXPECT_TRUE(prepare()); + std::string AliasHeaderContent = + createSingleAliasHeader(NewHeader, OldSymbol, NewSymbol, OldHeader); + EXPECT_EQ(OldHeaderContent, DummyFS[OldHeader]); + EXPECT_EQ(AliasHeaderContent, DummyFS[NewHeader]); + EXPECT_EQ(OldRule, BuildRules[OldTarget]); + EXPECT_EQ(NewRule, BuildRules[NewTarget]); + + EXPECT_TRUE(rename()); + // No affected file, so everything should remain unchanged. + EXPECT_EQ(2u, DummyFS.size()); + EXPECT_EQ(OldHeaderContent, DummyFS[OldHeader]); + EXPECT_EQ(AliasHeaderContent, DummyFS[NewHeader]); + EXPECT_EQ(OldRule, BuildRules[OldTarget]); + EXPECT_EQ(NewRule, BuildRules[NewTarget]); + + EXPECT_TRUE(migrate()); + EXPECT_EQ(2u, DummyFS.size()); + EXPECT_EQ(OldHeaderContentAfterMigration, DummyFS[OldHeader]); + EXPECT_EQ(NewHeaderContentAfterMigration, DummyFS[NewHeader]); +} + +TEST_F(DummyMigrateToolTest, MoveWithAffectedFiles) { + llvm::StringRef OldSymbol = "x::y::Old"; + llvm::StringRef NewSymbol = "a::b::New"; + llvm::StringRef OldHeader = "old.h"; + llvm::StringRef NewHeader = "new.h"; + llvm::StringRef OldTarget = "old"; + llvm::StringRef NewTarget = "new"; + llvm::StringRef AffectedFile = "user.h"; + llvm::StringRef AffectedTarget = "user"; + + std::string OldHeaderContent = + DummyFile(/*Includes=*/{}, /*SymbolDefinitions=*/{OldSymbol}, + /*SymbolReferences=*/{}) + .toYamlString(); + DummyFS[OldHeader] = OldHeaderContent; + + std::string OldHeaderContentAfterMigration = DummyFile().toYamlString(); + + std::string NewHeaderContentAfterMigration = + DummyFile(/*Includes=*/{}, /*SymbolDefinitions=*/{NewSymbol}, + /*SymbolReferences=*/{}) + .toYamlString(); + + std::string OldAffectedFileContent = + DummyFile(/*Includes=*/{OldHeader}, /*SymbolDefinitions=*/{"x::y::Z"}, + /*SymbolReferences=*/{OldSymbol}) + .toYamlString(); + + std::string NewAffectedFileContent = + DummyFile(/*Includes=*/{OldHeader, NewHeader}, + /*SymbolDefinitions=*/{"x::y::Z"}, + /*SymbolReferences=*/{NewSymbol}) + .toYamlString(); + DummyFS[AffectedFile] = OldAffectedFileContent; + + auto OldRule = + DummyBuildManager::BuildRule(OldTarget, /*Sources=*/{OldHeader}, + /*Dependencies=*/{}); + auto NewRule = + DummyBuildManager::BuildRule(NewTarget, /*Sources=*/{NewHeader}, + /*Dependencies=*/{}); + BuildRules[OldTarget] = OldRule; + + auto OldAffectedRule = + DummyBuildManager::BuildRule(AffectedTarget, /*Sources=*/{AffectedFile}, + /*Dependencies=*/{OldTarget}); + auto NewAffectedRule = + DummyBuildManager::BuildRule(AffectedTarget, /*Sources=*/{AffectedFile}, + /*Dependencies=*/{OldTarget, NewTarget}); + BuildRules[AffectedTarget] = OldAffectedRule; + + setupMigrateTool( + MigrateTool::MigrateSpec(OldHeader, NewHeader, OldSymbol, NewSymbol), + /*AffectedFiles=*/{AffectedFile}); + + EXPECT_TRUE(prepare()); + std::string AliasHeaderContent = + createSingleAliasHeader(NewHeader, OldSymbol, NewSymbol, OldHeader); + EXPECT_EQ(OldHeaderContent, DummyFS[OldHeader]); + EXPECT_EQ(AliasHeaderContent, DummyFS[NewHeader]); + EXPECT_EQ(OldRule, BuildRules[OldTarget]); + EXPECT_EQ(NewRule, BuildRules[NewTarget]); + + EXPECT_EQ(OldAffectedFileContent, DummyFS[AffectedFile]); + EXPECT_EQ(OldAffectedRule, BuildRules[AffectedTarget]); + + EXPECT_TRUE(rename()); + EXPECT_EQ(OldHeaderContent, DummyFS[OldHeader]); + EXPECT_EQ(AliasHeaderContent, DummyFS[NewHeader]); + EXPECT_EQ(OldRule, BuildRules[OldTarget]); + EXPECT_EQ(NewRule, BuildRules[NewTarget]); + + EXPECT_EQ(NewAffectedFileContent, DummyFS[AffectedFile]); + EXPECT_EQ(NewAffectedRule, BuildRules[AffectedTarget]); + + EXPECT_TRUE(migrate()); + EXPECT_EQ(3u, DummyFS.size()); + EXPECT_EQ(OldHeaderContentAfterMigration, DummyFS[OldHeader]); + EXPECT_EQ(NewHeaderContentAfterMigration, DummyFS[NewHeader]); + EXPECT_EQ(NewAffectedFileContent, DummyFS[AffectedFile]); +} + +} // anonymous namespace +} // namespace migrate_tool +} // namespace clang Index: unittests/migrate-tool/DummyMigrationEnvironment.h =================================================================== --- /dev/null +++ unittests/migrate-tool/DummyMigrationEnvironment.h @@ -0,0 +1,154 @@ +//===-- DummyMigrationEnvironment.h - Dummy environment. --------*- 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_DUMMYMIGRATIONENVIRONMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_DUMMYMIGRATIONENVIRONMENT_H + +#include "MigrationEnvironment.h" +#include "clang/Basic/VirtualFileSystem.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { +namespace migrate_tool { + +// A representation of a file in the dummy codebase. It can be converted to +// string in YAML format. +class DummyFile { +public: + DummyFile() {} + + DummyFile(llvm::ArrayRef Includes, + llvm::ArrayRef SymbolDefinitions, + llvm::ArrayRef SymbolReferences) + : Includes(Includes), SymbolDefinitions(SymbolDefinitions), + SymbolReferences(SymbolReferences) {} + + std::string toYamlString(); + + // #include headers in this file. + std::vector Includes; + // Fully qualified names of symbols that are defined in this file. + std::vector SymbolDefinitions; + // Fully qualified names of symbols that are referenced in this file. + std::vector SymbolReferences; +}; + +// A dummy `BuildManager` for the dummy codebase. The build rules of the +// codebase is stored in a map from target names to build rules. +class DummyBuildManager : public BuildManager { +public: + // A build rule in the dummy build system. + class BuildRule { + public: + BuildRule() {} + + BuildRule(llvm::StringRef TargetName, llvm::ArrayRef Sources, + llvm::ArrayRef Dependencies) + : TargetName(TargetName), Sources(Sources), Dependencies(Dependencies) { + } + + bool operator==(const BuildRule &RHS) const { + return TargetName == RHS.TargetName && Sources == RHS.Sources && + Dependencies == RHS.Dependencies; + } + + std::string TargetName; + std::vector Sources; + std::vector Dependencies; + }; + + DummyBuildManager(llvm::StringMap *BuildRules); + + bool addLibrary(llvm::ArrayRef Sources, + llvm::StringRef Name = llvm::StringRef()) override; + + bool addDependency(llvm::StringRef BuildTarget, + llvm::StringRef Dependency) override; + + std::string getBuildTargetForFile(llvm::StringRef File) override; + +private: + // A reference to all build rules in the codebase. + llvm::StringMap *BuildRules; + // A (cached) mapping from file paths to the target that contains it. + llvm::StringMap FileToBuildTarget; +}; + +// This performas refactoring actions on dummy files in the codebase. +class DummyRefactoringManager : public RefactoringManager { +public: + DummyRefactoringManager(FileSystemManager *Files) : FileSystemMgr(Files) {} + + llvm::Error renameSymbolsInAffectedFiles( + AffectedFilesFinder &Finder, BuildManager &BuildMgr, + const RefactoringManager::SymbolRenameSpec &RenameSpec) override; + + llvm::Error + changeNamespaceInFiles(llvm::StringRef OldNamespace, + llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, + llvm::ArrayRef Files) override; + + llvm::Error moveSymbolsAcrossFiles(llvm::ArrayRef Symbols, + llvm::StringRef OldHeader, + llvm::StringRef NewHeader) override; + +private: + FileSystemManager *FileSystemMgr; +}; + +// This stores a set of pre-computed affected files, which is provided during +// instantiation. +class DummyAffectedFilesFinder : public AffectedFilesFinder { +public: + DummyAffectedFilesFinder(llvm::ArrayRef &AffectedFiles) + : AffectedFiles(AffectedFiles) {} + + llvm::Expected> + getAffectedFiles(llvm::StringRef Symbol); + +private: + std::vector AffectedFiles; +}; + +// This provides interfaces to access/modify the dummy file system, which is +// just a mapping from file names to file content. +class DummyFileSystemManager : public FileSystemManager { +public: + DummyFileSystemManager(llvm::StringMap *DummyFS) + : DummyFS(DummyFS) {} + + bool exists(llvm::StringRef FilePath) override; + + llvm::Expected readFile(llvm::StringRef FilePath) override; + + llvm::Error createFile(llvm::StringRef FilePath, + llvm::StringRef Code) override; + + void deleteFile(llvm::StringRef FilePath) override; + +private: + llvm::StringMap *DummyFS; +}; + +// Creates a dummy migration environment with the dummy file system and dummy +// build rules. +// \param DummyFS A dummy file system where all files in the codebase live. +// \param BuildRules A reference to all build rules in the codebase. +// \param AffectedFiles A set of pre-computed affected files for a migration +// action. +std::unique_ptr createDummyMigrationEnvironment( + llvm::StringMap *DummyFS, + llvm::StringMap *BuildRules, + llvm::ArrayRef AffectedFiles); + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_DUMMYMIGRATIONENVIRONMENT_H Index: unittests/migrate-tool/DummyMigrationEnvironment.cpp =================================================================== --- /dev/null +++ unittests/migrate-tool/DummyMigrationEnvironment.cpp @@ -0,0 +1,247 @@ +//===-- DummyMigrationEnvironment.cpp - Dummy environment. ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DummyMigrationEnvironment.h" +#include "llvm/Support/YAMLTraits.h" + +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(std::string) + +namespace llvm { +namespace yaml { +template <> struct MappingTraits { + static void mapping(IO &io, clang::migrate_tool::DummyFile &File) { + io.mapOptional("Includes", File.Includes); + io.mapOptional("SymbolDefinitions", File.SymbolDefinitions); + io.mapOptional("SymbolReferences", File.SymbolReferences); + } +}; +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace migrate_tool { + +std::string DummyFile::toYamlString() { + std::string Content; + llvm::raw_string_ostream OS(Content); + llvm::yaml::Output YOut(OS); + YOut << *this; + OS.flush(); + return Content; +} + +DummyBuildManager::DummyBuildManager(llvm::StringMap *BuildRules) + : BuildRules(BuildRules) { + for (const auto &Rule : *BuildRules) + for (const auto &File : Rule.second.Sources) + FileToBuildTarget[File] = Rule.first(); +} + +bool DummyBuildManager::addLibrary(llvm::ArrayRef Sources, + llvm::StringRef Name) { + if (Sources.empty()) + return false; + // If `Name` is not specified, use the name of the first file as the library + // name. + if (Name.empty()) { + Name = Sources[0]; + Name = Name.substr(0, Name.find_last_of('.')); + } + + auto Rule = BuildRules->find(Name); + if (Rule != BuildRules->end()) { + // Update the cached mapping from files to build targets. + for (const auto &Source : Rule->second.Sources) { + auto I = FileToBuildTarget.find(Source); + if (I != FileToBuildTarget.end()) + FileToBuildTarget.erase(I); + } + // Override the existing rule. + Rule->second.Sources = Sources; + Rule->second.Dependencies.clear(); + } else { + // Create a new rule. + (*BuildRules)[Name] = BuildRule(Name, Sources, {}); + } + llvm::outs() << "Adding library \"" << Name << "\" with soucee files [" + << llvm::join(Sources.begin(), Sources.end(), ",") << "]\n"; + for (auto Source : Sources) { + FileToBuildTarget[Source] = Name; + } + return true; +} + +bool DummyBuildManager::addDependency(llvm::StringRef BuildTarget, + llvm::StringRef Dependency) { + auto I = BuildRules->find(BuildTarget); + if (I == BuildRules->end()) + return false; + I->second.Dependencies.push_back(Dependency); + return true; +} + +std::string DummyBuildManager::getBuildTargetForFile(llvm::StringRef File) { + return FileToBuildTarget[File]; +} + +static llvm::Expected +readFileAndParse(llvm::StringRef FileName, FileSystemManager *FileSystemMgr) { + llvm::Expected Content = FileSystemMgr->readFile(FileName); + if (!Content) + return Content.takeError(); + llvm::yaml::Input YIn(*Content); + DummyFile ParsedFile; + YIn >> ParsedFile; + if (YIn.error()) + return llvm::errorCodeToError(YIn.error()); + return ParsedFile; +} + +llvm::Error DummyRefactoringManager::renameSymbolsInAffectedFiles( + AffectedFilesFinder &Finder, BuildManager &BuildMgr, + const RefactoringManager::SymbolRenameSpec &RenameSpec) { + llvm::Expected> Files = + Finder.getAffectedFiles(RenameSpec.OldSymbol); + if (!Files) + return Files.takeError(); + + llvm::outs() << "Renaming:\n"; + llvm::outs() << " " << RenameSpec.OldSymbol << " to " << RenameSpec.NewSymbol + << "\n"; + llvm::outs() << "in files [" << llvm::join(Files->begin(), Files->end(), ",") + << "]\n"; + for (const auto &File : *Files) { + llvm::Expected ParsedFile = + readFileAndParse(File, FileSystemMgr); + if (!ParsedFile) + return ParsedFile.takeError(); + // Rename all definitions/references of OldSymbol to NewSymbol. + for (auto &Definition : ParsedFile->SymbolDefinitions) + if (Definition == RenameSpec.OldSymbol) + Definition = RenameSpec.NewSymbol; + + for (auto &Reference : ParsedFile->SymbolReferences) + if (Reference == RenameSpec.OldSymbol) + Reference = RenameSpec.NewSymbol; + + if (!RenameSpec.NewHeader.empty()) + ParsedFile->Includes.push_back(RenameSpec.NewHeader); + + if (!RenameSpec.NewDependency.empty()) + BuildMgr.addDependency(BuildMgr.getBuildTargetForFile(File), + RenameSpec.NewDependency); + + FileSystemMgr->createFile(File, ParsedFile->toYamlString()); + } + return llvm::Error::success(); +} + +llvm::Error DummyRefactoringManager::changeNamespaceInFiles( + llvm::StringRef OldNamespace, llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, llvm::ArrayRef Files) { + llvm::outs() << "Changing namespace from \"" << OldNamespace << "\" to \"" + << NewNamespace << "\" in filles matching \"" << FilePattern + << "\"\n" + << "Running change-namespace tool on files [" + << llvm::join(Files.begin(), Files.end(), ",") << "]\n"; + llvm::Regex RE(FilePattern); + for (auto File : Files) { + if (!RE.match(File)) continue; + llvm::Expected ParsedFile = + readFileAndParse(File, FileSystemMgr); + if (!ParsedFile) + return ParsedFile.takeError(); + for (auto &Symbol : ParsedFile->SymbolDefinitions) { + llvm::StringRef SymbolRef = Symbol; + if (!SymbolRef.consume_front(OldNamespace)) + continue; + Symbol = (NewNamespace + SymbolRef).str(); + } + FileSystemMgr->createFile(File, ParsedFile->toYamlString()); + } + return llvm::Error::success(); +} + +llvm::Error DummyRefactoringManager::moveSymbolsAcrossFiles( + llvm::ArrayRef Symbols, llvm::StringRef OldHeader, + llvm::StringRef NewHeader) { + llvm::outs() << "Moving symbols [" + << llvm::join(Symbols.begin(), Symbols.end(), ",") << "] from \"" + << OldHeader << "\" to \"" << NewHeader << "\"\n"; + llvm::Expected OldHeaderFile = + readFileAndParse(OldHeader, FileSystemMgr); + if (!OldHeaderFile) + return OldHeaderFile.takeError(); + + DummyFile NewHeaderFile; + if (FileSystemMgr->exists(NewHeader)) { + llvm::Expected ParsedNewHeader = + readFileAndParse(NewHeader, FileSystemMgr); + if (!ParsedNewHeader) + return ParsedNewHeader.takeError(); + NewHeaderFile = *ParsedNewHeader; + } + + std::vector RemainingSymbols; + for (auto Symbol : Symbols) { + for (const auto &Definition : OldHeaderFile->SymbolDefinitions) { + if (Symbol == Definition) + NewHeaderFile.SymbolDefinitions.push_back(Symbol); + else + RemainingSymbols.push_back(Symbol); + } + } + OldHeaderFile->SymbolDefinitions = RemainingSymbols; + FileSystemMgr->createFile(OldHeader, OldHeaderFile->toYamlString()); + FileSystemMgr->createFile(NewHeader, NewHeaderFile.toYamlString()); + return llvm::Error::success(); +} + +llvm::Expected> +DummyAffectedFilesFinder::getAffectedFiles(llvm::StringRef Symbol) { + return AffectedFiles; +} + +bool DummyFileSystemManager::exists(llvm::StringRef FilePath) { + return DummyFS->find(FilePath) != DummyFS->end(); +} + +llvm::Expected +DummyFileSystemManager::readFile(llvm::StringRef FilePath) { + auto I = DummyFS->find(FilePath); + if (I != DummyFS->end()) + return I->second; + return llvm::errorCodeToError( + std::make_error_code(std::errc::no_such_file_or_directory)); +} + +llvm::Error DummyFileSystemManager::createFile(llvm::StringRef FilePath, + llvm::StringRef Code) { + (*DummyFS)[FilePath] = Code; + return llvm::Error::success(); +} + +void DummyFileSystemManager::deleteFile(llvm::StringRef FilePath) { + auto I = DummyFS->find(FilePath); + if (I != DummyFS->end()) + DummyFS->erase(I); +} + +std::unique_ptr createDummyMigrationEnvironment( + llvm::StringMap *DummyFS, + llvm::StringMap *BuildRules, + llvm::ArrayRef AffectedFiles) { + auto *Files = new DummyFileSystemManager(DummyFS); + return llvm::make_unique( + new DummyBuildManager(BuildRules), new DummyRefactoringManager(Files), + new DummyAffectedFilesFinder(AffectedFiles), Files); +} + +} // namespace migrate_tool +} // namespace clang 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