[LibTooling] Add `before` and `after` selectors for selecting point-ranges relative to nodes.

Summary:
The `before` and `after` selectors allow users to specify a zero-length range --
a point -- at the relevant location in an AST-node's source.  Point ranges can
be useful, for example, to insert a change using an API that takes a range to be
modified (e.g. `tooling::change()`).

Reviewers: ilya-biryukov

Subscribers: cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D62419

llvm-svn: 361955
This commit is contained in:
Yitzhak Mandelbaum 2019-05-29 12:40:36 +00:00
parent 33da02762f
commit e1312c54d3
3 changed files with 85 additions and 3 deletions

View File

@ -37,6 +37,15 @@ RangeSelector range(RangeSelector Begin, RangeSelector End);
/// Convenience version of \c range where end-points are bound nodes.
RangeSelector range(std::string BeginID, std::string EndID);
/// Selects the (empty) range [B,B) when \p Selector selects the range [B,E).
RangeSelector before(RangeSelector Selector);
/// Selects the the point immediately following \p Selector. That is, the
/// (empty) range [E,E), when \p Selector selects either
/// * the CharRange [B,E) or
/// * the TokenRange [B,E'] where the token at E' spans the range [E,E').
RangeSelector after(RangeSelector Selector);
/// Selects a node, including trailing semicolon (for non-expression
/// statements). \p ID is the node's binding in the match result.
RangeSelector node(std::string ID);

View File

@ -104,6 +104,28 @@ static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM,
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);

View File

@ -21,13 +21,15 @@ using namespace tooling;
using namespace ast_matchers;
namespace {
using ::testing::AllOf;
using ::testing::HasSubstr;
using MatchResult = MatchFinder::MatchResult;
using ::llvm::Expected;
using ::llvm::Failed;
using ::llvm::HasValue;
using ::llvm::StringError;
using ::testing::AllOf;
using ::testing::HasSubstr;
using ::testing::Property;
using MatchResult = MatchFinder::MatchResult;
struct TestMatch {
// The AST unit from which `result` is built. We bundle it because it backs
@ -117,6 +119,55 @@ TEST(RangeSelectorTest, UnboundNode) {
Failed<StringError>(withUnboundNodeMessage()));
}
MATCHER_P(EqualsCharSourceRange, Range, "") {
return Range.getAsRange() == arg.getAsRange() &&
Range.isTokenRange() == arg.isTokenRange();
}
// FIXME: here and elsewhere: use llvm::Annotations library to explicitly mark
// points and ranges of interest, enabling more readable tests.
TEST(RangeSelectorTest, BeforeOp) {
StringRef Code = R"cc(
int f(int x, int y, int z) { return 3; }
int g() { return f(/* comment */ 3, 7 /* comment */, 9); }
)cc";
StringRef Call = "call";
TestMatch Match = matchCode(Code, callExpr().bind(Call));
const auto* E = Match.Result.Nodes.getNodeAs<Expr>(Call);
assert(E != nullptr);
auto ExprBegin = E->getSourceRange().getBegin();
EXPECT_THAT_EXPECTED(
before(node(Call))(Match.Result),
HasValue(EqualsCharSourceRange(
CharSourceRange::getCharRange(ExprBegin, ExprBegin))));
}
TEST(RangeSelectorTest, AfterOp) {
StringRef Code = R"cc(
int f(int x, int y, int z) { return 3; }
int g() { return f(/* comment */ 3, 7 /* comment */, 9); }
)cc";
StringRef Call = "call";
TestMatch Match = matchCode(Code, callExpr().bind(Call));
const auto* E = Match.Result.Nodes.getNodeAs<Expr>(Call);
assert(E != nullptr);
const SourceRange Range = E->getSourceRange();
// The end token, a right paren, is one character wide, so advance by one,
// bringing us to the semicolon.
const SourceLocation SemiLoc = Range.getEnd().getLocWithOffset(1);
const auto ExpectedAfter = CharSourceRange::getCharRange(SemiLoc, SemiLoc);
// Test with a char range.
auto CharRange = CharSourceRange::getCharRange(Range.getBegin(), SemiLoc);
EXPECT_THAT_EXPECTED(after(charRange(CharRange))(Match.Result),
HasValue(EqualsCharSourceRange(ExpectedAfter)));
// Test with a token range.
auto TokenRange = CharSourceRange::getTokenRange(Range);
EXPECT_THAT_EXPECTED(after(charRange(TokenRange))(Match.Result),
HasValue(EqualsCharSourceRange(ExpectedAfter)));
}
TEST(RangeSelectorTest, RangeOp) {
StringRef Code = R"cc(
int f(int x, int y, int z) { return 3; }