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 <map>
17 #include <set>
18 #include <algorithm>
19 #include <sstream>
20
21 #include "public/public.h"
22 #include "lsp/include/api.h"
23 #include "lsp/include/organize_imports.h"
24
25 namespace ark::es2panda::lsp {
26
IsImportUsed(es2panda_Context * ctx,const ImportSpecifier & spec)27 bool IsImportUsed(es2panda_Context *ctx, const ImportSpecifier &spec)
28 {
29 auto *context = reinterpret_cast<public_lib::Context *>(ctx);
30 auto *ast = context->parserProgram->Ast();
31 bool found = false;
32
33 ast->FindChild([&](ir::AstNode *node) {
34 if (node->IsETSImportDeclaration() || node->IsImportSpecifier()) {
35 return false;
36 }
37
38 if (spec.type == ImportType::NAMESPACE) {
39 if (node->IsTSQualifiedName()) {
40 auto *qname = node->AsTSQualifiedName();
41 found = qname->Left()->IsIdentifier() &&
42 (std::string(qname->Left()->AsIdentifier()->Name()) == spec.localName);
43 return found;
44 }
45
46 if (node->IsMemberExpression()) {
47 auto *member = node->AsMemberExpression();
48 found = member->Object()->IsIdentifier() &&
49 (std::string(member->Object()->AsIdentifier()->Name()) == spec.localName);
50 return found;
51 }
52 return false;
53 }
54
55 if (!node->IsIdentifier() || std::string(node->AsIdentifier()->Name()) != spec.localName) {
56 return false;
57 }
58
59 auto *parent = node->Parent();
60 bool isProperty =
61 parent != nullptr && parent->IsMemberExpression() && (parent->AsMemberExpression()->Property() == node);
62 bool isImportSpecifier = parent != nullptr && parent->IsImportSpecifier();
63 if (!isProperty && !isImportSpecifier) {
64 found = true;
65 return true;
66 }
67 return false;
68 });
69
70 return found;
71 }
72
ProcessImportSpecifier(ir::AstNode * spec,bool isTypeOnly,ImportInfo & info)73 void ProcessImportSpecifier(ir::AstNode *spec, bool isTypeOnly, ImportInfo &info)
74 {
75 if (!spec->IsImportSpecifier() && !spec->IsImportDefaultSpecifier() && !spec->IsImportNamespaceSpecifier()) {
76 return;
77 }
78
79 ImportSpecifier specInfo {};
80 ir::Identifier *local = nullptr;
81 if (spec->IsImportSpecifier()) {
82 auto *importSpec = spec->AsImportSpecifier();
83 local = importSpec->Local();
84 auto *imported = importSpec->Imported();
85 specInfo.importedName = imported != nullptr ? std::string(imported->Name()) : std::string(local->Name());
86 specInfo.type = isTypeOnly ? ImportType::TYPE_ONLY : ImportType::NORMAL;
87 } else if (spec->IsImportDefaultSpecifier()) {
88 auto *defaultSpec = spec->AsImportDefaultSpecifier();
89 local = defaultSpec->Local();
90 specInfo.importedName = std::string(local->Name());
91 specInfo.type = ImportType::DEFAULT;
92 } else {
93 auto *nsSpec = spec->AsImportNamespaceSpecifier();
94 local = nsSpec->Local();
95 specInfo.importedName = std::string(local->Name());
96 specInfo.type = ImportType::NAMESPACE;
97 }
98
99 if (local == nullptr) {
100 return;
101 }
102
103 specInfo.localName = std::string(local->Name());
104 specInfo.start = local->Start().index;
105 specInfo.length = local->End().index - local->Start().index;
106
107 info.namedImports.push_back(specInfo);
108 }
109
CollectImports(es2panda_Context * context,std::vector<ImportInfo> & imports)110 void CollectImports(es2panda_Context *context, std::vector<ImportInfo> &imports)
111 {
112 auto *ctx = reinterpret_cast<public_lib::Context *>(context);
113 if (ctx == nullptr || ctx->parserProgram == nullptr || ctx->parserProgram->Ast() == nullptr) {
114 return;
115 }
116
117 std::vector<ir::AstNode *> importsNode;
118 ctx->parserProgram->Ast()->Iterate([&importsNode](ir::AstNode *node) {
119 if (node->IsETSImportDeclaration()) {
120 importsNode.push_back(node);
121 }
122 });
123
124 std::sort(importsNode.begin(), importsNode.end(),
125 [](ir::AstNode *a, ir::AstNode *b) { return a->Start().index < b->Start().index; });
126
127 for (auto *importNode : importsNode) {
128 auto *importDecl = importNode->AsETSImportDeclaration();
129 if (importDecl == nullptr || importDecl->Source() == nullptr) {
130 continue;
131 }
132
133 auto *declInfo = static_cast<ir::ImportDeclaration *>(importNode);
134 if (declInfo == nullptr) {
135 continue;
136 }
137
138 ImportInfo info {};
139 info.moduleName = std::string(importDecl->Source()->Str());
140
141 bool isTypeOnly = declInfo->IsTypeKind();
142 auto &specifiers = importDecl->Specifiers();
143
144 for (auto *spec : specifiers) {
145 ProcessImportSpecifier(spec, isTypeOnly, info);
146 }
147
148 info.startIndex = importDecl->Start().index;
149 info.endIndex = importDecl->End().index;
150 imports.push_back(std::move(info));
151 }
152 }
153
RemoveUnusedImports(std::vector<ImportInfo> & imports,es2panda_Context * ctx)154 void RemoveUnusedImports(std::vector<ImportInfo> &imports, es2panda_Context *ctx)
155 {
156 for (auto &import : imports) {
157 std::vector<ImportSpecifier> used;
158
159 for (auto &namedImport : import.namedImports) {
160 if (IsImportUsed(ctx, namedImport)) {
161 used.push_back(namedImport);
162 }
163 }
164
165 import.namedImports = used;
166 }
167 }
168
GenerateTextChanges(const std::vector<ImportInfo> & imports)169 std::vector<TextChange> GenerateTextChanges(const std::vector<ImportInfo> &imports)
170 {
171 if (imports.empty()) {
172 return {};
173 }
174
175 size_t start = imports.front().startIndex;
176 size_t end = imports.back().endIndex;
177 std::ostringstream oss;
178
179 auto generateImportBlock = [](const ImportInfo &imp, std::ostringstream &osst, const std::string &prefix) {
180 osst << prefix;
181 size_t index = 0;
182 for (auto &namedImport : imp.namedImports) {
183 const auto &spec = namedImport;
184 if (spec.importedName != spec.localName) {
185 osst << spec.importedName << " as " << spec.localName;
186 } else {
187 osst << spec.localName;
188 }
189 if (index + 1 < imp.namedImports.size()) {
190 osst << ", ";
191 }
192 index++;
193 }
194 osst << " } from \'" << imp.moduleName << "\';\n";
195 };
196
197 for (const auto &imp : imports) {
198 if (imp.namedImports.empty()) {
199 continue;
200 }
201
202 switch (imp.namedImports[0].type) {
203 case ImportType::NORMAL:
204 generateImportBlock(imp, oss, "import { ");
205 break;
206 case ImportType::DEFAULT:
207 oss << "import " << imp.namedImports[0].localName << " from \'" << imp.moduleName << "\';\n";
208 break;
209 case ImportType::NAMESPACE:
210 oss << "import * as " << imp.namedImports[0].localName << " from \'" << imp.moduleName << "\';\n";
211 break;
212 case ImportType::TYPE_ONLY:
213 generateImportBlock(imp, oss, "import type { ");
214 break;
215 }
216 }
217
218 return {TextChange(TextSpan(start, end - start), oss.str())};
219 }
220
Organize(es2panda_Context * context,const std::string & fileName)221 std::vector<FileTextChanges> OrganizeImports::Organize(es2panda_Context *context, const std::string &fileName)
222 {
223 std::vector<ImportInfo> imports;
224
225 CollectImports(context, imports);
226 RemoveUnusedImports(imports, context);
227
228 return {FileTextChanges(fileName, GenerateTextChanges(imports))};
229 }
230 } // namespace ark::es2panda::lsp
231