spoom 1.5.0 → 1.7.2

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -0
  3. data/lib/spoom/backtrace_filter/minitest.rb +3 -4
  4. data/lib/spoom/cli/deadcode.rb +1 -2
  5. data/lib/spoom/cli/helper.rb +41 -31
  6. data/lib/spoom/cli/srb/assertions.rb +48 -0
  7. data/lib/spoom/cli/srb/bump.rb +1 -2
  8. data/lib/spoom/cli/srb/coverage.rb +1 -1
  9. data/lib/spoom/cli/srb/metrics.rb +68 -0
  10. data/lib/spoom/cli/srb/sigs.rb +209 -0
  11. data/lib/spoom/cli/srb/tc.rb +16 -1
  12. data/lib/spoom/cli/srb.rb +16 -4
  13. data/lib/spoom/cli.rb +1 -2
  14. data/lib/spoom/colors.rb +2 -6
  15. data/lib/spoom/context/bundle.rb +8 -9
  16. data/lib/spoom/context/exec.rb +3 -6
  17. data/lib/spoom/context/file_system.rb +12 -19
  18. data/lib/spoom/context/git.rb +14 -19
  19. data/lib/spoom/context/sorbet.rb +14 -27
  20. data/lib/spoom/context.rb +4 -8
  21. data/lib/spoom/counters.rb +22 -0
  22. data/lib/spoom/coverage/d3/base.rb +6 -8
  23. data/lib/spoom/coverage/d3/circle_map.rb +6 -16
  24. data/lib/spoom/coverage/d3/pie.rb +14 -19
  25. data/lib/spoom/coverage/d3/timeline.rb +46 -47
  26. data/lib/spoom/coverage/d3.rb +2 -4
  27. data/lib/spoom/coverage/report.rb +41 -79
  28. data/lib/spoom/coverage/snapshot.rb +8 -14
  29. data/lib/spoom/coverage.rb +3 -5
  30. data/lib/spoom/deadcode/definition.rb +12 -14
  31. data/lib/spoom/deadcode/erb.rb +10 -8
  32. data/lib/spoom/deadcode/index.rb +21 -25
  33. data/lib/spoom/deadcode/indexer.rb +5 -6
  34. data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -3
  35. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +2 -3
  36. data/lib/spoom/deadcode/plugins/actionpack.rb +19 -22
  37. data/lib/spoom/deadcode/plugins/active_model.rb +2 -3
  38. data/lib/spoom/deadcode/plugins/active_record.rb +62 -53
  39. data/lib/spoom/deadcode/plugins/active_support.rb +3 -2
  40. data/lib/spoom/deadcode/plugins/base.rb +29 -32
  41. data/lib/spoom/deadcode/plugins/graphql.rb +2 -3
  42. data/lib/spoom/deadcode/plugins/minitest.rb +4 -4
  43. data/lib/spoom/deadcode/plugins/namespaces.rb +5 -5
  44. data/lib/spoom/deadcode/plugins/rails.rb +5 -5
  45. data/lib/spoom/deadcode/plugins/rubocop.rb +5 -5
  46. data/lib/spoom/deadcode/plugins/ruby.rb +3 -4
  47. data/lib/spoom/deadcode/plugins/sorbet.rb +12 -6
  48. data/lib/spoom/deadcode/plugins/thor.rb +2 -3
  49. data/lib/spoom/deadcode/plugins.rb +23 -31
  50. data/lib/spoom/deadcode/remover.rb +58 -79
  51. data/lib/spoom/deadcode/send.rb +2 -8
  52. data/lib/spoom/file_collector.rb +11 -19
  53. data/lib/spoom/file_tree.rb +36 -51
  54. data/lib/spoom/location.rb +9 -20
  55. data/lib/spoom/model/builder.rb +54 -17
  56. data/lib/spoom/model/model.rb +71 -74
  57. data/lib/spoom/model/namespace_visitor.rb +4 -3
  58. data/lib/spoom/model/reference.rb +4 -8
  59. data/lib/spoom/model/references_visitor.rb +50 -30
  60. data/lib/spoom/parse.rb +4 -4
  61. data/lib/spoom/poset.rb +22 -24
  62. data/lib/spoom/printer.rb +10 -13
  63. data/lib/spoom/rbs.rb +77 -0
  64. data/lib/spoom/sorbet/config.rb +17 -24
  65. data/lib/spoom/sorbet/errors.rb +87 -45
  66. data/lib/spoom/sorbet/lsp/base.rb +10 -16
  67. data/lib/spoom/sorbet/lsp/errors.rb +8 -16
  68. data/lib/spoom/sorbet/lsp/structures.rb +65 -91
  69. data/lib/spoom/sorbet/lsp.rb +20 -22
  70. data/lib/spoom/sorbet/metrics/code_metrics_visitor.rb +236 -0
  71. data/lib/spoom/sorbet/metrics/metrics_file_parser.rb +34 -0
  72. data/lib/spoom/sorbet/metrics.rb +2 -32
  73. data/lib/spoom/sorbet/sigils.rb +16 -23
  74. data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +242 -0
  75. data/lib/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments.rb +123 -0
  76. data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +293 -0
  77. data/lib/spoom/sorbet/translate/strip_sorbet_sigs.rb +23 -0
  78. data/lib/spoom/sorbet/translate/translator.rb +71 -0
  79. data/lib/spoom/sorbet/translate.rb +49 -0
  80. data/lib/spoom/sorbet.rb +6 -12
  81. data/lib/spoom/source/rewriter.rb +167 -0
  82. data/lib/spoom/source.rb +4 -0
  83. data/lib/spoom/timeline.rb +4 -6
  84. data/lib/spoom/version.rb +1 -1
  85. data/lib/spoom/visitor.rb +298 -151
  86. data/lib/spoom.rb +4 -3
  87. data/rbi/spoom.rbi +3567 -0
  88. metadata +62 -8
@@ -5,8 +5,6 @@ module Spoom
5
5
  module Deadcode
6
6
  module Plugins
7
7
  class Ruby < Base
8
- extend T::Sig
9
-
10
8
  ignore_methods_named(
11
9
  "==",
12
10
  "extended",
@@ -20,7 +18,8 @@ module Spoom
20
18
  "to_s",
21
19
  )
22
20
 
23
- sig { override.params(send: Send).void }
21
+ # @override
22
+ #: (Send send) -> void
24
23
  def on_send(send)
25
24
  case send.name
26
25
  when "const_defined?", "const_get", "const_source_location"
@@ -42,7 +41,7 @@ module Spoom
42
41
 
43
42
  private
44
43
 
45
- sig { params(send: Send, node: Prism::Node).void }
44
+ #: (Send send, Prism::Node node) -> void
46
45
  def reference_symbol_as_constant(send, node)
47
46
  case node
48
47
  when Prism::SymbolNode
@@ -5,26 +5,32 @@ module Spoom
5
5
  module Deadcode
6
6
  module Plugins
7
7
  class Sorbet < Base
8
- extend T::Sig
9
-
10
- sig { override.params(definition: Model::Constant).void }
8
+ # @override
9
+ #: (Model::Constant definition) -> void
11
10
  def on_define_constant(definition)
12
11
  @index.ignore(definition) if sorbet_type_member?(definition) || sorbet_enum_constant?(definition)
13
12
  end
14
13
 
15
- sig { override.params(definition: Model::Method).void }
14
+ # @override
15
+ #: (Model::Method definition) -> void
16
16
  def on_define_method(definition)
17
+ # Ignore signatures containing `override` or `overridable`, like `sig { override.void }`
17
18
  @index.ignore(definition) if definition.sigs.any? { |sig| sig.string =~ /(override|overridable)/ }
19
+
20
+ # Ignore comments `@override` and `@overridable`
21
+ @index.ignore(definition) if definition.comments.any? do |comment|
22
+ comment.string == "@override" || comment.string == "@overridable"
23
+ end
18
24
  end
19
25
 
20
26
  private
21
27
 
22
- sig { params(definition: Model::Constant).returns(T::Boolean) }
28
+ #: (Model::Constant definition) -> bool
23
29
  def sorbet_type_member?(definition)
24
30
  definition.value.match?(/^(type_member|type_template)/)
25
31
  end
26
32
 
27
- sig { params(definition: Model::Constant).returns(T::Boolean) }
33
+ #: (Model::Constant definition) -> bool
28
34
  def sorbet_enum_constant?(definition)
29
35
  owner = definition.owner
30
36
  return false unless owner.is_a?(Model::Class)
@@ -5,11 +5,10 @@ module Spoom
5
5
  module Deadcode
6
6
  module Plugins
7
7
  class Thor < Base
8
- extend T::Sig
9
-
10
8
  ignore_methods_named("exit_on_failure?")
11
9
 
12
- sig { override.params(definition: Model::Method).void }
10
+ # @override
11
+ #: (Model::Method definition) -> void
13
12
  def on_define_method(definition)
14
13
  owner = definition.owner
15
14
  return unless owner.is_a?(Model::Class)
@@ -25,39 +25,31 @@ module Spoom
25
25
  module Deadcode
26
26
  DEFAULT_CUSTOM_PLUGINS_PATH = ".spoom/deadcode/plugins"
27
27
 
28
- DEFAULT_PLUGINS = T.let(
29
- Set.new([
30
- Spoom::Deadcode::Plugins::Namespaces,
31
- Spoom::Deadcode::Plugins::Ruby,
32
- ]).freeze,
33
- T::Set[T.class_of(Plugins::Base)],
34
- )
28
+ DEFAULT_PLUGINS = Set.new([
29
+ Spoom::Deadcode::Plugins::Namespaces,
30
+ Spoom::Deadcode::Plugins::Ruby,
31
+ ]).freeze #: Set[singleton(Plugins::Base)]
35
32
 
36
- PLUGINS_FOR_GEM = T.let(
37
- {
38
- "actionmailer" => Spoom::Deadcode::Plugins::ActionMailer,
39
- "actionpack" => Spoom::Deadcode::Plugins::ActionPack,
40
- "activejob" => Spoom::Deadcode::Plugins::ActiveJob,
41
- "activemodel" => Spoom::Deadcode::Plugins::ActiveModel,
42
- "activerecord" => Spoom::Deadcode::Plugins::ActiveRecord,
43
- "activesupport" => Spoom::Deadcode::Plugins::ActiveSupport,
44
- "graphql" => Spoom::Deadcode::Plugins::GraphQL,
45
- "minitest" => Spoom::Deadcode::Plugins::Minitest,
46
- "rails" => Spoom::Deadcode::Plugins::Rails,
47
- "rake" => Spoom::Deadcode::Plugins::Rake,
48
- "rspec" => Spoom::Deadcode::Plugins::RSpec,
49
- "rubocop" => Spoom::Deadcode::Plugins::Rubocop,
50
- "sorbet-runtime" => Spoom::Deadcode::Plugins::Sorbet,
51
- "sorbet-static" => Spoom::Deadcode::Plugins::Sorbet,
52
- "thor" => Spoom::Deadcode::Plugins::Thor,
53
- }.freeze,
54
- T::Hash[String, T.class_of(Plugins::Base)],
55
- )
33
+ PLUGINS_FOR_GEM = {
34
+ "actionmailer" => Spoom::Deadcode::Plugins::ActionMailer,
35
+ "actionpack" => Spoom::Deadcode::Plugins::ActionPack,
36
+ "activejob" => Spoom::Deadcode::Plugins::ActiveJob,
37
+ "activemodel" => Spoom::Deadcode::Plugins::ActiveModel,
38
+ "activerecord" => Spoom::Deadcode::Plugins::ActiveRecord,
39
+ "activesupport" => Spoom::Deadcode::Plugins::ActiveSupport,
40
+ "graphql" => Spoom::Deadcode::Plugins::GraphQL,
41
+ "minitest" => Spoom::Deadcode::Plugins::Minitest,
42
+ "rails" => Spoom::Deadcode::Plugins::Rails,
43
+ "rake" => Spoom::Deadcode::Plugins::Rake,
44
+ "rspec" => Spoom::Deadcode::Plugins::RSpec,
45
+ "rubocop" => Spoom::Deadcode::Plugins::Rubocop,
46
+ "sorbet-runtime" => Spoom::Deadcode::Plugins::Sorbet,
47
+ "sorbet-static" => Spoom::Deadcode::Plugins::Sorbet,
48
+ "thor" => Spoom::Deadcode::Plugins::Thor,
49
+ }.freeze #: Hash[String, singleton(Plugins::Base)]
56
50
 
57
51
  class << self
58
- extend T::Sig
59
-
60
- sig { params(context: Context).returns(T::Set[T.class_of(Plugins::Base)]) }
52
+ #: (Context context) -> Set[singleton(Plugins::Base)]
61
53
  def plugins_from_gemfile_lock(context)
62
54
  # These plugins are always loaded
63
55
  plugin_classes = DEFAULT_PLUGINS.dup
@@ -71,7 +63,7 @@ module Spoom
71
63
  plugin_classes
72
64
  end
73
65
 
74
- sig { params(context: Context).returns(T::Array[T.class_of(Plugins::Base)]) }
66
+ #: (Context context) -> Array[singleton(Plugins::Base)]
75
67
  def load_custom_plugins(context)
76
68
  context.glob("#{DEFAULT_CUSTOM_PLUGINS_PATH}/*.rb").each do |path|
77
69
  require("#{context.absolute_path}/#{path}")
@@ -4,16 +4,14 @@
4
4
  module Spoom
5
5
  module Deadcode
6
6
  class Remover
7
- extend T::Sig
8
-
9
7
  class Error < Spoom::Error; end
10
8
 
11
- sig { params(context: Context).void }
9
+ #: (Context context) -> void
12
10
  def initialize(context)
13
11
  @context = context
14
12
  end
15
13
 
16
- sig { params(kind: T.nilable(Definition::Kind), location: Location).returns(String) }
14
+ #: (Definition::Kind? kind, Location location) -> String
17
15
  def remove_location(kind, location)
18
16
  file = location.file
19
17
 
@@ -27,22 +25,20 @@ module Spoom
27
25
  end
28
26
 
29
27
  class NodeRemover
30
- extend T::Sig
31
-
32
- sig { returns(String) }
28
+ #: String
33
29
  attr_reader :new_source
34
30
 
35
- sig { params(source: String, kind: T.nilable(Definition::Kind), location: Location).void }
31
+ #: (String source, Definition::Kind? kind, Location location) -> void
36
32
  def initialize(source, kind, location)
37
33
  @old_source = source
38
- @new_source = T.let(source.dup, String)
34
+ @new_source = source.dup #: String
39
35
  @kind = kind
40
36
  @location = location
41
37
 
42
- @node_context = T.let(NodeFinder.find(source, location, kind), NodeContext)
38
+ @node_context = NodeFinder.find(source, location, kind) #: NodeContext
43
39
  end
44
40
 
45
- sig { void }
41
+ #: -> void
46
42
  def apply_edit
47
43
  sclass_context = @node_context.sclass_context
48
44
  if sclass_context
@@ -69,7 +65,7 @@ module Spoom
69
65
 
70
66
  private
71
67
 
72
- sig { params(context: NodeContext).void }
68
+ #: (NodeContext context) -> void
73
69
  def delete_constant_assignment(context)
74
70
  case context.node
75
71
  when Prism::ConstantWriteNode, Prism::ConstantOperatorWriteNode,
@@ -103,8 +99,10 @@ module Spoom
103
99
  prev_node = context.previous_node
104
100
  next_node = context.next_node
105
101
 
106
- if (prev_node && prev_node.location.end_line != node.location.start_line) &&
107
- (next_node && next_node.location.start_line != node.location.end_line)
102
+ has_prev_node_on_different_line = prev_node && prev_node.location.end_line != node.location.start_line
103
+ has_next_node_on_different_line = next_node && next_node.location.start_line != node.location.end_line
104
+
105
+ if has_prev_node_on_different_line && has_next_node_on_different_line
108
106
  # We have a node before and after, but on different lines, we need to remove the whole line
109
107
  #
110
108
  # ~~~
@@ -149,7 +147,7 @@ module Spoom
149
147
  end
150
148
  end
151
149
 
152
- sig { params(context: NodeContext).void }
150
+ #: (NodeContext context) -> void
153
151
  def delete_attr_accessor(context)
154
152
  args_context = context.parent_context
155
153
  send_context = args_context.parent_context
@@ -168,8 +166,10 @@ module Spoom
168
166
  prev_node = context.previous_node
169
167
  next_node = context.next_node
170
168
 
171
- if (prev_node && prev_node.location.end_line != context.node.location.start_line) &&
172
- (next_node && next_node.location.start_line != context.node.location.end_line)
169
+ has_prev_node_on_different_line = prev_node && prev_node.location.end_line != context.node.location.start_line
170
+ has_next_node_on_different_line = next_node && next_node.location.start_line != context.node.location.end_line
171
+
172
+ if has_prev_node_on_different_line && has_next_node_on_different_line
173
173
  # We have a node before and after, but on different lines, we need to remove the whole line
174
174
  #
175
175
  # ~~~
@@ -208,13 +208,7 @@ module Spoom
208
208
  insert_accessor(context.node, send_context, was_removed: false) if need_accessor
209
209
  end
210
210
 
211
- sig do
212
- params(
213
- node: Prism::Node,
214
- send_context: NodeContext,
215
- was_removed: T::Boolean,
216
- ).void
217
- end
211
+ #: (Prism::Node node, NodeContext send_context, was_removed: bool) -> void
218
212
  def insert_accessor(node, send_context, was_removed:)
219
213
  name = node.slice
220
214
  code = case @kind
@@ -257,7 +251,7 @@ module Spoom
257
251
  @new_source = lines.join
258
252
  end
259
253
 
260
- sig { params(context: NodeContext).void }
254
+ #: (NodeContext context) -> void
261
255
  def delete_node_and_comments_and_sigs(context)
262
256
  start_line = context.node.location.start_line
263
257
  end_line = context.node.location.end_line
@@ -285,7 +279,7 @@ module Spoom
285
279
 
286
280
  # Adjust the lines to remove to include previous blank lines
287
281
  prev_context = NodeContext.new(@old_source, @node_context.comments, first_node, context.nesting)
288
- before = T.let(prev_context.previous_node, T.nilable(T.any(Prism::Node, Prism::Comment)))
282
+ before = prev_context.previous_node #: (Prism::Node | Prism::Comment)?
289
283
 
290
284
  # There may be an unrelated comment between the current node and the one before
291
285
  # if there is, we only want to delete lines up to the last comment found
@@ -320,26 +314,26 @@ module Spoom
320
314
  delete_lines(start_line, end_line)
321
315
  end
322
316
 
323
- sig { params(start_line: Integer, end_line: Integer).void }
317
+ #: (Integer start_line, Integer end_line) -> void
324
318
  def delete_lines(start_line, end_line)
325
319
  lines = @new_source.lines
326
320
  lines[start_line - 1...end_line] = []
327
321
  @new_source = lines.join
328
322
  end
329
323
 
330
- sig { params(start_char: Integer, end_char: Integer).void }
324
+ #: (Integer start_char, Integer end_char) -> void
331
325
  def delete_chars(start_char, end_char)
332
326
  @new_source[start_char...end_char] = ""
333
327
  end
334
328
 
335
- sig { params(start_char: Integer, end_char: Integer, replacement: String).void }
329
+ #: (Integer start_char, Integer end_char, String replacement) -> void
336
330
  def replace_chars(start_char, end_char, replacement)
337
331
  @new_source[start_char...end_char] = replacement
338
332
  end
339
333
 
340
- sig { params(node: Prism::CallNode, name: String, kind: T.nilable(Definition::Kind)).returns(String) }
334
+ #: (Prism::CallNode node, name: String, kind: Definition::Kind?) -> String
341
335
  def transform_sig(node, name:, kind:)
342
- type = T.let(nil, T.nilable(String))
336
+ type = nil #: String?
343
337
 
344
338
  block = T.cast(node.block, Prism::BlockNode)
345
339
  statements = T.cast(block.body, Prism::StatementsNode)
@@ -370,25 +364,16 @@ module Spoom
370
364
  end
371
365
 
372
366
  class NodeContext
373
- extend T::Sig
374
-
375
- sig { returns(T::Hash[Integer, Prism::Comment]) }
367
+ #: Hash[Integer, Prism::Comment]
376
368
  attr_reader :comments
377
369
 
378
- sig { returns(Prism::Node) }
370
+ #: Prism::Node
379
371
  attr_reader :node
380
372
 
381
- sig { returns(T::Array[Prism::Node]) }
373
+ #: Array[Prism::Node]
382
374
  attr_accessor :nesting
383
375
 
384
- sig do
385
- params(
386
- source: String,
387
- comments: T::Hash[Integer, Prism::Comment],
388
- node: Prism::Node,
389
- nesting: T::Array[Prism::Node],
390
- ).void
391
- end
376
+ #: (String source, Hash[Integer, Prism::Comment] comments, Prism::Node node, Array[Prism::Node] nesting) -> void
392
377
  def initialize(source, comments, node, nesting)
393
378
  @source = source
394
379
  @comments = comments
@@ -396,7 +381,7 @@ module Spoom
396
381
  @nesting = nesting
397
382
  end
398
383
 
399
- sig { returns(Prism::Node) }
384
+ #: -> Prism::Node
400
385
  def parent_node
401
386
  parent = @nesting.last
402
387
  raise Error, "No parent for node #{node}" unless parent
@@ -404,7 +389,7 @@ module Spoom
404
389
  parent
405
390
  end
406
391
 
407
- sig { returns(NodeContext) }
392
+ #: -> NodeContext
408
393
  def parent_context
409
394
  nesting = @nesting.dup
410
395
  parent = nesting.pop
@@ -413,7 +398,7 @@ module Spoom
413
398
  NodeContext.new(@source, @comments, parent, nesting)
414
399
  end
415
400
 
416
- sig { returns(T::Array[Prism::Node]) }
401
+ #: -> Array[Prism::Node]
417
402
  def previous_nodes
418
403
  parent = parent_node
419
404
  child_nodes = parent.child_nodes.compact
@@ -424,12 +409,12 @@ module Spoom
424
409
  T.must(child_nodes[0...index])
425
410
  end
426
411
 
427
- sig { returns(T.nilable(Prism::Node)) }
412
+ #: -> Prism::Node?
428
413
  def previous_node
429
414
  previous_nodes.last
430
415
  end
431
416
 
432
- sig { returns(T::Array[Prism::Node]) }
417
+ #: -> Array[Prism::Node]
433
418
  def next_nodes
434
419
  parent = parent_node
435
420
  child_nodes = parent.child_nodes.compact
@@ -440,14 +425,14 @@ module Spoom
440
425
  T.must(child_nodes.compact[(index + 1)..-1])
441
426
  end
442
427
 
443
- sig { returns(T.nilable(Prism::Node)) }
428
+ #: -> Prism::Node?
444
429
  def next_node
445
430
  next_nodes.first
446
431
  end
447
432
 
448
- sig { returns(T.nilable(NodeContext)) }
433
+ #: -> NodeContext?
449
434
  def sclass_context
450
- sclass = T.let(nil, T.nilable(Prism::SingletonClassNode))
435
+ sclass = nil #: Prism::SingletonClassNode?
451
436
 
452
437
  nesting = @nesting.dup
453
438
  until nesting.empty? || sclass
@@ -473,12 +458,12 @@ module Spoom
473
458
  nil
474
459
  end
475
460
 
476
- sig { params(node: T.nilable(Prism::Node)).returns(T::Boolean) }
461
+ #: (Prism::Node? node) -> bool
477
462
  def sorbet_signature?(node)
478
463
  node.is_a?(Prism::CallNode) && node.name == :sig
479
464
  end
480
465
 
481
- sig { params(node: T.nilable(Prism::Node)).returns(T::Boolean) }
466
+ #: (Prism::Node? node) -> bool
482
467
  def sorbet_extend_sig?(node)
483
468
  return false unless node.is_a?(Prism::CallNode)
484
469
  return false unless node.name == :extend
@@ -490,9 +475,9 @@ module Spoom
490
475
  args.arguments.first&.slice == "T::Sig"
491
476
  end
492
477
 
493
- sig { params(start_line: Integer, end_line: Integer).returns(T::Array[Prism::Comment]) }
478
+ #: (Integer start_line, Integer end_line) -> Array[Prism::Comment]
494
479
  def comments_between_lines(start_line, end_line)
495
- comments = T.let([], T::Array[Prism::Comment])
480
+ comments = [] #: Array[Prism::Comment]
496
481
 
497
482
  (start_line + 1).upto(end_line - 1) do |line|
498
483
  comment = @comments[line]
@@ -502,9 +487,9 @@ module Spoom
502
487
  comments
503
488
  end
504
489
 
505
- sig { params(node: Prism::Node).returns(T::Array[Prism::Comment]) }
490
+ #: (Prism::Node node) -> Array[Prism::Comment]
506
491
  def attached_comments(node)
507
- comments = T.let([], T::Array[Prism::Comment])
492
+ comments = [] #: Array[Prism::Comment]
508
493
 
509
494
  start_line = node.location.start_line - 1
510
495
  start_line.downto(1) do |line|
@@ -517,9 +502,9 @@ module Spoom
517
502
  comments.reverse
518
503
  end
519
504
 
520
- sig { returns(T::Array[Prism::Node]) }
505
+ #: -> Array[Prism::Node]
521
506
  def attached_sigs
522
- nodes = T.let([], T::Array[Prism::Node])
507
+ nodes = [] #: Array[Prism::Node]
523
508
 
524
509
  previous_nodes.reverse_each do |prev_node|
525
510
  break unless sorbet_signature?(prev_node)
@@ -530,7 +515,7 @@ module Spoom
530
515
  nodes.reverse
531
516
  end
532
517
 
533
- sig { returns(T.nilable(Prism::CallNode)) }
518
+ #: -> Prism::CallNode?
534
519
  def attached_sig
535
520
  previous_nodes.reverse_each do |node|
536
521
  if node.is_a?(Prism::Comment)
@@ -547,12 +532,8 @@ module Spoom
547
532
  end
548
533
 
549
534
  class NodeFinder < Visitor
550
- extend T::Sig
551
-
552
535
  class << self
553
- extend T::Sig
554
-
555
- sig { params(source: String, location: Location, kind: T.nilable(Definition::Kind)).returns(NodeContext) }
536
+ #: (String source, Location location, Definition::Kind? kind) -> NodeContext
556
537
  def find(source, location, kind)
557
538
  result = Prism.parse(source)
558
539
 
@@ -576,17 +557,14 @@ module Spoom
576
557
  raise Error, "Can't find node at #{location}, expected #{kind} but got #{node.class}"
577
558
  end
578
559
 
579
- comments_by_line = T.let(
580
- result.comments.to_h do |comment|
581
- [comment.location.start_line, comment]
582
- end,
583
- T::Hash[Integer, Prism::Comment],
584
- )
560
+ comments_by_line = result.comments.to_h do |comment|
561
+ [comment.location.start_line, comment]
562
+ end #: Hash[Integer, Prism::Comment]
585
563
 
586
564
  NodeContext.new(source, comments_by_line, node, visitor.nodes_nesting)
587
565
  end
588
566
 
589
- sig { params(node: Prism::Node, kind: Definition::Kind).returns(T::Boolean) }
567
+ #: (Prism::Node node, Definition::Kind kind) -> bool
590
568
  def node_match_kind?(node, kind)
591
569
  case kind
592
570
  when Definition::Kind::AttrReader, Definition::Kind::AttrWriter
@@ -611,22 +589,23 @@ module Spoom
611
589
  end
612
590
  end
613
591
 
614
- sig { returns(T.nilable(Prism::Node)) }
592
+ #: Prism::Node?
615
593
  attr_reader :node
616
594
 
617
- sig { returns(T::Array[Prism::Node]) }
595
+ #: Array[Prism::Node]
618
596
  attr_reader :nodes_nesting
619
597
 
620
- sig { params(location: Location, kind: T.nilable(Definition::Kind)).void }
598
+ #: (Location location, Definition::Kind? kind) -> void
621
599
  def initialize(location, kind)
622
600
  super()
623
601
  @location = location
624
602
  @kind = kind
625
- @node = T.let(nil, T.nilable(Prism::Node))
626
- @nodes_nesting = T.let([], T::Array[Prism::Node])
603
+ @node = nil #: Prism::Node?
604
+ @nodes_nesting = [] #: Array[Prism::Node]
627
605
  end
628
606
 
629
- sig { override.params(node: T.nilable(Prism::Node)).void }
607
+ # @override
608
+ #: (Prism::Node? node) -> void
630
609
  def visit(node)
631
610
  return unless node
632
611
 
@@ -5,8 +5,6 @@ module Spoom
5
5
  module Deadcode
6
6
  # An abstraction to simplify handling of Prism::CallNode nodes.
7
7
  class Send < T::Struct
8
- extend T::Sig
9
-
10
8
  const :node, Prism::CallNode
11
9
  const :name, String
12
10
  const :recv, T.nilable(Prism::Node), default: nil
@@ -14,18 +12,14 @@ module Spoom
14
12
  const :block, T.nilable(Prism::Node), default: nil
15
13
  const :location, Location
16
14
 
17
- sig do
18
- type_parameters(:T)
19
- .params(arg_type: T::Class[T.type_parameter(:T)], block: T.proc.params(arg: T.type_parameter(:T)).void)
20
- .void
21
- end
15
+ #: [T] (Class[T] arg_type) { (T arg) -> void } -> void
22
16
  def each_arg(arg_type, &block)
23
17
  args.each do |arg|
24
18
  yield(T.unsafe(arg)) if arg.is_a?(arg_type)
25
19
  end
26
20
  end
27
21
 
28
- sig { params(block: T.proc.params(key: Prism::Node, value: T.nilable(Prism::Node)).void).void }
22
+ #: { (Prism::Node key, Prism::Node? value) -> void } -> void
29
23
  def each_arg_assoc(&block)
30
24
  args.each do |arg|
31
25
  next unless arg.is_a?(Prism::KeywordHashNode) || arg.is_a?(Prism::HashNode)
@@ -3,9 +3,7 @@
3
3
 
4
4
  module Spoom
5
5
  class FileCollector
6
- extend T::Sig
7
-
8
- sig { returns(T::Array[String]) }
6
+ #: Array[String]
9
7
  attr_reader :files
10
8
 
11
9
  # Initialize a new file collector
@@ -16,26 +14,20 @@ module Spoom
16
14
  # If `allow_mime_types` is empty, all files are collected.
17
15
  # If `allow_mime_types` is an array of mimetypes, files without an extension are collected if their mimetype is in
18
16
  # the list.
19
- sig do
20
- params(
21
- allow_extensions: T::Array[String],
22
- allow_mime_types: T::Array[String],
23
- exclude_patterns: T::Array[String],
24
- ).void
25
- end
17
+ #: (?allow_extensions: Array[String], ?allow_mime_types: Array[String], ?exclude_patterns: Array[String]) -> void
26
18
  def initialize(allow_extensions: [], allow_mime_types: [], exclude_patterns: [])
27
- @files = T.let([], T::Array[String])
19
+ @files = [] #: Array[String]
28
20
  @allow_extensions = allow_extensions
29
21
  @allow_mime_types = allow_mime_types
30
22
  @exclude_patterns = exclude_patterns
31
23
  end
32
24
 
33
- sig { params(paths: T::Array[String]).void }
25
+ #: (Array[String] paths) -> void
34
26
  def visit_paths(paths)
35
27
  paths.each { |path| visit_path(path) }
36
28
  end
37
29
 
38
- sig { params(path: String).void }
30
+ #: (String path) -> void
39
31
  def visit_path(path)
40
32
  path = clean_path(path)
41
33
 
@@ -52,24 +44,24 @@ module Spoom
52
44
 
53
45
  private
54
46
 
55
- sig { params(path: String).returns(String) }
47
+ #: (String path) -> String
56
48
  def clean_path(path)
57
49
  Pathname.new(path).cleanpath.to_s
58
50
  end
59
51
 
60
- sig { params(path: String).void }
52
+ #: (String path) -> void
61
53
  def visit_file(path)
62
54
  return if excluded_file?(path)
63
55
 
64
56
  @files << path
65
57
  end
66
58
 
67
- sig { params(path: String).void }
59
+ #: (String path) -> void
68
60
  def visit_directory(path)
69
61
  visit_paths(Dir.glob("#{path}/*"))
70
62
  end
71
63
 
72
- sig { params(path: String).returns(T::Boolean) }
64
+ #: (String path) -> bool
73
65
  def excluded_file?(path)
74
66
  return false if @allow_extensions.empty?
75
67
 
@@ -84,7 +76,7 @@ module Spoom
84
76
  end
85
77
  end
86
78
 
87
- sig { params(path: String).returns(T::Boolean) }
79
+ #: (String path) -> bool
88
80
  def excluded_path?(path)
89
81
  @exclude_patterns.any? do |pattern|
90
82
  # Use `FNM_PATHNAME` so patterns do not match directory separators
@@ -93,7 +85,7 @@ module Spoom
93
85
  end
94
86
  end
95
87
 
96
- sig { params(path: String).returns(T.nilable(String)) }
88
+ #: (String path) -> String?
97
89
  def mime_type_for(path)
98
90
  # The `file` command appears to be hanging on MacOS for some files so we timeout after 1s.
99
91
  %x{timeout 1s file --mime-type -b '#{path}'}.split("; ").first&.strip