spoom 1.2.0 → 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 +4 -5
- data/lib/spoom/cli.rb +4 -8
- data/lib/spoom/context/sorbet.rb +25 -7
- data/lib/spoom/coverage/d3/circle_map.rb +22 -36
- data/lib/spoom/coverage/report.rb +45 -32
- data/lib/spoom/coverage.rb +12 -12
- 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/version.rb +1 -1
- data/lib/spoom.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b116c81229f1eb09a9e8676643fd54bb3436f7fa2534b06275d657bcd995f0c4
|
4
|
+
data.tar.gz: 93431ed795ccdcb7aa3afcec9cd3bc468c5b1893bfac4f269c33df9f52e32055
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 826328b46d178109253dc01197f1cf80102c2174d7dc5b3ac1d52c142fee9785e7100e4b444c4bba71e5303fa711d15f8368cd173177b3614b54ce14c60c5b09
|
7
|
+
data.tar.gz: 60a28d2220e2507d9deaf12d7e3f51e5ea08418f79685c40aa469da5bc317dbddfded2c8d49cd0102035e5a990d9fa60693ae65a7d1e7538118768bea1140f7f
|
data/lib/spoom/cli/bump.rb
CHANGED
@@ -54,6 +54,7 @@ module Spoom
|
|
54
54
|
dry = options[:dry]
|
55
55
|
only = options[:only]
|
56
56
|
cmd = options[:suggest_bump_command]
|
57
|
+
directory = File.expand_path(directory)
|
57
58
|
exec_path = File.expand_path(self.exec_path)
|
58
59
|
|
59
60
|
unless Sorbet::Sigils.valid_strictness?(from)
|
@@ -73,11 +74,9 @@ module Spoom
|
|
73
74
|
|
74
75
|
say("Checking files...")
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
files_from_config = context.srb_files.map { |file| File.expand_path(file) }
|
80
|
-
files_to_bump.select! { |file| files_from_config.include?(file) }
|
77
|
+
files_to_bump = context.srb_files_with_strictness(from, include_rbis: false)
|
78
|
+
.map { |file| File.expand_path(file, context.absolute_path) }
|
79
|
+
.select { |file| file.start_with?(directory) }
|
81
80
|
|
82
81
|
if only
|
83
82
|
list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
|
data/lib/spoom/cli.rb
CHANGED
@@ -39,23 +39,19 @@ module Spoom
|
|
39
39
|
|
40
40
|
desc "files", "List all the files typechecked by Sorbet"
|
41
41
|
option :tree, type: :boolean, default: true, desc: "Display list as an indented tree"
|
42
|
-
option :rbi, type: :boolean, default:
|
42
|
+
option :rbi, type: :boolean, default: false, desc: "Show RBI files"
|
43
43
|
def files
|
44
44
|
context = context_requiring_sorbet!
|
45
|
-
files = context.srb_files
|
46
|
-
|
47
|
-
unless options[:rbi]
|
48
|
-
files = files.reject { |file| file.end_with?(".rbi") }
|
49
|
-
end
|
50
45
|
|
46
|
+
files = context.srb_files(include_rbis: options[:rbi])
|
51
47
|
if files.empty?
|
52
48
|
say_error("No file matching `#{Sorbet::CONFIG_PATH}`")
|
53
49
|
exit(1)
|
54
50
|
end
|
55
51
|
|
56
52
|
if options[:tree]
|
57
|
-
tree = FileTree.new(files
|
58
|
-
tree.
|
53
|
+
tree = FileTree.new(files)
|
54
|
+
tree.print_with_strictnesses(context, colors: options[:color])
|
59
55
|
else
|
60
56
|
puts files
|
61
57
|
end
|
data/lib/spoom/context/sorbet.rb
CHANGED
@@ -61,14 +61,32 @@ module Spoom
|
|
61
61
|
end
|
62
62
|
|
63
63
|
# List all files typechecked by Sorbet from its `config`
|
64
|
-
sig { params(with_config: T.nilable(Spoom::Sorbet::Config)).returns(T::Array[String]) }
|
65
|
-
def srb_files(with_config: nil)
|
64
|
+
sig { params(with_config: T.nilable(Spoom::Sorbet::Config), include_rbis: T::Boolean).returns(T::Array[String]) }
|
65
|
+
def srb_files(with_config: nil, include_rbis: true)
|
66
66
|
config = with_config || sorbet_config
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
|
68
|
+
allowed_extensions = config.allowed_extensions
|
69
|
+
allowed_extensions = Spoom::Sorbet::Config::DEFAULT_ALLOWED_EXTENSIONS if allowed_extensions.empty?
|
70
|
+
allowed_extensions -= [".rbi"] unless include_rbis
|
71
|
+
|
72
|
+
excluded_patterns = config.ignore.map { |string| File.join("**", string, "**") }
|
73
|
+
|
74
|
+
collector = FileCollector.new(allow_extensions: allowed_extensions, exclude_patterns: excluded_patterns)
|
75
|
+
collector.visit_paths(config.paths.map { |path| absolute_path_to(path) })
|
76
|
+
collector.files.map { |file| file.delete_prefix("#{absolute_path}/") }.sort
|
77
|
+
end
|
78
|
+
|
79
|
+
# List all files typechecked by Sorbet from its `config` that matches `strictness`
|
80
|
+
sig do
|
81
|
+
params(
|
82
|
+
strictness: String,
|
83
|
+
with_config: T.nilable(Spoom::Sorbet::Config),
|
84
|
+
include_rbis: T::Boolean,
|
85
|
+
).returns(T::Array[String])
|
86
|
+
end
|
87
|
+
def srb_files_with_strictness(strictness, with_config: nil, include_rbis: true)
|
88
|
+
srb_files(with_config: with_config, include_rbis: include_rbis)
|
89
|
+
.select { |file| read_file_strictness(file) == strictness }
|
72
90
|
end
|
73
91
|
|
74
92
|
sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(T.nilable(String)) }
|
@@ -148,48 +148,34 @@ module Spoom
|
|
148
148
|
class Sigils < CircleMap
|
149
149
|
extend T::Sig
|
150
150
|
|
151
|
-
sig
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
151
|
+
sig do
|
152
|
+
params(
|
153
|
+
id: String,
|
154
|
+
file_tree: FileTree,
|
155
|
+
nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
|
156
|
+
nodes_scores: T::Hash[FileTree::Node, Float],
|
157
|
+
).void
|
158
|
+
end
|
159
|
+
def initialize(id, file_tree, nodes_strictnesses, nodes_scores)
|
160
|
+
@nodes_strictnesses = nodes_strictnesses
|
161
|
+
@nodes_scores = nodes_scores
|
162
|
+
super(id, file_tree.roots.map { |r| tree_node_to_json(r) })
|
157
163
|
end
|
158
164
|
|
159
165
|
sig { params(node: FileTree::Node).returns(T::Hash[Symbol, T.untyped]) }
|
160
166
|
def tree_node_to_json(node)
|
161
167
|
if node.children.empty?
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
sig { params(node: FileTree::Node).returns(T.nilable(String)) }
|
173
|
-
def tree_node_strictness(node)
|
174
|
-
prefix = @sigils_tree.strip_prefix
|
175
|
-
path = node.path
|
176
|
-
path = "#{prefix}/#{path}" if prefix
|
177
|
-
@strictnesses[node] ||= Spoom::Sorbet::Sigils.file_strictness(path)
|
178
|
-
end
|
179
|
-
|
180
|
-
sig { params(node: FileTree::Node).returns(Float) }
|
181
|
-
def tree_node_score(node)
|
182
|
-
unless @scores.key?(node)
|
183
|
-
if node.name =~ /\.rbi?$/
|
184
|
-
case tree_node_strictness(node)
|
185
|
-
when "true", "strict", "strong"
|
186
|
-
@scores[node] = 1.0
|
187
|
-
end
|
188
|
-
elsif !node.children.empty?
|
189
|
-
@scores[node] = node.children.values.sum { |n| tree_node_score(n) } / node.children.size.to_f
|
190
|
-
end
|
168
|
+
{
|
169
|
+
name: node.name,
|
170
|
+
strictness: @nodes_strictnesses.fetch(node, "false"),
|
171
|
+
}
|
172
|
+
else
|
173
|
+
{
|
174
|
+
name: node.name,
|
175
|
+
children: node.children.values.map { |n| tree_node_to_json(n) },
|
176
|
+
score: @nodes_scores.fetch(node, 0.0),
|
177
|
+
}
|
191
178
|
end
|
192
|
-
@scores[node] || 0.0
|
193
179
|
end
|
194
180
|
end
|
195
181
|
end
|
@@ -153,9 +153,24 @@ module Spoom
|
|
153
153
|
class Map < Card
|
154
154
|
extend T::Sig
|
155
155
|
|
156
|
-
sig
|
157
|
-
|
158
|
-
|
156
|
+
sig do
|
157
|
+
params(
|
158
|
+
file_tree: FileTree,
|
159
|
+
nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
|
160
|
+
nodes_strictness_scores: T::Hash[FileTree::Node, Float],
|
161
|
+
title: String,
|
162
|
+
).void
|
163
|
+
end
|
164
|
+
def initialize(file_tree:, nodes_strictnesses:, nodes_strictness_scores:, title: "Strictness Map")
|
165
|
+
super(
|
166
|
+
title: title,
|
167
|
+
body: D3::CircleMap::Sigils.new(
|
168
|
+
"map_sigils",
|
169
|
+
file_tree,
|
170
|
+
nodes_strictnesses,
|
171
|
+
nodes_strictness_scores,
|
172
|
+
).html
|
173
|
+
)
|
159
174
|
end
|
160
175
|
end
|
161
176
|
|
@@ -246,27 +261,14 @@ module Spoom
|
|
246
261
|
class Report < Page
|
247
262
|
extend T::Sig
|
248
263
|
|
249
|
-
sig { returns(String) }
|
250
|
-
attr_reader :project_name
|
251
|
-
|
252
|
-
sig { returns(T.nilable(String)) }
|
253
|
-
attr_reader :sorbet_intro_commit
|
254
|
-
|
255
|
-
sig { returns(T.nilable(Time)) }
|
256
|
-
attr_reader :sorbet_intro_date
|
257
|
-
|
258
|
-
sig { returns(T::Array[Snapshot]) }
|
259
|
-
attr_reader :snapshots
|
260
|
-
|
261
|
-
sig { returns(FileTree) }
|
262
|
-
attr_reader :sigils_tree
|
263
|
-
|
264
264
|
sig do
|
265
265
|
params(
|
266
266
|
project_name: String,
|
267
267
|
palette: D3::ColorPalette,
|
268
268
|
snapshots: T::Array[Snapshot],
|
269
|
-
|
269
|
+
file_tree: FileTree,
|
270
|
+
nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
|
271
|
+
nodes_strictness_scores: T::Hash[FileTree::Node, Float],
|
270
272
|
sorbet_intro_commit: T.nilable(String),
|
271
273
|
sorbet_intro_date: T.nilable(Time),
|
272
274
|
).void
|
@@ -275,24 +277,28 @@ module Spoom
|
|
275
277
|
project_name:,
|
276
278
|
palette:,
|
277
279
|
snapshots:,
|
278
|
-
|
280
|
+
file_tree:,
|
281
|
+
nodes_strictnesses:,
|
282
|
+
nodes_strictness_scores:,
|
279
283
|
sorbet_intro_commit: nil,
|
280
284
|
sorbet_intro_date: nil
|
281
285
|
)
|
282
286
|
super(title: project_name, palette: palette)
|
283
287
|
@project_name = project_name
|
284
288
|
@snapshots = snapshots
|
285
|
-
@
|
289
|
+
@file_tree = file_tree
|
290
|
+
@nodes_strictnesses = nodes_strictnesses
|
291
|
+
@nodes_strictness_scores = nodes_strictness_scores
|
286
292
|
@sorbet_intro_commit = sorbet_intro_commit
|
287
293
|
@sorbet_intro_date = sorbet_intro_date
|
288
294
|
end
|
289
295
|
|
290
296
|
sig { override.returns(String) }
|
291
297
|
def header_html
|
292
|
-
last = T.must(snapshots.last)
|
298
|
+
last = T.must(@snapshots.last)
|
293
299
|
<<~ERB
|
294
300
|
<h1 class="display-3">
|
295
|
-
#{project_name}
|
301
|
+
#{@project_name}
|
296
302
|
<span class="badge badge-pill badge-dark" style="font-size: 20%;">#{last.commit_sha}</span>
|
297
303
|
</h1>
|
298
304
|
ERB
|
@@ -300,17 +306,24 @@ module Spoom
|
|
300
306
|
|
301
307
|
sig { override.returns(T::Array[Cards::Card]) }
|
302
308
|
def cards
|
303
|
-
last = T.must(snapshots.last)
|
309
|
+
last = T.must(@snapshots.last)
|
304
310
|
cards = []
|
305
311
|
cards << Cards::Snapshot.new(snapshot: last)
|
306
|
-
cards << Cards::Map.new(
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
cards << Cards::Timeline::
|
312
|
-
cards << Cards::Timeline::
|
313
|
-
cards << Cards::
|
312
|
+
cards << Cards::Map.new(
|
313
|
+
file_tree: @file_tree,
|
314
|
+
nodes_strictnesses: @nodes_strictnesses,
|
315
|
+
nodes_strictness_scores: @nodes_strictness_scores,
|
316
|
+
)
|
317
|
+
cards << Cards::Timeline::Sigils.new(snapshots: @snapshots)
|
318
|
+
cards << Cards::Timeline::Calls.new(snapshots: @snapshots)
|
319
|
+
cards << Cards::Timeline::Sigs.new(snapshots: @snapshots)
|
320
|
+
cards << Cards::Timeline::RBIs.new(snapshots: @snapshots)
|
321
|
+
cards << Cards::Timeline::Versions.new(snapshots: @snapshots)
|
322
|
+
cards << Cards::Timeline::Runtimes.new(snapshots: @snapshots)
|
323
|
+
cards << Cards::SorbetIntro.new(
|
324
|
+
sorbet_intro_commit: @sorbet_intro_commit,
|
325
|
+
sorbet_intro_date: @sorbet_intro_date,
|
326
|
+
)
|
314
327
|
cards
|
315
328
|
end
|
316
329
|
end
|
data/lib/spoom/coverage.rb
CHANGED
@@ -83,29 +83,29 @@ module Spoom
|
|
83
83
|
def report(context, snapshots, palette:)
|
84
84
|
intro_commit = context.sorbet_intro_commit
|
85
85
|
|
86
|
+
file_tree = file_tree(context)
|
87
|
+
v = FileTree::CollectScores.new(context)
|
88
|
+
v.visit_tree(file_tree)
|
89
|
+
|
86
90
|
Report.new(
|
87
91
|
project_name: File.basename(context.absolute_path),
|
88
92
|
palette: palette,
|
89
93
|
snapshots: snapshots,
|
90
|
-
|
94
|
+
file_tree: file_tree,
|
95
|
+
nodes_strictnesses: v.strictnesses,
|
96
|
+
nodes_strictness_scores: v.scores,
|
91
97
|
sorbet_intro_commit: intro_commit&.sha,
|
92
98
|
sorbet_intro_date: intro_commit&.time,
|
93
99
|
)
|
94
100
|
end
|
95
101
|
|
96
102
|
sig { params(context: Context).returns(FileTree) }
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
extensions = context.sorbet_config.allowed_extensions
|
101
|
-
extensions = [".rb"] if extensions.empty?
|
102
|
-
extensions -= [".rbi"]
|
103
|
-
|
104
|
-
pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
|
105
|
-
files.select! { |file| file =~ pattern }
|
106
|
-
files.reject! { |file| file =~ %r{/test/} }
|
103
|
+
def file_tree(context)
|
104
|
+
config = context.sorbet_config
|
105
|
+
config.ignore += ["test"]
|
107
106
|
|
108
|
-
|
107
|
+
files = context.srb_files(with_config: config, include_rbis: false)
|
108
|
+
FileTree.new(files)
|
109
109
|
end
|
110
110
|
end
|
111
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/version.rb
CHANGED
data/lib/spoom.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spoom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Terrasa
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03-
|
11
|
+
date: 2023-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -143,6 +143,7 @@ files:
|
|
143
143
|
- lib/spoom/coverage/d3/timeline.rb
|
144
144
|
- lib/spoom/coverage/report.rb
|
145
145
|
- lib/spoom/coverage/snapshot.rb
|
146
|
+
- lib/spoom/file_collector.rb
|
146
147
|
- lib/spoom/file_tree.rb
|
147
148
|
- lib/spoom/printer.rb
|
148
149
|
- lib/spoom/sorbet.rb
|