[clang-tools-extra] r299419 - [clang-rename] Support renaming qualified symbol

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 4 02:30:06 PDT 2017


Author: hokein
Date: Tue Apr  4 04:30:06 2017
New Revision: 299419

URL: http://llvm.org/viewvc/llvm-project?rev=299419&view=rev
Log:
[clang-rename] Support renaming qualified symbol

Summary:
The patch adds a new feature for renaming qualified symbol references.
Unlike orginal clang-rename behavior, when renaming a qualified symbol to a new
qualified symbol (e.g "A::Foo" => "B::Bar"), this new rename behavior will
consider the prefix qualifiers of the symbol, and calculate the new prefix
qualifiers.  It aims to add as few additional qualifiers as possible.

As this is an early version (only supports renaming classes), I don't change
current clang-rename interfaces at the moment, and would like to keep its
(command-line tool) behavior. So I added new interfaces for the prototype.
In the long run, these interfaces should be unified.

No functionality changes in original clang-rename command-line tool.

This patch also contains a few bug fixes of clang-rename which are discovered by
the new unittest:

* fix a potential nullptr accessment when class declaration doesn't have definition.
* add USRs of nested declartaions in "getNamedDeclFor".

Reviewers: ioeric

Reviewed By: ioeric

Subscribers: alexfh, cfe-commits, mgorny

Differential Revision: https://reviews.llvm.org/D31176

Added:
    clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h
      - copied, changed from r299340, clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp
    clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp
Removed:
    clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp
Modified:
    clang-tools-extra/trunk/clang-rename/CMakeLists.txt
    clang-tools-extra/trunk/clang-rename/RenamingAction.cpp
    clang-tools-extra/trunk/clang-rename/RenamingAction.h
    clang-tools-extra/trunk/clang-rename/USRFinder.cpp
    clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp
    clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp
    clang-tools-extra/trunk/clang-rename/USRLocFinder.h
    clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt

Modified: clang-tools-extra/trunk/clang-rename/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-rename/CMakeLists.txt?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-rename/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clang-rename/CMakeLists.txt Tue Apr  4 04:30:06 2017
@@ -13,6 +13,7 @@ add_clang_library(clangRename
   clangIndex
   clangLex
   clangToolingCore
+  clangToolingRefactor
   )
 
 add_subdirectory(tool)

Modified: clang-tools-extra/trunk/clang-rename/RenamingAction.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-rename/RenamingAction.cpp?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-rename/RenamingAction.cpp (original)
+++ clang-tools-extra/trunk/clang-rename/RenamingAction.cpp Tue Apr  4 04:30:06 2017
@@ -84,10 +84,52 @@ private:
   bool PrintLocations;
 };
 
+// A renamer to rename symbols which are identified by a give USRList to
+// new name.
+//
+// FIXME: Merge with the above RenamingASTConsumer.
+class USRSymbolRenamer: public ASTConsumer {
+public:
+  USRSymbolRenamer(const std::vector<std::string> &NewNames,
+                   const std::vector<std::vector<std::string>> &USRList,
+                   std::map<std::string, tooling::Replacements> &FileToReplaces)
+      : NewNames(NewNames), USRList(USRList), FileToReplaces(FileToReplaces) {
+    assert(USRList.size() == NewNames.size());
+  }
+
+  void HandleTranslationUnit(ASTContext &Context) override {
+    for (unsigned I = 0; I < NewNames.size(); ++I) {
+      // FIXME: Apply AtomicChanges directly once the refactoring APIs are
+      // ready.
+      auto AtomicChanges = createRenameAtomicChanges(
+          USRList[I], NewNames[I], Context.getTranslationUnitDecl());
+      for (const auto AtomicChange : AtomicChanges) {
+        for (const auto &Replace : AtomicChange.getReplacements()) {
+          llvm::Error Err = FileToReplaces[Replace.getFilePath()].add(Replace);
+          if (Err) {
+            llvm::errs() << "Renaming failed in " << Replace.getFilePath()
+                         << "! " << llvm::toString(std::move(Err)) << "\n";
+          }
+      }
+      }
+    }
+  }
+
+private:
+  const std::vector<std::string> &NewNames;
+  const std::vector<std::vector<std::string>> &USRList;
+  std::map<std::string, tooling::Replacements> &FileToReplaces;
+};
+
 std::unique_ptr<ASTConsumer> RenamingAction::newASTConsumer() {
   return llvm::make_unique<RenamingASTConsumer>(NewNames, PrevNames, USRList,
                                                 FileToReplaces, PrintLocations);
 }
 
+std::unique_ptr<ASTConsumer> QualifiedRenamingAction::newASTConsumer() {
+  return llvm::make_unique<USRSymbolRenamer>(
+      NewNames, USRList, FileToReplaces);
+}
+
 } // namespace rename
 } // namespace clang

Modified: clang-tools-extra/trunk/clang-rename/RenamingAction.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-rename/RenamingAction.h?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-rename/RenamingAction.h (original)
+++ clang-tools-extra/trunk/clang-rename/RenamingAction.h Tue Apr  4 04:30:06 2017
@@ -42,6 +42,28 @@ private:
   bool PrintLocations;
 };
 
+/// Rename all symbols identified by the given USRs.
+class QualifiedRenamingAction {
+public:
+  QualifiedRenamingAction(
+      const std::vector<std::string> &NewNames,
+      const std::vector<std::vector<std::string>> &USRList,
+      std::map<std::string, tooling::Replacements> &FileToReplaces)
+      : NewNames(NewNames), USRList(USRList), FileToReplaces(FileToReplaces) {}
+
+  std::unique_ptr<ASTConsumer> newASTConsumer();
+
+private:
+  /// New symbol names.
+  const std::vector<std::string> &NewNames;
+
+  /// A list of USRs. Each element represents USRs of a symbol being renamed.
+  const std::vector<std::vector<std::string>> &USRList;
+
+  /// A file path to replacements map.
+  std::map<std::string, tooling::Replacements> &FileToReplaces;
+};
+
 } // namespace rename
 } // namespace clang
 

Modified: clang-tools-extra/trunk/clang-rename/USRFinder.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-rename/USRFinder.cpp?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-rename/USRFinder.cpp (original)
+++ clang-tools-extra/trunk/clang-rename/USRFinder.cpp Tue Apr  4 04:30:06 2017
@@ -135,7 +135,8 @@ private:
         return true;
     } else {
       // Fully qualified name is used to find the declaration.
-      if (Name != Decl->getQualifiedNameAsString())
+      if (Name != Decl->getQualifiedNameAsString() &&
+          Name != "::" + Decl->getQualifiedNameAsString())
         return true;
     }
     Result = Decl;

Modified: clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp (original)
+++ clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp Tue Apr  4 04:30:06 2017
@@ -104,6 +104,10 @@ private:
   void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl) {
     RecordDecl = RecordDecl->getDefinition();
 
+    // Skip if the CXXRecordDecl doesn't have definition.
+    if (!RecordDecl)
+      return;
+
     for (const auto *CtorDecl : RecordDecl->ctors())
       USRSet.insert(getUSRForDecl(CtorDecl));
 

Modified: clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp (original)
+++ clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp Tue Apr  4 04:30:06 2017
@@ -22,6 +22,7 @@
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Lex/Lexer.h"
+#include "clang/Tooling/Core/Lookup.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Casting.h"
 #include <cstddef>
@@ -148,6 +149,300 @@ private:
   const ASTContext &Context;
 };
 
+SourceLocation StartLocationForType(TypeLoc TL) {
+  // For elaborated types (e.g. `struct a::A`) we want the portion after the
+  // `struct` but including the namespace qualifier, `a::`.
+  if (auto ElaboratedTypeLoc = TL.getAs<clang::ElaboratedTypeLoc>()) {
+    NestedNameSpecifierLoc NestedNameSpecifier =
+        ElaboratedTypeLoc.getQualifierLoc();
+    if (NestedNameSpecifier.getNestedNameSpecifier())
+      return NestedNameSpecifier.getBeginLoc();
+    TL = TL.getNextTypeLoc();
+  }
+  return TL.getLocStart();
+}
+
+SourceLocation EndLocationForType(TypeLoc TL) {
+  // Dig past any namespace or keyword qualifications.
+  while (TL.getTypeLocClass() == TypeLoc::Elaborated ||
+         TL.getTypeLocClass() == TypeLoc::Qualified)
+    TL = TL.getNextTypeLoc();
+
+  // The location for template specializations (e.g. Foo<int>) includes the
+  // templated types in its location range.  We want to restrict this to just
+  // before the `<` character.
+  if (TL.getTypeLocClass() == TypeLoc::TemplateSpecialization) {
+    return TL.castAs<TemplateSpecializationTypeLoc>()
+        .getLAngleLoc()
+        .getLocWithOffset(-1);
+  }
+  return TL.getEndLoc();
+}
+
+NestedNameSpecifier *GetNestedNameForType(TypeLoc TL) {
+  // Dig past any keyword qualifications.
+  while (TL.getTypeLocClass() == TypeLoc::Qualified)
+    TL = TL.getNextTypeLoc();
+
+  // For elaborated types (e.g. `struct a::A`) we want the portion after the
+  // `struct` but including the namespace qualifier, `a::`.
+  if (auto ElaboratedTypeLoc = TL.getAs<clang::ElaboratedTypeLoc>())
+    return ElaboratedTypeLoc.getQualifierLoc().getNestedNameSpecifier();
+  return nullptr;
+}
+
+// Find all locations identified by the given USRs for rename.
+//
+// This class will traverse the AST and find every AST node whose USR is in the
+// given USRs' set.
+class RenameLocFinder
+    : public RecursiveASTVisitor<RenameLocFinder> {
+public:
+  RenameLocFinder(llvm::ArrayRef<std::string> USRs, ASTContext &Context)
+      : USRSet(USRs.begin(), USRs.end()), Context(Context) {}
+
+  // A structure records all information of a symbol reference being renamed.
+  // We try to add as few prefix qualifiers as possible.
+  struct RenameInfo {
+    // The begin location of a symbol being renamed.
+    SourceLocation Begin;
+    // The end location of a symbol being renamed.
+    SourceLocation End;
+    // The declaration of a symbol being renamed (can be nullptr).
+    const NamedDecl *FromDecl;
+    // The declaration in which the nested name is contained (can be nullptr).
+    const Decl *Context;
+    // The nested name being replaced (can be nullptr).
+    const NestedNameSpecifier *Specifier;
+  };
+
+  // FIXME: Currently, prefix qualifiers will be added to the renamed symbol
+  // definition (e.g. "class Foo {};" => "class b::Bar {};" when renaming
+  // "a::Foo" to "b::Bar").
+  // For renaming declarations/definitions, prefix qualifiers should be filtered
+  // out.
+  bool VisitNamedDecl(const NamedDecl *Decl) {
+    // UsingDecl has been handled in other place.
+    if (llvm::isa<UsingDecl>(Decl))
+      return true;
+
+    // DestructorDecl has been handled in Typeloc.
+    if (llvm::isa<CXXDestructorDecl>(Decl))
+      return true;
+
+    if (Decl->isImplicit())
+      return true;
+
+    if (isInUSRSet(Decl)) {
+      RenameInfo Info = {Decl->getLocation(), Decl->getLocation(), nullptr,
+                         nullptr, nullptr};
+      RenameInfos.push_back(Info);
+    }
+    return true;
+  }
+
+  bool VisitDeclRefExpr(const DeclRefExpr *Expr) {
+    const NamedDecl *Decl = Expr->getFoundDecl();
+    if (isInUSRSet(Decl)) {
+      RenameInfo Info = {Expr->getSourceRange().getBegin(),
+                         Expr->getSourceRange().getEnd(), Decl,
+                         getClosestAncestorDecl(*Expr), Expr->getQualifier()};
+      RenameInfos.push_back(Info);
+    }
+
+    return true;
+  }
+
+  bool VisitUsingDecl(const UsingDecl *Using) {
+    for (const auto *UsingShadow : Using->shadows()) {
+      if (isInUSRSet(UsingShadow->getTargetDecl())) {
+        UsingDecls.push_back(Using);
+        break;
+      }
+    }
+    return true;
+  }
+
+  bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) {
+    if (!NestedLoc.getNestedNameSpecifier()->getAsType())
+      return true;
+    if (IsTypeAliasWhichWillBeRenamedElsewhere(NestedLoc.getTypeLoc()))
+      return true;
+
+    if (const auto *TargetDecl =
+            getSupportedDeclFromTypeLoc(NestedLoc.getTypeLoc())) {
+      if (isInUSRSet(TargetDecl)) {
+        RenameInfo Info = {NestedLoc.getBeginLoc(),
+                           EndLocationForType(NestedLoc.getTypeLoc()),
+                           TargetDecl, getClosestAncestorDecl(NestedLoc),
+                           NestedLoc.getNestedNameSpecifier()->getPrefix()};
+        RenameInfos.push_back(Info);
+      }
+    }
+    return true;
+  }
+
+  bool VisitTypeLoc(TypeLoc Loc) {
+    if (IsTypeAliasWhichWillBeRenamedElsewhere(Loc))
+      return true;
+
+    auto Parents = Context.getParents(Loc);
+    TypeLoc ParentTypeLoc;
+    if (!Parents.empty()) {
+      // Handle cases of nested name specificier locations.
+      //
+      // The VisitNestedNameSpecifierLoc interface is not impelmented in
+      // RecursiveASTVisitor, we have to handle it explicitly.
+      if (const auto *NSL = Parents[0].get<NestedNameSpecifierLoc>()) {
+        VisitNestedNameSpecifierLocations(*NSL);
+        return true;
+      }
+
+      if (const auto *TL = Parents[0].get<TypeLoc>())
+        ParentTypeLoc = *TL;
+    }
+
+    // Handle the outermost TypeLoc which is directly linked to the interesting
+    // declaration and don't handle nested name specifier locations.
+    if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(Loc)) {
+      if (isInUSRSet(TargetDecl)) {
+        // Only handle the outermost typeLoc.
+        //
+        // For a type like "a::Foo", there will be two typeLocs for it.
+        // One ElaboratedType, the other is RecordType:
+        //
+        //   ElaboratedType 0x33b9390 'a::Foo' sugar
+        //   `-RecordType 0x338fef0 'class a::Foo'
+        //     `-CXXRecord 0x338fe58 'Foo'
+        //
+        // Skip if this is an inner typeLoc.
+        if (!ParentTypeLoc.isNull() &&
+            isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc)))
+          return true;
+        RenameInfo Info = {StartLocationForType(Loc), EndLocationForType(Loc),
+                           TargetDecl, getClosestAncestorDecl(Loc),
+                           GetNestedNameForType(Loc)};
+        RenameInfos.push_back(Info);
+        return true;
+      }
+    }
+
+    // Handle specific template class specialiation cases.
+    if (const auto *TemplateSpecType =
+            dyn_cast<TemplateSpecializationType>(Loc.getType())) {
+      TypeLoc TargetLoc = Loc;
+      if (!ParentTypeLoc.isNull()) {
+        if (llvm::isa<ElaboratedType>(ParentTypeLoc.getType()))
+          TargetLoc = ParentTypeLoc;
+      }
+
+      if (isInUSRSet(TemplateSpecType->getTemplateName().getAsTemplateDecl())) {
+        TypeLoc TargetLoc = Loc;
+        // FIXME: Find a better way to handle this case.
+        // For the qualified template class specification type like
+        // "ns::Foo<int>" in "ns::Foo<int>& f();", we want the parent typeLoc
+        // (ElaboratedType) of the TemplateSpecializationType in order to
+        // catch the prefix qualifiers "ns::".
+        if (!ParentTypeLoc.isNull() &&
+            llvm::isa<ElaboratedType>(ParentTypeLoc.getType()))
+          TargetLoc = ParentTypeLoc;
+        RenameInfo Info = {
+            StartLocationForType(TargetLoc), EndLocationForType(TargetLoc),
+            TemplateSpecType->getTemplateName().getAsTemplateDecl(),
+            getClosestAncestorDecl(
+                ast_type_traits::DynTypedNode::create(TargetLoc)),
+            GetNestedNameForType(TargetLoc)};
+        RenameInfos.push_back(Info);
+      }
+    }
+    return true;
+  }
+
+  // Returns a list of RenameInfo.
+  const std::vector<RenameInfo> &getRenameInfos() const { return RenameInfos; }
+
+  // Returns a list of using declarations which are needed to update.
+  const std::vector<const UsingDecl *> &getUsingDecls() const {
+    return UsingDecls;
+  }
+
+private:
+  // FIXME: This method may not be suitable for renaming other types like alias
+  // types. Need to figure out a way to handle it.
+  bool IsTypeAliasWhichWillBeRenamedElsewhere(TypeLoc TL) const {
+    while (!TL.isNull()) {
+      // SubstTemplateTypeParm is the TypeLocation class for a substituted type
+      // inside a template expansion so we ignore these.  For example:
+      //
+      // template<typename T> struct S {
+      //   T t;  // <-- this T becomes a TypeLoc(int) with class
+      //         //     SubstTemplateTypeParm when S<int> is instantiated
+      // }
+      if (TL.getTypeLocClass() == TypeLoc::SubstTemplateTypeParm)
+        return true;
+
+      // Typedef is the TypeLocation class for a type which is a typedef to the
+      // type we want to replace.  We ignore the use of the typedef as we will
+      // replace the definition of it.  For example:
+      //
+      // typedef int T;
+      // T a;  // <---  This T is a TypeLoc(int) with class Typedef.
+      if (TL.getTypeLocClass() == TypeLoc::Typedef)
+        return true;
+      TL = TL.getNextTypeLoc();
+    }
+    return false;
+  }
+
+  // Get the supported declaration from a given typeLoc. If the declaration type
+  // is not supported, returns nullptr.
+  //
+  // FIXME: support more types, e.g. enum, type alias.
+  const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) {
+    if (const auto *RD = Loc.getType()->getAsCXXRecordDecl())
+      return RD;
+    return nullptr;
+  }
+
+  // Get the closest ancester which is a declaration of a given AST node.
+  template <typename ASTNodeType>
+  const Decl *getClosestAncestorDecl(ASTNodeType Node) {
+    auto Parents = Context.getParents(Node);
+    // FIXME: figure out how to handle it when there are multiple parents.
+    if (Parents.size() != 1)
+      return nullptr;
+    if (ast_type_traits::ASTNodeKind::getFromNodeKind<Decl>().isBaseOf(
+            Parents[0].getNodeKind()))
+      return Parents[0].template get<Decl>();
+    return getClosestAncestorDecl(Parents[0]);
+  }
+
+  // Get the parent typeLoc of a given typeLoc. If there is no such parent,
+  // return nullptr.
+  const TypeLoc *getParentTypeLoc(TypeLoc Loc) const {
+    auto Parents = Context.getParents(Loc);
+    // FIXME: figure out how to handle it when there are multiple parents.
+    if (Parents.size() != 1)
+      return nullptr;
+    return Parents[0].get<TypeLoc>();
+  }
+
+  // Check whether the USR of a given Decl is in the USRSet.
+  bool isInUSRSet(const Decl *Decl) const {
+    auto USR = getUSRForDecl(Decl);
+    if (USR.empty())
+      return false;
+    return llvm::is_contained(USRSet, USR);
+  }
+
+  const std::set<std::string> USRSet;
+  ASTContext &Context;
+  std::vector<RenameInfo> RenameInfos;
+  // Record all interested using declarations which contains the using-shadow
+  // declarations of the symbol declarations being renamed.
+  std::vector<const UsingDecl *> UsingDecls;
+};
+
 } // namespace
 
 std::vector<SourceLocation>
@@ -163,5 +458,53 @@ getLocationsOfUSRs(const std::vector<std
   return Visitor.getLocationsFound();
 }
 
+std::vector<tooling::AtomicChange>
+createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs,
+                          llvm::StringRef NewName, Decl *TranslationUnitDecl) {
+  RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext());
+  Finder.TraverseDecl(TranslationUnitDecl);
+
+  const SourceManager &SM =
+      TranslationUnitDecl->getASTContext().getSourceManager();
+
+  std::vector<tooling::AtomicChange> AtomicChanges;
+  auto Replace = [&](SourceLocation Start, SourceLocation End,
+                     llvm::StringRef Text) {
+    tooling::AtomicChange ReplaceChange = tooling::AtomicChange(SM, Start);
+    llvm::Error Err = ReplaceChange.replace(
+        SM, CharSourceRange::getTokenRange(Start, End), Text);
+    if (Err) {
+      llvm::errs() << "Faile to add replacement to AtomicChange: "
+                   << llvm::toString(std::move(Err)) << "\n";
+      return;
+    }
+    AtomicChanges.push_back(std::move(ReplaceChange));
+  };
+
+  for (const auto &RenameInfo : Finder.getRenameInfos()) {
+    std::string ReplacedName = NewName.str();
+    if (RenameInfo.FromDecl && RenameInfo.Context) {
+      if (!llvm::isa<clang::TranslationUnitDecl>(
+              RenameInfo.Context->getDeclContext())) {
+        ReplacedName = tooling::replaceNestedName(
+            RenameInfo.Specifier, RenameInfo.Context->getDeclContext(),
+            RenameInfo.FromDecl,
+            NewName.startswith("::") ? NewName.str() : ("::" + NewName).str());
+      }
+    }
+    // If the NewName contains leading "::", add it back.
+    if (NewName.startswith("::") && NewName.substr(2) == ReplacedName)
+      ReplacedName = NewName.str();
+    Replace(RenameInfo.Begin, RenameInfo.End, ReplacedName);
+  }
+
+  // Hanlde using declarations explicitly as "using a::Foo" don't trigger
+  // typeLoc for "a::Foo".
+  for (const auto *Using : Finder.getUsingDecls())
+    Replace(Using->getLocStart(), Using->getLocEnd(), "using " + NewName.str());
+
+  return AtomicChanges;
+}
+
 } // namespace rename
 } // namespace clang

Modified: clang-tools-extra/trunk/clang-rename/USRLocFinder.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-rename/USRLocFinder.h?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-rename/USRLocFinder.h (original)
+++ clang-tools-extra/trunk/clang-rename/USRLocFinder.h Tue Apr  4 04:30:06 2017
@@ -18,12 +18,26 @@
 
 #include "clang/AST/AST.h"
 #include "llvm/ADT/StringRef.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
 #include <string>
 #include <vector>
 
 namespace clang {
 namespace rename {
 
+/// Create atomic changes for renaming all symbol references which are
+/// identified by the USRs set to a given new name.
+///
+/// \param USRs: The set containing USRs of a particular old symbol.
+/// \param NewName: The new name to replace old symbol name.
+/// \param TranslationUnitDecl: The translation unit declaration.
+///
+/// \return Atomic changes for renaming.
+std::vector<tooling::AtomicChange>
+createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs,
+                          llvm::StringRef NewName, Decl *TranslationUnitDecl);
+
 // FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree!
 std::vector<SourceLocation>
 getLocationsOfUSRs(const std::vector<std::string> &USRs,

Modified: clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt?rev=299419&r1=299418&r2=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt Tue Apr  4 04:30:06 2017
@@ -12,7 +12,7 @@ include_directories(
 include_directories(${CLANG_SOURCE_DIR})
 
 add_extra_unittest(ClangRenameTests
-  ClangRenameTests.cpp
+  RenameClassTest.cpp
   )
 
 target_link_libraries(ClangRenameTests

Copied: clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h (from r299340, clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp)
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h?p2=clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h&p1=clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp&r1=299340&r2=299419&rev=299419&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h Tue Apr  4 04:30:06 2017
@@ -30,19 +30,19 @@
 
 namespace clang {
 namespace clang_rename {
-namespace {
+namespace test {
 
 struct Case {
   std::string Before;
   std::string After;
+  std::string OldName;
+  std::string NewName;
 };
 
 class ClangRenameTest : public testing::Test,
                         public testing::WithParamInterface<Case> {
 protected:
-  void AppendToHeader(StringRef Code) {
-    HeaderContent += Code.str();
-  }
+  void AppendToHeader(StringRef Code) { HeaderContent += Code.str(); }
 
   std::string runClangRenameOnCode(llvm::StringRef Code,
                                    llvm::StringRef OldName,
@@ -68,11 +68,10 @@ protected:
 
     const std::vector<std::vector<std::string>> &USRList =
         FindingAction.getUSRList();
-    const std::vector<std::string> &PrevNames = FindingAction.getUSRSpellings();
     std::vector<std::string> NewNames = {NewName};
     std::map<std::string, tooling::Replacements> FileToReplacements;
-    rename::RenamingAction RenameAction(NewNames, PrevNames, USRList,
-                                        FileToReplacements);
+    rename::QualifiedRenamingAction RenameAction(NewNames, USRList,
+                                                 FileToReplacements);
     auto RenameActionFactory = tooling::newFrontendActionFactory(&RenameAction);
     if (!tooling::runToolOnCodeWithArgs(
             RenameActionFactory->create(), NewCode, {"-std=c++11"}, CCName,
@@ -108,46 +107,6 @@ protected:
   std::string CCName = "input.cc";
 };
 
-class RenameClassTest : public ClangRenameTest {
- public:
-  RenameClassTest() {
-    AppendToHeader("\nclass Foo {};\n");
-  }
-};
-
-INSTANTIATE_TEST_CASE_P(
-    RenameTests, RenameClassTest,
-    testing::ValuesIn(std::vector<Case>({
-      {"Foo f;", "Bar f;"},
-      {"void f(Foo f) {}", "void f(Bar f) {}"},
-      {"void f(Foo *f) {}", "void f(Bar *f) {}"},
-      {"Foo f() { return Foo(); }", "Bar f() { return Bar(); }"},
-    })));
-
-TEST_P(RenameClassTest, RenameClasses) {
-  auto Param = GetParam();
-  std::string OldName = "Foo";
-  std::string NewName = "Bar";
-  std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName);
-  CompareSnippets(Param.After, Actual);
-}
-
-class RenameFunctionTest : public ClangRenameTest {};
-
-INSTANTIATE_TEST_CASE_P(
-    RenameTests, RenameFunctionTest,
-    testing::ValuesIn(std::vector<Case>({
-      {"void func1() {}", "void func2() {}"},
-    })));
-
-TEST_P(RenameFunctionTest, RenameFunctions) {
-  auto Param = GetParam();
-  std::string OldName = "func1";
-  std::string NewName = "func2";
-  std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName);
-  CompareSnippets(Param.After, Actual);
-}
-
-} // anonymous namespace
+} // namespace test
 } // namespace clang_rename
 } // namesdpace clang

Removed: clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp?rev=299418&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp (removed)
@@ -1,153 +0,0 @@
-//===-- ClangRenameTests.cpp - clang-rename unit tests --------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "RenamingAction.h"
-#include "USRFindingAction.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/Format.h"
-#include "llvm/Support/MemoryBuffer.h"
-#include "gtest/gtest.h"
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace clang {
-namespace clang_rename {
-namespace {
-
-struct Case {
-  std::string Before;
-  std::string After;
-};
-
-class ClangRenameTest : public testing::Test,
-                        public testing::WithParamInterface<Case> {
-protected:
-  void AppendToHeader(StringRef Code) {
-    HeaderContent += Code.str();
-  }
-
-  std::string runClangRenameOnCode(llvm::StringRef Code,
-                                   llvm::StringRef OldName,
-                                   llvm::StringRef NewName) {
-    std::string NewCode;
-    llvm::raw_string_ostream(NewCode) << llvm::format(
-        "#include \"%s\"\n%s", HeaderName.c_str(), Code.str().c_str());
-    tooling::FileContentMappings FileContents = {{HeaderName, HeaderContent},
-                                                 {CCName, NewCode}};
-    clang::RewriterTestContext Context;
-    Context.createInMemoryFile(HeaderName, HeaderContent);
-    clang::FileID InputFileID = Context.createInMemoryFile(CCName, NewCode);
-
-    rename::USRFindingAction FindingAction({}, {OldName});
-    std::unique_ptr<tooling::FrontendActionFactory> USRFindingActionFactory =
-        tooling::newFrontendActionFactory(&FindingAction);
-
-    if (!tooling::runToolOnCodeWithArgs(
-            USRFindingActionFactory->create(), NewCode, {"-std=c++11"}, CCName,
-            "clang-rename", std::make_shared<PCHContainerOperations>(),
-            FileContents))
-      return "";
-
-    const std::vector<std::vector<std::string>> &USRList =
-        FindingAction.getUSRList();
-    const std::vector<std::string> &PrevNames = FindingAction.getUSRSpellings();
-    std::vector<std::string> NewNames = {NewName};
-    std::map<std::string, tooling::Replacements> FileToReplacements;
-    rename::RenamingAction RenameAction(NewNames, PrevNames, USRList,
-                                        FileToReplacements);
-    auto RenameActionFactory = tooling::newFrontendActionFactory(&RenameAction);
-    if (!tooling::runToolOnCodeWithArgs(
-            RenameActionFactory->create(), NewCode, {"-std=c++11"}, CCName,
-            "clang-rename", std::make_shared<PCHContainerOperations>(),
-            FileContents))
-      return "";
-
-    formatAndApplyAllReplacements(FileToReplacements, Context.Rewrite, "llvm");
-    return Context.getRewrittenText(InputFileID);
-  }
-
-  void CompareSnippets(StringRef Expected, StringRef Actual) {
-    std::string ExpectedCode;
-    llvm::raw_string_ostream(ExpectedCode) << llvm::format(
-        "#include \"%s\"\n%s", HeaderName.c_str(), Expected.str().c_str());
-    EXPECT_EQ(format(ExpectedCode), format(Actual));
-  }
-
-  std::string format(llvm::StringRef Code) {
-    tooling::Replacements Replaces = format::reformat(
-        format::getLLVMStyle(), Code, {tooling::Range(0, Code.size())});
-    auto ChangedCode = tooling::applyAllReplacements(Code, Replaces);
-    EXPECT_TRUE(static_cast<bool>(ChangedCode));
-    if (!ChangedCode) {
-      llvm::errs() << llvm::toString(ChangedCode.takeError());
-      return "";
-    }
-    return *ChangedCode;
-  }
-
-  std::string HeaderContent;
-  std::string HeaderName = "header.h";
-  std::string CCName = "input.cc";
-};
-
-class RenameClassTest : public ClangRenameTest {
- public:
-  RenameClassTest() {
-    AppendToHeader("\nclass Foo {};\n");
-  }
-};
-
-INSTANTIATE_TEST_CASE_P(
-    RenameTests, RenameClassTest,
-    testing::ValuesIn(std::vector<Case>({
-      {"Foo f;", "Bar f;"},
-      {"void f(Foo f) {}", "void f(Bar f) {}"},
-      {"void f(Foo *f) {}", "void f(Bar *f) {}"},
-      {"Foo f() { return Foo(); }", "Bar f() { return Bar(); }"},
-    })));
-
-TEST_P(RenameClassTest, RenameClasses) {
-  auto Param = GetParam();
-  std::string OldName = "Foo";
-  std::string NewName = "Bar";
-  std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName);
-  CompareSnippets(Param.After, Actual);
-}
-
-class RenameFunctionTest : public ClangRenameTest {};
-
-INSTANTIATE_TEST_CASE_P(
-    RenameTests, RenameFunctionTest,
-    testing::ValuesIn(std::vector<Case>({
-      {"void func1() {}", "void func2() {}"},
-    })));
-
-TEST_P(RenameFunctionTest, RenameFunctions) {
-  auto Param = GetParam();
-  std::string OldName = "func1";
-  std::string NewName = "func2";
-  std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName);
-  CompareSnippets(Param.After, Actual);
-}
-
-} // anonymous namespace
-} // namespace clang_rename
-} // namesdpace clang

Added: clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp?rev=299419&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp (added)
+++ clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp Tue Apr  4 04:30:06 2017
@@ -0,0 +1,668 @@
+//===-- RenameClassTest.cpp - unit tests for renaming classes -------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangRenameTest.h"
+
+namespace clang {
+namespace clang_rename {
+namespace test {
+namespace {
+
+class RenameClassTest : public ClangRenameTest {
+public:
+  RenameClassTest() {
+    AppendToHeader(R"(
+      namespace a {
+        class Foo {
+          public:
+            struct Nested {
+              enum NestedEnum {E1, E2};
+            };
+            void func() {}
+          static int Constant;
+        };
+        class Goo {
+          public:
+            struct Nested {
+              enum NestedEnum {E1, E2};
+            };
+        };
+        int Foo::Constant = 1;
+      } // namespace a
+      namespace b {
+      class Foo {};
+      } // namespace b
+
+      #define MACRO(x) x
+
+      template<typename T> class ptr {};
+    )");
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(
+    RenameClassTests, RenameClassTest,
+    testing::ValuesIn(std::vector<Case>({
+        // basic classes
+        {"a::Foo f;", "b::Bar f;"},
+        {"void f(a::Foo f) {}", "void f(b::Bar f) {}"},
+        {"void f(a::Foo *f) {}", "void f(b::Bar *f) {}"},
+        {"a::Foo f() { return a::Foo(); }", "b::Bar f() { return b::Bar(); }"},
+        {"namespace a {a::Foo f() { return Foo(); }}",
+         "namespace a {b::Bar f() { return b::Bar(); }}"},
+        {"void f(const a::Foo& a1) {}", "void f(const b::Bar& a1) {}"},
+        {"void f(const a::Foo* a1) {}", "void f(const b::Bar* a1) {}"},
+        {"namespace a { void f(Foo a1) {} }",
+         "namespace a { void f(b::Bar a1) {} }"},
+        {"void f(MACRO(a::Foo) a1) {}", "void f(MACRO(b::Bar) a1) {}"},
+        {"void f(MACRO(a::Foo a1)) {}", "void f(MACRO(b::Bar a1)) {}"},
+        {"a::Foo::Nested ns;", "b::Bar::Nested ns;"},
+        {"auto t = a::Foo::Constant;", "auto t = b::Bar::Constant;"},
+        {"a::Foo::Nested ns;", "a::Foo::Nested2 ns;", "a::Foo::Nested",
+         "a::Foo::Nested2"},
+
+        // use namespace and typedefs
+        {"using a::Foo; Foo gA;", "using b::Bar; b::Bar gA;"},
+        {"using a::Foo; void f(Foo gA) {}", "using b::Bar; void f(Bar gA) {}"},
+        {"using a::Foo; namespace x { Foo gA; }",
+         "using b::Bar; namespace x { Bar gA; }"},
+        {"struct S { using T = a::Foo; T a_; };",
+         "struct S { using T = b::Bar; T a_; };"},
+        {"using T = a::Foo; T gA;", "using T = b::Bar; T gA;"},
+        {"typedef a::Foo T; T gA;", "typedef b::Bar T; T gA;"},
+        {"typedef MACRO(a::Foo) T; T gA;", "typedef MACRO(b::Bar) T; T gA;"},
+
+        // struct members and other oddities
+        {"struct S : public a::Foo {};", "struct S : public b::Bar {};"},
+        {"struct F { void f(a::Foo a1) {} };",
+         "struct F { void f(b::Bar a1) {} };"},
+        {"struct F { a::Foo a_; };", "struct F { b::Bar a_; };"},
+        {"struct F { ptr<a::Foo> a_; };", "struct F { ptr<b::Bar> a_; };"},
+
+        {"void f() { a::Foo::Nested ne; }", "void f() { b::Bar::Nested ne; }"},
+        {"void f() { a::Goo::Nested ne; }", "void f() { a::Goo::Nested ne; }"},
+        {"void f() { a::Foo::Nested::NestedEnum e; }",
+         "void f() { b::Bar::Nested::NestedEnum e; }"},
+        {"void f() { auto e = a::Foo::Nested::NestedEnum::E1; }",
+         "void f() { auto e = b::Bar::Nested::NestedEnum::E1; }"},
+        {"void f() { auto e = a::Foo::Nested::E1; }",
+         "void f() { auto e = b::Bar::Nested::E1; }"},
+
+        // templates
+        {"template <typename T> struct Foo { T t; };\n"
+         "void f() { Foo<a::Foo> foo; }",
+         "template <typename T> struct Foo { T t; };\n"
+         "void f() { Foo<b::Bar> foo; }"},
+        {"template <typename T> struct Foo { a::Foo a; };",
+         "template <typename T> struct Foo { b::Bar a; };"},
+        {"template <typename T> void f(T t) {}\n"
+         "void g() { f<a::Foo>(a::Foo()); }",
+         "template <typename T> void f(T t) {}\n"
+         "void g() { f<b::Bar>(b::Bar()); }"},
+        {"template <typename T> int f() { return 1; }\n"
+         "template <> int f<a::Foo>() { return 2; }\n"
+         "int g() { return f<a::Foo>(); }",
+         "template <typename T> int f() { return 1; }\n"
+         "template <> int f<b::Bar>() { return 2; }\n"
+         "int g() { return f<b::Bar>(); }"},
+        {"struct Foo { template <typename T> T foo(); };\n"
+         "void g() { Foo f;  auto a = f.template foo<a::Foo>(); }",
+         "struct Foo { template <typename T> T foo(); };\n"
+         "void g() { Foo f;  auto a = f.template foo<b::Bar>(); }"},
+
+        // The following two templates are distilled from regressions found in
+        // unique_ptr<> and type_traits.h
+        {"template <typename T> struct outer {\n"
+         "     typedef T type;\n"
+         "     type Baz();\n"
+         "    };\n"
+         "    outer<a::Foo> g_A;",
+         "template <typename T> struct outer {\n"
+         "      typedef T type;\n"
+         "      type Baz();\n"
+         "    };\n"
+         "    outer<b::Bar> g_A;"},
+        {"template <typename T> struct nested { typedef T type; };\n"
+         "template <typename T> struct outer { typename nested<T>::type Foo(); "
+         "};\n"
+         "outer<a::Foo> g_A;",
+         "template <typename T> struct nested { typedef T type; };\n"
+         "template <typename T> struct outer { typename nested<T>::type Foo(); "
+         "};\n"
+         "outer<b::Bar> g_A;"},
+
+        // macros
+        {"#define FOO(T, t) T t\n"
+         "void f() { FOO(a::Foo, a1); FOO(a::Foo, a2); }",
+         "#define FOO(T, t) T t\n"
+         "void f() { FOO(b::Bar, a1); FOO(b::Bar, a2); }"},
+        {"#define FOO(n) a::Foo n\n"
+         " void f() { FOO(a1); FOO(a2); }",
+         "#define FOO(n) b::Bar n\n"
+         " void f() { FOO(a1); FOO(a2); }"},
+
+        // Pointer to member functions
+        {"auto gA = &a::Foo::func;", "auto gA = &b::Bar::func;"},
+        {"using a::Foo; auto gA = &Foo::func;",
+         "using b::Bar; auto gA = &b::Bar::func;"},
+        {"using a::Foo; namespace x { auto gA = &Foo::func; }",
+         "using b::Bar; namespace x { auto gA = &Bar::func; }"},
+        {"typedef a::Foo T; auto gA = &T::func;",
+         "typedef b::Bar T; auto gA = &T::func;"},
+        {"auto gA = &MACRO(a::Foo)::func;", "auto gA = &MACRO(b::Bar)::func;"},
+
+        // Short match inside a namespace
+        {"namespace a { void f(Foo a1) {} }",
+         "namespace a { void f(b::Bar a1) {} }"},
+
+        // Correct match.
+        {"using a::Foo; struct F { ptr<Foo> a_; };",
+         "using b::Bar; struct F { ptr<Bar> a_; };"},
+
+        // avoid false positives
+        {"void f(b::Foo a) {}", "void f(b::Foo a) {}"},
+        {"namespace b { void f(Foo a) {} }",
+         "namespace b { void f(Foo a) {} }"},
+
+        // friends, everyone needs friends.
+        {"class Foo { int i; friend class a::Foo; };",
+         "class Foo { int i; friend class b::Bar; };"},
+    })));
+
+TEST_P(RenameClassTest, RenameClasses) {
+  auto Param = GetParam();
+  std::string OldName = Param.OldName.empty() ? "a::Foo" : Param.OldName;
+  std::string NewName = Param.NewName.empty() ? "b::Bar" : Param.NewName;
+  std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName);
+  CompareSnippets(Param.After, Actual);
+}
+
+class NamespaceDetectionTest : public ClangRenameTest {
+protected:
+  NamespaceDetectionTest() {
+    AppendToHeader(R"(
+         class Old {};
+         namespace o1 {
+         class Old {};
+         namespace o2 {
+         class Old {};
+         namespace o3 {
+         class Old {};
+         }  // namespace o3
+         }  // namespace o2
+         }  // namespace o1
+     )");
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(
+    RenameClassTest, NamespaceDetectionTest,
+    ::testing::ValuesIn(std::vector<Case>({
+        // Test old and new namespace overlap.
+        {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }",
+         "namespace o1 { namespace o2 { namespace o3 { New moo; } } }",
+         "o1::o2::o3::Old", "o1::o2::o3::New"},
+        {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }",
+         "namespace o1 { namespace o2 { namespace o3 { n3::New moo; } } }",
+         "o1::o2::o3::Old", "o1::o2::n3::New"},
+        {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }",
+         "namespace o1 { namespace o2 { namespace o3 { n2::n3::New moo; } } }",
+         "o1::o2::o3::Old", "o1::n2::n3::New"},
+        {"namespace o1 { namespace o2 { Old moo; } }",
+         "namespace o1 { namespace o2 { New moo; } }", "::o1::o2::Old",
+         "::o1::o2::New"},
+        {"namespace o1 { namespace o2 { Old moo; } }",
+         "namespace o1 { namespace o2 { n2::New moo; } }", "::o1::o2::Old",
+         "::o1::n2::New"},
+        {"namespace o1 { namespace o2 { Old moo; } }",
+         "namespace o1 { namespace o2 { ::n1::n2::New moo; } }",
+         "::o1::o2::Old", "::n1::n2::New"},
+        {"namespace o1 { namespace o2 { Old moo; } }",
+         "namespace o1 { namespace o2 { n1::n2::New moo; } }", "::o1::o2::Old",
+         "n1::n2::New"},
+
+        // Test old and new namespace with differing depths.
+        {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }",
+         "namespace o1 { namespace o2 { namespace o3 { New moo; } } }",
+         "o1::o2::o3::Old", "::o1::New"},
+        {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }",
+         "namespace o1 { namespace o2 { namespace o3 { New moo; } } }",
+         "o1::o2::o3::Old", "::o1::o2::New"},
+        {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }",
+         "namespace o1 { namespace o2 { namespace o3 { New moo; } } }",
+         "o1::o2::o3::Old", "o1::New"},
+        {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }",
+         "namespace o1 { namespace o2 { namespace o3 { New moo; } } }",
+         "o1::o2::o3::Old", "o1::o2::New"},
+        {"Old moo;", "o1::New moo;", "::Old", "o1::New"},
+        {"Old moo;", "o1::New moo;", "Old", "o1::New"},
+        {"namespace o1 { ::Old moo; }", "namespace o1 { New moo; }", "Old",
+         "o1::New"},
+        {"namespace o1 { namespace o2 {  Old moo; } }",
+         "namespace o1 { namespace o2 {  ::New moo; } }", "::o1::o2::Old",
+         "::New"},
+        {"namespace o1 { namespace o2 {  Old moo; } }",
+         "namespace o1 { namespace o2 {  New moo; } }", "::o1::o2::Old", "New"},
+
+        // Test moving into the new namespace at different levels.
+        {"namespace n1 { namespace n2 { o1::o2::Old moo; } }",
+         "namespace n1 { namespace n2 { New moo; } }", "::o1::o2::Old",
+         "::n1::n2::New"},
+        {"namespace n1 { namespace n2 { o1::o2::Old moo; } }",
+         "namespace n1 { namespace n2 { New moo; } }", "::o1::o2::Old",
+         "n1::n2::New"},
+        {"namespace n1 { namespace n2 { o1::o2::Old moo; } }",
+         "namespace n1 { namespace n2 { o2::New moo; } }", "::o1::o2::Old",
+         "::n1::o2::New"},
+        {"namespace n1 { namespace n2 { o1::o2::Old moo; } }",
+         "namespace n1 { namespace n2 { o2::New moo; } }", "::o1::o2::Old",
+         "n1::o2::New"},
+        {"namespace n1 { namespace n2 { o1::o2::Old moo; } }",
+         "namespace n1 { namespace n2 { ::o1::o2::New moo; } }",
+         "::o1::o2::Old", "::o1::o2::New"},
+        {"namespace n1 { namespace n2 { o1::o2::Old moo; } }",
+         "namespace n1 { namespace n2 { o1::o2::New moo; } }", "::o1::o2::Old",
+         "o1::o2::New"},
+
+        // Test friends declarations.
+        {"class Foo { friend class o1::Old; };",
+         "class Foo { friend class o1::New; };", "o1::Old", "o1::New"},
+        {"class Foo { int i; friend class o1::Old; };",
+         "class Foo { int i; friend class ::o1::New; };", "::o1::Old",
+         "::o1::New"},
+        {"namespace o1 { class Foo { int i; friend class Old; }; }",
+         "namespace o1 { class Foo { int i; friend class New; }; }", "o1::Old",
+         "o1::New"},
+        {"namespace o1 { class Foo { int i; friend class Old; }; }",
+         "namespace o1 { class Foo { int i; friend class New; }; }",
+         "::o1::Old", "::o1::New"},
+    })));
+
+TEST_P(NamespaceDetectionTest, RenameClasses) {
+  auto Param = GetParam();
+  std::string Actual =
+      runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName);
+  CompareSnippets(Param.After, Actual);
+}
+
+class TemplatedClassRenameTest : public ClangRenameTest {
+protected:
+  TemplatedClassRenameTest() {
+    AppendToHeader(R"(
+           template <typename T> struct Old {
+             T t_;
+             T f() { return T(); };
+             static T s(T t) { return t; }
+           };
+           namespace ns {
+           template <typename T> struct Old {
+             T t_;
+             T f() { return T(); };
+             static T s(T t) { return t; }
+           };
+           }  // namespace ns
+
+           namespace o1 {
+           namespace o2 {
+           namespace o3 {
+           template <typename T> struct Old {
+             T t_;
+             T f() { return T(); };
+             static T s(T t) { return t; }
+           };
+           }  // namespace o3
+           }  // namespace o2
+           }  // namespace o1
+       )");
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(
+    RenameClassTests, TemplatedClassRenameTest,
+    ::testing::ValuesIn(std::vector<Case>({
+        {"Old<int> gI; Old<bool> gB;", "New<int> gI; New<bool> gB;", "Old",
+         "New"},
+        {"ns::Old<int> gI; ns::Old<bool> gB;",
+         "ns::New<int> gI; ns::New<bool> gB;", "ns::Old", "ns::New"},
+        {"auto gI = &Old<int>::f; auto gB = &Old<bool>::f;",
+         "auto gI = &New<int>::f; auto gB = &New<bool>::f;", "Old", "New"},
+        {"auto gI = &ns::Old<int>::f;", "auto gI = &ns::New<int>::f;",
+         "ns::Old", "ns::New"},
+
+        {"int gI = Old<int>::s(0); bool gB = Old<bool>::s(false);",
+         "int gI = New<int>::s(0); bool gB = New<bool>::s(false);", "Old",
+         "New"},
+        {"int gI = ns::Old<int>::s(0); bool gB = ns::Old<bool>::s(false);",
+         "int gI = ns::New<int>::s(0); bool gB = ns::New<bool>::s(false);",
+         "ns::Old", "ns::New"},
+
+        {"struct S { Old<int*> o_; };", "struct S { New<int*> o_; };", "Old",
+         "New"},
+        {"struct S { ns::Old<int*> o_; };", "struct S { ns::New<int*> o_; };",
+         "ns::Old", "ns::New"},
+
+        {"auto a = reinterpret_cast<Old<int>*>(new Old<int>);",
+         "auto a = reinterpret_cast<New<int>*>(new New<int>);", "Old", "New"},
+        {"auto a = reinterpret_cast<ns::Old<int>*>(new ns::Old<int>);",
+         "auto a = reinterpret_cast<ns::New<int>*>(new ns::New<int>);",
+         "ns::Old", "ns::New"},
+        {"auto a = reinterpret_cast<const Old<int>*>(new Old<int>);",
+         "auto a = reinterpret_cast<const New<int>*>(new New<int>);", "Old",
+         "New"},
+        {"auto a = reinterpret_cast<const ns::Old<int>*>(new ns::Old<int>);",
+         "auto a = reinterpret_cast<const ns::New<int>*>(new ns::New<int>);",
+         "ns::Old", "ns::New"},
+
+        {"Old<bool>& foo();", "New<bool>& foo();", "Old", "New"},
+        {"ns::Old<bool>& foo();", "ns::New<bool>& foo();", "ns::Old",
+         "ns::New"},
+        {"o1::o2::o3::Old<bool>& foo();", "o1::o2::o3::New<bool>& foo();",
+         "o1::o2::o3::Old", "o1::o2::o3::New"},
+        {"namespace ns { Old<bool>& foo(); }",
+         "namespace ns { New<bool>& foo(); }", "ns::Old", "ns::New"},
+        {"const Old<bool>& foo();", "const New<bool>& foo();", "Old", "New"},
+        {"const ns::Old<bool>& foo();", "const ns::New<bool>& foo();",
+         "ns::Old", "ns::New"},
+
+        // FIXME: figure out why this only works when Moo gets
+        // specialized at some point.
+        {"template <typename T> struct Moo { Old<T> o_; }; Moo<int> m;",
+         "template <typename T> struct Moo { New<T> o_; }; Moo<int> m;", "Old",
+         "New"},
+        {"template <typename T> struct Moo { ns::Old<T> o_; }; Moo<int> m;",
+         "template <typename T> struct Moo { ns::New<T> o_; }; Moo<int> m;",
+         "ns::Old", "ns::New"},
+    })));
+
+TEST_P(TemplatedClassRenameTest, RenameTemplateClasses) {
+  auto Param = GetParam();
+  std::string Actual =
+      runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName);
+  CompareSnippets(Param.After, Actual);
+}
+
+TEST_F(ClangRenameTest, RenameClassWithOutOfLineMembers) {
+  std::string Before = R"(
+      class Old {
+       public:
+        Old();
+        ~Old();
+
+        Old* next();
+
+       private:
+        Old* next_;
+      };
+
+      Old::Old() {}
+      Old::~Old() {}
+      Old* Old::next() { return next_; }
+    )";
+  std::string Expected = R"(
+      class New {
+       public:
+        New();
+        ~New();
+
+        New* next();
+
+       private:
+        New* next_;
+      };
+
+      New::New() {}
+      New::~New() {}
+      New* New::next() { return next_; }
+    )";
+  std::string After = runClangRenameOnCode(Before, "Old", "New");
+  CompareSnippets(Expected, After);
+}
+
+TEST_F(ClangRenameTest, RenameClassWithInlineMembers) {
+  std::string Before = R"(
+      class Old {
+       public:
+        Old() {}
+        ~Old() {}
+
+        Old* next() { return next_; }
+
+       private:
+        Old* next_;
+      };
+    )";
+  std::string Expected = R"(
+      class New {
+       public:
+        New() {}
+        ~New() {}
+
+        New* next() { return next_; }
+
+       private:
+        New* next_;
+      };
+    )";
+  std::string After = runClangRenameOnCode(Before, "Old", "New");
+  CompareSnippets(Expected, After);
+}
+
+// FIXME: no prefix qualifiers being added to the class definition and
+// constructor.
+TEST_F(ClangRenameTest, RenameClassWithNamespaceWithInlineMembers) {
+  std::string Before = R"(
+      namespace ns {
+      class Old {
+       public:
+        Old() {}
+        ~Old() {}
+
+        Old* next() { return next_; }
+
+       private:
+        Old* next_;
+      };
+      }  // namespace ns
+    )";
+  std::string Expected = R"(
+      namespace ns {
+      class ns::New {
+       public:
+        ns::New() {}
+        ~New() {}
+
+        New* next() { return next_; }
+
+       private:
+        New* next_;
+      };
+      }  // namespace ns
+    )";
+  std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New");
+  CompareSnippets(Expected, After);
+}
+
+// FIXME: no prefix qualifiers being added to the class definition and
+// constructor.
+TEST_F(ClangRenameTest, RenameClassWithNamespaceWithOutOfInlineMembers) {
+  std::string Before = R"(
+      namespace ns {
+      class Old {
+       public:
+        Old();
+        ~Old();
+
+        Old* next();
+
+       private:
+        Old* next_;
+      };
+
+      Old::Old() {}
+      Old::~Old() {}
+      Old* Old::next() { return next_; }
+      }  // namespace ns
+    )";
+  std::string Expected = R"(
+      namespace ns {
+      class ns::New {
+       public:
+        ns::New();
+        ~New();
+
+        New* next();
+
+       private:
+        New* next_;
+      };
+
+      New::ns::New() {}
+      New::~New() {}
+      New* New::next() { return next_; }
+      }  // namespace ns
+    )";
+  std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New");
+  CompareSnippets(Expected, After);
+}
+
+// FIXME: no prefix qualifiers being added to the definition.
+TEST_F(ClangRenameTest, RenameClassInInheritedConstructor) {
+  // `using Base::Base;` will generate an implicit constructor containing usage
+  // of `::ns::Old` which should not be matched.
+  std::string Before = R"(
+      namespace ns {
+      class Old {
+        int x;
+      };
+      class Base {
+       protected:
+        Old *moo_;
+       public:
+        Base(Old *moo) : moo_(moo) {}
+      };
+      class Derived : public Base {
+       public:
+         using Base::Base;
+      };
+      }  // namespace ns
+      int main() {
+        ::ns::Old foo;
+        ::ns::Derived d(&foo);
+        return 0;
+      })";
+  std::string Expected = R"(
+      namespace ns {
+      class ns::New {
+        int x;
+      };
+      class Base {
+       protected:
+        New *moo_;
+       public:
+        Base(New *moo) : moo_(moo) {}
+      };
+      class Derived : public Base {
+       public:
+         using Base::Base;
+      };
+      }  // namespace ns
+      int main() {
+        ::ns::New foo;
+        ::ns::Derived d(&foo);
+        return 0;
+      })";
+  std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New");
+  CompareSnippets(Expected, After);
+}
+
+TEST_F(ClangRenameTest, DontRenameReferencesInImplicitFunction) {
+  std::string Before = R"(
+      namespace ns {
+      class Old {
+      };
+      } // namespace ns
+      struct S {
+        int y;
+        ns::Old old;
+      };
+      void f() {
+        S s1, s2, s3;
+        // This causes an implicit assignment operator to be created.
+        s1 = s2 = s3;
+      }
+      )";
+  std::string Expected = R"(
+      namespace ns {
+      class ::new_ns::New {
+      };
+      } // namespace ns
+      struct S {
+        int y;
+        ::new_ns::New old;
+      };
+      void f() {
+        S s1, s2, s3;
+        // This causes an implicit assignment operator to be created.
+        s1 = s2 = s3;
+      }
+      )";
+  std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New");
+  CompareSnippets(Expected, After);
+}
+
+// FIXME: no prefix qualifiers being adding to the definition.
+TEST_F(ClangRenameTest, ReferencesInLambdaFunctionParameters) {
+  std::string Before = R"(
+      template <class T>
+      class function;
+      template <class R, class... ArgTypes>
+      class function<R(ArgTypes...)> {
+      public:
+        template <typename Functor>
+        function(Functor f) {}
+
+        function() {}
+
+        R operator()(ArgTypes...) const {}
+      };
+
+      namespace ns {
+      class Old {};
+      void f() {
+        function<void(Old)> func;
+      }
+      }  // namespace ns)";
+  std::string Expected = R"(
+      template <class T>
+      class function;
+      template <class R, class... ArgTypes>
+      class function<R(ArgTypes...)> {
+      public:
+        template <typename Functor>
+        function(Functor f) {}
+
+        function() {}
+
+        R operator()(ArgTypes...) const {}
+      };
+
+      namespace ns {
+      class ::new_ns::New {};
+      void f() {
+        function<void(::new_ns::New)> func;
+      }
+      }  // namespace ns)";
+  std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New");
+  CompareSnippets(Expected, After);
+}
+
+} // anonymous namespace
+} // namespace test
+} // namespace clang_rename
+} // namesdpace clang




More information about the cfe-commits mailing list