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