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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -0
  3. data/lib/spoom/backtrace_filter/minitest.rb +21 -0
  4. data/lib/spoom/cli/coverage.rb +1 -1
  5. data/lib/spoom/context/git.rb +4 -4
  6. data/lib/spoom/context/sorbet.rb +6 -6
  7. data/lib/spoom/deadcode/erb.rb +4 -4
  8. data/lib/spoom/deadcode/indexer.rb +83 -6
  9. data/lib/spoom/deadcode/location.rb +29 -1
  10. data/lib/spoom/deadcode/plugins/action_mailer.rb +21 -0
  11. data/lib/spoom/deadcode/plugins/actionpack.rb +61 -0
  12. data/lib/spoom/deadcode/plugins/active_job.rb +13 -0
  13. data/lib/spoom/deadcode/plugins/active_model.rb +46 -0
  14. data/lib/spoom/deadcode/plugins/active_record.rb +111 -0
  15. data/lib/spoom/deadcode/plugins/active_support.rb +21 -0
  16. data/lib/spoom/deadcode/plugins/base.rb +354 -0
  17. data/lib/spoom/deadcode/plugins/graphql.rb +47 -0
  18. data/lib/spoom/deadcode/plugins/minitest.rb +28 -0
  19. data/lib/spoom/deadcode/plugins/namespaces.rb +34 -0
  20. data/lib/spoom/deadcode/plugins/rails.rb +31 -0
  21. data/lib/spoom/deadcode/plugins/rake.rb +12 -0
  22. data/lib/spoom/deadcode/plugins/rspec.rb +19 -0
  23. data/lib/spoom/deadcode/plugins/rubocop.rb +41 -0
  24. data/lib/spoom/deadcode/plugins/ruby.rb +65 -0
  25. data/lib/spoom/deadcode/plugins/sorbet.rb +46 -0
  26. data/lib/spoom/deadcode/plugins/thor.rb +21 -0
  27. data/lib/spoom/deadcode/plugins.rb +95 -0
  28. data/lib/spoom/deadcode/remover.rb +616 -0
  29. data/lib/spoom/deadcode/send.rb +22 -0
  30. data/lib/spoom/deadcode.rb +8 -6
  31. data/lib/spoom/sorbet/lsp.rb +2 -2
  32. data/lib/spoom/sorbet/sigils.rb +2 -2
  33. data/lib/spoom/sorbet.rb +1 -0
  34. data/lib/spoom/version.rb +1 -1
  35. 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,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Rake < Base
8
+ ignore_constants_named("APP_RAKEFILE")
9
+ end
10
+ end
11
+ end
12
+ 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