78 changes: 78 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,63 @@ bool fromJSON(const json::Expr &Params, CompletionClientCapabilities &R) {
return true;
}

bool fromJSON(const json::Expr &E, SymbolKind &Out) {
if (auto T = E.asInteger()) {
if (*T < static_cast<int>(SymbolKind::File) ||
*T > static_cast<int>(SymbolKind::TypeParameter))
return false;
Out = static_cast<SymbolKind>(*T);
return true;
}
return false;
}

bool fromJSON(const json::Expr &E, std::vector<SymbolKind> &Out) {
if (auto *A = E.asArray()) {
Out.clear();
for (size_t I = 0; I < A->size(); ++I) {
SymbolKind KindOut;
if (fromJSON((*A)[I], KindOut))
Out.push_back(KindOut);
}
return true;
}
return false;
}

bool fromJSON(const json::Expr &Params, SymbolKindCapabilities &R) {
json::ObjectMapper O(Params);
return O && O.map("valueSet", R.valueSet);
}

SymbolKind adjustKindToCapability(SymbolKind Kind,
SymbolKindBitset &supportedSymbolKinds) {
auto KindVal = static_cast<size_t>(Kind);
if (KindVal >= SymbolKindMin && KindVal <= supportedSymbolKinds.size() &&
supportedSymbolKinds[KindVal])
return Kind;

switch (Kind) {
// Provide some fall backs for common kinds that are close enough.
case SymbolKind::Struct:
return SymbolKind::Class;
case SymbolKind::EnumMember:
return SymbolKind::Enum;
default:
return SymbolKind::String;
}
}

bool fromJSON(const json::Expr &Params, WorkspaceSymbolCapabilities &R) {
json::ObjectMapper O(Params);
return O && O.map("symbolKind", R.symbolKind);
}

bool fromJSON(const json::Expr &Params, WorkspaceClientCapabilities &R) {
json::ObjectMapper O(Params);
return O && O.map("symbol", R.symbol);
}

bool fromJSON(const json::Expr &Params, TextDocumentClientCapabilities &R) {
json::ObjectMapper O(Params);
if (!O)
Expand All @@ -189,6 +246,7 @@ bool fromJSON(const json::Expr &Params, ClientCapabilities &R) {
if (!O)
return false;
O.map("textDocument", R.textDocument);
O.map("workspace", R.workspace);
return true;
}

Expand Down Expand Up @@ -351,6 +409,26 @@ bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) {
return false; // Unrecognized command.
}

json::Expr toJSON(const SymbolInformation &P) {
return json::obj{
{"name", P.name},
{"kind", static_cast<int>(P.kind)},
{"location", P.location},
{"containerName", P.containerName},
};
}

llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
const SymbolInformation &SI) {
O << SI.containerName << "::" << SI.name << " - " << toJSON(SI);
return O;
}

bool fromJSON(const json::Expr &Params, WorkspaceSymbolParams &R) {
json::ObjectMapper O(Params);
return O && O.map("query", R.query);
}

json::Expr toJSON(const Command &C) {
auto Cmd = json::obj{{"title", C.title}, {"command", C.command}};
if (C.workspaceEdit)
Expand Down
90 changes: 88 additions & 2 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "JSONExpr.h"
#include "URI.h"
#include "llvm/ADT/Optional.h"
#include <bitset>
#include <string>
#include <vector>

Expand Down Expand Up @@ -237,6 +238,67 @@ struct CompletionClientCapabilities {
};
bool fromJSON(const json::Expr &, CompletionClientCapabilities &);

/// A symbol kind.
enum class SymbolKind {
File = 1,
Module = 2,
Namespace = 3,
Package = 4,
Class = 5,
Method = 6,
Property = 7,
Field = 8,
Constructor = 9,
Enum = 10,
Interface = 11,
Function = 12,
Variable = 13,
Constant = 14,
String = 15,
Number = 16,
Boolean = 17,
Array = 18,
Object = 19,
Key = 20,
Null = 21,
EnumMember = 22,
Struct = 23,
Event = 24,
Operator = 25,
TypeParameter = 26
};

constexpr auto SymbolKindMin = static_cast<size_t>(SymbolKind::File);
constexpr auto SymbolKindMax = static_cast<size_t>(SymbolKind::TypeParameter);
using SymbolKindBitset = std::bitset<SymbolKindMax + 1>;

bool fromJSON(const json::Expr &, SymbolKind &);

struct SymbolKindCapabilities {
/// The SymbolKinds that the client supports. If not set, the client only
/// supports <= SymbolKind::Array and will not fall back to a valid default
/// value.
llvm::Optional<std::vector<SymbolKind>> valueSet;
};
bool fromJSON(const json::Expr &, std::vector<SymbolKind> &);
bool fromJSON(const json::Expr &, SymbolKindCapabilities &);
SymbolKind adjustKindToCapability(SymbolKind Kind,
SymbolKindBitset &supportedSymbolKinds);

struct WorkspaceSymbolCapabilities {
/// Capabilities SymbolKind.
llvm::Optional<SymbolKindCapabilities> symbolKind;
};
bool fromJSON(const json::Expr &, WorkspaceSymbolCapabilities &);

// FIXME: most of the capabilities are missing from this struct. Only the ones
// used by clangd are currently there.
struct WorkspaceClientCapabilities {
/// Capabilities specific to `workspace/symbol`.
llvm::Optional<WorkspaceSymbolCapabilities> symbol;
};
bool fromJSON(const json::Expr &, WorkspaceClientCapabilities &);

// FIXME: most of the capabilities are missing from this struct. Only the ones
// used by clangd are currently there.
struct TextDocumentClientCapabilities {
Expand All @@ -247,8 +309,7 @@ bool fromJSON(const json::Expr &, TextDocumentClientCapabilities &);

struct ClientCapabilities {
// Workspace specific client capabilities.
// NOTE: not used by clangd at the moment.
// WorkspaceClientCapabilities workspace;
llvm::Optional<WorkspaceClientCapabilities> workspace;

// Text document specific client capabilities.
TextDocumentClientCapabilities textDocument;
Expand Down Expand Up @@ -525,6 +586,31 @@ struct Command : public ExecuteCommandParams {

json::Expr toJSON(const Command &C);

/// Represents information about programming constructs like variables, classes,
/// interfaces etc.
struct SymbolInformation {
/// The name of this symbol.
std::string name;

/// The kind of this symbol.
SymbolKind kind;

/// The location of this symbol.
Location location;

/// The name of the symbol containing this symbol.
std::string containerName;
};
json::Expr toJSON(const SymbolInformation &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolInformation &);

/// The parameters of a Workspace Symbol Request.
struct WorkspaceSymbolParams {
/// A non-empty query string
std::string query;
};
bool fromJSON(const json::Expr &, WorkspaceSymbolParams &);

struct ApplyWorkspaceEditParams {
WorkspaceEdit edit;
};
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/ProtocolHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
&ProtocolCallbacks::onDocumentHighlight);
Register("workspace/didChangeConfiguration",
&ProtocolCallbacks::onChangeConfiguration);
Register("workspace/symbol", &ProtocolCallbacks::onWorkspaceSymbol);
}
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/ProtocolHandlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ProtocolCallbacks {
virtual void onSwitchSourceHeader(TextDocumentIdentifier &Params) = 0;
virtual void onFileEvent(DidChangeWatchedFilesParams &Params) = 0;
virtual void onCommand(ExecuteCommandParams &Params) = 0;
virtual void onWorkspaceSymbol(WorkspaceSymbolParams &Params) = 0;
virtual void onRename(RenameParams &Parames) = 0;
virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0;
virtual void onHover(TextDocumentPositionParams &Params) = 0;
Expand Down
8 changes: 8 additions & 0 deletions clang-tools-extra/clangd/SourceCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,13 @@ Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) {
return {Begin, End};
}

std::pair<llvm::StringRef, llvm::StringRef>
splitQualifiedName(llvm::StringRef QName) {
size_t Pos = QName.rfind("::");
if (Pos == llvm::StringRef::npos)
return {StringRef(), QName};
return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)};
}

} // namespace clangd
} // namespace clang
5 changes: 5 additions & 0 deletions clang-tools-extra/clangd/SourceCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc);
// Note that clang also uses closed source ranges, which this can't handle!
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R);

/// From "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no
/// qualifier.
std::pair<llvm::StringRef, llvm::StringRef>
splitQualifiedName(llvm::StringRef QName);

} // namespace clangd
} // namespace clang
#endif
12 changes: 2 additions & 10 deletions clang-tools-extra/clangd/index/SymbolCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "../AST.h"
#include "../CodeCompletionStrings.h"
#include "../Logger.h"
#include "../SourceCode.h"
#include "../URI.h"
#include "CanonicalIncludes.h"
#include "clang/AST/DeclCXX.h"
Expand Down Expand Up @@ -89,16 +90,6 @@ llvm::Optional<std::string> toURI(const SourceManager &SM, StringRef Path,
return llvm::None;
}

// "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no qualifier.
std::pair<llvm::StringRef, llvm::StringRef>
splitQualifiedName(llvm::StringRef QName) {
assert(!QName.startswith("::") && "Qualified names should not start with ::");
size_t Pos = QName.rfind("::");
if (Pos == llvm::StringRef::npos)
return {StringRef(), QName};
return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)};
}

bool shouldFilterDecl(const NamedDecl *ND, ASTContext *ASTCtx,
const SymbolCollector::Options &Opts) {
using namespace clang::ast_matchers;
Expand Down Expand Up @@ -321,6 +312,7 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
Policy.SuppressUnwrittenScope = true;
ND.printQualifiedName(OS, Policy);
OS.flush();
assert(!StringRef(QName).startswith("::"));

Symbol S;
S.ID = std::move(ID);
Expand Down
22 changes: 11 additions & 11 deletions clang-tools-extra/clangd/tool/ClangdMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ static llvm::cl::opt<PCHStorageFlag> PCHStorage(
clEnumValN(PCHStorageFlag::Memory, "memory", "store PCHs in memory")),
llvm::cl::init(PCHStorageFlag::Disk));

static llvm::cl::opt<int> LimitCompletionResult(
"completion-limit",
llvm::cl::desc("Limit the number of completion results returned by clangd. "
static llvm::cl::opt<int> LimitResults(
"limit-results",
llvm::cl::desc("Limit the number of results returned by clangd. "
"0 means no limit."),
llvm::cl::init(100));

Expand All @@ -118,11 +118,11 @@ static llvm::cl::opt<Path> InputMirrorFile(
"Mirror all LSP input to the specified file. Useful for debugging."),
llvm::cl::init(""), llvm::cl::Hidden);

static llvm::cl::opt<bool> EnableIndexBasedCompletion(
"enable-index-based-completion",
llvm::cl::desc(
"Enable index-based global code completion. "
"Clang uses an index built from symbols in opened files"),
static llvm::cl::opt<bool> EnableIndex(
"index",
llvm::cl::desc("Enable index-based features such as global code completion "
"and searching for symbols."
"Clang uses an index built from symbols in opened files"),
llvm::cl::init(true));

static llvm::cl::opt<Path> YamlSymbolFile(
Expand Down Expand Up @@ -220,17 +220,17 @@ int main(int argc, char *argv[]) {
}
if (!ResourceDir.empty())
Opts.ResourceDir = ResourceDir;
Opts.BuildDynamicSymbolIndex = EnableIndexBasedCompletion;
Opts.BuildDynamicSymbolIndex = EnableIndex;
std::unique_ptr<SymbolIndex> StaticIdx;
if (EnableIndexBasedCompletion && !YamlSymbolFile.empty()) {
if (EnableIndex && !YamlSymbolFile.empty()) {
StaticIdx = BuildStaticIndex(YamlSymbolFile);
Opts.StaticIndex = StaticIdx.get();
}
Opts.AsyncThreadsCount = WorkerThreadsCount;

clangd::CodeCompleteOptions CCOpts;
CCOpts.IncludeIneligibleResults = IncludeIneligibleResults;
CCOpts.Limit = LimitCompletionResult;
CCOpts.Limit = LimitResults;

// Initialize and run ClangdLSPServer.
ClangdLSPServer LSPServer(Out, CCOpts, CompileCommandsDirPath, Opts);
Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/test/clangd/initialize-params-invalid.test
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
# CHECK-NEXT: ","
# CHECK-NEXT: ]
# CHECK-NEXT: },
# CHECK-NEXT: "textDocumentSync": 2
# CHECK-NEXT: "textDocumentSync": 2,
# CHECK-NEXT: "workspaceSymbolProvider": true
# CHECK-NEXT: }
# CHECK-NEXT: }
---
Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/test/clangd/initialize-params.test
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
# CHECK-NEXT: ","
# CHECK-NEXT: ]
# CHECK-NEXT: },
# CHECK-NEXT: "textDocumentSync": 2
# CHECK-NEXT: "textDocumentSync": 2,
# CHECK-NEXT: "workspaceSymbolProvider": true
# CHECK-NEXT: }
# CHECK-NEXT: }
---
Expand Down
33 changes: 33 additions & 0 deletions clang-tools-extra/test/clangd/symbols.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# RUN: clangd -lit-test < %s | FileCheck %s
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"workspace":{"symbol":{"symbolKind":{"valueSet": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}}}},"trace":"off"}}
---
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"#include <sstream>\nvoid foo(); int main() { foo(); }\n"}}}
---
{"jsonrpc":"2.0","id":1,"method":"workspace/symbol","params":{"query":"std::basic_ostringstream"}}
# CHECK: "id": 1,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: {
# CHECK-NEXT: "containerName": "std",
# CHECK-NEXT: "kind": 5,
# CHECK-NEXT: "location": {
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": {{.*}},
# CHECK-NEXT: "line": {{.*}}
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": {{.*}},
# CHECK-NEXT: "line": {{.*}}
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/sstream"
# CHECK-NEXT: },
# CHECK-NEXT: "name": "basic_ostringstream"
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT:}
---
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
1 change: 1 addition & 0 deletions clang-tools-extra/unittests/clangd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ add_extra_unittest(ClangdTests
ContextTests.cpp
DraftStoreTests.cpp
FileIndexTests.cpp
FindSymbolsTests.cpp
FuzzyMatchTests.cpp
GlobalCompilationDatabaseTests.cpp
HeadersTests.cpp
Expand Down
247 changes: 247 additions & 0 deletions clang-tools-extra/unittests/clangd/FindSymbolsTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
//===-- FindSymbolsTests.cpp -------------------------*- C++ -*------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Annotations.h"
#include "ClangdServer.h"
#include "FindSymbols.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {

namespace {

using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;

class IgnoreDiagnostics : public DiagnosticsConsumer {
void onDiagnosticsReady(PathRef File,
std::vector<Diag> Diagnostics) override {}
};

// GMock helpers for matching SymbolInfos items.
MATCHER_P(Named, Name, "") { return arg.name == Name; }
MATCHER_P(InContainer, ContainerName, "") {
return arg.containerName == ContainerName;
}
MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; }

ClangdServer::Options optsForTests() {
auto ServerOpts = ClangdServer::optsForTest();
ServerOpts.BuildDynamicSymbolIndex = true;
return ServerOpts;
}

class WorkspaceSymbolsTest : public ::testing::Test {
public:
WorkspaceSymbolsTest()
: Server(CDB, FSProvider, DiagConsumer, optsForTests()) {}

protected:
MockFSProvider FSProvider;
MockCompilationDatabase CDB;
IgnoreDiagnostics DiagConsumer;
ClangdServer Server;
int Limit;

std::vector<SymbolInformation> getSymbols(StringRef Query) {
EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble";
auto SymbolInfos = runWorkspaceSymbols(Server, Query, Limit);
EXPECT_TRUE(bool(SymbolInfos)) << "workspaceSymbols returned an error";
return *SymbolInfos;
}

void addFile(StringRef FileName, StringRef Contents) {
auto Path = testPath(FileName);
FSProvider.Files[Path] = Contents;
Server.addDocument(Path, Contents);
}
};

} // namespace

TEST_F(WorkspaceSymbolsTest, NoMacro) {
addFile("foo.cpp", R"cpp(
#define MACRO X
)cpp");

// Macros are not in the index.
EXPECT_THAT(getSymbols("macro"), IsEmpty());
}

TEST_F(WorkspaceSymbolsTest, NoLocals) {
addFile("foo.cpp", R"cpp(
void test(int FirstParam, int SecondParam) {
struct LocalClass {};
int local_var;
})cpp");
EXPECT_THAT(getSymbols("l"), IsEmpty());
EXPECT_THAT(getSymbols("p"), IsEmpty());
}

TEST_F(WorkspaceSymbolsTest, Globals) {
addFile("foo.h", R"cpp(
int global_var;
int global_func();
struct GlobalStruct {};)cpp");
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
EXPECT_THAT(getSymbols("global"),
UnorderedElementsAre(AllOf(Named("GlobalStruct"), InContainer(""),
WithKind(SymbolKind::Struct)),
AllOf(Named("global_func"), InContainer(""),
WithKind(SymbolKind::Function)),
AllOf(Named("global_var"), InContainer(""),
WithKind(SymbolKind::Variable))));
}

TEST_F(WorkspaceSymbolsTest, Unnamed) {
addFile("foo.h", R"cpp(
struct {
int InUnnamed;
} UnnamedStruct;)cpp");
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
EXPECT_THAT(getSymbols("UnnamedStruct"),
ElementsAre(AllOf(Named("UnnamedStruct"),
WithKind(SymbolKind::Variable))));
EXPECT_THAT(getSymbols("InUnnamed"), IsEmpty());
}

TEST_F(WorkspaceSymbolsTest, InMainFile) {
addFile("foo.cpp", R"cpp(
int test() {
}
)cpp");
EXPECT_THAT(getSymbols("test"), IsEmpty());
}

TEST_F(WorkspaceSymbolsTest, Namespaces) {
addFile("foo.h", R"cpp(
namespace ans1 {
int ai1;
namespace ans2 {
int ai2;
}
}
)cpp");
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
EXPECT_THAT(
getSymbols("a"),
UnorderedElementsAre(AllOf(Named("ans1"), InContainer("")),
AllOf(Named("ai1"), InContainer("ans1")),
AllOf(Named("ans2"), InContainer("ans1")),
AllOf(Named("ai2"), InContainer("ans1::ans2"))));
EXPECT_THAT(getSymbols("::"),
ElementsAre(AllOf(Named("ans1"), InContainer(""))));
EXPECT_THAT(getSymbols("::a"),
ElementsAre(AllOf(Named("ans1"), InContainer(""))));
EXPECT_THAT(getSymbols("ans1::"),
UnorderedElementsAre(AllOf(Named("ai1"), InContainer("ans1")),
AllOf(Named("ans2"), InContainer("ans1"))));
EXPECT_THAT(getSymbols("::ans1"),
ElementsAre(AllOf(Named("ans1"), InContainer(""))));
EXPECT_THAT(getSymbols("::ans1::"),
UnorderedElementsAre(AllOf(Named("ai1"), InContainer("ans1")),
AllOf(Named("ans2"), InContainer("ans1"))));
EXPECT_THAT(getSymbols("::ans1::ans2"),
ElementsAre(AllOf(Named("ans2"), InContainer("ans1"))));
EXPECT_THAT(getSymbols("::ans1::ans2::"),
ElementsAre(AllOf(Named("ai2"), InContainer("ans1::ans2"))));
}

TEST_F(WorkspaceSymbolsTest, AnonymousNamespace) {
addFile("foo.h", R"cpp(
namespace {
void test() {}
}
)cpp");
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
EXPECT_THAT(getSymbols("test"), IsEmpty());
}

TEST_F(WorkspaceSymbolsTest, MultiFile) {
addFile("foo.h", R"cpp(
int foo() {
}
)cpp");
addFile("foo2.h", R"cpp(
int foo2() {
}
)cpp");
addFile("foo.cpp", R"cpp(
#include "foo.h"
#include "foo2.h"
)cpp");
EXPECT_THAT(getSymbols("foo"),
UnorderedElementsAre(AllOf(Named("foo"), InContainer("")),
AllOf(Named("foo2"), InContainer(""))));
}

TEST_F(WorkspaceSymbolsTest, GlobalNamespaceQueries) {
addFile("foo.h", R"cpp(
int foo() {
}
class Foo {
int a;
};
namespace ns {
int foo2() {
}
}
)cpp");
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
EXPECT_THAT(
getSymbols("::"),
UnorderedElementsAre(
AllOf(Named("Foo"), InContainer(""), WithKind(SymbolKind::Class)),
AllOf(Named("foo"), InContainer(""), WithKind(SymbolKind::Function)),
AllOf(Named("ns"), InContainer(""),
WithKind(SymbolKind::Namespace))));
EXPECT_THAT(getSymbols(":"), IsEmpty());
EXPECT_THAT(getSymbols(""), IsEmpty());
}

TEST_F(WorkspaceSymbolsTest, WithLimit) {
addFile("foo.h", R"cpp(
int foo;
int foo2;
)cpp");
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
EXPECT_THAT(getSymbols("foo"),
ElementsAre(AllOf(Named("foo"), InContainer(""),
WithKind(SymbolKind::Variable)),
AllOf(Named("foo2"), InContainer(""),
WithKind(SymbolKind::Variable))));

Limit = 1;
EXPECT_THAT(getSymbols("foo"),
ElementsAre(AnyOf((Named("foo"), InContainer("")),
AllOf(Named("foo2"), InContainer("")))));
}

} // namespace clangd
} // namespace clang
7 changes: 7 additions & 0 deletions clang-tools-extra/unittests/clangd/SyncAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,12 @@ std::string runDumpAST(ClangdServer &Server, PathRef File) {
return std::move(*Result);
}

llvm::Expected<std::vector<SymbolInformation>>
runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit) {
llvm::Optional<llvm::Expected<std::vector<SymbolInformation>>> Result;
Server.workspaceSymbols(Query, Limit, capture(Result));
return std::move(*Result);
}

} // namespace clangd
} // namespace clang
3 changes: 3 additions & 0 deletions clang-tools-extra/unittests/clangd/SyncAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ runRename(ClangdServer &Server, PathRef File, Position Pos, StringRef NewName);

std::string runDumpAST(ClangdServer &Server, PathRef File);

llvm::Expected<std::vector<SymbolInformation>>
runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit);

} // namespace clangd
} // namespace clang

Expand Down