Index: clang-tools-extra/trunk/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/CMakeLists.txt +++ clang-tools-extra/trunk/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(clang-tidy-vs) endif() +add_subdirectory(change-namespace) add_subdirectory(clang-query) add_subdirectory(include-fixer) add_subdirectory(pp-trace) Index: clang-tools-extra/trunk/change-namespace/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/change-namespace/CMakeLists.txt +++ clang-tools-extra/trunk/change-namespace/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangChangeNamespace + ChangeNamespace.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) Index: clang-tools-extra/trunk/change-namespace/ChangeNamespace.h =================================================================== --- clang-tools-extra/trunk/change-namespace/ChangeNamespace.h +++ clang-tools-extra/trunk/change-namespace/ChangeNamespace.h @@ -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 + +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 *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 &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> 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> InsertFwdDecls; +}; + +} // namespace change_namespace +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H Index: clang-tools-extra/trunk/change-namespace/ChangeNamespace.cpp =================================================================== --- clang-tools-extra/trunk/change-namespace/ChangeNamespace.cpp +++ clang-tools-extra/trunk/change-namespace/ChangeNamespace.cpp @@ -0,0 +1,509 @@ +//===-- ChangeNamespace.cpp - Change namespace implementation -------------===// +// +// 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 "clang/Format/Format.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace change_namespace { + +namespace { + +inline std::string +joinNamespaces(const llvm::SmallVectorImpl &Namespaces) { + if (Namespaces.empty()) + return ""; + std::string Result = Namespaces.front(); + for (auto I = Namespaces.begin() + 1, E = Namespaces.end(); I != E; ++I) + Result += ("::" + *I).str(); + return Result; +} + +SourceLocation startLocationForType(TypeLoc TLoc) { + // For elaborated types (e.g. `struct a::A`) we want the portion after the + // `struct` but including the namespace qualifier, `a::`. + if (TLoc.getTypeLocClass() == TypeLoc::Elaborated) { + NestedNameSpecifierLoc NestedNameSpecifier = + TLoc.castAs().getQualifierLoc(); + if (NestedNameSpecifier.getNestedNameSpecifier()) + return NestedNameSpecifier.getBeginLoc(); + TLoc = TLoc.getNextTypeLoc(); + } + return TLoc.getLocStart(); +} + +SourceLocation EndLocationForType(TypeLoc TLoc) { + // Dig past any namespace or keyword qualifications. + while (TLoc.getTypeLocClass() == TypeLoc::Elaborated || + TLoc.getTypeLocClass() == TypeLoc::Qualified) + TLoc = TLoc.getNextTypeLoc(); + + // The location for template specializations (e.g. Foo) includes the + // templated types in its location range. We want to restrict this to just + // before the `<` character. + if (TLoc.getTypeLocClass() == TypeLoc::TemplateSpecialization) + return TLoc.castAs() + .getLAngleLoc() + .getLocWithOffset(-1); + return TLoc.getEndLoc(); +} + +// Returns the containing namespace of `InnerNs` by skipping `PartialNsName`. +// If the `InnerNs` does not have `PartialNsName` as suffix, nullptr is +// returned. +// For example, if `InnerNs` is "a::b::c" and `PartialNsName` is "b::c", then +// the NamespaceDecl of namespace "a" will be returned. +const NamespaceDecl *getOuterNamespace(const NamespaceDecl *InnerNs, + llvm::StringRef PartialNsName) { + const auto *CurrentContext = llvm::cast(InnerNs); + const auto *CurrentNs = InnerNs; + llvm::SmallVector PartialNsNameSplitted; + PartialNsName.split(PartialNsNameSplitted, "::"); + while (!PartialNsNameSplitted.empty()) { + // Get the inner-most namespace in CurrentContext. + while (CurrentContext && !llvm::isa(CurrentContext)) + CurrentContext = CurrentContext->getParent(); + if (!CurrentContext) + return nullptr; + CurrentNs = llvm::cast(CurrentContext); + if (PartialNsNameSplitted.back() != CurrentNs->getNameAsString()) + return nullptr; + PartialNsNameSplitted.pop_back(); + CurrentContext = CurrentContext->getParent(); + } + return CurrentNs; +} + +// FIXME: get rid of this helper function if this is supported in clang-refactor +// library. +SourceLocation getStartOfNextLine(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LangOpts) { + if (Loc.isMacroID() && + !Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc)) + return SourceLocation(); + // Break down the source location. + std::pair LocInfo = SM.getDecomposedLoc(Loc); + // Try to load the file buffer. + bool InvalidTemp = false; + llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp); + if (InvalidTemp) + return SourceLocation(); + + const char *TokBegin = File.data() + LocInfo.second; + // Lex from the start of the given location. + Lexer Lex(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(), + TokBegin, File.end()); + + llvm::SmallVector Line; + // FIXME: this is a bit hacky to get ReadToEndOfLine work. + Lex.setParsingPreprocessorDirective(true); + Lex.ReadToEndOfLine(&Line); + // FIXME: should not +1 at EOF. + return Loc.getLocWithOffset(Line.size() + 1); +} + +// Returns `R` with new range that refers to code after `Replaces` being +// applied. +tooling::Replacement +getReplacementInChangedCode(const tooling::Replacements &Replaces, + const tooling::Replacement &R) { + unsigned NewStart = Replaces.getShiftedCodePosition(R.getOffset()); + unsigned NewEnd = + Replaces.getShiftedCodePosition(R.getOffset() + R.getLength()); + return tooling::Replacement(R.getFilePath(), NewStart, NewEnd - NewStart, + R.getReplacementText()); +} + +// Adds a replacement `R` into `Replaces` or merges it into `Replaces` by +// applying all existing Replaces first if there is conflict. +void addOrMergeReplacement(const tooling::Replacement &R, + tooling::Replacements *Replaces) { + auto Err = Replaces->add(R); + if (Err) { + llvm::consumeError(std::move(Err)); + auto Replace = getReplacementInChangedCode(*Replaces, R); + *Replaces = Replaces->merge(tooling::Replacements(Replace)); + } +} + +tooling::Replacement createReplacement(SourceLocation Start, SourceLocation End, + llvm::StringRef ReplacementText, + const SourceManager &SM) { + if (!Start.isValid() || !End.isValid()) { + llvm::errs() << "start or end location were invalid\n"; + return tooling::Replacement(); + } + if (SM.getDecomposedLoc(Start).first != SM.getDecomposedLoc(End).first) { + llvm::errs() + << "start or end location were in different macro expansions\n"; + return tooling::Replacement(); + } + Start = SM.getSpellingLoc(Start); + End = SM.getSpellingLoc(End); + if (SM.getFileID(Start) != SM.getFileID(End)) { + llvm::errs() << "start or end location were in different files\n"; + return tooling::Replacement(); + } + return tooling::Replacement( + SM, CharSourceRange::getTokenRange(SM.getSpellingLoc(Start), + SM.getSpellingLoc(End)), + ReplacementText); +} + +tooling::Replacement createInsertion(SourceLocation Loc, + llvm::StringRef InsertText, + const SourceManager &SM) { + if (Loc.isInvalid()) { + llvm::errs() << "insert Location is invalid.\n"; + return tooling::Replacement(); + } + Loc = SM.getSpellingLoc(Loc); + return tooling::Replacement(SM, Loc, 0, InsertText); +} + +// Returns the shortest qualified name for declaration `DeclName` in the +// namespace `NsName`. For example, if `DeclName` is "a::b::X" and `NsName` +// is "a::c::d", then "b::X" will be returned. +std::string getShortestQualifiedNameInNamespace(llvm::StringRef DeclName, + llvm::StringRef NsName) { + llvm::SmallVector DeclNameSplitted; + DeclName.split(DeclNameSplitted, "::"); + if (DeclNameSplitted.size() == 1) + return DeclName; + const auto UnqualifiedName = DeclNameSplitted.back(); + while (true) { + const auto Pos = NsName.find_last_of(':'); + if (Pos == llvm::StringRef::npos) + return DeclName; + const auto Prefix = NsName.substr(0, Pos - 1); + if (DeclName.startswith(Prefix)) + return (Prefix + "::" + UnqualifiedName).str(); + NsName = Prefix; + } + return DeclName; +} + +std::string wrapCodeInNamespace(StringRef NestedNs, std::string Code) { + if (Code.back() != '\n') + Code += "\n"; + llvm::SmallVector NsSplitted; + NestedNs.split(NsSplitted, "::"); + while (!NsSplitted.empty()) { + // FIXME: consider code style for comments. + Code = ("namespace " + NsSplitted.back() + " {\n" + Code + + "} // namespace " + NsSplitted.back() + "\n") + .str(); + NsSplitted.pop_back(); + } + return Code; +} + +} // anonymous namespace + +ChangeNamespaceTool::ChangeNamespaceTool( + llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern, + std::map *FileToReplacements, + llvm::StringRef FallbackStyle) + : FallbackStyle(FallbackStyle), FileToReplacements(*FileToReplacements), + OldNamespace(OldNs.ltrim(':')), NewNamespace(NewNs.ltrim(':')), + FilePattern(FilePattern) { + FileToReplacements->clear(); + llvm::SmallVector OldNsSplitted; + llvm::SmallVector NewNsSplitted; + llvm::StringRef(OldNamespace).split(OldNsSplitted, "::"); + llvm::StringRef(NewNamespace).split(NewNsSplitted, "::"); + // Calculates `DiffOldNamespace` and `DiffNewNamespace`. + while (!OldNsSplitted.empty() && !NewNsSplitted.empty() && + OldNsSplitted.front() == NewNsSplitted.front()) { + OldNsSplitted.erase(OldNsSplitted.begin()); + NewNsSplitted.erase(NewNsSplitted.begin()); + } + DiffOldNamespace = joinNamespaces(OldNsSplitted); + DiffNewNamespace = joinNamespaces(NewNsSplitted); +} + +// FIXME: handle the following symbols: +// - Types in `UsingShadowDecl` (e.g. `using a::b::c;`) which are not matched +// by `typeLoc`. +// - Types in nested name specifier, e.g. "na::X" in "na::X::Nested". +// - Function references. +// - Variable references. +void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) { + // Match old namespace blocks. + std::string FullOldNs = "::" + OldNamespace; + Finder->addMatcher( + namespaceDecl(hasName(FullOldNs), isExpansionInFileMatching(FilePattern)) + .bind("old_ns"), + this); + + auto IsInMovedNs = + allOf(hasAncestor(namespaceDecl(hasName(FullOldNs)).bind("ns_decl")), + isExpansionInFileMatching(FilePattern)); + + // Match forward-declarations in the old namespace. + Finder->addMatcher( + cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), IsInMovedNs) + .bind("fwd_decl"), + this); + + // Match references to types that are not defined in the old namespace. + // Forward-declarations in the old namespace are also matched since they will + // be moved back to the old namespace. + auto DeclMatcher = namedDecl( + hasAncestor(namespaceDecl()), + unless(anyOf( + hasAncestor(namespaceDecl(isAnonymous())), + hasAncestor(cxxRecordDecl()), + allOf(IsInMovedNs, unless(cxxRecordDecl(unless(isDefinition()))))))); + // Match TypeLocs on the declaration. Carefully match only the outermost + // TypeLoc that's directly linked to the old class and don't handle nested + // name specifier locs. + // FIXME: match and handle nested name specifier locs. + Finder->addMatcher( + typeLoc(IsInMovedNs, + loc(qualType(hasDeclaration(DeclMatcher.bind("from_decl")))), + unless(anyOf(hasParent(typeLoc( + loc(qualType(hasDeclaration(DeclMatcher))))), + hasParent(nestedNameSpecifierLoc()))), + hasAncestor(decl().bind("dc"))) + .bind("type"), + this); +} + +void ChangeNamespaceTool::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (const auto *NsDecl = Result.Nodes.getNodeAs("old_ns")) { + moveOldNamespace(Result, NsDecl); + } else if (const auto *FwdDecl = + Result.Nodes.getNodeAs("fwd_decl")) { + moveClassForwardDeclaration(Result, FwdDecl); + } else { + const auto *TLoc = Result.Nodes.getNodeAs("type"); + assert(TLoc != nullptr && "Expecting callback for TypeLoc"); + fixTypeLoc(Result, startLocationForType(*TLoc), EndLocationForType(*TLoc), + *TLoc); + } +} + +// Stores information about a moved namespace in `MoveNamespaces` and leaves +// the actual movement to `onEndOfTranslationUnit()`. +void ChangeNamespaceTool::moveOldNamespace( + const ast_matchers::MatchFinder::MatchResult &Result, + const NamespaceDecl *NsDecl) { + // If the namespace is empty, do nothing. + if (Decl::castToDeclContext(NsDecl)->decls_empty()) + return; + + // Get the range of the code in the old namespace. + SourceLocation Start = NsDecl->decls_begin()->getLocStart(); + SourceLocation End = NsDecl->getRBraceLoc().getLocWithOffset(-1); + // Create a replacement that deletes the code in the old namespace merely for + // retrieving offset and length from it. + const auto R = createReplacement(Start, End, "", *Result.SourceManager); + MoveNamespace MoveNs; + MoveNs.Offset = R.getOffset(); + MoveNs.Length = R.getLength(); + + // Insert the new namespace after `DiffOldNamespace`. For example, if + // `OldNamespace` is "a::b::c" and `NewNamespace` is `a::x::y`, then + // "x::y" will be inserted inside the existing namespace "a" and after "a::b". + // `OuterNs` is the first namespace in `DiffOldNamespace`, e.g. "namespace b" + // in the above example. + // FIXME: consider the case where DiffOldNamespace is empty. + const NamespaceDecl *OuterNs = getOuterNamespace(NsDecl, DiffOldNamespace); + SourceLocation LocAfterNs = + getStartOfNextLine(OuterNs->getRBraceLoc(), *Result.SourceManager, + Result.Context->getLangOpts()); + assert(LocAfterNs.isValid() && + "Failed to get location after DiffOldNamespace"); + MoveNs.InsertionOffset = Result.SourceManager->getFileOffset( + Result.SourceManager->getSpellingLoc(LocAfterNs)); + + MoveNs.FileID = Result.SourceManager->getFileID(Start); + MoveNs.SourceManager = Result.SourceManager; + MoveNamespaces[R.getFilePath()].push_back(MoveNs); +} + +// Removes a class forward declaration from the code in the moved namespace and +// creates an `InsertForwardDeclaration` to insert the forward declaration back +// into the old namespace after moving code from the old namespace to the new +// 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 +void ChangeNamespaceTool::moveClassForwardDeclaration( + const ast_matchers::MatchFinder::MatchResult &Result, + const CXXRecordDecl *FwdDecl) { + SourceLocation Start = FwdDecl->getLocStart(); + SourceLocation End = FwdDecl->getLocEnd(); + SourceLocation AfterSemi = Lexer::findLocationAfterToken( + End, tok::semi, *Result.SourceManager, Result.Context->getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true); + if (AfterSemi.isValid()) + End = AfterSemi.getLocWithOffset(-1); + // Delete the forward declaration from the code to be moved. + const auto Deletion = + createReplacement(Start, End, "", *Result.SourceManager); + addOrMergeReplacement(Deletion, &FileToReplacements[Deletion.getFilePath()]); + llvm::StringRef Code = Lexer::getSourceText( + CharSourceRange::getTokenRange( + Result.SourceManager->getSpellingLoc(Start), + Result.SourceManager->getSpellingLoc(End)), + *Result.SourceManager, Result.Context->getLangOpts()); + // Insert the forward declaration back into the old namespace after moving the + // code from old namespace to new namespace. + // Insertion information is stored in `InsertFwdDecls` and actual + // insertion will be performed in `onEndOfTranslationUnit`. + // Get the (old) namespace that contains the forward declaration. + const auto *NsDecl = Result.Nodes.getNodeAs("ns_decl"); + // The namespace contains the forward declaration, so it must not be empty. + assert(!NsDecl->decls_empty()); + const auto Insertion = createInsertion(NsDecl->decls_begin()->getLocStart(), + Code, *Result.SourceManager); + InsertForwardDeclaration InsertFwd; + InsertFwd.InsertionOffset = Insertion.getOffset(); + InsertFwd.ForwardDeclText = Insertion.getReplacementText().str(); + InsertFwdDecls[Insertion.getFilePath()].push_back(InsertFwd); +} + +// Replaces a qualified symbol that refers to a declaration `DeclName` with the +// shortest qualified name possible when the reference is in `NewNamespace`. +void ChangeNamespaceTool::replaceQualifiedSymbolInDeclContext( + const ast_matchers::MatchFinder::MatchResult &Result, const Decl *DeclCtx, + SourceLocation Start, SourceLocation End, llvm::StringRef DeclName) { + const auto *NsDeclContext = + DeclCtx->getDeclContext()->getEnclosingNamespaceContext(); + const auto *NsDecl = llvm::dyn_cast(NsDeclContext); + // Calculate the name of the `NsDecl` after it is moved to new namespace. + std::string OldNs = NsDecl->getQualifiedNameAsString(); + llvm::StringRef Postfix = OldNs; + bool Consumed = Postfix.consume_front(OldNamespace); + assert(Consumed && "Expect OldNS to start with OldNamespace."); + (void)Consumed; + const std::string NewNs = (NewNamespace + Postfix).str(); + + llvm::StringRef NestedName = Lexer::getSourceText( + CharSourceRange::getTokenRange( + Result.SourceManager->getSpellingLoc(Start), + Result.SourceManager->getSpellingLoc(End)), + *Result.SourceManager, Result.Context->getLangOpts()); + // If the symbol is already fully qualified, no change needs to be make. + if (NestedName.startswith("::")) + return; + std::string ReplaceName = + getShortestQualifiedNameInNamespace(DeclName, NewNs); + // If the new nested name in the new namespace is the same as it was in the + // old namespace, we don't create replacement. + if (NestedName == ReplaceName) + return; + auto R = createReplacement(Start, End, ReplaceName, *Result.SourceManager); + addOrMergeReplacement(R, &FileToReplacements[R.getFilePath()]); +} + +// Replace the [Start, End] of `Type` with the shortest qualified name when the +// `Type` is in `NewNamespace`. +void ChangeNamespaceTool::fixTypeLoc( + const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Start, + SourceLocation End, TypeLoc Type) { + // FIXME: do not rename template parameter. + if (Start.isInvalid() || End.isInvalid()) + return; + // The declaration which this TypeLoc refers to. + const auto *FromDecl = Result.Nodes.getNodeAs("from_decl"); + // `hasDeclaration` gives underlying declaration, but if the type is + // a typedef type, we need to use the typedef type instead. + if (auto *Typedef = Type.getType()->getAs()) + FromDecl = Typedef->getDecl(); + + const Decl *DeclCtx = Result.Nodes.getNodeAs("dc"); + assert(DeclCtx && "Empty decl context."); + replaceQualifiedSymbolInDeclContext(Result, DeclCtx, Start, End, + FromDecl->getQualifiedNameAsString()); +} + +void ChangeNamespaceTool::onEndOfTranslationUnit() { + // Move namespace blocks and insert forward declaration to old namespace. + for (const auto &FileAndNsMoves : MoveNamespaces) { + auto &NsMoves = FileAndNsMoves.second; + if (NsMoves.empty()) + continue; + const std::string &FilePath = FileAndNsMoves.first; + auto &Replaces = FileToReplacements[FilePath]; + auto &SM = *NsMoves.begin()->SourceManager; + llvm::StringRef Code = SM.getBufferData(NsMoves.begin()->FileID); + auto ChangedCode = tooling::applyAllReplacements(Code, Replaces); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + continue; + } + // Replacements on the changed code for moving namespaces and inserting + // forward declarations to old namespaces. + tooling::Replacements NewReplacements; + // Cut the changed code from the old namespace and paste the code in the new + // namespace. + for (const auto &NsMove : NsMoves) { + // Calculate the range of the old namespace block in the changed + // code. + const unsigned NewOffset = Replaces.getShiftedCodePosition(NsMove.Offset); + const unsigned NewLength = + Replaces.getShiftedCodePosition(NsMove.Offset + NsMove.Length) - + NewOffset; + tooling::Replacement Deletion(FilePath, NewOffset, NewLength, ""); + std::string MovedCode = ChangedCode->substr(NewOffset, NewLength); + std::string MovedCodeWrappedInNewNs = + wrapCodeInNamespace(DiffNewNamespace, MovedCode); + // Calculate the new offset at which the code will be inserted in the + // changed code. + unsigned NewInsertionOffset = + Replaces.getShiftedCodePosition(NsMove.InsertionOffset); + tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0, + MovedCodeWrappedInNewNs); + addOrMergeReplacement(Deletion, &NewReplacements); + addOrMergeReplacement(Insertion, &NewReplacements); + } + // After moving namespaces, insert forward declarations back to old + // namespaces. + const auto &FwdDeclInsertions = InsertFwdDecls[FilePath]; + for (const auto &FwdDeclInsertion : FwdDeclInsertions) { + unsigned NewInsertionOffset = + Replaces.getShiftedCodePosition(FwdDeclInsertion.InsertionOffset); + tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0, + FwdDeclInsertion.ForwardDeclText); + addOrMergeReplacement(Insertion, &NewReplacements); + } + // Add replacements referring to the changed code to existing replacements, + // which refers to the original code. + Replaces = Replaces.merge(NewReplacements); + format::FormatStyle Style = + format::getStyle("file", FilePath, FallbackStyle); + // Clean up old namespaces if there is nothing in it after moving. + auto CleanReplacements = + format::cleanupAroundReplacements(Code, Replaces, Style); + if (!CleanReplacements) { + llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n"; + continue; + } + FileToReplacements[FilePath] = *CleanReplacements; + } +} + +} // namespace change_namespace +} // namespace clang Index: clang-tools-extra/trunk/change-namespace/tool/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/change-namespace/tool/CMakeLists.txt +++ clang-tools-extra/trunk/change-namespace/tool/CMakeLists.txt @@ -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) Index: clang-tools-extra/trunk/change-namespace/tool/ClangChangeNamespace.cpp =================================================================== --- clang-tools-extra/trunk/change-namespace/tool/ClangChangeNamespace.cpp +++ clang-tools-extra/trunk/change-namespace/tool/ClangChangeNamespace.cpp @@ -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 OldNamespace("old_namespace", cl::Required, + cl::desc("Old namespace."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt NewNamespace("new_namespace", cl::Required, + cl::desc("New namespace."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt FilePattern( + "file_pattern", cl::Required, + cl::desc("Only rename namespaces in files that match the given pattern."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt Inplace("i", cl::desc("Inplace edit s, if specified."), + cl::cat(ChangeNamespaceCategory)); + +cl::opt 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 Factory = + tooling::newFrontendActionFactory(&Finder); + + if (int Result = Tool.run(Factory.get())) + return Result; + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(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; +} Index: clang-tools-extra/trunk/test/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/test/CMakeLists.txt +++ clang-tools-extra/trunk/test/CMakeLists.txt @@ -42,6 +42,7 @@ # Individual tools we test. clang-apply-replacements + clang-change-namespace clang-include-fixer clang-query clang-rename Index: clang-tools-extra/trunk/test/change-namespace/simple-move.cpp =================================================================== --- clang-tools-extra/trunk/test/change-namespace/simple-move.cpp +++ clang-tools-extra/trunk/test/change-namespace/simple-move.cpp @@ -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 +} +} Index: clang-tools-extra/trunk/unittests/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/unittests/CMakeLists.txt +++ clang-tools-extra/trunk/unittests/CMakeLists.txt @@ -5,6 +5,7 @@ add_unittest(ExtraToolsUnitTests ${test_dirname} ${ARGN}) endfunction() +add_subdirectory(change-namespace) add_subdirectory(clang-apply-replacements) add_subdirectory(clang-query) add_subdirectory(clang-tidy) Index: clang-tools-extra/trunk/unittests/change-namespace/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/unittests/change-namespace/CMakeLists.txt +++ clang-tools-extra/trunk/unittests/change-namespace/CMakeLists.txt @@ -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 + ) Index: clang-tools-extra/trunk/unittests/change-namespace/ChangeNamespaceTests.cpp =================================================================== --- clang-tools-extra/trunk/unittests/change-namespace/ChangeNamespaceTests.cpp +++ clang-tools-extra/trunk/unittests/change-namespace/ChangeNamespaceTests.cpp @@ -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 +#include +#include + +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 FileToReplacements; + change_namespace::ChangeNamespaceTool NamespaceTool( + OldNamespace, NewNamespace, FilePattern, &FileToReplacements); + ast_matchers::MatchFinder Finder; + NamespaceTool.registerMatchers(&Finder); + std::unique_ptr 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(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