spoom 1.2.2 → 1.2.4
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/README.md +10 -0
- data/lib/spoom/backtrace_filter/minitest.rb +21 -0
- data/lib/spoom/cli/coverage.rb +1 -1
- data/lib/spoom/context/git.rb +4 -4
- data/lib/spoom/context/sorbet.rb +6 -6
- data/lib/spoom/deadcode/erb.rb +4 -4
- data/lib/spoom/deadcode/indexer.rb +83 -6
- data/lib/spoom/deadcode/location.rb +29 -1
- data/lib/spoom/deadcode/plugins/action_mailer.rb +21 -0
- data/lib/spoom/deadcode/plugins/actionpack.rb +61 -0
- data/lib/spoom/deadcode/plugins/active_job.rb +13 -0
- data/lib/spoom/deadcode/plugins/active_model.rb +46 -0
- data/lib/spoom/deadcode/plugins/active_record.rb +111 -0
- data/lib/spoom/deadcode/plugins/active_support.rb +21 -0
- data/lib/spoom/deadcode/plugins/base.rb +354 -0
- data/lib/spoom/deadcode/plugins/graphql.rb +47 -0
- data/lib/spoom/deadcode/plugins/minitest.rb +28 -0
- data/lib/spoom/deadcode/plugins/namespaces.rb +34 -0
- data/lib/spoom/deadcode/plugins/rails.rb +31 -0
- data/lib/spoom/deadcode/plugins/rake.rb +12 -0
- data/lib/spoom/deadcode/plugins/rspec.rb +19 -0
- data/lib/spoom/deadcode/plugins/rubocop.rb +41 -0
- data/lib/spoom/deadcode/plugins/ruby.rb +65 -0
- data/lib/spoom/deadcode/plugins/sorbet.rb +46 -0
- data/lib/spoom/deadcode/plugins/thor.rb +21 -0
- data/lib/spoom/deadcode/plugins.rb +95 -0
- data/lib/spoom/deadcode/remover.rb +616 -0
- data/lib/spoom/deadcode/send.rb +22 -0
- data/lib/spoom/deadcode.rb +8 -6
- 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
- metadata +24 -18
@@ -0,0 +1,354 @@
|
|
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 classes 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_class_names(
|
27
|
+
# "Foo",
|
28
|
+
# "Bar",
|
29
|
+
# /Baz.*/,
|
30
|
+
# )
|
31
|
+
# end
|
32
|
+
# ~~~
|
33
|
+
sig { params(names: T.any(String, Regexp)).void }
|
34
|
+
def ignore_classes_named(*names)
|
35
|
+
save_names_and_patterns(names, :@ignored_class_names, :@ignored_class_patterns)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Mark classes directly subclassing a class matching `names` as ignored.
|
39
|
+
#
|
40
|
+
# Names can be either strings or regexps:
|
41
|
+
#
|
42
|
+
# ~~~rb
|
43
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
44
|
+
# ignore_classes_inheriting_from(
|
45
|
+
# "Foo",
|
46
|
+
# "Bar",
|
47
|
+
# /Baz.*/,
|
48
|
+
# )
|
49
|
+
# end
|
50
|
+
# ~~~
|
51
|
+
sig { params(names: T.any(String, Regexp)).void }
|
52
|
+
def ignore_classes_inheriting_from(*names)
|
53
|
+
save_names_and_patterns(names, :@ignored_subclasses_of_names, :@ignored_subclasses_of_patterns)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Mark constants matching `names` as ignored.
|
57
|
+
#
|
58
|
+
# Names can be either strings or regexps:
|
59
|
+
#
|
60
|
+
# ~~~rb
|
61
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
62
|
+
# ignore_class_names(
|
63
|
+
# "FOO",
|
64
|
+
# "BAR",
|
65
|
+
# /BAZ.*/,
|
66
|
+
# )
|
67
|
+
# end
|
68
|
+
# ~~~
|
69
|
+
sig { params(names: T.any(String, Regexp)).void }
|
70
|
+
def ignore_constants_named(*names)
|
71
|
+
save_names_and_patterns(names, :@ignored_constant_names, :@ignored_constant_patterns)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Mark methods matching `names` as ignored.
|
75
|
+
#
|
76
|
+
# Names can be either strings or regexps:
|
77
|
+
#
|
78
|
+
# ~~~rb
|
79
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
80
|
+
# ignore_method_names(
|
81
|
+
# "foo",
|
82
|
+
# "bar",
|
83
|
+
# /baz.*/,
|
84
|
+
# )
|
85
|
+
# end
|
86
|
+
# ~~~
|
87
|
+
sig { params(names: T.any(String, Regexp)).void }
|
88
|
+
def ignore_methods_named(*names)
|
89
|
+
save_names_and_patterns(names, :@ignored_method_names, :@ignored_method_patterns)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Mark modules matching `names` as ignored.
|
93
|
+
#
|
94
|
+
# Names can be either strings or regexps:
|
95
|
+
#
|
96
|
+
# ~~~rb
|
97
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
98
|
+
# ignore_class_names(
|
99
|
+
# "Foo",
|
100
|
+
# "Bar",
|
101
|
+
# /Baz.*/,
|
102
|
+
# )
|
103
|
+
# end
|
104
|
+
# ~~~
|
105
|
+
sig { params(names: T.any(String, Regexp)).void }
|
106
|
+
def ignore_modules_named(*names)
|
107
|
+
save_names_and_patterns(names, :@ignored_module_names, :@ignored_module_patterns)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
sig { params(names: T::Array[T.any(String, Regexp)], names_variable: Symbol, patterns_variable: Symbol).void }
|
113
|
+
def save_names_and_patterns(names, names_variable, patterns_variable)
|
114
|
+
ignored_names = instance_variable_set(names_variable, Set.new)
|
115
|
+
ignored_patterns = instance_variable_set(patterns_variable, [])
|
116
|
+
|
117
|
+
names.each do |name|
|
118
|
+
case name
|
119
|
+
when String
|
120
|
+
ignored_names << name.delete_prefix("::")
|
121
|
+
when Regexp
|
122
|
+
ignored_patterns << name
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Indexing event methods
|
129
|
+
|
130
|
+
# Called when an accessor is defined.
|
131
|
+
#
|
132
|
+
# Will be called when the indexer processes a `attr_reader`, `attr_writer` or `attr_accessor` node.
|
133
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
134
|
+
# It is still possible to ignore it from the plugin:
|
135
|
+
#
|
136
|
+
# ~~~rb
|
137
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
138
|
+
# def on_define_accessor(indexer, definition)
|
139
|
+
# definition.ignored! if definition.name == "foo"
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
# ~~~
|
143
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
144
|
+
def on_define_accessor(indexer, definition)
|
145
|
+
# no-op
|
146
|
+
end
|
147
|
+
|
148
|
+
# Do not override this method, use `on_define_accessor` instead.
|
149
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
150
|
+
def internal_on_define_accessor(indexer, definition)
|
151
|
+
on_define_accessor(indexer, definition)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Called when a class is defined.
|
155
|
+
#
|
156
|
+
# Will be called when the indexer processes a `class` node.
|
157
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
158
|
+
# It is still possible to ignore it from the plugin:
|
159
|
+
#
|
160
|
+
# ~~~rb
|
161
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
162
|
+
# def on_define_class(indexer, definition)
|
163
|
+
# definition.ignored! if definition.name == "Foo"
|
164
|
+
# end
|
165
|
+
# end
|
166
|
+
# ~~~
|
167
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
168
|
+
def on_define_class(indexer, definition)
|
169
|
+
# no-op
|
170
|
+
end
|
171
|
+
|
172
|
+
# Do not override this method, use `on_define_class` instead.
|
173
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
174
|
+
def internal_on_define_class(indexer, definition)
|
175
|
+
if ignored_class_name?(definition.name)
|
176
|
+
definition.ignored!
|
177
|
+
elsif ignored_subclass?(indexer.nesting_class_superclass_name)
|
178
|
+
definition.ignored!
|
179
|
+
end
|
180
|
+
|
181
|
+
on_define_class(indexer, definition)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Called when a constant is defined.
|
185
|
+
#
|
186
|
+
# Will be called when the indexer processes a `CONST =` node.
|
187
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
188
|
+
# It is still possible to ignore it from the plugin:
|
189
|
+
#
|
190
|
+
# ~~~rb
|
191
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
192
|
+
# def on_define_constant(indexer, definition)
|
193
|
+
# definition.ignored! if definition.name == "FOO"
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
# ~~~
|
197
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
198
|
+
def on_define_constant(indexer, definition)
|
199
|
+
# no-op
|
200
|
+
end
|
201
|
+
|
202
|
+
# Do not override this method, use `on_define_constant` instead.
|
203
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
204
|
+
def internal_on_define_constant(indexer, definition)
|
205
|
+
definition.ignored! if ignored_constant_name?(definition.name)
|
206
|
+
|
207
|
+
on_define_constant(indexer, definition)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Called when a method is defined.
|
211
|
+
#
|
212
|
+
# Will be called when the indexer processes a `def` or `defs` node.
|
213
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
214
|
+
# It is still possible to ignore it from the plugin:
|
215
|
+
#
|
216
|
+
# ~~~rb
|
217
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
218
|
+
# def on_define_method(indexer, definition)
|
219
|
+
# super # So the `ignore_method_names` DSL is still applied
|
220
|
+
#
|
221
|
+
# definition.ignored! if definition.name == "foo"
|
222
|
+
# end
|
223
|
+
# end
|
224
|
+
# ~~~
|
225
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
226
|
+
def on_define_method(indexer, definition)
|
227
|
+
# no-op
|
228
|
+
end
|
229
|
+
|
230
|
+
# Do not override this method, use `on_define_method` instead.
|
231
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
232
|
+
def internal_on_define_method(indexer, definition)
|
233
|
+
definition.ignored! if ignored_method_name?(definition.name)
|
234
|
+
|
235
|
+
on_define_method(indexer, definition)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Called when a module is defined.
|
239
|
+
#
|
240
|
+
# Will be called when the indexer processes a `module` node.
|
241
|
+
# Note that when this method is called, the definition for the node has already been added to the index.
|
242
|
+
# It is still possible to ignore it from the plugin:
|
243
|
+
#
|
244
|
+
# ~~~rb
|
245
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
246
|
+
# def on_define_module(indexer, definition)
|
247
|
+
# definition.ignored! if definition.name == "Foo"
|
248
|
+
# end
|
249
|
+
# end
|
250
|
+
# ~~~
|
251
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
252
|
+
def on_define_module(indexer, definition)
|
253
|
+
# no-op
|
254
|
+
end
|
255
|
+
|
256
|
+
# Do not override this method, use `on_define_module` instead.
|
257
|
+
sig { params(indexer: Indexer, definition: Definition).void }
|
258
|
+
def internal_on_define_module(indexer, definition)
|
259
|
+
definition.ignored! if ignored_module_name?(definition.name)
|
260
|
+
|
261
|
+
on_define_module(indexer, definition)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Called when a send is being processed
|
265
|
+
#
|
266
|
+
# ~~~rb
|
267
|
+
# class MyPlugin < Spoom::Deadcode::Plugins::Base
|
268
|
+
# def on_send(indexer, send)
|
269
|
+
# return unless send.name == "dsl_method"
|
270
|
+
# return if send.args.empty?
|
271
|
+
#
|
272
|
+
# method_name = indexer.node_string(send.args.first).delete_prefix(":")
|
273
|
+
# indexer.reference_method(method_name, send.node)
|
274
|
+
# end
|
275
|
+
# end
|
276
|
+
# ~~~
|
277
|
+
sig { params(indexer: Indexer, send: Send).void }
|
278
|
+
def on_send(indexer, send)
|
279
|
+
# no-op
|
280
|
+
end
|
281
|
+
|
282
|
+
# Do not override this method, use `on_send` instead.
|
283
|
+
sig { params(indexer: Indexer, send: Send).void }
|
284
|
+
def internal_on_send(indexer, send)
|
285
|
+
on_send(indexer, send)
|
286
|
+
end
|
287
|
+
|
288
|
+
private
|
289
|
+
|
290
|
+
# DSL support
|
291
|
+
|
292
|
+
sig { params(name: T.nilable(String)).returns(T::Boolean) }
|
293
|
+
def ignored_class_name?(name)
|
294
|
+
return false unless name
|
295
|
+
|
296
|
+
ignored_name?(name, :@ignored_class_names, :@ignored_class_patterns)
|
297
|
+
end
|
298
|
+
|
299
|
+
sig { params(superclass_name: T.nilable(String)).returns(T::Boolean) }
|
300
|
+
def ignored_subclass?(superclass_name)
|
301
|
+
return false unless superclass_name
|
302
|
+
|
303
|
+
ignored_name?(superclass_name, :@ignored_subclasses_of_names, :@ignored_subclasses_of_patterns)
|
304
|
+
end
|
305
|
+
|
306
|
+
sig { params(name: String).returns(T::Boolean) }
|
307
|
+
def ignored_constant_name?(name)
|
308
|
+
ignored_name?(name, :@ignored_constant_names, :@ignored_constant_patterns)
|
309
|
+
end
|
310
|
+
|
311
|
+
sig { params(name: String).returns(T::Boolean) }
|
312
|
+
def ignored_method_name?(name)
|
313
|
+
ignored_name?(name, :@ignored_method_names, :@ignored_method_patterns)
|
314
|
+
end
|
315
|
+
|
316
|
+
sig { params(name: String).returns(T::Boolean) }
|
317
|
+
def ignored_module_name?(name)
|
318
|
+
ignored_name?(name, :@ignored_module_names, :@ignored_module_patterns)
|
319
|
+
end
|
320
|
+
|
321
|
+
sig { params(name: String, names_variable: Symbol, patterns_variable: Symbol).returns(T::Boolean) }
|
322
|
+
def ignored_name?(name, names_variable, patterns_variable)
|
323
|
+
names(names_variable).include?(name) || patterns(patterns_variable).any? { |pattern| pattern.match?(name) }
|
324
|
+
end
|
325
|
+
|
326
|
+
sig { params(const: Symbol).returns(T::Set[String]) }
|
327
|
+
def names(const)
|
328
|
+
self.class.instance_variable_get(const) || Set.new
|
329
|
+
end
|
330
|
+
|
331
|
+
sig { params(const: Symbol).returns(T::Array[Regexp]) }
|
332
|
+
def patterns(const)
|
333
|
+
self.class.instance_variable_get(const) || []
|
334
|
+
end
|
335
|
+
|
336
|
+
# Plugin utils
|
337
|
+
|
338
|
+
sig { params(name: String).returns(String) }
|
339
|
+
def camelize(name)
|
340
|
+
name = T.must(name.split("::").last)
|
341
|
+
name = T.must(name.split("/").last)
|
342
|
+
name = name.gsub(/[^a-zA-Z0-9_]/, "")
|
343
|
+
name = name.sub(/^[a-z\d]*/, &:capitalize)
|
344
|
+
name = name.gsub(%r{(?:_|(/))([a-z\d]*)}) do
|
345
|
+
s1 = Regexp.last_match(1)
|
346
|
+
s2 = Regexp.last_match(2)
|
347
|
+
"#{s1}#{s2&.capitalize}"
|
348
|
+
end
|
349
|
+
name
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class GraphQL < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ignore_classes_inheriting_from(
|
11
|
+
/^(::)?GraphQL::Schema::Enum$/,
|
12
|
+
/^(::)?GraphQL::Schema::Object$/,
|
13
|
+
/^(::)?GraphQL::Schema::Scalar$/,
|
14
|
+
/^(::)?GraphQL::Schema::Union$/,
|
15
|
+
)
|
16
|
+
|
17
|
+
ignore_methods_named(
|
18
|
+
"coerce_input",
|
19
|
+
"coerce_result",
|
20
|
+
"graphql_name",
|
21
|
+
"resolve",
|
22
|
+
"resolve_type",
|
23
|
+
"subscribed",
|
24
|
+
"unsubscribed",
|
25
|
+
)
|
26
|
+
|
27
|
+
sig { override.params(indexer: Indexer, send: Send).void }
|
28
|
+
def on_send(indexer, send)
|
29
|
+
return unless send.recv.nil? && send.name == "field"
|
30
|
+
|
31
|
+
arg = send.args.first
|
32
|
+
return unless arg.is_a?(SyntaxTree::SymbolLiteral)
|
33
|
+
|
34
|
+
indexer.reference_method(indexer.node_string(arg.value), send.node)
|
35
|
+
|
36
|
+
send.each_arg_assoc do |key, value|
|
37
|
+
key = indexer.node_string(key).delete_suffix(":")
|
38
|
+
next unless key == "resolver_method"
|
39
|
+
next unless value
|
40
|
+
|
41
|
+
indexer.reference_method(indexer.symbol_string(value), send.node)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class Minitest < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ignore_classes_named(/Test$/)
|
11
|
+
|
12
|
+
ignore_methods_named(
|
13
|
+
"after_all",
|
14
|
+
"around",
|
15
|
+
"around_all",
|
16
|
+
"before_all",
|
17
|
+
"setup",
|
18
|
+
"teardown",
|
19
|
+
)
|
20
|
+
|
21
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
22
|
+
def on_define_method(indexer, definition)
|
23
|
+
definition.ignored! if indexer.path.match?(%r{test/.*test\.rb$}) && definition.name.match?(/^test_/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class Namespaces < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
11
|
+
def on_define_class(indexer, definition)
|
12
|
+
definition.ignored! if used_as_namespace?(indexer)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
16
|
+
def on_define_module(indexer, definition)
|
17
|
+
definition.ignored! if used_as_namespace?(indexer)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
sig { params(indexer: Indexer).returns(T::Boolean) }
|
23
|
+
def used_as_namespace?(indexer)
|
24
|
+
node = indexer.current_node
|
25
|
+
return false unless node.is_a?(SyntaxTree::ClassDeclaration) || node.is_a?(SyntaxTree::ModuleDeclaration)
|
26
|
+
|
27
|
+
node.bodystmt.statements.body.any? do |stmt|
|
28
|
+
!stmt.is_a?(SyntaxTree::VoidStmt)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class Rails < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ignore_constants_named("APP_PATH", "ENGINE_PATH", "ENGINE_ROOT")
|
11
|
+
|
12
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
13
|
+
def on_define_class(indexer, definition)
|
14
|
+
definition.ignored! if file_is_helper?(indexer)
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
18
|
+
def on_define_module(indexer, definition)
|
19
|
+
definition.ignored! if file_is_helper?(indexer)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
sig { params(indexer: Indexer).returns(T::Boolean) }
|
25
|
+
def file_is_helper?(indexer)
|
26
|
+
indexer.path.match?(%r{app/helpers/.*\.rb$})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class RSpec < Base
|
8
|
+
ignore_classes_named(/Spec$/)
|
9
|
+
|
10
|
+
ignore_methods_named(
|
11
|
+
"after_setup",
|
12
|
+
"after_teardown",
|
13
|
+
"before_setup",
|
14
|
+
"before_teardown",
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class Rubocop < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
RUBOCOP_CONSTANTS = T.let(["MSG", "RESTRICT_ON_SEND"].to_set.freeze, T::Set[String])
|
11
|
+
|
12
|
+
ignore_classes_inheriting_from(
|
13
|
+
/^(::)?RuboCop::Cop::Cop$/,
|
14
|
+
/^(::)?RuboCop::Cop::Base$/,
|
15
|
+
)
|
16
|
+
|
17
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
18
|
+
def on_define_constant(indexer, definition)
|
19
|
+
definition.ignored! if rubocop_constant?(indexer, definition)
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
23
|
+
def on_define_method(indexer, definition)
|
24
|
+
definition.ignored! if rubocop_method?(indexer, definition)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
|
30
|
+
def rubocop_constant?(indexer, definition)
|
31
|
+
ignored_subclass?(indexer.nesting_class_superclass_name) && RUBOCOP_CONSTANTS.include?(definition.name)
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
|
35
|
+
def rubocop_method?(indexer, definition)
|
36
|
+
ignored_subclass?(indexer.nesting_class_superclass_name) && definition.name == "on_send"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class Ruby < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ignore_methods_named(
|
11
|
+
"==",
|
12
|
+
"extended",
|
13
|
+
"included",
|
14
|
+
"inherited",
|
15
|
+
"initialize",
|
16
|
+
"method_added",
|
17
|
+
"method_missing",
|
18
|
+
"prepended",
|
19
|
+
"respond_to_missing?",
|
20
|
+
"to_s",
|
21
|
+
)
|
22
|
+
|
23
|
+
sig { override.params(indexer: Indexer, send: Send).void }
|
24
|
+
def on_send(indexer, send)
|
25
|
+
case send.name
|
26
|
+
when "const_defined?", "const_get", "const_source_location"
|
27
|
+
reference_symbol_as_constant(indexer, send, T.must(send.args.first))
|
28
|
+
when "send", "__send__", "try"
|
29
|
+
arg = send.args.first
|
30
|
+
indexer.reference_method(indexer.node_string(arg.value), send.node) if arg.is_a?(SyntaxTree::SymbolLiteral)
|
31
|
+
when "alias_method"
|
32
|
+
last_arg = send.args.last
|
33
|
+
|
34
|
+
name = case last_arg
|
35
|
+
when SyntaxTree::SymbolLiteral
|
36
|
+
indexer.node_string(last_arg.value)
|
37
|
+
when SyntaxTree::StringLiteral
|
38
|
+
last_arg.parts.map { |part| indexer.node_string(part) }.join
|
39
|
+
end
|
40
|
+
|
41
|
+
return unless name
|
42
|
+
|
43
|
+
indexer.reference_method(name, send.node)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
sig { params(indexer: Indexer, send: Send, node: SyntaxTree::Node).void }
|
50
|
+
def reference_symbol_as_constant(indexer, send, node)
|
51
|
+
case node
|
52
|
+
when SyntaxTree::SymbolLiteral
|
53
|
+
name = indexer.node_string(node.value)
|
54
|
+
indexer.reference_constant(name, send.node)
|
55
|
+
when SyntaxTree::StringLiteral
|
56
|
+
string = T.must(indexer.node_string(node)[1..-2])
|
57
|
+
string.split("::").each do |name|
|
58
|
+
indexer.reference_constant(name, send.node) unless name.empty?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class Sorbet < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
11
|
+
def on_define_constant(indexer, definition)
|
12
|
+
definition.ignored! if sorbet_type_member?(indexer, definition) || sorbet_enum_constant?(indexer, definition)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
16
|
+
def on_define_method(indexer, definition)
|
17
|
+
definition.ignored! if indexer.last_sig =~ /(override|overridable)/
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
|
23
|
+
def sorbet_type_member?(indexer, definition)
|
24
|
+
assign = indexer.nesting_node(SyntaxTree::Assign)
|
25
|
+
return false unless assign
|
26
|
+
|
27
|
+
value = assign.value
|
28
|
+
|
29
|
+
case value
|
30
|
+
when SyntaxTree::MethodAddBlock
|
31
|
+
indexer.node_string(value.call).match?(/^(type_member|type_template)/)
|
32
|
+
when SyntaxTree::VCall
|
33
|
+
indexer.node_string(value.value).match?(/^(type_member|type_template)/)
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
|
40
|
+
def sorbet_enum_constant?(indexer, definition)
|
41
|
+
/^(::)?T::Enum$/.match?(indexer.nesting_class_superclass_name) && indexer.nesting_block_call_name == "enums"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|