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-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10/// 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&: Context.getASTContext());
40 std::string PrevName = ND->getNameAsString();
41 return getOccurrencesOfUSRs(USRs, PrevName,
42 Decl: Context.getASTContext().getTranslationUnitDecl());
43}
44
45} // end anonymous namespace
46
47const RefactoringDescriptor &RenameOccurrences::describe() {
48 static const RefactoringDescriptor Descriptor = {
49 .Name: "local-rename",
50 .Title: "Rename",
51 .Description: "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: Context.getASTContext(), Point: SelectionRange.getBegin());
61 if (!ND)
62 return Context.createDiagnosticError(
63 Loc: SelectionRange.getBegin(), DiagID: diag::err_refactor_selection_no_symbol);
64 return RenameOccurrences(getCanonicalSymbolDeclaration(FoundDecl: 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: *Occurrences, SM: Context.getASTContext().getSourceManager(), NewName: Name);
79}
80
81Expected<QualifiedRenameRule>
82QualifiedRenameRule::initiate(RefactoringRuleContext &Context,
83 std::string OldQualifiedName,
84 std::string NewQualifiedName) {
85 const NamedDecl *ND =
86 getNamedDeclFor(Context: Context.getASTContext(), Name: OldQualifiedName);
87 if (!ND)
88 return llvm::make_error<llvm::StringError>(Args: "Could not find symbol " +
89 OldQualifiedName,
90 Args: 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 new
103 qualified name. All symbol references will be correctly qualified; For
104 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&: Context.getASTContext());
130 assert(!USRs.empty());
131 return tooling::createRenameAtomicChanges(
132 USRs, NewName: NewQualifiedName, TranslationUnitDecl: 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(First&: Ranges)) {
146 auto Error =
147 Change.replace(SM, Range: CharSourceRange::getCharRange(R: Range.value()),
148 ReplacementText: NewName.getNamePieces()[Range.index()]);
149 if (Error)
150 return std::move(Error);
151 }
152 Changes.push_back(x: std::move(Change));
153 }
154 return std::move(Changes);
155}
156
157/// Takes each atomic change and inserts its replacements into the set of
158/// 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(R: Replace);
166 if (Err) {
167 llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! "
168 << llvm::toString(E: 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, NewName: NewNames[I], PrevName: PrevNames[I], USRs: 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, Decl: 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(SpellingLoc: 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, SM: SourceMgr, NewName: NewNameRef);
216 if (!Change) {
217 llvm::errs() << "Failed to create renaming replacements for '" << PrevName
218 << "'! " << llvm::toString(E: Change.takeError()) << "\n";
219 return;
220 }
221 convertChangesToFileReplacements(AtomicChanges: *Change, FileToReplaces: &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 to
232// 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 are
247 // ready.
248 auto AtomicChanges = tooling::createRenameAtomicChanges(
249 USRs: USRList[I], NewName: NewNames[I], TranslationUnitDecl: Context.getTranslationUnitDecl());
250 convertChangesToFileReplacements(AtomicChanges, FileToReplaces: &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>(args: NewNames, args: PrevNames, args: USRList,
262 args&: FileToReplaces, args&: PrintLocations);
263}
264
265std::unique_ptr<ASTConsumer> QualifiedRenamingAction::newASTConsumer() {
266 return std::make_unique<USRSymbolRenamer>(args: NewNames, args: USRList, args&: FileToReplaces);
267}
268
269} // end namespace tooling
270} // end namespace clang
271