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"
|
2018-07-02 19:13:16 +08:00
|
|
|
#include "Quality.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-07-23 18:56:37 +08:00
|
|
|
#include "clang/Sema/CodeCompleteConsumer.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 option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
using ::testing::HasSubstr;
|
[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.
|
2018-07-02 19:13:16 +08:00
|
|
|
MATCHER_P(Named, Name, "") { return arg.Name == Name; }
|
|
|
|
MATCHER_P(Scope, S, "") { return arg.Scope == S; }
|
|
|
|
MATCHER_P(Qualifier, Q, "") { return arg.RequiredQualifier == Q; }
|
2018-06-15 21:34:18 +08:00
|
|
|
MATCHER_P(Labeled, Label, "") {
|
2018-07-02 19:13:16 +08:00
|
|
|
return arg.RequiredQualifier + arg.Name + arg.Signature == Label;
|
2018-06-15 21:34:18 +08:00
|
|
|
}
|
|
|
|
MATCHER_P(SigHelpLabeled, Label, "") { return arg.label == Label; }
|
2018-07-02 19:13:16 +08:00
|
|
|
MATCHER_P(Kind, K, "") { return arg.Kind == K; }
|
|
|
|
MATCHER_P(Doc, D, "") { return arg.Documentation == D; }
|
|
|
|
MATCHER_P(ReturnType, D, "") { return arg.ReturnType == D; }
|
2018-05-15 23:29:32 +08:00
|
|
|
MATCHER_P(InsertInclude, IncludeHeader, "") {
|
2018-07-02 19:13:16 +08:00
|
|
|
return arg.Header == IncludeHeader && bool(arg.HeaderInsertion);
|
2017-12-08 23:00:59 +08:00
|
|
|
}
|
2018-07-02 19:13:16 +08:00
|
|
|
MATCHER(InsertInclude, "") { return bool(arg.HeaderInsertion); }
|
|
|
|
MATCHER_P(SnippetSuffix, Text, "") { return arg.SnippetSuffix == Text; }
|
2018-07-05 14:20:41 +08:00
|
|
|
MATCHER_P(Origin, OriginSet, "") { return arg.Origin == OriginSet; }
|
2018-05-15 23:29:32 +08:00
|
|
|
|
2017-12-06 04:11:29 +08:00
|
|
|
// Shorthand for Contains(Named(Name)).
|
2018-07-02 19:13:16 +08:00
|
|
|
Matcher<const std::vector<CodeCompletion> &> Has(std::string Name) {
|
2017-12-06 04:11:29 +08:00
|
|
|
return Contains(Named(std::move(Name)));
|
2017-12-05 15:20:26 +08:00
|
|
|
}
|
2018-07-02 19:13:16 +08:00
|
|
|
Matcher<const std::vector<CodeCompletion> &> Has(std::string Name,
|
2017-12-08 23:00:59 +08:00
|
|
|
CompletionItemKind K) {
|
|
|
|
return Contains(AllOf(Named(std::move(Name)), Kind(K)));
|
2017-12-06 04:11:29 +08:00
|
|
|
}
|
2018-07-02 19:13:16 +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-08-08 16:59:29 +08:00
|
|
|
CodeCompleteResult completions(ClangdServer &Server, StringRef TestCode,
|
|
|
|
Position point,
|
|
|
|
std::vector<Symbol> IndexSymbols = {},
|
|
|
|
clangd::CodeCompleteOptions Opts = {}) {
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto File = testPath("foo.cpp");
|
|
|
|
runAddDocument(Server, File, TestCode);
|
|
|
|
auto CompletionList = cantFail(runCodeComplete(Server, File, point, Opts));
|
|
|
|
return CompletionList;
|
|
|
|
}
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
CodeCompleteResult completions(ClangdServer &Server, StringRef Text,
|
|
|
|
std::vector<Symbol> IndexSymbols = {},
|
|
|
|
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
|
|
|
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.
|
2018-07-02 19:13:16 +08:00
|
|
|
CodeCompleteResult completions(StringRef Text,
|
|
|
|
std::vector<Symbol> IndexSymbols = {},
|
|
|
|
clangd::CodeCompleteOptions Opts = {}) {
|
2018-05-15 23:29:32 +08:00
|
|
|
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.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-07-05 14:20:41 +08:00
|
|
|
Sym.Origin = SymbolOrigin::Static;
|
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
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_TRUE(Results.HasMore);
|
|
|
|
EXPECT_THAT(Results.Completions, 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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").Completions,
|
2018-06-14 21:50:30 +08:00
|
|
|
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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(completions(Body + "int main() { C^ }").Completions,
|
2018-06-14 21:50:30 +08:00
|
|
|
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-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
AllOf(Has("method"), Has("field"), Not(Has("ClassWithMembers")),
|
2018-06-07 20:49:17 +08:00
|
|
|
Not(Has("operator=")), Not(Has("~ClassWithMembers"))));
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeIneligibleResults, Results.Completions,
|
2017-12-08 23:00:59 +08:00
|
|
|
Has("private_field"));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Global items.
|
2018-01-19 22:34:02 +08:00
|
|
|
EXPECT_THAT(
|
2018-07-02 19:13:16 +08:00
|
|
|
Results.Completions,
|
2018-01-19 22:34:02 +08:00
|
|
|
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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
Not(Contains(Kind(CompletionItemKind::Snippet))));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Check documentation.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeComments, Results.Completions,
|
|
|
|
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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
2017-12-06 04:11:29 +08:00
|
|
|
Not(AnyOf(Has("method"), Has("method()"), Has("field"))));
|
|
|
|
// Global items.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
AllOf(Has("global_var"), Has("index_var"), Has("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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeMacros, Results.Completions, Has("MACRO"));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Local items. Must be present always.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
2017-12-12 20:56:46 +08:00
|
|
|
AllOf(Has("local_var"), Has("LocalClass"),
|
|
|
|
Contains(Kind(CompletionItemKind::Snippet))));
|
2017-12-06 04:11:29 +08:00
|
|
|
// Check documentation.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_IFF(Opts.IncludeComments, Results.Completions,
|
|
|
|
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::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");
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Internal.Completions,
|
2017-12-08 23:00:59 +08:00
|
|
|
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");
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(External.Completions,
|
2017-12-08 23:00:59 +08:00
|
|
|
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");
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
HasSubsequence(AllOf(Qualifier(""), Named("bar")),
|
|
|
|
AllOf(Qualifier("Foo::"), Named("foo"))));
|
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
Not(Contains(AllOf(Qualifier(""), Named("foo"))))); // private
|
2017-12-08 23:00:59 +08:00
|
|
|
}
|
|
|
|
|
2018-06-07 20:49:17 +08:00
|
|
|
TEST(CompletionTest, InjectedTypename) {
|
|
|
|
// These are suppressed when accessed as a member...
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(completions("struct X{}; void foo(){ X().^ }").Completions,
|
2018-06-07 20:49:17 +08:00
|
|
|
Not(Has("X")));
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(completions("struct X{ void foo(){ this->^ } };").Completions,
|
2018-06-07 20:49:17 +08:00
|
|
|
Not(Has("X")));
|
|
|
|
// ...but accessible in other, more useful cases.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(completions("struct X{ void foo(){ ^ } };").Completions,
|
|
|
|
Has("X"));
|
|
|
|
EXPECT_THAT(
|
|
|
|
completions("struct Y{}; struct X:Y{ void foo(){ ^ } };").Completions,
|
|
|
|
Has("Y"));
|
2018-06-07 20:49:17 +08:00
|
|
|
EXPECT_THAT(
|
|
|
|
completions(
|
|
|
|
"template<class> struct Y{}; struct X:Y<int>{ void foo(){ ^ } };")
|
2018-07-02 19:13:16 +08:00
|
|
|
.Completions,
|
2018-06-07 20:49:17 +08:00
|
|
|
Has("Y"));
|
|
|
|
// This case is marginal (`using X::X` is useful), we allow it for now.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(completions("struct X{}; void foo(){ X::^ }").Completions,
|
|
|
|
Has("X"));
|
2018-06-07 20:49:17 +08:00
|
|
|
}
|
|
|
|
|
2017-12-08 23:00:59 +08:00
|
|
|
TEST(CompletionTest, Snippets) {
|
|
|
|
clangd::CodeCompleteOptions Opts;
|
|
|
|
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);
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(
|
|
|
|
Results.Completions,
|
|
|
|
HasSubsequence(Named("a"),
|
|
|
|
SnippetSuffix("(${1:int i}, ${2:const float f})")));
|
2017-12-08 23:00:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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")});
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
2018-01-19 22:34:02 +08:00
|
|
|
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^");
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
Has("namespace", CompletionItemKind::Snippet));
|
2017-12-08 23:00:59 +08:00
|
|
|
}
|
|
|
|
|
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-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, 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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
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-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
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")});
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("XYZ")));
|
2017-12-20 00:50:37 +08:00
|
|
|
}
|
|
|
|
|
2018-05-03 22:53:02 +08:00
|
|
|
TEST(CompletionTest, ReferencesAffectRanking) {
|
2018-07-25 19:26:35 +08:00
|
|
|
auto Results = completions("int main() { abs^ }", {ns("absl"), func("absb")});
|
|
|
|
EXPECT_THAT(Results.Completions, HasSubsequence(Named("absb"), Named("absl")));
|
2018-05-03 22:53:02 +08:00
|
|
|
Results = completions("int main() { abs^ }",
|
2018-07-25 19:26:35 +08:00
|
|
|
{withReferences(10000, ns("absl")), func("absb")});
|
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
HasSubsequence(Named("absl"), Named("absb")));
|
2018-05-03 22:53:02 +08:00
|
|
|
}
|
|
|
|
|
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")});
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
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-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
AllOf(Has("XYZ", CompletionItemKind::Class),
|
|
|
|
Has("bar", CompletionItemKind::Function)));
|
2018-01-19 22:34:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2018-07-05 14:20:41 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
UnorderedElementsAre(
|
|
|
|
AllOf(Named("local"), Origin(SymbolOrigin::AST)),
|
|
|
|
AllOf(Named("Index"), Origin(SymbolOrigin::Static)),
|
|
|
|
AllOf(Named("both"),
|
|
|
|
Origin(SymbolOrigin::AST | SymbolOrigin::Static))));
|
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);
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_EQ(Results.Completions.size(), Opts.Limit);
|
|
|
|
EXPECT_TRUE(Results.HasMore);
|
2018-01-25 17:20:09 +08:00
|
|
|
}
|
|
|
|
|
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});
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
ElementsAre(AllOf(Named("X"), InsertInclude("\"bar.h\""))));
|
2018-05-15 23:29:32 +08:00
|
|
|
// 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});
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, ElementsAre(AllOf(Named("X"), Labeled("X"),
|
|
|
|
Not(InsertInclude()))));
|
2018-05-15 23:29:32 +08:00
|
|
|
}
|
|
|
|
|
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});
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
ElementsAre(AllOf(Named("X"), Not(InsertInclude())),
|
|
|
|
AllOf(Named("Y"), Not(InsertInclude()))));
|
2018-05-30 17:03:39 +08:00
|
|
|
}
|
|
|
|
|
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-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(WithIndex.Completions,
|
2018-01-13 02:30:08 +08:00
|
|
|
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-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(ClassFromPreamble.Completions, 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-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(WithoutIndex.Completions,
|
2018-02-13 16:59:23 +08:00
|
|
|
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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, Has("XYZ", CompletionItemKind::Class));
|
|
|
|
EXPECT_THAT(Results.Completions, Has("foo", CompletionItemKind::Function));
|
|
|
|
EXPECT_THAT(Results.Completions, Has("XXX", CompletionItemKind::Class));
|
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
Contains((Named("fooooo"), Kind(CompletionItemKind::Function),
|
|
|
|
Doc("Doooc"), ReturnType("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");
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
2018-05-16 20:32:49 +08:00
|
|
|
Contains(AllOf(Named("foo"), Doc("Non-doxygen comment."))));
|
|
|
|
EXPECT_THAT(
|
2018-07-02 19:13:16 +08:00
|
|
|
Results.Completions,
|
2018-05-16 20:32:49 +08:00
|
|
|
Contains(AllOf(Named("bar"), Doc("Doxygen comment.\n\\param int a"))));
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
2018-05-16 20:32:49 +08:00
|
|
|
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});
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, IsEmpty());
|
[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
|
|
|
}
|
|
|
|
|
2018-01-25 17:44:06 +08:00
|
|
|
TEST(CodeCompleteTest, DisableTypoCorrection) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
namespace clang { int v; }
|
|
|
|
void f() { clangd::^
|
|
|
|
)cpp");
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_TRUE(Results.Completions.empty());
|
2018-01-25 17:44:06 +08:00
|
|
|
}
|
|
|
|
|
2018-03-07 00:45:21 +08:00
|
|
|
TEST(CodeCompleteTest, NoColonColonAtTheEnd) {
|
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
namespace clang { }
|
|
|
|
void f() {
|
|
|
|
clan^
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, Contains(Labeled("clang")));
|
|
|
|
EXPECT_THAT(Results.Completions, Not(Contains(Labeled("clang::"))));
|
2018-03-07 00:45:21 +08:00
|
|
|
}
|
|
|
|
|
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");
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, ElementsAre(Labeled("FooBarBaz")));
|
2018-03-16 23:23:44 +08:00
|
|
|
|
|
|
|
// 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");
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions,
|
2018-05-24 19:20:19 +08:00
|
|
|
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");
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Results.Completions, Contains(Named("X")));
|
2018-05-24 19:20:19 +08:00
|
|
|
}
|
|
|
|
|
2018-07-11 21:15:31 +08:00
|
|
|
TEST(CompletionTest, IgnoreCompleteInExcludedPPBranchWithRecoveryContext) {
|
2018-03-16 23:23:44 +08:00
|
|
|
auto Results = completions(R"cpp(
|
|
|
|
int bar(int param_in_bar) {
|
|
|
|
}
|
|
|
|
|
|
|
|
int foo(int param_in_foo) {
|
|
|
|
#if 0
|
2018-07-11 21:15:31 +08:00
|
|
|
// In recorvery mode, "param_in_foo" will also be suggested among many other
|
|
|
|
// unrelated symbols; however, this is really a special case where this works.
|
|
|
|
// If the #if block is outside of the function, "param_in_foo" is still
|
|
|
|
// suggested, but "bar" and "foo" are missing. So the recovery mode doesn't
|
|
|
|
// really provide useful results in excluded branches.
|
2018-03-16 23:23:44 +08:00
|
|
|
par^
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
2018-07-11 21:15:31 +08:00
|
|
|
EXPECT_TRUE(Results.Completions.empty());
|
2018-03-16 23:23:44 +08:00
|
|
|
}
|
|
|
|
|
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) {
|
2018-06-15 21:34:18 +08:00
|
|
|
return AllOf(SigHelpLabeled(Label), ParamsAre(Params));
|
2018-01-18 17:27:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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-08-06 21:14:32 +08:00
|
|
|
void findOccurrences(const OccurrencesRequest &Req,
|
|
|
|
llvm::function_ref<void(const SymbolOccurrence &)>
|
|
|
|
Callback) 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")});
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Completions.Completions,
|
2018-05-14 18:50:04 +08:00
|
|
|
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")});
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Completions.Completions,
|
2018-05-14 18:50:04 +08:00
|
|
|
Not(Contains(Labeled("SomeNameInTheIndex"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
auto Completions = completions(
|
|
|
|
R"cpp(
|
|
|
|
template <class T>
|
|
|
|
void foo() {
|
|
|
|
T::template Y<int>::^
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
{func("::SomeNameInTheIndex")});
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Completions.Completions,
|
2018-05-14 18:50:04 +08:00
|
|
|
Not(Contains(Labeled("SomeNameInTheIndex"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
auto Completions = completions(
|
|
|
|
R"cpp(
|
|
|
|
template <class T>
|
|
|
|
void foo() {
|
|
|
|
T::foo::^
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
{func("::SomeNameInTheIndex")});
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Completions.Completions,
|
2018-05-14 18:50:04 +08:00
|
|
|
Not(Contains(Labeled("SomeNameInTheIndex"))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[clangd] Add option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
TEST(CompletionTest, OverloadBundling) {
|
|
|
|
clangd::CodeCompleteOptions Opts;
|
|
|
|
Opts.BundleOverloads = true;
|
|
|
|
|
|
|
|
std::string Context = R"cpp(
|
|
|
|
struct X {
|
|
|
|
// Overload with int
|
|
|
|
int a(int);
|
|
|
|
// Overload with bool
|
|
|
|
int a(bool);
|
|
|
|
int b(float);
|
|
|
|
};
|
|
|
|
int GFuncC(int);
|
|
|
|
int GFuncD(int);
|
|
|
|
)cpp";
|
|
|
|
|
|
|
|
// Member completions are bundled.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(completions(Context + "int y = X().^", {}, Opts).Completions,
|
[clangd] Add option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
UnorderedElementsAre(Labeled("a(…)"), Labeled("b(float)")));
|
|
|
|
|
|
|
|
// Non-member completions are bundled, including index+sema.
|
|
|
|
Symbol NoArgsGFunc = func("GFuncC");
|
|
|
|
EXPECT_THAT(
|
2018-07-02 19:13:16 +08:00
|
|
|
completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions,
|
[clangd] Add option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
UnorderedElementsAre(Labeled("GFuncC(…)"), Labeled("GFuncD(int)")));
|
|
|
|
|
|
|
|
// Differences in header-to-insert suppress bundling.
|
|
|
|
Symbol::Details Detail;
|
|
|
|
std::string DeclFile = URI::createFile(testPath("foo")).toString();
|
|
|
|
NoArgsGFunc.CanonicalDeclaration.FileURI = DeclFile;
|
|
|
|
Detail.IncludeHeader = "<foo>";
|
|
|
|
NoArgsGFunc.Detail = &Detail;
|
|
|
|
EXPECT_THAT(
|
2018-07-02 19:13:16 +08:00
|
|
|
completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions,
|
|
|
|
UnorderedElementsAre(AllOf(Named("GFuncC"), InsertInclude("<foo>")),
|
|
|
|
Labeled("GFuncC(int)"), Labeled("GFuncD(int)")));
|
[clangd] Add option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
|
|
|
|
// Examine a bundled completion in detail.
|
2018-07-02 19:13:16 +08:00
|
|
|
auto A =
|
|
|
|
completions(Context + "int y = X().a^", {}, Opts).Completions.front();
|
|
|
|
EXPECT_EQ(A.Name, "a");
|
|
|
|
EXPECT_EQ(A.Signature, "(…)");
|
|
|
|
EXPECT_EQ(A.BundleSize, 2u);
|
|
|
|
EXPECT_EQ(A.Kind, CompletionItemKind::Method);
|
|
|
|
EXPECT_EQ(A.ReturnType, "int"); // All overloads return int.
|
[clangd] Add option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
// For now we just return one of the doc strings arbitrarily.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(A.Documentation, AnyOf(HasSubstr("Overload with int"),
|
[clangd] Add option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
HasSubstr("Overload with bool")));
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_EQ(A.SnippetSuffix, "(${0})");
|
[clangd] Add option to fold overloads into a single completion item.
Summary:
Adds a CodeCompleteOption to folds together compatible function/method overloads
into a single item. This feels pretty good (for editors with signatureHelp
support), but has limitations.
This happens in the code completion merge step, so there may be inconsistencies
(e.g. if only one overload made it into the index result list, no folding).
We don't want to bundle together completions that have different side-effects
(include insertion), because we can't constructo a coherent CompletionItem.
This may be confusing for users, as the reason for non-bundling may not
be immediately obvious. (Also, the implementation seems a little fragile)
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47957
llvm-svn: 334822
2018-06-15 19:06:29 +08:00
|
|
|
}
|
|
|
|
|
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;
|
2018-07-02 19:13:16 +08:00
|
|
|
CodeCompleteResult Completions =
|
2018-05-24 22:49:23 +08:00
|
|
|
cantFail(runCodeComplete(Server, FooCpp, Source.point(), Opts));
|
|
|
|
// We shouldn't crash. Unfortunately, current workaround is to not produce
|
|
|
|
// comments for symbols from headers.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(Completions.Completions,
|
2018-05-24 22:49:23 +08:00
|
|
|
Contains(AllOf(Not(IsDocumented()), Named("func"))));
|
|
|
|
}
|
|
|
|
|
2018-06-15 16:31:17 +08:00
|
|
|
TEST(CompletionTest, NonDocComments) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
auto FooCpp = testPath("foo.cpp");
|
|
|
|
FS.Files[FooCpp] = "";
|
|
|
|
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
|
|
|
|
Annotations Source(R"cpp(
|
2018-06-27 17:47:20 +08:00
|
|
|
// We ignore namespace comments, for rationale see CodeCompletionStrings.h.
|
|
|
|
namespace comments_ns {
|
|
|
|
}
|
|
|
|
|
2018-06-15 16:31:17 +08:00
|
|
|
// ------------------
|
|
|
|
int comments_foo();
|
|
|
|
|
|
|
|
// A comment and a decl are separated by newlines.
|
|
|
|
// Therefore, the comment shouldn't show up as doc comment.
|
|
|
|
|
|
|
|
int comments_bar();
|
|
|
|
|
|
|
|
// this comment should be in the results.
|
|
|
|
int comments_baz();
|
|
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
struct Struct {
|
|
|
|
int comments_qux();
|
|
|
|
int comments_quux();
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// This comment should not be there.
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
int Struct<T>::comments_qux() {
|
|
|
|
}
|
|
|
|
|
|
|
|
// This comment **should** be in results.
|
|
|
|
template <class T>
|
|
|
|
int Struct<T>::comments_quux() {
|
|
|
|
int a = comments^;
|
|
|
|
}
|
|
|
|
)cpp");
|
2018-06-19 02:55:10 +08:00
|
|
|
// FIXME: Auto-completion in a template requires disabling delayed template
|
|
|
|
// parsing.
|
|
|
|
CDB.ExtraClangFlags.push_back("-fno-delayed-template-parsing");
|
2018-06-15 16:31:17 +08:00
|
|
|
Server.addDocument(FooCpp, Source.code(), WantDiagnostics::Yes);
|
2018-07-02 19:13:16 +08:00
|
|
|
CodeCompleteResult Completions = cantFail(runCodeComplete(
|
2018-06-15 16:31:17 +08:00
|
|
|
Server, FooCpp, Source.point(), clangd::CodeCompleteOptions()));
|
|
|
|
|
|
|
|
// We should not get any of those comments in completion.
|
|
|
|
EXPECT_THAT(
|
2018-07-02 19:13:16 +08:00
|
|
|
Completions.Completions,
|
2018-06-15 16:31:17 +08:00
|
|
|
UnorderedElementsAre(AllOf(Not(IsDocumented()), Named("comments_foo")),
|
|
|
|
AllOf(IsDocumented(), Named("comments_baz")),
|
|
|
|
AllOf(IsDocumented(), Named("comments_quux")),
|
2018-06-27 17:47:20 +08:00
|
|
|
AllOf(Not(IsDocumented()), Named("comments_ns")),
|
2018-06-15 16:31:17 +08:00
|
|
|
// FIXME(ibiryukov): the following items should have
|
|
|
|
// empty documentation, since they are separated from
|
|
|
|
// a comment with an empty line. Unfortunately, I
|
|
|
|
// couldn't make Sema tests pass if we ignore those.
|
|
|
|
AllOf(IsDocumented(), Named("comments_bar")),
|
|
|
|
AllOf(IsDocumented(), Named("comments_qux"))));
|
|
|
|
}
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2018-06-22 18:46:59 +08:00
|
|
|
TEST(CompletionTest, QualifiedNames) {
|
|
|
|
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.
|
2018-07-02 19:13:16 +08:00
|
|
|
EXPECT_THAT(
|
|
|
|
Results.Completions,
|
|
|
|
UnorderedElementsAre(Scope("ns::"), Scope("ns::"), Scope("ns::")));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, Render) {
|
|
|
|
CodeCompletion C;
|
|
|
|
C.Name = "x";
|
|
|
|
C.Signature = "(bool) const";
|
|
|
|
C.SnippetSuffix = "(${0:bool})";
|
|
|
|
C.ReturnType = "int";
|
|
|
|
C.RequiredQualifier = "Foo::";
|
|
|
|
C.Scope = "ns::Foo::";
|
|
|
|
C.Documentation = "This is x().";
|
|
|
|
C.Header = "\"foo.h\"";
|
|
|
|
C.Kind = CompletionItemKind::Method;
|
|
|
|
C.Score.Total = 1.0;
|
2018-07-06 19:50:49 +08:00
|
|
|
C.Origin = SymbolOrigin::AST | SymbolOrigin::Static;
|
2018-07-02 19:13:16 +08:00
|
|
|
|
|
|
|
CodeCompleteOptions Opts;
|
|
|
|
Opts.IncludeIndicator.Insert = "^";
|
|
|
|
Opts.IncludeIndicator.NoInsert = "";
|
|
|
|
Opts.EnableSnippets = false;
|
|
|
|
|
|
|
|
auto R = C.render(Opts);
|
|
|
|
EXPECT_EQ(R.label, "Foo::x(bool) const");
|
|
|
|
EXPECT_EQ(R.insertText, "Foo::x");
|
|
|
|
EXPECT_EQ(R.insertTextFormat, InsertTextFormat::PlainText);
|
|
|
|
EXPECT_EQ(R.filterText, "x");
|
|
|
|
EXPECT_EQ(R.detail, "int\n\"foo.h\"");
|
|
|
|
EXPECT_EQ(R.documentation, "This is x().");
|
|
|
|
EXPECT_THAT(R.additionalTextEdits, IsEmpty());
|
|
|
|
EXPECT_EQ(R.sortText, sortText(1.0, "x"));
|
|
|
|
|
|
|
|
Opts.EnableSnippets = true;
|
|
|
|
R = C.render(Opts);
|
|
|
|
EXPECT_EQ(R.insertText, "Foo::x(${0:bool})");
|
|
|
|
EXPECT_EQ(R.insertTextFormat, InsertTextFormat::Snippet);
|
|
|
|
|
|
|
|
C.HeaderInsertion.emplace();
|
|
|
|
R = C.render(Opts);
|
|
|
|
EXPECT_EQ(R.label, "^Foo::x(bool) const");
|
|
|
|
EXPECT_THAT(R.additionalTextEdits, Not(IsEmpty()));
|
|
|
|
|
2018-07-05 14:20:41 +08:00
|
|
|
Opts.ShowOrigins = true;
|
|
|
|
R = C.render(Opts);
|
|
|
|
EXPECT_EQ(R.label, "^[AS]Foo::x(bool) const");
|
|
|
|
|
2018-07-02 19:13:16 +08:00
|
|
|
C.BundleSize = 2;
|
|
|
|
R = C.render(Opts);
|
|
|
|
EXPECT_EQ(R.detail, "[2 overloads]\n\"foo.h\"");
|
2018-06-22 18:46:59 +08:00
|
|
|
}
|
2018-05-28 20:11:37 +08:00
|
|
|
|
2018-07-11 21:15:31 +08:00
|
|
|
TEST(CompletionTest, IgnoreRecoveryResults) {
|
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
namespace ns { int NotRecovered() { return 0; } }
|
|
|
|
void f() {
|
|
|
|
// Sema enters recovery mode first and then normal mode.
|
|
|
|
if (auto x = ns::NotRecover^)
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("NotRecovered")));
|
|
|
|
}
|
|
|
|
|
2018-07-18 23:31:14 +08:00
|
|
|
TEST(CompletionTest, ScopeOfClassFieldInConstructorInitializer) {
|
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
namespace ns {
|
|
|
|
class X { public: X(); int x_; };
|
|
|
|
X::X() : x_^(0) {}
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(Results.Completions,
|
|
|
|
UnorderedElementsAre(AllOf(Scope("ns::X::"), Named("x_"))));
|
|
|
|
}
|
|
|
|
|
2018-07-23 18:56:37 +08:00
|
|
|
TEST(CompletionTest, CodeCompletionContext) {
|
|
|
|
auto Results = completions(
|
|
|
|
R"cpp(
|
|
|
|
namespace ns {
|
|
|
|
class X { public: X(); int x_; };
|
|
|
|
void f() {
|
|
|
|
X x;
|
|
|
|
x.^;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
|
|
|
|
EXPECT_THAT(Results.Context, CodeCompletionContext::CCC_DotMemberAccess);
|
|
|
|
}
|
|
|
|
|
2018-08-08 16:59:29 +08:00
|
|
|
TEST(CompletionTest, FixItForArrowToDot) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
|
|
|
|
CodeCompleteOptions Opts;
|
|
|
|
Opts.IncludeFixIts = true;
|
|
|
|
Annotations TestCode(
|
|
|
|
R"cpp(
|
|
|
|
class Auxilary {
|
|
|
|
public:
|
|
|
|
void AuxFunction();
|
|
|
|
};
|
|
|
|
class ClassWithPtr {
|
|
|
|
public:
|
|
|
|
void MemberFunction();
|
|
|
|
Auxilary* operator->() const;
|
|
|
|
Auxilary* Aux;
|
|
|
|
};
|
|
|
|
void f() {
|
|
|
|
ClassWithPtr x;
|
|
|
|
x[[->]]^;
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
auto Results =
|
|
|
|
completions(Server, TestCode.code(), TestCode.point(), {}, Opts);
|
|
|
|
EXPECT_EQ(Results.Completions.size(), 3u);
|
|
|
|
|
|
|
|
TextEdit ReplacementEdit;
|
|
|
|
ReplacementEdit.range = TestCode.range();
|
|
|
|
ReplacementEdit.newText = ".";
|
|
|
|
for (const auto &C : Results.Completions) {
|
|
|
|
EXPECT_TRUE(C.FixIts.size() == 1u || C.Name == "AuxFunction");
|
2018-08-10 16:34:16 +08:00
|
|
|
if (!C.FixIts.empty()) {
|
2018-08-08 16:59:29 +08:00
|
|
|
EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit));
|
2018-08-10 16:34:16 +08:00
|
|
|
}
|
2018-08-08 16:59:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, FixItForDotToArrow) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
|
|
|
|
CodeCompleteOptions Opts;
|
|
|
|
Opts.IncludeFixIts = true;
|
|
|
|
Annotations TestCode(
|
|
|
|
R"cpp(
|
|
|
|
class Auxilary {
|
|
|
|
public:
|
|
|
|
void AuxFunction();
|
|
|
|
};
|
|
|
|
class ClassWithPtr {
|
|
|
|
public:
|
|
|
|
void MemberFunction();
|
|
|
|
Auxilary* operator->() const;
|
|
|
|
Auxilary* Aux;
|
|
|
|
};
|
|
|
|
void f() {
|
|
|
|
ClassWithPtr x;
|
|
|
|
x[[.]]^;
|
|
|
|
}
|
|
|
|
)cpp");
|
|
|
|
auto Results =
|
|
|
|
completions(Server, TestCode.code(), TestCode.point(), {}, Opts);
|
|
|
|
EXPECT_EQ(Results.Completions.size(), 3u);
|
|
|
|
|
|
|
|
TextEdit ReplacementEdit;
|
|
|
|
ReplacementEdit.range = TestCode.range();
|
|
|
|
ReplacementEdit.newText = "->";
|
|
|
|
for (const auto &C : Results.Completions) {
|
|
|
|
EXPECT_TRUE(C.FixIts.empty() || C.Name == "AuxFunction");
|
|
|
|
if (!C.FixIts.empty()) {
|
|
|
|
EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-13 16:23:01 +08:00
|
|
|
TEST(CompletionTest, RenderWithFixItMerged) {
|
|
|
|
TextEdit FixIt;
|
|
|
|
FixIt.range.end.character = 5;
|
|
|
|
FixIt.newText = "->";
|
|
|
|
|
|
|
|
CodeCompletion C;
|
|
|
|
C.Name = "x";
|
|
|
|
C.RequiredQualifier = "Foo::";
|
|
|
|
C.FixIts = {FixIt};
|
|
|
|
C.CompletionTokenRange.start.character = 5;
|
|
|
|
|
|
|
|
CodeCompleteOptions Opts;
|
|
|
|
Opts.IncludeFixIts = true;
|
|
|
|
|
|
|
|
auto R = C.render(Opts);
|
|
|
|
EXPECT_TRUE(R.textEdit);
|
|
|
|
EXPECT_EQ(R.textEdit->newText, "->Foo::x");
|
|
|
|
EXPECT_TRUE(R.additionalTextEdits.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, RenderWithFixItNonMerged) {
|
|
|
|
TextEdit FixIt;
|
|
|
|
FixIt.range.end.character = 4;
|
|
|
|
FixIt.newText = "->";
|
|
|
|
|
|
|
|
CodeCompletion C;
|
|
|
|
C.Name = "x";
|
|
|
|
C.RequiredQualifier = "Foo::";
|
|
|
|
C.FixIts = {FixIt};
|
|
|
|
C.CompletionTokenRange.start.character = 5;
|
|
|
|
|
|
|
|
CodeCompleteOptions Opts;
|
|
|
|
Opts.IncludeFixIts = true;
|
|
|
|
|
|
|
|
auto R = C.render(Opts);
|
|
|
|
EXPECT_TRUE(R.textEdit);
|
|
|
|
EXPECT_EQ(R.textEdit->newText, "Foo::x");
|
|
|
|
EXPECT_THAT(R.additionalTextEdits, UnorderedElementsAre(FixIt));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(CompletionTest, CompletionTokenRange) {
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
|
|
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
|
|
|
|
|
|
|
|
constexpr const char *TestCodes[] = {
|
|
|
|
R"cpp(
|
|
|
|
class Auxilary {
|
|
|
|
public:
|
|
|
|
void AuxFunction();
|
|
|
|
};
|
|
|
|
void f() {
|
|
|
|
Auxilary x;
|
|
|
|
x.[[Aux]]^;
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
|
|
class Auxilary {
|
|
|
|
public:
|
|
|
|
void AuxFunction();
|
|
|
|
};
|
|
|
|
void f() {
|
|
|
|
Auxilary x;
|
|
|
|
x.[[]]^;
|
|
|
|
}
|
|
|
|
)cpp"};
|
|
|
|
for (const auto &Text : TestCodes) {
|
|
|
|
Annotations TestCode(Text);
|
|
|
|
auto Results = completions(Server, TestCode.code(), TestCode.point());
|
|
|
|
|
|
|
|
EXPECT_EQ(Results.Completions.size(), 1u);
|
|
|
|
EXPECT_THAT(Results.Completions.front().CompletionTokenRange, TestCode.range());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-13 16:40:05 +08:00
|
|
|
TEST(SignatureHelpTest, OverloadsOrdering) {
|
|
|
|
const auto Results = signatures(R"cpp(
|
|
|
|
void foo(int x);
|
|
|
|
void foo(int x, float y);
|
|
|
|
void foo(float x, int y);
|
|
|
|
void foo(float x, float y);
|
|
|
|
void foo(int x, int y = 0);
|
|
|
|
int main() { foo(^); }
|
|
|
|
)cpp");
|
|
|
|
EXPECT_THAT(
|
|
|
|
Results.signatures,
|
|
|
|
ElementsAre(
|
|
|
|
Sig("foo(int x) -> void", {"int x"}),
|
|
|
|
Sig("foo(int x, int y = 0) -> void", {"int x", "int y = 0"}),
|
|
|
|
Sig("foo(float x, int y) -> void", {"float x", "int y"}),
|
|
|
|
Sig("foo(int x, float y) -> void", {"int x", "float y"}),
|
|
|
|
Sig("foo(float x, float y) -> void", {"float x", "float y"})));
|
|
|
|
// We always prefer the first signature.
|
|
|
|
EXPECT_EQ(0, Results.activeSignature);
|
|
|
|
EXPECT_EQ(0, Results.activeParameter);
|
|
|
|
}
|
|
|
|
|
2018-08-14 17:36:32 +08:00
|
|
|
TEST(SignatureHelpTest, InstantiatedSignatures) {
|
2018-08-16 19:41:19 +08:00
|
|
|
StringRef Sig0 = R"cpp(
|
2018-08-14 17:36:32 +08:00
|
|
|
template <class T>
|
|
|
|
void foo(T, T, T);
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
foo<int>(^);
|
|
|
|
}
|
2018-08-16 19:41:19 +08:00
|
|
|
)cpp";
|
|
|
|
|
|
|
|
EXPECT_THAT(signatures(Sig0).signatures,
|
2018-08-14 17:36:32 +08:00
|
|
|
ElementsAre(Sig("foo(T, T, T) -> void", {"T", "T", "T"})));
|
|
|
|
|
2018-08-16 19:41:19 +08:00
|
|
|
StringRef Sig1 = R"cpp(
|
2018-08-14 17:36:32 +08:00
|
|
|
template <class T>
|
|
|
|
void foo(T, T, T);
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
foo(10, ^);
|
2018-08-16 19:41:19 +08:00
|
|
|
})cpp";
|
|
|
|
|
|
|
|
EXPECT_THAT(signatures(Sig1).signatures,
|
2018-08-14 17:36:32 +08:00
|
|
|
ElementsAre(Sig("foo(T, T, T) -> void", {"T", "T", "T"})));
|
|
|
|
|
2018-08-16 19:41:19 +08:00
|
|
|
StringRef Sig2 = R"cpp(
|
2018-08-14 17:36:32 +08:00
|
|
|
template <class ...T>
|
|
|
|
void foo(T...);
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
foo<int>(^);
|
|
|
|
}
|
2018-08-16 19:41:19 +08:00
|
|
|
)cpp";
|
|
|
|
|
|
|
|
EXPECT_THAT(signatures(Sig2).signatures,
|
2018-08-14 17:36:32 +08:00
|
|
|
ElementsAre(Sig("foo(T...) -> void", {"T..."})));
|
|
|
|
|
|
|
|
// It is debatable whether we should substitute the outer template parameter
|
|
|
|
// ('T') in that case. Currently we don't substitute it in signature help, but
|
|
|
|
// do substitute in code complete.
|
|
|
|
// FIXME: make code complete and signature help consistent, figure out which
|
|
|
|
// way is better.
|
2018-08-16 19:41:19 +08:00
|
|
|
StringRef Sig3 = R"cpp(
|
2018-08-14 17:36:32 +08:00
|
|
|
template <class T>
|
|
|
|
struct X {
|
|
|
|
template <class U>
|
|
|
|
void foo(T, U);
|
|
|
|
};
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
X<int>().foo<double>(^)
|
|
|
|
}
|
2018-08-16 19:41:19 +08:00
|
|
|
)cpp";
|
|
|
|
|
|
|
|
EXPECT_THAT(signatures(Sig3).signatures,
|
2018-08-14 17:36:32 +08:00
|
|
|
ElementsAre(Sig("foo(T, U) -> void", {"T", "U"})));
|
|
|
|
}
|
|
|
|
|
2017-12-05 15:20:26 +08:00
|
|
|
} // namespace
|
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|