Haojian Wu 7276a446ce [clangd] Narrow rename to local symbols.
Summary:
Previously, we performed rename for all kinds of symbols (local, global).

This patch narrows the scope by only renaming symbols not being used
outside of the main file (with index asisitance). Renaming global
symbols is not supported at the moment (return an error).

Reviewers: sammccall

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D63426

llvm-svn: 364283
2019-06-25 08:43:17 +00:00

279 lines
10 KiB
C++

//===--- RenamingAction.cpp - Clang refactoring library -------------------===//
//
// 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
/// Provides an action to rename every symbol at a point.
///
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/Rename/RenamingAction.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/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Refactoring/RefactoringAction.h"
#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
#include "clang/Tooling/Refactoring/RefactoringOptions.h"
#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include <string>
#include <vector>
using namespace llvm;
namespace clang {
namespace tooling {
namespace {
Expected<SymbolOccurrences>
findSymbolOccurrences(const NamedDecl *ND, RefactoringRuleContext &Context) {
std::vector<std::string> USRs =
getUSRsForDeclaration(ND, Context.getASTContext());
std::string PrevName = ND->getNameAsString();
return getOccurrencesOfUSRs(USRs, PrevName,
Context.getASTContext().getTranslationUnitDecl());
}
} // end anonymous namespace
const RefactoringDescriptor &RenameOccurrences::describe() {
static const RefactoringDescriptor Descriptor = {
"local-rename",
"Rename",
"Finds and renames symbols in code with no indexer support",
};
return Descriptor;
}
Expected<RenameOccurrences>
RenameOccurrences::initiate(RefactoringRuleContext &Context,
SourceRange SelectionRange, std::string NewName) {
const NamedDecl *ND =
getNamedDeclAt(Context.getASTContext(), SelectionRange.getBegin());
if (!ND)
return Context.createDiagnosticError(
SelectionRange.getBegin(), diag::err_refactor_selection_no_symbol);
return RenameOccurrences(getCanonicalSymbolDeclaration(ND),
std::move(NewName));
}
const NamedDecl *RenameOccurrences::getRenameDecl() const { return ND; }
Expected<AtomicChanges>
RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) {
Expected<SymbolOccurrences> Occurrences = findSymbolOccurrences(ND, Context);
if (!Occurrences)
return Occurrences.takeError();
// FIXME: Verify that the new name is valid.
SymbolName Name(NewName);
return createRenameReplacements(
*Occurrences, Context.getASTContext().getSourceManager(), Name);
}
Expected<QualifiedRenameRule>
QualifiedRenameRule::initiate(RefactoringRuleContext &Context,
std::string OldQualifiedName,
std::string NewQualifiedName) {
const NamedDecl *ND =
getNamedDeclFor(Context.getASTContext(), OldQualifiedName);
if (!ND)
return llvm::make_error<llvm::StringError>("Could not find symbol " +
OldQualifiedName,
llvm::errc::invalid_argument);
return QualifiedRenameRule(ND, std::move(NewQualifiedName));
}
const RefactoringDescriptor &QualifiedRenameRule::describe() {
static const RefactoringDescriptor Descriptor = {
/*Name=*/"local-qualified-rename",
/*Title=*/"Qualified Rename",
/*Description=*/
R"(Finds and renames qualified symbols in code within a translation unit.
It is used to move/rename a symbol to a new namespace/name:
* Supported symbols: classes, class members, functions, enums, and type alias.
* Renames all symbol occurrences from the old qualified name to the new
qualified name. All symbol references will be correctly qualified; For
symbol definitions, only name will be changed.
For example, rename "A::Foo" to "B::Bar":
Old code:
namespace foo {
class A {};
}
namespace bar {
void f(foo::A a) {}
}
New code after rename:
namespace foo {
class B {};
}
namespace bar {
void f(B b) {}
})"
};
return Descriptor;
}
Expected<AtomicChanges>
QualifiedRenameRule::createSourceReplacements(RefactoringRuleContext &Context) {
auto USRs = getUSRsForDeclaration(ND, Context.getASTContext());
assert(!USRs.empty());
return tooling::createRenameAtomicChanges(
USRs, NewQualifiedName, Context.getASTContext().getTranslationUnitDecl());
}
Expected<std::vector<AtomicChange>>
createRenameReplacements(const SymbolOccurrences &Occurrences,
const SourceManager &SM, const SymbolName &NewName) {
// FIXME: A true local rename can use just one AtomicChange.
std::vector<AtomicChange> Changes;
for (const auto &Occurrence : Occurrences) {
ArrayRef<SourceRange> Ranges = Occurrence.getNameRanges();
assert(NewName.getNamePieces().size() == Ranges.size() &&
"Mismatching number of ranges and name pieces");
AtomicChange Change(SM, Ranges[0].getBegin());
for (const auto &Range : llvm::enumerate(Ranges)) {
auto Error =
Change.replace(SM, CharSourceRange::getCharRange(Range.value()),
NewName.getNamePieces()[Range.index()]);
if (Error)
return std::move(Error);
}
Changes.push_back(std::move(Change));
}
return std::move(Changes);
}
/// Takes each atomic change and inserts its replacements into the set of
/// replacements that belong to the appropriate file.
static void convertChangesToFileReplacements(
ArrayRef<AtomicChange> AtomicChanges,
std::map<std::string, tooling::Replacements> *FileToReplaces) {
for (const auto &AtomicChange : AtomicChanges) {
for (const auto &Replace : AtomicChange.getReplacements()) {
llvm::Error Err = (*FileToReplaces)[Replace.getFilePath()].add(Replace);
if (Err) {
llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! "
<< llvm::toString(std::move(Err)) << "\n";
}
}
}
}
class RenamingASTConsumer : public ASTConsumer {
public:
RenamingASTConsumer(
const std::vector<std::string> &NewNames,
const std::vector<std::string> &PrevNames,
const std::vector<std::vector<std::string>> &USRList,
std::map<std::string, tooling::Replacements> &FileToReplaces,
bool PrintLocations)
: NewNames(NewNames), PrevNames(PrevNames), USRList(USRList),
FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {}
void HandleTranslationUnit(ASTContext &Context) override {
for (unsigned I = 0; I < NewNames.size(); ++I) {
// If the previous name was not found, ignore this rename request.
if (PrevNames[I].empty())
continue;
HandleOneRename(Context, NewNames[I], PrevNames[I], USRList[I]);
}
}
void HandleOneRename(ASTContext &Context, const std::string &NewName,
const std::string &PrevName,
const std::vector<std::string> &USRs) {
const SourceManager &SourceMgr = Context.getSourceManager();
SymbolOccurrences Occurrences = tooling::getOccurrencesOfUSRs(
USRs, PrevName, Context.getTranslationUnitDecl());
if (PrintLocations) {
for (const auto &Occurrence : Occurrences) {
FullSourceLoc FullLoc(Occurrence.getNameRanges()[0].getBegin(),
SourceMgr);
errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(FullLoc)
<< ":" << FullLoc.getSpellingLineNumber() << ":"
<< FullLoc.getSpellingColumnNumber() << "\n";
}
}
// FIXME: Support multi-piece names.
// FIXME: better error handling (propagate error out).
SymbolName NewNameRef(NewName);
Expected<std::vector<AtomicChange>> Change =
createRenameReplacements(Occurrences, SourceMgr, NewNameRef);
if (!Change) {
llvm::errs() << "Failed to create renaming replacements for '" << PrevName
<< "'! " << llvm::toString(Change.takeError()) << "\n";
return;
}
convertChangesToFileReplacements(*Change, &FileToReplaces);
}
private:
const std::vector<std::string> &NewNames, &PrevNames;
const std::vector<std::vector<std::string>> &USRList;
std::map<std::string, tooling::Replacements> &FileToReplaces;
bool PrintLocations;
};
// A renamer to rename symbols which are identified by a give USRList to
// new name.
//
// FIXME: Merge with the above RenamingASTConsumer.
class USRSymbolRenamer : public ASTConsumer {
public:
USRSymbolRenamer(const std::vector<std::string> &NewNames,
const std::vector<std::vector<std::string>> &USRList,
std::map<std::string, tooling::Replacements> &FileToReplaces)
: NewNames(NewNames), USRList(USRList), FileToReplaces(FileToReplaces) {
assert(USRList.size() == NewNames.size());
}
void HandleTranslationUnit(ASTContext &Context) override {
for (unsigned I = 0; I < NewNames.size(); ++I) {
// FIXME: Apply AtomicChanges directly once the refactoring APIs are
// ready.
auto AtomicChanges = tooling::createRenameAtomicChanges(
USRList[I], NewNames[I], Context.getTranslationUnitDecl());
convertChangesToFileReplacements(AtomicChanges, &FileToReplaces);
}
}
private:
const std::vector<std::string> &NewNames;
const std::vector<std::vector<std::string>> &USRList;
std::map<std::string, tooling::Replacements> &FileToReplaces;
};
std::unique_ptr<ASTConsumer> RenamingAction::newASTConsumer() {
return llvm::make_unique<RenamingASTConsumer>(NewNames, PrevNames, USRList,
FileToReplaces, PrintLocations);
}
std::unique_ptr<ASTConsumer> QualifiedRenamingAction::newASTConsumer() {
return llvm::make_unique<USRSymbolRenamer>(NewNames, USRList, FileToReplaces);
}
} // end namespace tooling
} // end namespace clang