ruby-lsp 0.17.12 → 0.17.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +6 -4
  4. data/exe/ruby-lsp-check +1 -1
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +1 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +6 -1
  7. data/lib/ruby_indexer/ruby_indexer.rb +0 -8
  8. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  9. data/lib/ruby_lsp/addon.rb +9 -4
  10. data/lib/ruby_lsp/base_server.rb +7 -2
  11. data/lib/ruby_lsp/document.rb +9 -178
  12. data/lib/ruby_lsp/erb_document.rb +16 -2
  13. data/lib/ruby_lsp/global_state.rb +34 -14
  14. data/lib/ruby_lsp/internal.rb +1 -0
  15. data/lib/ruby_lsp/listeners/completion.rb +5 -5
  16. data/lib/ruby_lsp/listeners/definition.rb +3 -3
  17. data/lib/ruby_lsp/listeners/hover.rb +5 -5
  18. data/lib/ruby_lsp/listeners/signature_help.rb +1 -1
  19. data/lib/ruby_lsp/rbs_document.rb +41 -0
  20. data/lib/ruby_lsp/requests/code_action_resolve.rb +37 -21
  21. data/lib/ruby_lsp/requests/code_actions.rb +3 -3
  22. data/lib/ruby_lsp/requests/completion.rb +3 -3
  23. data/lib/ruby_lsp/requests/definition.rb +2 -2
  24. data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
  25. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  26. data/lib/ruby_lsp/requests/formatting.rb +1 -1
  27. data/lib/ruby_lsp/requests/hover.rb +2 -2
  28. data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
  29. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  30. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
  31. data/lib/ruby_lsp/requests/selection_ranges.rb +2 -1
  32. data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
  33. data/lib/ruby_lsp/requests/signature_help.rb +2 -2
  34. data/lib/ruby_lsp/requests/support/common.rb +2 -2
  35. data/lib/ruby_lsp/requests/support/formatter.rb +2 -2
  36. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +1 -1
  37. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
  38. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +2 -2
  39. data/lib/ruby_lsp/ruby_document.rb +183 -1
  40. data/lib/ruby_lsp/server.rb +125 -15
  41. data/lib/ruby_lsp/setup_bundler.rb +15 -4
  42. data/lib/ruby_lsp/store.rb +10 -6
  43. data/lib/ruby_lsp/utils.rb +12 -0
  44. metadata +4 -3
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ class RBSDocument < Document
6
+ extend T::Sig
7
+ extend T::Generic
8
+
9
+ ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
10
+
11
+ sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
12
+ def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
13
+ @syntax_error = T.let(false, T::Boolean)
14
+ super
15
+ end
16
+
17
+ sig { override.returns(ParseResultType) }
18
+ def parse
19
+ return @parse_result unless @needs_parsing
20
+
21
+ @needs_parsing = false
22
+
23
+ _, _, declarations = RBS::Parser.parse_signature(@source)
24
+ @syntax_error = false
25
+ @parse_result = declarations
26
+ rescue RBS::ParsingError
27
+ @syntax_error = true
28
+ @parse_result
29
+ end
30
+
31
+ sig { override.returns(T::Boolean) }
32
+ def syntax_error?
33
+ @syntax_error
34
+ end
35
+
36
+ sig { override.returns(LanguageId) }
37
+ def language_id
38
+ LanguageId::RBS
39
+ end
40
+ end
41
+ end
@@ -38,7 +38,7 @@ module RubyLsp
38
38
  end
39
39
  end
40
40
 
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 }
42
42
  def initialize(document, code_action)
43
43
  super()
44
44
  @document = document
@@ -54,7 +54,7 @@ module RubyLsp
54
54
  refactor_variable
55
55
  when CodeActions::EXTRACT_TO_METHOD_TITLE
56
56
  refactor_method
57
- when CodeActions::SWITCH_BLOCK_STYLE_TITLE
57
+ when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
58
58
  switch_block_style
59
59
  else
60
60
  Error::UnknownCodeAction
@@ -81,7 +81,7 @@ module RubyLsp
81
81
  indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
82
82
 
83
83
  Interface::CodeAction.new(
84
- title: CodeActions::SWITCH_BLOCK_STYLE_TITLE,
84
+ title: CodeActions::TOGGLE_BLOCK_STYLE_TITLE,
85
85
  edit: Interface::WorkspaceEdit.new(
86
86
  document_changes: [
87
87
  Interface::TextDocumentEdit.new(
@@ -112,7 +112,7 @@ module RubyLsp
112
112
  extracted_source = T.must(@document.source[start_index...end_index])
113
113
 
114
114
  # Find the closest statements node, so that we place the refactor in a valid position
115
- node_context = @document
115
+ node_context = RubyDocument
116
116
  .locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
117
117
 
118
118
  closest_statements = node_context.node
@@ -206,28 +206,44 @@ module RubyLsp
206
206
  extracted_source = T.must(@document.source[start_index...end_index])
207
207
 
208
208
  # Find the closest method declaration node, so that we place the refactor in a valid position
209
- node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
210
- closest_def = T.cast(node_context.node, Prism::DefNode)
211
- return Error::InvalidTargetRange if closest_def.nil?
209
+ node_context = RubyDocument.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
210
+ closest_node = node_context.node
211
+ return Error::InvalidTargetRange unless closest_node
212
212
 
213
- end_keyword_loc = closest_def.end_keyword_loc
214
- return Error::InvalidTargetRange if end_keyword_loc.nil?
213
+ target_range = if closest_node.is_a?(Prism::DefNode)
214
+ end_keyword_loc = closest_node.end_keyword_loc
215
+ return Error::InvalidTargetRange unless end_keyword_loc
215
216
 
216
- end_line = end_keyword_loc.end_line - 1
217
- character = end_keyword_loc.end_column
218
- indentation = " " * end_keyword_loc.start_column
219
- target_range = {
220
- start: { line: end_line, character: character },
221
- end: { line: end_line, character: character },
222
- }
217
+ end_line = end_keyword_loc.end_line - 1
218
+ character = end_keyword_loc.end_column
219
+ indentation = " " * end_keyword_loc.start_column
223
220
 
224
- new_method_source = <<~RUBY.chomp
221
+ new_method_source = <<~RUBY.chomp
225
222
 
226
223
 
227
- #{indentation}def #{NEW_METHOD_NAME}
228
- #{indentation} #{extracted_source}
229
- #{indentation}end
230
- RUBY
224
+ #{indentation}def #{NEW_METHOD_NAME}
225
+ #{indentation} #{extracted_source}
226
+ #{indentation}end
227
+ RUBY
228
+
229
+ {
230
+ start: { line: end_line, character: character },
231
+ end: { line: end_line, character: character },
232
+ }
233
+ else
234
+ new_method_source = <<~RUBY
235
+ #{indentation}def #{NEW_METHOD_NAME}
236
+ #{indentation} #{extracted_source.gsub("\n", "\n ")}
237
+ #{indentation}end
238
+
239
+ RUBY
240
+
241
+ line = [0, source_range.dig(:start, :line) - 1].max
242
+ {
243
+ start: { line: line, character: source_range.dig(:start, :character) },
244
+ end: { line: line, character: source_range.dig(:start, :character) },
245
+ }
246
+ end
231
247
 
232
248
  Interface::CodeAction.new(
233
249
  title: CodeActions::EXTRACT_TO_METHOD_TITLE,
@@ -21,7 +21,7 @@ module RubyLsp
21
21
 
22
22
  EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
23
23
  EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
24
- SWITCH_BLOCK_STYLE_TITLE = "Refactor: Switch block style"
24
+ TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
25
25
 
26
26
  class << self
27
27
  extend T::Sig
@@ -37,7 +37,7 @@ module RubyLsp
37
37
 
38
38
  sig do
39
39
  params(
40
- document: Document,
40
+ document: T.any(RubyDocument, ERBDocument),
41
41
  range: T::Hash[Symbol, T.untyped],
42
42
  context: T::Hash[Symbol, T.untyped],
43
43
  ).void
@@ -71,7 +71,7 @@ module RubyLsp
71
71
  data: { range: @range, uri: @uri.to_s },
72
72
  )
73
73
  code_actions << Interface::CodeAction.new(
74
- title: SWITCH_BLOCK_STYLE_TITLE,
74
+ title: TOGGLE_BLOCK_STYLE_TITLE,
75
75
  kind: Constant::CodeActionKind::REFACTOR_REWRITE,
76
76
  data: { range: @range, uri: @uri.to_s },
77
77
  )
@@ -46,10 +46,10 @@ module RubyLsp
46
46
 
47
47
  sig do
48
48
  params(
49
- document: Document,
49
+ document: T.any(RubyDocument, ERBDocument),
50
50
  global_state: GlobalState,
51
51
  params: T::Hash[Symbol, T.untyped],
52
- sorbet_level: Document::SorbetLevel,
52
+ sorbet_level: RubyDocument::SorbetLevel,
53
53
  dispatcher: Prism::Dispatcher,
54
54
  ).void
55
55
  end
@@ -60,7 +60,7 @@ module RubyLsp
60
60
  # Completion always receives the position immediately after the character that was just typed. Here we adjust it
61
61
  # back by 1, so that we find the right node
62
62
  char_position = document.create_scanner.find_char_position(params[:position]) - 1
63
- node_context = document.locate(
63
+ node_context = RubyDocument.locate(
64
64
  document.parse_result.value,
65
65
  char_position,
66
66
  node_types: [
@@ -38,11 +38,11 @@ module RubyLsp
38
38
 
39
39
  sig do
40
40
  params(
41
- document: Document,
41
+ document: T.any(RubyDocument, ERBDocument),
42
42
  global_state: GlobalState,
43
43
  position: T::Hash[Symbol, T.untyped],
44
44
  dispatcher: Prism::Dispatcher,
45
- sorbet_level: Document::SorbetLevel,
45
+ sorbet_level: RubyDocument::SorbetLevel,
46
46
  ).void
47
47
  end
48
48
  def initialize(document, global_state, position, dispatcher, sorbet_level)
@@ -32,7 +32,7 @@ module RubyLsp
32
32
  end
33
33
  end
34
34
 
35
- sig { params(global_state: GlobalState, document: Document).void }
35
+ sig { params(global_state: GlobalState, document: RubyDocument).void }
36
36
  def initialize(global_state, document)
37
37
  super()
38
38
  @active_linters = T.let(global_state.active_linters, T::Array[Support::Formatter])
@@ -29,7 +29,7 @@ module RubyLsp
29
29
 
30
30
  sig do
31
31
  params(
32
- document: Document,
32
+ document: T.any(RubyDocument, ERBDocument),
33
33
  position: T::Hash[Symbol, T.untyped],
34
34
  dispatcher: Prism::Dispatcher,
35
35
  ).void
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  end
41
41
  end
42
42
 
43
- sig { params(global_state: GlobalState, document: Document).void }
43
+ sig { params(global_state: GlobalState, document: RubyDocument).void }
44
44
  def initialize(global_state, document)
45
45
  super()
46
46
  @document = document
@@ -32,11 +32,11 @@ module RubyLsp
32
32
 
33
33
  sig do
34
34
  params(
35
- document: Document,
35
+ document: T.any(RubyDocument, ERBDocument),
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
39
- sorbet_level: Document::SorbetLevel,
39
+ sorbet_level: RubyDocument::SorbetLevel,
40
40
  ).void
41
41
  end
42
42
  def initialize(document, global_state, position, dispatcher, sorbet_level)
@@ -52,7 +52,7 @@ module RubyLsp
52
52
 
53
53
  sig do
54
54
  params(
55
- document: Document,
55
+ document: T.any(RubyDocument, ERBDocument),
56
56
  range: T::Hash[Symbol, T.untyped],
57
57
  hints_configuration: RequestConfig,
58
58
  dispatcher: Prism::Dispatcher,
@@ -42,7 +42,7 @@ module RubyLsp
42
42
 
43
43
  sig do
44
44
  params(
45
- document: Document,
45
+ document: RubyDocument,
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  trigger_character: String,
48
48
  client_name: String,
@@ -35,7 +35,7 @@ module RubyLsp
35
35
 
36
36
  sig do
37
37
  params(
38
- document: Document,
38
+ document: T.any(RubyDocument, ERBDocument),
39
39
  index: RubyIndexer::Index,
40
40
  position: T::Hash[Symbol, T.untyped],
41
41
  ).void
@@ -23,7 +23,8 @@ module RubyLsp
23
23
  class SelectionRanges < Request
24
24
  extend T::Sig
25
25
  include Support::Common
26
- sig { params(document: Document).void }
26
+
27
+ sig { params(document: T.any(RubyDocument, ERBDocument)).void }
27
28
  def initialize(document)
28
29
  super()
29
30
  @document = document
@@ -20,7 +20,7 @@ module RubyLsp
20
20
  class ShowSyntaxTree < Request
21
21
  extend T::Sig
22
22
 
23
- sig { params(document: Document, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
23
+ sig { params(document: RubyDocument, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
24
24
  def initialize(document, range)
25
25
  super()
26
26
  @document = document
@@ -41,12 +41,12 @@ module RubyLsp
41
41
 
42
42
  sig do
43
43
  params(
44
- document: Document,
44
+ document: T.any(RubyDocument, ERBDocument),
45
45
  global_state: GlobalState,
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  context: T.nilable(T::Hash[Symbol, T.untyped]),
48
48
  dispatcher: Prism::Dispatcher,
49
- sorbet_level: Document::SorbetLevel,
49
+ sorbet_level: RubyDocument::SorbetLevel,
50
50
  ).void
51
51
  end
52
52
  def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
@@ -209,9 +209,9 @@ module RubyLsp
209
209
  end
210
210
  end
211
211
 
212
- sig { params(sorbet_level: Document::SorbetLevel).returns(T::Boolean) }
212
+ sig { params(sorbet_level: RubyDocument::SorbetLevel).returns(T::Boolean) }
213
213
  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
215
215
  end
216
216
  end
217
217
  end
@@ -10,13 +10,13 @@ module RubyLsp
10
10
 
11
11
  interface!
12
12
 
13
- sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
13
+ sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
14
14
  def run_formatting(uri, document); end
15
15
 
16
16
  sig do
17
17
  abstract.params(
18
18
  uri: URI::Generic,
19
- document: Document,
19
+ document: RubyDocument,
20
20
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
21
21
  end
22
22
  def run_diagnostic(uri, document); end
@@ -31,7 +31,7 @@ module RubyLsp
31
31
 
32
32
  # TODO: avoid passing document once we have alternative ways to get at
33
33
  # encoding and file source
34
- sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
34
+ sig { params(document: RubyDocument, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
35
35
  def initialize(document, offense, uri)
36
36
  @document = document
37
37
  @offense = offense
@@ -17,7 +17,7 @@ module RubyLsp
17
17
  @format_runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
18
18
  end
19
19
 
20
- sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
20
+ sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
21
21
  def run_formatting(uri, document)
22
22
  filename = T.must(uri.to_standardized_path || uri.opaque)
23
23
 
@@ -29,7 +29,7 @@ module RubyLsp
29
29
  sig do
30
30
  override.params(
31
31
  uri: URI::Generic,
32
- document: Document,
32
+ document: RubyDocument,
33
33
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
34
34
  end
35
35
  def run_diagnostic(uri, document)
@@ -29,7 +29,7 @@ module RubyLsp
29
29
  )
30
30
  end
31
31
 
32
- sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
32
+ sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
33
33
  def run_formatting(uri, document)
34
34
  path = uri.to_standardized_path
35
35
  return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  sig do
41
41
  override.params(
42
42
  uri: URI::Generic,
43
- document: Document,
43
+ document: RubyDocument,
44
44
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
45
45
  end
46
46
  def run_diagnostic(uri, document)
@@ -3,7 +3,125 @@
3
3
 
4
4
  module RubyLsp
5
5
  class RubyDocument < Document
6
- sig { override.returns(Prism::ParseResult) }
6
+ extend T::Sig
7
+ extend T::Generic
8
+
9
+ ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
+
11
+ class SorbetLevel < T::Enum
12
+ enums do
13
+ None = new("none")
14
+ Ignore = new("ignore")
15
+ False = new("false")
16
+ True = new("true")
17
+ Strict = new("strict")
18
+ end
19
+ end
20
+
21
+ class << self
22
+ extend T::Sig
23
+
24
+ sig do
25
+ params(
26
+ node: Prism::Node,
27
+ char_position: Integer,
28
+ node_types: T::Array[T.class_of(Prism::Node)],
29
+ ).returns(NodeContext)
30
+ end
31
+ def locate(node, char_position, node_types: [])
32
+ queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
33
+ closest = node
34
+ parent = T.let(nil, T.nilable(Prism::Node))
35
+ nesting_nodes = T.let(
36
+ [],
37
+ T::Array[T.any(
38
+ Prism::ClassNode,
39
+ Prism::ModuleNode,
40
+ Prism::SingletonClassNode,
41
+ Prism::DefNode,
42
+ Prism::BlockNode,
43
+ Prism::LambdaNode,
44
+ Prism::ProgramNode,
45
+ )],
46
+ )
47
+
48
+ nesting_nodes << node if node.is_a?(Prism::ProgramNode)
49
+ call_node = T.let(nil, T.nilable(Prism::CallNode))
50
+
51
+ until queue.empty?
52
+ candidate = queue.shift
53
+
54
+ # Skip nil child nodes
55
+ next if candidate.nil?
56
+
57
+ # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
58
+ # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
59
+ # sibling
60
+ T.unsafe(queue).unshift(*candidate.child_nodes)
61
+
62
+ # Skip if the current node doesn't cover the desired position
63
+ loc = candidate.location
64
+ next unless (loc.start_offset...loc.end_offset).cover?(char_position)
65
+
66
+ # If the node's start character is already past the position, then we should've found the closest node
67
+ # already
68
+ break if char_position < loc.start_offset
69
+
70
+ # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level
71
+ # and need to pop the stack
72
+ previous_level = nesting_nodes.last
73
+ nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
74
+
75
+ # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of
76
+ # the target when it is a constant
77
+ case candidate
78
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
79
+ Prism::LambdaNode
80
+ nesting_nodes << candidate
81
+ end
82
+
83
+ if candidate.is_a?(Prism::CallNode)
84
+ arg_loc = candidate.arguments&.location
85
+ blk_loc = candidate.block&.location
86
+ if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
87
+ (blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
88
+ call_node = candidate
89
+ end
90
+ end
91
+
92
+ # If there are node types to filter by, and the current node is not one of those types, then skip it
93
+ next if node_types.any? && node_types.none? { |type| candidate.class == type }
94
+
95
+ # If the current node is narrower than or equal to the previous closest node, then it is more precise
96
+ closest_loc = closest.location
97
+ if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
98
+ parent = closest
99
+ closest = candidate
100
+ end
101
+ end
102
+
103
+ # When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated.
104
+ # That is, when targeting Bar in the following example:
105
+ #
106
+ # ```ruby
107
+ # class Foo::Bar; end
108
+ # ```
109
+ # The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack,
110
+ # even though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
111
+ if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
112
+ last_level = nesting_nodes.last
113
+
114
+ if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
115
+ last_level.constant_path == closest
116
+ nesting_nodes.pop
117
+ end
118
+ end
119
+
120
+ NodeContext.new(closest, parent, nesting_nodes, call_node)
121
+ end
122
+ end
123
+
124
+ sig { override.returns(ParseResultType) }
7
125
  def parse
8
126
  return @parse_result unless @needs_parsing
9
127
 
@@ -20,5 +138,69 @@ module RubyLsp
20
138
  def language_id
21
139
  LanguageId::Ruby
22
140
  end
141
+
142
+ sig { returns(SorbetLevel) }
143
+ def sorbet_level
144
+ sigil = parse_result.magic_comments.find do |comment|
145
+ comment.key == "typed"
146
+ end&.value
147
+
148
+ case sigil
149
+ when "ignore"
150
+ SorbetLevel::Ignore
151
+ when "false"
152
+ SorbetLevel::False
153
+ when "true"
154
+ SorbetLevel::True
155
+ when "strict", "strong"
156
+ SorbetLevel::Strict
157
+ else
158
+ SorbetLevel::None
159
+ end
160
+ end
161
+
162
+ sig do
163
+ params(
164
+ range: T::Hash[Symbol, T.untyped],
165
+ node_types: T::Array[T.class_of(Prism::Node)],
166
+ ).returns(T.nilable(Prism::Node))
167
+ end
168
+ def locate_first_within_range(range, node_types: [])
169
+ scanner = create_scanner
170
+ start_position = scanner.find_char_position(range[:start])
171
+ end_position = scanner.find_char_position(range[:end])
172
+ desired_range = (start_position...end_position)
173
+ queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
174
+
175
+ until queue.empty?
176
+ candidate = queue.shift
177
+
178
+ # Skip nil child nodes
179
+ next if candidate.nil?
180
+
181
+ # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
182
+ # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
183
+ # sibling
184
+ T.unsafe(queue).unshift(*candidate.child_nodes)
185
+
186
+ # Skip if the current node doesn't cover the desired position
187
+ loc = candidate.location
188
+
189
+ if desired_range.cover?(loc.start_offset...loc.end_offset) &&
190
+ (node_types.empty? || node_types.any? { |type| candidate.class == type })
191
+ return candidate
192
+ end
193
+ end
194
+ end
195
+
196
+ sig do
197
+ params(
198
+ position: T::Hash[Symbol, T.untyped],
199
+ node_types: T::Array[T.class_of(Prism::Node)],
200
+ ).returns(NodeContext)
201
+ end
202
+ def locate_node(position, node_types: [])
203
+ RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
204
+ end
23
205
  end
24
206
  end