spoom 1.0.7 → 1.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +45 -2
- data/lib/spoom.rb +19 -2
- data/lib/spoom/cli.rb +10 -9
- data/lib/spoom/cli/bump.rb +67 -9
- data/lib/spoom/cli/config.rb +1 -1
- data/lib/spoom/cli/coverage.rb +30 -19
- data/lib/spoom/cli/helper.rb +2 -1
- data/lib/spoom/cli/lsp.rb +10 -10
- data/lib/spoom/cli/run.rb +54 -24
- data/lib/spoom/coverage.rb +22 -6
- data/lib/spoom/coverage/report.rb +3 -3
- data/lib/spoom/file_tree.rb +1 -1
- data/lib/spoom/sorbet.rb +96 -58
- data/lib/spoom/sorbet/config.rb +30 -0
- data/lib/spoom/sorbet/errors.rb +8 -0
- data/lib/spoom/sorbet/lsp.rb +2 -6
- data/lib/spoom/sorbet/sigils.rb +3 -3
- data/lib/spoom/version.rb +2 -2
- metadata +2 -3
- data/lib/spoom/config.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 866446fc75971d51c7a71d7ed713cf256f682ffbd0fabfaf97c95b601638876b
|
4
|
+
data.tar.gz: a38dd3b7859929349c2d84b4119e5f8d8d374c0cf5062adbb3904065a1d6f0f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 257a78cbdf439ee83e23a823ed39918ada7a1b66786967a77604e5dca2bb825aeb8af2f5fcda6c497686f29f9d0c7dba1d051060d984694f22c0064dcbd8c1a9
|
7
|
+
data.tar.gz: 4a66b832da23c6237d1dedc16735cce058306ce66327fd2e074d72a481d55412aa7dc8047f0fa25957e1369747478347b77ccf8cd7c85cdb286fcff6a564a412
|
data/README.md
CHANGED
@@ -69,7 +69,7 @@ $ spoom files
|
|
69
69
|
List all typechecking errors sorted by location:
|
70
70
|
|
71
71
|
```
|
72
|
-
$ spoom tc -s
|
72
|
+
$ spoom tc -s loc
|
73
73
|
```
|
74
74
|
|
75
75
|
List all typechecking errors sorted by error code first:
|
@@ -96,6 +96,31 @@ These options can be combined:
|
|
96
96
|
$ spoom tc -s -c 7004 -l 10
|
97
97
|
```
|
98
98
|
|
99
|
+
Remove duplicated error lines:
|
100
|
+
|
101
|
+
```
|
102
|
+
$ spoom tc -u
|
103
|
+
```
|
104
|
+
|
105
|
+
Format each error line:
|
106
|
+
|
107
|
+
```
|
108
|
+
$ spoom tc -f '%C - %F:%L: %M'
|
109
|
+
```
|
110
|
+
|
111
|
+
Where:
|
112
|
+
|
113
|
+
* `%C` is the error code
|
114
|
+
* `%F` is the file the error is from
|
115
|
+
* `%L` is the line the error is from
|
116
|
+
* `%M` is the error message
|
117
|
+
|
118
|
+
Hide the `Errors: X` at the end of the list:
|
119
|
+
|
120
|
+
```
|
121
|
+
$ spoom tc --no-count
|
122
|
+
```
|
123
|
+
|
99
124
|
#### Typing coverage
|
100
125
|
|
101
126
|
Show metrics about the project contents and the typing coverage:
|
@@ -183,6 +208,24 @@ Bump the strictness from all files currently at `typed: false` to `typed: true`
|
|
183
208
|
$ spoom bump --from false --to true -f
|
184
209
|
```
|
185
210
|
|
211
|
+
Bump the strictness from a list of files (one file by line):
|
212
|
+
|
213
|
+
```
|
214
|
+
$ spoom bump --from false --to true -o list.txt
|
215
|
+
```
|
216
|
+
|
217
|
+
Check if files can be bumped without applying any change:
|
218
|
+
|
219
|
+
```
|
220
|
+
$ spoom bump --from false --to true --dry
|
221
|
+
```
|
222
|
+
|
223
|
+
Bump files using a custom instance of Sorbet:
|
224
|
+
|
225
|
+
```
|
226
|
+
$ spoom bump --from false --to true --sorbet /path/to/sorbet/bin
|
227
|
+
```
|
228
|
+
|
186
229
|
#### Interact with Sorbet LSP mode
|
187
230
|
|
188
231
|
**Experimental**
|
@@ -273,7 +316,7 @@ Create an LSP client:
|
|
273
316
|
|
274
317
|
```rb
|
275
318
|
client = Spoom::LSP::Client.new(
|
276
|
-
Spoom::
|
319
|
+
Spoom::Sorbet::BIN_PATH,
|
277
320
|
"--lsp",
|
278
321
|
"--enable-all-experimental-lsp-features",
|
279
322
|
"--disable-watchman",
|
data/lib/spoom.rb
CHANGED
@@ -4,10 +4,27 @@
|
|
4
4
|
require "sorbet-runtime"
|
5
5
|
|
6
6
|
module Spoom
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
SPOOM_PATH = (Pathname.new(__FILE__) / ".." / "..").to_s
|
10
|
+
|
7
11
|
class Error < StandardError; end
|
8
|
-
end
|
9
12
|
|
10
|
-
|
13
|
+
sig do
|
14
|
+
params(
|
15
|
+
cmd: String,
|
16
|
+
arg: String,
|
17
|
+
path: String,
|
18
|
+
capture_err: T::Boolean
|
19
|
+
).returns([String, T::Boolean])
|
20
|
+
end
|
21
|
+
def self.exec(cmd, *arg, path: '.', capture_err: false)
|
22
|
+
method = capture_err ? "popen2e" : "popen2"
|
23
|
+
Open3.send(method, [cmd, *arg].join(" "), chdir: path) do |_, o, t|
|
24
|
+
[o.read, T.cast(t.value, Process::Status).success?]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
11
28
|
|
12
29
|
require "spoom/sorbet"
|
13
30
|
require "spoom/cli"
|
data/lib/spoom/cli.rb
CHANGED
@@ -17,26 +17,27 @@ module Spoom
|
|
17
17
|
extend T::Sig
|
18
18
|
include Helper
|
19
19
|
|
20
|
-
class_option :color,
|
21
|
-
class_option :path,
|
20
|
+
class_option :color, type: :boolean, default: true, desc: "Use colors"
|
21
|
+
class_option :path, type: :string, default: ".", aliases: :p, desc: "Run spoom in a specific path"
|
22
|
+
|
22
23
|
map T.unsafe(%w[--version -v] => :__print_version)
|
23
24
|
|
24
|
-
desc "bump", "
|
25
|
+
desc "bump", "Bump Sorbet sigils from `false` to `true` when no errors"
|
25
26
|
subcommand "bump", Spoom::Cli::Bump
|
26
27
|
|
27
|
-
desc "config", "
|
28
|
+
desc "config", "Manage Sorbet config"
|
28
29
|
subcommand "config", Spoom::Cli::Config
|
29
30
|
|
30
|
-
desc "coverage", "
|
31
|
+
desc "coverage", "Collect metrics related to Sorbet coverage"
|
31
32
|
subcommand "coverage", Spoom::Cli::Coverage
|
32
33
|
|
33
|
-
desc "lsp", "
|
34
|
+
desc "lsp", "Send LSP requests to Sorbet"
|
34
35
|
subcommand "lsp", Spoom::Cli::LSP
|
35
36
|
|
36
|
-
desc "tc", "
|
37
|
+
desc "tc", "Run Sorbet and parses its output"
|
37
38
|
subcommand "tc", Spoom::Cli::Run
|
38
39
|
|
39
|
-
desc "files", "
|
40
|
+
desc "files", "List all the files typechecked by Sorbet"
|
40
41
|
def files
|
41
42
|
in_sorbet_project!
|
42
43
|
|
@@ -53,7 +54,7 @@ module Spoom
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
56
|
-
desc "--version", "
|
57
|
+
desc "--version", "Show version"
|
57
58
|
def __print_version
|
58
59
|
puts "Spoom v#{Spoom::VERSION}"
|
59
60
|
end
|
data/lib/spoom/cli/bump.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'find'
|
@@ -12,10 +12,18 @@ module Spoom
|
|
12
12
|
|
13
13
|
default_task :bump
|
14
14
|
|
15
|
-
desc "bump DIRECTORY", "
|
16
|
-
option :from, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE
|
17
|
-
|
18
|
-
option :
|
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)"
|
19
27
|
sig { params(directory: String).void }
|
20
28
|
def bump(directory = ".")
|
21
29
|
in_sorbet_project!
|
@@ -23,6 +31,9 @@ module Spoom
|
|
23
31
|
from = options[:from]
|
24
32
|
to = options[:to]
|
25
33
|
force = options[:force]
|
34
|
+
dry = options[:dry]
|
35
|
+
only = options[:only]
|
36
|
+
exec_path = File.expand_path(self.exec_path)
|
26
37
|
|
27
38
|
unless Sorbet::Sigils.valid_strictness?(from)
|
28
39
|
say_error("Invalid strictness #{from} for option --from")
|
@@ -36,13 +47,32 @@ module Spoom
|
|
36
47
|
|
37
48
|
directory = File.expand_path(directory)
|
38
49
|
files_to_bump = Sorbet::Sigils.files_with_sigil_strictness(directory, from)
|
50
|
+
|
51
|
+
if only
|
52
|
+
list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
|
53
|
+
files_to_bump.select! { |file| list.include?(File.expand_path(file)) }
|
54
|
+
end
|
55
|
+
|
56
|
+
if files_to_bump.empty?
|
57
|
+
$stderr.puts("No file to bump from #{from} to #{to}")
|
58
|
+
exit(0)
|
59
|
+
end
|
60
|
+
|
39
61
|
Sorbet::Sigils.change_sigil_in_files(files_to_bump, to)
|
40
62
|
|
41
|
-
|
63
|
+
if force
|
64
|
+
print_changes(files_to_bump, from: from, to: to, dry: dry, path: exec_path)
|
65
|
+
undo_changes(files_to_bump, from) if dry
|
66
|
+
exit(files_to_bump.empty?)
|
67
|
+
end
|
42
68
|
|
43
|
-
output, no_errors = Sorbet.srb_tc(path: exec_path, capture_err: true)
|
69
|
+
output, no_errors = Sorbet.srb_tc(path: exec_path, capture_err: true, sorbet_bin: options[:sorbet])
|
44
70
|
|
45
|
-
|
71
|
+
if no_errors
|
72
|
+
print_changes(files_to_bump, from: from, to: to, dry: dry, path: exec_path)
|
73
|
+
undo_changes(files_to_bump, from) if dry
|
74
|
+
exit(files_to_bump.empty?)
|
75
|
+
end
|
46
76
|
|
47
77
|
errors = Sorbet::Errors::Parser.parse_string(output)
|
48
78
|
|
@@ -53,7 +83,35 @@ module Spoom
|
|
53
83
|
path
|
54
84
|
end.compact.uniq
|
55
85
|
|
56
|
-
|
86
|
+
undo_changes(files_with_errors, from)
|
87
|
+
|
88
|
+
files_changed = files_to_bump - files_with_errors
|
89
|
+
print_changes(files_changed, from: from, to: to, dry: dry, path: exec_path)
|
90
|
+
undo_changes(files_to_bump, from) if dry
|
91
|
+
exit(files_changed.empty?)
|
92
|
+
end
|
93
|
+
|
94
|
+
no_commands do
|
95
|
+
def print_changes(files, from: "false", to: "true", dry: false, path: File.expand_path("."))
|
96
|
+
if files.empty?
|
97
|
+
$stderr.puts("No file to bump from #{from} to #{to}")
|
98
|
+
return
|
99
|
+
end
|
100
|
+
$stderr.write(dry ? "Can bump" : "Bumped")
|
101
|
+
$stderr.write(" #{files.size} file#{'s' if files.size > 1}")
|
102
|
+
$stderr.puts(" from #{from} to #{to}:")
|
103
|
+
files.each do |file|
|
104
|
+
file_path = Pathname.new(file).relative_path_from(path)
|
105
|
+
$stderr.puts(" + #{file_path}")
|
106
|
+
end
|
107
|
+
if dry
|
108
|
+
$stderr.puts("\nRun `spoom bump --from #{from} --to #{to}` to bump them")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def undo_changes(files, from_strictness)
|
113
|
+
Sorbet::Sigils.change_sigil_in_files(files, from_strictness)
|
114
|
+
end
|
57
115
|
end
|
58
116
|
end
|
59
117
|
end
|
data/lib/spoom/cli/config.rb
CHANGED
data/lib/spoom/cli/coverage.rb
CHANGED
@@ -13,13 +13,16 @@ module Spoom
|
|
13
13
|
|
14
14
|
default_task :snapshot
|
15
15
|
|
16
|
-
desc "snapshot", "
|
17
|
-
option :save, type: :string, desc: "Save snapshot data as json"
|
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"
|
18
20
|
def snapshot
|
19
21
|
in_sorbet_project!
|
20
|
-
|
21
22
|
path = exec_path
|
22
|
-
|
23
|
+
sorbet = options[:sorbet]
|
24
|
+
|
25
|
+
snapshot = Spoom::Coverage.snapshot(path: path, rbi: options[:rbi], sorbet_bin: sorbet)
|
23
26
|
snapshot.print
|
24
27
|
|
25
28
|
save_dir = options[:save]
|
@@ -30,14 +33,16 @@ module Spoom
|
|
30
33
|
puts "\nSnapshot data saved under #{file}"
|
31
34
|
end
|
32
35
|
|
33
|
-
desc "timeline", "
|
34
|
-
option :from, type: :string
|
35
|
-
option :to, type: :string, default: Time.now.strftime("%F")
|
36
|
-
option :save, type: :string, desc: "Save snapshot data as json"
|
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"
|
37
40
|
option :bundle_install, type: :boolean, desc: "Execute `bundle install` before collecting metrics"
|
41
|
+
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
38
42
|
def timeline
|
39
43
|
in_sorbet_project!
|
40
44
|
path = exec_path
|
45
|
+
sorbet = options[:sorbet]
|
41
46
|
|
42
47
|
sha_before = Spoom::Git.last_commit(path: path)
|
43
48
|
unless sha_before
|
@@ -83,10 +88,10 @@ module Spoom
|
|
83
88
|
if options[:bundle_install]
|
84
89
|
Bundler.with_clean_env do
|
85
90
|
next unless bundle_install(path, sha)
|
86
|
-
snapshot = Spoom::Coverage.snapshot(path: path)
|
91
|
+
snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
|
87
92
|
end
|
88
93
|
else
|
89
|
-
snapshot = Spoom::Coverage.snapshot(path: path)
|
94
|
+
snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
|
90
95
|
end
|
91
96
|
next unless snapshot
|
92
97
|
|
@@ -101,14 +106,20 @@ module Spoom
|
|
101
106
|
Spoom::Git.checkout(sha_before, path: path)
|
102
107
|
end
|
103
108
|
|
104
|
-
desc "report", "
|
105
|
-
option :data, type: :string, desc: "Snapshots JSON data"
|
106
|
-
option :file, type: :string, default: "spoom_report.html", aliases: :f
|
107
|
-
|
108
|
-
option :
|
109
|
-
|
110
|
-
option :
|
111
|
-
|
109
|
+
desc "report", "Produce a typing coverage report"
|
110
|
+
option :data, type: :string, default: DATA_DIR, desc: "Snapshots JSON data"
|
111
|
+
option :file, type: :string, default: "spoom_report.html", aliases: :f,
|
112
|
+
desc: "Save report to file"
|
113
|
+
option :color_ignore, type: :string, default: Spoom::Coverage::D3::COLOR_IGNORE,
|
114
|
+
desc: "Color used for typed: ignore"
|
115
|
+
option :color_false, type: :string, default: Spoom::Coverage::D3::COLOR_FALSE,
|
116
|
+
desc: "Color used for typed: false"
|
117
|
+
option :color_true, type: :string, default: Spoom::Coverage::D3::COLOR_TRUE,
|
118
|
+
desc: "Color used for typed: true"
|
119
|
+
option :color_strict, type: :string, default: Spoom::Coverage::D3::COLOR_STRICT,
|
120
|
+
desc: "Color used for typed: strict"
|
121
|
+
option :color_strong, type: :string, default: Spoom::Coverage::D3::COLOR_STRONG,
|
122
|
+
desc: "Color used for typed: strong"
|
112
123
|
def report
|
113
124
|
in_sorbet_project!
|
114
125
|
|
@@ -139,7 +150,7 @@ module Spoom
|
|
139
150
|
puts "\nUse #{colorize('spoom coverage open', :blue)} to open it."
|
140
151
|
end
|
141
152
|
|
142
|
-
desc "open", "
|
153
|
+
desc "open", "Open the typing coverage report"
|
143
154
|
def open(file = "spoom_report.html")
|
144
155
|
unless File.exist?(file)
|
145
156
|
say_error("No report file to open #{colorize(file, :blue)}")
|
data/lib/spoom/cli/helper.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "fileutils"
|
4
5
|
require "pathname"
|
5
6
|
require "stringio"
|
6
7
|
|
@@ -54,7 +55,7 @@ module Spoom
|
|
54
55
|
|
55
56
|
sig { returns(String) }
|
56
57
|
def sorbet_config
|
57
|
-
Pathname.new("#{exec_path}/#{Spoom::
|
58
|
+
Pathname.new("#{exec_path}/#{Spoom::Sorbet::CONFIG_PATH}").cleanpath.to_s
|
58
59
|
end
|
59
60
|
|
60
61
|
# Is the `--color` option true?
|
data/lib/spoom/cli/lsp.rb
CHANGED
@@ -12,7 +12,7 @@ module Spoom
|
|
12
12
|
|
13
13
|
default_task :show
|
14
14
|
|
15
|
-
desc "interactive", "
|
15
|
+
desc "interactive", "Interactive LSP mode"
|
16
16
|
def show
|
17
17
|
in_sorbet_project!
|
18
18
|
lsp = lsp_client
|
@@ -20,7 +20,7 @@ module Spoom
|
|
20
20
|
puts lsp
|
21
21
|
end
|
22
22
|
|
23
|
-
desc "list", "
|
23
|
+
desc "list", "List all known symbols"
|
24
24
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
25
25
|
def list
|
26
26
|
run do |client|
|
@@ -34,7 +34,7 @@ module Spoom
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
desc "hover", "
|
37
|
+
desc "hover", "Request hover informations"
|
38
38
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
39
39
|
def hover(file, line, col)
|
40
40
|
run do |client|
|
@@ -48,7 +48,7 @@ module Spoom
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
desc "defs", "
|
51
|
+
desc "defs", "List definitions of a symbol"
|
52
52
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
53
53
|
def defs(file, line, col)
|
54
54
|
run do |client|
|
@@ -58,7 +58,7 @@ module Spoom
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
desc "find", "
|
61
|
+
desc "find", "Find symbols matching a query"
|
62
62
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
63
63
|
def find(query)
|
64
64
|
run do |client|
|
@@ -68,7 +68,7 @@ module Spoom
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
desc "symbols", "
|
71
|
+
desc "symbols", "List symbols from a file"
|
72
72
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
73
73
|
def symbols(file)
|
74
74
|
run do |client|
|
@@ -78,7 +78,7 @@ module Spoom
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
desc "refs", "
|
81
|
+
desc "refs", "List references to a symbol"
|
82
82
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
83
83
|
def refs(file, line, col)
|
84
84
|
run do |client|
|
@@ -88,7 +88,7 @@ module Spoom
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
-
desc "sigs", "
|
91
|
+
desc "sigs", "List signatures for a symbol"
|
92
92
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
93
93
|
def sigs(file, line, col)
|
94
94
|
run do |client|
|
@@ -98,7 +98,7 @@ module Spoom
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
-
desc "types", "
|
101
|
+
desc "types", "Display type of a symbol"
|
102
102
|
# TODO: options, filter, limit, kind etc.. filter rbi
|
103
103
|
def types(file, line, col)
|
104
104
|
run do |client|
|
@@ -113,7 +113,7 @@ module Spoom
|
|
113
113
|
in_sorbet_project!
|
114
114
|
path = exec_path
|
115
115
|
client = Spoom::LSP::Client.new(
|
116
|
-
Spoom::
|
116
|
+
Spoom::Sorbet::BIN_PATH,
|
117
117
|
"--lsp",
|
118
118
|
"--enable-all-experimental-lsp-features",
|
119
119
|
"--disable-watchman",
|
data/lib/spoom/cli/run.rb
CHANGED
@@ -8,59 +8,89 @@ module Spoom
|
|
8
8
|
|
9
9
|
default_task :tc
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
SORT_CODE = "code"
|
12
|
+
SORT_LOC = "loc"
|
13
|
+
SORT_ENUM = [SORT_CODE, SORT_LOC]
|
14
|
+
|
15
|
+
DEFAULT_FORMAT = "%C - %F:%L: %M"
|
16
|
+
|
17
|
+
desc "tc", "Run `srb tc`"
|
18
|
+
option :limit, type: :numeric, aliases: :l, desc: "Limit displayed errors"
|
19
|
+
option :code, type: :numeric, aliases: :c, desc: "Filter displayed errors by code"
|
20
|
+
option :sort, type: :string, aliases: :s, desc: "Sort errors", enum: SORT_ENUM, lazy_default: SORT_LOC
|
21
|
+
option :format, type: :string, aliases: :f, desc: "Format line output"
|
22
|
+
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
23
|
+
option :count, type: :boolean, default: true, desc: "Show errors count"
|
24
|
+
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
25
|
+
def tc(*arg)
|
16
26
|
in_sorbet_project!
|
17
27
|
|
18
28
|
path = exec_path
|
19
29
|
limit = options[:limit]
|
20
30
|
sort = options[:sort]
|
21
31
|
code = options[:code]
|
22
|
-
|
32
|
+
uniq = options[:uniq]
|
33
|
+
format = options[:format]
|
34
|
+
count = options[:count]
|
35
|
+
sorbet = options[:sorbet]
|
23
36
|
|
24
37
|
unless limit || code || sort
|
25
|
-
|
38
|
+
output, status = T.unsafe(Spoom::Sorbet).srb_tc(*arg, path: path, capture_err: false, sorbet_bin: sorbet)
|
39
|
+
$stderr.print(output)
|
40
|
+
exit(status)
|
26
41
|
end
|
27
42
|
|
28
|
-
output, status = Spoom::Sorbet.srb_tc(path: path, capture_err: true)
|
43
|
+
output, status = T.unsafe(Spoom::Sorbet).srb_tc(*arg, path: path, capture_err: true, sorbet_bin: sorbet)
|
29
44
|
if status
|
30
45
|
$stderr.print(output)
|
31
|
-
|
46
|
+
exit(0)
|
32
47
|
end
|
33
48
|
|
34
49
|
errors = Spoom::Sorbet::Errors::Parser.parse_string(output)
|
35
50
|
errors_count = errors.size
|
36
51
|
|
37
|
-
errors =
|
52
|
+
errors = case sort
|
53
|
+
when SORT_CODE
|
54
|
+
Spoom::Sorbet::Errors.sort_errors_by_code(errors)
|
55
|
+
when SORT_LOC
|
56
|
+
errors.sort
|
57
|
+
else
|
58
|
+
errors # preserve natural sort
|
59
|
+
end
|
60
|
+
|
38
61
|
errors = errors.select { |e| e.code == code } if code
|
39
62
|
errors = T.must(errors.slice(0, limit)) if limit
|
40
63
|
|
41
|
-
errors.
|
42
|
-
|
43
|
-
|
44
|
-
|
64
|
+
lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
|
65
|
+
lines = lines.uniq if uniq
|
66
|
+
|
67
|
+
lines.each do |line|
|
68
|
+
$stderr.puts line
|
45
69
|
end
|
46
70
|
|
47
|
-
if
|
48
|
-
|
49
|
-
|
50
|
-
|
71
|
+
if count
|
72
|
+
if errors_count == errors.size
|
73
|
+
$stderr.puts "Errors: #{errors_count}"
|
74
|
+
else
|
75
|
+
$stderr.puts "Errors: #{errors.size} shown, #{errors_count} total"
|
76
|
+
end
|
51
77
|
end
|
52
78
|
|
53
|
-
1
|
79
|
+
exit(1)
|
54
80
|
end
|
55
81
|
|
56
82
|
no_commands do
|
57
|
-
def
|
58
|
-
|
59
|
-
code.to_s
|
83
|
+
def format_error(error, format)
|
84
|
+
line = format
|
85
|
+
line = line.gsub(/%C/, colorize(error.code.to_s, :yellow))
|
86
|
+
line = line.gsub(/%F/, error.file)
|
87
|
+
line = line.gsub(/%L/, error.line.to_s)
|
88
|
+
line = line.gsub(/%M/, colorize_message(error.message))
|
89
|
+
line
|
60
90
|
end
|
61
91
|
|
62
|
-
def colorize_message(message
|
63
|
-
return message unless
|
92
|
+
def colorize_message(message)
|
93
|
+
return message unless color?
|
64
94
|
|
65
95
|
cyan = T.let(false, T::Boolean)
|
66
96
|
word = StringIO.new
|
data/lib/spoom/coverage.rb
CHANGED
@@ -11,10 +11,23 @@ module Spoom
|
|
11
11
|
module Coverage
|
12
12
|
extend T::Sig
|
13
13
|
|
14
|
-
sig { params(path: String).returns(Snapshot) }
|
15
|
-
def self.snapshot(path: '.')
|
14
|
+
sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
|
15
|
+
def self.snapshot(path: '.', rbi: true, sorbet_bin: nil)
|
16
|
+
config = sorbet_config(path: path)
|
17
|
+
config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
|
18
|
+
|
19
|
+
new_config = config.copy
|
20
|
+
new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" }
|
21
|
+
|
22
|
+
metrics = Spoom::Sorbet.srb_metrics(
|
23
|
+
"--no-config",
|
24
|
+
new_config.options_string,
|
25
|
+
path: path,
|
26
|
+
capture_err: true,
|
27
|
+
sorbet_bin: sorbet_bin
|
28
|
+
)
|
29
|
+
|
16
30
|
snapshot = Snapshot.new
|
17
|
-
metrics = Spoom::Sorbet.srb_metrics(path: path, capture_err: true)
|
18
31
|
return snapshot unless metrics
|
19
32
|
|
20
33
|
sha = Spoom::Git.last_commit(path: path)
|
@@ -59,11 +72,14 @@ module Spoom
|
|
59
72
|
)
|
60
73
|
end
|
61
74
|
|
75
|
+
sig { params(path: String).returns(Sorbet::Config) }
|
76
|
+
def self.sorbet_config(path: ".")
|
77
|
+
Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
|
78
|
+
end
|
79
|
+
|
62
80
|
sig { params(path: String).returns(FileTree) }
|
63
81
|
def self.sigils_tree(path: ".")
|
64
|
-
|
65
|
-
return FileTree.new unless File.exist?(config_file)
|
66
|
-
config = Sorbet::Config.parse_file(config_file)
|
82
|
+
config = sorbet_config(path: path)
|
67
83
|
files = Sorbet.srb_files(config, path: path)
|
68
84
|
files.select! { |file| file =~ /\.rb$/ }
|
69
85
|
files.reject! { |file| file =~ %r{/test/} }
|
@@ -41,7 +41,7 @@ module Spoom
|
|
41
41
|
|
42
42
|
abstract!
|
43
43
|
|
44
|
-
TEMPLATE = T.let("#{Spoom::
|
44
|
+
TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/page.erb", String)
|
45
45
|
|
46
46
|
sig { returns(String) }
|
47
47
|
attr_reader :title
|
@@ -89,7 +89,7 @@ module Spoom
|
|
89
89
|
class Card < Template
|
90
90
|
extend T::Sig
|
91
91
|
|
92
|
-
TEMPLATE = T.let("#{Spoom::
|
92
|
+
TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card.erb", String)
|
93
93
|
|
94
94
|
sig { returns(T.nilable(String)) }
|
95
95
|
attr_reader :title, :body
|
@@ -123,7 +123,7 @@ module Spoom
|
|
123
123
|
class Snapshot < Card
|
124
124
|
extend T::Sig
|
125
125
|
|
126
|
-
TEMPLATE = T.let("#{Spoom::
|
126
|
+
TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card_snapshot.erb", String)
|
127
127
|
|
128
128
|
sig { returns(Coverage::Snapshot) }
|
129
129
|
attr_reader :snapshot
|
data/lib/spoom/file_tree.rb
CHANGED
@@ -80,7 +80,7 @@ module Spoom
|
|
80
80
|
|
81
81
|
private
|
82
82
|
|
83
|
-
sig { params(node: FileTree::Node, collected_nodes: T::Array[Node]).returns(T::Array[
|
83
|
+
sig { params(node: FileTree::Node, collected_nodes: T::Array[Node]).returns(T::Array[Node]) }
|
84
84
|
def collect_nodes(node, collected_nodes = [])
|
85
85
|
collected_nodes << node
|
86
86
|
node.children.values.each { |child| collect_nodes(child, collected_nodes) }
|
data/lib/spoom/sorbet.rb
CHANGED
@@ -11,73 +11,111 @@ require "open3"
|
|
11
11
|
|
12
12
|
module Spoom
|
13
13
|
module Sorbet
|
14
|
-
|
14
|
+
CONFIG_PATH = "sorbet/config"
|
15
|
+
GEM_PATH = Gem::Specification.find_by_name("sorbet-static").full_gem_path
|
16
|
+
BIN_PATH = (Pathname.new(GEM_PATH) / "libexec" / "sorbet").to_s
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
class << self
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
sig do
|
22
|
+
params(
|
23
|
+
arg: String,
|
24
|
+
path: String,
|
25
|
+
capture_err: T::Boolean,
|
26
|
+
sorbet_bin: T.nilable(String)
|
27
|
+
).returns([String, T::Boolean])
|
28
|
+
end
|
29
|
+
def srb(*arg, path: '.', capture_err: false, sorbet_bin: nil)
|
30
|
+
if sorbet_bin
|
31
|
+
arg.prepend(sorbet_bin)
|
32
|
+
else
|
33
|
+
arg.prepend("bundle", "exec", "srb")
|
31
34
|
end
|
35
|
+
T.unsafe(Spoom).exec(*arg, path: path, capture_err: capture_err)
|
32
36
|
end
|
33
|
-
[out || "", res]
|
34
|
-
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
sig do
|
39
|
+
params(
|
40
|
+
arg: String,
|
41
|
+
path: String,
|
42
|
+
capture_err: T::Boolean,
|
43
|
+
sorbet_bin: T.nilable(String)
|
44
|
+
).returns([String, T::Boolean])
|
45
|
+
end
|
46
|
+
def srb_tc(*arg, path: '.', capture_err: false, sorbet_bin: nil)
|
47
|
+
arg.prepend("tc") unless sorbet_bin
|
48
|
+
T.unsafe(self).srb(*arg, path: path, capture_err: capture_err, sorbet_bin: sorbet_bin)
|
49
|
+
end
|
40
50
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
51
|
+
# List all files typechecked by Sorbet from its `config`
|
52
|
+
sig { params(config: Config, path: String).returns(T::Array[String]) }
|
53
|
+
def srb_files(config, path: '.')
|
54
|
+
regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
|
55
|
+
exts = config.allowed_extensions.empty? ? ['.rb', '.rbi'] : config.allowed_extensions
|
56
|
+
Dir.glob((Pathname.new(path) / "**/*{#{exts.join(',')}}").to_s).reject do |f|
|
57
|
+
regs.any? { |re| re.match?(f) }
|
58
|
+
end.sort
|
59
|
+
end
|
50
60
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
61
|
+
sig do
|
62
|
+
params(
|
63
|
+
arg: String,
|
64
|
+
path: String,
|
65
|
+
capture_err: T::Boolean,
|
66
|
+
sorbet_bin: T.nilable(String)
|
67
|
+
).returns(T.nilable(String))
|
68
|
+
end
|
69
|
+
def srb_version(*arg, path: '.', capture_err: false, sorbet_bin: nil)
|
70
|
+
out, res = T.unsafe(self).srb_tc(
|
71
|
+
"--version",
|
72
|
+
*arg,
|
73
|
+
path: path,
|
74
|
+
capture_err: capture_err,
|
75
|
+
sorbet_bin: sorbet_bin
|
76
|
+
)
|
77
|
+
return nil unless res
|
78
|
+
out.split(" ")[2]
|
79
|
+
end
|
57
80
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
81
|
+
sig do
|
82
|
+
params(
|
83
|
+
arg: String,
|
84
|
+
path: String,
|
85
|
+
capture_err: T::Boolean,
|
86
|
+
sorbet_bin: T.nilable(String)
|
87
|
+
).returns(T.nilable(T::Hash[String, Integer]))
|
88
|
+
end
|
89
|
+
def srb_metrics(*arg, path: '.', capture_err: false, sorbet_bin: nil)
|
90
|
+
metrics_file = "metrics.tmp"
|
91
|
+
metrics_path = "#{path}/#{metrics_file}"
|
92
|
+
T.unsafe(self).srb_tc(
|
93
|
+
"--metrics-file",
|
94
|
+
metrics_file,
|
95
|
+
*arg,
|
96
|
+
path: path,
|
97
|
+
capture_err: capture_err,
|
98
|
+
sorbet_bin: sorbet_bin
|
99
|
+
)
|
100
|
+
if File.exist?(metrics_path)
|
101
|
+
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
102
|
+
File.delete(metrics_path)
|
103
|
+
return metrics
|
104
|
+
end
|
105
|
+
nil
|
67
106
|
end
|
68
|
-
nil
|
69
|
-
end
|
70
107
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
108
|
+
# Get `gem` version from the `Gemfile.lock` content
|
109
|
+
#
|
110
|
+
# Returns `nil` if `gem` cannot be found in the Gemfile.
|
111
|
+
sig { params(gem: String, path: String).returns(T.nilable(String)) }
|
112
|
+
def version_from_gemfile_lock(gem: 'sorbet', path: '.')
|
113
|
+
gemfile_path = "#{path}/Gemfile.lock"
|
114
|
+
return nil unless File.exist?(gemfile_path)
|
115
|
+
content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
|
116
|
+
return nil unless content
|
117
|
+
content[1]
|
118
|
+
end
|
81
119
|
end
|
82
120
|
end
|
83
121
|
end
|
data/lib/spoom/sorbet/config.rb
CHANGED
@@ -36,6 +36,36 @@ module Spoom
|
|
36
36
|
@allowed_extensions = T.let([], T::Array[String])
|
37
37
|
end
|
38
38
|
|
39
|
+
sig { returns(Config) }
|
40
|
+
def copy
|
41
|
+
new_config = Sorbet::Config.new
|
42
|
+
new_config.paths.concat(@paths)
|
43
|
+
new_config.ignore.concat(@ignore)
|
44
|
+
new_config.allowed_extensions.concat(@allowed_extensions)
|
45
|
+
new_config
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns self as a string of options that can be passed to Sorbet
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
# ~~~rb
|
52
|
+
# config = Sorbet::Config.new
|
53
|
+
# config.paths << "/foo"
|
54
|
+
# config.paths << "/bar"
|
55
|
+
# config.ignore << "/baz"
|
56
|
+
# config.allowed_extensions << ".rb"
|
57
|
+
#
|
58
|
+
# puts config.options_string # "/foo /bar --ignore /baz --allowed-extension .rb"
|
59
|
+
# ~~~
|
60
|
+
sig { returns(String) }
|
61
|
+
def options_string
|
62
|
+
opts = []
|
63
|
+
opts.concat(paths)
|
64
|
+
opts.concat(ignore.map { |p| "--ignore #{p}" })
|
65
|
+
opts.concat(allowed_extensions.map { |ext| "--allowed-extension #{ext}" })
|
66
|
+
opts.join(" ")
|
67
|
+
end
|
68
|
+
|
39
69
|
class << self
|
40
70
|
extend T::Sig
|
41
71
|
|
data/lib/spoom/sorbet/errors.rb
CHANGED
@@ -4,6 +4,8 @@
|
|
4
4
|
module Spoom
|
5
5
|
module Sorbet
|
6
6
|
module Errors
|
7
|
+
extend T::Sig
|
8
|
+
|
7
9
|
# Parse errors from Sorbet output
|
8
10
|
class Parser
|
9
11
|
extend T::Sig
|
@@ -123,6 +125,7 @@ module Spoom
|
|
123
125
|
@more = more
|
124
126
|
end
|
125
127
|
|
128
|
+
# By default errors are sorted by location
|
126
129
|
sig { params(other: T.untyped).returns(Integer) }
|
127
130
|
def <=>(other)
|
128
131
|
return 0 unless other.is_a?(Error)
|
@@ -134,6 +137,11 @@ module Spoom
|
|
134
137
|
"#{file}:#{line}: #{message} (#{code})"
|
135
138
|
end
|
136
139
|
end
|
140
|
+
|
141
|
+
sig { params(errors: T::Array[Error]).returns(T::Array[Error]) }
|
142
|
+
def self.sort_errors_by_code(errors)
|
143
|
+
errors.sort_by { |e| [e.code, e.file, e.line, e.message] }
|
144
|
+
end
|
137
145
|
end
|
138
146
|
end
|
139
147
|
end
|
data/lib/spoom/sorbet/lsp.rb
CHANGED
@@ -11,13 +11,9 @@ require_relative 'lsp/errors'
|
|
11
11
|
module Spoom
|
12
12
|
module LSP
|
13
13
|
class Client
|
14
|
-
def initialize(
|
14
|
+
def initialize(sorbet_bin, *sorbet_args, path: ".")
|
15
15
|
@id = 0
|
16
|
-
|
17
|
-
opts = {}
|
18
|
-
opts[:chdir] = path
|
19
|
-
@in, @out, @err, @status = Open3.popen3([sorbet_cmd, *sorbet_args].join(" "), opts)
|
20
|
-
end
|
16
|
+
@in, @out, @err, @status = T.unsafe(Open3).popen3(sorbet_bin, *sorbet_args, chdir: path)
|
21
17
|
end
|
22
18
|
|
23
19
|
def next_id
|
data/lib/spoom/sorbet/sigils.rb
CHANGED
@@ -56,14 +56,14 @@ module Spoom
|
|
56
56
|
sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
|
57
57
|
def self.file_strictness(path)
|
58
58
|
return nil unless File.exist?(path)
|
59
|
-
content = File.read(path)
|
59
|
+
content = File.read(path, encoding: Encoding::ASCII_8BIT)
|
60
60
|
strictness_in_content(content)
|
61
61
|
end
|
62
62
|
|
63
63
|
# changes the sigil in the file at the passed path to the specified new strictness
|
64
64
|
sig { params(path: T.any(String, Pathname), new_strictness: String).returns(T::Boolean) }
|
65
65
|
def self.change_sigil_in_file(path, new_strictness)
|
66
|
-
content = File.read(path)
|
66
|
+
content = File.read(path, encoding: Encoding::ASCII_8BIT)
|
67
67
|
new_content = update_sigil(content, new_strictness)
|
68
68
|
|
69
69
|
File.write(path, new_content)
|
@@ -87,7 +87,7 @@ module Spoom
|
|
87
87
|
extension: String
|
88
88
|
).returns(T::Array[String])
|
89
89
|
end
|
90
|
-
def self.files_with_sigil_strictness(directory, strictness, extension
|
90
|
+
def self.files_with_sigil_strictness(directory, strictness, extension: ".rb")
|
91
91
|
paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
|
92
92
|
paths.filter do |path|
|
93
93
|
file_strictness(path) == strictness
|
data/lib/spoom/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spoom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Terrasa
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -128,7 +128,6 @@ files:
|
|
128
128
|
- lib/spoom/cli/helper.rb
|
129
129
|
- lib/spoom/cli/lsp.rb
|
130
130
|
- lib/spoom/cli/run.rb
|
131
|
-
- lib/spoom/config.rb
|
132
131
|
- lib/spoom/coverage.rb
|
133
132
|
- lib/spoom/coverage/d3.rb
|
134
133
|
- lib/spoom/coverage/d3/base.rb
|
data/lib/spoom/config.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'pathname'
|
5
|
-
|
6
|
-
module Spoom
|
7
|
-
module Config
|
8
|
-
SORBET_CONFIG = "sorbet/config"
|
9
|
-
SORBET_GEM_PATH = Gem::Specification.find_by_name("sorbet-static").full_gem_path
|
10
|
-
SORBET_PATH = (Pathname.new(SORBET_GEM_PATH) / "libexec" / "sorbet").to_s
|
11
|
-
SPOOM_PATH = (Pathname.new(__FILE__) / ".." / ".." / "..").to_s
|
12
|
-
end
|
13
|
-
end
|