spoom 1.1.11 → 1.1.13
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|