spoom 1.2.3 → 1.3.0

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