diff --git a/ets2panda/lsp/BUILD.gn b/ets2panda/lsp/BUILD.gn index 5927ee5e6810eedf918db2d3578f072f8c86fcb8..a776effebcd65fc2840dc2ea033a9338a028ad23 100644 --- a/ets2panda/lsp/BUILD.gn +++ b/ets2panda/lsp/BUILD.gn @@ -94,6 +94,7 @@ ohos_source_set("libes2panda_lsp_static") { "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_class_incorrectly_implements_interface.cpp", "src/register_code_fix/fix_class_super_must_precede_this_access.cpp", "src/register_code_fix/fix_expected_comma.cpp", "src/register_code_fix/fix_extends_interface_becomes_implements.cpp", diff --git a/ets2panda/lsp/CMakeLists.txt b/ets2panda/lsp/CMakeLists.txt index 0126ccf9b1593baf081b0844b0194678b62c1435..b416becde5dba954978bd88cc7cb08fc872cfbfe 100644 --- a/ets2panda/lsp/CMakeLists.txt +++ b/ets2panda/lsp/CMakeLists.txt @@ -126,6 +126,7 @@ set(ES2PANDA_LSP_SRC ./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/register_code_fix/fix_class_incorrectly_implements_interface.cpp ./src/get_signature.cpp ./src/get_name_or_dotted_name_span.cpp ./src/get_node.cpp diff --git a/ets2panda/lsp/include/register_code_fix/fix_class_incorrectly_implements_interface.h b/ets2panda/lsp/include/register_code_fix/fix_class_incorrectly_implements_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..954f25160159b5542ff4c40382a411813ed2dcd5 --- /dev/null +++ b/ets2panda/lsp/include/register_code_fix/fix_class_incorrectly_implements_interface.h @@ -0,0 +1,54 @@ +/** + * 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_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_H +#define FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_H + +#include "lsp/include/code_fixes/code_fix_types.h" +#include "lsp/include/services/text_change/change_tracker.h" +#include "public/es2panda_lib.h" + +namespace ark::es2panda::lsp { + +class FixClassIncorrectlyImplementsInterface : public CodeFixRegistration { +public: + FixClassIncorrectlyImplementsInterface(); + + std::vector GetCodeActions(const CodeFixContext &context) override; + + CombinedCodeActions GetAllCodeActions(const CodeFixAllContext &codeFixAll) override; + +private: + void MakeChangeForMissingInterfaceMembers(ChangeTracker &changeTracker, es2panda_Context *context, size_t pos); + std::vector GetCodeActionsToImplementMissingMembers(const CodeFixContext &context); + + std::string MakeNewTextForMember(ir::AstNode *node); + std::vector FindMissingInterfaceMembers(ir::ClassDefinition *classDef); + std::vector GetInterfaceDefinitions(ir::ClassDefinition *classDef); + bool IsMemberImplemented(ir::ClassDefinition *classDef, const std::string &memberSignature); + + ir::AstNode *FindInterfaceDefinition(ir::TSClassImplements *implement); + void ProcessInterfaceMembers(ir::TSInterfaceDeclaration *interface, ir::ClassDefinition *classDef, + std::vector &missingMembers); + std::string GenerateMemberSignature(ir::MethodDefinition *methodDef, const std::string &memberName); + void GroupMissingMembers(const std::vector &missingMembers, + std::vector &missingGetters, std::vector &missingSetters); + void CreateCodeActionForType(const std::vector &members, const CodeFixContext &context, + bool isGetter, std::vector &returnedActions); +}; + +} // namespace ark::es2panda::lsp + +#endif // FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_H \ No newline at end of file diff --git a/ets2panda/lsp/src/register_code_fix/fix_class_incorrectly_implements_interface.cpp b/ets2panda/lsp/src/register_code_fix/fix_class_incorrectly_implements_interface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..43d0d00c45a9e51d24aa7a19bf2403d834611a22 --- /dev/null +++ b/ets2panda/lsp/src/register_code_fix/fix_class_incorrectly_implements_interface.cpp @@ -0,0 +1,364 @@ +/** + * 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_class_incorrectly_implements_interface.h" + +#include "compiler/lowering/util.h" +#include "lsp/include/code_fix_provider.h" +#include "lsp/include/internal_api.h" +#include "ir/astNode.h" +#include "ir/base/classDefinition.h" +#include "ir/ts/tsInterfaceDeclaration.h" +#include "ir/ts/tsInterfaceBody.h" +#include "ir/ts/tsClassImplements.h" +#include "ir/base/methodDefinition.h" + +namespace ark::es2panda::lsp { +using codefixes::FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER; +using codefixes::FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER; + +std::string FixClassIncorrectlyImplementsInterface::MakeNewTextForMember(ir::AstNode *node) +{ + if (node == nullptr || !node->IsMethodDefinition()) { + return ""; + } + + auto *methodDef = node->AsMethodDefinition(); + auto methodName = methodDef->Key()->AsIdentifier()->Name(); + std::string newText; + std::string prefix = "\n\n "; + std::string suffix; + + if (methodDef->IsGetter()) { + newText = "get " + std::string(methodName) + methodDef->Function()->DumpEtsSrc(); + suffix = " {\n return null;\n }"; + } else if (methodDef->IsSetter()) { + newText = "set " + std::string(methodName) + methodDef->Function()->DumpEtsSrc(); + suffix = " {\n }"; + } else { + newText = std::string(methodName) + methodDef->Function()->DumpEtsSrc(); + suffix = " {}"; + } + + newText.insert(0, prefix); + newText.insert(newText.size() - 1, suffix); + return newText; +} + +void FixClassIncorrectlyImplementsInterface::MakeChangeForMissingInterfaceMembers(ChangeTracker &changeTracker, + es2panda_Context *context, size_t pos) +{ + auto *token = GetTouchingToken(context, pos, false); + if (token == nullptr) { + return; + } + + auto *classNode = token; + while (classNode != nullptr && !classNode->IsClassDefinition()) { + classNode = classNode->Parent(); + } + + if (classNode == nullptr || !classNode->IsClassDefinition()) { + return; + } + + auto *classDef = classNode->AsClassDefinition(); + std::vector missingMembers = FindMissingInterfaceMembers(classDef); + if (missingMembers.empty()) { + return; + } + + auto classBody = classDef->Body(); + size_t insertPos = classDef->End().index; + if (!classBody.empty()) { + auto lastElement = classBody.back(); + insertPos = lastElement->End().index; + } + + std::string newText; + for (auto *missingMember : missingMembers) { + newText += MakeNewTextForMember(missingMember); + } + + if (!newText.empty()) { + TextRange insertRange = {insertPos, insertPos}; + auto astContext = reinterpret_cast(context); + changeTracker.ReplaceRangeWithText(astContext->sourceFile, insertRange, newText); + } +} + +std::vector FixClassIncorrectlyImplementsInterface::GetCodeActionsToImplementMissingMembers( + const CodeFixContext &context) +{ + TextChangesContext textChangesContext = {context.host, context.formatContext, context.preferences}; + + auto fileTextChanges = ChangeTracker::With(textChangesContext, [&](ChangeTracker &tracker) { + MakeChangeForMissingInterfaceMembers(tracker, context.context, context.span.start); + }); + + return fileTextChanges; +} + +FixClassIncorrectlyImplementsInterface::FixClassIncorrectlyImplementsInterface() +{ + auto getterErrorCodes = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER.GetSupportedCodeNumbers(); + auto setterErrorCodes = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER.GetSupportedCodeNumbers(); + + std::vector allErrorCodes; + allErrorCodes.insert(allErrorCodes.end(), getterErrorCodes.begin(), getterErrorCodes.end()); + allErrorCodes.insert(allErrorCodes.end(), setterErrorCodes.begin(), setterErrorCodes.end()); + + SetErrorCodes(allErrorCodes); + SetFixIds({FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER.GetFixId().data(), + FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER.GetFixId().data()}); +} + +void FixClassIncorrectlyImplementsInterface::GroupMissingMembers(const std::vector &missingMembers, + std::vector &missingGetters, + std::vector &missingSetters) +{ + for (auto *member : missingMembers) { + if (!member->IsMethodDefinition()) { + continue; + } + + auto *methodDef = member->AsMethodDefinition(); + if (methodDef->IsGetter()) { + missingGetters.push_back(member); + } else if (methodDef->IsSetter()) { + missingSetters.push_back(member); + } + } +} + +void FixClassIncorrectlyImplementsInterface::CreateCodeActionForType(const std::vector &members, + const CodeFixContext &context, bool isGetter, + std::vector &returnedActions) +{ + if (members.empty()) { + return; + } + + auto changes = GetCodeActionsToImplementMissingMembers(context); + if (changes.empty()) { + return; + } + + CodeFixAction codeAction; + if (isGetter) { + codeAction.fixName = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER.GetFixId().data(); + codeAction.description = "Add missing interface getter implementations"; + codeAction.fixAllDescription = "Implement all missing interface getters"; + codeAction.fixId = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER.GetFixId().data(); + } else { + codeAction.fixName = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER.GetFixId().data(); + codeAction.description = "Add missing interface setter implementations"; + codeAction.fixAllDescription = "Implement all missing interface setters"; + codeAction.fixId = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER.GetFixId().data(); + } + + codeAction.changes = changes; + returnedActions.push_back(codeAction); +} + +std::vector FixClassIncorrectlyImplementsInterface::GetCodeActions(const CodeFixContext &context) +{ + std::vector returnedActions; + + auto *token = GetTouchingToken(context.context, context.span.start, false); + if (token == nullptr) { + return returnedActions; + } + + auto *classNode = token; + while (classNode != nullptr && !classNode->IsClassDefinition()) { + classNode = classNode->Parent(); + } + + if (classNode == nullptr || !classNode->IsClassDefinition()) { + return returnedActions; + } + + auto *classDef = classNode->AsClassDefinition(); + std::vector missingMembers = FindMissingInterfaceMembers(classDef); + if (missingMembers.empty()) { + return returnedActions; + } + + std::vector missingGetters; + std::vector missingSetters; + GroupMissingMembers(missingMembers, missingGetters, missingSetters); + + CreateCodeActionForType(missingGetters, context, true, returnedActions); + CreateCodeActionForType(missingSetters, context, false, returnedActions); + + return returnedActions; +} + +CombinedCodeActions FixClassIncorrectlyImplementsInterface::GetAllCodeActions(const CodeFixAllContext &codeFixAllCtx) +{ + CodeFixProvider provider; + const auto changes = provider.CodeFixAll( + codeFixAllCtx, GetErrorCodes(), [&](ChangeTracker &tracker, const DiagnosticWithLocation &diag) { + MakeChangeForMissingInterfaceMembers(tracker, codeFixAllCtx.context, diag.GetStart()); + }); + + CombinedCodeActions combinedCodeActions; + combinedCodeActions.changes = changes.changes; + combinedCodeActions.commands = changes.commands; + + return combinedCodeActions; +} + +ir::AstNode *FixClassIncorrectlyImplementsInterface::FindInterfaceDefinition(ir::TSClassImplements *implement) +{ + if (!implement->IsTSClassImplements()) { + return nullptr; + } + + auto *tsImplements = implement->AsTSClassImplements(); + auto *expr = tsImplements->Expr(); + if (!expr->IsETSTypeReference()) { + return nullptr; + } + + auto *part = expr->AsETSTypeReference()->Part(); + if (!part->IsETSTypeReferencePart()) { + return nullptr; + } + + auto *name = part->Name(); + if (!name->IsIdentifier()) { + return nullptr; + } + + return compiler::DeclarationFromIdentifier(name->AsIdentifier()); +} + +std::vector FixClassIncorrectlyImplementsInterface::GetInterfaceDefinitions( + ir::ClassDefinition *classDef) +{ + std::vector interfaces; + if (classDef == nullptr) { + return interfaces; + } + + auto implements = classDef->Implements(); + for (auto *implement : implements) { + auto *interfaceDef = FindInterfaceDefinition(implement); + if (interfaceDef != nullptr) { + interfaces.push_back(interfaceDef); + } + } + return interfaces; +} + +std::string FixClassIncorrectlyImplementsInterface::GenerateMemberSignature(ir::MethodDefinition *methodDef, + const std::string &memberName) +{ + if (methodDef->IsGetter()) { + return "get " + memberName; + } + if (methodDef->IsSetter()) { + return "set " + memberName; + } + return ""; +} + +bool FixClassIncorrectlyImplementsInterface::IsMemberImplemented(ir::ClassDefinition *classDef, + const std::string &memberSignature) +{ + if (classDef == nullptr) { + return false; + } + + auto classBody = classDef->Body(); + for (auto *member : classBody) { + if (!member->IsMethodDefinition()) { + continue; + } + + auto *methodDef = member->AsMethodDefinition(); + auto *methodId = methodDef->Key()->AsIdentifier(); + std::string currentMemberName = std::string(methodId->Name()); + std::string currentSignature = GenerateMemberSignature(methodDef, currentMemberName); + if (currentSignature.empty()) { + continue; + } + + if (currentSignature == memberSignature) { + return true; + } + } + return false; +} + +void FixClassIncorrectlyImplementsInterface::ProcessInterfaceMembers(ir::TSInterfaceDeclaration *interface, + ir::ClassDefinition *classDef, + std::vector &missingMembers) +{ + auto *interfaceBodyNode = interface->Body(); + if (interfaceBodyNode == nullptr) { + return; + } + + auto interfaceBody = interfaceBodyNode->Body(); + for (auto *member : interfaceBody) { + if (!member->IsMethodDefinition()) { + continue; + } + + auto *methodDef = member->AsMethodDefinition(); + auto *methodId = methodDef->Key()->AsIdentifier(); + std::string memberName = std::string(methodId->Name()); + std::string memberSignature = GenerateMemberSignature(methodDef, memberName); + if (memberSignature.empty()) { + continue; + } + + if (!IsMemberImplemented(classDef, memberSignature)) { + missingMembers.push_back(member); + } + } +} + +std::vector FixClassIncorrectlyImplementsInterface::FindMissingInterfaceMembers( + ir::ClassDefinition *classDef) +{ + std::vector missingMembers; + if (classDef == nullptr) { + return missingMembers; + } + + auto interfaces = GetInterfaceDefinitions(classDef); + for (auto *interfaceDef : interfaces) { + if (interfaceDef->IsTSInterfaceDeclaration()) { + auto *interface = interfaceDef->AsTSInterfaceDeclaration(); + ProcessInterfaceMembers(interface, classDef, missingMembers); + } + } + + return missingMembers; +} + +// NOLINTNEXTLINE +AutoCodeFixRegister g_FixClassIncorrectlyImplementsInterfaceForGetter( + FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER.GetFixId().data()); + +// NOLINTNEXTLINE +AutoCodeFixRegister g_FixClassIncorrectlyImplementsInterfaceForSetter( + FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER.GetFixId().data()); + +} // namespace ark::es2panda::lsp diff --git a/ets2panda/test/unit/lsp/CMakeLists.txt b/ets2panda/test/unit/lsp/CMakeLists.txt index 5792c19b5e7c2b8e153c6f6174b2a11885d3f0cd..5c70cbc9eb12557363417bd85b07f842b0c435eb 100644 --- a/ets2panda/test/unit/lsp/CMakeLists.txt +++ b/ets2panda/test/unit/lsp/CMakeLists.txt @@ -270,9 +270,15 @@ ets2panda_add_gtest(lsp_api_test_fix_return_type_in_async_func 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_class_incorrectly_implements_interface CPP_SOURCES + fix_class_incorrectly_implements_interface_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_class_incorrectly_implements_interface_test.cpp b/ets2panda/test/unit/lsp/fix_class_incorrectly_implements_interface_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d6bd0429b995a8d2af813035da9756f39e3f2a1 --- /dev/null +++ b/ets2panda/test/unit/lsp/fix_class_incorrectly_implements_interface_test.cpp @@ -0,0 +1,159 @@ +/** + * 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_class_incorrectly_implements_interface.h" + +namespace { + +using ark::es2panda::lsp::Initializer; +using ark::es2panda::lsp::codefixes::FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER; +using ark::es2panda::lsp::codefixes::FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER; + +constexpr std::string_view EXPECTED_GETTER_FIX_NAME = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER.GetFixId(); +constexpr std::string_view EXPECTED_SETTER_FIX_NAME = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER.GetFixId(); + +constexpr auto GETTER_ERROR_CODES = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_GETTER.GetSupportedCodeNumbers(); +constexpr auto SETTER_ERROR_CODES = FIX_CLASS_INCORRECTLY_IMPLEMENTS_INTERFACE_FOR_SETTER.GetSupportedCodeNumbers(); + +constexpr int DEFAULT_THROTTLE = 20; + +class FixClassIncorrectlyImplementsInterfaceTests : public LSPAPITests { +public: + class NullCancellationToken : public ark::es2panda::lsp::HostCancellationToken { + public: + bool IsCancellationRequested() override + { + return false; + } + }; + + static ark::es2panda::lsp::CancellationToken CreateToken() + { + static NullCancellationToken nullToken; + return ark::es2panda::lsp::CancellationToken(DEFAULT_THROTTLE, &nullToken); + } + + 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()); + auto pos = index.GetOffset(ark::es2panda::lexer::SourceLocation(line, col, ctx->parserProgram)); + return pos; + } +}; + +TEST_F(FixClassIncorrectlyImplementsInterfaceTests, TestImplementMissingGetter) +{ + std::vector fileNames = {"TestImplementMissingGetter.ets"}; + std::vector fileContents = {R"( +interface User { + get name(): String; +} + +class Person implements User { +} +)"}; + auto filePaths = CreateTempFile(fileNames, fileContents); + ASSERT_EQ(fileNames.size(), filePaths.size()); + + Initializer initializer = Initializer(); + auto *context = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + const size_t start = LineColToPos(context, 6, 30); + const size_t length = 1; + + std::vector errorCodes(GETTER_ERROR_CODES.begin(), GETTER_ERROR_CODES.end()); + CodeFixOptions emptyOptions = {CreateToken(), ark::es2panda::lsp::FormatCodeSettings(), {}}; + auto fixResult = + ark::es2panda::lsp::GetCodeFixesAtPositionImpl(context, start, start + length, errorCodes, emptyOptions); + + ASSERT_GT(fixResult.size(), 0); + ASSERT_EQ(fixResult[0].fixName_, EXPECTED_GETTER_FIX_NAME); + ASSERT_EQ(fixResult[0].fixId_, EXPECTED_GETTER_FIX_NAME); + ASSERT_EQ(fixResult[0].description_, "Add missing interface getter implementations"); + ASSERT_TRUE(fixResult[0].changes_[0].textChanges[0].newText.find("get name(): String") != std::string::npos); + ASSERT_TRUE(fixResult[0].changes_[0].textChanges[0].newText.find("return null;") != std::string::npos); + + initializer.DestroyContext(context); +} + +TEST_F(FixClassIncorrectlyImplementsInterfaceTests, TestImplementMissingSetter) +{ + std::vector fileNames = {"TestImplementMissingSetter.ets"}; + std::vector fileContents = {R"( +interface Writable { + set value(data: String); +} + +class Writer implements Writable { +} +)"}; + auto filePaths = CreateTempFile(fileNames, fileContents); + ASSERT_EQ(fileNames.size(), filePaths.size()); + + Initializer initializer = Initializer(); + auto *context = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + const size_t start = LineColToPos(context, 6, 30); + const size_t length = 1; + + std::vector errorCodes(SETTER_ERROR_CODES.begin(), SETTER_ERROR_CODES.end()); + CodeFixOptions emptyOptions = {CreateToken(), ark::es2panda::lsp::FormatCodeSettings(), {}}; + auto fixResult = + ark::es2panda::lsp::GetCodeFixesAtPositionImpl(context, start, start + length, errorCodes, emptyOptions); + + ASSERT_GT(fixResult.size(), 0); + ASSERT_EQ(fixResult[0].fixName_, EXPECTED_SETTER_FIX_NAME); + ASSERT_EQ(fixResult[0].fixId_, EXPECTED_SETTER_FIX_NAME); + ASSERT_EQ(fixResult[0].description_, "Add missing interface setter implementations"); + ASSERT_TRUE(fixResult[0].changes_[0].textChanges[0].newText.find("set value(data: String)") != std::string::npos); + + initializer.DestroyContext(context); +} + +TEST_F(FixClassIncorrectlyImplementsInterfaceTests, TestCombinedCodeFixAll) +{ + std::vector fileNames = {"TestCombinedCodeFixAll.ets"}; + std::vector fileContents = {R"( +interface PropertyInterface { + get name(): String; + set age(value: Double); +} + +class Implementation implements PropertyInterface { +} +)"}; + auto filePaths = CreateTempFile(fileNames, fileContents); + ASSERT_EQ(fileNames.size(), filePaths.size()); + + Initializer initializer = Initializer(); + auto *context = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + CodeFixOptions emptyOptions = {CreateToken(), ark::es2panda::lsp::FormatCodeSettings(), {}}; + CombinedCodeActionsInfo combinedFixResult = + ark::es2panda::lsp::GetCombinedCodeFixImpl(context, EXPECTED_GETTER_FIX_NAME.data(), emptyOptions); + + ASSERT_GT(combinedFixResult.changes_.size(), 0); + + ASSERT_EQ(combinedFixResult.changes_[0].fileName, filePaths[0]); + + 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 509c9dbf7bc51d7ab05e7c641e6bd94cbb84bf3f..027268ab490aa9b169f0d3bdc64f768d7cf81ff8 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -520,6 +520,7 @@ semantic: - name: GETTER_MISSING_IMPL id: 188 message: "{} is not abstract and does not implement getter for {} property in {}" + code_fix_ids: [FixClassIncorrectlyImplementsInterfaceForSetter] - name: GETTER_VOID id: 86 @@ -1182,6 +1183,7 @@ semantic: - name: SETTER_MISSING_IMPL id: 189 message: "{} is not abstract and does not implement setter for {} property in {}" + code_fix_ids: [FixClassIncorrectlyImplementsInterfaceForGetter] - name: SIG_INVISIBLE id: 139