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