From da3b8a95f805927e803e408fb26bb379efa6ff34 Mon Sep 17 00:00:00 2001 From: yunusemrekarakaya Date: Mon, 4 Aug 2025 08:43:54 +0300 Subject: [PATCH] [LSPAPI] SignatureHelpItems - BugFix Issue : https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICQD85 Signed-off-by: yunusemrekarakaya --- ets2panda/lsp/include/signature_help.h | 4 +- ets2panda/lsp/src/signature_help.cpp | 157 ++++++++++++----- .../unit/lsp/signature_help_items_test.cpp | 166 +++++++++++------- 3 files changed, 222 insertions(+), 105 deletions(-) diff --git a/ets2panda/lsp/include/signature_help.h b/ets2panda/lsp/include/signature_help.h index 6933ca168a..aadd47734c 100644 --- a/ets2panda/lsp/include/signature_help.h +++ b/ets2panda/lsp/include/signature_help.h @@ -306,9 +306,9 @@ ir::AstNode *FindTokenOnLeftOfPosition(es2panda_Context *context, size_t positio SignatureHelpItems GetSignatureHelpItems(es2panda_Context *ctx, size_t position, SignatureHelpTriggerReason triggeredReason, CancellationToken cancellationToken); -std::optional GetCandidateOrTypeInfo(const std::optional info, ir::AstNode *parent, +std::optional GetCandidateOrTypeInfo(const std::optional info, const bool onlyUseSyntacticOwners); -checker::Signature *GetResolvedSignatureForSignatureHelp(const ir::AstNode *call, const ir::AstNode *parent, +checker::Signature *GetResolvedSignatureForSignatureHelp(const ir::AstNode *call, std::vector &candidates); } // namespace ark::es2panda::lsp diff --git a/ets2panda/lsp/src/signature_help.cpp b/ets2panda/lsp/src/signature_help.cpp index 03cb954749..095d71fd6a 100644 --- a/ets2panda/lsp/src/signature_help.cpp +++ b/ets2panda/lsp/src/signature_help.cpp @@ -21,6 +21,7 @@ #include #include #include +#include "compiler/lowering/util.h" #include #include "create_type_help_items.h" @@ -47,36 +48,37 @@ bool IsSyntacticOwner(const ir::AstNode *node) { return node->IsCallExpression() || node->IsNewExpression(); } -checker::Signature *GetResolvedSignatureForSignatureHelp(const ir::AstNode *call, const ir::AstNode *parent, +checker::Signature *GetResolvedSignatureForSignatureHelp(const ir::AstNode *call, std::vector &candidates) { - parent->FindChild([&call, &candidates](ir::AstNode *n) { - switch (n->Type()) { - case ir::AstNodeType::METHOD_DEFINITION: - if (call->AsCallExpression()->Callee()->ToString() == n->AsMethodDefinition()->Id()->ToString()) { - candidates.push_back(n->AsMethodDefinition()->Function()->Signature()); - } - break; - - case ir::AstNodeType::CALL_EXPRESSION: - if (call->AsCallExpression()->Callee()->ToString() == n->AsCallExpression()->Callee()->ToString()) { - candidates.push_back(n->AsCallExpression()->Signature()); - } - break; - - default: - break; - } - return false; - }); + checker::Signature *signature = nullptr; if (call->IsCallExpression()) { auto callExpr = call->AsCallExpression(); - return callExpr->Signature(); + if (callExpr->Callee()->IsIdentifier()) { + auto decl = compiler::DeclarationFromIdentifier(callExpr->Callee()->AsIdentifier()); + if (decl->IsMethodDefinition()) { + candidates.push_back(decl->AsMethodDefinition()->Function()->Signature()); + signature = decl->AsMethodDefinition()->Function()->Signature(); + } + } else if (callExpr->Callee()->IsMemberExpression()) { + auto decl = + compiler::DeclarationFromIdentifier(callExpr->Callee()->AsMemberExpression()->Object()->AsIdentifier()); + if (decl->IsClassProperty()) { + candidates.push_back( + decl->AsClassProperty()->Value()->AsETSNewClassInstanceExpression()->GetSignature()); + signature = decl->AsClassProperty()->Value()->AsETSNewClassInstanceExpression()->GetSignature(); + } else if (decl->IsMethodDefinition()) { + candidates.push_back(decl->AsMethodDefinition()->Function()->Signature()); + signature = decl->AsMethodDefinition()->Function()->Signature(); + } + } else { + signature = call->AsCallExpression()->Signature(); + } } - return nullptr; + return signature; } -std::optional GetCandidateOrTypeInfo(const std::optional info, ir::AstNode *parent, +std::optional GetCandidateOrTypeInfo(const std::optional info, const bool onlyUseSyntacticOwners) { if (const auto *call = std::get_if(&info->GetInvocation()); @@ -87,10 +89,9 @@ std::optional GetCandidateOrTypeInfo(const std::optional candidates; checker::Signature *resolvedSignature = nullptr; if (call->callExpressionNode != nullptr && call->callExpressionNode->IsCallExpression()) { - resolvedSignature = GetResolvedSignatureForSignatureHelp(call->callExpressionNode, parent, candidates); + resolvedSignature = GetResolvedSignatureForSignatureHelp(call->callExpressionNode, candidates); } else { - resolvedSignature = - GetResolvedSignatureForSignatureHelp(call->callExpressionNode->Parent(), parent, candidates); + resolvedSignature = GetResolvedSignatureForSignatureHelp(call->callExpressionNode->Parent(), candidates); } if (!candidates.empty()) { const auto can = CandidateInfo {CandidateOrTypeKind::CANDIDATE, candidates, resolvedSignature}; @@ -149,9 +150,6 @@ SignatureHelpItems GetSignatureHelpItems(es2panda_Context *ctx, size_t position, return {}; } - auto context = reinterpret_cast(ctx); - auto astNode = reinterpret_cast(context->parserProgram->Ast()); - const auto onlyUseSyntacticOwners = IsReasonCharacterTyped(triggeredReason) == "characterTyped"; if (onlyUseSyntacticOwners) { return {}; @@ -164,7 +162,7 @@ SignatureHelpItems GetSignatureHelpItems(es2panda_Context *ctx, size_t position, if (cancellationToken.IsCancellationRequested()) { return {}; } - const auto candidateInfoOpt = GetCandidateOrTypeInfo(argumentInfo, astNode, onlyUseSyntacticOwners); + const auto candidateInfoOpt = GetCandidateOrTypeInfo(argumentInfo, onlyUseSyntacticOwners); if (candidateInfoOpt == std::nullopt) { return {}; } @@ -236,36 +234,111 @@ size_t GetArgumentCount(ir::AstNode *node, bool ignoreTrailingComma) argumentCount++; } else if (!ignoreTrailingComma && child->IsMemberExpression()) { argumentCount++; + } else if (!ignoreTrailingComma && child->IsNumberLiteral()) { + argumentCount++; + } else if (!ignoreTrailingComma && child->IsStringLiteral()) { + argumentCount++; + } else if (!ignoreTrailingComma && child->IsBooleanLiteral()) { + argumentCount++; + } else if (!ignoreTrailingComma && child->IsCharLiteral()) { + argumentCount++; + } else if (!ignoreTrailingComma && child->IsUndefinedLiteral()) { + argumentCount++; + } else if (!ignoreTrailingComma && child->IsTemplateLiteral()) { + argumentCount++; + } else if (!ignoreTrailingComma && child->IsBigIntLiteral()) { + argumentCount++; } return false; }); return argumentCount; } -std::vector GetArgumentOrParameterListAndIndex(ir::AstNode *node, std::vector &list) + +void IsCalleeIdentifier(ir::AstNode *node, std::vector &list) { - if (node->IsMethodDefinition()) { - const auto params = node->AsMethodDefinition()->Function()->Params(); + auto decl = compiler::DeclarationFromIdentifier(node->AsCallExpression()->Callee()->AsIdentifier()); + if (decl->IsMethodDefinition()) { + const auto params = decl->AsMethodDefinition()->Function()->Params(); for (const auto param : params) { auto argum = ArgumentListInfo(); argum.SetInvocation(Invocation(CallInvocation {InvocationKind::CALL, param})); argum.SetApplicableSpan(CreateTextSpanForNode(param)); - argum.SetArgumentIndex(param->Start().index); + argum.SetArgumentIndex(node->AsCallExpression()->Arguments().size()); argum.SetArgumentCount(params.size()); list.push_back(argum); } } - if (node->IsCallExpression()) { - const auto params = node->AsCallExpression()->Arguments(); +} + +void IsCalleeMemberExpression(ir::AstNode *node, std::vector &list) +{ + auto decl = compiler::DeclarationFromIdentifier( + node->AsCallExpression()->Callee()->AsMemberExpression()->Object()->AsIdentifier()); + if (decl->IsClassProperty()) { + auto part = decl->AsClassProperty() + ->Value() + ->AsETSNewClassInstanceExpression() + ->GetTypeRef() + ->AsETSTypeReference() + ->Part(); + + const auto params = part->TypeParams()->Params(); for (const auto param : params) { auto argum = ArgumentListInfo(); argum.SetInvocation(Invocation(CallInvocation {InvocationKind::CALL, param})); argum.SetApplicableSpan(CreateTextSpanForNode(param)); - argum.SetArgumentIndex(param->Start().index); + argum.SetArgumentIndex(node->AsCallExpression()->Arguments().size()); + argum.SetArgumentCount(params.size()); + list.push_back(argum); + } + } else if (decl->IsMethodDefinition()) { + const auto params = decl->AsMethodDefinition()->Function()->Params(); + for (const auto param : params) { + auto argum = ArgumentListInfo(); + argum.SetInvocation(Invocation(CallInvocation {InvocationKind::CALL, param})); + argum.SetApplicableSpan(CreateTextSpanForNode(param)); + argum.SetArgumentIndex(node->AsCallExpression()->Arguments().size()); argum.SetArgumentCount(params.size()); list.push_back(argum); } } +} +void IsCalleeOthers(ir::AstNode *node, std::vector &list) +{ + const auto params = node->AsCallExpression()->Arguments(); + for (const auto param : params) { + auto argum = ArgumentListInfo(); + argum.SetInvocation(Invocation(CallInvocation {InvocationKind::CALL, param})); + argum.SetApplicableSpan(CreateTextSpanForNode(param)); + argum.SetArgumentIndex(param->Start().index); + argum.SetArgumentCount(params.size()); + list.push_back(argum); + } +} + +std::vector GetArgumentOrParameterListAndIndex(ir::AstNode *node, std::vector &list) +{ + if (node->IsMethodDefinition()) { + const auto params = node->AsMethodDefinition()->Function()->Params(); + for (const auto param : params) { + auto argum = ArgumentListInfo(); + argum.SetInvocation(Invocation(CallInvocation {InvocationKind::CALL, param})); + argum.SetApplicableSpan(CreateTextSpanForNode(param)); + argum.SetArgumentIndex(param->Start().index); + argum.SetArgumentCount(params.size()); + list.push_back(argum); + } + } + if (node->IsCallExpression()) { + if (node->AsCallExpression()->Callee()->IsIdentifier()) { + IsCalleeIdentifier(node, list); + } else if (node->AsCallExpression()->Callee()->IsMemberExpression()) { + IsCalleeMemberExpression(node, list); + } else { + IsCalleeOthers(node, list); + } + } return list; } ContextualSignatureLocationInfo GetArgumentOrParameterListInfo(ir::AstNode *node) @@ -276,9 +349,8 @@ ContextualSignatureLocationInfo GetArgumentOrParameterListInfo(ir::AstNode *node return ContextualSignatureLocationInfo {}; } - const auto argumentCount = GetArgumentCount(node, false); auto textSpan = CreateTextSpanForNode(node); - return {info, node->Start().index, argumentCount, textSpan}; + return {info, node->Start().index, info.at(0).GetArgumentCount(), textSpan}; } std::optional TryGetParameterInfo(ir::AstNode *node) @@ -325,7 +397,12 @@ std::optional GetImmediatelyContainingArgumentInfo(ir::AstNode if (position == 0) { return std::nullopt; } - auto parent = node->Parent(); + ir::AstNode *parent = nullptr; + if (node->IsCallExpression()) { + parent = node; + } else { + parent = node->Parent(); + } if (parent->Type() == ir::AstNodeType::CALL_EXPRESSION || parent->Type() == ir::AstNodeType::NEW_EXPRESSION || parent->Type() == ir::AstNodeType::MEMBER_EXPRESSION) { if (parent->IsMemberExpression() && parent->Parent() != nullptr && parent->Parent()->IsCallExpression()) { diff --git a/ets2panda/test/unit/lsp/signature_help_items_test.cpp b/ets2panda/test/unit/lsp/signature_help_items_test.cpp index f1296efaa9..07c762f74a 100644 --- a/ets2panda/test/unit/lsp/signature_help_items_test.cpp +++ b/ets2panda/test/unit/lsp/signature_help_items_test.cpp @@ -26,9 +26,7 @@ #include "ir/astNode.h" #include "ir/expressions/functionExpression.h" #include "ir/statements/functionDeclaration.h" -#include #include -#include #include #include "lsp/include/signature_help.h" @@ -47,28 +45,117 @@ ark::es2panda::ir::AstNode *FindTokenOnLeftOfPosition(es2panda_Context *context, return ark::es2panda::lsp::FindPrecedingToken(position, ctx->parserProgram->Ast(), ctx->allocator); } +TEST_F(LSPSignatureHelpItemsTests, UserDefinedFunction_EmptyCall) +{ + std::vector files = {"getSignatureHelpItemsTest_user_empty_call.ets"}; + std::vector texts = {R"( +function test(a: number, b: string): void { + console.log(a); +} +test(); +)"}; + auto filePaths = CreateTempFile(files, texts); + ASSERT_EQ(filePaths.size(), 1); + + LSPAPI const *lspApi = GetImpl(); + size_t const offset = texts[0].find("test()") + 5; // position after '(' + const size_t count0 = 0; + const size_t count1 = 1; + const size_t count2 = 2; + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + auto res = lspApi->getSignatureHelpItems(ctx, offset); + ASSERT_EQ(res.GetItems().size(), count1); + ASSERT_EQ(res.GetArgumentCount(), count0); // opening paren counts as first arg + + auto &item = res.GetItem(0); + auto ¶meters = item.GetParameters(); + ASSERT_EQ(parameters.size(), count2); + + std::vector expectedFirst; + expectedFirst.emplace_back("a", "paramName"); + expectedFirst.emplace_back(":", "punctuation"); + expectedFirst.emplace_back("Double", "typeName"); + expectedFirst.emplace_back(",", "punctuation"); + + std::vector expectedSecond; + expectedSecond.emplace_back("b", "paramName"); + expectedSecond.emplace_back(":", "punctuation"); + expectedSecond.emplace_back("String", "typeName"); + + ASSERT_EQ(parameters[0].GetDisplayParts(), expectedFirst); + ASSERT_EQ(parameters[1].GetDisplayParts(), expectedSecond); + + initializer.DestroyContext(ctx); +} + +TEST_F(LSPSignatureHelpItemsTests, UserDefinedFunction_MissingSecondArgument) +{ + std::vector files = {"getSignatureHelpItemsTest_user_missing_second.ets"}; + std::vector texts = {R"( +function test(a: number, b: string): void { + console.log(a); +} +test(1,); +)"}; + const size_t count0 = 0; + const size_t count1 = 1; + const size_t count2 = 2; + + auto filePaths = CreateTempFile(files, texts); + ASSERT_EQ(filePaths.size(), count1); + + LSPAPI const *lspApi = GetImpl(); + size_t const offset = texts[0].find("test(1,") + 7; // after comma + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + auto res = lspApi->getSignatureHelpItems(ctx, offset); + ASSERT_EQ(res.GetItems().size(), count1); + ASSERT_EQ(res.GetArgumentCount(), count1); + + auto &item = res.GetItem(count0); + auto ¶meters = item.GetParameters(); + ASSERT_EQ(parameters.size(), count2); + + std::vector expectedFirst; + expectedFirst.emplace_back("a", "paramName"); + expectedFirst.emplace_back(":", "punctuation"); + expectedFirst.emplace_back("Double", "typeName"); + expectedFirst.emplace_back(",", "punctuation"); + + std::vector expectedSecond; + expectedSecond.emplace_back("b", "paramName"); + expectedSecond.emplace_back(":", "punctuation"); + expectedSecond.emplace_back("String", "typeName"); + + ASSERT_EQ(parameters[0].GetDisplayParts(), expectedFirst); + ASSERT_EQ(parameters[1].GetDisplayParts(), expectedSecond); + + initializer.DestroyContext(ctx); +} + TEST_F(LSPSignatureHelpItemsTests, StdLibMapGet) { std::vector files = {"getSignatureHelpItemsTest_map.ets"}; std::vector texts = {R"(let map = new Map(); map.set("a", 1); -let a = map.get("a"); +let a = map.get(); )"}; auto filePaths = CreateTempFile(files, texts); size_t const expectedFileCount = 1; ASSERT_EQ(filePaths.size(), expectedFileCount); - size_t const offset = 68; + LSPAPI const *lspApi = GetImpl(); + size_t const offset = texts.at(0).find("map.get(") + 8; Initializer initializer = Initializer(); es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); - ASSERT_EQ(ContextState(ctx), ES2PANDA_STATE_CHECKED); - const size_t defaultTime = 20; - auto invokedReason = ark::es2panda::lsp::SignatureHelpInvokedReason(); - auto cancellationToken = ark::es2panda::lsp::CancellationToken(defaultTime, nullptr); - auto res = ark::es2panda::lsp::GetSignatureHelpItems(ctx, offset, invokedReason, cancellationToken); - size_t const expectedSize = 2; + auto res = lspApi->getSignatureHelpItems(ctx, offset); + size_t const expectedSize = 1; size_t const expectedStart = 62; - size_t const expectedLength = 12; + size_t const expectedLength = 9; size_t const expectedArgumentCount = 1; ASSERT_EQ(res.GetItems().size(), expectedSize); ASSERT_EQ(res.GetApplicableSpan().start, expectedStart); @@ -77,21 +164,15 @@ let a = map.get("a"); auto &item = res.GetItem(0); auto &prefix = item.GetPrefixDisplayParts(); auto &suffix = item.GetSuffixDisplayParts(); - auto ¶meters = item.GetParameters()[0].GetDisplayParts(); auto expectedPrefix = std::vector {SymbolDisplayPart {"(", "punctuation"}}; auto expectedSuffix = std::vector { SymbolDisplayPart {")", "punctuation"}, SymbolDisplayPart {":", "punctuation"}, - SymbolDisplayPart {"Double|undefined", "typeName"}, - }; - auto expectedParameters = std::vector { - SymbolDisplayPart {"key", "paramName"}, - SymbolDisplayPart {":", "punctuation"}, - SymbolDisplayPart {"String", "typeName"}, + SymbolDisplayPart {"void", "typeName"}, }; + ASSERT_EQ(prefix, expectedPrefix); ASSERT_EQ(suffix, expectedSuffix); - ASSERT_EQ(parameters, expectedParameters); initializer.DestroyContext(ctx); } @@ -138,7 +219,7 @@ return x + y; let result = add(1, 2); )"; const size_t index0 = 0; - const size_t index2 = 3; + const size_t index1 = 1; const size_t position = 77; std::vector files = {fileName}; std::vector texts = {fileText}; @@ -149,56 +230,15 @@ let result = add(1, 2); auto callToken = FindTokenOnLeftOfPosition(ctx, position); const auto callExpr = callToken->Parent(); - auto context = reinterpret_cast(ctx); - auto astNode = reinterpret_cast(context->parserProgram->Ast()); ASSERT_NE(callExpr, nullptr); ASSERT_NE(callExpr, nullptr); ASSERT_TRUE(callExpr->IsCallExpression()); std::vector candidates; - auto *sig = ark::es2panda::lsp::GetResolvedSignatureForSignatureHelp(callExpr, astNode, candidates); + auto *sig = ark::es2panda::lsp::GetResolvedSignatureForSignatureHelp(callExpr, candidates); ASSERT_NE(sig, nullptr); - ASSERT_EQ(candidates.size(), index2); - - initializer.DestroyContext(ctx); -} -TEST_F(LSPSignatureHelpItemsTests, GetCandidateOrTypeInfo) -{ - const auto fileName = "candidateOrTypeInfo.ets"; - const auto fileText = R"( -function multiply(a: number, b: number): number { -return a * b; -} -let result = multiply(10, 20); - )"; - const size_t index0 = 0; - const size_t position = 82; - - std::vector files = {fileName}; - std::vector texts = {fileText}; - auto filePaths = CreateTempFile(files, texts); - - Initializer initializer; - es2panda_Context *ctx = initializer.CreateContext(files.at(index0).c_str(), ES2PANDA_STATE_CHECKED, fileText); - - auto callToken = ark::es2panda::lsp::FindTokenOnLeftOfPosition(ctx, position); - ASSERT_NE(callToken, nullptr); - const auto callee = callToken->Parent(); - std::vector argumentInfoVec; - ark::es2panda::lsp::GetArgumentOrParameterListAndIndex(callee, argumentInfoVec); - auto context = reinterpret_cast(ctx); - auto astNode = reinterpret_cast(context->parserProgram->Ast()); - - ASSERT_FALSE(argumentInfoVec.empty()); - ark::es2panda::lsp::ArgumentListInfo info = argumentInfoVec[index0]; - auto result = ark::es2panda::lsp::GetCandidateOrTypeInfo(info, astNode, false); - ASSERT_TRUE(result.has_value()); - ASSERT_TRUE(std::holds_alternative(*result)); - auto candidateInfo = std::get(*result); - EXPECT_EQ(candidateInfo.GetKind(), ark::es2panda::lsp::CandidateOrTypeKind::CANDIDATE); - EXPECT_FALSE(candidateInfo.GetSignatures().empty()); - EXPECT_NE(candidateInfo.GetResolvedSignature(), nullptr); + ASSERT_EQ(candidates.size(), index1); initializer.DestroyContext(ctx); } -- Gitee