Skip to content

Commit

Permalink
Move Sorbet level and locate_first_within_range to RubyDocument (#2435
Browse files Browse the repository at this point in the history
)

* Move sorbet_level to RubyDocument

* Move locate_first_within_range to RubyDocument
  • Loading branch information
vinistock authored Aug 13, 2024
1 parent 3f2046a commit 6c4702c
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 94 deletions.
64 changes: 0 additions & 64 deletions lib/ruby_lsp/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@ class LanguageId < T::Enum
end
end

class SorbetLevel < T::Enum
enums do
None = new("none")
Ignore = new("ignore")
False = new("false")
True = new("true")
Strict = new("strict")
end
end

extend T::Sig
extend T::Helpers

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

sig do
params(
range: T::Hash[Symbol, T.untyped],
node_types: T::Array[T.class_of(Prism::Node)],
).returns(T.nilable(Prism::Node))
end
def locate_first_within_range(range, node_types: [])
scanner = create_scanner
start_position = scanner.find_char_position(range[:start])
end_position = scanner.find_char_position(range[:end])
desired_range = (start_position...end_position)
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])

until queue.empty?
candidate = queue.shift

# Skip nil child nodes
next if candidate.nil?

# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
# sibling
T.unsafe(queue).unshift(*candidate.child_nodes)

# Skip if the current node doesn't cover the desired position
loc = candidate.location

if desired_range.cover?(loc.start_offset...loc.end_offset) &&
(node_types.empty? || node_types.any? { |type| candidate.class == type })
return candidate
end
end
end

sig { returns(SorbetLevel) }
def sorbet_level
sigil = parse_result.magic_comments.find do |comment|
comment.key == "typed"
end&.value

case sigil
when "ignore"
SorbetLevel::Ignore
when "false"
SorbetLevel::False
when "true"
SorbetLevel::True
when "strict", "strong"
SorbetLevel::Strict
else
SorbetLevel::None
end
end

class Scanner
extend T::Sig

Expand Down
10 changes: 5 additions & 5 deletions lib/ruby_lsp/listeners/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Completion
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
global_state: GlobalState,
node_context: NodeContext,
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
dispatcher: Prism::Dispatcher,
uri: URI::Generic,
trigger_character: T.nilable(String),
Expand Down Expand Up @@ -99,7 +99,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
def on_constant_read_node_enter(node)
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
# no sigil, Sorbet will still provide completion for constants
return if @sorbet_level != Document::SorbetLevel::Ignore
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore

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

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

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

type = @type_inferrer.infer_receiver_type(@node_context)
return unless type
Expand Down
6 changes: 3 additions & 3 deletions lib/ruby_lsp/listeners/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Definition
uri: URI::Generic,
node_context: NodeContext,
dispatcher: Prism::Dispatcher,
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
).void
end
def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
Expand Down Expand Up @@ -181,7 +181,7 @@ def handle_super_node_definition
def handle_instance_variable_definition(name)
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
# to provide all features for them
return if @sorbet_level == Document::SorbetLevel::Strict
return if @sorbet_level == RubyDocument::SorbetLevel::Strict

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

@response_builder << Interface::LocationLink.new(
target_uri: URI::Generic.from_path(path: file_path).to_s,
Expand Down
10 changes: 5 additions & 5 deletions lib/ruby_lsp/listeners/hover.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Hover
uri: URI::Generic,
node_context: NodeContext,
dispatcher: Prism::Dispatcher,
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
).void
end
def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
Expand Down Expand Up @@ -73,7 +73,7 @@ def initialize(response_builder, global_state, uri, node_context, dispatcher, so

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

name = constant_name(node)
return if name.nil?
Expand All @@ -83,14 +83,14 @@ def on_constant_read_node_enter(node)

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

generate_hover(node.name.to_s, node.name_loc)
end

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

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

type = @type_inferrer.infer_receiver_type(@node_context)
return unless type
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_lsp/listeners/signature_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class SignatureHelp
global_state: GlobalState,
node_context: NodeContext,
dispatcher: Prism::Dispatcher,
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
).void
end
def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_lsp/requests/code_action_resolve.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Error < ::T::Enum
end
end

sig { params(document: Document, code_action: T::Hash[Symbol, T.untyped]).void }
sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
def initialize(document, code_action)
super()
@document = document
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_lsp/requests/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def provider
document: Document,
global_state: GlobalState,
params: T::Hash[Symbol, T.untyped],
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
dispatcher: Prism::Dispatcher,
).void
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_lsp/requests/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Definition < Request
global_state: GlobalState,
position: T::Hash[Symbol, T.untyped],
dispatcher: Prism::Dispatcher,
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
).void
end
def initialize(document, global_state, position, dispatcher, sorbet_level)
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_lsp/requests/hover.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def provider
global_state: GlobalState,
position: T::Hash[Symbol, T.untyped],
dispatcher: Prism::Dispatcher,
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
).void
end
def initialize(document, global_state, position, dispatcher, sorbet_level)
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_lsp/requests/signature_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def provider
position: T::Hash[Symbol, T.untyped],
context: T.nilable(T::Hash[Symbol, T.untyped]),
dispatcher: Prism::Dispatcher,
sorbet_level: Document::SorbetLevel,
sorbet_level: RubyDocument::SorbetLevel,
).void
end
def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby_lsp/requests/support/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,9 @@ def kind_for_entry(entry)
end
end

sig { params(sorbet_level: Document::SorbetLevel).returns(T::Boolean) }
sig { params(sorbet_level: RubyDocument::SorbetLevel).returns(T::Boolean) }
def sorbet_level_true_or_higher?(sorbet_level)
sorbet_level == Document::SorbetLevel::True || sorbet_level == Document::SorbetLevel::Strict
sorbet_level == RubyDocument::SorbetLevel::True || sorbet_level == RubyDocument::SorbetLevel::Strict
end
end
end
Expand Down
64 changes: 64 additions & 0 deletions lib/ruby_lsp/ruby_document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@

module RubyLsp
class RubyDocument < Document
class SorbetLevel < T::Enum
enums do
None = new("none")
Ignore = new("ignore")
False = new("false")
True = new("true")
Strict = new("strict")
end
end

sig { override.returns(Prism::ParseResult) }
def parse
return @parse_result unless @needs_parsing
Expand All @@ -20,5 +30,59 @@ def syntax_error?
def language_id
LanguageId::Ruby
end

sig { returns(SorbetLevel) }
def sorbet_level
sigil = parse_result.magic_comments.find do |comment|
comment.key == "typed"
end&.value

case sigil
when "ignore"
SorbetLevel::Ignore
when "false"
SorbetLevel::False
when "true"
SorbetLevel::True
when "strict", "strong"
SorbetLevel::Strict
else
SorbetLevel::None
end
end

sig do
params(
range: T::Hash[Symbol, T.untyped],
node_types: T::Array[T.class_of(Prism::Node)],
).returns(T.nilable(Prism::Node))
end
def locate_first_within_range(range, node_types: [])
scanner = create_scanner
start_position = scanner.find_char_position(range[:start])
end_position = scanner.find_char_position(range[:end])
desired_range = (start_position...end_position)
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])

until queue.empty?
candidate = queue.shift

# Skip nil child nodes
next if candidate.nil?

# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
# sibling
T.unsafe(queue).unshift(*candidate.child_nodes)

# Skip if the current node doesn't cover the desired position
loc = candidate.location

if desired_range.cover?(loc.start_offset...loc.end_offset) &&
(node_types.empty? || node_types.any? { |type| candidate.class == type })
return candidate
end
end
end
end
end
11 changes: 9 additions & 2 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,10 @@ def text_document_hover(message)
)
end

sig { params(document: Document).returns(Document::SorbetLevel) }
sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
def sorbet_level(document)
return Document::SorbetLevel::Ignore unless @global_state.has_type_checker
return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)

document.sorbet_level
end
Expand Down Expand Up @@ -535,6 +536,12 @@ def code_action_resolve(message)
params = message[:params]
uri = URI(params.dig(:data, :uri))
document = @store.get(uri)

unless document.is_a?(RubyDocument)
send_message(Notification.window_show_error("Code actions are currently only available for Ruby documents"))
raise Requests::CodeActionResolve::CodeActionError
end

result = Requests::CodeActionResolve.new(document, params).perform

case result
Expand Down
Loading

0 comments on commit 6c4702c

Please sign in to comment.