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 <gtest/gtest.h>
17 #include "lsp/include/organize_imports.h"
18 #include "lsp/include/api.h"
19 #include "lsp/include/internal_api.h"
20 #include "test/unit/lsp/lsp_api_test.h"
21
22 namespace {
23 using ark::es2panda::lsp::Initializer;
24 using ark::es2panda::lsp::OrganizeImports;
25 } // namespace
26
27 class OrganizeImportsTest : public LSPAPITests {};
28
TEST_F(OrganizeImportsTest,NormalImports)29 TEST_F(OrganizeImportsTest, NormalImports)
30 {
31 std::vector<std::string> files = {"normal-imports-test.ets", "organize-imports-1.ets", "organize-imports-2.ets"};
32 std::vector<std::string> texts = {
33 R"(
34 import {B, C, A} from "./organize-imports-1";
35 import { X } from "./organize-imports-2";
36 const a = B;
37 const b = C;
38 )",
39 R"(export const A = 1; export const B = 2; export const C = 3;)", R"(export const X = 1;)"};
40 auto filePaths = CreateTempFile(files, texts);
41
42 Initializer initializer;
43 es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
44
45 std::vector<FileTextChanges> changes = OrganizeImports::Organize(ctx, filePaths[0]);
46
47 ASSERT_EQ(changes.size(), 1);
48 ASSERT_EQ(changes[0].textChanges.size(), 1);
49
50 std::string result = changes[0].textChanges[0].newText;
51
52 EXPECT_TRUE(result.find("import { X } from \'./organize-imports-2\';") == std::string::npos);
53 EXPECT_TRUE(result.find("import { B, C } from \'./organize-imports-1\';") != std::string::npos);
54
55 initializer.DestroyContext(ctx);
56 }
57
TEST_F(OrganizeImportsTest,TypeOnlyImports)58 TEST_F(OrganizeImportsTest, TypeOnlyImports)
59 {
60 std::vector<std::string> files = {"typeonly-imports-test.ets", "typeonly-index.ets"};
61 std::vector<std::string> texts = {
62 R"(
63 import type { T } from "./typeonly-index";
64 import type {T as T1} from "./typeonly-index";
65 import type {T as T2} from "./typeonly-index";
66 let t: T1;
67 function foo(arg: T2) {};
68 )",
69 R"(export type T = string;)"};
70
71 auto filePaths = CreateTempFile(files, texts);
72 Initializer initializer;
73 es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
74
75 std::vector<FileTextChanges> changes = OrganizeImports::Organize(ctx, filePaths[0]);
76
77 ASSERT_EQ(changes.size(), 1);
78
79 std::string result = changes[0].textChanges[0].newText;
80
81 EXPECT_TRUE(result.find("import type { T } from \'./typeonly-index\';") == std::string::npos);
82 EXPECT_TRUE(result.find("import type { T as T1 } from \'./typeonly-index\';") != std::string::npos);
83 EXPECT_TRUE(result.find("import type { T as T2 } from \'./typeonly-index\';") != std::string::npos);
84
85 initializer.DestroyContext(ctx);
86 }
87
TEST_F(OrganizeImportsTest,NamespaceImports)88 TEST_F(OrganizeImportsTest, NamespaceImports)
89 {
90 std::vector<std::string> files = {"namespace-imports-test.ets", "namespace-imports-index.ets"};
91 std::vector<std::string> texts = {
92 R"(
93 import * as NS1 from "./namespace-imports-index";
94 import * as NS2 from "./namespace-imports-index";
95 const b = NS1.color.red;
96 )",
97 R"(export const A = 1; export const B = 2;export enum color{red, blue, yellow};)"};
98 auto filePaths = CreateTempFile(files, texts);
99
100 Initializer initializer;
101 es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
102
103 std::vector<FileTextChanges> changes = OrganizeImports::Organize(ctx, filePaths[0]);
104
105 ASSERT_EQ(changes.size(), 1);
106
107 std::string result = changes[0].textChanges[0].newText;
108
109 EXPECT_TRUE(result.find("import * as NS1 from \'./namespace-imports-index\';") != std::string::npos);
110 EXPECT_TRUE(result.find("import * as NS2 from \'./namespace-imports-index\';") == std::string::npos);
111
112 initializer.DestroyContext(ctx);
113 }
114
TEST_F(OrganizeImportsTest,AliasImports)115 TEST_F(OrganizeImportsTest, AliasImports)
116 {
117 std::vector<std::string> files = {"alias-imports-test.ets", "alias-imports-index.ets"};
118 std::vector<std::string> texts = {
119 R"(
120 import {B as B1, C} from "./alias-imports-index";
121 import { B as B2 } from "./alias-imports-index";
122 const b = B1;
123 )",
124 R"(export const B = 2; export const C = 3;)"};
125
126 auto filePaths = CreateTempFile(files, texts);
127 Initializer initializer;
128 es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
129
130 std::vector<FileTextChanges> changes = OrganizeImports::Organize(ctx, filePaths[0]);
131
132 ASSERT_EQ(changes.size(), 1);
133
134 std::string result = changes[0].textChanges[0].newText;
135
136 EXPECT_TRUE(result.find("import { B as B1 } from \'./alias-imports-index\'") != std::string::npos);
137 EXPECT_TRUE(result.find("import { B as B2 } from \'./alias-imports-index\'") == std::string::npos);
138
139 initializer.DestroyContext(ctx);
140 }
141
TEST_F(OrganizeImportsTest,DefaultImports)142 TEST_F(OrganizeImportsTest, DefaultImports)
143 {
144 std::vector<std::string> files = {"default-imports-test.ets", "default-imports-index.ets"};
145 std::vector<std::string> texts = {
146 R"(
147 import B from "./default-imports-index";
148 import {C as C1} from "./default-imports-index";
149 const b = B;
150 const c = C1;
151 )",
152 R"(export default const B = 2; export const C = 3;)"};
153
154 auto filePaths = CreateTempFile(files, texts);
155 Initializer initializer;
156 es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
157
158 std::vector<FileTextChanges> changes = OrganizeImports::Organize(ctx, filePaths[0]);
159
160 ASSERT_EQ(changes.size(), 1);
161
162 std::string result = changes[0].textChanges[0].newText;
163
164 EXPECT_TRUE(result.find("import B from \'./default-imports-index\'") != std::string::npos);
165 EXPECT_TRUE(result.find("import { C as C1 } from \'./default-imports-index\'") != std::string::npos);
166
167 initializer.DestroyContext(ctx);
168 }
169
TEST_F(OrganizeImportsTest,SystemDefaultImports)170 TEST_F(OrganizeImportsTest, SystemDefaultImports)
171 {
172 std::vector<std::string> files = {"system-default-imports-test.ets", "system-default-index.ets"};
173 std::vector<std::string> texts = {
174 R"(
175 import * as T1 from "./system-default-index";
176 import * as T2 from "./system-default-index";
177 let t: T2.T = "hello";
178 )",
179 R"(export type T = string;)"};
180
181 auto filePaths = CreateTempFile(files, texts);
182 Initializer initializer;
183 es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED);
184
185 std::vector<FileTextChanges> changes = OrganizeImports::Organize(ctx, filePaths[0]);
186
187 ASSERT_EQ(changes.size(), 1);
188
189 std::string result = changes[0].textChanges[0].newText;
190
191 EXPECT_TRUE(result.find("import * as T1 from \'./system-default-index\';") == std::string::npos);
192 EXPECT_TRUE(result.find("import * as T2 from \'./system-default-index\';") != std::string::npos);
193
194 initializer.DestroyContext(ctx);
195 }
196