From bb18130cbf83dfff86d376621199c82112b91fd9 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Fri, 17 May 2024 13:53:04 +0900 Subject: [PATCH 01/15] Write failing test for formatter The result's response is nil, but (one assumes) it shouldn't be $ bundle exec m test/ruby_lsp_addon_test.rb --- Gemfile | 2 + Gemfile.lock | 11 ++- lib/ruby_lsp/standard/addon.rb | 55 ++++++++++++ .../wraps_built_in_lsp_standardizer.rb | 89 +++++++++++++++++++ test/ruby_lsp_addon_test.rb | 69 ++++++++++++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 lib/ruby_lsp/standard/addon.rb create mode 100644 lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb create mode 100644 test/ruby_lsp_addon_test.rb diff --git a/Gemfile b/Gemfile index 0ca8b324..cfda9294 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,8 @@ gem "bundler" gem "minitest", "~> 5.0" gem "rake", "~> 13.0" gem "m" +gem "mutex_m" +gem "ruby-lsp" # You may want to run these off path locally: # gem "lint_roller", path: "../lint_roller" diff --git a/Gemfile.lock b/Gemfile.lock index ad21bebc..deae607c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -21,10 +21,12 @@ GEM rake (>= 0.9.2.2) method_source (1.0.0) minitest (5.20.0) + mutex_m (0.2.0) parallel (1.23.0) parser (3.3.0.5) ast (~> 2.4.1) racc + prism (0.27.0) racc (1.7.1) rainbow (3.1.1) rake (13.0.6) @@ -46,6 +48,10 @@ GEM rubocop-performance (1.21.0) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) + ruby-lsp (0.16.6) + language_server-protocol (~> 3.17.0) + prism (>= 0.23.0, < 0.28) + sorbet-runtime (>= 0.5.10782) ruby-progressbar (1.13.0) simplecov (0.22.0) docile (~> 1.1) @@ -53,6 +59,7 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) + sorbet-runtime (0.5.11380) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) @@ -69,9 +76,11 @@ DEPENDENCIES bundler m minitest (~> 5.0) + mutex_m rake (~> 13.0) + ruby-lsp simplecov standard! BUNDLED WITH - 2.4.12 + 2.5.7 diff --git a/lib/ruby_lsp/standard/addon.rb b/lib/ruby_lsp/standard/addon.rb new file mode 100644 index 00000000..5270b756 --- /dev/null +++ b/lib/ruby_lsp/standard/addon.rb @@ -0,0 +1,55 @@ +require_relative "wraps_built_in_lsp_standardizer" + +module RubyLsp + module Standard + class Addon < ::RubyLsp::Addon + def initializer + @wraps_built_in_lsp_standardizer = nil + end + + def name + "Standard Ruby" + end + + def activate(global_state, message_queue) + @wraps_built_in_lsp_standardizer = WrapsBuiltinLspStandardizer.new + global_state.register_formatter("standard", @wraps_built_in_lsp_standardizer) + end + + def deactivate + @wraps_built_in_lsp_standardizer = nil + end + + def register_additional_file_watchers(global_state, message_queue) + return unless global_state.supports_watching_files + + message_queue << Request.new( + id: "standard-file-watcher", + method: "client/registerCapability", + params: Interface::RegistrationParams.new( + registrations: [ + Interface::Registration.new( + id: "workspace/didChangeWatchedFilesMyGem", + method: "workspace/didChangeWatchedFiles", + register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new( + watchers: [ + Interface::FileSystemWatcher.new( + glob_pattern: "**/.standard.yml", + kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE + ) + ] + ) + ) + ] + ) + ) + end + + def workspace_did_change_watched_files(changes) + if changes.any? { |change| change[:uri].end_with?(".standard.yml") } + @wraps_built_in_lsp_standardizer.init! + end + end + end + end +end diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb new file mode 100644 index 00000000..6eb64384 --- /dev/null +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -0,0 +1,89 @@ +require "lint_roller" +require_relative "../../standard/builds_config" +require_relative "../../standard/lsp/standardizer" +require_relative "../../standard/lsp/logger" + +module RubyLsp + module Standard + class WrapsBuiltinLspStandardizer + include RubyLsp::Requests::Support::Formatter + def initialize + init! + end + + def init! + @config = ::Standard::BuildsConfig.new.call(ARGV) + @standardizer = ::Standard::Lsp::Standardizer.new( + @config, + ::Standard::Lsp::Logger.new + ) + @cop_registry = RuboCop::Cop::Registry.global.to_h + end + + def run_formatting(uri, document) + # TODO: this isn't being triggered by my test + @standardizer.format(uri_to_path(uri), document.source) + end + + def run_diagnostic(uri, document) + offenses = @standardizer.offenses(uri_to_path(uri), document.source) + + offenses.map { |o| + cop_name = o[:cop_name] + + msg = o[:message].delete_prefix(cop_name) + loc = o[:location] + + severity = case o[:severity] + when "error", "fatal" + RubyLsp::Constant::DiagnosticSeverity::ERROR + when "warning" + RubyLsp::Constant::DiagnosticSeverity::WARNING + when "convention" + RubyLsp::Constant::DiagnosticSeverity::INFORMATION + when "refactor", "info" + RubyLsp::Constant::DiagnosticSeverity::HINT + else # the above cases fully cover what RuboCop sends at this time + logger.puts "Unknown severity: #{severity.inspect}" + RubyLsp::Constant::DiagnosticSeverity::HINT + end + + RubyLsp::Interface::Diagnostic.new( + code: cop_name, + code_description: code_description(cop_name), + message: msg, + source: "Standard Ruby", + severity: severity, + range: RubyLsp::Interface::Range.new( + start: RubyLsp::Interface::Position.new(line: loc[:start_line] - 1, character: loc[:start_column] - 1), + end: RubyLsp::Interface::Position.new(line: loc[:last_line] - 1, character: loc[:last_column] - 1) + ) + # TODO: do I need something like this? + # See: https://github.com/Shopify/ruby-lsp/blob/4c1906172add4d5c39c35d3396aa29c768bfb898/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb#L62 + # data: { + # correctable: @offense.correctable?, + # code_actions: to_lsp_code_actions + # } + ) + } + end + + private + + # duplicated from: lib/standard/lsp/routes.rb + def uri_to_path(uri) + uri.sub(%r{^file://}, "") + end + + # lifted from: + # https://github.com/Shopify/ruby-lsp/blob/4c1906172add4d5c39c35d3396aa29c768bfb898/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb#L84 + def code_description(cop_name) + if (cop_class = @cop_registry[cop_name]&.first) + if (doc_url = cop_class.documentation_url) + Interface::CodeDescription.new(href: doc_url) + end + end + end + end + end +end diff --git a/test/ruby_lsp_addon_test.rb b/test/ruby_lsp_addon_test.rb new file mode 100644 index 00000000..553e6176 --- /dev/null +++ b/test/ruby_lsp_addon_test.rb @@ -0,0 +1,69 @@ +require "ruby_lsp/internal" +require "test_helper" +require "ruby_lsp/standard/addon" + +class RubyLspAddonTest < UnitTest + def setup + @addon = RubyLsp::Standard::Addon.new + super + end + + def test_name + assert_equal "Standard Ruby", @addon.name + end + + def test_format + source = <<~RUBY + s = 'hello' + puts s + RUBY + with_server(source) do |server, uri| + server.process_message( + id: 1, + method: "textDocument/formatting", + params: {textDocument: {uri: uri}, position: {line: 0, character: 0}} + ) + + result = server.pop_response + + assert_instance_of(RubyLsp::Result, result) + refute_nil result.response + end + end + + private + + # Lifted from here, because we need to override the formatter to "standard" in the test helper: + # https://github.com/Shopify/ruby-lsp/blob/4c1906172add4d5c39c35d3396aa29c768bfb898/lib/ruby_lsp/test_helper.rb#L20 + def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true, + &block) + server = RubyLsp::Server.new(test_mode: true) + server.global_state.formatter = "standard" # <-- TODO this should work, right? + server.global_state.stubs(:typechecker).returns(false) if stub_no_typechecker + + if source + server.process_message({ + method: "textDocument/didOpen", + params: { + textDocument: { + uri: uri, + text: source, + version: 1 + } + } + }) + end + + server.global_state.index.index_single( + RubyIndexer::IndexablePath.new(nil, uri.to_standardized_path), + source + ) + server.load_addons if load_addons + block.call(server, uri) + ensure + if load_addons + RubyLsp::Addon.addons.each(&:deactivate) + RubyLsp::Addon.addons.clear + end + end +end From adf15e751353cbccdaecb76c35d5fdc1c214296c Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Fri, 17 May 2024 15:39:44 +0900 Subject: [PATCH 02/15] closer! everything makes sense except for this range end value --- .../wraps_built_in_lsp_standardizer.rb | 3 +- test/fixture/ruby_lsp/.standard.yml | 0 test/fixture/ruby_lsp/simple.rb | 2 + test/ruby_lsp_addon_test.rb | 60 ++++++++++++------- 4 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 test/fixture/ruby_lsp/.standard.yml create mode 100644 test/fixture/ruby_lsp/simple.rb diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb index 6eb64384..4c4403cc 100644 --- a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -21,7 +21,6 @@ def init! end def run_formatting(uri, document) - # TODO: this isn't being triggered by my test @standardizer.format(uri_to_path(uri), document.source) end @@ -72,7 +71,7 @@ def run_diagnostic(uri, document) # duplicated from: lib/standard/lsp/routes.rb def uri_to_path(uri) - uri.sub(%r{^file://}, "") + uri.to_s.sub(%r{^file://}, "") end # lifted from: diff --git a/test/fixture/ruby_lsp/.standard.yml b/test/fixture/ruby_lsp/.standard.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/fixture/ruby_lsp/simple.rb b/test/fixture/ruby_lsp/simple.rb new file mode 100644 index 00000000..1110d83a --- /dev/null +++ b/test/fixture/ruby_lsp/simple.rb @@ -0,0 +1,2 @@ +s = 'hi' +puts s diff --git a/test/ruby_lsp_addon_test.rb b/test/ruby_lsp_addon_test.rb index 553e6176..e05d5de2 100644 --- a/test/ruby_lsp_addon_test.rb +++ b/test/ruby_lsp_addon_test.rb @@ -17,7 +17,7 @@ def test_format s = 'hello' puts s RUBY - with_server(source) do |server, uri| + with_server(source, "test/fixture/ruby_lsp/simple.rb") do |server, uri| server.process_message( id: 1, method: "textDocument/formatting", @@ -27,7 +27,18 @@ def test_format result = server.pop_response assert_instance_of(RubyLsp::Result, result) - refute_nil result.response + assert 1, result.response.size + assert_equal({ + range: RubyLsp::Interface::Range.new( + start: RubyLsp::Interface::Position.new(line: 0, character: 0), + # Fails! Actual is line: 19, character: 19?????? + end: RubyLsp::Interface::Position.new(line: 2, character: 5) + ), + newText: <<~RUBY + s = "hello" + puts s + RUBY + }, result.response.first.to_hash) end end @@ -35,31 +46,34 @@ def test_format # Lifted from here, because we need to override the formatter to "standard" in the test helper: # https://github.com/Shopify/ruby-lsp/blob/4c1906172add4d5c39c35d3396aa29c768bfb898/lib/ruby_lsp/test_helper.rb#L20 - def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true, + def with_server(source = nil, path = "fake.rb", pwd: "test/fixture/ruby_lsp", stub_no_typechecker: false, load_addons: true, &block) - server = RubyLsp::Server.new(test_mode: true) - server.global_state.formatter = "standard" # <-- TODO this should work, right? - server.global_state.stubs(:typechecker).returns(false) if stub_no_typechecker + Dir.chdir pwd do + server = RubyLsp::Server.new(test_mode: true) + uri = Kernel.URI(File.join(server.global_state.workspace_path, path)) + server.global_state.formatter = "standard" # <-- TODO this should work, right? + server.global_state.stubs(:typechecker).returns(false) if stub_no_typechecker - if source - server.process_message({ - method: "textDocument/didOpen", - params: { - textDocument: { - uri: uri, - text: source, - version: 1 + if source + server.process_message({ + method: "textDocument/didOpen", + params: { + textDocument: { + uri: uri, + text: source, + version: 1 + } } - } - }) - end + }) + end - server.global_state.index.index_single( - RubyIndexer::IndexablePath.new(nil, uri.to_standardized_path), - source - ) - server.load_addons if load_addons - block.call(server, uri) + server.global_state.index.index_single( + RubyIndexer::IndexablePath.new(nil, uri.to_standardized_path), + source + ) + server.load_addons if load_addons + block.call(server, uri) + end ensure if load_addons RubyLsp::Addon.addons.each(&:deactivate) From 36531a7e515d5e8e371cb2a71ff56e617b8eb860 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Mon, 20 May 2024 13:02:22 +0900 Subject: [PATCH 03/15] get LSP addon working, maybe --- Gemfile.lock | 2 +- test/ruby_lsp_addon_test.rb | 56 +++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index deae607c..e2ca69c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -59,7 +59,7 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - sorbet-runtime (0.5.11380) + sorbet-runtime (0.5.11385) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) diff --git a/test/ruby_lsp_addon_test.rb b/test/ruby_lsp_addon_test.rb index e05d5de2..6429cd36 100644 --- a/test/ruby_lsp_addon_test.rb +++ b/test/ruby_lsp_addon_test.rb @@ -1,6 +1,6 @@ require "ruby_lsp/internal" -require "test_helper" require "ruby_lsp/standard/addon" +require_relative "test_helper" class RubyLspAddonTest < UnitTest def setup @@ -12,14 +12,46 @@ def test_name assert_equal "Standard Ruby", @addon.name end + def test_diagnostic + source = <<~RUBY + s = 'hello' + puts s + RUBY + with_server(source, "simple.rb") do |server, uri| + server.process_message( + id: 2, + method: "textDocument/diagnostic", + params: { + textDocument: { + uri: uri + } + } + ) + + result = server.pop_response + + assert_instance_of(RubyLsp::Result, result) + assert_equal "full", result.response.kind + assert_equal 1, result.response.items.size + item = result.response.items.first + assert_equal({line: 0, character: 4}, item.range.start.to_hash) + assert_equal({line: 0, character: 10}, item.range.end.to_hash) + assert_equal RubyLsp::Constant::DiagnosticSeverity::INFORMATION, item.severity + assert_equal "Style/StringLiterals", item.code + assert_equal "https://docs.rubocop.org/rubocop/cops_style.html#stylestringliterals", item.code_description.href + assert_equal "Standard Ruby", item.source + assert_equal "Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.", item.message + end + end + def test_format source = <<~RUBY s = 'hello' puts s RUBY - with_server(source, "test/fixture/ruby_lsp/simple.rb") do |server, uri| + with_server(source, "simple.rb") do |server, uri| server.process_message( - id: 1, + id: 2, method: "textDocument/formatting", params: {textDocument: {uri: uri}, position: {line: 0, character: 0}} ) @@ -28,17 +60,10 @@ def test_format assert_instance_of(RubyLsp::Result, result) assert 1, result.response.size - assert_equal({ - range: RubyLsp::Interface::Range.new( - start: RubyLsp::Interface::Position.new(line: 0, character: 0), - # Fails! Actual is line: 19, character: 19?????? - end: RubyLsp::Interface::Position.new(line: 2, character: 5) - ), - newText: <<~RUBY - s = "hello" - puts s - RUBY - }, result.response.first.to_hash) + assert_equal <<~RUBY, result.response.first.new_text + s = "hello" + puts s + RUBY end end @@ -51,7 +76,8 @@ def with_server(source = nil, path = "fake.rb", pwd: "test/fixture/ruby_lsp", st Dir.chdir pwd do server = RubyLsp::Server.new(test_mode: true) uri = Kernel.URI(File.join(server.global_state.workspace_path, path)) - server.global_state.formatter = "standard" # <-- TODO this should work, right? + server.global_state.formatter = "standard" + server.global_state.instance_variable_set(:@linters, ["standard"]) server.global_state.stubs(:typechecker).returns(false) if stub_no_typechecker if source From ae5f6b24143d79b58c646e6b2783b0b999b6b591 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Thu, 23 May 2024 10:51:30 +0900 Subject: [PATCH 04/15] "Fix" the issue with cli_test breaking by manually requiring everything the test server needs --- test/ruby_lsp_addon_test.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/ruby_lsp_addon_test.rb b/test/ruby_lsp_addon_test.rb index 6429cd36..edf45943 100644 --- a/test/ruby_lsp_addon_test.rb +++ b/test/ruby_lsp_addon_test.rb @@ -1,5 +1,21 @@ -require "ruby_lsp/internal" +# All of these requires were needed because `ruby_lsp/internal` mutates rubocop +# in a way that breaks test/standard/cli_test.rb +require "sorbet-runtime" +require "language_server-protocol" +require "ruby_lsp/base_server" +require "ruby_lsp/server" +require "ruby_lsp/requests" +require "ruby_lsp/addon" +require "ruby_lsp/utils" +require "ruby_lsp/store" +require "ruby_lsp/document" +require "ruby_lsp/global_state" +require "core_ext/uri" +require "ruby_indexer/ruby_indexer" +require "ruby_lsp/ruby_document" +require "prism" require "ruby_lsp/standard/addon" + require_relative "test_helper" class RubyLspAddonTest < UnitTest From 98adf0c896575d342c09f952f0f84d9146d0f987 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Thu, 23 May 2024 10:54:32 +0900 Subject: [PATCH 05/15] Accidentally bumped to a bundler that requires ruby 3 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e2ca69c4..3a70df62 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -83,4 +83,4 @@ DEPENDENCIES standard! BUNDLED WITH - 2.5.7 + 2.4.12 From 9fbdb838abf456ecddcfc00fdbfb12a16b0f6f09 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Thu, 23 May 2024 11:00:13 +0900 Subject: [PATCH 06/15] update standard's internal language target to 3.0 --- .standard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.standard.yml b/.standard.yml index 5d4a7077..0f13c2e6 100644 --- a/.standard.yml +++ b/.standard.yml @@ -1,4 +1,4 @@ -ruby_version: 2.6 +ruby_version: 3.0 ignore: - tmp/**/* - test/fixture/**/* From 60d1393b24276b5653624b7c8dc22afc2a3d62f0 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Thu, 23 May 2024 11:01:14 +0900 Subject: [PATCH 07/15] fix standard errors when targeting 3.0+ --- lib/standard/plugin/merges_plugins_into_rubocop_config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/standard/plugin/merges_plugins_into_rubocop_config.rb b/lib/standard/plugin/merges_plugins_into_rubocop_config.rb index 6883c566..1a297c26 100644 --- a/lib/standard/plugin/merges_plugins_into_rubocop_config.rb +++ b/lib/standard/plugin/merges_plugins_into_rubocop_config.rb @@ -154,7 +154,7 @@ def blank_rubocop_config(example_config) end def except(hash_or_config, keys) - hash_or_config.to_h.reject { |key, _| keys.include?(key) }.to_h + hash_or_config.to_h.except(*keys).to_h end # Always deletes nil entries, always overwrites arrays From 77359eeb06d0c27223531c6039262349678bfa8b Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Fri, 24 May 2024 21:27:17 +0900 Subject: [PATCH 08/15] be less cute by just requiring standard properly --- lib/ruby_lsp/standard/addon.rb | 1 + lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/ruby_lsp/standard/addon.rb b/lib/ruby_lsp/standard/addon.rb index 5270b756..92e1fade 100644 --- a/lib/ruby_lsp/standard/addon.rb +++ b/lib/ruby_lsp/standard/addon.rb @@ -1,3 +1,4 @@ +require "standard" require_relative "wraps_built_in_lsp_standardizer" module RubyLsp diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb index 4c4403cc..b00b0a06 100644 --- a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -1,8 +1,3 @@ -require "lint_roller" -require_relative "../../standard/builds_config" -require_relative "../../standard/lsp/standardizer" -require_relative "../../standard/lsp/logger" - module RubyLsp module Standard class WrapsBuiltinLspStandardizer From 2ac669c9e1dcf157e4681d73ed8aa95d615e918b Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Sun, 23 Jun 2024 10:52:31 -0400 Subject: [PATCH 09/15] Output startup status --- lib/ruby_lsp/standard/addon.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ruby_lsp/standard/addon.rb b/lib/ruby_lsp/standard/addon.rb index 92e1fade..c31f8e88 100644 --- a/lib/ruby_lsp/standard/addon.rb +++ b/lib/ruby_lsp/standard/addon.rb @@ -13,8 +13,10 @@ def name end def activate(global_state, message_queue) + warn "Activating Standard Ruby LSP addon v#{::Standard::VERSION}" @wraps_built_in_lsp_standardizer = WrapsBuiltinLspStandardizer.new global_state.register_formatter("standard", @wraps_built_in_lsp_standardizer) + warn "Initialized Standard Ruby LSP addon #{::Standard::VERSION}" end def deactivate @@ -49,6 +51,7 @@ def register_additional_file_watchers(global_state, message_queue) def workspace_did_change_watched_files(changes) if changes.any? { |change| change[:uri].end_with?(".standard.yml") } @wraps_built_in_lsp_standardizer.init! + warn "Re-initialized Standard Ruby LSP addon #{::Standard::VERSION} due to .standard.yml file change" end end end From c2e6870d1afe478aa964b0a9ac3631dac7adbf05 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Sun, 23 Jun 2024 10:52:52 -0400 Subject: [PATCH 10/15] Invoke (and appropriately name) the file watcher so the addon re-initializes successfully --- lib/ruby_lsp/standard/addon.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ruby_lsp/standard/addon.rb b/lib/ruby_lsp/standard/addon.rb index c31f8e88..031bfe29 100644 --- a/lib/ruby_lsp/standard/addon.rb +++ b/lib/ruby_lsp/standard/addon.rb @@ -16,6 +16,7 @@ def activate(global_state, message_queue) warn "Activating Standard Ruby LSP addon v#{::Standard::VERSION}" @wraps_built_in_lsp_standardizer = WrapsBuiltinLspStandardizer.new global_state.register_formatter("standard", @wraps_built_in_lsp_standardizer) + register_additional_file_watchers(global_state, message_queue) warn "Initialized Standard Ruby LSP addon #{::Standard::VERSION}" end @@ -32,7 +33,7 @@ def register_additional_file_watchers(global_state, message_queue) params: Interface::RegistrationParams.new( registrations: [ Interface::Registration.new( - id: "workspace/didChangeWatchedFilesMyGem", + id: "workspace/didChangeWatchedFilesStandard", method: "workspace/didChangeWatchedFiles", register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new( watchers: [ From 768a27f560867da72d56e1c74850e4a13941794e Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Sun, 23 Jun 2024 10:53:17 -0400 Subject: [PATCH 11/15] Send an empty ARGV since we aren't in control of it --- lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb index b00b0a06..33037fd6 100644 --- a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -7,7 +7,7 @@ def initialize end def init! - @config = ::Standard::BuildsConfig.new.call(ARGV) + @config = ::Standard::BuildsConfig.new.call([]) @standardizer = ::Standard::Lsp::Standardizer.new( @config, ::Standard::Lsp::Logger.new From 2f2d54b22e3517e9841359559666e51541dfc435 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Sun, 23 Jun 2024 10:54:18 -0400 Subject: [PATCH 12/15] Use the config-dependent documentation_url call from rubocop 1.64.0 --- lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb index 33037fd6..767bb2ac 100644 --- a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -12,6 +12,7 @@ def init! @config, ::Standard::Lsp::Logger.new ) + @rubocop_config = @config.rubocop_config_store.for_pwd @cop_registry = RuboCop::Cop::Registry.global.to_h end @@ -73,7 +74,7 @@ def uri_to_path(uri) # https://github.com/Shopify/ruby-lsp/blob/4c1906172add4d5c39c35d3396aa29c768bfb898/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb#L84 def code_description(cop_name) if (cop_class = @cop_registry[cop_name]&.first) - if (doc_url = cop_class.documentation_url) + if (doc_url = cop_class.documentation_url(@rubocop_config)) Interface::CodeDescription.new(href: doc_url) end end From f00f3e0b38124112960e9d5b0d7e1ff0aa1f54e5 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Sun, 23 Jun 2024 10:54:40 -0400 Subject: [PATCH 13/15] Use Ruby LSP's URI#to_standardized_path method when available and not returning nil --- lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb index 767bb2ac..ddcf60e4 100644 --- a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -66,8 +66,13 @@ def run_diagnostic(uri, document) private # duplicated from: lib/standard/lsp/routes.rb + # modified to incorporate Ruby LSP's to_standardized_path method def uri_to_path(uri) - uri.to_s.sub(%r{^file://}, "") + if uri.respond_to?(:to_standardized_path) && !(standardized_path = uri.to_standardized_path).nil? + standardized_path + else + uri.to_s.sub(%r{^file://}, "") + end end # lifted from: From f51abba665b02c39f392bdbe66c7cf2218dabe65 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Sun, 23 Jun 2024 10:55:08 -0400 Subject: [PATCH 14/15] Update this comment. Code actions will require invoking a long-lived RuboCop executor directly instead of via --stdin --- .../standard/wraps_built_in_lsp_standardizer.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb index ddcf60e4..23794042 100644 --- a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -53,12 +53,21 @@ def run_diagnostic(uri, document) start: RubyLsp::Interface::Position.new(line: loc[:start_line] - 1, character: loc[:start_column] - 1), end: RubyLsp::Interface::Position.new(line: loc[:last_line] - 1, character: loc[:last_column] - 1) ) - # TODO: do I need something like this? + # TODO: We need to do something like to support quickfixes thru code actions # See: https://github.com/Shopify/ruby-lsp/blob/4c1906172add4d5c39c35d3396aa29c768bfb898/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb#L62 # data: { - # correctable: @offense.correctable?, + # correctable: correctable?(offense), # code_actions: to_lsp_code_actions # } + # + # Right now, our offenses are all just JSON parsed from stdout shelling to RuboCop, so + # it seems we don't have the corrector available to us. + # + # Lifted from: + # https://github.com/Shopify/ruby-lsp/blob/8d4c17efce4e8ecc8e7c557ab2981db6b22c0b6d/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb#L201 + # def correctable?(offense) + # !offense.corrector.nil? + # end ) } end From 5ce7a2b7c5c8e36d045f155fdf5fba05e9c575d5 Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Sun, 23 Jun 2024 11:27:58 -0400 Subject: [PATCH 15/15] backport https://github.com/standardrb/standard/commit/de139d7385c36d195b2b1fbadd5c112764f60b8b --- lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb | 2 +- test/ruby_lsp_addon_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb index 23794042..60012a2a 100644 --- a/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb +++ b/lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb @@ -51,7 +51,7 @@ def run_diagnostic(uri, document) severity: severity, range: RubyLsp::Interface::Range.new( start: RubyLsp::Interface::Position.new(line: loc[:start_line] - 1, character: loc[:start_column] - 1), - end: RubyLsp::Interface::Position.new(line: loc[:last_line] - 1, character: loc[:last_column] - 1) + end: RubyLsp::Interface::Position.new(line: loc[:last_line] - 1, character: loc[:last_column]) ) # TODO: We need to do something like to support quickfixes thru code actions # See: https://github.com/Shopify/ruby-lsp/blob/4c1906172add4d5c39c35d3396aa29c768bfb898/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb#L62 diff --git a/test/ruby_lsp_addon_test.rb b/test/ruby_lsp_addon_test.rb index edf45943..8c1f12d3 100644 --- a/test/ruby_lsp_addon_test.rb +++ b/test/ruby_lsp_addon_test.rb @@ -51,7 +51,7 @@ def test_diagnostic assert_equal 1, result.response.items.size item = result.response.items.first assert_equal({line: 0, character: 4}, item.range.start.to_hash) - assert_equal({line: 0, character: 10}, item.range.end.to_hash) + assert_equal({line: 0, character: 11}, item.range.end.to_hash) assert_equal RubyLsp::Constant::DiagnosticSeverity::INFORMATION, item.severity assert_equal "Style/StringLiterals", item.code assert_equal "https://docs.rubocop.org/rubocop/cops_style.html#stylestringliterals", item.code_description.href