ruby-lsp 0.17.13 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 042725d7afce428b5c024933a7101151f2f69c57cbb40b4f938384c13d6c974b
4
- data.tar.gz: bec8636d402451e1009e87ddd98e6fdedc062643e614b596a0698ffcfcc9b271
3
+ metadata.gz: 17c576dbc88c8c4012eb777b15df7df630a827d7692f6bceec06b68178b25357
4
+ data.tar.gz: f023f22a818616b1c8c6fb1de77d0637e7bdae37b60b46e941b297aaa95729a9
5
5
  SHA512:
6
- metadata.gz: 6a6adb40a9ccaaf916f46c041f3eb2cc7a81be73c5d3bf7c05a45472cd9d08e5a9dd04d804b8e15f091645d420037e324368ccbcc619311a1c60c81f241c1b89
7
- data.tar.gz: d6b19c8d02cfab8dca9e4d675e8ee549e8b392631a6080757c0413da29a07d1db411d9360b7553a798805d9e6faac7318863e35ba664488c5eb41c869a7e734a
6
+ metadata.gz: e0848d63fef1677ccf276182cdea5a8304e4a1e04a7ec4936f8f083c27bc62f575fe3f0a207f75805cf3c8eb106e43d5d1afe2df366028980cb5eee6ed97c14b
7
+ data.tar.gz: d57869c1ee7dcec65becd9ee4fc225c91e946e697c3aab8a42d2453c1a85bcc91cd8e0a6364d810f8109c2b97102f101d24bf8986d2e4b3905b56368e260b0d3
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.13
1
+ 0.17.14
data/exe/ruby-lsp CHANGED
@@ -81,6 +81,8 @@ require "ruby_lsp/load_sorbet"
81
81
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
82
82
  require "ruby_lsp/internal"
83
83
 
84
+ T::Utils.run_all_sig_blocks
85
+
84
86
  if options[:debug]
85
87
  if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
86
88
  $stderr.puts "Debugging is not supported on Windows"
@@ -7,15 +7,19 @@ module RubyLsp
7
7
  enums do
8
8
  Ruby = new("ruby")
9
9
  ERB = new("erb")
10
+ RBS = new("rbs")
10
11
  end
11
12
  end
12
13
 
13
14
  extend T::Sig
14
15
  extend T::Helpers
16
+ extend T::Generic
17
+
18
+ ParseResultType = type_member
15
19
 
16
20
  abstract!
17
21
 
18
- sig { returns(Prism::ParseResult) }
22
+ sig { returns(ParseResultType) }
19
23
  attr_reader :parse_result
20
24
 
21
25
  sig { returns(String) }
@@ -38,10 +42,10 @@ module RubyLsp
38
42
  @version = T.let(version, Integer)
39
43
  @uri = T.let(uri, URI::Generic)
40
44
  @needs_parsing = T.let(true, T::Boolean)
41
- @parse_result = T.let(parse, Prism::ParseResult)
45
+ @parse_result = T.let(parse, ParseResultType)
42
46
  end
43
47
 
44
- sig { params(other: Document).returns(T::Boolean) }
48
+ sig { params(other: Document[T.untyped]).returns(T::Boolean) }
45
49
  def ==(other)
46
50
  self.class == other.class && uri == other.uri && @source == other.source
47
51
  end
@@ -54,7 +58,7 @@ module RubyLsp
54
58
  type_parameters(:T)
55
59
  .params(
56
60
  request_name: String,
57
- block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
61
+ block: T.proc.params(document: Document[ParseResultType]).returns(T.type_parameter(:T)),
58
62
  ).returns(T.type_parameter(:T))
59
63
  end
60
64
  def cache_fetch(request_name, &block)
@@ -93,7 +97,7 @@ module RubyLsp
93
97
  @cache.clear
94
98
  end
95
99
 
96
- sig { abstract.returns(Prism::ParseResult) }
100
+ sig { abstract.returns(ParseResultType) }
97
101
  def parse; end
98
102
 
99
103
  sig { abstract.returns(T::Boolean) }
@@ -104,115 +108,6 @@ module RubyLsp
104
108
  Scanner.new(@source, @encoding)
105
109
  end
106
110
 
107
- sig do
108
- params(
109
- position: T::Hash[Symbol, T.untyped],
110
- node_types: T::Array[T.class_of(Prism::Node)],
111
- ).returns(NodeContext)
112
- end
113
- def locate_node(position, node_types: [])
114
- locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
115
- end
116
-
117
- sig do
118
- params(
119
- node: Prism::Node,
120
- char_position: Integer,
121
- node_types: T::Array[T.class_of(Prism::Node)],
122
- ).returns(NodeContext)
123
- end
124
- def locate(node, char_position, node_types: [])
125
- queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
126
- closest = node
127
- parent = T.let(nil, T.nilable(Prism::Node))
128
- nesting_nodes = T.let(
129
- [],
130
- T::Array[T.any(
131
- Prism::ClassNode,
132
- Prism::ModuleNode,
133
- Prism::SingletonClassNode,
134
- Prism::DefNode,
135
- Prism::BlockNode,
136
- Prism::LambdaNode,
137
- Prism::ProgramNode,
138
- )],
139
- )
140
-
141
- nesting_nodes << node if node.is_a?(Prism::ProgramNode)
142
- call_node = T.let(nil, T.nilable(Prism::CallNode))
143
-
144
- until queue.empty?
145
- candidate = queue.shift
146
-
147
- # Skip nil child nodes
148
- next if candidate.nil?
149
-
150
- # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
151
- # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
152
- # sibling
153
- T.unsafe(queue).unshift(*candidate.child_nodes)
154
-
155
- # Skip if the current node doesn't cover the desired position
156
- loc = candidate.location
157
- next unless (loc.start_offset...loc.end_offset).cover?(char_position)
158
-
159
- # If the node's start character is already past the position, then we should've found the closest node
160
- # already
161
- break if char_position < loc.start_offset
162
-
163
- # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
164
- # need to pop the stack
165
- previous_level = nesting_nodes.last
166
- nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
167
-
168
- # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
169
- # target when it is a constant
170
- case candidate
171
- when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
172
- Prism::LambdaNode
173
- nesting_nodes << candidate
174
- end
175
-
176
- if candidate.is_a?(Prism::CallNode)
177
- arg_loc = candidate.arguments&.location
178
- blk_loc = candidate.block&.location
179
- if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
180
- (blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
181
- call_node = candidate
182
- end
183
- end
184
-
185
- # If there are node types to filter by, and the current node is not one of those types, then skip it
186
- next if node_types.any? && node_types.none? { |type| candidate.class == type }
187
-
188
- # If the current node is narrower than or equal to the previous closest node, then it is more precise
189
- closest_loc = closest.location
190
- if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
191
- parent = closest
192
- closest = candidate
193
- end
194
- end
195
-
196
- # When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated. That
197
- # is, when targeting Bar in the following example:
198
- #
199
- # ```ruby
200
- # class Foo::Bar; end
201
- # ```
202
- # The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
203
- # though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
204
- if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
205
- last_level = nesting_nodes.last
206
-
207
- if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
208
- last_level.constant_path == closest
209
- nesting_nodes.pop
210
- end
211
- end
212
-
213
- NodeContext.new(closest, parent, nesting_nodes, call_node)
214
- end
215
-
216
111
  class Scanner
217
112
  extend T::Sig
218
113
 
@@ -4,15 +4,19 @@
4
4
  module RubyLsp
5
5
  class ERBDocument < Document
6
6
  extend T::Sig
7
+ extend T::Generic
7
8
 
8
- sig { override.returns(Prism::ParseResult) }
9
+ ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
+
11
+ sig { override.returns(ParseResultType) }
9
12
  def parse
10
13
  return @parse_result unless @needs_parsing
11
14
 
12
15
  @needs_parsing = false
13
16
  scanner = ERBScanner.new(@source)
14
17
  scanner.scan
15
- @parse_result = Prism.parse(scanner.ruby)
18
+ # assigning empty scopes to turn Prism into eval mode
19
+ @parse_result = Prism.parse(scanner.ruby, scopes: [[]])
16
20
  end
17
21
 
18
22
  sig { override.returns(T::Boolean) }
@@ -25,6 +29,16 @@ module RubyLsp
25
29
  LanguageId::ERB
26
30
  end
27
31
 
32
+ sig do
33
+ params(
34
+ position: T::Hash[Symbol, T.untyped],
35
+ node_types: T::Array[T.class_of(Prism::Node)],
36
+ ).returns(NodeContext)
37
+ end
38
+ def locate_node(position, node_types: [])
39
+ RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
40
+ end
41
+
28
42
  class ERBScanner
29
43
  extend T::Sig
30
44
 
@@ -93,7 +93,7 @@ module RubyLsp
93
93
  @test_library = detect_test_library(direct_dependencies)
94
94
  notifications << Notification.window_log_message("Detected test library: #{@test_library}")
95
95
 
96
- @has_type_checker = detect_typechecker(direct_dependencies)
96
+ @has_type_checker = detect_typechecker(all_dependencies)
97
97
  if @has_type_checker
98
98
  notifications << Notification.window_log_message(
99
99
  "Ruby LSP detected this is a Sorbet project and will defer to the Sorbet LSP for some functionality",
@@ -36,6 +36,7 @@ require "ruby_lsp/node_context"
36
36
  require "ruby_lsp/document"
37
37
  require "ruby_lsp/ruby_document"
38
38
  require "ruby_lsp/erb_document"
39
+ require "ruby_lsp/rbs_document"
39
40
  require "ruby_lsp/store"
40
41
  require "ruby_lsp/addon"
41
42
  require "ruby_lsp/requests/support/rubocop_runner"
@@ -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
@@ -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,
@@ -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
@@ -46,7 +46,7 @@ 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
52
  sorbet_level: RubyDocument::SorbetLevel,
@@ -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,7 +38,7 @@ 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,
@@ -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,7 +32,7 @@ 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,
@@ -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,7 +41,7 @@ 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]),
@@ -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,6 +3,11 @@
3
3
 
4
4
  module RubyLsp
5
5
  class RubyDocument < Document
6
+ extend T::Sig
7
+ extend T::Generic
8
+
9
+ ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
+
6
11
  class SorbetLevel < T::Enum
7
12
  enums do
8
13
  None = new("none")
@@ -13,7 +18,110 @@ module RubyLsp
13
18
  end
14
19
  end
15
20
 
16
- sig { override.returns(Prism::ParseResult) }
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) }
17
125
  def parse
18
126
  return @parse_result unless @needs_parsing
19
127
 
@@ -84,5 +192,15 @@ module RubyLsp
84
192
  end
85
193
  end
86
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
87
205
  end
88
206
  end
@@ -298,9 +298,12 @@ module RubyLsp
298
298
  language_id = case text_document[:languageId]
299
299
  when "erb", "eruby"
300
300
  Document::LanguageId::ERB
301
+ when "rbs"
302
+ Document::LanguageId::RBS
301
303
  else
302
304
  Document::LanguageId::Ruby
303
305
  end
306
+
304
307
  @store.set(
305
308
  uri: text_document[:uri],
306
309
  source: text_document[:text],
@@ -341,7 +344,12 @@ module RubyLsp
341
344
  def text_document_selection_range(message)
342
345
  uri = message.dig(:params, :textDocument, :uri)
343
346
  ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
344
- Requests::SelectionRanges.new(document).perform
347
+ case document
348
+ when RubyDocument, ERBDocument
349
+ Requests::SelectionRanges.new(document).perform
350
+ else
351
+ []
352
+ end
345
353
  end
346
354
 
347
355
  # Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
@@ -363,6 +371,11 @@ module RubyLsp
363
371
  uri = URI(message.dig(:params, :textDocument, :uri))
364
372
  document = @store.get(uri)
365
373
 
374
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
375
+ send_empty_response(message[:id])
376
+ return
377
+ end
378
+
366
379
  # If the response has already been cached by another request, return it
367
380
  cached_response = document.cache_get(message[:method])
368
381
  if cached_response
@@ -407,6 +420,12 @@ module RubyLsp
407
420
  range = params[:range]
408
421
  uri = params.dig(:textDocument, :uri)
409
422
  document = @store.get(uri)
423
+
424
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
425
+ send_empty_response(message[:id])
426
+ return
427
+ end
428
+
410
429
  start_line = range.dig(:start, :line)
411
430
  end_line = range.dig(:end, :line)
412
431
 
@@ -436,6 +455,10 @@ module RubyLsp
436
455
  end
437
456
 
438
457
  document = @store.get(uri)
458
+ unless document.is_a?(RubyDocument)
459
+ send_empty_response(message[:id])
460
+ return
461
+ end
439
462
 
440
463
  response = Requests::Formatting.new(@global_state, document).perform
441
464
  send_message(Result.new(id: message[:id], response: response))
@@ -452,6 +475,12 @@ module RubyLsp
452
475
  params = message[:params]
453
476
  dispatcher = Prism::Dispatcher.new
454
477
  document = @store.get(params.dig(:textDocument, :uri))
478
+
479
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
480
+ send_empty_response(message[:id])
481
+ return
482
+ end
483
+
455
484
  request = Requests::DocumentHighlight.new(document, params[:position], dispatcher)
456
485
  dispatcher.dispatch(document.parse_result.value)
457
486
  send_message(Result.new(id: message[:id], response: request.perform))
@@ -462,6 +491,11 @@ module RubyLsp
462
491
  params = message[:params]
463
492
  document = @store.get(params.dig(:textDocument, :uri))
464
493
 
494
+ unless document.is_a?(RubyDocument)
495
+ send_empty_response(message[:id])
496
+ return
497
+ end
498
+
465
499
  send_message(
466
500
  Result.new(
467
501
  id: message[:id],
@@ -481,6 +515,11 @@ module RubyLsp
481
515
  dispatcher = Prism::Dispatcher.new
482
516
  document = @store.get(params.dig(:textDocument, :uri))
483
517
 
518
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
519
+ send_empty_response(message[:id])
520
+ return
521
+ end
522
+
484
523
  send_message(
485
524
  Result.new(
486
525
  id: message[:id],
@@ -495,7 +534,7 @@ module RubyLsp
495
534
  )
496
535
  end
497
536
 
498
- sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
537
+ sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
499
538
  def sorbet_level(document)
500
539
  return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
501
540
  return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
@@ -509,6 +548,12 @@ module RubyLsp
509
548
  hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
510
549
  dispatcher = Prism::Dispatcher.new
511
550
  document = @store.get(params.dig(:textDocument, :uri))
551
+
552
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
553
+ send_empty_response(message[:id])
554
+ return
555
+ end
556
+
512
557
  request = Requests::InlayHints.new(document, params[:range], hints_configurations, dispatcher)
513
558
  dispatcher.visit(document.parse_result.value)
514
559
  send_message(Result.new(id: message[:id], response: request.perform))
@@ -519,6 +564,11 @@ module RubyLsp
519
564
  params = message[:params]
520
565
  document = @store.get(params.dig(:textDocument, :uri))
521
566
 
567
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
568
+ send_empty_response(message[:id])
569
+ return
570
+ end
571
+
522
572
  send_message(
523
573
  Result.new(
524
574
  id: message[:id],
@@ -581,7 +631,10 @@ module RubyLsp
581
631
  document = @store.get(uri)
582
632
 
583
633
  response = document.cache_fetch("textDocument/diagnostic") do |document|
584
- Requests::Diagnostics.new(@global_state, document).perform
634
+ case document
635
+ when RubyDocument
636
+ Requests::Diagnostics.new(@global_state, document).perform
637
+ end
585
638
  end
586
639
 
587
640
  send_message(
@@ -604,6 +657,11 @@ module RubyLsp
604
657
  dispatcher = Prism::Dispatcher.new
605
658
  document = @store.get(params.dig(:textDocument, :uri))
606
659
 
660
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
661
+ send_empty_response(message[:id])
662
+ return
663
+ end
664
+
607
665
  send_message(
608
666
  Result.new(
609
667
  id: message[:id],
@@ -632,6 +690,11 @@ module RubyLsp
632
690
  dispatcher = Prism::Dispatcher.new
633
691
  document = @store.get(params.dig(:textDocument, :uri))
634
692
 
693
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
694
+ send_empty_response(message[:id])
695
+ return
696
+ end
697
+
635
698
  send_message(
636
699
  Result.new(
637
700
  id: message[:id],
@@ -653,6 +716,11 @@ module RubyLsp
653
716
  dispatcher = Prism::Dispatcher.new
654
717
  document = @store.get(params.dig(:textDocument, :uri))
655
718
 
719
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
720
+ send_empty_response(message[:id])
721
+ return
722
+ end
723
+
656
724
  send_message(
657
725
  Result.new(
658
726
  id: message[:id],
@@ -710,9 +778,16 @@ module RubyLsp
710
778
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
711
779
  def text_document_show_syntax_tree(message)
712
780
  params = message[:params]
781
+ document = @store.get(params.dig(:textDocument, :uri))
782
+
783
+ unless document.is_a?(RubyDocument)
784
+ send_empty_response(message[:id])
785
+ return
786
+ end
787
+
713
788
  response = {
714
789
  ast: Requests::ShowSyntaxTree.new(
715
- @store.get(params.dig(:textDocument, :uri)),
790
+ document,
716
791
  params[:range],
717
792
  ).perform,
718
793
  }
@@ -722,11 +797,19 @@ module RubyLsp
722
797
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
723
798
  def text_document_prepare_type_hierarchy(message)
724
799
  params = message[:params]
800
+ document = @store.get(params.dig(:textDocument, :uri))
801
+
802
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
803
+ send_empty_response(message[:id])
804
+ return
805
+ end
806
+
725
807
  response = Requests::PrepareTypeHierarchy.new(
726
- @store.get(params.dig(:textDocument, :uri)),
808
+ document,
727
809
  @global_state.index,
728
810
  params[:position],
729
811
  ).perform
812
+
730
813
  send_message(Result.new(id: message[:id], response: response))
731
814
  end
732
815
 
@@ -793,6 +876,11 @@ module RubyLsp
793
876
  send_message(Notification.window_show_error("Error while indexing: #{error.message}"))
794
877
  end
795
878
 
879
+ # Indexing produces a high number of short lived object allocations. That might lead to some fragmentation and
880
+ # an unnecessarily expanded heap. Compacting ensures that the heap is as small as possible and that future
881
+ # allocations and garbage collections are faster
882
+ GC.compact
883
+
796
884
  # Always end the progress notification even if indexing failed or else it never goes away and the user has no
797
885
  # way of dismissing it
798
886
  end_progress("indexing-progress")
@@ -50,6 +50,7 @@ module RubyLsp
50
50
  @last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
51
51
 
52
52
  @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
53
+ @rails_app = T.let(rails_app?, T::Boolean)
53
54
  @retry = T.let(false, T::Boolean)
54
55
  end
55
56
 
@@ -62,7 +63,7 @@ module RubyLsp
62
63
  # Do not set up a custom bundle if LSP dependencies are already in the Gemfile
63
64
  if @dependencies["ruby-lsp"] &&
64
65
  @dependencies["debug"] &&
65
- (@dependencies["rails"] ? @dependencies["ruby-lsp-rails"] : true)
66
+ (@rails_app ? @dependencies["ruby-lsp-rails"] : true)
66
67
  $stderr.puts(
67
68
  "Ruby LSP> Skipping custom bundle setup since LSP dependencies are already in #{@gemfile}",
68
69
  )
@@ -148,7 +149,7 @@ module RubyLsp
148
149
  parts << 'gem "debug", require: false, group: :development, platforms: :mri'
149
150
  end
150
151
 
151
- if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
152
+ if @rails_app && !@dependencies["ruby-lsp-rails"]
152
153
  parts << 'gem "ruby-lsp-rails", require: false, group: :development'
153
154
  end
154
155
 
@@ -209,7 +210,7 @@ module RubyLsp
209
210
  command << " && bundle update "
210
211
  command << "ruby-lsp " unless @dependencies["ruby-lsp"]
211
212
  command << "debug " unless @dependencies["debug"]
212
- command << "ruby-lsp-rails " if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
213
+ command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
213
214
  command << "--pre" if @experimental
214
215
  command.delete_suffix!(" ")
215
216
  command << ")"
@@ -244,7 +245,7 @@ module RubyLsp
244
245
  def should_bundle_update?
245
246
  # If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
246
247
  # will produce version control changes
247
- if @dependencies["rails"]
248
+ if @rails_app
248
249
  return false if @dependencies.values_at("ruby-lsp", "ruby-lsp-rails", "debug").all?
249
250
 
250
251
  # If the custom lockfile doesn't include `ruby-lsp`, `ruby-lsp-rails` or `debug`, we need to run bundle install
@@ -280,5 +281,15 @@ module RubyLsp
280
281
 
281
282
  @custom_lockfile.write(content)
282
283
  end
284
+
285
+ # Detects if the project is a Rails app by looking if the superclass of the main class is `Rails::Application`
286
+ sig { returns(T::Boolean) }
287
+ def rails_app?
288
+ config = Pathname.new("config/application.rb").expand_path
289
+ application_contents = config.read if config.exist?
290
+ return false unless application_contents
291
+
292
+ /class .* < (::)?Rails::Application/.match?(application_contents)
293
+ end
283
294
  end
284
295
  end
@@ -18,7 +18,7 @@ module RubyLsp
18
18
 
19
19
  sig { void }
20
20
  def initialize
21
- @state = T.let({}, T::Hash[String, Document])
21
+ @state = T.let({}, T::Hash[String, Document[T.untyped]])
22
22
  @supports_progress = T.let(true, T::Boolean)
23
23
  @features_configuration = T.let(
24
24
  {
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  @client_name = T.let("Unknown", String)
34
34
  end
35
35
 
36
- sig { params(uri: URI::Generic).returns(Document) }
36
+ sig { params(uri: URI::Generic).returns(Document[T.untyped]) }
37
37
  def get(uri)
38
38
  document = @state[uri.to_s]
39
39
  return document unless document.nil?
@@ -44,8 +44,11 @@ module RubyLsp
44
44
  raise NonExistingDocumentError, uri.to_s unless path
45
45
 
46
46
  ext = File.extname(path)
47
- language_id = if ext == ".erb" || ext == ".rhtml"
47
+ language_id = case ext
48
+ when ".erb", ".rhtml"
48
49
  Document::LanguageId::ERB
50
+ when ".rbs"
51
+ Document::LanguageId::RBS
49
52
  else
50
53
  Document::LanguageId::Ruby
51
54
  end
@@ -66,13 +69,14 @@ module RubyLsp
66
69
  ).void
67
70
  end
68
71
  def set(uri:, source:, version:, language_id:, encoding: Encoding::UTF_8)
69
- document = case language_id
72
+ @state[uri.to_s] = case language_id
70
73
  when Document::LanguageId::ERB
71
74
  ERBDocument.new(source: source, version: version, uri: uri, encoding: encoding)
75
+ when Document::LanguageId::RBS
76
+ RBSDocument.new(source: source, version: version, uri: uri, encoding: encoding)
72
77
  else
73
78
  RubyDocument.new(source: source, version: version, uri: uri, encoding: encoding)
74
79
  end
75
- @state[uri.to_s] = document
76
80
  end
77
81
 
78
82
  sig { params(uri: URI::Generic, edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
@@ -100,7 +104,7 @@ module RubyLsp
100
104
  .params(
101
105
  uri: URI::Generic,
102
106
  request_name: String,
103
- block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
107
+ block: T.proc.params(document: Document[T.untyped]).returns(T.type_parameter(:T)),
104
108
  ).returns(T.type_parameter(:T))
105
109
  end
106
110
  def cache_fetch(uri, request_name, &block)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.13
4
+ version: 0.17.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-14 00:00:00.000000000 Z
11
+ date: 2024-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -137,6 +137,7 @@ files:
137
137
  - lib/ruby_lsp/load_sorbet.rb
138
138
  - lib/ruby_lsp/node_context.rb
139
139
  - lib/ruby_lsp/parameter_scope.rb
140
+ - lib/ruby_lsp/rbs_document.rb
140
141
  - lib/ruby_lsp/requests.rb
141
142
  - lib/ruby_lsp/requests/code_action_resolve.rb
142
143
  - lib/ruby_lsp/requests/code_actions.rb