| //===--- RangeSelector.cpp - RangeSelector implementations ------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Tooling/Transformer/RangeSelector.h" |
| #include "clang/AST/Expr.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Lex/Lexer.h" |
| #include "clang/Tooling/Transformer/SourceCode.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Errc.h" |
| #include "llvm/Support/Error.h" |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| using namespace clang; |
| using namespace tooling; |
| |
| using ast_matchers::MatchFinder; |
| using ast_type_traits::ASTNodeKind; |
| using ast_type_traits::DynTypedNode; |
| using llvm::Error; |
| using llvm::StringError; |
| |
| using MatchResult = MatchFinder::MatchResult; |
| |
| static Error invalidArgumentError(Twine Message) { |
| return llvm::make_error<StringError>(llvm::errc::invalid_argument, Message); |
| } |
| |
| static Error typeError(StringRef ID, const ASTNodeKind &Kind) { |
| return invalidArgumentError("mismatched type (node id=" + ID + |
| " kind=" + Kind.asStringRef() + ")"); |
| } |
| |
| static Error typeError(StringRef ID, const ASTNodeKind &Kind, |
| Twine ExpectedType) { |
| return invalidArgumentError("mismatched type: expected one of " + |
| ExpectedType + " (node id=" + ID + |
| " kind=" + Kind.asStringRef() + ")"); |
| } |
| |
| static Error missingPropertyError(StringRef ID, Twine Description, |
| StringRef Property) { |
| return invalidArgumentError(Description + " requires property '" + Property + |
| "' (node id=" + ID + ")"); |
| } |
| |
| static Expected<DynTypedNode> getNode(const ast_matchers::BoundNodes &Nodes, |
| StringRef ID) { |
| auto &NodesMap = Nodes.getMap(); |
| auto It = NodesMap.find(ID); |
| if (It == NodesMap.end()) |
| return invalidArgumentError("ID not bound: " + ID); |
| return It->second; |
| } |
| |
| // FIXME: handling of macros should be configurable. |
| static SourceLocation findPreviousTokenStart(SourceLocation Start, |
| const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| if (Start.isInvalid() || Start.isMacroID()) |
| return SourceLocation(); |
| |
| SourceLocation BeforeStart = Start.getLocWithOffset(-1); |
| if (BeforeStart.isInvalid() || BeforeStart.isMacroID()) |
| return SourceLocation(); |
| |
| return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts); |
| } |
| |
| // Finds the start location of the previous token of kind \p TK. |
| // FIXME: handling of macros should be configurable. |
| static SourceLocation findPreviousTokenKind(SourceLocation Start, |
| const SourceManager &SM, |
| const LangOptions &LangOpts, |
| tok::TokenKind TK) { |
| while (true) { |
| SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); |
| if (L.isInvalid() || L.isMacroID()) |
| return SourceLocation(); |
| |
| Token T; |
| if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true)) |
| return SourceLocation(); |
| |
| if (T.is(TK)) |
| return T.getLocation(); |
| |
| Start = L; |
| } |
| } |
| |
| static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM, |
| const LangOptions &LangOpts) { |
| SourceLocation EndLoc = |
| E.getNumArgs() == 0 ? E.getRParenLoc() : E.getArg(0)->getBeginLoc(); |
| return findPreviousTokenKind(EndLoc, SM, LangOpts, tok::TokenKind::l_paren); |
| } |
| |
| RangeSelector tooling::before(RangeSelector Selector) { |
| return [Selector](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<CharSourceRange> SelectedRange = Selector(Result); |
| if (!SelectedRange) |
| return SelectedRange.takeError(); |
| return CharSourceRange::getCharRange(SelectedRange->getBegin()); |
| }; |
| } |
| |
| RangeSelector tooling::after(RangeSelector Selector) { |
| return [Selector](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<CharSourceRange> SelectedRange = Selector(Result); |
| if (!SelectedRange) |
| return SelectedRange.takeError(); |
| if (SelectedRange->isCharRange()) |
| return CharSourceRange::getCharRange(SelectedRange->getEnd()); |
| return CharSourceRange::getCharRange(Lexer::getLocForEndOfToken( |
| SelectedRange->getEnd(), 0, Result.Context->getSourceManager(), |
| Result.Context->getLangOpts())); |
| }; |
| } |
| |
| RangeSelector tooling::node(std::string ID) { |
| return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); |
| if (!Node) |
| return Node.takeError(); |
| return Node->get<Stmt>() != nullptr && Node->get<Expr>() == nullptr |
| ? getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context) |
| : CharSourceRange::getTokenRange(Node->getSourceRange()); |
| }; |
| } |
| |
| RangeSelector tooling::statement(std::string ID) { |
| return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); |
| if (!Node) |
| return Node.takeError(); |
| return getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context); |
| }; |
| } |
| |
| RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) { |
| return [Begin, End](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<CharSourceRange> BeginRange = Begin(Result); |
| if (!BeginRange) |
| return BeginRange.takeError(); |
| Expected<CharSourceRange> EndRange = End(Result); |
| if (!EndRange) |
| return EndRange.takeError(); |
| SourceLocation B = BeginRange->getBegin(); |
| SourceLocation E = EndRange->getEnd(); |
| // Note: we are precluding the possibility of sub-token ranges in the case |
| // that EndRange is a token range. |
| if (Result.SourceManager->isBeforeInTranslationUnit(E, B)) { |
| return invalidArgumentError("Bad range: out of order"); |
| } |
| return CharSourceRange(SourceRange(B, E), EndRange->isTokenRange()); |
| }; |
| } |
| |
| RangeSelector tooling::range(std::string BeginID, std::string EndID) { |
| return tooling::range(node(std::move(BeginID)), node(std::move(EndID))); |
| } |
| |
| RangeSelector tooling::member(std::string ID) { |
| return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); |
| if (!Node) |
| return Node.takeError(); |
| if (auto *M = Node->get<clang::MemberExpr>()) |
| return CharSourceRange::getTokenRange( |
| M->getMemberNameInfo().getSourceRange()); |
| return typeError(ID, Node->getNodeKind(), "MemberExpr"); |
| }; |
| } |
| |
| RangeSelector tooling::name(std::string ID) { |
| return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<DynTypedNode> N = getNode(Result.Nodes, ID); |
| if (!N) |
| return N.takeError(); |
| auto &Node = *N; |
| if (const auto *D = Node.get<NamedDecl>()) { |
| if (!D->getDeclName().isIdentifier()) |
| return missingPropertyError(ID, "name", "identifier"); |
| SourceLocation L = D->getLocation(); |
| auto R = CharSourceRange::getTokenRange(L, L); |
| // Verify that the range covers exactly the name. |
| // FIXME: extend this code to support cases like `operator +` or |
| // `foo<int>` for which this range will be too short. Doing so will |
| // require subcasing `NamedDecl`, because it doesn't provide virtual |
| // access to the \c DeclarationNameInfo. |
| if (getText(R, *Result.Context) != D->getName()) |
| return CharSourceRange(); |
| return R; |
| } |
| if (const auto *E = Node.get<DeclRefExpr>()) { |
| if (!E->getNameInfo().getName().isIdentifier()) |
| return missingPropertyError(ID, "name", "identifier"); |
| SourceLocation L = E->getLocation(); |
| return CharSourceRange::getTokenRange(L, L); |
| } |
| if (const auto *I = Node.get<CXXCtorInitializer>()) { |
| if (!I->isMemberInitializer() && I->isWritten()) |
| return missingPropertyError(ID, "name", "explicit member initializer"); |
| SourceLocation L = I->getMemberLocation(); |
| return CharSourceRange::getTokenRange(L, L); |
| } |
| return typeError(ID, Node.getNodeKind(), |
| "DeclRefExpr, NamedDecl, CXXCtorInitializer"); |
| }; |
| } |
| |
| namespace { |
| // FIXME: make this available in the public API for users to easily create their |
| // own selectors. |
| |
| // Creates a selector from a range-selection function \p Func, which selects a |
| // range that is relative to a bound node id. \c T is the node type expected by |
| // \p Func. |
| template <typename T, CharSourceRange (*Func)(const MatchResult &, const T &)> |
| class RelativeSelector { |
| std::string ID; |
| |
| public: |
| RelativeSelector(std::string ID) : ID(std::move(ID)) {} |
| |
| Expected<CharSourceRange> operator()(const MatchResult &Result) { |
| Expected<DynTypedNode> N = getNode(Result.Nodes, ID); |
| if (!N) |
| return N.takeError(); |
| if (const auto *Arg = N->get<T>()) |
| return Func(Result, *Arg); |
| return typeError(ID, N->getNodeKind()); |
| } |
| }; |
| } // namespace |
| |
| // FIXME: Change the following functions from being in an anonymous namespace |
| // to static functions, after the minimum Visual C++ has _MSC_VER >= 1915 |
| // (equivalent to Visual Studio 2017 v15.8 or higher). Using the anonymous |
| // namespace works around a bug in earlier versions. |
| namespace { |
| // Returns the range of the statements (all source between the braces). |
| CharSourceRange getStatementsRange(const MatchResult &, |
| const CompoundStmt &CS) { |
| return CharSourceRange::getCharRange(CS.getLBracLoc().getLocWithOffset(1), |
| CS.getRBracLoc()); |
| } |
| } // namespace |
| |
| RangeSelector tooling::statements(std::string ID) { |
| return RelativeSelector<CompoundStmt, getStatementsRange>(std::move(ID)); |
| } |
| |
| namespace { |
| // Returns the range of the source between the call's parentheses. |
| CharSourceRange getCallArgumentsRange(const MatchResult &Result, |
| const CallExpr &CE) { |
| return CharSourceRange::getCharRange( |
| findOpenParen(CE, *Result.SourceManager, Result.Context->getLangOpts()) |
| .getLocWithOffset(1), |
| CE.getRParenLoc()); |
| } |
| } // namespace |
| |
| RangeSelector tooling::callArgs(std::string ID) { |
| return RelativeSelector<CallExpr, getCallArgumentsRange>(std::move(ID)); |
| } |
| |
| namespace { |
| // Returns the range of the elements of the initializer list. Includes all |
| // source between the braces. |
| CharSourceRange getElementsRange(const MatchResult &, |
| const InitListExpr &E) { |
| return CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1), |
| E.getRBraceLoc()); |
| } |
| } // namespace |
| |
| RangeSelector tooling::initListElements(std::string ID) { |
| return RelativeSelector<InitListExpr, getElementsRange>(std::move(ID)); |
| } |
| |
| namespace { |
| // Returns the range of the else branch, including the `else` keyword. |
| CharSourceRange getElseRange(const MatchResult &Result, const IfStmt &S) { |
| return maybeExtendRange( |
| CharSourceRange::getTokenRange(S.getElseLoc(), S.getEndLoc()), |
| tok::TokenKind::semi, *Result.Context); |
| } |
| } // namespace |
| |
| RangeSelector tooling::elseBranch(std::string ID) { |
| return RelativeSelector<IfStmt, getElseRange>(std::move(ID)); |
| } |
| |
| RangeSelector tooling::expansion(RangeSelector S) { |
| return [S](const MatchResult &Result) -> Expected<CharSourceRange> { |
| Expected<CharSourceRange> SRange = S(Result); |
| if (!SRange) |
| return SRange.takeError(); |
| return Result.SourceManager->getExpansionRange(*SRange); |
| }; |
| } |