spoom 1.1.11 → 1.1.13
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/Gemfile +1 -0
- data/README.md +6 -0
- data/Rakefile +1 -0
- data/lib/spoom/cli/bump.rb +17 -12
- data/lib/spoom/cli/coverage.rb +20 -17
- data/lib/spoom/cli/helper.rb +6 -5
- data/lib/spoom/cli/lsp.rb +4 -3
- data/lib/spoom/cli/run.rb +18 -8
- data/lib/spoom/cli.rb +6 -4
- data/lib/spoom/context.rb +219 -0
- data/lib/spoom/coverage/d3/base.rb +12 -8
- data/lib/spoom/coverage/d3/circle_map.rb +40 -37
- data/lib/spoom/coverage/d3/pie.rb +41 -30
- data/lib/spoom/coverage/d3/timeline.rb +95 -88
- data/lib/spoom/coverage/d3.rb +72 -70
- data/lib/spoom/coverage/report.rb +6 -6
- data/lib/spoom/coverage/snapshot.rb +65 -34
- data/lib/spoom/coverage.rb +104 -81
- data/lib/spoom/file_tree.rb +5 -3
- data/lib/spoom/git.rb +102 -96
- data/lib/spoom/printer.rb +4 -0
- data/lib/spoom/sorbet/errors.rb +30 -12
- data/lib/spoom/sorbet/lsp/base.rb +1 -1
- data/lib/spoom/sorbet/lsp/errors.rb +23 -17
- data/lib/spoom/sorbet/lsp/structures.rb +86 -55
- data/lib/spoom/sorbet/lsp.rb +78 -70
- data/lib/spoom/sorbet/metrics.rb +18 -16
- data/lib/spoom/sorbet/sigils.rb +59 -54
- data/lib/spoom/sorbet.rb +17 -14
- data/lib/spoom/timeline.rb +6 -5
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +43 -25
- metadata +20 -20
- data/lib/spoom/test_helpers/project.rb +0 -138
@@ -109,7 +109,7 @@ module Spoom
|
|
109
109
|
abstract!
|
110
110
|
|
111
111
|
sig { void }
|
112
|
-
def initialize; end
|
112
|
+
def initialize; end # rubocop:disable Lint/MissingSuper
|
113
113
|
|
114
114
|
sig { override.returns(String) }
|
115
115
|
def html
|
@@ -136,17 +136,17 @@ module Spoom
|
|
136
136
|
|
137
137
|
sig { returns(D3::Pie::Sigils) }
|
138
138
|
def pie_sigils
|
139
|
-
D3::Pie::Sigils.new(
|
139
|
+
D3::Pie::Sigils.new("pie_sigils", "Sigils", snapshot)
|
140
140
|
end
|
141
141
|
|
142
142
|
sig { returns(D3::Pie::Calls) }
|
143
143
|
def pie_calls
|
144
|
-
D3::Pie::Calls.new(
|
144
|
+
D3::Pie::Calls.new("pie_calls", "Calls", snapshot)
|
145
145
|
end
|
146
146
|
|
147
147
|
sig { returns(D3::Pie::Sigs) }
|
148
148
|
def pie_sigs
|
149
|
-
D3::Pie::Sigs.new(
|
149
|
+
D3::Pie::Sigs.new("pie_sigs", "Sigs", snapshot)
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
@@ -226,7 +226,7 @@ module Spoom
|
|
226
226
|
extend T::Sig
|
227
227
|
|
228
228
|
sig { params(sorbet_intro_commit: T.nilable(String), sorbet_intro_date: T.nilable(Time)).void }
|
229
|
-
def initialize(sorbet_intro_commit: nil, sorbet_intro_date: nil)
|
229
|
+
def initialize(sorbet_intro_commit: nil, sorbet_intro_date: nil) # rubocop:disable Lint/MissingSuper
|
230
230
|
@sorbet_intro_commit = sorbet_intro_commit
|
231
231
|
@sorbet_intro_date = sorbet_intro_date
|
232
232
|
end
|
@@ -235,7 +235,7 @@ module Spoom
|
|
235
235
|
def erb
|
236
236
|
<<~ERB
|
237
237
|
<div class="text-center" style="margin-top: 30px">
|
238
|
-
Typchecked by Sorbet since <b>#{@sorbet_intro_date&.strftime(
|
238
|
+
Typchecked by Sorbet since <b>#{@sorbet_intro_date&.strftime("%F")}</b>
|
239
239
|
(commit <b>#{@sorbet_intro_commit}</b>).
|
240
240
|
</div>
|
241
241
|
ERB
|
@@ -22,6 +22,9 @@ module Spoom
|
|
22
22
|
prop :calls_untyped, Integer, default: 0
|
23
23
|
prop :calls_typed, Integer, default: 0
|
24
24
|
prop :sigils, T::Hash[String, Integer], default: Hash.new(0)
|
25
|
+
prop :methods_with_sig_excluding_rbis, Integer, default: 0
|
26
|
+
prop :methods_without_sig_excluding_rbis, Integer, default: 0
|
27
|
+
prop :sigils_excluding_rbis, T::Hash[String, Integer], default: Hash.new(0)
|
25
28
|
|
26
29
|
# The strictness name as found in the Sorbet metrics file
|
27
30
|
STRICTNESSES = T.let(["ignore", "false", "true", "strict", "strong", "stdlib"].freeze, T::Array[String])
|
@@ -32,44 +35,60 @@ module Spoom
|
|
32
35
|
printer.print_snapshot(self)
|
33
36
|
end
|
34
37
|
|
35
|
-
sig { params(
|
36
|
-
def
|
37
|
-
|
38
|
+
sig { params(arg: T.untyped).returns(String) }
|
39
|
+
def to_json(*arg)
|
40
|
+
serialize.to_json(*arg)
|
38
41
|
end
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
snapshot.duration = obj.fetch("duration", 0)
|
47
|
-
snapshot.commit_sha = obj.fetch("commit_sha", nil)
|
48
|
-
snapshot.commit_timestamp = obj.fetch("commit_timestamp", nil)
|
49
|
-
snapshot.files = obj.fetch("files", 0)
|
50
|
-
snapshot.rbi_files = obj.fetch("rbi_files", 0)
|
51
|
-
snapshot.modules = obj.fetch("modules", 0)
|
52
|
-
snapshot.classes = obj.fetch("classes", 0)
|
53
|
-
snapshot.singleton_classes = obj.fetch("singleton_classes", 0)
|
54
|
-
snapshot.methods_with_sig = obj.fetch("methods_with_sig", 0)
|
55
|
-
snapshot.methods_without_sig = obj.fetch("methods_without_sig", 0)
|
56
|
-
snapshot.calls_typed = obj.fetch("calls_typed", 0)
|
57
|
-
snapshot.calls_untyped = obj.fetch("calls_untyped", 0)
|
58
|
-
|
59
|
-
sigils = obj.fetch("sigils", {})
|
60
|
-
if sigils
|
61
|
-
Snapshot::STRICTNESSES.each do |strictness|
|
62
|
-
next unless sigils.key?(strictness)
|
63
|
-
snapshot.sigils[strictness] = sigils[strictness]
|
64
|
-
end
|
43
|
+
class << self
|
44
|
+
extend T::Sig
|
45
|
+
|
46
|
+
sig { params(json: String).returns(Snapshot) }
|
47
|
+
def from_json(json)
|
48
|
+
from_obj(JSON.parse(json))
|
65
49
|
end
|
66
50
|
|
67
|
-
|
68
|
-
|
51
|
+
sig { params(obj: T::Hash[String, T.untyped]).returns(Snapshot) }
|
52
|
+
def from_obj(obj)
|
53
|
+
snapshot = Snapshot.new
|
54
|
+
snapshot.timestamp = obj.fetch("timestamp", 0)
|
55
|
+
snapshot.version_static = obj.fetch("version_static", nil)
|
56
|
+
snapshot.version_runtime = obj.fetch("version_runtime", nil)
|
57
|
+
snapshot.duration = obj.fetch("duration", 0)
|
58
|
+
snapshot.commit_sha = obj.fetch("commit_sha", nil)
|
59
|
+
snapshot.commit_timestamp = obj.fetch("commit_timestamp", nil)
|
60
|
+
snapshot.files = obj.fetch("files", 0)
|
61
|
+
snapshot.rbi_files = obj.fetch("rbi_files", 0)
|
62
|
+
snapshot.modules = obj.fetch("modules", 0)
|
63
|
+
snapshot.classes = obj.fetch("classes", 0)
|
64
|
+
snapshot.singleton_classes = obj.fetch("singleton_classes", 0)
|
65
|
+
snapshot.methods_with_sig = obj.fetch("methods_with_sig", 0)
|
66
|
+
snapshot.methods_without_sig = obj.fetch("methods_without_sig", 0)
|
67
|
+
snapshot.calls_typed = obj.fetch("calls_typed", 0)
|
68
|
+
snapshot.calls_untyped = obj.fetch("calls_untyped", 0)
|
69
|
+
snapshot.methods_with_sig_excluding_rbis = obj.fetch("methods_with_sig_excluding_rbis", 0)
|
70
|
+
snapshot.methods_without_sig_excluding_rbis = obj.fetch("methods_without_sig_excluding_rbis", 0)
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
72
|
+
sigils = obj.fetch("sigils", {})
|
73
|
+
if sigils
|
74
|
+
Snapshot::STRICTNESSES.each do |strictness|
|
75
|
+
next unless sigils.key?(strictness)
|
76
|
+
|
77
|
+
snapshot.sigils[strictness] = sigils[strictness]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
sigils_excluding_rbis = obj.fetch("sigils_excluding_rbis", {})
|
82
|
+
if sigils_excluding_rbis
|
83
|
+
Snapshot::STRICTNESSES.each do |strictness|
|
84
|
+
next unless sigils_excluding_rbis.key?(strictness)
|
85
|
+
|
86
|
+
snapshot.sigils_excluding_rbis[strictness] = sigils_excluding_rbis[strictness]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
snapshot
|
91
|
+
end
|
73
92
|
end
|
74
93
|
end
|
75
94
|
|
@@ -79,6 +98,7 @@ module Spoom
|
|
79
98
|
sig { params(snapshot: Snapshot).void }
|
80
99
|
def print_snapshot(snapshot)
|
81
100
|
methods = snapshot.methods_with_sig + snapshot.methods_without_sig
|
101
|
+
methods_excluding_rbis = snapshot.methods_with_sig_excluding_rbis + snapshot.methods_without_sig_excluding_rbis
|
82
102
|
calls = snapshot.calls_typed + snapshot.calls_untyped
|
83
103
|
|
84
104
|
if snapshot.version_static || snapshot.version_runtime
|
@@ -88,10 +108,12 @@ module Spoom
|
|
88
108
|
end
|
89
109
|
printl("Content:")
|
90
110
|
indent
|
91
|
-
printl("files: #{snapshot.files}
|
111
|
+
printl("files: #{snapshot.files}")
|
112
|
+
printl("files excluding rbis: #{snapshot.files - snapshot.rbi_files}")
|
92
113
|
printl("modules: #{snapshot.modules}")
|
93
114
|
printl("classes: #{snapshot.classes - snapshot.singleton_classes}")
|
94
115
|
printl("methods: #{methods}")
|
116
|
+
printl("methods excluding rbis: #{methods_excluding_rbis}")
|
95
117
|
dedent
|
96
118
|
printn
|
97
119
|
printl("Sigils:")
|
@@ -104,6 +126,13 @@ module Spoom
|
|
104
126
|
}
|
105
127
|
print_map(methods_map, methods)
|
106
128
|
printn
|
129
|
+
printl("Methods excluding RBIs")
|
130
|
+
methods_excluding_rbis_map = {
|
131
|
+
"with signature" => snapshot.methods_with_sig_excluding_rbis,
|
132
|
+
"without signature" => snapshot.methods_without_sig_excluding_rbis,
|
133
|
+
}
|
134
|
+
print_map(methods_excluding_rbis_map, methods_excluding_rbis)
|
135
|
+
printn
|
107
136
|
printl("Calls:")
|
108
137
|
calls_map = {
|
109
138
|
"typed" => snapshot.calls_typed,
|
@@ -119,6 +148,7 @@ module Spoom
|
|
119
148
|
indent
|
120
149
|
hash.each do |key, value|
|
121
150
|
next unless value > 0
|
151
|
+
|
122
152
|
printl("#{key}: #{value}#{percent(value, total)}")
|
123
153
|
end
|
124
154
|
dedent
|
@@ -127,6 +157,7 @@ module Spoom
|
|
127
157
|
sig { params(value: T.nilable(Integer), total: T.nilable(Integer)).returns(String) }
|
128
158
|
def percent(value, total)
|
129
159
|
return "" if value.nil? || total.nil? || total == 0
|
160
|
+
|
130
161
|
" (#{(value.to_f * 100.0 / total.to_f).round}%)"
|
131
162
|
end
|
132
163
|
end
|
data/lib/spoom/coverage.rb
CHANGED
@@ -9,94 +9,117 @@ require "date"
|
|
9
9
|
|
10
10
|
module Spoom
|
11
11
|
module Coverage
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
snapshot.
|
12
|
+
class << self
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
|
16
|
+
def snapshot(path: ".", rbi: true, sorbet_bin: nil)
|
17
|
+
config = sorbet_config(path: path)
|
18
|
+
config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
|
19
|
+
|
20
|
+
new_config = config.copy
|
21
|
+
new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" }
|
22
|
+
flags = [
|
23
|
+
"--no-config",
|
24
|
+
"--no-error-sections",
|
25
|
+
"--no-error-count",
|
26
|
+
"--isolate-error-code=0",
|
27
|
+
new_config.options_string,
|
28
|
+
]
|
29
|
+
|
30
|
+
metrics = Spoom::Sorbet.srb_metrics(
|
31
|
+
*flags,
|
32
|
+
path: path,
|
33
|
+
capture_err: true,
|
34
|
+
sorbet_bin: sorbet_bin,
|
35
|
+
)
|
36
|
+
# Collect extra information using a different configuration
|
37
|
+
flags << "--ignore sorbet/rbi/"
|
38
|
+
metrics_without_rbis = Spoom::Sorbet.srb_metrics(
|
39
|
+
*flags,
|
40
|
+
path: path,
|
41
|
+
capture_err: true,
|
42
|
+
sorbet_bin: sorbet_bin,
|
43
|
+
)
|
44
|
+
|
45
|
+
snapshot = Snapshot.new
|
46
|
+
return snapshot unless metrics
|
47
|
+
|
48
|
+
last_commit = Spoom::Git.last_commit(path: path)
|
49
|
+
snapshot.commit_sha = last_commit&.sha
|
50
|
+
snapshot.commit_timestamp = last_commit&.timestamp
|
51
|
+
|
52
|
+
snapshot.files = metrics.fetch("types.input.files", 0)
|
53
|
+
snapshot.modules = metrics.fetch("types.input.modules.total", 0)
|
54
|
+
snapshot.classes = metrics.fetch("types.input.classes.total", 0)
|
55
|
+
snapshot.singleton_classes = metrics.fetch("types.input.singleton_classes.total", 0)
|
56
|
+
snapshot.methods_with_sig = metrics.fetch("types.sig.count", 0)
|
57
|
+
snapshot.methods_without_sig = metrics.fetch("types.input.methods.total", 0) - snapshot.methods_with_sig
|
58
|
+
snapshot.calls_typed = metrics.fetch("types.input.sends.typed", 0)
|
59
|
+
snapshot.calls_untyped = metrics.fetch("types.input.sends.total", 0) - snapshot.calls_typed
|
60
|
+
|
61
|
+
snapshot.duration += metrics.fetch("run.utilization.system_time.us", 0)
|
62
|
+
snapshot.duration += metrics.fetch("run.utilization.user_time.us", 0)
|
63
|
+
|
64
|
+
if metrics_without_rbis
|
65
|
+
snapshot.methods_with_sig_excluding_rbis = metrics_without_rbis.fetch("types.sig.count", 0)
|
66
|
+
snapshot.methods_without_sig_excluding_rbis = metrics_without_rbis.fetch("types.input.methods.total",
|
67
|
+
0) - snapshot.methods_with_sig_excluding_rbis
|
68
|
+
end
|
69
|
+
|
70
|
+
Snapshot::STRICTNESSES.each do |strictness|
|
71
|
+
if metrics.key?("types.input.files.sigil.#{strictness}")
|
72
|
+
snapshot.sigils[strictness] = T.must(metrics["types.input.files.sigil.#{strictness}"])
|
73
|
+
end
|
74
|
+
if metrics_without_rbis&.key?("types.input.files.sigil.#{strictness}")
|
75
|
+
snapshot.sigils_excluding_rbis[strictness] =
|
76
|
+
T.must(metrics_without_rbis["types.input.files.sigil.#{strictness}"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
|
81
|
+
snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
|
82
|
+
|
83
|
+
files = Spoom::Sorbet.srb_files(new_config, path: path)
|
84
|
+
snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
|
85
|
+
|
86
|
+
snapshot
|
55
87
|
end
|
56
88
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
Report.new(
|
72
|
-
project_name: File.basename(File.expand_path(path)),
|
73
|
-
palette: palette,
|
74
|
-
snapshots: snapshots,
|
75
|
-
sigils_tree: sigils_tree(path: path),
|
76
|
-
sorbet_intro_commit: intro_commit,
|
77
|
-
sorbet_intro_date: intro_date,
|
78
|
-
)
|
79
|
-
end
|
89
|
+
sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) }
|
90
|
+
def report(snapshots, palette:, path: ".")
|
91
|
+
intro_commit = Git.sorbet_intro_commit(path: path)
|
92
|
+
|
93
|
+
Report.new(
|
94
|
+
project_name: File.basename(File.expand_path(path)),
|
95
|
+
palette: palette,
|
96
|
+
snapshots: snapshots,
|
97
|
+
sigils_tree: sigils_tree(path: path),
|
98
|
+
sorbet_intro_commit: intro_commit&.sha,
|
99
|
+
sorbet_intro_date: intro_commit&.time,
|
100
|
+
)
|
101
|
+
end
|
80
102
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
103
|
+
sig { params(path: String).returns(Sorbet::Config) }
|
104
|
+
def sorbet_config(path: ".")
|
105
|
+
Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
|
106
|
+
end
|
85
107
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
108
|
+
sig { params(path: String).returns(FileTree) }
|
109
|
+
def sigils_tree(path: ".")
|
110
|
+
config = sorbet_config(path: path)
|
111
|
+
files = Sorbet.srb_files(config, path: path)
|
90
112
|
|
91
|
-
|
92
|
-
|
93
|
-
|
113
|
+
extensions = config.allowed_extensions
|
114
|
+
extensions = [".rb"] if extensions.empty?
|
115
|
+
extensions -= [".rbi"]
|
94
116
|
|
95
|
-
|
96
|
-
|
97
|
-
|
117
|
+
pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
|
118
|
+
files.select! { |file| file =~ pattern }
|
119
|
+
files.reject! { |file| file =~ %r{/test/} }
|
98
120
|
|
99
|
-
|
121
|
+
FileTree.new(files, strip_prefix: path)
|
122
|
+
end
|
100
123
|
end
|
101
124
|
end
|
102
125
|
end
|
data/lib/spoom/file_tree.rb
CHANGED
@@ -33,6 +33,7 @@ module Spoom
|
|
33
33
|
if path.empty? || parts.size == 1
|
34
34
|
return @roots[path] ||= Node.new(parent: nil, name: path)
|
35
35
|
end
|
36
|
+
|
36
37
|
parent_path = T.must(parts[0...-1]).join("/")
|
37
38
|
parent = add_path(parent_path)
|
38
39
|
name = T.must(parts.last)
|
@@ -64,7 +65,7 @@ module Spoom
|
|
64
65
|
out: T.any(IO, StringIO),
|
65
66
|
show_strictness: T::Boolean,
|
66
67
|
colors: T::Boolean,
|
67
|
-
indent_level: Integer
|
68
|
+
indent_level: Integer,
|
68
69
|
).void
|
69
70
|
end
|
70
71
|
def print(out: $stdout, show_strictness: true, colors: true, indent_level: 0)
|
@@ -73,7 +74,7 @@ module Spoom
|
|
73
74
|
out: out,
|
74
75
|
show_strictness: show_strictness,
|
75
76
|
colors: colors,
|
76
|
-
indent_level: indent_level
|
77
|
+
indent_level: indent_level,
|
77
78
|
)
|
78
79
|
printer.print_tree
|
79
80
|
end
|
@@ -105,6 +106,7 @@ module Spoom
|
|
105
106
|
def path
|
106
107
|
parent = self.parent
|
107
108
|
return name unless parent
|
109
|
+
|
108
110
|
"#{parent.path}/#{name}"
|
109
111
|
end
|
110
112
|
end
|
@@ -124,7 +126,7 @@ module Spoom
|
|
124
126
|
out: T.any(IO, StringIO),
|
125
127
|
show_strictness: T::Boolean,
|
126
128
|
colors: T::Boolean,
|
127
|
-
indent_level: Integer
|
129
|
+
indent_level: Integer,
|
128
130
|
).void
|
129
131
|
end
|
130
132
|
def initialize(tree:, out: $stdout, show_strictness: true, colors: true, indent_level: 0)
|