ruby-lsp 0.17.6 → 0.17.8

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