17 #include "clang/AST/DeclCXX.h" 18 #include "clang/AST/DeclTemplate.h" 19 #include "clang/Basic/SourceLocation.h" 20 #include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" 21 #include "clang/Tooling/Syntax/Tokens.h" 22 #include "llvm/ADT/None.h" 23 #include "llvm/ADT/STLExtras.h" 24 #include "llvm/Support/Casting.h" 25 #include "llvm/Support/Error.h" 26 #include "llvm/Support/FormatVariadic.h" 33 llvm::Optional<std::string> filePath(
const SymbolLocation &
Loc,
34 llvm::StringRef HintFilePath) {
39 elog(
"Could not resolve URI {0}: {1}", Loc.FileURI,
Path.takeError());
47 bool isInMacroBody(
const SourceManager &SM, SourceLocation Loc) {
48 while (Loc.isMacroID()) {
49 if (SM.isMacroBodyExpansion(Loc))
51 Loc = SM.getImmediateMacroCallerLoc(Loc);
58 llvm::Optional<std::string> getOtherRefFile(
const Decl &D, StringRef
MainFile,
59 const SymbolIndex &
Index) {
66 llvm::Optional<std::string> OtherFile;
67 Index.refs(Req, [&](
const Ref &R) {
70 if (
auto RefFilePath = filePath(R.Location, MainFile)) {
71 if (*RefFilePath != MainFile)
72 OtherFile = *RefFilePath;
78 llvm::DenseSet<const Decl *> locateDeclAt(ParsedAST &AST,
79 SourceLocation TokenStartLoc) {
81 AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second;
83 SelectionTree Selection(AST.getASTContext(), AST.getTokens(),
Offset);
84 const SelectionTree::Node *SelectedNode = Selection.commonAncestor();
88 llvm::DenseSet<const Decl *> Result;
89 for (
const NamedDecl *D :
93 D = tooling::getCanonicalSymbolDeclaration(D);
108 llvm::Optional<ReasonToReject> renameable(
const Decl &RenameDecl,
109 StringRef MainFilePath,
113 if (llvm::isa<NamespaceDecl>(&RenameDecl))
114 return ReasonToReject::UnsupportedSymbol;
115 if (
const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
116 if (FD->isOverloadedOperator())
117 return ReasonToReject::UnsupportedSymbol;
120 if (RenameDecl.getParentFunctionOrMethod())
124 auto &ASTCtx = RenameDecl.getASTContext();
125 bool MainFileIsHeader =
isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
126 bool DeclaredInMainFile =
128 bool IsMainFileOnly =
true;
129 if (MainFileIsHeader)
131 IsMainFileOnly =
false;
132 else if (!DeclaredInMainFile)
133 IsMainFileOnly =
false;
135 isa<NamedDecl>(RenameDecl) &&
137 cast<NamedDecl>(RenameDecl), RenameDecl.getASTContext(),
140 return ReasonToReject::NonIndexable;
143 if (!DeclaredInMainFile)
145 return ReasonToReject::UsedOutsideFile;
149 if (!MainFileIsHeader)
153 return ReasonToReject::NoIndexProvided;
155 auto OtherFile = getOtherRefFile(RenameDecl, MainFilePath, *Index);
162 return ReasonToReject::UsedOutsideFile;
167 return ReasonToReject::NoIndexProvided;
173 if (RenameDecl.getDescribedTemplate())
174 return ReasonToReject::UnsupportedSymbol;
179 if (
const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
181 return ReasonToReject::UnsupportedSymbol;
189 case ReasonToReject::NoSymbolFound:
190 return "there is no symbol at the given location";
191 case ReasonToReject::NoIndexProvided:
192 return "no index provided";
193 case ReasonToReject::UsedOutsideFile:
194 return "the symbol is used outside main file";
195 case ReasonToReject::NonIndexable:
196 return "symbol may be used in other files (not eligible for indexing)";
197 case ReasonToReject::UnsupportedSymbol:
198 return "symbol is not a supported kind (e.g. namespace, macro)";
199 case AmbiguousSymbol:
200 return "there are multiple symbols at the given location";
202 llvm_unreachable(
"unhandled reason kind");
204 return llvm::make_error<llvm::StringError>(
205 llvm::formatv(
"Cannot rename symbol: {0}",
Message(Reason)),
206 llvm::inconvertibleErrorCode());
210 std::vector<SourceLocation> findOccurrencesWithinFile(
ParsedAST &AST,
211 const NamedDecl &ND) {
218 const auto RenameDecl =
219 ND.getDescribedTemplate() ? ND.getDescribedTemplate() : &ND;
220 std::vector<std::string> RenameUSRs =
221 tooling::getUSRsForDeclaration(RenameDecl, AST.
getASTContext());
222 llvm::DenseSet<SymbolID> TargetIDs;
223 for (
auto &USR : RenameUSRs)
226 std::vector<SourceLocation>
Results;
231 for (
const auto *Target : Ref.
Targets) {
233 if (!ID || TargetIDs.find(*ID) == TargetIDs.end())
236 Results.push_back(Ref.
NameLoc);
244 llvm::Expected<tooling::Replacements>
245 renameWithinFile(
ParsedAST &AST,
const NamedDecl &RenameDecl,
246 llvm::StringRef NewName) {
249 tooling::Replacements FilteredChanges;
250 for (SourceLocation Loc : findOccurrencesWithinFile(AST, RenameDecl)) {
251 SourceLocation RenameLoc =
Loc;
254 if (RenameLoc.isMacroID()) {
255 if (isInMacroBody(SM, RenameLoc))
257 RenameLoc = SM.getSpellingLoc(Loc);
268 if (
auto Err = FilteredChanges.add(tooling::Replacement(
270 return std::move(Err);
272 return FilteredChanges;
286 llvm::Expected<llvm::StringMap<std::vector<Range>>>
287 findOccurrencesOutsideFile(
const NamedDecl &RenameDecl,
288 llvm::StringRef MainFile,
const SymbolIndex &Index) {
293 llvm::StringMap<std::vector<Range>> AffectedFiles;
295 static constexpr
size_t MaxLimitFiles = 50;
296 bool HasMore = Index.
refs(RQuest, [&](
const Ref &R) {
297 if (AffectedFiles.size() > MaxLimitFiles)
299 if (
auto RefFilePath = filePath(R.
Location, MainFile)) {
300 if (*RefFilePath != MainFile)
301 AffectedFiles[*RefFilePath].push_back(toRange(R.
Location));
305 if (AffectedFiles.size() > MaxLimitFiles)
306 return llvm::make_error<llvm::StringError>(
307 llvm::formatv(
"The number of affected files exceeds the max limit {0}",
309 llvm::inconvertibleErrorCode());
311 return llvm::make_error<llvm::StringError>(
312 llvm::formatv(
"The symbol {0} has too many occurrences",
313 RenameDecl.getQualifiedNameAsString()),
314 llvm::inconvertibleErrorCode());
317 for (
auto &FileAndOccurrences : AffectedFiles) {
318 auto &Ranges = FileAndOccurrences.getValue();
320 Ranges.erase(std::unique(Ranges.begin(), Ranges.end()), Ranges.end());
322 return AffectedFiles;
342 llvm::Expected<FileEdits> renameOutsideFile(
343 const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
345 llvm::function_ref<llvm::Expected<std::string>(
PathRef)> GetFileContent) {
347 findOccurrencesOutsideFile(RenameDecl, MainFilePath, Index);
349 return AffectedFiles.takeError();
351 for (
auto &FileAndOccurrences : *AffectedFiles) {
352 llvm::StringRef FilePath = FileAndOccurrences.first();
354 auto AffectedFileCode = GetFileContent(FilePath);
355 if (!AffectedFileCode) {
356 elog(
"Fail to read file content: {0}", AffectedFileCode.takeError());
361 std::move(FileAndOccurrences.second),
362 RenameDecl.getASTContext().getLangOpts());
367 return llvm::make_error<llvm::StringError>(
368 llvm::formatv(
"Index results don't match the content of file {0} " 369 "(the index may be stale)",
371 llvm::inconvertibleErrorCode());
376 return llvm::make_error<llvm::StringError>(
377 llvm::formatv(
"fail to build rename edit for file {0}: {1}", FilePath,
379 llvm::inconvertibleErrorCode());
381 if (!RenameEdit->Replacements.empty())
382 Results.insert({FilePath, std::move(*RenameEdit)});
404 std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest,
405 ArrayRef<Range> LexedRest,
int LexedIndex,
int &Fuel,
406 llvm::function_ref<
void(
const std::vector<size_t> &)> MatchedCB) {
409 if (IndexedRest.size() > LexedRest.size())
411 if (IndexedRest.empty()) {
412 MatchedCB(PartialMatch);
415 if (impliesSimpleEdit(IndexedRest.front().start, LexedRest.front().start)) {
416 PartialMatch.push_back(LexedIndex);
417 findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(),
418 LexedIndex + 1, Fuel, MatchedCB);
419 PartialMatch.pop_back();
421 findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(),
422 LexedIndex + 1, Fuel, MatchedCB);
430 llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
431 auto GetFileContent = [&RInputs,
432 &SM](
PathRef AbsPath) -> llvm::Expected<std::string> {
433 llvm::Optional<std::string> DirtyBuffer;
436 return std::move(*DirtyBuffer);
439 SM.getFileManager().getVirtualFileSystem().getBufferForFile(AbsPath);
441 return llvm::createStringError(
442 llvm::inconvertibleErrorCode(),
443 llvm::formatv(
"Fail to open file {0}: {1}", AbsPath,
444 Content.getError().message()));
446 return llvm::createStringError(
447 llvm::inconvertibleErrorCode(),
448 llvm::formatv(
"Got no buffer for file {0}", AbsPath));
450 return (*Content)->getBuffer().str();
455 return Loc.takeError();
456 const syntax::Token *IdentifierToken =
457 spelledIdentifierTouching(*Loc, AST.
getTokens());
459 if (!IdentifierToken)
460 return makeError(ReasonToReject::NoSymbolFound);
464 return makeError(ReasonToReject::UnsupportedSymbol);
466 auto DeclsUnderCursor = locateDeclAt(AST, IdentifierToken->location());
467 if (DeclsUnderCursor.empty())
468 return makeError(ReasonToReject::NoSymbolFound);
469 if (DeclsUnderCursor.size() > 1)
470 return makeError(ReasonToReject::AmbiguousSymbol);
472 const auto *RenameDecl = llvm::dyn_cast<NamedDecl>(*DeclsUnderCursor.begin());
474 return makeError(ReasonToReject::UnsupportedSymbol);
477 renameable(*RenameDecl->getCanonicalDecl(), RInputs.
MainFilePath,
491 auto MainFileRenameEdit = renameWithinFile(AST, *RenameDecl, RInputs.
NewName);
492 if (!MainFileRenameEdit)
493 return MainFileRenameEdit.takeError();
499 Edit{MainFileCode, std::move(*MainFileRenameEdit)})});
506 auto OtherFilesEdits =
508 *RInputs.
Index, GetFileContent);
509 if (!OtherFilesEdits)
510 return OtherFilesEdits.takeError();
511 Results = std::move(*OtherFilesEdits);
515 std::move(*MainFileRenameEdit));
520 llvm::StringRef InitialCode,
521 std::vector<Range> Occurrences,
522 llvm::StringRef NewName) {
523 assert(std::is_sorted(Occurrences.begin(), Occurrences.end()));
524 assert(std::unique(Occurrences.begin(), Occurrences.end()) ==
526 "Occurrences must be unique");
530 size_t LastOffset = 0;
532 auto Offset = [&](
const Position &P) -> llvm::Expected<size_t> {
533 assert(LastPos <= P &&
"malformed input");
535 P.
line - LastPos.line,
536 P.line > LastPos.line ? P.character : P.character - LastPos.character};
540 return llvm::make_error<llvm::StringError>(
541 llvm::formatv(
"fail to convert the position {0} to offset ({1})", P,
543 llvm::inconvertibleErrorCode());
545 LastOffset += *ShiftedOffset;
549 std::vector<std::pair< size_t,
size_t>> OccurrencesOffsets;
550 for (
const auto &R : Occurrences) {
551 auto StartOffset =
Offset(R.start);
553 return StartOffset.takeError();
554 auto EndOffset =
Offset(R.end);
556 return EndOffset.takeError();
557 OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
560 tooling::Replacements RenameEdit;
561 for (
const auto &R : OccurrencesOffsets) {
562 auto ByteLength = R.second - R.first;
563 if (
auto Err = RenameEdit.add(
564 tooling::Replacement(AbsFilePath, R.first, ByteLength, NewName)))
565 return std::move(Err);
567 return Edit(InitialCode, std::move(RenameEdit));
583 llvm::Optional<std::vector<Range>>
585 std::vector<Range> Indexed,
const LangOptions &LangOpts) {
586 assert(!Indexed.empty());
587 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
588 std::vector<Range> Lexed =
595 ArrayRef<Range> Lexed) {
596 assert(!Indexed.empty());
597 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
598 assert(std::is_sorted(Lexed.begin(), Lexed.end()));
600 if (Indexed.size() > Lexed.size()) {
601 vlog(
"The number of lexed occurrences is less than indexed occurrences");
605 if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end()))
606 return Indexed.vec();
608 std::vector<size_t> Best;
609 size_t BestCost = std::numeric_limits<size_t>::max();
610 bool HasMultiple = 0;
611 std::vector<size_t> ResultStorage;
613 findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel,
614 [&](
const std::vector<size_t> &Matched) {
617 if (MCost < BestCost) {
619 Best = std::move(Matched);
623 if (MCost == BestCost)
627 vlog(
"The best near miss is not unique.");
631 vlog(
"Didn't find a near miss.");
634 std::vector<Range> Mapped;
636 Mapped.push_back(Lexed[I]);
656 ArrayRef<size_t> MappedIndex) {
657 assert(Indexed.size() == MappedIndex.size());
658 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
659 assert(std::is_sorted(Lexed.begin(), Lexed.end()));
662 int LastDLine = 0, LastDColumn = 0;
664 for (
size_t I = 0; I < Indexed.size(); ++I) {
665 int DLine = Indexed[I].start.line - Lexed[MappedIndex[I]].start.line;
667 Indexed[I].start.character - Lexed[MappedIndex[I]].start.character;
668 int Line = Indexed[I].start.line;
669 if (Line != LastLine)
671 Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn);
672 std::tie(LastLine, LastDLine, LastDColumn) = std::tie(Line, DLine, DColumn);
SourceLocation Loc
'#' location in the include directive
const FunctionDecl * Decl
llvm::DenseSet< SymbolID > IDs
Position start
The range's start position.
llvm::Optional< SymbolID > getSymbolID(const Decl *D)
Gets the symbol ID for a declaration, if possible.
static llvm::Error makeError(const char *Msg)
Preprocessor & getPreprocessor()
llvm::Optional< std::vector< Range > > getMappedRanges(ArrayRef< Range > Indexed, ArrayRef< Range > Lexed)
Calculates the lexed occurrences that the given indexed occurrences map to.
Information about a reference written in the source code, independent of the actual AST node that thi...
This is the pattern the template specialization was instantiated from.
Interface for symbol indexes that can be used for searching or matching symbols among a set of symbol...
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
Represents a symbol occurrence in the source file.
constexpr llvm::StringLiteral Message
llvm::StringRef PathRef
A typedef to represent a ref to file path.
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
std::vector< CodeCompletionResult > Results
ArrayRef< Decl * > getLocalTopLevelDecls()
This function returns top-level decls present in the main file of the AST.
Documents should not be synced at all.
void vlog(const char *Fmt, Ts &&... Vals)
void elog(const char *Fmt, Ts &&... Vals)
llvm::Expected< SourceLocation > sourceLocationInMainFile(const SourceManager &SM, Position P)
Return the file location, corresponding to P.
This declaration is an alias that was referred to.
const syntax::TokenBuffer & getTokens() const
Tokens recorded while parsing the main file.
ASTContext & getASTContext()
Note that the returned ast will not contain decls from the preamble that were not deserialized during...
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
SourceLocation NameLoc
Start location of the last name part, i.e. 'foo' in 'ns::foo<int>'.
SymbolLocation Location
The source location where the symbol is named.
std::string Path
A typedef to represent a file path.
virtual bool refs(const RefsRequest &Req, llvm::function_ref< void(const Ref &)> Callback) const =0
Finds all occurrences (e.g.
llvm::SmallVector< const NamedDecl *, 1 > Targets
A list of targets referenced by this name.
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
llvm::SmallVector< const NamedDecl *, 1 > targetDecl(const ast_type_traits::DynTypedNode &N, DeclRelationSet Mask)
targetDecl() finds the declaration referred to by an AST node.
Stores and provides access to parsed AST.
llvm::StringMap< Edit > FileEdits
A mapping from absolute file path (the one used for accessing the underlying VFS) to edits...
SourceManager & getSourceManager()
int line
Line position in a document (zero-based).
int character
Character offset on a line in a document (zero-based).
static bool shouldCollectSymbol(const NamedDecl &ND, const ASTContext &ASTCtx, const Options &Opts, bool IsMainFileSymbol)
Returns true is ND should be collected.
Position Start
The symbol range, using half-open range [Start, End).
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out)
Recursively traverse S and report all references explicitly written in the code.
size_t renameRangeAdjustmentCost(ArrayRef< Range > Indexed, ArrayRef< Range > Lexed, ArrayRef< size_t > MappedIndex)
Evaluates how good the mapped result is.
bool isHeaderFile(llvm::StringRef FileName, llvm::Optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
static llvm::Expected< std::string > resolve(const URI &U, llvm::StringRef HintPath="")
Resolves the absolute path of U.
llvm::Optional< std::vector< Range > > adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier, std::vector< Range > Indexed, const LangOptions &LangOpts)
Adjusts indexed occurrences to match the current state of the file.
llvm::Expected< FileEdits > rename(const RenameInputs &RInputs)
Renames all occurrences of the symbol.
Position end
The range's end position.
std::vector< Range > collectIdentifierRanges(llvm::StringRef Identifier, llvm::StringRef Content, const LangOptions &LangOpts)
Collects all ranges of the given identifier in the source code.
std::array< uint8_t, 20 > SymbolID
llvm::Optional< DefinedMacro > locateMacroAt(SourceLocation Loc, Preprocessor &PP)
Gets the macro at a specified Loc.
A set of edits generated for a single file.
llvm::Expected< Edit > buildRenameEdit(llvm::StringRef AbsFilePath, llvm::StringRef InitialCode, std::vector< Range > Occurrences, llvm::StringRef NewName)
Generates rename edits that replaces all given occurrences with the NewName.
const SymbolIndex * Index