Class: Spoom::Deadcode::Indexer

Inherits:
SyntaxTree::Visitor
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/spoom/deadcode/indexer.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, source, index, plugins: []) ⇒ Indexer

Returns a new instance of Indexer.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/spoom/deadcode/indexer.rb', line 16

def initialize(path, source, index, plugins: [])
  super()

  @path = path
  @file_name = T.let(File.basename(path), String)
  @source = source
  @index = index
  @plugins = plugins
  @previous_node = T.let(nil, T.nilable(SyntaxTree::Node))
  @names_nesting = T.let([], T::Array[String])
  @nodes_nesting = T.let([], T::Array[SyntaxTree::Node])
  @in_const_field = T.let(false, T::Boolean)
  @in_opassign = T.let(false, T::Boolean)
  @in_symbol_literal = T.let(false, T::Boolean)
end

Instance Attribute Details

#file_nameObject (readonly)

Returns the value of attribute file_name.



10
11
12
# File 'lib/spoom/deadcode/indexer.rb', line 10

def file_name
  @file_name
end

#indexObject (readonly)

Returns the value of attribute index.



13
14
15
# File 'lib/spoom/deadcode/indexer.rb', line 13

def index
  @index
end

#pathObject (readonly)

Returns the value of attribute path.



10
11
12
# File 'lib/spoom/deadcode/indexer.rb', line 10

def path
  @path
end

Instance Method Details

#call_args(node) ⇒ Object



459
460
461
462
463
464
465
466
467
468
# File 'lib/spoom/deadcode/indexer.rb', line 459

def call_args(node)
  case node
  when SyntaxTree::ArgParen
    call_args(node.arguments)
  when SyntaxTree::Args
    node.parts
  else
    []
  end
end

#current_nodeObject



367
368
369
# File 'lib/spoom/deadcode/indexer.rb', line 367

def current_node
  T.must(@nodes_nesting.last)
end

#define_attr_reader(name, full_name, node) ⇒ Object



281
282
283
284
285
286
287
288
289
290
# File 'lib/spoom/deadcode/indexer.rb', line 281

def define_attr_reader(name, full_name, node)
  definition = Definition.new(
    kind: Definition::Kind::AttrReader,
    name: name,
    full_name: full_name,
    location: node_location(node),
  )
  @index.define(definition)
  @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
end

#define_attr_writer(name, full_name, node) ⇒ Object



293
294
295
296
297
298
299
300
301
302
# File 'lib/spoom/deadcode/indexer.rb', line 293

def define_attr_writer(name, full_name, node)
  definition = Definition.new(
    kind: Definition::Kind::AttrWriter,
    name: name,
    full_name: full_name,
    location: node_location(node),
  )
  @index.define(definition)
  @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
end

#define_class(name, full_name, node) ⇒ Object



305
306
307
308
309
310
311
312
313
314
# File 'lib/spoom/deadcode/indexer.rb', line 305

def define_class(name, full_name, node)
  definition = Definition.new(
    kind: Definition::Kind::Class,
    name: name,
    full_name: full_name,
    location: node_location(node),
  )
  @index.define(definition)
  @plugins.each { |plugin| plugin.internal_on_define_class(self, definition) }
end

#define_constant(name, full_name, node) ⇒ Object



317
318
319
320
321
322
323
324
325
326
# File 'lib/spoom/deadcode/indexer.rb', line 317

def define_constant(name, full_name, node)
  definition = Definition.new(
    kind: Definition::Kind::Constant,
    name: name,
    full_name: full_name,
    location: node_location(node),
  )
  @index.define(definition)
  @plugins.each { |plugin| plugin.internal_on_define_constant(self, definition) }
end

#define_method(name, full_name, node) ⇒ Object



329
330
331
332
333
334
335
336
337
338
# File 'lib/spoom/deadcode/indexer.rb', line 329

def define_method(name, full_name, node)
  definition = Definition.new(
    kind: Definition::Kind::Method,
    name: name,
    full_name: full_name,
    location: node_location(node),
  )
  @index.define(definition)
  @plugins.each { |plugin| plugin.internal_on_define_method(self, definition) }
end

#define_module(name, full_name, node) ⇒ Object



341
342
343
344
345
346
347
348
349
350
# File 'lib/spoom/deadcode/indexer.rb', line 341

def define_module(name, full_name, node)
  definition = Definition.new(
    kind: Definition::Kind::Module,
    name: name,
    full_name: full_name,
    location: node_location(node),
  )
  @index.define(definition)
  @plugins.each { |plugin| plugin.internal_on_define_module(self, definition) }
end

#last_sigObject



426
427
428
429
430
# File 'lib/spoom/deadcode/indexer.rb', line 426

def last_sig
  return unless @previous_node.is_a?(SyntaxTree::MethodAddBlock)

  node_string(@previous_node)
end

#nesting_blockObject



386
387
388
# File 'lib/spoom/deadcode/indexer.rb', line 386

def nesting_block
  nesting_node(SyntaxTree::BlockNode)
end

#nesting_block_callObject



391
392
393
# File 'lib/spoom/deadcode/indexer.rb', line 391

def nesting_block_call
  nesting_node(SyntaxTree::MethodAddBlock)
end

#nesting_block_call_nameObject



396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/spoom/deadcode/indexer.rb', line 396

def nesting_block_call_name
  block = nesting_block_call
  return unless block.is_a?(SyntaxTree::MethodAddBlock)

  call = block.call
  case call
  when SyntaxTree::ARef
    node_string(call.collection)
  when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
    node_string(call.message)
  end
end

#nesting_classObject



381
382
383
# File 'lib/spoom/deadcode/indexer.rb', line 381

def nesting_class
  nesting_node(SyntaxTree::ClassDeclaration)
end

#nesting_class_nameObject



410
411
412
413
414
415
# File 'lib/spoom/deadcode/indexer.rb', line 410

def nesting_class_name
  nesting_class = self.nesting_class
  return unless nesting_class

  node_string(nesting_class.constant)
end

#nesting_class_superclass_nameObject



418
419
420
421
422
423
# File 'lib/spoom/deadcode/indexer.rb', line 418

def nesting_class_superclass_name
  nesting_class_superclass = nesting_class&.superclass
  return unless nesting_class_superclass

  node_string(nesting_class_superclass).delete_prefix("::")
end

#nesting_node(type) ⇒ Object



372
373
374
375
376
377
378
# File 'lib/spoom/deadcode/indexer.rb', line 372

def nesting_node(type)
  @nodes_nesting.reverse_each do |node|
    return T.unsafe(node) if node.is_a?(type)
  end

  nil
end

#node_location(node) ⇒ Object



445
446
447
# File 'lib/spoom/deadcode/indexer.rb', line 445

def node_location(node)
  Location.from_syntax_tree(@path, node.location)
end

#node_string(node) ⇒ Object



435
436
437
438
439
440
441
442
# File 'lib/spoom/deadcode/indexer.rb', line 435

def node_string(node)
  case node
  when Symbol
    node.to_s
  else
    T.must(@source[node.location.start_char...node.location.end_char])
  end
end

#reference_constant(name, node) ⇒ Object



355
356
357
# File 'lib/spoom/deadcode/indexer.rb', line 355

def reference_constant(name, node)
  @index.reference(Reference.new(name: name, kind: Reference::Kind::Constant, location: node_location(node)))
end

#reference_method(name, node) ⇒ Object



360
361
362
# File 'lib/spoom/deadcode/indexer.rb', line 360

def reference_method(name, node)
  @index.reference(Reference.new(name: name, kind: Reference::Kind::Method, location: node_location(node)))
end

#symbol_string(node) ⇒ Object



450
451
452
# File 'lib/spoom/deadcode/indexer.rb', line 450

def symbol_string(node)
  node_string(node).delete_prefix(":")
end

#visit(node) ⇒ Object



35
36
37
38
39
40
41
42
# File 'lib/spoom/deadcode/indexer.rb', line 35

def visit(node)
  return unless node

  @nodes_nesting << node
  super
  @nodes_nesting.pop
  @previous_node = node
end

#visit_alias(node) ⇒ Object



45
46
47
# File 'lib/spoom/deadcode/indexer.rb', line 45

def visit_alias(node)
  reference_method(node_string(node.right), node)
end

#visit_aref(node) ⇒ Object



50
51
52
53
54
# File 'lib/spoom/deadcode/indexer.rb', line 50

def visit_aref(node)
  super

  reference_method("[]", node)
end

#visit_aref_field(node) ⇒ Object



57
58
59
60
61
# File 'lib/spoom/deadcode/indexer.rb', line 57

def visit_aref_field(node)
  super

  reference_method("[]=", node)
end

#visit_arg_block(node) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/spoom/deadcode/indexer.rb', line 64

def visit_arg_block(node)
  value = node.value

  case value
  when SyntaxTree::SymbolLiteral
    # If the block call is something like `x.select(&:foo)`, we need to reference the `foo` method
    reference_method(symbol_string(value), node)
  when SyntaxTree::VCall
    # If the block call is something like `x.select { ... }`, we need to visit the block
    super
  end
end

#visit_binary(node) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/spoom/deadcode/indexer.rb', line 78

def visit_binary(node)
  super

  op = node.operator

  # Reference the operator itself
  reference_method(op.to_s, node)

  case op
  when :<, :>, :<=, :>=
    # For comparison operators, we also reference the `<=>` method
    reference_method("<=>", node)
  end
end

#visit_call(node) ⇒ Object



94
95
96
97
98
99
100
101
102
103
# File 'lib/spoom/deadcode/indexer.rb', line 94

def visit_call(node)
  visit_send(
    Send.new(
      node: node,
      name: node_string(node.message),
      recv: node.receiver,
      args: call_args(node.arguments),
    ),
  )
end

#visit_class(node) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
# File 'lib/spoom/deadcode/indexer.rb', line 106

def visit_class(node)
  const_name = node_string(node.constant)
  @names_nesting << const_name
  define_class(T.must(const_name.split("::").last), @names_nesting.join("::"), node)

  # We do not call `super` here because we don't want to visit the `constant` again
  visit(node.superclass) if node.superclass
  visit(node.bodystmt)

  @names_nesting.pop
end

#visit_command(node) ⇒ Object



119
120
121
122
123
124
125
126
127
128
# File 'lib/spoom/deadcode/indexer.rb', line 119

def visit_command(node)
  visit_send(
    Send.new(
      node: node,
      name: node_string(node.message),
      args: call_args(node.arguments),
      block: node.block,
    ),
  )
end

#visit_command_call(node) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
# File 'lib/spoom/deadcode/indexer.rb', line 131

def visit_command_call(node)
  visit_send(
    Send.new(
      node: node,
      name: node_string(node.message),
      recv: node.receiver,
      args: call_args(node.arguments),
      block: node.block,
    ),
  )
end

#visit_const(node) ⇒ Object



144
145
146
# File 'lib/spoom/deadcode/indexer.rb', line 144

def visit_const(node)
  reference_constant(node.value, node) unless @in_symbol_literal
end

#visit_const_path_field(node) ⇒ Object



149
150
151
152
153
154
155
156
# File 'lib/spoom/deadcode/indexer.rb', line 149

def visit_const_path_field(node)
  # We do not call `super` here because we don't want to visit the `constant` again
  visit(node.parent)

  name = node.constant.value
  full_name = [*@names_nesting, node_string(node.parent), name].join("::")
  define_constant(name, full_name, node)
end

#visit_def(node) ⇒ Object



159
160
161
162
163
164
# File 'lib/spoom/deadcode/indexer.rb', line 159

def visit_def(node)
  name = node_string(node.name)
  define_method(name, [*@names_nesting, name].join("::"), node)

  super
end

#visit_field(node) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/spoom/deadcode/indexer.rb', line 167

def visit_field(node)
  visit(node.parent)

  name = node.name
  case name
  when SyntaxTree::Const
    name = name.value
    full_name = [*@names_nesting, node_string(node.parent), name].join("::")
    define_constant(name, full_name, node)
  when SyntaxTree::Ident
    reference_method(name.value, node) if @in_opassign
    reference_method("#{name.value}=", node)
  end
end

#visit_module(node) ⇒ Object



183
184
185
186
187
188
189
190
191
192
# File 'lib/spoom/deadcode/indexer.rb', line 183

def visit_module(node)
  const_name = node_string(node.constant)
  @names_nesting << const_name
  define_module(T.must(const_name.split("::").last), @names_nesting.join("::"), node)

  # We do not call `super` here because we don't want to visit the `constant` again
  visit(node.bodystmt)

  @names_nesting.pop
end

#visit_opassign(node) ⇒ Object



195
196
197
198
199
200
201
# File 'lib/spoom/deadcode/indexer.rb', line 195

def visit_opassign(node)
  # Both `FOO = x` and `FOO += x` yield a VarField node, but the former is a constant definition and the latter is
  # a constant reference. We need to distinguish between the two cases.
  @in_opassign = true
  super
  @in_opassign = false
end

#visit_send(send) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/spoom/deadcode/indexer.rb', line 204

def visit_send(send)
  visit(send.recv)

  case send.name
  when "attr_reader"
    send.args.each do |arg|
      next unless arg.is_a?(SyntaxTree::SymbolLiteral)

      name = symbol_string(arg)
      define_attr_reader(name, [*@names_nesting, name].join("::"), arg)
    end
  when "attr_writer"
    send.args.each do |arg|
      next unless arg.is_a?(SyntaxTree::SymbolLiteral)

      name = symbol_string(arg)
      define_attr_writer("#{name}=", "#{[*@names_nesting, name].join("::")}=", arg)
    end
  when "attr_accessor"
    send.args.each do |arg|
      next unless arg.is_a?(SyntaxTree::SymbolLiteral)

      name = symbol_string(arg)
      full_name = [*@names_nesting, name].join("::")
      define_attr_reader(name, full_name, arg)
      define_attr_writer("#{name}=", "#{full_name}=", arg)
    end
  else
    @plugins.each do |plugin|
      plugin.internal_on_send(self, send)
    end

    reference_method(send.name, send.node)
    visit_all(send.args)
    visit(send.block)
  end
end

#visit_symbol_literal(node) ⇒ Object



243
244
245
246
247
248
249
# File 'lib/spoom/deadcode/indexer.rb', line 243

def visit_symbol_literal(node)
  # Something like `:FOO` will yield a Const node but we do not want to treat it as a constant reference.
  # So we need to distinguish between the two cases.
  @in_symbol_literal = true
  super
  @in_symbol_literal = false
end

#visit_top_const_field(node) ⇒ Object



252
253
254
# File 'lib/spoom/deadcode/indexer.rb', line 252

def visit_top_const_field(node)
  define_constant(node.constant.value, node.constant.value, node)
end

#visit_var_field(node) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/spoom/deadcode/indexer.rb', line 257

def visit_var_field(node)
  value = node.value
  case value
  when SyntaxTree::Const
    if @in_opassign
      reference_constant(value.value, node)
    else
      name = value.value
      define_constant(name, [*@names_nesting, name].join("::"), node)
    end
  when SyntaxTree::Ident
    reference_method(value.value, node) if @in_opassign
    reference_method("#{value.value}=", node)
  end
end

#visit_vcall(node) ⇒ Object



274
275
276
# File 'lib/spoom/deadcode/indexer.rb', line 274

def visit_vcall(node)
  visit_send(Send.new(node: node, name: node_string(node.value)))
end