diff --git a/ets2panda/lsp/BUILD.gn b/ets2panda/lsp/BUILD.gn index 78dec71308cb8958669941d823fa4f3e3ce87f48..28cbfcaf2773434dc7ce4c1c99fc4f0df4b87f10 100644 --- a/ets2panda/lsp/BUILD.gn +++ b/ets2panda/lsp/BUILD.gn @@ -81,15 +81,16 @@ ohos_source_set("libes2panda_lsp_static") { "src/refactors/refactor_types.cpp", "src/references.cpp", "src/register_code_fix/add_missing_declare_property.cpp", - "src/register_code_fix/fix_class_doesnt_implement_inherited_abstract_member.cpp", "src/register_code_fix/convert_const_to_let.cpp", + "src/register_code_fix/fix_add_function_return_statement.cpp", + "src/register_code_fix/fix_class_doesnt_implement_inherited_abstract_member.cpp", "src/register_code_fix/fix_missing_call_parantheses.cpp", "src/register_code_fix/fix_nan_equality.cpp", "src/register_code_fix/forgetten_this_property_access.cpp", "src/register_code_fix/import_fixes.cpp", "src/register_code_fix/remove_accidental_call_parentheses.cpp", - "src/rename.cpp", "src/register_code_fix/ui_plugin_suggest.cpp", + "src/rename.cpp", "src/script_element_kind.cpp", "src/services/services.cpp", "src/services/text_change/change_tracker.cpp", diff --git a/ets2panda/lsp/CMakeLists.txt b/ets2panda/lsp/CMakeLists.txt index bb3d59457c48eef00230ab2c6729276cd534dbc4..ba609dcadcdb01a86178938991adfca168a51592 100644 --- a/ets2panda/lsp/CMakeLists.txt +++ b/ets2panda/lsp/CMakeLists.txt @@ -118,6 +118,7 @@ set(ES2PANDA_LSP_SRC ./src/register_code_fix/forgetten_this_property_access.cpp ./src/register_code_fix/import_fixes.cpp ./src/register_code_fix/remove_accidental_call_parentheses.cpp + ./src/register_code_fix/fix_add_function_return_statement.cpp ./src/register_code_fix/ui_plugin_suggest.cpp ./src/get_name_or_dotted_name_span.cpp ) diff --git a/ets2panda/lsp/include/register_code_fix/fix_add_function_return_statement.h b/ets2panda/lsp/include/register_code_fix/fix_add_function_return_statement.h new file mode 100644 index 0000000000000000000000000000000000000000..f7f5189a99a846d629d1fca45c4cfc2dfe00f2a3 --- /dev/null +++ b/ets2panda/lsp/include/register_code_fix/fix_add_function_return_statement.h @@ -0,0 +1,69 @@ +/** + * 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. + */ + +#ifndef FIX_ADD_FUNCTION_RETURN_STATEMENT_H +#define FIX_ADD_FUNCTION_RETURN_STATEMENT_H + +#include +#include "../services/text_change/change_tracker.h" +#include "lsp/include/types.h" +#include "lsp/include/code_fixes/code_fix_types.h" +#include "utils/arena_containers.h" + +namespace ark::es2panda::lsp { + +class FixAddFunctionReturnStatement : public CodeFixRegistration { +public: + FixAddFunctionReturnStatement(); + + std::vector GetCodeActions(const CodeFixContext &context) override; + + CombinedCodeActions GetAllCodeActions(const CodeFixAllContext &ctx) override; +}; + +struct Info { +private: + ark::es2panda::ir::AstNode *returnTypeNode_; + ark::es2panda::ir::AstNode *body_; + std::vector statements_; + +public: + Info(ark::es2panda::ir::AstNode *returnTypeNode, ark::es2panda::ir::AstNode *body, + std::vector statements) + : returnTypeNode_(returnTypeNode), body_(body), statements_(std::move(statements)) + { + } + ark::es2panda::ir::AstNode *GetReturnTypeNode() const + { + return returnTypeNode_; + } + ark::es2panda::ir::AstNode *GetBody() const + { + return body_; + } + const std::vector &GetStatements() const + { + return statements_; + } +}; + +ir::AstNode *FindAncessor(ir::AstNode *node); +Info GetInfo(es2panda_Context *context, size_t position); +void ReplaceReturnType(ChangeTracker &changes, es2panda_Context *context, Info &info); +void AddReturnStatement(ChangeTracker &changes, es2panda_Context *context, std::vector statements, + ir::AstNode *body); + +} // namespace ark::es2panda::lsp +#endif diff --git a/ets2panda/lsp/src/register_code_fix/fix_add_function_return_statement.cpp b/ets2panda/lsp/src/register_code_fix/fix_add_function_return_statement.cpp new file mode 100644 index 0000000000000000000000000000000000000000..51beebf135f06381ca8df9a5ad6711611bc1bba1 --- /dev/null +++ b/ets2panda/lsp/src/register_code_fix/fix_add_function_return_statement.cpp @@ -0,0 +1,147 @@ +/** + * 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 "lsp/include/register_code_fix/fix_add_function_return_statement.h" +#include +#include +#include +#include +#include +#include "generated/code_fix_register.h" +#include "lsp/include/code_fix_provider.h" +#include "lsp/include/internal_api.h" +#include "public/es2panda_lib.h" + +namespace ark::es2panda::lsp { +using codefixes::FIX_ADD_FUNCTION_RETURN_STATEMENT; + +FixAddFunctionReturnStatement::FixAddFunctionReturnStatement() +{ + auto errorCodes = FIX_ADD_FUNCTION_RETURN_STATEMENT.GetSupportedCodeNumbers(); + SetErrorCodes({errorCodes.begin(), errorCodes.end()}); // change this to the error code you want to handle + SetFixIds({FIX_ADD_FUNCTION_RETURN_STATEMENT.GetFixId().data()}); +} + +std::vector FixAddFunctionReturnStatement::GetCodeActions(const CodeFixContext &context) +{ + std::vector returnedActions; + auto info = GetInfo(context.context, context.span.start); + if (info.GetReturnTypeNode() == nullptr || info.GetBody() == nullptr) { + return returnedActions; // No valid return type or body found + } + + TextChangesContext textChangesContext {context.host, context.formatContext, context.preferences}; + auto replaceReturnTypeChanges = ChangeTracker::With( + textChangesContext, [&](ChangeTracker &tracker) { ReplaceReturnType(tracker, context.context, info); }); + auto addReturnStatementChanges = ChangeTracker::With(textChangesContext, [&](ChangeTracker &tracker) { + AddReturnStatement(tracker, context.context, info.GetStatements(), info.GetBody()); + }); + CodeFixAction action; + action.fixName = FIX_ADD_FUNCTION_RETURN_STATEMENT.GetFixId().data(); + action.description = "Add missing return statement"; + action.fixId = FIX_ADD_FUNCTION_RETURN_STATEMENT.GetFixId().data(); + action.fixAllDescription = "Add all missing return statement"; + action.changes.insert(action.changes.end(), replaceReturnTypeChanges.begin(), replaceReturnTypeChanges.end()); + action.changes.insert(action.changes.end(), addReturnStatementChanges.begin(), addReturnStatementChanges.end()); + returnedActions.push_back(action); + + return returnedActions; +} + +CombinedCodeActions FixAddFunctionReturnStatement::GetAllCodeActions([[maybe_unused]] const CodeFixAllContext &ctx) +{ + CombinedCodeActions combinedActions; + return combinedActions; +} + +Info GetInfo(es2panda_Context *context, size_t position) +{ + const auto token = GetDefinitionAtPositionImpl(context, position); + const auto node = token.first; + const auto declaration = FindAncessor(node); + if (!declaration->IsFunctionExpression()) { + return Info(nullptr, nullptr, {}); + } + const auto returnTypeNode = declaration->AsFunctionExpression()->Function()->ReturnTypeAnnotation(); + if (returnTypeNode == nullptr || !returnTypeNode->IsETSTypeReference()) { + return Info(nullptr, nullptr, {}); + } + if (!declaration->AsFunctionExpression()->Function()->Body()->IsBlockStatement()) { + return Info(nullptr, nullptr, {}); + } + + const auto body = declaration->AsFunctionExpression()->Function()->Body(); + const auto statements = body->AsBlockStatement()->Statements(); + return Info(returnTypeNode, body, {statements.begin(), statements.end()}); +} + +void ReplaceReturnType(ChangeTracker &changes, es2panda_Context *context, Info &info) +{ + auto ctx = reinterpret_cast(context); + const auto &statements = info.GetStatements(); + bool statementFlag = false; + for (const auto &statement : statements) { + if (statement->IsReturnStatement()) { + statementFlag = true; + break; + } + } + if (statementFlag) { + return; + } + auto newNode = info.GetReturnTypeNode()->Clone(ctx->Allocator(), info.GetReturnTypeNode()->Parent()); + if (!newNode->IsETSTypeReference()) { + return; // Not a valid type reference node + } + auto typeRef = newNode->AsETSTypeReference(); + if (typeRef->Part()->IsETSTypeReferencePart()) { + auto part = typeRef->Part()->AsETSTypeReferencePart(); + part->GetIdent()->SetName("void"); // Change the type to 'void' + } + + changes.ReplaceNode(context, info.GetReturnTypeNode(), newNode, {}); +} +void AddReturnStatement(ChangeTracker &changes, es2panda_Context *context, std::vector statements, + ir::AstNode *body) +{ + const auto impl = es2panda_GetImpl(ES2PANDA_LIB_VERSION); + if (!statements.empty()) { + // If the body is empty, we can add a return statement + auto *returnStmt = impl->CreateReturnStatement(context); + changes.InsertNodeAfter(context, statements[statements.size() - 1]->AsStatement(), + reinterpret_cast(returnStmt)); + } else { + size_t newSize = statements.size() + 1; + std::vector newStatementsVec(newSize, nullptr); + for (size_t i = 0; i < statements.size(); ++i) { + newStatementsVec[i] = reinterpret_cast(statements[i]->AsStatement()); + } + newStatementsVec[statements.size()] = impl->CreateReturnStatement(context); + auto newBody = impl->CreateBlockStatement(context, newStatementsVec.data(), statements.size()); + changes.ReplaceNode(context, body->AsBlockStatement(), reinterpret_cast(newBody), {}); + } +} + +ir::AstNode *FindAncessor(ir::AstNode *node) +{ + if (node->IsFunctionDeclaration() || node->IsFunctionExpression()) { + return node; + } + return FindAncessor(node->Parent()); +} + +// NOLINTNEXTLINE(fuchsia-statically-constructed-objects, cert-err58-cpp) +AutoCodeFixRegister g_fixAddFunctionReturnStatement("FixAddFunctionReturnStatement"); +} // namespace ark::es2panda::lsp diff --git a/ets2panda/test/unit/lsp/CMakeLists.txt b/ets2panda/test/unit/lsp/CMakeLists.txt index 06df7356e7ca56749ff9627c3445c18711ba02a4..71a614a2aa01874250e731732297fc8f34636549 100644 --- a/ets2panda/test/unit/lsp/CMakeLists.txt +++ b/ets2panda/test/unit/lsp/CMakeLists.txt @@ -250,7 +250,9 @@ ets2panda_add_gtest(lsp_api_test_code_fix_registration CPP_SOURCES ets2panda_add_gtest(lsp_api_test_fix_abstract_member CPP_SOURCES fix_class_doesnt_implement_inherited_abstract_member_test.cpp ) - +ets2panda_add_gtest(lsp_api_test_fix_add_function_return_statement_test CPP_SOURCES + fix_add_function_return_statement_test.cpp +) ets2panda_add_gtest(lsp_api_test_get_name_or_dotted_name_span CPP_SOURCES get_name_or_dotted_name_span_test.cpp ) diff --git a/ets2panda/test/unit/lsp/fix_add_function_return_statement_test.cpp b/ets2panda/test/unit/lsp/fix_add_function_return_statement_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1fe8540a8ae853336cd6bdb06cd9159e05b94e23 --- /dev/null +++ b/ets2panda/test/unit/lsp/fix_add_function_return_statement_test.cpp @@ -0,0 +1,99 @@ +/** + * 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 "gtest/gtest.h" +#include "lsp_api_test.h" +#include +#include +#include "lsp/include/register_code_fix/fix_add_function_return_statement.h" + +namespace { +class FixAddFunctionReturnStatementTests : public LSPAPITests {}; + +TEST_F(FixAddFunctionReturnStatementTests, AddMissingReturnStatement_GetInfo) +{ + const char *source = R"( +function multiply(a: number, b: number): number { +a * b; +} + )"; + ark::es2panda::lsp::Initializer initializer = ark::es2panda::lsp::Initializer(); + es2panda_Context *ctx = + initializer.CreateContext("AddMissingReturnStatement_GetInfo.ets", ES2PANDA_STATE_CHECKED, source); + const size_t position = 20; // Position of the function body start + const auto wordA = "a"; + const auto wordB = "b"; + const auto wordNum = "number"; + auto info = ark::es2panda::lsp::GetInfo(ctx, position); + const auto type = info.GetStatements().at(0)->AsExpressionStatement()->GetExpression()->Type(); + EXPECT_EQ(type, ark::es2panda::ir::AstNodeType::BINARY_EXPRESSION); + const auto left = + info.GetStatements().at(0)->AsExpressionStatement()->GetExpression()->AsBinaryExpression()->Left()->ToString(); + EXPECT_EQ(left, wordA); + const auto right = + info.GetStatements().at(0)->AsExpressionStatement()->GetExpression()->AsBinaryExpression()->Right()->ToString(); + EXPECT_EQ(right, wordB); + const auto returnTypeNode = info.GetReturnTypeNode()->AsETSTypeReference()->BaseName()->Name(); + EXPECT_EQ(returnTypeNode, wordNum); + initializer.DestroyContext(ctx); +} + +TEST_F(FixAddFunctionReturnStatementTests, AddMissingReturnStatement_ReplaceReturnType) +{ + const char *source = R"( +function multiply(a: number, b: number): string { +a; +} + )"; + ark::es2panda::lsp::Initializer initializer = ark::es2panda::lsp::Initializer(); + es2panda_Context *ctx = initializer.CreateContext("test1.ets", ES2PANDA_STATE_CHECKED, source); + const size_t position = 20; // Position of the function body start + const size_t size1 = 1; + auto info = ark::es2panda::lsp::GetInfo(ctx, position); + EXPECT_TRUE(info.GetReturnTypeNode()->IsETSTypeReference()); + ark::es2panda::lsp::FormatCodeSettings settings; + auto formatContext = ark::es2panda::lsp::GetFormatContext(settings); + TextChangesContext changeText {{}, formatContext, {}}; + ark::es2panda::lsp::ChangeTracker tracker = ark::es2panda::lsp::ChangeTracker::FromContext(changeText); + ark::es2panda::lsp::ReplaceReturnType(tracker, ctx, info); + auto changes = tracker.GetChangeList(); + EXPECT_EQ(changes.size(), size1); + initializer.DestroyContext(ctx); +} + +TEST_F(FixAddFunctionReturnStatementTests, AddMissingReturnStatement_AddReturnStatement) +{ + const char *source = R"( +function multiply(a: number, b: number): string { +a; +} + )"; + ark::es2panda::lsp::Initializer initializer = ark::es2panda::lsp::Initializer(); + es2panda_Context *ctx = + initializer.CreateContext("AddMissingReturnStatement_AddReturnStatement.ets", ES2PANDA_STATE_CHECKED, source); + const size_t position = 20; // Position of the function body start + const size_t size1 = 1; + auto info = ark::es2panda::lsp::GetInfo(ctx, position); + EXPECT_TRUE(info.GetReturnTypeNode()->IsETSTypeReference()); + ark::es2panda::lsp::FormatCodeSettings settings; + auto formatContext = ark::es2panda::lsp::GetFormatContext(settings); + TextChangesContext changeText {{}, formatContext, {}}; + ark::es2panda::lsp::ChangeTracker tracker = ark::es2panda::lsp::ChangeTracker::FromContext(changeText); + ark::es2panda::lsp::AddReturnStatement(tracker, ctx, info.GetStatements(), info.GetBody()); + auto changes = tracker.GetChangeList(); + EXPECT_EQ(changes.size(), size1); + initializer.DestroyContext(ctx); +} + +} // namespace \ No newline at end of file diff --git a/ets2panda/util/diagnostic/semantic.yaml b/ets2panda/util/diagnostic/semantic.yaml index aad4b5292a7218957136b01566f5610209990c7d..d050eaed9d2256c85bd22934ef2e5bd5d9455ec9 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -113,6 +113,10 @@ semantic: id: 12 message: "Invalid value for annotation field, expected a constant literal." +- name: ANNOTATION_INSTANTIATION + id: 396 + message: "{} is an annotation therefore cannot be instantiated." + - name: ANNOTATION_ON_LAMBDA_LOCAL_TYPE id: 5 message: Annotations without 'SOURCE' cannot be used on lambda expressions, local declarations, or types. @@ -799,6 +803,11 @@ semantic: id: 149 message: "Invalid return function expression" +- name: INVALID_RETURN_VALUE + id: 397 + message: "A function whose declared type is neither 'void' nor 'any' must return a value." + code_fix_ids: [FixAddFunctionReturnStatement] + - name: INVALID_SPREAD_IN_TUPLE id: 55 message: "'{}' cannot be spread in tuple."