18 #include "clang/AST/DeclCXX.h"
19 #include "clang/AST/DeclTemplate.h"
20 #include "clang/Basic/SourceLocation.h"
21 #include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
22 #include "clang/Tooling/Syntax/Tokens.h"
23 #include "llvm/ADT/None.h"
24 #include "llvm/ADT/STLExtras.h"
25 #include "llvm/Support/Casting.h"
26 #include "llvm/Support/Error.h"
27 #include "llvm/Support/FormatVariadic.h"
34 llvm::Optional<std::string> filePath(
const SymbolLocation &
Loc,
35 llvm::StringRef HintFilePath) {
40 elog(
"Could not resolve URI {0}: {1}",
Loc.FileURI,
Path.takeError());
48 bool isInMacroBody(
const SourceManager &SM, SourceLocation
Loc) {
49 while (
Loc.isMacroID()) {
50 if (SM.isMacroBodyExpansion(
Loc))
52 Loc = SM.getImmediateMacroCallerLoc(
Loc);
59 llvm::Optional<std::string> getOtherRefFile(
const Decl &D, StringRef
MainFile,
60 const SymbolIndex &
Index) {
67 llvm::Optional<std::string> OtherFile;
71 if (
auto RefFilePath = filePath(R.Location,
MainFile)) {
72 if (*RefFilePath != MainFile)
73 OtherFile = *RefFilePath;
79 llvm::DenseSet<const NamedDecl *> locateDeclAt(ParsedAST &AST,
80 SourceLocation TokenStartLoc) {
86 const SelectionTree::Node *SelectedNode = Selection.
commonAncestor();
90 llvm::DenseSet<const NamedDecl *> Result;
91 for (
const NamedDecl *D :
95 D = tooling::getCanonicalSymbolDeclaration(D);
104 bool isExcluded(
const NamedDecl &RenameDecl) {
106 RenameDecl.getASTContext().getSourceManager()))
108 static const auto *StdSymbols =
new llvm::DenseSet<llvm::StringRef>({
109 #define SYMBOL(Name, NameSpace, Header) {#NameSpace #Name},
125 llvm::Optional<ReasonToReject> renameable(
const NamedDecl &RenameDecl,
126 StringRef MainFilePath,
131 if (llvm::isa<NamespaceDecl>(&RenameDecl))
132 return ReasonToReject::UnsupportedSymbol;
133 if (
const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
134 if (FD->isOverloadedOperator())
135 return ReasonToReject::UnsupportedSymbol;
138 if (RenameDecl.getParentFunctionOrMethod())
141 if (isExcluded(RenameDecl))
142 return ReasonToReject::UnsupportedSymbol;
145 auto &ASTCtx = RenameDecl.getASTContext();
146 bool MainFileIsHeader =
isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
147 bool DeclaredInMainFile =
149 bool IsMainFileOnly =
true;
150 if (MainFileIsHeader)
152 IsMainFileOnly =
false;
153 else if (!DeclaredInMainFile)
154 IsMainFileOnly =
false;
157 RenameDecl, RenameDecl.getASTContext(), SymbolCollector::Options(),
159 return ReasonToReject::NonIndexable;
162 if (!DeclaredInMainFile)
164 return ReasonToReject::UsedOutsideFile;
168 if (!MainFileIsHeader)
172 return ReasonToReject::NoIndexProvided;
174 auto OtherFile = getOtherRefFile(RenameDecl, MainFilePath, *
Index);
181 return ReasonToReject::UsedOutsideFile;
186 return ReasonToReject::NoIndexProvided;
191 if (
const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
193 return ReasonToReject::UnsupportedSymbol;
201 case ReasonToReject::NoSymbolFound:
202 return "there is no symbol at the given location";
203 case ReasonToReject::NoIndexProvided:
204 return "no index provided";
205 case ReasonToReject::UsedOutsideFile:
206 return "the symbol is used outside main file";
207 case ReasonToReject::NonIndexable:
208 return "symbol may be used in other files (not eligible for indexing)";
209 case ReasonToReject::UnsupportedSymbol:
210 return "symbol is not a supported kind (e.g. namespace, macro)";
211 case AmbiguousSymbol:
212 return "there are multiple symbols at the given location";
214 llvm_unreachable(
"unhandled reason kind");
216 return llvm::make_error<llvm::StringError>(
217 llvm::formatv(
"Cannot rename symbol: {0}",
Message(Reason)),
218 llvm::inconvertibleErrorCode());
222 std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST,
223 const NamedDecl &ND) {
224 trace::Span
Tracer(
"FindOccurrenceeWithinFile");
231 const auto *RenameDecl =
232 ND.getDescribedTemplate() ? ND.getDescribedTemplate() : &ND;
233 std::vector<std::string> RenameUSRs =
234 tooling::getUSRsForDeclaration(RenameDecl, AST.
getASTContext());
235 llvm::DenseSet<SymbolID> TargetIDs;
236 for (
auto &USR : RenameUSRs)
239 std::vector<SourceLocation>
Results;
242 if (Ref.Targets.empty())
244 for (
const auto *Target : Ref.Targets) {
246 if (!ID || TargetIDs.find(*ID) == TargetIDs.end())
249 Results.push_back(Ref.NameLoc);
257 llvm::Expected<tooling::Replacements>
258 renameWithinFile(ParsedAST &AST,
const NamedDecl &RenameDecl,
259 llvm::StringRef NewName) {
260 trace::Span
Tracer(
"RenameWithinFile");
263 tooling::Replacements FilteredChanges;
264 for (SourceLocation
Loc : findOccurrencesWithinFile(AST, RenameDecl)) {
265 SourceLocation RenameLoc =
Loc;
268 if (RenameLoc.isMacroID()) {
269 if (isInMacroBody(SM, RenameLoc))
271 RenameLoc = SM.getSpellingLoc(
Loc);
282 if (
auto Err = FilteredChanges.add(tooling::Replacement(
283 SM, CharSourceRange::getTokenRange(RenameLoc), NewName)))
284 return std::move(Err);
286 return FilteredChanges;
291 R.start.line = L.Start.line();
292 R.start.character = L.Start.column();
293 R.end.line = L.End.line();
294 R.end.character = L.End.column();
300 llvm::Expected<llvm::StringMap<std::vector<Range>>>
301 findOccurrencesOutsideFile(
const NamedDecl &RenameDecl,
303 size_t MaxLimitFiles) {
304 trace::Span
Tracer(
"FindOccurrencesOutsideFile");
309 llvm::StringMap<std::vector<Range>> AffectedFiles;
310 bool HasMore =
Index.
refs(RQuest, [&](
const Ref &R) {
311 if (AffectedFiles.size() >= MaxLimitFiles)
315 if (
auto RefFilePath = filePath(R.Location,
MainFile)) {
317 AffectedFiles[*RefFilePath].push_back(
toRange(R.Location));
321 if (AffectedFiles.size() >= MaxLimitFiles)
322 return llvm::make_error<llvm::StringError>(
323 llvm::formatv(
"The number of affected files exceeds the max limit {0}",
325 llvm::inconvertibleErrorCode());
327 return llvm::make_error<llvm::StringError>(
328 llvm::formatv(
"The symbol {0} has too many occurrences",
329 RenameDecl.getQualifiedNameAsString()),
330 llvm::inconvertibleErrorCode());
333 for (
auto &FileAndOccurrences : AffectedFiles) {
334 auto &Ranges = FileAndOccurrences.getValue();
336 Ranges.erase(std::unique(Ranges.begin(), Ranges.end()), Ranges.end());
339 static_cast<int64_t>(Ranges.size()));
341 return AffectedFiles;
356 llvm::Expected<FileEdits> renameOutsideFile(
357 const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
358 llvm::StringRef NewName,
const SymbolIndex &
Index,
size_t MaxLimitFiles,
359 llvm::function_ref<llvm::Expected<std::string>(
PathRef)> GetFileContent) {
360 trace::Span
Tracer(
"RenameOutsideFile");
361 auto AffectedFiles = findOccurrencesOutsideFile(RenameDecl, MainFilePath,
362 Index, MaxLimitFiles);
364 return AffectedFiles.takeError();
366 for (
auto &FileAndOccurrences : *AffectedFiles) {
367 llvm::StringRef FilePath = FileAndOccurrences.first();
369 auto AffectedFileCode = GetFileContent(FilePath);
370 if (!AffectedFileCode) {
371 elog(
"Fail to read file content: {0}", AffectedFileCode.takeError());
376 std::move(FileAndOccurrences.second),
377 RenameDecl.getASTContext().getLangOpts());
382 return llvm::make_error<llvm::StringError>(
383 llvm::formatv(
"Index results don't match the content of file {0} "
384 "(the index may be stale)",
386 llvm::inconvertibleErrorCode());
391 return llvm::make_error<llvm::StringError>(
392 llvm::formatv(
"fail to build rename edit for file {0}: {1}", FilePath,
394 llvm::inconvertibleErrorCode());
396 if (!RenameEdit->Replacements.empty())
397 Results.insert({FilePath, std::move(*RenameEdit)});
403 bool impliesSimpleEdit(
const Position &LHS,
const Position &RHS) {
404 return LHS.line == RHS.line || LHS.character == RHS.character;
419 std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest,
420 ArrayRef<Range> LexedRest,
int LexedIndex,
int &Fuel,
421 llvm::function_ref<
void(
const std::vector<size_t> &)> MatchedCB) {
424 if (IndexedRest.size() > LexedRest.size())
426 if (IndexedRest.empty()) {
427 MatchedCB(PartialMatch);
430 if (impliesSimpleEdit(IndexedRest.front().start, LexedRest.front().start)) {
431 PartialMatch.push_back(LexedIndex);
432 findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(),
433 LexedIndex + 1, Fuel, MatchedCB);
434 PartialMatch.pop_back();
436 findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(),
437 LexedIndex + 1, Fuel, MatchedCB);
444 const auto &Opts = RInputs.
Opts;
447 llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
448 auto GetFileContent = [&RInputs,
449 &SM](
PathRef AbsPath) -> llvm::Expected<std::string> {
450 llvm::Optional<std::string> DirtyBuffer;
453 return std::move(*DirtyBuffer);
456 SM.getFileManager().getVirtualFileSystem().getBufferForFile(AbsPath);
458 return llvm::createStringError(
459 llvm::inconvertibleErrorCode(),
460 llvm::formatv(
"Fail to open file {0}: {1}", AbsPath,
461 Content.getError().message()));
463 return llvm::createStringError(
464 llvm::inconvertibleErrorCode(),
465 llvm::formatv(
"Got no buffer for file {0}", AbsPath));
467 return (*Content)->getBuffer().str();
472 return Loc.takeError();
473 const syntax::Token *IdentifierToken =
476 if (!IdentifierToken)
477 return makeError(ReasonToReject::NoSymbolFound);
481 return makeError(ReasonToReject::UnsupportedSymbol);
483 auto DeclsUnderCursor = locateDeclAt(AST, IdentifierToken->location());
484 if (DeclsUnderCursor.empty())
485 return makeError(ReasonToReject::NoSymbolFound);
486 if (DeclsUnderCursor.size() > 1)
487 return makeError(ReasonToReject::AmbiguousSymbol);
489 const auto &RenameDecl =
490 llvm::cast<NamedDecl>(*(*DeclsUnderCursor.begin())->getCanonicalDecl());
492 Opts.AllowCrossFile);
505 auto MainFileRenameEdit = renameWithinFile(AST, RenameDecl, RInputs.
NewName);
506 if (!MainFileRenameEdit)
507 return MainFileRenameEdit.takeError();
511 if (!Opts.AllowCrossFile || RenameDecl.getParentFunctionOrMethod()) {
514 Edit{MainFileCode, std::move(*MainFileRenameEdit)})});
521 auto OtherFilesEdits = renameOutsideFile(
523 Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
526 if (!OtherFilesEdits)
527 return OtherFilesEdits.takeError();
528 Results = std::move(*OtherFilesEdits);
532 std::move(*MainFileRenameEdit));
537 llvm::StringRef InitialCode,
538 std::vector<Range> Occurrences,
539 llvm::StringRef NewName) {
543 static_cast<int64_t>(Occurrences.size()));
545 assert(std::is_sorted(Occurrences.begin(), Occurrences.end()));
546 assert(std::unique(Occurrences.begin(), Occurrences.end()) ==
548 "Occurrences must be unique");
552 size_t LastOffset = 0;
555 assert(LastPos <= P &&
"malformed input");
557 P.
line - LastPos.line,
558 P.line > LastPos.line ? P.character : P.character - LastPos.character};
562 return llvm::make_error<llvm::StringError>(
563 llvm::formatv(
"fail to convert the position {0} to offset ({1})", P,
565 llvm::inconvertibleErrorCode());
567 LastOffset += *ShiftedOffset;
571 std::vector<std::pair< size_t,
size_t>> OccurrencesOffsets;
572 for (
const auto &R : Occurrences) {
573 auto StartOffset =
Offset(R.start);
575 return StartOffset.takeError();
576 auto EndOffset =
Offset(R.end);
578 return EndOffset.takeError();
579 OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
582 tooling::Replacements RenameEdit;
583 for (
const auto &R : OccurrencesOffsets) {
584 auto ByteLength = R.second - R.first;
585 if (
auto Err = RenameEdit.add(
586 tooling::Replacement(AbsFilePath, R.first, ByteLength, NewName)))
587 return std::move(Err);
589 return Edit(InitialCode, std::move(RenameEdit));
605 llvm::Optional<std::vector<Range>>
607 std::vector<Range> Indexed,
const LangOptions &LangOpts) {
609 assert(!Indexed.empty());
610 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
611 std::vector<Range> Lexed =
618 ArrayRef<Range> Lexed) {
620 assert(!Indexed.empty());
621 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
622 assert(std::is_sorted(Lexed.begin(), Lexed.end()));
624 if (Indexed.size() > Lexed.size()) {
625 vlog(
"The number of lexed occurrences is less than indexed occurrences");
628 "The number of lexed occurrences is less than indexed occurrences");
632 if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end()))
633 return Indexed.vec();
635 std::vector<size_t> Best;
636 size_t BestCost = std::numeric_limits<size_t>::max();
637 bool HasMultiple = 0;
638 std::vector<size_t> ResultStorage;
640 findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel,
641 [&](
const std::vector<size_t> &Matched) {
644 if (MCost < BestCost) {
646 Best = std::move(Matched);
650 if (MCost == BestCost)
654 vlog(
"The best near miss is not unique.");
659 vlog(
"Didn't find a near miss.");
663 std::vector<Range> Mapped;
665 Mapped.push_back(Lexed[I]);
686 ArrayRef<size_t> MappedIndex) {
687 assert(Indexed.size() == MappedIndex.size());
688 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
689 assert(std::is_sorted(Lexed.begin(), Lexed.end()));
692 int LastDLine = 0, LastDColumn = 0;
694 for (
size_t I = 0; I < Indexed.size(); ++I) {
695 int DLine = Indexed[I].start.line - Lexed[MappedIndex[I]].start.line;
697 Indexed[I].start.character - Lexed[MappedIndex[I]].start.character;
698 int Line = Indexed[I].start.line;
699 if (
Line != LastLine)
701 Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn);
702 std::tie(LastLine, LastDLine, LastDColumn) = std::tie(
Line, DLine, DColumn);