Skip to content

Commit 57dc835

Browse files
committed
Fail requests that are searching for a non existing position
1 parent b0eb396 commit 57dc835

File tree

4 files changed

+76
-2
lines changed

4 files changed

+76
-2
lines changed

lib/ruby_lsp/document.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class LanguageId < T::Enum
1515
extend T::Helpers
1616
extend T::Generic
1717

18+
class LocationNotFoundError < StandardError; end
1819
ParseResultType = type_member
1920

2021
# This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
@@ -144,7 +145,15 @@ def initialize(source, encoding)
144145
def find_char_position(position)
145146
# Find the character index for the beginning of the requested line
146147
until @current_line == position[:line]
147-
@pos += 1 until LINE_BREAK == @source[@pos]
148+
until LINE_BREAK == @source[@pos]
149+
@pos += 1
150+
151+
if @pos >= @source.length
152+
# Pack the code points back into the original string to provide context in the error message
153+
raise LocationNotFoundError, "Requested position: #{position}\nSource:\n\n#{@source.pack("U*")}"
154+
end
155+
end
156+
148157
@pos += 1
149158
@current_line += 1
150159
end

lib/ruby_lsp/server.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,24 @@ def process_message(message)
119119
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
120120
# from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
121121
# reporting these to our telemetry
122-
if e.is_a?(Store::NonExistingDocumentError)
122+
case e
123+
when Store::NonExistingDocumentError
123124
send_message(Error.new(
124125
id: message[:id],
125126
code: Constant::ErrorCodes::INVALID_PARAMS,
126127
message: e.full_message,
127128
))
129+
when Document::LocationNotFoundError
130+
send_message(Error.new(
131+
id: message[:id],
132+
code: Constant::ErrorCodes::REQUEST_FAILED,
133+
message: <<~MESSAGE,
134+
Request #{message[:method]} failed to find the target position.
135+
The file might have been modified while the server was in the middle of searching for the target.
136+
If you experience this regularly, please report any findings and extra information on
137+
https://github.com/Shopify/ruby-lsp/issues/2446
138+
MESSAGE
139+
))
128140
else
129141
send_message(Error.new(
130142
id: message[:id],

test/ruby_document_test.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,29 @@ class Foo
770770
assert_nil(document.cache_get("textDocument/codeLens"))
771771
end
772772

773+
def test_locating_a_non_existing_location_raises
774+
document = RubyLsp::RubyDocument.new(source: <<~RUBY.chomp, version: 1, uri: URI("file:///foo/bar.rb"))
775+
class Foo
776+
end
777+
RUBY
778+
779+
# Exactly at the last character doesn't raise
780+
document.locate_node({ line: 1, character: 2 })
781+
782+
# Anything beyond does
783+
error = assert_raises(RubyLsp::Document::LocationNotFoundError) do
784+
document.locate_node({ line: 3, character: 2 })
785+
end
786+
787+
assert_equal(<<~MESSAGE.chomp, error.message)
788+
Requested position: {:line=>3, :character=>2}
789+
Source:
790+
791+
class Foo
792+
end
793+
MESSAGE
794+
end
795+
773796
private
774797

775798
def assert_error_edit(actual, error_range)

test/server_test.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,36 @@ def handle_window_show_message_response(title)
714714
end
715715
end
716716

717+
def test_requests_to_a_non_existing_position_return_error
718+
uri = URI("file:///foo.rb")
719+
720+
@server.process_message({
721+
method: "textDocument/didOpen",
722+
params: {
723+
textDocument: {
724+
uri: uri,
725+
text: "class Foo\nend",
726+
version: 1,
727+
languageId: "ruby",
728+
},
729+
},
730+
})
731+
732+
@server.process_message({
733+
id: 1,
734+
method: "textDocument/completion",
735+
params: {
736+
textDocument: {
737+
uri: uri,
738+
},
739+
position: { line: 10, character: 0 },
740+
},
741+
})
742+
743+
error = find_message(RubyLsp::Error)
744+
assert_match("Request textDocument/completion failed to find the target position.", error.message)
745+
end
746+
717747
private
718748

719749
def with_uninstalled_rubocop(&block)

0 commit comments

Comments
 (0)