blob: cc18a05bcdbe61c1635d6ce5ad5597bba209e2a4 [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
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000016#include "clang/Basic/Diagnostic.h"
17#include "clang/Basic/DiagnosticOptions.h"
Manuel Klimekde237262014-08-20 01:39:05 +000018#include "clang/Basic/FileManager.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000019#include "clang/Basic/IdentifierTable.h"
Manuel Klimekde237262014-08-20 01:39:05 +000020#include "clang/Basic/LangOptions.h"
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000021#include "clang/Basic/SourceManager.h"
22#include "clang/Basic/TokenKinds.h"
Manuel Klimekde237262014-08-20 01:39:05 +000023#include "clang/Frontend/TextDiagnosticPrinter.h"
Manuel Klimekde237262014-08-20 01:39:05 +000024#include "clang/Rewrite/Core/Rewriter.h"
25#include "clang/Tooling/CommonOptionsParser.h"
26#include "clang/Tooling/Refactoring.h"
Alex Lorenz4abbd922017-06-30 16:36:09 +000027#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
28#include "clang/Tooling/Refactoring/Rename/USRFindingAction.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"
Manuel Klimekde237262014-08-20 01:39:05 +000036#include <string>
Eugene Zelenkoa4c2efb2016-07-25 20:30:13 +000037#include <system_error>
Manuel Klimekde237262014-08-20 01:39:05 +000038
39using namespace llvm;
Manuel Klimekde237262014-08-20 01:39:05 +000040using namespace clang;
41
Miklos Vajna3d71b512016-08-09 18:20:41 +000042/// \brief An oldname -> newname rename.
43struct RenameAllInfo {
Miklos Vajnac29c81a2016-08-10 07:13:29 +000044 unsigned Offset = 0;
Kirill Bobyreve5e7e152016-09-16 08:45:19 +000045 std::string QualifiedName;
Miklos Vajna3d71b512016-08-09 18:20:41 +000046 std::string NewName;
Miklos Vajna3d71b512016-08-09 18:20:41 +000047};
48
49LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo)
50
51namespace llvm {
52namespace yaml {
53
Miklos Vajnac29c81a2016-08-10 07:13:29 +000054/// \brief Specialized MappingTraits to describe how a RenameAllInfo is
Miklos Vajna3d71b512016-08-09 18:20:41 +000055/// (de)serialized.
56template <> struct MappingTraits<RenameAllInfo> {
57 static void mapping(IO &IO, RenameAllInfo &Info) {
Miklos Vajna3d71b512016-08-09 18:20:41 +000058 IO.mapOptional("Offset", Info.Offset);
Kirill Bobyreve5e7e152016-09-16 08:45:19 +000059 IO.mapOptional("QualifiedName", Info.QualifiedName);
Miklos Vajna3d71b512016-08-09 18:20:41 +000060 IO.mapRequired("NewName", Info.NewName);
61 }
62};
63
64} // end namespace yaml
65} // end namespace llvm
66
Kirill Bobyreve5e7e152016-09-16 08:45:19 +000067static cl::OptionCategory ClangRenameOptions("clang-rename common options");
68
69static cl::list<unsigned> SymbolOffsets(
70 "offset",
71 cl::desc("Locates the symbol by offset as opposed to <line>:<column>."),
72 cl::ZeroOrMore, cl::cat(ClangRenameOptions));
73static cl::opt<bool> Inplace("i", cl::desc("Overwrite edited <file>s."),
74 cl::cat(ClangRenameOptions));
75static cl::list<std::string>
76 QualifiedNames("qualified-name",
77 cl::desc("The fully qualified name of the symbol."),
78 cl::ZeroOrMore, cl::cat(ClangRenameOptions));
79
80static cl::list<std::string>
81 NewNames("new-name", cl::desc("The new name to change the symbol to."),
82 cl::ZeroOrMore, cl::cat(ClangRenameOptions));
83static cl::opt<bool> PrintName(
84 "pn",
85 cl::desc("Print the found symbol's name prior to renaming to stderr."),
86 cl::cat(ClangRenameOptions));
87static cl::opt<bool> PrintLocations(
88 "pl", cl::desc("Print the locations affected by renaming to stderr."),
89 cl::cat(ClangRenameOptions));
90static cl::opt<std::string>
91 ExportFixes("export-fixes",
92 cl::desc("YAML file to store suggested fixes in."),
93 cl::value_desc("filename"), cl::cat(ClangRenameOptions));
94static cl::opt<std::string>
95 Input("input", cl::desc("YAML file to load oldname-newname pairs from."),
96 cl::Optional, cl::cat(ClangRenameOptions));
Miklos Vajnaa0ce6322017-06-02 09:32:28 +000097static cl::opt<bool> Force("force",
98 cl::desc("Ignore nonexistent qualified names."),
99 cl::cat(ClangRenameOptions));
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000100
Manuel Klimekde237262014-08-20 01:39:05 +0000101int main(int argc, const char **argv) {
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000102 tooling::CommonOptionsParser OP(argc, argv, ClangRenameOptions);
Manuel Klimekde237262014-08-20 01:39:05 +0000103
Miklos Vajna3d71b512016-08-09 18:20:41 +0000104 if (!Input.empty()) {
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000105 // Populate QualifiedNames and NewNames from a YAML file.
Miklos Vajnac29c81a2016-08-10 07:13:29 +0000106 ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
107 llvm::MemoryBuffer::getFile(Input);
Miklos Vajna3d71b512016-08-09 18:20:41 +0000108 if (!Buffer) {
109 errs() << "clang-rename: failed to read " << Input << ": "
110 << Buffer.getError().message() << "\n";
Miklos Vajnac29c81a2016-08-10 07:13:29 +0000111 return 1;
Miklos Vajna3d71b512016-08-09 18:20:41 +0000112 }
113
114 std::vector<RenameAllInfo> Infos;
115 llvm::yaml::Input YAML(Buffer.get()->getBuffer());
116 YAML >> Infos;
117 for (const auto &Info : Infos) {
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000118 if (!Info.QualifiedName.empty())
119 QualifiedNames.push_back(Info.QualifiedName);
Miklos Vajna3d71b512016-08-09 18:20:41 +0000120 else
121 SymbolOffsets.push_back(Info.Offset);
122 NewNames.push_back(Info.NewName);
123 }
124 }
125
Manuel Klimekde237262014-08-20 01:39:05 +0000126 // Check the arguments for correctness.
Miklos Vajna3d71b512016-08-09 18:20:41 +0000127 if (NewNames.empty()) {
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000128 errs() << "clang-rename: -new-name must be specified.\n\n";
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000129 return 1;
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000130 }
131
132 if (SymbolOffsets.empty() == QualifiedNames.empty()) {
133 errs() << "clang-rename: -offset and -qualified-name can't be present at "
134 "the same time.\n";
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000135 return 1;
Miklos Vajna3d71b512016-08-09 18:20:41 +0000136 }
137
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000138 // Check if NewNames is a valid identifier in C++17.
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000139 LangOptions Options;
140 Options.CPlusPlus = true;
141 Options.CPlusPlus1z = true;
142 IdentifierTable Table(Options);
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000143 for (const auto &NewName : NewNames) {
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000144 auto NewNameTokKind = Table.get(NewName).getTokenID();
145 if (!tok::isAnyIdentifier(NewNameTokKind)) {
146 errs() << "ERROR: new name is not a valid identifier in C++17.\n\n";
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000147 return 1;
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000148 }
149 }
150
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000151 if (SymbolOffsets.size() + QualifiedNames.size() != NewNames.size()) {
152 errs() << "clang-rename: number of symbol offsets(" << SymbolOffsets.size()
153 << ") + number of qualified names (" << QualifiedNames.size()
154 << ") must be equal to number of new names(" << NewNames.size()
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000155 << ").\n\n";
156 cl::PrintHelpMessage();
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000157 return 1;
Kirill Bobyrev08c588c2016-07-21 10:21:31 +0000158 }
159
Kirill Bobyrev8d78af42016-09-14 13:23:14 +0000160 auto Files = OP.getSourcePathList();
161 tooling::RefactoringTool Tool(OP.getCompilations(), Files);
Alex Lorenz4abbd922017-06-30 16:36:09 +0000162 tooling::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames, Force);
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000163 Tool.run(tooling::newFrontendActionFactory(&FindingAction).get());
164 const std::vector<std::vector<std::string>> &USRList =
165 FindingAction.getUSRList();
166 const std::vector<std::string> &PrevNames = FindingAction.getUSRSpellings();
167 if (PrintName) {
168 for (const auto &PrevName : PrevNames) {
169 outs() << "clang-rename found name: " << PrevName << '\n';
Kirill Bobyrev8d78af42016-09-14 13:23:14 +0000170 }
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000171 }
Kirill Bobyrev8d78af42016-09-14 13:23:14 +0000172
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000173 if (FindingAction.errorOccurred()) {
174 // Diagnostics are already issued at this point.
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000175 return 1;
Benjamin Kramer1afefc02016-07-14 09:46:03 +0000176 }
Manuel Klimekde237262014-08-20 01:39:05 +0000177
Miklos Vajnaa0ce6322017-06-02 09:32:28 +0000178 if (Force && PrevNames.size() < NewNames.size()) {
179 // No matching PrevName for all NewNames. Without Force this is an error
180 // above already.
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000181 return 0;
Miklos Vajnaa0ce6322017-06-02 09:32:28 +0000182 }
183
Manuel Klimekde237262014-08-20 01:39:05 +0000184 // Perform the renaming.
Alex Lorenz4abbd922017-06-30 16:36:09 +0000185 tooling::RenamingAction RenameAction(NewNames, PrevNames, USRList,
186 Tool.getReplacements(), PrintLocations);
Kirill Bobyreve5e7e152016-09-16 08:45:19 +0000187 std::unique_ptr<tooling::FrontendActionFactory> Factory =
188 tooling::newFrontendActionFactory(&RenameAction);
Kirill Bobyrev32db7692016-07-15 11:29:16 +0000189 int ExitCode;
Manuel Klimekde237262014-08-20 01:39:05 +0000190
191 if (Inplace) {
Kirill Bobyrev32db7692016-07-15 11:29:16 +0000192 ExitCode = Tool.runAndSave(Factory.get());
Manuel Klimekde237262014-08-20 01:39:05 +0000193 } else {
Kirill Bobyrev32db7692016-07-15 11:29:16 +0000194 ExitCode = Tool.run(Factory.get());
Manuel Klimekde237262014-08-20 01:39:05 +0000195
Miklos Vajnaa2ca3ed2016-06-27 19:34:47 +0000196 if (!ExportFixes.empty()) {
197 std::error_code EC;
198 llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None);
199 if (EC) {
200 llvm::errs() << "Error opening output file: " << EC.message() << '\n';
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000201 return 1;
Miklos Vajnaa2ca3ed2016-06-27 19:34:47 +0000202 }
203
204 // Export replacements.
205 tooling::TranslationUnitReplacements TUR;
Eric Liu267034c2016-08-01 10:16:39 +0000206 const auto &FileToReplacements = Tool.getReplacements();
207 for (const auto &Entry : FileToReplacements)
208 TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(),
209 Entry.second.end());
Miklos Vajnaa2ca3ed2016-06-27 19:34:47 +0000210
211 yaml::Output YAML(OS);
212 YAML << TUR;
213 OS.close();
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000214 return 0;
Miklos Vajnaa2ca3ed2016-06-27 19:34:47 +0000215 }
216
Manuel Klimekde237262014-08-20 01:39:05 +0000217 // Write every file to stdout. Right now we just barf the files without any
218 // indication of which files start where, other than that we print the files
219 // in the same order we see them.
220 LangOptions DefaultLangOptions;
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000221 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
Manuel Klimekde237262014-08-20 01:39:05 +0000222 TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
223 DiagnosticsEngine Diagnostics(
Miklos Vajnaaaec9b62016-08-02 09:51:31 +0000224 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
225 &DiagnosticPrinter, false);
Manuel Klimekde237262014-08-20 01:39:05 +0000226 auto &FileMgr = Tool.getFiles();
227 SourceManager Sources(Diagnostics, FileMgr);
228 Rewriter Rewrite(Sources, DefaultLangOptions);
229
230 Tool.applyAllReplacements(Rewrite);
231 for (const auto &File : Files) {
232 const auto *Entry = FileMgr.getFile(File);
Alexander Shaposhnikove4920c62016-09-17 17:08:47 +0000233 const auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User);
Manuel Klimekde237262014-08-20 01:39:05 +0000234 Rewrite.getEditBuffer(ID).write(outs());
235 }
236 }
237
Benjamin Kramer1fec6cb2017-06-30 20:24:32 +0000238 return ExitCode;
Manuel Klimekde237262014-08-20 01:39:05 +0000239}