spoom 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +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
|