spoom 1.2.3 → 1.3.0
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 +64 -55
- data/lib/spoom/backtrace_filter/minitest.rb +21 -0
- data/lib/spoom/cli/deadcode.rb +172 -0
- data/lib/spoom/cli/helper.rb +20 -0
- data/lib/spoom/cli/srb/bump.rb +200 -0
- data/lib/spoom/cli/srb/coverage.rb +224 -0
- data/lib/spoom/cli/srb/lsp.rb +159 -0
- data/lib/spoom/cli/srb/tc.rb +150 -0
- data/lib/spoom/cli/srb.rb +27 -0
- data/lib/spoom/cli.rb +72 -32
- data/lib/spoom/context/git.rb +2 -2
- data/lib/spoom/context/sorbet.rb +2 -2
- data/lib/spoom/deadcode/definition.rb +11 -0
- data/lib/spoom/deadcode/erb.rb +4 -4
- data/lib/spoom/deadcode/indexer.rb +266 -200
- data/lib/spoom/deadcode/location.rb +30 -2
- data/lib/spoom/deadcode/plugins/action_mailer.rb +21 -0
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
- data/lib/spoom/deadcode/plugins/actionpack.rb +59 -0
- data/lib/spoom/deadcode/plugins/active_job.rb +13 -0
- data/lib/spoom/deadcode/plugins/active_model.rb +46 -0
- data/lib/spoom/deadcode/plugins/active_record.rb +108 -0
- data/lib/spoom/deadcode/plugins/active_support.rb +32 -0
- data/lib/spoom/deadcode/plugins/base.rb +165 -12
- data/lib/spoom/deadcode/plugins/graphql.rb +47 -0
- data/lib/spoom/deadcode/plugins/minitest.rb +28 -0
- data/lib/spoom/deadcode/plugins/namespaces.rb +32 -0
- data/lib/spoom/deadcode/plugins/rails.rb +31 -0
- data/lib/spoom/deadcode/plugins/rake.rb +12 -0
- data/lib/spoom/deadcode/plugins/rspec.rb +19 -0
- data/lib/spoom/deadcode/plugins/rubocop.rb +41 -0
- data/lib/spoom/deadcode/plugins/ruby.rb +10 -18
- data/lib/spoom/deadcode/plugins/sorbet.rb +40 -0
- data/lib/spoom/deadcode/plugins/thor.rb +21 -0
- data/lib/spoom/deadcode/plugins.rb +91 -0
- data/lib/spoom/deadcode/remover.rb +651 -0
- data/lib/spoom/deadcode/send.rb +27 -6
- data/lib/spoom/deadcode/visitor.rb +755 -0
- data/lib/spoom/deadcode.rb +41 -10
- data/lib/spoom/file_tree.rb +0 -16
- data/lib/spoom/sorbet/errors.rb +1 -1
- data/lib/spoom/sorbet/lsp/structures.rb +2 -2
- data/lib/spoom/version.rb +1 -1
- metadata +36 -15
- data/lib/spoom/cli/bump.rb +0 -198
- data/lib/spoom/cli/coverage.rb +0 -222
- data/lib/spoom/cli/lsp.rb +0 -168
- data/lib/spoom/cli/run.rb +0 -148
data/lib/spoom/cli/coverage.rb
DELETED
@@ -1,222 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require_relative "../coverage"
|
5
|
-
require_relative "../timeline"
|
6
|
-
|
7
|
-
module Spoom
|
8
|
-
module Cli
|
9
|
-
class Coverage < Thor
|
10
|
-
include Helper
|
11
|
-
|
12
|
-
DATA_DIR = "spoom_data"
|
13
|
-
|
14
|
-
default_task :snapshot
|
15
|
-
|
16
|
-
desc "snapshot", "Run srb tc and display metrics"
|
17
|
-
option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
|
18
|
-
option :rbi, type: :boolean, default: true, desc: "Include RBI files in metrics"
|
19
|
-
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
20
|
-
def snapshot
|
21
|
-
context = context_requiring_sorbet!
|
22
|
-
sorbet = options[:sorbet]
|
23
|
-
|
24
|
-
snapshot = Spoom::Coverage.snapshot(context, rbi: options[:rbi], sorbet_bin: sorbet)
|
25
|
-
snapshot.print
|
26
|
-
|
27
|
-
save_dir = options[:save]
|
28
|
-
return unless save_dir
|
29
|
-
|
30
|
-
FileUtils.mkdir_p(save_dir)
|
31
|
-
file = "#{save_dir}/#{snapshot.commit_sha || snapshot.timestamp}.json"
|
32
|
-
File.write(file, snapshot.to_json)
|
33
|
-
say("\nSnapshot data saved under `#{file}`")
|
34
|
-
end
|
35
|
-
|
36
|
-
desc "timeline", "Replay a project and collect metrics"
|
37
|
-
option :from, type: :string, desc: "From commit date"
|
38
|
-
option :to, type: :string, default: Time.now.strftime("%F"), desc: "To commit date"
|
39
|
-
option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
|
40
|
-
option :bundle_install, type: :boolean, desc: "Execute `bundle install` before collecting metrics"
|
41
|
-
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
42
|
-
def timeline
|
43
|
-
context = context_requiring_sorbet!
|
44
|
-
path = exec_path
|
45
|
-
sorbet = options[:sorbet]
|
46
|
-
|
47
|
-
ref_before = context.git_current_branch
|
48
|
-
ref_before = context.git_last_commit&.sha unless ref_before
|
49
|
-
unless ref_before
|
50
|
-
say_error("Not in a git repository")
|
51
|
-
say_error("\nSpoom needs to checkout into your previous commits to build the timeline.", status: nil)
|
52
|
-
exit(1)
|
53
|
-
end
|
54
|
-
|
55
|
-
unless context.git_workdir_clean?
|
56
|
-
say_error("Uncommited changes")
|
57
|
-
say_error(<<~ERR, status: nil)
|
58
|
-
|
59
|
-
Spoom needs to checkout into your previous commits to build the timeline."
|
60
|
-
|
61
|
-
Please `git commit` or `git stash` your changes then try again
|
62
|
-
ERR
|
63
|
-
exit(1)
|
64
|
-
end
|
65
|
-
|
66
|
-
save_dir = options[:save]
|
67
|
-
FileUtils.mkdir_p(save_dir) if save_dir
|
68
|
-
|
69
|
-
from = parse_time(options[:from], "--from")
|
70
|
-
to = parse_time(options[:to], "--to")
|
71
|
-
|
72
|
-
unless from
|
73
|
-
intro_commit = context.sorbet_intro_commit
|
74
|
-
intro_commit = T.must(intro_commit) # we know it's in there since in_sorbet_project!
|
75
|
-
from = intro_commit.time
|
76
|
-
end
|
77
|
-
|
78
|
-
timeline = Spoom::Timeline.new(context, from, to)
|
79
|
-
ticks = timeline.ticks
|
80
|
-
|
81
|
-
if ticks.empty?
|
82
|
-
say_error("No commits to replay, try different `--from` and `--to` options")
|
83
|
-
exit(1)
|
84
|
-
end
|
85
|
-
|
86
|
-
ticks.each_with_index do |commit, i|
|
87
|
-
say("Analyzing commit `#{commit.sha}` - #{commit.time.strftime("%F")} (#{i + 1} / #{ticks.size})")
|
88
|
-
|
89
|
-
context.git_checkout!(ref: commit.sha)
|
90
|
-
|
91
|
-
snapshot = T.let(nil, T.nilable(Spoom::Coverage::Snapshot))
|
92
|
-
if options[:bundle_install]
|
93
|
-
Bundler.with_unbundled_env do
|
94
|
-
next unless bundle_install(path, commit.sha)
|
95
|
-
|
96
|
-
snapshot = Spoom::Coverage.snapshot(context, sorbet_bin: sorbet)
|
97
|
-
end
|
98
|
-
else
|
99
|
-
snapshot = Spoom::Coverage.snapshot(context, sorbet_bin: sorbet)
|
100
|
-
end
|
101
|
-
next unless snapshot
|
102
|
-
|
103
|
-
snapshot.print(indent_level: 2)
|
104
|
-
say("\n")
|
105
|
-
|
106
|
-
next unless save_dir
|
107
|
-
|
108
|
-
file = "#{save_dir}/#{commit.sha}.json"
|
109
|
-
File.write(file, snapshot.to_json)
|
110
|
-
say(" Snapshot data saved under `#{file}`\n\n")
|
111
|
-
end
|
112
|
-
context.git_checkout!(ref: ref_before)
|
113
|
-
end
|
114
|
-
|
115
|
-
desc "report", "Produce a typing coverage report"
|
116
|
-
option :data, type: :string, default: DATA_DIR, desc: "Snapshots JSON data"
|
117
|
-
option :file,
|
118
|
-
type: :string,
|
119
|
-
default: "spoom_report.html",
|
120
|
-
aliases: :f,
|
121
|
-
desc: "Save report to file"
|
122
|
-
option :color_ignore,
|
123
|
-
type: :string,
|
124
|
-
default: Spoom::Coverage::D3::COLOR_IGNORE,
|
125
|
-
desc: "Color used for typed: ignore"
|
126
|
-
option :color_false,
|
127
|
-
type: :string,
|
128
|
-
default: Spoom::Coverage::D3::COLOR_FALSE,
|
129
|
-
desc: "Color used for typed: false"
|
130
|
-
option :color_true,
|
131
|
-
type: :string,
|
132
|
-
default: Spoom::Coverage::D3::COLOR_TRUE,
|
133
|
-
desc: "Color used for typed: true"
|
134
|
-
option :color_strict,
|
135
|
-
type: :string,
|
136
|
-
default: Spoom::Coverage::D3::COLOR_STRICT,
|
137
|
-
desc: "Color used for typed: strict"
|
138
|
-
option :color_strong,
|
139
|
-
type: :string,
|
140
|
-
default: Spoom::Coverage::D3::COLOR_STRONG,
|
141
|
-
desc: "Color used for typed: strong"
|
142
|
-
def report
|
143
|
-
context = context_requiring_sorbet!
|
144
|
-
|
145
|
-
data_dir = options[:data]
|
146
|
-
files = Dir.glob("#{data_dir}/*.json")
|
147
|
-
if files.empty?
|
148
|
-
message_no_data(data_dir)
|
149
|
-
exit(1)
|
150
|
-
end
|
151
|
-
|
152
|
-
snapshots = files.sort.map do |file|
|
153
|
-
json = File.read(file)
|
154
|
-
Spoom::Coverage::Snapshot.from_json(json)
|
155
|
-
end.filter(&:commit_timestamp).sort_by!(&:commit_timestamp)
|
156
|
-
|
157
|
-
palette = Spoom::Coverage::D3::ColorPalette.new(
|
158
|
-
ignore: options[:color_ignore],
|
159
|
-
false: options[:color_false],
|
160
|
-
true: options[:color_true],
|
161
|
-
strict: options[:color_strict],
|
162
|
-
strong: options[:color_strong],
|
163
|
-
)
|
164
|
-
|
165
|
-
report = Spoom::Coverage.report(context, snapshots, palette: palette)
|
166
|
-
file = options[:file]
|
167
|
-
File.write(file, report.html)
|
168
|
-
say("Report generated under `#{file}`")
|
169
|
-
say("\nUse `spoom coverage open` to open it.")
|
170
|
-
end
|
171
|
-
|
172
|
-
desc "open", "Open the typing coverage report"
|
173
|
-
def open(file = "spoom_report.html")
|
174
|
-
unless File.exist?(file)
|
175
|
-
say_error("No report file to open `#{file}`")
|
176
|
-
say_error(<<~ERR, status: nil)
|
177
|
-
|
178
|
-
If you already generated a report under another name use #{blue("spoom coverage open PATH")}.
|
179
|
-
|
180
|
-
To generate a report run #{blue("spoom coverage report")}.
|
181
|
-
ERR
|
182
|
-
exit(1)
|
183
|
-
end
|
184
|
-
|
185
|
-
exec("open #{file}")
|
186
|
-
end
|
187
|
-
|
188
|
-
no_commands do
|
189
|
-
def parse_time(string, option)
|
190
|
-
return unless string
|
191
|
-
|
192
|
-
Time.parse(string)
|
193
|
-
rescue ArgumentError
|
194
|
-
say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
|
195
|
-
exit(1)
|
196
|
-
end
|
197
|
-
|
198
|
-
def bundle_install(path, sha)
|
199
|
-
opts = {}
|
200
|
-
opts[:chdir] = path
|
201
|
-
out, status = Open3.capture2e("bundle install", opts)
|
202
|
-
unless status.success?
|
203
|
-
say_error("Can't run `bundle install` for commit `#{sha}`. Skipping snapshot")
|
204
|
-
say_error(out, status: nil)
|
205
|
-
return false
|
206
|
-
end
|
207
|
-
true
|
208
|
-
end
|
209
|
-
|
210
|
-
def message_no_data(file)
|
211
|
-
say_error("No snapshot files found in `#{file}`")
|
212
|
-
say_error(<<~ERR, status: nil)
|
213
|
-
|
214
|
-
If you already generated snapshot files under another directory use #{blue("spoom coverage report PATH")}.
|
215
|
-
|
216
|
-
To generate snapshot files run #{blue("spoom coverage timeline --save")}.
|
217
|
-
ERR
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
data/lib/spoom/cli/lsp.rb
DELETED
@@ -1,168 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "shellwords"
|
5
|
-
|
6
|
-
require_relative "../sorbet/lsp"
|
7
|
-
|
8
|
-
module Spoom
|
9
|
-
module Cli
|
10
|
-
class LSP < Thor
|
11
|
-
include Helper
|
12
|
-
|
13
|
-
default_task :show
|
14
|
-
|
15
|
-
desc "interactive", "Interactive LSP mode"
|
16
|
-
def show
|
17
|
-
context_requiring_sorbet!
|
18
|
-
|
19
|
-
lsp = lsp_client
|
20
|
-
# TODO: run interactive mode
|
21
|
-
puts lsp
|
22
|
-
end
|
23
|
-
|
24
|
-
desc "list", "List all known symbols"
|
25
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
26
|
-
def list
|
27
|
-
run do |client|
|
28
|
-
printer = symbol_printer
|
29
|
-
Dir["**/*.rb"].each do |file|
|
30
|
-
res = client.document_symbols(to_uri(file))
|
31
|
-
next if res.empty?
|
32
|
-
|
33
|
-
say("Symbols from `#{file}`:")
|
34
|
-
printer.print_objects(res)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
desc "hover", "Request hover informations"
|
40
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
41
|
-
def hover(file, line, col)
|
42
|
-
run do |client|
|
43
|
-
res = client.hover(to_uri(file), line.to_i, col.to_i)
|
44
|
-
say("Hovering `#{file}:#{line}:#{col}`:")
|
45
|
-
if res
|
46
|
-
symbol_printer.print_object(res)
|
47
|
-
else
|
48
|
-
say("<no data>")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
desc "defs", "List definitions of a symbol"
|
54
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
55
|
-
def defs(file, line, col)
|
56
|
-
run do |client|
|
57
|
-
res = client.definitions(to_uri(file), line.to_i, col.to_i)
|
58
|
-
say("Definitions for `#{file}:#{line}:#{col}`:")
|
59
|
-
symbol_printer.print_list(res)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
desc "find", "Find symbols matching a query"
|
64
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
65
|
-
def find(query)
|
66
|
-
run do |client|
|
67
|
-
res = client.symbols(query).reject { |symbol| symbol.location.uri.start_with?("https") }
|
68
|
-
say("Symbols matching `#{query}`:")
|
69
|
-
symbol_printer.print_objects(res)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
desc "symbols", "List symbols from a file"
|
74
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
75
|
-
def symbols(file)
|
76
|
-
run do |client|
|
77
|
-
res = client.document_symbols(to_uri(file))
|
78
|
-
say("Symbols from `#{file}`:")
|
79
|
-
symbol_printer.print_objects(res)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
desc "refs", "List references to a symbol"
|
84
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
85
|
-
def refs(file, line, col)
|
86
|
-
run do |client|
|
87
|
-
res = client.references(to_uri(file), line.to_i, col.to_i)
|
88
|
-
say("References to `#{file}:#{line}:#{col}`:")
|
89
|
-
symbol_printer.print_list(res)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
desc "sigs", "List signatures for a symbol"
|
94
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
95
|
-
def sigs(file, line, col)
|
96
|
-
run do |client|
|
97
|
-
res = client.signatures(to_uri(file), line.to_i, col.to_i)
|
98
|
-
say("Signature for `#{file}:#{line}:#{col}`:")
|
99
|
-
symbol_printer.print_list(res)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
desc "types", "Display type of a symbol"
|
104
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
105
|
-
def types(file, line, col)
|
106
|
-
run do |client|
|
107
|
-
res = client.type_definitions(to_uri(file), line.to_i, col.to_i)
|
108
|
-
say("Type for `#{file}:#{line}:#{col}`:")
|
109
|
-
symbol_printer.print_list(res)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
no_commands do
|
114
|
-
def lsp_client
|
115
|
-
context_requiring_sorbet!
|
116
|
-
|
117
|
-
path = exec_path
|
118
|
-
client = Spoom::LSP::Client.new(
|
119
|
-
Spoom::Sorbet::BIN_PATH,
|
120
|
-
"--lsp",
|
121
|
-
"--enable-all-experimental-lsp-features",
|
122
|
-
"--disable-watchman",
|
123
|
-
path: path,
|
124
|
-
)
|
125
|
-
client.open(File.expand_path(path))
|
126
|
-
client
|
127
|
-
end
|
128
|
-
|
129
|
-
def symbol_printer
|
130
|
-
Spoom::LSP::SymbolPrinter.new(
|
131
|
-
indent_level: 2,
|
132
|
-
colors: options[:color],
|
133
|
-
prefix: "file://#{File.expand_path(exec_path)}",
|
134
|
-
)
|
135
|
-
end
|
136
|
-
|
137
|
-
def run(&block)
|
138
|
-
client = lsp_client
|
139
|
-
block.call(client)
|
140
|
-
rescue Spoom::LSP::Error::Diagnostics => err
|
141
|
-
say_error("Sorbet returned typechecking errors for `#{symbol_printer.clean_uri(err.uri)}`")
|
142
|
-
err.diagnostics.each do |d|
|
143
|
-
say_error("#{d.message} (#{d.code})", status: " #{d.range}")
|
144
|
-
end
|
145
|
-
exit(1)
|
146
|
-
rescue Spoom::LSP::Error::BadHeaders => err
|
147
|
-
say_error("Sorbet didn't answer correctly (#{err.message})")
|
148
|
-
exit(1)
|
149
|
-
rescue Spoom::LSP::Error => err
|
150
|
-
say_error(err.message)
|
151
|
-
exit(1)
|
152
|
-
ensure
|
153
|
-
begin
|
154
|
-
client&.close
|
155
|
-
rescue
|
156
|
-
# We can't do much if Sorbet refuse to close.
|
157
|
-
# We kill the parent process and let the child be killed.
|
158
|
-
exit(1)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def to_uri(path)
|
163
|
-
"file://" + File.join(File.expand_path(exec_path), path)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
data/lib/spoom/cli/run.rb
DELETED
@@ -1,148 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Spoom
|
5
|
-
module Cli
|
6
|
-
class Run < Thor
|
7
|
-
include Helper
|
8
|
-
|
9
|
-
default_task :tc
|
10
|
-
|
11
|
-
SORT_CODE = "code"
|
12
|
-
SORT_LOC = "loc"
|
13
|
-
SORT_ENUM = [SORT_CODE, SORT_LOC]
|
14
|
-
|
15
|
-
DEFAULT_FORMAT = "%C - %F:%L: %M"
|
16
|
-
|
17
|
-
desc "tc", "Run `srb tc`"
|
18
|
-
option :limit, type: :numeric, aliases: :l, desc: "Limit displayed errors"
|
19
|
-
option :code, type: :numeric, aliases: :c, desc: "Filter displayed errors by code"
|
20
|
-
option :sort, type: :string, aliases: :s, desc: "Sort errors", enum: SORT_ENUM, default: SORT_LOC
|
21
|
-
option :format, type: :string, aliases: :f, desc: "Format line output"
|
22
|
-
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
23
|
-
option :count, type: :boolean, default: true, desc: "Show errors count"
|
24
|
-
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
25
|
-
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
26
|
-
def tc(*paths_to_select)
|
27
|
-
context = context_requiring_sorbet!
|
28
|
-
limit = options[:limit]
|
29
|
-
sort = options[:sort]
|
30
|
-
code = options[:code]
|
31
|
-
uniq = options[:uniq]
|
32
|
-
format = options[:format]
|
33
|
-
count = options[:count]
|
34
|
-
sorbet = options[:sorbet]
|
35
|
-
|
36
|
-
unless limit || code || sort
|
37
|
-
result = T.unsafe(context).srb_tc(
|
38
|
-
*options[:sorbet_options].split(" "),
|
39
|
-
capture_err: false,
|
40
|
-
sorbet_bin: sorbet,
|
41
|
-
)
|
42
|
-
|
43
|
-
say_error(result.err, status: nil, nl: false)
|
44
|
-
exit(result.status)
|
45
|
-
end
|
46
|
-
|
47
|
-
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
48
|
-
result = T.unsafe(context).srb_tc(
|
49
|
-
*options[:sorbet_options].split(" "),
|
50
|
-
"--error-url-base=#{error_url_base}",
|
51
|
-
capture_err: true,
|
52
|
-
sorbet_bin: sorbet,
|
53
|
-
)
|
54
|
-
|
55
|
-
if result.status
|
56
|
-
say_error(result.err, status: nil, nl: false)
|
57
|
-
exit(0)
|
58
|
-
end
|
59
|
-
|
60
|
-
unless result.exit_code == 100
|
61
|
-
# Sorbet will return exit code 100 if there are type checking errors.
|
62
|
-
# If Sorbet returned something else, it means it didn't terminate normally.
|
63
|
-
say_error(result.err, status: nil, nl: false)
|
64
|
-
exit(1)
|
65
|
-
end
|
66
|
-
|
67
|
-
errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
|
68
|
-
errors_count = errors.size
|
69
|
-
|
70
|
-
errors = errors.select { |e| e.code == code } if code
|
71
|
-
|
72
|
-
unless paths_to_select.empty?
|
73
|
-
errors.select! do |error|
|
74
|
-
paths_to_select.any? { |path_to_select| error.file&.start_with?(path_to_select) }
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
errors = case sort
|
79
|
-
when SORT_CODE
|
80
|
-
Spoom::Sorbet::Errors.sort_errors_by_code(errors)
|
81
|
-
when SORT_LOC
|
82
|
-
errors.sort
|
83
|
-
else
|
84
|
-
errors # preserve natural sort
|
85
|
-
end
|
86
|
-
|
87
|
-
errors = T.must(errors.slice(0, limit)) if limit
|
88
|
-
|
89
|
-
lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
|
90
|
-
lines = lines.uniq if uniq
|
91
|
-
|
92
|
-
lines.each do |line|
|
93
|
-
say_error(line, status: nil)
|
94
|
-
end
|
95
|
-
|
96
|
-
if count
|
97
|
-
if errors_count == errors.size
|
98
|
-
say_error("Errors: #{errors_count}", status: nil)
|
99
|
-
else
|
100
|
-
say_error("Errors: #{errors.size} shown, #{errors_count} total", status: nil)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
exit(1)
|
105
|
-
rescue Spoom::Sorbet::Error::Segfault => error
|
106
|
-
say_error(<<~ERR, status: nil)
|
107
|
-
#{red("!!! Sorbet exited with code #{error.result.exit_code} - SEGFAULT !!!")}
|
108
|
-
|
109
|
-
This is most likely related to a bug in Sorbet.
|
110
|
-
ERR
|
111
|
-
|
112
|
-
exit(error.result.exit_code)
|
113
|
-
rescue Spoom::Sorbet::Error::Killed => error
|
114
|
-
say_error(<<~ERR, status: nil)
|
115
|
-
#{red("!!! Sorbet exited with code #{error.result.exit_code} - KILLED !!!")}
|
116
|
-
ERR
|
117
|
-
|
118
|
-
exit(error.result.exit_code)
|
119
|
-
end
|
120
|
-
|
121
|
-
no_commands do
|
122
|
-
def format_error(error, format)
|
123
|
-
line = format
|
124
|
-
line = line.gsub(/%C/, yellow(error.code.to_s))
|
125
|
-
line = line.gsub(/%F/, error.file)
|
126
|
-
line = line.gsub(/%L/, error.line.to_s)
|
127
|
-
line = line.gsub(/%M/, colorize_message(error.message))
|
128
|
-
line
|
129
|
-
end
|
130
|
-
|
131
|
-
def colorize_message(message)
|
132
|
-
return message unless color?
|
133
|
-
|
134
|
-
cyan = T.let(false, T::Boolean)
|
135
|
-
word = StringIO.new
|
136
|
-
message.chars.each do |c|
|
137
|
-
if c == "`"
|
138
|
-
cyan = !cyan
|
139
|
-
next
|
140
|
-
end
|
141
|
-
word << (cyan ? cyan(c) : red(c))
|
142
|
-
end
|
143
|
-
word.string
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|