ruby-lsp 0.13.1 → 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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +1 -1
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +30 -1
  6. data/lib/ruby_lsp/check_docs.rb +3 -3
  7. data/lib/ruby_lsp/document.rb +15 -7
  8. data/lib/ruby_lsp/executor.rb +85 -226
  9. data/lib/ruby_lsp/listener.rb +1 -50
  10. data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
  11. data/lib/ruby_lsp/listeners/completion.rb +275 -0
  12. data/lib/ruby_lsp/listeners/definition.rb +158 -0
  13. data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
  14. data/lib/ruby_lsp/listeners/document_link.rb +162 -0
  15. data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
  16. data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
  17. data/lib/ruby_lsp/listeners/hover.rb +152 -0
  18. data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
  19. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
  20. data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
  21. data/lib/ruby_lsp/requests/code_action_resolve.rb +5 -5
  22. data/lib/ruby_lsp/requests/code_actions.rb +15 -6
  23. data/lib/ruby_lsp/requests/code_lens.rb +21 -221
  24. data/lib/ruby_lsp/requests/completion.rb +64 -246
  25. data/lib/ruby_lsp/requests/definition.rb +34 -147
  26. data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
  27. data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
  28. data/lib/ruby_lsp/requests/document_link.rb +11 -132
  29. data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
  30. data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
  31. data/lib/ruby_lsp/requests/formatting.rb +4 -4
  32. data/lib/ruby_lsp/requests/hover.rb +48 -92
  33. data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
  34. data/lib/ruby_lsp/requests/on_type_formatting.rb +18 -6
  35. data/lib/ruby_lsp/requests/request.rb +17 -0
  36. data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
  37. data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
  38. data/lib/ruby_lsp/requests/show_syntax_tree.rb +5 -5
  39. data/lib/ruby_lsp/requests/signature_help.rb +87 -0
  40. data/lib/ruby_lsp/requests/support/common.rb +3 -2
  41. data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
  42. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -1
  43. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  44. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  45. data/lib/ruby_lsp/requests.rb +3 -1
  46. data/lib/ruby_lsp/store.rb +1 -1
  47. data/lib/ruby_lsp/utils.rb +8 -0
  48. metadata +20 -8
  49. 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,271 +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
- parameters = entry.parameters
158
- new_text = parameters.empty? ? name : "#{name}(#{parameters.map(&:name).join(", ")})"
159
-
160
- Interface::CompletionItem.new(
161
- label: name,
162
- filter_text: name,
163
- text_edit: Interface::TextEdit.new(range: range_from_node(node), new_text: new_text),
164
- kind: Constant::CompletionItemKind::METHOD,
165
- label_details: Interface::CompletionItemLabelDetails.new(
166
- description: entry.file_name,
167
- ),
168
- 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],
169
68
  )
170
- end
171
69
 
172
- sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
173
- def build_completion(label, node)
174
- # We should use the content location as we only replace the content and not the delimiters of the string
175
- loc = node.content_loc
176
-
177
- Interface::CompletionItem.new(
178
- label: label,
179
- text_edit: Interface::TextEdit.new(
180
- range: range_from_location(loc),
181
- new_text: label,
182
- ),
183
- kind: Constant::CompletionItemKind::FILE,
70
+ @listener = T.let(
71
+ Listeners::Completion.new(index, nesting, typechecker_enabled, dispatcher),
72
+ Listener[ResponseType],
184
73
  )
185
- end
186
74
 
187
- sig do
188
- params(
189
- real_name: String,
190
- incomplete_name: String,
191
- node: Prism::Node,
192
- entries: T::Array[RubyIndexer::Entry],
193
- top_level: T::Boolean,
194
- ).returns(Interface::CompletionItem)
195
- end
196
- def build_entry_completion(real_name, incomplete_name, node, entries, top_level)
197
- first_entry = T.must(entries.first)
198
- kind = case first_entry
199
- when RubyIndexer::Entry::Class
200
- Constant::CompletionItemKind::CLASS
201
- when RubyIndexer::Entry::Module
202
- Constant::CompletionItemKind::MODULE
203
- when RubyIndexer::Entry::Constant
204
- Constant::CompletionItemKind::CONSTANT
205
- else
206
- Constant::CompletionItemKind::REFERENCE
207
- end
208
-
209
- insertion_text = real_name.dup
210
- filter_text = real_name.dup
211
-
212
- # If we have two entries with the same name inside the current namespace and the user selects the top level
213
- # option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
214
- # If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
215
- # then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
216
- # to complete to `::Bar`.
217
- if top_level
218
- insertion_text.prepend("::")
219
- filter_text.prepend("::")
220
- end
221
-
222
- # If the user is searching for a constant inside the current namespace, then we prefer completing the short name
223
- # of that constant. E.g.:
224
- #
225
- # module Foo
226
- # class Bar
227
- # end
228
- #
229
- # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
230
- # end
231
- @nesting.each do |namespace|
232
- prefix = "#{namespace}::"
233
- shortened_name = insertion_text.delete_prefix(prefix)
234
-
235
- # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
236
- conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
237
- break if real_name != conflict_name && @index[conflict_name]
238
-
239
- insertion_text = shortened_name
240
-
241
- # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
242
- # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
243
- # typing
244
- 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
245
99
  end
246
-
247
- # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
248
- # For these top level references, we need to include the `::` as part of the filter text or else it won't match
249
- # the right entries in the index
250
- Interface::CompletionItem.new(
251
- label: real_name,
252
- filter_text: filter_text,
253
- text_edit: Interface::TextEdit.new(
254
- range: range_from_node(node),
255
- new_text: insertion_text,
256
- ),
257
- kind: kind,
258
- label_details: Interface::CompletionItemLabelDetails.new(
259
- description: entries.map(&:file_name).join(","),
260
- ),
261
- documentation: markdown_from_index_entries(real_name, entries),
262
- )
263
100
  end
264
101
 
265
- # Check if there are any conflicting names for `entry_name`, which would require us to use a top level reference.
266
- # For example:
267
- #
268
- # ```ruby
269
- # class Bar; end
270
- #
271
- # module Foo
272
- # class Bar; end
273
- #
274
- # # in this case, the completion for `Bar` conflicts with `Foo::Bar`, so we can't suggest `Bar` as the
275
- # # completion, but instead need to suggest `::Bar`
276
- # B
277
- # end
278
- # ```
279
- sig { params(entry_name: String).returns(T::Boolean) }
280
- def top_level?(entry_name)
281
- @nesting.length.downto(0).each do |i|
282
- prefix = T.must(@nesting[0...i]).join("::")
283
- full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
284
- next if full_name == entry_name
285
-
286
- return true if @index[full_name]
287
- end
102
+ sig { override.returns(ResponseType) }
103
+ def perform
104
+ return [] unless @target
288
105
 
289
- false
106
+ @dispatcher.dispatch_once(@target)
107
+ @listener.response
290
108
  end
291
109
  end
292
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