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(Key: "Offset", Val&: Info.Offset);
58 IO.mapOptional(Key: "QualifiedName", Val&: Info.QualifiedName);
59 IO.mapRequired(Key: "NewName", Val&: 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<unsigned> SymbolOffsets(
69 "offset",
70 cl::desc("Locates the symbol by offset as opposed to <line>:<column>."),
71 cl::cat(ClangRenameOptions));
72static cl::opt<bool> Inplace("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::cat(ClangRenameOptions));
78
79static cl::list<std::string>
80 NewNames("new-name", cl::desc("The new name to change the symbol to."),
81 cl::cat(ClangRenameOptions));
82static cl::opt<bool> PrintName(
83 "pn",
84 cl::desc("Print the found symbol's name prior to renaming to stderr."),
85 cl::cat(ClangRenameOptions));
86static cl::opt<bool> PrintLocations(
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<bool> Force("force",
97 cl::desc("Ignore nonexistent qualified names."),
98 cl::cat(ClangRenameOptions));
99
100int main(int argc, const char **argv) {
101 auto ExpectedParser =
102 tooling::CommonOptionsParser::create(argc, argv, Category&: 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(Filename: 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(value: Info.QualifiedName);
125 else
126 SymbolOffsets.push_back(value: Info.Offset);
127 NewNames.push_back(value: 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(Name: NewName).getTokenID();
150 if (!tok::isAnyIdentifier(K: 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(Action: tooling::newFrontendActionFactory(ConsumerFactory: &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(ConsumerFactory: &RenameAction);
188 int ExitCode;
189
190 if (Inplace) {
191 ExitCode = Tool.runAndSave(ActionFactory: Factory.get());
192 } else {
193 ExitCode = Tool.run(Action: 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(position: TUR.Replacements.end(), first: Entry.second.begin(),
208 last: 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.getOptionalFileRef(Filename: File);
232 if (!Entry) {
233 errs() << "clang-rename: " << File << " does not exist.\n";
234 return 1;
235 }
236 const auto ID = Sources.getOrCreateFileID(SourceFile: *Entry, FileCharacter: SrcMgr::C_User);
237 Rewrite.getEditBuffer(FID: ID).write(Stream&: outs());
238 }
239 }
240
241 return ExitCode;
242}
243