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 "inlay_hints.h"
17 #include <cstddef>
18 #include <string>
19 #include <vector>
20 #include "public/public.h"
21 #include "internal_api.h"
22 #include "cancellation_token.h"
23 #include "utils/arena_containers.h"
24
25 namespace ark::es2panda::lsp {
26
GetFullWidth(const ir::AstNode * node)27 int GetFullWidth(const ir::AstNode *node)
28 {
29 return node->End().index - node->Start().index;
30 }
31
AddEnumMemberValueHints(const std::string & text,const int position,InlayHintList * result)32 void AddEnumMemberValueHints(const std::string &text, const int position, InlayHintList *result)
33 {
34 const auto kind = InlayHintKind::ENUM;
35 const auto whitespaceBerfore = true;
36 const auto whitespaceAfter = false;
37 const auto hint = InlayHint {text, position, kind, whitespaceBerfore, whitespaceAfter};
38 result->hints.push_back(hint);
39 }
40
AddTypeHints(const std::string & text,const int position,InlayHintList * result)41 void AddTypeHints(const std::string &text, const int position, InlayHintList *result)
42 {
43 const auto kind = InlayHintKind::TYPE;
44 const auto whitespaceBerfore = true;
45 const auto whitespaceAfter = false;
46 const auto hint = InlayHint {text, position, kind, whitespaceBerfore, whitespaceAfter};
47 result->hints.push_back(hint);
48 }
49
AddParameterHints(const std::string & text,const int position,const bool isFirstVariadicArgument,InlayHintList * result)50 void AddParameterHints(const std::string &text, const int position, const bool isFirstVariadicArgument,
51 InlayHintList *result)
52 {
53 const auto kind = InlayHintKind::PARAMETER;
54 const auto whitespaceBerfore = false;
55 const auto whitespaceAfter = true;
56 std::string textForPush;
57 if (isFirstVariadicArgument) {
58 textForPush = "..." + text;
59 } else {
60 textForPush = text;
61 }
62
63 const auto hint = InlayHint {textForPush, position, kind, whitespaceBerfore, whitespaceAfter};
64 result->hints.push_back(hint);
65 }
DecodedTextSpanIntersectsWith(const int start1,const int length1,const int start2,const int length2)66 bool DecodedTextSpanIntersectsWith(const int start1, const int length1, const int start2, const int length2)
67 {
68 const int end1 = start1 + length1;
69 const int end2 = start2 + length2;
70 return start2 <= end1 && end2 >= start1;
71 }
72
TextSpanIntersectsWith(const TextSpan span,const int position,const int nodeSize)73 bool TextSpanIntersectsWith(const TextSpan span, const int position, const int nodeSize)
74 {
75 return DecodedTextSpanIntersectsWith(span.start, span.length, position, nodeSize);
76 }
77
IsExpressionWithTypeArguments(const ir::AstNode * node)78 bool IsExpressionWithTypeArguments(const ir::AstNode *node)
79 {
80 return node->Type() == ir::AstNodeType::CALL_EXPRESSION;
81 }
82
GetVariableDeclarationTypeForHints(const ir::AstNode * decl,InlayHintList * result)83 void GetVariableDeclarationTypeForHints(const ir::AstNode *decl, InlayHintList *result)
84 {
85 const std::string listStr = "list";
86
87 decl->FindChild([decl, &result, &listStr](ark::es2panda::ir::AstNode *node) {
88 if (node->IsNumberLiteral()) {
89 AddTypeHints(TokenToString(ark::es2panda::lexer::TokenType::KEYW_NUMBER), decl->End().index, result);
90 }
91 if (node->IsStringLiteral()) {
92 AddTypeHints(TokenToString(ark::es2panda::lexer::TokenType::KEYW_STRING), decl->End().index, result);
93 }
94 if (node->IsBooleanLiteral()) {
95 AddTypeHints(TokenToString(ark::es2panda::lexer::TokenType::KEYW_BOOLEAN), decl->End().index, result);
96 TokenToString(ark::es2panda::lexer::TokenType::KEYW_BOOLEAN);
97 }
98 if (node->IsObjectExpression()) {
99 AddTypeHints(TokenToString(ark::es2panda::lexer::TokenType::KEYW_OBJECT), decl->End().index, result);
100 TokenToString(ark::es2panda::lexer::TokenType::KEYW_OBJECT);
101 }
102 if (node->IsArrayExpression()) {
103 AddTypeHints(listStr, decl->End().index, result);
104 }
105 return false;
106 });
107 }
AddParamIfTypeRef(const ir::AstNode * childNode,const ArenaVector<ir::Expression * > & args,InlayHintList * result)108 void AddParamIfTypeRef(const ir::AstNode *childNode, const ArenaVector<ir::Expression *> &args, InlayHintList *result)
109 {
110 int paramIndex = 0;
111 childNode->FindChild([¶mIndex, args, &result](ark::es2panda::ir::AstNode *node) {
112 if (node->IsETSParameterExpression()) {
113 auto part = node->AsETSParameterExpression()->Name().Utf8();
114 std::string s1 {part.data(), part.size()};
115 AddParameterHints(std::string(s1), args.at(paramIndex)->Start().index, false, result);
116 paramIndex++;
117 }
118 return false;
119 });
120 }
121
GetCallExpTypeForHints(const ir::AstNode * expr,const ir::AstNode * parent,InlayHintList * result)122 void GetCallExpTypeForHints(const ir::AstNode *expr, const ir::AstNode *parent, InlayHintList *result)
123 {
124 const ir::Expression *callee;
125 if (expr->IsCallExpression()) {
126 callee = expr->AsCallExpression()->Callee();
127 } else if (expr->IsNewExpression()) {
128 callee = expr->AsNewExpression()->Callee();
129 } else {
130 return;
131 }
132 if (!callee->IsIdentifier()) {
133 return;
134 }
135 const auto args =
136 expr->IsCallExpression() ? expr->AsCallExpression()->Arguments() : expr->AsNewExpression()->Arguments();
137 if (args.empty()) {
138 return;
139 }
140
141 parent->FindChild([args, callee, &result](ark::es2panda::ir::AstNode *childNode) {
142 if (childNode->IsMethodDefinition() &&
143 childNode->AsMethodDefinition()->Function()->Id()->ToString() == callee->AsIdentifier()->Name().Utf8()) {
144 AddParamIfTypeRef(childNode, args, result);
145 }
146 return false;
147 });
148 }
149
ShouldShowParameterNameHints(const UserPreferences & preferences)150 bool ShouldShowParameterNameHints(const UserPreferences &preferences)
151 {
152 return preferences.GetIncludeInlayParameterNameHints() ==
153 UserPreferences::IncludeInlayParameterNameHints::LITERALS ||
154 preferences.GetIncludeInlayParameterNameHints() == UserPreferences::IncludeInlayParameterNameHints::ALL;
155 }
GetIndexForEnum(const ir::AstNode * mem,const std::string & assignName)156 int GetIndexForEnum(const ir::AstNode *mem, const std::string &assignName)
157 {
158 auto const list = mem->AsClassProperty()->Value()->AsArrayExpression()->Elements();
159 for (size_t index = 0; index < list.size(); index++) {
160 const auto nameD = std::string(list.at(index)->AsStringLiteral()->Str());
161 if (strstr(assignName.c_str(), nameD.c_str()) != nullptr) {
162 return index;
163 }
164 }
165 return -1;
166 }
SaveNumEnums(ir::AstNode * mem,const ir::AstNode * member,InlayHintList * result,int & enumValueIndex)167 void SaveNumEnums(ir::AstNode *mem, const ir::AstNode *member, InlayHintList *result, int &enumValueIndex)
168 {
169 const int exitNum = -1;
170 const auto stringValuesArray = "#StringValuesArray";
171 if (!mem->IsClassProperty() || !mem->AsClassProperty()->Key()->IsIdentifier()) {
172 return;
173 }
174 if (mem->AsClassProperty()->Key()->AsIdentifier()->Name() != stringValuesArray) {
175 return;
176 }
177 auto const list = mem->AsClassProperty()->Value()->AsArrayExpression()->Elements();
178 SaveToList(list.at(enumValueIndex), member, result);
179 enumValueIndex = exitNum;
180 }
181
SaveStringEnums(ir::AstNode * mem,const ir::AstNode * member,InlayHintList * result,int & enumValueIndex)182 void SaveStringEnums(ir::AstNode *mem, const ir::AstNode *member, InlayHintList *result, int &enumValueIndex)
183 {
184 const int exitNum = -1;
185 auto const list = mem->AsClassProperty()->Value()->AsArrayExpression()->Elements();
186 SaveToList(list.at(enumValueIndex), member, result);
187 enumValueIndex = exitNum;
188 }
189
GetEnumIndexForSave(const ir::AstNode * enumMember,const ir::AstNode * member,InlayHintList * result)190 void GetEnumIndexForSave(const ir::AstNode *enumMember, const ir::AstNode *member, InlayHintList *result)
191 {
192 const int miles = -1;
193 int enumValueIndex = miles;
194 const auto assignName = std::string(member->AsAssignmentExpression()->Right()->ToString());
195
196 enumMember->AsClassDefinition()->FindChild([member, assignName, &enumValueIndex, &result](ir::AstNode *mem) {
197 const int exitNum = -1;
198 const auto namesArray = "#NamesArray";
199 const auto valuesArray = "#ValuesArray";
200 const auto stringValuesArray = "#StringValuesArray";
201 if (!mem->IsClassProperty() || !mem->AsClassProperty()->Key()->IsIdentifier()) {
202 return false;
203 }
204 const auto keyName = mem->AsClassProperty()->Key()->AsIdentifier()->Name();
205 if (keyName == namesArray) {
206 enumValueIndex = GetIndexForEnum(mem, assignName);
207 } else if (enumValueIndex != exitNum && keyName == valuesArray) {
208 SaveNumEnums(mem, member, result, enumValueIndex);
209 } else if (enumValueIndex != exitNum && keyName == stringValuesArray) {
210 SaveStringEnums(mem, member, result, enumValueIndex);
211 }
212 return false;
213 });
214 }
215
SaveToList(const ir::Expression * mem,const ir::AstNode * asignNode,InlayHintList * list)216 void SaveToList(const ir::Expression *mem, const ir::AstNode *asignNode, InlayHintList *list)
217 {
218 std::string enumValue;
219 if (mem->IsNumberLiteral()) {
220 if (mem->AsNumberLiteral()->Number().IsInt()) {
221 enumValue = std::to_string(mem->AsNumberLiteral()->Number().GetInt());
222 } else if (mem->AsNumberLiteral()->Number().IsDouble()) {
223 enumValue = std::to_string(mem->AsNumberLiteral()->Number().GetDouble());
224 } else if (mem->AsNumberLiteral()->Number().IsFloat()) {
225 enumValue = std::to_string(mem->AsNumberLiteral()->Number().GetFloat());
226 } else if (mem->AsNumberLiteral()->Number().IsLong()) {
227 enumValue = std::to_string(mem->AsNumberLiteral()->Number().GetLong());
228 }
229 } else if (mem->IsStringLiteral()) {
230 enumValue = std::string(mem->AsStringLiteral()->Str().Utf8());
231 }
232 if (!enumValue.empty()) {
233 AddEnumMemberValueHints(enumValue, asignNode->End().index, list);
234 }
235 }
236
GetEnumTypeForHints(const ir::AstNode * member,const ir::AstNode * parent,InlayHintList * result)237 void GetEnumTypeForHints(const ir::AstNode *member, const ir::AstNode *parent, InlayHintList *result)
238 {
239 parent->FindChild([&result, member](ark::es2panda::ir::AstNode *enumMember) {
240 if (!enumMember->IsClassDefinition()) {
241 return false;
242 }
243 const auto assignName = std::string(member->AsAssignmentExpression()->Right()->ToString());
244 const auto findName = std::string(enumMember->AsClassDefinition()->Ident()->Name());
245 if (assignName.empty() || findName.empty()) {
246 return false;
247 }
248 if (strstr(assignName.c_str(), findName.c_str()) == nullptr) {
249 return false;
250 }
251 GetEnumIndexForSave(enumMember, member, result);
252 return false;
253 });
254 }
255
AddReturnTypeTextFromFunc(const ir::ScriptFunction * func,const ir::AstNode * decl,InlayHintList * result)256 void AddReturnTypeTextFromFunc(const ir::ScriptFunction *func, const ir::AstNode *decl, InlayHintList *result)
257 {
258 AddTypeHints(func->Signature()->ReturnType()->ToString(), decl->End().index, result);
259 }
260
GetFunctionReturnTypeForHints(const ir::AstNode * decl,InlayHintList * result)261 void GetFunctionReturnTypeForHints(const ir::AstNode *decl, InlayHintList *result)
262 {
263 std::string retType;
264 if (decl->IsArrowFunctionExpression()) {
265 AddReturnTypeTextFromFunc(decl->AsArrowFunctionExpression()->Function(), decl, result);
266 } else if (decl->IsMethodDefinition()) {
267 AddReturnTypeTextFromFunc(decl->AsMethodDefinition()->Function(), decl, result);
268 } else if (decl->IsFunctionDeclaration()) {
269 AddReturnTypeTextFromFunc(decl->AsFunctionDeclaration()->Function(), decl, result);
270 }
271 }
272
AddTypeParamIfTypeRef(const ir::AstNode * childNode,const ir::AstNode * param,InlayHintList * result)273 void AddTypeParamIfTypeRef(const ir::AstNode *childNode, const ir::AstNode *param, InlayHintList *result)
274 {
275 if (childNode->IsETSTypeReference()) {
276 AddTypeHints(std::string(childNode->AsETSTypeReference()->Part()->Name()->ToString()), param->End().index,
277 result);
278 }
279 }
280
GetFunctionParameterTypeForHints(const ir::AstNode * node,InlayHintList * result)281 void GetFunctionParameterTypeForHints(const ir::AstNode *node, InlayHintList *result)
282 {
283 const auto nodeParams = node->AsMethodDefinition()->Function()->Params();
284 if (nodeParams.empty()) {
285 return;
286 }
287 for (const auto param : nodeParams) {
288 if (param->IsETSParameterExpression()) {
289 param->AsETSParameterExpression()->FindChild([param, &result](ark::es2panda::ir::AstNode *childNode) {
290 AddTypeParamIfTypeRef(childNode, param, result);
291 return false;
292 });
293 }
294 }
295 }
296
IsSignatureSupportingReturnAnnotation(const ir::AstNode * node)297 bool IsSignatureSupportingReturnAnnotation(const ir::AstNode *node)
298 {
299 return node->IsArrowFunctionExpression() || node->IsFunctionExpression() || node->IsFunctionDeclaration() ||
300 node->IsMethodDefinition() || (node->IsMethodDefinition());
301 }
302
ShouldProcessNode(const ir::AstNode * node,const TextSpan * span)303 bool ShouldProcessNode(const ir::AstNode *node, const TextSpan *span)
304 {
305 if (node == nullptr || GetFullWidth(node) == 0) {
306 return false;
307 }
308
309 if (!TextSpanIntersectsWith(*span, node->Start().index, GetFullWidth(node))) {
310 return false;
311 }
312
313 if (node->IsTyped() && !IsExpressionWithTypeArguments(node)) {
314 return false;
315 }
316
317 return true;
318 }
IsCancellableNode(const ir::AstNode * node)319 bool IsCancellableNode(const ir::AstNode *node)
320 {
321 switch (node->Type()) {
322 case ir::AstNodeType::TS_MODULE_DECLARATION:
323 case ir::AstNodeType::CLASS_DECLARATION:
324 case ir::AstNodeType::TS_INTERFACE_DECLARATION:
325 case ir::AstNodeType::FUNCTION_DECLARATION:
326 case ir::AstNodeType::CLASS_EXPRESSION:
327 case ir::AstNodeType::FUNCTION_EXPRESSION:
328 case ir::AstNodeType::METHOD_DEFINITION:
329 case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION:
330 return true;
331 default:
332 return false;
333 }
334 }
335
ProcessFunctionRelatedHints(const ir::AstNode * node,const UserPreferences & preferences,InlayHintList * result)336 void ProcessFunctionRelatedHints(const ir::AstNode *node, const UserPreferences &preferences, InlayHintList *result)
337 {
338 if (preferences.GetIncludeInlayFunctionParameterTypeHints() && node->IsFunctionDeclaration()) {
339 GetFunctionParameterTypeForHints(node, result);
340 }
341 if (preferences.GetIncludeInlayFunctionLikeReturnTypeHints() && IsSignatureSupportingReturnAnnotation(node)) {
342 GetFunctionReturnTypeForHints(node, result);
343 }
344 }
345
ProcessNodeBasedOnPreferences(const ir::AstNode * node,const ir::AstNode * parent,const UserPreferences & preferences,InlayHintList * result)346 void ProcessNodeBasedOnPreferences(const ir::AstNode *node, const ir::AstNode *parent,
347 const UserPreferences &preferences, InlayHintList *result)
348 {
349 if (preferences.GetIncludeInlayVariableTypeHints() && node->IsAssignmentExpression()) {
350 GetVariableDeclarationTypeForHints(node->AsAssignmentExpression(), result);
351 } else if (preferences.GetIncludeInlayEnumMemberValueHints() && node->IsAssignmentExpression()) {
352 GetEnumTypeForHints(node, parent, result);
353 } else if (ShouldShowParameterNameHints(preferences) && (node->IsCallExpression() || node->IsNewExpression())) {
354 GetCallExpTypeForHints(node, parent, result);
355 } else {
356 ProcessFunctionRelatedHints(node, preferences, result);
357 }
358 }
359
360 // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
361 struct InlayHintProcessingContext {
362 const TextSpan *span = nullptr;
363 const ir::AstNode *ast = nullptr;
364 CancellationToken &cancellationToken;
365 UserPreferences &preferences;
366
InlayHintProcessingContextark::es2panda::lsp::InlayHintProcessingContext367 InlayHintProcessingContext(const TextSpan *s, const ir::AstNode *a, CancellationToken &c, UserPreferences &p)
368 : span(s), ast(a), cancellationToken(c), preferences(p)
369 {
370 }
371 };
372 // NOLINTEND(misc-non-private-member-variables-in-classes)
373
Visitor(const ir::AstNode * node,InlayHintProcessingContext & context,InlayHintList * result)374 void Visitor(const ir::AstNode *node, InlayHintProcessingContext &context, InlayHintList *result)
375 {
376 if (!ShouldProcessNode(node, context.span)) {
377 return;
378 }
379 if (context.cancellationToken.IsCancellationRequested() && IsCancellableNode(node)) {
380 return;
381 }
382 ProcessNodeBasedOnPreferences(node, context.ast, context.preferences, result);
383 }
384
ProvideInlayHintsImpl(es2panda_Context * context,const TextSpan * span,CancellationToken & cancellationToken,UserPreferences & preferences)385 InlayHintList ProvideInlayHintsImpl(es2panda_Context *context, const TextSpan *span,
386 CancellationToken &cancellationToken, UserPreferences &preferences)
387 {
388 auto result = InlayHintList();
389 if (context == nullptr) {
390 return {};
391 }
392 const auto ctx = reinterpret_cast<public_lib::Context *>(context);
393 if (ctx == nullptr) {
394 return {};
395 }
396 const auto *parent = reinterpret_cast<ir::AstNode *>(ctx->parserProgram->Ast());
397 if (parent == nullptr) {
398 return {};
399 }
400 InlayHintProcessingContext processingContext = {span, parent, cancellationToken, preferences};
401 parent->FindChild([&processingContext, &result](ir::AstNode *childNode) {
402 Visitor(childNode, processingContext, &result);
403 return false;
404 });
405
406 return result;
407 }
408
409 } // namespace ark::es2panda::lsp