Clang Project

clang_source_code/tools/clang-rename/ClangRename.cpp
1//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===//
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/// This file implements a clang-rename tool that automatically finds and
11/// renames symbols in C++ code.
12///
13//===----------------------------------------------------------------------===//
14
15#include "clang/Basic/Diagnostic.h"
16#include "clang/Basic/DiagnosticOptions.h"
17#include "clang/Basic/FileManager.h"
18#include "clang/Basic/IdentifierTable.h"
19#include "clang/Basic/LangOptions.h"
20#include "clang/Basic/SourceManager.h"
21#include "clang/Basic/TokenKinds.h"
22#include "clang/Frontend/TextDiagnosticPrinter.h"
23#include "clang/Rewrite/Core/Rewriter.h"
24#include "clang/Tooling/CommonOptionsParser.h"
25#include "clang/Tooling/Refactoring.h"
26#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
27#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
28#include "clang/Tooling/ReplacementsYaml.h"
29#include "clang/Tooling/Tooling.h"
30#include "llvm/ADT/IntrusiveRefCntPtr.h"
31#include "llvm/Support/CommandLine.h"
32#include "llvm/Support/FileSystem.h"
33#include "llvm/Support/YAMLTraits.h"
34#include "llvm/Support/raw_ostream.h"
35#include <string>
36#include <system_error>
37
38using namespace llvm;
39using namespace clang;
40
41/// An oldname -> newname rename.
42struct RenameAllInfo {
43  unsigned Offset = 0;
44  std::string QualifiedName;
45  std::string NewName;
46};
47
48LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo)
49
50namespace llvm {
51namespace yaml {
52
53/// Specialized MappingTraits to describe how a RenameAllInfo is
54/// (de)serialized.
55template <> struct MappingTraits<RenameAllInfo> {
56  static void mapping(IO &IO, RenameAllInfo &Info) {
57    IO.mapOptional("Offset", Info.Offset);
58    IO.mapOptional("QualifiedName", Info.QualifiedName);
59    IO.mapRequired("NewName", Info.NewName);
60  }
61};
62
63// end namespace yaml
64// end namespace llvm
65
66static cl::OptionCategory ClangRenameOptions("clang-rename common options");
67
68static cl::list<unsignedSymbolOffsets(
69    "offset",
70    cl::desc("Locates the symbol by offset as opposed to <line>:<column>."),
71    cl::ZeroOrMore, cl::cat(ClangRenameOptions));
72static cl::opt<boolInplace("i", cl::desc("Overwrite edited <file>s."),
73                             cl::cat(ClangRenameOptions));
74static cl::list<std::string>
75    QualifiedNames("qualified-name",
76                   cl::desc("The fully qualified name of the symbol."),
77                   cl::ZeroOrMore, cl::cat(ClangRenameOptions));
78
79static cl::list<std::string>
80    NewNames("new-name", cl::desc("The new name to change the symbol to."),
81             cl::ZeroOrMore, cl::cat(ClangRenameOptions));
82static cl::opt<boolPrintName(
83    "pn",
84    cl::desc("Print the found symbol's name prior to renaming to stderr."),
85    cl::cat(ClangRenameOptions));
86static cl::opt<boolPrintLocations(
87    "pl", cl::desc("Print the locations affected by renaming to stderr."),
88    cl::cat(ClangRenameOptions));
89static cl::opt<std::string>
90    ExportFixes("export-fixes",
91                cl::desc("YAML file to store suggested fixes in."),
92                cl::value_desc("filename"), cl::cat(ClangRenameOptions));
93static cl::opt<std::string>
94    Input("input", cl::desc("YAML file to load oldname-newname pairs from."),
95          cl::Optional, cl::cat(ClangRenameOptions));
96static cl::opt<boolForce("force",
97                           cl::desc("Ignore nonexistent qualified names."),
98                           cl::cat(ClangRenameOptions));
99
100int main(int argcconst char **argv) {
101  tooling::CommonOptionsParser OP(argc, argv, ClangRenameOptions);
102
103  if (!Input.empty()) {
104    // Populate QualifiedNames and NewNames from a YAML file.
105    ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
106        llvm::MemoryBuffer::getFile(Input);
107    if (!Buffer) {
108      errs() << "clang-rename: failed to read " << Input << ": "
109             << Buffer.getError().message() << "\n";
110      return 1;
111    }
112
113    std::vector<RenameAllInfoInfos;
114    llvm::yaml::Input YAML(Buffer.get()->getBuffer());
115    YAML >> Infos;
116    for (const auto &Info : Infos) {
117      if (!Info.QualifiedName.empty())
118        QualifiedNames.push_back(Info.QualifiedName);
119      else
120        SymbolOffsets.push_back(Info.Offset);
121      NewNames.push_back(Info.NewName);
122    }
123  }
124
125  // Check the arguments for correctness.
126  if (NewNames.empty()) {
127    errs() << "clang-rename: -new-name must be specified.\n\n";
128    return 1;
129  }
130
131  if (SymbolOffsets.empty() == QualifiedNames.empty()) {
132    errs() << "clang-rename: -offset and -qualified-name can't be present at "
133              "the same time.\n";
134    return 1;
135  }
136
137  // Check if NewNames is a valid identifier in C++17.
138  LangOptions Options;
139  Options.CPlusPlus = true;
140  Options.CPlusPlus17 = true;
141  IdentifierTable Table(Options);
142  for (const auto &NewName : NewNames) {
143    auto NewNameTokKind = Table.get(NewName).getTokenID();
144    if (!tok::isAnyIdentifier(NewNameTokKind)) {
145      errs() << "ERROR: new name is not a valid identifier in C++17.\n\n";
146      return 1;
147    }
148  }
149
150  if (SymbolOffsets.size() + QualifiedNames.size() != NewNames.size()) {
151    errs() << "clang-rename: number of symbol offsets(" << SymbolOffsets.size()
152           << ") + number of qualified names (" << QualifiedNames.size()
153           << ") must be equal to number of new names(" << NewNames.size()
154           << ").\n\n";
155    cl::PrintHelpMessage();
156    return 1;
157  }
158
159  auto Files = OP.getSourcePathList();
160  tooling::RefactoringTool Tool(OP.getCompilations(), Files);
161  tooling::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames, Force);
162  Tool.run(tooling::newFrontendActionFactory(&FindingAction).get());
163  const std::vector<std::vector<std::string>> &USRList =
164      FindingAction.getUSRList();
165  const std::vector<std::string> &PrevNames = FindingAction.getUSRSpellings();
166  if (PrintName) {
167    for (const auto &PrevName : PrevNames) {
168      outs() << "clang-rename found name: " << PrevName << '\n';
169    }
170  }
171
172  if (FindingAction.errorOccurred()) {
173    // Diagnostics are already issued at this point.
174    return 1;
175  }
176
177  // Perform the renaming.
178  tooling::RenamingAction RenameAction(NewNames, PrevNames, USRList,
179                                       Tool.getReplacements(), PrintLocations);
180  std::unique_ptr<tooling::FrontendActionFactoryFactory =
181      tooling::newFrontendActionFactory(&RenameAction);
182  int ExitCode;
183
184  if (Inplace) {
185    ExitCode = Tool.runAndSave(Factory.get());
186  } else {
187    ExitCode = Tool.run(Factory.get());
188
189    if (!ExportFixes.empty()) {
190      std::error_code EC;
191      llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None);
192      if (EC) {
193        llvm::errs() << "Error opening output file: " << EC.message() << '\n';
194        return 1;
195      }
196
197      // Export replacements.
198      tooling::TranslationUnitReplacements TUR;
199      const auto &FileToReplacements = Tool.getReplacements();
200      for (const auto &Entry : FileToReplacements)
201        TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(),
202                                Entry.second.end());
203
204      yaml::Output YAML(OS);
205      YAML << TUR;
206      OS.close();
207      return 0;
208    }
209
210    // Write every file to stdout. Right now we just barf the files without any
211    // indication of which files start where, other than that we print the files
212    // in the same order we see them.
213    LangOptions DefaultLangOptions;
214    IntrusiveRefCntPtr<DiagnosticOptionsDiagOpts = new DiagnosticOptions();
215    TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
216    DiagnosticsEngine Diagnostics(
217        IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
218        &DiagnosticPrinter, false);
219    auto &FileMgr = Tool.getFiles();
220    SourceManager Sources(Diagnostics, FileMgr);
221    Rewriter Rewrite(SourcesDefaultLangOptions);
222
223    Tool.applyAllReplacements(Rewrite);
224    for (const auto &File : Files) {
225      const auto *Entry = FileMgr.getFile(File);
226      const auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User);
227      Rewrite.getEditBuffer(ID).write(outs());
228    }
229  }
230
231  return ExitCode;
232}
233