ruby-lsp 0.13.2 → 0.13.3

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_lsp/check_docs.rb +3 -3
  5. data/lib/ruby_lsp/document.rb +12 -0
  6. data/lib/ruby_lsp/executor.rb +77 -266
  7. data/lib/ruby_lsp/listener.rb +1 -50
  8. data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
  9. data/lib/ruby_lsp/listeners/completion.rb +275 -0
  10. data/lib/ruby_lsp/listeners/definition.rb +158 -0
  11. data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
  12. data/lib/ruby_lsp/listeners/document_link.rb +162 -0
  13. data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
  14. data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
  15. data/lib/ruby_lsp/listeners/hover.rb +152 -0
  16. data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
  17. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
  18. data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
  19. data/lib/ruby_lsp/requests/code_action_resolve.rb +4 -4
  20. data/lib/ruby_lsp/requests/code_actions.rb +13 -4
  21. data/lib/ruby_lsp/requests/code_lens.rb +21 -221
  22. data/lib/ruby_lsp/requests/completion.rb +64 -244
  23. data/lib/ruby_lsp/requests/definition.rb +34 -147
  24. data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
  25. data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
  26. data/lib/ruby_lsp/requests/document_link.rb +11 -132
  27. data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
  28. data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
  29. data/lib/ruby_lsp/requests/formatting.rb +4 -4
  30. data/lib/ruby_lsp/requests/hover.rb +48 -92
  31. data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
  32. data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -4
  33. data/lib/ruby_lsp/requests/request.rb +17 -0
  34. data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
  36. data/lib/ruby_lsp/requests/show_syntax_tree.rb +4 -4
  37. data/lib/ruby_lsp/requests/signature_help.rb +43 -51
  38. data/lib/ruby_lsp/requests/support/common.rb +3 -2
  39. data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
  40. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  42. data/lib/ruby_lsp/requests.rb +1 -1
  43. data/lib/ruby_lsp/utils.rb +8 -0
  44. metadata +15 -4
  45. data/lib/ruby_lsp/requests/base_request.rb +0 -24
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/listeners/completion"
5
+
4
6
  module RubyLsp
5
7
  module Requests
6
8
  # ![Completion demo](../../completion.gif)
@@ -22,269 +24,87 @@ module RubyLsp
22
24
  #
23
25
  # RubyLsp::Requests:: # --> completion: suggests `Completion`, `Hover`, ...
24
26
  # ```
25
- class Completion < Listener
27
+ class Completion < Request
26
28
  extend T::Sig
27
29
  extend T::Generic
28
30
 
29
- ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
30
-
31
- sig { override.returns(ResponseType) }
32
- attr_reader :_response
33
-
34
- sig do
35
- params(
36
- index: RubyIndexer::Index,
37
- nesting: T::Array[String],
38
- dispatcher: Prism::Dispatcher,
39
- ).void
40
- end
41
- def initialize(index, nesting, dispatcher)
42
- super(dispatcher)
43
- @_response = T.let([], ResponseType)
44
- @index = index
45
- @nesting = nesting
46
-
47
- dispatcher.register(
48
- self,
49
- :on_string_node_enter,
50
- :on_constant_path_node_enter,
51
- :on_constant_read_node_enter,
52
- :on_call_node_enter,
53
- )
54
- end
55
-
56
- sig { params(node: Prism::StringNode).void }
57
- def on_string_node_enter(node)
58
- @index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
59
- @_response << build_completion(T.must(path), node)
60
- end
61
- end
62
-
63
- # Handle completion on regular constant references (e.g. `Bar`)
64
- sig { params(node: Prism::ConstantReadNode).void }
65
- def on_constant_read_node_enter(node)
66
- return if DependencyDetector.instance.typechecker
67
-
68
- name = node.slice
69
- candidates = @index.prefix_search(name, @nesting)
70
- candidates.each do |entries|
71
- complete_name = T.must(entries.first).name
72
- @_response << build_entry_completion(
73
- complete_name,
74
- name,
75
- node,
76
- entries,
77
- top_level?(complete_name),
78
- )
79
- end
80
- end
81
-
82
- # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
83
- sig { params(node: Prism::ConstantPathNode).void }
84
- def on_constant_path_node_enter(node)
85
- return if DependencyDetector.instance.typechecker
86
-
87
- name = node.slice
88
-
89
- top_level_reference = if name.start_with?("::")
90
- name = name.delete_prefix("::")
91
- true
92
- else
93
- false
94
- end
95
-
96
- # If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
97
- # order to find which possible constants match the desired search
98
- *namespace, incomplete_name = name.split("::")
99
- aliased_namespace = T.must(namespace).join("::")
100
- namespace_entries = @index.resolve(aliased_namespace, @nesting)
101
- return unless namespace_entries
102
-
103
- real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
104
-
105
- candidates = @index.prefix_search("#{real_namespace}::#{incomplete_name}", top_level_reference ? [] : @nesting)
106
- candidates.each do |entries|
107
- # The only time we may have a private constant reference from outside of the namespace is if we're dealing
108
- # with ConstantPath and the entry name doesn't start with the current nesting
109
- first_entry = T.must(entries.first)
110
- next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
111
-
112
- constant_name = T.must(first_entry.name.split("::").last)
113
-
114
- full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
115
-
116
- @_response << build_entry_completion(
117
- full_name,
118
- name,
119
- node,
120
- entries,
121
- top_level_reference || top_level?(T.must(entries.first).name),
31
+ class << self
32
+ extend T::Sig
33
+
34
+ sig { returns(Interface::CompletionOptions) }
35
+ def provider
36
+ Interface::CompletionOptions.new(
37
+ resolve_provider: false,
38
+ trigger_characters: ["/"],
39
+ completion_item: {
40
+ labelDetailsSupport: true,
41
+ },
122
42
  )
123
43
  end
124
44
  end
125
45
 
126
- sig { params(node: Prism::CallNode).void }
127
- def on_call_node_enter(node)
128
- return if DependencyDetector.instance.typechecker
129
- return unless self_receiver?(node)
130
-
131
- name = node.message
132
- return unless name
133
-
134
- receiver_entries = @index[@nesting.join("::")]
135
- return unless receiver_entries
136
-
137
- receiver = T.must(receiver_entries.first)
138
-
139
- @index.prefix_search(name).each do |entries|
140
- entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
141
- next unless entry
142
-
143
- @_response << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
144
- end
145
- end
146
-
147
- private
46
+ ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
148
47
 
149
48
  sig do
150
49
  params(
151
- entry: RubyIndexer::Entry::Member,
152
- node: Prism::CallNode,
153
- ).returns(Interface::CompletionItem)
50
+ document: Document,
51
+ index: RubyIndexer::Index,
52
+ position: T::Hash[Symbol, T.untyped],
53
+ typechecker_enabled: T::Boolean,
54
+ dispatcher: Prism::Dispatcher,
55
+ ).void
154
56
  end
155
- def build_method_completion(entry, node)
156
- name = entry.name
157
-
158
- Interface::CompletionItem.new(
159
- label: name,
160
- filter_text: name,
161
- text_edit: Interface::TextEdit.new(range: range_from_node(node), new_text: name),
162
- kind: Constant::CompletionItemKind::METHOD,
163
- label_details: Interface::CompletionItemLabelDetails.new(
164
- description: entry.file_name,
165
- ),
166
- documentation: markdown_from_index_entries(name, entry),
57
+ def initialize(document, index, position, typechecker_enabled, dispatcher)
58
+ super()
59
+ @target = T.let(nil, T.nilable(Prism::Node))
60
+ @dispatcher = dispatcher
61
+ # Completion always receives the position immediately after the character that was just typed. Here we adjust it
62
+ # back by 1, so that we find the right node
63
+ char_position = document.create_scanner.find_char_position(position) - 1
64
+ matched, parent, nesting = document.locate(
65
+ document.tree,
66
+ char_position,
67
+ node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
167
68
  )
168
- end
169
69
 
170
- sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
171
- def build_completion(label, node)
172
- # We should use the content location as we only replace the content and not the delimiters of the string
173
- loc = node.content_loc
174
-
175
- Interface::CompletionItem.new(
176
- label: label,
177
- text_edit: Interface::TextEdit.new(
178
- range: range_from_location(loc),
179
- new_text: label,
180
- ),
181
- kind: Constant::CompletionItemKind::FILE,
70
+ @listener = T.let(
71
+ Listeners::Completion.new(index, nesting, typechecker_enabled, dispatcher),
72
+ Listener[ResponseType],
182
73
  )
183
- end
184
74
 
185
- sig do
186
- params(
187
- real_name: String,
188
- incomplete_name: String,
189
- node: Prism::Node,
190
- entries: T::Array[RubyIndexer::Entry],
191
- top_level: T::Boolean,
192
- ).returns(Interface::CompletionItem)
193
- end
194
- def build_entry_completion(real_name, incomplete_name, node, entries, top_level)
195
- first_entry = T.must(entries.first)
196
- kind = case first_entry
197
- when RubyIndexer::Entry::Class
198
- Constant::CompletionItemKind::CLASS
199
- when RubyIndexer::Entry::Module
200
- Constant::CompletionItemKind::MODULE
201
- when RubyIndexer::Entry::Constant
202
- Constant::CompletionItemKind::CONSTANT
203
- else
204
- Constant::CompletionItemKind::REFERENCE
205
- end
206
-
207
- insertion_text = real_name.dup
208
- filter_text = real_name.dup
209
-
210
- # If we have two entries with the same name inside the current namespace and the user selects the top level
211
- # option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
212
- # If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
213
- # then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
214
- # to complete to `::Bar`.
215
- if top_level
216
- insertion_text.prepend("::")
217
- filter_text.prepend("::")
218
- end
219
-
220
- # If the user is searching for a constant inside the current namespace, then we prefer completing the short name
221
- # of that constant. E.g.:
222
- #
223
- # module Foo
224
- # class Bar
225
- # end
226
- #
227
- # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
228
- # end
229
- @nesting.each do |namespace|
230
- prefix = "#{namespace}::"
231
- shortened_name = insertion_text.delete_prefix(prefix)
232
-
233
- # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
234
- conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
235
- break if real_name != conflict_name && @index[conflict_name]
236
-
237
- insertion_text = shortened_name
238
-
239
- # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
240
- # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
241
- # typing
242
- filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
75
+ return unless matched && parent
76
+
77
+ @target = case matched
78
+ when Prism::CallNode
79
+ message = matched.message
80
+
81
+ if message == "require"
82
+ args = matched.arguments&.arguments
83
+ return if args.nil? || args.is_a?(Prism::ForwardingArgumentsNode)
84
+
85
+ argument = args.first
86
+ return unless argument.is_a?(Prism::StringNode)
87
+ return unless (argument.location.start_offset..argument.location.end_offset).cover?(char_position)
88
+
89
+ argument
90
+ else
91
+ matched
92
+ end
93
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
94
+ if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
95
+ parent
96
+ else
97
+ matched
98
+ end
243
99
  end
244
-
245
- # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
246
- # For these top level references, we need to include the `::` as part of the filter text or else it won't match
247
- # the right entries in the index
248
- Interface::CompletionItem.new(
249
- label: real_name,
250
- filter_text: filter_text,
251
- text_edit: Interface::TextEdit.new(
252
- range: range_from_node(node),
253
- new_text: insertion_text,
254
- ),
255
- kind: kind,
256
- label_details: Interface::CompletionItemLabelDetails.new(
257
- description: entries.map(&:file_name).join(","),
258
- ),
259
- documentation: markdown_from_index_entries(real_name, entries),
260
- )
261
100
  end
262
101
 
263
- # Check if there are any conflicting names for `entry_name`, which would require us to use a top level reference.
264
- # For example:
265
- #
266
- # ```ruby
267
- # class Bar; end
268
- #
269
- # module Foo
270
- # class Bar; end
271
- #
272
- # # in this case, the completion for `Bar` conflicts with `Foo::Bar`, so we can't suggest `Bar` as the
273
- # # completion, but instead need to suggest `::Bar`
274
- # B
275
- # end
276
- # ```
277
- sig { params(entry_name: String).returns(T::Boolean) }
278
- def top_level?(entry_name)
279
- @nesting.length.downto(0).each do |i|
280
- prefix = T.must(@nesting[0...i]).join("::")
281
- full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
282
- next if full_name == entry_name
283
-
284
- return true if @index[full_name]
285
- end
102
+ sig { override.returns(ResponseType) }
103
+ def perform
104
+ return [] unless @target
286
105
 
287
- false
106
+ @dispatcher.dispatch_once(@target)
107
+ @listener.response
288
108
  end
289
109
  end
290
110
  end
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/listeners/definition"
5
+
4
6
  module RubyLsp
5
7
  module Requests
6
8
  # ![Definition demo](../../definition.gif)
@@ -22,174 +24,59 @@ module RubyLsp
22
24
  # require "some_gem/file" # <- Request go to definition on this string will take you to the file
23
25
  # Product.new # <- Request go to definition on this class name will take you to its declaration.
24
26
  # ```
25
- class Definition < ExtensibleListener
27
+ class Definition < Request
26
28
  extend T::Sig
27
29
  extend T::Generic
28
30
 
29
31
  ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
30
32
 
31
- sig { override.returns(ResponseType) }
32
- attr_reader :_response
33
-
34
33
  sig do
35
34
  params(
36
- uri: URI::Generic,
37
- nesting: T::Array[String],
35
+ document: Document,
38
36
  index: RubyIndexer::Index,
37
+ position: T::Hash[Symbol, T.untyped],
39
38
  dispatcher: Prism::Dispatcher,
39
+ typechecker_enabled: T::Boolean,
40
40
  ).void
41
41
  end
42
- def initialize(uri, nesting, index, dispatcher)
43
- @uri = uri
44
- @nesting = nesting
45
- @index = index
46
- @_response = T.let(nil, ResponseType)
47
-
48
- super(dispatcher)
49
-
50
- dispatcher.register(
51
- self,
52
- :on_call_node_enter,
53
- :on_constant_read_node_enter,
54
- :on_constant_path_node_enter,
42
+ def initialize(document, index, position, dispatcher, typechecker_enabled)
43
+ super()
44
+ target, parent, nesting = document.locate_node(
45
+ position,
46
+ node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
55
47
  )
56
- end
57
-
58
- sig { override.params(addon: Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
59
- def initialize_external_listener(addon)
60
- addon.create_definition_listener(@uri, @nesting, @index, @dispatcher)
61
- end
62
48
 
63
- sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
64
- def merge_response!(other)
65
- other_response = other._response
66
-
67
- case @_response
68
- when Interface::Location
69
- @_response = [@_response, *other_response]
70
- when Array
71
- @_response.concat(Array(other_response))
72
- when nil
73
- @_response = other_response
74
- end
49
+ target = parent if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
75
50
 
76
- self
77
- end
78
-
79
- sig { params(node: Prism::CallNode).void }
80
- def on_call_node_enter(node)
81
- message = node.name
82
-
83
- if message == :require || message == :require_relative
84
- handle_require_definition(node)
85
- else
86
- handle_method_definition(node)
51
+ @listeners = T.let(
52
+ [Listeners::Definition.new(document.uri, nesting, index, dispatcher, typechecker_enabled)],
53
+ T::Array[Listener[T.nilable(T.any(T::Array[Interface::Location], Interface::Location))]],
54
+ )
55
+ Addon.addons.each do |addon|
56
+ addon_listener = addon.create_definition_listener(document.uri, nesting, index, dispatcher)
57
+ @listeners << addon_listener if addon_listener
87
58
  end
88
- end
89
59
 
90
- sig { params(node: Prism::ConstantPathNode).void }
91
- def on_constant_path_node_enter(node)
92
- find_in_index(node.slice)
60
+ @target = T.let(target, T.nilable(Prism::Node))
61
+ @dispatcher = dispatcher
93
62
  end
94
63
 
95
- sig { params(node: Prism::ConstantReadNode).void }
96
- def on_constant_read_node_enter(node)
97
- find_in_index(node.slice)
98
- end
99
-
100
- private
101
-
102
- sig { params(node: Prism::CallNode).void }
103
- def handle_method_definition(node)
104
- return unless self_receiver?(node)
105
-
106
- message = node.message
107
- return unless message
108
-
109
- target_method = @index.resolve_method(message, @nesting.join("::"))
110
- return unless target_method
111
-
112
- location = target_method.location
113
- file_path = target_method.file_path
114
- return if defined_in_gem?(file_path)
115
-
116
- @_response = Interface::Location.new(
117
- uri: URI::Generic.from_path(path: file_path).to_s,
118
- range: Interface::Range.new(
119
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
120
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
121
- ),
122
- )
123
- end
124
-
125
- sig { params(node: Prism::CallNode).void }
126
- def handle_require_definition(node)
127
- message = node.name
128
- arguments = node.arguments
129
- return unless arguments
130
-
131
- argument = arguments.arguments.first
132
- return unless argument.is_a?(Prism::StringNode)
133
-
134
- case message
135
- when :require
136
- entry = @index.search_require_paths(argument.content).find do |indexable_path|
137
- indexable_path.require_path == argument.content
138
- end
139
-
140
- if entry
141
- candidate = entry.full_path
142
-
143
- @_response = Interface::Location.new(
144
- uri: URI::Generic.from_path(path: candidate).to_s,
145
- range: Interface::Range.new(
146
- start: Interface::Position.new(line: 0, character: 0),
147
- end: Interface::Position.new(line: 0, character: 0),
148
- ),
149
- )
64
+ sig { override.returns(ResponseType) }
65
+ def perform
66
+ @dispatcher.dispatch_once(@target)
67
+ result = []
68
+
69
+ @listeners.each do |listener|
70
+ res = listener.response
71
+ case res
72
+ when Interface::Location
73
+ result << res
74
+ when Array
75
+ result.concat(res)
150
76
  end
151
- when :require_relative
152
- required_file = "#{argument.content}.rb"
153
- path = @uri.to_standardized_path
154
- current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
155
- candidate = File.expand_path(File.join(current_folder, required_file))
156
-
157
- @_response = Interface::Location.new(
158
- uri: URI::Generic.from_path(path: candidate).to_s,
159
- range: Interface::Range.new(
160
- start: Interface::Position.new(line: 0, character: 0),
161
- end: Interface::Position.new(line: 0, character: 0),
162
- ),
163
- )
164
77
  end
165
- end
166
78
 
167
- sig { params(value: String).void }
168
- def find_in_index(value)
169
- entries = @index.resolve(value, @nesting)
170
- return unless entries
171
-
172
- # We should only allow jumping to the definition of private constants if the constant is defined in the same
173
- # namespace as the reference
174
- first_entry = T.must(entries.first)
175
- return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
176
-
177
- @_response = entries.filter_map do |entry|
178
- location = entry.location
179
- # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
180
- # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
181
- # in the project, even if the files are typed false
182
- file_path = entry.file_path
183
- next if defined_in_gem?(file_path)
184
-
185
- Interface::Location.new(
186
- uri: URI::Generic.from_path(path: file_path).to_s,
187
- range: Interface::Range.new(
188
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
189
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
190
- ),
191
- )
192
- end
79
+ result if result.any?
193
80
  end
194
81
  end
195
82
  end
@@ -18,21 +18,33 @@ module RubyLsp
18
18
  # puts "Hello" # --> diagnostics: incorrect indentation
19
19
  # end
20
20
  # ```
21
- class Diagnostics < BaseRequest
21
+ class Diagnostics < Request
22
22
  extend T::Sig
23
23
 
24
+ class << self
25
+ extend T::Sig
26
+
27
+ sig { returns(T::Hash[Symbol, T::Boolean]) }
28
+ def provider
29
+ {
30
+ interFileDependencies: false,
31
+ workspaceDiagnostics: false,
32
+ }
33
+ end
34
+ end
35
+
24
36
  sig { params(document: Document).void }
25
37
  def initialize(document)
26
- super(document)
27
-
38
+ super()
39
+ @document = document
28
40
  @uri = T.let(document.uri, URI::Generic)
29
41
  end
30
42
 
31
43
  sig { override.returns(T.nilable(T.all(T::Array[Interface::Diagnostic], Object))) }
32
- def run
44
+ def perform
33
45
  # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
34
46
  return syntax_error_diagnostics if @document.syntax_error?
35
- return unless defined?(Support::RuboCopDiagnosticsRunner)
47
+ return [] unless defined?(Support::RuboCopDiagnosticsRunner)
36
48
 
37
49
  Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document).map!(&:to_lsp_diagnostic)
38
50
  end