Class: RubyLsp::Listeners::SemanticHighlighting

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

Constant Summary collapse

SPECIAL_RUBY_METHODS =
[
  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).freeze

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?

Constructor Details

#initialize(dispatcher, response_builder) ⇒ SemanticHighlighting

: (Prism::Dispatcher dispatcher, ResponseBuilders::SemanticHighlighting response_builder) -> void



18
19
20
21
22
23
24
25
26
27
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
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 18

def initialize(dispatcher, response_builder)
  @response_builder = response_builder
  @special_methods = nil #: Array[String]?
  @current_scope = Scope.new #: Scope
  @inside_regex_capture = false #: bool
  @inside_implicit_node = false #: bool

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

: (Prism::BlockLocalVariableNode node) -> void



121
122
123
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 121

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

#on_block_node_enter(node) ⇒ Object

: (Prism::BlockNode node) -> void



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

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

#on_block_node_leave(node) ⇒ Object

: (Prism::BlockNode node) -> void



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

def on_block_node_leave(node)
  @current_scope = @current_scope.parent #: as !nil
end

#on_block_parameter_node_enter(node) ⇒ Object

: (Prism::BlockParameterNode node) -> void



126
127
128
129
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 126

def on_block_parameter_node_enter(node)
  name = node.name
  @current_scope.add(name.to_sym, :parameter) if name
end

#on_call_node_enter(node) ⇒ Object

: (Prism::CallNode node) -> void



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 57

def on_call_node_enter(node)
  return if @inside_implicit_node

  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)

  if Requests::Support::Sorbet.annotation?(node)
    @response_builder.add_token(
      node.message_loc, #: as !nil
      :type,
    )
  elsif !node.receiver && !node.opening_loc
    # If the node has a receiver, then the syntax is not ambiguous and semantic highlighting is not necessary to
    # determine that the token is a method call. The only ambiguous case is method calls with implicit self
    # receiver and no parenthesis, which may be confused with local variables
    @response_builder.add_token(
      node.message_loc, #: as !nil
      :method,
    )
  end
end

#on_class_node_enter(node) ⇒ Object

: (Prism::ClassNode node) -> void



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 219

def on_class_node_enter(node)
  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_def_node_enter(node) ⇒ Object

: (Prism::DefNode node) -> void



101
102
103
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 101

def on_def_node_enter(node)
  @current_scope = Scope.new(@current_scope)
end

#on_def_node_leave(node) ⇒ Object

: (Prism::DefNode node) -> void



106
107
108
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 106

def on_def_node_leave(node)
  @current_scope = @current_scope.parent #: as !nil
end

#on_implicit_node_enter(node) ⇒ Object

: (Prism::ImplicitNode node) -> void



279
280
281
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 279

def on_implicit_node_enter(node)
  @inside_implicit_node = true
end

#on_implicit_node_leave(node) ⇒ Object

: (Prism::ImplicitNode node) -> void



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

def on_implicit_node_leave(node)
  @inside_implicit_node = false
end

#on_keyword_rest_parameter_node_enter(node) ⇒ Object

: (Prism::KeywordRestParameterNode node) -> void



142
143
144
145
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 142

def on_keyword_rest_parameter_node_enter(node)
  name = node.name
  @current_scope.add(name.to_sym, :parameter) if name
end

#on_local_variable_and_write_node_enter(node) ⇒ Object

: (Prism::LocalVariableAndWriteNode node) -> void



189
190
191
192
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 189

def on_local_variable_and_write_node_enter(node)
  local = @current_scope.lookup(node.name)
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
end

#on_local_variable_operator_write_node_enter(node) ⇒ Object

: (Prism::LocalVariableOperatorWriteNode node) -> void



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

def on_local_variable_operator_write_node_enter(node)
  local = @current_scope.lookup(node.name)
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
end

#on_local_variable_or_write_node_enter(node) ⇒ Object

: (Prism::LocalVariableOrWriteNode node) -> void



201
202
203
204
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 201

def on_local_variable_or_write_node_enter(node)
  local = @current_scope.lookup(node.name)
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
end

#on_local_variable_read_node_enter(node) ⇒ Object

: (Prism::LocalVariableReadNode node) -> void



175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 175

def on_local_variable_read_node_enter(node)
  return if @inside_implicit_node

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

  local = @current_scope.lookup(node.name)
  @response_builder.add_token(node.location, local&.type || :variable)
end

#on_local_variable_target_node_enter(node) ⇒ Object

: (Prism::LocalVariableTargetNode node) -> void



207
208
209
210
211
212
213
214
215
216
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 207

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

  local = @current_scope.lookup(node.name)
  @response_builder.add_token(node.location, local&.type || :variable)
end

#on_local_variable_write_node_enter(node) ⇒ Object

: (Prism::LocalVariableWriteNode node) -> void



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

def on_local_variable_write_node_enter(node)
  local = @current_scope.lookup(node.name)
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
end

#on_match_write_node_enter(node) ⇒ Object

: (Prism::MatchWriteNode node) -> void



86
87
88
89
90
91
92
93
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 86

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

: (Prism::MatchWriteNode node) -> void



96
97
98
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 96

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

#on_module_node_enter(node) ⇒ Object

: (Prism::ModuleNode node) -> void



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 258

def on_module_node_enter(node)
  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

: (Prism::OptionalKeywordParameterNode node) -> void



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

def on_optional_keyword_parameter_node_enter(node)
  @current_scope.add(node.name, :parameter)
end

#on_optional_parameter_node_enter(node) ⇒ Object

: (Prism::OptionalParameterNode node) -> void



148
149
150
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 148

def on_optional_parameter_node_enter(node)
  @current_scope.add(node.name, :parameter)
end

#on_required_keyword_parameter_node_enter(node) ⇒ Object

: (Prism::RequiredKeywordParameterNode node) -> void



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

def on_required_keyword_parameter_node_enter(node)
  @current_scope.add(node.name, :parameter)
end

#on_required_parameter_node_enter(node) ⇒ Object

: (Prism::RequiredParameterNode node) -> void



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

def on_required_parameter_node_enter(node)
  @current_scope.add(node.name, :parameter)
end

#on_rest_parameter_node_enter(node) ⇒ Object

: (Prism::RestParameterNode node) -> void



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

def on_rest_parameter_node_enter(node)
  name = node.name
  @current_scope.add(name.to_sym, :parameter) if name
end

#on_self_node_enter(node) ⇒ Object

: (Prism::SelfNode node) -> void



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

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