forked from OSchip/llvm-project
192 lines
7.2 KiB
C++
192 lines
7.2 KiB
C++
//===--- Rename.cpp - Symbol-rename refactorings -----------------*- C++-*-===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "refactor/Rename.h"
|
|
#include "AST.h"
|
|
#include "Logger.h"
|
|
#include "index/SymbolCollector.h"
|
|
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
|
|
#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
|
|
#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
|
|
#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
llvm::Optional<std::string> filePath(const SymbolLocation &Loc,
|
|
llvm::StringRef HintFilePath) {
|
|
if (!Loc)
|
|
return None;
|
|
auto Uri = URI::parse(Loc.FileURI);
|
|
if (!Uri) {
|
|
elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError());
|
|
return None;
|
|
}
|
|
auto U = URIForFile::fromURI(*Uri, HintFilePath);
|
|
if (!U) {
|
|
elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError());
|
|
return None;
|
|
}
|
|
return U->file().str();
|
|
}
|
|
|
|
// Query the index to find some other files where the Decl is referenced.
|
|
llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
|
|
const SymbolIndex &Index) {
|
|
RefsRequest Req;
|
|
// We limit the number of results, this is a correctness/performance
|
|
// tradeoff. We expect the number of symbol references in the current file
|
|
// is smaller than the limit.
|
|
Req.Limit = 100;
|
|
if (auto ID = getSymbolID(&D))
|
|
Req.IDs.insert(*ID);
|
|
llvm::Optional<std::string> OtherFile;
|
|
Index.refs(Req, [&](const Ref &R) {
|
|
if (OtherFile)
|
|
return;
|
|
if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
|
|
if (*RefFilePath != MainFile)
|
|
OtherFile = *RefFilePath;
|
|
}
|
|
});
|
|
return OtherFile;
|
|
}
|
|
|
|
enum ReasonToReject {
|
|
NoSymbolFound,
|
|
NoIndexProvided,
|
|
NonIndexable,
|
|
UsedOutsideFile,
|
|
UnsupportedSymbol,
|
|
};
|
|
|
|
// Check the symbol Decl is renameable (per the index) within the file.
|
|
llvm::Optional<ReasonToReject> renamableWithinFile(const Decl &RenameDecl,
|
|
StringRef MainFile,
|
|
const SymbolIndex *Index) {
|
|
if (llvm::isa<NamespaceDecl>(&RenameDecl))
|
|
return ReasonToReject::UnsupportedSymbol;
|
|
auto &ASTCtx = RenameDecl.getASTContext();
|
|
const auto &SM = ASTCtx.getSourceManager();
|
|
bool MainFileIsHeader = ASTCtx.getLangOpts().IsHeaderFile;
|
|
bool DeclaredInMainFile = isInsideMainFile(RenameDecl.getBeginLoc(), SM);
|
|
|
|
// If the symbol is declared in the main file (which is not a header), we
|
|
// rename it.
|
|
if (DeclaredInMainFile && !MainFileIsHeader)
|
|
return None;
|
|
|
|
// Below are cases where the symbol is declared in the header.
|
|
// If the symbol is function-local, we rename it.
|
|
if (RenameDecl.getParentFunctionOrMethod())
|
|
return None;
|
|
|
|
if (!Index)
|
|
return ReasonToReject::NoIndexProvided;
|
|
|
|
bool IsIndexable = isa<NamedDecl>(RenameDecl) &&
|
|
SymbolCollector::shouldCollectSymbol(
|
|
cast<NamedDecl>(RenameDecl), ASTCtx, {}, false);
|
|
// If the symbol is not indexable, we disallow rename.
|
|
if (!IsIndexable)
|
|
return ReasonToReject::NonIndexable;
|
|
auto OtherFile = getOtherRefFile(RenameDecl, MainFile, *Index);
|
|
// If the symbol is indexable and has no refs from other files in the index,
|
|
// we rename it.
|
|
if (!OtherFile)
|
|
return None;
|
|
// If the symbol is indexable and has refs from other files in the index,
|
|
// we disallow rename.
|
|
return ReasonToReject::UsedOutsideFile;
|
|
}
|
|
|
|
llvm::Error makeError(ReasonToReject Reason) {
|
|
auto Message = [](ReasonToReject Reason) {
|
|
switch (Reason) {
|
|
case NoSymbolFound:
|
|
return "there is no symbol at the given location";
|
|
case NoIndexProvided:
|
|
return "symbol may be used in other files (no index available)";
|
|
case UsedOutsideFile:
|
|
return "the symbol is used outside main file";
|
|
case NonIndexable:
|
|
return "symbol may be used in other files (not eligible for indexing)";
|
|
case UnsupportedSymbol:
|
|
return "symbol is not a supported kind (e.g. namespace, macro)";
|
|
}
|
|
llvm_unreachable("unhandled reason kind");
|
|
};
|
|
return llvm::make_error<llvm::StringError>(
|
|
llvm::formatv("Cannot rename symbol: {0}", Message(Reason)),
|
|
llvm::inconvertibleErrorCode());
|
|
}
|
|
|
|
// Return all rename occurrences in the main file.
|
|
tooling::SymbolOccurrences
|
|
findOccurrencesWithinFile(ParsedAST &AST, const NamedDecl *RenameDecl) {
|
|
const NamedDecl *CanonicalRenameDecl =
|
|
tooling::getCanonicalSymbolDeclaration(RenameDecl);
|
|
assert(CanonicalRenameDecl && "RenameDecl must be not null");
|
|
std::vector<std::string> RenameUSRs =
|
|
tooling::getUSRsForDeclaration(CanonicalRenameDecl, AST.getASTContext());
|
|
std::string OldName = CanonicalRenameDecl->getNameAsString();
|
|
tooling::SymbolOccurrences Result;
|
|
for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) {
|
|
tooling::SymbolOccurrences RenameInDecl =
|
|
tooling::getOccurrencesOfUSRs(RenameUSRs, OldName, TopLevelDecl);
|
|
Result.insert(Result.end(), std::make_move_iterator(RenameInDecl.begin()),
|
|
std::make_move_iterator(RenameInDecl.end()));
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
llvm::Expected<tooling::Replacements>
|
|
renameWithinFile(ParsedAST &AST, llvm::StringRef File, Position Pos,
|
|
llvm::StringRef NewName, const SymbolIndex *Index) {
|
|
SourceLocation SourceLocationBeg = clangd::getBeginningOfIdentifier(
|
|
AST, Pos, AST.getSourceManager().getMainFileID());
|
|
// FIXME: renaming macros is not supported yet, the macro-handling code should
|
|
// be moved to rename tooling library.
|
|
if (locateMacroAt(SourceLocationBeg, AST.getPreprocessor()))
|
|
return makeError(UnsupportedSymbol);
|
|
|
|
const auto *RenameDecl =
|
|
tooling::getNamedDeclAt(AST.getASTContext(), SourceLocationBeg);
|
|
if (!RenameDecl)
|
|
return makeError(NoSymbolFound);
|
|
|
|
if (auto Reject =
|
|
renamableWithinFile(*RenameDecl->getCanonicalDecl(), File, Index))
|
|
return makeError(*Reject);
|
|
|
|
// Rename sometimes returns duplicate edits (which is a bug). A side-effect of
|
|
// adding them to a single Replacements object is these are deduplicated.
|
|
tooling::Replacements FilteredChanges;
|
|
for (const tooling::SymbolOccurrence &Rename :
|
|
findOccurrencesWithinFile(AST, RenameDecl)) {
|
|
// Currently, we only support normal rename (one range) for C/C++.
|
|
// FIXME: support multiple-range rename for objective-c methods.
|
|
if (Rename.getNameRanges().size() > 1)
|
|
continue;
|
|
// We shouldn't have conflicting replacements. If there are conflicts, it
|
|
// means that we have bugs either in clangd or in Rename library, therefore
|
|
// we refuse to perform the rename.
|
|
if (auto Err = FilteredChanges.add(tooling::Replacement(
|
|
AST.getASTContext().getSourceManager(),
|
|
CharSourceRange::getCharRange(Rename.getNameRanges()[0]), NewName)))
|
|
return std::move(Err);
|
|
}
|
|
return FilteredChanges;
|
|
}
|
|
|
|
} // namespace clangd
|
|
} // namespace clang
|