spoom 1.2.2 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
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