1 //===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===//
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
7 //===----------------------------------------------------------------------===//
10 /// This file implements a clang-rename tool that automatically finds and
11 /// renames symbols in C++ code.
13 //===----------------------------------------------------------------------===//
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"
36 #include <system_error>
39 using namespace clang
;
41 /// An oldname -> newname rename.
42 struct RenameAllInfo
{
44 std::string QualifiedName
;
48 LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo
)
53 /// Specialized MappingTraits to describe how a RenameAllInfo is
55 template <> struct MappingTraits
<RenameAllInfo
> {
56 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
);
63 } // end namespace yaml
64 } // end namespace llvm
66 static cl::OptionCategory
ClangRenameOptions("clang-rename common options");
68 static cl::list
<unsigned> SymbolOffsets(
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
));
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(
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
));
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();
107 tooling::CommonOptionsParser
&OP
= ExpectedParser
.get();
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
);
114 errs() << "clang-rename: failed to read " << Input
<< ": "
115 << Buffer
.getError().message() << "\n";
119 std::vector
<RenameAllInfo
> Infos
;
120 llvm::yaml::Input
YAML(Buffer
.get()->getBuffer());
122 for (const auto &Info
: Infos
) {
123 if (!Info
.QualifiedName
.empty())
124 QualifiedNames
.push_back(Info
.QualifiedName
);
126 SymbolOffsets
.push_back(Info
.Offset
);
127 NewNames
.push_back(Info
.NewName
);
131 // Check the arguments for correctness.
132 if (NewNames
.empty()) {
133 errs() << "clang-rename: -new-name must be specified.\n\n";
137 if (SymbolOffsets
.empty() == QualifiedNames
.empty()) {
138 errs() << "clang-rename: -offset and -qualified-name can't be present at "
143 // Check if NewNames is a valid identifier in C++17.
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";
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()
161 cl::PrintHelpMessage();
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();
173 for (const auto &PrevName
: PrevNames
) {
174 outs() << "clang-rename found name: " << PrevName
<< '\n';
178 if (FindingAction
.errorOccurred()) {
179 // Diagnostics are already issued at this point.
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
);
191 ExitCode
= Tool
.runAndSave(Factory
.get());
193 ExitCode
= Tool
.run(Factory
.get());
195 if (!ExportFixes
.empty()) {
197 llvm::raw_fd_ostream
OS(ExportFixes
, EC
, llvm::sys::fs::OF_None
);
199 llvm::errs() << "Error opening output file: " << EC
.message() << '\n';
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(),
210 yaml::Output
YAML(OS
);
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
);
229 Tool
.applyAllReplacements(Rewrite
);
230 for (const auto &File
: Files
) {
231 auto Entry
= FileMgr
.getOptionalFileRef(File
);
233 errs() << "clang-rename: " << File
<< " does not exist.\n";
236 const auto ID
= Sources
.getOrCreateFileID(*Entry
, SrcMgr::C_User
);
237 Rewrite
.getEditBuffer(ID
).write(outs());