• 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 "suggestion_diagnostics.h"
17 #include "internal_api.h"
18 #include "utils/arena_containers.h"
19 #include <memory>
20 #include <unordered_map>
21 #include <vector>
22 
23 namespace ark::es2panda::lsp {
24 
GetSuggestionDiagnosticsImpl(ir::AstNode * astNode)25 std::vector<FileDiagnostic> GetSuggestionDiagnosticsImpl(ir::AstNode *astNode)
26 {
27     std::unordered_map<std::string, bool> visitedNestedConvertibleFunctions;
28     std::vector<FileDiagnostic> diags;
29 
30     if (astNode != nullptr) {
31         Check(astNode, diags, visitedNestedConvertibleFunctions);
32         if (!diags.empty()) {
33             std::sort(diags.begin(), diags.end(), [](const FileDiagnostic &d1, const FileDiagnostic &d2) {
34                 return d1.diagnostic.range_.start.line_ < d2.diagnostic.range_.start.line_;
35             });
36         }
37     }
38     return diags;
39 }
40 
Check(ir::AstNode * node,std::vector<FileDiagnostic> & diag,std::unordered_map<std::string,bool> & visitedFunc)41 void Check(ir::AstNode *node, std::vector<FileDiagnostic> &diag, std::unordered_map<std::string, bool> &visitedFunc)
42 {
43     if (CanBeConvertedToAsync(node)) {
44         AddConvertToAsyncFunctionDiagnostics(node, diag, visitedFunc);
45     }
46 
47     node->FindChild([&diag, &visitedFunc](ir::AstNode *childNode) {
48         Check(childNode, diag, visitedFunc);
49         return false;
50     });
51 }
52 
CheckGivenTypeExistInChilds(ir::AstNode * node,ir::AstNodeType type)53 bool CheckGivenTypeExistInChilds(ir::AstNode *node, ir::AstNodeType type)
54 {
55     auto desiredNode = node->FindChild([type](ir::AstNode *n) {
56         return n->Type() != type;
57         return false;
58     });
59     return desiredNode != nullptr;
60 }
61 
AddConvertToAsyncFunctionDiagnostics(ir::AstNode * node,std::vector<FileDiagnostic> & diag,std::unordered_map<std::string,bool> & visitedFunc)62 void AddConvertToAsyncFunctionDiagnostics(ir::AstNode *node, std::vector<FileDiagnostic> &diag,
63                                           std::unordered_map<std::string, bool> &visitedFunc)
64 {
65     if (IsConvertibleFunction(node, visitedFunc) && (visitedFunc.count(GetKeyFromNode(node)) == 0)) {
66         Position posStart(node->Range().start.line, node->Range().start.index);
67         Position posEnd(node->Range().end.line, node->Range().end.index);
68         Range range(posStart, posEnd);
69         const std::string message = "This_may_be_converted_to_an_async_function";
70         Diagnostic diagnostic(range, {}, {}, DiagnosticSeverity::Hint, 0, message, {}, {}, {});
71 
72         diag.push_back(lsp::CreateDiagnosticForNode(reinterpret_cast<es2panda_AstNode *>(node), diagnostic));
73     }
74 }
75 
IsConvertibleFunction(ir::AstNode * node,std::unordered_map<std::string,bool> & visitedFunc)76 bool IsConvertibleFunction(ir::AstNode *node, std::unordered_map<std::string, bool> &visitedFunc)
77 {
78     return !(node->IsAsync()) && CheckGivenTypeExistInChilds(node, ir::AstNodeType::BLOCK_STATEMENT) &&
79            HasReturnStatementWithPromiseHandler(node, visitedFunc);
80 }
81 
HasReturnStatementWithPromiseHandler(ir::AstNode * node,std::unordered_map<std::string,bool> & visitedFunc)82 bool HasReturnStatementWithPromiseHandler(ir::AstNode *node, std::unordered_map<std::string, bool> &visitedFunc)
83 {
84     auto desiredNode = node->FindChild([&visitedFunc](ir::AstNode *childNode) {
85         return IsReturnStatementWithFixablePromiseHandler(childNode, visitedFunc);
86     });
87     return desiredNode != nullptr;
88 }
89 
IsReturnStatementWithFixablePromiseHandler(ir::AstNode * node,std::unordered_map<std::string,bool> & visitedFunc)90 bool IsReturnStatementWithFixablePromiseHandler(ir::AstNode *node, std::unordered_map<std::string, bool> &visitedFunc)
91 {
92     auto desiredNode = node->FindChild([&visitedFunc](ir::AstNode *childNode) {
93         if (childNode->Type() == ir::AstNodeType::RETURN_STATEMENT) {
94             return childNode->IsReturnStatement() &&
95                    CheckGivenTypeExistInChilds(childNode, ir::AstNodeType::CALL_EXPRESSION) &&
96                    IsFixablePromiseHandler(childNode, visitedFunc);
97         }
98         return false;
99     });
100     return desiredNode != nullptr;
101 }
102 
HasSpecificIdentifier(ir::AstNode * node)103 ir::AstNode *HasSpecificIdentifier(ir::AstNode *node)
104 {
105     std::vector<ark::es2panda::ir::AstNode *> memberExpNodes;
106     std::vector<ark::es2panda::ir::AstNode *> identNodes;
107     node->FindChild([&memberExpNodes](ir::AstNode *n) {
108         if (n->Type() == ark::es2panda::ir::AstNodeType::MEMBER_EXPRESSION) {
109             memberExpNodes.push_back(n);
110         }
111         return false;
112     });
113 
114     if (memberExpNodes.empty()) {
115         return nullptr;
116     }
117     memberExpNodes.at(0)->FindChild([&identNodes](ir::AstNode *in) {
118         if (in->Type() == ark::es2panda::ir::AstNodeType::IDENTIFIER) {
119             identNodes.push_back(in);
120         }
121         return false;
122     });
123     if (identNodes.empty()) {
124         return nullptr;
125     }
126     for (auto n : identNodes) {
127         if (n->AsIdentifier()->Name() == "then" || n->AsIdentifier()->Name() == "catch" ||
128             n->AsIdentifier()->Name() == "finally") {
129             return n;
130         }
131     }
132     return identNodes.at(0);
133 }
134 
IsFixablePromiseHandler(ir::AstNode * node,std::unordered_map<std::string,bool> & visitedFunc)135 bool IsFixablePromiseHandler(ir::AstNode *node, std::unordered_map<std::string, bool> &visitedFunc)
136 {
137     auto desiredNode = node->FindChild([&visitedFunc](ir::AstNode *childNode) {
138         if (childNode->Type() == ark::es2panda::ir::AstNodeType::CALL_EXPRESSION &&
139             (!IsPromiseHandler(childNode) || !HasSupportedNumberOfArguments(childNode) ||
140              childNode->FindChild(
141                  [&visitedFunc](ir::AstNode *n) { return IsFixablePromiseArgument(n, visitedFunc); }) == nullptr)) {
142             return false;
143         }
144         while (IsPromiseHandler(childNode) || HasSpecificIdentifier(childNode) != nullptr) {
145             if (childNode->Type() == ir::AstNodeType::CALL_EXPRESSION &&
146                 (!HasSupportedNumberOfArguments(childNode) || childNode->FindChild([&visitedFunc](ir::AstNode *n) {
147                     return IsFixablePromiseHandler(n, visitedFunc);
148                 }) != nullptr)) {
149                 return false;
150             }
151             if (childNode->IsCallExpression()) {
152                 childNode = childNode->AsCallExpression()->Callee();
153             } else if (childNode->IsExpression()) {
154                 childNode->AsExpression();
155             }
156             return false;
157         }
158         return true;
159     });
160     return desiredNode != nullptr;
161 }
162 
HasPropertyAccessExpressionWithName(ir::AstNode * node,const std::string & func)163 bool HasPropertyAccessExpressionWithName(ir::AstNode *node, const std::string &func)
164 {
165     return node->IsIdentifier() && node->AsIdentifier()->Name().Utf8() == func;
166 }
167 
IsPromiseHandler(ir::AstNode * node)168 bool IsPromiseHandler(ir::AstNode *node)
169 {
170     ir::AstNode *currentIdentNode = nullptr;
171     if (HasSpecificIdentifier(node) == nullptr) {
172         return false;
173     }
174     currentIdentNode = HasSpecificIdentifier(node);
175     return ir::AstNodeType::CALL_EXPRESSION == node->Type() &&
176            (HasPropertyAccessExpressionWithName(currentIdentNode, "then") ||
177             HasPropertyAccessExpressionWithName(currentIdentNode, "catch") ||
178             HasPropertyAccessExpressionWithName(currentIdentNode, "finally"));
179 }
180 
IsExist(ArenaVector<ir::Expression * > args)181 bool IsExist(ArenaVector<ir::Expression *> args)
182 {
183     return std::any_of(args.begin(), args.end(), [](ir::Expression *e) {
184         return e->Type() == ir::AstNodeType::TS_NULL_KEYWORD ||
185                (e->IsIdentifier() && e->AsIdentifier()->Name() == "undefined");
186     });
187 }
188 
HasSupportedNumberOfArguments(ir::AstNode * node)189 bool HasSupportedNumberOfArguments(ir::AstNode *node)
190 {
191     ir::AstNode *currentIdentNode = nullptr;
192     if (HasSpecificIdentifier(node) == nullptr) {
193         return false;
194     }
195     currentIdentNode = HasSpecificIdentifier(node);
196     auto const &name = currentIdentNode->AsIdentifier()->Name();
197     auto const maxArguments = name == "then" ? 2 : name == "catch" ? 1 : name == "finally" ? 1 : 0;
198     if ((int)(node->AsCallExpression()->Arguments().size()) > maxArguments) {
199         return false;
200     }
201     if ((int)(node->AsCallExpression()->Arguments().size()) < maxArguments) {
202         return true;
203     }
204     auto args = node->AsCallExpression()->Arguments();
205 
206     return maxArguments == 1 || IsExist(args);
207 }
208 
IsFixablePromiseArgument(ir::AstNode * node,std::unordered_map<std::string,bool> & visitedFunc)209 bool IsFixablePromiseArgument(ir::AstNode *node, std::unordered_map<std::string, bool> &visitedFunc)
210 {
211     switch (node->Type()) {
212         case ir::AstNodeType::FUNCTION_DECLARATION:
213         case ir::AstNodeType::FUNCTION_EXPRESSION:
214         case ir::AstNodeType::IDENTIFIER:
215         case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION:
216             visitedFunc[GetKeyFromNode(node)] = true;
217             return true;
218         case ir::AstNodeType::TS_NULL_KEYWORD:
219             return true;
220         default:
221             return false;
222     }
223 }
224 
GetKeyFromNode(ir::AstNode * node)225 std::string GetKeyFromNode(ir::AstNode *node)
226 {
227     return std::to_string(node->Start().index) + ":" + std::to_string(node->End().index);
228 }
229 
CanBeConvertedToAsync(ir::AstNode * node)230 bool CanBeConvertedToAsync(ir::AstNode *node)
231 {
232     switch (node->Type()) {
233         case ir::AstNodeType::FUNCTION_DECLARATION:
234         case ir::AstNodeType::METHOD_DEFINITION:
235         case ir::AstNodeType::FUNCTION_EXPRESSION:
236         case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION:
237             return true;
238         default:
239             return false;
240     }
241 }
242 }  // namespace ark::es2panda::lsp