ruby-lsp 0.11.2 → 0.12.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +11 -2
  4. data/exe/ruby-lsp-check +2 -1
  5. data/exe/ruby-lsp-doctor +15 -0
  6. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +125 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +10 -2
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +205 -0
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +23 -106
  10. data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +1 -1
  11. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -6
  12. data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +101 -49
  13. data/lib/ruby_indexer/ruby_indexer.rb +4 -3
  14. data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -16
  15. data/lib/ruby_indexer/test/constant_test.rb +99 -36
  16. data/lib/ruby_indexer/test/index_test.rb +1 -1
  17. data/lib/ruby_indexer/test/method_test.rb +73 -0
  18. data/lib/ruby_indexer/test/test_case.rb +5 -1
  19. data/lib/ruby_lsp/addon.rb +8 -8
  20. data/lib/ruby_lsp/document.rb +14 -14
  21. data/lib/ruby_lsp/executor.rb +89 -53
  22. data/lib/ruby_lsp/internal.rb +7 -2
  23. data/lib/ruby_lsp/listener.rb +6 -6
  24. data/lib/ruby_lsp/requests/base_request.rb +1 -9
  25. data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
  26. data/lib/ruby_lsp/requests/code_lens.rb +47 -31
  27. data/lib/ruby_lsp/requests/completion.rb +83 -32
  28. data/lib/ruby_lsp/requests/definition.rb +21 -15
  29. data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
  30. data/lib/ruby_lsp/requests/document_highlight.rb +508 -31
  31. data/lib/ruby_lsp/requests/document_link.rb +24 -17
  32. data/lib/ruby_lsp/requests/document_symbol.rb +42 -42
  33. data/lib/ruby_lsp/requests/folding_ranges.rb +83 -77
  34. data/lib/ruby_lsp/requests/hover.rb +22 -17
  35. data/lib/ruby_lsp/requests/inlay_hints.rb +6 -6
  36. data/lib/ruby_lsp/requests/selection_ranges.rb +13 -105
  37. data/lib/ruby_lsp/requests/semantic_highlighting.rb +92 -92
  38. data/lib/ruby_lsp/requests/support/annotation.rb +3 -3
  39. data/lib/ruby_lsp/requests/support/common.rb +5 -5
  40. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +21 -7
  41. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +19 -0
  42. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +10 -7
  43. data/lib/ruby_lsp/requests/support/sorbet.rb +28 -28
  44. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
  45. data/lib/ruby_lsp/requests.rb +0 -1
  46. data/lib/ruby_lsp/setup_bundler.rb +26 -17
  47. metadata +20 -17
  48. data/lib/ruby_lsp/event_emitter.rb +0 -351
  49. data/lib/ruby_lsp/requests/support/highlight_target.rb +0 -118
@@ -30,42 +30,53 @@ module RubyLsp
30
30
  params(
31
31
  index: RubyIndexer::Index,
32
32
  nesting: T::Array[String],
33
- emitter: EventEmitter,
33
+ dispatcher: Prism::Dispatcher,
34
34
  message_queue: Thread::Queue,
35
35
  ).void
36
36
  end
37
- def initialize(index, nesting, emitter, message_queue)
38
- super(emitter, message_queue)
37
+ def initialize(index, nesting, dispatcher, message_queue)
38
+ super(dispatcher, message_queue)
39
39
  @_response = T.let([], ResponseType)
40
40
  @index = index
41
41
  @nesting = nesting
42
42
 
43
- emitter.register(self, :on_string, :on_constant_path, :on_constant_read)
43
+ dispatcher.register(
44
+ self,
45
+ :on_string_node_enter,
46
+ :on_constant_path_node_enter,
47
+ :on_constant_read_node_enter,
48
+ )
44
49
  end
45
50
 
46
- sig { params(node: YARP::StringNode).void }
47
- def on_string(node)
51
+ sig { params(node: Prism::StringNode).void }
52
+ def on_string_node_enter(node)
48
53
  @index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
49
54
  @_response << build_completion(T.must(path), node)
50
55
  end
51
56
  end
52
57
 
53
58
  # Handle completion on regular constant references (e.g. `Bar`)
54
- sig { params(node: YARP::ConstantReadNode).void }
55
- def on_constant_read(node)
59
+ sig { params(node: Prism::ConstantReadNode).void }
60
+ def on_constant_read_node_enter(node)
56
61
  return if DependencyDetector.instance.typechecker
57
62
 
58
63
  name = node.slice
59
64
  candidates = @index.prefix_search(name, @nesting)
60
65
  candidates.each do |entries|
61
66
  complete_name = T.must(entries.first).name
62
- @_response << build_entry_completion(complete_name, node, entries, top_level?(complete_name, candidates))
67
+ @_response << build_entry_completion(
68
+ complete_name,
69
+ name,
70
+ node,
71
+ entries,
72
+ top_level?(complete_name),
73
+ )
63
74
  end
64
75
  end
65
76
 
66
77
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
67
- sig { params(node: YARP::ConstantPathNode).void }
68
- def on_constant_path(node)
78
+ sig { params(node: Prism::ConstantPathNode).void }
79
+ def on_constant_path_node_enter(node)
69
80
  return if DependencyDetector.instance.typechecker
70
81
 
71
82
  name = node.slice
@@ -80,7 +91,7 @@ module RubyLsp
80
91
  # If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
81
92
  # order to find which possible constants match the desired search
82
93
  *namespace, incomplete_name = name.split("::")
83
- aliased_namespace = namespace.join("::")
94
+ aliased_namespace = T.must(namespace).join("::")
84
95
  namespace_entries = @index.resolve(aliased_namespace, @nesting)
85
96
  return unless namespace_entries
86
97
 
@@ -99,16 +110,17 @@ module RubyLsp
99
110
 
100
111
  @_response << build_entry_completion(
101
112
  full_name,
113
+ name,
102
114
  node,
103
115
  entries,
104
- top_level_reference || top_level?(T.must(entries.first).name, candidates),
116
+ top_level_reference || top_level?(T.must(entries.first).name),
105
117
  )
106
118
  end
107
119
  end
108
120
 
109
121
  private
110
122
 
111
- sig { params(label: String, node: YARP::StringNode).returns(Interface::CompletionItem) }
123
+ sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
112
124
  def build_completion(label, node)
113
125
  Interface::CompletionItem.new(
114
126
  label: label,
@@ -122,33 +134,38 @@ module RubyLsp
122
134
 
123
135
  sig do
124
136
  params(
125
- name: String,
126
- node: YARP::Node,
127
- entries: T::Array[RubyIndexer::Index::Entry],
137
+ real_name: String,
138
+ incomplete_name: String,
139
+ node: Prism::Node,
140
+ entries: T::Array[RubyIndexer::Entry],
128
141
  top_level: T::Boolean,
129
142
  ).returns(Interface::CompletionItem)
130
143
  end
131
- def build_entry_completion(name, node, entries, top_level)
144
+ def build_entry_completion(real_name, incomplete_name, node, entries, top_level)
132
145
  first_entry = T.must(entries.first)
133
146
  kind = case first_entry
134
- when RubyIndexer::Index::Entry::Class
147
+ when RubyIndexer::Entry::Class
135
148
  Constant::CompletionItemKind::CLASS
136
- when RubyIndexer::Index::Entry::Module
149
+ when RubyIndexer::Entry::Module
137
150
  Constant::CompletionItemKind::MODULE
138
- when RubyIndexer::Index::Entry::Constant
151
+ when RubyIndexer::Entry::Constant
139
152
  Constant::CompletionItemKind::CONSTANT
140
153
  else
141
154
  Constant::CompletionItemKind::REFERENCE
142
155
  end
143
156
 
144
- insertion_text = name.dup
157
+ insertion_text = real_name.dup
158
+ filter_text = real_name.dup
145
159
 
146
160
  # If we have two entries with the same name inside the current namespace and the user selects the top level
147
161
  # option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
148
162
  # If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
149
163
  # then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
150
164
  # to complete to `::Bar`.
151
- insertion_text.prepend("::") if top_level
165
+ if top_level
166
+ insertion_text.prepend("::")
167
+ filter_text.prepend("::")
168
+ end
152
169
 
153
170
  # If the user is searching for a constant inside the current namespace, then we prefer completing the short name
154
171
  # of that constant. E.g.:
@@ -159,14 +176,28 @@ module RubyLsp
159
176
  #
160
177
  # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
161
178
  # end
162
- @nesting.each { |namespace| insertion_text.delete_prefix!("#{namespace}::") }
179
+ @nesting.each do |namespace|
180
+ prefix = "#{namespace}::"
181
+ shortened_name = insertion_text.delete_prefix(prefix)
182
+
183
+ # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
184
+ conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
185
+ break if real_name != conflict_name && @index[conflict_name]
186
+
187
+ insertion_text = shortened_name
188
+
189
+ # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
190
+ # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
191
+ # typing
192
+ filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
193
+ end
163
194
 
164
195
  # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
165
196
  # For these top level references, we need to include the `::` as part of the filter text or else it won't match
166
197
  # the right entries in the index
167
198
  Interface::CompletionItem.new(
168
- label: name,
169
- filter_text: top_level ? "::#{name}" : name,
199
+ label: real_name,
200
+ filter_text: filter_text,
170
201
  text_edit: Interface::TextEdit.new(
171
202
  range: range_from_node(node),
172
203
  new_text: insertion_text,
@@ -175,15 +206,35 @@ module RubyLsp
175
206
  label_details: Interface::CompletionItemLabelDetails.new(
176
207
  description: entries.map(&:file_name).join(","),
177
208
  ),
178
- documentation: markdown_from_index_entries(name, entries),
209
+ documentation: markdown_from_index_entries(real_name, entries),
179
210
  )
180
211
  end
181
212
 
182
- # Check if the `entry_name` has potential conflicts in `candidates`, so that we use a top level reference instead
183
- # of a short name
184
- sig { params(entry_name: String, candidates: T::Array[T::Array[RubyIndexer::Index::Entry]]).returns(T::Boolean) }
185
- def top_level?(entry_name, candidates)
186
- candidates.any? { |entries| T.must(entries.first).name == "#{@nesting.join("::")}::#{entry_name}" }
213
+ # Check if there are any conflicting names for `entry_name`, which would require us to use a top level reference.
214
+ # For example:
215
+ #
216
+ # ```ruby
217
+ # class Bar; end
218
+ #
219
+ # module Foo
220
+ # class Bar; end
221
+ #
222
+ # # in this case, the completion for `Bar` conflicts with `Foo::Bar`, so we can't suggest `Bar` as the
223
+ # # completion, but instead need to suggest `::Bar`
224
+ # B
225
+ # end
226
+ # ```
227
+ sig { params(entry_name: String).returns(T::Boolean) }
228
+ def top_level?(entry_name)
229
+ @nesting.length.downto(0).each do |i|
230
+ prefix = T.must(@nesting[0...i]).join("::")
231
+ full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
232
+ next if full_name == entry_name
233
+
234
+ return true if @index[full_name]
235
+ end
236
+
237
+ false
187
238
  end
188
239
  end
189
240
  end
@@ -31,23 +31,29 @@ module RubyLsp
31
31
  uri: URI::Generic,
32
32
  nesting: T::Array[String],
33
33
  index: RubyIndexer::Index,
34
- emitter: EventEmitter,
34
+ dispatcher: Prism::Dispatcher,
35
35
  message_queue: Thread::Queue,
36
36
  ).void
37
37
  end
38
- def initialize(uri, nesting, index, emitter, message_queue)
38
+ def initialize(uri, nesting, index, dispatcher, message_queue)
39
39
  @uri = uri
40
40
  @nesting = nesting
41
41
  @index = index
42
42
  @_response = T.let(nil, ResponseType)
43
43
 
44
- super(emitter, message_queue)
44
+ super(dispatcher, message_queue)
45
45
 
46
- emitter.register(self, :on_call, :on_constant_read, :on_constant_path)
46
+ dispatcher.register(
47
+ self,
48
+ :on_call_node_enter,
49
+ :on_constant_read_node_enter,
50
+ :on_constant_path_node_enter,
51
+ )
47
52
  end
53
+
48
54
  sig { override.params(addon: Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
49
55
  def initialize_external_listener(addon)
50
- addon.create_definition_listener(@uri, @nesting, @index, @emitter, @message_queue)
56
+ addon.create_definition_listener(@uri, @nesting, @index, @dispatcher, @message_queue)
51
57
  end
52
58
 
53
59
  sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
@@ -66,19 +72,19 @@ module RubyLsp
66
72
  self
67
73
  end
68
74
 
69
- sig { params(node: YARP::CallNode).void }
70
- def on_call(node)
75
+ sig { params(node: Prism::CallNode).void }
76
+ def on_call_node_enter(node)
71
77
  message = node.name
72
- return unless message == "require" || message == "require_relative"
78
+ return unless message == :require || message == :require_relative
73
79
 
74
80
  arguments = node.arguments
75
81
  return unless arguments
76
82
 
77
83
  argument = arguments.arguments.first
78
- return unless argument.is_a?(YARP::StringNode)
84
+ return unless argument.is_a?(Prism::StringNode)
79
85
 
80
86
  case message
81
- when "require"
87
+ when :require
82
88
  entry = @index.search_require_paths(argument.content).find do |indexable_path|
83
89
  indexable_path.require_path == argument.content
84
90
  end
@@ -94,7 +100,7 @@ module RubyLsp
94
100
  ),
95
101
  )
96
102
  end
97
- when "require_relative"
103
+ when :require_relative
98
104
  required_file = "#{argument.content}.rb"
99
105
  path = @uri.to_standardized_path
100
106
  current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
@@ -110,13 +116,13 @@ module RubyLsp
110
116
  end
111
117
  end
112
118
 
113
- sig { params(node: YARP::ConstantPathNode).void }
114
- def on_constant_path(node)
119
+ sig { params(node: Prism::ConstantPathNode).void }
120
+ def on_constant_path_node_enter(node)
115
121
  find_in_index(node.slice)
116
122
  end
117
123
 
118
- sig { params(node: YARP::ConstantReadNode).void }
119
- def on_constant_read(node)
124
+ sig { params(node: Prism::ConstantReadNode).void }
125
+ def on_constant_read_node_enter(node)
120
126
  find_in_index(node.slice)
121
127
  end
122
128
 
@@ -60,7 +60,7 @@ module RubyLsp
60
60
  ),
61
61
  message: error.message,
62
62
  severity: Constant::DiagnosticSeverity::ERROR,
63
- source: "YARP",
63
+ source: "Prism",
64
64
  )
65
65
  end
66
66
  end