271 lines · cpp
1//===--- RenamingAction.cpp - Clang refactoring library -------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// Provides an action to rename every symbol at a point.11///12//===----------------------------------------------------------------------===//13 14#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"15#include "clang/AST/ASTConsumer.h"16#include "clang/AST/ASTContext.h"17#include "clang/Frontend/CompilerInstance.h"18#include "clang/Lex/Lexer.h"19#include "clang/Tooling/Refactoring/Rename/SymbolName.h"20#include "clang/Tooling/Refactoring/Rename/USRFinder.h"21#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"22#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"23#include "llvm/ADT/STLExtras.h"24#include "llvm/Support/Errc.h"25#include "llvm/Support/Error.h"26#include <string>27#include <vector>28 29using namespace llvm;30 31namespace clang {32namespace tooling {33 34namespace {35 36Expected<SymbolOccurrences>37findSymbolOccurrences(const NamedDecl *ND, RefactoringRuleContext &Context) {38 std::vector<std::string> USRs =39 getUSRsForDeclaration(ND, Context.getASTContext());40 std::string PrevName = ND->getNameAsString();41 return getOccurrencesOfUSRs(USRs, PrevName,42 Context.getASTContext().getTranslationUnitDecl());43}44 45} // end anonymous namespace46 47const RefactoringDescriptor &RenameOccurrences::describe() {48 static const RefactoringDescriptor Descriptor = {49 "local-rename",50 "Rename",51 "Finds and renames symbols in code with no indexer support",52 };53 return Descriptor;54}55 56Expected<RenameOccurrences>57RenameOccurrences::initiate(RefactoringRuleContext &Context,58 SourceRange SelectionRange, std::string NewName) {59 const NamedDecl *ND =60 getNamedDeclAt(Context.getASTContext(), SelectionRange.getBegin());61 if (!ND)62 return Context.createDiagnosticError(63 SelectionRange.getBegin(), diag::err_refactor_selection_no_symbol);64 return RenameOccurrences(getCanonicalSymbolDeclaration(ND),65 std::move(NewName));66}67 68const NamedDecl *RenameOccurrences::getRenameDecl() const { return ND; }69 70Expected<AtomicChanges>71RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) {72 Expected<SymbolOccurrences> Occurrences = findSymbolOccurrences(ND, Context);73 if (!Occurrences)74 return Occurrences.takeError();75 // FIXME: Verify that the new name is valid.76 SymbolName Name(NewName);77 return createRenameReplacements(78 *Occurrences, Context.getASTContext().getSourceManager(), Name);79}80 81Expected<QualifiedRenameRule>82QualifiedRenameRule::initiate(RefactoringRuleContext &Context,83 std::string OldQualifiedName,84 std::string NewQualifiedName) {85 const NamedDecl *ND =86 getNamedDeclFor(Context.getASTContext(), OldQualifiedName);87 if (!ND)88 return llvm::make_error<llvm::StringError>("Could not find symbol " +89 OldQualifiedName,90 llvm::errc::invalid_argument);91 return QualifiedRenameRule(ND, std::move(NewQualifiedName));92}93 94const RefactoringDescriptor &QualifiedRenameRule::describe() {95 static const RefactoringDescriptor Descriptor = {96 /*Name=*/"local-qualified-rename",97 /*Title=*/"Qualified Rename",98 /*Description=*/99 R"(Finds and renames qualified symbols in code within a translation unit.100It is used to move/rename a symbol to a new namespace/name:101 * Supported symbols: classes, class members, functions, enums, and type alias.102 * Renames all symbol occurrences from the old qualified name to the new103 qualified name. All symbol references will be correctly qualified; For104 symbol definitions, only name will be changed.105For example, rename "A::Foo" to "B::Bar":106 Old code:107 namespace foo {108 class A {};109 }110 111 namespace bar {112 void f(foo::A a) {}113 }114 115 New code after rename:116 namespace foo {117 class B {};118 }119 120 namespace bar {121 void f(B b) {}122 })"123 };124 return Descriptor;125}126 127Expected<AtomicChanges>128QualifiedRenameRule::createSourceReplacements(RefactoringRuleContext &Context) {129 auto USRs = getUSRsForDeclaration(ND, Context.getASTContext());130 assert(!USRs.empty());131 return tooling::createRenameAtomicChanges(132 USRs, NewQualifiedName, Context.getASTContext().getTranslationUnitDecl());133}134 135Expected<std::vector<AtomicChange>>136createRenameReplacements(const SymbolOccurrences &Occurrences,137 const SourceManager &SM, const SymbolName &NewName) {138 // FIXME: A true local rename can use just one AtomicChange.139 std::vector<AtomicChange> Changes;140 for (const auto &Occurrence : Occurrences) {141 ArrayRef<SourceRange> Ranges = Occurrence.getNameRanges();142 assert(NewName.getNamePieces().size() == Ranges.size() &&143 "Mismatching number of ranges and name pieces");144 AtomicChange Change(SM, Ranges[0].getBegin());145 for (const auto &Range : llvm::enumerate(Ranges)) {146 auto Error =147 Change.replace(SM, CharSourceRange::getCharRange(Range.value()),148 NewName.getNamePieces()[Range.index()]);149 if (Error)150 return std::move(Error);151 }152 Changes.push_back(std::move(Change));153 }154 return std::move(Changes);155}156 157/// Takes each atomic change and inserts its replacements into the set of158/// replacements that belong to the appropriate file.159static void convertChangesToFileReplacements(160 ArrayRef<AtomicChange> AtomicChanges,161 std::map<std::string, tooling::Replacements> *FileToReplaces) {162 for (const auto &AtomicChange : AtomicChanges) {163 for (const auto &Replace : AtomicChange.getReplacements()) {164 llvm::Error Err =165 (*FileToReplaces)[std::string(Replace.getFilePath())].add(Replace);166 if (Err) {167 llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! "168 << llvm::toString(std::move(Err)) << "\n";169 }170 }171 }172}173 174class RenamingASTConsumer : public ASTConsumer {175public:176 RenamingASTConsumer(177 const std::vector<std::string> &NewNames,178 const std::vector<std::string> &PrevNames,179 const std::vector<std::vector<std::string>> &USRList,180 std::map<std::string, tooling::Replacements> &FileToReplaces,181 bool PrintLocations)182 : NewNames(NewNames), PrevNames(PrevNames), USRList(USRList),183 FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {}184 185 void HandleTranslationUnit(ASTContext &Context) override {186 for (unsigned I = 0; I < NewNames.size(); ++I) {187 // If the previous name was not found, ignore this rename request.188 if (PrevNames[I].empty())189 continue;190 191 HandleOneRename(Context, NewNames[I], PrevNames[I], USRList[I]);192 }193 }194 195 void HandleOneRename(ASTContext &Context, const std::string &NewName,196 const std::string &PrevName,197 const std::vector<std::string> &USRs) {198 const SourceManager &SourceMgr = Context.getSourceManager();199 200 SymbolOccurrences Occurrences = tooling::getOccurrencesOfUSRs(201 USRs, PrevName, Context.getTranslationUnitDecl());202 if (PrintLocations) {203 for (const auto &Occurrence : Occurrences) {204 FullSourceLoc FullLoc(Occurrence.getNameRanges()[0].getBegin(),205 SourceMgr);206 errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(FullLoc)207 << ":" << FullLoc.getSpellingLineNumber() << ":"208 << FullLoc.getSpellingColumnNumber() << "\n";209 }210 }211 // FIXME: Support multi-piece names.212 // FIXME: better error handling (propagate error out).213 SymbolName NewNameRef(NewName);214 Expected<std::vector<AtomicChange>> Change =215 createRenameReplacements(Occurrences, SourceMgr, NewNameRef);216 if (!Change) {217 llvm::errs() << "Failed to create renaming replacements for '" << PrevName218 << "'! " << llvm::toString(Change.takeError()) << "\n";219 return;220 }221 convertChangesToFileReplacements(*Change, &FileToReplaces);222 }223 224private:225 const std::vector<std::string> &NewNames, &PrevNames;226 const std::vector<std::vector<std::string>> &USRList;227 std::map<std::string, tooling::Replacements> &FileToReplaces;228 bool PrintLocations;229};230 231// A renamer to rename symbols which are identified by a give USRList to232// new name.233//234// FIXME: Merge with the above RenamingASTConsumer.235class USRSymbolRenamer : public ASTConsumer {236public:237 USRSymbolRenamer(const std::vector<std::string> &NewNames,238 const std::vector<std::vector<std::string>> &USRList,239 std::map<std::string, tooling::Replacements> &FileToReplaces)240 : NewNames(NewNames), USRList(USRList), FileToReplaces(FileToReplaces) {241 assert(USRList.size() == NewNames.size());242 }243 244 void HandleTranslationUnit(ASTContext &Context) override {245 for (unsigned I = 0; I < NewNames.size(); ++I) {246 // FIXME: Apply AtomicChanges directly once the refactoring APIs are247 // ready.248 auto AtomicChanges = tooling::createRenameAtomicChanges(249 USRList[I], NewNames[I], Context.getTranslationUnitDecl());250 convertChangesToFileReplacements(AtomicChanges, &FileToReplaces);251 }252 }253 254private:255 const std::vector<std::string> &NewNames;256 const std::vector<std::vector<std::string>> &USRList;257 std::map<std::string, tooling::Replacements> &FileToReplaces;258};259 260std::unique_ptr<ASTConsumer> RenamingAction::newASTConsumer() {261 return std::make_unique<RenamingASTConsumer>(NewNames, PrevNames, USRList,262 FileToReplaces, PrintLocations);263}264 265std::unique_ptr<ASTConsumer> QualifiedRenamingAction::newASTConsumer() {266 return std::make_unique<USRSymbolRenamer>(NewNames, USRList, FileToReplaces);267}268 269} // end namespace tooling270} // end namespace clang271