blob: d1fc159debc4cf1f354b378e87ca5b88d0d6231b [file] [log] [blame]
Sam McCall9aad25f2017-12-05 07:20:26 +00001//===-- CodeCompleteTests.cpp -----------------------------------*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9#include "ClangdServer.h"
10#include "Compiler.h"
11#include "Protocol.h"
12#include "TestFS.h"
13#include "gtest/gtest.h"
14
15namespace clang {
16namespace clangd {
17namespace {
18using namespace llvm;
19
20class IgnoreDiagnostics : public DiagnosticsConsumer {
21 void onDiagnosticsReady(
22 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) override {}
23};
24
25struct StringWithPos {
26 std::string Text;
27 clangd::Position MarkerPos;
28};
29
30/// Returns location of "{mark}" substring in \p Text and removes it from \p
31/// Text. Note that \p Text must contain exactly one occurence of "{mark}".
32///
33/// Marker name can be configured using \p MarkerName parameter.
34StringWithPos parseTextMarker(StringRef Text, StringRef MarkerName = "mark") {
35 SmallString<16> Marker;
36 Twine("{" + MarkerName + "}").toVector(/*ref*/ Marker);
37
38 std::size_t MarkerOffset = Text.find(Marker);
39 assert(MarkerOffset != StringRef::npos && "{mark} wasn't found in Text.");
40
41 std::string WithoutMarker;
42 WithoutMarker += Text.take_front(MarkerOffset);
43 WithoutMarker += Text.drop_front(MarkerOffset + Marker.size());
44 assert(StringRef(WithoutMarker).find(Marker) == StringRef::npos &&
45 "There were multiple occurences of {mark} inside Text");
46
47 clangd::Position MarkerPos =
48 clangd::offsetToPosition(WithoutMarker, MarkerOffset);
49 return {std::move(WithoutMarker), MarkerPos};
50}
51
52class ClangdCompletionTest : public ::testing::Test {
53protected:
54 template <class Predicate>
55 bool ContainsItemPred(CompletionList const &Items, Predicate Pred) {
56 for (const auto &Item : Items.items) {
57 if (Pred(Item))
58 return true;
59 }
60 return false;
61 }
62
63 bool ContainsItem(CompletionList const &Items, StringRef Name) {
64 return ContainsItemPred(Items, [Name](clangd::CompletionItem Item) {
65 return Item.insertText == Name;
66 });
67 return false;
68 }
69};
70
71TEST_F(ClangdCompletionTest, CheckContentsOverride) {
72 MockFSProvider FS;
73 IgnoreDiagnostics DiagConsumer;
Sam McCall93cd9912017-12-05 07:34:35 +000074 MockCompilationDatabase CDB;
Sam McCall9aad25f2017-12-05 07:20:26 +000075
76 ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
77 /*StorePreamblesInMemory=*/true,
78 clangd::CodeCompleteOptions(),
79 EmptyLogger::getInstance());
80
81 auto FooCpp = getVirtualTestFilePath("foo.cpp");
82 const auto SourceContents = R"cpp(
83int aba;
84int b = ;
85)cpp";
86
87 const auto OverridenSourceContents = R"cpp(
88int cbc;
89int b = ;
90)cpp";
91 // Complete after '=' sign. We need to be careful to keep the SourceContents'
92 // size the same.
93 // We complete on the 3rd line (2nd in zero-based numbering), because raw
94 // string literal of the SourceContents starts with a newline(it's easy to
95 // miss).
96 Position CompletePos = {2, 8};
97 FS.Files[FooCpp] = SourceContents;
98 FS.ExpectedFile = FooCpp;
99
100 // No need to sync reparses here as there are no asserts on diagnostics (or
101 // other async operations).
102 Server.addDocument(FooCpp, SourceContents);
103
104 {
105 auto CodeCompletionResults1 =
106 Server.codeComplete(FooCpp, CompletePos, None).get().Value;
107 EXPECT_TRUE(ContainsItem(CodeCompletionResults1, "aba"));
108 EXPECT_FALSE(ContainsItem(CodeCompletionResults1, "cbc"));
109 }
110
111 {
112 auto CodeCompletionResultsOverriden =
113 Server
114 .codeComplete(FooCpp, CompletePos,
115 StringRef(OverridenSourceContents))
116 .get()
117 .Value;
118 EXPECT_TRUE(ContainsItem(CodeCompletionResultsOverriden, "cbc"));
119 EXPECT_FALSE(ContainsItem(CodeCompletionResultsOverriden, "aba"));
120 }
121
122 {
123 auto CodeCompletionResults2 =
124 Server.codeComplete(FooCpp, CompletePos, None).get().Value;
125 EXPECT_TRUE(ContainsItem(CodeCompletionResults2, "aba"));
126 EXPECT_FALSE(ContainsItem(CodeCompletionResults2, "cbc"));
127 }
128}
129
130TEST_F(ClangdCompletionTest, Limit) {
131 MockFSProvider FS;
Sam McCall93cd9912017-12-05 07:34:35 +0000132 MockCompilationDatabase CDB;
Sam McCall9aad25f2017-12-05 07:20:26 +0000133 CDB.ExtraClangFlags.push_back("-xc++");
134 IgnoreDiagnostics DiagConsumer;
135 clangd::CodeCompleteOptions Opts;
136 Opts.Limit = 2;
137 ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
138 /*StorePreamblesInMemory=*/true, Opts,
139 EmptyLogger::getInstance());
140
141 auto FooCpp = getVirtualTestFilePath("foo.cpp");
142 FS.Files[FooCpp] = "";
143 FS.ExpectedFile = FooCpp;
144 StringWithPos Completion = parseTextMarker(R"cpp(
145struct ClassWithMembers {
146 int AAA();
147 int BBB();
148 int CCC();
149}
150int main() { ClassWithMembers().{complete} }
151 )cpp",
152 "complete");
153 Server.addDocument(FooCpp, Completion.Text);
154
155 /// For after-dot completion we must always get consistent results.
156 auto Results = Server
157 .codeComplete(FooCpp, Completion.MarkerPos,
158 StringRef(Completion.Text))
159 .get()
160 .Value;
161
162 EXPECT_TRUE(Results.isIncomplete);
163 EXPECT_EQ(Opts.Limit, Results.items.size());
164 EXPECT_TRUE(ContainsItem(Results, "AAA"));
165 EXPECT_TRUE(ContainsItem(Results, "BBB"));
166 EXPECT_FALSE(ContainsItem(Results, "CCC"));
167}
168
169TEST_F(ClangdCompletionTest, Filter) {
170 MockFSProvider FS;
Sam McCall93cd9912017-12-05 07:34:35 +0000171 MockCompilationDatabase CDB;
Sam McCall9aad25f2017-12-05 07:20:26 +0000172 CDB.ExtraClangFlags.push_back("-xc++");
173 IgnoreDiagnostics DiagConsumer;
174 clangd::CodeCompleteOptions Opts;
175 ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
176 /*StorePreamblesInMemory=*/true, Opts,
177 EmptyLogger::getInstance());
178
179 auto FooCpp = getVirtualTestFilePath("foo.cpp");
180 FS.Files[FooCpp] = "";
181 FS.ExpectedFile = FooCpp;
182 const char *Body = R"cpp(
183 int Abracadabra;
184 int Alakazam;
185 struct S {
186 int FooBar;
187 int FooBaz;
188 int Qux;
189 };
190 )cpp";
191 auto Complete = [&](StringRef Query) {
192 StringWithPos Completion = parseTextMarker(
193 formatv("{0} int main() { {1}{{complete}} }", Body, Query).str(),
194 "complete");
195 Server.addDocument(FooCpp, Completion.Text);
196 return Server
197 .codeComplete(FooCpp, Completion.MarkerPos, StringRef(Completion.Text))
198 .get()
199 .Value;
200 };
201
202 auto Foba = Complete("S().Foba");
203 EXPECT_TRUE(ContainsItem(Foba, "FooBar"));
204 EXPECT_TRUE(ContainsItem(Foba, "FooBaz"));
205 EXPECT_FALSE(ContainsItem(Foba, "Qux"));
206
207 auto FR = Complete("S().FR");
208 EXPECT_TRUE(ContainsItem(FR, "FooBar"));
209 EXPECT_FALSE(ContainsItem(FR, "FooBaz"));
210 EXPECT_FALSE(ContainsItem(FR, "Qux"));
211
212 auto Op = Complete("S().opr");
213 EXPECT_TRUE(ContainsItem(Op, "operator="));
214
215 auto Aaa = Complete("aaa");
216 EXPECT_TRUE(ContainsItem(Aaa, "Abracadabra"));
217 EXPECT_TRUE(ContainsItem(Aaa, "Alakazam"));
218
219 auto UA = Complete("_a");
220 EXPECT_TRUE(ContainsItem(UA, "static_cast"));
221 EXPECT_FALSE(ContainsItem(UA, "Abracadabra"));
222}
223
224TEST_F(ClangdCompletionTest, CompletionOptions) {
225 MockFSProvider FS;
226 IgnoreDiagnostics DiagConsumer;
Sam McCall93cd9912017-12-05 07:34:35 +0000227 MockCompilationDatabase CDB;
Sam McCall9aad25f2017-12-05 07:20:26 +0000228 CDB.ExtraClangFlags.push_back("-xc++");
229
230 auto FooCpp = getVirtualTestFilePath("foo.cpp");
231 FS.Files[FooCpp] = "";
232 FS.ExpectedFile = FooCpp;
233
234 const auto GlobalCompletionSourceTemplate = R"cpp(
235#define MACRO X
236
237int global_var;
238int global_func();
239
240struct GlobalClass {};
241
242struct ClassWithMembers {
243 /// Doc for method.
244 int method();
245};
246
247int test() {
248 struct LocalClass {};
249
250 /// Doc for local_var.
251 int local_var;
252
253 {complete}
254}
255)cpp";
256 const auto MemberCompletionSourceTemplate = R"cpp(
257#define MACRO X
258
259int global_var;
260
261int global_func();
262
263struct GlobalClass {};
264
265struct ClassWithMembers {
266 /// Doc for method.
267 int method();
268
269 int field;
270private:
271 int private_field;
272};
273
274int test() {
275 struct LocalClass {};
276
277 /// Doc for local_var.
278 int local_var;
279
280 ClassWithMembers().{complete}
281}
282)cpp";
283
284 StringWithPos GlobalCompletion =
285 parseTextMarker(GlobalCompletionSourceTemplate, "complete");
286 StringWithPos MemberCompletion =
287 parseTextMarker(MemberCompletionSourceTemplate, "complete");
288
289 auto TestWithOpts = [&](clangd::CodeCompleteOptions Opts) {
290 ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
291 /*StorePreamblesInMemory=*/true, Opts,
292 EmptyLogger::getInstance());
293 // No need to sync reparses here as there are no asserts on diagnostics (or
294 // other async operations).
295 Server.addDocument(FooCpp, GlobalCompletion.Text);
296
297 StringRef MethodItemText = Opts.EnableSnippets ? "method()" : "method";
298 StringRef GlobalFuncItemText =
299 Opts.EnableSnippets ? "global_func()" : "global_func";
300
301 /// For after-dot completion we must always get consistent results.
302 {
303 auto Results = Server
304 .codeComplete(FooCpp, MemberCompletion.MarkerPos,
305 StringRef(MemberCompletion.Text))
306 .get()
307 .Value;
308
309 // Class members. The only items that must be present in after-dor
310 // completion.
311 EXPECT_TRUE(ContainsItem(Results, MethodItemText));
312 EXPECT_TRUE(ContainsItem(Results, MethodItemText));
313 EXPECT_TRUE(ContainsItem(Results, "field"));
314 EXPECT_EQ(Opts.IncludeIneligibleResults,
315 ContainsItem(Results, "private_field"));
316 // Global items.
317 EXPECT_FALSE(ContainsItem(Results, "global_var"));
318 EXPECT_FALSE(ContainsItem(Results, GlobalFuncItemText));
319 EXPECT_FALSE(ContainsItem(Results, "GlobalClass"));
320 // A macro.
321 EXPECT_FALSE(ContainsItem(Results, "MACRO"));
322 // Local items.
323 EXPECT_FALSE(ContainsItem(Results, "LocalClass"));
324 // There should be no code patterns (aka snippets) in after-dot
325 // completion. At least there aren't any we're aware of.
326 EXPECT_FALSE(
327 ContainsItemPred(Results, [](clangd::CompletionItem const &Item) {
328 return Item.kind == clangd::CompletionItemKind::Snippet;
329 }));
330 // Check documentation.
331 EXPECT_EQ(
332 Opts.IncludeBriefComments,
333 ContainsItemPred(Results, [](clangd::CompletionItem const &Item) {
334 return !Item.documentation.empty();
335 }));
336 }
337 // Global completion differs based on the Opts that were passed.
338 {
339 auto Results = Server
340 .codeComplete(FooCpp, GlobalCompletion.MarkerPos,
341 StringRef(GlobalCompletion.Text))
342 .get()
343 .Value;
344
345 // Class members. Should never be present in global completions.
346 EXPECT_FALSE(ContainsItem(Results, MethodItemText));
347 EXPECT_FALSE(ContainsItem(Results, "field"));
348 // Global items.
349 EXPECT_EQ(ContainsItem(Results, "global_var"), Opts.IncludeGlobals);
350 EXPECT_EQ(ContainsItem(Results, GlobalFuncItemText), Opts.IncludeGlobals);
351 EXPECT_EQ(ContainsItem(Results, "GlobalClass"), Opts.IncludeGlobals);
352 // A macro.
353 EXPECT_EQ(ContainsItem(Results, "MACRO"), Opts.IncludeMacros);
354 // Local items. Must be present always.
355 EXPECT_TRUE(ContainsItem(Results, "local_var"));
356 EXPECT_TRUE(ContainsItem(Results, "LocalClass"));
357 // FIXME(ibiryukov): snippets have wrong Item.kind now. Reenable this
358 // check after https://reviews.llvm.org/D38720 makes it in.
359 //
360 // Code patterns (aka snippets).
361 // EXPECT_EQ(
362 // Opts.IncludeCodePatterns && Opts.EnableSnippets,
363 // ContainsItemPred(Results, [](clangd::CompletionItem const &Item) {
364 // return Item.kind == clangd::CompletionItemKind::Snippet;
365 // }));
366
367 // Check documentation.
368 EXPECT_EQ(
369 Opts.IncludeBriefComments,
370 ContainsItemPred(Results, [](clangd::CompletionItem const &Item) {
371 return !Item.documentation.empty();
372 }));
373 }
374 };
375
376 clangd::CodeCompleteOptions CCOpts;
377 for (bool IncludeMacros : {true, false}) {
378 CCOpts.IncludeMacros = IncludeMacros;
379 for (bool IncludeGlobals : {true, false}) {
380 CCOpts.IncludeGlobals = IncludeGlobals;
381 for (bool IncludeBriefComments : {true, false}) {
382 CCOpts.IncludeBriefComments = IncludeBriefComments;
383 for (bool EnableSnippets : {true, false}) {
384 CCOpts.EnableSnippets = EnableSnippets;
385 for (bool IncludeCodePatterns : {true, false}) {
386 CCOpts.IncludeCodePatterns = IncludeCodePatterns;
387 for (bool IncludeIneligibleResults : {true, false}) {
388 CCOpts.IncludeIneligibleResults = IncludeIneligibleResults;
389 TestWithOpts(CCOpts);
390 }
391 }
392 }
393 }
394 }
395 }
396}
397
398} // namespace
399} // namespace clangd
400} // namespace clang