ruby-lsp 0.13.2 → 0.13.4

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