Index: include/clang/Tooling/Refactoring/Rename/RenamingAction.h =================================================================== --- /dev/null +++ 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: include/clang/Tooling/Refactoring/Rename/USRFinder.h =================================================================== --- /dev/null +++ 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: include/clang/Tooling/Refactoring/Rename/USRFindingAction.h =================================================================== --- /dev/null +++ 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: include/clang/Tooling/Refactoring/Rename/USRLocFinder.h =================================================================== --- /dev/null +++ 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: include/clang/module.modulemap =================================================================== --- include/clang/module.modulemap +++ 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: lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- lib/Tooling/Refactoring/CMakeLists.txt +++ 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: lib/Tooling/Refactoring/Rename/RenamingAction.cpp =================================================================== --- /dev/null +++ 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: lib/Tooling/Refactoring/Rename/USRFinder.cpp =================================================================== --- /dev/null +++ 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: lib/Tooling/Refactoring/Rename/USRFindingAction.cpp =================================================================== --- /dev/null +++ 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: lib/Tooling/Refactoring/Rename/USRLocFinder.cpp =================================================================== --- /dev/null +++ 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: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -47,6 +47,7 @@ clang-tblgen clang-offload-bundler clang-import-test + clang-rename ) if(CLANG_ENABLE_STATIC_ANALYZER) Index: test/clang-rename/ClassAsTemplateArgument.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/ClassFindByName.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/ClassReplacements.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/ClassSimpleRenaming.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/ClassTestMulti.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/ClassTestMultiByName.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/ComplexFunctionOverride.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/ComplicatedClassType.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/Ctor.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/CtorInitializer.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/DeclRefExpr.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/Field.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/FunctionMacro.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/FunctionOverride.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/FunctionWithClassFindByName.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/IncludeHeaderWithSymbol.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/Inputs/HeaderWithSymbol.h =================================================================== --- /dev/null +++ test/clang-rename/Inputs/HeaderWithSymbol.h @@ -0,0 +1 @@ +struct Foo {}; Index: test/clang-rename/Inputs/OffsetToNewName.yaml =================================================================== --- /dev/null +++ test/clang-rename/Inputs/OffsetToNewName.yaml @@ -0,0 +1,6 @@ +--- +- Offset: 6 + NewName: Bar1 +- Offset: 44 + NewName: Bar2 +... Index: test/clang-rename/Inputs/QualifiedNameToNewName.yaml =================================================================== --- /dev/null +++ test/clang-rename/Inputs/QualifiedNameToNewName.yaml @@ -0,0 +1,6 @@ +--- +- QualifiedName: Foo1 + NewName: Bar1 +- QualifiedName: Foo2 + NewName: Bar2 +... Index: test/clang-rename/InvalidNewName.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/InvalidOffset.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/InvalidQualifiedName.cpp =================================================================== --- /dev/null +++ test/clang-rename/InvalidQualifiedName.cpp @@ -0,0 +1,4 @@ +struct S { +}; + +// RUN: clang-rename -force -qualified-name S2 -new-name=T %s -- Index: test/clang-rename/MemberExprMacro.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/Namespace.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/NoNewName.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/TemplateClassInstantiation.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/TemplateTypename.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/TemplatedClassFunction.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/UserDefinedConversion.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/Variable.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/VariableMacro.cpp =================================================================== --- /dev/null +++ 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: test/clang-rename/YAMLInput.cpp =================================================================== --- /dev/null +++ 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: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ 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: tools/clang-rename/CMakeLists.txt =================================================================== --- /dev/null +++ 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: tools/clang-rename/ClangRename.cpp =================================================================== --- /dev/null +++ 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: tools/clang-rename/clang-rename.el =================================================================== --- /dev/null +++ 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: tools/clang-rename/clang-rename.py =================================================================== --- /dev/null +++ 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: tools/extra/CMakeLists.txt =================================================================== --- tools/extra/CMakeLists.txt +++ tools/extra/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: tools/extra/clang-rename/CMakeLists.txt =================================================================== --- tools/extra/clang-rename/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -set(LLVM_LINK_COMPONENTS support) - -add_clang_library(clangRename - USRFinder.cpp - USRFindingAction.cpp - USRLocFinder.cpp - RenamingAction.cpp - - LINK_LIBS - clangAST - clangASTMatchers - clangBasic - clangIndex - clangLex - clangToolingCore - clangToolingRefactor - ) - -add_subdirectory(tool) Index: tools/extra/clang-rename/RenamingAction.h =================================================================== --- tools/extra/clang-rename/RenamingAction.h +++ /dev/null @@ -1,70 +0,0 @@ -//===--- tools/extra/clang-rename/RenamingAction.h - 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 Provides an action to rename every symbol at a point. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H - -#include "clang/Tooling/Refactoring.h" - -namespace clang { -class ASTConsumer; -class CompilerInstance; - -namespace rename { - -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; -}; - -} // namespace rename -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H Index: tools/extra/clang-rename/RenamingAction.cpp =================================================================== --- tools/extra/clang-rename/RenamingAction.cpp +++ /dev/null @@ -1,134 +0,0 @@ -//===--- tools/extra/clang-rename/RenamingAction.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 Provides an action to rename every symbol at a point. -/// -//===----------------------------------------------------------------------===// - -#include "RenamingAction.h" -#include "USRLocFinder.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/Tooling.h" -#include -#include - -using namespace llvm; - -namespace clang { -namespace rename { - -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 = - 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 = createRenameAtomicChanges( - USRList[I], NewNames[I], Context.getTranslationUnitDecl()); - for (const auto AtomicChange : AtomicChanges) { - for (const auto &Replace : AtomicChange.getReplacements()) { - llvm::Error Err = FileToReplaces[Replace.getFilePath()].add(Replace); - if (Err) { - llvm::errs() << "Renaming failed in " << Replace.getFilePath() - << "! " << llvm::toString(std::move(Err)) << "\n"; - } - } - } - } - } - -private: - const std::vector &NewNames; - const std::vector> &USRList; - std::map &FileToReplaces; -}; - -std::unique_ptr RenamingAction::newASTConsumer() { - return llvm::make_unique(NewNames, PrevNames, USRList, - FileToReplaces, PrintLocations); -} - -std::unique_ptr QualifiedRenamingAction::newASTConsumer() { - return llvm::make_unique(NewNames, USRList, FileToReplaces); -} - -} // namespace rename -} // namespace clang Index: tools/extra/clang-rename/USRFinder.h =================================================================== --- tools/extra/clang-rename/USRFinder.h +++ /dev/null @@ -1,84 +0,0 @@ -//===--- tools/extra/clang-rename/USRFinder.h - 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 Methods for determining the USR of a symbol at a location in source -/// code. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_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 rename { - -// 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; -}; - -} // namespace rename -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H Index: tools/extra/clang-rename/USRFinder.cpp =================================================================== --- tools/extra/clang-rename/USRFinder.cpp +++ /dev/null @@ -1,213 +0,0 @@ -//===--- tools/extra/clang-rename/USRFinder.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 Implements a recursive AST visitor that finds the USR of a symbol at a -/// point. -/// -//===----------------------------------------------------------------------===// - -#include "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 rename { - -// 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()); -} - -} // namespace rename -} // namespace clang Index: tools/extra/clang-rename/USRFindingAction.h =================================================================== --- tools/extra/clang-rename/USRFindingAction.h +++ /dev/null @@ -1,54 +0,0 @@ -//===--- tools/extra/clang-rename/USRFindingAction.h - 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 Provides an action to find all relevant USRs at a point. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_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 rename { - -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; -}; - -} // namespace rename -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H Index: tools/extra/clang-rename/USRFindingAction.cpp =================================================================== --- tools/extra/clang-rename/USRFindingAction.cpp +++ /dev/null @@ -1,236 +0,0 @@ -//===--- tools/extra/clang-rename/USRFindingAction.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 Provides an action to find USR for the symbol at , as well as -/// all additional USRs. -/// -//===----------------------------------------------------------------------===// - -#include "USRFindingAction.h" -#include "USRFinder.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/Tooling.h" - -#include -#include -#include -#include - -using namespace llvm; - -namespace clang { -namespace rename { - -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); -} - -} // namespace rename -} // namespace clang Index: tools/extra/clang-rename/USRLocFinder.h =================================================================== --- tools/extra/clang-rename/USRLocFinder.h +++ /dev/null @@ -1,49 +0,0 @@ -//===--- tools/extra/clang-rename/USRLocFinder.h - 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 Provides functionality for finding all instances of a USR in a given -/// AST. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_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 rename { - -/// Create atomic changes for renaming all symbol references which are -/// identified by the USRs set to a given new name. -/// -/// \param USRs The set containing USRs of a particular old symbol. -/// \param NewName The new name to replace old symbol name. -/// \param TranslationUnitDecl The translation unit declaration. -/// -/// \return Atomic changes for renaming. -std::vector -createRenameAtomicChanges(llvm::ArrayRef USRs, - llvm::StringRef NewName, Decl *TranslationUnitDecl); - -// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! -std::vector -getLocationsOfUSRs(const std::vector &USRs, - llvm::StringRef PrevName, Decl *Decl); - -} // namespace rename -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H Index: tools/extra/clang-rename/USRLocFinder.cpp =================================================================== --- tools/extra/clang-rename/USRLocFinder.cpp +++ /dev/null @@ -1,509 +0,0 @@ -//===--- tools/extra/clang-rename/USRLocFinder.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 Mehtods 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 "USRLocFinder.h" -#include "USRFinder.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 "llvm/ADT/StringRef.h" -#include "llvm/Support/Casting.h" -#include -#include -#include -#include - -using namespace llvm; - -namespace clang { -namespace rename { - -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; -} - -} // namespace rename -} // namespace clang Index: tools/extra/clang-rename/tool/CMakeLists.txt =================================================================== --- tools/extra/clang-rename/tool/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -add_clang_executable(clang-rename ClangRename.cpp) - -target_link_libraries(clang-rename - clangBasic - clangFrontend - clangRename - clangRewrite - clangTooling - clangToolingCore - ) - -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: tools/extra/clang-rename/tool/ClangRename.cpp =================================================================== --- tools/extra/clang-rename/tool/ClangRename.cpp +++ /dev/null @@ -1,240 +0,0 @@ -//===--- 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 "../RenamingAction.h" -#include "../USRFindingAction.h" -#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/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); - rename::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. - rename::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: tools/extra/clang-rename/tool/clang-rename.el =================================================================== --- tools/extra/clang-rename/tool/clang-rename.el +++ /dev/null @@ -1,79 +0,0 @@ -;;; 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: tools/extra/clang-rename/tool/clang-rename.py =================================================================== --- tools/extra/clang-rename/tool/clang-rename.py +++ /dev/null @@ -1,61 +0,0 @@ -''' -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: tools/extra/test/CMakeLists.txt =================================================================== --- tools/extra/test/CMakeLists.txt +++ tools/extra/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: tools/extra/test/clang-rename/ClassAsTemplateArgument.cpp =================================================================== --- tools/extra/test/clang-rename/ClassAsTemplateArgument.cpp +++ /dev/null @@ -1,21 +0,0 @@ -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: tools/extra/test/clang-rename/ClassFindByName.cpp =================================================================== --- tools/extra/test/clang-rename/ClassFindByName.cpp +++ /dev/null @@ -1,10 +0,0 @@ -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: tools/extra/test/clang-rename/ClassReplacements.cpp =================================================================== --- tools/extra/test/clang-rename/ClassReplacements.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// 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: tools/extra/test/clang-rename/ClassSimpleRenaming.cpp =================================================================== --- tools/extra/test/clang-rename/ClassSimpleRenaming.cpp +++ /dev/null @@ -1,14 +0,0 @@ -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: tools/extra/test/clang-rename/ClassTestMulti.cpp =================================================================== --- tools/extra/test/clang-rename/ClassTestMulti.cpp +++ /dev/null @@ -1,11 +0,0 @@ -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: tools/extra/test/clang-rename/ClassTestMultiByName.cpp =================================================================== --- tools/extra/test/clang-rename/ClassTestMultiByName.cpp +++ /dev/null @@ -1,8 +0,0 @@ -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: tools/extra/test/clang-rename/ComplexFunctionOverride.cpp =================================================================== --- tools/extra/test/clang-rename/ComplexFunctionOverride.cpp +++ /dev/null @@ -1,47 +0,0 @@ -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: tools/extra/test/clang-rename/ComplicatedClassType.cpp =================================================================== --- tools/extra/test/clang-rename/ComplicatedClassType.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// 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: tools/extra/test/clang-rename/Ctor.cpp =================================================================== --- tools/extra/test/clang-rename/Ctor.cpp +++ /dev/null @@ -1,14 +0,0 @@ -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: tools/extra/test/clang-rename/CtorInitializer.cpp =================================================================== --- tools/extra/test/clang-rename/CtorInitializer.cpp +++ /dev/null @@ -1,17 +0,0 @@ -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: tools/extra/test/clang-rename/DeclRefExpr.cpp =================================================================== --- tools/extra/test/clang-rename/DeclRefExpr.cpp +++ /dev/null @@ -1,24 +0,0 @@ -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: tools/extra/test/clang-rename/Field.cpp =================================================================== --- tools/extra/test/clang-rename/Field.cpp +++ /dev/null @@ -1,15 +0,0 @@ -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: tools/extra/test/clang-rename/FunctionMacro.cpp =================================================================== --- tools/extra/test/clang-rename/FunctionMacro.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#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: tools/extra/test/clang-rename/FunctionOverride.cpp =================================================================== --- tools/extra/test/clang-rename/FunctionOverride.cpp +++ /dev/null @@ -1,13 +0,0 @@ -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: tools/extra/test/clang-rename/FunctionWithClassFindByName.cpp =================================================================== --- tools/extra/test/clang-rename/FunctionWithClassFindByName.cpp +++ /dev/null @@ -1,12 +0,0 @@ -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: tools/extra/test/clang-rename/IncludeHeaderWithSymbol.cpp =================================================================== --- tools/extra/test/clang-rename/IncludeHeaderWithSymbol.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#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: tools/extra/test/clang-rename/Inputs/HeaderWithSymbol.h =================================================================== --- tools/extra/test/clang-rename/Inputs/HeaderWithSymbol.h +++ /dev/null @@ -1 +0,0 @@ -struct Foo {}; Index: tools/extra/test/clang-rename/Inputs/OffsetToNewName.yaml =================================================================== --- tools/extra/test/clang-rename/Inputs/OffsetToNewName.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- Offset: 6 - NewName: Bar1 -- Offset: 44 - NewName: Bar2 -... Index: tools/extra/test/clang-rename/Inputs/QualifiedNameToNewName.yaml =================================================================== --- tools/extra/test/clang-rename/Inputs/QualifiedNameToNewName.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- QualifiedName: Foo1 - NewName: Bar1 -- QualifiedName: Foo2 - NewName: Bar2 -... Index: tools/extra/test/clang-rename/InvalidNewName.cpp =================================================================== --- tools/extra/test/clang-rename/InvalidNewName.cpp +++ /dev/null @@ -1,2 +0,0 @@ -// 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: tools/extra/test/clang-rename/InvalidOffset.cpp =================================================================== --- tools/extra/test/clang-rename/InvalidOffset.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#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: tools/extra/test/clang-rename/InvalidQualifiedName.cpp =================================================================== --- tools/extra/test/clang-rename/InvalidQualifiedName.cpp +++ /dev/null @@ -1,4 +0,0 @@ -struct S { -}; - -// RUN: clang-rename -force -qualified-name S2 -new-name=T %s -- Index: tools/extra/test/clang-rename/MemberExprMacro.cpp =================================================================== --- tools/extra/test/clang-rename/MemberExprMacro.cpp +++ /dev/null @@ -1,22 +0,0 @@ -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: tools/extra/test/clang-rename/Namespace.cpp =================================================================== --- tools/extra/test/clang-rename/Namespace.cpp +++ /dev/null @@ -1,13 +0,0 @@ -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: tools/extra/test/clang-rename/NoNewName.cpp =================================================================== --- tools/extra/test/clang-rename/NoNewName.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// 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: tools/extra/test/clang-rename/TemplateClassInstantiation.cpp =================================================================== --- tools/extra/test/clang-rename/TemplateClassInstantiation.cpp +++ /dev/null @@ -1,42 +0,0 @@ -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: tools/extra/test/clang-rename/TemplateTypename.cpp =================================================================== --- tools/extra/test/clang-rename/TemplateTypename.cpp +++ /dev/null @@ -1,24 +0,0 @@ -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: tools/extra/test/clang-rename/TemplatedClassFunction.cpp =================================================================== --- tools/extra/test/clang-rename/TemplatedClassFunction.cpp +++ /dev/null @@ -1,22 +0,0 @@ -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: tools/extra/test/clang-rename/UserDefinedConversion.cpp =================================================================== --- tools/extra/test/clang-rename/UserDefinedConversion.cpp +++ /dev/null @@ -1,26 +0,0 @@ -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: tools/extra/test/clang-rename/Variable.cpp =================================================================== --- tools/extra/test/clang-rename/Variable.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#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: tools/extra/test/clang-rename/VariableMacro.cpp =================================================================== --- tools/extra/test/clang-rename/VariableMacro.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#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: tools/extra/test/clang-rename/YAMLInput.cpp =================================================================== --- tools/extra/test/clang-rename/YAMLInput.cpp +++ /dev/null @@ -1,10 +0,0 @@ -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: tools/extra/unittests/CMakeLists.txt =================================================================== --- tools/extra/unittests/CMakeLists.txt +++ tools/extra/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) Index: tools/extra/unittests/clang-rename/CMakeLists.txt =================================================================== --- tools/extra/unittests/clang-rename/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -set(LLVM_LINK_COMPONENTS - support - ) - -get_filename_component(CLANG_RENAME_SOURCE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-rename REALPATH) -include_directories( - ${CLANG_RENAME_SOURCE_DIR} - ) - -# 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 - clangRename - clangRewrite - clangTooling - clangToolingCore - ) Index: tools/extra/unittests/clang-rename/ClangRenameTest.h =================================================================== --- tools/extra/unittests/clang-rename/ClangRenameTest.h +++ /dev/null @@ -1,112 +0,0 @@ -//===-- ClangRenameTests.cpp - clang-rename unit tests --------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "RenamingAction.h" -#include "USRFindingAction.h" -#include "unittests/Tooling/RewriterTestContext.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/FileSystemOptions.h" -#include "clang/Basic/VirtualFileSystem.h" -#include "clang/Format/Format.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/PCHContainerOperations.h" -#include "clang/Tooling/Refactoring.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/IntrusiveRefCntPtr.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/MemoryBuffer.h" -#include "gtest/gtest.h" -#include -#include -#include - -namespace clang { -namespace clang_rename { -namespace test { - -struct Case { - std::string Before; - std::string After; - std::string OldName; - std::string NewName; -}; - -class ClangRenameTest : public testing::Test, - public testing::WithParamInterface { -protected: - void AppendToHeader(StringRef Code) { HeaderContent += Code.str(); } - - std::string runClangRenameOnCode(llvm::StringRef Code, - llvm::StringRef OldName, - llvm::StringRef NewName) { - std::string NewCode; - llvm::raw_string_ostream(NewCode) << llvm::format( - "#include \"%s\"\n%s", HeaderName.c_str(), Code.str().c_str()); - tooling::FileContentMappings FileContents = {{HeaderName, HeaderContent}, - {CCName, NewCode}}; - clang::RewriterTestContext Context; - Context.createInMemoryFile(HeaderName, HeaderContent); - clang::FileID InputFileID = Context.createInMemoryFile(CCName, NewCode); - - rename::USRFindingAction FindingAction({}, {OldName}, 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; - rename::QualifiedRenamingAction RenameAction(NewNames, USRList, - FileToReplacements); - auto RenameActionFactory = tooling::newFrontendActionFactory(&RenameAction); - if (!tooling::runToolOnCodeWithArgs( - RenameActionFactory->create(), NewCode, {"-std=c++11"}, CCName, - "clang-rename", std::make_shared(), - FileContents)) - return ""; - - formatAndApplyAllReplacements(FileToReplacements, Context.Rewrite, "llvm"); - return Context.getRewrittenText(InputFileID); - } - - void CompareSnippets(StringRef Expected, StringRef Actual) { - std::string ExpectedCode; - llvm::raw_string_ostream(ExpectedCode) << llvm::format( - "#include \"%s\"\n%s", HeaderName.c_str(), Expected.str().c_str()); - EXPECT_EQ(format(ExpectedCode), format(Actual)); - } - - std::string format(llvm::StringRef Code) { - tooling::Replacements Replaces = format::reformat( - format::getLLVMStyle(), Code, {tooling::Range(0, Code.size())}); - auto ChangedCode = tooling::applyAllReplacements(Code, Replaces); - EXPECT_TRUE(static_cast(ChangedCode)); - if (!ChangedCode) { - llvm::errs() << llvm::toString(ChangedCode.takeError()); - return ""; - } - return *ChangedCode; - } - - std::string HeaderContent; - std::string HeaderName = "header.h"; - std::string CCName = "input.cc"; -}; - -} // namespace test -} // namespace clang_rename -} // namesdpace clang Index: tools/extra/unittests/clang-rename/RenameClassTest.cpp =================================================================== --- tools/extra/unittests/clang-rename/RenameClassTest.cpp +++ /dev/null @@ -1,684 +0,0 @@ -//===-- 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: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -29,3 +29,4 @@ if(NOT WIN32 AND CLANG_TOOL_LIBCLANG_BUILD) add_subdirectory(libclang) endif() +add_subdirectory(Rename) Index: unittests/Rename/CMakeLists.txt =================================================================== --- /dev/null +++ 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: unittests/Rename/ClangRenameTest.h =================================================================== --- /dev/null +++ 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: unittests/Rename/RenameClassTest.cpp =================================================================== --- /dev/null +++ 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