• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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