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) ⇒ Indexer

Returns a new instance of Indexer.



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

def initialize(path, source, index)
  super()

  @path = path
  @file_name = T.let(File.basename(path), String)
  @source = source
  @index = index
  @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

#visit(node) ⇒ Object



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

def visit(node)
  return unless node

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

#visit_alias(node) ⇒ Object



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

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

#visit_aref(node) ⇒ Object



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

def visit_aref(node)
  super

  reference_method("[]", node)
end

#visit_aref_field(node) ⇒ Object



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

def visit_aref_field(node)
  super

  reference_method("[]=", node)
end

#visit_arg_block(node) ⇒ Object



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

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



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

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



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

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



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

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



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

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



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

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



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

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

#visit_const_path_field(node) ⇒ Object



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

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



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

def visit_def(node)
  super

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

#visit_field(node) ⇒ Object



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

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



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

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



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

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



203
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
# File 'lib/spoom/deadcode/indexer.rb', line 203

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
    reference_method(send.name, send.node)
    visit_all(send.args)
    visit(send.block)
  end
end

#visit_symbol_literal(node) ⇒ Object



238
239
240
241
242
243
244
# File 'lib/spoom/deadcode/indexer.rb', line 238

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



247
248
249
# File 'lib/spoom/deadcode/indexer.rb', line 247

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

#visit_var_field(node) ⇒ Object



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/spoom/deadcode/indexer.rb', line 252

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



269
270
271
# File 'lib/spoom/deadcode/indexer.rb', line 269

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