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> { 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); 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::ZeroOrMore, 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::ZeroOrMore, 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::ZeroOrMore, 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 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