spoom 1.0.4 → 1.0.9
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/Gemfile +0 -1
- data/README.md +296 -1
- data/Rakefile +1 -0
- data/lib/spoom.rb +21 -2
- data/lib/spoom/cli.rb +56 -10
- data/lib/spoom/cli/bump.rb +138 -0
- data/lib/spoom/cli/config.rb +51 -0
- data/lib/spoom/cli/coverage.rb +206 -0
- data/lib/spoom/cli/helper.rb +149 -0
- data/lib/spoom/cli/lsp.rb +165 -0
- data/lib/spoom/cli/run.rb +109 -0
- data/lib/spoom/coverage.rb +89 -0
- data/lib/spoom/coverage/d3.rb +110 -0
- data/lib/spoom/coverage/d3/base.rb +50 -0
- data/lib/spoom/coverage/d3/circle_map.rb +195 -0
- data/lib/spoom/coverage/d3/pie.rb +175 -0
- data/lib/spoom/coverage/d3/timeline.rb +486 -0
- data/lib/spoom/coverage/report.rb +308 -0
- data/lib/spoom/coverage/snapshot.rb +132 -0
- data/lib/spoom/file_tree.rb +196 -0
- data/lib/spoom/git.rb +98 -0
- data/lib/spoom/printer.rb +80 -0
- data/lib/spoom/sorbet.rb +99 -47
- data/lib/spoom/sorbet/config.rb +30 -0
- data/lib/spoom/sorbet/errors.rb +33 -15
- data/lib/spoom/sorbet/lsp.rb +2 -4
- data/lib/spoom/sorbet/lsp/structures.rb +108 -14
- data/lib/spoom/sorbet/metrics.rb +10 -79
- data/lib/spoom/sorbet/sigils.rb +98 -0
- data/lib/spoom/test_helpers/project.rb +112 -0
- data/lib/spoom/timeline.rb +53 -0
- data/lib/spoom/version.rb +2 -2
- data/templates/card.erb +8 -0
- data/templates/card_snapshot.erb +22 -0
- data/templates/page.erb +50 -0
- metadata +28 -11
- data/lib/spoom/cli/commands/base.rb +0 -36
- data/lib/spoom/cli/commands/config.rb +0 -67
- data/lib/spoom/cli/commands/lsp.rb +0 -156
- data/lib/spoom/cli/commands/run.rb +0 -92
- data/lib/spoom/cli/symbol_printer.rb +0 -71
- data/lib/spoom/config.rb +0 -11
@@ -0,0 +1,138 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'find'
|
5
|
+
require 'open3'
|
6
|
+
|
7
|
+
module Spoom
|
8
|
+
module Cli
|
9
|
+
class Bump < Thor
|
10
|
+
extend T::Sig
|
11
|
+
include Helper
|
12
|
+
|
13
|
+
default_task :bump
|
14
|
+
|
15
|
+
desc "bump DIRECTORY", "Change Sorbet sigils from one strictness to another when no errors"
|
16
|
+
option :from, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE,
|
17
|
+
desc: "Change only files from this strictness"
|
18
|
+
option :to, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_TRUE,
|
19
|
+
desc: "Change files to this strictness"
|
20
|
+
option :force, type: :boolean, default: false, aliases: :f,
|
21
|
+
desc: "Change strictness without type checking"
|
22
|
+
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
23
|
+
option :dry, type: :boolean, default: false, aliases: :d,
|
24
|
+
desc: "Only display what would happen, do not actually change sigils"
|
25
|
+
option :only, type: :string, default: nil, aliases: :o,
|
26
|
+
desc: "Only change specified list (one file by line)"
|
27
|
+
option :suggest_bump_command, type: :string,
|
28
|
+
desc: "Command to suggest if files can be bumped"
|
29
|
+
sig { params(directory: String).void }
|
30
|
+
def bump(directory = ".")
|
31
|
+
in_sorbet_project!
|
32
|
+
|
33
|
+
from = options[:from]
|
34
|
+
to = options[:to]
|
35
|
+
force = options[:force]
|
36
|
+
dry = options[:dry]
|
37
|
+
only = options[:only]
|
38
|
+
cmd = options[:suggest_bump_command]
|
39
|
+
exec_path = File.expand_path(self.exec_path)
|
40
|
+
|
41
|
+
unless Sorbet::Sigils.valid_strictness?(from)
|
42
|
+
say_error("Invalid strictness `#{from}` for option `--from`")
|
43
|
+
exit(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
unless Sorbet::Sigils.valid_strictness?(to)
|
47
|
+
say_error("Invalid strictness `#{to}` for option `--to`")
|
48
|
+
exit(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
say("Checking files...")
|
52
|
+
|
53
|
+
directory = File.expand_path(directory)
|
54
|
+
files_to_bump = Sorbet::Sigils.files_with_sigil_strictness(directory, from)
|
55
|
+
|
56
|
+
files_from_config = config_files(path: exec_path)
|
57
|
+
files_to_bump.select! { |file| files_from_config.include?(file) }
|
58
|
+
|
59
|
+
if only
|
60
|
+
list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
|
61
|
+
files_to_bump.select! { |file| list.include?(File.expand_path(file)) }
|
62
|
+
end
|
63
|
+
|
64
|
+
say("\n")
|
65
|
+
|
66
|
+
if files_to_bump.empty?
|
67
|
+
say("No file to bump from `#{from}` to `#{to}`")
|
68
|
+
exit(0)
|
69
|
+
end
|
70
|
+
|
71
|
+
Sorbet::Sigils.change_sigil_in_files(files_to_bump, to)
|
72
|
+
|
73
|
+
if force
|
74
|
+
print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
75
|
+
undo_changes(files_to_bump, from) if dry
|
76
|
+
exit(files_to_bump.empty?)
|
77
|
+
end
|
78
|
+
|
79
|
+
output, no_errors = Sorbet.srb_tc(path: exec_path, capture_err: true, sorbet_bin: options[:sorbet])
|
80
|
+
|
81
|
+
if no_errors
|
82
|
+
print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
83
|
+
undo_changes(files_to_bump, from) if dry
|
84
|
+
exit(files_to_bump.empty?)
|
85
|
+
end
|
86
|
+
|
87
|
+
errors = Sorbet::Errors::Parser.parse_string(output)
|
88
|
+
|
89
|
+
files_with_errors = errors.map do |err|
|
90
|
+
path = File.expand_path(err.file)
|
91
|
+
next unless path.start_with?(directory)
|
92
|
+
next unless File.file?(path)
|
93
|
+
path
|
94
|
+
end.compact.uniq
|
95
|
+
|
96
|
+
undo_changes(files_with_errors, from)
|
97
|
+
|
98
|
+
files_changed = files_to_bump - files_with_errors
|
99
|
+
print_changes(files_changed, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
100
|
+
undo_changes(files_to_bump, from) if dry
|
101
|
+
exit(files_changed.empty?)
|
102
|
+
end
|
103
|
+
|
104
|
+
no_commands do
|
105
|
+
def print_changes(files, command:, from: "false", to: "true", dry: false, path: File.expand_path("."))
|
106
|
+
if files.empty?
|
107
|
+
say("No file to bump from `#{from}` to `#{to}`")
|
108
|
+
return
|
109
|
+
end
|
110
|
+
message = StringIO.new
|
111
|
+
message << (dry ? "Can bump" : "Bumped")
|
112
|
+
message << " `#{files.size}` file#{'s' if files.size > 1}"
|
113
|
+
message << " from `#{from}` to `#{to}`:"
|
114
|
+
say(message.string)
|
115
|
+
files.each do |file|
|
116
|
+
file_path = Pathname.new(file).relative_path_from(path)
|
117
|
+
say(" + #{file_path}")
|
118
|
+
end
|
119
|
+
if dry && command
|
120
|
+
say("\nRun `#{command}` to bump them")
|
121
|
+
elsif dry
|
122
|
+
say("\nRun `spoom bump --from #{from} --to #{to}` to bump them")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def undo_changes(files, from_strictness)
|
127
|
+
Sorbet::Sigils.change_sigil_in_files(files, from_strictness)
|
128
|
+
end
|
129
|
+
|
130
|
+
def config_files(path: ".")
|
131
|
+
config = sorbet_config
|
132
|
+
files = Sorbet.srb_files(config, path: path)
|
133
|
+
files.map { |file| File.expand_path(file) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../file_tree"
|
5
|
+
require_relative "../sorbet/config"
|
6
|
+
|
7
|
+
module Spoom
|
8
|
+
module Cli
|
9
|
+
class Config < Thor
|
10
|
+
include Helper
|
11
|
+
|
12
|
+
default_task :show
|
13
|
+
|
14
|
+
desc "show", "Show Sorbet config"
|
15
|
+
def show
|
16
|
+
in_sorbet_project!
|
17
|
+
config = sorbet_config
|
18
|
+
|
19
|
+
say("Found Sorbet config at `#{sorbet_config_file}`.")
|
20
|
+
|
21
|
+
say("\nPaths typechecked:")
|
22
|
+
if config.paths.empty?
|
23
|
+
say(" * (default: .)")
|
24
|
+
else
|
25
|
+
config.paths.each do |path|
|
26
|
+
say(" * #{path}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
say("\nPaths ignored:")
|
31
|
+
if config.ignore.empty?
|
32
|
+
say(" * (default: none)")
|
33
|
+
else
|
34
|
+
config.ignore.each do |path|
|
35
|
+
say(" * #{path}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
say("\nAllowed extensions:")
|
40
|
+
if config.allowed_extensions.empty?
|
41
|
+
say(" * .rb (default)")
|
42
|
+
say(" * .rbi (default)")
|
43
|
+
else
|
44
|
+
config.allowed_extensions.each do |ext|
|
45
|
+
say(" * #{ext}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,206 @@
|
|
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: "Exclude RBI files from metrics"
|
19
|
+
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
20
|
+
def snapshot
|
21
|
+
in_sorbet_project!
|
22
|
+
path = exec_path
|
23
|
+
sorbet = options[:sorbet]
|
24
|
+
|
25
|
+
snapshot = Spoom::Coverage.snapshot(path: path, rbi: options[:rbi], sorbet_bin: sorbet)
|
26
|
+
snapshot.print
|
27
|
+
|
28
|
+
save_dir = options[:save]
|
29
|
+
return unless save_dir
|
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
|
+
in_sorbet_project!
|
44
|
+
path = exec_path
|
45
|
+
sorbet = options[:sorbet]
|
46
|
+
|
47
|
+
sha_before = Spoom::Git.last_commit(path: path)
|
48
|
+
unless sha_before
|
49
|
+
say_error("Not in a git repository")
|
50
|
+
say_error("\nSpoom needs to checkout into your previous commits to build the timeline.", status: nil)
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
|
54
|
+
unless Spoom::Git.workdir_clean?(path: path)
|
55
|
+
say_error("Uncommited changes")
|
56
|
+
say_error(<<~ERR, status: nil)
|
57
|
+
|
58
|
+
Spoom needs to checkout into your previous commits to build the timeline."
|
59
|
+
|
60
|
+
Please `git commit` or `git stash` your changes then try again
|
61
|
+
ERR
|
62
|
+
exit(1)
|
63
|
+
end
|
64
|
+
|
65
|
+
save_dir = options[:save]
|
66
|
+
FileUtils.mkdir_p(save_dir) if save_dir
|
67
|
+
|
68
|
+
from = parse_time(options[:from], "--from")
|
69
|
+
to = parse_time(options[:to], "--to")
|
70
|
+
|
71
|
+
unless from
|
72
|
+
intro_sha = Spoom::Git.sorbet_intro_commit(path: path)
|
73
|
+
intro_sha = T.must(intro_sha) # we know it's in there since in_sorbet_project!
|
74
|
+
from = Spoom::Git.commit_time(intro_sha, path: path)
|
75
|
+
end
|
76
|
+
|
77
|
+
timeline = Spoom::Timeline.new(from, to, path: path)
|
78
|
+
ticks = timeline.ticks
|
79
|
+
|
80
|
+
if ticks.empty?
|
81
|
+
say_error("No commits to replay, try different `--from` and `--to` options")
|
82
|
+
exit(1)
|
83
|
+
end
|
84
|
+
|
85
|
+
ticks.each_with_index do |sha, i|
|
86
|
+
date = Spoom::Git.commit_time(sha, path: path)
|
87
|
+
say("Analyzing commit `#{sha}` - #{date&.strftime('%F')} (#{i + 1} / #{ticks.size})")
|
88
|
+
|
89
|
+
Spoom::Git.checkout(sha, path: path)
|
90
|
+
|
91
|
+
snapshot = T.let(nil, T.nilable(Spoom::Coverage::Snapshot))
|
92
|
+
if options[:bundle_install]
|
93
|
+
Bundler.with_clean_env do
|
94
|
+
next unless bundle_install(path, sha)
|
95
|
+
snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
|
96
|
+
end
|
97
|
+
else
|
98
|
+
snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
|
99
|
+
end
|
100
|
+
next unless snapshot
|
101
|
+
|
102
|
+
snapshot.print(indent_level: 2)
|
103
|
+
say("\n")
|
104
|
+
|
105
|
+
next unless save_dir
|
106
|
+
file = "#{save_dir}/#{sha}.json"
|
107
|
+
File.write(file, snapshot.to_json)
|
108
|
+
say(" Snapshot data saved under `#{file}`\n\n")
|
109
|
+
end
|
110
|
+
Spoom::Git.checkout(sha_before, path: path)
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "report", "Produce a typing coverage report"
|
114
|
+
option :data, type: :string, default: DATA_DIR, desc: "Snapshots JSON data"
|
115
|
+
option :file, type: :string, default: "spoom_report.html", aliases: :f,
|
116
|
+
desc: "Save report to file"
|
117
|
+
option :color_ignore, type: :string, default: Spoom::Coverage::D3::COLOR_IGNORE,
|
118
|
+
desc: "Color used for typed: ignore"
|
119
|
+
option :color_false, type: :string, default: Spoom::Coverage::D3::COLOR_FALSE,
|
120
|
+
desc: "Color used for typed: false"
|
121
|
+
option :color_true, type: :string, default: Spoom::Coverage::D3::COLOR_TRUE,
|
122
|
+
desc: "Color used for typed: true"
|
123
|
+
option :color_strict, type: :string, default: Spoom::Coverage::D3::COLOR_STRICT,
|
124
|
+
desc: "Color used for typed: strict"
|
125
|
+
option :color_strong, type: :string, default: Spoom::Coverage::D3::COLOR_STRONG,
|
126
|
+
desc: "Color used for typed: strong"
|
127
|
+
def report
|
128
|
+
in_sorbet_project!
|
129
|
+
|
130
|
+
data_dir = options[:data]
|
131
|
+
files = Dir.glob("#{data_dir}/*.json")
|
132
|
+
if files.empty?
|
133
|
+
message_no_data(data_dir)
|
134
|
+
exit(1)
|
135
|
+
end
|
136
|
+
|
137
|
+
snapshots = files.sort.map do |file|
|
138
|
+
json = File.read(file)
|
139
|
+
Spoom::Coverage::Snapshot.from_json(json)
|
140
|
+
end.filter(&:commit_timestamp).sort_by!(&:commit_timestamp)
|
141
|
+
|
142
|
+
palette = Spoom::Coverage::D3::ColorPalette.new(
|
143
|
+
ignore: options[:color_ignore],
|
144
|
+
false: options[:color_false],
|
145
|
+
true: options[:color_true],
|
146
|
+
strict: options[:color_strict],
|
147
|
+
strong: options[:color_strong]
|
148
|
+
)
|
149
|
+
|
150
|
+
report = Spoom::Coverage.report(snapshots, palette: palette, path: exec_path)
|
151
|
+
file = options[:file]
|
152
|
+
File.write(file, report.html)
|
153
|
+
say("Report generated under `#{file}`")
|
154
|
+
say("\nUse `spoom coverage open` to open it.")
|
155
|
+
end
|
156
|
+
|
157
|
+
desc "open", "Open the typing coverage report"
|
158
|
+
def open(file = "spoom_report.html")
|
159
|
+
unless File.exist?(file)
|
160
|
+
say_error("No report file to open `#{file}`")
|
161
|
+
say_error(<<~ERR, status: nil)
|
162
|
+
|
163
|
+
If you already generated a report under another name use #{blue('spoom coverage open PATH')}.
|
164
|
+
|
165
|
+
To generate a report run #{blue('spoom coverage report')}.
|
166
|
+
ERR
|
167
|
+
exit(1)
|
168
|
+
end
|
169
|
+
|
170
|
+
exec("open #{file}")
|
171
|
+
end
|
172
|
+
|
173
|
+
no_commands do
|
174
|
+
def parse_time(string, option)
|
175
|
+
return nil unless string
|
176
|
+
Time.parse(string)
|
177
|
+
rescue ArgumentError
|
178
|
+
say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
|
179
|
+
exit(1)
|
180
|
+
end
|
181
|
+
|
182
|
+
def bundle_install(path, sha)
|
183
|
+
opts = {}
|
184
|
+
opts[:chdir] = path
|
185
|
+
out, status = Open3.capture2e("bundle install", opts)
|
186
|
+
unless status.success?
|
187
|
+
say_error("Can't run `bundle install` for commit `#{sha}`. Skipping snapshot")
|
188
|
+
say_error(out, status: nil)
|
189
|
+
return false
|
190
|
+
end
|
191
|
+
true
|
192
|
+
end
|
193
|
+
|
194
|
+
def message_no_data(file)
|
195
|
+
say_error("No snapshot files found in `#{file}`")
|
196
|
+
say_error(<<~ERR, status: nil)
|
197
|
+
|
198
|
+
If you already generated snapshot files under another directory use #{blue('spoom coverage report PATH')}.
|
199
|
+
|
200
|
+
To generate snapshot files run #{blue('spoom coverage timeline --save-dir spoom_data')}.
|
201
|
+
ERR
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "fileutils"
|
5
|
+
require "pathname"
|
6
|
+
require "stringio"
|
7
|
+
|
8
|
+
module Spoom
|
9
|
+
module Cli
|
10
|
+
module Helper
|
11
|
+
extend T::Sig
|
12
|
+
include Thor::Shell
|
13
|
+
|
14
|
+
# Print `message` on `$stdout`
|
15
|
+
sig { params(message: String).void }
|
16
|
+
def say(message)
|
17
|
+
buffer = StringIO.new
|
18
|
+
buffer << highlight(message)
|
19
|
+
buffer << "\n" unless message.end_with?("\n")
|
20
|
+
|
21
|
+
$stdout.print(buffer.string)
|
22
|
+
$stdout.flush
|
23
|
+
end
|
24
|
+
|
25
|
+
# Print `message` on `$stderr`
|
26
|
+
#
|
27
|
+
# The message is prefixed by a status (default: `Error`).
|
28
|
+
sig do
|
29
|
+
params(
|
30
|
+
message: String,
|
31
|
+
status: T.nilable(String),
|
32
|
+
nl: T::Boolean
|
33
|
+
).void
|
34
|
+
end
|
35
|
+
def say_error(message, status: "Error", nl: true)
|
36
|
+
buffer = StringIO.new
|
37
|
+
buffer << "#{red(status)}: " if status
|
38
|
+
buffer << highlight(message)
|
39
|
+
buffer << "\n" if nl && !message.end_with?("\n")
|
40
|
+
|
41
|
+
$stderr.print(buffer.string)
|
42
|
+
$stderr.flush
|
43
|
+
end
|
44
|
+
|
45
|
+
# Is `spoom` ran inside a project with a `sorbet/config` file?
|
46
|
+
sig { returns(T::Boolean) }
|
47
|
+
def in_sorbet_project?
|
48
|
+
File.file?(sorbet_config_file)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Enforce that `spoom` is ran inside a project with a `sorbet/config` file
|
52
|
+
#
|
53
|
+
# Display an error message and exit otherwise.
|
54
|
+
sig { void }
|
55
|
+
def in_sorbet_project!
|
56
|
+
unless in_sorbet_project?
|
57
|
+
say_error(
|
58
|
+
"not in a Sorbet project (`#{sorbet_config_file}` not found)\n\n" \
|
59
|
+
"When running spoom from another path than the project's root, " \
|
60
|
+
"use `--path PATH` to specify the path to the root."
|
61
|
+
)
|
62
|
+
Kernel.exit(1)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return the path specified through `--path`
|
67
|
+
sig { returns(String) }
|
68
|
+
def exec_path
|
69
|
+
T.unsafe(self).options[:path] # TODO: requires_ancestor
|
70
|
+
end
|
71
|
+
|
72
|
+
sig { returns(String) }
|
73
|
+
def sorbet_config_file
|
74
|
+
Pathname.new("#{exec_path}/#{Spoom::Sorbet::CONFIG_PATH}").cleanpath.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { returns(Sorbet::Config) }
|
78
|
+
def sorbet_config
|
79
|
+
Sorbet::Config.parse_file(sorbet_config_file)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Colors
|
83
|
+
|
84
|
+
# Color used to highlight expressions in backticks
|
85
|
+
HIGHLIGHT_COLOR = :blue
|
86
|
+
|
87
|
+
# Is the `--color` option true?
|
88
|
+
sig { returns(T::Boolean) }
|
89
|
+
def color?
|
90
|
+
T.unsafe(self).options[:color] # TODO: requires_ancestor
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { params(string: String).returns(String) }
|
94
|
+
def highlight(string)
|
95
|
+
return string unless color?
|
96
|
+
|
97
|
+
res = StringIO.new
|
98
|
+
word = StringIO.new
|
99
|
+
in_ticks = T.let(false, T::Boolean)
|
100
|
+
string.chars.each do |c|
|
101
|
+
if c == '`' && !in_ticks
|
102
|
+
in_ticks = true
|
103
|
+
elsif c == '`' && in_ticks
|
104
|
+
in_ticks = false
|
105
|
+
res << colorize(word.string, HIGHLIGHT_COLOR)
|
106
|
+
word = StringIO.new
|
107
|
+
elsif in_ticks
|
108
|
+
word << c
|
109
|
+
else
|
110
|
+
res << c
|
111
|
+
end
|
112
|
+
end
|
113
|
+
res.string
|
114
|
+
end
|
115
|
+
|
116
|
+
# Colorize a string if `color?`
|
117
|
+
sig { params(string: String, color: Symbol).returns(String) }
|
118
|
+
def colorize(string, color)
|
119
|
+
return string unless color?
|
120
|
+
string.colorize(color)
|
121
|
+
end
|
122
|
+
|
123
|
+
sig { params(string: String).returns(String) }
|
124
|
+
def blue(string)
|
125
|
+
colorize(string, :blue)
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { params(string: String).returns(String) }
|
129
|
+
def gray(string)
|
130
|
+
colorize(string, :light_black)
|
131
|
+
end
|
132
|
+
|
133
|
+
sig { params(string: String).returns(String) }
|
134
|
+
def green(string)
|
135
|
+
colorize(string, :green)
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { params(string: String).returns(String) }
|
139
|
+
def red(string)
|
140
|
+
colorize(string, :red)
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { params(string: String).returns(String) }
|
144
|
+
def yellow(string)
|
145
|
+
colorize(string, :yellow)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|