ClangRename.cpp 8.3 KB

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