spoom 1.3.1 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spoom/cli/deadcode.rb +21 -17
  3. data/lib/spoom/deadcode/index.rb +178 -10
  4. data/lib/spoom/deadcode/indexer.rb +14 -435
  5. data/lib/spoom/deadcode/plugins/action_mailer.rb +3 -3
  6. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +9 -3
  7. data/lib/spoom/deadcode/plugins/actionpack.rb +12 -9
  8. data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
  9. data/lib/spoom/deadcode/plugins/active_record.rb +5 -5
  10. data/lib/spoom/deadcode/plugins/active_support.rb +4 -4
  11. data/lib/spoom/deadcode/plugins/base.rb +70 -57
  12. data/lib/spoom/deadcode/plugins/graphql.rb +8 -8
  13. data/lib/spoom/deadcode/plugins/minitest.rb +4 -3
  14. data/lib/spoom/deadcode/plugins/namespaces.rb +9 -12
  15. data/lib/spoom/deadcode/plugins/rails.rb +9 -9
  16. data/lib/spoom/deadcode/plugins/rubocop.rb +13 -17
  17. data/lib/spoom/deadcode/plugins/ruby.rb +9 -9
  18. data/lib/spoom/deadcode/plugins/sorbet.rb +15 -18
  19. data/lib/spoom/deadcode/plugins/thor.rb +5 -4
  20. data/lib/spoom/deadcode/plugins.rb +4 -5
  21. data/lib/spoom/deadcode/remover.rb +14 -10
  22. data/lib/spoom/deadcode/send.rb +1 -0
  23. data/lib/spoom/deadcode.rb +4 -73
  24. data/lib/spoom/location.rb +84 -0
  25. data/lib/spoom/model/builder.rb +246 -0
  26. data/lib/spoom/model/model.rb +328 -0
  27. data/lib/spoom/model/namespace_visitor.rb +50 -0
  28. data/lib/spoom/model/reference.rb +49 -0
  29. data/lib/spoom/model/references_visitor.rb +200 -0
  30. data/lib/spoom/model.rb +10 -0
  31. data/lib/spoom/parse.rb +28 -0
  32. data/lib/spoom/poset.rb +197 -0
  33. data/lib/spoom/sorbet/errors.rb +5 -3
  34. data/lib/spoom/sorbet/lsp/errors.rb +1 -1
  35. data/lib/spoom/sorbet.rb +1 -1
  36. data/lib/spoom/version.rb +1 -1
  37. data/lib/spoom/visitor.rb +755 -0
  38. data/lib/spoom.rb +2 -0
  39. metadata +20 -13
  40. data/lib/spoom/deadcode/location.rb +0 -86
  41. data/lib/spoom/deadcode/reference.rb +0 -34
  42. 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.1"
5
+ VERSION = "1.3.3"
6
6
  end