ruby-lsp 0.11.2 → 0.12.1

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