diff --git a/ets2panda/lsp/BUILD.gn b/ets2panda/lsp/BUILD.gn index 5dfcae14ba3c2da739c598ba4439349d37f46eed..dd82b70fd57768a64a00439f5a4c9a3fe1150410 100644 --- a/ets2panda/lsp/BUILD.gn +++ b/ets2panda/lsp/BUILD.gn @@ -92,6 +92,7 @@ ohos_source_set("libes2panda_lsp_static") { "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_expected_comma.cpp", + "src/register_code_fix/fix_extends_interface_becomes_implements.cpp", "src/register_code_fix/fix_missing_call_parantheses.cpp", "src/register_code_fix/fix_nan_equality.cpp", "src/register_code_fix/fix_return_type_in_async_function.cpp", diff --git a/ets2panda/lsp/CMakeLists.txt b/ets2panda/lsp/CMakeLists.txt index c152fd89c3e3e84c6c4c93d971020c985e06332e..ebcd6576726807b30ed9ddeb6d07b270f429bc6d 100644 --- a/ets2panda/lsp/CMakeLists.txt +++ b/ets2panda/lsp/CMakeLists.txt @@ -113,6 +113,7 @@ set(ES2PANDA_LSP_SRC ./src/register_code_fix/add_missing_new_operator.cpp ./src/register_code_fix/fix_class_doesnt_implement_inherited_abstract_member.cpp ./src/register_code_fix/fix_expected_comma.cpp + ./src/register_code_fix/fix_extends_interface_becomes_implements.cpp ./src/register_code_fix/fix_return_type_in_async_function.cpp ./src/register_code_fix/add_missing_declare_property.cpp ./src/register_code_fix/convert_const_to_let.cpp diff --git a/ets2panda/lsp/include/register_code_fix/fix_extends_interface_becomes_implements.h b/ets2panda/lsp/include/register_code_fix/fix_extends_interface_becomes_implements.h new file mode 100644 index 0000000000000000000000000000000000000000..b0ed6fd48c3bb4a254026dae2eaacbb840164b9e --- /dev/null +++ b/ets2panda/lsp/include/register_code_fix/fix_extends_interface_becomes_implements.h @@ -0,0 +1,41 @@ +/** + * 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_EXTENDS_INTERFACE_BECOMES_IMPLEMENTS_H +#define FIX_EXTENDS_INTERFACE_BECOMES_IMPLEMENTS_H + +#include + +#include "lsp/include/code_fixes/code_fix_types.h" +#include "lsp/include/services/text_change/change_tracker.h" +#include "lsp/include/types.h" + +namespace ark::es2panda::lsp { + +class FixExtendsInterfaceBecomesImplements : public CodeFixRegistration { +public: + FixExtendsInterfaceBecomesImplements(); + + std::vector GetCodeActions(const CodeFixContext &context) override; + CombinedCodeActions GetAllCodeActions(const CodeFixAllContext &codeFixAll) override; + +private: + void MakeChangeForExtendsInterfaceBecomesImplements(ChangeTracker &changeTracker, es2panda_Context *context, + size_t pos); + std::vector GetCodeActionsToExtendsInterfaceBecomesImplements(const CodeFixContext &context); +}; + +} // namespace ark::es2panda::lsp +#endif diff --git a/ets2panda/lsp/src/register_code_fix/fix_extends_interface_becomes_implements.cpp b/ets2panda/lsp/src/register_code_fix/fix_extends_interface_becomes_implements.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9deb1bc3b2d1c1510b40a4f3c3229b078f75660f --- /dev/null +++ b/ets2panda/lsp/src/register_code_fix/fix_extends_interface_becomes_implements.cpp @@ -0,0 +1,116 @@ +/** + * 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_extends_interface_becomes_implements.h" + +#include "compiler/lowering/util.h" +#include "generated/code_fix_register.h" +#include "lsp/include/code_fix_provider.h" +#include "lsp/include/internal_api.h" + +namespace ark::es2panda::lsp { +using codefixes::EXTENDS_INTERFACE_BECOMES_IMPLEMENTS; + +constexpr std::string_view KEYW_IMPLEMENTS_STR = "implements"; + +void FixExtendsInterfaceBecomesImplements::MakeChangeForExtendsInterfaceBecomesImplements(ChangeTracker &changeTracker, + es2panda_Context *context, + size_t pos) +{ + auto *token = GetTouchingToken(context, pos, false); + + size_t changeEnd = 0; + size_t changeStart = 0; + size_t spaceChar = 1; + if (!token->IsClassDeclaration()) { + return; + } + token->FindChild([&](ir::AstNode *n) { + if (n->IsIdentifier()) { + changeStart = n->End().index + spaceChar; + return true; + } + return false; + }); + + token->FindChild([&](ir::AstNode *n) { + if (n->IsETSTypeReference()) { + changeEnd = n->Start().index - spaceChar; + return true; + } + return false; + }); + + TextRange extendsRange = {changeStart, changeEnd}; + const std::string replaceText(KEYW_IMPLEMENTS_STR); + auto astContext = reinterpret_cast(context); + changeTracker.ReplaceRangeWithText(astContext->sourceFile, extendsRange, replaceText); +} + +std::vector FixExtendsInterfaceBecomesImplements::GetCodeActionsToExtendsInterfaceBecomesImplements( + const CodeFixContext &context) +{ + TextChangesContext textChangesContext = {context.host, context.formatContext, context.preferences}; + auto fileTextChanges = ChangeTracker::With(textChangesContext, [&](ChangeTracker &tracker) { + MakeChangeForExtendsInterfaceBecomesImplements(tracker, context.context, context.span.start); + }); + + return fileTextChanges; +} + +FixExtendsInterfaceBecomesImplements::FixExtendsInterfaceBecomesImplements() +{ + auto errorCodes = EXTENDS_INTERFACE_BECOMES_IMPLEMENTS.GetSupportedCodeNumbers(); + SetErrorCodes({errorCodes.begin(), errorCodes.end()}); + SetFixIds({EXTENDS_INTERFACE_BECOMES_IMPLEMENTS.GetFixId().data()}); +} + +std::vector FixExtendsInterfaceBecomesImplements::GetCodeActions(const CodeFixContext &context) +{ + std::vector returnedActions; + auto changes = GetCodeActionsToExtendsInterfaceBecomesImplements(context); + if (!changes.empty()) { + CodeFixAction codeAction; + codeAction.fixName = EXTENDS_INTERFACE_BECOMES_IMPLEMENTS.GetFixId().data(); + codeAction.description = "Change 'extends' to 'implements'"; + codeAction.changes = changes; + codeAction.fixId = EXTENDS_INTERFACE_BECOMES_IMPLEMENTS.GetFixId().data(); + codeAction.fixAllDescription = "Change all 'extends' on interfaces to 'implements'"; + returnedActions.push_back(codeAction); + } + + return returnedActions; +} + +CombinedCodeActions FixExtendsInterfaceBecomesImplements::GetAllCodeActions(const CodeFixAllContext &codeFixAllCtx) +{ + CodeFixProvider provider; + const auto changes = provider.CodeFixAll( + codeFixAllCtx, GetErrorCodes(), [&](ChangeTracker &tracker, const DiagnosticWithLocation &diag) { + MakeChangeForExtendsInterfaceBecomesImplements(tracker, codeFixAllCtx.context, diag.GetStart()); + }); + + CombinedCodeActions combinedCodeActions; + combinedCodeActions.changes = changes.changes; + combinedCodeActions.commands = changes.commands; + + return combinedCodeActions; +} + +// NOLINTNEXTLINE +AutoCodeFixRegister g_FixExtendsInterfaceBecomesImplements( + EXTENDS_INTERFACE_BECOMES_IMPLEMENTS.GetFixId().data()); + +} // namespace ark::es2panda::lsp \ No newline at end of file diff --git a/ets2panda/test/unit/lsp/CMakeLists.txt b/ets2panda/test/unit/lsp/CMakeLists.txt index a8eb6f31107ce8814233435c0ccfaa21195b7d18..5611ef98b183a226e27c2dfa2b9bb2452fefee94 100644 --- a/ets2panda/test/unit/lsp/CMakeLists.txt +++ b/ets2panda/test/unit/lsp/CMakeLists.txt @@ -278,6 +278,10 @@ ets2panda_add_gtest(lsp_api_test_fix_convert_const_to_let CPP_SOURCES fix_convert_const_to_let_test.cpp ) +ets2panda_add_gtest(lsp_api_test_fix_extends_interface_becomes_implements CPP_SOURCES + fix_extends_interface_becomes_implements_test.cpp +) + ets2panda_add_gtest(lsp_api_test_forgotten_this_property_access CPP_SOURCES forgotten_this_property_access_test.cpp ) diff --git a/ets2panda/test/unit/lsp/fix_extends_interface_becomes_implements_test.cpp b/ets2panda/test/unit/lsp/fix_extends_interface_becomes_implements_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..45caee40fedd4ad2aadc338600b4b5f6a6227d14 --- /dev/null +++ b/ets2panda/test/unit/lsp/fix_extends_interface_becomes_implements_test.cpp @@ -0,0 +1,98 @@ +/** + * 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_api_test.h" + +#include + +#include "lsp/include/api.h" +#include "lsp/include/cancellation_token.h" +#include "lsp/include/register_code_fix/fix_extends_interface_becomes_implements.h" + +namespace { + +using ark::es2panda::lsp::Initializer; +using ark::es2panda::lsp::codefixes::EXTENDS_INTERFACE_BECOMES_IMPLEMENTS; + +constexpr std::string_view EXPECTED_FIX_NAME = EXTENDS_INTERFACE_BECOMES_IMPLEMENTS.GetFixId(); +constexpr auto ERROR_CODES = EXTENDS_INTERFACE_BECOMES_IMPLEMENTS.GetSupportedCodeNumbers(); +constexpr std::string_view EXPECTED_FIX_DESCRIPTION = "Change 'extends' to 'implements'"; +constexpr std::string_view EXPECTED_TEXT_CHANGE_NEW_TEXT = "implements"; +constexpr int DEFAULT_THROTTLE = 20; + +class FixExtendsInterfaceBecomesImplementsTests : public LSPAPITests { +public: + static ark::es2panda::lsp::CancellationToken CreateNonCancellationToken() + { + return ark::es2panda::lsp::CancellationToken(DEFAULT_THROTTLE, &GetNullHost()); + } + + static size_t LineColToPos(es2panda_Context *context, const size_t line, const size_t col) + { + auto ctx = reinterpret_cast(context); + auto index = ark::es2panda::lexer::LineIndex(ctx->parserProgram->SourceCode()); + return index.GetOffset(ark::es2panda::lexer::SourceLocation(line, col, ctx->parserProgram)); + } + +private: + class NullCancellationToken : public ark::es2panda::lsp::HostCancellationToken { + public: + bool IsCancellationRequested() override + { + return false; + } + }; + + static NullCancellationToken &GetNullHost() + { + static NullCancellationToken instance; + return instance; + } +}; + +TEST_F(FixExtendsInterfaceBecomesImplementsTests, TestFixExtendsToImplements) +{ + std::vector fileNames = {"FixExtendsToImplements.ets"}; + std::vector fileContents = {R"( +interface IFoo {} +class Bar extends IFoo {} +)"}; + auto filePaths = CreateTempFile(fileNames, fileContents); + ASSERT_EQ(fileNames.size(), filePaths.size()); + + Initializer initializer; + auto *context = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + const size_t start = LineColToPos(context, 3, 13); // inside "extends" + const size_t length = 1; + + std::vector errorCodes(ERROR_CODES.begin(), ERROR_CODES.end()); + CodeFixOptions emptyOptions = {CreateNonCancellationToken(), ark::es2panda::lsp::FormatCodeSettings(), {}}; + + auto fixResult = + ark::es2panda::lsp::GetCodeFixesAtPositionImpl(context, start, start + length, errorCodes, emptyOptions); + ASSERT_EQ(fixResult.size(), 1); + const auto &textChange = fixResult[0].changes_[0].textChanges[0]; + + EXPECT_EQ(fixResult[0].fixName_, EXPECTED_FIX_NAME); + EXPECT_EQ(fixResult[0].fixId_, EXPECTED_FIX_NAME); + EXPECT_EQ(fixResult[0].description_, EXPECTED_FIX_DESCRIPTION); + EXPECT_EQ(fixResult[0].changes_[0].fileName, filePaths[0]); + EXPECT_EQ(textChange.newText, EXPECTED_TEXT_CHANGE_NEW_TEXT); + + initializer.DestroyContext(context); +} + +} // namespace \ No newline at end of file diff --git a/ets2panda/util/diagnostic/semantic.yaml b/ets2panda/util/diagnostic/semantic.yaml index cd59e9ec84e35644e64de3f533c63c5de23c3e9b..cea1e3cd485343342ed866a1cd740cd11babbae6 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -494,6 +494,7 @@ semantic: - name: EXTENDING_UTILITY_TYPE id: 176 message: "The super type of '{}' class is not extensible." + code_fix_ids: [extendsInterfaceBecomesImplements] - name: EXTENDS_NON_OBJECT id: 185