spoom 1.8.11 → 1.8.12
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/README.md +14 -0
- data/lib/spoom/cli/deadcode.rb +12 -1
- data/lib/spoom/cli/srb/coverage.rb +3 -0
- data/lib/spoom/coverage/d3/base.rb +15 -0
- data/lib/spoom/coverage/d3/circle_map.rb +2 -2
- data/lib/spoom/coverage/d3/pie.rb +4 -4
- data/lib/spoom/coverage/d3/timeline.rb +17 -17
- data/lib/spoom/coverage/d3.rb +9 -0
- data/lib/spoom/coverage/report.rb +4 -4
- data/lib/spoom/coverage/snapshot.rb +40 -19
- data/lib/spoom/version.rb +1 -1
- data/rbi/spoom.rbi +12 -0
- data/templates/page.erb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 284bf38248b3fb6c057966d56d0bbcd01e3fa4142e6a619ffacb98eb159a6d7d
|
|
4
|
+
data.tar.gz: 44551f71e890cde0d0a71e77b8a4d627c7aafbddaa8e0e3a7106135c6962398a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60c251eaa56b3ad7ffac08afe381203869146040b144c7893013dcb098fc9150bad9a05b49bc303919946904b0757a6ddd285282a80bf64f607630d942b57d19
|
|
7
|
+
data.tar.gz: 462f445ffb6b740ef17486e52d3e3c65d000f482199206ea3ef6e39c7a9aa7183c9df8097b73967f89f3db5c88d3e751edba61c3fb691f2909a48930a0d78b5e
|
data/README.md
CHANGED
|
@@ -387,6 +387,20 @@ Remove a reported dead code candidate:
|
|
|
387
387
|
spoom deadcode remove path/to/file.rb:42:18-47:23
|
|
388
388
|
```
|
|
389
389
|
|
|
390
|
+
#### Custom plugins
|
|
391
|
+
|
|
392
|
+
Teach `spoom deadcode` about your project's own DSLs by adding plugins to `.spoom/deadcode/plugins/*.rb`. Each file should define a subclass of `Spoom::Deadcode::Plugins::Base`:
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
# .spoom/deadcode/plugins/my_plugin.rb
|
|
396
|
+
class MyPlugin < Spoom::Deadcode::Plugins::Base
|
|
397
|
+
ignore_methods_named("my_dsl_method")
|
|
398
|
+
end
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
> [!WARNING]
|
|
402
|
+
> Custom plugins are loaded with `require`, so `spoom deadcode` executes any Ruby code in `.spoom/deadcode/plugins/` of the project being analyzed. Only run `spoom deadcode` on projects you trust, and inspect or remove `.spoom/deadcode/plugins/` before analyzing untrusted code (such as a pull request from an outside contributor).
|
|
403
|
+
|
|
390
404
|
## Ruby API
|
|
391
405
|
|
|
392
406
|
### Parsing Sorbet config
|
data/lib/spoom/cli/deadcode.rb
CHANGED
|
@@ -73,7 +73,18 @@ module Spoom
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
plugin_classes = Spoom::Deadcode.plugins_from_gemfile_lock(context)
|
|
76
|
-
|
|
76
|
+
|
|
77
|
+
custom_plugin_files = context.glob("#{Spoom::Deadcode::DEFAULT_CUSTOM_PLUGINS_PATH}/*.rb")
|
|
78
|
+
unless custom_plugin_files.empty?
|
|
79
|
+
say_warning(
|
|
80
|
+
"Loading custom plugins from `#{Spoom::Deadcode::DEFAULT_CUSTOM_PLUGINS_PATH}`. " \
|
|
81
|
+
"This executes Ruby code from the analyzed project:",
|
|
82
|
+
)
|
|
83
|
+
custom_plugin_files.each do |file|
|
|
84
|
+
$stderr.puts " #{gray(file)}"
|
|
85
|
+
end
|
|
86
|
+
plugin_classes.merge(Spoom::Deadcode.load_custom_plugins(context))
|
|
87
|
+
end
|
|
77
88
|
if options[:show_plugins]
|
|
78
89
|
$stderr.puts "\nLoaded #{blue(plugin_classes.size.to_s)} plugins\n"
|
|
79
90
|
plugin_classes.each do |plugin|
|
|
@@ -153,6 +153,9 @@ module Spoom
|
|
|
153
153
|
snapshots = files.sort.map do |file|
|
|
154
154
|
json = File.read(file)
|
|
155
155
|
Spoom::Coverage::Snapshot.from_json(json)
|
|
156
|
+
rescue Spoom::Coverage::Snapshot::Error => e
|
|
157
|
+
say_error("Invalid snapshot `#{file}`: #{e.message}")
|
|
158
|
+
exit(1)
|
|
156
159
|
end.filter(&:commit_timestamp).sort_by!(&:commit_timestamp)
|
|
157
160
|
|
|
158
161
|
palette = Spoom::Coverage::D3::ColorPalette.new(
|
|
@@ -6,6 +6,9 @@ module Spoom
|
|
|
6
6
|
module D3
|
|
7
7
|
# @abstract
|
|
8
8
|
class Base
|
|
9
|
+
# Characters that must not appear raw inside a `<script>` element, and their JSON string escapes
|
|
10
|
+
JS_LITERAL_ESCAPES = { "<" => '\u003c', ">" => '\u003e', "&" => '\u0026' }.freeze #: Hash[String, String]
|
|
11
|
+
|
|
9
12
|
#: String
|
|
10
13
|
attr_reader :id
|
|
11
14
|
|
|
@@ -43,6 +46,18 @@ module Spoom
|
|
|
43
46
|
# @abstract
|
|
44
47
|
#: -> String
|
|
45
48
|
def script = raise NotImplementedError, "Abstract method called"
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# Serialize `value` as a JavaScript literal that is safe to embed in a `<script>` tag
|
|
53
|
+
#
|
|
54
|
+
# `script_safe` escapes `/` so the data can't contain `</script>`. We also escape `<`, `>` and `&`
|
|
55
|
+
# so the data can't contain `<!--` or `<script`, which change how the HTML parser reads the rest
|
|
56
|
+
# of the `<script>` element.
|
|
57
|
+
#: (untyped value) -> String
|
|
58
|
+
def js_literal(value)
|
|
59
|
+
JSON.generate(value, script_safe: true).gsub(/[<>&]/, JS_LITERAL_ESCAPES)
|
|
60
|
+
end
|
|
46
61
|
end
|
|
47
62
|
end
|
|
48
63
|
end
|
|
@@ -47,7 +47,7 @@ module Spoom
|
|
|
47
47
|
|
|
48
48
|
function tooltipMap(d) {
|
|
49
49
|
moveTooltip(d)
|
|
50
|
-
.html("<b>" + d.data.name + "</b>")
|
|
50
|
+
.html("<b>" + escapeHtml(d.data.name) + "</b>")
|
|
51
51
|
}
|
|
52
52
|
JS
|
|
53
53
|
end
|
|
@@ -57,7 +57,7 @@ module Spoom
|
|
|
57
57
|
#: -> String
|
|
58
58
|
def script
|
|
59
59
|
<<~JS
|
|
60
|
-
var root = {children: #{@data
|
|
60
|
+
var root = {children: #{js_literal(@data)}}
|
|
61
61
|
var dataHeight = treeHeight(root)
|
|
62
62
|
|
|
63
63
|
var opacity = d3.scaleLinear()
|
|
@@ -38,8 +38,8 @@ module Spoom
|
|
|
38
38
|
<<~JS
|
|
39
39
|
function tooltipPie(d, title, kind, sum) {
|
|
40
40
|
moveTooltip(d)
|
|
41
|
-
.html("<b>" + title + "</b><br><br>"
|
|
42
|
-
+ "<b>" + d.data.value + "</b> " + kind + "<br>"
|
|
41
|
+
.html("<b>" + escapeHtml(title) + "</b><br><br>"
|
|
42
|
+
+ "<b>" + escapeHtml(d.data.value) + "</b> " + kind + "<br>"
|
|
43
43
|
+ "<b>" + toPercent(d.data.value, sum) + "</b>%")
|
|
44
44
|
}
|
|
45
45
|
JS
|
|
@@ -52,11 +52,11 @@ module Spoom
|
|
|
52
52
|
<<~JS
|
|
53
53
|
#{tooltip}
|
|
54
54
|
|
|
55
|
-
var json_#{id} = #{@data
|
|
55
|
+
var json_#{id} = #{js_literal(@data)};
|
|
56
56
|
var pie_#{id} = d3.pie().value((d) => d.value);
|
|
57
57
|
var data_#{id} = pie_#{id}(d3.entries(json_#{id}));
|
|
58
58
|
var sum_#{id} = d3.sum(data_#{id}, (d) => d.data.value);
|
|
59
|
-
var title_#{id} = #{@title
|
|
59
|
+
var title_#{id} = #{js_literal(@title)};
|
|
60
60
|
|
|
61
61
|
function draw_#{id}() {
|
|
62
62
|
var pieSize_#{id} = document.getElementById("#{id}").clientWidth - 10;
|
|
@@ -81,10 +81,10 @@ module Spoom
|
|
|
81
81
|
|
|
82
82
|
function tooltipTimeline(d, kind) {
|
|
83
83
|
moveTooltip(d)
|
|
84
|
-
.html("commit <b>" + d.data.commit + "</b><br>"
|
|
84
|
+
.html("commit <b>" + escapeHtml(d.data.commit) + "</b><br>"
|
|
85
85
|
+ d3.timeFormat("%y/%m/%d")(parseDate(d.data.timestamp)) + "<br><br>"
|
|
86
|
-
+ "<b>typed: " + d.key + "</b><br><br>"
|
|
87
|
-
+ "<b>" + (d.data.values[d.key] ? d.data.values[d.key] : 0) + "</b> " + kind +"<br>"
|
|
86
|
+
+ "<b>typed: " + escapeHtml(d.key) + "</b><br><br>"
|
|
87
|
+
+ "<b>" + escapeHtml(d.data.values[d.key] ? d.data.values[d.key] : 0) + "</b> " + kind +"<br>"
|
|
88
88
|
+ "<b>" + toPercent(d.data.values[d.key] ? d.data.values[d.key] : 0, d.data.total) + "%")
|
|
89
89
|
}
|
|
90
90
|
JS
|
|
@@ -97,7 +97,7 @@ module Spoom
|
|
|
97
97
|
<<~HTML
|
|
98
98
|
#{tooltip}
|
|
99
99
|
|
|
100
|
-
var data_#{id} = #{@data
|
|
100
|
+
var data_#{id} = #{js_literal(@data)};
|
|
101
101
|
|
|
102
102
|
function draw_#{id}() {
|
|
103
103
|
var width_#{id} = document.getElementById("#{id}").clientWidth;
|
|
@@ -245,10 +245,10 @@ module Spoom
|
|
|
245
245
|
<<~JS
|
|
246
246
|
function tooltip_#{id}(d) {
|
|
247
247
|
moveTooltip(d)
|
|
248
|
-
.html("commit <b>" + d.commit + "</b><br>"
|
|
248
|
+
.html("commit <b>" + escapeHtml(d.commit) + "</b><br>"
|
|
249
249
|
+ d3.timeFormat("%y/%m/%d")(parseDate(d.timestamp)) + "<br><br>"
|
|
250
|
-
+ "static: v<b>" + d.static + "</b><br>"
|
|
251
|
-
+ "runtime: v<b>" + d.runtime + "</b><br><br>"
|
|
250
|
+
+ "static: v<b>" + escapeHtml(d.static) + "</b><br>"
|
|
251
|
+
+ "runtime: v<b>" + escapeHtml(d.runtime) + "</b><br><br>"
|
|
252
252
|
+ "versions from<br>Gemfile.lock")
|
|
253
253
|
}
|
|
254
254
|
JS
|
|
@@ -294,9 +294,9 @@ module Spoom
|
|
|
294
294
|
<<~JS
|
|
295
295
|
function tooltip_#{id}(d) {
|
|
296
296
|
moveTooltip(d)
|
|
297
|
-
.html("commit <b>" + d.commit + "</b><br>"
|
|
297
|
+
.html("commit <b>" + escapeHtml(d.commit) + "</b><br>"
|
|
298
298
|
+ d3.timeFormat("%y/%m/%d")(parseDate(d.timestamp)) + "<br><br>"
|
|
299
|
-
+ "<b>" + d.runtime + "</b>s<br><br>"
|
|
299
|
+
+ "<b>" + escapeHtml(d.runtime) + "</b>s<br><br>"
|
|
300
300
|
+ "(sorbet user + system time)")
|
|
301
301
|
}
|
|
302
302
|
JS
|
|
@@ -330,8 +330,8 @@ module Spoom
|
|
|
330
330
|
<<~JS
|
|
331
331
|
#{tooltip}
|
|
332
332
|
|
|
333
|
-
var data_#{id} = #{@data
|
|
334
|
-
var keys_#{id} = #{
|
|
333
|
+
var data_#{id} = #{js_literal(@data)};
|
|
334
|
+
var keys_#{id} = #{js_literal(@keys)};
|
|
335
335
|
|
|
336
336
|
var stack_#{id} = d3.stack()
|
|
337
337
|
.keys(keys_#{id})
|
|
@@ -515,11 +515,11 @@ module Spoom
|
|
|
515
515
|
<<~JS
|
|
516
516
|
function tooltip_#{id}(d) {
|
|
517
517
|
moveTooltip(d)
|
|
518
|
-
.html("commit <b>" + d.data.commit + "</b><br>"
|
|
518
|
+
.html("commit <b>" + escapeHtml(d.data.commit) + "</b><br>"
|
|
519
519
|
+ d3.timeFormat("%y/%m/%d")(parseDate(d.data.timestamp)) + "<br><br>"
|
|
520
|
-
+ "Files: <b>" + d.data.values.files + "</b><br>"
|
|
521
|
-
+ "RBIs: <b>" + d.data.values.rbis + "</b><br><br>"
|
|
522
|
-
+ "Total: <b>" + d.data.total + "</b>")
|
|
520
|
+
+ "Files: <b>" + escapeHtml(d.data.values.files) + "</b><br>"
|
|
521
|
+
+ "RBIs: <b>" + escapeHtml(d.data.values.rbis) + "</b><br><br>"
|
|
522
|
+
+ "Total: <b>" + escapeHtml(d.data.total) + "</b>")
|
|
523
523
|
}
|
|
524
524
|
JS
|
|
525
525
|
end
|
|
@@ -530,8 +530,8 @@ module Spoom
|
|
|
530
530
|
<<~JS
|
|
531
531
|
#{tooltip}
|
|
532
532
|
|
|
533
|
-
var data_#{id} = #{@data
|
|
534
|
-
var keys_#{id} = #{
|
|
533
|
+
var data_#{id} = #{js_literal(@data)};
|
|
534
|
+
var keys_#{id} = #{js_literal(@keys)};
|
|
535
535
|
|
|
536
536
|
var stack_#{id} = d3.stack()
|
|
537
537
|
.keys(keys_#{id})
|
data/lib/spoom/coverage/d3.rb
CHANGED
|
@@ -76,6 +76,15 @@ module Spoom
|
|
|
76
76
|
return "#{palette.false_color}";
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function escapeHtml(value) {
|
|
80
|
+
return String(value)
|
|
81
|
+
.replace(/&/g, "&")
|
|
82
|
+
.replace(/</g, "<")
|
|
83
|
+
.replace(/>/g, ">")
|
|
84
|
+
.replace(/"/g, """)
|
|
85
|
+
.replace(/'/g, "'");
|
|
86
|
+
}
|
|
87
|
+
|
|
79
88
|
function toPercent(value, sum) {
|
|
80
89
|
return value ? Math.round(value * 100 / sum) : 0;
|
|
81
90
|
}
|
|
@@ -60,7 +60,7 @@ module Spoom
|
|
|
60
60
|
|
|
61
61
|
#: -> String
|
|
62
62
|
def header_html
|
|
63
|
-
"<h1 class='display-3'>#{title}</h1>"
|
|
63
|
+
"<h1 class='display-3'>#{ERB::Util.html_escape(title)}</h1>"
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
#: -> String
|
|
@@ -219,7 +219,7 @@ module Spoom
|
|
|
219
219
|
<<~ERB
|
|
220
220
|
<div class="text-center" style="margin-top: 30px">
|
|
221
221
|
Typchecked by Sorbet since <b>#{@sorbet_intro_date&.strftime("%F")}</b>
|
|
222
|
-
(commit <b>#{@sorbet_intro_commit}</b>).
|
|
222
|
+
(commit <b>#{ERB::Util.html_escape(@sorbet_intro_commit)}</b>).
|
|
223
223
|
</div>
|
|
224
224
|
ERB
|
|
225
225
|
end
|
|
@@ -263,8 +263,8 @@ module Spoom
|
|
|
263
263
|
last = T.must(@snapshots.last)
|
|
264
264
|
<<~ERB
|
|
265
265
|
<h1 class="display-3">
|
|
266
|
-
#{@project_name}
|
|
267
|
-
<span class="badge badge-pill badge-dark" style="font-size: 20%;">#{last.commit_sha}</span>
|
|
266
|
+
#{ERB::Util.html_escape(@project_name)}
|
|
267
|
+
<span class="badge badge-pill badge-dark" style="font-size: 20%;">#{ERB::Util.html_escape(last.commit_sha)}</span>
|
|
268
268
|
</h1>
|
|
269
269
|
ERB
|
|
270
270
|
end
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
module Spoom
|
|
5
5
|
module Coverage
|
|
6
6
|
class Snapshot
|
|
7
|
+
class Error < Spoom::Error; end
|
|
8
|
+
|
|
7
9
|
#: Integer
|
|
8
10
|
attr_accessor :timestamp
|
|
9
11
|
|
|
@@ -173,30 +175,32 @@ module Spoom
|
|
|
173
175
|
#: (Hash[String, untyped] obj) -> Snapshot
|
|
174
176
|
def from_obj(obj)
|
|
175
177
|
snapshot = Snapshot.new
|
|
176
|
-
snapshot.timestamp = obj
|
|
177
|
-
snapshot.version_static = obj.fetch("version_static", nil)
|
|
178
|
-
snapshot.version_runtime = obj.fetch("version_runtime", nil)
|
|
179
|
-
snapshot.duration = obj
|
|
180
|
-
snapshot.commit_sha = obj.fetch("commit_sha", nil)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
snapshot.
|
|
185
|
-
snapshot.
|
|
186
|
-
snapshot.
|
|
187
|
-
snapshot.
|
|
188
|
-
snapshot.
|
|
189
|
-
snapshot.
|
|
190
|
-
snapshot.
|
|
191
|
-
snapshot.
|
|
192
|
-
snapshot.
|
|
178
|
+
snapshot.timestamp = integer_field(obj, "timestamp")
|
|
179
|
+
snapshot.version_static = obj.fetch("version_static", nil)&.to_s
|
|
180
|
+
snapshot.version_runtime = obj.fetch("version_runtime", nil)&.to_s
|
|
181
|
+
snapshot.duration = integer_field(obj, "duration")
|
|
182
|
+
snapshot.commit_sha = obj.fetch("commit_sha", nil)&.to_s
|
|
183
|
+
unless obj.fetch("commit_timestamp", nil).nil?
|
|
184
|
+
snapshot.commit_timestamp = integer_field(obj, "commit_timestamp")
|
|
185
|
+
end
|
|
186
|
+
snapshot.files = integer_field(obj, "files")
|
|
187
|
+
snapshot.rbi_files = integer_field(obj, "rbi_files")
|
|
188
|
+
snapshot.modules = integer_field(obj, "modules")
|
|
189
|
+
snapshot.classes = integer_field(obj, "classes")
|
|
190
|
+
snapshot.singleton_classes = integer_field(obj, "singleton_classes")
|
|
191
|
+
snapshot.methods_with_sig = integer_field(obj, "methods_with_sig")
|
|
192
|
+
snapshot.methods_without_sig = integer_field(obj, "methods_without_sig")
|
|
193
|
+
snapshot.calls_typed = integer_field(obj, "calls_typed")
|
|
194
|
+
snapshot.calls_untyped = integer_field(obj, "calls_untyped")
|
|
195
|
+
snapshot.methods_with_sig_excluding_rbis = integer_field(obj, "methods_with_sig_excluding_rbis")
|
|
196
|
+
snapshot.methods_without_sig_excluding_rbis = integer_field(obj, "methods_without_sig_excluding_rbis")
|
|
193
197
|
|
|
194
198
|
sigils = obj.fetch("sigils", {})
|
|
195
199
|
if sigils
|
|
196
200
|
Snapshot::STRICTNESSES.each do |strictness|
|
|
197
201
|
next unless sigils.key?(strictness)
|
|
198
202
|
|
|
199
|
-
snapshot.sigils[strictness] = sigils
|
|
203
|
+
snapshot.sigils[strictness] = integer_field(sigils, strictness)
|
|
200
204
|
end
|
|
201
205
|
end
|
|
202
206
|
|
|
@@ -205,12 +209,29 @@ module Spoom
|
|
|
205
209
|
Snapshot::STRICTNESSES.each do |strictness|
|
|
206
210
|
next unless sigils_excluding_rbis.key?(strictness)
|
|
207
211
|
|
|
208
|
-
snapshot.sigils_excluding_rbis[strictness] = sigils_excluding_rbis
|
|
212
|
+
snapshot.sigils_excluding_rbis[strictness] = integer_field(sigils_excluding_rbis, strictness)
|
|
209
213
|
end
|
|
210
214
|
end
|
|
211
215
|
|
|
212
216
|
snapshot
|
|
213
217
|
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
# Read `key` from `obj` as an Integer
|
|
222
|
+
#
|
|
223
|
+
# Snapshot files live in the analyzed project and can't be trusted to hold the types spoom wrote.
|
|
224
|
+
# Raises `Snapshot::Error` if the value can't be interpreted as an Integer.
|
|
225
|
+
#: (Hash[String, untyped] obj, String key, ?default: Integer) -> Integer
|
|
226
|
+
def integer_field(obj, key, default: 0)
|
|
227
|
+
if (value = obj[key])
|
|
228
|
+
Integer(value)
|
|
229
|
+
else
|
|
230
|
+
default
|
|
231
|
+
end
|
|
232
|
+
rescue ArgumentError, TypeError
|
|
233
|
+
raise Error, "Expected an integer for `#{key}`, got #{value.inspect}"
|
|
234
|
+
end
|
|
214
235
|
end
|
|
215
236
|
end
|
|
216
237
|
|
data/lib/spoom/version.rb
CHANGED
data/rbi/spoom.rbi
CHANGED
|
@@ -614,6 +614,11 @@ class Spoom::Coverage::D3::Base
|
|
|
614
614
|
sig { returns(::String) }
|
|
615
615
|
def tooltip; end
|
|
616
616
|
|
|
617
|
+
private
|
|
618
|
+
|
|
619
|
+
sig { params(value: T.untyped).returns(::String) }
|
|
620
|
+
def js_literal(value); end
|
|
621
|
+
|
|
617
622
|
class << self
|
|
618
623
|
sig { returns(::String) }
|
|
619
624
|
def header_script; end
|
|
@@ -623,6 +628,7 @@ class Spoom::Coverage::D3::Base
|
|
|
623
628
|
end
|
|
624
629
|
end
|
|
625
630
|
|
|
631
|
+
Spoom::Coverage::D3::Base::JS_LITERAL_ESCAPES = T.let(T.unsafe(nil), Hash)
|
|
626
632
|
Spoom::Coverage::D3::COLOR_FALSE = T.let(T.unsafe(nil), String)
|
|
627
633
|
Spoom::Coverage::D3::COLOR_IGNORE = T.let(T.unsafe(nil), String)
|
|
628
634
|
Spoom::Coverage::D3::COLOR_STRICT = T.let(T.unsafe(nil), String)
|
|
@@ -1070,9 +1076,15 @@ class Spoom::Coverage::Snapshot
|
|
|
1070
1076
|
|
|
1071
1077
|
sig { params(obj: T::Hash[::String, T.untyped]).returns(::Spoom::Coverage::Snapshot) }
|
|
1072
1078
|
def from_obj(obj); end
|
|
1079
|
+
|
|
1080
|
+
private
|
|
1081
|
+
|
|
1082
|
+
sig { params(obj: T::Hash[::String, T.untyped], key: ::String, default: ::Integer).returns(::Integer) }
|
|
1083
|
+
def integer_field(obj, key, default: T.unsafe(nil)); end
|
|
1073
1084
|
end
|
|
1074
1085
|
end
|
|
1075
1086
|
|
|
1087
|
+
class Spoom::Coverage::Snapshot::Error < ::Spoom::Error; end
|
|
1076
1088
|
Spoom::Coverage::Snapshot::STRICTNESSES = T.let(T.unsafe(nil), Array)
|
|
1077
1089
|
|
|
1078
1090
|
class Spoom::Coverage::SnapshotPrinter < ::Spoom::Printer
|
data/templates/page.erb
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
7
|
|
|
8
|
-
<title><%= title %></title>
|
|
8
|
+
<title><%= ERB::Util.html_escape(title) %></title>
|
|
9
9
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
|
|
10
10
|
|
|
11
11
|
<style>
|