Skip to content

Commit 6c4702c

Browse files
authored
Move Sorbet level and locate_first_within_range to RubyDocument (#2435)
* Move sorbet_level to RubyDocument * Move locate_first_within_range to RubyDocument
1 parent 3f2046a commit 6c4702c

14 files changed

+101
-94
lines changed

lib/ruby_lsp/document.rb

-64
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,6 @@ class LanguageId < T::Enum
1010
end
1111
end
1212

13-
class SorbetLevel < T::Enum
14-
enums do
15-
None = new("none")
16-
Ignore = new("ignore")
17-
False = new("false")
18-
True = new("true")
19-
Strict = new("strict")
20-
end
21-
end
22-
2313
extend T::Sig
2414
extend T::Helpers
2515

@@ -223,60 +213,6 @@ def locate(node, char_position, node_types: [])
223213
NodeContext.new(closest, parent, nesting_nodes, call_node)
224214
end
225215

226-
sig do
227-
params(
228-
range: T::Hash[Symbol, T.untyped],
229-
node_types: T::Array[T.class_of(Prism::Node)],
230-
).returns(T.nilable(Prism::Node))
231-
end
232-
def locate_first_within_range(range, node_types: [])
233-
scanner = create_scanner
234-
start_position = scanner.find_char_position(range[:start])
235-
end_position = scanner.find_char_position(range[:end])
236-
desired_range = (start_position...end_position)
237-
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
238-
239-
until queue.empty?
240-
candidate = queue.shift
241-
242-
# Skip nil child nodes
243-
next if candidate.nil?
244-
245-
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
246-
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
247-
# sibling
248-
T.unsafe(queue).unshift(*candidate.child_nodes)
249-
250-
# Skip if the current node doesn't cover the desired position
251-
loc = candidate.location
252-
253-
if desired_range.cover?(loc.start_offset...loc.end_offset) &&
254-
(node_types.empty? || node_types.any? { |type| candidate.class == type })
255-
return candidate
256-
end
257-
end
258-
end
259-
260-
sig { returns(SorbetLevel) }
261-
def sorbet_level
262-
sigil = parse_result.magic_comments.find do |comment|
263-
comment.key == "typed"
264-
end&.value
265-
266-
case sigil
267-
when "ignore"
268-
SorbetLevel::Ignore
269-
when "false"
270-
SorbetLevel::False
271-
when "true"
272-
SorbetLevel::True
273-
when "strict", "strong"
274-
SorbetLevel::Strict
275-
else
276-
SorbetLevel::None
277-
end
278-
end
279-
280216
class Scanner
281217
extend T::Sig
282218

lib/ruby_lsp/listeners/completion.rb

+5-5
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class Completion
5656
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
5757
global_state: GlobalState,
5858
node_context: NodeContext,
59-
sorbet_level: Document::SorbetLevel,
59+
sorbet_level: RubyDocument::SorbetLevel,
6060
dispatcher: Prism::Dispatcher,
6161
uri: URI::Generic,
6262
trigger_character: T.nilable(String),
@@ -99,7 +99,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
9999
def on_constant_read_node_enter(node)
100100
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
101101
# no sigil, Sorbet will still provide completion for constants
102-
return if @sorbet_level != Document::SorbetLevel::Ignore
102+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
103103

104104
name = constant_name(node)
105105
return if name.nil?
@@ -122,7 +122,7 @@ def on_constant_read_node_enter(node)
122122
def on_constant_path_node_enter(node)
123123
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
124124
# no sigil, Sorbet will still provide completion for constants
125-
return if @sorbet_level != Document::SorbetLevel::Ignore
125+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
126126

127127
name = constant_name(node)
128128
return if name.nil?
@@ -134,7 +134,7 @@ def on_constant_path_node_enter(node)
134134
def on_call_node_enter(node)
135135
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
136136
# no sigil, Sorbet will still provide completion for constants
137-
if @sorbet_level == Document::SorbetLevel::Ignore
137+
if @sorbet_level == RubyDocument::SorbetLevel::Ignore
138138
receiver = node.receiver
139139

140140
# When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
@@ -257,7 +257,7 @@ def constant_path_completion(name, range)
257257
def handle_instance_variable_completion(name, location)
258258
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
259259
# to provide all features for them
260-
return if @sorbet_level == Document::SorbetLevel::Strict
260+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
261261

262262
type = @type_inferrer.infer_receiver_type(@node_context)
263263
return unless type

lib/ruby_lsp/listeners/definition.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Definition
2020
uri: URI::Generic,
2121
node_context: NodeContext,
2222
dispatcher: Prism::Dispatcher,
23-
sorbet_level: Document::SorbetLevel,
23+
sorbet_level: RubyDocument::SorbetLevel,
2424
).void
2525
end
2626
def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
@@ -181,7 +181,7 @@ def handle_super_node_definition
181181
def handle_instance_variable_definition(name)
182182
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
183183
# to provide all features for them
184-
return if @sorbet_level == Document::SorbetLevel::Strict
184+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
185185

186186
type = @type_inferrer.infer_receiver_type(@node_context)
187187
return unless type
@@ -289,7 +289,7 @@ def find_in_index(value)
289289
# additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
290290
# ignore
291291
file_path = entry.file_path
292-
next if @sorbet_level != Document::SorbetLevel::Ignore && not_in_dependencies?(file_path)
292+
next if @sorbet_level != RubyDocument::SorbetLevel::Ignore && not_in_dependencies?(file_path)
293293

294294
@response_builder << Interface::LocationLink.new(
295295
target_uri: URI::Generic.from_path(path: file_path).to_s,

lib/ruby_lsp/listeners/hover.rb

+5-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class Hover
4242
uri: URI::Generic,
4343
node_context: NodeContext,
4444
dispatcher: Prism::Dispatcher,
45-
sorbet_level: Document::SorbetLevel,
45+
sorbet_level: RubyDocument::SorbetLevel,
4646
).void
4747
end
4848
def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
@@ -73,7 +73,7 @@ def initialize(response_builder, global_state, uri, node_context, dispatcher, so
7373

7474
sig { params(node: Prism::ConstantReadNode).void }
7575
def on_constant_read_node_enter(node)
76-
return if @sorbet_level != Document::SorbetLevel::Ignore
76+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
7777

7878
name = constant_name(node)
7979
return if name.nil?
@@ -83,14 +83,14 @@ def on_constant_read_node_enter(node)
8383

8484
sig { params(node: Prism::ConstantWriteNode).void }
8585
def on_constant_write_node_enter(node)
86-
return if @sorbet_level != Document::SorbetLevel::Ignore
86+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
8787

8888
generate_hover(node.name.to_s, node.name_loc)
8989
end
9090

9191
sig { params(node: Prism::ConstantPathNode).void }
9292
def on_constant_path_node_enter(node)
93-
return if @sorbet_level != Document::SorbetLevel::Ignore
93+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
9494

9595
name = constant_name(node)
9696
return if name.nil?
@@ -193,7 +193,7 @@ def handle_method_hover(message, inherited_only: false)
193193
def handle_instance_variable_hover(name)
194194
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
195195
# to provide all features for them
196-
return if @sorbet_level == Document::SorbetLevel::Strict
196+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
197197

198198
type = @type_inferrer.infer_receiver_type(@node_context)
199199
return unless type

lib/ruby_lsp/listeners/signature_help.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class SignatureHelp
1313
global_state: GlobalState,
1414
node_context: NodeContext,
1515
dispatcher: Prism::Dispatcher,
16-
sorbet_level: Document::SorbetLevel,
16+
sorbet_level: RubyDocument::SorbetLevel,
1717
).void
1818
end
1919
def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)

lib/ruby_lsp/requests/code_action_resolve.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Error < ::T::Enum
3838
end
3939
end
4040

41-
sig { params(document: Document, code_action: T::Hash[Symbol, T.untyped]).void }
41+
sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
4242
def initialize(document, code_action)
4343
super()
4444
@document = document

lib/ruby_lsp/requests/completion.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def provider
4949
document: Document,
5050
global_state: GlobalState,
5151
params: T::Hash[Symbol, T.untyped],
52-
sorbet_level: Document::SorbetLevel,
52+
sorbet_level: RubyDocument::SorbetLevel,
5353
dispatcher: Prism::Dispatcher,
5454
).void
5555
end

lib/ruby_lsp/requests/definition.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class Definition < Request
4242
global_state: GlobalState,
4343
position: T::Hash[Symbol, T.untyped],
4444
dispatcher: Prism::Dispatcher,
45-
sorbet_level: Document::SorbetLevel,
45+
sorbet_level: RubyDocument::SorbetLevel,
4646
).void
4747
end
4848
def initialize(document, global_state, position, dispatcher, sorbet_level)

lib/ruby_lsp/requests/hover.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def provider
3636
global_state: GlobalState,
3737
position: T::Hash[Symbol, T.untyped],
3838
dispatcher: Prism::Dispatcher,
39-
sorbet_level: Document::SorbetLevel,
39+
sorbet_level: RubyDocument::SorbetLevel,
4040
).void
4141
end
4242
def initialize(document, global_state, position, dispatcher, sorbet_level)

lib/ruby_lsp/requests/signature_help.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def provider
4646
position: T::Hash[Symbol, T.untyped],
4747
context: T.nilable(T::Hash[Symbol, T.untyped]),
4848
dispatcher: Prism::Dispatcher,
49-
sorbet_level: Document::SorbetLevel,
49+
sorbet_level: RubyDocument::SorbetLevel,
5050
).void
5151
end
5252
def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists

lib/ruby_lsp/requests/support/common.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,9 @@ def kind_for_entry(entry)
209209
end
210210
end
211211

212-
sig { params(sorbet_level: Document::SorbetLevel).returns(T::Boolean) }
212+
sig { params(sorbet_level: RubyDocument::SorbetLevel).returns(T::Boolean) }
213213
def sorbet_level_true_or_higher?(sorbet_level)
214-
sorbet_level == Document::SorbetLevel::True || sorbet_level == Document::SorbetLevel::Strict
214+
sorbet_level == RubyDocument::SorbetLevel::True || sorbet_level == RubyDocument::SorbetLevel::Strict
215215
end
216216
end
217217
end

lib/ruby_lsp/ruby_document.rb

+64
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33

44
module RubyLsp
55
class RubyDocument < Document
6+
class SorbetLevel < T::Enum
7+
enums do
8+
None = new("none")
9+
Ignore = new("ignore")
10+
False = new("false")
11+
True = new("true")
12+
Strict = new("strict")
13+
end
14+
end
15+
616
sig { override.returns(Prism::ParseResult) }
717
def parse
818
return @parse_result unless @needs_parsing
@@ -20,5 +30,59 @@ def syntax_error?
2030
def language_id
2131
LanguageId::Ruby
2232
end
33+
34+
sig { returns(SorbetLevel) }
35+
def sorbet_level
36+
sigil = parse_result.magic_comments.find do |comment|
37+
comment.key == "typed"
38+
end&.value
39+
40+
case sigil
41+
when "ignore"
42+
SorbetLevel::Ignore
43+
when "false"
44+
SorbetLevel::False
45+
when "true"
46+
SorbetLevel::True
47+
when "strict", "strong"
48+
SorbetLevel::Strict
49+
else
50+
SorbetLevel::None
51+
end
52+
end
53+
54+
sig do
55+
params(
56+
range: T::Hash[Symbol, T.untyped],
57+
node_types: T::Array[T.class_of(Prism::Node)],
58+
).returns(T.nilable(Prism::Node))
59+
end
60+
def locate_first_within_range(range, node_types: [])
61+
scanner = create_scanner
62+
start_position = scanner.find_char_position(range[:start])
63+
end_position = scanner.find_char_position(range[:end])
64+
desired_range = (start_position...end_position)
65+
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
66+
67+
until queue.empty?
68+
candidate = queue.shift
69+
70+
# Skip nil child nodes
71+
next if candidate.nil?
72+
73+
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
74+
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
75+
# sibling
76+
T.unsafe(queue).unshift(*candidate.child_nodes)
77+
78+
# Skip if the current node doesn't cover the desired position
79+
loc = candidate.location
80+
81+
if desired_range.cover?(loc.start_offset...loc.end_offset) &&
82+
(node_types.empty? || node_types.any? { |type| candidate.class == type })
83+
return candidate
84+
end
85+
end
86+
end
2387
end
2488
end

lib/ruby_lsp/server.rb

+9-2
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,10 @@ def text_document_hover(message)
495495
)
496496
end
497497

498-
sig { params(document: Document).returns(Document::SorbetLevel) }
498+
sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
499499
def sorbet_level(document)
500-
return Document::SorbetLevel::Ignore unless @global_state.has_type_checker
500+
return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
501+
return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
501502

502503
document.sorbet_level
503504
end
@@ -535,6 +536,12 @@ def code_action_resolve(message)
535536
params = message[:params]
536537
uri = URI(params.dig(:data, :uri))
537538
document = @store.get(uri)
539+
540+
unless document.is_a?(RubyDocument)
541+
send_message(Notification.window_show_error("Code actions are currently only available for Ruby documents"))
542+
raise Requests::CodeActionResolve::CodeActionError
543+
end
544+
538545
result = Requests::CodeActionResolve.new(document, params).perform
539546

540547
case result

0 commit comments

Comments
 (0)