spoom 1.0.8 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/README.md +6 -0
- data/lib/spoom.rb +1 -0
- data/lib/spoom/cli.rb +15 -5
- data/lib/spoom/cli/bump.rb +49 -15
- data/lib/spoom/cli/config.rb +2 -2
- data/lib/spoom/cli/coverage.rb +33 -28
- data/lib/spoom/cli/helper.rb +89 -12
- data/lib/spoom/cli/lsp.rb +10 -10
- data/lib/spoom/cli/run.rb +7 -7
- data/lib/spoom/coverage.rb +14 -1
- data/lib/spoom/coverage/d3/timeline.rb +146 -9
- data/lib/spoom/coverage/report.rb +10 -0
- data/lib/spoom/coverage/snapshot.rb +3 -1
- data/lib/spoom/git.rb +22 -2
- data/lib/spoom/printer.rb +0 -1
- data/lib/spoom/sorbet.rb +1 -0
- data/lib/spoom/sorbet/config.rb +13 -0
- data/lib/spoom/sorbet/sigils.rb +1 -1
- data/lib/spoom/test_helpers/project.rb +19 -0
- data/lib/spoom/version.rb +1 -1
- metadata +11 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9b05f4a851903017a55999c9ba093a08e690af7294145dbe6e4401e1cb919f1
|
4
|
+
data.tar.gz: 24db18c1f5f061a275576c5a863ec1c190da06eb1f76c1b420266c5c85818641
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85f76b4b4ece46d92484dee9063ca001114a5231f6371cdac09d403ce8e23120ccd4bbd8c9e51e6ed8de43b4a0f9db0f0453c21e2bf20ab71cc25108745201e1
|
7
|
+
data.tar.gz: fb41d83d4dee426c81fb2d2d0786ea0ebe21c42c3fda3ed907b522f6ee0f9745cc860ba86f01e97a224c334ae6d837e97af94f45bfd4cb4e1eba45c5640dfde2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -226,6 +226,12 @@ Bump files using a custom instance of Sorbet:
|
|
226
226
|
$ spoom bump --from false --to true --sorbet /path/to/sorbet/bin
|
227
227
|
```
|
228
228
|
|
229
|
+
Count the number of type-checking errors if all files were bumped to true:
|
230
|
+
|
231
|
+
```
|
232
|
+
$ spoom bump --count-errors --dry
|
233
|
+
```
|
234
|
+
|
229
235
|
#### Interact with Sorbet LSP mode
|
230
236
|
|
231
237
|
**Experimental**
|
data/lib/spoom.rb
CHANGED
data/lib/spoom/cli.rb
CHANGED
@@ -38,19 +38,29 @@ module Spoom
|
|
38
38
|
subcommand "tc", Spoom::Cli::Run
|
39
39
|
|
40
40
|
desc "files", "List all the files typechecked by Sorbet"
|
41
|
+
option :tree, type: :boolean, default: true, desc: "Display list as an indented tree"
|
42
|
+
option :rbi, type: :boolean, default: true, desc: "Show RBI files"
|
41
43
|
def files
|
42
44
|
in_sorbet_project!
|
43
45
|
|
44
46
|
path = exec_path
|
45
|
-
config =
|
47
|
+
config = sorbet_config
|
46
48
|
files = Spoom::Sorbet.srb_files(config, path: path)
|
47
49
|
|
48
|
-
|
50
|
+
unless options[:rbi]
|
51
|
+
files = files.reject { |file| file.end_with?(".rbi") }
|
52
|
+
end
|
53
|
+
|
49
54
|
if files.empty?
|
50
|
-
|
51
|
-
|
55
|
+
say_error("No file matching `#{sorbet_config_file}`")
|
56
|
+
exit(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
if options[:tree]
|
52
60
|
tree = FileTree.new(files, strip_prefix: path)
|
53
|
-
tree.print(colors: options[:color], indent_level:
|
61
|
+
tree.print(colors: options[:color], indent_level: 0)
|
62
|
+
else
|
63
|
+
puts files
|
54
64
|
end
|
55
65
|
end
|
56
66
|
|
data/lib/spoom/cli/bump.rb
CHANGED
@@ -24,6 +24,10 @@ module Spoom
|
|
24
24
|
desc: "Only display what would happen, do not actually change sigils"
|
25
25
|
option :only, type: :string, default: nil, aliases: :o,
|
26
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
|
+
option :count_errors, type: :boolean, default: false,
|
30
|
+
desc: "Count the number of errors if all files were bumped"
|
27
31
|
sig { params(directory: String).void }
|
28
32
|
def bump(directory = ".")
|
29
33
|
in_sorbet_project!
|
@@ -33,43 +37,61 @@ module Spoom
|
|
33
37
|
force = options[:force]
|
34
38
|
dry = options[:dry]
|
35
39
|
only = options[:only]
|
40
|
+
cmd = options[:suggest_bump_command]
|
36
41
|
exec_path = File.expand_path(self.exec_path)
|
37
42
|
|
38
43
|
unless Sorbet::Sigils.valid_strictness?(from)
|
39
|
-
say_error("Invalid strictness
|
44
|
+
say_error("Invalid strictness `#{from}` for option `--from`")
|
40
45
|
exit(1)
|
41
46
|
end
|
42
47
|
|
43
48
|
unless Sorbet::Sigils.valid_strictness?(to)
|
44
|
-
say_error("Invalid strictness
|
49
|
+
say_error("Invalid strictness `#{to}` for option `--to`")
|
45
50
|
exit(1)
|
46
51
|
end
|
47
52
|
|
53
|
+
if options[:count_errors] && !dry
|
54
|
+
say_error("`--count-errors` can only be used with `--dry`")
|
55
|
+
exit(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
say("Checking files...")
|
59
|
+
|
48
60
|
directory = File.expand_path(directory)
|
49
61
|
files_to_bump = Sorbet::Sigils.files_with_sigil_strictness(directory, from)
|
50
62
|
|
63
|
+
files_from_config = config_files(path: exec_path)
|
64
|
+
files_to_bump.select! { |file| files_from_config.include?(file) }
|
65
|
+
|
51
66
|
if only
|
52
67
|
list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
|
53
68
|
files_to_bump.select! { |file| list.include?(File.expand_path(file)) }
|
54
69
|
end
|
55
70
|
|
71
|
+
say("\n")
|
72
|
+
|
56
73
|
if files_to_bump.empty?
|
57
|
-
|
74
|
+
say("No file to bump from `#{from}` to `#{to}`")
|
58
75
|
exit(0)
|
59
76
|
end
|
60
77
|
|
61
78
|
Sorbet::Sigils.change_sigil_in_files(files_to_bump, to)
|
62
79
|
|
63
80
|
if force
|
64
|
-
print_changes(files_to_bump, from: from, to: to, dry: dry, path: exec_path)
|
81
|
+
print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
65
82
|
undo_changes(files_to_bump, from) if dry
|
66
83
|
exit(files_to_bump.empty?)
|
67
84
|
end
|
68
85
|
|
69
|
-
output, no_errors = Sorbet.srb_tc(
|
86
|
+
output, no_errors = Sorbet.srb_tc(
|
87
|
+
"--no-error-sections",
|
88
|
+
path: exec_path,
|
89
|
+
capture_err: true,
|
90
|
+
sorbet_bin: options[:sorbet]
|
91
|
+
)
|
70
92
|
|
71
93
|
if no_errors
|
72
|
-
print_changes(files_to_bump, from: from, to: to, dry: dry, path: exec_path)
|
94
|
+
print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
73
95
|
undo_changes(files_to_bump, from) if dry
|
74
96
|
exit(files_to_bump.empty?)
|
75
97
|
end
|
@@ -85,33 +107,45 @@ module Spoom
|
|
85
107
|
|
86
108
|
undo_changes(files_with_errors, from)
|
87
109
|
|
110
|
+
say("Found #{errors.length} type checking error#{'s' if errors.length > 1}") if options[:count_errors]
|
111
|
+
|
88
112
|
files_changed = files_to_bump - files_with_errors
|
89
|
-
print_changes(files_changed, from: from, to: to, dry: dry, path: exec_path)
|
113
|
+
print_changes(files_changed, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
90
114
|
undo_changes(files_to_bump, from) if dry
|
91
115
|
exit(files_changed.empty?)
|
92
116
|
end
|
93
117
|
|
94
118
|
no_commands do
|
95
|
-
def print_changes(files, from: "false", to: "true", dry: false, path: File.expand_path("."))
|
119
|
+
def print_changes(files, command:, from: "false", to: "true", dry: false, path: File.expand_path("."))
|
96
120
|
if files.empty?
|
97
|
-
|
121
|
+
say("No file to bump from `#{from}` to `#{to}`")
|
98
122
|
return
|
99
123
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
124
|
+
message = StringIO.new
|
125
|
+
message << (dry ? "Can bump" : "Bumped")
|
126
|
+
message << " `#{files.size}` file#{'s' if files.size > 1}"
|
127
|
+
message << " from `#{from}` to `#{to}`:"
|
128
|
+
say(message.string)
|
103
129
|
files.each do |file|
|
104
130
|
file_path = Pathname.new(file).relative_path_from(path)
|
105
|
-
|
131
|
+
say(" + #{file_path}")
|
106
132
|
end
|
107
|
-
if dry
|
108
|
-
|
133
|
+
if dry && command
|
134
|
+
say("\nRun `#{command}` to bump them")
|
135
|
+
elsif dry
|
136
|
+
say("\nRun `spoom bump --from #{from} --to #{to}` locally then `commit the changes` and `push them`")
|
109
137
|
end
|
110
138
|
end
|
111
139
|
|
112
140
|
def undo_changes(files, from_strictness)
|
113
141
|
Sorbet::Sigils.change_sigil_in_files(files, from_strictness)
|
114
142
|
end
|
143
|
+
|
144
|
+
def config_files(path: ".")
|
145
|
+
config = sorbet_config
|
146
|
+
files = Sorbet.srb_files(config, path: path)
|
147
|
+
files.map { |file| File.expand_path(file) }
|
148
|
+
end
|
115
149
|
end
|
116
150
|
end
|
117
151
|
end
|
data/lib/spoom/cli/config.rb
CHANGED
@@ -14,9 +14,9 @@ module Spoom
|
|
14
14
|
desc "show", "Show Sorbet config"
|
15
15
|
def show
|
16
16
|
in_sorbet_project!
|
17
|
-
config =
|
17
|
+
config = sorbet_config
|
18
18
|
|
19
|
-
say("Found Sorbet config at `#{
|
19
|
+
say("Found Sorbet config at `#{sorbet_config_file}`.")
|
20
20
|
|
21
21
|
say("\nPaths typechecked:")
|
22
22
|
if config.paths.empty?
|
data/lib/spoom/cli/coverage.rb
CHANGED
@@ -15,7 +15,7 @@ module Spoom
|
|
15
15
|
|
16
16
|
desc "snapshot", "Run srb tc and display metrics"
|
17
17
|
option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
|
18
|
-
option :rbi, type: :boolean, default: true, desc: "
|
18
|
+
option :rbi, type: :boolean, default: true, desc: "Include RBI files in metrics"
|
19
19
|
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
20
20
|
def snapshot
|
21
21
|
in_sorbet_project!
|
@@ -30,7 +30,7 @@ module Spoom
|
|
30
30
|
FileUtils.mkdir_p(save_dir)
|
31
31
|
file = "#{save_dir}/#{snapshot.commit_sha || snapshot.timestamp}.json"
|
32
32
|
File.write(file, snapshot.to_json)
|
33
|
-
|
33
|
+
say("\nSnapshot data saved under `#{file}`")
|
34
34
|
end
|
35
35
|
|
36
36
|
desc "timeline", "Replay a project and collect metrics"
|
@@ -44,17 +44,22 @@ module Spoom
|
|
44
44
|
path = exec_path
|
45
45
|
sorbet = options[:sorbet]
|
46
46
|
|
47
|
-
|
48
|
-
unless
|
47
|
+
ref_before = Spoom::Git.current_branch
|
48
|
+
ref_before = Spoom::Git.last_commit(path: path) unless ref_before
|
49
|
+
unless ref_before
|
49
50
|
say_error("Not in a git repository")
|
50
|
-
|
51
|
+
say_error("\nSpoom needs to checkout into your previous commits to build the timeline.", status: nil)
|
51
52
|
exit(1)
|
52
53
|
end
|
53
54
|
|
54
55
|
unless Spoom::Git.workdir_clean?(path: path)
|
55
56
|
say_error("Uncommited changes")
|
56
|
-
|
57
|
-
|
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
|
58
63
|
exit(1)
|
59
64
|
end
|
60
65
|
|
@@ -74,19 +79,19 @@ module Spoom
|
|
74
79
|
ticks = timeline.ticks
|
75
80
|
|
76
81
|
if ticks.empty?
|
77
|
-
say_error("No commits to replay, try different
|
82
|
+
say_error("No commits to replay, try different `--from` and `--to` options")
|
78
83
|
exit(1)
|
79
84
|
end
|
80
85
|
|
81
86
|
ticks.each_with_index do |sha, i|
|
82
87
|
date = Spoom::Git.commit_time(sha, path: path)
|
83
|
-
|
88
|
+
say("Analyzing commit `#{sha}` - #{date&.strftime('%F')} (#{i + 1} / #{ticks.size})")
|
84
89
|
|
85
90
|
Spoom::Git.checkout(sha, path: path)
|
86
91
|
|
87
92
|
snapshot = T.let(nil, T.nilable(Spoom::Coverage::Snapshot))
|
88
93
|
if options[:bundle_install]
|
89
|
-
Bundler.
|
94
|
+
Bundler.with_unbundled_env do
|
90
95
|
next unless bundle_install(path, sha)
|
91
96
|
snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
|
92
97
|
end
|
@@ -96,14 +101,14 @@ module Spoom
|
|
96
101
|
next unless snapshot
|
97
102
|
|
98
103
|
snapshot.print(indent_level: 2)
|
99
|
-
|
104
|
+
say("\n")
|
100
105
|
|
101
106
|
next unless save_dir
|
102
107
|
file = "#{save_dir}/#{sha}.json"
|
103
108
|
File.write(file, snapshot.to_json)
|
104
|
-
|
109
|
+
say(" Snapshot data saved under `#{file}`\n\n")
|
105
110
|
end
|
106
|
-
Spoom::Git.checkout(
|
111
|
+
Spoom::Git.checkout(ref_before, path: path)
|
107
112
|
end
|
108
113
|
|
109
114
|
desc "report", "Produce a typing coverage report"
|
@@ -146,20 +151,20 @@ module Spoom
|
|
146
151
|
report = Spoom::Coverage.report(snapshots, palette: palette, path: exec_path)
|
147
152
|
file = options[:file]
|
148
153
|
File.write(file, report.html)
|
149
|
-
|
150
|
-
|
154
|
+
say("Report generated under `#{file}`")
|
155
|
+
say("\nUse `spoom coverage open` to open it.")
|
151
156
|
end
|
152
157
|
|
153
158
|
desc "open", "Open the typing coverage report"
|
154
159
|
def open(file = "spoom_report.html")
|
155
160
|
unless File.exist?(file)
|
156
|
-
say_error("No report file to open
|
157
|
-
|
161
|
+
say_error("No report file to open `#{file}`")
|
162
|
+
say_error(<<~ERR, status: nil)
|
158
163
|
|
159
|
-
If you already generated a report under another name use #{
|
164
|
+
If you already generated a report under another name use #{blue('spoom coverage open PATH')}.
|
160
165
|
|
161
|
-
To generate a report run #{
|
162
|
-
|
166
|
+
To generate a report run #{blue('spoom coverage report')}.
|
167
|
+
ERR
|
163
168
|
exit(1)
|
164
169
|
end
|
165
170
|
|
@@ -171,7 +176,7 @@ module Spoom
|
|
171
176
|
return nil unless string
|
172
177
|
Time.parse(string)
|
173
178
|
rescue ArgumentError
|
174
|
-
say_error("Invalid date `#{string}` for option
|
179
|
+
say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
|
175
180
|
exit(1)
|
176
181
|
end
|
177
182
|
|
@@ -180,21 +185,21 @@ module Spoom
|
|
180
185
|
opts[:chdir] = path
|
181
186
|
out, status = Open3.capture2e("bundle install", opts)
|
182
187
|
unless status.success?
|
183
|
-
say_error("Can't run `bundle install` for commit
|
184
|
-
|
188
|
+
say_error("Can't run `bundle install` for commit `#{sha}`. Skipping snapshot")
|
189
|
+
say_error(out, status: nil)
|
185
190
|
return false
|
186
191
|
end
|
187
192
|
true
|
188
193
|
end
|
189
194
|
|
190
195
|
def message_no_data(file)
|
191
|
-
say_error("No snapshot files found in
|
192
|
-
|
196
|
+
say_error("No snapshot files found in `#{file}`")
|
197
|
+
say_error(<<~ERR, status: nil)
|
193
198
|
|
194
|
-
If you already generated snapshot files under another directory use #{
|
199
|
+
If you already generated snapshot files under another directory use #{blue('spoom coverage report PATH')}.
|
195
200
|
|
196
|
-
To generate snapshot files run #{
|
197
|
-
|
201
|
+
To generate snapshot files run #{blue('spoom coverage timeline --save-dir spoom_data')}.
|
202
|
+
ERR
|
198
203
|
end
|
199
204
|
end
|
200
205
|
end
|
data/lib/spoom/cli/helper.rb
CHANGED
@@ -9,18 +9,37 @@ module Spoom
|
|
9
9
|
module Cli
|
10
10
|
module Helper
|
11
11
|
extend T::Sig
|
12
|
+
extend T::Helpers
|
12
13
|
include Thor::Shell
|
13
14
|
|
15
|
+
requires_ancestor Thor
|
16
|
+
|
17
|
+
# Print `message` on `$stdout`
|
18
|
+
sig { params(message: String).void }
|
19
|
+
def say(message)
|
20
|
+
buffer = StringIO.new
|
21
|
+
buffer << highlight(message)
|
22
|
+
buffer << "\n" unless message.end_with?("\n")
|
23
|
+
|
24
|
+
$stdout.print(buffer.string)
|
25
|
+
$stdout.flush
|
26
|
+
end
|
27
|
+
|
14
28
|
# Print `message` on `$stderr`
|
15
29
|
#
|
16
30
|
# The message is prefixed by a status (default: `Error`).
|
17
|
-
sig
|
18
|
-
|
19
|
-
|
20
|
-
|
31
|
+
sig do
|
32
|
+
params(
|
33
|
+
message: String,
|
34
|
+
status: T.nilable(String),
|
35
|
+
nl: T::Boolean
|
36
|
+
).void
|
37
|
+
end
|
38
|
+
def say_error(message, status: "Error", nl: true)
|
21
39
|
buffer = StringIO.new
|
22
|
-
buffer << "#{status}:
|
23
|
-
buffer <<
|
40
|
+
buffer << "#{red(status)}: " if status
|
41
|
+
buffer << highlight(message)
|
42
|
+
buffer << "\n" if nl && !message.end_with?("\n")
|
24
43
|
|
25
44
|
$stderr.print(buffer.string)
|
26
45
|
$stderr.flush
|
@@ -29,7 +48,7 @@ module Spoom
|
|
29
48
|
# Is `spoom` ran inside a project with a `sorbet/config` file?
|
30
49
|
sig { returns(T::Boolean) }
|
31
50
|
def in_sorbet_project?
|
32
|
-
File.file?(
|
51
|
+
File.file?(sorbet_config_file)
|
33
52
|
end
|
34
53
|
|
35
54
|
# Enforce that `spoom` is ran inside a project with a `sorbet/config` file
|
@@ -39,9 +58,9 @@ module Spoom
|
|
39
58
|
def in_sorbet_project!
|
40
59
|
unless in_sorbet_project?
|
41
60
|
say_error(
|
42
|
-
"not in a Sorbet project (
|
61
|
+
"not in a Sorbet project (`#{sorbet_config_file}` not found)\n\n" \
|
43
62
|
"When running spoom from another path than the project's root, " \
|
44
|
-
"use
|
63
|
+
"use `--path PATH` to specify the path to the root."
|
45
64
|
)
|
46
65
|
Kernel.exit(1)
|
47
66
|
end
|
@@ -50,18 +69,51 @@ module Spoom
|
|
50
69
|
# Return the path specified through `--path`
|
51
70
|
sig { returns(String) }
|
52
71
|
def exec_path
|
53
|
-
|
72
|
+
options[:path]
|
54
73
|
end
|
55
74
|
|
56
75
|
sig { returns(String) }
|
57
|
-
def
|
76
|
+
def sorbet_config_file
|
58
77
|
Pathname.new("#{exec_path}/#{Spoom::Sorbet::CONFIG_PATH}").cleanpath.to_s
|
59
78
|
end
|
60
79
|
|
80
|
+
sig { returns(Sorbet::Config) }
|
81
|
+
def sorbet_config
|
82
|
+
Sorbet::Config.parse_file(sorbet_config_file)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Colors
|
86
|
+
|
87
|
+
# Color used to highlight expressions in backticks
|
88
|
+
HIGHLIGHT_COLOR = :blue
|
89
|
+
|
61
90
|
# Is the `--color` option true?
|
62
91
|
sig { returns(T::Boolean) }
|
63
92
|
def color?
|
64
|
-
|
93
|
+
options[:color]
|
94
|
+
end
|
95
|
+
|
96
|
+
sig { params(string: String).returns(String) }
|
97
|
+
def highlight(string)
|
98
|
+
return string unless color?
|
99
|
+
|
100
|
+
res = StringIO.new
|
101
|
+
word = StringIO.new
|
102
|
+
in_ticks = T.let(false, T::Boolean)
|
103
|
+
string.chars.each do |c|
|
104
|
+
if c == '`' && !in_ticks
|
105
|
+
in_ticks = true
|
106
|
+
elsif c == '`' && in_ticks
|
107
|
+
in_ticks = false
|
108
|
+
res << colorize(word.string, HIGHLIGHT_COLOR)
|
109
|
+
word = StringIO.new
|
110
|
+
elsif in_ticks
|
111
|
+
word << c
|
112
|
+
else
|
113
|
+
res << c
|
114
|
+
end
|
115
|
+
end
|
116
|
+
res.string
|
65
117
|
end
|
66
118
|
|
67
119
|
# Colorize a string if `color?`
|
@@ -70,6 +122,31 @@ module Spoom
|
|
70
122
|
return string unless color?
|
71
123
|
string.colorize(color)
|
72
124
|
end
|
125
|
+
|
126
|
+
sig { params(string: String).returns(String) }
|
127
|
+
def blue(string)
|
128
|
+
colorize(string, :blue)
|
129
|
+
end
|
130
|
+
|
131
|
+
sig { params(string: String).returns(String) }
|
132
|
+
def gray(string)
|
133
|
+
colorize(string, :light_black)
|
134
|
+
end
|
135
|
+
|
136
|
+
sig { params(string: String).returns(String) }
|
137
|
+
def green(string)
|
138
|
+
colorize(string, :green)
|
139
|
+
end
|
140
|
+
|
141
|
+
sig { params(string: String).returns(String) }
|
142
|
+
def red(string)
|
143
|
+
colorize(string, :red)
|
144
|
+
end
|
145
|
+
|
146
|
+
sig { params(string: String).returns(String) }
|
147
|
+
def yellow(string)
|
148
|
+
colorize(string, :yellow)
|
149
|
+
end
|
73
150
|
end
|
74
151
|
end
|
75
152
|
end
|
data/lib/spoom/cli/lsp.rb
CHANGED
@@ -28,7 +28,7 @@ module Spoom
|
|
28
28
|
Dir["**/*.rb"].each do |file|
|
29
29
|
res = client.document_symbols(to_uri(file))
|
30
30
|
next if res.empty?
|
31
|
-
|
31
|
+
say("Symbols from `#{file}`:")
|
32
32
|
printer.print_objects(res)
|
33
33
|
end
|
34
34
|
end
|
@@ -39,11 +39,11 @@ module Spoom
|
|
39
39
|
def hover(file, line, col)
|
40
40
|
run do |client|
|
41
41
|
res = client.hover(to_uri(file), line.to_i, col.to_i)
|
42
|
-
say
|
42
|
+
say("Hovering `#{file}:#{line}:#{col}`:")
|
43
43
|
if res
|
44
44
|
symbol_printer.print_object(res)
|
45
45
|
else
|
46
|
-
|
46
|
+
say("<no data>")
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
@@ -53,7 +53,7 @@ module Spoom
|
|
53
53
|
def defs(file, line, col)
|
54
54
|
run do |client|
|
55
55
|
res = client.definitions(to_uri(file), line.to_i, col.to_i)
|
56
|
-
|
56
|
+
say("Definitions for `#{file}:#{line}:#{col}`:")
|
57
57
|
symbol_printer.print_list(res)
|
58
58
|
end
|
59
59
|
end
|
@@ -63,7 +63,7 @@ module Spoom
|
|
63
63
|
def find(query)
|
64
64
|
run do |client|
|
65
65
|
res = client.symbols(query).reject { |symbol| symbol.location.uri.start_with?("https") }
|
66
|
-
|
66
|
+
say("Symbols matching `#{query}`:")
|
67
67
|
symbol_printer.print_objects(res)
|
68
68
|
end
|
69
69
|
end
|
@@ -73,7 +73,7 @@ module Spoom
|
|
73
73
|
def symbols(file)
|
74
74
|
run do |client|
|
75
75
|
res = client.document_symbols(to_uri(file))
|
76
|
-
|
76
|
+
say("Symbols from `#{file}`:")
|
77
77
|
symbol_printer.print_objects(res)
|
78
78
|
end
|
79
79
|
end
|
@@ -83,7 +83,7 @@ module Spoom
|
|
83
83
|
def refs(file, line, col)
|
84
84
|
run do |client|
|
85
85
|
res = client.references(to_uri(file), line.to_i, col.to_i)
|
86
|
-
|
86
|
+
say("References to `#{file}:#{line}:#{col}`:")
|
87
87
|
symbol_printer.print_list(res)
|
88
88
|
end
|
89
89
|
end
|
@@ -93,7 +93,7 @@ module Spoom
|
|
93
93
|
def sigs(file, line, col)
|
94
94
|
run do |client|
|
95
95
|
res = client.signatures(to_uri(file), line.to_i, col.to_i)
|
96
|
-
|
96
|
+
say("Signature for `#{file}:#{line}:#{col}`:")
|
97
97
|
symbol_printer.print_list(res)
|
98
98
|
end
|
99
99
|
end
|
@@ -103,7 +103,7 @@ module Spoom
|
|
103
103
|
def types(file, line, col)
|
104
104
|
run do |client|
|
105
105
|
res = client.type_definitions(to_uri(file), line.to_i, col.to_i)
|
106
|
-
say
|
106
|
+
say("Type for `#{file}:#{line}:#{col}`:")
|
107
107
|
symbol_printer.print_list(res)
|
108
108
|
end
|
109
109
|
end
|
@@ -137,7 +137,7 @@ module Spoom
|
|
137
137
|
rescue Spoom::LSP::Error::Diagnostics => err
|
138
138
|
say_error("Sorbet returned typechecking errors for `#{symbol_printer.clean_uri(err.uri)}`")
|
139
139
|
err.diagnostics.each do |d|
|
140
|
-
say_error("#{d.message} (#{d.code})", " #{d.range}")
|
140
|
+
say_error("#{d.message} (#{d.code})", status: " #{d.range}")
|
141
141
|
end
|
142
142
|
exit(1)
|
143
143
|
rescue Spoom::LSP::Error::BadHeaders => err
|
data/lib/spoom/cli/run.rb
CHANGED
@@ -17,7 +17,7 @@ module Spoom
|
|
17
17
|
desc "tc", "Run `srb tc`"
|
18
18
|
option :limit, type: :numeric, aliases: :l, desc: "Limit displayed errors"
|
19
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,
|
20
|
+
option :sort, type: :string, aliases: :s, desc: "Sort errors", enum: SORT_ENUM, default: SORT_LOC
|
21
21
|
option :format, type: :string, aliases: :f, desc: "Format line output"
|
22
22
|
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
23
23
|
option :count, type: :boolean, default: true, desc: "Show errors count"
|
@@ -36,13 +36,13 @@ module Spoom
|
|
36
36
|
|
37
37
|
unless limit || code || sort
|
38
38
|
output, status = T.unsafe(Spoom::Sorbet).srb_tc(*arg, path: path, capture_err: false, sorbet_bin: sorbet)
|
39
|
-
|
39
|
+
say_error(output, status: nil, nl: false)
|
40
40
|
exit(status)
|
41
41
|
end
|
42
42
|
|
43
43
|
output, status = T.unsafe(Spoom::Sorbet).srb_tc(*arg, path: path, capture_err: true, sorbet_bin: sorbet)
|
44
44
|
if status
|
45
|
-
|
45
|
+
say_error(output, status: nil, nl: false)
|
46
46
|
exit(0)
|
47
47
|
end
|
48
48
|
|
@@ -65,14 +65,14 @@ module Spoom
|
|
65
65
|
lines = lines.uniq if uniq
|
66
66
|
|
67
67
|
lines.each do |line|
|
68
|
-
|
68
|
+
say_error(line, status: nil)
|
69
69
|
end
|
70
70
|
|
71
71
|
if count
|
72
72
|
if errors_count == errors.size
|
73
|
-
|
73
|
+
say_error("Errors: #{errors_count}", status: nil)
|
74
74
|
else
|
75
|
-
|
75
|
+
say_error("Errors: #{errors.size} shown, #{errors_count} total", status: nil)
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
@@ -82,7 +82,7 @@ module Spoom
|
|
82
82
|
no_commands do
|
83
83
|
def format_error(error, format)
|
84
84
|
line = format
|
85
|
-
line = line.gsub(/%C/,
|
85
|
+
line = line.gsub(/%C/, yellow(error.code.to_s))
|
86
86
|
line = line.gsub(/%F/, error.file)
|
87
87
|
line = line.gsub(/%L/, error.line.to_s)
|
88
88
|
line = line.gsub(/%M/, colorize_message(error.message))
|
data/lib/spoom/coverage.rb
CHANGED
@@ -21,6 +21,9 @@ module Spoom
|
|
21
21
|
|
22
22
|
metrics = Spoom::Sorbet.srb_metrics(
|
23
23
|
"--no-config",
|
24
|
+
"--no-error-sections",
|
25
|
+
"--no-error-count",
|
26
|
+
"--isolate-error-code=0",
|
24
27
|
new_config.options_string,
|
25
28
|
path: path,
|
26
29
|
capture_err: true,
|
@@ -54,6 +57,9 @@ module Spoom
|
|
54
57
|
snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
|
55
58
|
snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
|
56
59
|
|
60
|
+
files = Spoom::Sorbet.srb_files(new_config, path: path)
|
61
|
+
snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
|
62
|
+
|
57
63
|
snapshot
|
58
64
|
end
|
59
65
|
|
@@ -81,8 +87,15 @@ module Spoom
|
|
81
87
|
def self.sigils_tree(path: ".")
|
82
88
|
config = sorbet_config(path: path)
|
83
89
|
files = Sorbet.srb_files(config, path: path)
|
84
|
-
|
90
|
+
|
91
|
+
extensions = config.allowed_extensions
|
92
|
+
extensions = [".rb"] if extensions.empty?
|
93
|
+
extensions -= [".rbi"]
|
94
|
+
|
95
|
+
pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
|
96
|
+
files.select! { |file| file =~ pattern }
|
85
97
|
files.reject! { |file| file =~ %r{/test/} }
|
98
|
+
|
86
99
|
FileTree.new(files, strip_prefix: path)
|
87
100
|
end
|
88
101
|
end
|
@@ -36,10 +36,28 @@ module Spoom
|
|
36
36
|
pointer-events: none;
|
37
37
|
}
|
38
38
|
|
39
|
+
.area {
|
40
|
+
fill-opacity: 0.5;
|
41
|
+
}
|
42
|
+
|
43
|
+
.line {
|
44
|
+
stroke-width: 2;
|
45
|
+
fill: transparent;
|
46
|
+
}
|
47
|
+
|
48
|
+
.dot {
|
49
|
+
r: 2;
|
50
|
+
fill: #888;
|
51
|
+
}
|
52
|
+
|
39
53
|
.inverted .grid line {
|
40
54
|
stroke: #777;
|
41
55
|
}
|
42
56
|
|
57
|
+
.inverted .area {
|
58
|
+
fill-opacity: 0.9;
|
59
|
+
}
|
60
|
+
|
43
61
|
.inverted .axis text {
|
44
62
|
fill: #fff;
|
45
63
|
}
|
@@ -47,6 +65,10 @@ module Spoom
|
|
47
65
|
.inverted .axis line {
|
48
66
|
stroke: #fff;
|
49
67
|
}
|
68
|
+
|
69
|
+
.inverted .dot {
|
70
|
+
fill: #fff;
|
71
|
+
}
|
50
72
|
CSS
|
51
73
|
end
|
52
74
|
|
@@ -170,7 +192,6 @@ module Spoom
|
|
170
192
|
.y1((d) => yScale_#{id}(#{y}))
|
171
193
|
.curve(d3.#{curve}))
|
172
194
|
.attr("fill", "#{color}")
|
173
|
-
.attr("fill-opacity", 0.5)
|
174
195
|
HTML
|
175
196
|
end
|
176
197
|
|
@@ -185,8 +206,6 @@ module Spoom
|
|
185
206
|
.y((d) => yScale_#{id}(#{y}))
|
186
207
|
.curve(d3.#{curve}))
|
187
208
|
.attr("stroke", "#{color}")
|
188
|
-
.attr("stroke-width", 3)
|
189
|
-
.attr("fill", "transparent")
|
190
209
|
HTML
|
191
210
|
end
|
192
211
|
|
@@ -198,10 +217,8 @@ module Spoom
|
|
198
217
|
.enter()
|
199
218
|
.append("circle")
|
200
219
|
.attr("class", "dot")
|
201
|
-
.attr("r", 3)
|
202
220
|
.attr("cx", (d) => xScale_#{id}(parseDate(d.timestamp)))
|
203
221
|
.attr("cy", (d, i) => yScale_#{id}(#{y}))
|
204
|
-
.attr("fill", "#aaa")
|
205
222
|
.on("mouseover", (d) => tooltip.style("opacity", 1))
|
206
223
|
.on("mousemove", tooltip_#{id})
|
207
224
|
.on("mouseleave", (d) => tooltip.style("opacity", 0));
|
@@ -381,18 +398,15 @@ module Spoom
|
|
381
398
|
layer.append("path")
|
382
399
|
.attr("class", "area")
|
383
400
|
.attr("d", area_#{id})
|
384
|
-
.attr("fill", (d) =>
|
385
|
-
.attr("fill-opacity", 0.9)
|
401
|
+
.attr("fill", (d) => #{color})
|
386
402
|
|
387
403
|
svg_#{id}.selectAll("circle")
|
388
404
|
.data(points_#{id})
|
389
405
|
.enter()
|
390
406
|
.append("circle")
|
391
407
|
.attr("class", "dot")
|
392
|
-
.attr("r", 2)
|
393
408
|
.attr("cx", (d) => xScale_#{id}(parseDate(#{y})))
|
394
409
|
.attr("cy", (d, i) => yScale_#{id}(d[1]))
|
395
|
-
.attr("fill", "#fff")
|
396
410
|
.on("mouseover", (d) => tooltip.style("opacity", 1))
|
397
411
|
.on("mousemove", tooltip_#{id})
|
398
412
|
.on("mouseleave", (d) => tooltip.style("opacity", 0));
|
@@ -480,6 +494,129 @@ module Spoom
|
|
480
494
|
JS
|
481
495
|
end
|
482
496
|
end
|
497
|
+
|
498
|
+
class RBIs < Stacked
|
499
|
+
extend T::Sig
|
500
|
+
|
501
|
+
sig { params(id: String, snapshots: T::Array[Snapshot]).void }
|
502
|
+
def initialize(id, snapshots)
|
503
|
+
keys = ['rbis', 'files']
|
504
|
+
data = snapshots.map do |snapshot|
|
505
|
+
{
|
506
|
+
timestamp: snapshot.commit_timestamp,
|
507
|
+
commit: snapshot.commit_sha,
|
508
|
+
total: snapshot.files,
|
509
|
+
values: { files: snapshot.files - snapshot.rbi_files, rbis: snapshot.rbi_files },
|
510
|
+
}
|
511
|
+
end
|
512
|
+
super(id, data, keys)
|
513
|
+
end
|
514
|
+
|
515
|
+
sig { override.returns(String) }
|
516
|
+
def tooltip
|
517
|
+
<<~JS
|
518
|
+
function tooltip_#{id}(d) {
|
519
|
+
moveTooltip(d)
|
520
|
+
.html("commit <b>" + d.data.commit + "</b><br>"
|
521
|
+
+ d3.timeFormat("%y/%m/%d")(parseDate(d.data.timestamp)) + "<br><br>"
|
522
|
+
+ "Files: <b>" + d.data.values.files + "</b><br>"
|
523
|
+
+ "RBIs: <b>" + d.data.values.rbis + "</b><br><br>"
|
524
|
+
+ "Total: <b>" + d.data.total + "</b>")
|
525
|
+
}
|
526
|
+
JS
|
527
|
+
end
|
528
|
+
|
529
|
+
sig { override.returns(String) }
|
530
|
+
def script
|
531
|
+
<<~JS
|
532
|
+
#{tooltip}
|
533
|
+
|
534
|
+
var data_#{id} = #{@data.to_json};
|
535
|
+
var keys_#{id} = #{T.unsafe(@keys).to_json};
|
536
|
+
|
537
|
+
var stack_#{id} = d3.stack()
|
538
|
+
.keys(keys_#{id})
|
539
|
+
.value((d, key) => d.values[key]);
|
540
|
+
|
541
|
+
var layers_#{id} = stack_#{id}(data_#{id});
|
542
|
+
|
543
|
+
var points_#{id} = []
|
544
|
+
layers_#{id}.forEach(function(d) {
|
545
|
+
d.forEach(function(p) {
|
546
|
+
p.key = d.key
|
547
|
+
points_#{id}.push(p);
|
548
|
+
});
|
549
|
+
})
|
550
|
+
|
551
|
+
function draw_#{id}() {
|
552
|
+
var width_#{id} = document.getElementById("#{id}").clientWidth;
|
553
|
+
var height_#{id} = 200;
|
554
|
+
|
555
|
+
d3.select("##{id}").selectAll("*").remove()
|
556
|
+
|
557
|
+
var svg_#{id} = d3.select("##{id}")
|
558
|
+
.attr("width", width_#{id})
|
559
|
+
.attr("height", height_#{id});
|
560
|
+
|
561
|
+
#{plot}
|
562
|
+
}
|
563
|
+
|
564
|
+
draw_#{id}();
|
565
|
+
window.addEventListener("resize", draw_#{id});
|
566
|
+
JS
|
567
|
+
end
|
568
|
+
|
569
|
+
sig { override.params(y: String, color: String, curve: String).returns(String) }
|
570
|
+
def line(y:, color: 'strictnessColor(d.key)', curve: 'curveCatmullRom.alpha(1)')
|
571
|
+
<<~JS
|
572
|
+
var area_#{id} = d3.area()
|
573
|
+
.x((d) => xScale_#{id}(parseDate(#{y})))
|
574
|
+
.y0((d) => yScale_#{id}(d[0]))
|
575
|
+
.y1((d) => yScale_#{id}(d[1]))
|
576
|
+
.curve(d3.#{curve});
|
577
|
+
|
578
|
+
var layer = svg_#{id}.selectAll(".layer")
|
579
|
+
.data(layers_#{id})
|
580
|
+
.enter().append("g")
|
581
|
+
.attr("class", "layer")
|
582
|
+
|
583
|
+
layer.append("path")
|
584
|
+
.attr("class", "area")
|
585
|
+
.attr("d", area_#{id})
|
586
|
+
.attr("fill", (d) => #{color})
|
587
|
+
|
588
|
+
layer.append("path")
|
589
|
+
.attr("class", "line")
|
590
|
+
.attr("d", d3.line()
|
591
|
+
.x((d) => xScale_#{id}(parseDate(#{y})))
|
592
|
+
.y((d, i) => yScale_#{id}(d[1]))
|
593
|
+
.curve(d3.#{curve}))
|
594
|
+
.attr("stroke", (d) => #{color})
|
595
|
+
|
596
|
+
svg_#{id}.selectAll("circle")
|
597
|
+
.data(points_#{id})
|
598
|
+
.enter()
|
599
|
+
.append("circle")
|
600
|
+
.attr("class", "dot")
|
601
|
+
.attr("cx", (d) => xScale_#{id}(parseDate(#{y})))
|
602
|
+
.attr("cy", (d, i) => yScale_#{id}(d[1]))
|
603
|
+
.on("mouseover", (d) => tooltip.style("opacity", 1))
|
604
|
+
.on("mousemove", tooltip_#{id})
|
605
|
+
.on("mouseleave", (d) => tooltip.style("opacity", 0));
|
606
|
+
JS
|
607
|
+
end
|
608
|
+
|
609
|
+
sig { override.returns(String) }
|
610
|
+
def plot
|
611
|
+
<<~JS
|
612
|
+
#{x_scale}
|
613
|
+
#{y_scale(min: '0', max: "d3.max(data_#{id}, (d) => d.total + 10)", ticks: 'tickValues([0, 25, 50, 75, 100])')}
|
614
|
+
#{line(y: 'd.data.timestamp', color: "d.key == 'rbis' ? '#8673ff' : '#007bff'")}
|
615
|
+
#{x_ticks}
|
616
|
+
#{y_ticks(ticks: 'tickValues([25, 50, 75])', format: 'd', padding: 20)}
|
617
|
+
JS
|
618
|
+
end
|
619
|
+
end
|
483
620
|
end
|
484
621
|
end
|
485
622
|
end
|
@@ -194,6 +194,15 @@ module Spoom
|
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
|
+
class RBIs < Timeline
|
198
|
+
extend T::Sig
|
199
|
+
|
200
|
+
sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void }
|
201
|
+
def initialize(snapshots:, title: "RBIs Timeline")
|
202
|
+
super(title: title, timeline: D3::Timeline::RBIs.new("timeline_rbis", snapshots))
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
197
206
|
class Versions < Timeline
|
198
207
|
extend T::Sig
|
199
208
|
|
@@ -298,6 +307,7 @@ module Spoom
|
|
298
307
|
cards << Cards::Timeline::Sigils.new(snapshots: snapshots)
|
299
308
|
cards << Cards::Timeline::Calls.new(snapshots: snapshots)
|
300
309
|
cards << Cards::Timeline::Sigs.new(snapshots: snapshots)
|
310
|
+
cards << Cards::Timeline::RBIs.new(snapshots: snapshots)
|
301
311
|
cards << Cards::Timeline::Versions.new(snapshots: snapshots)
|
302
312
|
cards << Cards::Timeline::Runtimes.new(snapshots: snapshots)
|
303
313
|
cards << Cards::SorbetIntro.new(sorbet_intro_commit: sorbet_intro_commit, sorbet_intro_date: sorbet_intro_date)
|
@@ -13,6 +13,7 @@ module Spoom
|
|
13
13
|
prop :commit_sha, T.nilable(String), default: nil
|
14
14
|
prop :commit_timestamp, T.nilable(Integer), default: nil
|
15
15
|
prop :files, Integer, default: 0
|
16
|
+
prop :rbi_files, Integer, default: 0
|
16
17
|
prop :modules, Integer, default: 0
|
17
18
|
prop :classes, Integer, default: 0
|
18
19
|
prop :singleton_classes, Integer, default: 0
|
@@ -46,6 +47,7 @@ module Spoom
|
|
46
47
|
snapshot.commit_sha = obj.fetch("commit_sha", nil)
|
47
48
|
snapshot.commit_timestamp = obj.fetch("commit_timestamp", nil)
|
48
49
|
snapshot.files = obj.fetch("files", 0)
|
50
|
+
snapshot.rbi_files = obj.fetch("rbi_files", 0)
|
49
51
|
snapshot.modules = obj.fetch("modules", 0)
|
50
52
|
snapshot.classes = obj.fetch("classes", 0)
|
51
53
|
snapshot.singleton_classes = obj.fetch("singleton_classes", 0)
|
@@ -86,7 +88,7 @@ module Spoom
|
|
86
88
|
end
|
87
89
|
printl("Content:")
|
88
90
|
indent
|
89
|
-
printl("files: #{snapshot.files}")
|
91
|
+
printl("files: #{snapshot.files} (including #{snapshot.rbi_files} RBIs)")
|
90
92
|
printl("modules: #{snapshot.modules}")
|
91
93
|
printl("classes: #{snapshot.classes - snapshot.singleton_classes}")
|
92
94
|
printl("methods: #{methods}")
|
data/lib/spoom/git.rb
CHANGED
@@ -14,11 +14,12 @@ module Spoom
|
|
14
14
|
return "", "Error: `#{path}` is not a directory.", false unless File.directory?(path)
|
15
15
|
opts = {}
|
16
16
|
opts[:chdir] = path
|
17
|
-
|
17
|
+
i, o, e, s = Open3.popen3(*T.unsafe([command, *T.unsafe(arg), opts]))
|
18
18
|
out = o.read.to_s
|
19
19
|
o.close
|
20
20
|
err = e.read.to_s
|
21
21
|
e.close
|
22
|
+
i.close
|
22
23
|
[out, err, T.cast(s.value, Process::Status).success?]
|
23
24
|
end
|
24
25
|
|
@@ -49,6 +50,13 @@ module Spoom
|
|
49
50
|
exec("git show #{arg.join(' ')}", path: path)
|
50
51
|
end
|
51
52
|
|
53
|
+
sig { params(path: String).returns(T.nilable(String)) }
|
54
|
+
def self.current_branch(path: ".")
|
55
|
+
out, _, status = exec("git branch --show-current", path: path)
|
56
|
+
return nil unless status
|
57
|
+
out.strip
|
58
|
+
end
|
59
|
+
|
52
60
|
# Utils
|
53
61
|
|
54
62
|
# Get the commit epoch timestamp for a `sha`
|
@@ -92,7 +100,19 @@ module Spoom
|
|
92
100
|
def self.sorbet_intro_commit(path: ".")
|
93
101
|
res, _, status = Spoom::Git.log("--diff-filter=A --format='%h' -1 -- sorbet/config", path: path)
|
94
102
|
return nil unless status
|
95
|
-
res.strip
|
103
|
+
res.strip!
|
104
|
+
return nil if res.empty?
|
105
|
+
res
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get the hash of the commit removing the `sorbet/config` file
|
109
|
+
sig { params(path: String).returns(T.nilable(String)) }
|
110
|
+
def self.sorbet_removal_commit(path: ".")
|
111
|
+
res, _, status = Spoom::Git.log("--diff-filter=D --format='%h' -1 -- sorbet/config", path: path)
|
112
|
+
return nil unless status
|
113
|
+
res.strip!
|
114
|
+
return nil if res.empty?
|
115
|
+
res
|
96
116
|
end
|
97
117
|
end
|
98
118
|
end
|
data/lib/spoom/printer.rb
CHANGED
data/lib/spoom/sorbet.rb
CHANGED
data/lib/spoom/sorbet/config.rb
CHANGED
@@ -29,11 +29,15 @@ module Spoom
|
|
29
29
|
sig { returns(T::Array[String]) }
|
30
30
|
attr_reader :paths, :ignore, :allowed_extensions
|
31
31
|
|
32
|
+
sig { returns(T::Boolean) }
|
33
|
+
attr_accessor :no_stdlib
|
34
|
+
|
32
35
|
sig { void }
|
33
36
|
def initialize
|
34
37
|
@paths = T.let([], T::Array[String])
|
35
38
|
@ignore = T.let([], T::Array[String])
|
36
39
|
@allowed_extensions = T.let([], T::Array[String])
|
40
|
+
@no_stdlib = T.let(false, T::Boolean)
|
37
41
|
end
|
38
42
|
|
39
43
|
sig { returns(Config) }
|
@@ -42,6 +46,7 @@ module Spoom
|
|
42
46
|
new_config.paths.concat(@paths)
|
43
47
|
new_config.ignore.concat(@ignore)
|
44
48
|
new_config.allowed_extensions.concat(@allowed_extensions)
|
49
|
+
new_config.no_stdlib = @no_stdlib
|
45
50
|
new_config
|
46
51
|
end
|
47
52
|
|
@@ -63,6 +68,7 @@ module Spoom
|
|
63
68
|
opts.concat(paths)
|
64
69
|
opts.concat(ignore.map { |p| "--ignore #{p}" })
|
65
70
|
opts.concat(allowed_extensions.map { |ext| "--allowed-extension #{ext}" })
|
71
|
+
opts << "--no-stdlib" if @no_stdlib
|
66
72
|
opts.join(" ")
|
67
73
|
end
|
68
74
|
|
@@ -106,12 +112,19 @@ module Spoom
|
|
106
112
|
when /^--dir=/
|
107
113
|
config.paths << parse_option(line)
|
108
114
|
next
|
115
|
+
when /^--no-stdlib$/
|
116
|
+
config.no_stdlib = true
|
117
|
+
next
|
109
118
|
when /^--.*=/
|
110
119
|
next
|
111
120
|
when /^--/
|
112
121
|
state = :skip
|
113
122
|
when /^-.*=?/
|
114
123
|
next
|
124
|
+
when /^#/
|
125
|
+
next
|
126
|
+
when /^$/
|
127
|
+
next
|
115
128
|
else
|
116
129
|
case state
|
117
130
|
when :ignore
|
data/lib/spoom/sorbet/sigils.rb
CHANGED
@@ -55,7 +55,7 @@ module Spoom
|
|
55
55
|
# * returns nil if no sigil
|
56
56
|
sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
|
57
57
|
def self.file_strictness(path)
|
58
|
-
return nil unless File.
|
58
|
+
return nil unless File.file?(path)
|
59
59
|
content = File.read(path, encoding: Encoding::ASCII_8BIT)
|
60
60
|
strictness_in_content(content)
|
61
61
|
end
|
@@ -76,6 +76,15 @@ module Spoom
|
|
76
76
|
Spoom::Git.exec("GIT_COMMITTER_DATE=\"#{date}\" git commit -m '#{message}' --date '#{date}'", path: path)
|
77
77
|
end
|
78
78
|
|
79
|
+
# Run `bundle install` in this project
|
80
|
+
sig { returns([T.nilable(String), T.nilable(String), T::Boolean]) }
|
81
|
+
def bundle_install
|
82
|
+
opts = {}
|
83
|
+
opts[:chdir] = path
|
84
|
+
out, err, status = Open3.capture3("bundle", "install", opts)
|
85
|
+
[out, err, status.success?]
|
86
|
+
end
|
87
|
+
|
79
88
|
# Run a command with `bundle exec` in this project
|
80
89
|
sig { params(cmd: String, args: String).returns([T.nilable(String), T.nilable(String), T::Boolean]) }
|
81
90
|
def bundle_exec(cmd, *args)
|
@@ -91,6 +100,16 @@ module Spoom
|
|
91
100
|
FileUtils.rm_rf(path)
|
92
101
|
end
|
93
102
|
|
103
|
+
sig { params(name: String).void }
|
104
|
+
def create_and_checkout_branch(name)
|
105
|
+
Spoom::Git.exec("git checkout -b #{name}", path: path)
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { returns(T.nilable(String)) }
|
109
|
+
def current_branch
|
110
|
+
Spoom::Git.current_branch(path: path)
|
111
|
+
end
|
112
|
+
|
94
113
|
private
|
95
114
|
|
96
115
|
# Create an absolute path from `self.path` and `rel_path`
|
data/lib/spoom/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spoom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Terrasa
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.2.10
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.2.10
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,16 +70,16 @@ dependencies:
|
|
70
70
|
name: sorbet
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.5.
|
75
|
+
version: 0.5.6347
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.5.
|
82
|
+
version: 0.5.6347
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: thor
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -174,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
174
|
- !ruby/object:Gem::Version
|
175
175
|
version: '0'
|
176
176
|
requirements: []
|
177
|
-
rubygems_version: 3.
|
177
|
+
rubygems_version: 3.2.20
|
178
178
|
signing_key:
|
179
179
|
specification_version: 4
|
180
180
|
summary: Useful tools for Sorbet enthusiasts.
|