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::CLASS_MEMBER_SNIPPETS;
26 using ark::es2panda::lsp::sort_text::GLOBALS_OR_KEYWORDS;
27 using ark::es2panda::lsp::sort_text::MEMBER_DECLARED_BY_SPREAD_ASSIGNMENT;
28 using ark::es2panda::lsp::sort_text::SUGGESTED_CLASS_MEMBERS;
29
AssertCompletionsContainAndNotContainEntries(const std::vector<CompletionEntry> & entries,const std::vector<CompletionEntry> & expectedEntries,const std::vector<CompletionEntry> & unexpectedEntries)30 static void AssertCompletionsContainAndNotContainEntries(const std::vector<CompletionEntry> &entries,
31 const std::vector<CompletionEntry> &expectedEntries,
32 const std::vector<CompletionEntry> &unexpectedEntries)
33 {
34 auto emptyCheck = expectedEntries.empty() && !entries.empty();
35 ASSERT_FALSE(emptyCheck) << "Expected empty but the result is not. Actual account: " << entries.size();
36
37 for (const auto &expectedEntry : expectedEntries) {
38 bool found = false;
39 for (const auto &entry : entries) {
40 if (entry.GetName() == expectedEntry.GetName() &&
41 entry.GetCompletionKind() == expectedEntry.GetCompletionKind()) {
42 found = true;
43 break;
44 }
45 }
46 ASSERT_TRUE(found) << "Expected completion '" << expectedEntry.GetName() << "' not found";
47 }
48
49 for (const auto &unexpectedEntry : unexpectedEntries) {
50 bool found = false;
51 for (const auto &entry : entries) {
52 if (entry.GetName() == unexpectedEntry.GetName() &&
53 entry.GetCompletionKind() == unexpectedEntry.GetCompletionKind()) {
54 found = true;
55 break;
56 }
57 }
58 ASSERT_FALSE(found) << "Unexpected completion '" << unexpectedEntry.GetName() << "' found";
59 }
60 }
61
62 namespace {
63
TEST_F(LSPCompletionsTests,getCompletionsAtPosition19)64 TEST_F(LSPCompletionsTests, getCompletionsAtPosition19)
65 {
66 std::vector<std::string> files = {"getCompletionsAtPosition20.ets"};
67 std::vector<std::string> texts = {R"delimiter(
68 deep
69 )delimiter"};
70 auto filePaths = CreateTempFile(files, texts);
71
72 int const expectedFileCount = 1;
73 ASSERT_EQ(filePaths.size(), expectedFileCount);
74
75 LSPAPI const *lspApi = GetImpl();
76 size_t const offset = 5;
77 Initializer initializer = Initializer();
78 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
79 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
80 auto expectedEntries = std::vector<CompletionEntry> {
81 CompletionEntry("deepcopy", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(GLOBALS_OR_KEYWORDS))};
82 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
83 initializer.DestroyContext(ctx);
84 }
85
TEST_F(LSPCompletionsTests,getCompletionsAtPosition18)86 TEST_F(LSPCompletionsTests, getCompletionsAtPosition18)
87 {
88 std::vector<std::string> files = {"getCompletionsAtPosition19.ets"};
89 std::vector<std::string> texts = {R"delimiter(
90 Readonl
91 )delimiter"};
92 auto filePaths = CreateTempFile(files, texts);
93
94 int const expectedFileCount = 1;
95 ASSERT_EQ(filePaths.size(), expectedFileCount);
96
97 LSPAPI const *lspApi = GetImpl();
98 size_t const offset = 8;
99 Initializer initializer = Initializer();
100 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
101 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
102 auto expectedEntries = std::vector<CompletionEntry> {CompletionEntry(
103 "ReadonlyArray", ark::es2panda::lsp::CompletionEntryKind::INTERFACE, std::string(GLOBALS_OR_KEYWORDS))};
104 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
105 initializer.DestroyContext(ctx);
106 }
107
TEST_F(LSPCompletionsTests,getCompletionsAtPosition17)108 TEST_F(LSPCompletionsTests, getCompletionsAtPosition17)
109 {
110 std::vector<std::string> files = {"getCompletionsAtPosition18.ets"};
111 std::vector<std::string> texts = {R"delimiter(
112 con
113 )delimiter"};
114 auto filePaths = CreateTempFile(files, texts);
115
116 int const expectedFileCount = 1;
117 ASSERT_EQ(filePaths.size(), expectedFileCount);
118
119 LSPAPI const *lspApi = GetImpl();
120 size_t const offset = 4;
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 CompletionEntry("console", ark::es2panda::lsp::CompletionEntryKind::PROPERTY, std::string(GLOBALS_OR_KEYWORDS)),
126 CompletionEntry("Console", ark::es2panda::lsp::CompletionEntryKind::CLASS, std::string(GLOBALS_OR_KEYWORDS)),
127 CompletionEntry("ConcurrentHashMap", ark::es2panda::lsp::CompletionEntryKind::CLASS,
128 std::string(GLOBALS_OR_KEYWORDS))};
129 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
130 initializer.DestroyContext(ctx);
131 }
132
TEST_F(LSPCompletionsTests,getCompletionsAtPosition16)133 TEST_F(LSPCompletionsTests, getCompletionsAtPosition16)
134 {
135 std::vector<std::string> files = {"getCompletionsAtPosition17.ets"};
136 std::vector<std::string> texts = {R"delimiter(
137 struct MyClass {
138 property: string = '1'
139 get() {
140 return this._WILDCARD
141 }
142 }
143 )delimiter"};
144 auto filePaths = CreateTempFile(files, texts);
145
146 int const expectedFileCount = 1;
147 ASSERT_EQ(filePaths.size(), expectedFileCount);
148
149 LSPAPI const *lspApi = GetImpl();
150 size_t const offset = 78; // after 'j._WILDCARD'
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("property", ark::es2panda::lsp::CompletionEntryKind::PROPERTY,
156 std::string(SUGGESTED_CLASS_MEMBERS)),
157 CompletionEntry("get", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS))};
158 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
159 initializer.DestroyContext(ctx);
160 }
161
TEST_F(LSPCompletionsTests,getCompletionsAtPosition15)162 TEST_F(LSPCompletionsTests, getCompletionsAtPosition15)
163 {
164 std::vector<std::string> files = {"getCompletionsAtPosition16.ets"};
165 std::vector<std::string> texts = {R"delimiter(
166 export interface Method {
167 get(value: number): this;
168 }
169 export interface CommonMethod {
170 width(value: number): this;
171 height(value: number): this;
172 }
173 export interface TextAttribute extends CommonMethod {
174 font(value: number): this;
175 fontColor(value: number): this;
176 }
177 export declare function Text(
178 content?: string,
179 value?: string
180 ): TextAttribute
181 Text("Hello").font()._WILDCARD
182 )delimiter"};
183 auto filePaths = CreateTempFile(files, texts);
184
185 int const expectedFileCount = 1;
186 ASSERT_EQ(filePaths.size(), expectedFileCount);
187
188 LSPAPI const *lspApi = GetImpl();
189 size_t const offset = 385; // after 'j._WILDCARD'
190 Initializer initializer = Initializer();
191 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
192 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
193 auto expectedEntries = std::vector<CompletionEntry> {
194 CompletionEntry("font", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS)),
195 CompletionEntry("fontColor", ark::es2panda::lsp::CompletionEntryKind::METHOD,
196 std::string(CLASS_MEMBER_SNIPPETS)),
197 CompletionEntry("width", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS)),
198 CompletionEntry("height", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS))};
199 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
200 initializer.DestroyContext(ctx);
201 }
202
TEST_F(LSPCompletionsTests,getCompletionsAtPosition14)203 TEST_F(LSPCompletionsTests, getCompletionsAtPosition14)
204 {
205 std::vector<std::string> files = {"getCompletionsAtPosition15.ets"};
206 std::vector<std::string> texts = {R"delimiter(
207 export class MyClass0 {
208 public property0: string = '0'
209 public get0() {}
210 }
211 export class MyClass extends MyClass0 {
212 public property: string = '1'
213 public get() {}
214 }
215 export class MySonClass extends MyClass {
216 public property2: string = '2'
217 }
218 let c = new MySonClass()
219 let p = c._WILDCARD
220 )delimiter"};
221 auto filePaths = CreateTempFile(files, texts);
222
223 int const expectedFileCount = 1;
224 ASSERT_EQ(filePaths.size(), expectedFileCount);
225
226 LSPAPI const *lspApi = GetImpl();
227 size_t const offset = 292; // after 'j._WILDCARD'
228 Initializer initializer = Initializer();
229 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
230 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
231 auto expectedEntries = std::vector<CompletionEntry> {
232 CompletionEntry("property", ark::es2panda::lsp::CompletionEntryKind::PROPERTY,
233 std::string(SUGGESTED_CLASS_MEMBERS)),
234 CompletionEntry("property", ark::es2panda::lsp::CompletionEntryKind::PROPERTY,
235 std::string(SUGGESTED_CLASS_MEMBERS)),
236 CompletionEntry("property2", ark::es2panda::lsp::CompletionEntryKind::PROPERTY,
237 std::string(SUGGESTED_CLASS_MEMBERS)),
238 CompletionEntry("get", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS)),
239 CompletionEntry("get0", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS))};
240 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
241 initializer.DestroyContext(ctx);
242 }
243
TEST_F(LSPCompletionsTests,getCompletionsAtPosition13)244 TEST_F(LSPCompletionsTests, getCompletionsAtPosition13)
245 {
246 std::vector<std::string> files = {"getCompletionsAtPosition14.ets"};
247 std::vector<std::string> texts = {R"delimiter(
248 export interface Method {
249 get(value: number): this;
250 }
251 export interface CommonMethod {
252 width(value: number): this;
253 height(value: number): this;
254 }
255 export interface TextAttribute extends CommonMethod {
256 font(value: number): this;
257 fontColor(value: number): this;
258 }
259 export declare function Text(
260 content?: string,
261 value?: string
262 ): TextAttribute
263 Text("Hello")._WILDCARD
264 )delimiter"};
265 auto filePaths = CreateTempFile(files, texts);
266
267 int const expectedFileCount = 1;
268 ASSERT_EQ(filePaths.size(), expectedFileCount);
269
270 LSPAPI const *lspApi = GetImpl();
271 size_t const offset = 378; // after 'j._WILDCARD'
272 Initializer initializer = Initializer();
273 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
274 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
275 auto expectedEntries = std::vector<CompletionEntry> {
276 CompletionEntry("font", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS)),
277 CompletionEntry("fontColor", ark::es2panda::lsp::CompletionEntryKind::METHOD,
278 std::string(CLASS_MEMBER_SNIPPETS)),
279 CompletionEntry("width", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS)),
280 CompletionEntry("height", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS))};
281 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
282 initializer.DestroyContext(ctx);
283 }
284
TEST_F(LSPCompletionsTests,getCompletionsAtPosition12)285 TEST_F(LSPCompletionsTests, getCompletionsAtPosition12)
286 {
287 std::vector<std::string> files = {"getCompletionsAtPosition13.ets"};
288 std::vector<std::string> texts = {R"delimiter(
289 class JSON {
290 public static stringify(d: byte): String {
291 return StringBuilder.toString(d)
292 }
293 }
294 let j = new JSON()
295 let res = j._WILDCARD
296 )delimiter"};
297 auto filePaths = CreateTempFile(files, texts);
298 int const expectedFileCount = 1;
299 ASSERT_EQ(filePaths.size(), expectedFileCount);
300
301 LSPAPI const *lspApi = GetImpl();
302 size_t const offset = 142; // after 'j._WILDCARD'
303 Initializer initializer = Initializer();
304 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
305 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
306 auto expectedEntries = std::vector<CompletionEntry> {CompletionEntry(
307 "stringify", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS))};
308 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
309 initializer.DestroyContext(ctx);
310 }
311
TEST_F(LSPCompletionsTests,getCompletionsAtPosition11)312 TEST_F(LSPCompletionsTests, getCompletionsAtPosition11)
313 {
314 std::vector<std::string> files = {"getCompletionsAtPosition12.ets"};
315 std::vector<std::string> texts = {R"delimiter(
316 interface Inner { key : string; }
317 let i: Inner
318 i.k
319 let a = 1;)delimiter"};
320 auto filePaths = CreateTempFile(files, texts);
321 int const expectedFileCount = 1;
322 ASSERT_EQ(filePaths.size(), expectedFileCount);
323
324 LSPAPI const *lspApi = GetImpl();
325 size_t const offset = 51; // after 'i.k'
326 Initializer initializer = Initializer();
327 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
328 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
329 auto expectedEntries = std::vector<CompletionEntry> {
330 CompletionEntry("key", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS))};
331 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
332 initializer.DestroyContext(ctx);
333 }
334
TEST_F(LSPCompletionsTests,getCompletionsAtPosition10)335 TEST_F(LSPCompletionsTests, getCompletionsAtPosition10)
336 {
337 std::vector<std::string> files = {"getCompletionsAtPosition11.ets"};
338 std::vector<std::string> texts = {R"delimiter(
339 interface Inner { key : string; }
340 let i: Inner
341 i._WILDCARD
342 let a = 1;)delimiter"};
343 auto filePaths = CreateTempFile(files, texts);
344 int const expectedFileCount = 1;
345 ASSERT_EQ(filePaths.size(), expectedFileCount);
346
347 LSPAPI const *lspApi = GetImpl();
348 size_t const offset = 59; // after 'i._WILDCARD'
349 Initializer initializer = Initializer();
350 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
351 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
352 auto expectedEntries = std::vector<CompletionEntry> {
353 CompletionEntry("key", ark::es2panda::lsp::CompletionEntryKind::METHOD, std::string(CLASS_MEMBER_SNIPPETS))};
354 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
355 initializer.DestroyContext(ctx);
356 }
357
TEST_F(LSPCompletionsTests,getCompletionsAtPosition7)358 TEST_F(LSPCompletionsTests, getCompletionsAtPosition7)
359 {
360 std::vector<std::string> files = {"getCompletionsAtPosition10.ets"};
361 std::vector<std::string> texts = {R"delimiter(
362 class MyClass {
363 public myProp: number = 0;
364 public prop: number = 0;
365 }
366 let obj = new MyClass();
367 let p = obj._WILDCARD
368 let a = 1;)delimiter"};
369 auto filePaths = CreateTempFile(files, texts);
370 int const expectedFileCount = 1;
371 ASSERT_EQ(filePaths.size(), expectedFileCount);
372
373 LSPAPI const *lspApi = GetImpl();
374 size_t const offset = 121; // after 'obj._WILDCARD' in 'let p = obj._WILDCARD'
375 Initializer initializer = Initializer();
376 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
377 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
378 auto expectedEntries =
379 std::vector<CompletionEntry> {CompletionEntry("myProp", ark::es2panda::lsp::CompletionEntryKind::PROPERTY,
380 std::string(SUGGESTED_CLASS_MEMBERS)),
381 CompletionEntry("prop", ark::es2panda::lsp::CompletionEntryKind::PROPERTY,
382 std::string(SUGGESTED_CLASS_MEMBERS))};
383 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
384 initializer.DestroyContext(ctx);
385 }
386
TEST_F(LSPCompletionsTests,getCompletionsAtPosition8)387 TEST_F(LSPCompletionsTests, getCompletionsAtPosition8)
388 {
389 std::vector<std::string> files = {"getCompletionsAtPosition11.ets"};
390 std::vector<std::string> texts = {R"delimiter(
391 enum Color {
392 Red = "red",
393 Blue = "blue"
394 }
395 let myColor: Color = Color._WILDCARD)delimiter"};
396 auto filePaths = CreateTempFile(files, texts);
397 int const expectedFileCount = 1;
398 ASSERT_EQ(filePaths.size(), expectedFileCount);
399
400 LSPAPI const *lspApi = GetImpl();
401 size_t const offset = 83; // after 'Color._WILDCARD'
402 Initializer initializer = Initializer();
403 auto ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
404 auto res = lspApi->getCompletionsAtPosition(ctx, offset);
405 auto expectedEntries = std::vector<CompletionEntry> {
406 CompletionEntry("Red", CompletionEntryKind::ENUM_MEMBER, std::string(MEMBER_DECLARED_BY_SPREAD_ASSIGNMENT)),
407 CompletionEntry("Blue", CompletionEntryKind::ENUM_MEMBER, std::string(MEMBER_DECLARED_BY_SPREAD_ASSIGNMENT))};
408 AssertCompletionsContainAndNotContainEntries(res.GetEntries(), expectedEntries, {});
409 initializer.DestroyContext(ctx);
410 }
411
412 } // namespace
413