From fe835f65af68ec5e4827545476e3e699ea564cec Mon Sep 17 00:00:00 2001 From: dYildiz Date: Mon, 21 Jul 2025 01:31:02 +0300 Subject: [PATCH] [LSPAPI]CodeFixProvider_phase_2 Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICNJ52 Signed-off-by: doneyildiz --- ets2panda/lsp/code_fix_register.h.erb | 11 +- ets2panda/lsp/code_fix_register.rb | 7 +- ets2panda/lsp/include/code_fix_provider.h | 10 +- .../lsp/include/code_fixes/code_fix_types.h | 16 -- ets2panda/lsp/src/code_fix_provider.cpp | 58 +++--- .../unit/lsp/code_fix_registration_test.cpp | 190 +++++++++++++++++- 6 files changed, 234 insertions(+), 58 deletions(-) diff --git a/ets2panda/lsp/code_fix_register.h.erb b/ets2panda/lsp/code_fix_register.h.erb index 330f3c722b..775aad1d81 100644 --- a/ets2panda/lsp/code_fix_register.h.erb +++ b/ets2panda/lsp/code_fix_register.h.erb @@ -31,13 +31,19 @@ public: */ static constexpr int DIAGNOSTIC_CODE_MULTIPLIER = 1000; - constexpr DiagnosticCode(util::DiagnosticType type, int id) : type_(type), id_(id) {} + constexpr DiagnosticCode(util::DiagnosticType type, int id, std::string_view message) + : type_(type), id_(id), message_(message) {} constexpr int GetCodeNumber() const { return (static_cast(type_) * DIAGNOSTIC_CODE_MULTIPLIER + id_); } + constexpr std::string_view GetMessage() const + { + return message_; + } + constexpr bool operator==(const DiagnosticCode &other) const { return GetCodeNumber() == other.GetCodeNumber(); @@ -51,6 +57,7 @@ public: private: util::DiagnosticType type_; int id_; + std::string_view message_; }; template @@ -89,7 +96,7 @@ private: static constexpr CodeFixKind<<%= diag_vec.size %>> <%= code_fix_id.snakecase.upcase %> { std::array> { % diag_vec.each_with_index do |diag, index| - DiagnosticCode(util::DiagnosticType::<%= diag.type.to_s.upcase %>, <%= diag.id %>)<%= ',' unless index == diag_vec.size - 1 %> + DiagnosticCode(util::DiagnosticType::<%= diag.type.to_s.upcase %>, <%= diag.id %>, "<%= diag.message %>")<%= ',' unless index == diag_vec.size - 1 %> % end }, "<%= code_fix_id %>" diff --git a/ets2panda/lsp/code_fix_register.rb b/ets2panda/lsp/code_fix_register.rb index 0e20153f41..f400419942 100644 --- a/ets2panda/lsp/code_fix_register.rb +++ b/ets2panda/lsp/code_fix_register.rb @@ -20,11 +20,12 @@ module CodeFixRegister @codefix_map = Hash.new { |h, k| h[k] = [] } class DiagnosticCode - attr_reader :type, :id + attr_reader :type, :id, :message - def initialize(type, id) + def initialize(type, id, message) @type = type @id = id + @message = message end end @@ -35,7 +36,7 @@ module CodeFixRegister def collect_code_fix(diagnostic) diagnostic.code_fix_ids.each do |code_fix_id| - @codefix_map[code_fix_id] << DiagnosticCode.new(diagnostic.type, diagnostic.id) + @codefix_map[code_fix_id] << DiagnosticCode.new(diagnostic.type, diagnostic.id, diagnostic.message) end end diff --git a/ets2panda/lsp/include/code_fix_provider.h b/ets2panda/lsp/include/code_fix_provider.h index c3a8ad84bb..567be5aa86 100644 --- a/ets2panda/lsp/include/code_fix_provider.h +++ b/ets2panda/lsp/include/code_fix_provider.h @@ -21,10 +21,11 @@ #include #include "services/text_change/change_tracker.h" #include "code_fixes/code_fix_types.h" +#include "generated/code_fix_register.h" #include "es2panda.h" namespace ark::es2panda::lsp { - +using ark::es2panda::lsp::codefixes::DiagnosticCode; class CodeFixProvider { private: std::unordered_map> errorCodeToFixes_; @@ -44,16 +45,15 @@ public: static CodeFixProvider &Instance(); std::string FormatWithArgs(const std::string &text); - std::string DiagnosticToString(const DiagnosticAndArguments &diag); + std::string DiagnosticToString(const codefixes::DiagnosticCode &diag); CodeFixAction CreateCodeFixActionWorker(std::string &fixName, std::string &description, std::vector &changes, std::string &fixId, std::string &fixAllDescription, std::vector command); CodeFixAction CreateCodeFixActionWithoutFixAll(std::string &fixName, std::vector &changes, - DiagnosticAndArguments &description); + DiagnosticCode &diagCode); CodeFixAction CreateCodeFixAction(std::string fixName, std::vector changes, - DiagnosticAndArguments &description, std::string fixId, - DiagnosticAndArguments &fixAllDescription, + DiagnosticCode diagCode, std::string fixId, std::vector &command); std::string GetFileName(const std::string &filePath); std::vector GetSupportedErrorCodes(); diff --git a/ets2panda/lsp/include/code_fixes/code_fix_types.h b/ets2panda/lsp/include/code_fixes/code_fix_types.h index 31ce206b8e..1b5dd62704 100644 --- a/ets2panda/lsp/include/code_fixes/code_fix_types.h +++ b/ets2panda/lsp/include/code_fixes/code_fix_types.h @@ -35,22 +35,6 @@ namespace ark::es2panda::lsp { -enum DiagnosticCategory { WARNING, ERROR, SUGGESTION, MESSAGE }; - -struct DiagnosticMessage { - std::string key; - DiagnosticCategory category; - size_t code; - std::string message; - std::string reportsUnnecessary = {}; - std::string reportsDeprecated = {}; - bool elidedInCompatabilityPyramid; -}; -struct DiagnosticAndArguments { - DiagnosticMessage message; - std::vector arguments; -}; - struct CodeAction { std::string description; std::vector changes; diff --git a/ets2panda/lsp/src/code_fix_provider.cpp b/ets2panda/lsp/src/code_fix_provider.cpp index 349ab2c724..ba0150a575 100644 --- a/ets2panda/lsp/src/code_fix_provider.cpp +++ b/ets2panda/lsp/src/code_fix_provider.cpp @@ -15,17 +15,16 @@ #include "lsp/include/code_fix_provider.h" #include -#include #include #include #include #include -#include -#include +#include "generated/code_fix_register.h" #include "lsp/include/internal_api.h" namespace ark::es2panda::lsp { +// Registers a new code fix and maps it to its diagnostic error codes and fix IDs void CodeFixProvider::RegisterCodeFix(const std::string &aliasName, std::unique_ptr registration) { (void)aliasName; @@ -50,28 +49,19 @@ CodeFixProvider &CodeFixProvider::Instance() std::string CodeFixProvider::FormatWithArgs(const std::string &text) { - std::string result = text; - const std::string regExp = R"(\{(\d+)\})"; - std::regex pattern(regExp); - std::smatch match; - return result; + // This function is a placeholder for future implementation of string formatting with arguments. + return text; } -std::string CodeFixProvider::DiagnosticToString(const DiagnosticAndArguments &diag) +std::string CodeFixProvider::DiagnosticToString(const codefixes::DiagnosticCode &diag) { - std::string message; - if (diag.arguments.empty()) { - message = diag.message.message; - } else { - message = FormatWithArgs(diag.message.message); - } - return message; + return std::string(diag.GetMessage()); } CodeFixAction CodeFixProvider::CreateCodeFixActionWorker(std::string &fixName, std::string &description, std::vector &changes, std::string &fixId, std::string &fixAllDescription, - std::vector command = {}) + std::vector command) { CodeAction codeAction; codeAction.description = description; @@ -80,23 +70,25 @@ CodeFixAction CodeFixProvider::CreateCodeFixActionWorker(std::string &fixName, s return {codeAction, fixName, fixId, fixAllDescription}; } +// Creates a code fix action that doesn't include fix-all functionality CodeFixAction CodeFixProvider::CreateCodeFixActionWithoutFixAll(std::string &fixName, std::vector &changes, - DiagnosticAndArguments &description) + codefixes::DiagnosticCode &diagCode) { std::string fixId; - std::string descriptionMessage = DiagnosticToString(description); + std::string descriptionMessage = DiagnosticToString(diagCode); std::string fixAllDescription; - return CreateCodeFixActionWorker(fixName, descriptionMessage, changes, fixId, fixAllDescription); + return CreateCodeFixActionWorker(fixName, descriptionMessage, changes, fixId, fixAllDescription, {}); } +// Creates a full code fix action with fix-all and commands CodeFixAction CodeFixProvider::CreateCodeFixAction(std::string fixName, std::vector changes, - DiagnosticAndArguments &description, std::string fixId, - DiagnosticAndArguments &fixAllDescription, + codefixes::DiagnosticCode diagCode, std::string fixId, std::vector &command) { - std::string descriptionMessage = DiagnosticToString(description); - std::string fixAllDescriptionMessage = DiagnosticToString(fixAllDescription); + auto descriptionMessage = std::string(diagCode.GetMessage()); + std::string fixAllDescriptionMessage = "Fix all: " + std::string(diagCode.GetMessage()); + return CreateCodeFixActionWorker(fixName, descriptionMessage, changes, fixId, fixAllDescriptionMessage, std::move(command)); } @@ -153,14 +145,16 @@ std::unique_ptr CodeFixProvider::GetDiagnostics(const Code return result; } +// Determines whether fix-all should be enabled for this registration based on diagnostic count bool CodeFixProvider::ShouldIncludeFixAll(const CodeFixRegistration ®istration, const std::vector &diagnostics) { int maybeFixableDiagnostics = 0; const int minFixableDiagnostics = 1; - for (size_t i = 0; i <= diagnostics.size(); i++) { - if (std::find(registration.GetErrorCodes().begin(), registration.GetErrorCodes().end(), i) != - registration.GetErrorCodes().end()) { + for (const auto &diag : diagnostics) { + if (std::holds_alternative(diag.code_) && + std::find(registration.GetErrorCodes().begin(), registration.GetErrorCodes().end(), + std::get(diag.code_)) != registration.GetErrorCodes().end()) { ++maybeFixableDiagnostics; if (maybeFixableDiagnostics > minFixableDiagnostics) { break; @@ -170,6 +164,7 @@ bool CodeFixProvider::ShouldIncludeFixAll(const CodeFixRegistration ®istratio return maybeFixableDiagnostics > minFixableDiagnostics; } +// Returns all fixes associated with a fixId (used for fix-all) CombinedCodeActions CodeFixProvider::GetAllFixes(const CodeFixAllContext &context) { auto it = fixIdToRegistration_.find(context.fixId); @@ -180,6 +175,7 @@ CombinedCodeActions CodeFixProvider::GetAllFixes(const CodeFixAllContext &contex return registration->GetAllCodeActions(context); } +// Iterates through diagnostics matching given error codes and applies callback on each void CodeFixProvider::EachDiagnostic(const CodeFixAllContext &context, const std::vector &errorCodes, const std::function &cb) { @@ -217,6 +213,7 @@ void CodeFixProvider::EachDiagnostic(const CodeFixAllContext &context, const std } } +// Returns applicable fix actions for a given error code std::vector CodeFixProvider::GetFixes(const CodeFixContext &context) { std::vector result; @@ -225,14 +222,13 @@ std::vector CodeFixProvider::GetFixes(const CodeFixContext &conte const auto ®istrations = it->second; if (registrations) { auto actions = registrations->GetCodeActions(context); - for (auto &action : actions) { - result.push_back(action); - } + result.insert(result.end(), actions.begin(), actions.end()); } } return result; } +// Applies fix-all logic using a callback for each matching diagnostic CombinedCodeActions CodeFixProvider::CodeFixAll( const CodeFixAllContext &context, const std::vector &errorCodes, std::function use) @@ -251,4 +247,4 @@ FileTextChanges CodeFixProvider::CreateFileTextChanges(const std::string &fileNa return {fileName, textChanges}; } -} // namespace ark::es2panda::lsp +} // namespace ark::es2panda::lsp \ No newline at end of file diff --git a/ets2panda/test/unit/lsp/code_fix_registration_test.cpp b/ets2panda/test/unit/lsp/code_fix_registration_test.cpp index acb35182b5..f2779d152c 100644 --- a/ets2panda/test/unit/lsp/code_fix_registration_test.cpp +++ b/ets2panda/test/unit/lsp/code_fix_registration_test.cpp @@ -15,8 +15,69 @@ #include "lsp/include/code_fix_provider.h" #include "gtest/gtest.h" +#include "lsp_api_test.h" +#include "lsp/include/cancellation_token.h" +#include "lsp/include/register_code_fix/convert_const_to_let.h" +#include "generated/code_fix_register.h" namespace { +constexpr int DEFAULT_THROTTLE = 20; +constexpr auto ERROR_CODES = ark::es2panda::lsp::codefixes::FIX_CONVERT_CONST_TO_LET.GetSupportedCodeNumbers(); +constexpr std::string_view EXPECTED_FIX_DESCRIPTION = "Convert const to let"; +constexpr std::string_view EXPECTED_TEXT_CHANGE_NEW_TEXT = "let"; +constexpr auto EXPECTED_FIX_NAME = ark::es2panda::lsp::codefixes::FIX_CONVERT_CONST_TO_LET.GetFixId(); +constexpr int ERROR_CODE_EXAMPLE = 900; // Example error code for testing +constexpr int ERROR_CODE_EXAMPLE_2 = 901; // Another example error code for testing +constexpr unsigned int END_RANGE_EXAMPLE = 10; + +class DummyCodeFixRegistration : public ark::es2panda::lsp::CodeFixRegistration { +public: + std::vector GetCodeActions( + [[maybe_unused]] const ark::es2panda::lsp::CodeFixContext &context) override + { + return {}; + } + + ark::es2panda::lsp::CombinedCodeActions GetAllCodeActions( + [[maybe_unused]] const ark::es2panda::lsp::CodeFixAllContext &context) override + { + return {}; + } +}; + +class CodeFixProviderTest : public LSPAPITests { +protected: + class NullCancellationToken : public ark::es2panda::lsp::HostCancellationToken { + public: + bool IsCancellationRequested() override + { + return false; + } + }; + static NullCancellationToken &GetNullHost() + { + static NullCancellationToken instance; + return instance; + } + 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; + } + static void ValidateCodeFixActionInfo(const CodeFixActionInfo &info, const size_t expectedTextChangeStart, + const size_t expectedTextChangeLength, const std::string &expectedFileName) + { + ASSERT_EQ(info.fixName_, EXPECTED_FIX_NAME); + ASSERT_EQ(info.fixId_, EXPECTED_FIX_NAME); + ASSERT_EQ(info.description_, EXPECTED_FIX_DESCRIPTION); + ASSERT_EQ(info.changes_[0].fileName, expectedFileName); + ASSERT_EQ(info.changes_[0].textChanges[0].span.start, expectedTextChangeStart); + ASSERT_EQ(info.changes_[0].textChanges[0].span.length, expectedTextChangeLength); + ASSERT_EQ(info.changes_[0].textChanges[0].newText, EXPECTED_TEXT_CHANGE_NEW_TEXT); + } +}; TEST(RefactorProviderRegistrationTest, RegistersConvertFunctionRefactor) { @@ -27,4 +88,131 @@ TEST(RefactorProviderRegistrationTest, RegistersConvertFunctionRefactor) EXPECT_FALSE(fixIdToRegistration.empty()); EXPECT_EQ(errors.size(), fixIdToRegistration.size()); } -} // namespace + +TEST_F(CodeFixProviderTest, CreatesCodeFixActionWithoutFixAll) +{ + std::string fixName = "testFix"; + std::vector changes = { + ark::es2panda::lsp::CodeFixProvider::Instance().CreateFileTextChanges("CodeFixProviderTest.ets", {})}; + + ark::es2panda::lsp::codefixes::DiagnosticCode diag(ark::es2panda::util::DiagnosticType::SEMANTIC, + ERROR_CODE_EXAMPLE, "Test message without arguments"); + + auto action = + ark::es2panda::lsp::CodeFixProvider::Instance().CreateCodeFixActionWithoutFixAll(fixName, changes, diag); + + EXPECT_EQ(action.fixName, fixName); + EXPECT_EQ(action.fixAllDescription, ""); + EXPECT_EQ(action.fixId, ""); + EXPECT_EQ(action.description, "Test message without arguments"); + EXPECT_EQ(action.changes.size(), 1); + EXPECT_EQ(action.changes[0].fileName, "CodeFixProviderTest.ets"); +} + +TEST_F(CodeFixProviderTest, CreatesCodeFixActionWithFixAll) +{ + std::string fixName = "fixWithAll"; + std::string fixId = "fix-all-id"; + ark::es2panda::lsp::codefixes::DiagnosticCode diag(ark::es2panda::util::DiagnosticType::SEMANTIC, + ERROR_CODE_EXAMPLE, "Message"); + + std::vector changes = { + ark::es2panda::lsp::CodeFixProvider::Instance().CreateFileTextChanges("CodeFixProviderFile.ets", {})}; + + ark::es2panda::lsp::CodeActionCommand cmd; + cmd.type = "commandName"; + std::vector commands = {cmd}; + + auto action = + ark::es2panda::lsp::CodeFixProvider::Instance().CreateCodeFixAction(fixName, changes, diag, fixId, commands); + + EXPECT_EQ(action.fixName, fixName); + EXPECT_EQ(action.fixId, fixId); + EXPECT_EQ(action.description, "Message"); + EXPECT_EQ(action.fixAllDescription, "Fix all: Message"); + EXPECT_EQ(action.changes.size(), 1); + EXPECT_EQ(action.changes[0].fileName, "CodeFixProviderFile.ets"); + EXPECT_FALSE(action.commands.empty()); + EXPECT_EQ(action.commands[0].type, "commandName"); +} + +TEST_F(CodeFixProviderTest, DiagnosticToStringHandlesMessage) +{ + ark::es2panda::lsp::codefixes::DiagnosticCode diagNoArgs(ark::es2panda::util::DiagnosticType::SEMANTIC, + ERROR_CODE_EXAMPLE, "No args message"); + EXPECT_EQ(ark::es2panda::lsp::CodeFixProvider::Instance().DiagnosticToString(diagNoArgs), "No args message"); + ark::es2panda::lsp::codefixes::DiagnosticCode diagWithPlaceholder(ark::es2panda::util::DiagnosticType::SEMANTIC, + ERROR_CODE_EXAMPLE_2, "With {0} placeholder"); + // Even though formatting is not implemented yet, it should return the raw message + EXPECT_EQ(ark::es2panda::lsp::CodeFixProvider::Instance().DiagnosticToString(diagWithPlaceholder), + "With {0} placeholder"); +} + +TEST_F(CodeFixProviderTest, GetSupportedErrorCodesReturnsNonEmpty) +{ + auto supported = ark::es2panda::lsp::CodeFixProvider::Instance().GetSupportedErrorCodes(); + EXPECT_FALSE(supported.empty()); +} + +TEST_F(CodeFixProviderTest, ShouldIncludeFixAllBehavior) +{ + DummyCodeFixRegistration dummyReg; + dummyReg.SetErrorCodes({ERROR_CODE_EXAMPLE}); + + // Common dummy data + Range dummyRange {{0, 0}, {0, END_RANGE_EXAMPLE}}; + std::vector tags; + std::vector relatedInfo; + DiagnosticSeverity severity = DiagnosticSeverity::Error; + + Diagnostic diag1(dummyRange, tags, relatedInfo, severity, ERROR_CODE_EXAMPLE, "Error 900"); + Diagnostic diag2(dummyRange, tags, relatedInfo, severity, ERROR_CODE_EXAMPLE_2, "Error 901"); + + std::vector single = {diag1}; + std::vector multiple = {diag1, diag2, diag1}; + + EXPECT_FALSE(ark::es2panda::lsp::CodeFixProvider::Instance().ShouldIncludeFixAll(dummyReg, single)); + EXPECT_TRUE(ark::es2panda::lsp::CodeFixProvider::Instance().ShouldIncludeFixAll(dummyReg, multiple)); +} + +TEST_F(CodeFixProviderTest, TestFixExample) +{ + std::vector fileNames = {"TestFixExample.ets"}; + std::vector fileContents = {R"( +const a:Int = 0; +a = 1; +)"}; + auto filePaths = CreateTempFile(fileNames, fileContents); + + ASSERT_EQ(fileNames.size(), filePaths.size()); + ark::es2panda::lsp::Initializer initializer = ark::es2panda::lsp::Initializer(); + auto *context = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + const size_t start = LineColToPos(context, 3, 1); + const size_t length = 1; + const size_t expectedTextChangeStart = 1; + const size_t expectedTextChangeLength = 5; + const int expectedFixResultSize = 1; + const int expectedCombinedTextChangesSize = 1; + + std::vector errorCodes(ERROR_CODES.begin(), ERROR_CODES.end()); + ark::es2panda::lsp::CancellationToken cancelationToken(DEFAULT_THROTTLE, &GetNullHost()); + CodeFixOptions emptyOptions = {cancelationToken, ark::es2panda::lsp::FormatCodeSettings(), {}}; + auto fixResult = + ark::es2panda::lsp::GetCodeFixesAtPositionImpl(context, start, start + length, errorCodes, emptyOptions); + ASSERT_EQ(fixResult.size(), expectedFixResultSize); + + ValidateCodeFixActionInfo(fixResult[0], expectedTextChangeStart, expectedTextChangeLength, filePaths[0]); + + CombinedCodeActionsInfo combinedFixResult = ark::es2panda::lsp::GetCombinedCodeFixImpl( + context, std::string(ark::es2panda::lsp::codefixes::FIX_CONVERT_CONST_TO_LET.GetFixId()), emptyOptions); + ASSERT_EQ(combinedFixResult.changes_.size(), expectedFixResultSize); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges.size(), expectedCombinedTextChangesSize); + ASSERT_EQ(combinedFixResult.changes_[0].fileName, filePaths[0]); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[0].span.start, expectedTextChangeStart); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[0].span.length, expectedTextChangeLength); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[0].newText, EXPECTED_TEXT_CHANGE_NEW_TEXT.data()); + initializer.DestroyContext(context); +} + +} // namespace \ No newline at end of file -- Gitee