spoom 1.1.16 → 1.2.1
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/bump.rb +6 -15
- data/lib/spoom/cli/config.rb +4 -3
- data/lib/spoom/cli/coverage.rb +14 -15
- data/lib/spoom/cli/helper.rb +11 -21
- data/lib/spoom/cli/lsp.rb +4 -2
- data/lib/spoom/cli/run.rb +3 -7
- data/lib/spoom/cli.rb +6 -13
- data/lib/spoom/context/bundle.rb +58 -0
- data/lib/spoom/context/exec.rb +50 -0
- data/lib/spoom/context/file_system.rb +93 -0
- data/lib/spoom/context/git.rb +121 -0
- data/lib/spoom/context/sorbet.rb +154 -0
- data/lib/spoom/context.rb +16 -200
- data/lib/spoom/coverage/d3/circle_map.rb +22 -36
- data/lib/spoom/coverage/report.rb +45 -32
- data/lib/spoom/coverage.rb +27 -42
- data/lib/spoom/file_collector.rb +79 -0
- data/lib/spoom/file_tree.rb +168 -83
- data/lib/spoom/sorbet/config.rb +3 -1
- data/lib/spoom/sorbet/sigils.rb +0 -15
- data/lib/spoom/sorbet.rb +0 -119
- data/lib/spoom/timeline.rb +5 -8
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +1 -52
- metadata +9 -4
- data/lib/spoom/git.rb +0 -109
data/lib/spoom/coverage.rb
CHANGED
@@ -12,9 +12,9 @@ module Spoom
|
|
12
12
|
class << self
|
13
13
|
extend T::Sig
|
14
14
|
|
15
|
-
sig { params(
|
16
|
-
def snapshot(
|
17
|
-
config = sorbet_config
|
15
|
+
sig { params(context: Context, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
|
16
|
+
def snapshot(context, rbi: true, sorbet_bin: nil)
|
17
|
+
config = context.sorbet_config
|
18
18
|
config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
|
19
19
|
|
20
20
|
new_config = config.copy
|
@@ -27,25 +27,16 @@ module Spoom
|
|
27
27
|
new_config.options_string,
|
28
28
|
]
|
29
29
|
|
30
|
-
metrics =
|
31
|
-
|
32
|
-
path: path,
|
33
|
-
capture_err: true,
|
34
|
-
sorbet_bin: sorbet_bin,
|
35
|
-
)
|
30
|
+
metrics = context.srb_metrics(*flags, sorbet_bin: sorbet_bin)
|
31
|
+
|
36
32
|
# Collect extra information using a different configuration
|
37
33
|
flags << "--ignore sorbet/rbi/"
|
38
|
-
metrics_without_rbis =
|
39
|
-
*flags,
|
40
|
-
path: path,
|
41
|
-
capture_err: true,
|
42
|
-
sorbet_bin: sorbet_bin,
|
43
|
-
)
|
34
|
+
metrics_without_rbis = context.srb_metrics(*flags, sorbet_bin: sorbet_bin)
|
44
35
|
|
45
36
|
snapshot = Snapshot.new
|
46
37
|
return snapshot unless metrics
|
47
38
|
|
48
|
-
last_commit =
|
39
|
+
last_commit = context.git_last_commit
|
49
40
|
snapshot.commit_sha = last_commit&.sha
|
50
41
|
snapshot.commit_timestamp = last_commit&.timestamp
|
51
42
|
|
@@ -79,48 +70,42 @@ module Spoom
|
|
79
70
|
end
|
80
71
|
end
|
81
72
|
|
82
|
-
snapshot.version_static =
|
83
|
-
snapshot.version_runtime =
|
73
|
+
snapshot.version_static = context.gem_version_from_gemfile_lock("sorbet-static")
|
74
|
+
snapshot.version_runtime = context.gem_version_from_gemfile_lock("sorbet-runtime")
|
84
75
|
|
85
|
-
files =
|
76
|
+
files = context.srb_files(with_config: new_config)
|
86
77
|
snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
|
87
78
|
|
88
79
|
snapshot
|
89
80
|
end
|
90
81
|
|
91
|
-
sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette
|
92
|
-
def report(snapshots, palette
|
93
|
-
intro_commit =
|
82
|
+
sig { params(context: Context, snapshots: T::Array[Snapshot], palette: D3::ColorPalette).returns(Report) }
|
83
|
+
def report(context, snapshots, palette:)
|
84
|
+
intro_commit = context.sorbet_intro_commit
|
85
|
+
|
86
|
+
file_tree = file_tree(context)
|
87
|
+
v = FileTree::CollectScores.new(context)
|
88
|
+
v.visit_tree(file_tree)
|
94
89
|
|
95
90
|
Report.new(
|
96
|
-
project_name: File.basename(
|
91
|
+
project_name: File.basename(context.absolute_path),
|
97
92
|
palette: palette,
|
98
93
|
snapshots: snapshots,
|
99
|
-
|
94
|
+
file_tree: file_tree,
|
95
|
+
nodes_strictnesses: v.strictnesses,
|
96
|
+
nodes_strictness_scores: v.scores,
|
100
97
|
sorbet_intro_commit: intro_commit&.sha,
|
101
98
|
sorbet_intro_date: intro_commit&.time,
|
102
99
|
)
|
103
100
|
end
|
104
101
|
|
105
|
-
sig { params(
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
sig { params(path: String).returns(FileTree) }
|
111
|
-
def sigils_tree(path: ".")
|
112
|
-
config = sorbet_config(path: path)
|
113
|
-
files = Sorbet.srb_files(config, path: path)
|
114
|
-
|
115
|
-
extensions = config.allowed_extensions
|
116
|
-
extensions = [".rb"] if extensions.empty?
|
117
|
-
extensions -= [".rbi"]
|
118
|
-
|
119
|
-
pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
|
120
|
-
files.select! { |file| file =~ pattern }
|
121
|
-
files.reject! { |file| file =~ %r{/test/} }
|
102
|
+
sig { params(context: Context).returns(FileTree) }
|
103
|
+
def file_tree(context)
|
104
|
+
config = context.sorbet_config
|
105
|
+
config.ignore += ["test"]
|
122
106
|
|
123
|
-
|
107
|
+
files = context.srb_files(with_config: config, include_rbis: false)
|
108
|
+
FileTree.new(files)
|
124
109
|
end
|
125
110
|
end
|
126
111
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
class FileCollector
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns(T::Array[String]) }
|
9
|
+
attr_reader :files
|
10
|
+
|
11
|
+
# Initialize a new file collector
|
12
|
+
#
|
13
|
+
# If `allow_extensions` is empty, all files are collected.
|
14
|
+
# If `allow_extensions` is an array of extensions, only files with one of these extensions are collected.
|
15
|
+
sig do
|
16
|
+
params(
|
17
|
+
allow_extensions: T::Array[String],
|
18
|
+
exclude_patterns: T::Array[String],
|
19
|
+
).void
|
20
|
+
end
|
21
|
+
def initialize(allow_extensions: [], exclude_patterns: [])
|
22
|
+
@files = T.let([], T::Array[String])
|
23
|
+
@allow_extensions = allow_extensions
|
24
|
+
@exclude_patterns = exclude_patterns
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(paths: T::Array[String]).void }
|
28
|
+
def visit_paths(paths)
|
29
|
+
paths.each { |path| visit_path(path) }
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(path: String).void }
|
33
|
+
def visit_path(path)
|
34
|
+
path = clean_path(path)
|
35
|
+
|
36
|
+
return if excluded_path?(path)
|
37
|
+
|
38
|
+
if File.file?(path)
|
39
|
+
visit_file(path)
|
40
|
+
elsif File.directory?(path)
|
41
|
+
visit_directory(path)
|
42
|
+
else # rubocop:disable Style/EmptyElse
|
43
|
+
# Ignore aliases, sockets, etc.
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
sig { params(path: String).returns(String) }
|
50
|
+
def clean_path(path)
|
51
|
+
Pathname.new(path).cleanpath.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(path: String).void }
|
55
|
+
def visit_file(path)
|
56
|
+
return if excluded_file?(path)
|
57
|
+
|
58
|
+
@files << path
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { params(path: String).void }
|
62
|
+
def visit_directory(path)
|
63
|
+
visit_paths(Dir.glob("#{path}/*"))
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { params(path: String).returns(T::Boolean) }
|
67
|
+
def excluded_file?(path)
|
68
|
+
return false if @allow_extensions.empty?
|
69
|
+
|
70
|
+
extension = File.extname(path)
|
71
|
+
@allow_extensions.none? { |allowed| extension == allowed }
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(path: String).returns(T::Boolean) }
|
75
|
+
def excluded_path?(path)
|
76
|
+
@exclude_patterns.any? { |pattern| File.fnmatch?(pattern, path) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/spoom/file_tree.rb
CHANGED
@@ -6,13 +6,9 @@ module Spoom
|
|
6
6
|
class FileTree
|
7
7
|
extend T::Sig
|
8
8
|
|
9
|
-
sig {
|
10
|
-
|
11
|
-
|
12
|
-
sig { params(paths: T::Enumerable[String], strip_prefix: T.nilable(String)).void }
|
13
|
-
def initialize(paths = [], strip_prefix: nil)
|
9
|
+
sig { params(paths: T::Enumerable[String]).void }
|
10
|
+
def initialize(paths = [])
|
14
11
|
@roots = T.let({}, T::Hash[String, Node])
|
15
|
-
@strip_prefix = strip_prefix
|
16
12
|
add_paths(paths)
|
17
13
|
end
|
18
14
|
|
@@ -27,8 +23,6 @@ module Spoom
|
|
27
23
|
# This will create all nodes until the root of `path`.
|
28
24
|
sig { params(path: String).returns(Node) }
|
29
25
|
def add_path(path)
|
30
|
-
prefix = @strip_prefix
|
31
|
-
path = path.delete_prefix("#{prefix}/") if prefix
|
32
26
|
parts = path.split("/")
|
33
27
|
if path.empty? || parts.size == 1
|
34
28
|
return @roots[path] ||= Node.new(parent: nil, name: path)
|
@@ -49,43 +43,51 @@ module Spoom
|
|
49
43
|
# All the nodes in this tree
|
50
44
|
sig { returns(T::Array[Node]) }
|
51
45
|
def nodes
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
v = CollectNodes.new
|
47
|
+
v.visit_tree(self)
|
48
|
+
v.nodes
|
55
49
|
end
|
56
50
|
|
57
51
|
# All the paths in this tree
|
58
52
|
sig { returns(T::Array[String]) }
|
59
53
|
def paths
|
60
|
-
nodes.
|
54
|
+
nodes.map(&:path)
|
61
55
|
end
|
62
56
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
).void
|
57
|
+
# Return a map of strictnesses for each node in the tree
|
58
|
+
sig { params(context: Context).returns(T::Hash[Node, T.nilable(String)]) }
|
59
|
+
def nodes_strictnesses(context)
|
60
|
+
v = CollectStrictnesses.new(context)
|
61
|
+
v.visit_tree(self)
|
62
|
+
v.strictnesses
|
70
63
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
)
|
79
|
-
printer.print_tree
|
64
|
+
|
65
|
+
# Return a map of typing scores for each node in the tree
|
66
|
+
sig { params(context: Context).returns(T::Hash[Node, Float]) }
|
67
|
+
def nodes_strictness_scores(context)
|
68
|
+
v = CollectScores.new(context)
|
69
|
+
v.visit_tree(self)
|
70
|
+
v.scores
|
80
71
|
end
|
81
72
|
|
82
|
-
|
73
|
+
# Return a map of typing scores for each path in the tree
|
74
|
+
sig { params(context: Context).returns(T::Hash[String, Float]) }
|
75
|
+
def paths_strictness_scores(context)
|
76
|
+
nodes_strictness_scores(context).map { |node, score| [node.path, score] }.to_h
|
77
|
+
end
|
83
78
|
|
84
|
-
sig { params(
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
79
|
+
sig { params(out: T.any(IO, StringIO), colors: T::Boolean).void }
|
80
|
+
def print(out: $stdout, colors: true)
|
81
|
+
printer = Printer.new({}, out: out, colors: colors)
|
82
|
+
printer.visit_tree(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
sig { params(context: Context, out: T.any(IO, StringIO), colors: T::Boolean).void }
|
86
|
+
def print_with_strictnesses(context, out: $stdout, colors: true)
|
87
|
+
strictnesses = nodes_strictnesses(context)
|
88
|
+
|
89
|
+
printer = Printer.new(strictnesses, out: out, colors: colors)
|
90
|
+
printer.visit_tree(self)
|
89
91
|
end
|
90
92
|
|
91
93
|
# A node representing either a file or a directory inside a FileTree
|
@@ -111,77 +113,160 @@ module Spoom
|
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
116
|
+
# An abstract visitor for FileTree
|
117
|
+
class Visitor
|
118
|
+
extend T::Sig
|
119
|
+
extend T::Helpers
|
120
|
+
|
121
|
+
abstract!
|
122
|
+
|
123
|
+
sig { params(tree: FileTree).void }
|
124
|
+
def visit_tree(tree)
|
125
|
+
visit_nodes(tree.roots)
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { params(node: FileTree::Node).void }
|
129
|
+
def visit_node(node)
|
130
|
+
visit_nodes(node.children.values)
|
131
|
+
end
|
132
|
+
|
133
|
+
sig { params(nodes: T::Array[FileTree::Node]).void }
|
134
|
+
def visit_nodes(nodes)
|
135
|
+
nodes.each { |node| visit_node(node) }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# A visitor that collects all the nodes in a tree
|
140
|
+
class CollectNodes < Visitor
|
141
|
+
extend T::Sig
|
142
|
+
|
143
|
+
sig { returns(T::Array[FileTree::Node]) }
|
144
|
+
attr_reader :nodes
|
145
|
+
|
146
|
+
sig { void }
|
147
|
+
def initialize
|
148
|
+
super()
|
149
|
+
@nodes = T.let([], T::Array[FileTree::Node])
|
150
|
+
end
|
151
|
+
|
152
|
+
sig { override.params(node: FileTree::Node).void }
|
153
|
+
def visit_node(node)
|
154
|
+
@nodes << node
|
155
|
+
super
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# A visitor that collects the strictness of each node in a tree
|
160
|
+
class CollectStrictnesses < Visitor
|
161
|
+
extend T::Sig
|
162
|
+
|
163
|
+
sig { returns(T::Hash[Node, T.nilable(String)]) }
|
164
|
+
attr_reader :strictnesses
|
165
|
+
|
166
|
+
sig { params(context: Context).void }
|
167
|
+
def initialize(context)
|
168
|
+
super()
|
169
|
+
@context = context
|
170
|
+
@strictnesses = T.let({}, T::Hash[Node, T.nilable(String)])
|
171
|
+
end
|
172
|
+
|
173
|
+
sig { override.params(node: FileTree::Node).void }
|
174
|
+
def visit_node(node)
|
175
|
+
path = node.path
|
176
|
+
@strictnesses[node] = @context.read_file_strictness(path) if @context.file?(path)
|
177
|
+
|
178
|
+
super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# A visitor that collects the typing score of each node in a tree
|
183
|
+
class CollectScores < CollectStrictnesses
|
184
|
+
extend T::Sig
|
185
|
+
|
186
|
+
sig { returns(T::Hash[Node, Float]) }
|
187
|
+
attr_reader :scores
|
188
|
+
|
189
|
+
sig { params(context: Context).void }
|
190
|
+
def initialize(context)
|
191
|
+
super
|
192
|
+
@context = context
|
193
|
+
@scores = T.let({}, T::Hash[Node, Float])
|
194
|
+
end
|
195
|
+
|
196
|
+
sig { override.params(node: FileTree::Node).void }
|
197
|
+
def visit_node(node)
|
198
|
+
super
|
199
|
+
|
200
|
+
@scores[node] = node_score(node)
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
sig { params(node: Node).returns(Float) }
|
206
|
+
def node_score(node)
|
207
|
+
if @context.file?(node.path)
|
208
|
+
strictness_score(@strictnesses[node])
|
209
|
+
else
|
210
|
+
node.children.values.sum { |child| @scores.fetch(child, 0.0) } / node.children.size.to_f
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
sig { params(strictness: T.nilable(String)).returns(Float) }
|
215
|
+
def strictness_score(strictness)
|
216
|
+
case strictness
|
217
|
+
when "true", "strict", "strong"
|
218
|
+
1.0
|
219
|
+
else
|
220
|
+
0.0
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
114
225
|
# An internal class used to print a FileTree
|
115
226
|
#
|
116
227
|
# See `FileTree#print`
|
117
|
-
class
|
228
|
+
class Printer < Visitor
|
118
229
|
extend T::Sig
|
119
230
|
|
120
|
-
sig { returns(FileTree) }
|
121
|
-
attr_reader :tree
|
122
|
-
|
123
231
|
sig do
|
124
232
|
params(
|
125
|
-
|
233
|
+
strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
|
126
234
|
out: T.any(IO, StringIO),
|
127
|
-
show_strictness: T::Boolean,
|
128
235
|
colors: T::Boolean,
|
129
|
-
indent_level: Integer,
|
130
236
|
).void
|
131
237
|
end
|
132
|
-
def initialize(
|
133
|
-
super(
|
134
|
-
@
|
135
|
-
@
|
238
|
+
def initialize(strictnesses, out: $stdout, colors: true)
|
239
|
+
super()
|
240
|
+
@strictnesses = strictnesses
|
241
|
+
@colors = colors
|
242
|
+
@printer = T.let(Spoom::Printer.new(out: out, colors: colors), Spoom::Printer)
|
136
243
|
end
|
137
244
|
|
138
|
-
sig { void }
|
139
|
-
def
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
sig { params(node: FileTree::Node).void }
|
144
|
-
def print_node(node)
|
145
|
-
printt
|
245
|
+
sig { override.params(node: FileTree::Node).void }
|
246
|
+
def visit_node(node)
|
247
|
+
@printer.printt
|
146
248
|
if node.children.empty?
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
print("#{node.name} (#{strictness})")
|
153
|
-
else
|
154
|
-
print(node.name.to_s)
|
155
|
-
end
|
249
|
+
strictness = @strictnesses[node]
|
250
|
+
if @colors
|
251
|
+
@printer.print_colored(node.name, strictness_color(strictness))
|
252
|
+
elsif strictness
|
253
|
+
@printer.print("#{node.name} (#{strictness})")
|
156
254
|
else
|
157
|
-
print(node.name.to_s)
|
255
|
+
@printer.print(node.name.to_s)
|
158
256
|
end
|
159
|
-
print("\n")
|
257
|
+
@printer.print("\n")
|
160
258
|
else
|
161
|
-
print_colored(node.name, Color::BLUE)
|
162
|
-
print("/")
|
163
|
-
printn
|
164
|
-
indent
|
165
|
-
|
166
|
-
dedent
|
259
|
+
@printer.print_colored(node.name, Color::BLUE)
|
260
|
+
@printer.print("/")
|
261
|
+
@printer.printn
|
262
|
+
@printer.indent
|
263
|
+
super
|
264
|
+
@printer.dedent
|
167
265
|
end
|
168
266
|
end
|
169
267
|
|
170
|
-
sig { params(nodes: T::Array[FileTree::Node]).void }
|
171
|
-
def print_nodes(nodes)
|
172
|
-
nodes.each { |node| print_node(node) }
|
173
|
-
end
|
174
|
-
|
175
268
|
private
|
176
269
|
|
177
|
-
sig { params(node: FileTree::Node).returns(T.nilable(String)) }
|
178
|
-
def node_strictness(node)
|
179
|
-
path = node.path
|
180
|
-
prefix = tree.strip_prefix
|
181
|
-
path = "#{prefix}/#{path}" if prefix
|
182
|
-
Spoom::Sorbet::Sigils.file_strictness(path)
|
183
|
-
end
|
184
|
-
|
185
270
|
sig { params(strictness: T.nilable(String)).returns(Color) }
|
186
271
|
def strictness_color(strictness)
|
187
272
|
case strictness
|
data/lib/spoom/sorbet/config.rb
CHANGED
@@ -26,8 +26,10 @@ module Spoom
|
|
26
26
|
class Config
|
27
27
|
extend T::Sig
|
28
28
|
|
29
|
+
DEFAULT_ALLOWED_EXTENSIONS = T.let([".rb", ".rbi"].freeze, T::Array[String])
|
30
|
+
|
29
31
|
sig { returns(T::Array[String]) }
|
30
|
-
|
32
|
+
attr_accessor :paths, :ignore, :allowed_extensions
|
31
33
|
|
32
34
|
sig { returns(T::Boolean) }
|
33
35
|
attr_accessor :no_stdlib
|
data/lib/spoom/sorbet/sigils.rb
CHANGED
@@ -85,21 +85,6 @@ module Spoom
|
|
85
85
|
change_sigil_in_file(path, new_strictness)
|
86
86
|
end
|
87
87
|
end
|
88
|
-
|
89
|
-
# finds all files in the specified directory with the passed strictness
|
90
|
-
sig do
|
91
|
-
params(
|
92
|
-
directory: T.any(String, Pathname),
|
93
|
-
strictness: String,
|
94
|
-
extension: String,
|
95
|
-
).returns(T::Array[String])
|
96
|
-
end
|
97
|
-
def files_with_sigil_strictness(directory, strictness, extension: ".rb")
|
98
|
-
paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
|
99
|
-
paths.filter do |path|
|
100
|
-
file_strictness(path) == strictness
|
101
|
-
end
|
102
|
-
end
|
103
88
|
end
|
104
89
|
end
|
105
90
|
end
|
data/lib/spoom/sorbet.rb
CHANGED
@@ -39,124 +39,5 @@ module Spoom
|
|
39
39
|
|
40
40
|
KILLED_CODE = 137
|
41
41
|
SEGFAULT_CODE = 139
|
42
|
-
|
43
|
-
class << self
|
44
|
-
extend T::Sig
|
45
|
-
|
46
|
-
sig do
|
47
|
-
params(
|
48
|
-
arg: String,
|
49
|
-
path: String,
|
50
|
-
capture_err: T::Boolean,
|
51
|
-
sorbet_bin: T.nilable(String),
|
52
|
-
).returns(ExecResult)
|
53
|
-
end
|
54
|
-
def srb(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
55
|
-
if sorbet_bin
|
56
|
-
arg.prepend(sorbet_bin)
|
57
|
-
else
|
58
|
-
arg.prepend("bundle", "exec", "srb")
|
59
|
-
end
|
60
|
-
result = Spoom.exec(*T.unsafe(arg), path: path, capture_err: capture_err)
|
61
|
-
|
62
|
-
case result.exit_code
|
63
|
-
when KILLED_CODE
|
64
|
-
raise Error::Killed.new("Sorbet was killed.", result)
|
65
|
-
when SEGFAULT_CODE
|
66
|
-
raise Error::Segfault.new("Sorbet segfaulted.", result)
|
67
|
-
end
|
68
|
-
|
69
|
-
result
|
70
|
-
end
|
71
|
-
|
72
|
-
sig do
|
73
|
-
params(
|
74
|
-
arg: String,
|
75
|
-
path: String,
|
76
|
-
capture_err: T::Boolean,
|
77
|
-
sorbet_bin: T.nilable(String),
|
78
|
-
).returns(ExecResult)
|
79
|
-
end
|
80
|
-
def srb_tc(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
81
|
-
arg.prepend("tc") unless sorbet_bin
|
82
|
-
srb(*T.unsafe(arg), path: path, capture_err: capture_err, sorbet_bin: sorbet_bin)
|
83
|
-
end
|
84
|
-
|
85
|
-
# List all files typechecked by Sorbet from its `config`
|
86
|
-
sig { params(config: Config, path: String).returns(T::Array[String]) }
|
87
|
-
def srb_files(config, path: ".")
|
88
|
-
regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
|
89
|
-
exts = config.allowed_extensions.empty? ? [".rb", ".rbi"] : config.allowed_extensions
|
90
|
-
Dir.glob((Pathname.new(path) / "**/*{#{exts.join(",")}}").to_s).reject do |f|
|
91
|
-
regs.any? { |re| re.match?(f) }
|
92
|
-
end.sort
|
93
|
-
end
|
94
|
-
|
95
|
-
sig do
|
96
|
-
params(
|
97
|
-
arg: String,
|
98
|
-
path: String,
|
99
|
-
capture_err: T::Boolean,
|
100
|
-
sorbet_bin: T.nilable(String),
|
101
|
-
).returns(T.nilable(String))
|
102
|
-
end
|
103
|
-
def srb_version(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
104
|
-
result = T.let(
|
105
|
-
T.unsafe(self).srb_tc(
|
106
|
-
"--no-config",
|
107
|
-
"--version",
|
108
|
-
*arg,
|
109
|
-
path: path,
|
110
|
-
capture_err: capture_err,
|
111
|
-
sorbet_bin: sorbet_bin,
|
112
|
-
),
|
113
|
-
ExecResult,
|
114
|
-
)
|
115
|
-
return nil unless result.status
|
116
|
-
|
117
|
-
result.out.split(" ")[2]
|
118
|
-
end
|
119
|
-
|
120
|
-
sig do
|
121
|
-
params(
|
122
|
-
arg: String,
|
123
|
-
path: String,
|
124
|
-
capture_err: T::Boolean,
|
125
|
-
sorbet_bin: T.nilable(String),
|
126
|
-
).returns(T.nilable(T::Hash[String, Integer]))
|
127
|
-
end
|
128
|
-
def srb_metrics(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
129
|
-
metrics_file = "metrics.tmp"
|
130
|
-
metrics_path = "#{path}/#{metrics_file}"
|
131
|
-
T.unsafe(self).srb_tc(
|
132
|
-
"--metrics-file",
|
133
|
-
metrics_file,
|
134
|
-
*arg,
|
135
|
-
path: path,
|
136
|
-
capture_err: capture_err,
|
137
|
-
sorbet_bin: sorbet_bin,
|
138
|
-
)
|
139
|
-
if File.exist?(metrics_path)
|
140
|
-
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
141
|
-
File.delete(metrics_path)
|
142
|
-
return metrics
|
143
|
-
end
|
144
|
-
nil
|
145
|
-
end
|
146
|
-
|
147
|
-
# Get `gem` version from the `Gemfile.lock` content
|
148
|
-
#
|
149
|
-
# Returns `nil` if `gem` cannot be found in the Gemfile.
|
150
|
-
sig { params(gem: String, path: String).returns(T.nilable(String)) }
|
151
|
-
def version_from_gemfile_lock(gem: "sorbet", path: ".")
|
152
|
-
gemfile_path = "#{path}/Gemfile.lock"
|
153
|
-
return nil unless File.exist?(gemfile_path)
|
154
|
-
|
155
|
-
content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
|
156
|
-
return nil unless content
|
157
|
-
|
158
|
-
content[1]
|
159
|
-
end
|
160
|
-
end
|
161
42
|
end
|
162
43
|
end
|