forked from OSchip/llvm-project
297 lines
11 KiB
C++
297 lines
11 KiB
C++
//===--- 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/Refactoring/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/Refactoring/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 {
|
|
// 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));
|
|
}
|
|
|
|
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);
|
|
};
|
|
}
|