ruby-lsp 0.17.3 → 0.17.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +251 -100
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +173 -114
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +337 -77
  7. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +43 -14
  8. data/lib/ruby_indexer/test/classes_and_modules_test.rb +79 -3
  9. data/lib/ruby_indexer/test/index_test.rb +563 -29
  10. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  11. data/lib/ruby_indexer/test/method_test.rb +75 -25
  12. data/lib/ruby_indexer/test/rbs_indexer_test.rb +38 -2
  13. data/lib/ruby_indexer/test/test_case.rb +1 -5
  14. data/lib/ruby_lsp/addon.rb +13 -1
  15. data/lib/ruby_lsp/document.rb +50 -23
  16. data/lib/ruby_lsp/erb_document.rb +125 -0
  17. data/lib/ruby_lsp/global_state.rb +11 -4
  18. data/lib/ruby_lsp/internal.rb +3 -0
  19. data/lib/ruby_lsp/listeners/completion.rb +69 -34
  20. data/lib/ruby_lsp/listeners/definition.rb +34 -23
  21. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  22. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  23. data/lib/ruby_lsp/node_context.rb +6 -1
  24. data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
  25. data/lib/ruby_lsp/requests/completion.rb +6 -5
  26. data/lib/ruby_lsp/requests/completion_resolve.rb +7 -4
  27. data/lib/ruby_lsp/requests/definition.rb +4 -3
  28. data/lib/ruby_lsp/requests/formatting.rb +2 -0
  29. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  30. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  31. data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
  32. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  33. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  34. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +87 -0
  35. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  36. data/lib/ruby_lsp/requests.rb +2 -0
  37. data/lib/ruby_lsp/ruby_document.rb +10 -0
  38. data/lib/ruby_lsp/server.rb +95 -26
  39. data/lib/ruby_lsp/store.rb +23 -8
  40. data/lib/ruby_lsp/test_helper.rb +3 -1
  41. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  42. metadata +10 -6
@@ -22,6 +22,8 @@ module RubyIndexer
22
22
  sig { returns(RubyIndexer::Location) }
23
23
  attr_reader :location
24
24
 
25
+ alias_method :name_location, :location
26
+
25
27
  sig { returns(T::Array[String]) }
26
28
  attr_reader :comments
27
29
 
@@ -57,6 +59,16 @@ module RubyIndexer
57
59
  )
58
60
  end
59
61
 
62
+ sig { returns(T::Boolean) }
63
+ def public?
64
+ visibility == Visibility::PUBLIC
65
+ end
66
+
67
+ sig { returns(T::Boolean) }
68
+ def protected?
69
+ visibility == Visibility::PROTECTED
70
+ end
71
+
60
72
  sig { returns(T::Boolean) }
61
73
  def private?
62
74
  visibility == Visibility::PRIVATE
@@ -84,7 +96,6 @@ module RubyIndexer
84
96
 
85
97
  class Include < ModuleOperation; end
86
98
  class Prepend < ModuleOperation; end
87
- class Extend < ModuleOperation; end
88
99
 
89
100
  class Namespace < Entry
90
101
  extend T::Sig
@@ -95,20 +106,39 @@ module RubyIndexer
95
106
  sig { returns(T::Array[String]) }
96
107
  attr_reader :nesting
97
108
 
109
+ # Returns the location of the constant name, excluding the parent class or the body
110
+ sig { returns(Location) }
111
+ attr_reader :name_location
112
+
98
113
  sig do
99
114
  params(
100
115
  nesting: T::Array[String],
101
116
  file_path: String,
102
117
  location: T.any(Prism::Location, RubyIndexer::Location),
118
+ name_location: T.any(Prism::Location, Location),
103
119
  comments: T::Array[String],
104
120
  ).void
105
121
  end
106
- def initialize(nesting, file_path, location, comments)
122
+ def initialize(nesting, file_path, location, name_location, comments)
107
123
  @name = T.let(nesting.join("::"), String)
108
124
  # The original nesting where this namespace was discovered
109
125
  @nesting = nesting
110
126
 
111
127
  super(@name, file_path, location, comments)
128
+
129
+ @name_location = T.let(
130
+ if name_location.is_a?(Prism::Location)
131
+ Location.new(
132
+ name_location.start_line,
133
+ name_location.end_line,
134
+ name_location.start_column,
135
+ name_location.end_column,
136
+ )
137
+ else
138
+ name_location
139
+ end,
140
+ RubyIndexer::Location,
141
+ )
112
142
  end
113
143
 
114
144
  sig { returns(T::Array[String]) }
@@ -146,14 +176,14 @@ module RubyIndexer
146
176
  nesting: T::Array[String],
147
177
  file_path: String,
148
178
  location: T.any(Prism::Location, RubyIndexer::Location),
179
+ name_location: T.any(Prism::Location, Location),
149
180
  comments: T::Array[String],
150
181
  parent_class: T.nilable(String),
151
182
  ).void
152
183
  end
153
- def initialize(nesting, file_path, location, comments, parent_class)
154
- super(nesting, file_path, location, comments)
155
-
156
- @parent_class = T.let(parent_class, T.nilable(String))
184
+ def initialize(nesting, file_path, location, name_location, comments, parent_class) # rubocop:disable Metrics/ParameterLists
185
+ super(nesting, file_path, location, name_location, comments)
186
+ @parent_class = parent_class
157
187
  end
158
188
 
159
189
  sig { override.returns(Integer) }
@@ -162,6 +192,28 @@ module RubyIndexer
162
192
  end
163
193
  end
164
194
 
195
+ class SingletonClass < Class
196
+ extend T::Sig
197
+
198
+ sig { params(location: Prism::Location, name_location: Prism::Location, comments: T::Array[String]).void }
199
+ def update_singleton_information(location, name_location, comments)
200
+ # Create a new RubyIndexer::Location object from the Prism location
201
+ @location = Location.new(
202
+ location.start_line,
203
+ location.end_line,
204
+ location.start_column,
205
+ location.end_column,
206
+ )
207
+ @name_location = Location.new(
208
+ name_location.start_line,
209
+ name_location.end_line,
210
+ name_location.start_column,
211
+ name_location.end_column,
212
+ )
213
+ @comments.concat(comments)
214
+ end
215
+ end
216
+
165
217
  class Constant < Entry
166
218
  end
167
219
 
@@ -190,6 +242,10 @@ module RubyIndexer
190
242
 
191
243
  # An optional method parameter, e.g. `def foo(a = 123)`
192
244
  class OptionalParameter < Parameter
245
+ sig { override.returns(Symbol) }
246
+ def decorated_name
247
+ :"#{@name} = <default>"
248
+ end
193
249
  end
194
250
 
195
251
  # An required keyword method parameter, e.g. `def foo(a:)`
@@ -204,7 +260,7 @@ module RubyIndexer
204
260
  class OptionalKeywordParameter < Parameter
205
261
  sig { override.returns(Symbol) }
206
262
  def decorated_name
207
- :"#{@name}:"
263
+ :"#{@name}: <default>"
208
264
  end
209
265
  end
210
266
 
@@ -247,6 +303,11 @@ module RubyIndexer
247
303
  sig { returns(T.nilable(Entry::Namespace)) }
248
304
  attr_reader :owner
249
305
 
306
+ sig { returns(T::Array[RubyIndexer::Entry::Parameter]) }
307
+ def parameters
308
+ T.must(signatures.first).parameters
309
+ end
310
+
250
311
  sig do
251
312
  params(
252
313
  name: String,
@@ -263,140 +324,75 @@ module RubyIndexer
263
324
  @owner = owner
264
325
  end
265
326
 
266
- sig { abstract.returns(T::Array[Parameter]) }
267
- def parameters; end
327
+ sig { abstract.returns(T::Array[Entry::Signature]) }
328
+ def signatures; end
329
+
330
+ sig { returns(String) }
331
+ def decorated_parameters
332
+ first_signature = signatures.first
333
+ return "()" unless first_signature
334
+
335
+ "(#{first_signature.format})"
336
+ end
268
337
  end
269
338
 
270
339
  class Accessor < Member
271
340
  extend T::Sig
272
341
 
273
- sig { override.returns(T::Array[Parameter]) }
274
- def parameters
275
- params = []
276
- params << RequiredParameter.new(name: name.delete_suffix("=").to_sym) if name.end_with?("=")
277
- params
342
+ sig { override.returns(T::Array[Signature]) }
343
+ def signatures
344
+ @signatures ||= T.let(
345
+ begin
346
+ params = []
347
+ params << RequiredParameter.new(name: name.delete_suffix("=").to_sym) if name.end_with?("=")
348
+ [Entry::Signature.new(params)]
349
+ end,
350
+ T.nilable(T::Array[Signature]),
351
+ )
278
352
  end
279
353
  end
280
354
 
281
355
  class Method < Member
282
356
  extend T::Sig
283
- extend T::Helpers
284
357
 
285
- abstract!
358
+ sig { override.returns(T::Array[Signature]) }
359
+ attr_reader :signatures
286
360
 
287
- sig { override.returns(T::Array[Parameter]) }
288
- attr_reader :parameters
361
+ # Returns the location of the method name, excluding parameters or the body
362
+ sig { returns(Location) }
363
+ attr_reader :name_location
289
364
 
290
365
  sig do
291
366
  params(
292
367
  name: String,
293
368
  file_path: String,
294
369
  location: T.any(Prism::Location, RubyIndexer::Location),
370
+ name_location: T.any(Prism::Location, Location),
295
371
  comments: T::Array[String],
296
- parameters_node: T.nilable(Prism::ParametersNode),
372
+ signatures: T::Array[Signature],
297
373
  visibility: Visibility,
298
374
  owner: T.nilable(Entry::Namespace),
299
375
  ).void
300
376
  end
301
- def initialize(name, file_path, location, comments, parameters_node, visibility, owner) # rubocop:disable Metrics/ParameterLists
377
+ def initialize(name, file_path, location, name_location, comments, signatures, visibility, owner) # rubocop:disable Metrics/ParameterLists
302
378
  super(name, file_path, location, comments, visibility, owner)
303
-
304
- @parameters = T.let(list_params(parameters_node), T::Array[Parameter])
305
- end
306
-
307
- private
308
-
309
- sig { params(parameters_node: T.nilable(Prism::ParametersNode)).returns(T::Array[Parameter]) }
310
- def list_params(parameters_node)
311
- return [] unless parameters_node
312
-
313
- parameters = []
314
-
315
- parameters_node.requireds.each do |required|
316
- name = parameter_name(required)
317
- next unless name
318
-
319
- parameters << RequiredParameter.new(name: name)
320
- end
321
-
322
- parameters_node.optionals.each do |optional|
323
- name = parameter_name(optional)
324
- next unless name
325
-
326
- parameters << OptionalParameter.new(name: name)
327
- end
328
-
329
- parameters_node.keywords.each do |keyword|
330
- name = parameter_name(keyword)
331
- next unless name
332
-
333
- case keyword
334
- when Prism::RequiredKeywordParameterNode
335
- parameters << KeywordParameter.new(name: name)
336
- when Prism::OptionalKeywordParameterNode
337
- parameters << OptionalKeywordParameter.new(name: name)
338
- end
339
- end
340
-
341
- rest = parameters_node.rest
342
-
343
- if rest.is_a?(Prism::RestParameterNode)
344
- rest_name = rest.name || RestParameter::DEFAULT_NAME
345
- parameters << RestParameter.new(name: rest_name)
346
- end
347
-
348
- keyword_rest = parameters_node.keyword_rest
349
-
350
- if keyword_rest.is_a?(Prism::KeywordRestParameterNode)
351
- keyword_rest_name = parameter_name(keyword_rest) || KeywordRestParameter::DEFAULT_NAME
352
- parameters << KeywordRestParameter.new(name: keyword_rest_name)
353
- end
354
-
355
- parameters_node.posts.each do |post|
356
- name = parameter_name(post)
357
- next unless name
358
-
359
- parameters << RequiredParameter.new(name: name)
360
- end
361
-
362
- block = parameters_node.block
363
- parameters << BlockParameter.new(name: block.name || BlockParameter::DEFAULT_NAME) if block
364
-
365
- parameters
366
- end
367
-
368
- sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(Symbol)) }
369
- def parameter_name(node)
370
- case node
371
- when Prism::RequiredParameterNode, Prism::OptionalParameterNode,
372
- Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
373
- Prism::RestParameterNode, Prism::KeywordRestParameterNode
374
- node.name
375
- when Prism::MultiTargetNode
376
- names = node.lefts.map { |parameter_node| parameter_name(parameter_node) }
377
-
378
- rest = node.rest
379
- if rest.is_a?(Prism::SplatNode)
380
- name = rest.expression&.slice
381
- names << (rest.operator == "*" ? "*#{name}".to_sym : name&.to_sym)
382
- end
383
-
384
- names << nil if rest.is_a?(Prism::ImplicitRestNode)
385
-
386
- names.concat(node.rights.map { |parameter_node| parameter_name(parameter_node) })
387
-
388
- names_with_commas = names.join(", ")
389
- :"(#{names_with_commas})"
390
- end
379
+ @signatures = signatures
380
+ @name_location = T.let(
381
+ if name_location.is_a?(Prism::Location)
382
+ Location.new(
383
+ name_location.start_line,
384
+ name_location.end_line,
385
+ name_location.start_column,
386
+ name_location.end_column,
387
+ )
388
+ else
389
+ name_location
390
+ end,
391
+ RubyIndexer::Location,
392
+ )
391
393
  end
392
394
  end
393
395
 
394
- class SingletonMethod < Method
395
- end
396
-
397
- class InstanceMethod < Method
398
- end
399
-
400
396
  # An UnresolvedAlias points to a constant alias with a right hand side that has not yet been resolved. For
401
397
  # example, if we find
402
398
  #
@@ -470,6 +466,9 @@ module RubyIndexer
470
466
  end
471
467
  end
472
468
 
469
+ # An unresolved method alias is an alias entry for which we aren't sure what the right hand side points to yet. For
470
+ # example, if we have `alias a b`, we create an unresolved alias for `a` because we aren't sure immediate what `b`
471
+ # is referring to
473
472
  class UnresolvedMethodAlias < Entry
474
473
  extend T::Sig
475
474
 
@@ -497,5 +496,65 @@ module RubyIndexer
497
496
  @owner = owner
498
497
  end
499
498
  end
499
+
500
+ # A method alias is a resolved alias entry that points to the exact method target it refers to
501
+ class MethodAlias < Entry
502
+ extend T::Sig
503
+
504
+ sig { returns(T.any(Member, MethodAlias)) }
505
+ attr_reader :target
506
+
507
+ sig { returns(T.nilable(Entry::Namespace)) }
508
+ attr_reader :owner
509
+
510
+ sig { params(target: T.any(Member, MethodAlias), unresolved_alias: UnresolvedMethodAlias).void }
511
+ def initialize(target, unresolved_alias)
512
+ full_comments = ["Alias for #{target.name}\n"]
513
+ full_comments.concat(unresolved_alias.comments)
514
+ full_comments << "\n"
515
+ full_comments.concat(target.comments)
516
+
517
+ super(
518
+ unresolved_alias.new_name,
519
+ unresolved_alias.file_path,
520
+ unresolved_alias.location,
521
+ full_comments,
522
+ )
523
+
524
+ @target = target
525
+ @owner = T.let(unresolved_alias.owner, T.nilable(Entry::Namespace))
526
+ end
527
+
528
+ sig { returns(T::Array[Parameter]) }
529
+ def parameters
530
+ @target.parameters
531
+ end
532
+
533
+ sig { returns(String) }
534
+ def decorated_parameters
535
+ @target.decorated_parameters
536
+ end
537
+ end
538
+
539
+ # Ruby doesn't support method overloading, so a method will have only one signature.
540
+ # However RBS can represent the concept of method overloading, with different return types based on the arguments
541
+ # passed, so we need to store all the signatures.
542
+ class Signature
543
+ extend T::Sig
544
+
545
+ sig { returns(T::Array[Parameter]) }
546
+ attr_reader :parameters
547
+
548
+ sig { params(parameters: T::Array[Parameter]).void }
549
+ def initialize(parameters)
550
+ @parameters = parameters
551
+ end
552
+
553
+ # Returns a string with the decorated names of the parameters of this member. E.g.: `(a, b = 1, c: 2)`
554
+ sig { returns(String) }
555
+ def format
556
+ @parameters.map(&:decorated_name).join(", ")
557
+ end
558
+ end
500
559
  end
501
560
  end