Index: clang-tools-extra/trunk/clang-rename/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/clang-rename/CMakeLists.txt +++ clang-tools-extra/trunk/clang-rename/CMakeLists.txt @@ -13,6 +13,7 @@ clangIndex clangLex clangToolingCore + clangToolingRefactor ) add_subdirectory(tool) Index: clang-tools-extra/trunk/clang-rename/RenamingAction.h =================================================================== --- clang-tools-extra/trunk/clang-rename/RenamingAction.h +++ clang-tools-extra/trunk/clang-rename/RenamingAction.h @@ -42,6 +42,28 @@ bool PrintLocations; }; +/// Rename all symbols identified by the given USRs. +class QualifiedRenamingAction { +public: + QualifiedRenamingAction( + const std::vector &NewNames, + const std::vector> &USRList, + std::map &FileToReplaces) + : NewNames(NewNames), USRList(USRList), FileToReplaces(FileToReplaces) {} + + std::unique_ptr newASTConsumer(); + +private: + /// New symbol names. + const std::vector &NewNames; + + /// A list of USRs. Each element represents USRs of a symbol being renamed. + const std::vector> &USRList; + + /// A file path to replacements map. + std::map &FileToReplaces; +}; + } // namespace rename } // namespace clang Index: clang-tools-extra/trunk/clang-rename/RenamingAction.cpp =================================================================== --- clang-tools-extra/trunk/clang-rename/RenamingAction.cpp +++ clang-tools-extra/trunk/clang-rename/RenamingAction.cpp @@ -84,10 +84,52 @@ 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 &NewNames, + const std::vector> &USRList, + std::map &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 &NewNames; + const std::vector> &USRList; + std::map &FileToReplaces; +}; + std::unique_ptr RenamingAction::newASTConsumer() { return llvm::make_unique(NewNames, PrevNames, USRList, FileToReplaces, PrintLocations); } +std::unique_ptr QualifiedRenamingAction::newASTConsumer() { + return llvm::make_unique( + NewNames, USRList, FileToReplaces); +} + } // namespace rename } // namespace clang Index: clang-tools-extra/trunk/clang-rename/USRFinder.cpp =================================================================== --- clang-tools-extra/trunk/clang-rename/USRFinder.cpp +++ clang-tools-extra/trunk/clang-rename/USRFinder.cpp @@ -135,7 +135,8 @@ 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; Index: clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp =================================================================== --- clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp +++ clang-tools-extra/trunk/clang-rename/USRFindingAction.cpp @@ -104,6 +104,10 @@ 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)); Index: clang-tools-extra/trunk/clang-rename/USRLocFinder.h =================================================================== --- clang-tools-extra/trunk/clang-rename/USRLocFinder.h +++ clang-tools-extra/trunk/clang-rename/USRLocFinder.h @@ -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 #include 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 +createRenameAtomicChanges(llvm::ArrayRef USRs, + llvm::StringRef NewName, Decl *TranslationUnitDecl); + // FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! std::vector getLocationsOfUSRs(const std::vector &USRs, Index: clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp =================================================================== --- clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp +++ clang-tools-extra/trunk/clang-rename/USRLocFinder.cpp @@ -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 @@ -148,6 +149,300 @@ 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()) { + 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) 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() + .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()) + 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 { +public: + RenameLocFinder(llvm::ArrayRef 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(Decl)) + return true; + + // DestructorDecl has been handled in Typeloc. + if (llvm::isa(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()) { + VisitNestedNameSpecifierLocations(*NSL); + return true; + } + + if (const auto *TL = Parents[0].get()) + 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(Loc.getType())) { + TypeLoc TargetLoc = Loc; + if (!ParentTypeLoc.isNull()) { + if (llvm::isa(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" in "ns::Foo& f();", we want the parent typeLoc + // (ElaboratedType) of the TemplateSpecializationType in order to + // catch the prefix qualifiers "ns::". + if (!ParentTypeLoc.isNull() && + llvm::isa(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 &getRenameInfos() const { return RenameInfos; } + + // Returns a list of using declarations which are needed to update. + const std::vector &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 struct S { + // T t; // <-- this T becomes a TypeLoc(int) with class + // // SubstTemplateTypeParm when S 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 + 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().isBaseOf( + Parents[0].getNodeKind())) + return Parents[0].template get(); + 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(); + } + + // 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 USRSet; + ASTContext &Context; + std::vector RenameInfos; + // Record all interested using declarations which contains the using-shadow + // declarations of the symbol declarations being renamed. + std::vector UsingDecls; +}; + } // namespace std::vector @@ -163,5 +458,53 @@ return Visitor.getLocationsFound(); } +std::vector +createRenameAtomicChanges(llvm::ArrayRef USRs, + llvm::StringRef NewName, Decl *TranslationUnitDecl) { + RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext()); + Finder.TraverseDecl(TranslationUnitDecl); + + const SourceManager &SM = + TranslationUnitDecl->getASTContext().getSourceManager(); + + std::vector 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( + 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 Index: clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt +++ clang-tools-extra/trunk/unittests/clang-rename/CMakeLists.txt @@ -12,7 +12,7 @@ include_directories(${CLANG_SOURCE_DIR}) add_extra_unittest(ClangRenameTests - ClangRenameTests.cpp + RenameClassTest.cpp ) target_link_libraries(ClangRenameTests Index: clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h =================================================================== --- clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h +++ clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTest.h @@ -0,0 +1,112 @@ +//===-- 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 +#include +#include + +namespace clang { +namespace clang_rename { +namespace test { + +struct Case { + std::string Before; + std::string After; + std::string OldName; + std::string NewName; +}; + +class ClangRenameTest : public testing::Test, + public testing::WithParamInterface { +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 USRFindingActionFactory = + tooling::newFrontendActionFactory(&FindingAction); + + if (!tooling::runToolOnCodeWithArgs( + USRFindingActionFactory->create(), NewCode, {"-std=c++11"}, CCName, + "clang-rename", std::make_shared(), + FileContents)) + return ""; + + const std::vector> &USRList = + FindingAction.getUSRList(); + std::vector NewNames = {NewName}; + std::map FileToReplacements; + rename::QualifiedRenamingAction RenameAction(NewNames, USRList, + FileToReplacements); + auto RenameActionFactory = tooling::newFrontendActionFactory(&RenameAction); + if (!tooling::runToolOnCodeWithArgs( + RenameActionFactory->create(), NewCode, {"-std=c++11"}, CCName, + "clang-rename", std::make_shared(), + 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(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"; +}; + +} // namespace test +} // namespace clang_rename +} // namesdpace clang Index: clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp +++ clang-tools-extra/trunk/unittests/clang-rename/ClangRenameTests.cpp @@ -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 -#include -#include - -namespace clang { -namespace clang_rename { -namespace { - -struct Case { - std::string Before; - std::string After; -}; - -class ClangRenameTest : public testing::Test, - public testing::WithParamInterface { -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 USRFindingActionFactory = - tooling::newFrontendActionFactory(&FindingAction); - - if (!tooling::runToolOnCodeWithArgs( - USRFindingActionFactory->create(), NewCode, {"-std=c++11"}, CCName, - "clang-rename", std::make_shared(), - FileContents)) - return ""; - - const std::vector> &USRList = - FindingAction.getUSRList(); - const std::vector &PrevNames = FindingAction.getUSRSpellings(); - std::vector NewNames = {NewName}; - std::map 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(), - 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(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({ - {"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({ - {"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 Index: clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp +++ clang-tools-extra/trunk/unittests/clang-rename/RenameClassTest.cpp @@ -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 class ptr {}; + )"); + } +}; + +INSTANTIATE_TEST_CASE_P( + RenameClassTests, RenameClassTest, + testing::ValuesIn(std::vector({ + // 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_; };", "struct F { ptr 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 struct Foo { T t; };\n" + "void f() { Foo foo; }", + "template struct Foo { T t; };\n" + "void f() { Foo foo; }"}, + {"template struct Foo { a::Foo a; };", + "template struct Foo { b::Bar a; };"}, + {"template void f(T t) {}\n" + "void g() { f(a::Foo()); }", + "template void f(T t) {}\n" + "void g() { f(b::Bar()); }"}, + {"template int f() { return 1; }\n" + "template <> int f() { return 2; }\n" + "int g() { return f(); }", + "template int f() { return 1; }\n" + "template <> int f() { return 2; }\n" + "int g() { return f(); }"}, + {"struct Foo { template T foo(); };\n" + "void g() { Foo f; auto a = f.template foo(); }", + "struct Foo { template T foo(); };\n" + "void g() { Foo f; auto a = f.template foo(); }"}, + + // The following two templates are distilled from regressions found in + // unique_ptr<> and type_traits.h + {"template struct outer {\n" + " typedef T type;\n" + " type Baz();\n" + " };\n" + " outer g_A;", + "template struct outer {\n" + " typedef T type;\n" + " type Baz();\n" + " };\n" + " outer g_A;"}, + {"template struct nested { typedef T type; };\n" + "template struct outer { typename nested::type Foo(); " + "};\n" + "outer g_A;", + "template struct nested { typedef T type; };\n" + "template struct outer { typename nested::type Foo(); " + "};\n" + "outer 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 a_; };", + "using b::Bar; struct F { ptr 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({ + // 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 struct Old { + T t_; + T f() { return T(); }; + static T s(T t) { return t; } + }; + namespace ns { + template struct Old { + T t_; + T f() { return T(); }; + static T s(T t) { return t; } + }; + } // namespace ns + + namespace o1 { + namespace o2 { + namespace o3 { + template 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({ + {"Old gI; Old gB;", "New gI; New gB;", "Old", + "New"}, + {"ns::Old gI; ns::Old gB;", + "ns::New gI; ns::New gB;", "ns::Old", "ns::New"}, + {"auto gI = &Old::f; auto gB = &Old::f;", + "auto gI = &New::f; auto gB = &New::f;", "Old", "New"}, + {"auto gI = &ns::Old::f;", "auto gI = &ns::New::f;", + "ns::Old", "ns::New"}, + + {"int gI = Old::s(0); bool gB = Old::s(false);", + "int gI = New::s(0); bool gB = New::s(false);", "Old", + "New"}, + {"int gI = ns::Old::s(0); bool gB = ns::Old::s(false);", + "int gI = ns::New::s(0); bool gB = ns::New::s(false);", + "ns::Old", "ns::New"}, + + {"struct S { Old o_; };", "struct S { New o_; };", "Old", + "New"}, + {"struct S { ns::Old o_; };", "struct S { ns::New o_; };", + "ns::Old", "ns::New"}, + + {"auto a = reinterpret_cast*>(new Old);", + "auto a = reinterpret_cast*>(new New);", "Old", "New"}, + {"auto a = reinterpret_cast*>(new ns::Old);", + "auto a = reinterpret_cast*>(new ns::New);", + "ns::Old", "ns::New"}, + {"auto a = reinterpret_cast*>(new Old);", + "auto a = reinterpret_cast*>(new New);", "Old", + "New"}, + {"auto a = reinterpret_cast*>(new ns::Old);", + "auto a = reinterpret_cast*>(new ns::New);", + "ns::Old", "ns::New"}, + + {"Old& foo();", "New& foo();", "Old", "New"}, + {"ns::Old& foo();", "ns::New& foo();", "ns::Old", + "ns::New"}, + {"o1::o2::o3::Old& foo();", "o1::o2::o3::New& foo();", + "o1::o2::o3::Old", "o1::o2::o3::New"}, + {"namespace ns { Old& foo(); }", + "namespace ns { New& foo(); }", "ns::Old", "ns::New"}, + {"const Old& foo();", "const New& foo();", "Old", "New"}, + {"const ns::Old& foo();", "const ns::New& foo();", + "ns::Old", "ns::New"}, + + // FIXME: figure out why this only works when Moo gets + // specialized at some point. + {"template struct Moo { Old o_; }; Moo m;", + "template struct Moo { New o_; }; Moo m;", "Old", + "New"}, + {"template struct Moo { ns::Old o_; }; Moo m;", + "template struct Moo { ns::New o_; }; Moo 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 function; + template + class function { + public: + template + function(Functor f) {} + + function() {} + + R operator()(ArgTypes...) const {} + }; + + namespace ns { + class Old {}; + void f() { + function func; + } + } // namespace ns)"; + std::string Expected = R"( + template + class function; + template + class function { + public: + template + function(Functor f) {} + + function() {} + + R operator()(ArgTypes...) const {} + }; + + namespace ns { + class ::new_ns::New {}; + void f() { + function 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