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/include/class_hierarchy_info.h"
17 #include "lsp_api_test.h"
18 #include "lsp/include/internal_api.h"
19 #include <gtest/gtest.h>
20
21 using ark::es2panda::lsp::Initializer;
22
23 namespace {
24
25 class LspGetClassHierarchyInfoTests : public LSPAPITests {};
26
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_1)27 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_1)
28 {
29 LSPAPI const *lspApi = GetImpl();
30 ASSERT_TRUE(lspApi != nullptr);
31 const std::string text = R"(class Parent {
32 private privateMethod(): void {
33 console.log("Parent method");
34 }
35 public publicMethod(): void {
36 console.log("Parent method");
37 }
38 protected action(fileName: string, position: number): number {
39 return position;
40 }
41 static staticMethod(): void {
42 console.log("Parent static method");
43 }
44 }
45 class Child extends Parent {
46 public display(): void {
47 console.log("need display");
48 }
49 })";
50
51 auto pos = text.find("Child");
52 ASSERT_NE(pos, std::string::npos);
53 Initializer initializer = Initializer();
54 auto context = initializer.CreateContext("class_hierarchy_info_1.ets", ES2PANDA_STATE_CHECKED, text.c_str());
55 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
56
57 ASSERT_EQ(classHierarchy.size(), 1);
58 ASSERT_EQ(classHierarchy[0].GetClassName(), "Parent");
59 auto methods = classHierarchy[0].GetMethodItemList();
60 auto it = methods.find("publicMethod(): void");
61 ASSERT_TRUE(it != methods.end());
62 ASSERT_TRUE(it->second != nullptr);
63 ASSERT_EQ(it->second->GetSetterStyle(), ark::es2panda::lsp::SetterStyle::NONE);
64 ASSERT_EQ(it->second->GetAccessModifierStyle(), ark::es2panda::lsp::AccessModifierStyle::PUBLIC);
65 it = methods.find("action(fileName: string, position: number): number");
66 ASSERT_TRUE(it != methods.end());
67 ASSERT_TRUE(it->second != nullptr);
68 ASSERT_EQ(it->second->GetSetterStyle(), ark::es2panda::lsp::SetterStyle::NONE);
69 ASSERT_EQ(it->second->GetAccessModifierStyle(), ark::es2panda::lsp::AccessModifierStyle::PROTECTED);
70 initializer.DestroyContext(context);
71 }
72
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_2)73 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_2)
74 {
75 LSPAPI const *lspApi = GetImpl();
76 ASSERT_TRUE(lspApi != nullptr);
77 const std::string text = R"(class Animal {
78 private body_: string = '';
79
80 protected action(): void {
81 console.log("need Animal action");
82 }
83 protected sleep(): void {
84 console.log("need Animal sleep");
85 }
86 }
87
88 class Bird extends Animal {
89 action(): void {
90 console.log("need action");
91 }
92
93 Drink(): void {
94 console.log("need Drink");
95 }
96 }
97
98 class Magpie extends Bird {
99 public action(): void {}
100 Drink(): void {
101 console.log("need Drink");
102 }
103 })";
104
105 auto pos = text.find("Magpie");
106 ASSERT_NE(pos, std::string::npos);
107 Initializer initializer = Initializer();
108 auto context = initializer.CreateContext("class_hierarchy_info_2.ets", ES2PANDA_STATE_CHECKED, text.c_str());
109 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
110 ASSERT_EQ(classHierarchy.size(), 1);
111 ASSERT_EQ(classHierarchy[0].GetClassName(), "Animal");
112
113 auto methods = classHierarchy[0].GetMethodItemList();
114 auto it = methods.find("sleep(): void");
115 ASSERT_TRUE(it != methods.end());
116 ASSERT_TRUE(it->second != nullptr);
117 ASSERT_EQ(it->second->GetSetterStyle(), ark::es2panda::lsp::SetterStyle::NONE);
118 ASSERT_EQ(it->second->GetAccessModifierStyle(), ark::es2panda::lsp::AccessModifierStyle::PROTECTED);
119 initializer.DestroyContext(context);
120 }
121
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_3)122 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_3)
123 {
124 LSPAPI const *lspApi = GetImpl();
125 ASSERT_TRUE(lspApi != nullptr);
126 const std::string text = R"(class Animal {
127 private body_: string = '';
128
129 protected action(): void {
130 console.log("need action");
131 }
132 protected sleep(): void {
133 console.log("need sleep");
134 }
135 })";
136
137 auto pos = text.find("Animal");
138 ASSERT_NE(pos, std::string::npos);
139 Initializer initializer = Initializer();
140 auto context = initializer.CreateContext("class_hierarchy_info_3.ets", ES2PANDA_STATE_CHECKED, text.c_str());
141 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
142 ASSERT_TRUE(classHierarchy.empty());
143 initializer.DestroyContext(context);
144 }
145
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_4)146 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_4)
147 {
148 LSPAPI const *lspApi = GetImpl();
149 ASSERT_TRUE(lspApi != nullptr);
150 const std::string text = R"(class ii {
151 private body_: string = '';
152
153 action(): void {
154 console.log("need sleep");
155 }
156
157 set Body(value: string) {
158 this.body_ = value;
159 }
160 get Body(): string {
161 return this.body_;
162 }
163 }
164
165 class jj extends ii {
166 private age_: number = 18;
167 public action(): void {
168 console.log("need sleep and fly");
169 }
170 })";
171
172 auto pos = text.find("jj");
173 ASSERT_NE(pos, std::string::npos);
174 Initializer initializer = Initializer();
175 auto context = initializer.CreateContext("class_hierarchy_info_4.ets", ES2PANDA_STATE_CHECKED, text.c_str());
176 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
177 ASSERT_EQ(classHierarchy.size(), 1);
178 ASSERT_EQ(classHierarchy[0].GetClassName(), "ii");
179
180 auto methods = classHierarchy[0].GetMethodItemList();
181 auto it = methods.find("Body(): string");
182 ASSERT_TRUE(it != methods.end());
183 ASSERT_TRUE(it->second != nullptr);
184 ASSERT_EQ(it->second->GetSetterStyle(), ark::es2panda::lsp::SetterStyle::GETTER);
185 ASSERT_EQ(it->second->GetAccessModifierStyle(), ark::es2panda::lsp::AccessModifierStyle::PUBLIC);
186 it = methods.find("Body(value: string)");
187 ASSERT_TRUE(it != methods.end());
188 ASSERT_TRUE(it->second != nullptr);
189 ASSERT_EQ(it->second->GetSetterStyle(), ark::es2panda::lsp::SetterStyle::SETTER);
190 ASSERT_EQ(it->second->GetAccessModifierStyle(), ark::es2panda::lsp::AccessModifierStyle::PUBLIC);
191 initializer.DestroyContext(context);
192 }
193
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_5)194 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_5)
195 {
196 LSPAPI const *lspApi = GetImpl();
197 ASSERT_TRUE(lspApi != nullptr);
198 const std::string text = R"(class C<T, M> {
199 func1(): void {}
200 func2(): string {
201 return "1";
202 }
203 func3(): number {
204 return 1;
205 }
206 func4(): boolean {
207 return false;
208 }
209 func5(): Array<number> {
210 return [1, 2];
211 }
212 }
213
214 class B<T, M> extends C<T, M> {
215 method1(): void {}
216 method2(parameter1: string, callBack: () => void): void {}
217 }
218
219 class A extends B<string, number> {/*1*/};)";
220
221 auto pos = text.find("/*1*/");
222 ASSERT_NE(pos, std::string::npos);
223 Initializer initializer = Initializer();
224 auto context = initializer.CreateContext("class_hierarchy_info_5.ets", ES2PANDA_STATE_CHECKED, text.c_str());
225 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
226 size_t expectInfoListSize = 2;
227 ASSERT_EQ(classHierarchy.size(), expectInfoListSize);
228 ASSERT_EQ(classHierarchy[0].GetClassName(), "B");
229 ASSERT_EQ(classHierarchy[1].GetClassName(), "C");
230 auto classBItems = classHierarchy[0].GetMethodItemList();
231 ASSERT_TRUE(classBItems.find("method1(): void") != classBItems.end());
232 ASSERT_TRUE(classBItems.find("method2(parameter1: string, callBack: (() => void)): void") != classBItems.end());
233 auto classCItems = classHierarchy[1].GetMethodItemList();
234 ASSERT_TRUE(classCItems.find("func1(): void") != classCItems.end());
235 ASSERT_TRUE(classCItems.find("func2(): string") != classCItems.end());
236 ASSERT_TRUE(classCItems.find("func3(): number") != classCItems.end());
237 ASSERT_TRUE(classCItems.find("func4(): boolean") != classCItems.end());
238 ASSERT_TRUE(classCItems.find("func5(): Array<number>") != classCItems.end());
239 initializer.DestroyContext(context);
240 }
241
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_6)242 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_6)
243 {
244 LSPAPI const *lspApi = GetImpl();
245 ASSERT_TRUE(lspApi != nullptr);
246 const std::string text = R"(type parameter = number;
247
248 class B {
249 public method1(parameter1: number): parameter {
250 return 1;
251 }
252 method2(parameter1: number): number {
253 return parameter1 as number;
254 }
255 async method3(parameter1: string): Promise<string> {
256 return '1';
257 }
258 }
259
260 class A extends B {/*1*/};)";
261
262 auto pos = text.find("/*1*/");
263 ASSERT_NE(pos, std::string::npos);
264 Initializer initializer = Initializer();
265 auto context = initializer.CreateContext("class_hierarchy_info_6.ets", ES2PANDA_STATE_CHECKED, text.c_str());
266 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
267 ASSERT_FALSE(classHierarchy.empty());
268 ASSERT_EQ(classHierarchy[0].GetClassName(), "B");
269 auto classBItems = classHierarchy[0].GetMethodItemList();
270 ASSERT_TRUE(classBItems.find("method1(parameter1: number): parameter") != classBItems.end());
271 ASSERT_TRUE(classBItems.find("method2(parameter1: number): number") != classBItems.end());
272 ASSERT_TRUE(classBItems.find("method3(parameter1: string): Promise<string>") != classBItems.end());
273 initializer.DestroyContext(context);
274 }
275
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_7)276 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_7)
277 {
278 LSPAPI const *lspApi = GetImpl();
279 ASSERT_TRUE(lspApi != nullptr);
280 const std::string text = R"(class C <T, M> {
281 func1(): void {}
282
283 func2(): boolean {
284 return false;
285 }
286
287 func3(): Array<number> {
288 return [1, 2];
289 }
290 };
291
292 class B<T, M> extends C<T, M> {
293 method1() {}
294 func2(): boolean {
295 return false;
296 }
297 }
298
299 class A extends B<string, number> {
300 method1() {}
301 func3(): Array<number> {
302 return [1, 2];
303 }/*1*/
304 })";
305
306 auto pos = text.find("/*1*/");
307 ASSERT_NE(pos, std::string::npos);
308 Initializer initializer = Initializer();
309 auto context = initializer.CreateContext("class_hierarchy_info_7.ets", ES2PANDA_STATE_CHECKED, text.c_str());
310 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
311 size_t expectInfoListSize = 2;
312 ASSERT_EQ(classHierarchy.size(), expectInfoListSize);
313 ASSERT_EQ(classHierarchy[0].GetClassName(), "B");
314 ASSERT_EQ(classHierarchy[1].GetClassName(), "C");
315 auto classBItems = classHierarchy[0].GetMethodItemList();
316 ASSERT_EQ(classBItems.size(), 1);
317 ASSERT_TRUE(classBItems.find("func2(): boolean") != classBItems.end());
318 auto classCItems = classHierarchy[1].GetMethodItemList();
319 ASSERT_EQ(classCItems.size(), 1);
320 ASSERT_TRUE(classCItems.find("func1(): void") != classCItems.end());
321 initializer.DestroyContext(context);
322 }
323
TEST_F(LspGetClassHierarchyInfoTests,GetClassHierarchyInfo_8)324 TEST_F(LspGetClassHierarchyInfoTests, GetClassHierarchyInfo_8)
325 {
326 LSPAPI const *lspApi = GetImpl();
327 ASSERT_TRUE(lspApi != nullptr);
328 const std::string text = R"(class Parent {
329 public property1: number = 1;
330 protected property2: string = '1';
331 private property3: boolean = true;
332 readonly property4: number = 1;
333 static property5: number = 1;
334 }
335
336 class Son extends Parent {
337 property1: number = 2;
338 ChildExtraProperty1: number = 2;
339 ChildExtraProperty2: number = 2;
340 }
341
342 class GrandSon extends Son {/*1*/
343 public property2: string = '2';
344 ChildExtraProperty1: number = 3;
345 })";
346
347 auto pos = text.find("/*1*/");
348 ASSERT_NE(pos, std::string::npos);
349 Initializer initializer = Initializer();
350 auto context = initializer.CreateContext("class_hierarchy_info_8.ets", ES2PANDA_STATE_CHECKED, text.c_str());
351 auto classHierarchy = lspApi->getClassHierarchyInfo(context, pos);
352 size_t expectInfoListSize = 2;
353 ASSERT_EQ(classHierarchy.size(), expectInfoListSize);
354 ASSERT_EQ(classHierarchy[0].GetClassName(), "Son");
355 ASSERT_EQ(classHierarchy[1].GetClassName(), "Parent");
356 auto sonItems = classHierarchy[0].GetPropertyItemList();
357 size_t expectPropertyListSize = 2;
358 ASSERT_EQ(sonItems.size(), expectPropertyListSize);
359 ASSERT_TRUE(sonItems.find("property1: number") != sonItems.end());
360 ASSERT_TRUE(sonItems.find("ChildExtraProperty2: number") != sonItems.end());
361 auto parentItems = classHierarchy[1].GetPropertyItemList();
362 ASSERT_EQ(parentItems.size(), 1);
363 ASSERT_TRUE(parentItems.find("property4: number") != parentItems.end());
364 initializer.DestroyContext(context);
365 }
366 } // namespace
367