spoom 1.2.3 → 1.3.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +64 -55
  3. data/lib/spoom/backtrace_filter/minitest.rb +21 -0
  4. data/lib/spoom/cli/deadcode.rb +172 -0
  5. data/lib/spoom/cli/helper.rb +20 -0
  6. data/lib/spoom/cli/srb/bump.rb +200 -0
  7. data/lib/spoom/cli/srb/coverage.rb +224 -0
  8. data/lib/spoom/cli/srb/lsp.rb +159 -0
  9. data/lib/spoom/cli/srb/tc.rb +150 -0
  10. data/lib/spoom/cli/srb.rb +27 -0
  11. data/lib/spoom/cli.rb +72 -32
  12. data/lib/spoom/context/git.rb +2 -2
  13. data/lib/spoom/context/sorbet.rb +2 -2
  14. data/lib/spoom/deadcode/definition.rb +11 -0
  15. data/lib/spoom/deadcode/erb.rb +4 -4
  16. data/lib/spoom/deadcode/indexer.rb +266 -200
  17. data/lib/spoom/deadcode/location.rb +30 -2
  18. data/lib/spoom/deadcode/plugins/action_mailer.rb +21 -0
  19. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
  20. data/lib/spoom/deadcode/plugins/actionpack.rb +59 -0
  21. data/lib/spoom/deadcode/plugins/active_job.rb +13 -0
  22. data/lib/spoom/deadcode/plugins/active_model.rb +46 -0
  23. data/lib/spoom/deadcode/plugins/active_record.rb +108 -0
  24. data/lib/spoom/deadcode/plugins/active_support.rb +32 -0
  25. data/lib/spoom/deadcode/plugins/base.rb +165 -12
  26. data/lib/spoom/deadcode/plugins/graphql.rb +47 -0
  27. data/lib/spoom/deadcode/plugins/minitest.rb +28 -0
  28. data/lib/spoom/deadcode/plugins/namespaces.rb +32 -0
  29. data/lib/spoom/deadcode/plugins/rails.rb +31 -0
  30. data/lib/spoom/deadcode/plugins/rake.rb +12 -0
  31. data/lib/spoom/deadcode/plugins/rspec.rb +19 -0
  32. data/lib/spoom/deadcode/plugins/rubocop.rb +41 -0
  33. data/lib/spoom/deadcode/plugins/ruby.rb +10 -18
  34. data/lib/spoom/deadcode/plugins/sorbet.rb +40 -0
  35. data/lib/spoom/deadcode/plugins/thor.rb +21 -0
  36. data/lib/spoom/deadcode/plugins.rb +91 -0
  37. data/lib/spoom/deadcode/remover.rb +651 -0
  38. data/lib/spoom/deadcode/send.rb +27 -6
  39. data/lib/spoom/deadcode/visitor.rb +755 -0
  40. data/lib/spoom/deadcode.rb +41 -10
  41. data/lib/spoom/file_tree.rb +0 -16
  42. data/lib/spoom/sorbet/errors.rb +1 -1
  43. data/lib/spoom/sorbet/lsp/structures.rb +2 -2
  44. data/lib/spoom/version.rb +1 -1
  45. metadata +36 -15
  46. data/lib/spoom/cli/bump.rb +0 -198
  47. data/lib/spoom/cli/coverage.rb +0 -222
  48. data/lib/spoom/cli/lsp.rb +0 -168
  49. data/lib/spoom/cli/run.rb +0 -148
@@ -13,8 +13,25 @@ module Spoom
13
13
  class << self
14
14
  extend T::Sig
15
15
 
16
- sig { params(file: String, location: SyntaxTree::Location).returns(Location) }
17
- def from_syntax_tree(file, location)
16
+ sig { params(location_string: String).returns(Location) }
17
+ def from_string(location_string)
18
+ file, rest = location_string.split(":", 2)
19
+ raise LocationError, "Invalid location string: #{location_string}" unless file && rest
20
+
21
+ start_line, rest = rest.split(":", 2)
22
+ raise LocationError, "Invalid location string: #{location_string}" unless start_line && rest
23
+
24
+ start_column, rest = rest.split("-", 2)
25
+ raise LocationError, "Invalid location string: #{location_string}" unless start_column && rest
26
+
27
+ end_line, end_column = rest.split(":", 2)
28
+ raise LocationError, "Invalid location string: #{location_string}" unless end_line && end_column
29
+
30
+ new(file, start_line.to_i, start_column.to_i, end_line.to_i, end_column.to_i)
31
+ end
32
+
33
+ sig { params(file: String, location: Prism::Location).returns(Location) }
34
+ def from_prism(file, location)
18
35
  new(file, location.start_line, location.start_column, location.end_line, location.end_column)
19
36
  end
20
37
  end
@@ -42,6 +59,17 @@ module Spoom
42
59
  @end_column = end_column
43
60
  end
44
61
 
62
+ sig { params(other: Location).returns(T::Boolean) }
63
+ def include?(other)
64
+ return false unless @file == other.file
65
+ return false if @start_line > other.start_line
66
+ return false if @start_line == other.start_line && @start_column > other.start_column
67
+ return false if @end_line < other.end_line
68
+ return false if @end_line == other.end_line && @end_column < other.end_column
69
+
70
+ true
71
+ end
72
+
45
73
  sig { override.params(other: BasicObject).returns(T.nilable(Integer)) }
46
74
  def <=>(other)
47
75
  return unless Location === other
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActionMailer < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(indexer: Indexer, send: Send).void }
11
+ def on_send(indexer, send)
12
+ return unless send.recv.nil? && ActionPack::CALLBACKS.include?(send.name)
13
+
14
+ send.each_arg(Prism::SymbolNode) do |arg|
15
+ indexer.reference_method(arg.unescaped, send.node)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ 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 ActionMailerPreview < Base
8
+ extend T::Sig
9
+
10
+ ignore_classes_inheriting_from("ActionMailer::Preview")
11
+
12
+ sig { override.params(indexer: Indexer, definition: Definition).void }
13
+ def on_define_method(indexer, definition)
14
+ definition.ignored! if indexer.nesting_class_superclass_name == "ActionMailer::Preview"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActionPack < Base
8
+ extend T::Sig
9
+
10
+ CALLBACKS = T.let(
11
+ [
12
+ "after_action",
13
+ "append_after_action",
14
+ "append_around_action",
15
+ "append_before_action",
16
+ "around_action",
17
+ "before_action",
18
+ "prepend_after_action",
19
+ "prepend_around_action",
20
+ "prepend_before_action",
21
+ "skip_after_action",
22
+ "skip_around_action",
23
+ "skip_before_action",
24
+ ].freeze,
25
+ T::Array[String],
26
+ )
27
+
28
+ ignore_classes_named(/Controller$/)
29
+
30
+ sig { override.params(indexer: Indexer, definition: Definition).void }
31
+ def on_define_method(indexer, definition)
32
+ definition.ignored! if ignored_class_name?(indexer.nesting_class_name)
33
+ end
34
+
35
+ sig { override.params(indexer: Indexer, send: Send).void }
36
+ def on_send(indexer, send)
37
+ return unless send.recv.nil? && CALLBACKS.include?(send.name)
38
+
39
+ arg = send.args.first
40
+ case arg
41
+ when Prism::SymbolNode
42
+ indexer.reference_method(arg.unescaped, send.node)
43
+ end
44
+
45
+ send.each_arg_assoc do |key, value|
46
+ key = key.slice.delete_suffix(":")
47
+
48
+ case key
49
+ when "if", "unless"
50
+ indexer.reference_method(value.slice.delete_prefix(":"), send.node) if value
51
+ else
52
+ indexer.reference_constant(camelize(key), send.node)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActiveJob < Base
8
+ ignore_classes_named("ApplicationJob")
9
+ ignore_methods_named("perform", "build_enumerator", "each_iteration")
10
+ end
11
+ end
12
+ end
13
+ 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 ActiveModel < Base
8
+ extend T::Sig
9
+
10
+ ignore_classes_inheriting_from(/^(::)?ActiveModel::EachValidator$/)
11
+ ignore_methods_named("validate_each")
12
+
13
+ sig { override.params(indexer: Indexer, send: Send).void }
14
+ def on_send(indexer, send)
15
+ return if send.recv
16
+
17
+ case send.name
18
+ when "attribute", "attributes"
19
+ send.each_arg(Prism::SymbolNode) do |arg|
20
+ indexer.reference_method(arg.unescaped, send.node)
21
+ end
22
+ when "validate", "validates", "validates!", "validates_each"
23
+ send.each_arg(Prism::SymbolNode) do |arg|
24
+ indexer.reference_method(arg.unescaped, send.node)
25
+ end
26
+ send.each_arg_assoc do |key, value|
27
+ key = key.slice.delete_suffix(":")
28
+
29
+ case key
30
+ when "if", "unless"
31
+ indexer.reference_method(value.slice.delete_prefix(":"), send.node) if value
32
+ else
33
+ indexer.reference_constant(camelize(key), send.node)
34
+ end
35
+ end
36
+ when "validates_with"
37
+ arg = send.args.first
38
+ if arg.is_a?(Prism::SymbolNode)
39
+ indexer.reference_constant(arg.unescaped, send.node)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,108 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActiveRecord < Base
8
+ extend T::Sig
9
+
10
+ ignore_classes_inheriting_from(/^(::)?ActiveRecord::Migration/)
11
+
12
+ ignore_methods_named(
13
+ "change",
14
+ "down",
15
+ "up",
16
+ "table_name_prefix",
17
+ "to_param",
18
+ )
19
+
20
+ CALLBACKS = T.let(
21
+ [
22
+ "after_commit",
23
+ "after_create_commit",
24
+ "after_create",
25
+ "after_destroy_commit",
26
+ "after_destroy",
27
+ "after_find",
28
+ "after_initialize",
29
+ "after_rollback",
30
+ "after_save_commit",
31
+ "after_save",
32
+ "after_touch",
33
+ "after_update_commit",
34
+ "after_update",
35
+ "after_validation",
36
+ "around_create",
37
+ "around_destroy",
38
+ "around_save",
39
+ "around_update",
40
+ "before_create",
41
+ "before_destroy",
42
+ "before_save",
43
+ "before_update",
44
+ "before_validation",
45
+ ].freeze,
46
+ T::Array[String],
47
+ )
48
+
49
+ CRUD_METHODS = T.let(
50
+ [
51
+ "assign_attributes",
52
+ "create",
53
+ "create!",
54
+ "insert",
55
+ "insert!",
56
+ "new",
57
+ "update",
58
+ "update!",
59
+ "upsert",
60
+ ].freeze,
61
+ T::Array[String],
62
+ )
63
+
64
+ ARRAY_METHODS = T.let(
65
+ [
66
+ "insert_all",
67
+ "insert_all!",
68
+ "upsert_all",
69
+ ].freeze,
70
+ T::Array[String],
71
+ )
72
+
73
+ sig { override.params(indexer: Indexer, send: Send).void }
74
+ def on_send(indexer, send)
75
+ if send.recv.nil? && CALLBACKS.include?(send.name)
76
+ send.each_arg(Prism::SymbolNode) do |arg|
77
+ indexer.reference_method(arg.unescaped, send.node)
78
+ end
79
+ return
80
+ end
81
+
82
+ return unless send.recv
83
+
84
+ case send.name
85
+ when *CRUD_METHODS
86
+ send.each_arg_assoc do |key, _value|
87
+ key = key.slice.delete_suffix(":")
88
+ indexer.reference_method("#{key}=", send.node)
89
+ end
90
+ when *ARRAY_METHODS
91
+ send.each_arg(Prism::ArrayNode) do |arg|
92
+ arg.elements.each do |part|
93
+ next unless part.is_a?(Prism::HashNode)
94
+
95
+ part.elements.each do |assoc|
96
+ next unless assoc.is_a?(Prism::AssocNode)
97
+
98
+ key = assoc.key.slice.delete_suffix(":")
99
+ indexer.reference_method("#{key}=", send.node)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActiveSupport < Base
8
+ ignore_classes_inheriting_from(/^(::)?ActiveSupport::TestCase$/)
9
+
10
+ ignore_methods_named(
11
+ "after_all",
12
+ "after_setup",
13
+ "after_teardown",
14
+ "before_all",
15
+ "before_setup",
16
+ "before_teardown",
17
+ )
18
+
19
+ SETUP_AND_TEARDOWN_METHODS = T.let(["setup", "teardown"], T::Array[String])
20
+
21
+ sig { override.params(indexer: Indexer, send: Send).void }
22
+ def on_send(indexer, send)
23
+ return unless send.recv.nil? && SETUP_AND_TEARDOWN_METHODS.include?(send.name)
24
+
25
+ send.each_arg(Prism::SymbolNode) do |arg|
26
+ indexer.reference_method(T.must(arg.value), send.node)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -17,6 +17,60 @@ module Spoom
17
17
 
18
18
  # Plugins DSL
19
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
+
20
74
  # Mark methods matching `names` as ignored.
21
75
  #
22
76
  # Names can be either strings or regexps:
@@ -31,10 +85,28 @@ module Spoom
31
85
  # end
32
86
  # ~~~
33
87
  sig { params(names: T.any(String, Regexp)).void }
34
- def ignore_method_names(*names)
88
+ def ignore_methods_named(*names)
35
89
  save_names_and_patterns(names, :@ignored_method_names, :@ignored_method_patterns)
36
90
  end
37
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
+
38
110
  private
39
111
 
40
112
  sig { params(names: T::Array[T.any(String, Regexp)], names_variable: Symbol, patterns_variable: Symbol).void }
@@ -45,7 +117,7 @@ module Spoom
45
117
  names.each do |name|
46
118
  case name
47
119
  when String
48
- ignored_names << name
120
+ ignored_names << name.delete_prefix("::")
49
121
  when Regexp
50
122
  ignored_patterns << name
51
123
  end
@@ -73,6 +145,12 @@ module Spoom
73
145
  # no-op
74
146
  end
75
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
+
76
154
  # Called when a class is defined.
77
155
  #
78
156
  # Will be called when the indexer processes a `class` node.
@@ -91,6 +169,18 @@ module Spoom
91
169
  # no-op
92
170
  end
93
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
+
94
184
  # Called when a constant is defined.
95
185
  #
96
186
  # Will be called when the indexer processes a `CONST =` node.
@@ -109,6 +199,14 @@ module Spoom
109
199
  # no-op
110
200
  end
111
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
+
112
210
  # Called when a method is defined.
113
211
  #
114
212
  # Will be called when the indexer processes a `def` or `defs` node.
@@ -126,7 +224,15 @@ module Spoom
126
224
  # ~~~
127
225
  sig { params(indexer: Indexer, definition: Definition).void }
128
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)
129
233
  definition.ignored! if ignored_method_name?(definition.name)
234
+
235
+ on_define_method(indexer, definition)
130
236
  end
131
237
 
132
238
  # Called when a module is defined.
@@ -147,6 +253,14 @@ module Spoom
147
253
  # no-op
148
254
  end
149
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
+
150
264
  # Called when a send is being processed
151
265
  #
152
266
  # ~~~rb
@@ -155,7 +269,7 @@ module Spoom
155
269
  # return unless send.name == "dsl_method"
156
270
  # return if send.args.empty?
157
271
  #
158
- # method_name = indexer.node_string(send.args.first).delete_prefix(":")
272
+ # method_name = send.args.first.slice.delete_prefix(":")
159
273
  # indexer.reference_method(method_name, send.node)
160
274
  # end
161
275
  # end
@@ -165,16 +279,43 @@ module Spoom
165
279
  # no-op
166
280
  end
167
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
+
168
288
  private
169
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
+
170
311
  sig { params(name: String).returns(T::Boolean) }
171
312
  def ignored_method_name?(name)
172
313
  ignored_name?(name, :@ignored_method_names, :@ignored_method_patterns)
173
314
  end
174
315
 
175
- sig { params(const: Symbol).returns(T::Set[String]) }
176
- def names(const)
177
- self.class.instance_variable_get(const) || Set.new
316
+ sig { params(name: String).returns(T::Boolean) }
317
+ def ignored_module_name?(name)
318
+ ignored_name?(name, :@ignored_module_names, :@ignored_module_patterns)
178
319
  end
179
320
 
180
321
  sig { params(name: String, names_variable: Symbol, patterns_variable: Symbol).returns(T::Boolean) }
@@ -182,18 +323,30 @@ module Spoom
182
323
  names(names_variable).include?(name) || patterns(patterns_variable).any? { |pattern| pattern.match?(name) }
183
324
  end
184
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
+
185
331
  sig { params(const: Symbol).returns(T::Array[Regexp]) }
186
332
  def patterns(const)
187
333
  self.class.instance_variable_get(const) || []
188
334
  end
189
335
 
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)
336
+ # Plugin utils
194
337
 
195
- name = indexer.node_string(first_arg.value)
196
- indexer.reference_method(name, send.node)
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
197
350
  end
198
351
  end
199
352
  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?(Prism::SymbolNode)
33
+
34
+ indexer.reference_method(arg.unescaped, send.node)
35
+
36
+ send.each_arg_assoc do |key, value|
37
+ key = key.slice.delete_suffix(":")
38
+ next unless key == "resolver_method"
39
+ next unless value
40
+
41
+ indexer.reference_method(value.slice.delete_prefix(":"), 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