Added renaming unittest and converted VarTest.cpp to use FileCheck. The added
unittest builds and passes fine.
http://reviews.llvm.org/D4739
Files:
CMakeLists.txt
Makefile
clang-rename/CMakeLists.txt
clang-rename/ClangRename.cpp
clang-rename/Makefile
clang-rename/RenamingAction.cpp
clang-rename/RenamingAction.h
clang-rename/USRFinder.cpp
clang-rename/USRFinder.h
clang-rename/USRFindingAction.cpp
clang-rename/USRFindingAction.h
clang-rename/USRLocFinder.cpp
clang-rename/USRLocFinder.h
test/CMakeLists.txt
test/clang-rename/VarTest.cpp
unittests/CMakeLists.txt
unittests/clang-rename/CMakeLists.txt
unittests/clang-rename/Makefile
unittests/clang-rename/USRLocFindingTest.cpp
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -1,5 +1,6 @@
add_subdirectory(clang-apply-replacements)
add_subdirectory(clang-modernize)
+add_subdirectory(clang-rename)
add_subdirectory(modularize)
add_subdirectory(module-map-checker)
add_subdirectory(remove-cstr-calls)
Index: Makefile
===================================================================
--- Makefile
+++ Makefile
@@ -13,8 +13,8 @@
PARALLEL_DIRS := remove-cstr-calls tool-template modularize \
module-map-checker pp-trace
-DIRS := clang-apply-replacements clang-modernize clang-tidy clang-query \
- unittests
+DIRS := clang-apply-replacements clang-modernize clang-rename clang-tidy \
+ clang-query unittests
include $(CLANG_LEVEL)/Makefile
Index: clang-rename/CMakeLists.txt
===================================================================
--- /dev/null
+++ clang-rename/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(LLVM_LINK_COMPONENTS support)
+
+add_clang_executable(clang-rename
+ ClangRename.cpp
+ USRFinder.cpp
+ USRFindingAction.cpp
+ USRLocFinder.cpp
+ RenamingAction.cpp
+ )
+
+target_link_libraries(clang-rename
+ clangAnalysis
+ clangAST
+ clangBasic
+ clangDriver
+ clangEdit
+ clangFrontend
+ clangFrontendTool
+ clangIndex
+ clangLex
+ clangParse
+ clangRewrite
+ clangRewriteFrontend
+ clangSerialization
+ clangSema
+ clangTooling
+ )
+
+install(TARGETS clang-rename RUNTIME DESTINATION bin)
\ No newline at end of file
Index: clang-rename/ClangRename.cpp
===================================================================
--- /dev/null
+++ clang-rename/ClangRename.cpp
@@ -0,0 +1,168 @@
+//===--- 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 "USRFindingAction.h"
+#include "RenamingAction.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/Basic/TargetOptions.h"
+#include "clang/Frontend/CommandLineSourceLoc.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Frontend/TextDiagnosticPrinter.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Parse/Parser.h"
+#include "clang/Parse/ParseAST.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/Support/Host.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string>
+#include <vector>
+
+using namespace llvm;
+
+cl::OptionCategory ClangRenameCategory("Clang-rename options");
+
+cl::opt<std::string>
+NewName("new-name", cl::desc("The new name to change the symbol to."),
+ cl::cat(ClangRenameCategory));
+static cl::opt<unsigned> SymbolOffset(
+ "offset",
+ cl::desc("Locates the symbol by offset as opposed to <line>:<column>."),
+ cl::cat(ClangRenameCategory));
+static cl::opt<bool> Inplace("o", cl::desc("Overwrite edited <file>s."),
+ cl::cat(ClangRenameCategory));
+static cl::opt<bool> PrintName(
+ "pn",
+ cl::desc("Print the found symbol's name prior to renaming to stderr."),
+ cl::cat(ClangRenameCategory));
+
+#define CLANG_RENAME_VERSION "0.0.1"
+
+static void PrintVersion() {
+ outs() << "clang-rename version " << CLANG_RENAME_VERSION << "\n";
+}
+
+using namespace clang;
+
+template <typename ActionT>
+inline std::unique_ptr<tooling::FrontendActionFactory>
+newFrontendActionFactory(ActionT *Action) {
+ // Renaming CreateASTConsumer to createASTConsumer doesn't work, so we have to
+ // create our own custom factory.
+ struct SimpleFactory : public tooling::FrontendActionFactory {
+ SimpleFactory(ActionT *Action) : Action(Action) {
+ }
+
+ FrontendAction *create() override {
+ return new ConsumerFactoryAdaptor(Action);
+ }
+
+ private:
+ struct ConsumerFactoryAdaptor : public ASTFrontendAction {
+ ConsumerFactoryAdaptor(ActionT *Action) : Action(Action) {
+ }
+
+ ASTConsumer *CreateASTConsumer(CompilerInstance &CI,
+ StringRef File) override {
+ return Action->CreateASTConsumer(CI, File);
+ }
+
+ private:
+ ActionT *Action;
+ };
+ ActionT *Action;
+ };
+
+ return std::unique_ptr<SimpleFactory>(new SimpleFactory(Action));
+}
+
+int main(int argc, const char **argv) {
+ cl::SetVersionPrinter(PrintVersion);
+ tooling::CommonOptionsParser OP(argc, argv, ClangRenameCategory,
+ "A tool to rename symbols in C/C++ code.\n\n\
+clang-rename renames every occurrence of the symbol found at <offset> in\n\
+<source0>. If <source0> is '-', input is taken from stdin and written to\n\
+stdout. If -o is specified along with multiple files, the files are edited\n\
+in-place. Otherwise, the results are written to stdout.\n");
+
+ // Check the arguments for correctness.
+
+ if (NewName.empty()) {
+ errs() << "clang-rename: no new name provided.\n\n";
+ cl::PrintHelpMessage();
+ exit(1);
+ }
+
+ // Get the USRs.
+ auto Files = OP.getSourcePathList();
+ tooling::RefactoringTool Tool(OP.getCompilations(), Files);
+ rename::USRFindingAction USRAction(SymbolOffset);
+
+ // Find the USRs.
+ Tool.run(newFrontendActionFactory(&USRAction).get());
+
+ if (USRAction.PrevName.empty())
+ exit(1);
+
+ const auto &USRs = USRAction.USRs;
+ const auto &PrevName = USRAction.PrevName;
+ if (PrintName)
+ errs() << "clang-rename: found name: " << PrevName;
+
+ // Perform the renaming.
+ rename::RenamingAction RenameAction(PrevName, USRs, Tool.getReplacements());
+ auto Factory = newFrontendActionFactory(&RenameAction);
+ int res;
+
+ if (Inplace) {
+ res = Tool.runAndSave(Factory.get());
+ } else {
+ res = Tool.run(Factory.get());
+
+ // 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);
+ auto ID = Sources.translateFile(Entry);
+ Rewrite.getEditBuffer(ID).write(outs());
+ }
+ }
+
+ exit(res);
+}
Index: clang-rename/Makefile
===================================================================
--- /dev/null
+++ clang-rename/Makefile
@@ -0,0 +1,16 @@
+##===- tools/extra/clang-rename/Makefile -------------------*- Makefile -*-===##
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+CLANG_LEVEL := ../../..
+TOOLNAME = clang-rename
+include $(CLANG_LEVEL)/../../Makefile.config
+
+DIRS = test
+
+include $(CLANG_LEVEL)/Makefile
Index: clang-rename/RenamingAction.cpp
===================================================================
--- /dev/null
+++ clang-rename/RenamingAction.cpp
@@ -0,0 +1,94 @@
+//===--- tools/extra/clang-rename/RenamingAction.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 Provides an action to rename every symbol at a point.
+///
+//===----------------------------------------------------------------------===//
+
+#include "RenamingAction.h"
+#include "USRLocFinder.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Tooling.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string>
+#include <vector>
+
+using namespace llvm;
+
+extern cl::OptionCategory ClangRenameCategory;
+
+static cl::opt<bool> PrintLocations(
+ "pl", cl::desc("Print the locations affected by renaming to stderr."),
+ cl::cat(ClangRenameCategory));
+
+extern cl::opt<std::string> NewName;
+
+namespace clang {
+namespace rename {
+
+class RenamingASTConsumer : public ASTConsumer {
+public:
+ RenamingASTConsumer(const std::string &PrevName,
+ const std::vector<std::string> &USRs,
+ tooling::Replacements &Replaces)
+ : PrevName(PrevName), USRs(USRs), Replaces(Replaces) {
+ }
+
+ void HandleTranslationUnit(ASTContext &Context) override {
+ const auto &SourceMgr = Context.getSourceManager();
+ std::vector<SourceLocation> RenamingCandidates;
+ std::vector<SourceLocation> NewCandidates;
+
+ for (const auto &USR : USRs) {
+ NewCandidates = getLocationsOfUSR(USR, Context.getTranslationUnitDecl());
+ RenamingCandidates.insert(RenamingCandidates.end(), NewCandidates.begin(),
+ NewCandidates.end());
+ NewCandidates.clear();
+ }
+
+ auto PrevNameLen = PrevName.length();
+ if (PrintLocations)
+ for (const auto &Loc : RenamingCandidates) {
+ FullSourceLoc FullLoc(Loc, SourceMgr);
+ errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(Loc)
+ << ":" << FullLoc.getSpellingLineNumber() << ":"
+ << FullLoc.getSpellingColumnNumber() << "\n";
+ Replaces.insert(tooling::Replacement(SourceMgr, Loc, PrevNameLen,
+ NewName));
+ }
+ else
+ for (const auto &Loc : RenamingCandidates)
+ Replaces.insert(tooling::Replacement(SourceMgr, Loc, PrevNameLen,
+ NewName));
+ }
+
+private:
+ const std::string &PrevName;
+ const std::vector<std::string> &USRs;
+ tooling::Replacements &Replaces;
+};
+
+ASTConsumer *RenamingAction::CreateASTConsumer(CompilerInstance &CI,
+ StringRef File) {
+ return new RenamingASTConsumer(PrevName, USRs, Replaces);
+}
+
+}
+}
Index: clang-rename/RenamingAction.h
===================================================================
--- /dev/null
+++ clang-rename/RenamingAction.h
@@ -0,0 +1,45 @@
+//===--- tools/extra/clang-rename/RenamingAction.h - 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 Provides an action to rename every symbol at a point.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H_
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H
+
+#include "clang/Tooling/Refactoring.h"
+
+namespace clang {
+class ASTConsumer;
+class CompilerInstance;
+
+namespace rename {
+
+class RenamingAction : public ASTFrontendAction {
+public:
+ RenamingAction(const std::string &PrevName,
+ const std::vector<std::string> &USRs,
+ tooling::Replacements &Replaces)
+ : PrevName(PrevName), USRs(USRs), Replaces(Replaces) {
+ }
+
+ ASTConsumer *CreateASTConsumer(CompilerInstance &, StringRef) override;
+
+private:
+ const std::string &PrevName;
+ const std::vector<std::string> &USRs;
+ tooling::Replacements &Replaces;
+};
+
+}
+}
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H
Index: clang-rename/USRFinder.cpp
===================================================================
--- /dev/null
+++ clang-rename/USRFinder.cpp
@@ -0,0 +1,279 @@
+//===--- tools/extra/clang-rename/USRFinder.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 Implements a recursive AST visitor that finds the USR of a symbol at a
+/// point.
+///
+//===----------------------------------------------------------------------===//
+
+#include "USRFinder.h"
+#include "clang/AST/AST.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Index/USRGeneration.h"
+#include "llvm/ADT/SmallVector.h"
+
+using namespace llvm;
+
+namespace clang {
+namespace rename {
+
+// NamedDeclFindingASTVisitor recursively visits each AST node to find the
+// symbol underneath the cursor.
+// FIXME: move to seperate .h/.cc file if this gets too large.
+namespace {
+class NamedDeclFindingASTVisitor
+ : public clang::RecursiveASTVisitor<NamedDeclFindingASTVisitor> {
+public:
+ // |context| is the |ASTContext| used to look up locations, and is not owned
+ // by us. |point| is the location to search for the USR. |result| is where we
+ // store the decl found at the point.
+ explicit NamedDeclFindingASTVisitor(const SourceManager &SourceMgr,
+ const LangOptions &LangOpts,
+ const SourceLocation Point,
+ const clang::NamedDecl **Result)
+ : Result(nullptr), SourceMgr(SourceMgr), LangOpts(LangOpts),
+ Point(Point) {
+ }
+
+ // Declaration visitors:
+
+ // Checks if the point falls within the NameDecl. This covers every
+ // declaration of a named entity that we may come across. Usually, just
+ // checking if the point lies within the length of the name of the declaration
+ // and the start location is sufficient. With overloaded operators or
+ // conversion, however, we need to adjust the range to include the actual
+ // operator as well as the "operator" keyword.
+ bool VisitNamedDecl(const NamedDecl *Decl) {
+ const auto *FuncDecl = dyn_cast<FunctionDecl>(Decl);
+ if (FuncDecl && (FuncDecl->isOverloadedOperator() ||
+ isa<CXXConversionDecl>(FuncDecl))) {
+ // Since overloaded operator declarations consist of multiple tokens, we
+ // need to handle them specially so that whatever comes after the
+ // "operator" keyword is included in the range. Thus, for operators, we
+ // make the range that from the first 'o' to the end of the actual
+ // operator.
+ //
+ // getEndLoc will return the location of the first character of the last
+ // token in the name of the declaration. For example:
+ // operator += ();
+ // ^
+ // Thus, to get the end location, we offset the operator's location by the
+ // length of the operator's spelling.
+ SmallVector<char, 32> Buffer;
+ auto OpLoc = FuncDecl->getNameInfo().getEndLoc();
+ if (OpLoc.isValid() && OpLoc.isFileID()) {
+ auto OpName = Lexer::getSpelling(OpLoc, Buffer, SourceMgr, LangOpts);
+ // FIXME: something with warnings here.
+ return setResult(FuncDecl, FuncDecl->getLocation(),
+ OpLoc.getLocWithOffset(OpName.size() - 1));
+ }
+ // We couldn't find the end of the operator, so just range over the
+ // keyword "operator".
+ return setResult(FuncDecl, FuncDecl->getLocation(), strlen("operator"));
+ }
+ return setResult(Decl, Decl->getLocation(),
+ Decl->getNameAsString().length());
+ }
+
+ // Expression visitors:
+
+ // Determines if a CallExpr is an overloaded operator or conversion call, and
+ // if it is, checks if the point falls inside of the expression. Most call
+ // expressions are handled correctly via DeclRefExpr. For explicit overloaded
+ // operator and operator conversion calls we need to fix the range we consider
+ // within the name.
+ //
+ // Note that explicit operator calls are ones that begin with the keyword
+ // "operator", for example:
+ // foo.operator +=(bar);
+ // is an explicit operator call, while
+ // foo += bar;
+ // is an implicit call.
+ bool VisitCallExpr(const CallExpr *Expr) {
+ if (const auto *Decl = Expr->getDirectCallee()) {
+ if (!isa<CXXOperatorCallExpr>(Expr) && (Decl->isOverloadedOperator() ||
+ isa<CXXConversionDecl>(Decl))) {
+ // If we got here, then we found an explicit operator call or
+ // conversion. We fix the range of such calls from the leading 'o' to
+ // the end of the actual operator.
+ const auto *Callee = Expr->getCallee();
+ const auto *MemExpr = dyn_cast<MemberExpr>(Callee);
+ // If this is a member expression, the start location should be after
+ // the '.' or '->'.
+ auto LocStart =
+ ((MemExpr) ? MemExpr->getMemberLoc() : Expr->getLocStart());
+ // getLocEnd returns the start of the last token in the callee
+ // expression. Thus, for 'operator +=', it will return the location of
+ // the '+'.
+ auto OpLoc = Callee->getLocEnd();
+ if (OpLoc.isValid() && OpLoc.isFileID()) {
+ SmallVector<char, 32> Buffer;
+ auto OpName = Lexer::getSpelling(OpLoc, Buffer, SourceMgr, LangOpts);
+ return setResult(Decl, LocStart,
+ OpLoc.getLocWithOffset(OpName.size() - 1));
+ }
+ // If we couldn't get the location of the end of the operator, we just
+ // check in the range of the keyword "operator".
+ return setResult(Decl, LocStart, strlen("operator"));
+ }
+ }
+ return true;
+ }
+
+ bool VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *Expr) {
+ // CXXOperatorCallExprs are overloaded operator calls that are of implicit
+ // form and do not include ".operator" or "->operator" (see VisitCallExpr
+ // for an example).
+ if (const auto *Decl = Expr->getDirectCallee()) {
+ SmallVector<char, 32> Buffer;
+ auto OpLoc = Expr->getOperatorLoc();
+ // Get the spelling of the operator so we know its length.
+ auto TokenSpelling =
+ Lexer::getSpelling(OpLoc, Buffer, SourceMgr, LangOpts);
+ return setResult(Decl, OpLoc, TokenSpelling.size());
+ }
+ return true;
+ }
+
+ bool VisitDeclRefExpr(const DeclRefExpr *Expr) {
+ // Check the namespace specifier first.
+ if (!checkNestedNameSpecifierLoc(Expr->getQualifierLoc()))
+ return false;
+
+ if (isa<CXXOperatorCallExpr>(Expr))
+ return true;
+
+ const auto *Decl = Expr->getFoundDecl();
+ // DeclRefExpr includes overloaded operators. We want to handle those
+ // seperately, as determining the length of their spelling can be
+ // problematic. For example, one can call an overloaded addition operator by
+ // either foo + bar or foo.operator+(bar).
+ if (const auto *FuncDecl= dyn_cast<FunctionDecl>(Decl)) {
+ if (FuncDecl->isOverloadedOperator() && !isa<CallExpr>(Expr)) {
+ // If we got here, we are attempting to not call, but pass an overloaded
+ // operator method somewhere. We can only do this with the "operator"
+ // keyword.
+ auto Op = FuncDecl->getOverloadedOperator();
+ // Ignore operators we don't want to check because their renaming
+ // requires changing syntax.
+ if (Op == OO_None || Op == OO_Call || Op == OO_Subscript)
+ return true;
+ auto LocStart = Expr->getLocStart();
+ auto OpLoc = Expr->getLocEnd();
+ if (OpLoc.isValid() && OpLoc.isFileID()) {
+ auto OpName = clang::getOperatorSpelling(Op);
+ auto OpLocEnd = OpLoc.getLocWithOffset(strlen(OpName) - 1);
+ return setResult(Decl, LocStart, OpLocEnd);
+ }
+ // If we couldn't get the range of the operator, get the range of the
+ // operator keyword.
+ return setResult(Decl, LocStart, strlen("operator"));
+ }
+ }
+ return setResult(Decl, Expr->getLocation(),
+ Decl->getNameAsString().length());
+ }
+
+ bool VisitMemberExpr(const MemberExpr *Expr) {
+ const auto *Decl = Expr->getFoundDecl().getDecl();
+ return setResult(Decl, Expr->getMemberLoc(),
+ Decl->getNameAsString().length());
+ }
+
+ const NamedDecl *getNamedDecl() {
+ return Result;
+ }
+
+private:
+ // Check a namespace qualifier |name_loc|. If it contains |point_|, set
+ // |result_| and return false.
+ bool checkNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) {
+ while (NameLoc) {
+ const auto *Decl = NameLoc.getNestedNameSpecifier()->getAsNamespace();
+ if (Decl && !setResult(Decl, NameLoc.getLocalBeginLoc(),
+ Decl->getNameAsString().length()))
+ return false;
+ NameLoc = NameLoc.getPrefix();
+ }
+ return true;
+ }
+
+ // Sets |result_| if the |point_| is within |start| and |end|. Returns false
+ // if |result_| was set in order to terminate the traversal early. If |start|
+ // or |end| are not valid file locations |result_| will not be set.
+ bool setResult(const NamedDecl *Decl, SourceLocation Start,
+ SourceLocation End) {
+ if (!Start.isValid() || !Start.isFileID() || !End.isValid() ||
+ !End.isFileID() || !isPointWithin(Start, End)) {
+ return true;
+ }
+ Result = Decl;
+ return false;
+ }
+
+ // Sets |result_| if the |point_| is within |start| and the |offset|. Returns
+ // false if |result_| was set in order to terminate the traversal early. If
+ // |start| is not a valid file location, |result_| will not be set.
+ bool setResult(const NamedDecl *Decl, SourceLocation Loc,
+ unsigned Offset) {
+ return Offset == 0 ||
+ setResult(Decl, Loc, Loc.getLocWithOffset(Offset - 1));
+ }
+
+ bool isPointWithin(const SourceLocation Start, const SourceLocation End) {
+ return Point == Start || Point == End ||
+ (SourceMgr.isBeforeInTranslationUnit(Start, Point) &&
+ SourceMgr.isBeforeInTranslationUnit(Point, End));
+ }
+
+ const NamedDecl *Result;
+ const SourceManager &SourceMgr;
+ const LangOptions &LangOpts;
+ const SourceLocation Point; // The location to find the NamedDecl.
+};
+}
+
+const NamedDecl *getNamedDeclAt(const ASTContext &Context,
+ const SourceLocation Point) {
+ const auto &SourceMgr = Context.getSourceManager();
+ const auto &LangOpts = Context.getLangOpts();
+ const auto SearchFile = SourceMgr.getFilename(Point);
+ const NamedDecl *Result = nullptr;
+
+ NamedDeclFindingASTVisitor Visitor(SourceMgr, LangOpts, Point, &Result);
+
+ // We only want to search the decls that exist in the same file as the point.
+ auto Decls = Context.getTranslationUnitDecl()->decls();
+ for (auto &CurrDecl : Decls) {
+ const auto FileLoc = CurrDecl->getLocStart();
+ const auto FileName = SourceMgr.getFilename(FileLoc);
+ if (FileName == SearchFile) {
+ Visitor.TraverseDecl(CurrDecl);
+ Result = Visitor.getNamedDecl();
+ if (Result != nullptr)
+ break;
+ }
+ }
+
+ return Result;
+}
+
+std::string getUSRForDecl(const Decl *Decl) {
+ llvm::SmallVector<char, 128> Buff;
+
+ if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff))
+ return "";
+
+ return std::string(Buff.data(), Buff.size());
+}
+
+} // namespace clang
+} // namespace rename
Index: clang-rename/USRFinder.h
===================================================================
--- /dev/null
+++ clang-rename/USRFinder.h
@@ -0,0 +1,39 @@
+//===--- tools/extra/clang-rename/USRFinder.h - 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 Methods for determining the USR of a symbol at a location in source
+/// code.
+///
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H
+
+#include <string>
+
+namespace clang {
+class ASTContext;
+class Decl;
+class SourceLocation;
+class NamedDecl;
+
+namespace rename {
+
+// Given an AST context and a point, returns a NamedDecl identifying the symbol
+// at the point. Returns null if nothing is found at the point.
+const NamedDecl *getNamedDeclAt(const ASTContext &Context,
+ const SourceLocation Point);
+
+// Converts a Decl into a USR.
+std::string getUSRForDecl(const Decl *Decl);
+
+}
+}
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H
Index: clang-rename/USRFindingAction.cpp
===================================================================
--- /dev/null
+++ clang-rename/USRFindingAction.cpp
@@ -0,0 +1,117 @@
+//===--- tools/extra/clang-rename/USRFindingAction.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 Provides an action to rename every symbol at a point.
+///
+//===----------------------------------------------------------------------===//
+
+#include "USRFindingAction.h"
+#include "USRFinder.h"
+#include "clang/AST/AST.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Tooling.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string>
+#include <vector>
+
+using namespace llvm;
+
+namespace clang {
+namespace rename {
+
+// Get the USRs for the constructors of the class.
+static std::vector<std::string> getAllConstructorUSRs(
+ const CXXRecordDecl *Decl) {
+ std::vector<std::string> USRs;
+
+ // We need to get the definition of the record (as opposed to any forward
+ // declarations) in order to find the constructor and destructor.
+ const auto *RecordDecl = Decl->getDefinition();
+
+ // Iterate over all the constructors and add their USRs.
+ for (const auto &CtorDecl : RecordDecl->ctors())
+ USRs.push_back(getUSRForDecl(CtorDecl));
+
+ // Ignore destructors. GetLocationsOfUSR will find the declaration of and
+ // explicit calls to a destructor through TagTypeLoc (and it is better for the
+ // purpose of renaming).
+ //
+ // For example, in the following code segment,
+ // 1 class C {
+ // 2 ~C();
+ // 3 };
+ // At line 3, there is a NamedDecl starting from '~' and a TagTypeLoc starting
+ // from 'C'.
+
+ return USRs;
+}
+
+struct NamedDeclFindingConsumer : public ASTConsumer {
+ void HandleTranslationUnit(ASTContext &Context) override {
+ const auto &SourceMgr = Context.getSourceManager();
+ // The file we look for the USR in will always be the main source file.
+ const auto Point = SourceMgr.getLocForStartOfFile(
+ SourceMgr.getMainFileID()).getLocWithOffset(SymbolOffset);
+ if (!Point.isValid())
+ return;
+ const NamedDecl *FoundDecl = getNamedDeclAt(Context, Point);
+ if (FoundDecl == nullptr) {
+ FullSourceLoc FullLoc(Point, SourceMgr);
+ errs() << "clang-rename: could not find symbol at "
+ << SourceMgr.getFilename(Point) << ":"
+ << FullLoc.getSpellingLineNumber() << ":"
+ << FullLoc.getSpellingColumnNumber() << " (offset " << SymbolOffset
+ << ").\n";
+ return;
+ }
+
+ // If the decl is a constructor or destructor, we want to instead take the
+ // decl of the parent record.
+ if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FoundDecl))
+ FoundDecl = CtorDecl->getParent();
+ else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(FoundDecl))
+ FoundDecl = DtorDecl->getParent();
+
+ // If the decl is in any way relatedpp to a class, we want to make sure we
+ // search for the constructor and destructor as well as everything else.
+ if (const auto *Record = dyn_cast<CXXRecordDecl>(FoundDecl))
+ *USRs = getAllConstructorUSRs(Record);
+
+ USRs->push_back(getUSRForDecl(FoundDecl));
+ *PrevName = FoundDecl->getNameAsString();
+ }
+
+ unsigned SymbolOffset;
+ std::string *PrevName;
+ std::vector<std::string> *USRs;
+};
+
+ASTConsumer *USRFindingAction::CreateASTConsumer(CompilerInstance &CI,
+ StringRef File) {
+ auto *Consumer = new NamedDeclFindingConsumer;
+ PrevName = "";
+ Consumer->SymbolOffset = SymbolOffset;
+ Consumer->USRs = &USRs;
+ Consumer->PrevName = &PrevName;
+ return Consumer;
+}
+
+} // namespace rename
+} // namespace clang
Index: clang-rename/USRFindingAction.h
===================================================================
--- /dev/null
+++ clang-rename/USRFindingAction.h
@@ -0,0 +1,41 @@
+//===--- tools/extra/clang-rename/USRFindingAction.h - 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 Provides an action to find all relevent USRs at a point.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H_
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H_
+
+#include "clang/Frontend/FrontendAction.h"
+
+namespace clang {
+class ASTConsumer;
+class CompilerInstance;
+class NamedDecl;
+
+namespace rename {
+
+struct USRFindingAction : public ASTFrontendAction {
+ USRFindingAction(unsigned Offset) : SymbolOffset(Offset) {
+ }
+ ASTConsumer *CreateASTConsumer(CompilerInstance &, StringRef) override;
+
+ std::string PrevName;
+ std::vector<std::string> USRs;
+private:
+ unsigned SymbolOffset;
+};
+
+}
+}
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H_
Index: clang-rename/USRLocFinder.cpp
===================================================================
--- /dev/null
+++ clang-rename/USRLocFinder.cpp
@@ -0,0 +1,102 @@
+//===--- tools/extra/clang-rename/USRLocFinder.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 Mehtods for finding all instances of a USR. Our strategy is very
+/// simple; we just compare the USR at every relevant AST node with the one
+/// provided.
+///
+//===----------------------------------------------------------------------===//
+
+#include "USRLocFinder.h"
+#include "USRFinder.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Index/USRGeneration.h"
+#include "llvm/ADT/SmallVector.h"
+
+using namespace llvm;
+
+namespace clang {
+namespace rename {
+
+namespace {
+// This visitor recursively searches for all instances of a USR in a translation
+// unit and stores them for later usage.
+class USRLocFindingASTVisitor
+ : public clang::RecursiveASTVisitor<USRLocFindingASTVisitor> {
+public:
+ explicit USRLocFindingASTVisitor(const std::string USR) : USR(USR) {}
+
+ // Declaration visitors:
+
+ bool VisitNamedDecl(const NamedDecl *Decl) {
+ if (getUSRForDecl(Decl) == USR) {
+ LocationsFound.push_back(Decl->getLocation());
+ }
+ return true;
+ }
+
+ // Expression visitors:
+
+ bool VisitDeclRefExpr(const DeclRefExpr *Expr) {
+ const auto *Decl = Expr->getFoundDecl();
+
+ checkNestedNameSpecifierLoc(Expr->getQualifierLoc());
+ if (getUSRForDecl(Decl) == USR) {
+ LocationsFound.push_back(Expr->getLocation());
+ }
+
+ return true;
+ }
+
+ bool VisitMemberExpr(const MemberExpr *Expr) {
+ const auto *Decl = Expr->getFoundDecl().getDecl();
+ if (getUSRForDecl(Decl) == USR) {
+ LocationsFound.push_back(Expr->getMemberLoc());
+ }
+ return true;
+ }
+
+ // Non-visitors:
+
+ // Returns a list of unique locations. Duplicate or overlapping locations are
+ // erroneous and should be reported!
+ const std::vector<clang::SourceLocation> &getLocationsFound() const {
+ return LocationsFound;
+ }
+
+private:
+ // Namespace traversal:
+ void checkNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) {
+ while (NameLoc) {
+ const auto *Decl = NameLoc.getNestedNameSpecifier()->getAsNamespace();
+ if (Decl && getUSRForDecl(Decl) == USR)
+ LocationsFound.push_back(NameLoc.getLocalBeginLoc());
+ NameLoc = NameLoc.getPrefix();
+ }
+ }
+
+ // All the locations of the USR were found.
+ const std::string USR;
+ std::vector<clang::SourceLocation> LocationsFound;
+};
+} // namespace
+
+std::vector<SourceLocation> getLocationsOfUSR(const std::string USR,
+ Decl *Decl) {
+ USRLocFindingASTVisitor visitor(USR);
+
+ visitor.TraverseDecl(Decl);
+ return visitor.getLocationsFound();
+}
+
+} // namespace rename
+} // namespace clang
Index: clang-rename/USRLocFinder.h
===================================================================
--- /dev/null
+++ clang-rename/USRLocFinder.h
@@ -0,0 +1,35 @@
+//===--- tools/extra/clang-rename/USRLocFinder.h - 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 Provides functionality for finding all instances of a USR in a given
+/// AST.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H
+
+#include <string>
+#include <vector>
+
+namespace clang {
+
+class Decl;
+class SourceLocation;
+
+namespace rename {
+
+// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree!
+std::vector<SourceLocation> getLocationsOfUSR(const std::string usr,
+ Decl *decl);
+}
+}
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H
Index: test/CMakeLists.txt
===================================================================
--- test/CMakeLists.txt
+++ test/CMakeLists.txt
@@ -37,6 +37,7 @@
# Individual tools we test.
clang-apply-replacements
clang-modernize
+ clang-rename
clang-query
clang-tidy
modularize
Index: test/clang-rename/VarTest.cpp
===================================================================
--- /dev/null
+++ test/clang-rename/VarTest.cpp
@@ -0,0 +1,22 @@
+// RUN: cat %s > %t.cpp
+// RUN: clang-rename -offset=126 -new-name=hector %t.cpp -- | FileCheck %s < %t.cpp
+namespace A {
+int foo; // CHECK: int hector;
+}
+int foo; // CHECK: int foo;
+int bar = foo; // CHECK: bar = foo;
+int baz = A::foo; // CHECK: baz = A::hector;
+void fun1() {
+ struct {
+ int foo; // CHECK: int foo;
+ } b = { 100 };
+ int foo = 100; // CHECK: int foo
+ baz = foo; // CHECK: baz = foo;
+ {
+ extern int foo; // CHECK: int foo;
+ baz = foo; // CHECK: baz = foo;
+ foo = A::foo + baz; // CHECK: foo = A::hector + baz;
+ A::foo = b.foo; // CHECK: A::hector = b.foo;
+ }
+ foo = b.foo; // CHECK: foo = b.foo;
+}
Index: unittests/CMakeLists.txt
===================================================================
--- unittests/CMakeLists.txt
+++ unittests/CMakeLists.txt
@@ -7,5 +7,6 @@
add_subdirectory(clang-apply-replacements)
add_subdirectory(clang-modernize)
+add_subdirectory(clang-rename)
add_subdirectory(clang-query)
add_subdirectory(clang-tidy)
Index: unittests/clang-rename/CMakeLists.txt
===================================================================
--- /dev/null
+++ unittests/clang-rename/CMakeLists.txt
@@ -0,0 +1,33 @@
+set(LLVM_LINK_COMPONENTS
+ support
+ )
+
+get_filename_component(CLANG_RENAME_SOURCE_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-rename REALPATH)
+include_directories(
+ ${CLANG_RENAME_SOURCE_DIR}
+ )
+
+add_extra_unittest(ClangRenameTests
+ USRLocFindingTest.cpp
+ ${CLANG_RENAME_SOURCE_DIR}/USRFinder.cpp
+ ${CLANG_RENAME_SOURCE_DIR}/USRFindingAction.cpp
+ )
+
+target_link_libraries(ClangRenameTests
+ clangAnalysis
+ clangAST
+ clangBasic
+ clangDriver
+ clangEdit
+ clangFrontend
+ clangFrontendTool
+ clangIndex
+ clangLex
+ clangParse
+ clangRewrite
+ clangRewriteFrontend
+ clangSerialization
+ clangSema
+ clangTooling
+ )
\ No newline at end of file
Index: unittests/clang-rename/Makefile
===================================================================
--- /dev/null
+++ unittests/clang-rename/Makefile
@@ -0,0 +1,24 @@
+##===- unittests/clang-rename/Makefile ---------------------*- Makefile -*-===##
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+CLANG_LEVEL = ../../../..
+include $(CLANG_LEVEL)/../../Makefile.config
+
+TESTNAME = ClangRenameTests
+LINK_COMPONENTS := asmparser bitreader support MC MCParser option \
+ TransformUtils
+USEDLIBS = clangAnalysis.a clangAST.a clangBasic.a clangDriver.a clangEdit.a \
+ clangFrontend.a clangFrontendTool.a clangIndex.a clangLex.a \
+ clangParse.a clangRewrite.a clangRewriteFrontend.a \
+ clangSerialization.a clangSema.a clangTooling.a
+
+include $(CLANG_LEVEL)/Makefile
+MAKEFILE_UNITTEST_NO_INCLUDE_COMMON := 1
+CPP.Flags += -I(PROJ_SRC_DIR)/../../clang-rename
+include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest
Index: unittests/clang-rename/USRLocFindingTest.cpp
===================================================================
--- /dev/null
+++ unittests/clang-rename/USRLocFindingTest.cpp
@@ -0,0 +1,115 @@
+#include "USRFindingAction.h"
+#include "gtest/gtest.h"
+#include "clang/Tooling/Tooling.h"
+#include <stdio.h>
+#include <set>
+#include <map>
+#include <vector>
+
+namespace clang {
+namespace rename {
+namespace test {
+
+template <typename ActionT>
+inline std::unique_ptr<tooling::FrontendActionFactory>
+newFrontendActionFactory(ActionT *Action) {
+ // Renaming CreateASTConsumer to createASTConsumer doesn't work, so we have to
+ // create our own custom factory.
+ struct SimpleFactory : public tooling::FrontendActionFactory {
+ SimpleFactory(ActionT *Action) : Action(Action) {
+ }
+
+ FrontendAction *create() override {
+ return new ConsumerFactoryAdaptor(Action);
+ }
+
+ private:
+ struct ConsumerFactoryAdaptor : public ASTFrontendAction {
+ ConsumerFactoryAdaptor(ActionT *Action) : Action(Action) {
+ }
+
+ ASTConsumer *CreateASTConsumer(CompilerInstance &CI,
+ StringRef File) override {
+ return Action->CreateASTConsumer(CI, File);
+ }
+
+ private:
+ ActionT *Action;
+ };
+ ActionT *Action;
+ };
+
+ return std::unique_ptr<SimpleFactory>(new SimpleFactory(Action));
+}
+
+struct OffsetGroup {
+ unsigned offset;
+ unsigned group;
+};
+
+static void testOffsetGroups(const char *Code,
+ const std::vector<OffsetGroup> Groups) {
+ std::set<std::string> AllUSRs;
+ std::map<unsigned, std::string> USRMap;
+ unsigned NumGroups = 0;
+
+ for (const auto &Group : Groups) {
+ USRFindingAction Action(Group.offset);
+ auto Factory = newFrontendActionFactory(&Action);
+ EXPECT_TRUE(tooling::runToolOnCode(Factory->create(), Code));
+ EXPECT_EQ(1u, Action.USRs.size());
+ if (USRMap.find(Group.group) == USRMap.end()) {
+ NumGroups++;
+ USRMap[Group.group] = Action.USRs[0];
+ AllUSRs.insert(Action.USRs[0]);
+ } else
+ EXPECT_EQ(USRMap[Group.group], Action.USRs[0]);
+ }
+
+ EXPECT_EQ(NumGroups, AllUSRs.size());
+}
+
+
+TEST(USRLocFinding, FindsVarUSR) {
+ const char VarTest[] = "\
+namespace A {\n\
+int foo;\n\
+}\n\
+int foo;\n\
+int bar = foo;\n\
+int baz = A::foo;\n\
+void fun1() {\n\
+ struct {\n\
+ int foo;\n\
+ } b = { 100 };\n\
+ int foo = 100;\n\
+ baz = foo;\n\
+ {\n\
+ extern int foo;\n\
+ baz = foo;\n\
+ foo = A::foo + baz;\n\
+ A::foo = b.foo;\n\
+ }\n\
+ foo = b.foo;\n\
+}";
+ std::vector<OffsetGroup> VarTestOffsets = {
+ { 19, 0 },
+ { 63, 0 },
+ { 205, 0 },
+ { 223, 0 },
+ { 30, 1 },
+ { 45, 1 },
+ { 129, 2 },
+ { 148, 2 },
+ { 172, 1 },
+ { 187, 1 },
+ { 242, 2 },
+ };
+
+ testOffsetGroups(VarTest, VarTestOffsets);
+}
+
+
+}
+}
+}
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits