diff --git a/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb b/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb index 12a7a515e6..67a6ccbbfe 100644 --- a/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +++ b/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb @@ -38,8 +38,8 @@ def to_lsp_code_actions code_actions end - sig { returns(Interface::Diagnostic) } - def to_lsp_diagnostic + sig { params(config: RuboCop::Config).returns(Interface::Diagnostic) } + def to_lsp_diagnostic(config) # highlighted_area contains the begin and end position of the first line # This ensures that multiline offenses don't clutter the editor highlighted = @offense.highlighted_area @@ -47,7 +47,7 @@ def to_lsp_diagnostic message: message, source: "RuboCop", code: @offense.cop_name, - code_description: code_description, + code_description: code_description(config), severity: severity, range: Interface::Range.new( start: Interface::Position.new( @@ -80,9 +80,17 @@ def severity RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name] end - sig { returns(T.nilable(Interface::CodeDescription)) } - def code_description - doc_url = RuboCopRunner.find_cop_by_name(@offense.cop_name)&.documentation_url + sig { params(config: RuboCop::Config).returns(T.nilable(Interface::CodeDescription)) } + def code_description(config) + cop = RuboCopRunner.find_cop_by_name(@offense.cop_name) + return unless cop + + doc_url = if cop.method(:documentation_url).parameters.none? + # Rubocop < 1.64 + cop.documentation_url + else + cop.documentation_url(config) + end Interface::CodeDescription.new(href: doc_url) if doc_url end diff --git a/lib/ruby_lsp/requests/support/rubocop_formatter.rb b/lib/ruby_lsp/requests/support/rubocop_formatter.rb index d11355f6a4..c2018ed8a0 100644 --- a/lib/ruby_lsp/requests/support/rubocop_formatter.rb +++ b/lib/ruby_lsp/requests/support/rubocop_formatter.rb @@ -38,7 +38,7 @@ def run_diagnostic(uri, document) @diagnostic_runner.run(filename, document.source) @diagnostic_runner.offenses.map do |offense| - Support::RuboCopDiagnostic.new(document, offense, uri).to_lsp_diagnostic + Support::RuboCopDiagnostic.new(document, offense, uri).to_lsp_diagnostic(@diagnostic_runner.config_for_pwd) end end end diff --git a/lib/ruby_lsp/requests/support/rubocop_runner.rb b/lib/ruby_lsp/requests/support/rubocop_runner.rb index 53001d3a1a..b443965700 100644 --- a/lib/ruby_lsp/requests/support/rubocop_runner.rb +++ b/lib/ruby_lsp/requests/support/rubocop_runner.rb @@ -50,6 +50,9 @@ class ConfigurationError < StandardError; end sig { returns(T::Array[RuboCop::Cop::Offense]) } attr_reader :offenses + sig { returns(::RuboCop::Config) } + attr_reader :config_for_pwd + DEFAULT_ARGS = T.let( [ "--stderr", # Print any output to stderr so that our stdout does not get polluted @@ -78,6 +81,7 @@ def initialize(*args) args += DEFAULT_ARGS rubocop_options = ::RuboCop::Options.new.parse(args).first config_store = ::RuboCop::ConfigStore.new + @config_for_pwd = T.let(config_store.for_pwd, ::RuboCop::Config) super(rubocop_options, config_store) end diff --git a/test/expectations/diagnostics/rubocop_extension_cop.exp.json b/test/expectations/diagnostics/rubocop_extension_cop.exp.json new file mode 100644 index 0000000000..8e0900cda6 --- /dev/null +++ b/test/expectations/diagnostics/rubocop_extension_cop.exp.json @@ -0,0 +1,100 @@ +{ + "result": [ + { + "range": { + "start": { + "line": 5, + "character": 4 + }, + "end": { + "line": 5, + "character": 25 + } + }, + "severity": 3, + "code": "Minitest/AssertEmptyLiteral", + "codeDescription": { + "href": "https://docs.rubocop.org/rubocop-minitest/cops_minitest.html#minitestassertemptyliteral" + }, + "source": "RuboCop", + "message": "Minitest/AssertEmptyLiteral: Prefer using `assert_empty(foo)`.", + "data": { + "correctable": true, + "code_actions": [ + { + "title": "Autocorrect Minitest/AssertEmptyLiteral", + "kind": "quickfix", + "isPreferred": true, + "edit": { + "documentChanges": [ + { + "textDocument": { + "uri": "file:///fake", + "version": null + }, + "edits": [ + { + "range": { + "start": { + "line": 5, + "character": 4 + }, + "end": { + "line": 5, + "character": 16 + } + }, + "newText": "assert_empty" + }, + { + "range": { + "start": { + "line": 5, + "character": 17 + }, + "end": { + "line": 5, + "character": 24 + } + }, + "newText": "foo" + } + ] + } + ] + } + }, + { + "title": "Disable Minitest/AssertEmptyLiteral for this line", + "kind": "quickfix", + "edit": { + "documentChanges": [ + { + "textDocument": { + "uri": "file:///fake", + "version": null + }, + "edits": [ + { + "range": { + "start": { + "line": 5, + "character": 25 + }, + "end": { + "line": 5, + "character": 25 + } + }, + "newText": " # rubocop:disable Minitest/AssertEmptyLiteral" + } + ] + } + ] + } + } + ] + } + } + ] +} diff --git a/test/fixtures/rubocop_extension_cop.rb b/test/fixtures/rubocop_extension_cop.rb new file mode 100644 index 0000000000..fd23103f2e --- /dev/null +++ b/test/fixtures/rubocop_extension_cop.rb @@ -0,0 +1,8 @@ +# typed: true +# frozen_string_literal: true + +class ExampleTest < Minitest::Test + def test_public + assert_equal([], foo) + end +end