2017-12-05 15:20:26 +08:00
|
|
|
//===-- CodeCompleteTests.cpp -----------------------------------*- C++ -*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
2017-12-20 00:50:37 +08:00
|
|
|
|
2017-12-21 00:06:05 +08:00
|
|
|
#include "Annotations.h"
|
2017-12-05 15:20:26 +08:00
|
|
|
#include "ClangdServer.h"
|
2017-12-21 00:06:05 +08:00
|
|
|
#include "CodeComplete.h"
|
2017-12-05 15:20:26 +08:00
|
|
|
#include "Compiler.h"
|
2017-12-13 20:53:16 +08:00
|
|
|
#include "Matchers.h"
|
2017-12-05 15:20:26 +08:00
|
|
|
#include "Protocol.h"
|
2017-12-19 20:23:48 +08:00
|
|
|
#include "SourceCode.h"
|
2018-02-12 19:37:28 +08:00
|
|
|
#include "SyncAPI.h"
|
2017-12-05 15:20:26 +08:00
|
|
|
#include "TestFS.h"
|
2017-12-20 00:50:37 +08:00
|
|
|
#include "index/MemIndex.h"
|
2018-05-24 22:49:23 +08:00
|
|
|
#include "llvm/Support/Error.h"
|
2018-05-28 20:11:37 +08:00
|
|
|
#include "llvm/Testing/Support/Error.h"
|
2017-12-06 04:11:29 +08:00
|
|
|
#include "gmock/gmock.h"
|
2017-12-05 15:20:26 +08:00
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
2017-12-06 04:11:29 +08:00
|
|
|
|
2017-12-05 15:20:26 +08:00
|
|
|
namespace {
|
|
|
|
using namespace llvm;
|
2017-12-06 04:11:29 +08:00
|
|
|
using ::testing::AllOf;
|
|
|
|
using ::testing::Contains;
|
2017-12-29 22:59:22 +08:00
|
|
|
using ::testing::Each;
|
2017-12-06 04:11:29 +08:00
|
|
|
using ::testing::ElementsAre;
|
2018-03-12 23:28:22 +08:00
|
|
|
using ::testing::Field;
|
[clangd] Add "member" symbols to the index
Summary:
This adds more symbols to the index:
- member variables and functions
- enum constants in scoped enums
The code completion behavior should remain intact but workspace symbols should
now provide much more useful symbols.
Other symbols should be considered such as the ones in "main files" (files not
being included) but this can be done separately as this introduces its fair
share of problems.
Signed-off-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
Reviewers: ioeric, sammccall
Reviewed By: ioeric, sammccall
Subscribers: hokein, sammccall, jkorous, klimek, ilya-biryukov, jkorous-apple, ioeric, MaskRay, cfe-commits
Differential Revision: https://reviews.llvm.org/D44954
llvm-svn: 334017
2018-06-05 22:01:40 +08:00
|
|
|
using ::testing::IsEmpty;
|
2017-12-06 04:11:29 +08:00
|
|
|
using ::testing::Not;
|
2018-01-13 02:30:08 +08:00
|
|
|
using ::testing::UnorderedElementsAre;
|
2017-12-05 15:20:26 +08:00
|
|
|
|
|
|
|
class IgnoreDiagnostics : public DiagnosticsConsumer {
|
2018-03-12 23:28:22 +08:00
|
|
|
void onDiagnosticsReady(PathRef File,
|
2018-03-13 07:22:35 +08:00
|
|
|
std::vector<Diag> Diagnostics) override {}
|
2017-12-05 15:20:26 +08:00
|
|
|
};
|
|
|
|
|
2017-12-06 04:11:29 +08:00
|
|
|
// GMock helpers for matching completion items.
|
|
|
|
MATCHER_P(Named, Name, "") { return arg.insertText == Name; }
|
2017-12-08 23:00:59 +08:00
|
|
|
MATCHER_P(Labeled, Label, "") { return arg.label == Label; }
|
|
|
|
MATCHER_P(Kind, K, "") { return arg.kind == K; }
|
2017-12-20 00:50:37 +08:00
|
|
|
MATCHER_P(Filter, F, "") { return arg.filterText == F; }
|
2018-01-10 01:32:00 +08:00
|
|
|
MATCHER_P(Doc, D, "") { return arg.documentation == D; }
|
|
|
|
MATCHER_P(Detail, D, "") { return arg.detail == D; }
|
2018-05-15 23:29:32 +08:00
|
|
|
MATCHER_P(InsertInclude, IncludeHeader, "") {
|
|
|
|
if (arg.additionalTextEdits.size() != 1)
|
|
|
|
return false;
|
|
|
|
const auto &Edit = arg.additionalTextEdits[0];
|
|
|
|
if (Edit.range.start != Edit.range.end)
|
|
|
|
return false;
|
|
|
|
SmallVector<StringRef, 2> Matches;
|
|
|
|
llvm::Regex RE(R"(#include[ ]*(["<][^">]*[">]))");
|
|
|
|
return RE.match(Edit.newText, &Matches) && Matches[1] == IncludeHeader;
|
|
|
|
}
|
2017-12-08 23:00:59 +08:00
|
|
|
MATCHER_P(PlainText, Text, "") {
|
|
|
|
return arg.insertTextFormat == clangd::InsertTextFormat::PlainText &&
|
|
|
|
arg.insertText == Text;
|
|
|
|
}
|
|
|
|
MATCHER_P(Snippet, Text, "") {
|
|
|
|
return arg.insertTextFormat == clangd::InsertTextFormat::Snippet &&
|
|
|
|
arg.insertText == Text;
|
|
|
|
}
|
2018-01-19 22:34:02 +08:00
|
|
|
MATCHER(NameContainsFilter, "") {
|
2017-12-29 22:59:22 +08:00
|
|
|
if (arg.filterText.empty())
|
|
|
|
return true;
|
|
|
|
return llvm::StringRef(arg.insertText).contains(arg.filterText);
|
|
|
|
}
|
2018-05-15 23:29:32 +08:00
|
|
|
MATCHER(HasAdditionalEdits, "") { return !arg.additionalTextEdits.empty(); }
|
|
|
|
|
2017-12-06 04:11:29 +08:00
|
|
|
// Shorthand for Contains(Named(Name)).
|
|
|
|
Matcher<const std::vector<CompletionItem> &> Has(std::string Name) {
|
|
|
|
return Contains(Named(std::move(Name)));
|
2017-12-05 15:20:26 +08:00
|
|
|
}
|
2017-12-08 23:00:59 +08:00
|
|
|
Matcher<const std::vector<CompletionItem> &> Has(std::string Name,
|
|
|
|
CompletionItemKind K) {
|
|
|
|
return Contains(AllOf(Named(std::move(Name)), Kind(K)));
|
2017-12-06 04:11:29 +08:00
|
|
|
}
|
2017-12-08 23:00:59 +08:00
|
|
|
MATCHER(IsDocumented, "") { return !arg.documentation.empty(); }
|
2017-12-06 04:11:29 +08:00
|
|
|
|
2018-01-18 17:27:56 +08:00
|
|
|
std::unique_ptr<SymbolIndex> memIndex(std::vector<Symbol> Symbols) {
|
|
|
|
SymbolSlab::Builder Slab;
|
|
|
|
for (const auto &Sym : Symbols)
|
|
|
|
Slab.insert(Sym);
|
|
|
|
return MemIndex::build(std::move(Slab).build());
|
|
|
|
}
|
|
|
|
|
2018-05-15 23:29:32 +08:00
|
|
|
CompletionList completions(ClangdServer &Server, StringRef Text,
|
2018-01-18 17:27:56 +08:00
|
|
|
std::vector<Symbol> IndexSymbols = {},
|
2017-12-06 04:11:29 +08:00
|
|
|
clangd::CodeCompleteOptions Opts = {}) {
|
2018-01-18 17:27:56 +08:00
|
|
|
std::unique_ptr<SymbolIndex> OverrideIndex;
|
|
|
|
if (!IndexSymbols.empty()) {
|
|
|
|
assert(!Opts.Index && "both Index and IndexSymbols given!");
|
|
|
|
OverrideIndex = memIndex(std::move(IndexSymbols));
|
|
|
|
Opts.Index = OverrideIndex.get();
|
|
|
|
}
|
|
|
|
|
2018-02-16 17:41:43 +08:00
|
|
|
auto File = testPath("foo.cpp");
|
2017-12-21 00:06:05 +08:00
|
|
|
Annotations Test(Text);
|
2018-03-06 01:28:54 +08:00
|
|
|
runAddDocument(Server, File, Test.code());
|
2018-03-13 07:22:35 +08:00
|
|
|
auto CompletionList =
|
|
|
|
cantFail(runCodeComplete(Server, File, Test.point(), Opts));
|
2017-12-29 22:59:22 +08:00
|
|
|
// Sanity-check that filterText is valid.
|
2018-01-19 22:34:02 +08:00
|
|
|
EXPECT_THAT(CompletionList.items, Each(NameContainsFilter()));
|
2017-12-29 22:59:22 +08:00
|
|
|
return CompletionList;
|
2017-12-06 04:11:29 +08:00
|
|
|
}
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2018-05-15 23:29:32 +08:00
|
|
|
// Builds a server and runs code completion.
|
|
|
|
// If IndexSymbols is non-empty, an index will be built and passed to opts.
|
|
|
|
CompletionList completions(StringRef Text,
|
|
|
|
std::vector<Symbol> IndexSymbols = {},
|
|
|
|
clangd::CodeCompleteOptions Opts = {}) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
return completions(Server, Text, std::move(IndexSymbols), std::move(Opts));
|
|
|
|
}
|
|
|
|
|
2018-01-19 22:34:02 +08:00
|
|
|
std::string replace(StringRef Haystack, StringRef Needle, StringRef Repl) {
|
|
|
|
std::string Result;
|
|
|
|
raw_string_ostream OS(Result);
|
|
|
|
std::pair<StringRef, StringRef> Split;
|
|
|
|
for (Split = Haystack.split(Needle); !Split.second.empty();
|
|
|
|
Split = Split.first.split(Needle))
|
|
|
|
OS << Split.first << Repl;
|
|
|
|
Result += Split.first;
|
|
|
|
OS.flush();
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2018-01-18 17:27:56 +08:00
|
|
|
// Helpers to produce fake index symbols for memIndex() or completions().
|
2018-01-19 22:34:02 +08:00
|
|
|
// USRFormat is a regex replacement string for the unqualified part of the USR.
|
|
|
|
Symbol sym(StringRef QName, index::SymbolKind Kind, StringRef USRFormat) {
|
2018-01-18 17:27:56 +08:00
|
|
|
Symbol Sym;
|
2018-01-19 22:34:02 +08:00
|
|
|
std::string USR = "c:"; // We synthesize a few simple cases of USRs by hand!
|
2018-01-18 17:27:56 +08:00
|
|
|
size_t Pos = QName.rfind("::");
|
|
|
|
if (Pos == llvm::StringRef::npos) {
|
|
|
|
Sym.Name = QName;
|
|
|
|
Sym.Scope = "";
|
|
|
|
} else {
|
|
|
|
Sym.Name = QName.substr(Pos + 2);
|
2018-01-20 06:18:21 +08:00
|
|
|
Sym.Scope = QName.substr(0, Pos + 2);
|
|
|
|
USR += "@N@" + replace(QName.substr(0, Pos), "::", "@N@"); // ns:: -> @N@ns
|
2018-01-18 17:27:56 +08:00
|
|
|
}
|
2018-01-19 22:34:02 +08:00
|
|
|
USR += Regex("^.*$").sub(USRFormat, Sym.Name); // e.g. func -> @F@func#
|
|
|
|
Sym.ID = SymbolID(USR);
|
2018-01-18 17:27:56 +08:00
|
|
|
Sym.CompletionPlainInsertText = Sym.Name;
|
2018-01-19 22:34:02 +08:00
|
|
|
Sym.CompletionSnippetInsertText = Sym.Name;
|
2018-01-18 17:27:56 +08:00
|
|
|
Sym.CompletionLabel = Sym.Name;
|
|
|
|
Sym.SymInfo.Kind = Kind;
|
[clangd] Add "member" symbols to the index
Summary:
This adds more symbols to the index:
- member variables and functions
- enum constants in scoped enums
The code completion behavior should remain intact but workspace symbols should
now provide much more useful symbols.
Other symbols should be considered such as the ones in "main files" (files not
being included) but this can be done separately as this introduces its fair
share of problems.
Signed-off-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
Reviewers: ioeric, sammccall
Reviewed By: ioeric, sammccall
Subscribers: hokein, sammccall, jkorous, klimek, ilya-biryukov, jkorous-apple, ioeric, MaskRay, cfe-commits
Differential Revision: https://reviews.llvm.org/D44954
llvm-svn: 334017
2018-06-05 22:01:40 +08:00
|
|
|
Sym.IsIndexedForCodeCompletion = true;
|
2018-01-18 17:27:56 +08:00
|
|
|
return Sym;
|
|
|
|
}
|
2018-01-19 22:34:02 +08:00
|
|
|
Symbol func(StringRef Name) { // Assumes the function has no args.
|
|
|
|
return sym(Name, index::SymbolKind::Function, "@F@\\0#"); // no args
|
|
|
|
}
|
|
|
|
Symbol cls(StringRef Name) {
|
2018-05-30 17:03:39 +08:00
|
|
|
return sym(Name, index::SymbolKind::Class, "@S@\\0");
|
2018-01-19 22:34:02 +08:00
|
|
|
}
|
|
|
|
Symbol var(StringRef Name) {
|
|
|
|
return sym(Name, index::SymbolKind::Variable, "@\\0");
|
|
|
|
}
|
2018-05-03 22:53:02 +08:00
|
|
|
Symbol ns(StringRef Name) {
|
|
|
|
return sym(Name, index::SymbolKind::Namespace, "@N@\\0");
|
|
|
|
}
|
|
|
|
Symbol withReferences(int N, Symbol S) {
|
|
|
|
S.References = N;
|
|
|
|
return S;
|
|
|
|
}
|
2018-01-18 17:27:56 +08:00
|
|
|
|
2017-12-06 04:11:29 +08:00
|
|
|
TEST(CompletionTest, Limit) {
|
|
|
|
clangd::CodeCompleteOptions Opts;
|
|
|
|
Opts.Limit = 2;
|
|
|
|
auto Results = completions(R"cpp(
|
2017-12-05 15:20:26 +08:00
|
|
|
struct ClassWithMembers {
|
|
|
|
int AAA();
|
|
|
|
int BBB();
|
|
|
|
int CCC();
|
|
|
|
}
|
2017-12-06 04:11:29 +08:00
|
|
|
int main() { ClassWithMembers().^ }
|
2017-12-05 15:20:26 +08:00
|
|
|
)cpp",
|
2018-01-18 17:27:56 +08:00
|
|
|
/*IndexSymbols=*/{}, Opts);
|
2017-12-05 15:20:26 +08:00
|
|
|
|
|
|
|
EXPECT_TRUE(Results.isIncomplete);
|
2017-12-06 04:11:29 +08:00
|
|
|
EXPECT_THAT(Results.items, ElementsAre(Named("AAA"), Named("BBB")));
|
2017-12-05 15:20:26 +08:00
|
|
|
}
|
|
|
|
|
2017-12-06 04:11:29 +08:00
|
|
|
TEST(CompletionTest, Filter) {
|
|
|
|
std::string Body = R"cpp(
|
2018-06-14 21:50:30 +08:00
|
|
|
#define MotorCar
|
|
|
|
int Car;
|
2017-12-05 15:20:26 +08:00
|
|
|
struct S {
|
|
|
|
int FooBar;
|
|
|
|
int FooBaz;
|
|
|
|
int Qux;
|
|
|
|
};
|
|
|
|
)cpp";
|
2017-12-06 04:11:29 +08:00
|
|
|
|
2018-06-14 21:50:30 +08:00
|
|
|
// Only items matching the fuzzy query are returned.
|
|
|
|
EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").items,
|
|
|
|
AllOf(Has("FooBar"), Has("FooBaz"), Not(Has("Qux"))));
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2018-06-14 21:50:30 +08:00
|
|
|
// Macros require prefix match.
|
|
|
|
EXPECT_THAT(completions(Body + "int main() { C^ }").items,
|
|
|
|
AllOf(Has("Car"), Not(Has("MotorCar"))));
|
2017-12-06 04:11:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) {
|
2017-12-08 23:00:59 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
#define MACRO X
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
int global_var;
|
2017-12-06 04:11:29 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
int global_func();
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
struct GlobalClass {};
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
struct ClassWithMembers {
|
|
|
|
/// Doc for method.
|
|
|
|
int method();
|
2017-12-06 04:11:29 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
int field;
|
|
|
|
private:
|
|
|
|
int private_field;
|
|
|
|
};
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
int test() {
|
|
|
|
struct LocalClass {};
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
/// Doc for local_var.
|
|
|
|
int local_var;
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
ClassWithMembers().^
|
|
|
|
}
|
|
|
|
)cpp",
|
2018-01-19 22:34:02 +08:00
|
|
|
{cls("IndexClass"), var("index_var"), func("index_func")}, Opts);
|
2017-12-06 04:11:29 +08:00
|
|
|
|
|
|
|
// Class members. The only items that must be present in after-dot
|
|
|
|
// completion.
|
2018-06-07 20:49:17 +08:00
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
AllOf(Has(Opts.EnableSnippets ? "method()" : "method"),
|
|
|
|
Has("field"), Not(Has("ClassWithMembers")),
|
|
|
|
Not(Has("operator=")), Not(Has("~ClassWithMembers"))));
|
2017-12-08 23:00:59 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeIneligibleResults, Results.items,
|
|
|
|
Has("private_field"));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Global items.
|
2018-01-19 22:34:02 +08:00
|
|
|
EXPECT_THAT(
|
|
|
|
Results.items,
|
|
|
|
Not(AnyOf(Has("global_var"), Has("index_var"), Has("global_func"),
|
|
|
|
Has("global_func()"), Has("index_func"), Has("GlobalClass"),
|
|
|
|
Has("IndexClass"), Has("MACRO"), Has("LocalClass"))));
|
2017-12-06 04:11:29 +08:00
|
|
|
// There should be no code patterns (aka snippets) in after-dot
|
|
|
|
// completion. At least there aren't any we're aware of.
|
2017-12-08 23:00:59 +08:00
|
|
|
EXPECT_THAT(Results.items, Not(Contains(Kind(CompletionItemKind::Snippet))));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Check documentation.
|
2018-05-16 20:32:44 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeComments, Results.items, Contains(IsDocumented()));
|
2017-12-06 04:11:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) {
|
2017-12-08 23:00:59 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
#define MACRO X
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
int global_var;
|
|
|
|
int global_func();
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
struct GlobalClass {};
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
struct ClassWithMembers {
|
|
|
|
/// Doc for method.
|
|
|
|
int method();
|
|
|
|
};
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
int test() {
|
|
|
|
struct LocalClass {};
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
/// Doc for local_var.
|
|
|
|
int local_var;
|
2017-12-05 15:20:26 +08:00
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
^
|
|
|
|
}
|
|
|
|
)cpp",
|
2018-01-19 22:34:02 +08:00
|
|
|
{cls("IndexClass"), var("index_var"), func("index_func")}, Opts);
|
2017-12-06 04:11:29 +08:00
|
|
|
|
|
|
|
// Class members. Should never be present in global completions.
|
2017-12-08 23:00:59 +08:00
|
|
|
EXPECT_THAT(Results.items,
|
2017-12-06 04:11:29 +08:00
|
|
|
Not(AnyOf(Has("method"), Has("method()"), Has("field"))));
|
|
|
|
// Global items.
|
2018-01-18 23:31:30 +08:00
|
|
|
EXPECT_THAT(Results.items,
|
2018-01-19 22:34:02 +08:00
|
|
|
AllOf(Has("global_var"), Has("index_var"),
|
2018-01-18 23:31:30 +08:00
|
|
|
Has(Opts.EnableSnippets ? "global_func()" : "global_func"),
|
2018-01-19 22:34:02 +08:00
|
|
|
Has("index_func" /* our fake symbol doesn't include () */),
|
|
|
|
Has("GlobalClass"), Has("IndexClass")));
|
2017-12-06 04:11:29 +08:00
|
|
|
// A macro.
|
2017-12-08 23:00:59 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeMacros, Results.items, Has("MACRO"));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Local items. Must be present always.
|
2017-12-12 20:56:46 +08:00
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
AllOf(Has("local_var"), Has("LocalClass"),
|
|
|
|
Contains(Kind(CompletionItemKind::Snippet))));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Check documentation.
|
2018-05-16 20:32:44 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeComments, Results.items, Contains(IsDocumented()));
|
2017-12-05 15:20:26 +08:00
|
|
|
}
|
|
|
|
|
2017-12-06 04:11:29 +08:00
|
|
|
TEST(CompletionTest, CompletionOptions) {
|
2018-01-16 20:21:24 +08:00
|
|
|
auto Test = [&](const clangd::CodeCompleteOptions &Opts) {
|
|
|
|
TestAfterDotCompletion(Opts);
|
|
|
|
TestGlobalScopeCompletion(Opts);
|
|
|
|
};
|
|
|
|
// We used to test every combination of options, but that got too slow (2^N).
|
|
|
|
auto Flags = {
|
2018-03-12 23:28:22 +08:00
|
|
|
&clangd::CodeCompleteOptions::IncludeMacros,
|
2018-05-16 20:32:44 +08:00
|
|
|
&clangd::CodeCompleteOptions::IncludeComments,
|
2018-03-12 23:28:22 +08:00
|
|
|
&clangd::CodeCompleteOptions::EnableSnippets,
|
|
|
|
&clangd::CodeCompleteOptions::IncludeCodePatterns,
|
|
|
|
&clangd::CodeCompleteOptions::IncludeIneligibleResults,
|
2018-01-16 20:21:24 +08:00
|
|
|
};
|
|
|
|
// Test default options.
|
|
|
|
Test({});
|
|
|
|
// Test with one flag flipped.
|
|
|
|
for (auto &F : Flags) {
|
|
|
|
clangd::CodeCompleteOptions O;
|
|
|
|
O.*F ^= true;
|
|
|
|
Test(O);
|
2017-12-05 15:20:26 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
TEST(CompletionTest, Priorities) {
|
|
|
|
auto Internal = completions(R"cpp(
|
|
|
|
class Foo {
|
|
|
|
public: void pub();
|
|
|
|
protected: void prot();
|
|
|
|
private: void priv();
|
|
|
|
};
|
|
|
|
void Foo::pub() { this->^ }
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Internal.items,
|
|
|
|
HasSubsequence(Named("priv"), Named("prot"), Named("pub")));
|
|
|
|
|
|
|
|
auto External = completions(R"cpp(
|
|
|
|
class Foo {
|
|
|
|
public: void pub();
|
|
|
|
protected: void prot();
|
|
|
|
private: void priv();
|
|
|
|
};
|
|
|
|
void test() {
|
|
|
|
Foo F;
|
|
|
|
F.^
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(External.items,
|
|
|
|
AllOf(Has("pub"), Not(Has("prot")), Not(Has("priv"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, Qualifiers) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
class Foo {
|
|
|
|
public: int foo() const;
|
|
|
|
int bar() const;
|
|
|
|
};
|
|
|
|
class Bar : public Foo {
|
|
|
|
int foo() const;
|
|
|
|
};
|
|
|
|
void test() { Bar().^ }
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Results.items, HasSubsequence(Labeled("bar() const"),
|
|
|
|
Labeled("Foo::foo() const")));
|
|
|
|
EXPECT_THAT(Results.items, Not(Contains(Labeled("foo() const")))); // private
|
|
|
|
}
|
|
|
|
|
2018-06-07 20:49:17 +08:00
|
|
|
TEST(CompletionTest, InjectedTypename) {
|
|
|
|
// These are suppressed when accessed as a member...
|
|
|
|
EXPECT_THAT(completions("struct X{}; void foo(){ X().^ }").items,
|
|
|
|
Not(Has("X")));
|
|
|
|
EXPECT_THAT(completions("struct X{ void foo(){ this->^ } };").items,
|
|
|
|
Not(Has("X")));
|
|
|
|
// ...but accessible in other, more useful cases.
|
|
|
|
EXPECT_THAT(completions("struct X{ void foo(){ ^ } };").items, Has("X"));
|
|
|
|
EXPECT_THAT(completions("struct Y{}; struct X:Y{ void foo(){ ^ } };").items,
|
|
|
|
Has("Y"));
|
|
|
|
EXPECT_THAT(
|
|
|
|
completions(
|
|
|
|
"template<class> struct Y{}; struct X:Y<int>{ void foo(){ ^ } };")
|
|
|
|
.items,
|
|
|
|
Has("Y"));
|
|
|
|
// This case is marginal (`using X::X` is useful), we allow it for now.
|
|
|
|
EXPECT_THAT(completions("struct X{}; void foo(){ X::^ }").items, Has("X"));
|
|
|
|
}
|
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
TEST(CompletionTest, Snippets) {
|
|
|
|
clangd::CodeCompleteOptions Opts;
|
|
|
|
Opts.EnableSnippets = true;
|
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
struct fake {
|
|
|
|
int a;
|
|
|
|
int f(int i, const float f) const;
|
|
|
|
};
|
|
|
|
int main() {
|
|
|
|
fake f;
|
|
|
|
f.^
|
|
|
|
}
|
|
|
|
)cpp",
|
2018-01-18 17:27:56 +08:00
|
|
|
/*IndexSymbols=*/{}, Opts);
|
2017-12-08 23:00:59 +08:00
|
|
|
EXPECT_THAT(Results.items,
|
2017-12-21 01:24:31 +08:00
|
|
|
HasSubsequence(Snippet("a"),
|
2017-12-08 23:00:59 +08:00
|
|
|
Snippet("f(${1:int i}, ${2:const float f})")));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, Kinds) {
|
2018-01-19 22:34:02 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
#define MACRO X
|
|
|
|
int variable;
|
|
|
|
struct Struct {};
|
|
|
|
int function();
|
|
|
|
int X = ^
|
|
|
|
)cpp",
|
|
|
|
{func("indexFunction"), var("indexVariable"), cls("indexClass")});
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
AllOf(Has("function", CompletionItemKind::Function),
|
|
|
|
Has("variable", CompletionItemKind::Variable),
|
|
|
|
Has("int", CompletionItemKind::Keyword),
|
|
|
|
Has("Struct", CompletionItemKind::Class),
|
|
|
|
Has("MACRO", CompletionItemKind::Text),
|
|
|
|
Has("indexFunction", CompletionItemKind::Function),
|
|
|
|
Has("indexVariable", CompletionItemKind::Variable),
|
|
|
|
Has("indexClass", CompletionItemKind::Class)));
|
2017-12-08 23:00:59 +08:00
|
|
|
|
|
|
|
Results = completions("nam^");
|
|
|
|
EXPECT_THAT(Results.items, Has("namespace", CompletionItemKind::Snippet));
|
|
|
|
}
|
|
|
|
|
2018-01-13 00:16:09 +08:00
|
|
|
TEST(CompletionTest, NoDuplicates) {
|
2018-01-19 22:34:02 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
class Adapter {
|
|
|
|
};
|
2018-01-13 00:16:09 +08:00
|
|
|
|
2018-05-30 17:03:39 +08:00
|
|
|
void f() {
|
2018-01-19 22:34:02 +08:00
|
|
|
Adapter^
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
{cls("Adapter")});
|
2018-01-13 00:16:09 +08:00
|
|
|
|
|
|
|
// Make sure there are no duplicate entries of 'Adapter'.
|
2018-01-23 05:05:00 +08:00
|
|
|
EXPECT_THAT(Results.items, ElementsAre(Named("Adapter")));
|
2018-01-13 00:16:09 +08:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:34:02 +08:00
|
|
|
TEST(CompletionTest, ScopedNoIndex) {
|
2018-01-18 17:27:56 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
2018-06-14 21:50:30 +08:00
|
|
|
namespace fake { int BigBang, Babble, Box; };
|
|
|
|
int main() { fake::ba^ }
|
2018-01-19 22:34:02 +08:00
|
|
|
")cpp");
|
2018-06-14 21:50:30 +08:00
|
|
|
// Babble is a better match than BigBang. Box doesn't match at all.
|
|
|
|
EXPECT_THAT(Results.items, ElementsAre(Named("Babble"), Named("BigBang")));
|
2018-01-10 22:44:34 +08:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:34:02 +08:00
|
|
|
TEST(CompletionTest, Scoped) {
|
2018-01-18 17:27:56 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
2018-06-14 21:50:30 +08:00
|
|
|
namespace fake { int Babble, Box; };
|
|
|
|
int main() { fake::ba^ }
|
2018-01-19 22:34:02 +08:00
|
|
|
")cpp",
|
|
|
|
{var("fake::BigBang")});
|
2018-06-14 21:50:30 +08:00
|
|
|
EXPECT_THAT(Results.items, ElementsAre(Named("Babble"), Named("BigBang")));
|
2017-12-20 00:50:37 +08:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:34:02 +08:00
|
|
|
TEST(CompletionTest, ScopedWithFilter) {
|
2018-01-18 17:27:56 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
void f() { ns::x^ }
|
|
|
|
)cpp",
|
|
|
|
{cls("ns::XYZ"), func("ns::foo")});
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
UnorderedElementsAre(AllOf(Named("XYZ"), Filter("XYZ"))));
|
2017-12-20 00:50:37 +08:00
|
|
|
}
|
|
|
|
|
2018-05-03 22:53:02 +08:00
|
|
|
TEST(CompletionTest, ReferencesAffectRanking) {
|
|
|
|
auto Results = completions("int main() { abs^ }", {ns("absl"), func("abs")});
|
|
|
|
EXPECT_THAT(Results.items, HasSubsequence(Named("abs"), Named("absl")));
|
|
|
|
Results = completions("int main() { abs^ }",
|
|
|
|
{withReferences(10000, ns("absl")), func("abs")});
|
|
|
|
EXPECT_THAT(Results.items, HasSubsequence(Named("absl"), Named("abs")));
|
|
|
|
}
|
|
|
|
|
2018-01-19 22:34:02 +08:00
|
|
|
TEST(CompletionTest, GlobalQualified) {
|
2018-01-18 17:27:56 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
void f() { ::^ }
|
|
|
|
)cpp",
|
|
|
|
{cls("XYZ")});
|
|
|
|
EXPECT_THAT(Results.items, AllOf(Has("XYZ", CompletionItemKind::Class),
|
|
|
|
Has("f", CompletionItemKind::Function)));
|
2017-12-20 00:50:37 +08:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:34:02 +08:00
|
|
|
TEST(CompletionTest, FullyQualified) {
|
2018-01-18 17:27:56 +08:00
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
2018-01-19 22:34:02 +08:00
|
|
|
namespace ns { void bar(); }
|
2018-01-18 17:27:56 +08:00
|
|
|
void f() { ::ns::^ }
|
|
|
|
)cpp",
|
|
|
|
{cls("ns::XYZ")});
|
2018-01-19 22:34:02 +08:00
|
|
|
EXPECT_THAT(Results.items, AllOf(Has("XYZ", CompletionItemKind::Class),
|
|
|
|
Has("bar", CompletionItemKind::Function)));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, SemaIndexMerge) {
|
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
namespace ns { int local; void both(); }
|
|
|
|
void f() { ::ns::^ }
|
|
|
|
)cpp",
|
|
|
|
{func("ns::both"), cls("ns::Index")});
|
|
|
|
// We get results from both index and sema, with no duplicates.
|
|
|
|
EXPECT_THAT(
|
|
|
|
Results.items,
|
|
|
|
UnorderedElementsAre(Named("local"), Named("Index"), Named("both")));
|
2017-12-20 00:50:37 +08:00
|
|
|
}
|
|
|
|
|
2018-01-25 17:20:09 +08:00
|
|
|
TEST(CompletionTest, SemaIndexMergeWithLimit) {
|
|
|
|
clangd::CodeCompleteOptions Opts;
|
|
|
|
Opts.Limit = 1;
|
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
namespace ns { int local; void both(); }
|
|
|
|
void f() { ::ns::^ }
|
|
|
|
)cpp",
|
|
|
|
{func("ns::both"), cls("ns::Index")}, Opts);
|
|
|
|
EXPECT_EQ(Results.items.size(), Opts.Limit);
|
|
|
|
EXPECT_TRUE(Results.isIncomplete);
|
|
|
|
}
|
|
|
|
|
2018-05-15 23:29:32 +08:00
|
|
|
TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
std::string Subdir = testPath("sub");
|
|
|
|
std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str();
|
|
|
|
CDB.ExtraClangFlags = {SearchDirArg.c_str()};
|
|
|
|
std::string BarHeader = testPath("sub/bar.h");
|
|
|
|
FS.Files[BarHeader] = "";
|
|
|
|
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
Symbol::Details Scratch;
|
|
|
|
auto BarURI = URI::createFile(BarHeader).toString();
|
|
|
|
Symbol Sym = cls("ns::X");
|
|
|
|
Sym.CanonicalDeclaration.FileURI = BarURI;
|
|
|
|
Scratch.IncludeHeader = BarURI;
|
|
|
|
Sym.Detail = &Scratch;
|
|
|
|
// Shoten include path based on search dirctory and insert.
|
|
|
|
auto Results = completions(Server,
|
|
|
|
R"cpp(
|
|
|
|
int main() { ns::^ }
|
|
|
|
)cpp",
|
|
|
|
{Sym});
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
ElementsAre(AllOf(Named("X"), InsertInclude("\"bar.h\""))));
|
|
|
|
// Duplicate based on inclusions in preamble.
|
|
|
|
Results = completions(Server,
|
|
|
|
R"cpp(
|
|
|
|
#include "sub/bar.h" // not shortest, so should only match resolved.
|
|
|
|
int main() { ns::^ }
|
|
|
|
)cpp",
|
|
|
|
{Sym});
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
ElementsAre(AllOf(Named("X"), Not(HasAdditionalEdits()))));
|
|
|
|
}
|
|
|
|
|
2018-05-30 17:03:39 +08:00
|
|
|
TEST(CompletionTest, NoIncludeInsertionWhenDeclFoundInFile) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
Symbol::Details Scratch;
|
|
|
|
Symbol SymX = cls("ns::X");
|
|
|
|
Symbol SymY = cls("ns::Y");
|
|
|
|
std::string BarHeader = testPath("bar.h");
|
|
|
|
auto BarURI = URI::createFile(BarHeader).toString();
|
|
|
|
SymX.CanonicalDeclaration.FileURI = BarURI;
|
|
|
|
SymY.CanonicalDeclaration.FileURI = BarURI;
|
|
|
|
Scratch.IncludeHeader = "<bar>";
|
|
|
|
SymX.Detail = &Scratch;
|
|
|
|
SymY.Detail = &Scratch;
|
|
|
|
// Shoten include path based on search dirctory and insert.
|
|
|
|
auto Results = completions(Server,
|
|
|
|
R"cpp(
|
|
|
|
namespace ns {
|
|
|
|
class X;
|
|
|
|
class Y {}
|
|
|
|
}
|
|
|
|
int main() { ns::^ }
|
|
|
|
)cpp",
|
|
|
|
{SymX, SymY});
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
ElementsAre(AllOf(Named("X"), Not(HasAdditionalEdits())),
|
|
|
|
AllOf(Named("Y"), Not(HasAdditionalEdits()))));
|
|
|
|
}
|
|
|
|
|
2018-01-13 02:30:08 +08:00
|
|
|
TEST(CompletionTest, IndexSuppressesPreambleCompletions) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
2018-03-06 01:28:54 +08:00
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
2018-01-13 02:30:08 +08:00
|
|
|
|
2018-02-16 17:41:43 +08:00
|
|
|
FS.Files[testPath("bar.h")] =
|
2018-01-25 01:53:32 +08:00
|
|
|
R"cpp(namespace ns { struct preamble { int member; }; })cpp";
|
2018-02-16 17:41:43 +08:00
|
|
|
auto File = testPath("foo.cpp");
|
2018-01-13 02:30:08 +08:00
|
|
|
Annotations Test(R"cpp(
|
|
|
|
#include "bar.h"
|
|
|
|
namespace ns { int local; }
|
2018-01-25 01:53:32 +08:00
|
|
|
void f() { ns::^; }
|
|
|
|
void f() { ns::preamble().$2^; }
|
2018-01-13 02:30:08 +08:00
|
|
|
)cpp");
|
2018-03-06 01:28:54 +08:00
|
|
|
runAddDocument(Server, File, Test.code());
|
2018-01-13 02:30:08 +08:00
|
|
|
clangd::CodeCompleteOptions Opts = {};
|
|
|
|
|
2018-01-18 17:27:56 +08:00
|
|
|
auto I = memIndex({var("ns::index")});
|
2018-01-13 02:30:08 +08:00
|
|
|
Opts.Index = I.get();
|
2018-03-13 07:22:35 +08:00
|
|
|
auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts));
|
2018-01-13 02:30:08 +08:00
|
|
|
EXPECT_THAT(WithIndex.items,
|
|
|
|
UnorderedElementsAre(Named("local"), Named("index")));
|
2018-01-25 01:53:32 +08:00
|
|
|
auto ClassFromPreamble =
|
2018-03-13 07:22:35 +08:00
|
|
|
cantFail(runCodeComplete(Server, File, Test.point("2"), Opts));
|
2018-01-25 01:53:32 +08:00
|
|
|
EXPECT_THAT(ClassFromPreamble.items, Contains(Named("member")));
|
2018-02-13 16:59:23 +08:00
|
|
|
|
|
|
|
Opts.Index = nullptr;
|
2018-03-13 07:22:35 +08:00
|
|
|
auto WithoutIndex =
|
|
|
|
cantFail(runCodeComplete(Server, File, Test.point(), Opts));
|
2018-02-13 16:59:23 +08:00
|
|
|
EXPECT_THAT(WithoutIndex.items,
|
|
|
|
UnorderedElementsAre(Named("local"), Named("preamble")));
|
2018-01-13 02:30:08 +08:00
|
|
|
}
|
|
|
|
|
2018-01-18 17:27:56 +08:00
|
|
|
TEST(CompletionTest, DynamicIndexMultiFile) {
|
2017-12-20 02:00:37 +08:00
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
2018-03-06 01:28:54 +08:00
|
|
|
auto Opts = ClangdServer::optsForTest();
|
|
|
|
Opts.BuildDynamicSymbolIndex = true;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, Opts);
|
2017-12-20 02:00:37 +08:00
|
|
|
|
2018-02-20 02:48:44 +08:00
|
|
|
FS.Files[testPath("foo.h")] = R"cpp(
|
2018-01-10 01:32:00 +08:00
|
|
|
namespace ns { class XYZ {}; void foo(int x) {} }
|
2018-02-20 02:48:44 +08:00
|
|
|
)cpp";
|
2018-03-06 01:28:54 +08:00
|
|
|
runAddDocument(Server, testPath("foo.cpp"), R"cpp(
|
2018-02-20 02:48:44 +08:00
|
|
|
#include "foo.h"
|
2018-02-13 16:59:23 +08:00
|
|
|
)cpp");
|
2017-12-20 02:00:37 +08:00
|
|
|
|
2018-02-16 17:41:43 +08:00
|
|
|
auto File = testPath("bar.cpp");
|
2017-12-21 00:06:05 +08:00
|
|
|
Annotations Test(R"cpp(
|
2018-01-10 01:32:00 +08:00
|
|
|
namespace ns {
|
|
|
|
class XXX {};
|
|
|
|
/// Doooc
|
|
|
|
void fooooo() {}
|
|
|
|
}
|
2017-12-20 02:00:37 +08:00
|
|
|
void f() { ns::^ }
|
|
|
|
)cpp");
|
2018-03-06 01:28:54 +08:00
|
|
|
runAddDocument(Server, File, Test.code());
|
2017-12-20 02:00:37 +08:00
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {}));
|
2017-12-20 02:00:37 +08:00
|
|
|
// "XYZ" and "foo" are not included in the file being completed but are still
|
|
|
|
// visible through the index.
|
|
|
|
EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
|
|
|
|
EXPECT_THAT(Results.items, Has("foo", CompletionItemKind::Function));
|
|
|
|
EXPECT_THAT(Results.items, Has("XXX", CompletionItemKind::Class));
|
2018-01-10 01:32:00 +08:00
|
|
|
EXPECT_THAT(Results.items, Contains(AllOf(Named("fooooo"), Filter("fooooo"),
|
|
|
|
Kind(CompletionItemKind::Function),
|
|
|
|
Doc("Doooc"), Detail("void"))));
|
2017-12-20 02:00:37 +08:00
|
|
|
}
|
|
|
|
|
2018-05-16 20:32:49 +08:00
|
|
|
TEST(CompletionTest, Documentation) {
|
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
// Non-doxygen comment.
|
|
|
|
int foo();
|
|
|
|
/// Doxygen comment.
|
|
|
|
/// \param int a
|
|
|
|
int bar(int a);
|
|
|
|
/* Multi-line
|
|
|
|
block comment
|
|
|
|
*/
|
|
|
|
int baz();
|
|
|
|
|
|
|
|
int x = ^
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
Contains(AllOf(Named("foo"), Doc("Non-doxygen comment."))));
|
|
|
|
EXPECT_THAT(
|
|
|
|
Results.items,
|
|
|
|
Contains(AllOf(Named("bar"), Doc("Doxygen comment.\n\\param int a"))));
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
Contains(AllOf(Named("baz"), Doc("Multi-line\nblock comment"))));
|
|
|
|
}
|
|
|
|
|
[clangd] Add "member" symbols to the index
Summary:
This adds more symbols to the index:
- member variables and functions
- enum constants in scoped enums
The code completion behavior should remain intact but workspace symbols should
now provide much more useful symbols.
Other symbols should be considered such as the ones in "main files" (files not
being included) but this can be done separately as this introduces its fair
share of problems.
Signed-off-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
Reviewers: ioeric, sammccall
Reviewed By: ioeric, sammccall
Subscribers: hokein, sammccall, jkorous, klimek, ilya-biryukov, jkorous-apple, ioeric, MaskRay, cfe-commits
Differential Revision: https://reviews.llvm.org/D44954
llvm-svn: 334017
2018-06-05 22:01:40 +08:00
|
|
|
TEST(CompletionTest, GlobalCompletionFiltering) {
|
|
|
|
|
|
|
|
Symbol Class = cls("XYZ");
|
|
|
|
Class.IsIndexedForCodeCompletion = false;
|
|
|
|
Symbol Func = func("XYZ::foooo");
|
|
|
|
Func.IsIndexedForCodeCompletion = false;
|
|
|
|
|
|
|
|
auto Results = completions(R"(// void f() {
|
|
|
|
XYZ::foooo^
|
|
|
|
})",
|
|
|
|
{Class, Func});
|
|
|
|
EXPECT_THAT(Results.items, IsEmpty());
|
|
|
|
}
|
|
|
|
|
2018-01-25 17:44:06 +08:00
|
|
|
TEST(CodeCompleteTest, DisableTypoCorrection) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
namespace clang { int v; }
|
|
|
|
void f() { clangd::^
|
|
|
|
)cpp");
|
|
|
|
EXPECT_TRUE(Results.items.empty());
|
|
|
|
}
|
|
|
|
|
2018-03-07 00:45:21 +08:00
|
|
|
TEST(CodeCompleteTest, NoColonColonAtTheEnd) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
namespace clang { }
|
|
|
|
void f() {
|
|
|
|
clan^
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Results.items, Contains(Labeled("clang")));
|
|
|
|
EXPECT_THAT(Results.items, Not(Contains(Labeled("clang::"))));
|
|
|
|
}
|
|
|
|
|
2018-03-16 23:23:44 +08:00
|
|
|
TEST(CompletionTest, BacktrackCrashes) {
|
|
|
|
// Sema calls code completion callbacks twice in these cases.
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
namespace ns {
|
|
|
|
struct FooBarBaz {};
|
|
|
|
} // namespace ns
|
|
|
|
|
|
|
|
int foo(ns::FooBar^
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Results.items, ElementsAre(Labeled("FooBarBaz")));
|
|
|
|
|
|
|
|
// Check we don't crash in that case too.
|
|
|
|
completions(R"cpp(
|
|
|
|
struct FooBarBaz {};
|
|
|
|
void test() {
|
|
|
|
if (FooBarBaz * x^) {}
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
}
|
|
|
|
|
2018-05-24 19:20:19 +08:00
|
|
|
TEST(CompletionTest, CompleteInMacroWithStringification) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
void f(const char *, int x);
|
|
|
|
#define F(x) f(#x, x)
|
|
|
|
|
|
|
|
namespace ns {
|
|
|
|
int X;
|
|
|
|
int Y;
|
|
|
|
} // namespace ns
|
|
|
|
|
|
|
|
int f(int input_num) {
|
|
|
|
F(ns::^)
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Results.items,
|
|
|
|
UnorderedElementsAre(Named("X"), Named("Y")));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, CompleteInMacroAndNamespaceWithStringification) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
void f(const char *, int x);
|
|
|
|
#define F(x) f(#x, x)
|
|
|
|
|
|
|
|
namespace ns {
|
|
|
|
int X;
|
|
|
|
|
|
|
|
int f(int input_num) {
|
|
|
|
F(^)
|
|
|
|
}
|
|
|
|
} // namespace ns
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Results.items, Contains(Named("X")));
|
|
|
|
}
|
|
|
|
|
2018-03-16 23:23:44 +08:00
|
|
|
TEST(CompletionTest, CompleteInExcludedPPBranch) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
int bar(int param_in_bar) {
|
|
|
|
}
|
|
|
|
|
|
|
|
int foo(int param_in_foo) {
|
|
|
|
#if 0
|
|
|
|
par^
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Results.items, Contains(Labeled("param_in_foo")));
|
|
|
|
EXPECT_THAT(Results.items, Not(Contains(Labeled("param_in_bar"))));
|
|
|
|
}
|
|
|
|
|
2018-01-18 17:27:56 +08:00
|
|
|
SignatureHelp signatures(StringRef Text) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
2018-03-06 01:28:54 +08:00
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
2018-02-16 17:41:43 +08:00
|
|
|
auto File = testPath("foo.cpp");
|
2018-01-18 17:27:56 +08:00
|
|
|
Annotations Test(Text);
|
2018-03-06 01:28:54 +08:00
|
|
|
runAddDocument(Server, File, Test.code());
|
2018-03-13 07:22:35 +08:00
|
|
|
return cantFail(runSignatureHelp(Server, File, Test.point()));
|
2018-01-18 17:27:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
MATCHER_P(ParamsAre, P, "") {
|
|
|
|
if (P.size() != arg.parameters.size())
|
|
|
|
return false;
|
|
|
|
for (unsigned I = 0; I < P.size(); ++I)
|
|
|
|
if (P[I] != arg.parameters[I].label)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Matcher<SignatureInformation> Sig(std::string Label,
|
|
|
|
std::vector<std::string> Params) {
|
|
|
|
return AllOf(Labeled(Label), ParamsAre(Params));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(SignatureHelpTest, Overloads) {
|
|
|
|
auto Results = signatures(R"cpp(
|
|
|
|
void foo(int x, int y);
|
|
|
|
void foo(int x, float y);
|
|
|
|
void foo(float x, int y);
|
|
|
|
void foo(float x, float y);
|
|
|
|
void bar(int x, int y = 0);
|
|
|
|
int main() { foo(^); }
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Results.signatures,
|
|
|
|
UnorderedElementsAre(
|
|
|
|
Sig("foo(float x, float y) -> void", {"float x", "float y"}),
|
|
|
|
Sig("foo(float x, int y) -> void", {"float x", "int y"}),
|
|
|
|
Sig("foo(int x, float y) -> void", {"int x", "float y"}),
|
|
|
|
Sig("foo(int x, int y) -> void", {"int x", "int y"})));
|
|
|
|
// We always prefer the first signature.
|
|
|
|
EXPECT_EQ(0, Results.activeSignature);
|
|
|
|
EXPECT_EQ(0, Results.activeParameter);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(SignatureHelpTest, DefaultArgs) {
|
|
|
|
auto Results = signatures(R"cpp(
|
|
|
|
void bar(int x, int y = 0);
|
|
|
|
void bar(float x = 0, int y = 42);
|
|
|
|
int main() { bar(^
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Results.signatures,
|
|
|
|
UnorderedElementsAre(
|
|
|
|
Sig("bar(int x, int y = 0) -> void", {"int x", "int y = 0"}),
|
|
|
|
Sig("bar(float x = 0, int y = 42) -> void",
|
|
|
|
{"float x = 0", "int y = 42"})));
|
|
|
|
EXPECT_EQ(0, Results.activeSignature);
|
|
|
|
EXPECT_EQ(0, Results.activeParameter);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(SignatureHelpTest, ActiveArg) {
|
|
|
|
auto Results = signatures(R"cpp(
|
|
|
|
int baz(int a, int b, int c);
|
|
|
|
int main() { baz(baz(1,2,3), ^); }
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Results.signatures,
|
|
|
|
ElementsAre(Sig("baz(int a, int b, int c) -> int",
|
|
|
|
{"int a", "int b", "int c"})));
|
|
|
|
EXPECT_EQ(0, Results.activeSignature);
|
|
|
|
EXPECT_EQ(1, Results.activeParameter);
|
|
|
|
}
|
|
|
|
|
2018-01-23 19:37:26 +08:00
|
|
|
class IndexRequestCollector : public SymbolIndex {
|
|
|
|
public:
|
|
|
|
bool
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
fuzzyFind(const FuzzyFindRequest &Req,
|
2018-01-23 19:37:26 +08:00
|
|
|
llvm::function_ref<void(const Symbol &)> Callback) const override {
|
|
|
|
Requests.push_back(Req);
|
2018-02-19 21:04:41 +08:00
|
|
|
return true;
|
2018-01-23 19:37:26 +08:00
|
|
|
}
|
|
|
|
|
2018-03-14 17:48:05 +08:00
|
|
|
void lookup(const LookupRequest &,
|
|
|
|
llvm::function_ref<void(const Symbol &)>) const override {}
|
|
|
|
|
2018-01-23 19:37:26 +08:00
|
|
|
const std::vector<FuzzyFindRequest> allRequests() const { return Requests; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
mutable std::vector<FuzzyFindRequest> Requests;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<FuzzyFindRequest> captureIndexRequests(llvm::StringRef Code) {
|
|
|
|
clangd::CodeCompleteOptions Opts;
|
|
|
|
IndexRequestCollector Requests;
|
|
|
|
Opts.Index = &Requests;
|
|
|
|
completions(Code, {}, Opts);
|
|
|
|
return Requests.allRequests();
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, UnqualifiedIdQuery) {
|
|
|
|
auto Requests = captureIndexRequests(R"cpp(
|
|
|
|
namespace std {}
|
|
|
|
using namespace std;
|
|
|
|
namespace ns {
|
|
|
|
void f() {
|
|
|
|
vec^
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Requests,
|
|
|
|
ElementsAre(Field(&FuzzyFindRequest::Scopes,
|
|
|
|
UnorderedElementsAre("", "ns::", "std::"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, ResolvedQualifiedIdQuery) {
|
|
|
|
auto Requests = captureIndexRequests(R"cpp(
|
|
|
|
namespace ns1 {}
|
|
|
|
namespace ns2 {} // ignore
|
|
|
|
namespace ns3 { namespace nns3 {} }
|
|
|
|
namespace foo {
|
|
|
|
using namespace ns1;
|
|
|
|
using namespace ns3::nns3;
|
|
|
|
}
|
|
|
|
namespace ns {
|
|
|
|
void f() {
|
|
|
|
foo::^
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Requests,
|
|
|
|
ElementsAre(Field(
|
|
|
|
&FuzzyFindRequest::Scopes,
|
|
|
|
UnorderedElementsAre("foo::", "ns1::", "ns3::nns3::"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, UnresolvedQualifierIdQuery) {
|
|
|
|
auto Requests = captureIndexRequests(R"cpp(
|
|
|
|
namespace a {}
|
|
|
|
using namespace a;
|
|
|
|
namespace ns {
|
|
|
|
void f() {
|
|
|
|
bar::^
|
|
|
|
}
|
|
|
|
} // namespace ns
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes,
|
|
|
|
UnorderedElementsAre("bar::"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, UnresolvedNestedQualifierIdQuery) {
|
|
|
|
auto Requests = captureIndexRequests(R"cpp(
|
|
|
|
namespace a {}
|
|
|
|
using namespace a;
|
|
|
|
namespace ns {
|
|
|
|
void f() {
|
|
|
|
::a::bar::^
|
|
|
|
}
|
|
|
|
} // namespace ns
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes,
|
|
|
|
UnorderedElementsAre("a::bar::"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, EmptyQualifiedQuery) {
|
|
|
|
auto Requests = captureIndexRequests(R"cpp(
|
|
|
|
namespace ns {
|
|
|
|
void f() {
|
|
|
|
^
|
|
|
|
}
|
|
|
|
} // namespace ns
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes,
|
|
|
|
UnorderedElementsAre("", "ns::"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, GlobalQualifiedQuery) {
|
|
|
|
auto Requests = captureIndexRequests(R"cpp(
|
|
|
|
namespace ns {
|
|
|
|
void f() {
|
|
|
|
::^
|
|
|
|
}
|
|
|
|
} // namespace ns
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes,
|
|
|
|
UnorderedElementsAre(""))));
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:50:04 +08:00
|
|
|
TEST(CompletionTest, NoIndexCompletionsInsideClasses) {
|
|
|
|
auto Completions = completions(
|
|
|
|
R"cpp(
|
|
|
|
struct Foo {
|
|
|
|
int SomeNameOfField;
|
|
|
|
typedef int SomeNameOfTypedefField;
|
|
|
|
};
|
|
|
|
|
|
|
|
Foo::^)cpp",
|
|
|
|
{func("::SomeNameInTheIndex"), func("::Foo::SomeNameInTheIndex")});
|
|
|
|
|
|
|
|
EXPECT_THAT(Completions.items,
|
|
|
|
AllOf(Contains(Labeled("SomeNameOfField")),
|
|
|
|
Contains(Labeled("SomeNameOfTypedefField")),
|
|
|
|
Not(Contains(Labeled("SomeNameInTheIndex")))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, NoIndexCompletionsInsideDependentCode) {
|
|
|
|
{
|
|
|
|
auto Completions = completions(
|
|
|
|
R"cpp(
|
|
|
|
template <class T>
|
|
|
|
void foo() {
|
|
|
|
T::^
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
{func("::SomeNameInTheIndex")});
|
|
|
|
|
|
|
|
EXPECT_THAT(Completions.items,
|
|
|
|
Not(Contains(Labeled("SomeNameInTheIndex"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
auto Completions = completions(
|
|
|
|
R"cpp(
|
|
|
|
template <class T>
|
|
|
|
void foo() {
|
|
|
|
T::template Y<int>::^
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
{func("::SomeNameInTheIndex")});
|
|
|
|
|
|
|
|
EXPECT_THAT(Completions.items,
|
|
|
|
Not(Contains(Labeled("SomeNameInTheIndex"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
auto Completions = completions(
|
|
|
|
R"cpp(
|
|
|
|
template <class T>
|
|
|
|
void foo() {
|
|
|
|
T::foo::^
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
{func("::SomeNameInTheIndex")});
|
|
|
|
|
|
|
|
EXPECT_THAT(Completions.items,
|
|
|
|
Not(Contains(Labeled("SomeNameInTheIndex"))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-28 17:54:51 +08:00
|
|
|
TEST(CompletionTest, DocumentationFromChangedFileCrash) {
|
2018-05-24 22:49:23 +08:00
|
|
|
MockFSProvider FS;
|
|
|
|
auto FooH = testPath("foo.h");
|
|
|
|
auto FooCpp = testPath("foo.cpp");
|
|
|
|
FS.Files[FooH] = R"cpp(
|
|
|
|
// this is my documentation comment.
|
|
|
|
int func();
|
|
|
|
)cpp";
|
|
|
|
FS.Files[FooCpp] = "";
|
|
|
|
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
|
|
|
|
Annotations Source(R"cpp(
|
|
|
|
#include "foo.h"
|
|
|
|
int func() {
|
|
|
|
// This makes sure we have func from header in the AST.
|
|
|
|
}
|
|
|
|
int a = fun^
|
|
|
|
)cpp");
|
|
|
|
Server.addDocument(FooCpp, Source.code(), WantDiagnostics::Yes);
|
|
|
|
// We need to wait for preamble to build.
|
|
|
|
ASSERT_TRUE(Server.blockUntilIdleForTest());
|
|
|
|
|
|
|
|
// Change the header file. Completion will reuse the old preamble!
|
|
|
|
FS.Files[FooH] = R"cpp(
|
|
|
|
int func();
|
|
|
|
)cpp";
|
|
|
|
|
|
|
|
clangd::CodeCompleteOptions Opts;
|
|
|
|
Opts.IncludeComments = true;
|
|
|
|
CompletionList Completions =
|
|
|
|
cantFail(runCodeComplete(Server, FooCpp, Source.point(), Opts));
|
|
|
|
// We shouldn't crash. Unfortunately, current workaround is to not produce
|
|
|
|
// comments for symbols from headers.
|
|
|
|
EXPECT_THAT(Completions.items,
|
|
|
|
Contains(AllOf(Not(IsDocumented()), Named("func"))));
|
|
|
|
}
|
|
|
|
|
2018-05-28 20:11:37 +08:00
|
|
|
TEST(CompletionTest, CompleteOnInvalidLine) {
|
|
|
|
auto FooCpp = testPath("foo.cpp");
|
|
|
|
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
MockFSProvider FS;
|
|
|
|
FS.Files[FooCpp] = "// empty file";
|
|
|
|
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
// Run completion outside the file range.
|
|
|
|
Position Pos;
|
|
|
|
Pos.line = 100;
|
|
|
|
Pos.character = 0;
|
|
|
|
EXPECT_THAT_EXPECTED(
|
|
|
|
runCodeComplete(Server, FooCpp, Pos, clangd::CodeCompleteOptions()),
|
|
|
|
Failed());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-12-05 15:20:26 +08:00
|
|
|
} // namespace
|
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|