spoom 1.2.4 → 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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -55
  3. data/lib/spoom/cli/deadcode.rb +172 -0
  4. data/lib/spoom/cli/helper.rb +20 -0
  5. data/lib/spoom/cli/srb/bump.rb +200 -0
  6. data/lib/spoom/cli/srb/coverage.rb +224 -0
  7. data/lib/spoom/cli/srb/lsp.rb +159 -0
  8. data/lib/spoom/cli/srb/tc.rb +150 -0
  9. data/lib/spoom/cli/srb.rb +27 -0
  10. data/lib/spoom/cli.rb +72 -32
  11. data/lib/spoom/context/git.rb +2 -2
  12. data/lib/spoom/context/sorbet.rb +2 -2
  13. data/lib/spoom/deadcode/definition.rb +11 -0
  14. data/lib/spoom/deadcode/indexer.rb +222 -224
  15. data/lib/spoom/deadcode/location.rb +2 -2
  16. data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -2
  17. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
  18. data/lib/spoom/deadcode/plugins/actionpack.rb +4 -6
  19. data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
  20. data/lib/spoom/deadcode/plugins/active_record.rb +9 -12
  21. data/lib/spoom/deadcode/plugins/active_support.rb +11 -0
  22. data/lib/spoom/deadcode/plugins/base.rb +1 -1
  23. data/lib/spoom/deadcode/plugins/graphql.rb +4 -4
  24. data/lib/spoom/deadcode/plugins/namespaces.rb +2 -4
  25. data/lib/spoom/deadcode/plugins/ruby.rb +8 -17
  26. data/lib/spoom/deadcode/plugins/sorbet.rb +4 -10
  27. data/lib/spoom/deadcode/plugins.rb +1 -0
  28. data/lib/spoom/deadcode/remover.rb +209 -174
  29. data/lib/spoom/deadcode/send.rb +9 -10
  30. data/lib/spoom/deadcode/visitor.rb +755 -0
  31. data/lib/spoom/deadcode.rb +40 -10
  32. data/lib/spoom/file_tree.rb +0 -16
  33. data/lib/spoom/sorbet/errors.rb +1 -1
  34. data/lib/spoom/sorbet/lsp/structures.rb +2 -2
  35. data/lib/spoom/version.rb +1 -1
  36. metadata +19 -15
  37. data/lib/spoom/cli/bump.rb +0 -198
  38. data/lib/spoom/cli/coverage.rb +0 -222
  39. data/lib/spoom/cli/lsp.rb +0 -168
  40. data/lib/spoom/cli/run.rb +0 -148
@@ -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
@@ -38,18 +38,16 @@ module Spoom
38
38
 
39
39
  arg = send.args.first
40
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)
41
+ when Prism::SymbolNode
42
+ indexer.reference_method(arg.unescaped, send.node)
45
43
  end
46
44
 
47
45
  send.each_arg_assoc do |key, value|
48
- key = indexer.node_string(key).delete_suffix(":")
46
+ key = key.slice.delete_suffix(":")
49
47
 
50
48
  case key
51
49
  when "if", "unless"
52
- indexer.reference_method(indexer.symbol_string(value), send.node) if value
50
+ indexer.reference_method(value.slice.delete_prefix(":"), send.node) if value
53
51
  else
54
52
  indexer.reference_constant(camelize(key), send.node)
55
53
  end
@@ -16,27 +16,27 @@ module Spoom
16
16
 
17
17
  case send.name
18
18
  when "attribute", "attributes"
19
- send.each_arg(SyntaxTree::SymbolLiteral) do |arg|
20
- indexer.reference_method(indexer.node_string(arg.value), send.node)
19
+ send.each_arg(Prism::SymbolNode) do |arg|
20
+ indexer.reference_method(arg.unescaped, send.node)
21
21
  end
22
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)
23
+ send.each_arg(Prism::SymbolNode) do |arg|
24
+ indexer.reference_method(arg.unescaped, send.node)
25
25
  end
26
26
  send.each_arg_assoc do |key, value|
27
- key = indexer.node_string(key).delete_suffix(":")
27
+ key = key.slice.delete_suffix(":")
28
28
 
29
29
  case key
30
30
  when "if", "unless"
31
- indexer.reference_method(indexer.symbol_string(value), send.node) if value
31
+ indexer.reference_method(value.slice.delete_prefix(":"), send.node) if value
32
32
  else
33
33
  indexer.reference_constant(camelize(key), send.node)
34
34
  end
35
35
  end
36
36
  when "validates_with"
37
37
  arg = send.args.first
38
- if arg.is_a?(SyntaxTree::SymbolLiteral)
39
- indexer.reference_constant(indexer.node_string(arg.value), send.node)
38
+ if arg.is_a?(Prism::SymbolNode)
39
+ indexer.reference_constant(arg.unescaped, send.node)
40
40
  end
41
41
  end
42
42
  end
@@ -73,8 +73,8 @@ module Spoom
73
73
  sig { override.params(indexer: Indexer, send: Send).void }
74
74
  def on_send(indexer, send)
75
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)
76
+ send.each_arg(Prism::SymbolNode) do |arg|
77
+ indexer.reference_method(arg.unescaped, send.node)
78
78
  end
79
79
  return
80
80
  end
@@ -84,21 +84,18 @@ module Spoom
84
84
  case send.name
85
85
  when *CRUD_METHODS
86
86
  send.each_arg_assoc do |key, _value|
87
- key = indexer.symbol_string(key).delete_suffix(":")
87
+ key = key.slice.delete_suffix(":")
88
88
  indexer.reference_method("#{key}=", send.node)
89
89
  end
90
90
  when *ARRAY_METHODS
91
- send.each_arg(SyntaxTree::ArrayLiteral) do |arg|
92
- args = arg.contents
93
- next unless args.is_a?(SyntaxTree::Args)
91
+ send.each_arg(Prism::ArrayNode) do |arg|
92
+ arg.elements.each do |part|
93
+ next unless part.is_a?(Prism::HashNode)
94
94
 
95
- args.parts.each do |part|
96
- next unless part.is_a?(SyntaxTree::HashLiteral)
95
+ part.elements.each do |assoc|
96
+ next unless assoc.is_a?(Prism::AssocNode)
97
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(":")
98
+ key = assoc.key.slice.delete_suffix(":")
102
99
  indexer.reference_method("#{key}=", send.node)
103
100
  end
104
101
  end
@@ -15,6 +15,17 @@ module Spoom
15
15
  "before_setup",
16
16
  "before_teardown",
17
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
18
29
  end
19
30
  end
20
31
  end
@@ -269,7 +269,7 @@ module Spoom
269
269
  # return unless send.name == "dsl_method"
270
270
  # return if send.args.empty?
271
271
  #
272
- # method_name = indexer.node_string(send.args.first).delete_prefix(":")
272
+ # method_name = send.args.first.slice.delete_prefix(":")
273
273
  # indexer.reference_method(method_name, send.node)
274
274
  # end
275
275
  # end
@@ -29,16 +29,16 @@ module Spoom
29
29
  return unless send.recv.nil? && send.name == "field"
30
30
 
31
31
  arg = send.args.first
32
- return unless arg.is_a?(SyntaxTree::SymbolLiteral)
32
+ return unless arg.is_a?(Prism::SymbolNode)
33
33
 
34
- indexer.reference_method(indexer.node_string(arg.value), send.node)
34
+ indexer.reference_method(arg.unescaped, send.node)
35
35
 
36
36
  send.each_arg_assoc do |key, value|
37
- key = indexer.node_string(key).delete_suffix(":")
37
+ key = key.slice.delete_suffix(":")
38
38
  next unless key == "resolver_method"
39
39
  next unless value
40
40
 
41
- indexer.reference_method(indexer.symbol_string(value), send.node)
41
+ indexer.reference_method(value.slice.delete_prefix(":"), send.node)
42
42
  end
43
43
  end
44
44
  end
@@ -22,11 +22,9 @@ module Spoom
22
22
  sig { params(indexer: Indexer).returns(T::Boolean) }
23
23
  def used_as_namespace?(indexer)
24
24
  node = indexer.current_node
25
- return false unless node.is_a?(SyntaxTree::ClassDeclaration) || node.is_a?(SyntaxTree::ModuleDeclaration)
25
+ return false unless node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
26
26
 
27
- node.bodystmt.statements.body.any? do |stmt|
28
- !stmt.is_a?(SyntaxTree::VoidStmt)
29
- end
27
+ !!node.body
30
28
  end
31
29
  end
32
30
  end
@@ -27,34 +27,25 @@ module Spoom
27
27
  reference_symbol_as_constant(indexer, send, T.must(send.args.first))
28
28
  when "send", "__send__", "try"
29
29
  arg = send.args.first
30
- indexer.reference_method(indexer.node_string(arg.value), send.node) if arg.is_a?(SyntaxTree::SymbolLiteral)
30
+ indexer.reference_method(arg.unescaped, send.node) if arg.is_a?(Prism::SymbolNode)
31
31
  when "alias_method"
32
32
  last_arg = send.args.last
33
33
 
34
- name = case last_arg
35
- when SyntaxTree::SymbolLiteral
36
- indexer.node_string(last_arg.value)
37
- when SyntaxTree::StringLiteral
38
- last_arg.parts.map { |part| indexer.node_string(part) }.join
34
+ if last_arg.is_a?(Prism::SymbolNode) || last_arg.is_a?(Prism::StringNode)
35
+ indexer.reference_method(last_arg.unescaped, send.node)
39
36
  end
40
-
41
- return unless name
42
-
43
- indexer.reference_method(name, send.node)
44
37
  end
45
38
  end
46
39
 
47
40
  private
48
41
 
49
- sig { params(indexer: Indexer, send: Send, node: SyntaxTree::Node).void }
42
+ sig { params(indexer: Indexer, send: Send, node: Prism::Node).void }
50
43
  def reference_symbol_as_constant(indexer, send, node)
51
44
  case node
52
- when SyntaxTree::SymbolLiteral
53
- name = indexer.node_string(node.value)
54
- indexer.reference_constant(name, send.node)
55
- when SyntaxTree::StringLiteral
56
- string = T.must(indexer.node_string(node)[1..-2])
57
- string.split("::").each do |name|
45
+ when Prism::SymbolNode
46
+ indexer.reference_constant(node.unescaped, send.node)
47
+ when Prism::StringNode
48
+ node.unescaped.split("::").each do |name|
58
49
  indexer.reference_constant(name, send.node) unless name.empty?
59
50
  end
60
51
  end
@@ -21,24 +21,18 @@ module Spoom
21
21
 
22
22
  sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
23
23
  def sorbet_type_member?(indexer, definition)
24
- assign = indexer.nesting_node(SyntaxTree::Assign)
24
+ assign = indexer.nesting_node(Prism::ConstantWriteNode)
25
25
  return false unless assign
26
26
 
27
27
  value = assign.value
28
+ return false unless value.is_a?(Prism::CallNode)
28
29
 
29
- case value
30
- when SyntaxTree::MethodAddBlock
31
- indexer.node_string(value.call).match?(/^(type_member|type_template)/)
32
- when SyntaxTree::VCall
33
- indexer.node_string(value.value).match?(/^(type_member|type_template)/)
34
- else
35
- false
36
- end
30
+ value.name == :type_member || value.name == :type_template
37
31
  end
38
32
 
39
33
  sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
40
34
  def sorbet_enum_constant?(indexer, definition)
41
- /^(::)?T::Enum$/.match?(indexer.nesting_class_superclass_name) && indexer.nesting_block_call_name == "enums"
35
+ /^(::)?T::Enum$/.match?(indexer.nesting_class_superclass_name) && indexer.nesting_call&.name == :enums
42
36
  end
43
37
  end
44
38
  end
@@ -5,6 +5,7 @@ require_relative "plugins/base"
5
5
 
6
6
  require_relative "plugins/actionpack"
7
7
  require_relative "plugins/active_job"
8
+ require_relative "plugins/action_mailer_preview"
8
9
  require_relative "plugins/action_mailer"
9
10
  require_relative "plugins/active_model"
10
11
  require_relative "plugins/active_record"