Skip to content

Commit

Permalink
Write failing test for formatter
Browse files Browse the repository at this point in the history
The result's response is nil, but (one assumes) it shouldn't be

$ bundle exec m test/ruby_lsp_addon_test.rb
  • Loading branch information
searls committed May 17, 2024
1 parent 44b7061 commit 72cd1c3
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 10 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -46,13 +48,18 @@ 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)
simplecov-html (~> 0.11)
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)
Expand All @@ -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
55 changes: 55 additions & 0 deletions lib/ruby_lsp/standard/addon.rb
Original file line number Diff line number Diff line change
@@ -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
89 changes: 89 additions & 0 deletions lib/ruby_lsp/standard/wraps_built_in_lsp_standardizer.rb
Original file line number Diff line number Diff line change
@@ -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
69 changes: 69 additions & 0 deletions test/ruby_lsp_addon_test.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 72cd1c3

Please sign in to comment.