106f32e7eSjoerg //===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===//
206f32e7eSjoerg //
306f32e7eSjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
406f32e7eSjoerg // See https://llvm.org/LICENSE.txt for license information.
506f32e7eSjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
606f32e7eSjoerg //
706f32e7eSjoerg //===----------------------------------------------------------------------===//
806f32e7eSjoerg ///
906f32e7eSjoerg /// \file
1006f32e7eSjoerg /// This file implements a clang-rename tool that automatically finds and
1106f32e7eSjoerg /// renames symbols in C++ code.
1206f32e7eSjoerg ///
1306f32e7eSjoerg //===----------------------------------------------------------------------===//
1406f32e7eSjoerg 
1506f32e7eSjoerg #include "clang/Basic/Diagnostic.h"
1606f32e7eSjoerg #include "clang/Basic/DiagnosticOptions.h"
1706f32e7eSjoerg #include "clang/Basic/FileManager.h"
1806f32e7eSjoerg #include "clang/Basic/IdentifierTable.h"
1906f32e7eSjoerg #include "clang/Basic/LangOptions.h"
2006f32e7eSjoerg #include "clang/Basic/SourceManager.h"
2106f32e7eSjoerg #include "clang/Basic/TokenKinds.h"
2206f32e7eSjoerg #include "clang/Frontend/TextDiagnosticPrinter.h"
2306f32e7eSjoerg #include "clang/Rewrite/Core/Rewriter.h"
2406f32e7eSjoerg #include "clang/Tooling/CommonOptionsParser.h"
2506f32e7eSjoerg #include "clang/Tooling/Refactoring.h"
2606f32e7eSjoerg #include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
2706f32e7eSjoerg #include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
2806f32e7eSjoerg #include "clang/Tooling/ReplacementsYaml.h"
2906f32e7eSjoerg #include "clang/Tooling/Tooling.h"
3006f32e7eSjoerg #include "llvm/ADT/IntrusiveRefCntPtr.h"
3106f32e7eSjoerg #include "llvm/Support/CommandLine.h"
3206f32e7eSjoerg #include "llvm/Support/FileSystem.h"
3306f32e7eSjoerg #include "llvm/Support/YAMLTraits.h"
3406f32e7eSjoerg #include "llvm/Support/raw_ostream.h"
3506f32e7eSjoerg #include <string>
3606f32e7eSjoerg #include <system_error>
3706f32e7eSjoerg 
3806f32e7eSjoerg using namespace llvm;
3906f32e7eSjoerg using namespace clang;
4006f32e7eSjoerg 
4106f32e7eSjoerg /// An oldname -> newname rename.
4206f32e7eSjoerg struct RenameAllInfo {
4306f32e7eSjoerg   unsigned Offset = 0;
4406f32e7eSjoerg   std::string QualifiedName;
4506f32e7eSjoerg   std::string NewName;
4606f32e7eSjoerg };
4706f32e7eSjoerg 
4806f32e7eSjoerg LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo)
4906f32e7eSjoerg 
5006f32e7eSjoerg namespace llvm {
5106f32e7eSjoerg namespace yaml {
5206f32e7eSjoerg 
5306f32e7eSjoerg /// Specialized MappingTraits to describe how a RenameAllInfo is
5406f32e7eSjoerg /// (de)serialized.
5506f32e7eSjoerg template <> struct MappingTraits<RenameAllInfo> {
mappingllvm::yaml::MappingTraits5606f32e7eSjoerg   static void mapping(IO &IO, RenameAllInfo &Info) {
5706f32e7eSjoerg     IO.mapOptional("Offset", Info.Offset);
5806f32e7eSjoerg     IO.mapOptional("QualifiedName", Info.QualifiedName);
5906f32e7eSjoerg     IO.mapRequired("NewName", Info.NewName);
6006f32e7eSjoerg   }
6106f32e7eSjoerg };
6206f32e7eSjoerg 
6306f32e7eSjoerg } // end namespace yaml
6406f32e7eSjoerg } // end namespace llvm
6506f32e7eSjoerg 
6606f32e7eSjoerg static cl::OptionCategory ClangRenameOptions("clang-rename common options");
6706f32e7eSjoerg 
6806f32e7eSjoerg static cl::list<unsigned> SymbolOffsets(
6906f32e7eSjoerg     "offset",
7006f32e7eSjoerg     cl::desc("Locates the symbol by offset as opposed to <line>:<column>."),
7106f32e7eSjoerg     cl::ZeroOrMore, cl::cat(ClangRenameOptions));
7206f32e7eSjoerg static cl::opt<bool> Inplace("i", cl::desc("Overwrite edited <file>s."),
7306f32e7eSjoerg                              cl::cat(ClangRenameOptions));
7406f32e7eSjoerg static cl::list<std::string>
7506f32e7eSjoerg     QualifiedNames("qualified-name",
7606f32e7eSjoerg                    cl::desc("The fully qualified name of the symbol."),
7706f32e7eSjoerg                    cl::ZeroOrMore, cl::cat(ClangRenameOptions));
7806f32e7eSjoerg 
7906f32e7eSjoerg static cl::list<std::string>
8006f32e7eSjoerg     NewNames("new-name", cl::desc("The new name to change the symbol to."),
8106f32e7eSjoerg              cl::ZeroOrMore, cl::cat(ClangRenameOptions));
8206f32e7eSjoerg static cl::opt<bool> PrintName(
8306f32e7eSjoerg     "pn",
8406f32e7eSjoerg     cl::desc("Print the found symbol's name prior to renaming to stderr."),
8506f32e7eSjoerg     cl::cat(ClangRenameOptions));
8606f32e7eSjoerg static cl::opt<bool> PrintLocations(
8706f32e7eSjoerg     "pl", cl::desc("Print the locations affected by renaming to stderr."),
8806f32e7eSjoerg     cl::cat(ClangRenameOptions));
8906f32e7eSjoerg static cl::opt<std::string>
9006f32e7eSjoerg     ExportFixes("export-fixes",
9106f32e7eSjoerg                 cl::desc("YAML file to store suggested fixes in."),
9206f32e7eSjoerg                 cl::value_desc("filename"), cl::cat(ClangRenameOptions));
9306f32e7eSjoerg static cl::opt<std::string>
9406f32e7eSjoerg     Input("input", cl::desc("YAML file to load oldname-newname pairs from."),
9506f32e7eSjoerg           cl::Optional, cl::cat(ClangRenameOptions));
9606f32e7eSjoerg static cl::opt<bool> Force("force",
9706f32e7eSjoerg                            cl::desc("Ignore nonexistent qualified names."),
9806f32e7eSjoerg                            cl::cat(ClangRenameOptions));
9906f32e7eSjoerg 
main(int argc,const char ** argv)10006f32e7eSjoerg int main(int argc, const char **argv) {
101*13fbcb42Sjoerg   auto ExpectedParser =
102*13fbcb42Sjoerg       tooling::CommonOptionsParser::create(argc, argv, ClangRenameOptions);
103*13fbcb42Sjoerg   if (!ExpectedParser) {
104*13fbcb42Sjoerg     llvm::errs() << ExpectedParser.takeError();
105*13fbcb42Sjoerg     return 1;
106*13fbcb42Sjoerg   }
107*13fbcb42Sjoerg   tooling::CommonOptionsParser &OP = ExpectedParser.get();
10806f32e7eSjoerg 
10906f32e7eSjoerg   if (!Input.empty()) {
11006f32e7eSjoerg     // Populate QualifiedNames and NewNames from a YAML file.
11106f32e7eSjoerg     ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
11206f32e7eSjoerg         llvm::MemoryBuffer::getFile(Input);
11306f32e7eSjoerg     if (!Buffer) {
11406f32e7eSjoerg       errs() << "clang-rename: failed to read " << Input << ": "
11506f32e7eSjoerg              << Buffer.getError().message() << "\n";
11606f32e7eSjoerg       return 1;
11706f32e7eSjoerg     }
11806f32e7eSjoerg 
11906f32e7eSjoerg     std::vector<RenameAllInfo> Infos;
12006f32e7eSjoerg     llvm::yaml::Input YAML(Buffer.get()->getBuffer());
12106f32e7eSjoerg     YAML >> Infos;
12206f32e7eSjoerg     for (const auto &Info : Infos) {
12306f32e7eSjoerg       if (!Info.QualifiedName.empty())
12406f32e7eSjoerg         QualifiedNames.push_back(Info.QualifiedName);
12506f32e7eSjoerg       else
12606f32e7eSjoerg         SymbolOffsets.push_back(Info.Offset);
12706f32e7eSjoerg       NewNames.push_back(Info.NewName);
12806f32e7eSjoerg     }
12906f32e7eSjoerg   }
13006f32e7eSjoerg 
13106f32e7eSjoerg   // Check the arguments for correctness.
13206f32e7eSjoerg   if (NewNames.empty()) {
13306f32e7eSjoerg     errs() << "clang-rename: -new-name must be specified.\n\n";
13406f32e7eSjoerg     return 1;
13506f32e7eSjoerg   }
13606f32e7eSjoerg 
13706f32e7eSjoerg   if (SymbolOffsets.empty() == QualifiedNames.empty()) {
13806f32e7eSjoerg     errs() << "clang-rename: -offset and -qualified-name can't be present at "
13906f32e7eSjoerg               "the same time.\n";
14006f32e7eSjoerg     return 1;
14106f32e7eSjoerg   }
14206f32e7eSjoerg 
14306f32e7eSjoerg   // Check if NewNames is a valid identifier in C++17.
14406f32e7eSjoerg   LangOptions Options;
14506f32e7eSjoerg   Options.CPlusPlus = true;
14606f32e7eSjoerg   Options.CPlusPlus17 = true;
14706f32e7eSjoerg   IdentifierTable Table(Options);
14806f32e7eSjoerg   for (const auto &NewName : NewNames) {
14906f32e7eSjoerg     auto NewNameTokKind = Table.get(NewName).getTokenID();
15006f32e7eSjoerg     if (!tok::isAnyIdentifier(NewNameTokKind)) {
15106f32e7eSjoerg       errs() << "ERROR: new name is not a valid identifier in C++17.\n\n";
15206f32e7eSjoerg       return 1;
15306f32e7eSjoerg     }
15406f32e7eSjoerg   }
15506f32e7eSjoerg 
15606f32e7eSjoerg   if (SymbolOffsets.size() + QualifiedNames.size() != NewNames.size()) {
15706f32e7eSjoerg     errs() << "clang-rename: number of symbol offsets(" << SymbolOffsets.size()
15806f32e7eSjoerg            << ") + number of qualified names (" << QualifiedNames.size()
15906f32e7eSjoerg            << ") must be equal to number of new names(" << NewNames.size()
16006f32e7eSjoerg            << ").\n\n";
16106f32e7eSjoerg     cl::PrintHelpMessage();
16206f32e7eSjoerg     return 1;
16306f32e7eSjoerg   }
16406f32e7eSjoerg 
16506f32e7eSjoerg   auto Files = OP.getSourcePathList();
16606f32e7eSjoerg   tooling::RefactoringTool Tool(OP.getCompilations(), Files);
16706f32e7eSjoerg   tooling::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames, Force);
16806f32e7eSjoerg   Tool.run(tooling::newFrontendActionFactory(&FindingAction).get());
16906f32e7eSjoerg   const std::vector<std::vector<std::string>> &USRList =
17006f32e7eSjoerg       FindingAction.getUSRList();
17106f32e7eSjoerg   const std::vector<std::string> &PrevNames = FindingAction.getUSRSpellings();
17206f32e7eSjoerg   if (PrintName) {
17306f32e7eSjoerg     for (const auto &PrevName : PrevNames) {
17406f32e7eSjoerg       outs() << "clang-rename found name: " << PrevName << '\n';
17506f32e7eSjoerg     }
17606f32e7eSjoerg   }
17706f32e7eSjoerg 
17806f32e7eSjoerg   if (FindingAction.errorOccurred()) {
17906f32e7eSjoerg     // Diagnostics are already issued at this point.
18006f32e7eSjoerg     return 1;
18106f32e7eSjoerg   }
18206f32e7eSjoerg 
18306f32e7eSjoerg   // Perform the renaming.
18406f32e7eSjoerg   tooling::RenamingAction RenameAction(NewNames, PrevNames, USRList,
18506f32e7eSjoerg                                        Tool.getReplacements(), PrintLocations);
18606f32e7eSjoerg   std::unique_ptr<tooling::FrontendActionFactory> Factory =
18706f32e7eSjoerg       tooling::newFrontendActionFactory(&RenameAction);
18806f32e7eSjoerg   int ExitCode;
18906f32e7eSjoerg 
19006f32e7eSjoerg   if (Inplace) {
19106f32e7eSjoerg     ExitCode = Tool.runAndSave(Factory.get());
19206f32e7eSjoerg   } else {
19306f32e7eSjoerg     ExitCode = Tool.run(Factory.get());
19406f32e7eSjoerg 
19506f32e7eSjoerg     if (!ExportFixes.empty()) {
19606f32e7eSjoerg       std::error_code EC;
19706f32e7eSjoerg       llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::OF_None);
19806f32e7eSjoerg       if (EC) {
19906f32e7eSjoerg         llvm::errs() << "Error opening output file: " << EC.message() << '\n';
20006f32e7eSjoerg         return 1;
20106f32e7eSjoerg       }
20206f32e7eSjoerg 
20306f32e7eSjoerg       // Export replacements.
20406f32e7eSjoerg       tooling::TranslationUnitReplacements TUR;
20506f32e7eSjoerg       const auto &FileToReplacements = Tool.getReplacements();
20606f32e7eSjoerg       for (const auto &Entry : FileToReplacements)
20706f32e7eSjoerg         TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(),
20806f32e7eSjoerg                                 Entry.second.end());
20906f32e7eSjoerg 
21006f32e7eSjoerg       yaml::Output YAML(OS);
21106f32e7eSjoerg       YAML << TUR;
21206f32e7eSjoerg       OS.close();
21306f32e7eSjoerg       return 0;
21406f32e7eSjoerg     }
21506f32e7eSjoerg 
21606f32e7eSjoerg     // Write every file to stdout. Right now we just barf the files without any
21706f32e7eSjoerg     // indication of which files start where, other than that we print the files
21806f32e7eSjoerg     // in the same order we see them.
21906f32e7eSjoerg     LangOptions DefaultLangOptions;
22006f32e7eSjoerg     IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
22106f32e7eSjoerg     TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
22206f32e7eSjoerg     DiagnosticsEngine Diagnostics(
22306f32e7eSjoerg         IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
22406f32e7eSjoerg         &DiagnosticPrinter, false);
22506f32e7eSjoerg     auto &FileMgr = Tool.getFiles();
22606f32e7eSjoerg     SourceManager Sources(Diagnostics, FileMgr);
22706f32e7eSjoerg     Rewriter Rewrite(Sources, DefaultLangOptions);
22806f32e7eSjoerg 
22906f32e7eSjoerg     Tool.applyAllReplacements(Rewrite);
23006f32e7eSjoerg     for (const auto &File : Files) {
23106f32e7eSjoerg       auto Entry = FileMgr.getFile(File);
23206f32e7eSjoerg       const auto ID = Sources.getOrCreateFileID(*Entry, SrcMgr::C_User);
23306f32e7eSjoerg       Rewrite.getEditBuffer(ID).write(outs());
23406f32e7eSjoerg     }
23506f32e7eSjoerg   }
23606f32e7eSjoerg 
23706f32e7eSjoerg   return ExitCode;
23806f32e7eSjoerg }
239