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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5154e37a81129c70e93925cc2a545a0d7861fd645a3ec2422e6549efc0cb76b5
4
- data.tar.gz: 30c9ecdc3599a37309180fea890a7888d53b430c8701e0970f503bf5731979f9
3
+ metadata.gz: 23df97e815cc01b35dbc960ec1921bbc1c65ae4c6f0588c690a0d9156877a013
4
+ data.tar.gz: d82fdb3fb66404b0c2d98ea9206be9d86ec2284bbae2ff24a5653a521ec9155e
5
5
  SHA512:
6
- metadata.gz: a3246a42fa965e3ef75d0178ffe958a8cc5723812ecb86799b5e2e406d5da7487ffd738696ccb3f6d76ae140de281dab73dba3ec3d8af9fdef696820654ed869
7
- data.tar.gz: ba4c8ab2d87ff4451f420e2d062c7cebdd19202bc91226fd131ff9ab9f838e7494fa43560ffa317fc0565d023ecceef184c02f762e215ba6451d3ec164e3c75a
6
+ metadata.gz: 67b6209ba61fbc285fc89dcf64adcde27cfecf44bb37d644cfb8aef50b7fca6e53a15e8eec5bc45030e4c838a35d3777c50a2d75fb32461792a1bc5957d7b2e3
7
+ data.tar.gz: c3e06e5140aac671ff8489243dee9feba3912ec12681332b2abb37ddd5c367246dc180ea80f6207afe28f81f499626f4dcf4b9628baa263854600d19d593f2f5
data/README.md CHANGED
@@ -348,6 +348,16 @@ Find all the symbols for a file:
348
348
  puts client.document_symbols("file://path/to/my/file.rb")
349
349
  ```
350
350
 
351
+ ### Backtrace Filtering
352
+
353
+ Spoom provides a backtrace filter for Minitest to remove the Sorbet frames from test failures, giving a more readable output. To enable it:
354
+
355
+ ```ruby
356
+ # test/test_helper.rb
357
+ require "spoom/backtrace_filter/minitest"
358
+ Minitest.backtrace_filter = Spoom::BacktraceFilter::Minitest.new
359
+ ```
360
+
351
361
  ## Development
352
362
 
353
363
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Don't forget to run `bin/sanity` before pushing your changes.
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "minitest"
5
+
6
+ module Spoom
7
+ module BacktraceFilter
8
+ class Minitest < ::Minitest::BacktraceFilter
9
+ extend T::Sig
10
+
11
+ SORBET_PATHS = T.let(Gem.loaded_specs["sorbet-runtime"].full_require_paths.freeze, T::Array[String])
12
+
13
+ sig { override.params(bt: T.nilable(T::Array[String])).returns(T::Array[String]) }
14
+ def filter(bt)
15
+ super.select do |line|
16
+ SORBET_PATHS.none? { |path| line.include?(path) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -187,7 +187,7 @@ module Spoom
187
187
 
188
188
  no_commands do
189
189
  def parse_time(string, option)
190
- return nil unless string
190
+ return unless string
191
191
 
192
192
  Time.parse(string)
193
193
  rescue ArgumentError
@@ -13,7 +13,7 @@ module Spoom
13
13
  sig { params(string: String).returns(T.nilable(Commit)) }
14
14
  def parse_line(string)
15
15
  sha, epoch = string.split(" ", 2)
16
- return nil unless sha && epoch
16
+ return unless sha && epoch
17
17
 
18
18
  time = Time.strptime(epoch, "%s")
19
19
  Commit.new(sha: sha, time: time)
@@ -88,7 +88,7 @@ module Spoom
88
88
  sig { returns(T.nilable(String)) }
89
89
  def git_current_branch
90
90
  res = git("branch --show-current")
91
- return nil unless res.status
91
+ return unless res.status
92
92
 
93
93
  res.out.strip
94
94
  end
@@ -103,10 +103,10 @@ module Spoom
103
103
  sig { params(short_sha: T::Boolean).returns(T.nilable(Spoom::Git::Commit)) }
104
104
  def git_last_commit(short_sha: true)
105
105
  res = git_log("HEAD --format='%#{short_sha ? "h" : "H"} %at' -1")
106
- return nil unless res.status
106
+ return unless res.status
107
107
 
108
108
  out = res.out.strip
109
- return nil if out.empty?
109
+ return if out.empty?
110
110
 
111
111
  Spoom::Git::Commit.parse_line(out)
112
112
  end
@@ -52,7 +52,7 @@ module Spoom
52
52
  sorbet_bin: sorbet_bin,
53
53
  capture_err: capture_err,
54
54
  )
55
- return nil unless file?(metrics_file)
55
+ return unless file?(metrics_file)
56
56
 
57
57
  metrics_path = absolute_path_to(metrics_file)
58
58
  metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
@@ -109,7 +109,7 @@ module Spoom
109
109
  sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(T.nilable(String)) }
110
110
  def srb_version(*arg, sorbet_bin: nil, capture_err: true)
111
111
  res = T.unsafe(self).srb_tc("--no-config", "--version", *arg, sorbet_bin: sorbet_bin, capture_err: capture_err)
112
- return nil unless res.status
112
+ return unless res.status
113
113
 
114
114
  res.out.split(" ")[2]
115
115
  end
@@ -147,10 +147,10 @@ module Spoom
147
147
  sig { returns(T.nilable(Spoom::Git::Commit)) }
148
148
  def sorbet_intro_commit
149
149
  res = git_log("--diff-filter=A --format='%h %at' -1 -- sorbet/config")
150
- return nil unless res.status
150
+ return unless res.status
151
151
 
152
152
  out = res.out.strip
153
- return nil if out.empty?
153
+ return if out.empty?
154
154
 
155
155
  Spoom::Git::Commit.parse_line(out)
156
156
  end
@@ -159,10 +159,10 @@ module Spoom
159
159
  sig { returns(T.nilable(Spoom::Git::Commit)) }
160
160
  def sorbet_removal_commit
161
161
  res = git_log("--diff-filter=D --format='%h %at' -1 -- sorbet/config")
162
- return nil unless res.status
162
+ return unless res.status
163
163
 
164
164
  out = res.out.strip
165
- return nil if out.empty?
165
+ return if out.empty?
166
166
 
167
167
  Spoom::Git::Commit.parse_line(out)
168
168
  end
@@ -44,7 +44,7 @@ module Spoom
44
44
 
45
45
  private
46
46
 
47
- sig { params(text: T.untyped).void }
47
+ sig { override.params(text: T.untyped).void }
48
48
  def add_text(text)
49
49
  return if text.empty?
50
50
 
@@ -62,7 +62,7 @@ module Spoom
62
62
 
63
63
  BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
64
64
 
65
- sig { params(indicator: T.untyped, code: T.untyped).void }
65
+ sig { override.params(indicator: T.untyped, code: T.untyped).void }
66
66
  def add_expression(indicator, code)
67
67
  flush_newline_if_pending(src)
68
68
 
@@ -79,13 +79,13 @@ module Spoom
79
79
  end
80
80
  end
81
81
 
82
- sig { params(code: T.untyped).void }
82
+ sig { override.params(code: T.untyped).void }
83
83
  def add_code(code)
84
84
  flush_newline_if_pending(src)
85
85
  super
86
86
  end
87
87
 
88
- sig { params(_: T.untyped).void }
88
+ sig { override.params(_: T.untyped).void }
89
89
  def add_postamble(_)
90
90
  flush_newline_if_pending(src)
91
91
  super
@@ -12,14 +12,15 @@ module Spoom
12
12
  sig { returns(Index) }
13
13
  attr_reader :index
14
14
 
15
- sig { params(path: String, source: String, index: Index).void }
16
- def initialize(path, source, index)
15
+ sig { params(path: String, source: String, index: Index, plugins: T::Array[Plugins::Base]).void }
16
+ def initialize(path, source, index, plugins: [])
17
17
  super()
18
18
 
19
19
  @path = path
20
20
  @file_name = T.let(File.basename(path), String)
21
21
  @source = source
22
22
  @index = index
23
+ @plugins = plugins
23
24
  @previous_node = T.let(nil, T.nilable(SyntaxTree::Node))
24
25
  @names_nesting = T.let([], T::Array[String])
25
26
  @nodes_nesting = T.let([], T::Array[SyntaxTree::Node])
@@ -156,10 +157,10 @@ module Spoom
156
157
 
157
158
  sig { override.params(node: SyntaxTree::DefNode).void }
158
159
  def visit_def(node)
159
- super
160
-
161
160
  name = node_string(node.name)
162
161
  define_method(name, [*@names_nesting, name].join("::"), node)
162
+
163
+ super
163
164
  end
164
165
 
165
166
  sig { override.params(node: SyntaxTree::Field).void }
@@ -228,6 +229,10 @@ module Spoom
228
229
  define_attr_writer("#{name}=", "#{full_name}=", arg)
229
230
  end
230
231
  else
232
+ @plugins.each do |plugin|
233
+ plugin.internal_on_send(self, send)
234
+ end
235
+
231
236
  reference_method(send.name, send.node)
232
237
  visit_all(send.args)
233
238
  visit(send.block)
@@ -270,8 +275,6 @@ module Spoom
270
275
  visit_send(Send.new(node: node, name: node_string(node.value)))
271
276
  end
272
277
 
273
- private
274
-
275
278
  # Definition indexing
276
279
 
277
280
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -283,6 +286,7 @@ module Spoom
283
286
  location: node_location(node),
284
287
  )
285
288
  @index.define(definition)
289
+ @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
286
290
  end
287
291
 
288
292
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -294,6 +298,7 @@ module Spoom
294
298
  location: node_location(node),
295
299
  )
296
300
  @index.define(definition)
301
+ @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
297
302
  end
298
303
 
299
304
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -305,6 +310,7 @@ module Spoom
305
310
  location: node_location(node),
306
311
  )
307
312
  @index.define(definition)
313
+ @plugins.each { |plugin| plugin.internal_on_define_class(self, definition) }
308
314
  end
309
315
 
310
316
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -316,6 +322,7 @@ module Spoom
316
322
  location: node_location(node),
317
323
  )
318
324
  @index.define(definition)
325
+ @plugins.each { |plugin| plugin.internal_on_define_constant(self, definition) }
319
326
  end
320
327
 
321
328
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -327,6 +334,7 @@ module Spoom
327
334
  location: node_location(node),
328
335
  )
329
336
  @index.define(definition)
337
+ @plugins.each { |plugin| plugin.internal_on_define_method(self, definition) }
330
338
  end
331
339
 
332
340
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -338,6 +346,7 @@ module Spoom
338
346
  location: node_location(node),
339
347
  )
340
348
  @index.define(definition)
349
+ @plugins.each { |plugin| plugin.internal_on_define_module(self, definition) }
341
350
  end
342
351
 
343
352
  # Reference indexing
@@ -352,6 +361,74 @@ module Spoom
352
361
  @index.reference(Reference.new(name: name, kind: Reference::Kind::Method, location: node_location(node)))
353
362
  end
354
363
 
364
+ # Context
365
+
366
+ sig { returns(SyntaxTree::Node) }
367
+ def current_node
368
+ T.must(@nodes_nesting.last)
369
+ end
370
+
371
+ sig { type_parameters(:N).params(type: T::Class[T.type_parameter(:N)]).returns(T.nilable(T.type_parameter(:N))) }
372
+ def nesting_node(type)
373
+ @nodes_nesting.reverse_each do |node|
374
+ return T.unsafe(node) if node.is_a?(type)
375
+ end
376
+
377
+ nil
378
+ end
379
+
380
+ sig { returns(T.nilable(SyntaxTree::ClassDeclaration)) }
381
+ def nesting_class
382
+ nesting_node(SyntaxTree::ClassDeclaration)
383
+ end
384
+
385
+ sig { returns(T.nilable(SyntaxTree::BlockNode)) }
386
+ def nesting_block
387
+ nesting_node(SyntaxTree::BlockNode)
388
+ end
389
+
390
+ sig { returns(T.nilable(SyntaxTree::MethodAddBlock)) }
391
+ def nesting_block_call
392
+ nesting_node(SyntaxTree::MethodAddBlock)
393
+ end
394
+
395
+ sig { returns(T.nilable(String)) }
396
+ def nesting_block_call_name
397
+ block = nesting_block_call
398
+ return unless block.is_a?(SyntaxTree::MethodAddBlock)
399
+
400
+ call = block.call
401
+ case call
402
+ when SyntaxTree::ARef
403
+ node_string(call.collection)
404
+ when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
405
+ node_string(call.message)
406
+ end
407
+ end
408
+
409
+ sig { returns(T.nilable(String)) }
410
+ def nesting_class_name
411
+ nesting_class = self.nesting_class
412
+ return unless nesting_class
413
+
414
+ node_string(nesting_class.constant)
415
+ end
416
+
417
+ sig { returns(T.nilable(String)) }
418
+ def nesting_class_superclass_name
419
+ nesting_class_superclass = nesting_class&.superclass
420
+ return unless nesting_class_superclass
421
+
422
+ node_string(nesting_class_superclass).delete_prefix("::")
423
+ end
424
+
425
+ sig { returns(T.nilable(String)) }
426
+ def last_sig
427
+ return unless @previous_node.is_a?(SyntaxTree::MethodAddBlock)
428
+
429
+ node_string(@previous_node)
430
+ end
431
+
355
432
  # Node utils
356
433
 
357
434
  sig { params(node: T.any(Symbol, SyntaxTree::Node)).returns(String) }
@@ -13,6 +13,23 @@ module Spoom
13
13
  class << self
14
14
  extend T::Sig
15
15
 
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
+
16
33
  sig { params(file: String, location: SyntaxTree::Location).returns(Location) }
17
34
  def from_syntax_tree(file, location)
18
35
  new(file, location.start_line, location.start_column, location.end_line, location.end_column)
@@ -42,9 +59,20 @@ 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
- return nil unless Location === other
75
+ return unless Location === other
48
76
 
49
77
  to_s <=> other.to_s
50
78
  end
@@ -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(SyntaxTree::SymbolLiteral) do |arg|
15
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,61 @@
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 SyntaxTree::SymbolLiteral
42
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
43
+ when SyntaxTree::VarRef
44
+ indexer.reference_constant(indexer.node_string(arg), send.node)
45
+ end
46
+
47
+ send.each_arg_assoc do |key, value|
48
+ key = indexer.node_string(key).delete_suffix(":")
49
+
50
+ case key
51
+ when "if", "unless"
52
+ indexer.reference_method(indexer.symbol_string(value), send.node) if value
53
+ else
54
+ indexer.reference_constant(camelize(key), send.node)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ 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(SyntaxTree::SymbolLiteral) do |arg|
20
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
21
+ end
22
+ when "validate", "validates", "validates!", "validates_each"
23
+ send.each_arg(SyntaxTree::SymbolLiteral) do |arg|
24
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
25
+ end
26
+ send.each_arg_assoc do |key, value|
27
+ key = indexer.node_string(key).delete_suffix(":")
28
+
29
+ case key
30
+ when "if", "unless"
31
+ indexer.reference_method(indexer.symbol_string(value), 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?(SyntaxTree::SymbolLiteral)
39
+ indexer.reference_constant(indexer.node_string(arg.value), send.node)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,111 @@
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(SyntaxTree::SymbolLiteral) do |arg|
77
+ indexer.reference_method(indexer.node_string(arg.value), 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 = indexer.symbol_string(key).delete_suffix(":")
88
+ indexer.reference_method("#{key}=", send.node)
89
+ end
90
+ when *ARRAY_METHODS
91
+ send.each_arg(SyntaxTree::ArrayLiteral) do |arg|
92
+ args = arg.contents
93
+ next unless args.is_a?(SyntaxTree::Args)
94
+
95
+ args.parts.each do |part|
96
+ next unless part.is_a?(SyntaxTree::HashLiteral)
97
+
98
+ part.assocs.each do |assoc|
99
+ next unless assoc.is_a?(SyntaxTree::Assoc)
100
+
101
+ key = indexer.symbol_string(assoc.key).delete_suffix(":")
102
+ indexer.reference_method("#{key}=", send.node)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,21 @@
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
+ end
19
+ end
20
+ end
21
+ end