spoom 1.8.11 → 1.8.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/README.md +20 -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 +16 -1
- 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 +18 -18
- data/lib/spoom/coverage/d3.rb +9 -0
- data/lib/spoom/coverage/report.rb +6 -6
- data/lib/spoom/coverage/snapshot.rb +40 -19
- data/lib/spoom/rbs.rb +15 -1
- data/lib/spoom/sorbet/lsp/structures.rb +1 -1
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/base_translator.rb +20 -10
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs/human_readable_translator.rb +9 -0
- data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +12 -4
- data/lib/spoom/source/rewriter.rb +2 -2
- data/lib/spoom/version.rb +1 -1
- data/rbi/spoom.rbi +21 -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: f635fde4d393174f9f980312bd71fb2e2acb319e10d77ed894c62d795dde5ef9
|
|
4
|
+
data.tar.gz: fd6e3c6f2a271dd4b27e92c98373212d5aa61f5bfcd3c0519bf046d2255cccba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7cf3ad6755704d567b904fdbfe1f0508682f70f09f320e97e9b5c095c9edd62cfcc426443575078d1dc97ee7aa9045e73d1433f53706ab9e08a314b004e982fc
|
|
7
|
+
data.tar.gz: ec8a52440bb314422240b686f5abffbcd0351d61e96576cf38bf77cf2678d1fa72a84e2dc33c20af34deb7a9829a68927bc4f1c4fcda8ec0b1d083559ccdbd99
|
data/README.md
CHANGED
|
@@ -256,6 +256,12 @@ Translate signatures from RBI to RBS comments:
|
|
|
256
256
|
spoom srb sigs translate
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
+
Include abstract methods:
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
spoom srb sigs translate --translate-abstract-methods
|
|
263
|
+
```
|
|
264
|
+
|
|
259
265
|
Translate signatures from RBS comments to RBI:
|
|
260
266
|
|
|
261
267
|
```sh
|
|
@@ -387,6 +393,20 @@ Remove a reported dead code candidate:
|
|
|
387
393
|
spoom deadcode remove path/to/file.rb:42:18-47:23
|
|
388
394
|
```
|
|
389
395
|
|
|
396
|
+
#### Custom plugins
|
|
397
|
+
|
|
398
|
+
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`:
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
# .spoom/deadcode/plugins/my_plugin.rb
|
|
402
|
+
class MyPlugin < Spoom::Deadcode::Plugins::Base
|
|
403
|
+
ignore_methods_named("my_dsl_method")
|
|
404
|
+
end
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
> [!WARNING]
|
|
408
|
+
> 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).
|
|
409
|
+
|
|
390
410
|
## Ruby API
|
|
391
411
|
|
|
392
412
|
### 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
|
|
|
@@ -42,7 +45,19 @@ module Spoom
|
|
|
42
45
|
|
|
43
46
|
# @abstract
|
|
44
47
|
#: -> String
|
|
45
|
-
def script =
|
|
48
|
+
def script = super
|
|
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;
|
|
@@ -119,7 +119,7 @@ module Spoom
|
|
|
119
119
|
|
|
120
120
|
# @abstract
|
|
121
121
|
#: -> String
|
|
122
|
-
def plot =
|
|
122
|
+
def plot = super
|
|
123
123
|
|
|
124
124
|
#: -> String
|
|
125
125
|
def x_scale
|
|
@@ -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
|
|
@@ -70,7 +70,7 @@ module Spoom
|
|
|
70
70
|
|
|
71
71
|
# @abstract
|
|
72
72
|
#: -> Array[Cards::Card]
|
|
73
|
-
def cards =
|
|
73
|
+
def cards = super
|
|
74
74
|
|
|
75
75
|
#: -> String
|
|
76
76
|
def footer_html
|
|
@@ -106,7 +106,7 @@ module Spoom
|
|
|
106
106
|
|
|
107
107
|
# @abstract
|
|
108
108
|
#: -> String
|
|
109
|
-
def erb =
|
|
109
|
+
def erb = super
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
class Snapshot < Card
|
|
@@ -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/rbs.rb
CHANGED
|
@@ -66,6 +66,20 @@ module Spoom
|
|
|
66
66
|
@string = string
|
|
67
67
|
@location = location
|
|
68
68
|
end
|
|
69
|
+
|
|
70
|
+
# The `#` comments written inside this RBS comment (e.g.: #| String, # this is the inner comment)
|
|
71
|
+
#
|
|
72
|
+
# The RBS parser discards them, so they are found by lexing the RBS, which unlike looking for `#` doesn't
|
|
73
|
+
# mistake one written inside a string literal type (e.g. `"#"`) for a comment.
|
|
74
|
+
#
|
|
75
|
+
#: -> Array[String]
|
|
76
|
+
def inner_comments
|
|
77
|
+
return [] unless @string.include?("#")
|
|
78
|
+
|
|
79
|
+
::RBS::Parser.lex(@string).value.filter_map do |token|
|
|
80
|
+
token.location.source if token.type == :tCOMMENT || token.type == :tLINECOMMENT
|
|
81
|
+
end
|
|
82
|
+
end
|
|
69
83
|
end
|
|
70
84
|
|
|
71
85
|
class Annotation < Comment
|
|
@@ -120,7 +134,7 @@ module Spoom
|
|
|
120
134
|
continuation_locations = [] #: Array[Prism::Location]
|
|
121
135
|
|
|
122
136
|
continuation_comments.reverse_each do |continuation_comment|
|
|
123
|
-
string = "#{string}#{continuation_comment.slice.delete_prefix("#|")}"
|
|
137
|
+
string = "#{string}\n#{continuation_comment.slice.delete_prefix("#|")}"
|
|
124
138
|
location = location.join(continuation_comment.location)
|
|
125
139
|
continuation_locations << continuation_comment.location
|
|
126
140
|
end
|
|
@@ -127,7 +127,10 @@ module Spoom
|
|
|
127
127
|
@rewriter << Source::Replace.new(
|
|
128
128
|
signature.location.start_offset,
|
|
129
129
|
signature.location.end_offset,
|
|
130
|
-
pad_out_line_count(
|
|
130
|
+
pad_out_line_count(
|
|
131
|
+
of: preserve_comments(sig.string(max_line_length: @max_line_length), from: signature),
|
|
132
|
+
to_height_of: signature,
|
|
133
|
+
),
|
|
131
134
|
)
|
|
132
135
|
rescue ::RBS::ParsingError, ::RBI::Error
|
|
133
136
|
# Ignore signatures with errors
|
|
@@ -185,7 +188,10 @@ module Spoom
|
|
|
185
188
|
@rewriter << Source::Replace.new(
|
|
186
189
|
signature.location.start_offset,
|
|
187
190
|
signature.location.end_offset,
|
|
188
|
-
pad_out_line_count(
|
|
191
|
+
pad_out_line_count(
|
|
192
|
+
of: preserve_comments(sig.string(max_line_length: @max_line_length), from: signature),
|
|
193
|
+
to_height_of: signature,
|
|
194
|
+
),
|
|
189
195
|
)
|
|
190
196
|
end
|
|
191
197
|
|
|
@@ -215,7 +221,7 @@ module Spoom
|
|
|
215
221
|
# Called for every overloaded method sig that we discard because it wasn't the last one.
|
|
216
222
|
# @abstract
|
|
217
223
|
#: (Spoom::RBS::Signature) -> void
|
|
218
|
-
def rewrite_discarded_overload(signature) =
|
|
224
|
+
def rewrite_discarded_overload(signature) = super
|
|
219
225
|
|
|
220
226
|
#: (PrismTypes::anyScopeNode) -> void
|
|
221
227
|
def apply_class_annotations(node)
|
|
@@ -326,17 +332,17 @@ module Spoom
|
|
|
326
332
|
#| insert_pos: Integer,
|
|
327
333
|
#| sorbet_replacement: String?
|
|
328
334
|
#| ) -> void
|
|
329
|
-
def apply_class_annotation(annotation, parent_node:, insert_pos:, sorbet_replacement:) =
|
|
335
|
+
def apply_class_annotation(annotation, parent_node:, insert_pos:, sorbet_replacement:) = super
|
|
330
336
|
|
|
331
337
|
# Rewrites the `#: [...]` type params comment (e.g. delete it, or mark it as translated).
|
|
332
338
|
# @abstract
|
|
333
339
|
#: (Spoom::RBS::Signature, type_params: Array[::RBS::AST::TypeParam]) -> void
|
|
334
|
-
def rewrite_type_params_signature(signature, type_params:) =
|
|
340
|
+
def rewrite_type_params_signature(signature, type_params:) = super
|
|
335
341
|
|
|
336
342
|
# Inserts a single `type_member` declaration into the class/module body.
|
|
337
343
|
# @abstract
|
|
338
344
|
#: (String type_member, parent_node: PrismTypes::anyScopeNode, insert_pos: Integer) -> void
|
|
339
|
-
def insert_type_member(type_member, parent_node:, insert_pos:) =
|
|
345
|
+
def insert_type_member(type_member, parent_node:, insert_pos:) = super
|
|
340
346
|
|
|
341
347
|
#: (Array[Spoom::RBS::Annotation], RBI::Sig) -> Array[Spoom::RBS::Annotation]
|
|
342
348
|
def apply_member_annotations(annotations, sig)
|
|
@@ -389,7 +395,7 @@ module Spoom
|
|
|
389
395
|
|
|
390
396
|
# @abstract
|
|
391
397
|
#: (String mixin_name, into: PrismTypes::anyScopeNode, at: Integer) -> void
|
|
392
|
-
def extend_with(mixin_name, into:, at:) =
|
|
398
|
+
def extend_with(mixin_name, into:, at:) = super
|
|
393
399
|
|
|
394
400
|
#: (Array[Prism::Comment]) -> Array[Spoom::RBS::TypeAlias]
|
|
395
401
|
def collect_type_aliases(comments)
|
|
@@ -408,7 +414,7 @@ module Spoom
|
|
|
408
414
|
|
|
409
415
|
if string.start_with?("type ")
|
|
410
416
|
continuation_comments.reverse_each do |continuation_comment|
|
|
411
|
-
string = "#{string}#{continuation_comment.slice.delete_prefix("#|")}"
|
|
417
|
+
string = "#{string}\n#{continuation_comment.slice.delete_prefix("#|")}"
|
|
412
418
|
location = location.join(continuation_comment.location)
|
|
413
419
|
end
|
|
414
420
|
|
|
@@ -455,8 +461,8 @@ module Spoom
|
|
|
455
461
|
)
|
|
456
462
|
|
|
457
463
|
@rewriter << Source::Delete.new(from, to)
|
|
458
|
-
content = "#{
|
|
459
|
-
content = pad_out_line_count(of: content, to_height_of: type_alias)
|
|
464
|
+
content = preserve_comments("#{alias_name} = ::T.type_alias { #{sorbet_type.to_rbi} }", from: type_alias)
|
|
465
|
+
content = pad_out_line_count(of: "#{indent}#{content}\n", to_height_of: type_alias)
|
|
460
466
|
@rewriter << Source::Insert.new(insert_pos, content)
|
|
461
467
|
rescue ::RBS::ParsingError, ::RBI::Error
|
|
462
468
|
# Ignore type aliases with errors
|
|
@@ -464,6 +470,10 @@ module Spoom
|
|
|
464
470
|
end
|
|
465
471
|
end
|
|
466
472
|
|
|
473
|
+
# @overridable
|
|
474
|
+
#: (String replacement, from: Spoom::RBS::Comment) -> String
|
|
475
|
+
def preserve_comments(replacement, from:) = replacement # no-op
|
|
476
|
+
|
|
467
477
|
# @overridable
|
|
468
478
|
#: (of: String, to_height_of: Spoom::RBS::Comment) -> String
|
|
469
479
|
def pad_out_line_count(of:, to_height_of:)
|
|
@@ -56,6 +56,15 @@ module Spoom
|
|
|
56
56
|
@rewriter << Source::Insert.new(insert_pos, "\n#{indent}#{type_member}#{newline}")
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Keeps the comments written inside the RBS comment as lines above the replacement
|
|
60
|
+
#
|
|
61
|
+
# @override
|
|
62
|
+
#: (String replacement, from: Spoom::RBS::Comment) -> String
|
|
63
|
+
def preserve_comments(replacement, from:)
|
|
64
|
+
indent = " " * from.location.start_column
|
|
65
|
+
from.inner_comments.map { |comment| "#{comment}\n#{indent}" }.join + replacement
|
|
66
|
+
end
|
|
67
|
+
|
|
59
68
|
# @override
|
|
60
69
|
#: (String mixin_name, into: Prism::Node, at: Integer) -> void
|
|
61
70
|
def extend_with(mixin_name, into:, at:)
|
|
@@ -90,14 +90,22 @@ module Spoom
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
if last_sigs.any? { |_, sig| sig.is_abstract }
|
|
93
|
+
header = node.rparen_loc || node.parameters&.location || node.name_loc
|
|
94
|
+
comment = @comments.find do |comment|
|
|
95
|
+
comment.location.start_line == header.end_line &&
|
|
96
|
+
comment.location.start_offset >= header.end_offset &&
|
|
97
|
+
comment.location.end_offset < node.location.end_offset
|
|
98
|
+
end
|
|
99
|
+
header_comment = comment ? " #{comment.location.slice}" : ""
|
|
100
|
+
|
|
93
101
|
@rewriter << Source::Replace.new(
|
|
94
|
-
|
|
102
|
+
header.end_offset,
|
|
95
103
|
node.location.end_offset - 1,
|
|
96
|
-
if node.name.end_with?("=")
|
|
104
|
+
if node.name.end_with?("=") || (node.parameters && !node.rparen_loc)
|
|
97
105
|
indent = " " * node.location.start_column
|
|
98
|
-
"\n#{indent}
|
|
106
|
+
"#{header_comment}\n#{indent} super\n#{indent}end"
|
|
99
107
|
else
|
|
100
|
-
" =
|
|
108
|
+
" = super#{header_comment}"
|
|
101
109
|
end,
|
|
102
110
|
)
|
|
103
111
|
end
|
|
@@ -29,11 +29,11 @@ module Spoom
|
|
|
29
29
|
class Edit
|
|
30
30
|
# @abstract
|
|
31
31
|
#: (Array[Integer]) -> void
|
|
32
|
-
def apply(bytes) =
|
|
32
|
+
def apply(bytes) = super
|
|
33
33
|
|
|
34
34
|
# @abstract
|
|
35
35
|
#: -> [Integer, Integer]
|
|
36
|
-
def range =
|
|
36
|
+
def range = super
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
class Insert < Edit
|
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
|
|
@@ -2938,6 +2950,9 @@ class Spoom::RBS::Comment
|
|
|
2938
2950
|
sig { params(string: ::String, location: ::Prism::Location).void }
|
|
2939
2951
|
def initialize(string, location); end
|
|
2940
2952
|
|
|
2953
|
+
sig { returns(T::Array[::String]) }
|
|
2954
|
+
def inner_comments; end
|
|
2955
|
+
|
|
2941
2956
|
sig { returns(::Prism::Location) }
|
|
2942
2957
|
def location; end
|
|
2943
2958
|
|
|
@@ -3471,6 +3486,9 @@ class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::BaseTranslator < ::Spoo
|
|
|
3471
3486
|
sig { overridable.params(of: ::String, to_height_of: ::Spoom::RBS::Comment).returns(::String) }
|
|
3472
3487
|
def pad_out_line_count(of:, to_height_of:); end
|
|
3473
3488
|
|
|
3489
|
+
sig { overridable.params(replacement: ::String, from: ::Spoom::RBS::Comment).returns(::String) }
|
|
3490
|
+
def preserve_comments(replacement, from:); end
|
|
3491
|
+
|
|
3474
3492
|
sig { overridable.params(annotation: ::Spoom::RBS::Annotation, is_known: T::Boolean).void }
|
|
3475
3493
|
def rewrite_annotation(annotation, is_known:); end
|
|
3476
3494
|
|
|
@@ -3530,6 +3548,9 @@ class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::HumanReadableTranslator
|
|
|
3530
3548
|
end
|
|
3531
3549
|
def insert_type_member(type_member, parent_node:, insert_pos:); end
|
|
3532
3550
|
|
|
3551
|
+
sig { override.params(replacement: ::String, from: ::Spoom::RBS::Comment).returns(::String) }
|
|
3552
|
+
def preserve_comments(replacement, from:); end
|
|
3553
|
+
|
|
3533
3554
|
sig { override.params(signature: ::Spoom::RBS::Signature).void }
|
|
3534
3555
|
def rewrite_discarded_overload(signature); end
|
|
3535
3556
|
|
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>
|