blob: 4a964ef9ce45b93f41d8af5266c973e67bbeada3 [file] [log] [blame]
Manuel Klimekde237262014-08-20 01:39:05 +00001//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9///
10/// \file
11/// \brief This file implements a clang-rename tool that automatically finds and
12/// renames symbols in C++ code.
13///
14//===----------------------------------------------------------------------===//
15
Manuel Klimek3f840a92014-10-13 11:30:27 +000016#include "../RenamingAction.h"
Kirill Bobyrev81009402016-08-04 09:23:30 +000017#include "../USRFindingAction.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000018#include "clang/Basic/Diagnostic.h"
19#include "clang/Basic/DiagnosticOptions.h"
Manuel Klimekde237262014-08-20 01:39:05 +000020#include "clang/Basic/FileManager.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000021#include "clang/Basic/IdentifierTable.h"
Manuel Klimekde237262014-08-20 01:39:05 +000022#include "clang/Basic/LangOptions.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000023#include "clang/Basic/SourceManager.h"
24#include "clang/Basic/TokenKinds.h"
Manuel Klimekde237262014-08-20 01:39:05 +000025#include "clang/Frontend/TextDiagnosticPrinter.h"
Manuel Klimekde237262014-08-20 01:39:05 +000026#include "clang/Rewrite/Core/Rewriter.h"
27#include "clang/Tooling/CommonOptionsParser.h"
28#include "clang/Tooling/Refactoring.h"
Miklos Vajnaa2ca3ed2016-06-27 19:34:47 +000029#include "clang/Tooling/ReplacementsYaml.h"
Manuel Klimekde237262014-08-20 01:39:05 +000030#include "clang/Tooling/Tooling.h"
31#include "llvm/ADT/IntrusiveRefCntPtr.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000032#include "llvm/Support/CommandLine.h"
33#include "llvm/Support/FileSystem.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000034#include "llvm/Support/YAMLTraits.h"
Kirill Bobyrev81009402016-08-04 09:23:30 +000035#include "llvm/Support/raw_ostream.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000036#include <cstdlib>
Manuel Klimekde237262014-08-20 01:39:05 +000037#include <string>
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000038#include <system_error>
Manuel Klimekde237262014-08-20 01:39:05 +000039
40using namespace llvm;
41
Manuel Klimekde237262014-08-20 01:39:05 +000042using namespace clang;
43
Miklos Vajnaaaec9b62016-08-02 09:51:31 +000044cl::OptionCategory ClangRenameAtCategory("clang-rename rename-at options");
45cl::OptionCategory ClangRenameAllCategory("clang-rename rename-all options");
46
47const char RenameAtUsage[] = "A tool to rename symbols in C/C++ code.\n\
Manuel Klimekde237262014-08-20 01:39:05 +000048clang-rename renames every occurrence of a symbol found at <offset> in\n\
49<source0>. If -i is specified, the edited files are overwritten to disk.\n\
50Otherwise, the results are written to stdout.\n";
51
Miklos Vajnaaaec9b62016-08-02 09:51:31 +000052const char RenameAllUsage[] = "A tool to rename symbols in C/C++ code.\n\
53clang-rename renames every occurrence of a symbol named <old-name>.\n";
54
55static int renameAtMain(int argc, const char *argv[]);
56static int renameAllMain(int argc, const char *argv[]);
57static int helpMain(int argc, const char *argv[]);
58
Miklos Vajna3d71b512016-08-09 18:20:41 +000059/// \brief An oldname -> newname rename.
60struct RenameAllInfo {
61 std::string OldName;
Miklos Vajnac29c81a2016-08-10 07:13:29 +000062 unsigned Offset = 0;
Miklos Vajna3d71b512016-08-09 18:20:41 +000063 std::string NewName;
Miklos Vajna3d71b512016-08-09 18:20:41 +000064};
65
66LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo)
67
68namespace llvm {
69namespace yaml {
70
Miklos Vajnac29c81a2016-08-10 07:13:29 +000071/// \brief Specialized MappingTraits to describe how a RenameAllInfo is
Miklos Vajna3d71b512016-08-09 18:20:41 +000072/// (de)serialized.
73template <> struct MappingTraits<RenameAllInfo> {
74 static void mapping(IO &IO, RenameAllInfo &Info) {
75 IO.mapOptional("OldName", Info.OldName);
76 IO.mapOptional("Offset", Info.Offset);
77 IO.mapRequired("NewName", Info.NewName);
78 }
79};
80
81} // end namespace yaml
82} // end namespace llvm
83
Manuel Klimekde237262014-08-20 01:39:05 +000084int main(int argc, const char **argv) {
Miklos Vajnaaaec9b62016-08-02 09:51:31 +000085 if (argc > 1) {
86 using MainFunction = std::function<int(int, const char *[])>;
87 MainFunction Func = StringSwitch<MainFunction>(argv[1])
88 .Case("rename-at", renameAtMain)
89 .Case("rename-all", renameAllMain)
90 .Cases("-help", "--help", helpMain)
91 .Default(nullptr);
92
93 if (Func) {
94 std::string Invocation = std::string(argv[0]) + " " + argv[1];
95 argv[1] = Invocation.c_str();
96 return Func(argc - 1, argv + 1);
97 } else {
98 return renameAtMain(argc, argv);
99 }
100 }
101
102 helpMain(argc, argv);
103 return 1;
104}
105
106int subcommandMain(bool isRenameAll, int argc, const char **argv) {
107 cl::OptionCategory *Category = nullptr;
108 const char *Usage = nullptr;
109 if (isRenameAll) {
110 Category = &ClangRenameAllCategory;
111 Usage = RenameAllUsage;
112 } else {
113 Category = &ClangRenameAtCategory;
114 Usage = RenameAtUsage;
115 }
116
117 cl::list<std::string> NewNames(
118 "new-name", cl::desc("The new name to change the symbol to."),
Miklos Vajna3d71b512016-08-09 18:20:41 +0000119 (isRenameAll ? cl::ZeroOrMore : cl::Required), cl::cat(*Category));
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000120 cl::list<unsigned> SymbolOffsets(
121 "offset",
122 cl::desc("Locates the symbol by offset as opposed to <line>:<column>."),
123 (isRenameAll ? cl::ZeroOrMore : cl::Required), cl::cat(*Category));
124 cl::list<std::string> OldNames(
125 "old-name",
126 cl::desc(
127 "The fully qualified name of the symbol, if -offset is not used."),
128 (isRenameAll ? cl::ZeroOrMore : cl::Optional),
129 cl::cat(ClangRenameAllCategory));
130 cl::opt<bool> Inplace("i", cl::desc("Overwrite edited <file>s."),
131 cl::cat(*Category));
132 cl::opt<bool> PrintName(
133 "pn",
134 cl::desc("Print the found symbol's name prior to renaming to stderr."),
135 cl::cat(ClangRenameAtCategory));
136 cl::opt<bool> PrintLocations(
137 "pl", cl::desc("Print the locations affected by renaming to stderr."),
138 cl::cat(ClangRenameAtCategory));
139 cl::opt<std::string> ExportFixes(
140 "export-fixes", cl::desc("YAML file to store suggested fixes in."),
141 cl::value_desc("filename"), cl::cat(*Category));
Miklos Vajna3d71b512016-08-09 18:20:41 +0000142 cl::opt<std::string> Input(
143 "input", cl::desc("YAML file to load oldname-newname pairs from."),
144 cl::Optional, cl::cat(ClangRenameAllCategory));
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000145
146 tooling::CommonOptionsParser OP(argc, argv, *Category, Usage);
Manuel Klimekde237262014-08-20 01:39:05 +0000147
Miklos Vajna3d71b512016-08-09 18:20:41 +0000148 if (!Input.empty()) {
149 // Populate OldNames and NewNames from a YAML file.
Miklos Vajnac29c81a2016-08-10 07:13:29 +0000150 ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
151 llvm::MemoryBuffer::getFile(Input);
Miklos Vajna3d71b512016-08-09 18:20:41 +0000152 if (!Buffer) {
153 errs() << "clang-rename: failed to read " << Input << ": "
154 << Buffer.getError().message() << "\n";
Miklos Vajnac29c81a2016-08-10 07:13:29 +0000155 return 1;
Miklos Vajna3d71b512016-08-09 18:20:41 +0000156 }
157
158 std::vector<RenameAllInfo> Infos;
159 llvm::yaml::Input YAML(Buffer.get()->getBuffer());
160 YAML >> Infos;
161 for (const auto &Info : Infos) {
162 if (!Info.OldName.empty())
163 OldNames.push_back(Info.OldName);
164 else
165 SymbolOffsets.push_back(Info.Offset);
166 NewNames.push_back(Info.NewName);
167 }
168 }
169
Manuel Klimekde237262014-08-20 01:39:05 +0000170 // Check the arguments for correctness.
171
Miklos Vajna3d71b512016-08-09 18:20:41 +0000172 if (NewNames.empty()) {
173 errs() << "clang-rename: either -new-name or -input is required.\n\n";
174 exit(1);
175 }
176
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000177 // Check if NewNames is a valid identifier in C++17.
178 for (const auto &NewName : NewNames) {
179 LangOptions Options;
180 Options.CPlusPlus = true;
181 Options.CPlusPlus1z = true;
182 IdentifierTable Table(Options);
183 auto NewNameTokKind = Table.get(NewName).getTokenID();
184 if (!tok::isAnyIdentifier(NewNameTokKind)) {
185 errs() << "ERROR: new name is not a valid identifier in C++17.\n\n";
186 exit(1);
187 }
188 }
189
190 if (!OldNames.empty() && OldNames.size() != NewNames.size()) {
191 errs() << "clang-rename: number of old names (" << OldNames.size()
192 << ") do not equal to number of new names (" << NewNames.size()
193 << ").\n\n";
194 cl::PrintHelpMessage();
Kirill Bobyrev08c588c2016-07-21 10:21:31 +0000195 exit(1);
196 }
197
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000198 if (!SymbolOffsets.empty() && SymbolOffsets.size() != NewNames.size()) {
199 errs() << "clang-rename: number of symbol offsets (" << SymbolOffsets.size()
200 << ") do not equal to number of new names (" << NewNames.size()
201 << ").\n\n";
202 cl::PrintHelpMessage();
Manuel Klimekde237262014-08-20 01:39:05 +0000203 exit(1);
204 }
205
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000206 std::vector<std::vector<std::string>> USRList;
207 std::vector<std::string> PrevNames;
Manuel Klimekde237262014-08-20 01:39:05 +0000208 auto Files = OP.getSourcePathList();
209 tooling::RefactoringTool Tool(OP.getCompilations(), Files);
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000210 unsigned Count = OldNames.size() ? OldNames.size() : SymbolOffsets.size();
211 for (unsigned I = 0; I < Count; ++I) {
212 unsigned SymbolOffset = SymbolOffsets.empty() ? 0 : SymbolOffsets[I];
213 const std::string &OldName = OldNames.empty() ? std::string() : OldNames[I];
Manuel Klimekde237262014-08-20 01:39:05 +0000214
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000215 // Get the USRs.
216 rename::USRFindingAction USRAction(SymbolOffset, OldName);
Manuel Klimekde237262014-08-20 01:39:05 +0000217
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000218 // Find the USRs.
219 Tool.run(tooling::newFrontendActionFactory(&USRAction).get());
220 const auto &USRs = USRAction.getUSRs();
221 USRList.push_back(USRs);
222 const auto &PrevName = USRAction.getUSRSpelling();
223 PrevNames.push_back(PrevName);
Manuel Klimekde237262014-08-20 01:39:05 +0000224
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000225 if (PrevName.empty()) {
226 // An error should have already been printed.
227 exit(1);
228 }
229
230 if (PrintName) {
231 errs() << "clang-rename: found name: " << PrevName << '\n';
232 }
Benjamin Kramer1afefc02016-07-14 09:46:03 +0000233 }
Manuel Klimekde237262014-08-20 01:39:05 +0000234
235 // Perform the renaming.
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000236 rename::RenamingAction RenameAction(NewNames, PrevNames, USRList,
Manuel Klimekde237262014-08-20 01:39:05 +0000237 Tool.getReplacements(), PrintLocations);
238 auto Factory = tooling::newFrontendActionFactory(&RenameAction);
Kirill Bobyrev32db7692016-07-15 11:29:16 +0000239 int ExitCode;
Manuel Klimekde237262014-08-20 01:39:05 +0000240
241 if (Inplace) {
Kirill Bobyrev32db7692016-07-15 11:29:16 +0000242 ExitCode = Tool.runAndSave(Factory.get());
Manuel Klimekde237262014-08-20 01:39:05 +0000243 } else {
Kirill Bobyrev32db7692016-07-15 11:29:16 +0000244 ExitCode = Tool.run(Factory.get());
Manuel Klimekde237262014-08-20 01:39:05 +0000245
Miklos Vajnaa2ca3ed2016-06-27 19:34:47 +0000246 if (!ExportFixes.empty()) {
247 std::error_code EC;
248 llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None);
249 if (EC) {
250 llvm::errs() << "Error opening output file: " << EC.message() << '\n';
251 exit(1);
252 }
253
254 // Export replacements.
255 tooling::TranslationUnitReplacements TUR;
Eric Liu267034c2016-08-01 10:16:39 +0000256 const auto &FileToReplacements = Tool.getReplacements();
257 for (const auto &Entry : FileToReplacements)
258 TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(),
259 Entry.second.end());
Miklos Vajnaa2ca3ed2016-06-27 19:34:47 +0000260
261 yaml::Output YAML(OS);
262 YAML << TUR;
263 OS.close();
264 exit(0);
265 }
266
Manuel Klimekde237262014-08-20 01:39:05 +0000267 // Write every file to stdout. Right now we just barf the files without any
268 // indication of which files start where, other than that we print the files
269 // in the same order we see them.
270 LangOptions DefaultLangOptions;
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000271 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
Manuel Klimekde237262014-08-20 01:39:05 +0000272 TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
273 DiagnosticsEngine Diagnostics(
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000274 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
275 &DiagnosticPrinter, false);
Manuel Klimekde237262014-08-20 01:39:05 +0000276 auto &FileMgr = Tool.getFiles();
277 SourceManager Sources(Diagnostics, FileMgr);
278 Rewriter Rewrite(Sources, DefaultLangOptions);
279
280 Tool.applyAllReplacements(Rewrite);
281 for (const auto &File : Files) {
282 const auto *Entry = FileMgr.getFile(File);
283 auto ID = Sources.translateFile(Entry);
284 Rewrite.getEditBuffer(ID).write(outs());
285 }
286 }
287
Kirill Bobyrev32db7692016-07-15 11:29:16 +0000288 exit(ExitCode);
Manuel Klimekde237262014-08-20 01:39:05 +0000289}
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000290
291/// \brief Top level help.
292/// FIXME It would be better if this could be auto-generated.
293static int helpMain(int argc, const char *argv[]) {
294 errs() << "Usage: clang-rename {rename-at|rename-all} [OPTION]...\n\n"
295 "A tool to rename symbols in C/C++ code.\n\n"
296 "Subcommands:\n"
297 " rename-at: Perform rename off of a location in a file. (This "
298 "is the default.)\n"
299 " rename-all: Perform rename of all symbols matching one or more "
300 "fully qualified names.\n";
301 return 0;
302}
303
304static int renameAtMain(int argc, const char *argv[]) {
305 return subcommandMain(false, argc, argv);
306}
307
308static int renameAllMain(int argc, const char *argv[]) {
309 return subcommandMain(true, argc, argv);
310}