Class: RubyIndexer::DeclarationListener

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb

Constant Summary collapse

OBJECT_NESTING =
T.let(["Object"].freeze, T::Array[String])
BASIC_OBJECT_NESTING =
T.let(["BasicObject"].freeze, T::Array[String])

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(index, dispatcher, parse_result, file_path, enhancements: []) ⇒ DeclarationListener

Returns a new instance of DeclarationListener.



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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 23

def initialize(index, dispatcher, parse_result, file_path, enhancements: [])
  @index = index
  @file_path = file_path
  @enhancements = enhancements
  @visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
   = T.let(
    parse_result.comments.to_h do |c|
      [c.location.start_line, c]
    end,
    T::Hash[Integer, Prism::Comment],
  )
  @inside_def = T.let(false, T::Boolean)

  # The nesting stack we're currently inside. Used to determine the fully qualified name of constants, but only
  # stored by unresolved aliases which need the original nesting to be lazily resolved
  @stack = T.let([], T::Array[String])

  # A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
  @owner_stack = T.let([], T::Array[Entry::Namespace])
  @indexing_errors = T.let([], T::Array[String])

  dispatcher.register(
    self,
    :on_class_node_enter,
    :on_class_node_leave,
    :on_module_node_enter,
    :on_module_node_leave,
    :on_singleton_class_node_enter,
    :on_singleton_class_node_leave,
    :on_def_node_enter,
    :on_def_node_leave,
    :on_call_node_enter,
    :on_call_node_leave,
    :on_multi_write_node_enter,
    :on_constant_path_write_node_enter,
    :on_constant_path_or_write_node_enter,
    :on_constant_path_operator_write_node_enter,
    :on_constant_path_and_write_node_enter,
    :on_constant_or_write_node_enter,
    :on_constant_write_node_enter,
    :on_constant_or_write_node_enter,
    :on_constant_and_write_node_enter,
    :on_constant_operator_write_node_enter,
    :on_instance_variable_write_node_enter,
    :on_instance_variable_and_write_node_enter,
    :on_instance_variable_operator_write_node_enter,
    :on_instance_variable_or_write_node_enter,
    :on_instance_variable_target_node_enter,
    :on_alias_method_node_enter,
  )
end

Instance Attribute Details

#indexing_errorsObject (readonly)

Returns the value of attribute indexing_errors.



12
13
14
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 12

def indexing_errors
  @indexing_errors
end

Instance Method Details

#on_alias_method_node_enter(node) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 393

def on_alias_method_node_enter(node)
  method_name = node.new_name.slice
  comments = collect_comments(node)
  @index.add(
    Entry::UnresolvedMethodAlias.new(
      method_name,
      node.old_name.slice,
      @owner_stack.last,
      @file_path,
      node.new_name.location,
      comments,
    ),
  )
end

#on_call_node_enter(node) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 272

def on_call_node_enter(node)
  message = node.name

  case message
  when :private_constant
    handle_private_constant(node)
  when :attr_reader
    handle_attribute(node, reader: true, writer: false)
  when :attr_writer
    handle_attribute(node, reader: false, writer: true)
  when :attr_accessor
    handle_attribute(node, reader: true, writer: true)
  when :alias_method
    handle_alias_method(node)
  when :include, :prepend, :extend
    handle_module_operation(node, message)
  when :public
    @visibility_stack.push(Entry::Visibility::PUBLIC)
  when :protected
    @visibility_stack.push(Entry::Visibility::PROTECTED)
  when :private
    @visibility_stack.push(Entry::Visibility::PRIVATE)
  end

  @enhancements.each do |enhancement|
    enhancement.on_call_node(@index, @owner_stack.last, node, @file_path)
  rescue StandardError => e
    @indexing_errors << "Indexing error in #{@file_path} with '#{enhancement.class.name}' enhancement: #{e.message}"
  end
end

#on_call_node_leave(node) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 304

def on_call_node_leave(node)
  message = node.name
  case message
  when :public, :protected, :private
    # We want to restore the visibility stack when we leave a method definition with a visibility modifier
    # e.g. `private def foo; end`
    if node.arguments&.arguments&.first&.is_a?(Prism::DefNode)
      @visibility_stack.pop
    end
  end
end

#on_class_node_enter(node) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 76

def on_class_node_enter(node)
  @visibility_stack.push(Entry::Visibility::PUBLIC)
  constant_path = node.constant_path
  name = constant_path.slice

  comments = collect_comments(node)

  superclass = node.superclass

  nesting = actual_nesting(name)

  parent_class = case superclass
  when Prism::ConstantReadNode, Prism::ConstantPathNode
    superclass.slice
  else
    case nesting
    when OBJECT_NESTING
      # When Object is reopened, its parent class should still be the top-level BasicObject
      "::BasicObject"
    when BASIC_OBJECT_NESTING
      # When BasicObject is reopened, its parent class should still be nil
      nil
    else
      # Otherwise, the parent class should be the top-level Object
      "::Object"
    end
  end

  entry = Entry::Class.new(
    nesting,
    @file_path,
    node.location,
    constant_path.location,
    comments,
    parent_class,
  )

  @owner_stack << entry
  @index.add(entry)
  @stack << name
end

#on_class_node_leave(node) ⇒ Object



119
120
121
122
123
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 119

def on_class_node_leave(node)
  @stack.pop
  @owner_stack.pop
  @visibility_stack.pop
end

#on_constant_and_write_node_enter(node) ⇒ Object



260
261
262
263
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 260

def on_constant_and_write_node_enter(node)
  name = fully_qualify_name(node.name.to_s)
  add_constant(node, name)
end

#on_constant_operator_write_node_enter(node) ⇒ Object



266
267
268
269
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 266

def on_constant_operator_write_node_enter(node)
  name = fully_qualify_name(node.name.to_s)
  add_constant(node, name)
end

#on_constant_or_write_node_enter(node) ⇒ Object



254
255
256
257
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 254

def on_constant_or_write_node_enter(node)
  name = fully_qualify_name(node.name.to_s)
  add_constant(node, name)
end

#on_constant_path_and_write_node_enter(node) ⇒ Object



238
239
240
241
242
243
244
245
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 238

def on_constant_path_and_write_node_enter(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)

  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

#on_constant_path_operator_write_node_enter(node) ⇒ Object



228
229
230
231
232
233
234
235
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 228

def on_constant_path_operator_write_node_enter(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)

  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

#on_constant_path_or_write_node_enter(node) ⇒ Object



218
219
220
221
222
223
224
225
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 218

def on_constant_path_or_write_node_enter(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)

  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

#on_constant_path_write_node_enter(node) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 208

def on_constant_path_write_node_enter(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)

  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

#on_constant_write_node_enter(node) ⇒ Object



248
249
250
251
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 248

def on_constant_write_node_enter(node)
  name = fully_qualify_name(node.name.to_s)
  add_constant(node, name)
end

#on_def_node_enter(node) ⇒ Object



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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 317

def on_def_node_enter(node)
  @inside_def = true
  method_name = node.name.to_s
  comments = collect_comments(node)

  case node.receiver
  when nil
    @index.add(Entry::Method.new(
      method_name,
      @file_path,
      node.location,
      node.name_loc,
      comments,
      [Entry::Signature.new(list_params(node.parameters))],
      current_visibility,
      @owner_stack.last,
    ))
  when Prism::SelfNode
    owner = @owner_stack.last

    if owner
      singleton = @index.existing_or_new_singleton_class(owner.name)

      @index.add(Entry::Method.new(
        method_name,
        @file_path,
        node.location,
        node.name_loc,
        comments,
        [Entry::Signature.new(list_params(node.parameters))],
        current_visibility,
        singleton,
      ))

      @owner_stack << singleton
      @stack << "<Class:#{@stack.last}>"
    end
  end
end

#on_def_node_leave(node) ⇒ Object



358
359
360
361
362
363
364
365
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 358

def on_def_node_leave(node)
  @inside_def = false

  if node.receiver.is_a?(Prism::SelfNode)
    @owner_stack.pop
    @stack.pop
  end
end

#on_instance_variable_and_write_node_enter(node) ⇒ Object



373
374
375
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 373

def on_instance_variable_and_write_node_enter(node)
  handle_instance_variable(node, node.name_loc)
end

#on_instance_variable_operator_write_node_enter(node) ⇒ Object



378
379
380
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 378

def on_instance_variable_operator_write_node_enter(node)
  handle_instance_variable(node, node.name_loc)
end

#on_instance_variable_or_write_node_enter(node) ⇒ Object



383
384
385
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 383

def on_instance_variable_or_write_node_enter(node)
  handle_instance_variable(node, node.name_loc)
end

#on_instance_variable_target_node_enter(node) ⇒ Object



388
389
390
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 388

def on_instance_variable_target_node_enter(node)
  handle_instance_variable(node, node.location)
end

#on_instance_variable_write_node_enter(node) ⇒ Object



368
369
370
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 368

def on_instance_variable_write_node_enter(node)
  handle_instance_variable(node, node.name_loc)
end

#on_module_node_enter(node) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 126

def on_module_node_enter(node)
  @visibility_stack.push(Entry::Visibility::PUBLIC)
  constant_path = node.constant_path
  name = constant_path.slice

  comments = collect_comments(node)

  entry = Entry::Module.new(actual_nesting(name), @file_path, node.location, constant_path.location, comments)

  @owner_stack << entry
  @index.add(entry)
  @stack << name
end

#on_module_node_leave(node) ⇒ Object



141
142
143
144
145
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 141

def on_module_node_leave(node)
  @stack.pop
  @owner_stack.pop
  @visibility_stack.pop
end

#on_multi_write_node_enter(node) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 188

def on_multi_write_node_enter(node)
  value = node.value
  values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []

  [*node.lefts, *node.rest, *node.rights].each_with_index do |target, i|
    current_value = values[i]
    # The moment we find a splat on the right hand side of the assignment, we can no longer figure out which value
    # gets assigned to what
    values.clear if current_value.is_a?(Prism::SplatNode)

    case target
    when Prism::ConstantTargetNode
      add_constant(target, fully_qualify_name(target.name.to_s), current_value)
    when Prism::ConstantPathTargetNode
      add_constant(target, fully_qualify_name(target.slice), current_value)
    end
  end
end

#on_singleton_class_node_enter(node) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 148

def on_singleton_class_node_enter(node)
  @visibility_stack.push(Entry::Visibility::PUBLIC)

  current_owner = @owner_stack.last

  if current_owner
    expression = node.expression
    name = (expression.is_a?(Prism::SelfNode) ? "<Class:#{@stack.last}>" : "<Class:#{expression.slice}>")
    real_nesting = actual_nesting(name)

    existing_entries = T.cast(@index[real_nesting.join("::")], T.nilable(T::Array[Entry::SingletonClass]))

    if existing_entries
      entry = T.must(existing_entries.first)
      entry.update_singleton_information(node.location, expression.location, collect_comments(node))
    else
      entry = Entry::SingletonClass.new(
        real_nesting,
        @file_path,
        node.location,
        expression.location,
        collect_comments(node),
        nil,
      )
      @index.add(entry, skip_prefix_tree: true)
    end

    @owner_stack << entry
    @stack << name
  end
end

#on_singleton_class_node_leave(node) ⇒ Object



181
182
183
184
185
# File 'lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb', line 181

def on_singleton_class_node_leave(node)
  @stack.pop
  @owner_stack.pop
  @visibility_stack.pop
end