spoom 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/lib/spoom/cli/coverage.rb +1 -1
- data/lib/spoom/context/bundle.rb +16 -7
- data/lib/spoom/context/file_system.rb +17 -0
- data/lib/spoom/context/git.rb +21 -5
- data/lib/spoom/context/sorbet.rb +24 -7
- data/lib/spoom/deadcode/definition.rb +98 -0
- data/lib/spoom/deadcode/erb.rb +103 -0
- data/lib/spoom/deadcode/index.rb +61 -0
- data/lib/spoom/deadcode/indexer.rb +403 -0
- data/lib/spoom/deadcode/location.rb +58 -0
- data/lib/spoom/deadcode/plugins/base.rb +201 -0
- data/lib/spoom/deadcode/plugins/ruby.rb +64 -0
- data/lib/spoom/deadcode/plugins.rb +5 -0
- data/lib/spoom/deadcode/reference.rb +34 -0
- data/lib/spoom/deadcode/send.rb +18 -0
- data/lib/spoom/deadcode.rb +56 -0
- data/lib/spoom/file_collector.rb +26 -3
- data/lib/spoom/printer.rb +0 -2
- data/lib/spoom/sorbet/lsp/base.rb +0 -6
- data/lib/spoom/sorbet/lsp/structures.rb +1 -1
- data/lib/spoom/sorbet/lsp.rb +2 -2
- data/lib/spoom/sorbet/sigils.rb +2 -2
- data/lib/spoom/sorbet.rb +1 -0
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +1 -0
- metadata +33 -8
@@ -0,0 +1,403 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
class Indexer < SyntaxTree::Visitor
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(String) }
|
10
|
+
attr_reader :path, :file_name
|
11
|
+
|
12
|
+
sig { returns(Index) }
|
13
|
+
attr_reader :index
|
14
|
+
|
15
|
+
sig { params(path: String, source: String, index: Index, plugins: T::Array[Plugins::Base]).void }
|
16
|
+
def initialize(path, source, index, plugins: [])
|
17
|
+
super()
|
18
|
+
|
19
|
+
@path = path
|
20
|
+
@file_name = T.let(File.basename(path), String)
|
21
|
+
@source = source
|
22
|
+
@index = index
|
23
|
+
@plugins = plugins
|
24
|
+
@previous_node = T.let(nil, T.nilable(SyntaxTree::Node))
|
25
|
+
@names_nesting = T.let([], T::Array[String])
|
26
|
+
@nodes_nesting = T.let([], T::Array[SyntaxTree::Node])
|
27
|
+
@in_const_field = T.let(false, T::Boolean)
|
28
|
+
@in_opassign = T.let(false, T::Boolean)
|
29
|
+
@in_symbol_literal = T.let(false, T::Boolean)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Visit
|
33
|
+
|
34
|
+
sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
|
35
|
+
def visit(node)
|
36
|
+
return unless node
|
37
|
+
|
38
|
+
@nodes_nesting << node
|
39
|
+
super
|
40
|
+
@nodes_nesting.pop
|
41
|
+
@previous_node = node
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { override.params(node: SyntaxTree::AliasNode).void }
|
45
|
+
def visit_alias(node)
|
46
|
+
reference_method(node_string(node.right), node)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { override.params(node: SyntaxTree::ARef).void }
|
50
|
+
def visit_aref(node)
|
51
|
+
super
|
52
|
+
|
53
|
+
reference_method("[]", node)
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { override.params(node: SyntaxTree::ARefField).void }
|
57
|
+
def visit_aref_field(node)
|
58
|
+
super
|
59
|
+
|
60
|
+
reference_method("[]=", node)
|
61
|
+
end
|
62
|
+
|
63
|
+
sig { override.params(node: SyntaxTree::ArgBlock).void }
|
64
|
+
def visit_arg_block(node)
|
65
|
+
value = node.value
|
66
|
+
|
67
|
+
case value
|
68
|
+
when SyntaxTree::SymbolLiteral
|
69
|
+
# If the block call is something like `x.select(&:foo)`, we need to reference the `foo` method
|
70
|
+
reference_method(symbol_string(value), node)
|
71
|
+
when SyntaxTree::VCall
|
72
|
+
# If the block call is something like `x.select { ... }`, we need to visit the block
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { override.params(node: SyntaxTree::Binary).void }
|
78
|
+
def visit_binary(node)
|
79
|
+
super
|
80
|
+
|
81
|
+
op = node.operator
|
82
|
+
|
83
|
+
# Reference the operator itself
|
84
|
+
reference_method(op.to_s, node)
|
85
|
+
|
86
|
+
case op
|
87
|
+
when :<, :>, :<=, :>=
|
88
|
+
# For comparison operators, we also reference the `<=>` method
|
89
|
+
reference_method("<=>", node)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { override.params(node: SyntaxTree::CallNode).void }
|
94
|
+
def visit_call(node)
|
95
|
+
visit_send(
|
96
|
+
Send.new(
|
97
|
+
node: node,
|
98
|
+
name: node_string(node.message),
|
99
|
+
recv: node.receiver,
|
100
|
+
args: call_args(node.arguments),
|
101
|
+
),
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
sig { override.params(node: SyntaxTree::ClassDeclaration).void }
|
106
|
+
def visit_class(node)
|
107
|
+
const_name = node_string(node.constant)
|
108
|
+
@names_nesting << const_name
|
109
|
+
define_class(T.must(const_name.split("::").last), @names_nesting.join("::"), node)
|
110
|
+
|
111
|
+
# We do not call `super` here because we don't want to visit the `constant` again
|
112
|
+
visit(node.superclass) if node.superclass
|
113
|
+
visit(node.bodystmt)
|
114
|
+
|
115
|
+
@names_nesting.pop
|
116
|
+
end
|
117
|
+
|
118
|
+
sig { override.params(node: SyntaxTree::Command).void }
|
119
|
+
def visit_command(node)
|
120
|
+
visit_send(
|
121
|
+
Send.new(
|
122
|
+
node: node,
|
123
|
+
name: node_string(node.message),
|
124
|
+
args: call_args(node.arguments),
|
125
|
+
block: node.block,
|
126
|
+
),
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
sig { override.params(node: SyntaxTree::CommandCall).void }
|
131
|
+
def visit_command_call(node)
|
132
|
+
visit_send(
|
133
|
+
Send.new(
|
134
|
+
node: node,
|
135
|
+
name: node_string(node.message),
|
136
|
+
recv: node.receiver,
|
137
|
+
args: call_args(node.arguments),
|
138
|
+
block: node.block,
|
139
|
+
),
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { override.params(node: SyntaxTree::Const).void }
|
144
|
+
def visit_const(node)
|
145
|
+
reference_constant(node.value, node) unless @in_symbol_literal
|
146
|
+
end
|
147
|
+
|
148
|
+
sig { override.params(node: SyntaxTree::ConstPathField).void }
|
149
|
+
def visit_const_path_field(node)
|
150
|
+
# We do not call `super` here because we don't want to visit the `constant` again
|
151
|
+
visit(node.parent)
|
152
|
+
|
153
|
+
name = node.constant.value
|
154
|
+
full_name = [*@names_nesting, node_string(node.parent), name].join("::")
|
155
|
+
define_constant(name, full_name, node)
|
156
|
+
end
|
157
|
+
|
158
|
+
sig { override.params(node: SyntaxTree::DefNode).void }
|
159
|
+
def visit_def(node)
|
160
|
+
super
|
161
|
+
|
162
|
+
name = node_string(node.name)
|
163
|
+
define_method(name, [*@names_nesting, name].join("::"), node)
|
164
|
+
end
|
165
|
+
|
166
|
+
sig { override.params(node: SyntaxTree::Field).void }
|
167
|
+
def visit_field(node)
|
168
|
+
visit(node.parent)
|
169
|
+
|
170
|
+
name = node.name
|
171
|
+
case name
|
172
|
+
when SyntaxTree::Const
|
173
|
+
name = name.value
|
174
|
+
full_name = [*@names_nesting, node_string(node.parent), name].join("::")
|
175
|
+
define_constant(name, full_name, node)
|
176
|
+
when SyntaxTree::Ident
|
177
|
+
reference_method(name.value, node) if @in_opassign
|
178
|
+
reference_method("#{name.value}=", node)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
sig { override.params(node: SyntaxTree::ModuleDeclaration).void }
|
183
|
+
def visit_module(node)
|
184
|
+
const_name = node_string(node.constant)
|
185
|
+
@names_nesting << const_name
|
186
|
+
define_module(T.must(const_name.split("::").last), @names_nesting.join("::"), node)
|
187
|
+
|
188
|
+
# We do not call `super` here because we don't want to visit the `constant` again
|
189
|
+
visit(node.bodystmt)
|
190
|
+
|
191
|
+
@names_nesting.pop
|
192
|
+
end
|
193
|
+
|
194
|
+
sig { override.params(node: SyntaxTree::OpAssign).void }
|
195
|
+
def visit_opassign(node)
|
196
|
+
# Both `FOO = x` and `FOO += x` yield a VarField node, but the former is a constant definition and the latter is
|
197
|
+
# a constant reference. We need to distinguish between the two cases.
|
198
|
+
@in_opassign = true
|
199
|
+
super
|
200
|
+
@in_opassign = false
|
201
|
+
end
|
202
|
+
|
203
|
+
sig { params(send: Send).void }
|
204
|
+
def visit_send(send)
|
205
|
+
visit(send.recv)
|
206
|
+
|
207
|
+
case send.name
|
208
|
+
when "attr_reader"
|
209
|
+
send.args.each do |arg|
|
210
|
+
next unless arg.is_a?(SyntaxTree::SymbolLiteral)
|
211
|
+
|
212
|
+
name = symbol_string(arg)
|
213
|
+
define_attr_reader(name, [*@names_nesting, name].join("::"), arg)
|
214
|
+
end
|
215
|
+
when "attr_writer"
|
216
|
+
send.args.each do |arg|
|
217
|
+
next unless arg.is_a?(SyntaxTree::SymbolLiteral)
|
218
|
+
|
219
|
+
name = symbol_string(arg)
|
220
|
+
define_attr_writer("#{name}=", "#{[*@names_nesting, name].join("::")}=", arg)
|
221
|
+
end
|
222
|
+
when "attr_accessor"
|
223
|
+
send.args.each do |arg|
|
224
|
+
next unless arg.is_a?(SyntaxTree::SymbolLiteral)
|
225
|
+
|
226
|
+
name = symbol_string(arg)
|
227
|
+
full_name = [*@names_nesting, name].join("::")
|
228
|
+
define_attr_reader(name, full_name, arg)
|
229
|
+
define_attr_writer("#{name}=", "#{full_name}=", arg)
|
230
|
+
end
|
231
|
+
else
|
232
|
+
@plugins.each do |plugin|
|
233
|
+
plugin.on_send(self, send)
|
234
|
+
end
|
235
|
+
|
236
|
+
reference_method(send.name, send.node)
|
237
|
+
visit_all(send.args)
|
238
|
+
visit(send.block)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
sig { override.params(node: SyntaxTree::SymbolLiteral).void }
|
243
|
+
def visit_symbol_literal(node)
|
244
|
+
# Something like `:FOO` will yield a Const node but we do not want to treat it as a constant reference.
|
245
|
+
# So we need to distinguish between the two cases.
|
246
|
+
@in_symbol_literal = true
|
247
|
+
super
|
248
|
+
@in_symbol_literal = false
|
249
|
+
end
|
250
|
+
|
251
|
+
sig { override.params(node: SyntaxTree::TopConstField).void }
|
252
|
+
def visit_top_const_field(node)
|
253
|
+
define_constant(node.constant.value, node.constant.value, node)
|
254
|
+
end
|
255
|
+
|
256
|
+
sig { override.params(node: SyntaxTree::VarField).void }
|
257
|
+
def visit_var_field(node)
|
258
|
+
value = node.value
|
259
|
+
case value
|
260
|
+
when SyntaxTree::Const
|
261
|
+
if @in_opassign
|
262
|
+
reference_constant(value.value, node)
|
263
|
+
else
|
264
|
+
name = value.value
|
265
|
+
define_constant(name, [*@names_nesting, name].join("::"), node)
|
266
|
+
end
|
267
|
+
when SyntaxTree::Ident
|
268
|
+
reference_method(value.value, node) if @in_opassign
|
269
|
+
reference_method("#{value.value}=", node)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
sig { override.params(node: SyntaxTree::VCall).void }
|
274
|
+
def visit_vcall(node)
|
275
|
+
visit_send(Send.new(node: node, name: node_string(node.value)))
|
276
|
+
end
|
277
|
+
|
278
|
+
# Definition indexing
|
279
|
+
|
280
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
281
|
+
def define_attr_reader(name, full_name, node)
|
282
|
+
definition = Definition.new(
|
283
|
+
kind: Definition::Kind::AttrReader,
|
284
|
+
name: name,
|
285
|
+
full_name: full_name,
|
286
|
+
location: node_location(node),
|
287
|
+
)
|
288
|
+
@index.define(definition)
|
289
|
+
@plugins.each { |plugin| plugin.on_define_accessor(self, definition) }
|
290
|
+
end
|
291
|
+
|
292
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
293
|
+
def define_attr_writer(name, full_name, node)
|
294
|
+
definition = Definition.new(
|
295
|
+
kind: Definition::Kind::AttrWriter,
|
296
|
+
name: name,
|
297
|
+
full_name: full_name,
|
298
|
+
location: node_location(node),
|
299
|
+
)
|
300
|
+
@index.define(definition)
|
301
|
+
@plugins.each { |plugin| plugin.on_define_accessor(self, definition) }
|
302
|
+
end
|
303
|
+
|
304
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
305
|
+
def define_class(name, full_name, node)
|
306
|
+
definition = Definition.new(
|
307
|
+
kind: Definition::Kind::Class,
|
308
|
+
name: name,
|
309
|
+
full_name: full_name,
|
310
|
+
location: node_location(node),
|
311
|
+
)
|
312
|
+
@index.define(definition)
|
313
|
+
@plugins.each { |plugin| plugin.on_define_class(self, definition) }
|
314
|
+
end
|
315
|
+
|
316
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
317
|
+
def define_constant(name, full_name, node)
|
318
|
+
definition = Definition.new(
|
319
|
+
kind: Definition::Kind::Constant,
|
320
|
+
name: name,
|
321
|
+
full_name: full_name,
|
322
|
+
location: node_location(node),
|
323
|
+
)
|
324
|
+
@index.define(definition)
|
325
|
+
@plugins.each { |plugin| plugin.on_define_constant(self, definition) }
|
326
|
+
end
|
327
|
+
|
328
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
329
|
+
def define_method(name, full_name, node)
|
330
|
+
definition = Definition.new(
|
331
|
+
kind: Definition::Kind::Method,
|
332
|
+
name: name,
|
333
|
+
full_name: full_name,
|
334
|
+
location: node_location(node),
|
335
|
+
)
|
336
|
+
@index.define(definition)
|
337
|
+
@plugins.each { |plugin| plugin.on_define_method(self, definition) }
|
338
|
+
end
|
339
|
+
|
340
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
341
|
+
def define_module(name, full_name, node)
|
342
|
+
definition = Definition.new(
|
343
|
+
kind: Definition::Kind::Module,
|
344
|
+
name: name,
|
345
|
+
full_name: full_name,
|
346
|
+
location: node_location(node),
|
347
|
+
)
|
348
|
+
@index.define(definition)
|
349
|
+
@plugins.each { |plugin| plugin.on_define_module(self, definition) }
|
350
|
+
end
|
351
|
+
|
352
|
+
# Reference indexing
|
353
|
+
|
354
|
+
sig { params(name: String, node: SyntaxTree::Node).void }
|
355
|
+
def reference_constant(name, node)
|
356
|
+
@index.reference(Reference.new(name: name, kind: Reference::Kind::Constant, location: node_location(node)))
|
357
|
+
end
|
358
|
+
|
359
|
+
sig { params(name: String, node: SyntaxTree::Node).void }
|
360
|
+
def reference_method(name, node)
|
361
|
+
@index.reference(Reference.new(name: name, kind: Reference::Kind::Method, location: node_location(node)))
|
362
|
+
end
|
363
|
+
|
364
|
+
# Node utils
|
365
|
+
|
366
|
+
sig { params(node: T.any(Symbol, SyntaxTree::Node)).returns(String) }
|
367
|
+
def node_string(node)
|
368
|
+
case node
|
369
|
+
when Symbol
|
370
|
+
node.to_s
|
371
|
+
else
|
372
|
+
T.must(@source[node.location.start_char...node.location.end_char])
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
sig { params(node: SyntaxTree::Node).returns(Location) }
|
377
|
+
def node_location(node)
|
378
|
+
Location.from_syntax_tree(@path, node.location)
|
379
|
+
end
|
380
|
+
|
381
|
+
sig { params(node: SyntaxTree::Node).returns(String) }
|
382
|
+
def symbol_string(node)
|
383
|
+
node_string(node).delete_prefix(":")
|
384
|
+
end
|
385
|
+
|
386
|
+
sig do
|
387
|
+
params(
|
388
|
+
node: T.any(SyntaxTree::Args, SyntaxTree::ArgParen, SyntaxTree::ArgsForward, NilClass),
|
389
|
+
).returns(T::Array[SyntaxTree::Node])
|
390
|
+
end
|
391
|
+
def call_args(node)
|
392
|
+
case node
|
393
|
+
when SyntaxTree::ArgParen
|
394
|
+
call_args(node.arguments)
|
395
|
+
when SyntaxTree::Args
|
396
|
+
node.parts
|
397
|
+
else
|
398
|
+
[]
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
class Location
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
class LocationError < Spoom::Error; end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { params(file: String, location: SyntaxTree::Location).returns(Location) }
|
17
|
+
def from_syntax_tree(file, location)
|
18
|
+
new(file, location.start_line, location.start_column, location.end_line, location.end_column)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(String) }
|
23
|
+
attr_reader :file
|
24
|
+
|
25
|
+
sig { returns(Integer) }
|
26
|
+
attr_reader :start_line, :start_column, :end_line, :end_column
|
27
|
+
|
28
|
+
sig do
|
29
|
+
params(
|
30
|
+
file: String,
|
31
|
+
start_line: Integer,
|
32
|
+
start_column: Integer,
|
33
|
+
end_line: Integer,
|
34
|
+
end_column: Integer,
|
35
|
+
).void
|
36
|
+
end
|
37
|
+
def initialize(file, start_line, start_column, end_line, end_column)
|
38
|
+
@file = file
|
39
|
+
@start_line = start_line
|
40
|
+
@start_column = start_column
|
41
|
+
@end_line = end_line
|
42
|
+
@end_column = end_column
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { override.params(other: BasicObject).returns(T.nilable(Integer)) }
|
46
|
+
def <=>(other)
|
47
|
+
return unless Location === other
|
48
|
+
|
49
|
+
to_s <=> other.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(String) }
|
53
|
+
def to_s
|
54
|
+
"#{@file}:#{@start_line}:#{@start_column}-#{@end_line}:#{@end_column}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "set"
|
5
|
+
|
6
|
+
module Spoom
|
7
|
+
module Deadcode
|
8
|
+
module Plugins
|
9
|
+
class Base
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Helpers
|
12
|
+
|
13
|
+
abstract!
|
14
|
+
|
15
|
+
class << self
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
# Plugins DSL
|
19
|
+
|
20
|
+
# Mark methods matching `names` as ignored.
|
21
|
+
#
|
22
|
+
# Names can be either strings or regexps:
|
23
|
+
#
|
24
|
+
# ~~~rb
|
25
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
26
|
+
# ignore_method_names(
|
27
|
+
# "foo",
|
28
|
+
# "bar",
|
29
|
+
# /baz.*/,
|
30
|
+
# )
|
31
|
+
# end
|
32
|
+
# ~~~
|
33
|
+
sig { params(names: T.any(String, Regexp)).void }
|
34
|
+
def ignore_method_names(*names)
|
35
|
+
save_names_and_patterns(names, :@ignored_method_names, :@ignored_method_patterns)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
sig { params(names: T::Array[T.any(String, Regexp)], names_variable: Symbol, patterns_variable: Symbol).void }
|
41
|
+
def save_names_and_patterns(names, names_variable, patterns_variable)
|
42
|
+
ignored_names = instance_variable_set(names_variable, Set.new)
|
43
|
+
ignored_patterns = instance_variable_set(patterns_variable, [])
|
44
|
+
|
45
|
+
names.each do |name|
|
46
|
+
case name
|
47
|
+
when String
|
48
|
+
ignored_names << name
|
49
|
+
when Regexp
|
50
|
+
ignored_patterns << name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Indexing event methods
|
57
|
+
|
58
|
+
# Called when an accessor is defined.
|
59
|
+
#
|
60
|
+
# Will be called when the indexer processes a `attr_reader`, `attr_writer` or `attr_accessor` node.
|
61
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
62
|
+
# It is still possible to ignore it from the plugin:
|
63
|
+
#
|
64
|
+
# ~~~rb
|
65
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
66
|
+
# def on_define_accessor(indexer, definition)
|
67
|
+
# definition.ignored! if definition.name == "foo"
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
# ~~~
|
71
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
72
|
+
def on_define_accessor(indexer, definition)
|
73
|
+
# no-op
|
74
|
+
end
|
75
|
+
|
76
|
+
# Called when a class is defined.
|
77
|
+
#
|
78
|
+
# Will be called when the indexer processes a `class` node.
|
79
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
80
|
+
# It is still possible to ignore it from the plugin:
|
81
|
+
#
|
82
|
+
# ~~~rb
|
83
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
84
|
+
# def on_define_class(indexer, definition)
|
85
|
+
# definition.ignored! if definition.name == "Foo"
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
# ~~~
|
89
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
90
|
+
def on_define_class(indexer, definition)
|
91
|
+
# no-op
|
92
|
+
end
|
93
|
+
|
94
|
+
# Called when a constant is defined.
|
95
|
+
#
|
96
|
+
# Will be called when the indexer processes a `CONST =` node.
|
97
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
98
|
+
# It is still possible to ignore it from the plugin:
|
99
|
+
#
|
100
|
+
# ~~~rb
|
101
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
102
|
+
# def on_define_constant(indexer, definition)
|
103
|
+
# definition.ignored! if definition.name == "FOO"
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
# ~~~
|
107
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
108
|
+
def on_define_constant(indexer, definition)
|
109
|
+
# no-op
|
110
|
+
end
|
111
|
+
|
112
|
+
# Called when a method is defined.
|
113
|
+
#
|
114
|
+
# Will be called when the indexer processes a `def` or `defs` node.
|
115
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
116
|
+
# It is still possible to ignore it from the plugin:
|
117
|
+
#
|
118
|
+
# ~~~rb
|
119
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
120
|
+
# def on_define_method(indexer, definition)
|
121
|
+
# super # So the `ignore_method_names` DSL is still applied
|
122
|
+
#
|
123
|
+
# definition.ignored! if definition.name == "foo"
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
# ~~~
|
127
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
128
|
+
def on_define_method(indexer, definition)
|
129
|
+
definition.ignored! if ignored_method_name?(definition.name)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Called when a module is defined.
|
133
|
+
#
|
134
|
+
# Will be called when the indexer processes a `module` node.
|
135
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
136
|
+
# It is still possible to ignore it from the plugin:
|
137
|
+
#
|
138
|
+
# ~~~rb
|
139
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
140
|
+
# def on_define_module(indexer, definition)
|
141
|
+
# definition.ignored! if definition.name == "Foo"
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
# ~~~
|
145
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
146
|
+
def on_define_module(indexer, definition)
|
147
|
+
# no-op
|
148
|
+
end
|
149
|
+
|
150
|
+
# Called when a send is being processed
|
151
|
+
#
|
152
|
+
# ~~~rb
|
153
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
154
|
+
# def on_send(indexer, send)
|
155
|
+
# return unless send.name == "dsl_method"
|
156
|
+
# return if send.args.empty?
|
157
|
+
#
|
158
|
+
# method_name = indexer.node_string(send.args.first).delete_prefix(":")
|
159
|
+
# indexer.reference_method(method_name, send.node)
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
# ~~~
|
163
|
+
sig { params(indexer: Indexer, send: Send).void }
|
164
|
+
def on_send(indexer, send)
|
165
|
+
# no-op
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
sig { params(name: String).returns(T::Boolean) }
|
171
|
+
def ignored_method_name?(name)
|
172
|
+
ignored_name?(name, :@ignored_method_names, :@ignored_method_patterns)
|
173
|
+
end
|
174
|
+
|
175
|
+
sig { params(const: Symbol).returns(T::Set[String]) }
|
176
|
+
def names(const)
|
177
|
+
self.class.instance_variable_get(const) || Set.new
|
178
|
+
end
|
179
|
+
|
180
|
+
sig { params(name: String, names_variable: Symbol, patterns_variable: Symbol).returns(T::Boolean) }
|
181
|
+
def ignored_name?(name, names_variable, patterns_variable)
|
182
|
+
names(names_variable).include?(name) || patterns(patterns_variable).any? { |pattern| pattern.match?(name) }
|
183
|
+
end
|
184
|
+
|
185
|
+
sig { params(const: Symbol).returns(T::Array[Regexp]) }
|
186
|
+
def patterns(const)
|
187
|
+
self.class.instance_variable_get(const) || []
|
188
|
+
end
|
189
|
+
|
190
|
+
sig { params(indexer: Indexer, send: Send).void }
|
191
|
+
def reference_send_first_symbol_as_method(indexer, send)
|
192
|
+
first_arg = send.args.first
|
193
|
+
return unless first_arg.is_a?(SyntaxTree::SymbolLiteral)
|
194
|
+
|
195
|
+
name = indexer.node_string(first_arg.value)
|
196
|
+
indexer.reference_method(name, send.node)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|