71 changes: 66 additions & 5 deletions clang-tools-extra/clangd/unittests/SelectionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,26 @@ namespace clangd {
namespace {
using ::testing::UnorderedElementsAreArray;

// Create a selection tree corresponding to a point or pair of points.
// This uses the precisely-defined createRight semantics. The fuzzier
// createEach is tested separately.
SelectionTree makeSelectionTree(const StringRef MarkedCode, ParsedAST &AST) {
Annotations Test(MarkedCode);
switch (Test.points().size()) {
case 1: // Point selection.
return SelectionTree(AST.getASTContext(), AST.getTokens(),
cantFail(positionToOffset(Test.code(), Test.point())));
case 1: { // Point selection.
unsigned Offset = cantFail(positionToOffset(Test.code(), Test.point()));
return SelectionTree::createRight(AST.getASTContext(), AST.getTokens(),
Offset, Offset);
}
case 2: // Range selection.
return SelectionTree(
return SelectionTree::createRight(
AST.getASTContext(), AST.getTokens(),
cantFail(positionToOffset(Test.code(), Test.points()[0])),
cantFail(positionToOffset(Test.code(), Test.points()[1])));
default:
ADD_FAILURE() << "Expected 1-2 points for selection.\n" << MarkedCode;
return SelectionTree(AST.getASTContext(), AST.getTokens(), 0u, 0u);
return SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), 0u,
0u);
}
}

Expand Down Expand Up @@ -507,6 +513,61 @@ TEST(SelectionTest, Implicit) {
EXPECT_EQ("CXXConstructExpr", nodeKind(&Str->outerImplicit()));
}

TEST(SelectionTest, CreateAll) {
llvm::Annotations Test("int$unique^ a=1$ambiguous^+1; $empty^");
auto AST = TestTU::withCode(Test.code()).build();
unsigned Seen = 0;
SelectionTree::createEach(
AST.getASTContext(), AST.getTokens(), Test.point("ambiguous"),
Test.point("ambiguous"), [&](SelectionTree T) {
// Expect to see the right-biased tree first.
if (Seen == 0)
EXPECT_EQ("BinaryOperator", nodeKind(T.commonAncestor()));
else if (Seen == 1)
EXPECT_EQ("IntegerLiteral", nodeKind(T.commonAncestor()));
++Seen;
return false;
});
EXPECT_EQ(2u, Seen);

Seen = 0;
SelectionTree::createEach(AST.getASTContext(), AST.getTokens(),
Test.point("ambiguous"), Test.point("ambiguous"),
[&](SelectionTree T) {
++Seen;
return true;
});
EXPECT_EQ(1u, Seen) << "Return true --> stop iterating";

Seen = 0;
SelectionTree::createEach(AST.getASTContext(), AST.getTokens(),
Test.point("unique"), Test.point("unique"),
[&](SelectionTree T) {
++Seen;
return false;
});
EXPECT_EQ(1u, Seen) << "no ambiguity --> only one tree";

Seen = 0;
SelectionTree::createEach(AST.getASTContext(), AST.getTokens(),
Test.point("empty"), Test.point("empty"),
[&](SelectionTree T) {
EXPECT_FALSE(T.commonAncestor());
++Seen;
return false;
});
EXPECT_EQ(1u, Seen) << "empty tree still created";

Seen = 0;
SelectionTree::createEach(AST.getASTContext(), AST.getTokens(),
Test.point("unique"), Test.point("ambiguous"),
[&](SelectionTree T) {
++Seen;
return false;
});
EXPECT_EQ(1u, Seen) << "one tree for nontrivial selection";
}

} // namespace
} // namespace clangd
} // namespace clang
57 changes: 36 additions & 21 deletions clang-tools-extra/clangd/unittests/TweakTesting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,45 @@ std::pair<unsigned, unsigned> rangeOrPoint(const Annotations &A) {
cantFail(positionToOffset(A.code(), SelectionRng.end))};
}

// Prepare and apply the specified tweak based on the selection in Input.
// Returns None if and only if prepare() failed.
llvm::Optional<llvm::Expected<Tweak::Effect>>
applyTweak(ParsedAST &AST, const Annotations &Input, StringRef TweakID,
const SymbolIndex *Index) {
auto Range = rangeOrPoint(Input);
llvm::Optional<llvm::Expected<Tweak::Effect>> Result;
SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), Range.first,
Range.second, [&](SelectionTree ST) {
Tweak::Selection S(Index, AST, Range.first,
Range.second, std::move(ST));
if (auto T = prepareTweak(TweakID, S)) {
Result = (*T)->apply(S);
return true;
} else {
llvm::consumeError(T.takeError());
return false;
}
});
return Result;
}

MATCHER_P7(TweakIsAvailable, TweakID, Ctx, Header, ExtraArgs, ExtraFiles, Index,
FileName,
(TweakID + (negation ? " is unavailable" : " is available")).str()) {
std::string WrappedCode = wrap(Ctx, arg);
Annotations Input(WrappedCode);
auto Selection = rangeOrPoint(Input);
TestTU TU;
TU.Filename = FileName;
TU.HeaderCode = Header;
TU.Code = Input.code();
TU.ExtraArgs = ExtraArgs;
TU.AdditionalFiles = std::move(ExtraFiles);
ParsedAST AST = TU.build();
Tweak::Selection S(Index, AST, Selection.first, Selection.second);
auto PrepareResult = prepareTweak(TweakID, S);
if (PrepareResult)
return true;
llvm::consumeError(PrepareResult.takeError());
return false;
auto Result = applyTweak(AST, Input, TweakID, Index);
// We only care if prepare() succeeded, but must handle Errors.
if (Result && !*Result)
consumeError(Result->takeError());
return Result.hasValue();
}

} // namespace
Expand All @@ -90,32 +110,27 @@ std::string TweakTest::apply(llvm::StringRef MarkedCode,
llvm::StringMap<std::string> *EditedFiles) const {
std::string WrappedCode = wrap(Context, MarkedCode);
Annotations Input(WrappedCode);
auto Selection = rangeOrPoint(Input);

TestTU TU;
TU.Filename = FileName;
TU.HeaderCode = Header;
TU.AdditionalFiles = std::move(ExtraFiles);
TU.Code = Input.code();
TU.ExtraArgs = ExtraArgs;
ParsedAST AST = TU.build();
Tweak::Selection S(Index.get(), AST, Selection.first, Selection.second);

auto T = prepareTweak(TweakID, S);
if (!T) {
llvm::consumeError(T.takeError());
return "unavailable";
}
llvm::Expected<Tweak::Effect> Result = (*T)->apply(S);
auto Result = applyTweak(AST, Input, TweakID, Index.get());
if (!Result)
return "fail: " + llvm::toString(Result.takeError());
if (Result->ShowMessage)
return "message:\n" + *Result->ShowMessage;
if (Result->ApplyEdits.empty())
return "unavailable";
if (!*Result)
return "fail: " + llvm::toString(Result->takeError());
const auto &Effect = **Result;
if ((*Result)->ShowMessage)
return "message:\n" + *Effect.ShowMessage;
if (Effect.ApplyEdits.empty())
return "no effect";

std::string EditedMainFile;
for (auto &It : Result->ApplyEdits) {
for (auto &It : Effect.ApplyEdits) {
auto NewText = It.second.apply();
if (!NewText)
return "bad edits: " + llvm::toString(NewText.takeError());
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/TweakTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1971,7 +1971,7 @@ TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {
// Basic check for function body and signature.
EXPECT_AVAILABLE(R"cpp(
class Bar {
[[void [[f^o^o]]() [[{ return; }]]]]
[[void [[f^o^o^]]() [[{ return; }]]]]
};
void foo();
Expand Down
1 change: 0 additions & 1 deletion clang/lib/Tooling/Syntax/Tokens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ syntax::spelledTokensTouching(SourceLocation Loc,
assert(Loc.isFileID());
llvm::ArrayRef<syntax::Token> All =
Tokens.spelledTokens(Tokens.sourceManager().getFileID(Loc));
// Comparing SourceLocations is well-defined within a FileID.
auto *Right = llvm::partition_point(
All, [&](const syntax::Token &Tok) { return Tok.location() < Loc; });
bool AcceptRight = Right != All.end() && Right->location() <= Loc;
Expand Down