ruby-lsp 0.17.6 → 0.17.8

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: 7299237468ae17d80028a1454903f58b1be4ff34428a6c8a538530b5caad54d1
4
- data.tar.gz: 26351e5be1671219a9b3624076b2f50fb68c5838303fe1a021eecb39c4e3e9f1
3
+ metadata.gz: e3a8a120b4cf3045cdeec98d2fb417ffdd300947b6426788d827346c43812ffc
4
+ data.tar.gz: b43a823e71fca99a9cf79b3a6ce19e7a0ec86e499b943aecae1666788859d055
5
5
  SHA512:
6
- metadata.gz: 54cc76a3506a268398cfc51645a70300e2d14bda5b39c12274863270f70ed666b33bfebdf50dac8e013410e8ddba5645f4f435863af795d268710e0ccf9d8814
7
- data.tar.gz: 95e52a4aa6f68785495f7435fe54cc395c062809ad3754703f808c68d2599bc34a619578bbb8453cb54de88a8312ac49eff8dd2e72a82bc04554c7b659c477cd
6
+ metadata.gz: 9662d88d3f37b4fd7b744a69f38f2a20cd4bf00291f63968578e50bef3400916870efdcf58b6e81dd78a6829c4f8a7dd174e6d212ddc3a265420b29eaa2b6d8a
7
+ data.tar.gz: b788ef9185e02a732cd2dccef6273d8dd3fb471ebb8c2300b3ac78cc48d3952eee378a777cc4cf4ceb927590fff7bcdfc57c95f0924b21e589eb850ec9128641
data/README.md CHANGED
@@ -78,7 +78,12 @@ default gems, except for
78
78
  - Gems that only appear under the `:development` group
79
79
  - All Ruby files under `test/**/*.rb`
80
80
 
81
- By creating a `.index.yml` file, these configurations can be overridden and tuned. Note that indexing dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by the configurations placed here.
81
+ This behaviour can be overridden and tuned. Learn how to configure it [for VS Code](vscode/README.md#Indexing-Configuration) or [for other editors](EDITORS.md#Indexing-Configuration).
82
+
83
+ Note that indexing-dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by
84
+ the configuration changes.
85
+
86
+ The older approach of using a `.index.yml` file has been deprecated and will be removed in a future release.
82
87
 
83
88
  ```yaml
84
89
  # Exclude files based on a given pattern. Often used to exclude test files or fixtures
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.6
1
+ 0.17.8
data/exe/ruby-lsp CHANGED
@@ -36,6 +36,10 @@ parser = OptionParser.new do |opts|
36
36
  options[:experimental] = true
37
37
  end
38
38
 
39
+ opts.on("--doctor", "Run troubleshooting steps") do
40
+ options[:doctor] = true
41
+ end
42
+
39
43
  opts.on("-h", "--help", "Print this help") do
40
44
  puts opts.help
41
45
  puts
@@ -107,4 +111,25 @@ if options[:time_index]
107
111
  return
108
112
  end
109
113
 
114
+ if options[:doctor]
115
+ if File.exist?(".index.yml")
116
+ begin
117
+ config = YAML.parse_file(".index.yml").to_ruby
118
+ rescue => e
119
+ abort("Error parsing config: #{e.message}")
120
+ end
121
+ RubyIndexer.configuration.apply_config(config)
122
+ end
123
+
124
+ index = RubyIndexer::Index.new
125
+
126
+ puts "Globbing for indexable files"
127
+
128
+ RubyIndexer.configuration.indexables.each do |indexable|
129
+ puts "indexing: #{indexable.full_path}"
130
+ index.index_single(indexable)
131
+ end
132
+ return
133
+ end
134
+
110
135
  RubyLsp::Server.new.start
@@ -71,7 +71,7 @@ module RubyIndexer
71
71
 
72
72
  superclass = node.superclass
73
73
 
74
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
74
+ nesting = actual_nesting(name)
75
75
 
76
76
  parent_class = case superclass
77
77
  when Prism::ConstantReadNode, Prism::ConstantPathNode
@@ -119,8 +119,7 @@ module RubyIndexer
119
119
 
120
120
  comments = collect_comments(node)
121
121
 
122
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
123
- entry = Entry::Module.new(nesting, @file_path, node.location, constant_path.location, comments)
122
+ entry = Entry::Module.new(actual_nesting(name), @file_path, node.location, constant_path.location, comments)
124
123
 
125
124
  @owner_stack << entry
126
125
  @index.add(entry)
@@ -709,5 +708,19 @@ module RubyIndexer
709
708
  :"(#{names_with_commas})"
710
709
  end
711
710
  end
711
+
712
+ sig { params(name: String).returns(T::Array[String]) }
713
+ def actual_nesting(name)
714
+ nesting = @stack + [name]
715
+ corrected_nesting = []
716
+
717
+ nesting.reverse_each do |name|
718
+ corrected_nesting.prepend(name.delete_prefix("::"))
719
+
720
+ break if name.start_with?("::")
721
+ end
722
+
723
+ corrected_nesting
724
+ end
712
725
  end
713
726
  end
@@ -331,14 +331,17 @@ module RubyIndexer
331
331
  params(
332
332
  method_name: String,
333
333
  receiver_name: String,
334
+ inherited_only: T::Boolean,
334
335
  ).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
335
336
  end
336
- def resolve_method(method_name, receiver_name)
337
+ def resolve_method(method_name, receiver_name, inherited_only: false)
337
338
  method_entries = self[method_name]
338
339
  return unless method_entries
339
340
 
340
341
  ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
341
342
  ancestors.each do |ancestor|
343
+ next if inherited_only && ancestor == receiver_name
344
+
342
345
  found = method_entries.filter_map do |entry|
343
346
  case entry
344
347
  when Entry::Member, Entry::MethodAlias
@@ -546,5 +546,43 @@ module RubyIndexer
546
546
  assert_equal(7, name_location.start_column)
547
547
  assert_equal(10, name_location.end_column)
548
548
  end
549
+
550
+ def test_indexing_namespaces_inside_top_level_references
551
+ index(<<~RUBY)
552
+ module ::Foo
553
+ class Bar
554
+ end
555
+ end
556
+ RUBY
557
+
558
+ # We want to explicitly verify that we didn't introduce the leading `::` by accident, but `Index#[]` deletes the
559
+ # prefix when we use `refute_entry`
560
+ entries = @index.instance_variable_get(:@entries)
561
+ refute(entries.key?("::Foo"))
562
+ refute(entries.key?("::Foo::Bar"))
563
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:3-3")
564
+ assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
565
+ end
566
+
567
+ def test_indexing_namespaces_inside_nested_top_level_references
568
+ index(<<~RUBY)
569
+ class Baz
570
+ module ::Foo
571
+ class Bar
572
+ end
573
+
574
+ class ::Qux
575
+ end
576
+ end
577
+ end
578
+ RUBY
579
+
580
+ refute_entry("Baz::Foo")
581
+ refute_entry("Baz::Foo::Bar")
582
+ assert_entry("Baz", Entry::Class, "/fake/path/foo.rb:0-0:8-3")
583
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:1-2:7-5")
584
+ assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
585
+ assert_entry("Qux", Entry::Class, "/fake/path/foo.rb:5-4:6-7")
586
+ end
549
587
  end
550
588
  end
@@ -285,6 +285,43 @@ module RubyIndexer
285
285
  assert_includes(second_entry.comments, "Hello from second `bar`")
286
286
  end
287
287
 
288
+ def test_resolve_method_inherited_only
289
+ index(<<~RUBY)
290
+ class Bar
291
+ def baz; end
292
+ end
293
+
294
+ class Foo < Bar
295
+ def baz; end
296
+ end
297
+ RUBY
298
+
299
+ entry = T.must(@index.resolve_method("baz", "Foo", inherited_only: true).first)
300
+
301
+ assert_equal("Bar", T.must(entry.owner).name)
302
+ end
303
+
304
+ def test_resolve_method_inherited_only_for_prepended_module
305
+ index(<<~RUBY)
306
+ module Bar
307
+ def baz
308
+ super
309
+ end
310
+ end
311
+
312
+ class Foo
313
+ prepend Bar
314
+
315
+ def baz; end
316
+ end
317
+ RUBY
318
+
319
+ # This test is just to document the fact that we don't yet support resolving inherited methods for modules that
320
+ # are prepended. The only way to support this is to find all namespaces that have the module a subtype, so that we
321
+ # can show the results for everywhere the module has been prepended.
322
+ assert_nil(@index.resolve_method("baz", "Bar", inherited_only: true))
323
+ end
324
+
288
325
  def test_prefix_search_for_methods
289
326
  index(<<~RUBY)
290
327
  module Foo
@@ -53,7 +53,7 @@ module RubyLsp
53
53
 
54
54
  # We don't want to try to parse documents on text synchronization notifications
55
55
  @store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
56
- rescue Errno::ENOENT
56
+ rescue Store::NonExistingDocumentError
57
57
  # If we receive a request for a file that no longer exists, we don't want to fail
58
58
  end
59
59
  end
@@ -127,8 +127,18 @@ module RubyLsp
127
127
  parent = T.let(nil, T.nilable(Prism::Node))
128
128
  nesting_nodes = T.let(
129
129
  [],
130
- T::Array[T.any(Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode)],
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
+ )],
131
139
  )
140
+
141
+ nesting_nodes << node if node.is_a?(Prism::ProgramNode)
132
142
  call_node = T.let(nil, T.nilable(Prism::CallNode))
133
143
 
134
144
  until queue.empty?
@@ -158,11 +168,8 @@ module RubyLsp
158
168
  # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
159
169
  # target when it is a constant
160
170
  case candidate
161
- when Prism::ClassNode, Prism::ModuleNode
162
- nesting_nodes << candidate
163
- when Prism::SingletonClassNode
164
- nesting_nodes << candidate
165
- when Prism::DefNode
171
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
172
+ Prism::LambdaNode
166
173
  nesting_nodes << candidate
167
174
  end
168
175
 
@@ -203,24 +210,7 @@ module RubyLsp
203
210
  end
204
211
  end
205
212
 
206
- nesting = []
207
- surrounding_method = T.let(nil, T.nilable(String))
208
-
209
- nesting_nodes.each do |node|
210
- case node
211
- when Prism::ClassNode, Prism::ModuleNode
212
- nesting << node.constant_path.slice
213
- when Prism::SingletonClassNode
214
- nesting << "<Class:#{nesting.last}>"
215
- when Prism::DefNode
216
- surrounding_method = node.name.to_s
217
- next unless node.receiver.is_a?(Prism::SelfNode)
218
-
219
- nesting << "<Class:#{nesting.last}>"
220
- end
221
- end
222
-
223
- NodeContext.new(closest, parent, nesting, call_node, surrounding_method)
213
+ NodeContext.new(closest, parent, nesting_nodes, call_node)
224
214
  end
225
215
 
226
216
  sig { returns(T::Boolean) }
@@ -69,7 +69,7 @@ module RubyLsp
69
69
  @formatter = detect_formatter(direct_dependencies, all_dependencies) if @formatter == "auto"
70
70
 
71
71
  specified_linters = options.dig(:initializationOptions, :linters)
72
- @linters = specified_linters || detect_linters(direct_dependencies)
72
+ @linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
73
73
  @test_library = detect_test_library(direct_dependencies)
74
74
  @has_type_checker = detect_typechecker(direct_dependencies)
75
75
 
@@ -127,10 +127,14 @@ module RubyLsp
127
127
 
128
128
  # Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
129
129
  # single linter. To have multiple linters running, the user must configure them manually
130
- sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
131
- def detect_linters(dependencies)
130
+ sig { params(dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(T::Array[String]) }
131
+ def detect_linters(dependencies, all_dependencies)
132
132
  linters = []
133
- linters << "rubocop" if dependencies.any?(/^rubocop/)
133
+
134
+ if dependencies.any?(/^rubocop/) || (all_dependencies.include?("rubocop") && dot_rubocop_yml_present)
135
+ linters << "rubocop"
136
+ end
137
+
134
138
  linters
135
139
  end
136
140
 
@@ -7,6 +7,50 @@ module RubyLsp
7
7
  extend T::Sig
8
8
  include Requests::Support::Common
9
9
 
10
+ KEYWORDS = [
11
+ "alias",
12
+ "and",
13
+ "begin",
14
+ "BEGIN",
15
+ "break",
16
+ "case",
17
+ "class",
18
+ "def",
19
+ "defined?",
20
+ "do",
21
+ "else",
22
+ "elsif",
23
+ "end",
24
+ "END",
25
+ "ensure",
26
+ "false",
27
+ "for",
28
+ "if",
29
+ "in",
30
+ "module",
31
+ "next",
32
+ "nil",
33
+ "not",
34
+ "or",
35
+ "redo",
36
+ "rescue",
37
+ "retry",
38
+ "return",
39
+ "self",
40
+ "super",
41
+ "then",
42
+ "true",
43
+ "undef",
44
+ "unless",
45
+ "until",
46
+ "when",
47
+ "while",
48
+ "yield",
49
+ "__ENCODING__",
50
+ "__FILE__",
51
+ "__LINE__",
52
+ ].freeze
53
+
10
54
  sig do
11
55
  params(
12
56
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
@@ -277,6 +321,12 @@ module RubyLsp
277
321
 
278
322
  sig { params(node: Prism::CallNode, name: String).void }
279
323
  def complete_methods(node, name)
324
+ # If the node has a receiver, then we don't need to provide local nor keyword completions
325
+ if !@global_state.has_type_checker && !node.receiver
326
+ add_local_completions(node, name)
327
+ add_keyword_completions(node, name)
328
+ end
329
+
280
330
  type = @type_inferrer.infer_receiver_type(@node_context)
281
331
  return unless type
282
332
 
@@ -322,6 +372,44 @@ module RubyLsp
322
372
  # We have not indexed this namespace, so we can't provide any completions
323
373
  end
324
374
 
375
+ sig { params(node: Prism::CallNode, name: String).void }
376
+ def add_local_completions(node, name)
377
+ range = range_from_location(T.must(node.message_loc))
378
+
379
+ @node_context.locals_for_scope.each do |local|
380
+ local_name = local.to_s
381
+ next unless local_name.start_with?(name)
382
+
383
+ @response_builder << Interface::CompletionItem.new(
384
+ label: local_name,
385
+ filter_text: local_name,
386
+ text_edit: Interface::TextEdit.new(range: range, new_text: local_name),
387
+ kind: Constant::CompletionItemKind::VARIABLE,
388
+ data: {
389
+ skip_resolve: true,
390
+ },
391
+ )
392
+ end
393
+ end
394
+
395
+ sig { params(node: Prism::CallNode, name: String).void }
396
+ def add_keyword_completions(node, name)
397
+ range = range_from_location(T.must(node.message_loc))
398
+
399
+ KEYWORDS.each do |keyword|
400
+ next unless keyword.start_with?(name)
401
+
402
+ @response_builder << Interface::CompletionItem.new(
403
+ label: keyword,
404
+ text_edit: Interface::TextEdit.new(range: range, new_text: keyword),
405
+ kind: Constant::CompletionItemKind::KEYWORD,
406
+ data: {
407
+ skip_resolve: true,
408
+ },
409
+ )
410
+ end
411
+ end
412
+
325
413
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
326
414
  def build_completion(label, node)
327
415
  # We should use the content location as we only replace the content and not the delimiters of the string
@@ -46,6 +46,8 @@ module RubyLsp
46
46
  :on_instance_variable_or_write_node_enter,
47
47
  :on_instance_variable_target_node_enter,
48
48
  :on_string_node_enter,
49
+ :on_super_node_enter,
50
+ :on_forwarding_super_node_enter,
49
51
  )
50
52
  end
51
53
 
@@ -133,8 +135,30 @@ module RubyLsp
133
135
  handle_instance_variable_definition(node.name.to_s)
134
136
  end
135
137
 
138
+ sig { params(node: Prism::SuperNode).void }
139
+ def on_super_node_enter(node)
140
+ handle_super_node_definition
141
+ end
142
+
143
+ sig { params(node: Prism::ForwardingSuperNode).void }
144
+ def on_forwarding_super_node_enter(node)
145
+ handle_super_node_definition
146
+ end
147
+
136
148
  private
137
149
 
150
+ sig { void }
151
+ def handle_super_node_definition
152
+ surrounding_method = @node_context.surrounding_method
153
+ return unless surrounding_method
154
+
155
+ handle_method_definition(
156
+ surrounding_method,
157
+ @type_inferrer.infer_receiver_type(@node_context),
158
+ inherited_only: true,
159
+ )
160
+ end
161
+
138
162
  sig { params(name: String).void }
139
163
  def handle_instance_variable_definition(name)
140
164
  type = @type_inferrer.infer_receiver_type(@node_context)
@@ -158,10 +182,10 @@ module RubyLsp
158
182
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
159
183
  end
160
184
 
161
- sig { params(message: String, receiver_type: T.nilable(String)).void }
162
- def handle_method_definition(message, receiver_type)
185
+ sig { params(message: String, receiver_type: T.nilable(String), inherited_only: T::Boolean).void }
186
+ def handle_method_definition(message, receiver_type, inherited_only: false)
163
187
  methods = if receiver_type
164
- @index.resolve_method(message, receiver_type)
188
+ @index.resolve_method(message, receiver_type, inherited_only: inherited_only)
165
189
  else
166
190
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
167
191
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -21,6 +21,8 @@ module RubyLsp
21
21
  Prism::InstanceVariableWriteNode,
22
22
  Prism::SymbolNode,
23
23
  Prism::StringNode,
24
+ Prism::SuperNode,
25
+ Prism::ForwardingSuperNode,
24
26
  ],
25
27
  T::Array[T.class_of(Prism::Node)],
26
28
  )
@@ -64,6 +66,8 @@ module RubyLsp
64
66
  :on_instance_variable_operator_write_node_enter,
65
67
  :on_instance_variable_or_write_node_enter,
66
68
  :on_instance_variable_target_node_enter,
69
+ :on_super_node_enter,
70
+ :on_forwarding_super_node_enter,
67
71
  )
68
72
  end
69
73
 
@@ -106,17 +110,7 @@ module RubyLsp
106
110
  message = node.message
107
111
  return unless message
108
112
 
109
- type = @type_inferrer.infer_receiver_type(@node_context)
110
- return unless type
111
-
112
- methods = @index.resolve_method(message, type)
113
- return unless methods
114
-
115
- title = "#{message}#{T.must(methods.first).decorated_parameters}"
116
-
117
- categorized_markdown_from_index_entries(title, methods).each do |category, content|
118
- @response_builder.push(content, category: category)
119
- end
113
+ handle_method_hover(message)
120
114
  end
121
115
 
122
116
  sig { params(node: Prism::InstanceVariableReadNode).void }
@@ -149,8 +143,41 @@ module RubyLsp
149
143
  handle_instance_variable_hover(node.name.to_s)
150
144
  end
151
145
 
146
+ sig { params(node: Prism::SuperNode).void }
147
+ def on_super_node_enter(node)
148
+ handle_super_node_hover
149
+ end
150
+
151
+ sig { params(node: Prism::ForwardingSuperNode).void }
152
+ def on_forwarding_super_node_enter(node)
153
+ handle_super_node_hover
154
+ end
155
+
152
156
  private
153
157
 
158
+ sig { void }
159
+ def handle_super_node_hover
160
+ surrounding_method = @node_context.surrounding_method
161
+ return unless surrounding_method
162
+
163
+ handle_method_hover(surrounding_method, inherited_only: true)
164
+ end
165
+
166
+ sig { params(message: String, inherited_only: T::Boolean).void }
167
+ def handle_method_hover(message, inherited_only: false)
168
+ type = @type_inferrer.infer_receiver_type(@node_context)
169
+ return unless type
170
+
171
+ methods = @index.resolve_method(message, type, inherited_only: inherited_only)
172
+ return unless methods
173
+
174
+ title = "#{message}#{T.must(methods.first).decorated_parameters}"
175
+
176
+ categorized_markdown_from_index_entries(title, methods).each do |category, content|
177
+ @response_builder.push(content, category: category)
178
+ end
179
+ end
180
+
154
181
  sig { params(name: String).void }
155
182
  def handle_instance_variable_hover(name)
156
183
  type = @type_inferrer.infer_receiver_type(@node_context)
@@ -23,22 +23,82 @@ module RubyLsp
23
23
  params(
24
24
  node: T.nilable(Prism::Node),
25
25
  parent: T.nilable(Prism::Node),
26
- nesting: T::Array[String],
26
+ nesting_nodes: T::Array[T.any(
27
+ Prism::ClassNode,
28
+ Prism::ModuleNode,
29
+ Prism::SingletonClassNode,
30
+ Prism::DefNode,
31
+ Prism::BlockNode,
32
+ Prism::LambdaNode,
33
+ Prism::ProgramNode,
34
+ )],
27
35
  call_node: T.nilable(Prism::CallNode),
28
- surrounding_method: T.nilable(String),
29
36
  ).void
30
37
  end
31
- def initialize(node, parent, nesting, call_node, surrounding_method)
38
+ def initialize(node, parent, nesting_nodes, call_node)
32
39
  @node = node
33
40
  @parent = parent
34
- @nesting = nesting
41
+ @nesting_nodes = nesting_nodes
35
42
  @call_node = call_node
36
- @surrounding_method = surrounding_method
43
+
44
+ nesting, surrounding_method = handle_nesting_nodes(nesting_nodes)
45
+ @nesting = T.let(nesting, T::Array[String])
46
+ @surrounding_method = T.let(surrounding_method, T.nilable(String))
37
47
  end
38
48
 
39
49
  sig { returns(String) }
40
50
  def fully_qualified_name
41
51
  @fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
42
52
  end
53
+
54
+ sig { returns(T::Array[Symbol]) }
55
+ def locals_for_scope
56
+ locals = []
57
+
58
+ @nesting_nodes.each do |node|
59
+ if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::SingletonClassNode) ||
60
+ node.is_a?(Prism::DefNode)
61
+ locals.clear
62
+ end
63
+
64
+ locals.concat(node.locals)
65
+ end
66
+
67
+ locals
68
+ end
69
+
70
+ private
71
+
72
+ sig do
73
+ params(nodes: T::Array[T.any(
74
+ Prism::ClassNode,
75
+ Prism::ModuleNode,
76
+ Prism::SingletonClassNode,
77
+ Prism::DefNode,
78
+ Prism::BlockNode,
79
+ Prism::LambdaNode,
80
+ Prism::ProgramNode,
81
+ )]).returns([T::Array[String], T.nilable(String)])
82
+ end
83
+ def handle_nesting_nodes(nodes)
84
+ nesting = []
85
+ surrounding_method = T.let(nil, T.nilable(String))
86
+
87
+ @nesting_nodes.each do |node|
88
+ case node
89
+ when Prism::ClassNode, Prism::ModuleNode
90
+ nesting << node.constant_path.slice
91
+ when Prism::SingletonClassNode
92
+ nesting << "<Class:#{nesting.last}>"
93
+ when Prism::DefNode
94
+ surrounding_method = node.name.to_s
95
+ next unless node.receiver.is_a?(Prism::SelfNode)
96
+
97
+ nesting << "<Class:#{nesting.last}>"
98
+ end
99
+ end
100
+
101
+ [nesting, surrounding_method]
102
+ end
43
103
  end
44
104
  end
@@ -38,6 +38,8 @@ module RubyLsp
38
38
 
39
39
  sig { override.returns(T::Hash[Symbol, T.untyped]) }
40
40
  def perform
41
+ return @item if @item.dig(:data, :skip_resolve)
42
+
41
43
  # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
42
44
  # a completion resolve request must always return the original completion item without modifying ANY fields
43
45
  # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
@@ -62,6 +62,8 @@ module RubyLsp
62
62
  Prism::InstanceVariableWriteNode,
63
63
  Prism::SymbolNode,
64
64
  Prism::StringNode,
65
+ Prism::SuperNode,
66
+ Prism::ForwardingSuperNode,
65
67
  ],
66
68
  )
67
69
 
@@ -98,16 +98,27 @@ module RubyLsp
98
98
  rescue StandardError, LoadError => e
99
99
  # If an error occurred in a request, we have to return an error response or else the editor will hang
100
100
  if message[:id]
101
- send_message(Error.new(
102
- id: message[:id],
103
- code: Constant::ErrorCodes::INTERNAL_ERROR,
104
- message: e.full_message,
105
- data: {
106
- errorClass: e.class.name,
107
- errorMessage: e.message,
108
- backtrace: e.backtrace&.join("\n"),
109
- },
110
- ))
101
+ # If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
102
+ # from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
103
+ # reporting these to our telemetry
104
+ if e.is_a?(Store::NonExistingDocumentError)
105
+ send_message(Error.new(
106
+ id: message[:id],
107
+ code: Constant::ErrorCodes::INVALID_PARAMS,
108
+ message: e.full_message,
109
+ ))
110
+ else
111
+ send_message(Error.new(
112
+ id: message[:id],
113
+ code: Constant::ErrorCodes::INTERNAL_ERROR,
114
+ message: e.full_message,
115
+ data: {
116
+ errorClass: e.class.name,
117
+ errorMessage: e.message,
118
+ backtrace: e.backtrace&.join("\n"),
119
+ },
120
+ ))
121
+ end
111
122
  end
112
123
 
113
124
  $stderr.puts("Error processing #{message[:method]}: #{e.full_message}")
@@ -243,6 +254,8 @@ module RubyLsp
243
254
  )
244
255
  end
245
256
 
257
+ process_indexing_configuration(options.dig(:initializationOptions, :indexing))
258
+
246
259
  begin_progress("indexing-progress", "Ruby LSP: indexing files")
247
260
  end
248
261
 
@@ -251,28 +264,6 @@ module RubyLsp
251
264
  load_addons
252
265
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
253
266
 
254
- indexing_config = {}
255
-
256
- # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
257
- index_path = File.join(@global_state.workspace_path, ".index.yml")
258
-
259
- if File.exist?(index_path)
260
- begin
261
- indexing_config = YAML.parse_file(index_path).to_ruby
262
- rescue Psych::SyntaxError => e
263
- message = "Syntax error while loading configuration: #{e.message}"
264
- send_message(
265
- Notification.new(
266
- method: "window/showMessage",
267
- params: Interface::ShowMessageParams.new(
268
- type: Constant::MessageType::WARNING,
269
- message: message,
270
- ),
271
- ),
272
- )
273
- end
274
- end
275
-
276
267
  if defined?(Requests::Support::RuboCopFormatter)
277
268
  @global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
278
269
  end
@@ -280,7 +271,7 @@ module RubyLsp
280
271
  @global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
281
272
  end
282
273
 
283
- perform_initial_indexing(indexing_config)
274
+ perform_initial_indexing
284
275
  check_formatter_is_available
285
276
  end
286
277
 
@@ -766,12 +757,10 @@ module RubyLsp
766
757
  Addon.addons.each(&:deactivate)
767
758
  end
768
759
 
769
- sig { params(config_hash: T::Hash[String, T.untyped]).void }
770
- def perform_initial_indexing(config_hash)
760
+ sig { void }
761
+ def perform_initial_indexing
771
762
  # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
772
763
  # stuck indexing files
773
- RubyIndexer.configuration.apply_config(config_hash)
774
-
775
764
  Thread.new do
776
765
  begin
777
766
  RubyIndexer::RBSIndexer.new(@global_state.index).index_ruby_core
@@ -872,5 +861,46 @@ module RubyLsp
872
861
  )
873
862
  end
874
863
  end
864
+
865
+ sig { params(indexing_options: T.nilable(T::Hash[Symbol, T.untyped])).void }
866
+ def process_indexing_configuration(indexing_options)
867
+ # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
868
+ index_path = File.join(@global_state.workspace_path, ".index.yml")
869
+
870
+ if File.exist?(index_path)
871
+ begin
872
+ RubyIndexer.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
873
+ send_message(
874
+ Notification.new(
875
+ method: "window/showMessage",
876
+ params: Interface::ShowMessageParams.new(
877
+ type: Constant::MessageType::WARNING,
878
+ message: "The .index.yml configuration file is deprecated. " \
879
+ "Please use editor settings to configure the index",
880
+ ),
881
+ ),
882
+ )
883
+ rescue Psych::SyntaxError => e
884
+ message = "Syntax error while loading configuration: #{e.message}"
885
+ send_message(
886
+ Notification.new(
887
+ method: "window/showMessage",
888
+ params: Interface::ShowMessageParams.new(
889
+ type: Constant::MessageType::WARNING,
890
+ message: message,
891
+ ),
892
+ ),
893
+ )
894
+ end
895
+ return
896
+ end
897
+
898
+ return unless indexing_options
899
+
900
+ # The index expects snake case configurations, but VS Code standardizes on camel case settings
901
+ RubyIndexer.configuration.apply_config(
902
+ indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
903
+ )
904
+ end
875
905
  end
876
906
  end
@@ -5,6 +5,8 @@ module RubyLsp
5
5
  class Store
6
6
  extend T::Sig
7
7
 
8
+ class NonExistingDocumentError < StandardError; end
9
+
8
10
  sig { returns(T::Boolean) }
9
11
  attr_accessor :supports_progress
10
12
 
@@ -45,6 +47,8 @@ module RubyLsp
45
47
  end
46
48
  set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
47
49
  T.must(@state[uri.to_s])
50
+ rescue Errno::ENOENT
51
+ raise NonExistingDocumentError, uri.to_s
48
52
  end
49
53
 
50
54
  sig do
@@ -20,16 +20,9 @@ module RubyLsp
20
20
  when Prism::CallNode
21
21
  infer_receiver_for_call_node(node, node_context)
22
22
  when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
23
- Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode
24
- nesting = node_context.nesting
25
- # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
26
- # inherits from Object
27
- return "Object" if nesting.empty?
28
-
29
- fully_qualified_name = node_context.fully_qualified_name
30
- return fully_qualified_name if node_context.surrounding_method
31
-
32
- "#{fully_qualified_name}::<Class:#{nesting.last}>"
23
+ Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
24
+ Prism::SuperNode, Prism::ForwardingSuperNode
25
+ self_receiver_handling(node_context)
33
26
  end
34
27
  end
35
28
 
@@ -41,15 +34,7 @@ module RubyLsp
41
34
 
42
35
  case receiver
43
36
  when Prism::SelfNode, nil
44
- nesting = node_context.nesting
45
- # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
46
- # inherits from Object
47
- return "Object" if nesting.empty?
48
- return node_context.fully_qualified_name if node_context.surrounding_method
49
-
50
- # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
51
- # context
52
- "#{nesting.join("::")}::<Class:#{nesting.last}>"
37
+ self_receiver_handling(node_context)
53
38
  when Prism::ConstantPathNode, Prism::ConstantReadNode
54
39
  # When the receiver is a constant reference, we have to try to resolve it to figure out the right
55
40
  # receiver. But since the invocation is directly on the constant, that's the singleton context of that
@@ -68,6 +53,19 @@ module RubyLsp
68
53
  end
69
54
  end
70
55
 
56
+ sig { params(node_context: NodeContext).returns(String) }
57
+ def self_receiver_handling(node_context)
58
+ nesting = node_context.nesting
59
+ # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
60
+ # inherits from Object
61
+ return "Object" if nesting.empty?
62
+ return node_context.fully_qualified_name if node_context.surrounding_method
63
+
64
+ # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
65
+ # context
66
+ "#{nesting.join("::")}::<Class:#{nesting.last}>"
67
+ end
68
+
71
69
  sig do
72
70
  params(
73
71
  node: T.any(
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.6
4
+ version: 0.17.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-10 00:00:00.000000000 Z
11
+ date: 2024-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -84,7 +84,6 @@ email:
84
84
  executables:
85
85
  - ruby-lsp
86
86
  - ruby-lsp-check
87
- - ruby-lsp-doctor
88
87
  extensions: []
89
88
  extra_rdoc_files: []
90
89
  files:
@@ -93,7 +92,6 @@ files:
93
92
  - VERSION
94
93
  - exe/ruby-lsp
95
94
  - exe/ruby-lsp-check
96
- - exe/ruby-lsp-doctor
97
95
  - lib/core_ext/uri.rb
98
96
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
99
97
  - lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
@@ -206,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
204
  - !ruby/object:Gem::Version
207
205
  version: '0'
208
206
  requirements: []
209
- rubygems_version: 3.5.14
207
+ rubygems_version: 3.5.15
210
208
  signing_key:
211
209
  specification_version: 4
212
210
  summary: An opinionated language server for Ruby
data/exe/ruby-lsp-doctor DELETED
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
- require "ruby_lsp/internal"
6
-
7
- if File.exist?(".index.yml")
8
- begin
9
- config = YAML.parse_file(".index.yml").to_ruby
10
- rescue => e
11
- abort("Error parsing config: #{e.message}")
12
- end
13
- RubyIndexer.configuration.apply_config(config)
14
- end
15
-
16
- index = RubyIndexer::Index.new
17
-
18
- puts "Globbing for indexable files"
19
-
20
- RubyIndexer.configuration.indexables.each do |indexable|
21
- puts "indexing: #{indexable.full_path}"
22
- index.index_single(indexable)
23
- end