1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file implements a clang-rename tool that automatically finds and
/// renames symbols in C++ code.
///
//===----------------------------------------------------------------------===//
#include "../RenamingAction.h"
#include "../USRFindingAction.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/ReplacementsYaml.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include <cstdlib>
#include <string>
#include <system_error>
using namespace llvm;
using namespace clang;
cl::OptionCategory ClangRenameAtCategory("clang-rename rename-at options");
cl::OptionCategory ClangRenameAllCategory("clang-rename rename-all options");
const char RenameAtUsage[] = "A tool to rename symbols in C/C++ code.\n\
clang-rename renames every occurrence of a symbol found at <offset> in\n\
<source0>. If -i is specified, the edited files are overwritten to disk.\n\
Otherwise, the results are written to stdout.\n";
const char RenameAllUsage[] = "A tool to rename symbols in C/C++ code.\n\
clang-rename renames every occurrence of a symbol named <old-name>.\n";
static int renameAtMain(int argc, const char *argv[]);
static int renameAllMain(int argc, const char *argv[]);
static int helpMain(int argc, const char *argv[]);
int main(int argc, const char **argv) {
if (argc > 1) {
using MainFunction = std::function<int(int, const char *[])>;
MainFunction Func = StringSwitch<MainFunction>(argv[1])
.Case("rename-at", renameAtMain)
.Case("rename-all", renameAllMain)
.Cases("-help", "--help", helpMain)
.Default(nullptr);
if (Func) {
std::string Invocation = std::string(argv[0]) + " " + argv[1];
argv[1] = Invocation.c_str();
return Func(argc - 1, argv + 1);
} else {
return renameAtMain(argc, argv);
}
}
helpMain(argc, argv);
return 1;
}
int subcommandMain(bool isRenameAll, int argc, const char **argv) {
cl::OptionCategory *Category = nullptr;
const char *Usage = nullptr;
if (isRenameAll) {
Category = &ClangRenameAllCategory;
Usage = RenameAllUsage;
} else {
Category = &ClangRenameAtCategory;
Usage = RenameAtUsage;
}
cl::list<std::string> NewNames(
"new-name", cl::desc("The new name to change the symbol to."),
(isRenameAll ? cl::OneOrMore : cl::Required), cl::cat(*Category));
cl::list<unsigned> SymbolOffsets(
"offset",
cl::desc("Locates the symbol by offset as opposed to <line>:<column>."),
(isRenameAll ? cl::ZeroOrMore : cl::Required), cl::cat(*Category));
cl::list<std::string> OldNames(
"old-name",
cl::desc(
"The fully qualified name of the symbol, if -offset is not used."),
(isRenameAll ? cl::ZeroOrMore : cl::Optional),
cl::cat(ClangRenameAllCategory));
cl::opt<bool> Inplace("i", cl::desc("Overwrite edited <file>s."),
cl::cat(*Category));
cl::opt<bool> PrintName(
"pn",
cl::desc("Print the found symbol's name prior to renaming to stderr."),
cl::cat(ClangRenameAtCategory));
cl::opt<bool> PrintLocations(
"pl", cl::desc("Print the locations affected by renaming to stderr."),
cl::cat(ClangRenameAtCategory));
cl::opt<std::string> ExportFixes(
"export-fixes", cl::desc("YAML file to store suggested fixes in."),
cl::value_desc("filename"), cl::cat(*Category));
tooling::CommonOptionsParser OP(argc, argv, *Category, Usage);
// Check the arguments for correctness.
// Check if NewNames is a valid identifier in C++17.
for (const auto &NewName : NewNames) {
LangOptions Options;
Options.CPlusPlus = true;
Options.CPlusPlus1z = true;
IdentifierTable Table(Options);
auto NewNameTokKind = Table.get(NewName).getTokenID();
if (!tok::isAnyIdentifier(NewNameTokKind)) {
errs() << "ERROR: new name is not a valid identifier in C++17.\n\n";
exit(1);
}
}
if (!OldNames.empty() && OldNames.size() != NewNames.size()) {
errs() << "clang-rename: number of old names (" << OldNames.size()
<< ") do not equal to number of new names (" << NewNames.size()
<< ").\n\n";
cl::PrintHelpMessage();
exit(1);
}
if (!SymbolOffsets.empty() && SymbolOffsets.size() != NewNames.size()) {
errs() << "clang-rename: number of symbol offsets (" << SymbolOffsets.size()
<< ") do not equal to number of new names (" << NewNames.size()
<< ").\n\n";
cl::PrintHelpMessage();
exit(1);
}
std::vector<std::vector<std::string>> USRList;
std::vector<std::string> PrevNames;
auto Files = OP.getSourcePathList();
tooling::RefactoringTool Tool(OP.getCompilations(), Files);
unsigned Count = OldNames.size() ? OldNames.size() : SymbolOffsets.size();
for (unsigned I = 0; I < Count; ++I) {
unsigned SymbolOffset = SymbolOffsets.empty() ? 0 : SymbolOffsets[I];
const std::string &OldName = OldNames.empty() ? std::string() : OldNames[I];
// Get the USRs.
rename::USRFindingAction USRAction(SymbolOffset, OldName);
// Find the USRs.
Tool.run(tooling::newFrontendActionFactory(&USRAction).get());
const auto &USRs = USRAction.getUSRs();
USRList.push_back(USRs);
const auto &PrevName = USRAction.getUSRSpelling();
PrevNames.push_back(PrevName);
if (PrevName.empty()) {
// An error should have already been printed.
exit(1);
}
if (PrintName) {
errs() << "clang-rename: found name: " << PrevName << '\n';
}
}
// Perform the renaming.
rename::RenamingAction RenameAction(NewNames, PrevNames, USRList,
Tool.getReplacements(), PrintLocations);
auto Factory = tooling::newFrontendActionFactory(&RenameAction);
int ExitCode;
if (Inplace) {
ExitCode = Tool.runAndSave(Factory.get());
} else {
ExitCode = Tool.run(Factory.get());
if (!ExportFixes.empty()) {
std::error_code EC;
llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None);
if (EC) {
llvm::errs() << "Error opening output file: " << EC.message() << '\n';
exit(1);
}
// Export replacements.
tooling::TranslationUnitReplacements TUR;
const auto &FileToReplacements = Tool.getReplacements();
for (const auto &Entry : FileToReplacements)
TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(),
Entry.second.end());
yaml::Output YAML(OS);
YAML << TUR;
OS.close();
exit(0);
}
// Write every file to stdout. Right now we just barf the files without any
// indication of which files start where, other than that we print the files
// in the same order we see them.
LangOptions DefaultLangOptions;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
&DiagnosticPrinter, false);
auto &FileMgr = Tool.getFiles();
SourceManager Sources(Diagnostics, FileMgr);
Rewriter Rewrite(Sources, DefaultLangOptions);
Tool.applyAllReplacements(Rewrite);
for (const auto &File : Files) {
const auto *Entry = FileMgr.getFile(File);
auto ID = Sources.translateFile(Entry);
Rewrite.getEditBuffer(ID).write(outs());
}
}
exit(ExitCode);
}
/// \brief Top level help.
/// FIXME It would be better if this could be auto-generated.
static int helpMain(int argc, const char *argv[]) {
errs() << "Usage: clang-rename {rename-at|rename-all} [OPTION]...\n\n"
"A tool to rename symbols in C/C++ code.\n\n"
"Subcommands:\n"
" rename-at: Perform rename off of a location in a file. (This "
"is the default.)\n"
" rename-all: Perform rename of all symbols matching one or more "
"fully qualified names.\n";
return 0;
}
static int renameAtMain(int argc, const char *argv[]) {
return subcommandMain(false, argc, argv);
}
static int renameAllMain(int argc, const char *argv[]) {
return subcommandMain(true, argc, argv);
}
|