spoom 1.5.4 → 1.6.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.
- checksums.yaml +4 -4
- data/lib/spoom/backtrace_filter/minitest.rb +2 -3
- data/lib/spoom/cli/deadcode.rb +1 -2
- data/lib/spoom/cli/helper.rb +36 -28
- data/lib/spoom/cli/srb/assertions.rb +48 -0
- data/lib/spoom/cli/srb/bump.rb +1 -2
- data/lib/spoom/cli/srb/sigs.rb +133 -18
- data/lib/spoom/cli/srb.rb +8 -4
- data/lib/spoom/cli.rb +1 -2
- data/lib/spoom/colors.rb +2 -6
- data/lib/spoom/context/bundle.rb +8 -9
- data/lib/spoom/context/exec.rb +2 -5
- data/lib/spoom/context/file_system.rb +12 -19
- data/lib/spoom/context/git.rb +14 -19
- data/lib/spoom/context/sorbet.rb +13 -26
- data/lib/spoom/context.rb +3 -7
- data/lib/spoom/coverage/d3/base.rb +6 -8
- data/lib/spoom/coverage/d3/circle_map.rb +6 -16
- data/lib/spoom/coverage/d3/pie.rb +14 -19
- data/lib/spoom/coverage/d3/timeline.rb +46 -47
- data/lib/spoom/coverage/d3.rb +2 -4
- data/lib/spoom/coverage/report.rb +38 -76
- data/lib/spoom/coverage/snapshot.rb +7 -13
- data/lib/spoom/coverage.rb +3 -5
- data/lib/spoom/deadcode/definition.rb +12 -14
- data/lib/spoom/deadcode/erb.rb +10 -8
- data/lib/spoom/deadcode/index.rb +19 -23
- data/lib/spoom/deadcode/indexer.rb +5 -6
- data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -3
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +2 -3
- data/lib/spoom/deadcode/plugins/actionpack.rb +4 -4
- data/lib/spoom/deadcode/plugins/active_model.rb +2 -3
- data/lib/spoom/deadcode/plugins/active_record.rb +2 -3
- data/lib/spoom/deadcode/plugins/active_support.rb +2 -1
- data/lib/spoom/deadcode/plugins/base.rb +29 -32
- data/lib/spoom/deadcode/plugins/graphql.rb +2 -3
- data/lib/spoom/deadcode/plugins/minitest.rb +4 -4
- data/lib/spoom/deadcode/plugins/namespaces.rb +5 -5
- data/lib/spoom/deadcode/plugins/rails.rb +5 -5
- data/lib/spoom/deadcode/plugins/rubocop.rb +4 -4
- data/lib/spoom/deadcode/plugins/ruby.rb +3 -4
- data/lib/spoom/deadcode/plugins/sorbet.rb +12 -6
- data/lib/spoom/deadcode/plugins/thor.rb +2 -3
- data/lib/spoom/deadcode/plugins.rb +2 -4
- data/lib/spoom/deadcode/remover.rb +37 -59
- data/lib/spoom/deadcode/send.rb +2 -8
- data/lib/spoom/file_collector.rb +10 -18
- data/lib/spoom/file_tree.rb +31 -46
- data/lib/spoom/location.rb +9 -20
- data/lib/spoom/model/builder.rb +60 -15
- data/lib/spoom/model/model.rb +65 -68
- data/lib/spoom/model/namespace_visitor.rb +3 -2
- data/lib/spoom/model/reference.rb +4 -8
- data/lib/spoom/model/references_visitor.rb +49 -29
- data/lib/spoom/parse.rb +17 -3
- data/lib/spoom/poset.rb +17 -19
- data/lib/spoom/printer.rb +10 -13
- data/lib/spoom/sorbet/assertions.rb +278 -0
- data/lib/spoom/sorbet/config.rb +8 -12
- data/lib/spoom/sorbet/errors.rb +16 -31
- data/lib/spoom/sorbet/lsp/base.rb +9 -15
- data/lib/spoom/sorbet/lsp/errors.rb +8 -16
- data/lib/spoom/sorbet/lsp/structures.rb +36 -59
- data/lib/spoom/sorbet/lsp.rb +15 -17
- data/lib/spoom/sorbet/metrics.rb +3 -5
- data/lib/spoom/sorbet/sigils.rb +7 -11
- data/lib/spoom/sorbet/sigs.rb +118 -25
- data/lib/spoom/sorbet.rb +3 -9
- data/lib/spoom/timeline.rb +4 -6
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom/visitor.rb +298 -151
- data/lib/spoom.rb +0 -2
- data/rbi/spoom.rbi +3963 -0
- metadata +6 -3
data/lib/spoom/poset.rb
CHANGED
@@ -7,14 +7,13 @@ module Spoom
|
|
7
7
|
# The partial order relation is a binary relation that is reflexive, antisymmetric, and transitive.
|
8
8
|
# It can be used to represent a hierarchy of classes or modules, the dependencies between gems, etc.
|
9
9
|
class Poset
|
10
|
-
extend T::Sig
|
11
10
|
extend T::Generic
|
12
11
|
|
13
12
|
class Error < Spoom::Error; end
|
14
13
|
|
15
14
|
E = type_member { { upper: Object } }
|
16
15
|
|
17
|
-
|
16
|
+
#: -> void
|
18
17
|
def initialize
|
19
18
|
@elements = T.let({}, T::Hash[E, Element[E]])
|
20
19
|
end
|
@@ -22,7 +21,7 @@ module Spoom
|
|
22
21
|
# Get the POSet element for a given value
|
23
22
|
#
|
24
23
|
# Raises if the element is not found
|
25
|
-
|
24
|
+
#: (E value) -> Element[E]
|
26
25
|
def [](value)
|
27
26
|
element = @elements[value]
|
28
27
|
raise Error, "POSet::Element not found for #{value}" unless element
|
@@ -31,7 +30,7 @@ module Spoom
|
|
31
30
|
end
|
32
31
|
|
33
32
|
# Add an element to the POSet
|
34
|
-
|
33
|
+
#: (E value) -> Element[E]
|
35
34
|
def add_element(value)
|
36
35
|
element = @elements[value]
|
37
36
|
return element if element
|
@@ -40,7 +39,7 @@ module Spoom
|
|
40
39
|
end
|
41
40
|
|
42
41
|
# Is the given value a element in the POSet?
|
43
|
-
|
42
|
+
#: (E value) -> bool
|
44
43
|
def element?(value)
|
45
44
|
@elements.key?(value)
|
46
45
|
end
|
@@ -50,7 +49,7 @@ module Spoom
|
|
50
49
|
# Transitive edges (transitive closure) are automatically computed.
|
51
50
|
# Adds the elements if they don't exist.
|
52
51
|
# If the direct edge already exists, nothing is done.
|
53
|
-
|
52
|
+
#: (E from, E to) -> void
|
54
53
|
def add_direct_edge(from, to)
|
55
54
|
from_element = add_element(from)
|
56
55
|
to_element = add_element(to)
|
@@ -88,7 +87,7 @@ module Spoom
|
|
88
87
|
end
|
89
88
|
|
90
89
|
# Is there an edge (direct or indirect) from `from` to `to`?
|
91
|
-
|
90
|
+
#: (E from, E to) -> bool
|
92
91
|
def edge?(from, to)
|
93
92
|
from_element = @elements[from]
|
94
93
|
return false unless from_element
|
@@ -97,13 +96,13 @@ module Spoom
|
|
97
96
|
end
|
98
97
|
|
99
98
|
# Is there a direct edge from `from` to `to`?
|
100
|
-
|
99
|
+
#: (E from, E to) -> bool
|
101
100
|
def direct_edge?(from, to)
|
102
101
|
self[from].parents.include?(to)
|
103
102
|
end
|
104
103
|
|
105
104
|
# Show the POSet as a DOT graph using xdot (used for debugging)
|
106
|
-
|
105
|
+
#: (?direct: bool, ?transitive: bool) -> void
|
107
106
|
def show_dot(direct: true, transitive: true)
|
108
107
|
Open3.popen3("xdot -") do |stdin, _stdout, _stderr, _thread|
|
109
108
|
stdin.write(to_dot(direct: direct, transitive: transitive))
|
@@ -112,7 +111,7 @@ module Spoom
|
|
112
111
|
end
|
113
112
|
|
114
113
|
# Return the POSet as a DOT graph
|
115
|
-
|
114
|
+
#: (?direct: bool, ?transitive: bool) -> String
|
116
115
|
def to_dot(direct: true, transitive: true)
|
117
116
|
dot = +"digraph {\n"
|
118
117
|
dot << " rankdir=BT;\n"
|
@@ -134,21 +133,20 @@ module Spoom
|
|
134
133
|
|
135
134
|
# An element in a POSet
|
136
135
|
class Element
|
137
|
-
extend T::Sig
|
138
136
|
extend T::Generic
|
139
137
|
include Comparable
|
140
138
|
|
141
139
|
E = type_member { { upper: Object } }
|
142
140
|
|
143
141
|
# The value held by this element
|
144
|
-
|
142
|
+
#: E
|
145
143
|
attr_reader :value
|
146
144
|
|
147
145
|
# Edges (direct and indirect) from this element to other elements in the same POSet
|
148
|
-
|
146
|
+
#: Set[Element[E]]
|
149
147
|
attr_reader :dtos, :tos, :dfroms, :froms
|
150
148
|
|
151
|
-
|
149
|
+
#: (E value) -> void
|
152
150
|
def initialize(value)
|
153
151
|
@value = value
|
154
152
|
@dtos = T.let(Set.new, T::Set[Element[E]])
|
@@ -157,7 +155,7 @@ module Spoom
|
|
157
155
|
@froms = T.let(Set.new, T::Set[Element[E]])
|
158
156
|
end
|
159
157
|
|
160
|
-
|
158
|
+
#: (untyped other) -> Integer?
|
161
159
|
def <=>(other)
|
162
160
|
return unless other.is_a?(Element)
|
163
161
|
return 0 if self == other
|
@@ -170,25 +168,25 @@ module Spoom
|
|
170
168
|
end
|
171
169
|
|
172
170
|
# Direct parents of this element
|
173
|
-
|
171
|
+
#: -> Array[E]
|
174
172
|
def parents
|
175
173
|
@dtos.map(&:value)
|
176
174
|
end
|
177
175
|
|
178
176
|
# Direct and indirect ancestors of this element
|
179
|
-
|
177
|
+
#: -> Array[E]
|
180
178
|
def ancestors
|
181
179
|
@tos.map(&:value)
|
182
180
|
end
|
183
181
|
|
184
182
|
# Direct children of this element
|
185
|
-
|
183
|
+
#: -> Array[E]
|
186
184
|
def children
|
187
185
|
@dfroms.map(&:value)
|
188
186
|
end
|
189
187
|
|
190
188
|
# Direct and indirect descendants of this element
|
191
|
-
|
189
|
+
#: -> Array[E]
|
192
190
|
def descendants
|
193
191
|
@froms.map(&:value)
|
194
192
|
end
|
data/lib/spoom/printer.rb
CHANGED
@@ -5,15 +5,12 @@ require "stringio"
|
|
5
5
|
|
6
6
|
module Spoom
|
7
7
|
class Printer
|
8
|
-
extend T::Sig
|
9
|
-
extend T::Helpers
|
10
|
-
|
11
8
|
include Colorize
|
12
9
|
|
13
|
-
|
10
|
+
#: (IO | StringIO)
|
14
11
|
attr_accessor :out
|
15
12
|
|
16
|
-
|
13
|
+
#: (?out: (IO | StringIO), ?colors: bool, ?indent_level: Integer) -> void
|
17
14
|
def initialize(out: $stdout, colors: true, indent_level: 0)
|
18
15
|
@out = out
|
19
16
|
@colors = colors
|
@@ -21,19 +18,19 @@ module Spoom
|
|
21
18
|
end
|
22
19
|
|
23
20
|
# Increase indent level
|
24
|
-
|
21
|
+
#: -> void
|
25
22
|
def indent
|
26
23
|
@indent_level += 2
|
27
24
|
end
|
28
25
|
|
29
26
|
# Decrease indent level
|
30
|
-
|
27
|
+
#: -> void
|
31
28
|
def dedent
|
32
29
|
@indent_level -= 2
|
33
30
|
end
|
34
31
|
|
35
32
|
# Print `string` into `out`
|
36
|
-
|
33
|
+
#: (String? string) -> void
|
37
34
|
def print(string)
|
38
35
|
return unless string
|
39
36
|
|
@@ -43,7 +40,7 @@ module Spoom
|
|
43
40
|
# Print `string` colored with `color` into `out`
|
44
41
|
#
|
45
42
|
# Does not use colors unless `@colors`.
|
46
|
-
|
43
|
+
#: (String? string, *Color color) -> void
|
47
44
|
def print_colored(string, *color)
|
48
45
|
return unless string
|
49
46
|
|
@@ -52,13 +49,13 @@ module Spoom
|
|
52
49
|
end
|
53
50
|
|
54
51
|
# Print a new line into `out`
|
55
|
-
|
52
|
+
#: -> void
|
56
53
|
def printn
|
57
54
|
print("\n")
|
58
55
|
end
|
59
56
|
|
60
57
|
# Print `string` with indent and newline
|
61
|
-
|
58
|
+
#: (String? string) -> void
|
62
59
|
def printl(string)
|
63
60
|
return unless string
|
64
61
|
|
@@ -68,13 +65,13 @@ module Spoom
|
|
68
65
|
end
|
69
66
|
|
70
67
|
# Print an indent space into `out`
|
71
|
-
|
68
|
+
#: -> void
|
72
69
|
def printt
|
73
70
|
print(" " * @indent_level)
|
74
71
|
end
|
75
72
|
|
76
73
|
# Colorize `string` with color if `@colors`
|
77
|
-
|
74
|
+
#: (String string, *Spoom::Color color) -> String
|
78
75
|
def colorize(string, *color)
|
79
76
|
return string unless @colors
|
80
77
|
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "rbi"
|
5
|
+
|
6
|
+
module Spoom
|
7
|
+
module Sorbet
|
8
|
+
class Assertions
|
9
|
+
class << self
|
10
|
+
#: (String, file: String) -> String
|
11
|
+
def rbi_to_rbs(ruby_contents, file:)
|
12
|
+
old_encoding = ruby_contents.encoding
|
13
|
+
ruby_contents = ruby_contents.encode("UTF-8") unless old_encoding == "UTF-8"
|
14
|
+
ruby_bytes = ruby_contents.bytes
|
15
|
+
|
16
|
+
assigns = collect_assigns(ruby_contents, file: file)
|
17
|
+
|
18
|
+
assigns.reverse.each do |assign|
|
19
|
+
# Adjust the end offset to locate the end of the line:
|
20
|
+
#
|
21
|
+
# So this:
|
22
|
+
#
|
23
|
+
# (a = T.let(nil, T.nilable(String)))
|
24
|
+
#
|
25
|
+
# properly becomes:
|
26
|
+
#
|
27
|
+
# (a = nil) #: String?
|
28
|
+
#
|
29
|
+
# This is important to avoid translating the `nil` as `nil` instead of `nil #: String?`
|
30
|
+
end_offset = assign.node.location.end_offset
|
31
|
+
end_offset += 1 while (ruby_bytes[end_offset] != "\n".ord) && (end_offset < ruby_bytes.size)
|
32
|
+
T.unsafe(ruby_bytes).insert(end_offset, *" #: #{assign.rbs_type}".bytes)
|
33
|
+
|
34
|
+
# Rewrite the value
|
35
|
+
start_offset = assign.operator_loc.end_offset
|
36
|
+
end_offset = assign.node.value.location.start_offset + assign.node.value.location.length
|
37
|
+
ruby_bytes[start_offset...end_offset] = " #{dedent_value(assign)}".bytes
|
38
|
+
end
|
39
|
+
|
40
|
+
ruby_bytes.pack("C*").force_encoding(old_encoding)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
#: (String, file: String) -> Array[AssignNode]
|
46
|
+
def collect_assigns(ruby_contents, file:)
|
47
|
+
node = Spoom.parse_ruby(ruby_contents, file: file)
|
48
|
+
visitor = Locator.new
|
49
|
+
visitor.visit(node)
|
50
|
+
visitor.assigns
|
51
|
+
end
|
52
|
+
|
53
|
+
#: (AssignNode) -> String
|
54
|
+
def dedent_value(assign)
|
55
|
+
if assign.value.location.start_line == assign.node.location.start_line
|
56
|
+
# The value is on the same line as the assign, so we can just return the slice as is:
|
57
|
+
# ```rb
|
58
|
+
# a = T.let(nil, T.nilable(String))
|
59
|
+
# ```
|
60
|
+
# becomes
|
61
|
+
# ```rb
|
62
|
+
# a = nil #: String?
|
63
|
+
# ```
|
64
|
+
return assign.value.slice
|
65
|
+
end
|
66
|
+
|
67
|
+
# The value is on a different line, so we need to dedent it:
|
68
|
+
# ```rb
|
69
|
+
# a = T.let(
|
70
|
+
# [
|
71
|
+
# 1, 2, 3,
|
72
|
+
# ],
|
73
|
+
# T::Array[Integer],
|
74
|
+
# )
|
75
|
+
# ```
|
76
|
+
# becomes
|
77
|
+
# ```rb
|
78
|
+
# a = [
|
79
|
+
# 1, 2, 3,
|
80
|
+
# ] #: Array[Integer]
|
81
|
+
# ```
|
82
|
+
indent = assign.value.location.start_line - assign.node.location.start_line
|
83
|
+
lines = assign.value.slice.lines
|
84
|
+
if lines.size > 1
|
85
|
+
lines[1..]&.each_with_index do |line, i|
|
86
|
+
lines[i + 1] = line.delete_prefix(" " * indent)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
lines.join
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
AssignType = T.type_alias do
|
94
|
+
T.any(
|
95
|
+
Prism::ClassVariableAndWriteNode,
|
96
|
+
Prism::ClassVariableOrWriteNode,
|
97
|
+
Prism::ClassVariableOperatorWriteNode,
|
98
|
+
Prism::ClassVariableWriteNode,
|
99
|
+
Prism::ConstantAndWriteNode,
|
100
|
+
Prism::ConstantOrWriteNode,
|
101
|
+
Prism::ConstantOperatorWriteNode,
|
102
|
+
Prism::ConstantWriteNode,
|
103
|
+
Prism::ConstantPathAndWriteNode,
|
104
|
+
Prism::ConstantPathOrWriteNode,
|
105
|
+
Prism::ConstantPathOperatorWriteNode,
|
106
|
+
Prism::ConstantPathWriteNode,
|
107
|
+
Prism::GlobalVariableAndWriteNode,
|
108
|
+
Prism::GlobalVariableOrWriteNode,
|
109
|
+
Prism::GlobalVariableOperatorWriteNode,
|
110
|
+
Prism::GlobalVariableWriteNode,
|
111
|
+
Prism::InstanceVariableAndWriteNode,
|
112
|
+
Prism::InstanceVariableOperatorWriteNode,
|
113
|
+
Prism::InstanceVariableOrWriteNode,
|
114
|
+
Prism::InstanceVariableWriteNode,
|
115
|
+
Prism::LocalVariableAndWriteNode,
|
116
|
+
Prism::LocalVariableOperatorWriteNode,
|
117
|
+
Prism::LocalVariableOrWriteNode,
|
118
|
+
Prism::LocalVariableWriteNode,
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
class AssignNode
|
123
|
+
#: AssignType
|
124
|
+
attr_reader :node
|
125
|
+
|
126
|
+
#: Prism::Location
|
127
|
+
attr_reader :operator_loc
|
128
|
+
|
129
|
+
#: Prism::Node
|
130
|
+
attr_reader :value, :type
|
131
|
+
|
132
|
+
#: (AssignType, Prism::Location, Prism::Node, Prism::Node) -> void
|
133
|
+
def initialize(node, operator_loc, value, type)
|
134
|
+
@node = node
|
135
|
+
@operator_loc = operator_loc
|
136
|
+
@value = value
|
137
|
+
@type = type
|
138
|
+
end
|
139
|
+
|
140
|
+
#: -> String
|
141
|
+
def rbs_type
|
142
|
+
RBI::Type.parse_node(type).rbs_string
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Locator < Spoom::Visitor
|
147
|
+
ANNOTATION_METHODS = T.let([:let], T::Array[Symbol])
|
148
|
+
|
149
|
+
#: Array[AssignNode]
|
150
|
+
attr_reader :assigns
|
151
|
+
|
152
|
+
#: -> void
|
153
|
+
def initialize
|
154
|
+
super
|
155
|
+
@assigns = T.let([], T::Array[AssignNode])
|
156
|
+
end
|
157
|
+
|
158
|
+
#: (AssignType) -> void
|
159
|
+
def visit_assign(node)
|
160
|
+
call = node.value
|
161
|
+
return unless call.is_a?(Prism::CallNode) && t_annotation?(call)
|
162
|
+
|
163
|
+
# We do not support translating heredocs yet because the `#: ` would need to be added to the first line
|
164
|
+
# and it will requires us to adapt the annotation detection in Sorbet. But Sorbet desugars them into bare
|
165
|
+
# strings making them impossible to detect.
|
166
|
+
value = T.must(call.arguments&.arguments&.first)
|
167
|
+
return if contains_heredoc?(value)
|
168
|
+
|
169
|
+
operator_loc = case node
|
170
|
+
when Prism::ClassVariableOperatorWriteNode,
|
171
|
+
Prism::ConstantOperatorWriteNode,
|
172
|
+
Prism::ConstantPathOperatorWriteNode,
|
173
|
+
Prism::GlobalVariableOperatorWriteNode,
|
174
|
+
Prism::InstanceVariableOperatorWriteNode,
|
175
|
+
Prism::LocalVariableOperatorWriteNode
|
176
|
+
node.binary_operator_loc
|
177
|
+
else
|
178
|
+
node.operator_loc
|
179
|
+
end
|
180
|
+
|
181
|
+
@assigns << AssignNode.new(
|
182
|
+
node,
|
183
|
+
operator_loc,
|
184
|
+
value,
|
185
|
+
T.must(call.arguments&.arguments&.last),
|
186
|
+
)
|
187
|
+
end
|
188
|
+
|
189
|
+
alias_method(:visit_class_variable_and_write_node, :visit_assign)
|
190
|
+
alias_method(:visit_class_variable_operator_write_node, :visit_assign)
|
191
|
+
alias_method(:visit_class_variable_or_write_node, :visit_assign)
|
192
|
+
alias_method(:visit_class_variable_write_node, :visit_assign)
|
193
|
+
|
194
|
+
alias_method(:visit_constant_and_write_node, :visit_assign)
|
195
|
+
alias_method(:visit_constant_operator_write_node, :visit_assign)
|
196
|
+
alias_method(:visit_constant_or_write_node, :visit_assign)
|
197
|
+
alias_method(:visit_constant_write_node, :visit_assign)
|
198
|
+
|
199
|
+
alias_method(:visit_constant_path_and_write_node, :visit_assign)
|
200
|
+
alias_method(:visit_constant_path_operator_write_node, :visit_assign)
|
201
|
+
alias_method(:visit_constant_path_or_write_node, :visit_assign)
|
202
|
+
alias_method(:visit_constant_path_write_node, :visit_assign)
|
203
|
+
|
204
|
+
alias_method(:visit_global_variable_and_write_node, :visit_assign)
|
205
|
+
alias_method(:visit_global_variable_operator_write_node, :visit_assign)
|
206
|
+
alias_method(:visit_global_variable_or_write_node, :visit_assign)
|
207
|
+
alias_method(:visit_global_variable_write_node, :visit_assign)
|
208
|
+
|
209
|
+
alias_method(:visit_instance_variable_and_write_node, :visit_assign)
|
210
|
+
alias_method(:visit_instance_variable_operator_write_node, :visit_assign)
|
211
|
+
alias_method(:visit_instance_variable_or_write_node, :visit_assign)
|
212
|
+
alias_method(:visit_instance_variable_write_node, :visit_assign)
|
213
|
+
|
214
|
+
alias_method(:visit_local_variable_and_write_node, :visit_assign)
|
215
|
+
alias_method(:visit_local_variable_operator_write_node, :visit_assign)
|
216
|
+
alias_method(:visit_local_variable_or_write_node, :visit_assign)
|
217
|
+
alias_method(:visit_local_variable_write_node, :visit_assign)
|
218
|
+
|
219
|
+
alias_method(:visit_multi_write_node, :visit_assign)
|
220
|
+
|
221
|
+
# Is this node a `T` or `::T` constant?
|
222
|
+
#: (Prism::Node?) -> bool
|
223
|
+
def t?(node)
|
224
|
+
case node
|
225
|
+
when Prism::ConstantReadNode
|
226
|
+
node.name == :T
|
227
|
+
when Prism::ConstantPathNode
|
228
|
+
node.parent.nil? && node.name == :T
|
229
|
+
else
|
230
|
+
false
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Is this node a `T.let` or `T.cast`?
|
235
|
+
#: (Prism::CallNode) -> bool
|
236
|
+
def t_annotation?(node)
|
237
|
+
return false unless t?(node.receiver)
|
238
|
+
return false unless ANNOTATION_METHODS.include?(node.name)
|
239
|
+
return false unless node.arguments&.arguments&.size == 2
|
240
|
+
|
241
|
+
true
|
242
|
+
end
|
243
|
+
|
244
|
+
#: (Prism::Node) -> bool
|
245
|
+
def contains_heredoc?(node)
|
246
|
+
visitor = HeredocVisitor.new
|
247
|
+
visitor.visit(node)
|
248
|
+
visitor.contains_heredoc
|
249
|
+
end
|
250
|
+
|
251
|
+
class HeredocVisitor < Spoom::Visitor
|
252
|
+
#: bool
|
253
|
+
attr_reader :contains_heredoc
|
254
|
+
|
255
|
+
#: -> void
|
256
|
+
def initialize
|
257
|
+
@contains_heredoc = T.let(false, T::Boolean)
|
258
|
+
|
259
|
+
super
|
260
|
+
end
|
261
|
+
|
262
|
+
# @override
|
263
|
+
#: (Prism::Node?) -> void
|
264
|
+
def visit(node)
|
265
|
+
return if node.nil?
|
266
|
+
|
267
|
+
case node
|
268
|
+
when Prism::StringNode, Prism::InterpolatedStringNode
|
269
|
+
return @contains_heredoc = !!node.opening_loc&.slice&.match?(/<<~|<<-/)
|
270
|
+
end
|
271
|
+
|
272
|
+
super
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
data/lib/spoom/sorbet/config.rb
CHANGED
@@ -24,17 +24,15 @@ module Spoom
|
|
24
24
|
# puts config.ignore # "c"
|
25
25
|
# ```
|
26
26
|
class Config
|
27
|
-
extend T::Sig
|
28
|
-
|
29
27
|
DEFAULT_ALLOWED_EXTENSIONS = T.let([".rb", ".rbi"].freeze, T::Array[String])
|
30
28
|
|
31
|
-
|
29
|
+
#: Array[String]
|
32
30
|
attr_accessor :paths, :ignore, :allowed_extensions
|
33
31
|
|
34
|
-
|
32
|
+
#: bool
|
35
33
|
attr_accessor :no_stdlib
|
36
34
|
|
37
|
-
|
35
|
+
#: -> void
|
38
36
|
def initialize
|
39
37
|
@paths = T.let([], T::Array[String])
|
40
38
|
@ignore = T.let([], T::Array[String])
|
@@ -42,7 +40,7 @@ module Spoom
|
|
42
40
|
@no_stdlib = T.let(false, T::Boolean)
|
43
41
|
end
|
44
42
|
|
45
|
-
|
43
|
+
#: -> Config
|
46
44
|
def copy
|
47
45
|
new_config = Sorbet::Config.new
|
48
46
|
new_config.paths.concat(@paths)
|
@@ -64,7 +62,7 @@ module Spoom
|
|
64
62
|
#
|
65
63
|
# puts config.options_string # "/foo /bar --ignore /baz --allowed-extension .rb"
|
66
64
|
# ~~~
|
67
|
-
|
65
|
+
#: -> String
|
68
66
|
def options_string
|
69
67
|
opts = []
|
70
68
|
opts.concat(paths.map { |p| "'#{p}'" })
|
@@ -75,14 +73,12 @@ module Spoom
|
|
75
73
|
end
|
76
74
|
|
77
75
|
class << self
|
78
|
-
|
79
|
-
|
80
|
-
sig { params(sorbet_config_path: String).returns(Spoom::Sorbet::Config) }
|
76
|
+
#: (String sorbet_config_path) -> Spoom::Sorbet::Config
|
81
77
|
def parse_file(sorbet_config_path)
|
82
78
|
parse_string(File.read(sorbet_config_path))
|
83
79
|
end
|
84
80
|
|
85
|
-
|
81
|
+
#: (String sorbet_config) -> Spoom::Sorbet::Config
|
86
82
|
def parse_string(sorbet_config)
|
87
83
|
config = Config.new
|
88
84
|
state = T.let(nil, T.nilable(Symbol))
|
@@ -143,7 +139,7 @@ module Spoom
|
|
143
139
|
|
144
140
|
private
|
145
141
|
|
146
|
-
|
142
|
+
#: (String line) -> String
|
147
143
|
def parse_option(line)
|
148
144
|
T.must(line.split("=").last).strip
|
149
145
|
end
|