//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// /// /// \file /// This file implements a clang-rename tool that automatically finds and /// renames symbols in C++ code. /// //===----------------------------------------------------------------------===// #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TokenKinds.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Refactoring/Rename/RenamingAction.h" #include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" #include "clang/Tooling/ReplacementsYaml.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; using namespace clang; /// An oldname -> newname rename. struct RenameAllInfo { unsigned Offset = 0; std::string QualifiedName; std::string NewName; }; LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo) namespace llvm { namespace yaml { /// Specialized MappingTraits to describe how a RenameAllInfo is /// (de)serialized. template <> struct MappingTraits { static void mapping(IO &IO, RenameAllInfo &Info) { IO.mapOptional("Offset", Info.Offset); IO.mapOptional("QualifiedName", Info.QualifiedName); IO.mapRequired("NewName", Info.NewName); } }; } // end namespace yaml } // end namespace llvm static cl::OptionCategory ClangRenameOptions("clang-rename common options"); static cl::list SymbolOffsets( "offset", cl::desc("Locates the symbol by offset as opposed to :."), cl::cat(ClangRenameOptions)); static cl::opt Inplace("i", cl::desc("Overwrite edited s."), cl::cat(ClangRenameOptions)); static cl::list QualifiedNames("qualified-name", cl::desc("The fully qualified name of the symbol."), cl::cat(ClangRenameOptions)); static cl::list NewNames("new-name", cl::desc("The new name to change the symbol to."), cl::cat(ClangRenameOptions)); static cl::opt PrintName( "pn", cl::desc("Print the found symbol's name prior to renaming to stderr."), cl::cat(ClangRenameOptions)); static cl::opt PrintLocations( "pl", cl::desc("Print the locations affected by renaming to stderr."), cl::cat(ClangRenameOptions)); static cl::opt ExportFixes("export-fixes", cl::desc("YAML file to store suggested fixes in."), cl::value_desc("filename"), cl::cat(ClangRenameOptions)); static cl::opt Input("input", cl::desc("YAML file to load oldname-newname pairs from."), cl::Optional, cl::cat(ClangRenameOptions)); static cl::opt Force("force", cl::desc("Ignore nonexistent qualified names."), cl::cat(ClangRenameOptions)); int main(int argc, const char **argv) { auto ExpectedParser = tooling::CommonOptionsParser::create(argc, argv, ClangRenameOptions); if (!ExpectedParser) { llvm::errs() << ExpectedParser.takeError(); return 1; } tooling::CommonOptionsParser &OP = ExpectedParser.get(); if (!Input.empty()) { // Populate QualifiedNames and NewNames from a YAML file. ErrorOr> Buffer = llvm::MemoryBuffer::getFile(Input); if (!Buffer) { errs() << "clang-rename: failed to read " << Input << ": " << Buffer.getError().message() << "\n"; return 1; } std::vector Infos; llvm::yaml::Input YAML(Buffer.get()->getBuffer()); YAML >> Infos; for (const auto &Info : Infos) { if (!Info.QualifiedName.empty()) QualifiedNames.push_back(Info.QualifiedName); else SymbolOffsets.push_back(Info.Offset); NewNames.push_back(Info.NewName); } } // Check the arguments for correctness. if (NewNames.empty()) { errs() << "clang-rename: -new-name must be specified.\n\n"; return 1; } if (SymbolOffsets.empty() == QualifiedNames.empty()) { errs() << "clang-rename: -offset and -qualified-name can't be present at " "the same time.\n"; return 1; } // Check if NewNames is a valid identifier in C++17. LangOptions Options; Options.CPlusPlus = true; Options.CPlusPlus17 = true; IdentifierTable Table(Options); for (const auto &NewName : NewNames) { auto NewNameTokKind = Table.get(NewName).getTokenID(); if (!tok::isAnyIdentifier(NewNameTokKind)) { errs() << "ERROR: new name is not a valid identifier in C++17.\n\n"; return 1; } } if (SymbolOffsets.size() + QualifiedNames.size() != NewNames.size()) { errs() << "clang-rename: number of symbol offsets(" << SymbolOffsets.size() << ") + number of qualified names (" << QualifiedNames.size() << ") must be equal to number of new names(" << NewNames.size() << ").\n\n"; cl::PrintHelpMessage(); return 1; } auto Files = OP.getSourcePathList(); tooling::RefactoringTool Tool(OP.getCompilations(), Files); tooling::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames, Force); Tool.run(tooling::newFrontendActionFactory(&FindingAction).get()); const std::vector> &USRList = FindingAction.getUSRList(); const std::vector &PrevNames = FindingAction.getUSRSpellings(); if (PrintName) { for (const auto &PrevName : PrevNames) { outs() << "clang-rename found name: " << PrevName << '\n'; } } if (FindingAction.errorOccurred()) { // Diagnostics are already issued at this point. return 1; } // Perform the renaming. tooling::RenamingAction RenameAction(NewNames, PrevNames, USRList, Tool.getReplacements(), PrintLocations); std::unique_ptr Factory = tooling::newFrontendActionFactory(&RenameAction); int ExitCode; if (Inplace) { ExitCode = Tool.runAndSave(Factory.get()); } else { ExitCode = Tool.run(Factory.get()); if (!ExportFixes.empty()) { std::error_code EC; llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::OF_None); if (EC) { llvm::errs() << "Error opening output file: " << EC.message() << '\n'; return 1; } // Export replacements. tooling::TranslationUnitReplacements TUR; const auto &FileToReplacements = Tool.getReplacements(); for (const auto &Entry : FileToReplacements) TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(), Entry.second.end()); yaml::Output YAML(OS); YAML << TUR; OS.close(); return 0; } // Write every file to stdout. Right now we just barf the files without any // indication of which files start where, other than that we print the files // in the same order we see them. LangOptions DefaultLangOptions; IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); DiagnosticsEngine Diagnostics( IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false); auto &FileMgr = Tool.getFiles(); SourceManager Sources(Diagnostics, FileMgr); Rewriter Rewrite(Sources, DefaultLangOptions); Tool.applyAllReplacements(Rewrite); for (const auto &File : Files) { auto Entry = FileMgr.getFile(File); const auto ID = Sources.getOrCreateFileID(*Entry, SrcMgr::C_User); Rewrite.getEditBuffer(ID).write(outs()); } } return ExitCode; }