spoom 1.0.8 → 1.1.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/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.
|