| //===--- SourceLocationUtilities.cpp - Source location helper functions ---===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SourceLocationUtilities.h" |
| #include "clang/AST/Stmt.h" |
| #include "clang/Lex/Lexer.h" |
| #include <limits> |
| |
| namespace clang { |
| namespace tooling { |
| |
| SourceLocation findLastLocationOfSourceConstruct(SourceLocation HeaderEnd, |
| const Stmt *Body, |
| const SourceManager &SM) { |
| SourceLocation BodyStart = SM.getSpellingLoc(Body->getLocStart()); |
| unsigned BodyLine = SM.getSpellingLineNumber(BodyStart); |
| unsigned HeaderLine = SM.getSpellingLineNumber(HeaderEnd); |
| |
| if (BodyLine > HeaderLine) { |
| // The Last location on the previous line if the body is not on the same |
| // line as the last known location. |
| SourceLocation LineLocThatPrecedesBody = |
| SM.translateLineCol(SM.getFileID(BodyStart), BodyLine - 1, |
| std::numeric_limits<unsigned>::max()); |
| if (LineLocThatPrecedesBody.isValid()) |
| return LineLocThatPrecedesBody; |
| } |
| // We want to include the location of the '{'. |
| return isa<CompoundStmt>(Body) ? BodyStart : BodyStart.getLocWithOffset(-1); |
| } |
| |
| SourceLocation findFirstLocationOfSourceConstruct(SourceLocation HeaderStart, |
| const Stmt *PreviousBody, |
| const SourceManager &SM) { |
| if (!isa<CompoundStmt>(PreviousBody)) |
| return HeaderStart; |
| SourceLocation BodyEnd = SM.getSpellingLoc(PreviousBody->getLocEnd()); |
| unsigned BodyLine = SM.getSpellingLineNumber(BodyEnd); |
| unsigned HeaderLine = SM.getSpellingLineNumber(HeaderStart); |
| if (BodyLine >= HeaderLine) |
| return BodyEnd; |
| return HeaderStart; |
| } |
| |
| bool isLocationInAnyRange(SourceLocation Location, ArrayRef<SourceRange> Ranges, |
| const SourceManager &SM) { |
| for (const SourceRange &Range : Ranges) { |
| if (!isPointWithin(Location, Range.getBegin(), Range.getEnd(), SM)) |
| continue; |
| return true; |
| } |
| return false; |
| } |
| |
| SourceLocation getPreciseTokenLocEnd(SourceLocation Loc, |
| const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| return Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts); |
| } |
| |
| SourceLocation findClosingParenLocEnd(SourceLocation LastKnownLoc, |
| const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| return Lexer::findLocationAfterToken( |
| LastKnownLoc, tok::r_paren, SM, LangOpts, |
| /*SkipTrailingWhitespaceAndNewLine=*/false); |
| } |
| |
| SourceRange getRangeOfNextToken(SourceLocation Loc, tok::TokenKind Kind, |
| const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| SourceLocation NextLoc = |
| Lexer::findLocationAfterToken(Loc, Kind, SM, LangOpts, |
| /*SkipTrailingWhitespaceAndNewLine=*/false); |
| if (NextLoc.isInvalid()) |
| return SourceRange(); |
| return SourceRange( |
| Lexer::GetBeginningOfToken(NextLoc.getLocWithOffset(-1), SM, LangOpts), |
| NextLoc); |
| } |
| |
| SourceLocation findLastNonCompoundLocation(const Stmt *S) { |
| const auto *CS = dyn_cast<CompoundStmt>(S); |
| if (!CS) |
| return S->getLocEnd(); |
| return CS->body_back() ? CS->body_back()->getLocEnd() : SourceLocation(); |
| } |
| |
| bool areOnSameLine(SourceLocation Loc1, SourceLocation Loc2, |
| const SourceManager &SM) { |
| return !Loc1.isMacroID() && !Loc2.isMacroID() && |
| SM.getSpellingLineNumber(Loc1) == SM.getSpellingLineNumber(Loc2); |
| } |
| |
| SourceLocation |
| getLastLineLocationUnlessItHasOtherTokens(SourceLocation SpellingLoc, |
| const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| assert(!SpellingLoc.isMacroID() && "Expecting a spelling location"); |
| SourceLocation NextTokenLoc = |
| Lexer::findNextTokenLocationAfterTokenAt(SpellingLoc, SM, LangOpts); |
| if (NextTokenLoc.isValid()) { |
| bool IsSameLine = areOnSameLine(SpellingLoc, NextTokenLoc, SM); |
| if (IsSameLine) { |
| // Could be a ';' on the same line, so try looking after the ';' |
| if (isSemicolonAtLocation(NextTokenLoc, SM, LangOpts)) |
| return getLastLineLocationUnlessItHasOtherTokens(NextTokenLoc, SM, |
| LangOpts); |
| } else { |
| SourceLocation LastLoc = SM.translateLineCol( |
| SM.getFileID(SpellingLoc), SM.getSpellingLineNumber(SpellingLoc), |
| std::numeric_limits<unsigned>::max()); |
| if (LastLoc.isValid()) |
| return LastLoc; |
| } |
| } |
| return getPreciseTokenLocEnd(SpellingLoc, SM, LangOpts); |
| } |
| |
| bool isSemicolonAtLocation(SourceLocation TokenLoc, const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| return Lexer::getSourceText( |
| CharSourceRange::getTokenRange(TokenLoc, TokenLoc), SM, |
| LangOpts) == ";"; |
| } |
| |
| SourceRange trimSelectionRange(SourceRange Range, const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| bool IsInvalid = false; |
| StringRef Text = Lexer::getSourceText(CharSourceRange::getCharRange(Range), |
| SM, LangOpts, &IsInvalid); |
| if (IsInvalid || Text.empty()) |
| return Range; |
| assert(Range.getBegin().isFileID() && "Not a file range!"); |
| |
| std::string Source = Text.str(); |
| Lexer Lex(Range.getBegin(), LangOpts, Source.c_str(), Source.c_str(), |
| Source.c_str() + Source.size()); |
| // Get comment tokens as well. |
| Lex.SetCommentRetentionState(true); |
| SourceLocation StartLoc, EndLoc; |
| while (true) { |
| Token Tok; |
| Lex.LexFromRawLexer(Tok); |
| if (Tok.getKind() == tok::eof) |
| break; |
| if (StartLoc.isInvalid()) |
| StartLoc = Tok.getLocation(); |
| if (Tok.getKind() != tok::semi) |
| EndLoc = Tok.getEndLoc(); |
| } |
| return StartLoc.isValid() && EndLoc.isValid() ? SourceRange(StartLoc, EndLoc) |
| : SourceRange(); |
| } |
| |
| /// Tokenize the given file and check if it contains a comment that ends at the |
| /// given location. |
| static SourceLocation findCommentThatEndsAt(FileID FID, |
| SourceLocation StartOfFile, |
| const SourceManager &SM, |
| const LangOptions &LangOpts, |
| SourceLocation ExpectedEndLoc) { |
| // Try to load the file buffer. |
| bool InvalidTemp = false; |
| StringRef File = SM.getBufferData(FID, &InvalidTemp); |
| if (InvalidTemp) |
| return SourceLocation(); |
| |
| // Search for the comment that ends at the given location. |
| Lexer Lex(StartOfFile, LangOpts, File.begin(), File.begin(), File.end()); |
| Lex.SetCommentRetentionState(true); |
| Token Tok; |
| while (!Lex.LexFromRawLexer(Tok)) { |
| if (Tok.is(tok::comment) && Tok.getEndLoc() == ExpectedEndLoc) |
| return Tok.getLocation(); |
| } |
| // Find the token. |
| return SourceLocation(); |
| } |
| |
| SourceLocation getLocationOfPrecedingComment(SourceLocation Location, |
| const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| SourceLocation PrevResult = Location; |
| SourceLocation Result = Location; |
| if (Result.isMacroID()) |
| Result = SM.getExpansionLoc(Result); |
| FileID FID = SM.getFileID(Result); |
| SourceLocation StartOfFile = SM.getLocForStartOfFile(FID); |
| Token Tok; |
| Tok.setKind(tok::unknown); |
| SourceLocation TokenLoc = Result; |
| auto GetPreviousToken = [&]() -> bool { |
| TokenLoc = |
| Lexer::GetBeginningOfToken(TokenLoc.getLocWithOffset(-1), SM, LangOpts); |
| return !Lexer::getRawToken(TokenLoc, Tok, SM, LangOpts); |
| }; |
| // Look for a comment token. |
| while (TokenLoc != StartOfFile) { |
| bool LocHasToken = GetPreviousToken(); |
| if (LocHasToken && Tok.is(tok::slash)) { |
| // Check if this the end of a multiline '/*' comment before returning. |
| SourceLocation CommentLoc = findCommentThatEndsAt( |
| FID, StartOfFile, SM, LangOpts, Tok.getEndLoc()); |
| return CommentLoc.isInvalid() ? Result : CommentLoc; |
| } |
| if (LocHasToken && Tok.isNot(tok::comment)) |
| break; |
| if (!LocHasToken) |
| continue; |
| // We found a preceding comment. Check if there are other preceding |
| // comments. |
| PrevResult = Result; |
| Result = Tok.getLocation(); |
| while (TokenLoc != StartOfFile) { |
| bool LocHasToken = GetPreviousToken(); |
| if (LocHasToken && Tok.isNot(tok::comment)) { |
| // Reset the result to the previous location if this comment trails |
| // another token on the same line. |
| if (SM.getSpellingLineNumber(Tok.getEndLoc()) == |
| SM.getSpellingLineNumber(Result)) |
| Result = PrevResult; |
| break; |
| } |
| if (!LocHasToken) |
| continue; |
| // The location of this comment is accepted only when the next comment |
| // is located immediately after this comment. |
| if (SM.getSpellingLineNumber(Tok.getEndLoc()) != |
| SM.getSpellingLineNumber(Result) - 1) |
| break; |
| PrevResult = Result; |
| Result = Tok.getLocation(); |
| } |
| break; |
| } |
| return Result; |
| } |
| |
| SourceLocation getLocationOfPrecedingToken(SourceLocation Loc, |
| const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| SourceLocation Result = Loc; |
| if (Result.isMacroID()) |
| Result = SM.getExpansionLoc(Result); |
| FileID FID = SM.getFileID(Result); |
| SourceLocation StartOfFile = SM.getLocForStartOfFile(FID); |
| if (Loc == StartOfFile) |
| return SourceLocation(); |
| return Lexer::GetBeginningOfToken(Result.getLocWithOffset(-1), SM, LangOpts); |
| } |
| |
| } // end namespace tooling |
| } // end namespace clang |