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