ruby-lsp 0.13.2 → 0.13.4

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 +17 -6
  45. data/lib/ruby_lsp/requests/base_request.rb +0 -24
@@ -0,0 +1,152 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class Hover < Listener
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { fixed: T.nilable(Interface::Hover) } }
11
+
12
+ ALLOWED_TARGETS = T.let(
13
+ [
14
+ Prism::CallNode,
15
+ Prism::ConstantReadNode,
16
+ Prism::ConstantWriteNode,
17
+ Prism::ConstantPathNode,
18
+ ],
19
+ T::Array[T.class_of(Prism::Node)],
20
+ )
21
+
22
+ sig { override.returns(ResponseType) }
23
+ attr_reader :_response
24
+
25
+ sig do
26
+ params(
27
+ uri: URI::Generic,
28
+ nesting: T::Array[String],
29
+ index: RubyIndexer::Index,
30
+ dispatcher: Prism::Dispatcher,
31
+ typechecker_enabled: T::Boolean,
32
+ ).void
33
+ end
34
+ def initialize(uri, nesting, index, dispatcher, typechecker_enabled)
35
+ @path = T.let(uri.to_standardized_path, T.nilable(String))
36
+ @nesting = nesting
37
+ @index = index
38
+ @typechecker_enabled = typechecker_enabled
39
+ @_response = T.let(nil, ResponseType)
40
+
41
+ super(dispatcher)
42
+ dispatcher.register(
43
+ self,
44
+ :on_constant_read_node_enter,
45
+ :on_constant_write_node_enter,
46
+ :on_constant_path_node_enter,
47
+ :on_call_node_enter,
48
+ )
49
+ end
50
+
51
+ sig { params(node: Prism::ConstantReadNode).void }
52
+ def on_constant_read_node_enter(node)
53
+ return if @typechecker_enabled
54
+
55
+ generate_hover(node.slice, node.location)
56
+ end
57
+
58
+ sig { params(node: Prism::ConstantWriteNode).void }
59
+ def on_constant_write_node_enter(node)
60
+ return if DependencyDetector.instance.typechecker
61
+
62
+ generate_hover(node.name.to_s, node.name_loc)
63
+ end
64
+
65
+ sig { params(node: Prism::ConstantPathNode).void }
66
+ def on_constant_path_node_enter(node)
67
+ return if DependencyDetector.instance.typechecker
68
+
69
+ generate_hover(node.slice, node.location)
70
+ end
71
+
72
+ sig { params(node: Prism::CallNode).void }
73
+ def on_call_node_enter(node)
74
+ return unless self_receiver?(node)
75
+
76
+ if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem
77
+ generate_gem_hover(node)
78
+ return
79
+ end
80
+
81
+ return if @typechecker_enabled
82
+
83
+ message = node.message
84
+ return unless message
85
+
86
+ target_method = @index.resolve_method(message, @nesting.join("::"))
87
+ return unless target_method
88
+
89
+ location = target_method.location
90
+
91
+ @_response = Interface::Hover.new(
92
+ range: range_from_location(location),
93
+ contents: markdown_from_index_entries(message, target_method),
94
+ )
95
+ end
96
+
97
+ private
98
+
99
+ sig { params(name: String, location: Prism::Location).void }
100
+ def generate_hover(name, location)
101
+ entries = @index.resolve(name, @nesting)
102
+ return unless entries
103
+
104
+ # We should only show hover for private constants if the constant is defined in the same namespace as the
105
+ # reference
106
+ first_entry = T.must(entries.first)
107
+ return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{name}"
108
+
109
+ @_response = Interface::Hover.new(
110
+ range: range_from_location(location),
111
+ contents: markdown_from_index_entries(name, entries),
112
+ )
113
+ end
114
+
115
+ sig { params(node: Prism::CallNode).void }
116
+ def generate_gem_hover(node)
117
+ first_argument = node.arguments&.arguments&.first
118
+ return unless first_argument.is_a?(Prism::StringNode)
119
+
120
+ spec = Gem::Specification.find_by_name(first_argument.content)
121
+ return unless spec
122
+
123
+ info = T.let(
124
+ [
125
+ spec.description,
126
+ spec.summary,
127
+ "This rubygem does not have a description or summary.",
128
+ ].find { |text| !text.nil? && !text.empty? },
129
+ String,
130
+ )
131
+
132
+ # Remove leading whitespace if a heredoc was used for the summary or description
133
+ info = info.gsub(/^ +/, "")
134
+
135
+ markdown = <<~MARKDOWN
136
+ **#{spec.name}** (#{spec.version})
137
+ #{info}
138
+ MARKDOWN
139
+
140
+ @_response = Interface::Hover.new(
141
+ range: range_from_location(node.location),
142
+ contents: Interface::MarkupContent.new(
143
+ kind: Constant::MarkupKind::MARKDOWN,
144
+ value: markdown,
145
+ ),
146
+ )
147
+ rescue Gem::MissingSpecError
148
+ # Do nothing if the spec cannot be found
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,80 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class InlayHints < Listener
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { fixed: T::Array[Interface::InlayHint] } }
11
+
12
+ RESCUE_STRING_LENGTH = T.let("rescue".length, Integer)
13
+
14
+ sig { override.returns(ResponseType) }
15
+ attr_reader :_response
16
+
17
+ sig do
18
+ params(
19
+ range: T::Range[Integer],
20
+ hints_configuration: RequestConfig,
21
+ dispatcher: Prism::Dispatcher,
22
+ ).void
23
+ end
24
+ def initialize(range, hints_configuration, dispatcher)
25
+ super(dispatcher)
26
+
27
+ @_response = T.let([], ResponseType)
28
+ @range = range
29
+ @hints_configuration = hints_configuration
30
+
31
+ dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
32
+ end
33
+
34
+ sig { params(node: Prism::RescueNode).void }
35
+ def on_rescue_node_enter(node)
36
+ return unless @hints_configuration.enabled?(:implicitRescue)
37
+ return unless node.exceptions.empty?
38
+
39
+ loc = node.location
40
+ return unless visible?(node, @range)
41
+
42
+ @_response << Interface::InlayHint.new(
43
+ position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
44
+ label: "StandardError",
45
+ padding_left: true,
46
+ tooltip: "StandardError is implied in a bare rescue",
47
+ )
48
+ end
49
+
50
+ sig { params(node: Prism::ImplicitNode).void }
51
+ def on_implicit_node_enter(node)
52
+ return unless @hints_configuration.enabled?(:implicitHashValue)
53
+ return unless visible?(node, @range)
54
+
55
+ node_value = node.value
56
+ loc = node.location
57
+ tooltip = ""
58
+ node_name = ""
59
+ case node_value
60
+ when Prism::CallNode
61
+ node_name = node_value.name
62
+ tooltip = "This is a method call. Method name: #{node_name}"
63
+ when Prism::ConstantReadNode
64
+ node_name = node_value.name
65
+ tooltip = "This is a constant: #{node_name}"
66
+ when Prism::LocalVariableReadNode
67
+ node_name = node_value.name
68
+ tooltip = "This is a local variable: #{node_name}"
69
+ end
70
+
71
+ @_response << Interface::InlayHint.new(
72
+ position: { line: loc.start_line - 1, character: loc.start_column + node_name.length + 1 },
73
+ label: node_name,
74
+ padding_left: true,
75
+ tooltip: tooltip,
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,430 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class SemanticHighlighting < Listener
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { fixed: T::Array[SemanticToken] } }
11
+
12
+ TOKEN_TYPES = T.let(
13
+ {
14
+ namespace: 0,
15
+ type: 1,
16
+ class: 2,
17
+ enum: 3,
18
+ interface: 4,
19
+ struct: 5,
20
+ typeParameter: 6,
21
+ parameter: 7,
22
+ variable: 8,
23
+ property: 9,
24
+ enumMember: 10,
25
+ event: 11,
26
+ function: 12,
27
+ method: 13,
28
+ macro: 14,
29
+ keyword: 15,
30
+ modifier: 16,
31
+ comment: 17,
32
+ string: 18,
33
+ number: 19,
34
+ regexp: 20,
35
+ operator: 21,
36
+ decorator: 22,
37
+ }.freeze,
38
+ T::Hash[Symbol, Integer],
39
+ )
40
+
41
+ TOKEN_MODIFIERS = T.let(
42
+ {
43
+ declaration: 0,
44
+ definition: 1,
45
+ readonly: 2,
46
+ static: 3,
47
+ deprecated: 4,
48
+ abstract: 5,
49
+ async: 6,
50
+ modification: 7,
51
+ documentation: 8,
52
+ default_library: 9,
53
+ }.freeze,
54
+ T::Hash[Symbol, Integer],
55
+ )
56
+
57
+ SPECIAL_RUBY_METHODS = T.let(
58
+ [
59
+ Module.instance_methods(false),
60
+ Kernel.instance_methods(false),
61
+ Kernel.methods(false),
62
+ Bundler::Dsl.instance_methods(false),
63
+ Module.private_instance_methods(false),
64
+ ].flatten.map(&:to_s),
65
+ T::Array[String],
66
+ )
67
+
68
+ class SemanticToken
69
+ extend T::Sig
70
+
71
+ sig { returns(Prism::Location) }
72
+ attr_reader :location
73
+
74
+ sig { returns(Integer) }
75
+ attr_reader :length
76
+
77
+ sig { returns(Integer) }
78
+ attr_reader :type
79
+
80
+ sig { returns(T::Array[Integer]) }
81
+ attr_reader :modifier
82
+
83
+ sig { params(location: Prism::Location, length: Integer, type: Integer, modifier: T::Array[Integer]).void }
84
+ def initialize(location:, length:, type:, modifier:)
85
+ @location = location
86
+ @length = length
87
+ @type = type
88
+ @modifier = modifier
89
+ end
90
+ end
91
+
92
+ sig { override.returns(ResponseType) }
93
+ attr_reader :_response
94
+
95
+ sig { params(dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
96
+ def initialize(dispatcher, range: nil)
97
+ super(dispatcher)
98
+
99
+ @_response = T.let([], ResponseType)
100
+ @range = range
101
+ @special_methods = T.let(nil, T.nilable(T::Array[String]))
102
+ @current_scope = T.let(ParameterScope.new, ParameterScope)
103
+ @inside_regex_capture = T.let(false, T::Boolean)
104
+
105
+ dispatcher.register(
106
+ self,
107
+ :on_call_node_enter,
108
+ :on_class_node_enter,
109
+ :on_def_node_enter,
110
+ :on_def_node_leave,
111
+ :on_block_node_enter,
112
+ :on_block_node_leave,
113
+ :on_self_node_enter,
114
+ :on_module_node_enter,
115
+ :on_local_variable_write_node_enter,
116
+ :on_local_variable_read_node_enter,
117
+ :on_block_parameter_node_enter,
118
+ :on_required_keyword_parameter_node_enter,
119
+ :on_optional_keyword_parameter_node_enter,
120
+ :on_keyword_rest_parameter_node_enter,
121
+ :on_optional_parameter_node_enter,
122
+ :on_required_parameter_node_enter,
123
+ :on_rest_parameter_node_enter,
124
+ :on_constant_read_node_enter,
125
+ :on_constant_write_node_enter,
126
+ :on_constant_and_write_node_enter,
127
+ :on_constant_operator_write_node_enter,
128
+ :on_constant_or_write_node_enter,
129
+ :on_constant_target_node_enter,
130
+ :on_local_variable_and_write_node_enter,
131
+ :on_local_variable_operator_write_node_enter,
132
+ :on_local_variable_or_write_node_enter,
133
+ :on_local_variable_target_node_enter,
134
+ :on_block_local_variable_node_enter,
135
+ :on_match_write_node_enter,
136
+ :on_match_write_node_leave,
137
+ )
138
+ end
139
+
140
+ sig { params(node: Prism::CallNode).void }
141
+ def on_call_node_enter(node)
142
+ return unless visible?(node, @range)
143
+
144
+ message = node.message
145
+ return unless message
146
+
147
+ # We can't push a semantic token for [] and []= because the argument inside the brackets is a part of
148
+ # the message_loc
149
+ return if message.start_with?("[") && (message.end_with?("]") || message.end_with?("]="))
150
+ return if message == "=~"
151
+ return if special_method?(message)
152
+
153
+ type = Requests::Support::Sorbet.annotation?(node) ? :type : :method
154
+ add_token(T.must(node.message_loc), type)
155
+ end
156
+
157
+ sig { params(node: Prism::MatchWriteNode).void }
158
+ def on_match_write_node_enter(node)
159
+ call = node.call
160
+
161
+ if call.message == "=~"
162
+ @inside_regex_capture = true
163
+ process_regexp_locals(call)
164
+ end
165
+ end
166
+
167
+ sig { params(node: Prism::MatchWriteNode).void }
168
+ def on_match_write_node_leave(node)
169
+ @inside_regex_capture = true if node.call.message == "=~"
170
+ end
171
+
172
+ sig { params(node: Prism::ConstantReadNode).void }
173
+ def on_constant_read_node_enter(node)
174
+ return unless visible?(node, @range)
175
+ # When finding a module or class definition, we will have already pushed a token related to this constant. We
176
+ # need to look at the previous two tokens and if they match this locatione exactly, avoid pushing another token
177
+ # on top of the previous one
178
+ return if @_response.last(2).any? { |token| token.location == node.location }
179
+
180
+ add_token(node.location, :namespace)
181
+ end
182
+
183
+ sig { params(node: Prism::ConstantWriteNode).void }
184
+ def on_constant_write_node_enter(node)
185
+ return unless visible?(node, @range)
186
+
187
+ add_token(node.name_loc, :namespace)
188
+ end
189
+
190
+ sig { params(node: Prism::ConstantAndWriteNode).void }
191
+ def on_constant_and_write_node_enter(node)
192
+ return unless visible?(node, @range)
193
+
194
+ add_token(node.name_loc, :namespace)
195
+ end
196
+
197
+ sig { params(node: Prism::ConstantOperatorWriteNode).void }
198
+ def on_constant_operator_write_node_enter(node)
199
+ return unless visible?(node, @range)
200
+
201
+ add_token(node.name_loc, :namespace)
202
+ end
203
+
204
+ sig { params(node: Prism::ConstantOrWriteNode).void }
205
+ def on_constant_or_write_node_enter(node)
206
+ return unless visible?(node, @range)
207
+
208
+ add_token(node.name_loc, :namespace)
209
+ end
210
+
211
+ sig { params(node: Prism::ConstantTargetNode).void }
212
+ def on_constant_target_node_enter(node)
213
+ return unless visible?(node, @range)
214
+
215
+ add_token(node.location, :namespace)
216
+ end
217
+
218
+ sig { params(node: Prism::DefNode).void }
219
+ def on_def_node_enter(node)
220
+ @current_scope = ParameterScope.new(@current_scope)
221
+ return unless visible?(node, @range)
222
+
223
+ add_token(node.name_loc, :method, [:declaration])
224
+ end
225
+
226
+ sig { params(node: Prism::DefNode).void }
227
+ def on_def_node_leave(node)
228
+ @current_scope = T.must(@current_scope.parent)
229
+ end
230
+
231
+ sig { params(node: Prism::BlockNode).void }
232
+ def on_block_node_enter(node)
233
+ @current_scope = ParameterScope.new(@current_scope)
234
+ end
235
+
236
+ sig { params(node: Prism::BlockNode).void }
237
+ def on_block_node_leave(node)
238
+ @current_scope = T.must(@current_scope.parent)
239
+ end
240
+
241
+ sig { params(node: Prism::BlockLocalVariableNode).void }
242
+ def on_block_local_variable_node_enter(node)
243
+ add_token(node.location, :variable)
244
+ end
245
+
246
+ sig { params(node: Prism::BlockParameterNode).void }
247
+ def on_block_parameter_node_enter(node)
248
+ name = node.name
249
+ @current_scope << name.to_sym if name
250
+ end
251
+
252
+ sig { params(node: Prism::RequiredKeywordParameterNode).void }
253
+ def on_required_keyword_parameter_node_enter(node)
254
+ @current_scope << node.name
255
+ return unless visible?(node, @range)
256
+
257
+ location = node.name_loc
258
+ add_token(location.copy(length: location.length - 1), :parameter)
259
+ end
260
+
261
+ sig { params(node: Prism::OptionalKeywordParameterNode).void }
262
+ def on_optional_keyword_parameter_node_enter(node)
263
+ @current_scope << node.name
264
+ return unless visible?(node, @range)
265
+
266
+ location = node.name_loc
267
+ add_token(location.copy(length: location.length - 1), :parameter)
268
+ end
269
+
270
+ sig { params(node: Prism::KeywordRestParameterNode).void }
271
+ def on_keyword_rest_parameter_node_enter(node)
272
+ name = node.name
273
+
274
+ if name
275
+ @current_scope << name.to_sym
276
+
277
+ add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
278
+ end
279
+ end
280
+
281
+ sig { params(node: Prism::OptionalParameterNode).void }
282
+ def on_optional_parameter_node_enter(node)
283
+ @current_scope << node.name
284
+ return unless visible?(node, @range)
285
+
286
+ add_token(node.name_loc, :parameter)
287
+ end
288
+
289
+ sig { params(node: Prism::RequiredParameterNode).void }
290
+ def on_required_parameter_node_enter(node)
291
+ @current_scope << node.name
292
+ return unless visible?(node, @range)
293
+
294
+ add_token(node.location, :parameter)
295
+ end
296
+
297
+ sig { params(node: Prism::RestParameterNode).void }
298
+ def on_rest_parameter_node_enter(node)
299
+ name = node.name
300
+
301
+ if name
302
+ @current_scope << name.to_sym
303
+
304
+ add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
305
+ end
306
+ end
307
+
308
+ sig { params(node: Prism::SelfNode).void }
309
+ def on_self_node_enter(node)
310
+ return unless visible?(node, @range)
311
+
312
+ add_token(node.location, :variable, [:default_library])
313
+ end
314
+
315
+ sig { params(node: Prism::LocalVariableWriteNode).void }
316
+ def on_local_variable_write_node_enter(node)
317
+ return unless visible?(node, @range)
318
+
319
+ add_token(node.name_loc, @current_scope.type_for(node.name))
320
+ end
321
+
322
+ sig { params(node: Prism::LocalVariableReadNode).void }
323
+ def on_local_variable_read_node_enter(node)
324
+ return unless visible?(node, @range)
325
+
326
+ # Numbered parameters
327
+ if /_\d+/.match?(node.name)
328
+ add_token(node.location, :parameter)
329
+ return
330
+ end
331
+
332
+ add_token(node.location, @current_scope.type_for(node.name))
333
+ end
334
+
335
+ sig { params(node: Prism::LocalVariableAndWriteNode).void }
336
+ def on_local_variable_and_write_node_enter(node)
337
+ return unless visible?(node, @range)
338
+
339
+ add_token(node.name_loc, @current_scope.type_for(node.name))
340
+ end
341
+
342
+ sig { params(node: Prism::LocalVariableOperatorWriteNode).void }
343
+ def on_local_variable_operator_write_node_enter(node)
344
+ return unless visible?(node, @range)
345
+
346
+ add_token(node.name_loc, @current_scope.type_for(node.name))
347
+ end
348
+
349
+ sig { params(node: Prism::LocalVariableOrWriteNode).void }
350
+ def on_local_variable_or_write_node_enter(node)
351
+ return unless visible?(node, @range)
352
+
353
+ add_token(node.name_loc, @current_scope.type_for(node.name))
354
+ end
355
+
356
+ sig { params(node: Prism::LocalVariableTargetNode).void }
357
+ def on_local_variable_target_node_enter(node)
358
+ # If we're inside a regex capture, Prism will add LocalVariableTarget nodes for each captured variable.
359
+ # Unfortunately, if the regex contains a backslash, the location will be incorrect and we'll end up highlighting
360
+ # the entire regex as a local variable. We process these captures in process_regexp_locals instead and then
361
+ # prevent pushing local variable target tokens. See https://github.com/ruby/prism/issues/1912
362
+ return if @inside_regex_capture
363
+
364
+ return unless visible?(node, @range)
365
+
366
+ add_token(node.location, @current_scope.type_for(node.name))
367
+ end
368
+
369
+ sig { params(node: Prism::ClassNode).void }
370
+ def on_class_node_enter(node)
371
+ return unless visible?(node, @range)
372
+
373
+ add_token(node.constant_path.location, :class, [:declaration])
374
+
375
+ superclass = node.superclass
376
+ add_token(superclass.location, :class) if superclass
377
+ end
378
+
379
+ sig { params(node: Prism::ModuleNode).void }
380
+ def on_module_node_enter(node)
381
+ return unless visible?(node, @range)
382
+
383
+ add_token(node.constant_path.location, :namespace, [:declaration])
384
+ end
385
+
386
+ private
387
+
388
+ sig { params(location: Prism::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
389
+ def add_token(location, type, modifiers = [])
390
+ length = location.end_offset - location.start_offset
391
+ modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
392
+ @_response.push(
393
+ SemanticToken.new(
394
+ location: location,
395
+ length: length,
396
+ type: T.must(TOKEN_TYPES[type]),
397
+ modifier: modifiers_indices,
398
+ ),
399
+ )
400
+ end
401
+
402
+ # Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that
403
+ # highlighting, so we avoid making a semantic token for it.
404
+ sig { params(method_name: String).returns(T::Boolean) }
405
+ def special_method?(method_name)
406
+ SPECIAL_RUBY_METHODS.include?(method_name)
407
+ end
408
+
409
+ sig { params(node: Prism::CallNode).void }
410
+ def process_regexp_locals(node)
411
+ receiver = node.receiver
412
+
413
+ # The regexp needs to be the receiver of =~ for local variable capture
414
+ return unless receiver.is_a?(Prism::RegularExpressionNode)
415
+
416
+ content = receiver.content
417
+ loc = receiver.content_loc
418
+
419
+ # For each capture name we find in the regexp, look for a local in the current_scope
420
+ Regexp.new(content, Regexp::FIXEDENCODING).names.each do |name|
421
+ # The +3 is to compensate for the "(?<" part of the capture name
422
+ capture_name_offset = T.must(content.index("(?<#{name}>")) + 3
423
+ local_var_loc = loc.copy(start_offset: loc.start_offset + capture_name_offset, length: name.length)
424
+
425
+ add_token(local_var_loc, @current_scope.type_for(name))
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end