diff options
author | Alex Lorenz <arphaman@gmail.com> | 2017-06-30 16:36:09 +0000 |
---|---|---|
committer | Alex Lorenz <arphaman@gmail.com> | 2017-06-30 16:36:09 +0000 |
commit | 4803aff743e866df42dd12fd37093bf2b4af42ac (patch) | |
tree | 3444ad9cff30fbdde5ea2d2f7b30a33a3bb69b33 /tools | |
parent | fc0baba7f6ae7a67b39c2375bfbbb7948a10b026 (diff) |
[refactor] Move clang-rename into the clang repository
The core engine of clang-rename will be used for local and global renames in the
new refactoring engine, as mentioned in
http://lists.llvm.org/pipermail/cfe-dev/2017-June/054286.html.
The clang-rename tool is still supported but might get deprecated in the future.
Differential Revision: https://reviews.llvm.org/D34696
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@306840 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'tools')
-rw-r--r-- | tools/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tools/clang-rename/CMakeLists.txt | 19 | ||||
-rw-r--r-- | tools/clang-rename/ClangRename.cpp | 240 | ||||
-rw-r--r-- | tools/clang-rename/clang-rename.el | 79 | ||||
-rw-r--r-- | tools/clang-rename/clang-rename.py | 61 |
5 files changed, 401 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index b0c97f0f1e..4976332b7d 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -10,6 +10,8 @@ add_clang_subdirectory(clang-offload-bundler) add_clang_subdirectory(c-index-test) +add_clang_subdirectory(clang-rename) + if(CLANG_ENABLE_ARCMT) add_clang_subdirectory(arcmt-test) add_clang_subdirectory(c-arcmt-test) diff --git a/tools/clang-rename/CMakeLists.txt b/tools/clang-rename/CMakeLists.txt new file mode 100644 index 0000000000..f6a4f49f7a --- /dev/null +++ b/tools/clang-rename/CMakeLists.txt @@ -0,0 +1,19 @@ +add_clang_executable(clang-rename ClangRename.cpp) + +target_link_libraries(clang-rename + clangBasic + clangFrontend + clangRewrite + clangTooling + clangToolingCore + clangToolingRefactor + ) + +install(TARGETS clang-rename RUNTIME DESTINATION bin) + +install(PROGRAMS clang-rename.py + DESTINATION share/clang + COMPONENT clang-rename) +install(PROGRAMS clang-rename.el + DESTINATION share/clang + COMPONENT clang-rename) diff --git a/tools/clang-rename/ClangRename.cpp b/tools/clang-rename/ClangRename.cpp new file mode 100644 index 0000000000..0fdf9a3980 --- /dev/null +++ b/tools/clang-rename/ClangRename.cpp @@ -0,0 +1,240 @@ +//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename tool -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief 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 <cstdlib> +#include <string> +#include <system_error> + +using namespace llvm; +using namespace clang; + +/// \brief 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 { + +/// \brief Specialized MappingTraits to describe how a RenameAllInfo is +/// (de)serialized. +template <> struct MappingTraits<RenameAllInfo> { + 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<unsigned> SymbolOffsets( + "offset", + cl::desc("Locates the symbol by offset as opposed to <line>:<column>."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); +static cl::opt<bool> Inplace("i", cl::desc("Overwrite edited <file>s."), + cl::cat(ClangRenameOptions)); +static cl::list<std::string> + QualifiedNames("qualified-name", + cl::desc("The fully qualified name of the symbol."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); + +static cl::list<std::string> + NewNames("new-name", cl::desc("The new name to change the symbol to."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); +static cl::opt<bool> PrintName( + "pn", + cl::desc("Print the found symbol's name prior to renaming to stderr."), + cl::cat(ClangRenameOptions)); +static cl::opt<bool> PrintLocations( + "pl", cl::desc("Print the locations affected by renaming to stderr."), + cl::cat(ClangRenameOptions)); +static cl::opt<std::string> + ExportFixes("export-fixes", + cl::desc("YAML file to store suggested fixes in."), + cl::value_desc("filename"), cl::cat(ClangRenameOptions)); +static cl::opt<std::string> + Input("input", cl::desc("YAML file to load oldname-newname pairs from."), + cl::Optional, cl::cat(ClangRenameOptions)); +static cl::opt<bool> Force("force", + cl::desc("Ignore nonexistent qualified names."), + cl::cat(ClangRenameOptions)); + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser OP(argc, argv, ClangRenameOptions); + + if (!Input.empty()) { + // Populate QualifiedNames and NewNames from a YAML file. + ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = + llvm::MemoryBuffer::getFile(Input); + if (!Buffer) { + errs() << "clang-rename: failed to read " << Input << ": " + << Buffer.getError().message() << "\n"; + return 1; + } + + std::vector<RenameAllInfo> 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"; + exit(1); + } + + if (SymbolOffsets.empty() == QualifiedNames.empty()) { + errs() << "clang-rename: -offset and -qualified-name can't be present at " + "the same time.\n"; + exit(1); + } + + // Check if NewNames is a valid identifier in C++17. + LangOptions Options; + Options.CPlusPlus = true; + Options.CPlusPlus1z = 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"; + exit(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(); + exit(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<std::vector<std::string>> &USRList = + FindingAction.getUSRList(); + const std::vector<std::string> &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. + exit(1); + } + + if (Force && PrevNames.size() < NewNames.size()) { + // No matching PrevName for all NewNames. Without Force this is an error + // above already. + exit(0); + } + + // Perform the renaming. + tooling::RenamingAction RenameAction(NewNames, PrevNames, USRList, + Tool.getReplacements(), PrintLocations); + std::unique_ptr<tooling::FrontendActionFactory> 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::F_None); + if (EC) { + llvm::errs() << "Error opening output file: " << EC.message() << '\n'; + exit(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(); + exit(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<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(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) { + const auto *Entry = FileMgr.getFile(File); + const auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User); + Rewrite.getEditBuffer(ID).write(outs()); + } + } + + exit(ExitCode); +} diff --git a/tools/clang-rename/clang-rename.el b/tools/clang-rename/clang-rename.el new file mode 100644 index 0000000000..b6c3ed4c68 --- /dev/null +++ b/tools/clang-rename/clang-rename.el @@ -0,0 +1,79 @@ +;;; clang-rename.el --- Renames every occurrence of a symbol found at <offset>. -*- lexical-binding: t; -*- + +;; Keywords: tools, c + +;;; Commentary: + +;; To install clang-rename.el make sure the directory of this file is in your +;; `load-path' and add +;; +;; (require 'clang-rename) +;; +;; to your .emacs configuration. + +;;; Code: + +(defgroup clang-rename nil + "Integration with clang-rename" + :group 'c) + +(defcustom clang-rename-binary "clang-rename" + "Path to clang-rename executable." + :type '(file :must-match t) + :group 'clang-rename) + +;;;###autoload +(defun clang-rename (new-name) + "Rename all instances of the symbol at point to NEW-NAME using clang-rename." + (interactive "sEnter a new name: ") + (save-some-buffers :all) + ;; clang-rename should not be combined with other operations when undoing. + (undo-boundary) + (let ((output-buffer (get-buffer-create "*clang-rename*"))) + (with-current-buffer output-buffer (erase-buffer)) + (let ((exit-code (call-process + clang-rename-binary nil output-buffer nil + (format "-offset=%d" + ;; clang-rename wants file (byte) offsets, not + ;; buffer (character) positions. + (clang-rename--bufferpos-to-filepos + ;; Emacs treats one character after a symbol as + ;; part of the symbol, but clang-rename doesn’t. + ;; Use the beginning of the current symbol, if + ;; available, to resolve the inconsistency. + (or (car (bounds-of-thing-at-point 'symbol)) + (point)) + 'exact)) + (format "-new-name=%s" new-name) + "-i" (buffer-file-name)))) + (if (and (integerp exit-code) (zerop exit-code)) + ;; Success; revert current buffer so it gets the modifications. + (progn + (kill-buffer output-buffer) + (revert-buffer :ignore-auto :noconfirm :preserve-modes)) + ;; Failure; append exit code to output buffer and display it. + (let ((message (clang-rename--format-message + "clang-rename failed with %s %s" + (if (integerp exit-code) "exit status" "signal") + exit-code))) + (with-current-buffer output-buffer + (insert ?\n message ?\n)) + (message "%s" message) + (display-buffer output-buffer)))))) + +(defalias 'clang-rename--bufferpos-to-filepos + (if (fboundp 'bufferpos-to-filepos) + 'bufferpos-to-filepos + ;; Emacs 24 doesn’t have ‘bufferpos-to-filepos’, simulate it using + ;; ‘position-bytes’. + (lambda (position &optional _quality _coding-system) + (1- (position-bytes position))))) + +;; ‘format-message’ is new in Emacs 25.1. Provide a fallback for older +;; versions. +(defalias 'clang-rename--format-message + (if (fboundp 'format-message) 'format-message 'format)) + +(provide 'clang-rename) + +;;; clang-rename.el ends here diff --git a/tools/clang-rename/clang-rename.py b/tools/clang-rename/clang-rename.py new file mode 100644 index 0000000000..3cc6644ff8 --- /dev/null +++ b/tools/clang-rename/clang-rename.py @@ -0,0 +1,61 @@ +''' +Minimal clang-rename integration with Vim. + +Before installing make sure one of the following is satisfied: + +* clang-rename is in your PATH +* `g:clang_rename_path` in ~/.vimrc points to valid clang-rename executable +* `binary` in clang-rename.py points to valid to clang-rename executable + +To install, simply put this into your ~/.vimrc + + noremap <leader>cr :pyf <path-to>/clang-rename.py<cr> + +IMPORTANT NOTE: Before running the tool, make sure you saved the file. + +All you have to do now is to place a cursor on a variable/function/class which +you would like to rename and press '<leader>cr'. You will be prompted for a new +name if the cursor points to a valid symbol. +''' + +import vim +import subprocess +import sys + +def main(): + binary = 'clang-rename' + if vim.eval('exists("g:clang_rename_path")') == "1": + binary = vim.eval('g:clang_rename_path') + + # Get arguments for clang-rename binary. + offset = int(vim.eval('line2byte(line("."))+col(".")')) - 2 + if offset < 0: + print >> sys.stderr, '''Couldn\'t determine cursor position. + Is your file empty?''' + return + filename = vim.current.buffer.name + + new_name_request_message = 'type new name:' + new_name = vim.eval("input('{}\n')".format(new_name_request_message)) + + # Call clang-rename. + command = [binary, + filename, + '-i', + '-offset', str(offset), + '-new-name', str(new_name)] + # FIXME: make it possible to run the tool on unsaved file. + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + + if stderr: + print stderr + + # Reload all buffers in Vim. + vim.command("checktime") + + +if __name__ == '__main__': + main() |