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 #include <cstddef>
16 #include <string>
17 #include <vector>
18 #include <lexer/lexer.h>
19 #include <lexer/token/letters.h>
20 #include <lexer/token/sourceLocation.h>
21 #include "lsp_api_test.h"
22 #include "lsp/include/internal_api.h"
23 #include "lsp/include/inlay_hints.h"
24 #include "public/es2panda_lib.h"
25
26 class LSPInlayHintsTests : public LSPAPITests {};
27
TEST_F(LSPInlayHintsTests,VisitCallOrNewExpressionTest)28 TEST_F(LSPInlayHintsTests, VisitCallOrNewExpressionTest)
29 {
30 std::vector<std::string> files = {"call_file_test.ets"};
31
32 std::vector<std::string> fileContent = {R"(
33 function foo(param1: number, param2: number) {
34 }
35
36 function bar() {
37 foo(10, 20); // Call expression
38 }
39 )"};
40
41 auto filePath = CreateTempFile(files, fileContent);
42
43 using ark::es2panda::lsp::UserPreferences;
44 UserPreferences preferences = UserPreferences::GetDefaultUserPreferences();
45
46 preferences.SetQuotePreference(UserPreferences::QuotePreference::DOUBLE);
47 preferences.SetIncludePackageJsonAutoImports(UserPreferences::IncludePackageJsonAutoImports::ON);
48 preferences.SetDisableSuggestions(true);
49 preferences.SetIncludeInlayParameterNameHints(UserPreferences::IncludeInlayParameterNameHints::ALL);
50
51 InlayHintList result = {};
52
53 ark::es2panda::lsp::Initializer initializer;
54 const auto ctx = initializer.CreateContext(filePath[0].c_str(), ES2PANDA_STATE_CHECKED);
55 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx);
56 ASSERT_NE(astContext, nullptr);
57 auto *parent = reinterpret_cast<ark::es2panda::ir::AstNode *>(astContext->parserProgram->Ast());
58 ASSERT_NE(parent, nullptr);
59
60 ark::es2panda::ir::AstNode *callExprNode = nullptr;
61 parent->FindChild([&callExprNode, parent, &result](ark::es2panda::ir::AstNode *childNode) {
62 if (childNode->IsCallExpression()) {
63 callExprNode = childNode;
64 ark::es2panda::lsp::GetCallExpTypeForHints(callExprNode, parent, &result);
65 }
66 return false;
67 });
68 const size_t num1 = 108;
69 const size_t num2 = 112;
70 const std::string p1 = "param1";
71 const std::string p2 = "param2";
72 ASSERT_NE(callExprNode, nullptr);
73 std::vector<InlayHint> expectedHints = {{p1, num1, InlayHintKind::PARAMETER, false, true},
74 {p2, num2, InlayHintKind::PARAMETER, false, true}};
75
76 ASSERT_EQ(result.hints.size(), expectedHints.size());
77 for (size_t i = 0; i < expectedHints.size(); i++) {
78 ASSERT_EQ(result.hints[i].text, expectedHints[i].text);
79 ASSERT_EQ(result.hints[i].number, expectedHints[i].number);
80 ASSERT_EQ(result.hints[i].kind, expectedHints[i].kind);
81 ASSERT_EQ(result.hints[i].whitespaceBefore, expectedHints[i].whitespaceBefore);
82 ASSERT_EQ(result.hints[i].whitespaceAfter, expectedHints[i].whitespaceAfter);
83 }
84 initializer.DestroyContext(ctx);
85 }
86
TEST_F(LSPInlayHintsTests,VisitEnumMemberTest)87 TEST_F(LSPInlayHintsTests, VisitEnumMemberTest)
88 {
89 const std::vector<std::string> files = {"enum_file.ets"};
90
91 const std::vector<std::string> fileContent = {R"(
92 enum Color {
93 RED = 1,
94 GREEN = 2,
95 BLUE = 3
96 };
97 enum text {
98 name = "test",
99 className = "class",
100 color = "blue"
101 };
102
103 let myColor = Color.GREEN;
104 let myColor1 = Color.BLUE;
105 let name1 = text.name;
106 let classText = text.className;
107
108 )"};
109
110 const auto filePath = CreateTempFile(files, fileContent);
111
112 InlayHintList result = {};
113 const size_t size = 4;
114 const std::string enum2 = "2";
115 const std::string enum3 = "3";
116 const std::string enumtest = "test";
117 const std::string enumclass = "class";
118 const size_t i0 = 0;
119 const size_t i1 = 1;
120 const size_t i2 = 2;
121 const size_t i3 = 3;
122
123 ark::es2panda::lsp::Initializer initializer;
124 const auto ctx = initializer.CreateContext(filePath[i0].c_str(), ES2PANDA_STATE_CHECKED);
125 const auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx);
126 ASSERT_NE(astContext, nullptr);
127 const auto *parent = reinterpret_cast<ark::es2panda::ir::AstNode *>(astContext->parserProgram->Ast());
128 ASSERT_NE(parent, nullptr);
129
130 parent->FindChild([parent, &result](ark::es2panda::ir::AstNode *childNode) {
131 if (childNode->IsAssignmentExpression()) {
132 ark::es2panda::lsp::GetEnumTypeForHints(childNode, parent, &result);
133 }
134 return false;
135 });
136 initializer.DestroyContext(ctx);
137 ASSERT_EQ(result.hints.size(), size);
138 ASSERT_EQ(result.hints[i0].text, enum2);
139 ASSERT_EQ(result.hints[i1].text, enum3);
140 ASSERT_EQ(result.hints[i2].text, enumtest);
141 ASSERT_EQ(result.hints[i3].text, enumclass);
142 }
143
TEST_F(LSPInlayHintsTests,VisitFunctionLikeForParameterTypeTest)144 TEST_F(LSPInlayHintsTests, VisitFunctionLikeForParameterTypeTest)
145 {
146 const std::vector<std::string> files = {"function_test.ets"};
147 const std::vector<std::string> fileContent = {R"(
148 function add(a: number, b: number): number {
149 return a + b;
150 }
151
152 function greet(name: string, age: number): string {
153 return `Hello, ${name}! You are ${age} years old.`;
154 }
155
156 let sum = add(5, 10);
157 let message = greet("Alice", 30);
158 )"};
159 const std::string voidString = "void";
160 const std::string numberString = "number";
161 const std::string stdString = "string";
162 const size_t index1 = 32;
163 const size_t index2 = 43;
164 const size_t index3 = 127;
165 const size_t index4 = 140;
166 const size_t i0 = 0;
167 const size_t i1 = 1;
168 const size_t i2 = 2;
169 const size_t i3 = 3;
170 const auto filePaths = CreateTempFile(files, fileContent);
171 ASSERT_EQ(filePaths.size(), i1);
172 InlayHintList result = {};
173 ark::es2panda::lsp::Initializer initializer;
174
175 const auto ctx = initializer.CreateContext(filePaths[i0].c_str(), ES2PANDA_STATE_CHECKED);
176 ASSERT_NE(ctx, nullptr);
177
178 const auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx);
179 ASSERT_NE(astContext, nullptr);
180
181 const auto *parent = reinterpret_cast<ark::es2panda::ir::AstNode *>(astContext->parserProgram->Ast());
182 ASSERT_NE(parent, nullptr);
183
184 parent->FindChild([&result](ark::es2panda::ir::AstNode *childNode) {
185 if (childNode->IsMethodDefinition()) {
186 ark::es2panda::lsp::GetFunctionParameterTypeForHints(childNode, &result);
187 }
188 return false;
189 });
190
191 ASSERT_EQ(result.hints[i0].text, numberString);
192 ASSERT_EQ(result.hints[i0].number, index1);
193 ASSERT_EQ(result.hints[i1].text, numberString);
194 ASSERT_EQ(result.hints[i1].number, index2);
195 ASSERT_EQ(result.hints[i2].text, stdString);
196 ASSERT_EQ(result.hints[i2].number, index3);
197 ASSERT_EQ(result.hints[i3].text, numberString);
198 ASSERT_EQ(result.hints[i3].number, index4);
199
200 initializer.DestroyContext(ctx);
201 }
202
TEST_F(LSPInlayHintsTests,VisitFunctionDeclarationLikeForReturnTypeTest1)203 TEST_F(LSPInlayHintsTests, VisitFunctionDeclarationLikeForReturnTypeTest1)
204 {
205 const std::vector<std::string> files = {"function_return_type_test1.ets"};
206 const std::vector<std::string> fileContent = {R"(
207 function add(a: number, b: number): number {
208 return a + b;
209 }
210 const multiply = (a: number, b: number): number => {
211 return a * b;
212 };
213 )"};
214
215 const std::string doubleString = "double";
216 const size_t addIndex = 89;
217 const size_t multiplyIndex = 186;
218 const size_t i0 = 0;
219 const size_t i1 = 1;
220 const size_t i2 = 2;
221 const size_t i3 = 3;
222 const auto filePaths = CreateTempFile(files, fileContent);
223 ASSERT_EQ(filePaths.size(), i1);
224 InlayHintList result;
225 ark::es2panda::lsp::Initializer initializer;
226 const auto ctx = initializer.CreateContext(filePaths[i0].c_str(), ES2PANDA_STATE_CHECKED);
227 ASSERT_NE(ctx, nullptr);
228 const auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx);
229 ASSERT_NE(astContext, nullptr);
230 const auto *parent = reinterpret_cast<ark::es2panda::ir::AstNode *>(astContext->parserProgram->Ast());
231 ASSERT_NE(parent, nullptr);
232 parent->FindChild([&result](ark::es2panda::ir::AstNode *childNode) {
233 if (ark::es2panda::lsp::IsSignatureSupportingReturnAnnotation(childNode)) {
234 ark::es2panda::lsp::GetFunctionReturnTypeForHints(childNode, &result);
235 }
236 return false;
237 });
238 ASSERT_EQ(result.hints[i2].text, doubleString);
239 ASSERT_EQ(result.hints[i2].number, addIndex);
240 ASSERT_EQ(result.hints[i3].text, doubleString);
241 ASSERT_EQ(result.hints[i3].number, multiplyIndex);
242 initializer.DestroyContext(ctx);
243 }
244
TEST_F(LSPInlayHintsTests,VisitFunctionDeclarationLikeForReturnTypeTest2)245 TEST_F(LSPInlayHintsTests, VisitFunctionDeclarationLikeForReturnTypeTest2)
246 {
247 const std::vector<std::string> files = {"function_return_type_test.ets"};
248 const std::vector<std::string> fileContent = {R"(
249 function greet(name: string): string {
250 return `Hello, ${name}!`;
251 }
252 const sayHello = (): void => {
253 console.log("Hello!");
254 };
255
256 )"};
257 const std::string voidString = "void";
258 const std::string stdString = "String";
259
260 const size_t greetIndex = 95;
261 const size_t sayHelloIndex = 179;
262 const size_t i0 = 0;
263 const size_t i1 = 1;
264 const size_t i2 = 2;
265 const size_t i3 = 3;
266
267 const auto filePaths = CreateTempFile(files, fileContent);
268 ASSERT_EQ(filePaths.size(), i1);
269 InlayHintList result;
270 ark::es2panda::lsp::Initializer initializer;
271 const auto ctx = initializer.CreateContext(filePaths[i0].c_str(), ES2PANDA_STATE_CHECKED);
272 ASSERT_NE(ctx, nullptr);
273 const auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx);
274 ASSERT_NE(astContext, nullptr);
275 const auto *parent = reinterpret_cast<ark::es2panda::ir::AstNode *>(astContext->parserProgram->Ast());
276 ASSERT_NE(parent, nullptr);
277 parent->FindChild([&result](ark::es2panda::ir::AstNode *childNode) {
278 if (ark::es2panda::lsp::IsSignatureSupportingReturnAnnotation(childNode)) {
279 ark::es2panda::lsp::GetFunctionReturnTypeForHints(childNode, &result);
280 }
281 return false;
282 });
283 ASSERT_EQ(result.hints[i2].text, stdString);
284 ASSERT_EQ(result.hints[i2].number, greetIndex);
285 ASSERT_EQ(result.hints[i3].text, voidString);
286 ASSERT_EQ(result.hints[i3].number, sayHelloIndex);
287 initializer.DestroyContext(ctx);
288 }
289
TEST_F(LSPInlayHintsTests,VisitVariableLikeDeclarationTest)290 TEST_F(LSPInlayHintsTests, VisitVariableLikeDeclarationTest)
291 {
292 const std::vector<std::string> files = {"variable_test.ets"};
293 const std::vector<std::string> fileContent = {R"(
294 let x: number = 10;
295 let y = "Hello";
296 let z = true;
297 let obj = { a: 1, b: 2 };
298 let arr = [1, 2, 3];
299 )"};
300 const std::string numberString = "number";
301 const std::string stdString = "string";
302 const std::string boolString = "boolean";
303 const size_t index1 = 27;
304 const size_t index2 = 52;
305 const size_t index3 = 74;
306 const size_t i0 = 0;
307 const size_t i1 = 1;
308 const size_t i2 = 2;
309 const auto filePaths = CreateTempFile(files, fileContent);
310 ASSERT_EQ(filePaths.size(), i1);
311 InlayHintList result;
312 ark::es2panda::lsp::Initializer initializer;
313 const auto ctx = initializer.CreateContext(filePaths[i0].c_str(), ES2PANDA_STATE_CHECKED);
314 ASSERT_NE(ctx, nullptr);
315 const auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx);
316 ASSERT_NE(astContext, nullptr);
317 const auto *parent = reinterpret_cast<ark::es2panda::ir::AstNode *>(astContext->parserProgram->Ast());
318 ASSERT_NE(parent, nullptr);
319 ark::es2panda::lsp::UserPreferences preferences;
320 preferences.SetIncludeInlayVariableTypeHintsWhenTypeMatchesName(false);
321 parent->FindChild([&result](ark::es2panda::ir::AstNode *childNode) {
322 if (childNode->IsAssignmentExpression()) {
323 ark::es2panda::lsp::GetVariableDeclarationTypeForHints(childNode, &result);
324 }
325 if (result.hints.empty()) {
326 }
327 return false;
328 });
329 ASSERT_EQ(result.hints[i0].text, numberString);
330 ASSERT_EQ(result.hints[i0].number, index1);
331 ASSERT_EQ(result.hints[i1].text, stdString);
332 ASSERT_EQ(result.hints[i1].number, index2);
333 ASSERT_EQ(result.hints[i2].text, boolString);
334 ASSERT_EQ(result.hints[i2].number, index3);
335 initializer.DestroyContext(ctx);
336 }
337