Home | History | Annotate | Line # | Download | only in clang-rename
      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