Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract RubyFileChangeHandler #3225

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions lib/ruby_lsp/file_change_handlers/ruby.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module FileChangeHandlers
class Ruby
class << self
extend T::Sig
sig { params(store: Store, index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
def change(store, index, file_path, change_type)
load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)

case change_type
when Constant::FileChangeType::CREATED
content = File.read(file_path)
# If we receive a late created notification for a file that has already been claimed by the client, we want to
# handle change for that URI so that the require path tree is updated
store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
when Constant::FileChangeType::CHANGED
content = File.read(file_path)
# We only handle changes on file watched notifications if the client is not the one managing this URI.
# Otherwise, these changes are handled when running the combined requests
index.handle_change(uri, content) unless store.key?(uri)
when Constant::FileChangeType::DELETED
index.delete(uri)
end
rescue Errno::ENOENT
# If a file is created and then delete immediately afterwards, we will process the created notification before we
# receive the deleted one, but the file no longer exists. This may happen when running a test suite that creates
# and deletes files automatically.
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/ruby_lsp/internal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
require "ruby_lsp/rbs_document"
require "ruby_lsp/store"
require "ruby_lsp/addon"
require "ruby_lsp/file_change_handlers/ruby"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I put this in a namespace since we may want to extract handlers for other kinds kinds of files, e.g. RuboCop config).


# Response builders
require "ruby_lsp/response_builders/response_builder"
Expand Down
27 changes: 1 addition & 26 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,7 @@ def workspace_did_change_watched_files(message)
next if file_path.nil? || File.directory?(file_path)

if file_path.end_with?(".rb")
handle_ruby_file_change(index, file_path, change[:type])
FileChangeHandlers::Ruby.change(@store, index, file_path, change[:type])
next
end

Expand All @@ -1046,31 +1046,6 @@ def workspace_did_change_watched_files(message)
end
end

sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
def handle_ruby_file_change(index, file_path, change_type)
load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)

case change_type
when Constant::FileChangeType::CREATED
content = File.read(file_path)
# If we receive a late created notification for a file that has already been claimed by the client, we want to
# handle change for that URI so that the require path tree is updated
@store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
when Constant::FileChangeType::CHANGED
content = File.read(file_path)
# We only handle changes on file watched notifications if the client is not the one managing this URI.
# Otherwise, these changes are handled when running the combined requests
index.handle_change(uri, content) unless @store.key?(uri)
when Constant::FileChangeType::DELETED
index.delete(uri)
end
rescue Errno::ENOENT
# If a file is created and then delete immediately afterwards, we will process the created notification before we
# receive the deleted one, but the file no longer exists. This may happen when running a test suite that creates
# and deletes files automatically.
end

sig { params(uri: URI::Generic).void }
def handle_rubocop_config_change(uri)
return unless defined?(Requests::Support::RuboCopFormatter)
Expand Down
142 changes: 142 additions & 0 deletions test/file_change_handlers/ruby.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# typed: true
# frozen_string_literal: true

require "test_helper"

module RubyLsp
module FileChangeHandlers
class RubyTest < Minitest::Test
def setup
@server = RubyLsp::Server.new(test_mode: true)
end

def teardown
@server.run_shutdown
end

def test_did_change_watched_files_does_not_fail_for_non_existing_files
@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, "lib", "non_existing.rb")).to_s,
type: RubyLsp::Constant::FileChangeType::CREATED,
},
],
},
})

assert_raises(Timeout::Error) do
Timeout.timeout(0.5) do
notification = find_message(RubyLsp::Notification, "window/logMessage")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this test currently fails because find_message is not defined here)

flunk(notification.params.message)
end
end
end

def test_did_change_watched_files_handles_deletions
path = File.join(Dir.pwd, "lib", "foo.rb")

@server.global_state.index.expects(:delete).once.with do |uri|
uri.full_path == path
end

uri = URI::Generic.from_path(path: path)

@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: uri,
type: RubyLsp::Constant::FileChangeType::DELETED,
},
],
},
})
end

def test_did_change_watched_files_reports_addon_errors
Class.new(RubyLsp::Addon) do
def activate(global_state, outgoing_queue); end

def workspace_did_change_watched_files(changes)
raise StandardError, "boom"
end

def name
"Foo"
end

def deactivate; end

def version
"0.1.0"
end
end

Class.new(RubyLsp::Addon) do
def activate(global_state, outgoing_queue); end

def workspace_did_change_watched_files(changes)
end

def name
"Bar"
end

def deactivate; end

def version
"0.1.0"
end
end

@server.load_addons

bar = RubyLsp::Addon.get("Bar", "0.1.0")
bar.expects(:workspace_did_change_watched_files).once

begin
@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, ".rubocop.yml")).to_s,
type: RubyLsp::Constant::FileChangeType::CREATED,
},
],
},
})

message = @server.pop_response.params.message
assert_match("Error in Foo add-on while processing watched file notifications", message)
assert_match("boom", message)
ensure
RubyLsp::Addon.unload_addons
end
end

def test_did_change_watched_files_processes_unique_change_entries
@server.expects(:handle_rubocop_config_change).once
@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, ".rubocop.yml")).to_s,
type: RubyLsp::Constant::FileChangeType::CHANGED,
},
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, ".rubocop.yml")).to_s,
type: RubyLsp::Constant::FileChangeType::CHANGED,
},
],
},
})
end
end
end
end
124 changes: 0 additions & 124 deletions test/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -500,130 +500,6 @@ def test_changed_file_only_indexes_ruby
FileUtils.rm(T.must(path))
end

def test_did_change_watched_files_does_not_fail_for_non_existing_files
@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, "lib", "non_existing.rb")).to_s,
type: RubyLsp::Constant::FileChangeType::CREATED,
},
],
},
})

assert_raises(Timeout::Error) do
Timeout.timeout(0.5) do
notification = find_message(RubyLsp::Notification, "window/logMessage")
flunk(notification.params.message)
end
end
end

def test_did_change_watched_files_handles_deletions
path = File.join(Dir.pwd, "lib", "foo.rb")

@server.global_state.index.expects(:delete).once.with do |uri|
uri.full_path == path
end

uri = URI::Generic.from_path(path: path)

@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: uri,
type: RubyLsp::Constant::FileChangeType::DELETED,
},
],
},
})
end

def test_did_change_watched_files_reports_addon_errors
Class.new(RubyLsp::Addon) do
def activate(global_state, outgoing_queue); end

def workspace_did_change_watched_files(changes)
raise StandardError, "boom"
end

def name
"Foo"
end

def deactivate; end

def version
"0.1.0"
end
end

Class.new(RubyLsp::Addon) do
def activate(global_state, outgoing_queue); end

def workspace_did_change_watched_files(changes)
end

def name
"Bar"
end

def deactivate; end

def version
"0.1.0"
end
end

@server.load_addons

bar = RubyLsp::Addon.get("Bar", "0.1.0")
bar.expects(:workspace_did_change_watched_files).once

begin
@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, ".rubocop.yml")).to_s,
type: RubyLsp::Constant::FileChangeType::CREATED,
},
],
},
})

message = @server.pop_response.params.message
assert_match("Error in Foo add-on while processing watched file notifications", message)
assert_match("boom", message)
ensure
RubyLsp::Addon.unload_addons
end
end

def test_did_change_watched_files_processes_unique_change_entries
@server.expects(:handle_rubocop_config_change).once
@server.process_message({
method: "workspace/didChangeWatchedFiles",
params: {
changes: [
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, ".rubocop.yml")).to_s,
type: RubyLsp::Constant::FileChangeType::CHANGED,
},
{
uri: URI::Generic.from_path(path: File.join(Dir.pwd, ".rubocop.yml")).to_s,
type: RubyLsp::Constant::FileChangeType::CHANGED,
},
],
},
})
end

def test_workspace_addons
create_test_addons
@server.load_addons
Expand Down
Loading