forked from OSchip/llvm-project
333 lines
10 KiB
C++
333 lines
10 KiB
C++
//===-- SelectionTests.cpp - ----------------------------------------------===//
|
|
//
|
|
// 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 "Annotations.h"
|
|
#include "Selection.h"
|
|
#include "SourceCode.h"
|
|
#include "TestTU.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
using ::testing::UnorderedElementsAreArray;
|
|
|
|
SelectionTree makeSelectionTree(const StringRef MarkedCode, ParsedAST &AST) {
|
|
Annotations Test(MarkedCode);
|
|
switch (Test.points().size()) {
|
|
case 1: // Point selection.
|
|
return SelectionTree(AST.getASTContext(),
|
|
cantFail(positionToOffset(Test.code(), Test.point())));
|
|
case 2: // Range selection.
|
|
return SelectionTree(
|
|
AST.getASTContext(),
|
|
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(), 0u, 0u);
|
|
}
|
|
}
|
|
|
|
Range nodeRange(const SelectionTree::Node *N, ParsedAST &AST) {
|
|
if (!N)
|
|
return Range{};
|
|
SourceManager &SM = AST.getSourceManager();
|
|
StringRef Buffer = SM.getBufferData(SM.getMainFileID());
|
|
SourceRange SR = N->ASTNode.getSourceRange();
|
|
SR.setBegin(SM.getFileLoc(SR.getBegin()));
|
|
SR.setEnd(SM.getFileLoc(SR.getEnd()));
|
|
CharSourceRange R =
|
|
Lexer::getAsCharRange(SR, SM, AST.getASTContext().getLangOpts());
|
|
return Range{offsetToPosition(Buffer, SM.getFileOffset(R.getBegin())),
|
|
offsetToPosition(Buffer, SM.getFileOffset(R.getEnd()))};
|
|
}
|
|
|
|
std::string nodeKind(const SelectionTree::Node *N) {
|
|
if (!N)
|
|
return "<null>";
|
|
return N->ASTNode.getNodeKind().asStringRef().str();
|
|
}
|
|
|
|
std::vector<const SelectionTree::Node *> allNodes(const SelectionTree &T) {
|
|
std::vector<const SelectionTree::Node *> Result = {T.root()};
|
|
for (unsigned I = 0; I < Result.size(); ++I) {
|
|
const SelectionTree::Node *N = Result[I];
|
|
Result.insert(Result.end(), N->Children.begin(), N->Children.end());
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
// Returns true if Common is a descendent of Root.
|
|
// Verifies nothing is selected above Common.
|
|
bool verifyCommonAncestor(const SelectionTree::Node *Root,
|
|
const SelectionTree::Node *Common,
|
|
StringRef MarkedCode) {
|
|
if (Root == Common)
|
|
return true;
|
|
if (Root->Selected)
|
|
ADD_FAILURE() << "Selected nodes outside common ancestor\n" << MarkedCode;
|
|
bool Seen = false;
|
|
for (const SelectionTree::Node *Child : Root->Children)
|
|
if (verifyCommonAncestor(Child, Common, MarkedCode)) {
|
|
if (Seen)
|
|
ADD_FAILURE() << "Saw common ancestor twice\n" << MarkedCode;
|
|
Seen = true;
|
|
}
|
|
return Seen;
|
|
}
|
|
|
|
TEST(SelectionTest, CommonAncestor) {
|
|
struct Case {
|
|
// Selection is between ^marks^.
|
|
// common ancestor marked with a [[range]].
|
|
const char *Code;
|
|
const char *CommonAncestorKind;
|
|
};
|
|
Case Cases[] = {
|
|
{
|
|
R"cpp(
|
|
template <typename T>
|
|
int x = [[T::^U::]]ccc();
|
|
)cpp",
|
|
"NestedNameSpecifierLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = AAA::[[B^B^B]]::ccc();
|
|
)cpp",
|
|
"TypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = AAA::[[B^BB^]]::ccc();
|
|
)cpp",
|
|
"TypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = [[AAA::BBB::c^c^c]]();
|
|
)cpp",
|
|
"DeclRefExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = [[AAA::BBB::cc^c(^)]];
|
|
)cpp",
|
|
"CallExpr",
|
|
},
|
|
|
|
{
|
|
R"cpp(
|
|
void foo() { [[if (1^11) { return; } else {^ }]] }
|
|
)cpp",
|
|
"IfStmt",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X()
|
|
void bar() { CALL_FUNCTION([[f^o^o]]); }
|
|
)cpp",
|
|
"DeclRefExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X()
|
|
void bar() [[{ CALL_FUNC^TION(fo^o); }]]
|
|
)cpp",
|
|
"CompoundStmt",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X()
|
|
void bar() [[{ C^ALL_FUNC^TION(foo); }]]
|
|
)cpp",
|
|
"CompoundStmt",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X^()^
|
|
void bar() { CALL_FUNCTION(foo); }
|
|
)cpp",
|
|
nullptr,
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct S { S(const char*); };
|
|
S [[s ^= "foo"]];
|
|
)cpp",
|
|
"CXXConstructExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct S { S(const char*); };
|
|
[[S ^s = "foo"]];
|
|
)cpp",
|
|
"VarDecl",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[^void]] (*S)(int) = nullptr;
|
|
)cpp",
|
|
"TypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void (*S)^(int)]] = nullptr;
|
|
)cpp",
|
|
"TypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void (^*S)(int)]] = nullptr;
|
|
)cpp",
|
|
"TypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void (*^S)(int) = nullptr]];
|
|
)cpp",
|
|
"VarDecl",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void ^(*S)(int)]] = nullptr;
|
|
)cpp",
|
|
"TypeLoc",
|
|
},
|
|
|
|
// Point selections.
|
|
{"void foo() { [[^foo]](); }", "DeclRefExpr"},
|
|
{"void foo() { [[f^oo]](); }", "DeclRefExpr"},
|
|
{"void foo() { [[fo^o]](); }", "DeclRefExpr"},
|
|
{"void foo() { [[foo^()]]; }", "CallExpr"},
|
|
{"void foo() { [[foo^]] (); }", "DeclRefExpr"},
|
|
{"int bar; void foo() [[{ foo (); }]]^", "CompoundStmt"},
|
|
|
|
// Tricky case: FunctionTypeLoc in FunctionDecl has a hole in it.
|
|
{"[[^void]] foo();", "TypeLoc"},
|
|
{"[[void foo^()]];", "TypeLoc"},
|
|
{"[[^void foo^()]];", "FunctionDecl"},
|
|
{"[[void ^foo()]];", "FunctionDecl"},
|
|
// Tricky case: two VarDecls share a specifier.
|
|
{"[[int ^a]], b;", "VarDecl"},
|
|
{"[[int a, ^b]];", "VarDecl"},
|
|
// Tricky case: anonymous struct is a sibling of the VarDecl.
|
|
{"[[st^ruct {int x;}]] y;", "CXXRecordDecl"},
|
|
{"[[struct {int x;} ^y]];", "VarDecl"},
|
|
{"struct {[[int ^x]];} y;", "FieldDecl"},
|
|
|
|
{"^", nullptr},
|
|
{"void foo() { [[foo^^]] (); }", "DeclRefExpr"},
|
|
|
|
// FIXME: Ideally we'd get a declstmt or the VarDecl itself here.
|
|
// This doesn't happen now; the RAV doesn't traverse a node containing ;.
|
|
{"int x = 42;^", nullptr},
|
|
{"int x = 42^;", nullptr},
|
|
|
|
// Node types that have caused problems in the past.
|
|
{"template <typename T> void foo() { [[^T]] t; }", "TypeLoc"},
|
|
|
|
// No crash
|
|
{
|
|
R"cpp(
|
|
template <class T> struct Foo {};
|
|
template <[[template<class> class /*cursor here*/^U]]>
|
|
struct Foo<U<int>*> {};
|
|
)cpp",
|
|
"TemplateTemplateParmDecl"},
|
|
};
|
|
for (const Case &C : Cases) {
|
|
Annotations Test(C.Code);
|
|
auto AST = TestTU::withCode(Test.code()).build();
|
|
auto T = makeSelectionTree(C.Code, AST);
|
|
|
|
if (Test.ranges().empty()) {
|
|
// If no [[range]] is marked in the example, there should be no selection.
|
|
EXPECT_FALSE(T.commonAncestor()) << C.Code << "\n" << T;
|
|
EXPECT_FALSE(T.root()) << C.Code << "\n" << T;
|
|
} else {
|
|
// If there is an expected selection, both common ancestor and root
|
|
// should exist with the appropriate node types in them.
|
|
EXPECT_EQ(C.CommonAncestorKind, nodeKind(T.commonAncestor()))
|
|
<< C.Code << "\n"
|
|
<< T;
|
|
EXPECT_EQ("TranslationUnitDecl", nodeKind(T.root())) << C.Code;
|
|
// Convert the reported common ancestor to a range and verify it.
|
|
EXPECT_EQ(nodeRange(T.commonAncestor(), AST), Test.range())
|
|
<< C.Code << "\n"
|
|
<< T;
|
|
|
|
// Check that common ancestor is reachable on exactly one path from root,
|
|
// and no nodes outside it are selected.
|
|
EXPECT_TRUE(verifyCommonAncestor(T.root(), T.commonAncestor(), C.Code))
|
|
<< C.Code;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regression test: this used to match the injected X, not the outer X.
|
|
TEST(SelectionTest, InjectedClassName) {
|
|
const char* Code = "struct ^X { int x; };";
|
|
auto AST = TestTU::withCode(Annotations(Code).code()).build();
|
|
auto T = makeSelectionTree(Code, AST);
|
|
ASSERT_EQ("CXXRecordDecl", nodeKind(T.commonAncestor())) << T;
|
|
auto *D = dyn_cast<CXXRecordDecl>(T.commonAncestor()->ASTNode.get<Decl>());
|
|
EXPECT_FALSE(D->isInjectedClassName());
|
|
}
|
|
|
|
TEST(SelectionTest, Selected) {
|
|
// Selection with ^marks^.
|
|
// Partially selected nodes marked with a [[range]].
|
|
// Completely selected nodes marked with a $C[[range]].
|
|
const char *Cases[] = {
|
|
R"cpp( int abc, xyz = [[^ab^c]]; )cpp",
|
|
R"cpp( int abc, xyz = [[a^bc^]]; )cpp",
|
|
R"cpp( int abc, xyz = $C[[^abc^]]; )cpp",
|
|
R"cpp(
|
|
void foo() {
|
|
[[if ([[1^11]]) $C[[{
|
|
$C[[return]];
|
|
}]] else [[{^
|
|
}]]]]
|
|
}
|
|
)cpp",
|
|
R"cpp(
|
|
template <class T>
|
|
struct unique_ptr {};
|
|
void foo(^$C[[unique_ptr<unique_ptr<$C[[int]]>>]]^ a) {}
|
|
)cpp",
|
|
};
|
|
for (const char *C : Cases) {
|
|
Annotations Test(C);
|
|
auto AST = TestTU::withCode(Test.code()).build();
|
|
auto T = makeSelectionTree(C, AST);
|
|
|
|
std::vector<Range> Complete, Partial;
|
|
for (const SelectionTree::Node *N : allNodes(T))
|
|
if (N->Selected == SelectionTree::Complete)
|
|
Complete.push_back(nodeRange(N, AST));
|
|
else if (N->Selected == SelectionTree::Partial)
|
|
Partial.push_back(nodeRange(N, AST));
|
|
EXPECT_THAT(Complete, UnorderedElementsAreArray(Test.ranges("C"))) << C;
|
|
EXPECT_THAT(Partial, UnorderedElementsAreArray(Test.ranges())) << C;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|