Skip to content

Commit 8d80bc6

Browse files
SeanKGjscharf
andauthored
Support Sorbet typed: false files for completion requests (#1245)
* enable completion on typed: false files Co-authored-by: Joshua Scharf <jscharf@users.noreply.github.com> * Add tests for completion.rb Co-authored-by: Joshua Scharf <jscharf@users.noreply.github.com> * use prism to look for magic comments * rename method and clean up tests * use asser/refute_predicate * Add a test for other comments / strings * Remove completion on constants --------- Co-authored-by: Joshua Scharf <jscharf@users.noreply.github.com>
1 parent 2233c04 commit 8d80bc6

File tree

5 files changed

+133
-4
lines changed

5 files changed

+133
-4
lines changed

lib/ruby_lsp/document.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,18 @@ def locate(node, char_position, node_types: [])
173173
[closest, parent, nesting.map { |n| n.constant_path.location.slice }]
174174
end
175175

176+
sig { returns(T::Boolean) }
177+
def sorbet_sigil_is_true_or_higher
178+
parse_result.magic_comments.any? do |comment|
179+
comment.key == "typed" && ["true", "strict", "strong"].include?(comment.value)
180+
end
181+
end
182+
183+
sig { returns(T::Boolean) }
184+
def typechecker_enabled?
185+
DependencyDetector.instance.typechecker && sorbet_sigil_is_true_or_higher
186+
end
187+
176188
class Scanner
177189
extend T::Sig
178190

lib/ruby_lsp/executor.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ def completion(uri, position)
555555
return unless target
556556

557557
dispatcher = Prism::Dispatcher.new
558-
listener = Requests::Completion.new(@index, nesting, dispatcher)
558+
listener = Requests::Completion.new(@index, nesting, dispatcher, document.typechecker_enabled?)
559559
dispatcher.dispatch_once(target)
560560
listener.response
561561
end

lib/ruby_lsp/requests/completion.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ class Completion < Listener
3636
index: RubyIndexer::Index,
3737
nesting: T::Array[String],
3838
dispatcher: Prism::Dispatcher,
39+
typechecker_enabled: T::Boolean,
3940
).void
4041
end
41-
def initialize(index, nesting, dispatcher)
42+
def initialize(index, nesting, dispatcher, typechecker_enabled)
4243
super(dispatcher)
4344
@_response = T.let([], ResponseType)
4445
@index = index
4546
@nesting = nesting
47+
@typechecker_enabled = typechecker_enabled
4648

4749
dispatcher.register(
4850
self,
@@ -125,7 +127,7 @@ def on_constant_path_node_enter(node)
125127

126128
sig { params(node: Prism::CallNode).void }
127129
def on_call_node_enter(node)
128-
return if DependencyDetector.instance.typechecker
130+
return if @typechecker_enabled
129131
return unless self_receiver?(node)
130132

131133
name = node.message

test/requests/completion_test.rb

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ def setup
99
@uri = URI("file:///fake.rb")
1010
@store = RubyLsp::Store.new
1111
@executor = RubyLsp::Executor.new(@store, @message_queue)
12-
stub_no_typechecker
1312
end
1413

1514
def teardown
@@ -175,6 +174,7 @@ def test_completion_is_not_triggered_if_argument_is_not_a_string
175174
end
176175

177176
def test_completion_for_constants
177+
stub_no_typechecker
178178
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: @uri)
179179
class Foo
180180
end
@@ -196,6 +196,7 @@ class Foo
196196
end
197197

198198
def test_completion_for_constant_paths
199+
stub_no_typechecker
199200
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: @uri)
200201
class Bar
201202
end
@@ -235,6 +236,7 @@ module Foo
235236
end
236237

237238
def test_completion_conflicting_constants
239+
stub_no_typechecker
238240
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: @uri)
239241
module Foo
240242
class Qux; end
@@ -264,6 +266,7 @@ class Qux; end
264266
end
265267

266268
def test_completion_for_top_level_constants_inside_nesting
269+
stub_no_typechecker
267270
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: @uri)
268271
class Bar
269272
end
@@ -292,6 +295,7 @@ module Foo
292295
end
293296

294297
def test_completion_private_constants_inside_the_same_namespace
298+
stub_no_typechecker
295299
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: @uri)
296300
class A
297301
CONST = 1
@@ -542,6 +546,58 @@ def qux
542546
assert_equal(["bar", "bar="], result.map { |completion| completion.text_edit.new_text })
543547
end
544548

549+
def test_with_typed_false
550+
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: @uri)
551+
# typed: false
552+
class Foo
553+
def complete_me
554+
end
555+
556+
def you
557+
comp
558+
end
559+
end
560+
RUBY
561+
562+
end_position = { line: 6, character: 8 }
563+
@store.set(uri: @uri, source: document.source, version: 1)
564+
565+
index = @executor.instance_variable_get(:@index)
566+
index.index_single(RubyIndexer::IndexablePath.new(nil, @uri.to_standardized_path), document.source)
567+
568+
result = run_request(
569+
method: "textDocument/completion",
570+
params: { textDocument: { uri: @uri.to_s }, position: end_position },
571+
)
572+
assert_equal(["complete_me"], result.map(&:label))
573+
end
574+
575+
def test_with_typed_true
576+
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: @uri)
577+
# typed: true
578+
class Foo
579+
def complete_me
580+
end
581+
582+
def you
583+
comp
584+
end
585+
end
586+
RUBY
587+
588+
end_position = { line: 6, character: 8 }
589+
@store.set(uri: @uri, source: document.source, version: 1)
590+
591+
index = @executor.instance_variable_get(:@index)
592+
index.index_single(RubyIndexer::IndexablePath.new(nil, @uri.to_standardized_path), document.source)
593+
594+
result = run_request(
595+
method: "textDocument/completion",
596+
params: { textDocument: { uri: @uri.to_s }, position: end_position },
597+
)
598+
assert_empty(result)
599+
end
600+
545601
private
546602

547603
def run_request(method:, params: {})

test/ruby_document_test.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,65 @@ def test_cache_set_and_get
536536
assert_equal(value, document.cache_get("textDocument/semanticHighlighting"))
537537
end
538538

539+
def test_no_sigil
540+
document = RubyLsp::RubyDocument.new(
541+
source: +"# frozen_string_literal: true",
542+
version: 1,
543+
uri: URI("file:///foo/bar.rb"),
544+
)
545+
refute_predicate(document, :sorbet_sigil_is_true_or_higher)
546+
end
547+
548+
def test_sigil_ignore
549+
document = RubyLsp::RubyDocument.new(source: +"# typed: ignored", version: 1, uri: URI("file:///foo/bar.rb"))
550+
refute_predicate(document, :sorbet_sigil_is_true_or_higher)
551+
end
552+
553+
def test_sigil_false
554+
document = RubyLsp::RubyDocument.new(source: +"# typed: false", version: 1, uri: URI("file:///foo/bar.rb"))
555+
refute_predicate(document, :sorbet_sigil_is_true_or_higher)
556+
end
557+
558+
def test_sigil_true
559+
document = RubyLsp::RubyDocument.new(source: +"# typed: true", version: 1, uri: URI("file:///foo/bar.rb"))
560+
assert_predicate(document, :sorbet_sigil_is_true_or_higher)
561+
end
562+
563+
def test_sigil_strict
564+
document = RubyLsp::RubyDocument.new(source: +"# typed: strict", version: 1, uri: URI("file:///foo/bar.rb"))
565+
assert_predicate(document, :sorbet_sigil_is_true_or_higher)
566+
end
567+
568+
def test_sigil_strong
569+
document = RubyLsp::RubyDocument.new(source: +"# typed: strong", version: 1, uri: URI("file:///foo/bar.rb"))
570+
assert_predicate(document, :sorbet_sigil_is_true_or_higher)
571+
end
572+
573+
def test_sorbet_sigil_only_in_magic_comment
574+
document = RubyLsp::RubyDocument.new(source: +<<~RUBY, version: 1, uri: URI("file:///foo/bar.rb"))
575+
# typed: false
576+
577+
def foo
578+
some_string = "# typed: true"
579+
end
580+
581+
# Shouldn't be tricked by the following comment:
582+
# ```
583+
# # typed: strict
584+
#
585+
# def main; end
586+
# ```
587+
def bar; end
588+
589+
def baz
590+
<<-CODE
591+
# typed: strong
592+
CODE
593+
end
594+
RUBY
595+
refute_predicate(document, :sorbet_sigil_is_true_or_higher)
596+
end
597+
539598
private
540599

541600
def assert_error_edit(actual, error_range)

0 commit comments

Comments
 (0)