18#include "clang/AST/ASTContext.h"
19#include "clang/AST/ASTTypeTraits.h"
20#include "clang/AST/Decl.h"
21#include "clang/AST/DeclCXX.h"
22#include "clang/AST/DeclObjC.h"
23#include "clang/AST/DeclTemplate.h"
24#include "clang/AST/ParentMapContext.h"
25#include "clang/AST/Stmt.h"
26#include "clang/Basic/CharInfo.h"
27#include "clang/Basic/LLVM.h"
28#include "clang/Basic/SourceLocation.h"
29#include "clang/Tooling/Syntax/Tokens.h"
30#include "llvm/ADT/STLExtras.h"
31#include "llvm/ADT/StringExtras.h"
32#include "llvm/Support/Casting.h"
33#include "llvm/Support/Error.h"
34#include "llvm/Support/FormatVariadic.h"
35#include "llvm/Support/JSON.h"
43std::optional<std::string> filePath(
const SymbolLocation &
Loc,
44 llvm::StringRef HintFilePath) {
49 elog(
"Could not resolve URI {0}: {1}",
Loc.FileURI,
Path.takeError());
57bool isInMacroBody(
const SourceManager &SM, SourceLocation
Loc) {
58 while (
Loc.isMacroID()) {
59 if (SM.isMacroBodyExpansion(
Loc))
61 Loc = SM.getImmediateMacroCallerLoc(
Loc);
86const NamedDecl *canonicalRenameDecl(
const NamedDecl *D) {
87 if (
const auto *VarTemplate = dyn_cast<VarTemplateSpecializationDecl>(D))
88 return canonicalRenameDecl(
89 VarTemplate->getSpecializedTemplate()->getTemplatedDecl());
90 if (
const auto *Template = dyn_cast<TemplateDecl>(D))
91 if (
const NamedDecl *TemplatedDecl = Template->getTemplatedDecl())
92 return canonicalRenameDecl(TemplatedDecl);
93 if (
const auto *ClassTemplateSpecialization =
94 dyn_cast<ClassTemplateSpecializationDecl>(D))
95 return canonicalRenameDecl(
96 ClassTemplateSpecialization->getSpecializedTemplate()
97 ->getTemplatedDecl());
98 if (
const auto *
Method = dyn_cast<CXXMethodDecl>(D)) {
99 if (
Method->getDeclKind() == Decl::Kind::CXXConstructor ||
100 Method->getDeclKind() == Decl::Kind::CXXDestructor)
101 return canonicalRenameDecl(
Method->getParent());
102 if (
const FunctionDecl *InstantiatedMethod =
103 Method->getInstantiatedFromMemberFunction())
104 return canonicalRenameDecl(InstantiatedMethod);
109 if (
Method->isVirtual() &&
Method->size_overridden_methods())
110 return canonicalRenameDecl(*
Method->overridden_methods().begin());
112 if (
const auto *
Function = dyn_cast<FunctionDecl>(D))
113 if (
const FunctionTemplateDecl *Template =
Function->getPrimaryTemplate())
114 return canonicalRenameDecl(Template);
115 if (
const auto *
Field = dyn_cast<FieldDecl>(D)) {
120 const auto *FieldParent =
121 dyn_cast_or_null<CXXRecordDecl>(
Field->getParent());
123 return Field->getCanonicalDecl();
124 FieldParent = FieldParent->getTemplateInstantiationPattern();
126 if (!FieldParent ||
Field->getParent() == FieldParent)
127 return Field->getCanonicalDecl();
128 for (
const FieldDecl *
Candidate : FieldParent->fields())
131 elog(
"FieldParent should have field with the same name as Field.");
133 if (
const auto *VD = dyn_cast<VarDecl>(D)) {
134 if (
const VarDecl *OriginalVD = VD->getInstantiatedFromStaticDataMember())
135 return canonicalRenameDecl(OriginalVD);
137 if (
const auto *UD = dyn_cast<UsingShadowDecl>(D)) {
138 if (
const auto *TargetDecl = UD->getTargetDecl())
139 return canonicalRenameDecl(TargetDecl);
141 return dyn_cast<NamedDecl>(D->getCanonicalDecl());
146const NamedDecl *pickInterestingTarget(
const NamedDecl *D) {
150 if (
const auto *CD = dyn_cast<ObjCCategoryDecl>(D))
151 if (
const auto CI = CD->getClassInterface())
156llvm::DenseSet<const NamedDecl *> locateDeclAt(ParsedAST &
AST,
157 SourceLocation TokenStartLoc) {
159 AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second;
163 const SelectionTree::Node *SelectedNode = Selection.commonAncestor();
167 llvm::DenseSet<const NamedDecl *> Result;
168 for (
const NamedDecl *D :
171 AST.getHeuristicResolver())) {
172 D = pickInterestingTarget(D);
173 Result.insert(canonicalRenameDecl(D));
178void filterRenameTargets(llvm::DenseSet<const NamedDecl *> &Decls) {
186 auto UD = std::find_if(Decls.begin(), Decls.end(), [](
const NamedDecl *D) {
187 return llvm::isa<UsingDecl>(D);
189 if (UD != Decls.end()) {
197bool isExcluded(
const NamedDecl &RenameDecl) {
198 const auto &SM = RenameDecl.getASTContext().getSourceManager();
199 return SM.isInSystemHeader(RenameDecl.getLocation()) ||
203enum class ReasonToReject {
214std::optional<ReasonToReject> renameable(
const NamedDecl &RenameDecl,
215 StringRef MainFilePath,
216 const SymbolIndex *Index,
217 const RenameOptions &Opts) {
218 trace::Span Tracer(
"Renameable");
219 if (!Opts.RenameVirtual) {
220 if (
const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
222 return ReasonToReject::UnsupportedSymbol;
227 const auto *
ID = RenameDecl.getIdentifier();
228 if (!
ID && !isa<ObjCMethodDecl>(&RenameDecl))
229 return ReasonToReject::UnsupportedSymbol;
231 if (llvm::isa<NamespaceDecl>(&RenameDecl))
232 return ReasonToReject::UnsupportedSymbol;
233 if (
const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
234 if (FD->isOverloadedOperator())
235 return ReasonToReject::UnsupportedSymbol;
238 if (RenameDecl.getParentFunctionOrMethod())
241 if (isExcluded(RenameDecl))
242 return ReasonToReject::UnsupportedSymbol;
245 auto &ASTCtx = RenameDecl.getASTContext();
246 bool MainFileIsHeader =
isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
247 bool DeclaredInMainFile =
249 bool IsMainFileOnly =
true;
250 if (MainFileIsHeader)
252 IsMainFileOnly =
false;
253 else if (!DeclaredInMainFile)
254 IsMainFileOnly =
false;
257 RenameDecl, RenameDecl.getASTContext(), SymbolCollector::Options(),
259 return ReasonToReject::NonIndexable;
264llvm::Error makeError(ReasonToReject Reason) {
265 auto Message = [](ReasonToReject Reason) {
267 case ReasonToReject::NoSymbolFound:
268 return "there is no symbol at the given location";
269 case ReasonToReject::NoIndexProvided:
270 return "no index provided";
271 case ReasonToReject::NonIndexable:
272 return "symbol may be used in other files (not eligible for indexing)";
273 case ReasonToReject::UnsupportedSymbol:
274 return "symbol is not a supported kind (e.g. namespace, macro)";
275 case ReasonToReject::AmbiguousSymbol:
276 return "there are multiple symbols at the given location";
277 case ReasonToReject::SameName:
278 return "new name is the same as the old name";
280 llvm_unreachable(
"unhandled reason kind");
282 return error(
"Cannot rename symbol: {0}",
Message(Reason));
286std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &
AST,
287 const NamedDecl &ND) {
288 trace::Span Tracer(
"FindOccurrencesWithinFile");
289 assert(canonicalRenameDecl(&ND) == &ND &&
290 "ND should be already canonicalized.");
292 std::vector<SourceLocation>
Results;
293 for (
Decl *TopLevelDecl :
AST.getLocalTopLevelDecls()) {
296 [&](ReferenceLoc Ref) {
297 if (Ref.Targets.empty())
299 for (
const auto *Target : Ref.Targets) {
300 if (canonicalRenameDecl(Target) == &ND) {
301 Results.push_back(Ref.NameLoc);
306 AST.getHeuristicResolver());
313const NamedDecl *lookupSiblingWithinEnclosingScope(ASTContext &Ctx,
314 const NamedDecl &RenamedDecl,
318 DynTypedNodeList Storage(DynTypedNode::create(RenamedDecl));
319 auto GetSingleParent = [&](
const DynTypedNode &
Node) ->
const DynTypedNode * {
320 Storage = Ctx.getParents(Node);
321 return (Storage.size() == 1) ? Storage.begin() :
nullptr;
327 const auto *
Parent = GetSingleParent(DynTypedNode::create(RenamedDecl));
334 auto CheckDeclStmt = [&](
const DeclStmt *DS,
335 StringRef
Name) ->
const NamedDecl * {
338 for (
const auto &Child : DS->getDeclGroup())
339 if (
const auto *ND = dyn_cast<NamedDecl>(Child))
340 if (ND != &RenamedDecl && ND->getDeclName().isIdentifier() &&
341 ND->getName() ==
Name)
345 auto CheckCompoundStmt = [&](
const Stmt *S,
346 StringRef
Name) ->
const NamedDecl * {
347 if (
const auto *CS = dyn_cast_or_null<CompoundStmt>(S))
348 for (
const auto *Node : CS->children())
349 if (
const auto *Result = CheckDeclStmt(dyn_cast<DeclStmt>(Node),
Name))
353 auto CheckConditionVariable = [&](
const auto *Scope,
354 StringRef
Name) ->
const NamedDecl * {
357 return CheckDeclStmt(Scope->getConditionVariableDeclStmt(),
Name);
363 if (
const auto *EnclosingCS =
Parent->get<CompoundStmt>()) {
364 if (
const auto *Result = CheckCompoundStmt(EnclosingCS, NewName))
366 const auto *ScopeParent = GetSingleParent(*
Parent);
370 if (
const auto *Result =
371 CheckConditionVariable(ScopeParent->get<IfStmt>(), NewName))
373 if (
const auto *Result =
374 CheckConditionVariable(ScopeParent->get<WhileStmt>(), NewName))
376 if (
const auto *For = ScopeParent->get<ForStmt>())
377 if (
const auto *Result = CheckDeclStmt(
378 dyn_cast_or_null<DeclStmt>(For->getInit()), NewName))
381 if (
const auto *
Function = ScopeParent->get<FunctionDecl>())
390 if (
const auto *EnclosingIf =
Parent->get<IfStmt>()) {
391 if (
const auto *Result = CheckCompoundStmt(EnclosingIf->getElse(), NewName))
393 return CheckCompoundStmt(EnclosingIf->getThen(), NewName);
395 if (
const auto *EnclosingWhile =
Parent->get<WhileStmt>())
396 return CheckCompoundStmt(EnclosingWhile->getBody(), NewName);
397 if (
const auto *EnclosingFor =
Parent->get<ForStmt>()) {
400 if (
const auto *Result = CheckDeclStmt(
401 dyn_cast_or_null<DeclStmt>(EnclosingFor->getInit()), NewName))
403 return CheckCompoundStmt(EnclosingFor->getBody(), NewName);
423const NamedDecl *lookupSiblingsWithinContext(ASTContext &Ctx,
424 const NamedDecl &RenamedDecl,
425 llvm::StringRef NewName) {
426 const auto &II = Ctx.Idents.get(NewName);
427 DeclarationName LookupName(&II);
428 DeclContextLookupResult LookupResult;
429 const auto *DC = RenamedDecl.getDeclContext();
430 while (DC->isTransparentContext())
431 DC = DC->getParent();
432 switch (DC->getDeclKind()) {
441 case Decl::TranslationUnit:
442 case Decl::Namespace:
445 case Decl::CXXRecord:
446 LookupResult = DC->lookup(LookupName);
452 for (
const auto *D : LookupResult)
453 if (D->getCanonicalDecl() != RenamedDecl.getCanonicalDecl())
458const NamedDecl *lookupSiblingWithName(ASTContext &Ctx,
459 const NamedDecl &RenamedDecl,
460 llvm::StringRef NewName) {
461 trace::Span Tracer(
"LookupSiblingWithName");
462 if (
const auto *Result =
463 lookupSiblingsWithinContext(Ctx, RenamedDecl, NewName))
465 return lookupSiblingWithinEnclosingScope(Ctx, RenamedDecl, NewName);
477std::string
toString(InvalidName::Kind
K) {
479 case InvalidName::Keywords:
481 case InvalidName::Conflict:
483 case InvalidName::BadIdentifier:
484 return "BadIdentifier";
486 llvm_unreachable(
"unhandled InvalidName kind");
489llvm::Error makeError(InvalidName Reason) {
490 auto Message = [](
const InvalidName &Reason) {
492 case InvalidName::Keywords:
493 return llvm::formatv(
"the chosen name \"{0}\" is a keyword",
495 case InvalidName::Conflict:
496 return llvm::formatv(
"conflict with the symbol in {0}", Reason.Details);
497 case InvalidName::BadIdentifier:
498 return llvm::formatv(
"the chosen name \"{0}\" is not a valid identifier",
501 llvm_unreachable(
"unhandled InvalidName kind");
506static bool mayBeValidIdentifier(llvm::StringRef Ident,
bool AllowColon) {
507 assert(llvm::json::isUTF8(Ident));
511 bool AllowDollar =
true;
512 if (llvm::isASCII(Ident.front()) &&
513 !isAsciiIdentifierStart(Ident.front(), AllowDollar))
515 for (
char C : Ident) {
516 if (AllowColon &&
C ==
':')
518 if (llvm::isASCII(
C) && !isAsciiIdentifierContinue(
C, AllowDollar))
524std::string getName(
const NamedDecl &RenameDecl) {
525 if (
const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl))
526 return MD->getSelector().getAsString();
527 if (
const auto *
ID = RenameDecl.getIdentifier())
528 return ID->getName().str();
534llvm::Error checkName(
const NamedDecl &RenameDecl, llvm::StringRef NewName,
535 llvm::StringRef OldName) {
536 trace::Span Tracer(
"CheckName");
537 static constexpr trace::Metric InvalidNameMetric(
540 if (OldName == NewName)
541 return makeError(ReasonToReject::SameName);
543 if (
const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
544 const auto Sel = MD->getSelector();
545 if (Sel.getNumArgs() != NewName.count(
':') &&
546 NewName !=
"__clangd_rename_placeholder")
547 return makeError(InvalidName{InvalidName::BadIdentifier, NewName.str()});
550 auto &ASTCtx = RenameDecl.getASTContext();
551 std::optional<InvalidName> Result;
552 if (
isKeyword(NewName, ASTCtx.getLangOpts()))
553 Result = InvalidName{InvalidName::Keywords, NewName.str()};
554 else if (!mayBeValidIdentifier(NewName, isa<ObjCMethodDecl>(&RenameDecl)))
555 Result = InvalidName{InvalidName::BadIdentifier, NewName.str()};
559 if (RenameDecl.getKind() != Decl::Function &&
560 RenameDecl.getKind() != Decl::CXXMethod) {
561 if (
auto *Conflict = lookupSiblingWithName(ASTCtx, RenameDecl, NewName))
562 Result = InvalidName{
563 InvalidName::Conflict,
564 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
568 InvalidNameMetric.record(1,
toString(Result->K));
569 return makeError(*Result);
571 return llvm::Error::success();
574bool isSelectorLike(
const syntax::Token &Cur,
const syntax::Token &Next) {
575 return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
578 Cur.endLocation() == Next.location();
581bool isMatchingSelectorName(
const syntax::Token &Cur,
const syntax::Token &Next,
582 const SourceManager &SM,
583 llvm::StringRef SelectorName) {
584 if (SelectorName.empty())
585 return Cur.kind() == tok::colon;
586 return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
592std::optional<SymbolRange>
593findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
594 const SourceManager &SM, Selector Sel,
595 tok::TokenKind Terminator) {
596 assert(!Tokens.empty());
598 unsigned NumArgs = Sel.getNumArgs();
599 llvm::SmallVector<tok::TokenKind, 8> Closes;
600 std::vector<Range> SelectorPieces;
601 for (
unsigned Index = 0, Last = Tokens.size(); Index < Last - 1; ++Index) {
602 const auto &Tok = Tokens[Index];
604 if (Closes.empty()) {
605 auto PieceCount = SelectorPieces.size();
606 if (PieceCount < NumArgs &&
607 isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
608 Sel.getNameForSlot(PieceCount))) {
613 if (!Sel.getNameForSlot(PieceCount).empty())
615 SelectorPieces.push_back(
622 if (SelectorPieces.size() >= NumArgs &&
623 isSelectorLike(Tok, Tokens[Index + 1]))
627 if (Closes.empty() && Tok.kind() == Terminator)
628 return SelectorPieces.size() == NumArgs
629 ? std::optional(SymbolRange(SelectorPieces))
632 switch (Tok.kind()) {
634 Closes.push_back(tok::r_square);
637 Closes.push_back(tok::r_paren);
640 Closes.push_back(tok::r_brace);
645 if (Closes.empty() || Closes.back() != Tok.kind())
652 return SelectorPieces.size() == NumArgs
653 ? std::optional(SymbolRange(SelectorPieces))
668std::vector<SymbolRange> collectRenameIdentifierRanges(
669 llvm::StringRef
Identifier, llvm::StringRef Content,
670 const LangOptions &LangOpts, std::optional<Selector> Selector) {
671 std::vector<SymbolRange> Ranges;
673 auto IdentifierRanges =
675 for (
const auto &R : IdentifierRanges)
676 Ranges.emplace_back(R);
680 std::string NullTerminatedCode = Content.str();
681 SourceManagerForFile FileSM(
"mock_file_name.cpp", NullTerminatedCode);
682 auto &SM = FileSM.get();
687 llvm::SmallVector<tok::TokenKind, 8> Closes;
688 llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
690 auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
691 unsigned Last = Tokens.size() - 1;
692 for (
unsigned Index = 0; Index < Last; ++Index) {
693 const auto &Tok = Tokens[Index];
697 if ((Closes.empty() || Closes.back() == tok::r_square) &&
698 isMatchingSelectorName(Tok, Tokens[Index + 1], SM, FirstSelPiece)) {
719 auto SelectorRanges =
720 findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, *Selector,
721 Closes.empty() ? tok::l_brace : Closes.back());
723 Ranges.emplace_back(std::move(*SelectorRanges));
726 switch (Tok.kind()) {
728 Closes.push_back(tok::r_square);
731 Closes.push_back(tok::r_paren);
736 return std::vector<SymbolRange>();
738 if (Closes.back() == Tok.kind())
748clangd::Range tokenRangeForLoc(ParsedAST &
AST, SourceLocation TokLoc,
749 const SourceManager &SM,
750 const LangOptions &LangOpts) {
751 const auto *Token =
AST.getTokens().spelledTokenContaining(TokLoc);
752 assert(Token &&
"rename expects spelled tokens");
753 clangd::Range Result;
761llvm::Expected<tooling::Replacements>
762renameObjCMethodWithinFile(ParsedAST &
AST,
const ObjCMethodDecl *MD,
763 llvm::StringRef NewName,
764 std::vector<SourceLocation> SelectorOccurences) {
765 const SourceManager &SM =
AST.getSourceManager();
766 auto Code = SM.getBufferData(SM.getMainFileID());
767 auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
768 llvm::SmallVector<llvm::StringRef, 8> NewNames;
769 NewName.split(NewNames,
":");
771 std::vector<Range> Ranges;
772 const auto &LangOpts = MD->getASTContext().getLangOpts();
773 for (
const auto &
Loc : SelectorOccurences)
774 Ranges.push_back(tokenRangeForLoc(
AST,
Loc, SM, LangOpts));
775 auto FilePath =
AST.tuPath();
776 auto RenameRanges = collectRenameIdentifierRanges(
777 RenameIdentifier,
Code, LangOpts, MD->getSelector());
780 return error(
"failed to rename in file {0}: {1}", FilePath,
781 RenameEdit.takeError());
782 return RenameEdit->Replacements;
786llvm::Expected<tooling::Replacements>
787renameWithinFile(ParsedAST &
AST,
const NamedDecl &RenameDecl,
788 llvm::StringRef NewName) {
789 trace::Span Tracer(
"RenameWithinFile");
790 const SourceManager &SM =
AST.getSourceManager();
792 tooling::Replacements FilteredChanges;
793 std::vector<SourceLocation> Locs;
794 for (SourceLocation
Loc : findOccurrencesWithinFile(
AST, RenameDecl)) {
795 SourceLocation RenameLoc =
Loc;
798 if (RenameLoc.isMacroID()) {
799 if (isInMacroBody(SM, RenameLoc))
801 RenameLoc = SM.getSpellingLoc(
Loc);
812 Locs.push_back(RenameLoc);
814 if (
const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
819 if (MD->getSelector().getNumArgs() > 1)
820 return renameObjCMethodWithinFile(
AST, MD, NewName, std::move(Locs));
824 NewName.consume_back(
":");
826 for (
const auto &
Loc : Locs) {
827 if (
auto Err = FilteredChanges.add(tooling::Replacement(
828 SM, CharSourceRange::getTokenRange(
Loc), NewName)))
829 return std::move(Err);
831 return FilteredChanges;
836 R.start.line = L.Start.line();
837 R.start.character = L.Start.column();
838 R.end.line = L.End.line();
839 R.end.character = L.End.column();
846void insertTransitiveOverrides(SymbolID Base, llvm::DenseSet<SymbolID> &IDs,
847 const SymbolIndex &Index) {
848 RelationsRequest Req;
851 llvm::DenseSet<SymbolID> Pending = {Base};
852 while (!Pending.empty()) {
853 Req.Subjects = std::move(Pending);
856 Index.relations(Req, [&](
const SymbolID &,
const Symbol &Override) {
857 if (IDs.insert(Override.ID).second)
858 Pending.insert(Override.ID);
865llvm::Expected<llvm::StringMap<std::vector<Range>>>
866findOccurrencesOutsideFile(
const NamedDecl &RenameDecl,
867 llvm::StringRef
MainFile,
const SymbolIndex &Index,
868 size_t MaxLimitFiles) {
869 trace::Span Tracer(
"FindOccurrencesOutsideFile");
873 if (
const auto *MethodDecl = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl))
874 if (MethodDecl->isVirtual())
875 insertTransitiveOverrides(*RQuest.IDs.begin(), RQuest.IDs, Index);
878 llvm::StringMap<std::vector<Range>> AffectedFiles;
879 bool HasMore = Index.refs(RQuest, [&](
const Ref &R) {
880 if (AffectedFiles.size() >= MaxLimitFiles)
884 if (
auto RefFilePath = filePath(R.Location,
MainFile)) {
885 if (!pathEqual(*RefFilePath, MainFile))
886 AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
890 if (AffectedFiles.size() >= MaxLimitFiles)
891 return error(
"The number of affected files exceeds the max limit {0}",
894 return error(
"The symbol {0} has too many occurrences",
895 RenameDecl.getQualifiedNameAsString());
897 for (
auto &FileAndOccurrences : AffectedFiles) {
898 auto &Ranges = FileAndOccurrences.getValue();
900 Ranges.erase(std::unique(Ranges.begin(), Ranges.end()), Ranges.end());
903 static_cast<int64_t
>(Ranges.size()));
905 return AffectedFiles;
920llvm::Expected<FileEdits>
921renameOutsideFile(
const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
922 llvm::StringRef NewName,
const SymbolIndex &Index,
923 size_t MaxLimitFiles, llvm::vfs::FileSystem &FS) {
924 trace::Span Tracer(
"RenameOutsideFile");
925 auto AffectedFiles = findOccurrencesOutsideFile(RenameDecl, MainFilePath,
926 Index, MaxLimitFiles);
928 return AffectedFiles.takeError();
930 for (
auto &FileAndOccurrences : *AffectedFiles) {
931 llvm::StringRef FilePath = FileAndOccurrences.first();
933 auto ExpBuffer = FS.getBufferForFile(FilePath);
935 elog(
"Fail to read file content: Fail to open file {0}: {1}", FilePath,
936 ExpBuffer.getError().message());
939 std::string RenameIdentifier = RenameDecl.getNameAsString();
940 std::optional<Selector> Selector = std::nullopt;
941 llvm::SmallVector<llvm::StringRef, 8> NewNames;
942 if (
const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
943 RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
944 if (MD->getSelector().getNumArgs() > 1)
945 Selector = MD->getSelector();
947 NewName.split(NewNames,
":");
949 auto AffectedFileCode = (*ExpBuffer)->getBuffer();
952 std::move(FileAndOccurrences.second),
953 RenameDecl.getASTContext().getLangOpts(), Selector);
958 return error(
"Index results don't match the content of file {0} "
959 "(the index may be stale)",
965 return error(
"failed to rename in file {0}: {1}", FilePath,
966 RenameEdit.takeError());
967 if (!RenameEdit->Replacements.empty())
968 Results.insert({FilePath, std::move(*RenameEdit)});
974bool impliesSimpleEdit(
const Position &LHS,
const Position &RHS) {
975 return LHS.line == RHS.line || LHS.character == RHS.character;
990 std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest,
991 ArrayRef<SymbolRange> LexedRest,
int LexedIndex,
int &Fuel,
992 llvm::function_ref<
void(
const std::vector<size_t> &)> MatchedCB) {
995 if (IndexedRest.size() > LexedRest.size())
997 if (IndexedRest.empty()) {
998 MatchedCB(PartialMatch);
1001 if (impliesSimpleEdit(IndexedRest.front().start,
1002 LexedRest.front().range().start)) {
1003 PartialMatch.push_back(LexedIndex);
1004 findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(),
1005 LexedIndex + 1, Fuel, MatchedCB);
1006 PartialMatch.pop_back();
1008 findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(),
1009 LexedIndex + 1, Fuel, MatchedCB);
1014SymbolRange::SymbolRange(
Range R) : Ranges({R}) {}
1017 : Ranges(std::move(Ranges)) {}
1025 return !(LHS == RHS);
1032 assert(!RInputs.
Index == !RInputs.
FS &&
1033 "Index and FS must either both be specified or both null.");
1035 const auto &Opts = RInputs.
Opts;
1037 const SourceManager &SM =
AST.getSourceManager();
1038 llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
1042 return Loc.takeError();
1043 const syntax::Token *IdentifierToken =
1044 spelledIdentifierTouching(*
Loc,
AST.getTokens());
1047 if (!IdentifierToken)
1048 return makeError(ReasonToReject::NoSymbolFound);
1050 SM, CharSourceRange::getCharRange(IdentifierToken->location(),
1051 IdentifierToken->endLocation()));
1055 return makeError(ReasonToReject::UnsupportedSymbol);
1057 auto DeclsUnderCursor = locateDeclAt(
AST, IdentifierToken->location());
1058 filterRenameTargets(DeclsUnderCursor);
1059 if (DeclsUnderCursor.empty())
1060 return makeError(ReasonToReject::NoSymbolFound);
1061 if (DeclsUnderCursor.size() > 1)
1062 return makeError(ReasonToReject::AmbiguousSymbol);
1064 const auto &RenameDecl = **DeclsUnderCursor.begin();
1067 RenameTriggerCounter.
record(1, RenameDecl.getDeclKindName());
1069 std::string Placeholder = getName(RenameDecl);
1070 auto Invalid = checkName(RenameDecl, RInputs.
NewName, Placeholder);
1072 return std::move(Invalid);
1077 return makeError(*Reject);
1088 auto MainFileRenameEdit = renameWithinFile(
AST, RenameDecl, RInputs.
NewName);
1089 if (!MainFileRenameEdit)
1090 return MainFileRenameEdit.takeError();
1092 llvm::DenseSet<Range> RenamedRanges;
1093 if (!isa<ObjCMethodDecl>(RenameDecl)) {
1097 RenamedRanges.insert(CurrentIdentifier);
1105 for (
const auto &
Range : RenamedRanges) {
1109 return StartOffset.takeError();
1111 return EndOffset.takeError();
1113 *MainFileRenameEdit,
1114 [&StartOffset, &EndOffset](
const clang::tooling::Replacement &R) {
1115 return R.getOffset() == *StartOffset &&
1116 R.getLength() == *EndOffset - *StartOffset;
1118 return makeError(ReasonToReject::NoSymbolFound);
1122 Result.Target = CurrentIdentifier;
1123 Result.Placeholder = Placeholder;
1124 Edit MainFileEdits =
Edit(MainFileCode, std::move(*MainFileRenameEdit));
1126 Result.LocalChanges.push_back(TE.range);
1130 if (RenameDecl.getParentFunctionOrMethod()) {
1132 {std::make_pair(RInputs.
MainFilePath, std::move(MainFileEdits))});
1138 if (!RInputs.
Index) {
1139 assert(Result.GlobalChanges.empty());
1143 auto OtherFilesEdits = renameOutsideFile(
1145 Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
1148 if (!OtherFilesEdits)
1149 return OtherFilesEdits.takeError();
1150 Result.GlobalChanges = *OtherFilesEdits;
1153 std::move(MainFileEdits));
1158 llvm::StringRef InitialCode,
1159 std::vector<SymbolRange> Occurrences,
1160 llvm::ArrayRef<llvm::StringRef> NewNames) {
1164 static_cast<int64_t
>(Occurrences.size()));
1166 assert(llvm::is_sorted(Occurrences));
1167 assert(std::unique(Occurrences.begin(), Occurrences.end()) ==
1168 Occurrences.end() &&
1169 "Occurrences must be unique");
1173 size_t LastOffset = 0;
1176 assert(LastPos <= P &&
"malformed input");
1178 P.
line - LastPos.line,
1179 P.line > LastPos.line ? P.character : P.character - LastPos.character};
1180 auto ShiftedOffset =
1183 return error(
"fail to convert the position {0} to offset ({1})", P,
1184 ShiftedOffset.takeError());
1186 LastOffset += *ShiftedOffset;
1190 struct OccurrenceOffset {
1193 llvm::StringRef NewName;
1195 OccurrenceOffset(
size_t Start,
size_t End, llvm::StringRef NewName)
1196 : Start(Start), End(End), NewName(NewName) {}
1199 std::vector<OccurrenceOffset> OccurrencesOffsets;
1200 for (
const auto &SR : Occurrences) {
1201 for (
auto [
Range, NewName] : llvm::zip(SR.Ranges, NewNames)) {
1204 return StartOffset.takeError();
1207 return EndOffset.takeError();
1210 InitialCode.substr(*StartOffset, *EndOffset - *StartOffset);
1211 if (CurName == NewName)
1213 OccurrencesOffsets.emplace_back(*StartOffset, *EndOffset, NewName);
1217 tooling::Replacements RenameEdit;
1218 for (
const auto &R : OccurrencesOffsets) {
1219 auto ByteLength = R.End - R.Start;
1220 if (
auto Err = RenameEdit.add(
1221 tooling::Replacement(AbsFilePath, R.Start, ByteLength, R.NewName)))
1222 return std::move(Err);
1224 return Edit(InitialCode, std::move(RenameEdit));
1240std::optional<std::vector<SymbolRange>>
1242 std::vector<Range> Indexed,
const LangOptions &LangOpts,
1243 std::optional<Selector> Selector) {
1245 assert(!Indexed.empty());
1246 assert(llvm::is_sorted(Indexed));
1247 std::vector<SymbolRange> Lexed =
1248 collectRenameIdentifierRanges(
Identifier, DraftCode, LangOpts, Selector);
1253std::optional<std::vector<SymbolRange>>
1256 assert(!Indexed.empty());
1257 assert(llvm::is_sorted(Indexed));
1258 assert(llvm::is_sorted(Lexed));
1260 if (Indexed.size() > Lexed.size()) {
1261 vlog(
"The number of lexed occurrences is less than indexed occurrences");
1264 "The number of lexed occurrences is less than indexed occurrences");
1265 return std::nullopt;
1268 if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end()))
1271 std::vector<size_t> Best;
1272 size_t BestCost = std::numeric_limits<size_t>::max();
1273 bool HasMultiple =
false;
1274 std::vector<size_t> ResultStorage;
1276 findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel,
1277 [&](
const std::vector<size_t> &Matched) {
1280 if (MCost < BestCost) {
1282 Best = std::move(Matched);
1283 HasMultiple =
false;
1286 if (MCost == BestCost)
1290 vlog(
"The best near miss is not unique.");
1291 SPAN_ATTACH(Tracer,
"error",
"The best near miss is not unique");
1292 return std::nullopt;
1295 vlog(
"Didn't find a near miss.");
1296 SPAN_ATTACH(Tracer,
"error",
"Didn't find a near miss");
1297 return std::nullopt;
1299 std::vector<SymbolRange> Mapped;
1301 Mapped.push_back(Lexed[I]);
1302 SPAN_ATTACH(Tracer,
"mapped_ranges",
static_cast<int64_t
>(Mapped.size()));
1322 ArrayRef<SymbolRange> Lexed,
1323 ArrayRef<size_t> MappedIndex) {
1324 assert(Indexed.size() == MappedIndex.size());
1325 assert(llvm::is_sorted(Indexed));
1326 assert(llvm::is_sorted(Lexed));
1329 int LastDLine = 0, LastDColumn = 0;
1331 for (
size_t I = 0; I < Indexed.size(); ++I) {
1333 Indexed[I].start.line - Lexed[MappedIndex[I]].range().start.line;
1334 int DColumn = Indexed[I].start.character -
1335 Lexed[MappedIndex[I]].range().start.character;
1336 int Line = Indexed[I].start.line;
1339 Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn);
1340 std::tie(
LastLine, LastDLine, LastDColumn) = std::tie(
Line, DLine, DColumn);
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
std::vector< CodeCompletionResult > Results
SmallVector< Detail, DefaultLimit > Details
CharSourceRange Range
SourceRange for the file name.
::clang::DynTypedNode Node
std::unique_ptr< CompilerInvocation > CI
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Stores and provides access to parsed AST.
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.
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)
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
std::string Path
A typedef to represent a file path.
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
llvm::StringMap< Edit > FileEdits
A mapping from absolute file path (the one used for accessing the underlying VFS) to edits.
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.
bool operator!=(const SymbolRange &LHS, const SymbolRange &RHS)
std::optional< std::vector< SymbolRange > > adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier, std::vector< Range > Indexed, const LangOptions &LangOpts, std::optional< Selector > Selector)
Adjusts indexed occurrences to match the current state of the file.
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.
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.
constexpr llvm::StringLiteral Message
===– 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).
Position start
The range's start position.
Position end
The range's end position.
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.
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.