ruby-lsp 0.17.4 → 0.17.13

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +26 -1
  5. data/exe/ruby-lsp-check +1 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
  7. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
  10. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -8
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
  13. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  14. data/lib/ruby_indexer/test/constant_test.rb +17 -17
  15. data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
  16. data/lib/ruby_indexer/test/index_test.rb +367 -17
  17. data/lib/ruby_indexer/test/method_test.rb +58 -25
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
  19. data/lib/ruby_indexer/test/test_case.rb +1 -5
  20. data/lib/ruby_lsp/addon.rb +22 -5
  21. data/lib/ruby_lsp/base_server.rb +8 -3
  22. data/lib/ruby_lsp/document.rb +27 -46
  23. data/lib/ruby_lsp/erb_document.rb +125 -0
  24. data/lib/ruby_lsp/global_state.rb +47 -19
  25. data/lib/ruby_lsp/internal.rb +2 -0
  26. data/lib/ruby_lsp/listeners/completion.rb +161 -57
  27. data/lib/ruby_lsp/listeners/definition.rb +91 -27
  28. data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
  29. data/lib/ruby_lsp/listeners/hover.rb +61 -19
  30. data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
  31. data/lib/ruby_lsp/node_context.rb +65 -5
  32. data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
  33. data/lib/ruby_lsp/requests/code_actions.rb +11 -2
  34. data/lib/ruby_lsp/requests/completion.rb +4 -4
  35. data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
  36. data/lib/ruby_lsp/requests/definition.rb +18 -8
  37. data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
  38. data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
  39. data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
  40. data/lib/ruby_lsp/requests/formatting.rb +15 -0
  41. data/lib/ruby_lsp/requests/hover.rb +5 -5
  42. data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
  43. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  44. data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
  45. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  46. data/lib/ruby_lsp/requests/support/common.rb +11 -2
  47. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
  48. data/lib/ruby_lsp/ruby_document.rb +74 -0
  49. data/lib/ruby_lsp/server.rb +129 -54
  50. data/lib/ruby_lsp/store.rb +33 -9
  51. data/lib/ruby_lsp/test_helper.rb +3 -1
  52. data/lib/ruby_lsp/type_inferrer.rb +61 -25
  53. data/lib/ruby_lsp/utils.rb +13 -0
  54. metadata +9 -8
  55. data/exe/ruby-lsp-doctor +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71204369ec5201376f1af11875a2e1bf16df56a31719ea78932a7f5f6e9c7e5c
4
- data.tar.gz: d83e299ade275415fe4cac25abe78f7ee7ec622c47cc1699d42b3b772816fdba
3
+ metadata.gz: 042725d7afce428b5c024933a7101151f2f69c57cbb40b4f938384c13d6c974b
4
+ data.tar.gz: bec8636d402451e1009e87ddd98e6fdedc062643e614b596a0698ffcfcc9b271
5
5
  SHA512:
6
- metadata.gz: f3ac517cb1c711dc1a5ddf2c4027f705e393e30028b9ae7063b18a5f51362fd4d7277607d19d8ca7a9a3c9146960bd612a02ed4a8933c8223a42daf3dfe5e63b
7
- data.tar.gz: 0c4408865d1894547aeedfc0441fa558a8281f54e7e26c38b619e67fd67bfc3bed49aa27dc06d8b89924c7ee8db4452b5cd2f810c0dfb01157eef127910b2182
6
+ metadata.gz: 6a6adb40a9ccaaf916f46c041f3eb2cc7a81be73c5d3bf7c05a45472cd9d08e5a9dd04d804b8e15f091645d420037e324368ccbcc619311a1c60c81f241c1b89
7
+ data.tar.gz: d6b19c8d02cfab8dca9e4d675e8ee549e8b392631a6080757c0413da29a07d1db411d9360b7553a798805d9e6faac7318863e35ba664488c5eb41c869a7e734a
data/README.md CHANGED
@@ -33,7 +33,7 @@ The Ruby LSP features include
33
33
  - Completion for classes, modules, constants and require paths
34
34
  - Fuzzy search classes, modules and constants anywhere in the project and its dependencies (workspace symbol)
35
35
 
36
- Adding method support for definition, completion, hover and workspace symbol is partially supported, but not yet complete. Follow progress in https://github.com/Shopify/ruby-lsp/issues/899
36
+ As of July 2024, Ruby LSP has received significant enhancements to its code navigation features. For an in-depth look at these improvements, including video demonstrations, check out this [article](https://railsatscale.com/2024-07-18-mastering-ruby-code-navigation-major-enhancements-in-ruby-lsp-2024/). Despite these advancements, we plan to continue enhancing its code navigation support even further. You can follow our progress on this [GitHub issue](https://github.com/Shopify/ruby-lsp/issues/899).
37
37
 
38
38
  See complete information about features [here](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
39
39
 
@@ -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
@@ -109,6 +114,10 @@ features. This is the mechanism that powers addons like
109
114
  - [Ruby LSP RSpec](https://github.com/st0012/ruby-lsp-rspec)
110
115
  - [Ruby LSP rubyfmt](https://github.com/jscharf/ruby-lsp-rubyfmt)
111
116
 
117
+ Additionally, some tools may include a Ruby LSP addon directly, like
118
+
119
+ - [Standard Ruby (from v1.39.1)](https://github.com/standardrb/standard/wiki/IDE:-vscode#using-ruby-lsp)
120
+
112
121
  Other community driven addons can be found in [rubygems](https://rubygems.org/search?query=name%3A+ruby-lsp) by
113
122
  searching for the `ruby-lsp` prefix.
114
123
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.4
1
+ 0.17.13
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
@@ -102,9 +106,30 @@ if options[:time_index]
102
106
 
103
107
  puts <<~MSG
104
108
  Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{result.round(5)} seconds and generated:
105
- - #{entries_by_entry_type.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
109
+ - #{entries_by_entry_type.sort_by { |k, _| k.to_s }.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
106
110
  MSG
107
111
  return
108
112
  end
109
113
 
114
+ if options[:doctor]
115
+ index = RubyIndexer::Index.new
116
+
117
+ if File.exist?(".index.yml")
118
+ begin
119
+ config = YAML.parse_file(".index.yml").to_ruby
120
+ rescue => e
121
+ abort("Error parsing config: #{e.message}")
122
+ end
123
+ index.configuration.apply_config(config)
124
+ end
125
+
126
+ puts "Globbing for indexable files"
127
+
128
+ index.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
data/exe/ruby-lsp-check CHANGED
@@ -44,7 +44,7 @@ puts "\n"
44
44
  puts "Verifying that indexing executes successfully. This may take a while..."
45
45
 
46
46
  index = RubyIndexer::Index.new
47
- indexables = RubyIndexer.configuration.indexables
47
+ indexables = index.configuration.indexables
48
48
 
49
49
  indexables.each_with_index do |indexable, i|
50
50
  index.index_single(indexable)
@@ -8,12 +8,22 @@ module RubyIndexer
8
8
  OBJECT_NESTING = T.let(["Object"].freeze, T::Array[String])
9
9
  BASIC_OBJECT_NESTING = T.let(["BasicObject"].freeze, T::Array[String])
10
10
 
11
+ sig { returns(T::Array[String]) }
12
+ attr_reader :indexing_errors
13
+
11
14
  sig do
12
- params(index: Index, dispatcher: Prism::Dispatcher, parse_result: Prism::ParseResult, file_path: String).void
15
+ params(
16
+ index: Index,
17
+ dispatcher: Prism::Dispatcher,
18
+ parse_result: Prism::ParseResult,
19
+ file_path: String,
20
+ enhancements: T::Array[Enhancement],
21
+ ).void
13
22
  end
14
- def initialize(index, dispatcher, parse_result, file_path)
23
+ def initialize(index, dispatcher, parse_result, file_path, enhancements: [])
15
24
  @index = index
16
25
  @file_path = file_path
26
+ @enhancements = enhancements
17
27
  @visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
18
28
  @comments_by_line = T.let(
19
29
  parse_result.comments.to_h do |c|
@@ -29,6 +39,7 @@ module RubyIndexer
29
39
 
30
40
  # A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
31
41
  @owner_stack = T.let([], T::Array[Entry::Namespace])
42
+ @indexing_errors = T.let([], T::Array[String])
32
43
 
33
44
  dispatcher.register(
34
45
  self,
@@ -64,13 +75,14 @@ module RubyIndexer
64
75
  sig { params(node: Prism::ClassNode).void }
65
76
  def on_class_node_enter(node)
66
77
  @visibility_stack.push(Entry::Visibility::PUBLIC)
67
- name = node.constant_path.location.slice
78
+ constant_path = node.constant_path
79
+ name = constant_path.slice
68
80
 
69
81
  comments = collect_comments(node)
70
82
 
71
83
  superclass = node.superclass
72
84
 
73
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
85
+ nesting = actual_nesting(name)
74
86
 
75
87
  parent_class = case superclass
76
88
  when Prism::ConstantReadNode, Prism::ConstantPathNode
@@ -93,6 +105,7 @@ module RubyIndexer
93
105
  nesting,
94
106
  @file_path,
95
107
  node.location,
108
+ constant_path.location,
96
109
  comments,
97
110
  parent_class,
98
111
  )
@@ -112,12 +125,12 @@ module RubyIndexer
112
125
  sig { params(node: Prism::ModuleNode).void }
113
126
  def on_module_node_enter(node)
114
127
  @visibility_stack.push(Entry::Visibility::PUBLIC)
115
- name = node.constant_path.location.slice
128
+ constant_path = node.constant_path
129
+ name = constant_path.slice
116
130
 
117
131
  comments = collect_comments(node)
118
132
 
119
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
120
- entry = Entry::Module.new(nesting, @file_path, node.location, comments)
133
+ entry = Entry::Module.new(actual_nesting(name), @file_path, node.location, constant_path.location, comments)
121
134
 
122
135
  @owner_stack << entry
123
136
  @index.add(entry)
@@ -145,9 +158,16 @@ module RubyIndexer
145
158
 
146
159
  if existing_entries
147
160
  entry = T.must(existing_entries.first)
148
- entry.update_singleton_information(node.location, collect_comments(node))
161
+ entry.update_singleton_information(node.location, expression.location, collect_comments(node))
149
162
  else
150
- entry = Entry::SingletonClass.new(@stack, @file_path, node.location, collect_comments(node), nil)
163
+ entry = Entry::SingletonClass.new(
164
+ @stack,
165
+ @file_path,
166
+ node.location,
167
+ expression.location,
168
+ collect_comments(node),
169
+ nil,
170
+ )
151
171
  @index.add(entry, skip_prefix_tree: true)
152
172
  end
153
173
 
@@ -270,6 +290,12 @@ module RubyIndexer
270
290
  when :private
271
291
  @visibility_stack.push(Entry::Visibility::PRIVATE)
272
292
  end
293
+
294
+ @enhancements.each do |enhancement|
295
+ enhancement.on_call_node(@index, @owner_stack.last, node, @file_path)
296
+ rescue StandardError => e
297
+ @indexing_errors << "Indexing error in #{@file_path} with '#{enhancement.class.name}' enhancement: #{e.message}"
298
+ end
273
299
  end
274
300
 
275
301
  sig { params(node: Prism::CallNode).void }
@@ -297,25 +323,29 @@ module RubyIndexer
297
323
  method_name,
298
324
  @file_path,
299
325
  node.location,
326
+ node.name_loc,
300
327
  comments,
301
- list_params(node.parameters),
328
+ [Entry::Signature.new(list_params(node.parameters))],
302
329
  current_visibility,
303
330
  @owner_stack.last,
304
331
  ))
305
332
  when Prism::SelfNode
306
- singleton = singleton_klass
307
-
308
- @index.add(Entry::Method.new(
309
- method_name,
310
- @file_path,
311
- node.location,
312
- comments,
313
- list_params(node.parameters),
314
- current_visibility,
315
- singleton,
316
- ))
333
+ owner = @owner_stack.last
334
+
335
+ if owner
336
+ singleton = @index.existing_or_new_singleton_class(owner.name)
337
+
338
+ @index.add(Entry::Method.new(
339
+ method_name,
340
+ @file_path,
341
+ node.location,
342
+ node.name_loc,
343
+ comments,
344
+ [Entry::Signature.new(list_params(node.parameters))],
345
+ current_visibility,
346
+ singleton,
347
+ ))
317
348
 
318
- if singleton
319
349
  @owner_stack << singleton
320
350
  @stack << "<Class:#{@stack.last}>"
321
351
  end
@@ -393,7 +423,12 @@ module RubyIndexer
393
423
 
394
424
  # When instance variables are declared inside the class body, they turn into class instance variables rather than
395
425
  # regular instance variables
396
- owner = @inside_def ? @owner_stack.last : singleton_klass
426
+ owner = @owner_stack.last
427
+
428
+ if owner && !@inside_def
429
+ owner = @index.existing_or_new_singleton_class(owner.name)
430
+ end
431
+
397
432
  @index.add(Entry::InstanceVariable.new(name, @file_path, loc, collect_comments(node), owner))
398
433
  end
399
434
 
@@ -486,17 +521,17 @@ module RubyIndexer
486
521
  @index.add(
487
522
  case value
488
523
  when Prism::ConstantReadNode, Prism::ConstantPathNode
489
- Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
524
+ Entry::UnresolvedConstantAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
490
525
  when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
491
526
  Prism::ConstantOperatorWriteNode
492
527
 
493
528
  # If the right hand side is another constant assignment, we need to visit it because that constant has to be
494
529
  # indexed too
495
- Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
530
+ Entry::UnresolvedConstantAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
496
531
  when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
497
532
  Prism::ConstantPathAndWriteNode
498
533
 
499
- Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
534
+ Entry::UnresolvedConstantAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
500
535
  else
501
536
  Entry::Constant.new(name, @file_path, node.location, comments)
502
537
  end,
@@ -517,7 +552,7 @@ module RubyIndexer
517
552
  comment_content = comment.location.slice.chomp
518
553
 
519
554
  # invalid encodings would raise an "invalid byte sequence" exception
520
- if !comment_content.valid_encoding? || comment_content.match?(RubyIndexer.configuration.magic_comment_regex)
555
+ if !comment_content.valid_encoding? || comment_content.match?(@index.configuration.magic_comment_regex)
521
556
  next
522
557
  end
523
558
 
@@ -593,7 +628,8 @@ module RubyIndexer
593
628
  when :prepend
594
629
  owner.mixin_operations << Entry::Prepend.new(node.full_name)
595
630
  when :extend
596
- owner.mixin_operations << Entry::Extend.new(node.full_name)
631
+ singleton = @index.existing_or_new_singleton_class(owner.name)
632
+ singleton.mixin_operations << Entry::Include.new(node.full_name)
597
633
  end
598
634
  rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
599
635
  Prism::ConstantPathNode::MissingNodesInConstantPathError
@@ -690,23 +726,18 @@ module RubyIndexer
690
726
  end
691
727
  end
692
728
 
693
- sig { returns(T.nilable(Entry::Class)) }
694
- def singleton_klass
695
- attached_class = @owner_stack.last
696
- return unless attached_class
729
+ sig { params(name: String).returns(T::Array[String]) }
730
+ def actual_nesting(name)
731
+ nesting = @stack + [name]
732
+ corrected_nesting = []
697
733
 
698
- # Return the existing singleton class if available
699
- owner = T.cast(
700
- @index["#{attached_class.name}::<Class:#{attached_class.name}>"],
701
- T.nilable(T::Array[Entry::SingletonClass]),
702
- )
703
- return owner.first if owner
734
+ nesting.reverse_each do |name|
735
+ corrected_nesting.prepend(name.delete_prefix("::"))
736
+
737
+ break if name.start_with?("::")
738
+ end
704
739
 
705
- # If not available, create the singleton class lazily
706
- nesting = @stack + ["<Class:#{@stack.last}>"]
707
- entry = Entry::SingletonClass.new(nesting, @file_path, attached_class.location, [], nil)
708
- @index.add(entry, skip_prefix_tree: true)
709
- entry
740
+ corrected_nesting
710
741
  end
711
742
  end
712
743
  end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ module Enhancement
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ interface!
10
+
11
+ requires_ancestor { Object }
12
+
13
+ # The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
14
+ # register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
15
+ # `ClassMethods` modules
16
+ sig do
17
+ abstract.params(
18
+ index: Index,
19
+ owner: T.nilable(Entry::Namespace),
20
+ node: Prism::CallNode,
21
+ file_path: String,
22
+ ).void
23
+ end
24
+ def on_call_node(index, owner, node, file_path); end
25
+ end
26
+ end