1 /**
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "lsp_api_test.h"
17 #include "lsp/include/completions.h"
18 #include "lsp/include/internal_api.h"
19
20 class LSPCompletionsTests : public LSPAPITests {};
21
22 using ark::es2panda::lsp::CompletionEntry;
23 using ark::es2panda::lsp::CompletionEntryKind;
24 using ark::es2panda::lsp::Initializer;
25 using ark::es2panda::lsp::sort_text::GLOBALS_OR_KEYWORDS;
26
AssertCompletionsContainAndNotContainEntries(const std::vector<CompletionEntry> & entries,const std::vector<CompletionEntry> & expectedEntries,const std::vector<CompletionEntry> & unexpectedEntries)27 static void AssertCompletionsContainAndNotContainEntries(const std::vector<CompletionEntry> &entries,
28 const std::vector<CompletionEntry> &expectedEntries,
29 const std::vector<CompletionEntry> &unexpectedEntries)
30 {
31 auto emptyCheck = expectedEntries.empty() && !entries.empty();
32 ASSERT_FALSE(emptyCheck) << "Expected empty but the result is not. Actual account: " << entries.size();
33
34 for (const auto &expectedEntry : expectedEntries) {
35 bool found = false;
36 for (const auto &entry : entries) {
37 if (entry.GetName() == expectedEntry.GetName() &&
38 entry.GetCompletionKind() == expectedEntry.GetCompletionKind()) {
39 found = true;
40 break;
41 }
42 }
43 ASSERT_TRUE(found) << "Expected completion '" << expectedEntry.GetName() << "' not found";
44 }
45
46 for (const auto &unexpectedEntry : unexpectedEntries) {
47 bool found = false;
48 for (const auto &entry : entries) {
49 if (entry.GetName() == unexpectedEntry.GetName() &&
50 entry.GetCompletionKind() == unexpectedEntry.GetCompletionKind()) {
51 found = true;
52 break;
53 }
54 }
55 ASSERT_FALSE(found) << "Unexpected completion '" << unexpectedEntry.GetName() << "' found";
56 }
57 }
58
59 namespace {
TEST_F(LSPCompletionsTests,MemberCompletionsForClassTest9)60 TEST_F(LSPCompletionsTests, MemberCompletionsForClassTest9)
61 {
62 std::vector<std::string> files = {"getCompletionsAtPositionMember9.ets"};
63 std::vector<std::string> texts = {R"delimiter(
64 namespace space {
65 export class classInSpace {
66 public c: number = 2;
67 }
68 }
69 let numOfSpace: space._WILDCARD)delimiter"};
70 auto filePaths = CreateTempFile(files, texts);
71 int const expectedFileCount = 1;
72 ASSERT_EQ(filePaths.size(), expectedFileCount);
73 Initializer initializer = Initializer();
74 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
75 LSPAPI const *lspApi = GetImpl();
76 const size_t offset = 113;
77 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
78 auto entries = res.GetEntries();
79 std::string propertyName1 = "classInSpace";
80 ASSERT_TRUE(entries.size() == 1);
81 CompletionEntry entry1 =
82 CompletionEntry(propertyName1, CompletionEntryKind::CLASS,
83 std::string(ark::es2panda::lsp::sort_text::MEMBER_DECLARED_BY_SPREAD_ASSIGNMENT));
84 initializer.DestroyContext(ctx);
85 ASSERT_EQ(entry1, entries[0]);
86 }
87
TEST_F(LSPCompletionsTests,getCompletionsAtPosition6)88 TEST_F(LSPCompletionsTests, getCompletionsAtPosition6)
89 {
90 std::vector<std::string> files = {"getCompletionsAtPosition9.ets"};
91 std::vector<std::string> texts = {R"delimiter(
92 let a: num
93 )delimiter"};
94 auto filePaths = CreateTempFile(files, texts);
95 int const expectedFileCount = 1;
96 ASSERT_EQ(filePaths.size(), expectedFileCount);
97
98 LSPAPI const *lspApi = GetImpl();
99 size_t const offset = 11; // after 'n' in 'let a = n'
100 Initializer initializer = Initializer();
101 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
102 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
103 auto expectedEntries = std::vector<CompletionEntry> {
104 CompletionEntry("number", ark::es2panda::lsp::CompletionEntryKind::KEYWORD, std::string(GLOBALS_OR_KEYWORDS))};
105 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
106 initializer.DestroyContext(ctx);
107 }
108
TEST_F(LSPCompletionsTests,getCompletionsAtPosition5)109 TEST_F(LSPCompletionsTests, getCompletionsAtPosition5)
110 {
111 std::vector<std::string> files = {"getCompletionsAtPosition8.ets"};
112 std::vector<std::string> texts = {R"delimiter(
113 class
114 )delimiter"};
115 auto filePaths = CreateTempFile(files, texts);
116 int const expectedFileCount = 1;
117 ASSERT_EQ(filePaths.size(), expectedFileCount);
118
119 LSPAPI const *lspApi = GetImpl();
120 size_t const offset = 6; // after 'ss' in 'class'
121 Initializer initializer = Initializer();
122 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
123 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
124 auto expectedEntries = std::vector<CompletionEntry> {};
125 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
126 initializer.DestroyContext(ctx);
127 }
128
TEST_F(LSPCompletionsTests,getCompletionsAtPosition0)129 TEST_F(LSPCompletionsTests, getCompletionsAtPosition0)
130 {
131 std::vector<std::string> files = {"getCompletionsAtPosition7.ets"};
132 std::vector<std::string> texts = {R"delimiter(
133 function num1() {
134 return 1;
135 }
136
137 function num2() {
138 return 2;
139 }
140
141 console.log(1);
142
143 let a = n
144 )delimiter"};
145 auto filePaths = CreateTempFile(files, texts);
146 int const expectedFileCount = 1;
147 ASSERT_EQ(filePaths.size(), expectedFileCount);
148
149 LSPAPI const *lspApi = GetImpl();
150 size_t const offset = 97; // after 'n' in 'let a = n'
151 Initializer initializer = Initializer();
152 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
153 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
154 auto expectedEntries = std::vector<CompletionEntry> {
155 CompletionEntry("num1", ark::es2panda::lsp::CompletionEntryKind::FUNCTION, std::string(GLOBALS_OR_KEYWORDS)),
156 CompletionEntry("num2", ark::es2panda::lsp::CompletionEntryKind::FUNCTION, std::string(GLOBALS_OR_KEYWORDS)),
157 };
158 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
159 initializer.DestroyContext(ctx);
160 }
161
TEST_F(LSPCompletionsTests,getCompletionsAtPosition1)162 TEST_F(LSPCompletionsTests, getCompletionsAtPosition1)
163 {
164 std::vector<std::string> files = {"getCompletionsAtPosition1.ets"};
165 std::vector<std::string> texts = {R"delimiter(
166 function num1() {
167 return 1;
168 }
169
170 function num2() {
171 return 2;
172 }
173
174 let a = n
175 )delimiter"};
176 auto filePaths = CreateTempFile(files, texts);
177 int const expectedFileCount = 1;
178 ASSERT_EQ(filePaths.size(), expectedFileCount);
179 Initializer initializer = Initializer();
180 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
181 LSPAPI const *lspApi = GetImpl();
182 size_t const offset = 80; // after 'n' in 'let a = n'
183 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
184 auto expectedEntries = std::vector<CompletionEntry> {
185 CompletionEntry("num1", ark::es2panda::lsp::CompletionEntryKind::FUNCTION, std::string(GLOBALS_OR_KEYWORDS)),
186 CompletionEntry("num2", ark::es2panda::lsp::CompletionEntryKind::FUNCTION, std::string(GLOBALS_OR_KEYWORDS)),
187 };
188 initializer.DestroyContext(ctx);
189 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
190 }
191
TEST_F(LSPCompletionsTests,getCompletionsAtPosition2)192 TEST_F(LSPCompletionsTests, getCompletionsAtPosition2)
193 {
194 std::vector<std::string> files = {"getCompletionsAtPosition2.ets"};
195 std::vector<std::string> texts = {R"delimiter(
196 let aaa = 123;
197 const abb = 333;
198
199 function axx() {
200 return 444;
201 }
202
203 function foo() {
204 let bbb = 222;
205 let ccc = bbb + a
206 return bbb + ccc;
207 }
208 )delimiter"};
209 auto filePaths = CreateTempFile(files, texts);
210 int const expectedFileCount = 1;
211 ASSERT_EQ(filePaths.size(), expectedFileCount);
212 Initializer initializer = Initializer();
213 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
214 LSPAPI const *lspApi = GetImpl();
215 size_t const offset = 127; // after 'a' in 'let ccc = bbb + a'
216 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
217 auto expectedEntries = std::vector<CompletionEntry> {
218 CompletionEntry("aaa", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
219 CompletionEntry("abb", ark::es2panda::lsp::CompletionEntryKind::CONSTANT, std::string(GLOBALS_OR_KEYWORDS)),
220 CompletionEntry("axx", ark::es2panda::lsp::CompletionEntryKind::FUNCTION, std::string(GLOBALS_OR_KEYWORDS)),
221 };
222 initializer.DestroyContext(ctx);
223 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
224 }
225
TEST_F(LSPCompletionsTests,getCompletionsAtPosition3)226 TEST_F(LSPCompletionsTests, getCompletionsAtPosition3)
227 {
228 std::vector<std::string> files = {"getCompletionsAtPosition3.ets"};
229 std::vector<std::string> texts = {R"delimiter(
230 class Foo {
231 bar: number = 1;
232 }
233
234 let foo = new Foo();
235 foo.bar = 2;
236 let baa = 3;
237 let bbb = 4;
238
239 function bxx() {
240 return 5;
241 }
242
243 function fxx() {
244 let bcc = 6;
245 let axx = b
246 }
247 )delimiter"};
248 auto filePaths = CreateTempFile(files, texts);
249 int const expectedFileCount = 1;
250 ASSERT_EQ(filePaths.size(), expectedFileCount);
251 Initializer initializer = Initializer();
252 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
253 LSPAPI const *lspApi = GetImpl();
254 size_t const offset = 181; // after 'b' in 'let axx = b'
255 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
256 auto expectedEntries = std::vector<CompletionEntry> {
257 CompletionEntry("baa", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
258 CompletionEntry("bbb", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
259 CompletionEntry("bcc", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
260 CompletionEntry("bxx", ark::es2panda::lsp::CompletionEntryKind::FUNCTION, std::string(GLOBALS_OR_KEYWORDS)),
261 };
262 auto unexpectedEntries = std::vector<CompletionEntry> {
263 CompletionEntry("bar", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
264 };
265 initializer.DestroyContext(ctx);
266 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, unexpectedEntries);
267 }
268
TEST_F(LSPCompletionsTests,getCompletionsAtPosition4)269 TEST_F(LSPCompletionsTests, getCompletionsAtPosition4)
270 {
271 std::vector<std::string> files = {"getCompletionsAtPosition4.ets"};
272 std::vector<std::string> texts = {R"delimiter(
273 class Foo {
274 bar: number = 1;
275 }
276
277 let foo = new Foo();
278 foo.bar = 2;
279 let baa = 3;
280 let bbb = 4;
281
282 function bxx() {
283 let bcc = 6;
284 return 5;
285 }
286 let axx = b
287 )delimiter"};
288 auto filePaths = CreateTempFile(files, texts);
289 int const expectedFileCount = 1;
290 ASSERT_EQ(filePaths.size(), expectedFileCount);
291 Initializer initializer = Initializer();
292 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
293 LSPAPI const *lspApi = GetImpl();
294 size_t const offset = 159; // after 'b' in 'let axx = b'
295 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
296 auto expectedEntries = std::vector<CompletionEntry> {
297 CompletionEntry("baa", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
298 CompletionEntry("bbb", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
299 CompletionEntry("bxx", ark::es2panda::lsp::CompletionEntryKind::FUNCTION, std::string(GLOBALS_OR_KEYWORDS)),
300 };
301 auto unexpectedEntries = std::vector<CompletionEntry> {
302 CompletionEntry("bar", ark::es2panda::lsp::CompletionEntryKind::PROPERTY, std::string(GLOBALS_OR_KEYWORDS)),
303 CompletionEntry("bcc", ark::es2panda::lsp::CompletionEntryKind::VARIABLE, std::string(GLOBALS_OR_KEYWORDS)),
304 };
305 initializer.DestroyContext(ctx);
306 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, unexpectedEntries);
307 }
308
TEST_F(LSPCompletionsTests,MemberCompletionsForClassTest1)309 TEST_F(LSPCompletionsTests, MemberCompletionsForClassTest1)
310 {
311 std::vector<std::string> files = {"getCompletionsAtPosition5.ets"};
312 std::vector<std::string> texts = {R"delimiter(
313 class MyClass1 {
314 public myProp: number = 0;
315 public prop: number = 1;
316 }
317 let obj1 = new MyClass1()
318 let prop = obj1.yp)delimiter"};
319 auto filePaths = CreateTempFile(files, texts);
320 int const expectedFileCount = 1;
321 ASSERT_EQ(filePaths.size(), expectedFileCount);
322 Initializer initializer = Initializer();
323 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
324 LSPAPI const *lspApi = GetImpl();
325 const size_t offset = 120;
326 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
327 auto entries = res.GetEntries();
328 ASSERT_TRUE(entries.size() == 1);
329
330 std::string propertyName1 = "myProp";
331 CompletionEntry entry1 = CompletionEntry(propertyName1, CompletionEntryKind::PROPERTY,
332 std::string(ark::es2panda::lsp::sort_text::SUGGESTED_CLASS_MEMBERS));
333 initializer.DestroyContext(ctx);
334 ASSERT_EQ(entry1, entries[0]);
335 }
336
TEST_F(LSPCompletionsTests,MemberCompletionsForClassTest2)337 TEST_F(LSPCompletionsTests, MemberCompletionsForClassTest2)
338 {
339 std::vector<std::string> files = {"getCompletionsAtPosition6.ets"};
340 std::vector<std::string> texts = {R"delimiter(
341 namespace space {
342 export class classInSpace {
343 public c: number = 2;
344 }
345 }
346 let numOfSpace: space.classi)delimiter"};
347 auto filePaths = CreateTempFile(files, texts);
348 int const expectedFileCount = 1;
349 ASSERT_EQ(filePaths.size(), expectedFileCount);
350 Initializer initializer = Initializer();
351 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
352 LSPAPI const *lspApi = GetImpl();
353 const size_t offset = 110;
354 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
355 auto entries = res.GetEntries();
356 std::string propertyName1 = "classInSpace";
357 ASSERT_TRUE(entries.size() == 1);
358 CompletionEntry entry1 =
359 CompletionEntry(propertyName1, CompletionEntryKind::CLASS,
360 std::string(ark::es2panda::lsp::sort_text::MEMBER_DECLARED_BY_SPREAD_ASSIGNMENT));
361 initializer.DestroyContext(ctx);
362 ASSERT_EQ(entry1, entries[0]);
363 }
364
TEST_F(LSPCompletionsTests,MemberCompletionsForClassTest4)365 TEST_F(LSPCompletionsTests, MemberCompletionsForClassTest4)
366 {
367 std::vector<std::string> files = {"getCompletionsAtPosition6.ets"};
368 std::vector<std::string> texts = {R"delimiter(
369 enum Color {
370 Red = "red",
371 Blue = "blue"
372 }
373 let myColor: Color = Color.R)delimiter"};
374 auto filePaths = CreateTempFile(files, texts);
375 int const expectedFileCount = 1;
376 ASSERT_EQ(filePaths.size(), expectedFileCount);
377 Initializer initializer = Initializer();
378 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
379 LSPAPI const *lspApi = GetImpl();
380 const size_t offset = 75;
381 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
382 auto entries = res.GetEntries();
383 ASSERT_TRUE(entries.size() == 1);
384
385 std::string propertyName1 = "Red";
386 CompletionEntry entry1 =
387 CompletionEntry(propertyName1, CompletionEntryKind::ENUM_MEMBER,
388 std::string(ark::es2panda::lsp::sort_text::MEMBER_DECLARED_BY_SPREAD_ASSIGNMENT));
389 initializer.DestroyContext(ctx);
390 ASSERT_EQ(entry1, entries[0]);
391 }
392 } // namespace