Index: cfe/trunk/include/clang/Tooling/Refactoring/Rename/RenamingAction.h =================================================================== --- cfe/trunk/include/clang/Tooling/Refactoring/Rename/RenamingAction.h +++ cfe/trunk/include/clang/Tooling/Refactoring/Rename/RenamingAction.h @@ -0,0 +1,70 @@ +//===--- RenamingAction.h - Clang refactoring library ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_RENAME_RENAMING_ACTION_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAME_RENAMING_ACTION_H + +#include "clang/Tooling/Refactoring.h" + +namespace clang { +class ASTConsumer; +class CompilerInstance; + +namespace tooling { + +class RenamingAction { +public: + RenamingAction(const std::vector &NewNames, + const std::vector &PrevNames, + const std::vector> &USRList, + std::map &FileToReplaces, + bool PrintLocations = false) + : NewNames(NewNames), PrevNames(PrevNames), USRList(USRList), + FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {} + + std::unique_ptr newASTConsumer(); + +private: + const std::vector &NewNames, &PrevNames; + const std::vector> &USRList; + std::map &FileToReplaces; + 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; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAME_RENAMING_ACTION_H Index: cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFinder.h =================================================================== --- cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFinder.h +++ cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFinder.h @@ -0,0 +1,84 @@ +//===--- USRFinder.h - Clang refactoring library --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Methods for determining the USR of a symbol at a location in source +/// code. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_FINDER_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_FINDER_H + +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include + +using namespace llvm; +using namespace clang::ast_matchers; + +namespace clang { + +class ASTContext; +class Decl; +class SourceLocation; +class NamedDecl; + +namespace tooling { + +// Given an AST context and a point, returns a NamedDecl identifying the symbol +// at the point. Returns null if nothing is found at the point. +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point); + +// Given an AST context and a fully qualified name, returns a NamedDecl +// identifying the symbol with a matching name. Returns null if nothing is +// found for the name. +const NamedDecl *getNamedDeclFor(const ASTContext &Context, + const std::string &Name); + +// Converts a Decl into a USR. +std::string getUSRForDecl(const Decl *Decl); + +// FIXME: Implement RecursiveASTVisitor::VisitNestedNameSpecifier instead. +class NestedNameSpecifierLocFinder : public MatchFinder::MatchCallback { +public: + explicit NestedNameSpecifierLocFinder(ASTContext &Context) + : Context(Context) {} + + std::vector getNestedNameSpecifierLocations() { + addMatchers(); + Finder.matchAST(Context); + return Locations; + } + +private: + void addMatchers() { + const auto NestedNameSpecifierLocMatcher = + nestedNameSpecifierLoc().bind("nestedNameSpecifierLoc"); + Finder.addMatcher(NestedNameSpecifierLocMatcher, this); + } + + void run(const MatchFinder::MatchResult &Result) override { + const auto *NNS = Result.Nodes.getNodeAs( + "nestedNameSpecifierLoc"); + Locations.push_back(*NNS); + } + + ASTContext &Context; + std::vector Locations; + MatchFinder Finder; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_FINDER_H Index: cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h =================================================================== --- cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h +++ cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h @@ -0,0 +1,54 @@ +//===--- USRFindingAction.h - Clang refactoring library -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to find all relevant USRs at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_FINDING_ACTION_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_FINDING_ACTION_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/ArrayRef.h" + +#include +#include + +namespace clang { +class ASTConsumer; +class CompilerInstance; +class NamedDecl; + +namespace tooling { + +struct USRFindingAction { + USRFindingAction(ArrayRef SymbolOffsets, + ArrayRef QualifiedNames, bool Force) + : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), + ErrorOccurred(false), Force(Force) {} + std::unique_ptr newASTConsumer(); + + ArrayRef getUSRSpellings() { return SpellingNames; } + ArrayRef> getUSRList() { return USRList; } + bool errorOccurred() { return ErrorOccurred; } + +private: + std::vector SymbolOffsets; + std::vector QualifiedNames; + std::vector SpellingNames; + std::vector> USRList; + bool ErrorOccurred; + bool Force; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_FINDING_ACTION_H Index: cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRLocFinder.h =================================================================== --- cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRLocFinder.h +++ cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRLocFinder.h @@ -0,0 +1,49 @@ +//===--- USRLocFinder.h - Clang refactoring library -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides functionality for finding all instances of a USR in a given +/// AST. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_LOC_FINDER_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_LOC_FINDER_H + +#include "clang/AST/AST.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace clang { +namespace tooling { + +/// 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, + llvm::StringRef PrevName, Decl *Decl); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAME_USR_LOC_FINDER_H Index: cfe/trunk/include/clang/module.modulemap =================================================================== --- cfe/trunk/include/clang/module.modulemap +++ cfe/trunk/include/clang/module.modulemap @@ -133,9 +133,10 @@ module Clang_Tooling { requires cplusplus umbrella "Tooling" module * { export * } - // FIXME: Exclude this header to avoid pulling all of the AST matchers + // FIXME: Exclude these headers to avoid pulling all of the AST matchers // library into clang-format. Due to inline key functions in the headers, // importing the AST matchers library gives a link dependency on the AST // matchers (and thus the AST), which clang-format should not have. exclude header "Tooling/RefactoringCallbacks.h" + exclude header "Tooling/Refactoring/Rename/USRFinder.h" } Index: cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt +++ cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt @@ -5,8 +5,16 @@ add_clang_library(clangToolingRefactor AtomicChange.cpp + Rename/RenamingAction.cpp + Rename/USRFinder.cpp + Rename/USRFindingAction.cpp + Rename/USRLocFinder.cpp LINK_LIBS + clangAST + clangASTMatchers clangBasic + clangIndex + clangLex clangToolingCore ) Index: cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp =================================================================== --- cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -0,0 +1,134 @@ +//===--- RenamingAction.cpp - Clang refactoring library -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h" +#include "clang/Tooling/Tooling.h" +#include +#include + +using namespace llvm; + +namespace clang { +namespace tooling { + +class RenamingASTConsumer : public ASTConsumer { +public: + RenamingASTConsumer( + const std::vector &NewNames, + const std::vector &PrevNames, + const std::vector> &USRList, + std::map &FileToReplaces, + bool PrintLocations) + : NewNames(NewNames), PrevNames(PrevNames), USRList(USRList), + FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {} + + void HandleTranslationUnit(ASTContext &Context) override { + for (unsigned I = 0; I < NewNames.size(); ++I) + HandleOneRename(Context, NewNames[I], PrevNames[I], USRList[I]); + } + + void HandleOneRename(ASTContext &Context, const std::string &NewName, + const std::string &PrevName, + const std::vector &USRs) { + const SourceManager &SourceMgr = Context.getSourceManager(); + std::vector RenamingCandidates; + std::vector NewCandidates; + + NewCandidates = tooling::getLocationsOfUSRs( + USRs, PrevName, Context.getTranslationUnitDecl()); + RenamingCandidates.insert(RenamingCandidates.end(), NewCandidates.begin(), + NewCandidates.end()); + + unsigned PrevNameLen = PrevName.length(); + for (const auto &Loc : RenamingCandidates) { + if (PrintLocations) { + FullSourceLoc FullLoc(Loc, SourceMgr); + errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(Loc) + << ":" << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << "\n"; + } + // FIXME: better error handling. + tooling::Replacement Replace(SourceMgr, Loc, PrevNameLen, NewName); + 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, &PrevNames; + const std::vector> &USRList; + std::map &FileToReplaces; + 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 = tooling::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); +} + +} // end namespace tooling +} // end namespace clang Index: cfe/trunk/lib/Tooling/Refactoring/Rename/USRFinder.cpp =================================================================== --- cfe/trunk/lib/Tooling/Refactoring/Rename/USRFinder.cpp +++ cfe/trunk/lib/Tooling/Refactoring/Rename/USRFinder.cpp @@ -0,0 +1,213 @@ +//===--- USRFinder.cpp - Clang refactoring library ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Implements a recursive AST visitor that finds the USR of a symbol at a +/// point. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace tooling { + +// NamedDeclFindingASTVisitor recursively visits each AST node to find the +// symbol underneath the cursor. +// FIXME: move to separate .h/.cc file if this gets too large. +namespace { +class NamedDeclFindingASTVisitor + : public clang::RecursiveASTVisitor { +public: + // \brief Finds the NamedDecl at a point in the source. + // \param Point the location in the source to search for the NamedDecl. + explicit NamedDeclFindingASTVisitor(const SourceLocation Point, + const ASTContext &Context) + : Result(nullptr), Point(Point), Context(Context) {} + + // \brief Finds the NamedDecl for a name in the source. + // \param Name the fully qualified name. + explicit NamedDeclFindingASTVisitor(const std::string &Name, + const ASTContext &Context) + : Result(nullptr), Name(Name), Context(Context) {} + + // Declaration visitors: + + // \brief Checks if the point falls within the NameDecl. This covers every + // declaration of a named entity that we may come across. Usually, just + // checking if the point lies within the length of the name of the declaration + // and the start location is sufficient. + bool VisitNamedDecl(const NamedDecl *Decl) { + return dyn_cast(Decl) + ? true + : setResult(Decl, Decl->getLocation(), + Decl->getNameAsString().length()); + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + return setResult(Decl, Expr->getLocation(), + Decl->getNameAsString().length()); + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); + return setResult(Decl, Expr->getMemberLoc(), + Decl->getNameAsString().length()); + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + const SourceLocation TypeBeginLoc = Loc.getBeginLoc(); + const SourceLocation TypeEndLoc = Lexer::getLocForEndOfToken( + TypeBeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + if (const auto *TemplateTypeParm = + dyn_cast(Loc.getType())) + return setResult(TemplateTypeParm->getDecl(), TypeBeginLoc, TypeEndLoc); + if (const auto *TemplateSpecType = + dyn_cast(Loc.getType())) { + return setResult(TemplateSpecType->getTemplateName().getAsTemplateDecl(), + TypeBeginLoc, TypeEndLoc); + } + return setResult(Loc.getType()->getAsCXXRecordDecl(), TypeBeginLoc, + TypeEndLoc); + } + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { + const SourceLocation InitBeginLoc = Initializer->getSourceLocation(), + InitEndLoc = Lexer::getLocForEndOfToken( + InitBeginLoc, 0, Context.getSourceManager(), + Context.getLangOpts()); + if (!setResult(FieldDecl, InitBeginLoc, InitEndLoc)) + return false; + } + } + return true; + } + + // Other: + + const NamedDecl *getNamedDecl() { return Result; } + + // \brief Determines if a namespace qualifier contains the point. + // \returns false on success and sets Result. + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const NamespaceDecl *Decl = + NameLoc.getNestedNameSpecifier()->getAsNamespace(); + setResult(Decl, NameLoc.getLocalBeginLoc(), NameLoc.getLocalEndLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +private: + // \brief Sets Result to Decl if the Point is within Start and End. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Start, + SourceLocation End) { + if (!Decl) + return true; + if (Name.empty()) { + // Offset is used to find the declaration. + if (!Start.isValid() || !Start.isFileID() || !End.isValid() || + !End.isFileID() || !isPointWithin(Start, End)) + return true; + } else { + // Fully qualified name is used to find the declaration. + if (Name != Decl->getQualifiedNameAsString() && + Name != "::" + Decl->getQualifiedNameAsString()) + return true; + } + Result = Decl; + return false; + } + + // \brief Sets Result to Decl if Point is within Loc and Loc + Offset. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Loc, unsigned Offset) { + // FIXME: Add test for Offset == 0. Add test for Offset - 1 (vs -2 etc). + return Offset == 0 || + setResult(Decl, Loc, Loc.getLocWithOffset(Offset - 1)); + } + + // \brief Determines if the Point is within Start and End. + bool isPointWithin(const SourceLocation Start, const SourceLocation End) { + // FIXME: Add tests for Point == End. + return Point == Start || Point == End || + (Context.getSourceManager().isBeforeInTranslationUnit(Start, + Point) && + Context.getSourceManager().isBeforeInTranslationUnit(Point, End)); + } + + const NamedDecl *Result; + const SourceLocation Point; // The location to find the NamedDecl. + const std::string Name; + const ASTContext &Context; +}; +} // namespace + +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point) { + const SourceManager &SM = Context.getSourceManager(); + NamedDeclFindingASTVisitor Visitor(Point, Context); + + // Try to be clever about pruning down the number of top-level declarations we + // see. If both start and end is either before or after the point we're + // looking for the point cannot be inside of this decl. Don't even look at it. + for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { + SourceLocation StartLoc = CurrDecl->getLocStart(); + SourceLocation EndLoc = CurrDecl->getLocEnd(); + if (StartLoc.isValid() && EndLoc.isValid() && + SM.isBeforeInTranslationUnit(StartLoc, Point) != + SM.isBeforeInTranslationUnit(EndLoc, Point)) + Visitor.TraverseDecl(CurrDecl); + } + + NestedNameSpecifierLocFinder Finder(const_cast(Context)); + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + + return Visitor.getNamedDecl(); +} + +const NamedDecl *getNamedDeclFor(const ASTContext &Context, + const std::string &Name) { + NamedDeclFindingASTVisitor Visitor(Name, Context); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + + return Visitor.getNamedDecl(); +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector Buff; + + // FIXME: Add test for the nullptr case. + if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) + return ""; + + return std::string(Buff.data(), Buff.size()); +} + +} // end namespace tooling +} // end namespace clang Index: cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp =================================================================== --- cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp +++ cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp @@ -0,0 +1,236 @@ +//===--- USRFindingAction.cpp - Clang refactoring library -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to find USR for the symbol at , as well as +/// all additional USRs. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "clang/Tooling/Tooling.h" + +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace tooling { + +namespace { +// \brief NamedDeclFindingConsumer should delegate finding USRs of given Decl to +// AdditionalUSRFinder. AdditionalUSRFinder adds USRs of ctor and dtor if given +// Decl refers to class and adds USRs of all overridden methods if Decl refers +// to virtual method. +class AdditionalUSRFinder : public RecursiveASTVisitor { +public: + AdditionalUSRFinder(const Decl *FoundDecl, ASTContext &Context) + : FoundDecl(FoundDecl), Context(Context) {} + + std::vector Find() { + // Fill OverriddenMethods and PartialSpecs storages. + TraverseDecl(Context.getTranslationUnitDecl()); + if (const auto *MethodDecl = dyn_cast(FoundDecl)) { + addUSRsOfOverridenFunctions(MethodDecl); + for (const auto &OverriddenMethod : OverriddenMethods) { + if (checkIfOverriddenFunctionAscends(OverriddenMethod)) + USRSet.insert(getUSRForDecl(OverriddenMethod)); + } + } else if (const auto *RecordDecl = dyn_cast(FoundDecl)) { + handleCXXRecordDecl(RecordDecl); + } else if (const auto *TemplateDecl = + dyn_cast(FoundDecl)) { + handleClassTemplateDecl(TemplateDecl); + } else { + USRSet.insert(getUSRForDecl(FoundDecl)); + } + return std::vector(USRSet.begin(), USRSet.end()); + } + + bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) { + if (MethodDecl->isVirtual()) + OverriddenMethods.push_back(MethodDecl); + return true; + } + + bool VisitClassTemplatePartialSpecializationDecl( + const ClassTemplatePartialSpecializationDecl *PartialSpec) { + PartialSpecs.push_back(PartialSpec); + return true; + } + +private: + void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl) { + RecordDecl = RecordDecl->getDefinition(); + if (const auto *ClassTemplateSpecDecl = + dyn_cast(RecordDecl)) + handleClassTemplateDecl(ClassTemplateSpecDecl->getSpecializedTemplate()); + addUSRsOfCtorDtors(RecordDecl); + } + + void handleClassTemplateDecl(const ClassTemplateDecl *TemplateDecl) { + for (const auto *Specialization : TemplateDecl->specializations()) + addUSRsOfCtorDtors(Specialization); + + for (const auto *PartialSpec : PartialSpecs) { + if (PartialSpec->getSpecializedTemplate() == TemplateDecl) + addUSRsOfCtorDtors(PartialSpec); + } + addUSRsOfCtorDtors(TemplateDecl->getTemplatedDecl()); + } + + 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)); + + USRSet.insert(getUSRForDecl(RecordDecl->getDestructor())); + USRSet.insert(getUSRForDecl(RecordDecl)); + } + + void addUSRsOfOverridenFunctions(const CXXMethodDecl *MethodDecl) { + USRSet.insert(getUSRForDecl(MethodDecl)); + // Recursively visit each OverridenMethod. + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) + addUSRsOfOverridenFunctions(OverriddenMethod); + } + + bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl) { + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) { + if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) + return true; + return checkIfOverriddenFunctionAscends(OverriddenMethod); + } + return false; + } + + const Decl *FoundDecl; + ASTContext &Context; + std::set USRSet; + std::vector OverriddenMethods; + std::vector PartialSpecs; +}; +} // namespace + +class NamedDeclFindingConsumer : public ASTConsumer { +public: + NamedDeclFindingConsumer(ArrayRef SymbolOffsets, + ArrayRef QualifiedNames, + std::vector &SpellingNames, + std::vector> &USRList, + bool Force, bool &ErrorOccurred) + : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), + SpellingNames(SpellingNames), USRList(USRList), Force(Force), + ErrorOccurred(ErrorOccurred) {} + +private: + bool FindSymbol(ASTContext &Context, const SourceManager &SourceMgr, + unsigned SymbolOffset, const std::string &QualifiedName) { + DiagnosticsEngine &Engine = Context.getDiagnostics(); + const FileID MainFileID = SourceMgr.getMainFileID(); + + if (SymbolOffset >= SourceMgr.getFileIDSize(MainFileID)) { + ErrorOccurred = true; + unsigned InvalidOffset = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "SourceLocation in file %0 at offset %1 is invalid"); + Engine.Report(SourceLocation(), InvalidOffset) + << SourceMgr.getFileEntryForID(MainFileID)->getName() << SymbolOffset; + return false; + } + + const SourceLocation Point = SourceMgr.getLocForStartOfFile(MainFileID) + .getLocWithOffset(SymbolOffset); + const NamedDecl *FoundDecl = QualifiedName.empty() + ? getNamedDeclAt(Context, Point) + : getNamedDeclFor(Context, QualifiedName); + + if (FoundDecl == nullptr) { + if (QualifiedName.empty()) { + FullSourceLoc FullLoc(Point, SourceMgr); + unsigned CouldNotFindSymbolAt = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "clang-rename could not find symbol (offset %0)"); + Engine.Report(Point, CouldNotFindSymbolAt) << SymbolOffset; + ErrorOccurred = true; + return false; + } + + if (Force) + return true; + + unsigned CouldNotFindSymbolNamed = Engine.getCustomDiagID( + DiagnosticsEngine::Error, "clang-rename could not find symbol %0"); + Engine.Report(CouldNotFindSymbolNamed) << QualifiedName; + ErrorOccurred = true; + return false; + } + + // If FoundDecl is a constructor or destructor, we want to instead take + // the Decl of the corresponding class. + if (const auto *CtorDecl = dyn_cast(FoundDecl)) + FoundDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast(FoundDecl)) + FoundDecl = DtorDecl->getParent(); + + SpellingNames.push_back(FoundDecl->getNameAsString()); + AdditionalUSRFinder Finder(FoundDecl, Context); + USRList.push_back(Finder.Find()); + return true; + } + + void HandleTranslationUnit(ASTContext &Context) override { + const SourceManager &SourceMgr = Context.getSourceManager(); + for (unsigned Offset : SymbolOffsets) { + if (!FindSymbol(Context, SourceMgr, Offset, "")) + return; + } + for (const std::string &QualifiedName : QualifiedNames) { + if (!FindSymbol(Context, SourceMgr, 0, QualifiedName)) + return; + } + } + + ArrayRef SymbolOffsets; + ArrayRef QualifiedNames; + std::vector &SpellingNames; + std::vector> &USRList; + bool Force; + bool &ErrorOccurred; +}; + +std::unique_ptr USRFindingAction::newASTConsumer() { + return llvm::make_unique( + SymbolOffsets, QualifiedNames, SpellingNames, USRList, Force, + ErrorOccurred); +} + +} // end namespace tooling +} // end namespace clang Index: cfe/trunk/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp =================================================================== --- cfe/trunk/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp +++ cfe/trunk/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp @@ -0,0 +1,509 @@ +//===--- USRLocFinder.cpp - Clang refactoring library ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Methods for finding all instances of a USR. Our strategy is very +/// simple; we just compare the USR at every relevant AST node with the one +/// provided. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/Lookup.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace tooling { + +namespace { + +// \brief This visitor recursively searches for all instances of a USR in a +// translation unit and stores them for later usage. +class USRLocFindingASTVisitor + : public clang::RecursiveASTVisitor { +public: + explicit USRLocFindingASTVisitor(const std::vector &USRs, + StringRef PrevName, + const ASTContext &Context) + : USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) { + } + + // Declaration visitors: + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { + if (USRSet.find(getUSRForDecl(FieldDecl)) != USRSet.end()) + LocationsFound.push_back(Initializer->getSourceLocation()); + } + } + return true; + } + + bool VisitNamedDecl(const NamedDecl *Decl) { + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) + checkAndAddLocation(Decl->getLocation()); + return true; + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { + const SourceManager &Manager = Decl->getASTContext().getSourceManager(); + SourceLocation Location = Manager.getSpellingLoc(Expr->getLocation()); + checkAndAddLocation(Location); + } + + return true; + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { + const SourceManager &Manager = Decl->getASTContext().getSourceManager(); + SourceLocation Location = Manager.getSpellingLoc(Expr->getMemberLoc()); + checkAndAddLocation(Location); + } + return true; + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + if (USRSet.find(getUSRForDecl(Loc.getType()->getAsCXXRecordDecl())) != + USRSet.end()) + checkAndAddLocation(Loc.getBeginLoc()); + if (const auto *TemplateTypeParm = + dyn_cast(Loc.getType())) { + if (USRSet.find(getUSRForDecl(TemplateTypeParm->getDecl())) != + USRSet.end()) + checkAndAddLocation(Loc.getBeginLoc()); + } + return true; + } + + // Non-visitors: + + // \brief Returns a list of unique locations. Duplicate or overlapping + // locations are erroneous and should be reported! + const std::vector &getLocationsFound() const { + return LocationsFound; + } + + // Namespace traversal: + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const NamespaceDecl *Decl = + NameLoc.getNestedNameSpecifier()->getAsNamespace(); + if (Decl && USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) + checkAndAddLocation(NameLoc.getLocalBeginLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +private: + void checkAndAddLocation(SourceLocation Loc) { + const SourceLocation BeginLoc = Loc; + const SourceLocation EndLoc = Lexer::getLocForEndOfToken( + BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + StringRef TokenName = + Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), + Context.getSourceManager(), Context.getLangOpts()); + size_t Offset = TokenName.find(PrevName); + + // The token of the source location we find actually has the old + // name. + if (Offset != StringRef::npos) + LocationsFound.push_back(BeginLoc.getLocWithOffset(Offset)); + } + + const std::set USRSet; + const std::string PrevName; + std::vector LocationsFound; + 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(const 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 +getLocationsOfUSRs(const std::vector &USRs, StringRef PrevName, + Decl *Decl) { + USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); + Visitor.TraverseDecl(Decl); + NestedNameSpecifierLocFinder Finder(Decl->getASTContext()); + + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + + 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; +} + +} // end namespace tooling +} // end namespace clang Index: cfe/trunk/test/CMakeLists.txt =================================================================== --- cfe/trunk/test/CMakeLists.txt +++ cfe/trunk/test/CMakeLists.txt @@ -47,6 +47,7 @@ clang-tblgen clang-offload-bundler clang-import-test + clang-rename ) if(CLANG_ENABLE_STATIC_ANALYZER) Index: cfe/trunk/test/clang-rename/ClassAsTemplateArgument.cpp =================================================================== --- cfe/trunk/test/clang-rename/ClassAsTemplateArgument.cpp +++ cfe/trunk/test/clang-rename/ClassAsTemplateArgument.cpp @@ -0,0 +1,21 @@ +class Foo /* Test 1 */ {}; // CHECK: class Bar /* Test 1 */ {}; + +template +void func() {} + +template +class Baz {}; + +int main() { + func(); // CHECK: func(); + Baz /* Test 2 */ obj; // CHECK: Baz /* Test 2 */ obj; + return 0; +} + +// Test 1. +// RUN: clang-rename -offset=7 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=215 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/ClassFindByName.cpp =================================================================== --- cfe/trunk/test/clang-rename/ClassFindByName.cpp +++ cfe/trunk/test/clang-rename/ClassFindByName.cpp @@ -0,0 +1,10 @@ +class Foo { // CHECK: class Bar { +}; + +int main() { + Foo *Pointer = 0; // CHECK: Bar *Pointer = 0; + return 0; +} + +// Test 1. +// RUN: clang-rename -qualified-name=Foo -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s Index: cfe/trunk/test/clang-rename/ClassReplacements.cpp =================================================================== --- cfe/trunk/test/clang-rename/ClassReplacements.cpp +++ cfe/trunk/test/clang-rename/ClassReplacements.cpp @@ -0,0 +1,11 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t/fixes +// RUN: cat %s > %t.cpp +// RUN: clang-rename -offset=254 -new-name=Bar -export-fixes=%t/fixes/clang-rename.yaml %t.cpp -- +// RUN: clang-apply-replacements %t +// RUN: sed 's,//.*,,' %t.cpp | FileCheck %s + +class Foo {}; // CHECK: class Bar {}; + +// Use grep -FUbo 'Foo' to get the correct offset of Cla when changing +// this file. Index: cfe/trunk/test/clang-rename/ClassSimpleRenaming.cpp =================================================================== --- cfe/trunk/test/clang-rename/ClassSimpleRenaming.cpp +++ cfe/trunk/test/clang-rename/ClassSimpleRenaming.cpp @@ -0,0 +1,14 @@ +class Foo /* Test 1 */ { // CHECK: class Bar /* Test 1 */ { +public: + void foo(int x); +}; + +void Foo::foo(int x) /* Test 2 */ {} // CHECK: void Bar::foo(int x) /* Test 2 */ {} + +// Test 1. +// RUN: clang-rename -offset=6 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=109 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/ClassTestMulti.cpp =================================================================== --- cfe/trunk/test/clang-rename/ClassTestMulti.cpp +++ cfe/trunk/test/clang-rename/ClassTestMulti.cpp @@ -0,0 +1,11 @@ +class Foo1 /* Offset 1 */ { // CHECK: class Bar1 /* Offset 1 */ { +}; + +class Foo2 /* Offset 2 */ { // CHECK: class Bar2 /* Offset 2 */ { +}; + +// Test 1. +// RUN: clang-rename -offset=6 -new-name=Bar1 -offset=76 -new-name=Bar2 %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/ClassTestMultiByName.cpp =================================================================== --- cfe/trunk/test/clang-rename/ClassTestMultiByName.cpp +++ cfe/trunk/test/clang-rename/ClassTestMultiByName.cpp @@ -0,0 +1,8 @@ +class Foo1 { // CHECK: class Bar1 +}; + +class Foo2 { // CHECK: class Bar2 +}; + +// Test 1. +// RUN: clang-rename -qualified-name=Foo1 -new-name=Bar1 -qualified-name=Foo2 -new-name=Bar2 %s -- | sed 's,//.*,,' | FileCheck %s Index: cfe/trunk/test/clang-rename/ComplexFunctionOverride.cpp =================================================================== --- cfe/trunk/test/clang-rename/ComplexFunctionOverride.cpp +++ cfe/trunk/test/clang-rename/ComplexFunctionOverride.cpp @@ -0,0 +1,47 @@ +struct A { + virtual void foo() {} /* Test 1 */ // CHECK: virtual void bar() {} +}; + +struct B : A { + void foo() override {} /* Test 2 */ // CHECK: void bar() override {} +}; + +struct C : B { + void foo() override {} /* Test 3 */ // CHECK: void bar() override {} +}; + +struct D : B { + void foo() override {} /* Test 4 */ // CHECK: void bar() override {} +}; + +struct E : D { + void foo() override {} /* Test 5 */ // CHECK: void bar() override {} +}; + +int main() { + A a; + a.foo(); // CHECK: a.bar(); + B b; + b.foo(); // CHECK: b.bar(); + C c; + c.foo(); // CHECK: c.bar(); + D d; + d.foo(); // CHECK: d.bar(); + E e; + e.foo(); // CHECK: e.bar(); + return 0; +} + +// Test 1. +// RUN: clang-rename -offset=26 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=109 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=201 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 4. +// RUN: clang-rename -offset=293 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 5. +// RUN: clang-rename -offset=385 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'foo.*' Index: cfe/trunk/test/clang-rename/ComplicatedClassType.cpp =================================================================== --- cfe/trunk/test/clang-rename/ComplicatedClassType.cpp +++ cfe/trunk/test/clang-rename/ComplicatedClassType.cpp @@ -0,0 +1,63 @@ +// Forward declaration. +class Foo; /* Test 1 */ // CHECK: class Bar; /* Test 1 */ + +class Baz { + virtual int getValue() const = 0; +}; + +class Foo : public Baz { /* Test 2 */// CHECK: class Bar : public Baz { +public: + Foo(int value = 0) : x(value) {} // CHECK: Bar(int value = 0) : x(value) {} + + Foo &operator++(int) { // CHECK: Bar &operator++(int) { + x++; + return *this; + } + + bool operator<(Foo const &rhs) { // CHECK: bool operator<(Bar const &rhs) { + return this->x < rhs.x; + } + + int getValue() const { + return 0; + } + +private: + int x; +}; + +int main() { + Foo *Pointer = 0; // CHECK: Bar *Pointer = 0; + Foo Variable = Foo(10); // CHECK: Bar Variable = Bar(10); + for (Foo it; it < Variable; it++) { // CHECK: for (Bar it; it < Variable; it++) { + } + const Foo *C = new Foo(); // CHECK: const Bar *C = new Bar(); + const_cast(C)->getValue(); // CHECK: const_cast(C)->getValue(); + Foo foo; // CHECK: Bar foo; + const Baz &BazReference = foo; + const Baz *BazPointer = &foo; + dynamic_cast(BazReference).getValue(); /* Test 3 */ // CHECK: dynamic_cast(BazReference).getValue(); + dynamic_cast(BazPointer)->getValue(); /* Test 4 */ // CHECK: dynamic_cast(BazPointer)->getValue(); + reinterpret_cast(BazPointer)->getValue(); /* Test 5 */ // CHECK: reinterpret_cast(BazPointer)->getValue(); + static_cast(BazReference).getValue(); /* Test 6 */ // CHECK: static_cast(BazReference).getValue(); + static_cast(BazPointer)->getValue(); /* Test 7 */ // CHECK: static_cast(BazPointer)->getValue(); + return 0; +} + +// Test 1. +// RUN: clang-rename -offset=30 -new-name=Bar %s -- -frtti | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=155 -new-name=Bar %s -- -frtti | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=1133 -new-name=Bar %s -- -frtti | sed 's,//.*,,' | FileCheck %s +// Test 4. +// RUN: clang-rename -offset=1266 -new-name=Bar %s -- -frtti | sed 's,//.*,,' | FileCheck %s +// Test 5. +// RUN: clang-rename -offset=1402 -new-name=Bar %s -- -frtti | sed 's,//.*,,' | FileCheck %s +// Test 6. +// RUN: clang-rename -offset=1533 -new-name=Bar %s -- -frtti | sed 's,//.*,,' | FileCheck %s +// Test 7. +// RUN: clang-rename -offset=1665 -new-name=Bar %s -- -frtti | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/Ctor.cpp =================================================================== --- cfe/trunk/test/clang-rename/Ctor.cpp +++ cfe/trunk/test/clang-rename/Ctor.cpp @@ -0,0 +1,14 @@ +class Foo { // CHECK: class Bar { +public: + Foo(); /* Test 1 */ // CHECK: Bar(); +}; + +Foo::Foo() /* Test 2 */ {} // CHECK: Bar::Bar() /* Test 2 */ {} + +// Test 1. +// RUN: clang-rename -offset=62 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=116 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/CtorInitializer.cpp =================================================================== --- cfe/trunk/test/clang-rename/CtorInitializer.cpp +++ cfe/trunk/test/clang-rename/CtorInitializer.cpp @@ -0,0 +1,17 @@ +class Baz {}; + +class Qux { + Baz Foo; /* Test 1 */ // CHECK: Baz Bar; +public: + Qux(); +}; + +Qux::Qux() : Foo() /* Test 2 */ {} // CHECK: Qux::Qux() : Bar() /* Test 2 */ {} + +// Test 1. +// RUN: clang-rename -offset=33 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=118 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/DeclRefExpr.cpp =================================================================== --- cfe/trunk/test/clang-rename/DeclRefExpr.cpp +++ cfe/trunk/test/clang-rename/DeclRefExpr.cpp @@ -0,0 +1,24 @@ +class C { +public: + static int Foo; /* Test 1 */ // CHECK: static int Bar; +}; + +int foo(int x) { return 0; } +#define MACRO(a) foo(a) + +int main() { + C::Foo = 1; /* Test 2 */ // CHECK: C::Bar = 1; + MACRO(C::Foo); // CHECK: MACRO(C::Bar); + int y = C::Foo; /* Test 3 */ // CHECK: int y = C::Bar; + return 0; +} + +// Test 1. +// RUN: clang-rename -offset=31 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=152 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=271 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/Field.cpp =================================================================== --- cfe/trunk/test/clang-rename/Field.cpp +++ cfe/trunk/test/clang-rename/Field.cpp @@ -0,0 +1,15 @@ +class Baz { + int Foo; /* Test 1 */ // CHECK: int Bar; +public: + Baz(); +}; + +Baz::Baz() : Foo(0) /* Test 2 */ {} // CHECK: Baz::Baz() : Bar(0) + +// Test 1. +// RUN: clang-rename -offset=18 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=89 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/FunctionMacro.cpp =================================================================== --- cfe/trunk/test/clang-rename/FunctionMacro.cpp +++ cfe/trunk/test/clang-rename/FunctionMacro.cpp @@ -0,0 +1,20 @@ +#define moo foo // CHECK: #define moo macro_function + +int foo() /* Test 1 */ { // CHECK: int macro_function() /* Test 1 */ { + return 42; +} + +void boo(int value) {} + +void qoo() { + foo(); // CHECK: macro_function(); + boo(foo()); // CHECK: boo(macro_function()); + moo(); + boo(moo()); +} + +// Test 1. +// RUN: clang-rename -offset=68 -new-name=macro_function %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'foo.*' Index: cfe/trunk/test/clang-rename/FunctionOverride.cpp =================================================================== --- cfe/trunk/test/clang-rename/FunctionOverride.cpp +++ cfe/trunk/test/clang-rename/FunctionOverride.cpp @@ -0,0 +1,13 @@ +class A { virtual void foo(); /* Test 1 */ }; // CHECK: class A { virtual void bar(); +class B : public A { void foo(); /* Test 2 */ }; // CHECK: class B : public A { void bar(); +class C : public B { void foo(); /* Test 3 */ }; // CHECK: class C : public B { void bar(); + +// Test 1. +// RUN: clang-rename -offset=23 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=116 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=209 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'foo.*' Index: cfe/trunk/test/clang-rename/FunctionWithClassFindByName.cpp =================================================================== --- cfe/trunk/test/clang-rename/FunctionWithClassFindByName.cpp +++ cfe/trunk/test/clang-rename/FunctionWithClassFindByName.cpp @@ -0,0 +1,12 @@ +void foo() { +} + +class Foo { // CHECK: class Bar +}; + +int main() { + Foo *Pointer = 0; // CHECK: Bar *Pointer = 0; + return 0; +} + +// RUN: clang-rename -qualified-name=Foo -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s Index: cfe/trunk/test/clang-rename/IncludeHeaderWithSymbol.cpp =================================================================== --- cfe/trunk/test/clang-rename/IncludeHeaderWithSymbol.cpp +++ cfe/trunk/test/clang-rename/IncludeHeaderWithSymbol.cpp @@ -0,0 +1,10 @@ +#include "Inputs/HeaderWithSymbol.h" + +int main() { + return 0; // CHECK: {{^ return 0;}} +} + +// Test 1. +// The file IncludeHeaderWithSymbol.cpp doesn't contain the symbol Foo +// and is expected to be written to stdout without modifications +// RUN: clang-rename -qualified-name=Foo -new-name=Bar %s -- | FileCheck %s Index: cfe/trunk/test/clang-rename/Inputs/HeaderWithSymbol.h =================================================================== --- cfe/trunk/test/clang-rename/Inputs/HeaderWithSymbol.h +++ cfe/trunk/test/clang-rename/Inputs/HeaderWithSymbol.h @@ -0,0 +1 @@ +struct Foo {}; Index: cfe/trunk/test/clang-rename/Inputs/OffsetToNewName.yaml =================================================================== --- cfe/trunk/test/clang-rename/Inputs/OffsetToNewName.yaml +++ cfe/trunk/test/clang-rename/Inputs/OffsetToNewName.yaml @@ -0,0 +1,6 @@ +--- +- Offset: 6 + NewName: Bar1 +- Offset: 44 + NewName: Bar2 +... Index: cfe/trunk/test/clang-rename/Inputs/QualifiedNameToNewName.yaml =================================================================== --- cfe/trunk/test/clang-rename/Inputs/QualifiedNameToNewName.yaml +++ cfe/trunk/test/clang-rename/Inputs/QualifiedNameToNewName.yaml @@ -0,0 +1,6 @@ +--- +- QualifiedName: Foo1 + NewName: Bar1 +- QualifiedName: Foo2 + NewName: Bar2 +... Index: cfe/trunk/test/clang-rename/InvalidNewName.cpp =================================================================== --- cfe/trunk/test/clang-rename/InvalidNewName.cpp +++ cfe/trunk/test/clang-rename/InvalidNewName.cpp @@ -0,0 +1,2 @@ +// RUN: not clang-rename -new-name=class -offset=133 %s 2>&1 | FileCheck %s +// CHECK: ERROR: new name is not a valid identifier in C++17. Index: cfe/trunk/test/clang-rename/InvalidOffset.cpp =================================================================== --- cfe/trunk/test/clang-rename/InvalidOffset.cpp +++ cfe/trunk/test/clang-rename/InvalidOffset.cpp @@ -0,0 +1,9 @@ +#include "Inputs/HeaderWithSymbol.h" +#define FOO int bar; +FOO + +int foo; + +// RUN: not clang-rename -new-name=qux -offset=259 %s -- 2>&1 | FileCheck %s +// CHECK-NOT: CHECK +// CHECK: error: SourceLocation in file {{.*}}InvalidOffset.cpp at offset 259 is invalid Index: cfe/trunk/test/clang-rename/InvalidQualifiedName.cpp =================================================================== --- cfe/trunk/test/clang-rename/InvalidQualifiedName.cpp +++ cfe/trunk/test/clang-rename/InvalidQualifiedName.cpp @@ -0,0 +1,4 @@ +struct S { +}; + +// RUN: clang-rename -force -qualified-name S2 -new-name=T %s -- Index: cfe/trunk/test/clang-rename/MemberExprMacro.cpp =================================================================== --- cfe/trunk/test/clang-rename/MemberExprMacro.cpp +++ cfe/trunk/test/clang-rename/MemberExprMacro.cpp @@ -0,0 +1,22 @@ +class Baz { +public: + int Foo; /* Test 1 */ // CHECK: int Bar; +}; + +int qux(int x) { return 0; } +#define MACRO(a) qux(a) + +int main() { + Baz baz; + baz.Foo = 1; /* Test 2 */ // CHECK: baz.Bar = 1; + MACRO(baz.Foo); // CHECK: MACRO(baz.Bar); + int y = baz.Foo; // CHECK: int y = baz.Bar; +} + +// Test 1. +// RUN: clang-rename -offset=26 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=155 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/Namespace.cpp =================================================================== --- cfe/trunk/test/clang-rename/Namespace.cpp +++ cfe/trunk/test/clang-rename/Namespace.cpp @@ -0,0 +1,13 @@ +namespace gcc /* Test 1 */ { // CHECK: namespace clang /* Test 1 */ { + int x; +} + +void boo() { + gcc::x = 42; // CHECK: clang::x = 42; +} + +// Test 1. +// RUN: clang-rename -offset=10 -new-name=clang %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/NoNewName.cpp =================================================================== --- cfe/trunk/test/clang-rename/NoNewName.cpp +++ cfe/trunk/test/clang-rename/NoNewName.cpp @@ -0,0 +1,4 @@ +// Check for an error while -new-name argument has not been passed to +// clang-rename. +// RUN: not clang-rename -offset=133 %s 2>&1 | FileCheck %s +// CHECK: clang-rename: -new-name must be specified. Index: cfe/trunk/test/clang-rename/TemplateClassInstantiation.cpp =================================================================== --- cfe/trunk/test/clang-rename/TemplateClassInstantiation.cpp +++ cfe/trunk/test/clang-rename/TemplateClassInstantiation.cpp @@ -0,0 +1,42 @@ +template +class Foo { /* Test 1 */ // CHECK: class Bar { /* Test 1 */ +public: + T foo(T arg, T& ref, T* ptr) { + T value; + int number = 42; + value = (T)number; + value = static_cast(number); + return value; + } + static void foo(T value) {} + T member; +}; + +template +void func() { + Foo obj; /* Test 2 */ // CHECK: Bar obj; + obj.member = T(); + Foo::foo(); // CHECK: Bar::foo(); +} + +int main() { + Foo i; /* Test 3 */ // CHECK: Bar i; + i.member = 0; + Foo::foo(0); // CHECK: Bar::foo(0); + + Foo b; // CHECK: Bar b; + b.member = false; + Foo::foo(false); // CHECK: Bar::foo(false); + + return 0; +} + +// Test 1. +// RUN: clang-rename -offset=29 -new-name=Bar %s -- -fno-delayed-template-parsing | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=324 -new-name=Bar %s -- -fno-delayed-template-parsing | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=463 -new-name=Bar %s -- -fno-delayed-template-parsing | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/TemplateTypename.cpp =================================================================== --- cfe/trunk/test/clang-rename/TemplateTypename.cpp +++ cfe/trunk/test/clang-rename/TemplateTypename.cpp @@ -0,0 +1,24 @@ +template // CHECK: template +class Foo { +T foo(T arg, T& ref, T* /* Test 2 */ ptr) { // CHECK: U foo(U arg, U& ref, U* /* Test 2 */ ptr) { + T value; // CHECK: U value; + int number = 42; + value = (T)number; // CHECK: value = (U)number; + value = static_cast(number); // CHECK: value = static_cast(number); + return value; +} + +static void foo(T value) {} // CHECK: static void foo(U value) {} + +T member; // CHECK: U member; +}; + +// Test 1. +// RUN: clang-rename -offset=19 -new-name=U %s -- -fno-delayed-template-parsing | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=126 -new-name=U %s -- -fno-delayed-template-parsing | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=392 -new-name=U %s -- -fno-delayed-template-parsing | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'T.*' Index: cfe/trunk/test/clang-rename/TemplatedClassFunction.cpp =================================================================== --- cfe/trunk/test/clang-rename/TemplatedClassFunction.cpp +++ cfe/trunk/test/clang-rename/TemplatedClassFunction.cpp @@ -0,0 +1,22 @@ +template +class A { +public: + void foo() /* Test 1 */ {} // CHECK: void bar() /* Test 1 */ {} +}; + +int main(int argc, char **argv) { + A a; + a.foo(); /* Test 2 */ // CHECK: a.bar() /* Test 2 */ + return 0; +} + +// Test 1. +// RUN: clang-refactor rename -offset=48 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-refactor rename -offset=162 -new-name=bar %s -- | sed 's,//.*,,' | FileCheck %s +// +// Currently unsupported test. +// XFAIL: * + +// To find offsets after modifying the file, use: +// grep -Ubo 'foo.*' Index: cfe/trunk/test/clang-rename/UserDefinedConversion.cpp =================================================================== --- cfe/trunk/test/clang-rename/UserDefinedConversion.cpp +++ cfe/trunk/test/clang-rename/UserDefinedConversion.cpp @@ -0,0 +1,26 @@ +class Foo { /* Test 1 */ // CHECK: class Bar { +public: + Foo() {} // CHECK: Bar() {} +}; + +class Baz { +public: + operator Foo() /* Test 2 */ const { // CHECK: operator Bar() /* Test 2 */ const { + Foo foo; // CHECK: Bar foo; + return foo; + } +}; + +int main() { + Baz boo; + Foo foo = static_cast(boo); // CHECK: Bar foo = static_cast(boo); + return 0; +} + +// Test 1. +// RUN: clang-rename -offset=7 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=164 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/Variable.cpp =================================================================== --- cfe/trunk/test/clang-rename/Variable.cpp +++ cfe/trunk/test/clang-rename/Variable.cpp @@ -0,0 +1,33 @@ +#define NAMESPACE namespace A +NAMESPACE { +int Foo; /* Test 1 */ // CHECK: int Bar; +} +int Foo; // CHECK: int Foo; +int Qux = Foo; // CHECK: int Qux = Foo; +int Baz = A::Foo; /* Test 2 */ // CHECK: Baz = A::Bar; +void fun() { + struct { + int Foo; // CHECK: int Foo; + } b = {100}; + int Foo = 100; // CHECK: int Foo = 100; + Baz = Foo; // CHECK: Baz = Foo; + { + extern int Foo; // CHECK: extern int Foo; + Baz = Foo; // CHECK: Baz = Foo; + Foo = A::Foo /* Test 3 */ + Baz; // CHECK: Foo = A::Bar /* Test 3 */ + Baz; + A::Foo /* Test 4 */ = b.Foo; // CHECK: A::Bar /* Test 4 */ = b.Foo; + } + Foo = b.Foo; // Foo = b.Foo; +} + +// Test 1. +// RUN: clang-rename -offset=46 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=234 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=641 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 4. +// RUN: clang-rename -offset=716 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/VariableMacro.cpp =================================================================== --- cfe/trunk/test/clang-rename/VariableMacro.cpp +++ cfe/trunk/test/clang-rename/VariableMacro.cpp @@ -0,0 +1,21 @@ +#define Baz Foo // CHECK: #define Baz Bar + +void foo(int value) {} + +void macro() { + int Foo; /* Test 1 */ // CHECK: int Bar; + Foo = 42; /* Test 2 */ // CHECK: Bar = 42; + Baz -= 0; + foo(Foo); /* Test 3 */ // CHECK: foo(Bar); + foo(Baz); +} + +// Test 1. +// RUN: clang-rename -offset=88 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -offset=129 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s +// Test 3. +// RUN: clang-rename -offset=191 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s + +// To find offsets after modifying the file, use: +// grep -Ubo 'Foo.*' Index: cfe/trunk/test/clang-rename/YAMLInput.cpp =================================================================== --- cfe/trunk/test/clang-rename/YAMLInput.cpp +++ cfe/trunk/test/clang-rename/YAMLInput.cpp @@ -0,0 +1,10 @@ +class Foo1 { // CHECK: class Bar1 +}; + +class Foo2 { // CHECK: class Bar2 +}; + +// Test 1. +// RUN: clang-rename -input %S/Inputs/OffsetToNewName.yaml %s -- | sed 's,//.*,,' | FileCheck %s +// Test 2. +// RUN: clang-rename -input %S/Inputs/QualifiedNameToNewName.yaml %s -- | sed 's,//.*,,' | FileCheck %s Index: cfe/trunk/tools/CMakeLists.txt =================================================================== --- cfe/trunk/tools/CMakeLists.txt +++ cfe/trunk/tools/CMakeLists.txt @@ -10,6 +10,8 @@ add_clang_subdirectory(c-index-test) +add_clang_subdirectory(clang-rename) + if(CLANG_ENABLE_ARCMT) add_clang_subdirectory(arcmt-test) add_clang_subdirectory(c-arcmt-test) Index: cfe/trunk/tools/clang-rename/CMakeLists.txt =================================================================== --- cfe/trunk/tools/clang-rename/CMakeLists.txt +++ cfe/trunk/tools/clang-rename/CMakeLists.txt @@ -0,0 +1,19 @@ +add_clang_executable(clang-rename ClangRename.cpp) + +target_link_libraries(clang-rename + clangBasic + clangFrontend + clangRewrite + clangTooling + clangToolingCore + clangToolingRefactor + ) + +install(TARGETS clang-rename RUNTIME DESTINATION bin) + +install(PROGRAMS clang-rename.py + DESTINATION share/clang + COMPONENT clang-rename) +install(PROGRAMS clang-rename.el + DESTINATION share/clang + COMPONENT clang-rename) Index: cfe/trunk/tools/clang-rename/ClangRename.cpp =================================================================== --- cfe/trunk/tools/clang-rename/ClangRename.cpp +++ cfe/trunk/tools/clang-rename/ClangRename.cpp @@ -0,0 +1,240 @@ +//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file implements a clang-rename tool that automatically finds and +/// renames symbols in C++ code. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" +#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +using namespace llvm; +using namespace clang; + +/// \brief An oldname -> newname rename. +struct RenameAllInfo { + unsigned Offset = 0; + std::string QualifiedName; + std::string NewName; +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo) + +namespace llvm { +namespace yaml { + +/// \brief Specialized MappingTraits to describe how a RenameAllInfo is +/// (de)serialized. +template <> struct MappingTraits { + static void mapping(IO &IO, RenameAllInfo &Info) { + IO.mapOptional("Offset", Info.Offset); + IO.mapOptional("QualifiedName", Info.QualifiedName); + IO.mapRequired("NewName", Info.NewName); + } +}; + +} // end namespace yaml +} // end namespace llvm + +static cl::OptionCategory ClangRenameOptions("clang-rename common options"); + +static cl::list SymbolOffsets( + "offset", + cl::desc("Locates the symbol by offset as opposed to :."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); +static cl::opt Inplace("i", cl::desc("Overwrite edited s."), + cl::cat(ClangRenameOptions)); +static cl::list + QualifiedNames("qualified-name", + cl::desc("The fully qualified name of the symbol."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); + +static cl::list + NewNames("new-name", cl::desc("The new name to change the symbol to."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); +static cl::opt PrintName( + "pn", + cl::desc("Print the found symbol's name prior to renaming to stderr."), + cl::cat(ClangRenameOptions)); +static cl::opt PrintLocations( + "pl", cl::desc("Print the locations affected by renaming to stderr."), + cl::cat(ClangRenameOptions)); +static cl::opt + ExportFixes("export-fixes", + cl::desc("YAML file to store suggested fixes in."), + cl::value_desc("filename"), cl::cat(ClangRenameOptions)); +static cl::opt + Input("input", cl::desc("YAML file to load oldname-newname pairs from."), + cl::Optional, cl::cat(ClangRenameOptions)); +static cl::opt Force("force", + cl::desc("Ignore nonexistent qualified names."), + cl::cat(ClangRenameOptions)); + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser OP(argc, argv, ClangRenameOptions); + + if (!Input.empty()) { + // Populate QualifiedNames and NewNames from a YAML file. + ErrorOr> Buffer = + llvm::MemoryBuffer::getFile(Input); + if (!Buffer) { + errs() << "clang-rename: failed to read " << Input << ": " + << Buffer.getError().message() << "\n"; + return 1; + } + + std::vector Infos; + llvm::yaml::Input YAML(Buffer.get()->getBuffer()); + YAML >> Infos; + for (const auto &Info : Infos) { + if (!Info.QualifiedName.empty()) + QualifiedNames.push_back(Info.QualifiedName); + else + SymbolOffsets.push_back(Info.Offset); + NewNames.push_back(Info.NewName); + } + } + + // Check the arguments for correctness. + if (NewNames.empty()) { + errs() << "clang-rename: -new-name must be specified.\n\n"; + exit(1); + } + + if (SymbolOffsets.empty() == QualifiedNames.empty()) { + errs() << "clang-rename: -offset and -qualified-name can't be present at " + "the same time.\n"; + exit(1); + } + + // Check if NewNames is a valid identifier in C++17. + LangOptions Options; + Options.CPlusPlus = true; + Options.CPlusPlus1z = true; + IdentifierTable Table(Options); + for (const auto &NewName : NewNames) { + auto NewNameTokKind = Table.get(NewName).getTokenID(); + if (!tok::isAnyIdentifier(NewNameTokKind)) { + errs() << "ERROR: new name is not a valid identifier in C++17.\n\n"; + exit(1); + } + } + + if (SymbolOffsets.size() + QualifiedNames.size() != NewNames.size()) { + errs() << "clang-rename: number of symbol offsets(" << SymbolOffsets.size() + << ") + number of qualified names (" << QualifiedNames.size() + << ") must be equal to number of new names(" << NewNames.size() + << ").\n\n"; + cl::PrintHelpMessage(); + exit(1); + } + + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + tooling::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames, Force); + Tool.run(tooling::newFrontendActionFactory(&FindingAction).get()); + const std::vector> &USRList = + FindingAction.getUSRList(); + const std::vector &PrevNames = FindingAction.getUSRSpellings(); + if (PrintName) { + for (const auto &PrevName : PrevNames) { + outs() << "clang-rename found name: " << PrevName << '\n'; + } + } + + if (FindingAction.errorOccurred()) { + // Diagnostics are already issued at this point. + exit(1); + } + + if (Force && PrevNames.size() < NewNames.size()) { + // No matching PrevName for all NewNames. Without Force this is an error + // above already. + exit(0); + } + + // Perform the renaming. + tooling::RenamingAction RenameAction(NewNames, PrevNames, USRList, + Tool.getReplacements(), PrintLocations); + std::unique_ptr Factory = + tooling::newFrontendActionFactory(&RenameAction); + int ExitCode; + + if (Inplace) { + ExitCode = Tool.runAndSave(Factory.get()); + } else { + ExitCode = Tool.run(Factory.get()); + + if (!ExportFixes.empty()) { + std::error_code EC; + llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Error opening output file: " << EC.message() << '\n'; + exit(1); + } + + // Export replacements. + tooling::TranslationUnitReplacements TUR; + const auto &FileToReplacements = Tool.getReplacements(); + for (const auto &Entry : FileToReplacements) + TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(), + Entry.second.end()); + + yaml::Output YAML(OS); + YAML << TUR; + OS.close(); + exit(0); + } + + // Write every file to stdout. Right now we just barf the files without any + // indication of which files start where, other than that we print the files + // in the same order we see them. + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + auto &FileMgr = Tool.getFiles(); + SourceManager Sources(Diagnostics, FileMgr); + Rewriter Rewrite(Sources, DefaultLangOptions); + + Tool.applyAllReplacements(Rewrite); + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + const auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User); + Rewrite.getEditBuffer(ID).write(outs()); + } + } + + exit(ExitCode); +} Index: cfe/trunk/tools/clang-rename/clang-rename.el =================================================================== --- cfe/trunk/tools/clang-rename/clang-rename.el +++ cfe/trunk/tools/clang-rename/clang-rename.el @@ -0,0 +1,79 @@ +;;; clang-rename.el --- Renames every occurrence of a symbol found at . -*- lexical-binding: t; -*- + +;; Keywords: tools, c + +;;; Commentary: + +;; To install clang-rename.el make sure the directory of this file is in your +;; `load-path' and add +;; +;; (require 'clang-rename) +;; +;; to your .emacs configuration. + +;;; Code: + +(defgroup clang-rename nil + "Integration with clang-rename" + :group 'c) + +(defcustom clang-rename-binary "clang-rename" + "Path to clang-rename executable." + :type '(file :must-match t) + :group 'clang-rename) + +;;;###autoload +(defun clang-rename (new-name) + "Rename all instances of the symbol at point to NEW-NAME using clang-rename." + (interactive "sEnter a new name: ") + (save-some-buffers :all) + ;; clang-rename should not be combined with other operations when undoing. + (undo-boundary) + (let ((output-buffer (get-buffer-create "*clang-rename*"))) + (with-current-buffer output-buffer (erase-buffer)) + (let ((exit-code (call-process + clang-rename-binary nil output-buffer nil + (format "-offset=%d" + ;; clang-rename wants file (byte) offsets, not + ;; buffer (character) positions. + (clang-rename--bufferpos-to-filepos + ;; Emacs treats one character after a symbol as + ;; part of the symbol, but clang-rename doesn’t. + ;; Use the beginning of the current symbol, if + ;; available, to resolve the inconsistency. + (or (car (bounds-of-thing-at-point 'symbol)) + (point)) + 'exact)) + (format "-new-name=%s" new-name) + "-i" (buffer-file-name)))) + (if (and (integerp exit-code) (zerop exit-code)) + ;; Success; revert current buffer so it gets the modifications. + (progn + (kill-buffer output-buffer) + (revert-buffer :ignore-auto :noconfirm :preserve-modes)) + ;; Failure; append exit code to output buffer and display it. + (let ((message (clang-rename--format-message + "clang-rename failed with %s %s" + (if (integerp exit-code) "exit status" "signal") + exit-code))) + (with-current-buffer output-buffer + (insert ?\n message ?\n)) + (message "%s" message) + (display-buffer output-buffer)))))) + +(defalias 'clang-rename--bufferpos-to-filepos + (if (fboundp 'bufferpos-to-filepos) + 'bufferpos-to-filepos + ;; Emacs 24 doesn’t have ‘bufferpos-to-filepos’, simulate it using + ;; ‘position-bytes’. + (lambda (position &optional _quality _coding-system) + (1- (position-bytes position))))) + +;; ‘format-message’ is new in Emacs 25.1. Provide a fallback for older +;; versions. +(defalias 'clang-rename--format-message + (if (fboundp 'format-message) 'format-message 'format)) + +(provide 'clang-rename) + +;;; clang-rename.el ends here Index: cfe/trunk/tools/clang-rename/clang-rename.py =================================================================== --- cfe/trunk/tools/clang-rename/clang-rename.py +++ cfe/trunk/tools/clang-rename/clang-rename.py @@ -0,0 +1,61 @@ +''' +Minimal clang-rename integration with Vim. + +Before installing make sure one of the following is satisfied: + +* clang-rename is in your PATH +* `g:clang_rename_path` in ~/.vimrc points to valid clang-rename executable +* `binary` in clang-rename.py points to valid to clang-rename executable + +To install, simply put this into your ~/.vimrc + + noremap cr :pyf /clang-rename.py + +IMPORTANT NOTE: Before running the tool, make sure you saved the file. + +All you have to do now is to place a cursor on a variable/function/class which +you would like to rename and press 'cr'. You will be prompted for a new +name if the cursor points to a valid symbol. +''' + +import vim +import subprocess +import sys + +def main(): + binary = 'clang-rename' + if vim.eval('exists("g:clang_rename_path")') == "1": + binary = vim.eval('g:clang_rename_path') + + # Get arguments for clang-rename binary. + offset = int(vim.eval('line2byte(line("."))+col(".")')) - 2 + if offset < 0: + print >> sys.stderr, '''Couldn\'t determine cursor position. + Is your file empty?''' + return + filename = vim.current.buffer.name + + new_name_request_message = 'type new name:' + new_name = vim.eval("input('{}\n')".format(new_name_request_message)) + + # Call clang-rename. + command = [binary, + filename, + '-i', + '-offset', str(offset), + '-new-name', str(new_name)] + # FIXME: make it possible to run the tool on unsaved file. + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + + if stderr: + print stderr + + # Reload all buffers in Vim. + vim.command("checktime") + + +if __name__ == '__main__': + main() Index: cfe/trunk/unittests/CMakeLists.txt =================================================================== --- cfe/trunk/unittests/CMakeLists.txt +++ cfe/trunk/unittests/CMakeLists.txt @@ -29,3 +29,4 @@ if(NOT WIN32 AND CLANG_TOOL_LIBCLANG_BUILD) add_subdirectory(libclang) endif() +add_subdirectory(Rename) Index: cfe/trunk/unittests/Rename/CMakeLists.txt =================================================================== --- cfe/trunk/unittests/Rename/CMakeLists.txt +++ cfe/trunk/unittests/Rename/CMakeLists.txt @@ -0,0 +1,22 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +# We'd like clang/unittests/Tooling/RewriterTestContext.h in the test. +include_directories(${CLANG_SOURCE_DIR}) + +add_extra_unittest(ClangRenameTests + RenameClassTest.cpp + ) + +target_link_libraries(ClangRenameTests + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangRewrite + clangTooling + clangToolingCore + clangToolingRefactor + ) Index: cfe/trunk/unittests/Rename/ClangRenameTest.h =================================================================== --- cfe/trunk/unittests/Rename/ClangRenameTest.h +++ cfe/trunk/unittests/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 "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/Refactoring/Rename/RenamingAction.h" +#include "clang/Tooling/Refactoring/Rename/USRFindingAction.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); + + tooling::USRFindingAction FindingAction({}, {OldName}, false); + 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; + tooling::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: cfe/trunk/unittests/Rename/RenameClassTest.cpp =================================================================== --- cfe/trunk/unittests/Rename/RenameClassTest.cpp +++ cfe/trunk/unittests/Rename/RenameClassTest.cpp @@ -0,0 +1,684 @@ +//===-- 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 Index: clang-tools-extra/trunk/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/CMakeLists.txt +++ clang-tools-extra/trunk/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(clang-apply-replacements) -add_subdirectory(clang-rename) add_subdirectory(clang-reorder-fields) add_subdirectory(modularize) if(CLANG_ENABLE_STATIC_ANALYZER) Index: clang-tools-extra/trunk/test/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/test/CMakeLists.txt +++ clang-tools-extra/trunk/test/CMakeLists.txt @@ -44,7 +44,6 @@ clang-include-fixer clang-move clang-query - clang-rename clang-reorder-fields clang-tidy find-all-symbols Index: clang-tools-extra/trunk/unittests/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/unittests/CMakeLists.txt +++ clang-tools-extra/trunk/unittests/CMakeLists.txt @@ -10,6 +10,5 @@ add_subdirectory(clang-move) add_subdirectory(clang-query) add_subdirectory(clang-tidy) -add_subdirectory(clang-rename) add_subdirectory(clangd) add_subdirectory(include-fixer)