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
@@ -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