Class: RubyLsp::Listeners::SemanticHighlighting

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Includes:
Requests::Support::Common
Defined in:
lib/ruby_lsp/listeners/semantic_highlighting.rb

Constant Summary collapse

SPECIAL_RUBY_METHODS =
T.let(
  [
    Module.instance_methods(false),
    Kernel.instance_methods(false),
    Kernel.methods(false),
    Bundler::Dsl.instance_methods(false),
    Module.private_instance_methods(false),
  ].flatten.map(&:to_s),
  T::Array[String],
)

Instance Method Summary collapse

Methods included from Requests::Support::Common

#categorized_markdown_from_index_entries, #constant_name, #create_code_lens, #each_constant_path_part, #kind_for_entry, #markdown_from_index_entries, #namespace_constant_name, #not_in_dependencies?, #range_from_location, #range_from_node, #self_receiver?, #sorbet_level_true_or_higher?, #visible?

Constructor Details

#initialize(dispatcher, response_builder, range: nil) ⇒ SemanticHighlighting

Returns a new instance of SemanticHighlighting.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 28

def initialize(dispatcher, response_builder, range: nil)
  @response_builder = response_builder
  @range = range
  @special_methods = T.let(nil, T.nilable(T::Array[String]))
  @current_scope = T.let(ParameterScope.new, ParameterScope)
  @inside_regex_capture = T.let(false, T::Boolean)
  @inside_implicit_node = T.let(false, T::Boolean)

  dispatcher.register(
    self,
    :on_call_node_enter,
    :on_class_node_enter,
    :on_def_node_enter,
    :on_def_node_leave,
    :on_block_node_enter,
    :on_block_node_leave,
    :on_self_node_enter,
    :on_module_node_enter,
    :on_local_variable_write_node_enter,
    :on_local_variable_read_node_enter,
    :on_block_parameter_node_enter,
    :on_required_keyword_parameter_node_enter,
    :on_optional_keyword_parameter_node_enter,
    :on_keyword_rest_parameter_node_enter,
    :on_optional_parameter_node_enter,
    :on_required_parameter_node_enter,
    :on_rest_parameter_node_enter,
    :on_constant_read_node_enter,
    :on_constant_write_node_enter,
    :on_constant_and_write_node_enter,
    :on_constant_operator_write_node_enter,
    :on_constant_or_write_node_enter,
    :on_constant_target_node_enter,
    :on_constant_path_node_enter,
    :on_local_variable_and_write_node_enter,
    :on_local_variable_operator_write_node_enter,
    :on_local_variable_or_write_node_enter,
    :on_local_variable_target_node_enter,
    :on_block_local_variable_node_enter,
    :on_match_write_node_enter,
    :on_match_write_node_leave,
    :on_implicit_node_enter,
    :on_implicit_node_leave,
  )
end

Instance Method Details

#on_block_local_variable_node_enter(node) ⇒ Object



174
175
176
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 174

def on_block_local_variable_node_enter(node)
  @response_builder.add_token(node.location, :variable)
end

#on_block_node_enter(node) ⇒ Object



164
165
166
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 164

def on_block_node_enter(node)
  @current_scope = ParameterScope.new(@current_scope)
end

#on_block_node_leave(node) ⇒ Object



169
170
171
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 169

def on_block_node_leave(node)
  @current_scope = T.must(@current_scope.parent)
end

#on_block_parameter_node_enter(node) ⇒ Object



179
180
181
182
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 179

def on_block_parameter_node_enter(node)
  name = node.name
  @current_scope << name.to_sym if name
end

#on_call_node_enter(node) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 75

def on_call_node_enter(node)
  return if @inside_implicit_node
  return unless visible?(node, @range)

  message = node.message
  return unless message

  # We can't push a semantic token for [] and []= because the argument inside the brackets is a part of
  # the message_loc
  return if message.start_with?("[") && (message.end_with?("]") || message.end_with?("]="))
  return if message == "=~"
  return if special_method?(message)

  type = Requests::Support::Sorbet.annotation?(node) ? :type : :method
  @response_builder.add_token(T.must(node.message_loc), type)
end

#on_class_node_enter(node) ⇒ Object



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 303

def on_class_node_enter(node)
  return unless visible?(node, @range)

  constant_path = node.constant_path

  if constant_path.is_a?(Prism::ConstantReadNode)
    @response_builder.add_token(constant_path.location, :class, [:declaration])
  else
    each_constant_path_part(constant_path) do |part|
      loc = case part
      when Prism::ConstantPathNode
        part.name_loc
      when Prism::ConstantReadNode
        part.location
      end
      next unless loc

      @response_builder.add_token(loc, :class, [:declaration])
    end
  end

  superclass = node.superclass

  if superclass.is_a?(Prism::ConstantReadNode)
    @response_builder.add_token(superclass.location, :class)
  elsif superclass
    each_constant_path_part(superclass) do |part|
      loc = case part
      when Prism::ConstantPathNode
        part.name_loc
      when Prism::ConstantReadNode
        part.location
      end
      next unless loc

      @response_builder.add_token(loc, :class)
    end
  end
end

#on_constant_and_write_node_enter(node) ⇒ Object



123
124
125
126
127
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 123

def on_constant_and_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, :namespace)
end

#on_constant_operator_write_node_enter(node) ⇒ Object



130
131
132
133
134
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 130

def on_constant_operator_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, :namespace)
end

#on_constant_or_write_node_enter(node) ⇒ Object



137
138
139
140
141
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 137

def on_constant_or_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, :namespace)
end

#on_constant_path_node_enter(node) ⇒ Object



379
380
381
382
383
384
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 379

def on_constant_path_node_enter(node)
  return if @inside_implicit_node
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, :namespace)
end

#on_constant_read_node_enter(node) ⇒ Object



108
109
110
111
112
113
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 108

def on_constant_read_node_enter(node)
  return if @inside_implicit_node
  return unless visible?(node, @range)

  @response_builder.add_token(node.location, :namespace)
end

#on_constant_target_node_enter(node) ⇒ Object



144
145
146
147
148
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 144

def on_constant_target_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.location, :namespace)
end

#on_constant_write_node_enter(node) ⇒ Object



116
117
118
119
120
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 116

def on_constant_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, :namespace)
end

#on_def_node_enter(node) ⇒ Object



151
152
153
154
155
156
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 151

def on_def_node_enter(node)
  @current_scope = ParameterScope.new(@current_scope)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, :method, [:declaration])
end

#on_def_node_leave(node) ⇒ Object



159
160
161
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 159

def on_def_node_leave(node)
  @current_scope = T.must(@current_scope.parent)
end

#on_implicit_node_enter(node) ⇒ Object



367
368
369
370
371
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 367

def on_implicit_node_enter(node)
  return unless visible?(node, @range)

  @inside_implicit_node = true
end

#on_implicit_node_leave(node) ⇒ Object



374
375
376
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 374

def on_implicit_node_leave(node)
  @inside_implicit_node = false
end

#on_keyword_rest_parameter_node_enter(node) ⇒ Object



203
204
205
206
207
208
209
210
211
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 203

def on_keyword_rest_parameter_node_enter(node)
  name = node.name

  if name
    @current_scope << name.to_sym

    @response_builder.add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
  end
end

#on_local_variable_and_write_node_enter(node) ⇒ Object



269
270
271
272
273
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 269

def on_local_variable_and_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_local_variable_operator_write_node_enter(node) ⇒ Object



276
277
278
279
280
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 276

def on_local_variable_operator_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_local_variable_or_write_node_enter(node) ⇒ Object



283
284
285
286
287
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 283

def on_local_variable_or_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_local_variable_read_node_enter(node) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 255

def on_local_variable_read_node_enter(node)
  return if @inside_implicit_node
  return unless visible?(node, @range)

  # Numbered parameters
  if /_\d+/.match?(node.name)
    @response_builder.add_token(node.location, :parameter)
    return
  end

  @response_builder.add_token(node.location, @current_scope.type_for(node.name))
end

#on_local_variable_target_node_enter(node) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 290

def on_local_variable_target_node_enter(node)
  # If we're inside a regex capture, Prism will add LocalVariableTarget nodes for each captured variable.
  # Unfortunately, if the regex contains a backslash, the location will be incorrect and we'll end up highlighting
  # the entire regex as a local variable. We process these captures in process_regexp_locals instead and then
  # prevent pushing local variable target tokens. See https://github.com/ruby/prism/issues/1912
  return if @inside_regex_capture

  return unless visible?(node, @range)

  @response_builder.add_token(node.location, @current_scope.type_for(node.name))
end

#on_local_variable_write_node_enter(node) ⇒ Object



248
249
250
251
252
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 248

def on_local_variable_write_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_match_write_node_enter(node) ⇒ Object



93
94
95
96
97
98
99
100
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 93

def on_match_write_node_enter(node)
  call = node.call

  if call.message == "=~"
    @inside_regex_capture = true
    process_regexp_locals(call)
  end
end

#on_match_write_node_leave(node) ⇒ Object



103
104
105
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 103

def on_match_write_node_leave(node)
  @inside_regex_capture = true if node.call.message == "=~"
end

#on_module_node_enter(node) ⇒ Object



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 344

def on_module_node_enter(node)
  return unless visible?(node, @range)

  constant_path = node.constant_path

  if constant_path.is_a?(Prism::ConstantReadNode)
    @response_builder.add_token(constant_path.location, :namespace, [:declaration])
  else
    each_constant_path_part(constant_path) do |part|
      loc = case part
      when Prism::ConstantPathNode
        part.name_loc
      when Prism::ConstantReadNode
        part.location
      end
      next unless loc

      @response_builder.add_token(loc, :namespace, [:declaration])
    end
  end
end

#on_optional_keyword_parameter_node_enter(node) ⇒ Object



194
195
196
197
198
199
200
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 194

def on_optional_keyword_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  location = node.name_loc
  @response_builder.add_token(location.copy(length: location.length - 1), :parameter)
end

#on_optional_parameter_node_enter(node) ⇒ Object



214
215
216
217
218
219
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 214

def on_optional_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  @response_builder.add_token(node.name_loc, :parameter)
end

#on_required_keyword_parameter_node_enter(node) ⇒ Object



185
186
187
188
189
190
191
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 185

def on_required_keyword_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  location = node.name_loc
  @response_builder.add_token(location.copy(length: location.length - 1), :parameter)
end

#on_required_parameter_node_enter(node) ⇒ Object



222
223
224
225
226
227
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 222

def on_required_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  @response_builder.add_token(node.location, :parameter)
end

#on_rest_parameter_node_enter(node) ⇒ Object



230
231
232
233
234
235
236
237
238
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 230

def on_rest_parameter_node_enter(node)
  name = node.name

  if name
    @current_scope << name.to_sym

    @response_builder.add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
  end
end

#on_self_node_enter(node) ⇒ Object



241
242
243
244
245
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 241

def on_self_node_enter(node)
  return unless visible?(node, @range)

  @response_builder.add_token(node.location, :variable, [:default_library])
end