From 361ae1c7efdd921ecc92157aa4f243122432ad15 Mon Sep 17 00:00:00 2001 From: yunusemrekarakaya Date: Fri, 5 Sep 2025 09:43:01 +0300 Subject: [PATCH] [LSPAPI] Refactor ConvertFunctionExpression Issue : https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICVIA7 Signed-off-by: yunusemrekarakaya --- .../lsp/include/refactors/convert_function.h | 34 +- .../lsp/include/refactors/refactor_types.h | 6 +- .../lsp/src/refactors/convert_function.cpp | 297 +++++++++++++++++- ets2panda/test/unit/lsp/CMakeLists.txt | 5 +- .../test/unit/lsp/convert_function_test.cpp | 232 ++++++++++++++ 5 files changed, 564 insertions(+), 10 deletions(-) create mode 100644 ets2panda/test/unit/lsp/convert_function_test.cpp diff --git a/ets2panda/lsp/include/refactors/convert_function.h b/ets2panda/lsp/include/refactors/convert_function.h index 5581ab3dd5..5e357685ae 100644 --- a/ets2panda/lsp/include/refactors/convert_function.h +++ b/ets2panda/lsp/include/refactors/convert_function.h @@ -16,6 +16,15 @@ #ifndef CONVERT_FUNCTION_H #define CONVERT_FUNCTION_H +#include "ir/astNode.h" +#include "ir/expression.h" +#include "ir/expressions/arrowFunctionExpression.h" +#include "ir/expressions/functionExpression.h" +#include "ir/expressions/identifier.h" +#include "ir/statement.h" +#include "ir/statements/functionDeclaration.h" +#include "ir/statements/variableDeclaration.h" +#include "ir/statements/variableDeclarator.h" #include "refactor_types.h" namespace ark::es2panda::lsp { @@ -27,6 +36,18 @@ constexpr RefactorActionView TO_NAMED_FUNCTION_ACTION {"Convert to named functio constexpr RefactorActionView TO_ARROW_FUNCTION_ACTION {"Convert to arrow function", "Convert to arrow function", "refactor.rewrite.function.arrow"}; +struct FunctionInfo { + bool selectedVariableDeclaration; + ir::AstNode *func; +}; + +struct VariableInfo { + const ir::VariableDeclaration *variableDeclaration; + // const ir::VariableDeclarationList variableDeclarationList; + const ir::Statement *statement; + const ir::Identifier *name; +}; + class ConvertFunctionRefactor : public Refactor { public: ConvertFunctionRefactor(); @@ -35,6 +56,15 @@ public: const std::string &actionName) const override; }; +ir::Expression *TryGetFunctionFromVariableDeclaration(ir::AstNode *parent); +FunctionInfo GetFunctionInfo(es2panda_Context *context, const size_t startPosition); +std::optional GetVariableInfo(ir::AstNode *func); +std::vector GetEditInfoForConvertToAnonymousFunction(RefactorContext context, ir::Expression *func); +std::vector GetEditInfoForConvertToNamedFunction(RefactorContext context, + ir::ArrowFunctionExpression *const arrow, + VariableInfo info); +std::vector GetEditInfoForConvertToArrowFunction(RefactorContext context, + ir::FunctionDeclaration *func); +RefactorEditInfo GetRefactorEditsToConvertFunctionExpressions(RefactorContext &context, std::string_view actionName); } // namespace ark::es2panda::lsp - -#endif // CONVERT_FUNCTION_H +#endif // CONVERT_FUNCTION_H \ No newline at end of file diff --git a/ets2panda/lsp/include/refactors/refactor_types.h b/ets2panda/lsp/include/refactors/refactor_types.h index 513ba22f97..67365036b6 100644 --- a/ets2panda/lsp/include/refactors/refactor_types.h +++ b/ets2panda/lsp/include/refactors/refactor_types.h @@ -21,6 +21,7 @@ #include "../user_preferences.h" #include "../types.h" #include "es2panda.h" +#include "lsp/include/services/text_change/text_change_context.h" #include #include #include @@ -49,8 +50,9 @@ struct TextRange { }; struct RefactorContext { - ark::es2panda::lsp::CancellationToken *cancellationToken = nullptr; - ark::es2panda::lsp::UserPreferences *preferences = nullptr; + TextChangesContext *textChangesContext = nullptr; + CancellationToken *cancellationToken = nullptr; + UserPreferences *userPreferences = nullptr; TextRange span = {0, 0}; es2panda_Context *context = nullptr; std::string kind; diff --git a/ets2panda/lsp/src/refactors/convert_function.cpp b/ets2panda/lsp/src/refactors/convert_function.cpp index 097fe6fe65..6c38eb5fa9 100644 --- a/ets2panda/lsp/src/refactors/convert_function.cpp +++ b/ets2panda/lsp/src/refactors/convert_function.cpp @@ -13,13 +13,23 @@ * limitations under the License. */ -#include #include "refactors/convert_function.h" +#include +#include +#include +#include "ir/astNode.h" +#include "ir/expression.h" +#include "ir/expressions/functionExpression.h" +#include "ir/statements/functionDeclaration.h" +#include "public/es2panda_lib.h" #include "refactor_provider.h" #include "internal_api.h" +#include "refactors/refactor_types.h" +#include "services/text_change/change_tracker.h" +#include "services/text_change/text_change_context.h" +#include "types.h" namespace ark::es2panda::lsp { - ConvertFunctionRefactor::ConvertFunctionRefactor() { AddKind(std::string(TO_ANONYMOUS_FUNCTION_ACTION.kind)); @@ -49,6 +59,89 @@ bool HasArrowFunction(ark::es2panda::ir::AstNode *node) return false; } +ir::Expression *TryGetFunctionFromVariableDeclaration(ir::AstNode *parent) +{ + if (!parent->IsVariableDeclaration()) { + return nullptr; + } + const ir::VariableDeclaration *variableDeclaration = parent->AsVariableDeclaration(); + const auto initializer = variableDeclaration->Declarators().front()->Init(); + if (initializer != nullptr && (initializer->IsArrowFunctionExpression() || initializer->IsFunctionExpression())) { + return initializer; + } + return nullptr; +} +bool ContainingThis(ir::AstNode *node) +{ + auto isThis = [](ir::AstNode *n) { return n->IsThisExpression(); }; + return isThis(node); +} +bool StartEndContainsRange(size_t start, size_t end, TextRange range) +{ + return start <= range.pos && end >= range.end; +} + +bool RangeContainsRange(TextRange r1, TextRange r2) +{ + return StartEndContainsRange(r1.pos, r1.end, r2); +} + +FunctionInfo GetFunctionInfo(es2panda_Context *context, const size_t startPosition) +{ + const auto token = GetTouchingToken(context, startPosition, false); + if (token == nullptr) { + return {}; + } + const auto func = TryGetFunctionFromVariableDeclaration(token); + if (func != nullptr && !ContainingThis(func)) { + return {true, func}; + } + auto funcDecl = token; + while (funcDecl != nullptr && !funcDecl->IsFunctionDeclaration() && !funcDecl->IsArrowFunctionExpression()) { + funcDecl = funcDecl->Parent(); + } + if (funcDecl == nullptr) { + return {}; + } + const auto maybeFunc = + funcDecl->IsFunctionDeclaration() || funcDecl->IsArrowFunctionExpression() ? funcDecl : nullptr; + if (maybeFunc != nullptr && !ContainingThis(maybeFunc)) { + return {false, maybeFunc}; + } + + return {}; +} + +std::optional GetVariableInfo(ir::AstNode *func) +{ + if (func == nullptr) { + return std::nullopt; + } + + ir::AstNode *variableDeclaration = func->Parent(); + while (variableDeclaration != nullptr && !variableDeclaration->IsVariableDeclaration()) { + variableDeclaration = variableDeclaration->Parent(); + } + + if (variableDeclaration == nullptr || !variableDeclaration->IsVariableDeclaration()) { + return std::nullopt; + } + + ir::AstNode *statement = variableDeclaration->Parent(); + if (statement == nullptr || !statement->IsStatement()) { + return std::nullopt; + } + + ir::AstNode *name = variableDeclaration->AsVariableDeclaration()->Declarators().front()->Id(); + if (name == nullptr || !name->IsIdentifier()) { + return std::nullopt; + } + VariableInfo variableInfo = {variableDeclaration->AsVariableDeclaration(), statement->AsStatement(), + name->AsIdentifier()}; + + return variableInfo; +} + ApplicableRefactorInfo ConvertFunctionRefactor::GetAvailableActions(const RefactorContext &refContext) const { es2panda_Context *context = refContext.context; @@ -77,12 +170,206 @@ ApplicableRefactorInfo ConvertFunctionRefactor::GetAvailableActions(const Refact return res; } +static es2panda_FunctionSignature *CreateFunctionSignature(es2panda_Context *ctx, es2panda_AstNode *scriptFunc) +{ + const auto impl = es2panda_GetImpl(ES2PANDA_LIB_VERSION); + size_t paramCount = 0; + auto **params = impl->ScriptFunctionParams(ctx, scriptFunc, ¶mCount); + return impl->CreateFunctionSignature(ctx, nullptr, params, paramCount, nullptr, false); +} + +static es2panda_AstNode *CreateFunctionBody(es2panda_Context *ctx, es2panda_AstNode *scriptFunc) +{ + const auto impl = es2panda_GetImpl(ES2PANDA_LIB_VERSION); + es2panda_AstNode *body = impl->ScriptFunctionBody(ctx, scriptFunc); + if (body == nullptr) { + return nullptr; + } + + es2panda_AstNode *blockStmt = impl->CreateBlockStatement(ctx, nullptr, 0); + es2panda_AstNode *returnStmt = impl->CreateReturnStatement(ctx); + if (blockStmt == nullptr || returnStmt == nullptr) { + return nullptr; + } + + if (impl->IsBlockStatement(body)) { + size_t stmtCount = 0; + es2panda_AstNode **stmtsRaw = impl->BlockStatementStatements(ctx, body, &stmtCount); + if (stmtsRaw != nullptr && stmtCount > 0) { + Span stmtsView(stmtsRaw, stmtCount); + std::vector stmts; + stmts.reserve(stmtsView.size()); + for (auto *node : stmtsView) { + stmts.push_back(node); + } + if (!stmts.empty() && impl->IsReturnStatement(stmts.front())) { + es2panda_AstNode *returnArg = impl->ReturnStatementArgument(ctx, stmts.front()); + impl->ReturnStatementSetArgument(ctx, returnStmt, returnArg); + } + } + } else if (impl->IsExpression(body)) { + impl->ReturnStatementSetArgument(ctx, returnStmt, body); + } + + std::vector newStmts = {returnStmt}; + auto *stmtsMem = + static_cast(impl->AllocMemory(ctx, newStmts.size(), sizeof(es2panda_AstNode *))); + + Span stmtsMemView(stmtsMem, newStmts.size()); + std::copy(newStmts.begin(), newStmts.end(), stmtsMemView.begin()); + impl->BlockStatementSetStatements(ctx, blockStmt, stmtsMem, newStmts.size()); + return blockStmt; +} + +static std::vector MakeVector(es2panda_AstNode **arr, size_t count) +{ + std::vector vec; + vec.reserve(count); + + Span view(arr, count); + for (auto *node : view) { + if (node != nullptr) { + vec.push_back(node); + } + } + + return vec; +} + +std::vector GetEditInfoForConvertToNamedFunction(RefactorContext context, + ir::ArrowFunctionExpression *const arrow, + VariableInfo info) +{ + const auto ctx = context.context; + const auto impl = es2panda_GetImpl(ES2PANDA_LIB_VERSION); + if (arrow == nullptr || info.name == nullptr) { + return {}; + } + + const auto nameStr = info.name->Name().Mutf8(); + auto *funcId = impl->CreateIdentifier(ctx); + if (funcId == nullptr) { + return {}; + } + impl->IdentifierSetName(ctx, funcId, const_cast(nameStr.c_str())); + auto *scriptFunc = impl->ArrowFunctionExpressionFunction(ctx, reinterpret_cast(arrow)); + auto *blockStmt = CreateFunctionBody(ctx, scriptFunc); + auto *signature = CreateFunctionSignature(ctx, scriptFunc); + if (scriptFunc == nullptr || blockStmt == nullptr || signature == nullptr) { + return {}; + } + + auto *newScriptFunc = + impl->CreateScriptFunction(ctx, blockStmt, reinterpret_cast(signature), 0, 0); + if (newScriptFunc == nullptr) { + return {}; + } + + impl->ScriptFunctionSetIdent(ctx, newScriptFunc, funcId); + auto *funcDecl = impl->CreateFunctionDeclaration(ctx, newScriptFunc, nullptr, 0, false); + if (funcDecl == nullptr || !impl->IsFunctionDeclaration(funcDecl)) { + return {}; + } + + impl->AstNodeSetParent(ctx, funcId, funcDecl); + impl->AstNodeSetParent(ctx, blockStmt, funcDecl); + size_t paramCount = 0; + es2panda_AstNode **paramsRaw = impl->ScriptFunctionParams(ctx, scriptFunc, ¶mCount); + if (paramsRaw != nullptr && paramCount > 0) { + for (auto *param : MakeVector(paramsRaw, paramCount)) { + impl->AstNodeSetParent(ctx, param, funcDecl); + } + } + + TextChangesContext textChangesContext = {context.textChangesContext->host, + context.textChangesContext->formatContext, + context.textChangesContext->preferences}; + return ChangeTracker::With(textChangesContext, [&](ChangeTracker &tracker) { + tracker.ReplaceNode(context.context, arrow, reinterpret_cast(funcDecl), {}); + }); +} + +std::vector GetEditInfoForConvertToArrowFunction(RefactorContext context, + ir::FunctionDeclaration *func) +{ + const auto impl = es2panda_GetImpl(ES2PANDA_LIB_VERSION); + auto *funcId = impl->CreateIdentifier(context.context); + if (funcId == nullptr) { + return {}; + } + impl->IdentifierSetName(context.context, funcId, + const_cast(func->Function()->Id()->Name().Mutf8().c_str())); + + auto *scriptFunc = reinterpret_cast(func->Function()); + auto *blockStmt = CreateFunctionBody(context.context, scriptFunc); + auto *signature = CreateFunctionSignature(context.context, scriptFunc); + if (signature == nullptr) { + return {}; + } + auto *newScriptFunc = impl->CreateScriptFunction(context.context, blockStmt, + reinterpret_cast(signature), + Es2pandaScriptFunctionFlags::SCRIPT_FUNCTION_FLAGS_ARROW, 0); + if (newScriptFunc == nullptr) { + return {}; + } + impl->ScriptFunctionSetIdent(context.context, newScriptFunc, funcId); + auto *funcDecl = impl->CreateArrowFunctionExpression(context.context, newScriptFunc); + if (funcDecl == nullptr || !impl->IsArrowFunctionExpression(funcDecl)) { + return {}; + } + TextChangesContext textChangesContext = {context.textChangesContext->host, + context.textChangesContext->formatContext, + context.textChangesContext->preferences}; + + auto fileTextChanges = ChangeTracker::With(textChangesContext, [&](ChangeTracker &tracker) { + tracker.ReplaceNode(context.context, func, reinterpret_cast(funcDecl), {}); + }); + + return fileTextChanges; +} + +RefactorEditInfo GetRefactorEditsToConvertFunctionExpressions(RefactorContext &context, std::string_view actionName) +{ + const auto info = GetFunctionInfo(context.context, context.span.pos); + if (info.func == nullptr) { + return RefactorEditInfo {}; + } + std::vector edits; + if (actionName == TO_NAMED_FUNCTION_ACTION.name) { + const auto variableInfo = GetVariableInfo(info.func); + if (!variableInfo.has_value()) { + return RefactorEditInfo {}; + } + if (!info.func->IsArrowFunctionExpression()) { + return RefactorEditInfo {}; + } + const auto edit = + GetEditInfoForConvertToNamedFunction(context, info.func->AsArrowFunctionExpression(), variableInfo.value()); + if (edit.empty()) { + return RefactorEditInfo {}; + } + edits.insert(edits.end(), edit.begin(), edit.end()); + } else if (actionName == TO_ARROW_FUNCTION_ACTION.name) { + const auto funcDecl = info.func; + if (funcDecl == nullptr || !funcDecl->IsFunctionDeclaration()) { + return RefactorEditInfo {}; + } + const auto edit = GetEditInfoForConvertToArrowFunction(context, funcDecl->AsFunctionDeclaration()); + if (edit.empty()) { + return RefactorEditInfo {}; + } + edits.insert(edits.end(), edit.begin(), edit.end()); + } + + return RefactorEditInfo(edits); +} + std::unique_ptr ConvertFunctionRefactor::GetEditsForAction(const RefactorContext &context, const std::string &actionName) const { - (void)context; - (void)actionName; - return std::make_unique(); + RefactorContext mutableCopyCtx = context; + RefactorEditInfo refactor = GetRefactorEditsToConvertFunctionExpressions(mutableCopyCtx, actionName); + return refactor.GetFileTextChanges().empty() ? nullptr : std::make_unique(std::move(refactor)); } // NOLINTNEXTLINE(fuchsia-statically-constructed-objects, cert-err58-cpp) AutoRefactorRegister g_convertFunctionRefactorRegister("ConvertFunctionRefactor"); diff --git a/ets2panda/test/unit/lsp/CMakeLists.txt b/ets2panda/test/unit/lsp/CMakeLists.txt index 0efe307553..6582a73bc9 100644 --- a/ets2panda/test/unit/lsp/CMakeLists.txt +++ b/ets2panda/test/unit/lsp/CMakeLists.txt @@ -479,10 +479,13 @@ ets2panda_add_gtest(lsp_api_get_node_ts_nonnull_expression CPP_SOURCES get_node_ts_nonnull_expression_test.cpp ) +ets2panda_add_gtest(lsp_api_convert_function_test CPP_SOURCES + convert_function_test.cpp +) + ets2panda_add_gtest(lsp_api_get_node_function_declaration_test CPP_SOURCES get_node_function_declaration_test.cpp ) - ets2panda_add_gtest(lsp_api_get_node_info_interface_test CPP_SOURCES get_node_info_interface_test.cpp ) diff --git a/ets2panda/test/unit/lsp/convert_function_test.cpp b/ets2panda/test/unit/lsp/convert_function_test.cpp new file mode 100644 index 0000000000..d6f567ee67 --- /dev/null +++ b/ets2panda/test/unit/lsp/convert_function_test.cpp @@ -0,0 +1,232 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "ir/astNode.h" +#include +#include "lsp_api_test.h" +#include "public/es2panda_lib.h" +#include "lsp/include/internal_api.h" +#include "lsp/include/refactors/refactor_types.h" +#include "lsp/include/refactors/convert_function.h" +#include "lsp/include/services/text_change/text_change_context.h" + +namespace { +class LSPConvertFunction : public LSPAPITests { +public: + static constexpr std::string_view TO_NAMED_FUNCTION_KIND = "refactor.rewrite.function.named"; +}; + +TEST_F(LSPConvertFunction, ConvertFunction1Test) +{ + std::string testCode = R"( + const x = () => 42; +const y = function foo() { + const bar = () => this.x; +} +const z = 123; +)"; + + auto tempFiles = CreateTempFile({"convert_function.ets"}, {testCode}); + ASSERT_FALSE(tempFiles.empty()); + + ark::es2panda::lsp::Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(tempFiles[0].c_str(), ES2PANDA_STATE_PARSED); + auto context = reinterpret_cast(ctx); + auto ast = context->parserProgram->Ast(); + ark::es2panda::ir::AstNode *nodeToTry = nullptr; + ast->FindChild([&nodeToTry](ark::es2panda::ir::AstNode *node) { + if (node->IsVariableDeclaration()) { + const auto varDecl = node->AsVariableDeclaration(); + const auto decl = ark::es2panda::lsp::TryGetFunctionFromVariableDeclaration(varDecl); + nodeToTry = decl; + if (nodeToTry != nullptr && (nodeToTry->IsArrowFunctionExpression() || nodeToTry->IsFunctionExpression())) { + return true; + } + } + return false; + }); + ASSERT_TRUE(nodeToTry != nullptr && (nodeToTry->IsArrowFunctionExpression() || nodeToTry->IsFunctionExpression())); + initializer.DestroyContext(ctx); +} + +TEST_F(LSPConvertFunction, ContainingThisTest) +{ + std::string testCode = R"( + function foo() { + const bar = () => this.x; +} +)"; + auto tempFiles = CreateTempFile({"containing_this.ets"}, {testCode}); + ASSERT_FALSE(tempFiles.empty()); + const size_t funPos = 26; + const size_t thisPos = 38; + ark::es2panda::lsp::Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(tempFiles[0].c_str(), ES2PANDA_STATE_PARSED); + const auto func = ark::es2panda::lsp::GetFunctionInfo(ctx, funPos); + ASSERT_EQ(func.func->Start().index, thisPos); + initializer.DestroyContext(ctx); +} + +TEST_F(LSPConvertFunction, GetVariableInfoTest) +{ + std::string testCode = R"( + const foo = () => { + console.log("hello"); +}; +)"; + auto tempFiles = CreateTempFile({"get_variable_info_test.ets"}, {testCode}); + ASSERT_FALSE(tempFiles.empty()); + const char *name = "foo"; + ark::es2panda::lsp::Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(tempFiles[0].c_str(), ES2PANDA_STATE_PARSED); + auto context = reinterpret_cast(ctx); + auto ast = context->parserProgram->Ast(); + auto decl = ast->FindChild([](auto *node) { return node->IsArrowFunctionExpression(); }); + ASSERT_TRUE(decl != nullptr && decl->IsArrowFunctionExpression()); + auto varInfo = ark::es2panda::lsp::GetVariableInfo(decl); + ASSERT_TRUE(varInfo.has_value()); + ASSERT_TRUE(varInfo->name != nullptr); + ASSERT_EQ(varInfo->name->AsIdentifier()->Name().Utf8(), name); + + initializer.DestroyContext(ctx); +} + +TEST_F(LSPConvertFunction, ConvertArrowFunctionToNamedFunction) +{ + std::string source = R"( + const add = (a: number, b: number) => a + b; + )"; + + auto tempFiles = CreateTempFile({"convert_arrow_func_to_named_function.ets"}, {source}); + ASSERT_FALSE(tempFiles.empty()); + ark::es2panda::lsp::Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(tempFiles[0].c_str(), ES2PANDA_STATE_PARSED); + auto context = reinterpret_cast(ctx); + auto ast = context->parserProgram->Ast(); + auto decl = ast->FindChild([](auto *node) { return node->IsArrowFunctionExpression(); }); + ASSERT_TRUE(decl != nullptr && decl->IsArrowFunctionExpression()); + ark::es2panda::lsp::RefactorContext refactorContext; + ark::es2panda::lsp::FormatCodeSettings settings; + auto formatContext = ark::es2panda::lsp::GetFormatContext(settings); + TextChangesContext textChangesContext = {{}, formatContext, {}}; + refactorContext.textChangesContext = &textChangesContext; + refactorContext.context = ctx; + refactorContext.kind = std::string(TO_NAMED_FUNCTION_KIND); + refactorContext.span.pos = decl->Start().index; + auto info = ark::es2panda::lsp::GetVariableInfo(decl); + auto varInfo = ark::es2panda::lsp::GetEditInfoForConvertToNamedFunction( + refactorContext, decl->AsArrowFunctionExpression(), info.value()); + ASSERT_FALSE(varInfo.empty()); + const auto &fileChange = varInfo.front(); + auto change = fileChange.textChanges[0].span.start; + const size_t expectedPos = 9; // position of 'add' in source code + const std::string changeText = "function add(a: number, b: number) {\n return ((a) + (b));\n}\n"; + ASSERT_EQ(fileChange.textChanges[0].newText, changeText); + ASSERT_EQ(change, expectedPos); + ASSERT_FALSE(fileChange.textChanges.empty()); + initializer.DestroyContext(ctx); +} + +TEST_F(LSPConvertFunction, ConvertNamedFunctionToArrowFunction) +{ + std::string source = "function A(a:number, b:number):boolean{ return true;}"; + auto tempFiles = CreateTempFile({"convert_named_to_arrow_function.ets"}, {source}); + ASSERT_FALSE(tempFiles.empty()); + ark::es2panda::lsp::Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(tempFiles[0].c_str(), ES2PANDA_STATE_PARSED); + auto context = reinterpret_cast(ctx); + auto ast = context->parserProgram->Ast(); + auto decl = ast->FindChild([](auto *node) { return node->IsFunctionDeclaration(); }); + ASSERT_TRUE(decl != nullptr && decl->IsFunctionDeclaration()); + ark::es2panda::lsp::RefactorContext refactorContext; + ark::es2panda::lsp::FormatCodeSettings settings; + auto formatContext = ark::es2panda::lsp::GetFormatContext(settings); + TextChangesContext textChangesContext = {{}, formatContext, {}}; + refactorContext.textChangesContext = &textChangesContext; + refactorContext.context = ctx; + refactorContext.kind = std::string(TO_NAMED_FUNCTION_KIND); + refactorContext.span.pos = decl->Start().index; + auto varInfo = + ark::es2panda::lsp::GetEditInfoForConvertToArrowFunction(refactorContext, decl->AsFunctionDeclaration()); + ASSERT_FALSE(varInfo.empty()); + const auto &fileChange = varInfo.front(); + auto change = fileChange.textChanges[0].span.start; + const std::string changeText = "((a: number, b: number) => {\n return true;\n})"; + ASSERT_EQ(fileChange.textChanges[0].newText, changeText); + ASSERT_EQ(change, decl->Start().index); + ASSERT_FALSE(fileChange.textChanges.empty()); + initializer.DestroyContext(ctx); +} + +TEST_F(LSPConvertFunction, ConvertFunctionDeclaration_ToArrowFunction) +{ + std::string source = "function A(a:number, b:number):boolean{ return true;}"; + auto tempFiles = CreateTempFile({"convert_arrow_func_to_named_function_2.ets"}, {source}); + ASSERT_FALSE(tempFiles.empty()); + ark::es2panda::lsp::Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(tempFiles[0].c_str(), ES2PANDA_STATE_PARSED); + auto context = reinterpret_cast(ctx); + auto ast = context->parserProgram->Ast(); + auto decl = ast->FindChild([](auto *node) { return node->IsFunctionDeclaration(); }); + ASSERT_TRUE(decl != nullptr && decl->IsFunctionDeclaration()); + ark::es2panda::lsp::RefactorContext refactorContext; + ark::es2panda::lsp::FormatCodeSettings settings; + auto formatContext = ark::es2panda::lsp::GetFormatContext(settings); + TextChangesContext textChangesContext = {{}, formatContext, {}}; + refactorContext.textChangesContext = &textChangesContext; + refactorContext.context = ctx; + refactorContext.kind = std::string(TO_NAMED_FUNCTION_KIND); + refactorContext.span.pos = decl->Start().index; + auto result = ark::es2panda::lsp::GetRefactorEditsToConvertFunctionExpressions( + refactorContext, ark::es2panda::lsp::TO_ARROW_FUNCTION_ACTION.name); + + ASSERT_FALSE(result.GetFileTextChanges().empty()); + const std::string changeText = "((a: number, b: number) => {\n return true;\n})"; + ASSERT_EQ(result.GetFileTextChanges().front().textChanges[0].newText, changeText); +} + +TEST_F(LSPConvertFunction, ConvertArrowFunction_ToNamedFunction) +{ + std::string source = R"( + const add = (a: number, b: number) => a + b; + )"; + + auto tempFiles = CreateTempFile({"convert_arrow_func_to_named_function_1.ets"}, {source}); + ASSERT_FALSE(tempFiles.empty()); + ark::es2panda::lsp::Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(tempFiles[0].c_str(), ES2PANDA_STATE_PARSED); + auto context = reinterpret_cast(ctx); + auto ast = context->parserProgram->Ast(); + auto decl = ast->FindChild([](auto *node) { return node->IsArrowFunctionExpression(); }); + ASSERT_TRUE(decl != nullptr && decl->IsArrowFunctionExpression()); + ark::es2panda::lsp::RefactorContext refactorContext; + ark::es2panda::lsp::FormatCodeSettings settings; + auto formatContext = ark::es2panda::lsp::GetFormatContext(settings); + TextChangesContext textChangesContext = {{}, formatContext, {}}; + refactorContext.textChangesContext = &textChangesContext; + refactorContext.context = ctx; + refactorContext.kind = std::string(TO_NAMED_FUNCTION_KIND); + refactorContext.span.pos = decl->Start().index; + auto result = ark::es2panda::lsp::GetRefactorEditsToConvertFunctionExpressions( + refactorContext, ark::es2panda::lsp::TO_NAMED_FUNCTION_ACTION.name); + + ASSERT_FALSE(result.GetFileTextChanges().empty()); + const std::string changeText = "function add(a: number, b: number) {\n return ((a) + (b));\n}\n"; + ASSERT_EQ(result.GetFileTextChanges().front().textChanges[0].newText, changeText); +} + +} // namespace \ No newline at end of file -- Gitee