ruby-lsp 0.17.12 → 0.17.14

Sign up to get free protection for your applications and to get access to all the features.
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