spoom 1.5.0 → 1.7.2
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/backtrace_filter/minitest.rb +3 -4
- data/lib/spoom/cli/deadcode.rb +1 -2
- data/lib/spoom/cli/helper.rb +41 -31
- data/lib/spoom/cli/srb/assertions.rb +48 -0
- data/lib/spoom/cli/srb/bump.rb +1 -2
- data/lib/spoom/cli/srb/coverage.rb +1 -1
- data/lib/spoom/cli/srb/metrics.rb +68 -0
- data/lib/spoom/cli/srb/sigs.rb +209 -0
- data/lib/spoom/cli/srb/tc.rb +16 -1
- data/lib/spoom/cli/srb.rb +16 -4
- data/lib/spoom/cli.rb +1 -2
- data/lib/spoom/colors.rb +2 -6
- data/lib/spoom/context/bundle.rb +8 -9
- data/lib/spoom/context/exec.rb +3 -6
- data/lib/spoom/context/file_system.rb +12 -19
- data/lib/spoom/context/git.rb +14 -19
- data/lib/spoom/context/sorbet.rb +14 -27
- data/lib/spoom/context.rb +4 -8
- data/lib/spoom/counters.rb +22 -0
- data/lib/spoom/coverage/d3/base.rb +6 -8
- data/lib/spoom/coverage/d3/circle_map.rb +6 -16
- data/lib/spoom/coverage/d3/pie.rb +14 -19
- data/lib/spoom/coverage/d3/timeline.rb +46 -47
- data/lib/spoom/coverage/d3.rb +2 -4
- data/lib/spoom/coverage/report.rb +41 -79
- data/lib/spoom/coverage/snapshot.rb +8 -14
- data/lib/spoom/coverage.rb +3 -5
- data/lib/spoom/deadcode/definition.rb +12 -14
- data/lib/spoom/deadcode/erb.rb +10 -8
- data/lib/spoom/deadcode/index.rb +21 -25
- data/lib/spoom/deadcode/indexer.rb +5 -6
- data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -3
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +2 -3
- data/lib/spoom/deadcode/plugins/actionpack.rb +19 -22
- data/lib/spoom/deadcode/plugins/active_model.rb +2 -3
- data/lib/spoom/deadcode/plugins/active_record.rb +62 -53
- data/lib/spoom/deadcode/plugins/active_support.rb +3 -2
- data/lib/spoom/deadcode/plugins/base.rb +29 -32
- data/lib/spoom/deadcode/plugins/graphql.rb +2 -3
- data/lib/spoom/deadcode/plugins/minitest.rb +4 -4
- data/lib/spoom/deadcode/plugins/namespaces.rb +5 -5
- data/lib/spoom/deadcode/plugins/rails.rb +5 -5
- data/lib/spoom/deadcode/plugins/rubocop.rb +5 -5
- data/lib/spoom/deadcode/plugins/ruby.rb +3 -4
- data/lib/spoom/deadcode/plugins/sorbet.rb +12 -6
- data/lib/spoom/deadcode/plugins/thor.rb +2 -3
- data/lib/spoom/deadcode/plugins.rb +23 -31
- data/lib/spoom/deadcode/remover.rb +58 -79
- data/lib/spoom/deadcode/send.rb +2 -8
- data/lib/spoom/file_collector.rb +11 -19
- data/lib/spoom/file_tree.rb +36 -51
- data/lib/spoom/location.rb +9 -20
- data/lib/spoom/model/builder.rb +54 -17
- data/lib/spoom/model/model.rb +71 -74
- data/lib/spoom/model/namespace_visitor.rb +4 -3
- data/lib/spoom/model/reference.rb +4 -8
- data/lib/spoom/model/references_visitor.rb +50 -30
- data/lib/spoom/parse.rb +4 -4
- data/lib/spoom/poset.rb +22 -24
- data/lib/spoom/printer.rb +10 -13
- data/lib/spoom/rbs.rb +77 -0
- data/lib/spoom/sorbet/config.rb +17 -24
- data/lib/spoom/sorbet/errors.rb +87 -45
- data/lib/spoom/sorbet/lsp/base.rb +10 -16
- data/lib/spoom/sorbet/lsp/errors.rb +8 -16
- data/lib/spoom/sorbet/lsp/structures.rb +65 -91
- data/lib/spoom/sorbet/lsp.rb +20 -22
- data/lib/spoom/sorbet/metrics/code_metrics_visitor.rb +236 -0
- data/lib/spoom/sorbet/metrics/metrics_file_parser.rb +34 -0
- data/lib/spoom/sorbet/metrics.rb +2 -32
- data/lib/spoom/sorbet/sigils.rb +16 -23
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +242 -0
- data/lib/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments.rb +123 -0
- data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +293 -0
- data/lib/spoom/sorbet/translate/strip_sorbet_sigs.rb +23 -0
- data/lib/spoom/sorbet/translate/translator.rb +71 -0
- data/lib/spoom/sorbet/translate.rb +49 -0
- data/lib/spoom/sorbet.rb +6 -12
- data/lib/spoom/source/rewriter.rb +167 -0
- data/lib/spoom/source.rb +4 -0
- data/lib/spoom/timeline.rb +4 -6
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom/visitor.rb +298 -151
- data/lib/spoom.rb +4 -3
- data/rbi/spoom.rbi +3567 -0
- metadata +62 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59987238a3b2bae115f02d1da2a7e2a4cd6b3532d816bbe80d9153fa7819f989
|
4
|
+
data.tar.gz: d7e6bccfa046b7c457cb3e5d3d4383141243949021bd584b22cde0db0cc165ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2f53478b7fdee59bfa7c579c3686b6d6c388db8737584c02f613daa2c4d42c0be2c1baaa39c50e90137697f62d7b8bb04e9fdbb458defdfa7813be3b6cbbd36
|
7
|
+
data.tar.gz: 57b26d457d2235bedaff15c832accc34a9c5ffcb89876306394f12b74a87ce03964d0309cd62e213e9954607150968607227436ebeb522a84f27782f7cc8cac8
|
data/README.md
CHANGED
@@ -221,6 +221,20 @@ Count the number of type-checking errors if all files were bumped to true:
|
|
221
221
|
$ spoom srb bump --count-errors --dry
|
222
222
|
```
|
223
223
|
|
224
|
+
#### Translate sigs between RBI and RBS
|
225
|
+
|
226
|
+
Translate all file sigs from RBI to RBS:
|
227
|
+
|
228
|
+
```
|
229
|
+
$ spoom srb sigs translate
|
230
|
+
```
|
231
|
+
|
232
|
+
Translate one file's sigs from RBS to RBI:
|
233
|
+
|
234
|
+
```
|
235
|
+
$ spoom srb sigs translate --from rbs --to rbi /path/to/file.rb
|
236
|
+
```
|
237
|
+
|
224
238
|
#### Interact with Sorbet LSP mode
|
225
239
|
|
226
240
|
**Experimental**
|
@@ -6,11 +6,10 @@ require "minitest"
|
|
6
6
|
module Spoom
|
7
7
|
module BacktraceFilter
|
8
8
|
class Minitest < ::Minitest::BacktraceFilter
|
9
|
-
|
9
|
+
SORBET_PATHS = Gem.loaded_specs["sorbet-runtime"].full_require_paths.freeze #: Array[String]
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
sig { override.params(bt: T.nilable(T::Array[String])).returns(T::Array[String]) }
|
11
|
+
# @override
|
12
|
+
#: (Array[String]? bt) -> Array[String]
|
14
13
|
def filter(bt)
|
15
14
|
super.select do |line|
|
16
15
|
SORBET_PATHS.none? { |path| line.include?(path) }
|
data/lib/spoom/cli/deadcode.rb
CHANGED
@@ -6,7 +6,6 @@ require_relative "../deadcode"
|
|
6
6
|
module Spoom
|
7
7
|
module Cli
|
8
8
|
class Deadcode < Thor
|
9
|
-
extend T::Sig
|
10
9
|
include Helper
|
11
10
|
|
12
11
|
default_task :deadcode
|
@@ -48,7 +47,7 @@ module Spoom
|
|
48
47
|
default: "name",
|
49
48
|
enum: ["name", "location"],
|
50
49
|
desc: "Sort the output by name or location"
|
51
|
-
|
50
|
+
#: (*String paths) -> void
|
52
51
|
def deadcode(*paths)
|
53
52
|
context = self.context
|
54
53
|
|
data/lib/spoom/cli/helper.rb
CHANGED
@@ -8,7 +8,6 @@ require "stringio"
|
|
8
8
|
module Spoom
|
9
9
|
module Cli
|
10
10
|
module Helper
|
11
|
-
extend T::Sig
|
12
11
|
extend T::Helpers
|
13
12
|
|
14
13
|
include Colorize
|
@@ -16,7 +15,7 @@ module Spoom
|
|
16
15
|
requires_ancestor { Thor }
|
17
16
|
|
18
17
|
# Print `message` on `$stdout`
|
19
|
-
|
18
|
+
#: (String message) -> void
|
20
19
|
def say(message)
|
21
20
|
buffer = StringIO.new
|
22
21
|
buffer << highlight(message)
|
@@ -29,13 +28,7 @@ module Spoom
|
|
29
28
|
# Print `message` on `$stderr`
|
30
29
|
#
|
31
30
|
# The message is prefixed by a status (default: `Error`).
|
32
|
-
|
33
|
-
params(
|
34
|
-
message: String,
|
35
|
-
status: T.nilable(String),
|
36
|
-
nl: T::Boolean,
|
37
|
-
).void
|
38
|
-
end
|
31
|
+
#: (String message, ?status: String?, ?nl: bool) -> void
|
39
32
|
def say_error(message, status: "Error", nl: true)
|
40
33
|
buffer = StringIO.new
|
41
34
|
buffer << "#{red(status)}: " if status
|
@@ -49,13 +42,7 @@ module Spoom
|
|
49
42
|
# Print `message` on `$stderr`
|
50
43
|
#
|
51
44
|
# The message is prefixed by a status (default: `Warning`).
|
52
|
-
|
53
|
-
params(
|
54
|
-
message: String,
|
55
|
-
status: T.nilable(String),
|
56
|
-
nl: T::Boolean,
|
57
|
-
).void
|
58
|
-
end
|
45
|
+
#: (String message, ?status: String?, ?nl: bool) -> void
|
59
46
|
def say_warning(message, status: "Warning", nl: true)
|
60
47
|
buffer = StringIO.new
|
61
48
|
buffer << "#{yellow(status)}: " if status
|
@@ -67,13 +54,13 @@ module Spoom
|
|
67
54
|
end
|
68
55
|
|
69
56
|
# Returns the context at `--path` (by default the current working directory)
|
70
|
-
|
57
|
+
#: -> Context
|
71
58
|
def context
|
72
|
-
@context ||=
|
59
|
+
@context ||= Context.new(exec_path) #: Context?
|
73
60
|
end
|
74
61
|
|
75
62
|
# Raise if `spoom` is not ran inside a context with a `sorbet/config` file
|
76
|
-
|
63
|
+
#: -> Context
|
77
64
|
def context_requiring_sorbet!
|
78
65
|
context = self.context
|
79
66
|
unless context.has_sorbet_config?
|
@@ -88,29 +75,52 @@ module Spoom
|
|
88
75
|
end
|
89
76
|
|
90
77
|
# Return the path specified through `--path`
|
91
|
-
|
78
|
+
#: -> String
|
92
79
|
def exec_path
|
93
80
|
options[:path]
|
94
81
|
end
|
95
82
|
|
83
|
+
# Collect files from `paths`, defaulting to `exec_path`
|
84
|
+
#: (Array[String] paths, ?include_rbi_files: bool) -> Array[String]
|
85
|
+
def collect_files(paths, include_rbi_files: false)
|
86
|
+
paths << exec_path if paths.empty?
|
87
|
+
|
88
|
+
files = paths.flat_map do |path|
|
89
|
+
if File.file?(path)
|
90
|
+
path
|
91
|
+
else
|
92
|
+
exts = ["rb"]
|
93
|
+
exts << "rbi" if include_rbi_files
|
94
|
+
Dir.glob("#{path}/**/*.{#{exts.join(",")}}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if files.empty?
|
99
|
+
say_error("No files found")
|
100
|
+
exit(1)
|
101
|
+
end
|
102
|
+
|
103
|
+
files
|
104
|
+
end
|
105
|
+
|
96
106
|
# Colors
|
97
107
|
|
98
108
|
# Color used to highlight expressions in backticks
|
99
|
-
HIGHLIGHT_COLOR =
|
109
|
+
HIGHLIGHT_COLOR = Spoom::Color::BLUE #: Spoom::Color
|
100
110
|
|
101
111
|
# Is the `--color` option true?
|
102
|
-
|
112
|
+
#: -> bool
|
103
113
|
def color?
|
104
114
|
options[:color]
|
105
115
|
end
|
106
116
|
|
107
|
-
|
117
|
+
#: (String string) -> String
|
108
118
|
def highlight(string)
|
109
119
|
return string unless color?
|
110
120
|
|
111
121
|
res = StringIO.new
|
112
122
|
word = StringIO.new
|
113
|
-
in_ticks =
|
123
|
+
in_ticks = false #: bool
|
114
124
|
string.chars.each do |c|
|
115
125
|
if c == "`" && !in_ticks
|
116
126
|
in_ticks = true
|
@@ -128,39 +138,39 @@ module Spoom
|
|
128
138
|
end
|
129
139
|
|
130
140
|
# Colorize a string if `color?`
|
131
|
-
|
141
|
+
#: (String string, *Color color) -> String
|
132
142
|
def colorize(string, *color)
|
133
143
|
return string unless color?
|
134
144
|
|
135
145
|
T.unsafe(self).set_color(string, *color)
|
136
146
|
end
|
137
147
|
|
138
|
-
|
148
|
+
#: (String string) -> String
|
139
149
|
def blue(string)
|
140
150
|
colorize(string, Color::BLUE)
|
141
151
|
end
|
142
152
|
|
143
|
-
|
153
|
+
#: (String string) -> String
|
144
154
|
def cyan(string)
|
145
155
|
colorize(string, Color::CYAN)
|
146
156
|
end
|
147
157
|
|
148
|
-
|
158
|
+
#: (String string) -> String
|
149
159
|
def gray(string)
|
150
160
|
colorize(string, Color::LIGHT_BLACK)
|
151
161
|
end
|
152
162
|
|
153
|
-
|
163
|
+
#: (String string) -> String
|
154
164
|
def green(string)
|
155
165
|
colorize(string, Color::GREEN)
|
156
166
|
end
|
157
167
|
|
158
|
-
|
168
|
+
#: (String string) -> String
|
159
169
|
def red(string)
|
160
170
|
colorize(string, Color::RED)
|
161
171
|
end
|
162
172
|
|
163
|
-
|
173
|
+
#: (String string) -> String
|
164
174
|
def yellow(string)
|
165
175
|
colorize(string, Color::YELLOW)
|
166
176
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Cli
|
6
|
+
module Srb
|
7
|
+
class Assertions < Thor
|
8
|
+
include Helper
|
9
|
+
|
10
|
+
desc "translate", "Translate type assertions from/to RBI and RBS"
|
11
|
+
option :from, type: :string, aliases: :f, desc: "From format", enum: ["rbi"], default: "rbi"
|
12
|
+
option :to, type: :string, aliases: :t, desc: "To format", enum: ["rbs"], default: "rbs"
|
13
|
+
def translate(*paths)
|
14
|
+
from = options[:from]
|
15
|
+
to = options[:to]
|
16
|
+
files = collect_files(paths)
|
17
|
+
|
18
|
+
say("Translating type assertions from `#{from}` to `#{to}` " \
|
19
|
+
"in `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n")
|
20
|
+
|
21
|
+
transformed_files = transform_files(files) do |file, contents|
|
22
|
+
Spoom::Sorbet::Translate.sorbet_assertions_to_rbs_comments(contents, file: file)
|
23
|
+
end
|
24
|
+
|
25
|
+
say("Translated type assertions in `#{transformed_files}` file#{transformed_files == 1 ? "" : "s"}.")
|
26
|
+
end
|
27
|
+
|
28
|
+
no_commands do
|
29
|
+
def transform_files(files, &block)
|
30
|
+
transformed_count = 0
|
31
|
+
|
32
|
+
files.each do |file|
|
33
|
+
contents = File.read(file)
|
34
|
+
contents = block.call(file, contents)
|
35
|
+
File.write(file, contents)
|
36
|
+
transformed_count += 1
|
37
|
+
rescue Spoom::ParseError => error
|
38
|
+
say_warning("Can't parse #{file}: #{error.message}")
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
transformed_count
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/spoom/cli/srb/bump.rb
CHANGED
@@ -8,7 +8,6 @@ module Spoom
|
|
8
8
|
module Cli
|
9
9
|
module Srb
|
10
10
|
class Bump < Thor
|
11
|
-
extend T::Sig
|
12
11
|
include Helper
|
13
12
|
|
14
13
|
default_task :bump
|
@@ -46,7 +45,7 @@ module Spoom
|
|
46
45
|
default: false,
|
47
46
|
desc: "Count the number of errors if all files were bumped"
|
48
47
|
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
49
|
-
|
48
|
+
#: (?String directory) -> void
|
50
49
|
def bump(directory = ".")
|
51
50
|
context = context_requiring_sorbet!
|
52
51
|
from = options[:from]
|
@@ -89,7 +89,7 @@ module Spoom
|
|
89
89
|
|
90
90
|
context.git_checkout!(ref: commit.sha)
|
91
91
|
|
92
|
-
snapshot =
|
92
|
+
snapshot = nil #: Spoom::Coverage::Snapshot?
|
93
93
|
if options[:bundle_install]
|
94
94
|
Bundler.with_unbundled_env do
|
95
95
|
next unless bundle_install(path, commit.sha)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Cli
|
6
|
+
module Srb
|
7
|
+
class Metrics < Thor
|
8
|
+
include Helper
|
9
|
+
|
10
|
+
default_task :show
|
11
|
+
|
12
|
+
desc "show", "Show metrics about Sorbet usage"
|
13
|
+
option :dump, type: :boolean, default: false
|
14
|
+
def show(*paths)
|
15
|
+
files = collect_files(paths)
|
16
|
+
metrics = Spoom::Sorbet::Metrics.collect_code_metrics(files)
|
17
|
+
|
18
|
+
if options[:dump]
|
19
|
+
metrics.sort_by { |key, _value| key }.each do |key, value|
|
20
|
+
puts "#{key} #{value}"
|
21
|
+
end
|
22
|
+
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
say("Files: `#{files.size}`")
|
27
|
+
|
28
|
+
["classes", "modules", "singleton_classes"].each do |key|
|
29
|
+
value = metrics[key]
|
30
|
+
next if value == 0
|
31
|
+
|
32
|
+
say("\n#{key.capitalize}: `#{value}`")
|
33
|
+
["with_srb_type_params", "with_rbs_type_params"].each do |subkey|
|
34
|
+
say(" * #{subkey.gsub("_", " ")}: `#{metrics["#{key}_#{subkey}"]}`")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
["methods", "accessors"].each do |key|
|
39
|
+
value = metrics[key]
|
40
|
+
next if value == 0
|
41
|
+
|
42
|
+
say("\n#{key.capitalize}: `#{value}`")
|
43
|
+
["without_sig", "with_srb_sig", "with_rbs_sig"].each do |subkey|
|
44
|
+
say(" * #{subkey.gsub("_", " ")}: `#{metrics["#{key}_#{subkey}"]}`")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
say("\nT. calls: `#{metrics["T_calls"]}`")
|
49
|
+
metrics
|
50
|
+
.select { |key, _value| key.start_with?("T.") }
|
51
|
+
.sort_by { |_key, value| -value }
|
52
|
+
.each do |key, value|
|
53
|
+
say(" * #{key}: `#{value}`")
|
54
|
+
end
|
55
|
+
|
56
|
+
say("\nRBS Assertions: `#{metrics["rbs_assertions"]}`")
|
57
|
+
metrics
|
58
|
+
.reject { |key, _value| key == "rbs_assertions" }
|
59
|
+
.select { |key, _value| key.start_with?("rbs_") }
|
60
|
+
.sort_by { |_key, value| -value }
|
61
|
+
.each do |key, value|
|
62
|
+
say(" * #{key}: `#{value}`")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Cli
|
6
|
+
module Srb
|
7
|
+
class Sigs < Thor
|
8
|
+
include Helper
|
9
|
+
|
10
|
+
desc "translate", "Translate signatures from/to RBI and RBS"
|
11
|
+
option :from, type: :string, aliases: :f, desc: "From format", enum: ["rbi", "rbs"], default: "rbi"
|
12
|
+
option :to, type: :string, aliases: :t, desc: "To format", enum: ["rbi", "rbs"], default: "rbs"
|
13
|
+
option :positional_names,
|
14
|
+
type: :boolean,
|
15
|
+
aliases: :p,
|
16
|
+
desc: "Use positional names when translating from RBI to RBS",
|
17
|
+
default: true
|
18
|
+
option :include_rbi_files, type: :boolean, desc: "Include RBI files", default: false
|
19
|
+
def translate(*paths)
|
20
|
+
from = options[:from]
|
21
|
+
to = options[:to]
|
22
|
+
|
23
|
+
if from == to
|
24
|
+
say_error("Can't translate signatures from `#{from}` to `#{to}`")
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
files = collect_files(paths, include_rbi_files: options[:include_rbi_files])
|
29
|
+
|
30
|
+
say("Translating signatures from `#{from}` to `#{to}` " \
|
31
|
+
"in `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n")
|
32
|
+
|
33
|
+
case from
|
34
|
+
when "rbi"
|
35
|
+
transformed_files = transform_files(files) do |file, contents|
|
36
|
+
Spoom::Sorbet::Translate.sorbet_sigs_to_rbs_comments(contents, file: file, positional_names: options[:positional_names])
|
37
|
+
end
|
38
|
+
when "rbs"
|
39
|
+
transformed_files = transform_files(files) do |file, contents|
|
40
|
+
Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs(contents, file: file)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
say("Translated signatures in `#{transformed_files}` file#{transformed_files == 1 ? "" : "s"}.")
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "strip", "Strip all the signatures from the files"
|
48
|
+
def strip(*paths)
|
49
|
+
files = collect_files(paths)
|
50
|
+
|
51
|
+
say("Stripping signatures from `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n")
|
52
|
+
|
53
|
+
transformed_files = transform_files(files) do |file, contents|
|
54
|
+
Spoom::Sorbet::Translate.strip_sorbet_sigs(contents, file: file)
|
55
|
+
end
|
56
|
+
|
57
|
+
say("Stripped signatures from `#{transformed_files}` file#{transformed_files == 1 ? "" : "s"}.")
|
58
|
+
end
|
59
|
+
|
60
|
+
# Extract signatures from gem's files and save them to the output file
|
61
|
+
#
|
62
|
+
# This command will use Tapioca to generate a `.rbi` file that contains the signatures of all the files listed
|
63
|
+
# in the gemspec.
|
64
|
+
desc "export", "Export gem files signatures"
|
65
|
+
option :gemspec, type: :string, desc: "Path to the gemspec file", optional: true, default: nil
|
66
|
+
option :check_sync, type: :boolean, desc: "Check the generated RBI is up to date", default: false
|
67
|
+
def export(output_path = nil)
|
68
|
+
gemspec = options[:gemspec]
|
69
|
+
|
70
|
+
unless gemspec
|
71
|
+
say("Locating gemspec file...")
|
72
|
+
gemspec = Dir.glob("*.gemspec").first
|
73
|
+
unless gemspec
|
74
|
+
say_error("No gemspec file found")
|
75
|
+
exit(1)
|
76
|
+
end
|
77
|
+
say("Using `#{gemspec}` as gemspec file")
|
78
|
+
end
|
79
|
+
|
80
|
+
spec = Gem::Specification.load(gemspec)
|
81
|
+
|
82
|
+
# First, we copy the files to a temporary directory so we can rewrite them without messing with the
|
83
|
+
# original ones.
|
84
|
+
say("Copying files to a temporary directory...")
|
85
|
+
copy_context = Spoom::Context.mktmp!
|
86
|
+
FileUtils.cp_r(
|
87
|
+
["Gemfile", "Gemfile.lock", gemspec, "lib/"],
|
88
|
+
copy_context.absolute_path,
|
89
|
+
)
|
90
|
+
|
91
|
+
# Then, we transform the copied files to translate all the RBS signatures into RBI signatures.
|
92
|
+
say("Translating signatures from RBS to RBI...")
|
93
|
+
files = collect_files([copy_context.absolute_path])
|
94
|
+
transform_files(files) do |file, contents|
|
95
|
+
Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs(contents, file: file)
|
96
|
+
end
|
97
|
+
|
98
|
+
# We need to inject `extend T::Sig` to be sure all the classes can run the `sig{}` blocks.
|
99
|
+
# For this we find the entry point of the gem and inject the `extend T::Sig` line at the top of the file.
|
100
|
+
entry_point = "lib/#{spec.name}.rb"
|
101
|
+
unless copy_context.file?(entry_point)
|
102
|
+
say_error("No entry point found at `#{entry_point}`")
|
103
|
+
exit(1)
|
104
|
+
end
|
105
|
+
|
106
|
+
say("Injecting `extend T::Sig` to `#{entry_point}`...")
|
107
|
+
copy_context.write!(entry_point, <<~RB)
|
108
|
+
require "sorbet-runtime"
|
109
|
+
|
110
|
+
class Module; include T::Sig; end
|
111
|
+
extend T::Sig
|
112
|
+
|
113
|
+
#{copy_context.read(entry_point)}
|
114
|
+
RB
|
115
|
+
|
116
|
+
# Now we create a new context to import our modified gem and run tapioca
|
117
|
+
say("Running Tapioca...")
|
118
|
+
tapioca_context = Spoom::Context.mktmp!
|
119
|
+
tapioca_context.write!("Gemfile", <<~RB)
|
120
|
+
source "https://rubygems.org"
|
121
|
+
|
122
|
+
gem "tapioca"
|
123
|
+
gem "#{spec.name}", path: "#{copy_context.absolute_path}"
|
124
|
+
RB
|
125
|
+
exec(tapioca_context, "bundle install")
|
126
|
+
exec(tapioca_context, "bundle exec tapioca gem #{spec.name} --no-doc --no-loc --no-file-header")
|
127
|
+
|
128
|
+
rbi_path = tapioca_context.glob("sorbet/rbi/gems/#{spec.name}@*.rbi").first
|
129
|
+
unless rbi_path && tapioca_context.file?(rbi_path)
|
130
|
+
say_error("No RBI file found at `sorbet/rbi/gems/#{spec.name}@*.rbi`")
|
131
|
+
exit(1)
|
132
|
+
end
|
133
|
+
|
134
|
+
tapioca_context.write!(rbi_path, tapioca_context.read(rbi_path).gsub(/^# typed: true/, <<~RB.rstrip))
|
135
|
+
# typed: true
|
136
|
+
|
137
|
+
# DO NOT EDIT MANUALLY
|
138
|
+
# This is an autogenerated file for types exported from the `#{spec.name}` gem.
|
139
|
+
# Please instead update this file by running `spoom srb sigs export`.
|
140
|
+
RB
|
141
|
+
|
142
|
+
output_path ||= "rbi/#{spec.name}.rbi"
|
143
|
+
generated_path = tapioca_context.absolute_path_to(rbi_path)
|
144
|
+
|
145
|
+
if options[:check_sync]
|
146
|
+
# If the check option is set, we just compare the generated RBI with the one in the gem.
|
147
|
+
# If they are different, we exit with a non-zero exit code.
|
148
|
+
unless system("diff -u -L 'generated' -L 'current' #{generated_path} #{output_path} >&2")
|
149
|
+
say_error(<<~ERR, status: "\nError")
|
150
|
+
The RBI file at `#{output_path}` is not up to date
|
151
|
+
|
152
|
+
Please run `spoom srb sigs export` to update it.
|
153
|
+
ERR
|
154
|
+
exit(1)
|
155
|
+
end
|
156
|
+
|
157
|
+
say("The RBI file at `#{output_path}` is up to date")
|
158
|
+
exit(0)
|
159
|
+
else
|
160
|
+
output_dir = File.dirname(output_path)
|
161
|
+
FileUtils.rm_rf(output_dir)
|
162
|
+
FileUtils.mkdir_p(output_dir)
|
163
|
+
FileUtils.cp(generated_path, output_path)
|
164
|
+
|
165
|
+
say("Exported signatures to `#{output_path}`")
|
166
|
+
end
|
167
|
+
ensure
|
168
|
+
copy_context&.destroy!
|
169
|
+
tapioca_context&.destroy!
|
170
|
+
end
|
171
|
+
|
172
|
+
no_commands do
|
173
|
+
def transform_files(files, &block)
|
174
|
+
transformed_count = 0
|
175
|
+
|
176
|
+
files.each do |file|
|
177
|
+
contents = File.read(file)
|
178
|
+
first_line = contents.lines.first
|
179
|
+
|
180
|
+
if first_line&.start_with?("# encoding:")
|
181
|
+
encoding = T.must(first_line).gsub(/^#\s*encoding:\s*/, "").strip
|
182
|
+
contents = contents.force_encoding(encoding)
|
183
|
+
end
|
184
|
+
|
185
|
+
contents = block.call(file, contents)
|
186
|
+
File.write(file, contents)
|
187
|
+
transformed_count += 1
|
188
|
+
rescue RBI::Error => error
|
189
|
+
say_warning("Can't parse #{file}: #{error.message}")
|
190
|
+
next
|
191
|
+
end
|
192
|
+
|
193
|
+
transformed_count
|
194
|
+
end
|
195
|
+
|
196
|
+
def exec(context, command)
|
197
|
+
res = context.exec(command)
|
198
|
+
|
199
|
+
unless res.status
|
200
|
+
$stderr.puts "Error: #{command} failed"
|
201
|
+
$stderr.puts res.err
|
202
|
+
exit(1)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
data/lib/spoom/cli/srb/tc.rb
CHANGED
@@ -22,6 +22,7 @@ module Spoom
|
|
22
22
|
option :format, type: :string, aliases: :f, desc: "Format line output"
|
23
23
|
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
24
24
|
option :count, type: :boolean, default: true, desc: "Show errors count"
|
25
|
+
option :junit_output_path, type: :string, desc: "Output failures to XML file formatted for JUnit"
|
25
26
|
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
26
27
|
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
27
28
|
def tc(*paths_to_select)
|
@@ -32,6 +33,7 @@ module Spoom
|
|
32
33
|
uniq = options[:uniq]
|
33
34
|
format = options[:format]
|
34
35
|
count = options[:count]
|
36
|
+
junit_output_path = options[:junit_output_path]
|
35
37
|
sorbet = options[:sorbet]
|
36
38
|
|
37
39
|
unless limit || code || sort
|
@@ -55,6 +57,12 @@ module Spoom
|
|
55
57
|
|
56
58
|
if result.status
|
57
59
|
say_error(result.err, status: nil, nl: false)
|
60
|
+
if junit_output_path
|
61
|
+
doc = Spoom::Sorbet::Errors.to_junit_xml([])
|
62
|
+
file = File.open(junit_output_path, "w")
|
63
|
+
doc.write(output: file, indent: 2)
|
64
|
+
file.close
|
65
|
+
end
|
58
66
|
exit(0)
|
59
67
|
end
|
60
68
|
|
@@ -94,6 +102,13 @@ module Spoom
|
|
94
102
|
say_error(line, status: nil)
|
95
103
|
end
|
96
104
|
|
105
|
+
if junit_output_path
|
106
|
+
doc = Spoom::Sorbet::Errors.to_junit_xml(errors)
|
107
|
+
file = File.open(junit_output_path, "w")
|
108
|
+
doc.write(output: file, indent: 2)
|
109
|
+
file.close
|
110
|
+
end
|
111
|
+
|
97
112
|
if count
|
98
113
|
if errors_count == errors.size
|
99
114
|
say_error("Errors: #{errors_count}", status: nil)
|
@@ -132,7 +147,7 @@ module Spoom
|
|
132
147
|
def colorize_message(message)
|
133
148
|
return message unless color?
|
134
149
|
|
135
|
-
cyan =
|
150
|
+
cyan = false #: bool
|
136
151
|
word = StringIO.new
|
137
152
|
message.chars.each do |c|
|
138
153
|
if c == "`"
|
data/lib/spoom/cli/srb.rb
CHANGED
@@ -1,23 +1,35 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require_relative "srb/assertions"
|
4
5
|
require_relative "srb/bump"
|
5
6
|
require_relative "srb/coverage"
|
6
7
|
require_relative "srb/lsp"
|
8
|
+
require_relative "srb/metrics"
|
9
|
+
require_relative "srb/sigs"
|
7
10
|
require_relative "srb/tc"
|
8
11
|
|
9
12
|
module Spoom
|
10
13
|
module Cli
|
11
14
|
module Srb
|
12
15
|
class Main < Thor
|
13
|
-
desc "
|
14
|
-
subcommand "
|
16
|
+
desc "assertions", "Translate type assertions from/to RBI and RBS"
|
17
|
+
subcommand "assertions", Spoom::Cli::Srb::Assertions
|
18
|
+
|
19
|
+
desc "bump", "Change Sorbet sigils from one strictness to another when no errors"
|
20
|
+
subcommand "bump", Spoom::Cli::Srb::Bump
|
15
21
|
|
16
22
|
desc "coverage", "Collect metrics related to Sorbet coverage"
|
17
23
|
subcommand "coverage", Spoom::Cli::Srb::Coverage
|
18
24
|
|
19
|
-
desc "
|
20
|
-
subcommand "
|
25
|
+
desc "lsp", "Send LSP requests to Sorbet"
|
26
|
+
subcommand "lsp", Spoom::Cli::Srb::LSP
|
27
|
+
|
28
|
+
desc "metrics", "Collect metrics about Sorbet usage"
|
29
|
+
subcommand "metrics", Spoom::Cli::Srb::Metrics
|
30
|
+
|
31
|
+
desc "sigs", "Translate signatures from/to RBI and RBS"
|
32
|
+
subcommand "sigs", Spoom::Cli::Srb::Sigs
|
21
33
|
|
22
34
|
desc "tc", "Run typechecking with advanced options"
|
23
35
|
subcommand "tc", Spoom::Cli::Srb::Tc
|