spoom 1.3.2 → 1.3.3
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.
- checksums.yaml +4 -4
- data/lib/spoom/cli/deadcode.rb +21 -17
- data/lib/spoom/deadcode/index.rb +178 -10
- data/lib/spoom/deadcode/indexer.rb +14 -435
- data/lib/spoom/deadcode/plugins/action_mailer.rb +3 -3
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +9 -3
- data/lib/spoom/deadcode/plugins/actionpack.rb +12 -9
- data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
- data/lib/spoom/deadcode/plugins/active_record.rb +5 -5
- data/lib/spoom/deadcode/plugins/active_support.rb +4 -4
- data/lib/spoom/deadcode/plugins/base.rb +70 -57
- data/lib/spoom/deadcode/plugins/graphql.rb +8 -8
- data/lib/spoom/deadcode/plugins/minitest.rb +4 -3
- data/lib/spoom/deadcode/plugins/namespaces.rb +9 -12
- data/lib/spoom/deadcode/plugins/rails.rb +9 -9
- data/lib/spoom/deadcode/plugins/rubocop.rb +13 -17
- data/lib/spoom/deadcode/plugins/ruby.rb +9 -9
- data/lib/spoom/deadcode/plugins/sorbet.rb +15 -18
- data/lib/spoom/deadcode/plugins/thor.rb +5 -4
- data/lib/spoom/deadcode/plugins.rb +4 -5
- data/lib/spoom/deadcode/remover.rb +7 -7
- data/lib/spoom/deadcode/send.rb +1 -0
- data/lib/spoom/deadcode.rb +4 -73
- data/lib/spoom/location.rb +84 -0
- data/lib/spoom/model/builder.rb +246 -0
- data/lib/spoom/model/model.rb +328 -0
- data/lib/spoom/model/namespace_visitor.rb +50 -0
- data/lib/spoom/model/reference.rb +49 -0
- data/lib/spoom/model/references_visitor.rb +200 -0
- data/lib/spoom/model.rb +10 -0
- data/lib/spoom/parse.rb +28 -0
- data/lib/spoom/poset.rb +197 -0
- data/lib/spoom/sorbet/errors.rb +5 -3
- data/lib/spoom/sorbet/lsp/errors.rb +1 -1
- data/lib/spoom/sorbet.rb +1 -1
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom/visitor.rb +755 -0
- data/lib/spoom.rb +2 -0
- metadata +20 -13
- data/lib/spoom/deadcode/location.rb +0 -86
- data/lib/spoom/deadcode/reference.rb +0 -34
- data/lib/spoom/deadcode/visitor.rb +0 -755
data/lib/spoom/poset.rb
ADDED
@@ -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
|
data/lib/spoom/sorbet/errors.rb
CHANGED
@@ -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]
|
data/lib/spoom/sorbet.rb
CHANGED
data/lib/spoom/version.rb
CHANGED