• 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 "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([&paramIndex, 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