18#include "clang/AST/ASTContext.h"
19#include "clang/AST/ASTTypeTraits.h"
20#include "clang/AST/Decl.h"
21#include "clang/AST/DeclBase.h"
22#include "clang/AST/DeclCXX.h"
23#include "clang/AST/DeclObjC.h"
24#include "clang/AST/DeclTemplate.h"
25#include "clang/AST/DeclarationName.h"
26#include "clang/AST/ParentMapContext.h"
27#include "clang/AST/Stmt.h"
28#include "clang/Basic/CharInfo.h"
29#include "clang/Basic/LLVM.h"
30#include "clang/Basic/SourceLocation.h"
31#include "clang/Tooling/Syntax/Tokens.h"
32#include "llvm/ADT/STLExtras.h"
33#include "llvm/ADT/StringExtras.h"
34#include "llvm/Support/Casting.h"
35#include "llvm/Support/Error.h"
36#include "llvm/Support/FormatVariadic.h"
37#include "llvm/Support/JSON.h"
46 llvm::StringRef HintFilePath) {
51 elog(
"Could not resolve URI {0}: {1}", Loc.FileURI,
Path.takeError());
59bool isInMacroBody(
const SourceManager &SM, SourceLocation Loc) {
60 while (Loc.isMacroID()) {
61 if (SM.isMacroBodyExpansion(Loc))
63 Loc = SM.getImmediateMacroCallerLoc(Loc);
88const NamedDecl *canonicalRenameDecl(
const NamedDecl *D) {
89 if (
const auto *VarTemplate = dyn_cast<VarTemplateSpecializationDecl>(D))
90 return canonicalRenameDecl(
91 VarTemplate->getSpecializedTemplate()->getTemplatedDecl());
92 if (
const auto *Template = dyn_cast<TemplateDecl>(D))
93 if (
const NamedDecl *TemplatedDecl = Template->getTemplatedDecl())
94 return canonicalRenameDecl(TemplatedDecl);
95 if (
const auto *ClassTemplateSpecialization =
96 dyn_cast<ClassTemplateSpecializationDecl>(D))
97 return canonicalRenameDecl(
98 ClassTemplateSpecialization->getSpecializedTemplate()
99 ->getTemplatedDecl());
100 if (
const auto *
Method = dyn_cast<CXXMethodDecl>(D)) {
101 if (
Method->getDeclKind() == Decl::Kind::CXXConstructor ||
102 Method->getDeclKind() == Decl::Kind::CXXDestructor)
103 return canonicalRenameDecl(
Method->getParent());
104 if (
const FunctionDecl *InstantiatedMethod =
105 Method->getInstantiatedFromMemberFunction())
106 return canonicalRenameDecl(InstantiatedMethod);
111 if (
Method->isVirtual() &&
Method->size_overridden_methods())
112 return canonicalRenameDecl(*
Method->overridden_methods().begin());
114 if (
const auto *
Function = dyn_cast<FunctionDecl>(D))
115 if (
const FunctionTemplateDecl *Template =
Function->getPrimaryTemplate())
116 return canonicalRenameDecl(Template);
117 if (
const auto *
Field = dyn_cast<FieldDecl>(D)) {
122 const auto *FieldParent =
123 dyn_cast_or_null<CXXRecordDecl>(
Field->getParent());
125 return Field->getCanonicalDecl();
126 FieldParent = FieldParent->getTemplateInstantiationPattern();
128 if (!FieldParent ||
Field->getParent() == FieldParent)
129 return Field->getCanonicalDecl();
130 for (
const FieldDecl *Candidate : FieldParent->fields())
131 if (
Field->getDeclName() == Candidate->getDeclName())
132 return Candidate->getCanonicalDecl();
133 elog(
"FieldParent should have field with the same name as Field.");
135 if (
const auto *VD = dyn_cast<VarDecl>(D)) {
136 if (
const VarDecl *OriginalVD = VD->getInstantiatedFromStaticDataMember())
137 return canonicalRenameDecl(OriginalVD);
139 if (
const auto *UD = dyn_cast<UsingShadowDecl>(D)) {
140 if (
const auto *TargetDecl = UD->getTargetDecl())
141 return canonicalRenameDecl(TargetDecl);
143 return dyn_cast<NamedDecl>(
D->getCanonicalDecl());
148const NamedDecl *pickInterestingTarget(
const NamedDecl *D) {
152 if (
const auto *CD = dyn_cast<ObjCCategoryDecl>(D))
153 if (
const auto CI = CD->getClassInterface())
158llvm::DenseSet<const NamedDecl *> locateDeclAt(
ParsedAST &
AST,
159 SourceLocation TokenStartLoc) {
161 AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second;
164 AST.getASTContext(),
AST.getTokens(), Offset, Offset);
169 llvm::DenseSet<const NamedDecl *> Result;
170 for (
const NamedDecl *D :
173 AST.getHeuristicResolver())) {
174 D = pickInterestingTarget(D);
175 Result.insert(canonicalRenameDecl(D));
180void filterRenameTargets(llvm::DenseSet<const NamedDecl *> &Decls) {
188 auto UD = llvm::find_if(
189 Decls, [](
const NamedDecl *D) {
return llvm::isa<UsingDecl>(D); });
190 if (UD != Decls.end()) {
198bool isExcluded(
const NamedDecl &RenameDecl) {
199 const auto &SM = RenameDecl.getASTContext().getSourceManager();
200 return SM.isInSystemHeader(RenameDecl.getLocation()) ||
204enum class ReasonToReject {
215std::optional<ReasonToReject> renameable(
const NamedDecl &RenameDecl,
216 StringRef MainFilePath,
220 if (!Opts.RenameVirtual) {
221 if (
const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
223 return ReasonToReject::UnsupportedSymbol;
228 const auto *ID = RenameDecl.getIdentifier();
229 if (!ID && !isa<ObjCMethodDecl>(&RenameDecl))
230 return ReasonToReject::UnsupportedSymbol;
232 if (llvm::isa<NamespaceDecl>(&RenameDecl))
233 return ReasonToReject::UnsupportedSymbol;
234 if (
const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
235 if (FD->isOverloadedOperator())
236 return ReasonToReject::UnsupportedSymbol;
239 if (RenameDecl.getParentFunctionOrMethod())
242 if (isExcluded(RenameDecl))
243 return ReasonToReject::UnsupportedSymbol;
246 auto &ASTCtx = RenameDecl.getASTContext();
247 bool MainFileIsHeader =
isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
248 bool DeclaredInMainFile =
250 bool IsMainFileOnly =
true;
251 if (MainFileIsHeader)
253 IsMainFileOnly =
false;
254 else if (!DeclaredInMainFile)
255 IsMainFileOnly =
false;
260 return ReasonToReject::NonIndexable;
265llvm::Error makeError(ReasonToReject Reason) {
266 auto Message = [](ReasonToReject Reason) {
268 case ReasonToReject::NoSymbolFound:
269 return "there is no symbol at the given location";
270 case ReasonToReject::NoIndexProvided:
271 return "no index provided";
272 case ReasonToReject::NonIndexable:
273 return "symbol may be used in other files (not eligible for indexing)";
274 case ReasonToReject::UnsupportedSymbol:
275 return "symbol is not a supported kind (e.g. namespace, macro)";
276 case ReasonToReject::AmbiguousSymbol:
277 return "there are multiple symbols at the given location";
278 case ReasonToReject::SameName:
279 return "new name is the same as the old name";
281 llvm_unreachable(
"unhandled reason kind");
283 return error(
"Cannot rename symbol: {0}", Message(Reason));
287std::vector<SourceLocation> findOccurrencesWithinFile(
ParsedAST &
AST,
288 const NamedDecl &ND) {
290 assert(canonicalRenameDecl(&ND) == &ND &&
291 "ND should be already canonicalized.");
293 std::vector<SourceLocation> Results;
294 for (Decl *TopLevelDecl :
AST.getLocalTopLevelDecls()) {
298 if (
Ref.Targets.empty())
300 for (
const auto *Target :
Ref.Targets) {
301 if (canonicalRenameDecl(Target) == &ND) {
302 Results.push_back(
Ref.NameLoc);
307 AST.getHeuristicResolver());
314const NamedDecl *lookupSiblingWithinEnclosingScope(ASTContext &Ctx,
315 const NamedDecl &RenamedDecl,
319 DynTypedNodeList Storage(DynTypedNode::create(RenamedDecl));
320 auto GetSingleParent = [&](
const DynTypedNode &Node) ->
const DynTypedNode * {
321 Storage = Ctx.getParents(Node);
322 return (Storage.size() == 1) ? Storage.begin() :
nullptr;
328 const auto *Parent = GetSingleParent(DynTypedNode::create(RenamedDecl));
329 if (!Parent || !(Parent->get<DeclStmt>() || Parent->get<TypeLoc>()))
331 Parent = GetSingleParent(*Parent);
335 auto CheckDeclStmt = [&](
const DeclStmt *DS,
336 StringRef Name) ->
const NamedDecl * {
339 for (
const auto &Child : DS->getDeclGroup())
340 if (
const auto *ND = dyn_cast<NamedDecl>(Child))
341 if (ND != &RenamedDecl && ND->getDeclName().isIdentifier() &&
342 ND->getName() == Name &&
343 ND->getIdentifierNamespace() & RenamedDecl.getIdentifierNamespace())
347 auto CheckCompoundStmt = [&](
const Stmt *S,
348 StringRef Name) ->
const NamedDecl * {
349 if (
const auto *CS = dyn_cast_or_null<CompoundStmt>(S))
350 for (
const auto *Node : CS->children())
351 if (
const auto *Result = CheckDeclStmt(dyn_cast<DeclStmt>(Node), Name))
355 auto CheckConditionVariable = [&](
const auto *Scope,
356 StringRef Name) ->
const NamedDecl * {
359 return CheckDeclStmt(Scope->getConditionVariableDeclStmt(), Name);
365 if (
const auto *EnclosingCS = Parent->get<CompoundStmt>()) {
366 if (
const auto *Result = CheckCompoundStmt(EnclosingCS, NewName))
368 const auto *ScopeParent = GetSingleParent(*Parent);
372 if (
const auto *Result =
373 CheckConditionVariable(ScopeParent->get<IfStmt>(), NewName))
375 if (
const auto *Result =
376 CheckConditionVariable(ScopeParent->get<WhileStmt>(), NewName))
378 if (
const auto *For = ScopeParent->get<ForStmt>())
379 if (
const auto *Result = CheckDeclStmt(
380 dyn_cast_or_null<DeclStmt>(For->getInit()), NewName))
383 if (
const auto *
Function = ScopeParent->get<FunctionDecl>())
387 RenamedDecl.getIdentifierNamespace())
394 if (
const auto *EnclosingIf = Parent->get<IfStmt>()) {
395 if (
const auto *Result = CheckCompoundStmt(EnclosingIf->getElse(), NewName))
397 return CheckCompoundStmt(EnclosingIf->getThen(), NewName);
399 if (
const auto *EnclosingWhile = Parent->get<WhileStmt>())
400 return CheckCompoundStmt(EnclosingWhile->getBody(), NewName);
401 if (
const auto *EnclosingFor = Parent->get<ForStmt>()) {
404 if (
const auto *Result = CheckDeclStmt(
405 dyn_cast_or_null<DeclStmt>(EnclosingFor->getInit()), NewName))
407 return CheckCompoundStmt(EnclosingFor->getBody(), NewName);
409 if (
const auto *EnclosingFunction = Parent->get<FunctionDecl>()) {
411 for (
const auto *
Parameter : EnclosingFunction->parameters())
414 RenamedDecl.getIdentifierNamespace())
419 if (!EnclosingFunction->doesThisDeclarationHaveABody())
421 return CheckCompoundStmt(EnclosingFunction->getBody(), NewName);
429const NamedDecl *lookupSiblingsWithinContext(ASTContext &Ctx,
430 const NamedDecl &RenamedDecl,
431 llvm::StringRef NewName) {
432 const auto &II = Ctx.Idents.get(NewName);
433 DeclarationName LookupName(&II);
434 DeclContextLookupResult LookupResult;
435 const auto *DC = RenamedDecl.getDeclContext();
436 while (DC->isTransparentContext())
437 DC = DC->getParent();
438 switch (DC->getDeclKind()) {
447 case Decl::TranslationUnit:
448 case Decl::Namespace:
451 case Decl::CXXRecord:
452 LookupResult = DC->lookup(LookupName);
458 for (
const auto *D : LookupResult)
459 if (
D->getCanonicalDecl() != RenamedDecl.getCanonicalDecl() &&
460 D->getIdentifierNamespace() & RenamedDecl.getIdentifierNamespace())
465const NamedDecl *lookupSiblingWithName(ASTContext &Ctx,
466 const NamedDecl &RenamedDecl,
467 llvm::StringRef NewName) {
469 if (
const auto *Result =
470 lookupSiblingsWithinContext(Ctx, RenamedDecl, NewName))
472 return lookupSiblingWithinEnclosingScope(Ctx, RenamedDecl, NewName);
484std::string
toString(InvalidName::Kind K) {
486 case InvalidName::Keywords:
488 case InvalidName::Conflict:
490 case InvalidName::BadIdentifier:
491 return "BadIdentifier";
493 llvm_unreachable(
"unhandled InvalidName kind");
496llvm::Error makeError(InvalidName Reason) {
497 auto Message = [](
const InvalidName &Reason) {
499 case InvalidName::Keywords:
500 return llvm::formatv(
"the chosen name \"{0}\" is a keyword",
502 case InvalidName::Conflict:
503 return llvm::formatv(
"conflict with the symbol in {0}", Reason.Details);
504 case InvalidName::BadIdentifier:
505 return llvm::formatv(
"the chosen name \"{0}\" is not a valid identifier",
508 llvm_unreachable(
"unhandled InvalidName kind");
510 return error(
"invalid name: {0}", Message(Reason));
513static bool mayBeValidIdentifier(llvm::StringRef Ident,
bool AllowColon) {
514 assert(llvm::json::isUTF8(Ident));
518 bool AllowDollar =
true;
519 if (llvm::isASCII(Ident.front()) &&
520 !isAsciiIdentifierStart(Ident.front(), AllowDollar))
522 for (
char C : Ident) {
523 if (AllowColon && C ==
':')
525 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
531std::string getName(
const NamedDecl &RenameDecl) {
532 if (
const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl))
533 return MD->getSelector().getAsString();
534 if (
const auto *ID = RenameDecl.getIdentifier())
535 return ID->getName().str();
541llvm::Error checkName(
const NamedDecl &RenameDecl, llvm::StringRef NewName,
542 llvm::StringRef OldName) {
547 if (OldName == NewName)
548 return makeError(ReasonToReject::SameName);
550 if (
const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
551 const auto Sel = MD->getSelector();
552 if (Sel.getNumArgs() != NewName.count(
':') &&
553 NewName !=
"__clangd_rename_placeholder")
554 return makeError(InvalidName{InvalidName::BadIdentifier, NewName.str()});
557 auto &ASTCtx = RenameDecl.getASTContext();
558 std::optional<InvalidName> Result;
559 if (
isKeyword(NewName, ASTCtx.getLangOpts()))
560 Result = InvalidName{InvalidName::Keywords, NewName.str()};
561 else if (!mayBeValidIdentifier(NewName, isa<ObjCMethodDecl>(&RenameDecl)))
562 Result = InvalidName{InvalidName::BadIdentifier, NewName.str()};
566 if (RenameDecl.getKind() != Decl::Function &&
567 RenameDecl.getKind() != Decl::CXXMethod) {
568 if (
auto *Conflict = lookupSiblingWithName(ASTCtx, RenameDecl, NewName))
569 Result = InvalidName{
570 InvalidName::Conflict,
571 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
575 InvalidNameMetric.record(1,
toString(Result->K));
576 return makeError(*Result);
578 return llvm::Error::success();
581bool isSelectorLike(
const syntax::Token &Cur,
const syntax::Token &Next) {
582 return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
585 Cur.endLocation() == Next.location();
588bool isMatchingSelectorName(
const syntax::Token &Cur,
const syntax::Token &Next,
589 const SourceManager &SM,
590 llvm::StringRef SelectorName) {
591 if (SelectorName.empty())
592 return Cur.kind() == tok::colon;
593 return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
599std::optional<SymbolRange>
600findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
602 tok::TokenKind Terminator) {
603 assert(!Tokens.empty());
605 ArrayRef<std::string> NamePieces = Name.getNamePieces();
606 unsigned NumArgs = NamePieces.size();
607 llvm::SmallVector<tok::TokenKind, 8> Closes;
608 std::vector<Range> SelectorPieces;
609 for (
unsigned Index = 0, Last = Tokens.size(); Index < Last - 1; ++Index) {
610 const auto &Tok = Tokens[Index];
612 if (Closes.empty()) {
613 auto PieceCount = SelectorPieces.size();
614 if (PieceCount < NumArgs &&
615 isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
616 NamePieces[PieceCount])) {
621 if (!NamePieces[PieceCount].empty())
623 SelectorPieces.push_back(
630 if (SelectorPieces.size() >= NumArgs &&
631 isSelectorLike(Tok, Tokens[Index + 1]))
635 if (Closes.empty() && Tok.kind() == Terminator)
636 return SelectorPieces.size() == NumArgs
640 switch (Tok.kind()) {
642 Closes.push_back(tok::r_square);
645 Closes.push_back(tok::r_paren);
648 Closes.push_back(tok::r_brace);
653 if (Closes.empty() || Closes.back() != Tok.kind())
660 return SelectorPieces.size() == NumArgs
676std::vector<SymbolRange>
678 llvm::StringRef Content,
679 const LangOptions &LangOpts) {
680 std::vector<SymbolRange> Ranges;
681 if (
auto SinglePiece = Name.getSinglePiece()) {
682 auto IdentifierRanges =
684 for (
const auto &R : IdentifierRanges)
685 Ranges.emplace_back(R);
689 std::string NullTerminatedCode = Content.str();
690 SourceManagerForFile FileSM(
"mock_file_name.cpp", NullTerminatedCode);
691 auto &SM = FileSM.get();
696 llvm::SmallVector<tok::TokenKind, 8> Closes;
697 llvm::StringRef FirstSelPiece = Name.getNamePieces()[0];
699 auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
700 unsigned Last = Tokens.size() - 1;
701 for (
unsigned Index = 0; Index < Last; ++Index) {
702 const auto &Tok = Tokens[Index];
706 if ((Closes.empty() || Closes.back() == tok::r_square) &&
707 isMatchingSelectorName(Tok, Tokens[Index + 1], SM, FirstSelPiece)) {
728 auto SelectorRanges =
729 findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, Name,
730 Closes.empty() ? tok::l_brace : Closes.back());
732 Ranges.emplace_back(std::move(*SelectorRanges));
735 switch (Tok.kind()) {
737 Closes.push_back(tok::r_square);
740 Closes.push_back(tok::r_paren);
745 return std::vector<SymbolRange>();
747 if (Closes.back() == Tok.kind())
757clangd::Range tokenRangeForLoc(
ParsedAST &
AST, SourceLocation TokLoc,
758 const SourceManager &SM,
759 const LangOptions &LangOpts) {
760 const auto *
Token =
AST.getTokens().spelledTokenContaining(TokLoc);
761 assert(
Token &&
"rename expects spelled tokens");
762 clangd::Range Result;
770llvm::Expected<tooling::Replacements>
771renameObjCMethodWithinFile(
ParsedAST &
AST,
const ObjCMethodDecl *MD,
772 llvm::StringRef NewName,
773 std::vector<SourceLocation> SelectorOccurences) {
774 const SourceManager &SM =
AST.getSourceManager();
775 auto Code = SM.getBufferData(SM.getMainFileID());
776 llvm::SmallVector<llvm::StringRef, 8> NewNames;
777 NewName.split(NewNames,
":");
779 std::vector<Range> Ranges;
780 const auto &LangOpts = MD->getASTContext().getLangOpts();
781 for (
const auto &Loc : SelectorOccurences)
782 Ranges.push_back(tokenRangeForLoc(
AST, Loc, SM, LangOpts));
783 auto FilePath =
AST.tuPath();
784 auto RenameRanges = collectRenameIdentifierRanges(
786 auto RenameEdit =
buildRenameEdit(FilePath, Code, RenameRanges, NewNames);
788 return error(
"failed to rename in file {0}: {1}", FilePath,
789 RenameEdit.takeError());
790 return RenameEdit->Replacements;
794llvm::Expected<tooling::Replacements>
795renameWithinFile(
ParsedAST &
AST,
const NamedDecl &RenameDecl,
796 llvm::StringRef NewName) {
798 const SourceManager &SM =
AST.getSourceManager();
800 tooling::Replacements FilteredChanges;
801 std::vector<SourceLocation> Locs;
802 for (SourceLocation Loc : findOccurrencesWithinFile(
AST, RenameDecl)) {
803 SourceLocation RenameLoc = Loc;
806 if (RenameLoc.isMacroID()) {
807 if (isInMacroBody(SM, RenameLoc))
809 RenameLoc = SM.getSpellingLoc(Loc);
820 Locs.push_back(RenameLoc);
822 if (
const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
827 if (MD->getSelector().getNumArgs() > 1)
828 return renameObjCMethodWithinFile(
AST, MD, NewName, std::move(Locs));
832 NewName.consume_back(
":");
834 for (
const auto &Loc : Locs) {
835 if (
auto Err = FilteredChanges.add(tooling::Replacement(
836 SM, CharSourceRange::getTokenRange(Loc), NewName)))
837 return std::move(Err);
839 return FilteredChanges;
844 R.start.line = L.Start.line();
845 R.start.character = L.Start.column();
846 R.end.line = L.End.line();
847 R.end.character = L.End.column();
854void insertTransitiveOverrides(
SymbolID Base, llvm::DenseSet<SymbolID> &IDs,
859 llvm::DenseSet<SymbolID> Pending = {Base};
860 while (!Pending.empty()) {
861 Req.Subjects = std::move(Pending);
864 Index.relations(Req, [&](
const SymbolID &,
const Symbol &Override) {
865 if (IDs.insert(Override.ID).second)
866 Pending.insert(Override.ID);
873llvm::Expected<llvm::StringMap<std::vector<Range>>>
874findOccurrencesOutsideFile(
const NamedDecl &RenameDecl,
875 llvm::StringRef MainFile,
const SymbolIndex &Index,
876 size_t MaxLimitFiles) {
881 if (
const auto *MethodDecl = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl))
882 if (MethodDecl->isVirtual())
883 insertTransitiveOverrides(*RQuest.IDs.begin(), RQuest.IDs, Index);
886 llvm::StringMap<std::vector<Range>> AffectedFiles;
887 bool HasMore = Index.refs(RQuest, [&](
const Ref &R) {
888 if (AffectedFiles.size() >= MaxLimitFiles)
892 if (
auto RefFilePath = filePath(R.Location, MainFile)) {
894 AffectedFiles[*RefFilePath].push_back(
toRange(R.Location));
898 if (AffectedFiles.size() >= MaxLimitFiles)
899 return error(
"The number of affected files exceeds the max limit {0}",
902 return error(
"The symbol {0} has too many occurrences",
903 RenameDecl.getQualifiedNameAsString());
905 for (
auto &FileAndOccurrences : AffectedFiles) {
906 auto &Ranges = FileAndOccurrences.getValue();
908 Ranges.erase(llvm::unique(Ranges), Ranges.end());
911 static_cast<int64_t
>(Ranges.size()));
913 return AffectedFiles;
928llvm::Expected<FileEdits>
929renameOutsideFile(
const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
931 size_t MaxLimitFiles, llvm::vfs::FileSystem &FS) {
933 auto AffectedFiles = findOccurrencesOutsideFile(RenameDecl, MainFilePath,
934 Index, MaxLimitFiles);
936 return AffectedFiles.takeError();
938 for (
auto &FileAndOccurrences : *AffectedFiles) {
939 llvm::StringRef FilePath = FileAndOccurrences.first();
941 auto ExpBuffer = FS.getBufferForFile(FilePath);
943 elog(
"Fail to read file content: Fail to open file {0}: {1}", FilePath,
944 ExpBuffer.getError().message());
948 llvm::SmallVector<llvm::StringRef, 8> NewNames;
949 NewName.split(NewNames,
":");
951 auto AffectedFileCode = (*ExpBuffer)->getBuffer();
953 AffectedFileCode, RenameName, std::move(FileAndOccurrences.second),
954 RenameDecl.getASTContext().getLangOpts());
959 return error(
"Index results don't match the content of file {0} "
960 "(the index may be stale)",
966 return error(
"failed to rename in file {0}: {1}", FilePath,
967 RenameEdit.takeError());
968 if (!RenameEdit->Replacements.empty())
969 Results.insert({FilePath, std::move(*RenameEdit)});
976 return LHS.line == RHS.line || LHS.character == RHS.character;
991 std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest,
992 ArrayRef<SymbolRange> LexedRest,
int LexedIndex,
int &Fuel,
993 llvm::function_ref<
void(
const std::vector<size_t> &)> MatchedCB) {
996 if (IndexedRest.size() > LexedRest.size())
998 if (IndexedRest.empty()) {
999 MatchedCB(PartialMatch);
1002 if (impliesSimpleEdit(IndexedRest.front().start,
1003 LexedRest.front().range().start)) {
1004 PartialMatch.push_back(LexedIndex);
1005 findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(),
1006 LexedIndex + 1, Fuel, MatchedCB);
1007 PartialMatch.pop_back();
1009 findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(),
1010 LexedIndex + 1, Fuel, MatchedCB);
1018std::vector<std::string> extractNamePieces(
const DeclarationName &DeclName) {
1019 if (DeclName.getNameKind() ==
1020 DeclarationName::NameKind::ObjCMultiArgSelector ||
1021 DeclName.getNameKind() == DeclarationName::NameKind::ObjCOneArgSelector) {
1022 const Selector &Sel = DeclName.getObjCSelector();
1023 std::vector<std::string> Result;
1024 for (
unsigned Slot = 0; Slot < Sel.getNumArgs(); ++Slot) {
1025 Result.push_back(Sel.getNameForSlot(Slot).str());
1029 return {DeclName.getAsString()};
1037 for (
const auto &Piece : NamePieces)
1038 this->NamePieces.push_back(Piece);
1043 return NamePieces.front();
1045 return std::nullopt;
1050 llvm::raw_string_ostream OS(Result);
1056 llvm::interleave(NamePieces, OS,
":");
1070 return !(LHS == RHS);
1077 assert(!RInputs.
Index == !RInputs.
FS &&
1078 "Index and FS must either both be specified or both null.");
1080 const auto &Opts = RInputs.
Opts;
1082 const SourceManager &SM =
AST.getSourceManager();
1083 llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
1087 return Loc.takeError();
1088 const syntax::Token *IdentifierToken =
1089 spelledIdentifierTouching(*Loc,
AST.getTokens());
1092 if (!IdentifierToken)
1093 return makeError(ReasonToReject::NoSymbolFound);
1095 SM, CharSourceRange::getCharRange(IdentifierToken->location(),
1096 IdentifierToken->endLocation()));
1100 return makeError(ReasonToReject::UnsupportedSymbol);
1102 auto DeclsUnderCursor = locateDeclAt(
AST, IdentifierToken->location());
1103 filterRenameTargets(DeclsUnderCursor);
1104 if (DeclsUnderCursor.empty())
1105 return makeError(ReasonToReject::NoSymbolFound);
1106 if (DeclsUnderCursor.size() > 1)
1107 return makeError(ReasonToReject::AmbiguousSymbol);
1109 const auto &RenameDecl = **DeclsUnderCursor.begin();
1112 RenameTriggerCounter.
record(1, RenameDecl.getDeclKindName());
1114 std::string Placeholder = getName(RenameDecl);
1115 auto Invalid = checkName(RenameDecl, RInputs.
NewName, Placeholder);
1117 return std::move(Invalid);
1122 return makeError(*Reject);
1133 auto MainFileRenameEdit = renameWithinFile(
AST, RenameDecl, RInputs.
NewName);
1134 if (!MainFileRenameEdit)
1135 return MainFileRenameEdit.takeError();
1137 llvm::DenseSet<Range> RenamedRanges;
1138 if (!isa<ObjCMethodDecl>(RenameDecl)) {
1142 RenamedRanges.insert(CurrentIdentifier);
1150 for (
const auto &
Range : RenamedRanges) {
1154 return StartOffset.takeError();
1156 return EndOffset.takeError();
1158 *MainFileRenameEdit,
1159 [&StartOffset, &EndOffset](
const clang::tooling::Replacement &R) {
1160 return R.getOffset() == *StartOffset &&
1161 R.getLength() == *EndOffset - *StartOffset;
1163 return makeError(ReasonToReject::NoSymbolFound);
1167 Result.
Target = CurrentIdentifier;
1169 Edit MainFileEdits =
Edit(MainFileCode, std::move(*MainFileRenameEdit));
1175 if (RenameDecl.getParentFunctionOrMethod()) {
1177 {std::make_pair(RInputs.
MainFilePath, std::move(MainFileEdits))});
1183 if (!RInputs.
Index) {
1188 auto OtherFilesEdits = renameOutsideFile(
1190 Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
1193 if (!OtherFilesEdits)
1194 return OtherFilesEdits.takeError();
1198 std::move(MainFileEdits));
1203 llvm::StringRef InitialCode,
1204 std::vector<SymbolRange> Occurrences,
1205 llvm::ArrayRef<llvm::StringRef> NewNames) {
1209 static_cast<int64_t
>(Occurrences.size()));
1211 assert(llvm::is_sorted(Occurrences));
1212 assert(llvm::unique(Occurrences) == Occurrences.end() &&
1213 "Occurrences must be unique");
1217 size_t LastOffset = 0;
1219 auto Offset = [&](
const Position &P) -> llvm::Expected<size_t> {
1220 assert(LastPos <= P &&
"malformed input");
1222 P.line - LastPos.
line,
1223 P.line > LastPos.
line ? P.character : P.character - LastPos.
character};
1224 auto ShiftedOffset =
1227 return error(
"fail to convert the position {0} to offset ({1})", P,
1228 ShiftedOffset.takeError());
1230 LastOffset += *ShiftedOffset;
1234 struct OccurrenceOffset {
1237 llvm::StringRef NewName;
1239 OccurrenceOffset(
size_t Start,
size_t End, llvm::StringRef NewName)
1240 : Start(Start), End(End), NewName(NewName) {}
1243 std::vector<OccurrenceOffset> OccurrencesOffsets;
1244 for (
const auto &SR : Occurrences) {
1245 for (
auto [
Range, NewName] : llvm::zip(SR.Ranges, NewNames)) {
1248 return StartOffset.takeError();
1249 auto EndOffset = Offset(
Range.
end);
1251 return EndOffset.takeError();
1254 InitialCode.substr(*StartOffset, *EndOffset - *StartOffset);
1255 if (CurName == NewName)
1257 OccurrencesOffsets.emplace_back(*StartOffset, *EndOffset, NewName);
1261 tooling::Replacements RenameEdit;
1262 for (
const auto &R : OccurrencesOffsets) {
1263 auto ByteLength = R.End - R.Start;
1264 if (
auto Err = RenameEdit.add(
1265 tooling::Replacement(AbsFilePath, R.Start, ByteLength, R.NewName)))
1266 return std::move(Err);
1268 return Edit(InitialCode, std::move(RenameEdit));
1284std::optional<std::vector<SymbolRange>>
1286 std::vector<Range> Indexed,
const LangOptions &LangOpts) {
1288 assert(!Indexed.empty());
1289 assert(llvm::is_sorted(Indexed));
1290 std::vector<SymbolRange> Lexed =
1291 collectRenameIdentifierRanges(Name, DraftCode, LangOpts);
1296std::optional<std::vector<SymbolRange>>
1299 assert(!Indexed.empty());
1300 assert(llvm::is_sorted(Indexed));
1301 assert(llvm::is_sorted(Lexed));
1303 if (Indexed.size() > Lexed.size()) {
1304 vlog(
"The number of lexed occurrences is less than indexed occurrences");
1307 "The number of lexed occurrences is less than indexed occurrences");
1308 return std::nullopt;
1311 if (llvm::includes(Indexed, Lexed))
1314 std::vector<size_t> Best;
1315 size_t BestCost = std::numeric_limits<size_t>::max();
1316 bool HasMultiple =
false;
1317 std::vector<size_t> ResultStorage;
1319 findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel,
1320 [&](
const std::vector<size_t> &Matched) {
1323 if (MCost < BestCost) {
1325 Best = std::move(Matched);
1326 HasMultiple =
false;
1329 if (MCost == BestCost)
1333 vlog(
"The best near miss is not unique.");
1334 SPAN_ATTACH(Tracer,
"error",
"The best near miss is not unique");
1335 return std::nullopt;
1338 vlog(
"Didn't find a near miss.");
1339 SPAN_ATTACH(Tracer,
"error",
"Didn't find a near miss");
1340 return std::nullopt;
1342 std::vector<SymbolRange> Mapped;
1344 Mapped.push_back(Lexed[I]);
1345 SPAN_ATTACH(Tracer,
"mapped_ranges",
static_cast<int64_t
>(Mapped.size()));
1365 ArrayRef<SymbolRange> Lexed,
1366 ArrayRef<size_t> MappedIndex) {
1367 assert(Indexed.size() == MappedIndex.size());
1368 assert(llvm::is_sorted(Indexed));
1369 assert(llvm::is_sorted(Lexed));
1372 int LastDLine = 0, LastDColumn = 0;
1374 for (
size_t I = 0; I < Indexed.size(); ++I) {
1376 Indexed[I].start.line - Lexed[MappedIndex[I]].range().start.line;
1377 int DColumn = Indexed[I].start.character -
1378 Lexed[MappedIndex[I]].range().start.character;
1379 int Line = Indexed[I].start.line;
1380 if (Line != LastLine)
1382 Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn);
1383 std::tie(LastLine, LastDLine, LastDColumn) = std::tie(Line, DLine, DColumn);
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Stores and provides access to parsed AST.
A name of a symbol that should be renamed.
std::string getAsString() const
Returns a human-readable version of this symbol name.
ArrayRef< std::string > getNamePieces() const
void print(raw_ostream &OS) const
std::optional< std::string > getSinglePiece() const
If this symbol consists of a single piece return it, otherwise return None.
static SelectionTree createRight(ASTContext &AST, const syntax::TokenBuffer &Tokens, unsigned Begin, unsigned End)
static bool shouldCollectSymbol(const NamedDecl &ND, const ASTContext &ASTCtx, const Options &Opts, bool IsMainFileSymbol)
Returns true is ND should be collected.
Interface for symbol indexes that can be used for searching or matching symbols among a set of symbol...
static llvm::Expected< std::string > resolve(const URI &U, llvm::StringRef HintPath="")
Resolves the absolute path of U.
Records an event whose duration is the lifetime of the Span object.
Range toRange(llvm::SMRange R, const llvm::SourceMgr &SM)
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
bool pathEqual(PathRef A, PathRef B)
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
std::optional< std::vector< SymbolRange > > adjustRenameRanges(llvm::StringRef DraftCode, const RenameSymbolName &Name, std::vector< Range > Indexed, const LangOptions &LangOpts)
Adjusts indexed occurrences to match the current state of the file.
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
llvm::Expected< Edit > buildRenameEdit(llvm::StringRef AbsFilePath, llvm::StringRef InitialCode, std::vector< SymbolRange > Occurrences, llvm::ArrayRef< llvm::StringRef > NewNames)
Generates rename edits that replaces all given occurrences with the NewName.
void vlog(const char *Fmt, Ts &&... Vals)
static const char * toString(OffsetEncoding OE)
llvm::Expected< RenameResult > rename(const RenameInputs &RInputs)
Renames all occurrences of the symbol.
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out, const HeuristicResolver *Resolver)
Recursively traverse S and report all references explicitly written in the code.
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
bool operator==(const Inclusion &LHS, const Inclusion &RHS)
llvm::SmallVector< const NamedDecl *, 1 > targetDecl(const DynTypedNode &N, DeclRelationSet Mask, const HeuristicResolver *Resolver)
targetDecl() finds the declaration referred to by an AST node.
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
std::optional< DefinedMacro > locateMacroAt(const syntax::Token &SpelledTok, Preprocessor &PP)
Gets the macro referenced by SpelledTok.
llvm::StringMap< Edit > FileEdits
A mapping from absolute file path (the one used for accessing the underlying VFS) to edits.
bool operator!=(const SymbolRange &LHS, const SymbolRange &RHS)
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
bool operator<(const Ref &L, const Ref &R)
std::vector< Range > collectIdentifierRanges(llvm::StringRef Identifier, llvm::StringRef Content, const LangOptions &LangOpts)
Collects all ranges of the given identifier in the source code.
size_t renameRangeAdjustmentCost(ArrayRef< Range > Indexed, ArrayRef< SymbolRange > Lexed, ArrayRef< size_t > MappedIndex)
Evaluates how good the mapped result is.
llvm::Expected< SourceLocation > sourceLocationInMainFile(const SourceManager &SM, Position P)
Return the file location, corresponding to P.
@ Parameter
An inlay hint that is for a parameter.
std::string Path
A typedef to represent a file path.
bool isKeyword(llvm::StringRef NewName, const LangOptions &LangOpts)
Return true if the TokenName is in the list of reversed keywords of the language.
void elog(const char *Fmt, Ts &&... Vals)
bool isProtoFile(SourceLocation Loc, const SourceManager &SM)
Returns true if the given location is in a generated protobuf file.
@ TemplatePattern
This is the pattern the template specialization was instantiated from.
@ Alias
This declaration is an alias that was referred to.
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
std::optional< std::vector< SymbolRange > > getMappedRanges(ArrayRef< Range > Indexed, ArrayRef< SymbolRange > Lexed)
Calculates the lexed occurrences that the given indexed occurrences map to.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
A set of edits generated for a single file.
std::vector< TextEdit > asTextEdits() const
Represents Replacements as TextEdits that are available for use in LSP.
int line
Line position in a document (zero-based).
int character
Character offset on a line in a document (zero-based).
Position start
The range's start position.
Position end
The range's end position.
Represents a symbol occurrence in the source file.
Information about a reference written in the source code, independent of the actual AST node that thi...
llvm::DenseSet< SymbolID > IDs
std::vector< Range > LocalChanges
Represents a symbol range where the symbol can potentially have multiple tokens.
std::vector< Range > Ranges
Ranges for the tokens that make up the symbol's name.
Range range() const
Returns the first range.
The class presents a C++ symbol, e.g.
A single C++ or preprocessor token.
Represents measurements of clangd events, e.g.
@ Counter
An aggregate number whose rate of change over time is meaningful.
void record(double Value, llvm::StringRef Label="") const
Records a measurement for this metric to active tracer.