From ed4c3eb4602c48507fd70235ffd9b937cbf84248 Mon Sep 17 00:00:00 2001 From: zmw Date: Sat, 7 Jun 2025 16:53:31 +0800 Subject: [PATCH] Support Fix convert const to let Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICDLIO Description: Support Fix convert const to let Signed-off-by: zmw --- ets2panda/BUILD.gn | 26 +++ ets2panda/lsp/BUILD.gn | 2 + ets2panda/lsp/CMakeLists.txt | 31 +++ ets2panda/lsp/code_fix_register.h.erb | 97 +++++++++ ets2panda/lsp/code_fix_register.rb | 67 ++++++ ets2panda/lsp/include/code_fix_provider.h | 2 +- .../lsp/include/code_fixes/code_fix_types.h | 35 ++- ets2panda/lsp/include/internal_api.h | 3 + .../register_code_fix/convert_const_to_let.h | 40 ++++ .../services/text_change/change_tracker.h | 2 + ets2panda/lsp/src/code_fix_provider.cpp | 65 ++++-- ets2panda/lsp/src/internal_api.cpp | 41 +++- .../add_missing_declare_property.cpp | 2 +- .../convert_const_to_let.cpp | 111 ++++++++++ .../register_code_fix/fix_nan_equality.cpp | 7 +- .../services/text_change/change_tracker.cpp | 15 ++ ets2panda/test/unit/lsp/CMakeLists.txt | 4 + .../unit/lsp/code_fix_registration_test.cpp | 2 +- .../lsp/fix_convert_const_to_let_test.cpp | 205 ++++++++++++++++++ ets2panda/util/diagnostic.cpp | 10 + ets2panda/util/diagnostic.h | 3 + ets2panda/util/diagnostic/semantic.yaml | 2 + 22 files changed, 739 insertions(+), 33 deletions(-) create mode 100644 ets2panda/lsp/code_fix_register.h.erb create mode 100644 ets2panda/lsp/code_fix_register.rb create mode 100644 ets2panda/lsp/include/register_code_fix/convert_const_to_let.h create mode 100644 ets2panda/lsp/src/register_code_fix/convert_const_to_let.cpp create mode 100644 ets2panda/test/unit/lsp/fix_convert_const_to_let_test.cpp diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index 7d999578ef..0d223e885b 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -1285,3 +1285,29 @@ ark_gen("gen_es2panda_compiler") { destination = "$target_gen_dir/generated" api = [ "compiler/scripts/signatures.rb" ] } + +ark_gen("es2panda_lsp_code_fix_register_gen") { + data = [ + "util/diagnostic/syntax.yaml", + "util/diagnostic/semantic.yaml", + "util/diagnostic/warning.yaml", + "util/diagnostic/fatal.yaml", + "declgen_ets2ts/declgen_ets2ts_error.yaml", + "declgen_ets2ts/declgen_ets2ts_warning.yaml", + "declgen_ets2ts/isolated_declgen.yaml", + "util/diagnostic/arktsconfig_error.yaml", + ] + template_files = [ "code_fix_register.h.erb" ] + sources = "lsp" + destination = "$target_gen_dir/generated" + api = [ + "lsp/code_fix_register.rb", + "lsp/code_fix_register.rb", + "lsp/code_fix_register.rb", + "lsp/code_fix_register.rb", + "lsp/code_fix_register.rb", + "lsp/code_fix_register.rb", + "lsp/code_fix_register.rb", + "lsp/code_fix_register.rb", + ] +} diff --git a/ets2panda/lsp/BUILD.gn b/ets2panda/lsp/BUILD.gn index ff8b060335..298c58e5cb 100644 --- a/ets2panda/lsp/BUILD.gn +++ b/ets2panda/lsp/BUILD.gn @@ -78,6 +78,7 @@ ohos_source_set("libes2panda_lsp_static") { "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_missing_call_parantheses.cpp", "src/register_code_fix/fix_nan_equality.cpp", "src/register_code_fix/forgetten_this_property_access.cpp", @@ -106,6 +107,7 @@ ohos_source_set("libes2panda_lsp_static") { ] deps = [ + "../:es2panda_lsp_code_fix_register_gen_code_fix_register_h", "../:libes2panda_frontend_static", "../:libes2panda_public_frontend_static", ] diff --git a/ets2panda/lsp/CMakeLists.txt b/ets2panda/lsp/CMakeLists.txt index 9cd9dab4f2..d0f407e8e4 100644 --- a/ets2panda/lsp/CMakeLists.txt +++ b/ets2panda/lsp/CMakeLists.txt @@ -11,6 +11,35 @@ # See the License for the specific language governing permissions and # limitations under the License. +set(DIAGNOSTIC_DIR + ${ES2PANDA_ROOT}/util/diagnostic/ +) + +panda_gen( + DATA + ${DIAGNOSTIC_DIR}/syntax.yaml + ${DIAGNOSTIC_DIR}/semantic.yaml + ${DIAGNOSTIC_DIR}/warning.yaml + ${DIAGNOSTIC_DIR}/fatal.yaml + ${ES2PANDA_ROOT}/declgen_ets2ts/declgen_ets2ts_error.yaml + ${ES2PANDA_ROOT}/declgen_ets2ts/declgen_ets2ts_warning.yaml + ${ES2PANDA_ROOT}/declgen_ets2ts/isolated_declgen.yaml + ${DIAGNOSTIC_DIR}/arktsconfig_error.yaml + TARGET_NAME es2panda_code_fix_register_gen + TEMPLATES code_fix_register.h.erb + SOURCE ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${GENERATED_DIR} + API + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb + ${CMAKE_CURRENT_SOURCE_DIR}/code_fix_register.rb +) + set(ES2PANDA_LSP_SRC ./src/api.cpp ./src/class_hierarchy.cpp @@ -61,6 +90,7 @@ set(ES2PANDA_LSP_SRC ./src/code_fix_provider.cpp ./src/register_code_fix/fix_class_doesnt_implement_inherited_abstract_member.cpp ./src/register_code_fix/add_missing_declare_property.cpp + ./src/register_code_fix/convert_const_to_let.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 @@ -70,6 +100,7 @@ set(ES2PANDA_LSP_SRC ) panda_add_library(${LSP_LIB} SHARED ${ES2PANDA_LSP_SRC}) +add_dependencies(${LSP_LIB} es2panda_code_fix_register_gen) panda_target_include_directories(${LSP_LIB} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include diff --git a/ets2panda/lsp/code_fix_register.h.erb b/ets2panda/lsp/code_fix_register.h.erb new file mode 100644 index 0000000000..9bc626c66b --- /dev/null +++ b/ets2panda/lsp/code_fix_register.h.erb @@ -0,0 +1,97 @@ +/** + * 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. + */ + +// Autogenerated file -- DO NOT EDIT! + +#ifndef ES2PANDA_LSP_CODE_FIX_KIND_H +#define ES2PANDA_LSP_CODE_FIX_KIND_H + +#include "util/diagnostic.h" +#include + +namespace ark::es2panda::lsp::codefixs { + +class DiagnosticCode { +public: + static constexpr int DIAGNOSTIC_CODE_MULTIPIER = 1000; + + constexpr DiagnosticCode(util::DiagnosticType type, int id) : type_(type), id_(id) {} + + constexpr int GetCodeNumber() const + { + return (static_cast(type_) * DIAGNOSTIC_CODE_MULTIPIER + id_); + } + + constexpr bool operator==(const DiagnosticCode &other) const + { + return GetCodeNumber() == other.GetCodeNumber(); + } + + constexpr bool operator!=(const DiagnosticCode &other) const + { + return !(*this == other); + } + +private: + util::DiagnosticType type_; + int id_; +}; + +template +class CodeFixKind { +public: + constexpr CodeFixKind(std::array codes, const std::string_view id) + : diagnosticCodes_(codes), codeFixId_(id) + { + } + + constexpr const std::array &GetSupportedCodes() const + { + return diagnosticCodes_; + } + + constexpr std::array GetSupportedCodeNumbers() const + { + std::array codeNumbers {}; + for (size_t i = 0; i < N; ++i) { + codeNumbers[i] = diagnosticCodes_[i].GetCodeNumber(); + } + return codeNumbers; + } + + constexpr std::string_view GetFixId() const + { + return codeFixId_; + } + +private: + std::array diagnosticCodes_; + std::string_view codeFixId_; +}; + +% CodeFixRegister::codefix_map.each do |code_fix_id, diag_vec| +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 %> +% end + }, + "<%= code_fix_id %>" +}; +% end + +} // namespace ark::es2panda::lsp::codefixs + +#endif // ES2PANDA_LSP_CODE_FIX_KIND_H diff --git a/ets2panda/lsp/code_fix_register.rb b/ets2panda/lsp/code_fix_register.rb new file mode 100644 index 0000000000..0e20153f41 --- /dev/null +++ b/ets2panda/lsp/code_fix_register.rb @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby +# 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. + +require 'ostruct' +require 'set' +require 'delegate' + +module CodeFixRegister + @codefix_map = Hash.new { |h, k| h[k] = [] } + + class DiagnosticCode + attr_reader :type, :id + + def initialize(type, id) + @type = type + @id = id + end + end + + class << self + def codefix_map + @codefix_map + end + + 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) + end + end + + def wrap_data(data) + data.each_pair do |diagnostic_type, diagnostics| + diagnostics.each do |diagnostic| + if diagnostic.respond_to?(:code_fix_ids) + diagnostic.type = diagnostic_type + collect_code_fix(diagnostic) + end + end + end + end + end +end + +class String + def snakecase + self.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end +end + +def Gen.on_require(data) + CodeFixRegister.wrap_data(data) +end \ No newline at end of file diff --git a/ets2panda/lsp/include/code_fix_provider.h b/ets2panda/lsp/include/code_fix_provider.h index 7d18b2d7c4..c3a8ad84bb 100644 --- a/ets2panda/lsp/include/code_fix_provider.h +++ b/ets2panda/lsp/include/code_fix_provider.h @@ -57,7 +57,7 @@ public: std::vector &command); std::string GetFileName(const std::string &filePath); std::vector GetSupportedErrorCodes(); - DiagnosticReferences *GetDiagnostics(const CodeFixContextBase &context); + std::unique_ptr GetDiagnostics(const CodeFixContextBase &context); bool ShouldIncludeFixAll(const CodeFixRegistration ®istration, const std::vector &diagnostics); std::vector GetFixes(const CodeFixContext &context); diff --git a/ets2panda/lsp/include/code_fixes/code_fix_types.h b/ets2panda/lsp/include/code_fixes/code_fix_types.h index 1e38c16cb7..3f88578046 100644 --- a/ets2panda/lsp/include/code_fixes/code_fix_types.h +++ b/ets2panda/lsp/include/code_fixes/code_fix_types.h @@ -21,11 +21,13 @@ #include #include #include + #include "../user_preferences.h" #include "../cancellation_token.h" #include "../types.h" #include "../api.h" #include "es2panda.h" +#include "generated/code_fix_register.h" #include "public/es2panda_lib.h" #include "public/public.h" #include "../get_class_property_info.h" @@ -72,10 +74,34 @@ struct CodeFixAllContext : CodeFixContextBase { std::string fixId = {}; }; -struct DiagnosticWithLocation : Diagnostic { - SourceFile file; - size_t start = 0; - size_t length = 0; +class DiagnosticWithLocation : Diagnostic { +public: + DiagnosticWithLocation(Diagnostic &&base, const SourceFile &file, const size_t start = 0, const size_t length = 0) + : Diagnostic(std::move(base)), file_(file), start_(start), length_(length) + { + } + + using Diagnostic::Diagnostic; + + SourceFile GetFile() const + { + return file_; + } + + size_t GetStart() const + { + return start_; + } + + size_t Getlength() const + { + return length_; + } + +private: + SourceFile file_; + size_t start_ = 0; + size_t length_ = 0; }; struct CodeFixContext : CodeFixContextBase { @@ -101,6 +127,7 @@ public: { errorCodes_ = codes; } + void SetFixIds(const std::vector &ids) { fixIds_ = ids; diff --git a/ets2panda/lsp/include/internal_api.h b/ets2panda/lsp/include/internal_api.h index b021c901da..705cc09f10 100644 --- a/ets2panda/lsp/include/internal_api.h +++ b/ets2panda/lsp/include/internal_api.h @@ -134,6 +134,9 @@ std::vector GetCodeFixesAtPositionImpl(es2panda_Context *cont CodeFixOptions &codeFixOptions); CombinedCodeActionsInfo GetCombinedCodeFixImpl(es2panda_Context *context, const std::string &fixId, CodeFixOptions &codeFixOptions); +int CalculateCodeFromDiagnosticKind(const diagnostic::DiagnosticKind kind); +varbinder::Decl *FindDeclInFunctionScope(varbinder::Scope *scope, const util::StringView &name); +varbinder::Decl *FindDeclInGlobalScope(varbinder::Scope *scope, const util::StringView &name); } // namespace ark::es2panda::lsp diff --git a/ets2panda/lsp/include/register_code_fix/convert_const_to_let.h b/ets2panda/lsp/include/register_code_fix/convert_const_to_let.h new file mode 100644 index 0000000000..5ecc8d9259 --- /dev/null +++ b/ets2panda/lsp/include/register_code_fix/convert_const_to_let.h @@ -0,0 +1,40 @@ +/** + * 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 CONVERT_CONST_TO_LET_H +#define CONVERT_CONST_TO_LET_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 FixConvertConstToLet : public CodeFixRegistration { +public: + FixConvertConstToLet(); + + std::vector GetCodeActions(const CodeFixContext &context) override; + CombinedCodeActions GetAllCodeActions(const CodeFixAllContext &codeFixAll) override; + +private: + void MakeChangeForConvertConstToLet(ChangeTracker &changeTracker, es2panda_Context *context, size_t pos); + std::vector GetCodeActionsToConvertConstToLet(const CodeFixContext &context); +}; + +} // namespace ark::es2panda::lsp +#endif diff --git a/ets2panda/lsp/include/services/text_change/change_tracker.h b/ets2panda/lsp/include/services/text_change/change_tracker.h index d7d6d1042b..f42513500c 100644 --- a/ets2panda/lsp/include/services/text_change/change_tracker.h +++ b/ets2panda/lsp/include/services/text_change/change_tracker.h @@ -246,6 +246,8 @@ public: std::vector &namedImports, size_t index); void CreateNewFile(SourceFile *oldFile, const std::string &fileName, std::vector &statements); + void RfindNearestKeyWordTextRange(const es2panda_Context *context, const size_t pos, + const std::string_view &keywordStr, TextRange &range); }; } // namespace ark::es2panda::lsp diff --git a/ets2panda/lsp/src/code_fix_provider.cpp b/ets2panda/lsp/src/code_fix_provider.cpp index 0aacb84467..3d361a42e8 100644 --- a/ets2panda/lsp/src/code_fix_provider.cpp +++ b/ets2panda/lsp/src/code_fix_provider.cpp @@ -129,28 +129,26 @@ std::vector CodeFixProvider::GetSupportedErrorCodes() return result; } -DiagnosticReferences *CodeFixProvider::GetDiagnostics(const CodeFixContextBase &context) +std::unique_ptr CodeFixProvider::GetDiagnostics(const CodeFixContextBase &context) { - DiagnosticReferences *result = nullptr; LSPAPI const *lspApi = GetImpl(); + ASSERT(lspApi != nullptr); Initializer initializer = Initializer(); auto it = reinterpret_cast(context.context); - std::string fileNameStr(GetFileName(std::string(it->sourceFile->filePath))); - std::string sourceStr(it->sourceFile->source); - const auto ctx = initializer.CreateContext(fileNameStr.c_str(), ES2PANDA_STATE_CHECKED, sourceStr.c_str()); - DiagnosticReferences semantic = lspApi->getSemanticDiagnostics(ctx); - DiagnosticReferences syntactic = lspApi->getSyntacticDiagnostics(ctx); - DiagnosticReferences suggestions = lspApi->getSuggestionDiagnostics(ctx); - - for (const auto &d : semantic.diagnostic) { - result->diagnostic.push_back(d); - } - for (const auto &d : syntactic.diagnostic) { - result->diagnostic.push_back(d); - } - for (const auto &d : suggestions.diagnostic) { - result->diagnostic.push_back(d); - } + ASSERT(it != nullptr && it->sourceFile != nullptr); + const std::string_view fileName(it->sourceFile->filePath); + const std::string_view source(it->sourceFile->source); + const auto ctx = initializer.CreateContext(fileName.data(), ES2PANDA_STATE_CHECKED, source.data()); + auto [semantic, syntactic, suggestions] = std::make_tuple(lspApi->getSemanticDiagnostics(ctx), + lspApi->getSyntacticDiagnostics(ctx), + lspApi->getSuggestionDiagnostics(ctx)); + + auto result = std::make_unique(); + result->diagnostic.reserve(semantic.diagnostic.size() + syntactic.diagnostic.size() + + suggestions.diagnostic.size()); + std::move(semantic.diagnostic.begin(), semantic.diagnostic.end(), std::back_inserter(result->diagnostic)); + std::move(syntactic.diagnostic.begin(), syntactic.diagnostic.end(), std::back_inserter(result->diagnostic)); + std::move(suggestions.diagnostic.begin(), suggestions.diagnostic.end(), std::back_inserter(result->diagnostic)); initializer.DestroyContext(ctx); return result; } @@ -186,13 +184,36 @@ void CodeFixProvider::EachDiagnostic(const CodeFixAllContext &context, const std const std::function &cb) { if (errorCodes.empty()) { + return; } - if (cb) { - } + auto diagnostics = GetDiagnostics(context); - if (diagnostics != nullptr) { - for (size_t i = 0; i <= diagnostics->diagnostic.size(); i++) { + if (diagnostics == nullptr) { + return; + } + + auto it = reinterpret_cast(context.context); + ASSERT(it != nullptr && it->sourceFile != nullptr); + auto *parserProgram = it->parserProgram; + auto index = lexer::LineIndex(parserProgram->SourceCode()); + + for (auto &diag : diagnostics->diagnostic) { + auto isTargetError = [&diag](int code) { + return std::holds_alternative(diag.code_) && std::get(diag.code_) == code; + }; + if (!std::any_of(errorCodes.begin(), errorCodes.end(), isTargetError)) { + continue; } + + size_t startOffset = index.GetOffset( + lexer::SourceLocation(diag.range_.start.line_, diag.range_.start.character_, parserProgram)); + size_t endOffset = + index.GetOffset(lexer::SourceLocation(diag.range_.end.line_, diag.range_.end.character_, parserProgram)); + + ASSERT(endOffset >= startOffset); + size_t length = endOffset - startOffset; + auto diagWithLocate = DiagnosticWithLocation(std::move(diag), *it->sourceFile, startOffset, length); + cb(diagWithLocate); } } diff --git a/ets2panda/lsp/src/internal_api.cpp b/ets2panda/lsp/src/internal_api.cpp index 795777e89e..4840e7f15f 100644 --- a/ets2panda/lsp/src/internal_api.cpp +++ b/ets2panda/lsp/src/internal_api.cpp @@ -34,6 +34,8 @@ namespace ark::es2panda::lsp { +constexpr int DIAGNOSTIC_CODE_MULTIPIER = 1000; + Initializer::Initializer() { impl_ = es2panda_GetImpl(ES2PANDA_LIB_VERSION); @@ -528,7 +530,8 @@ int CreateCodeForDiagnostic(const util::DiagnosticBase *error) if (error->Type() == util::DiagnosticType::PLUGIN_ERROR || error->Type() == util::DiagnosticType::PLUGIN_WARNING) { return uiCode; } - return 1; + auto code = static_cast(error)->GetId(); + return static_cast(error->Type()) * DIAGNOSTIC_CODE_MULTIPIER + code; } Diagnostic CreateDiagnosticForError(es2panda_Context *context, const util::DiagnosticBase &error) @@ -784,4 +787,40 @@ CombinedCodeActionsInfo GetCombinedCodeFixImpl(es2panda_Context *context, const return CreateCombinedCodeActionsInfo(fixes); } +varbinder::Decl *FindDeclInGlobalScope(varbinder::Scope *scope, const util::StringView &name) +{ + const auto *scopeIter = scope; + varbinder::Decl *resolved = nullptr; + while (scopeIter != nullptr && !scopeIter->IsGlobalScope()) { + bool isModule = scopeIter->Node() != nullptr && scopeIter->Node()->IsClassDefinition() && + scopeIter->Node()->AsClassDefinition()->IsModule(); + if (isModule) { + resolved = scopeIter->FindDecl(name); + if (resolved != nullptr) { + break; + } + } + scopeIter = scopeIter->Parent(); + } + if (resolved == nullptr && scopeIter != nullptr && scopeIter->IsGlobalScope()) { + resolved = scopeIter->FindDecl(name); + } + return resolved; +} + +varbinder::Decl *FindDeclInFunctionScope(varbinder::Scope *scope, const util::StringView &name) +{ + const auto *scopeIter = scope; + while (scopeIter != nullptr && !scopeIter->IsGlobalScope()) { + if (!scopeIter->IsClassScope()) { + if (auto *const resolved = scopeIter->FindDecl(name); resolved != nullptr) { + return resolved; + } + } + scopeIter = scopeIter->Parent(); + } + + return nullptr; +} + } // namespace ark::es2panda::lsp diff --git a/ets2panda/lsp/src/register_code_fix/add_missing_declare_property.cpp b/ets2panda/lsp/src/register_code_fix/add_missing_declare_property.cpp index 600252a8b0..2f032995df 100644 --- a/ets2panda/lsp/src/register_code_fix/add_missing_declare_property.cpp +++ b/ets2panda/lsp/src/register_code_fix/add_missing_declare_property.cpp @@ -80,7 +80,7 @@ CombinedCodeActions AddMissingDeclareProperty::GetAllCodeActions(const CodeFixAl const auto changes = provider.CodeFixAll(codeFixAll, GetErrorCodes(), [&](ChangeTracker &tracker, const DiagnosticWithLocation &diag) { - MakeChange(tracker, codeFixAll.context, diag.start, fixedNodes); + MakeChange(tracker, codeFixAll.context, diag.GetStart(), fixedNodes); }); CombinedCodeActions combinedCodeActions; diff --git a/ets2panda/lsp/src/register_code_fix/convert_const_to_let.cpp b/ets2panda/lsp/src/register_code_fix/convert_const_to_let.cpp new file mode 100644 index 0000000000..8b92bcdf9f --- /dev/null +++ b/ets2panda/lsp/src/register_code_fix/convert_const_to_let.cpp @@ -0,0 +1,111 @@ +/** + * 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/convert_const_to_let.h" + +#include +#include + +#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 codefixs::FIX_CONVERT_CONST_TO_LET; + +constexpr std::string_view KEYW_CONST_STR = "const"; +constexpr std::string_view KEYW_LET_STR = "let"; + +void FixConvertConstToLet::MakeChangeForConvertConstToLet(ChangeTracker &changeTracker, es2panda_Context *context, + size_t pos) +{ + auto *token = GetTouchingToken(context, pos, false); + if (token == nullptr || !token->IsNumberLiteral() || token->OriginalNode() == nullptr) { + return; + } + + auto *scope = compiler::NearestScope(token); + auto *resolvedDecl = FindDeclInFunctionScope(scope, token->OriginalNode()->AsIdentifier()->Name()); + if (resolvedDecl == nullptr) { + resolvedDecl = FindDeclInGlobalScope(scope, token->OriginalNode()->AsIdentifier()->Name()); + } + + if (resolvedDecl == nullptr) { + return; + } + + TextRange constTokenRange = {0, 0}; + changeTracker.RfindNearestKeyWordTextRange(context, resolvedDecl->Node()->Start().index, KEYW_CONST_STR, + constTokenRange); + if (constTokenRange.end == 0) { + return; + } + auto astContext = reinterpret_cast(context); + const std::string replaceText(KEYW_LET_STR); + changeTracker.ReplaceRangeWithText(astContext->sourceFile, constTokenRange, replaceText); +} + +std::vector FixConvertConstToLet::GetCodeActionsToConvertConstToLet(const CodeFixContext &context) +{ + TextChangesContext textChangesContext = {context.host, context.formatContext, context.preferences}; + auto fileTextChanges = ChangeTracker::With(textChangesContext, [&](ChangeTracker &tracker) { + MakeChangeForConvertConstToLet(tracker, context.context, context.span.start); + }); + + return fileTextChanges; +} + +FixConvertConstToLet::FixConvertConstToLet() +{ + auto errorCodes = FIX_CONVERT_CONST_TO_LET.GetSupportedCodeNumbers(); + SetErrorCodes({errorCodes.begin(), errorCodes.end()}); + SetFixIds({FIX_CONVERT_CONST_TO_LET.GetFixId().data()}); +} + +std::vector FixConvertConstToLet::GetCodeActions(const CodeFixContext &context) +{ + std::vector returnedActions; + auto changes = GetCodeActionsToConvertConstToLet(context); + if (!changes.empty()) { + CodeFixAction codeAction; + codeAction.fixName = FIX_CONVERT_CONST_TO_LET.GetFixId().data(); + codeAction.description = "Convert const to let"; + codeAction.changes = changes; + codeAction.fixId = FIX_CONVERT_CONST_TO_LET.GetFixId().data(); + codeAction.fixAllDescription = "Convert all const to let"; + returnedActions.push_back(codeAction); + } + + return returnedActions; +} + +CombinedCodeActions FixConvertConstToLet::GetAllCodeActions(const CodeFixAllContext &codeFixAllCtx) +{ + CodeFixProvider provider; + const auto changes = provider.CodeFixAll( + codeFixAllCtx, GetErrorCodes(), [&](ChangeTracker &tracker, const DiagnosticWithLocation &diag) { + MakeChangeForConvertConstToLet(tracker, codeFixAllCtx.context, diag.GetStart()); + }); + + CombinedCodeActions combinedCodeActions; + combinedCodeActions.changes = changes.changes; + combinedCodeActions.commands = changes.commands; + + return combinedCodeActions; +} +// NOLINTNEXTLINE(fuchsia-statically-constructed-objects, cert-err58-cpp) +AutoCodeFixRegister g_fixConvertConstToLet(FIX_CONVERT_CONST_TO_LET.GetFixId().data()); +} // namespace ark::es2panda::lsp diff --git a/ets2panda/lsp/src/register_code_fix/fix_nan_equality.cpp b/ets2panda/lsp/src/register_code_fix/fix_nan_equality.cpp index 11080e91fb..f1040baacc 100644 --- a/ets2panda/lsp/src/register_code_fix/fix_nan_equality.cpp +++ b/ets2panda/lsp/src/register_code_fix/fix_nan_equality.cpp @@ -109,9 +109,10 @@ CombinedCodeActions FixNaNEquality::GetAllCodeActions(const CodeFixAllContext &c const auto changes = provider.CodeFixAll( codeFixAll, GetErrorCodes(), [&](ChangeTracker &tracker, const DiagnosticWithLocation &diag) { Initializer initializer = Initializer(); - auto ctx = - initializer.CreateContext(diag.file.source.data(), ES2PANDA_STATE_CHECKED, diag.file.source.data()); - MakeChangeForNaNEquality(tracker, ctx, diag.start, const_cast &>(fixedNodes)); + auto ctx = initializer.CreateContext(diag.GetFile().source.data(), ES2PANDA_STATE_CHECKED, + diag.GetFile().source.data()); + MakeChangeForNaNEquality(tracker, ctx, diag.GetStart(), + const_cast &>(fixedNodes)); initializer.DestroyContext(ctx); }); diff --git a/ets2panda/lsp/src/services/text_change/change_tracker.cpp b/ets2panda/lsp/src/services/text_change/change_tracker.cpp index 5b4aca7e1e..68eb9f8a18 100644 --- a/ets2panda/lsp/src/services/text_change/change_tracker.cpp +++ b/ets2panda/lsp/src/services/text_change/change_tracker.cpp @@ -60,6 +60,21 @@ size_t ChangeTracker::GetStartPositionOfLine(size_t line, const es2panda_Context } return 0; } + +void ChangeTracker::RfindNearestKeyWordTextRange(const es2panda_Context *context, const size_t pos, + const std::string_view &keywordStr, TextRange &range) +{ + auto ctx = reinterpret_cast(const_cast(context)); + const std::string_view &sourceCode = ctx->parserProgram->SourceCode().Utf8(); + auto start = sourceCode.rfind(keywordStr, pos); + if (start == std::string_view::npos) { + return; + } + + range.pos = start; + range.end = start + keywordStr.length(); +} + bool ChangeTracker::RangeContainsPosition(TextRange r, size_t pos) { return r.pos <= pos && pos <= r.end; diff --git a/ets2panda/test/unit/lsp/CMakeLists.txt b/ets2panda/test/unit/lsp/CMakeLists.txt index 8106544cc6..34a445e67e 100644 --- a/ets2panda/test/unit/lsp/CMakeLists.txt +++ b/ets2panda/test/unit/lsp/CMakeLists.txt @@ -222,3 +222,7 @@ ets2panda_add_gtest(lsp_api_test_fix_abstract_member CPP_SOURCES ets2panda_add_gtest(lsp_api_test_get_name_or_dotted_name_span CPP_SOURCES get_name_or_dotted_name_span_test.cpp ) + +ets2panda_add_gtest(lsp_api_test_fix_convert_const_to_let CPP_SOURCES + fix_convert_const_to_let_test.cpp +) diff --git a/ets2panda/test/unit/lsp/code_fix_registration_test.cpp b/ets2panda/test/unit/lsp/code_fix_registration_test.cpp index 7e5b793cce..acb35182b5 100644 --- a/ets2panda/test/unit/lsp/code_fix_registration_test.cpp +++ b/ets2panda/test/unit/lsp/code_fix_registration_test.cpp @@ -27,4 +27,4 @@ TEST(RefactorProviderRegistrationTest, RegistersConvertFunctionRefactor) EXPECT_FALSE(fixIdToRegistration.empty()); EXPECT_EQ(errors.size(), fixIdToRegistration.size()); } -} // namespace \ No newline at end of file +} // namespace diff --git a/ets2panda/test/unit/lsp/fix_convert_const_to_let_test.cpp b/ets2panda/test/unit/lsp/fix_convert_const_to_let_test.cpp new file mode 100644 index 0000000000..56dbecd0d1 --- /dev/null +++ b/ets2panda/test/unit/lsp/fix_convert_const_to_let_test.cpp @@ -0,0 +1,205 @@ +/** + * 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/convert_const_to_let.h" + +namespace { + +using ark::es2panda::lsp::Initializer; +using ark::es2panda::lsp::codefixs::FIX_CONVERT_CONST_TO_LET; + +constexpr std::string_view EXPECTED_FIX_NAME = FIX_CONVERT_CONST_TO_LET.GetFixId(); +constexpr auto ERROR_CODES = 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 int DEFAULT_THROTTLE = 20; + +class FixConvertConstToLetTests : 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()); + 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); + } + +private: + class NullCancellationToken : public ark::es2panda::lsp::HostCancellationToken { + public: + bool IsCancellationRequested() override + { + return false; + } + }; + + static NullCancellationToken &GetNullHost() + { + static NullCancellationToken instance; + return instance; + } +}; + +TEST_F(FixConvertConstToLetTests, TestFixConvertConstToLet01) +{ + std::vector fileNames = {"TestFixConvertConstToLet01.ets"}; + std::vector fileContents = {R"( +const a:Int = 0; +a = 1; +)"}; + 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, 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()); + CodeFixOptions emptyOptions = {CreateNonCancellationToken(), 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, EXPECTED_FIX_NAME.data(), 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); +} + +TEST_F(FixConvertConstToLetTests, TestFixConvertConstToLet02) +{ + std::vector fileNames = {"TestFixConvertConstToLet02.ets"}; + std::vector fileContents = {R"( +const a = 0; +const b = 1; +a = 3; +b = 2; +)"}; + 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 start1 = LineColToPos(context, 4, 1); + const size_t length1 = 1; + const size_t start2 = LineColToPos(context, 5, 1); + const size_t length2 = 1; + const size_t expectedTextChangeStart1 = 1; + const size_t expectedTextChangeLength1 = 5; + const size_t expectedTextChangeStart2 = 14; + const size_t expectedTextChangeLength2 = 5; + const int expectedFixResultSize = 1; + const int expectedCombinedTextChangesSize = 2; + + std::vector errorCodes(ERROR_CODES.begin(), ERROR_CODES.end()); + CodeFixOptions emptyOptions = {CreateNonCancellationToken(), ark::es2panda::lsp::FormatCodeSettings(), {}}; + auto fixResult1 = + ark::es2panda::lsp::GetCodeFixesAtPositionImpl(context, start1, start1 + length1, errorCodes, emptyOptions); + auto fixResult2 = + ark::es2panda::lsp::GetCodeFixesAtPositionImpl(context, start2, start2 + length2, errorCodes, emptyOptions); + ASSERT_EQ(fixResult1.size(), expectedFixResultSize); + ValidateCodeFixActionInfo(fixResult1[0], expectedTextChangeStart1, expectedTextChangeLength1, filePaths[0]); + + ASSERT_EQ(fixResult2.size(), expectedFixResultSize); + ValidateCodeFixActionInfo(fixResult2[0], expectedTextChangeStart2, expectedTextChangeLength2, filePaths[0]); + + CombinedCodeActionsInfo combinedFixResult = + ark::es2panda::lsp::GetCombinedCodeFixImpl(context, EXPECTED_FIX_NAME.data(), 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, expectedTextChangeStart1); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[0].span.length, expectedTextChangeLength1); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[0].newText, EXPECTED_TEXT_CHANGE_NEW_TEXT.data()); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[1].span.start, expectedTextChangeStart2); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[1].span.length, expectedTextChangeLength2); + ASSERT_EQ(combinedFixResult.changes_[0].textChanges[1].newText, EXPECTED_TEXT_CHANGE_NEW_TEXT.data()); + initializer.DestroyContext(context); +} + +TEST_F(FixConvertConstToLetTests, TestFixConvertConstToLet03) +{ + std::vector fileNames = {"TestFixConvertConstToLet03.ets"}; + std::vector fileContents = {R"( +function f() { +const a = 1; +a = 2; +} +)"}; + 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, 4, 1); + const size_t length = 1; + const size_t expectedTextChangeStart = 16; + const size_t expectedTextChangeLength = 5; + const int expectedFixResultSize = 1; + const int expectedCombinedTextChangesSize = 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(), expectedFixResultSize); + ValidateCodeFixActionInfo(fixResult[0], expectedTextChangeStart, expectedTextChangeLength, filePaths[0]); + + CombinedCodeActionsInfo combinedFixResult = + ark::es2panda::lsp::GetCombinedCodeFixImpl(context, EXPECTED_FIX_NAME.data(), emptyOptions); + ASSERT_EQ(combinedFixResult.changes_.size(), 1); + 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 diff --git a/ets2panda/util/diagnostic.cpp b/ets2panda/util/diagnostic.cpp index 8104e2ad61..6ad23cc9af 100644 --- a/ets2panda/util/diagnostic.cpp +++ b/ets2panda/util/diagnostic.cpp @@ -142,6 +142,11 @@ DiagnosticType Diagnostic::Type() const return diagnosticKind_->Type(); } +uint32_t Diagnostic::GetId() const + { + return diagnosticKind_->Id(); + } + std::string Diagnostic::Message() const { return Format(diagnosticKind_->Message(), diagnosticParams_); @@ -232,6 +237,11 @@ DiagnosticType Suggestion::Type() const return kind_->Type(); } +uint32_t Suggestion::GetId() const +{ + return kind_->Id(); +} + Diagnostic::Diagnostic(const diagnostic::DiagnosticKind &diagnosticKind, const util::DiagnosticMessageParams &diagnosticParams, const lexer::SourcePosition &pos, std::initializer_list suggestions) diff --git a/ets2panda/util/diagnostic.h b/ets2panda/util/diagnostic.h index 1bf39261bf..2a64c36338 100644 --- a/ets2panda/util/diagnostic.h +++ b/ets2panda/util/diagnostic.h @@ -193,6 +193,8 @@ public: DiagnosticType Type() const override; + uint32_t GetId() const; + private: const diagnostic::DiagnosticKind *kind_; const std::string substitutionCode_; @@ -224,6 +226,7 @@ public: { return suggestions_ != nullptr; } + uint32_t GetId() const; const std::vector &Suggestion() const { diff --git a/ets2panda/util/diagnostic/semantic.yaml b/ets2panda/util/diagnostic/semantic.yaml index 568a3b7312..1e29b6f7a4 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -107,6 +107,7 @@ semantic: - name: ASSIGNMENT_INVALID_LHS id: 25 message: "Invalid left-hand side of assignment expression" + code_fix_ids: [FixConvertConstToLet] - name: ABSTRACT_CALL id: 26 @@ -758,6 +759,7 @@ semantic: - name: MISSING_OVERRIDE_OF_ABSTRACT_METH id: 190 message: "{} is not abstract and does not override abstract method {}{} in {}" + code_fix_ids: [FixClassNotImplementingInheritedMembers] - name: LOCAL_CLASS_INVALID_CTX id: 191 -- Gitee