spoom 1.3.0 → 1.4.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spoom/cli/deadcode.rb +21 -17
  3. data/lib/spoom/cli.rb +2 -2
  4. data/lib/spoom/deadcode/index.rb +178 -10
  5. data/lib/spoom/deadcode/indexer.rb +14 -435
  6. data/lib/spoom/deadcode/plugins/action_mailer.rb +3 -3
  7. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +9 -3
  8. data/lib/spoom/deadcode/plugins/actionpack.rb +12 -9
  9. data/lib/spoom/deadcode/plugins/active_model.rb +9 -9
  10. data/lib/spoom/deadcode/plugins/active_record.rb +5 -5
  11. data/lib/spoom/deadcode/plugins/active_support.rb +4 -4
  12. data/lib/spoom/deadcode/plugins/base.rb +70 -57
  13. data/lib/spoom/deadcode/plugins/graphql.rb +8 -8
  14. data/lib/spoom/deadcode/plugins/minitest.rb +15 -3
  15. data/lib/spoom/deadcode/plugins/namespaces.rb +9 -12
  16. data/lib/spoom/deadcode/plugins/rails.rb +9 -9
  17. data/lib/spoom/deadcode/plugins/rubocop.rb +13 -17
  18. data/lib/spoom/deadcode/plugins/ruby.rb +9 -9
  19. data/lib/spoom/deadcode/plugins/sorbet.rb +15 -18
  20. data/lib/spoom/deadcode/plugins/thor.rb +5 -4
  21. data/lib/spoom/deadcode/plugins.rb +4 -5
  22. data/lib/spoom/deadcode/remover.rb +15 -11
  23. data/lib/spoom/deadcode/send.rb +1 -0
  24. data/lib/spoom/deadcode.rb +4 -73
  25. data/lib/spoom/location.rb +139 -0
  26. data/lib/spoom/model/builder.rb +246 -0
  27. data/lib/spoom/model/model.rb +328 -0
  28. data/lib/spoom/model/namespace_visitor.rb +50 -0
  29. data/lib/spoom/model/reference.rb +49 -0
  30. data/lib/spoom/model/references_visitor.rb +200 -0
  31. data/lib/spoom/model.rb +10 -0
  32. data/lib/spoom/parse.rb +28 -0
  33. data/lib/spoom/poset.rb +197 -0
  34. data/lib/spoom/sorbet/errors.rb +5 -3
  35. data/lib/spoom/sorbet/lsp/errors.rb +1 -1
  36. data/lib/spoom/sorbet.rb +1 -1
  37. data/lib/spoom/version.rb +1 -1
  38. data/lib/spoom/visitor.rb +755 -0
  39. data/lib/spoom.rb +2 -0
  40. metadata +22 -15
  41. data/lib/spoom/deadcode/location.rb +0 -86
  42. data/lib/spoom/deadcode/reference.rb +0 -34
  43. data/lib/spoom/deadcode/visitor.rb +0 -755
@@ -0,0 +1,197 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ # A Poset is a set of elements with a partial order relation.
6
+ #
7
+ # The partial order relation is a binary relation that is reflexive, antisymmetric, and transitive.
8
+ # It can be used to represent a hierarchy of classes or modules, the dependencies between gems, etc.
9
+ class Poset
10
+ extend T::Sig
11
+ extend T::Generic
12
+
13
+ class Error < Spoom::Error; end
14
+
15
+ E = type_member { { upper: Object } }
16
+
17
+ sig { void }
18
+ def initialize
19
+ @elements = T.let({}, T::Hash[E, Element[E]])
20
+ end
21
+
22
+ # Get the POSet element for a given value
23
+ #
24
+ # Raises if the element is not found
25
+ sig { params(value: E).returns(Element[E]) }
26
+ def [](value)
27
+ element = @elements[value]
28
+ raise Error, "POSet::Element not found for #{value}" unless element
29
+
30
+ element
31
+ end
32
+
33
+ # Add an element to the POSet
34
+ sig { params(value: E).returns(Element[E]) }
35
+ def add_element(value)
36
+ element = @elements[value]
37
+ return element if element
38
+
39
+ @elements[value] = Element[E].new(value)
40
+ end
41
+
42
+ # Is the given value a element in the POSet?
43
+ sig { params(value: E).returns(T::Boolean) }
44
+ def element?(value)
45
+ @elements.key?(value)
46
+ end
47
+
48
+ # Add a direct edge from one element to another
49
+ #
50
+ # Transitive edges (transitive closure) are automatically computed.
51
+ # Adds the elements if they don't exist.
52
+ # If the direct edge already exists, nothing is done.
53
+ sig { params(from: E, to: E).void }
54
+ def add_direct_edge(from, to)
55
+ from_element = add_element(from)
56
+ to_element = add_element(to)
57
+
58
+ # We already added this direct edge, which means we already computed the transitive closure
59
+ return if from_element.parents.include?(to)
60
+
61
+ # Add the direct edges
62
+ from_element.dtos << to_element
63
+ to_element.dfroms << from_element
64
+
65
+ # Compute the transitive closure
66
+
67
+ from_element.tos << to_element
68
+ from_element.froms.each do |child_element|
69
+ child_element.tos << to_element
70
+ to_element.froms << child_element
71
+
72
+ to_element.tos.each do |parent_element|
73
+ parent_element.froms << child_element
74
+ child_element.tos << parent_element
75
+ end
76
+ end
77
+
78
+ to_element.froms << from_element
79
+ to_element.tos.each do |parent_element|
80
+ parent_element.froms << from_element
81
+ from_element.tos << parent_element
82
+
83
+ from_element.froms.each do |child_element|
84
+ child_element.tos << parent_element
85
+ parent_element.froms << child_element
86
+ end
87
+ end
88
+ end
89
+
90
+ # Is there an edge (direct or indirect) from `from` to `to`?
91
+ sig { params(from: E, to: E).returns(T::Boolean) }
92
+ def edge?(from, to)
93
+ from_element = @elements[from]
94
+ return false unless from_element
95
+
96
+ from_element.ancestors.include?(to)
97
+ end
98
+
99
+ # Is there a direct edge from `from` to `to`?
100
+ sig { params(from: E, to: E).returns(T::Boolean) }
101
+ def direct_edge?(from, to)
102
+ self[from].parents.include?(to)
103
+ end
104
+
105
+ # Show the POSet as a DOT graph using xdot (used for debugging)
106
+ sig { params(direct: T::Boolean, transitive: T::Boolean).void }
107
+ def show_dot(direct: true, transitive: true)
108
+ Open3.popen3("xdot -") do |stdin, _stdout, _stderr, _thread|
109
+ stdin.write(to_dot(direct: direct, transitive: transitive))
110
+ stdin.close
111
+ end
112
+ end
113
+
114
+ # Return the POSet as a DOT graph
115
+ sig { params(direct: T::Boolean, transitive: T::Boolean).returns(String) }
116
+ def to_dot(direct: true, transitive: true)
117
+ dot = +"digraph {\n"
118
+ dot << " rankdir=BT;\n"
119
+ @elements.each do |value, element|
120
+ dot << " \"#{value}\";\n"
121
+ if direct
122
+ element.parents.each do |to|
123
+ dot << " \"#{value}\" -> \"#{to}\";\n"
124
+ end
125
+ end
126
+ if transitive # rubocop:disable Style/Next
127
+ element.ancestors.each do |ancestor|
128
+ dot << " \"#{value}\" -> \"#{ancestor}\" [style=dotted];\n"
129
+ end
130
+ end
131
+ end
132
+ dot << "}\n"
133
+ end
134
+
135
+ # An element in a POSet
136
+ class Element
137
+ extend T::Sig
138
+ extend T::Generic
139
+ include Comparable
140
+
141
+ E = type_member { { upper: Object } }
142
+
143
+ # The value held by this element
144
+ sig { returns(E) }
145
+ attr_reader :value
146
+
147
+ # Edges (direct and indirect) from this element to other elements in the same POSet
148
+ sig { returns(T::Set[Element[E]]) }
149
+ attr_reader :dtos, :tos, :dfroms, :froms
150
+
151
+ sig { params(value: E).void }
152
+ def initialize(value)
153
+ @value = value
154
+ @dtos = T.let(Set.new, T::Set[Element[E]])
155
+ @tos = T.let(Set.new, T::Set[Element[E]])
156
+ @dfroms = T.let(Set.new, T::Set[Element[E]])
157
+ @froms = T.let(Set.new, T::Set[Element[E]])
158
+ end
159
+
160
+ sig { params(other: T.untyped).returns(T.nilable(Integer)) }
161
+ def <=>(other)
162
+ return unless other.is_a?(Element)
163
+ return 0 if self == other
164
+
165
+ if tos.include?(other)
166
+ -1
167
+ elsif froms.include?(other)
168
+ 1
169
+ end
170
+ end
171
+
172
+ # Direct parents of this element
173
+ sig { returns(T::Array[E]) }
174
+ def parents
175
+ @dtos.map(&:value)
176
+ end
177
+
178
+ # Direct and indirect ancestors of this element
179
+ sig { returns(T::Array[E]) }
180
+ def ancestors
181
+ @tos.map(&:value)
182
+ end
183
+
184
+ # Direct children of this element
185
+ sig { returns(T::Array[E]) }
186
+ def children
187
+ @dfroms.map(&:value)
188
+ end
189
+
190
+ # Direct and indirect descendants of this element
191
+ sig { returns(T::Array[E]) }
192
+ def descendants
193
+ @froms.map(&:value)
194
+ end
195
+ end
196
+ end
197
+ end
@@ -18,6 +18,8 @@ module Spoom
18
18
  class Parser
19
19
  extend T::Sig
20
20
 
21
+ class ParseError < Spoom::Error; end
22
+
21
23
  HEADER = T.let(
22
24
  [
23
25
  "👋 Hey there! Heads up that this is not a release build of sorbet.",
@@ -97,14 +99,14 @@ module Spoom
97
99
 
98
100
  sig { params(error: Error).void }
99
101
  def open_error(error)
100
- raise "Error: Already parsing an error!" if @current_error
102
+ raise ParseError, "Error: Already parsing an error!" if @current_error
101
103
 
102
104
  @current_error = error
103
105
  end
104
106
 
105
107
  sig { void }
106
108
  def close_error
107
- raise "Error: Not already parsing an error!" unless @current_error
109
+ raise ParseError, "Error: Not already parsing an error!" unless @current_error
108
110
 
109
111
  @errors << @current_error
110
112
  @current_error = nil
@@ -112,7 +114,7 @@ module Spoom
112
114
 
113
115
  sig { params(line: String).void }
114
116
  def append_error(line)
115
- raise "Error: Not already parsing an error!" unless @current_error
117
+ raise ParseError, "Error: Not already parsing an error!" unless @current_error
116
118
 
117
119
  filepath_match = line.match(/^ (.*?):\d+/)
118
120
  if filepath_match && filepath_match[1]
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Spoom
5
5
  module LSP
6
- class Error < StandardError
6
+ class Error < Spoom::Error
7
7
  class AlreadyOpen < Error; end
8
8
  class BadHeaders < Error; end
9
9
 
data/lib/spoom/sorbet.rb CHANGED
@@ -11,7 +11,7 @@ require "open3"
11
11
 
12
12
  module Spoom
13
13
  module Sorbet
14
- class Error < StandardError
14
+ class Error < Spoom::Error
15
15
  extend T::Sig
16
16
 
17
17
  class Killed < Error; end
data/lib/spoom/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Spoom
5
- VERSION = "1.3.0"
5
+ VERSION = "1.4.2"
6
6
  end