spoom 1.1.15 → 1.2.0
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 +1 -1
- data/lib/spoom/cli/bump.rb +46 -25
- data/lib/spoom/cli/config.rb +4 -3
- data/lib/spoom/cli/coverage.rb +33 -21
- data/lib/spoom/cli/helper.rb +11 -35
- data/lib/spoom/cli/lsp.rb +4 -2
- data/lib/spoom/cli/run.rb +17 -10
- data/lib/spoom/cli.rb +4 -7
- data/lib/spoom/context/bundle.rb +58 -0
- data/lib/spoom/context/exec.rb +50 -0
- data/lib/spoom/context/file_system.rb +93 -0
- data/lib/spoom/context/git.rb +121 -0
- data/lib/spoom/context/sorbet.rb +136 -0
- data/lib/spoom/context.rb +17 -193
- data/lib/spoom/coverage/snapshot.rb +1 -1
- data/lib/spoom/coverage.rb +24 -37
- data/lib/spoom/sorbet/errors.rb +10 -7
- data/lib/spoom/sorbet/lsp/structures.rb +31 -28
- data/lib/spoom/sorbet/sigils.rb +11 -8
- data/lib/spoom/sorbet.rb +17 -101
- data/lib/spoom/timeline.rb +5 -8
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +0 -52
- metadata +8 -4
- data/lib/spoom/git.rb +0 -130
@@ -273,34 +273,37 @@ module Spoom
|
|
273
273
|
SYMBOL_KINDS[kind] || "<unknown:#{kind}>"
|
274
274
|
end
|
275
275
|
|
276
|
-
SYMBOL_KINDS = T.let(
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
276
|
+
SYMBOL_KINDS = T.let(
|
277
|
+
{
|
278
|
+
1 => "file",
|
279
|
+
2 => "module",
|
280
|
+
3 => "namespace",
|
281
|
+
4 => "package",
|
282
|
+
5 => "class",
|
283
|
+
6 => "def",
|
284
|
+
7 => "property",
|
285
|
+
8 => "field",
|
286
|
+
9 => "constructor",
|
287
|
+
10 => "enum",
|
288
|
+
11 => "interface",
|
289
|
+
12 => "function",
|
290
|
+
13 => "variable",
|
291
|
+
14 => "const",
|
292
|
+
15 => "string",
|
293
|
+
16 => "number",
|
294
|
+
17 => "boolean",
|
295
|
+
18 => "array",
|
296
|
+
19 => "object",
|
297
|
+
20 => "key",
|
298
|
+
21 => "null",
|
299
|
+
22 => "enum_member",
|
300
|
+
23 => "struct",
|
301
|
+
24 => "event",
|
302
|
+
25 => "operator",
|
303
|
+
26 => "type_parameter",
|
304
|
+
},
|
305
|
+
T::Hash[Integer, String],
|
306
|
+
)
|
304
307
|
end
|
305
308
|
|
306
309
|
class SymbolPrinter < Printer
|
data/lib/spoom/sorbet/sigils.rb
CHANGED
@@ -16,14 +16,17 @@ module Spoom
|
|
16
16
|
STRICTNESS_STRONG = "strong"
|
17
17
|
STRICTNESS_INTERNAL = "__STDLIB_INTERNAL"
|
18
18
|
|
19
|
-
VALID_STRICTNESS = T.let(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
VALID_STRICTNESS = T.let(
|
20
|
+
[
|
21
|
+
STRICTNESS_IGNORE,
|
22
|
+
STRICTNESS_FALSE,
|
23
|
+
STRICTNESS_TRUE,
|
24
|
+
STRICTNESS_STRICT,
|
25
|
+
STRICTNESS_STRONG,
|
26
|
+
STRICTNESS_INTERNAL,
|
27
|
+
].freeze,
|
28
|
+
T::Array[String],
|
29
|
+
)
|
27
30
|
|
28
31
|
SIGIL_REGEXP = T.let(/^#[\ t]*typed[\ t]*:[ \t]*(\w*)[ \t]*/.freeze, Regexp)
|
29
32
|
|
data/lib/spoom/sorbet.rb
CHANGED
@@ -11,117 +11,33 @@ require "open3"
|
|
11
11
|
|
12
12
|
module Spoom
|
13
13
|
module Sorbet
|
14
|
-
|
15
|
-
GEM_PATH = T.let(Gem::Specification.find_by_name("sorbet-static").full_gem_path, String)
|
16
|
-
BIN_PATH = T.let((Pathname.new(GEM_PATH) / "libexec" / "sorbet").to_s, String)
|
17
|
-
|
18
|
-
SEGFAULT_CODE = 139
|
19
|
-
|
20
|
-
class << self
|
14
|
+
class Error < StandardError
|
21
15
|
extend T::Sig
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
arg: String,
|
26
|
-
path: String,
|
27
|
-
capture_err: T::Boolean,
|
28
|
-
sorbet_bin: T.nilable(String),
|
29
|
-
).returns(ExecResult)
|
30
|
-
end
|
31
|
-
def srb(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
32
|
-
if sorbet_bin
|
33
|
-
arg.prepend(sorbet_bin)
|
34
|
-
else
|
35
|
-
arg.prepend("bundle", "exec", "srb")
|
36
|
-
end
|
37
|
-
Spoom.exec(*T.unsafe(arg), path: path, capture_err: capture_err)
|
38
|
-
end
|
17
|
+
class Killed < Error; end
|
18
|
+
class Segfault < Error; end
|
39
19
|
|
40
|
-
sig
|
41
|
-
|
42
|
-
arg: String,
|
43
|
-
path: String,
|
44
|
-
capture_err: T::Boolean,
|
45
|
-
sorbet_bin: T.nilable(String),
|
46
|
-
).returns(ExecResult)
|
47
|
-
end
|
48
|
-
def srb_tc(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
49
|
-
arg.prepend("tc") unless sorbet_bin
|
50
|
-
srb(*T.unsafe(arg), path: path, capture_err: capture_err, sorbet_bin: sorbet_bin)
|
51
|
-
end
|
52
|
-
|
53
|
-
# List all files typechecked by Sorbet from its `config`
|
54
|
-
sig { params(config: Config, path: String).returns(T::Array[String]) }
|
55
|
-
def srb_files(config, path: ".")
|
56
|
-
regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
|
57
|
-
exts = config.allowed_extensions.empty? ? [".rb", ".rbi"] : config.allowed_extensions
|
58
|
-
Dir.glob((Pathname.new(path) / "**/*{#{exts.join(",")}}").to_s).reject do |f|
|
59
|
-
regs.any? { |re| re.match?(f) }
|
60
|
-
end.sort
|
61
|
-
end
|
20
|
+
sig { returns(ExecResult) }
|
21
|
+
attr_reader :result
|
62
22
|
|
63
23
|
sig do
|
64
24
|
params(
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
sorbet_bin: T.nilable(String),
|
69
|
-
).returns(T.nilable(String))
|
25
|
+
message: String,
|
26
|
+
result: ExecResult,
|
27
|
+
).void
|
70
28
|
end
|
71
|
-
def
|
72
|
-
|
73
|
-
"--no-config",
|
74
|
-
"--version",
|
75
|
-
*arg,
|
76
|
-
path: path,
|
77
|
-
capture_err: capture_err,
|
78
|
-
sorbet_bin: sorbet_bin,
|
79
|
-
), ExecResult)
|
80
|
-
return nil unless result.status
|
29
|
+
def initialize(message, result)
|
30
|
+
super(message)
|
81
31
|
|
82
|
-
result
|
32
|
+
@result = result
|
83
33
|
end
|
34
|
+
end
|
84
35
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
path: String,
|
89
|
-
capture_err: T::Boolean,
|
90
|
-
sorbet_bin: T.nilable(String),
|
91
|
-
).returns(T.nilable(T::Hash[String, Integer]))
|
92
|
-
end
|
93
|
-
def srb_metrics(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
94
|
-
metrics_file = "metrics.tmp"
|
95
|
-
metrics_path = "#{path}/#{metrics_file}"
|
96
|
-
T.unsafe(self).srb_tc(
|
97
|
-
"--metrics-file",
|
98
|
-
metrics_file,
|
99
|
-
*arg,
|
100
|
-
path: path,
|
101
|
-
capture_err: capture_err,
|
102
|
-
sorbet_bin: sorbet_bin,
|
103
|
-
)
|
104
|
-
if File.exist?(metrics_path)
|
105
|
-
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
106
|
-
File.delete(metrics_path)
|
107
|
-
return metrics
|
108
|
-
end
|
109
|
-
nil
|
110
|
-
end
|
111
|
-
|
112
|
-
# Get `gem` version from the `Gemfile.lock` content
|
113
|
-
#
|
114
|
-
# Returns `nil` if `gem` cannot be found in the Gemfile.
|
115
|
-
sig { params(gem: String, path: String).returns(T.nilable(String)) }
|
116
|
-
def version_from_gemfile_lock(gem: "sorbet", path: ".")
|
117
|
-
gemfile_path = "#{path}/Gemfile.lock"
|
118
|
-
return nil unless File.exist?(gemfile_path)
|
119
|
-
|
120
|
-
content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
|
121
|
-
return nil unless content
|
36
|
+
CONFIG_PATH = "sorbet/config"
|
37
|
+
GEM_PATH = T.let(Gem::Specification.find_by_name("sorbet-static").full_gem_path, String)
|
38
|
+
BIN_PATH = T.let((Pathname.new(GEM_PATH) / "libexec" / "sorbet").to_s, String)
|
122
39
|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
40
|
+
KILLED_CODE = 137
|
41
|
+
SEGFAULT_CODE = 139
|
126
42
|
end
|
127
43
|
end
|
data/lib/spoom/timeline.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require_relative "git"
|
5
|
-
|
6
4
|
module Spoom
|
7
5
|
class Timeline
|
8
6
|
extend T::Sig
|
9
7
|
|
10
|
-
sig { params(
|
11
|
-
def initialize(from, to
|
8
|
+
sig { params(context: Context, from: Time, to: Time).void }
|
9
|
+
def initialize(context, from, to)
|
10
|
+
@context = context
|
12
11
|
@from = from
|
13
12
|
@to = to
|
14
|
-
@path = path
|
15
13
|
end
|
16
14
|
|
17
15
|
# Return one commit for each month between `from` and `to`
|
@@ -37,17 +35,16 @@ module Spoom
|
|
37
35
|
sig { params(dates: T::Array[Time]).returns(T::Array[Git::Commit]) }
|
38
36
|
def commits_for_dates(dates)
|
39
37
|
dates.map do |t|
|
40
|
-
result =
|
38
|
+
result = @context.git_log(
|
41
39
|
"--since='#{t}'",
|
42
40
|
"--until='#{t.to_date.next_month}'",
|
43
41
|
"--format='format:%h %at'",
|
44
42
|
"--author-date-order",
|
45
43
|
"-1",
|
46
|
-
path: @path,
|
47
44
|
)
|
48
45
|
next if result.out.empty?
|
49
46
|
|
50
|
-
Git.
|
47
|
+
Spoom::Git::Commit.parse_line(result.out.strip)
|
51
48
|
end.compact.uniq(&:sha)
|
52
49
|
end
|
53
50
|
end
|
data/lib/spoom/version.rb
CHANGED
data/lib/spoom.rb
CHANGED
@@ -10,58 +10,6 @@ module Spoom
|
|
10
10
|
SPOOM_PATH = T.let((Pathname.new(__FILE__) / ".." / "..").to_s, String)
|
11
11
|
|
12
12
|
class Error < StandardError; end
|
13
|
-
|
14
|
-
class ExecResult < T::Struct
|
15
|
-
extend T::Sig
|
16
|
-
|
17
|
-
const :out, String
|
18
|
-
const :err, String
|
19
|
-
const :status, T::Boolean
|
20
|
-
const :exit_code, Integer
|
21
|
-
|
22
|
-
sig { returns(String) }
|
23
|
-
def to_s
|
24
|
-
<<~STR
|
25
|
-
########## STDOUT ##########
|
26
|
-
#{out.empty? ? "<empty>" : out}
|
27
|
-
########## STDERR ##########
|
28
|
-
#{err.empty? ? "<empty>" : err}
|
29
|
-
########## STATUS: #{status} ##########
|
30
|
-
STR
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class << self
|
35
|
-
extend T::Sig
|
36
|
-
|
37
|
-
sig do
|
38
|
-
params(
|
39
|
-
cmd: String,
|
40
|
-
arg: String,
|
41
|
-
path: String,
|
42
|
-
capture_err: T::Boolean,
|
43
|
-
).returns(ExecResult)
|
44
|
-
end
|
45
|
-
def exec(cmd, *arg, path: ".", capture_err: false)
|
46
|
-
if capture_err
|
47
|
-
stdout, stderr, status = T.unsafe(Open3).capture3([cmd, *arg].join(" "), chdir: path)
|
48
|
-
ExecResult.new(
|
49
|
-
out: stdout,
|
50
|
-
err: stderr,
|
51
|
-
status: status.success?,
|
52
|
-
exit_code: status.exitstatus,
|
53
|
-
)
|
54
|
-
else
|
55
|
-
stdout, status = T.unsafe(Open3).capture2([cmd, *arg].join(" "), chdir: path)
|
56
|
-
ExecResult.new(
|
57
|
-
out: stdout,
|
58
|
-
err: "",
|
59
|
-
status: status.success?,
|
60
|
-
exit_code: status.exitstatus,
|
61
|
-
)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
13
|
end
|
66
14
|
|
67
15
|
require "spoom/context"
|
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.
|
4
|
+
version: 1.2.0
|
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: 2023-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -130,6 +130,11 @@ files:
|
|
130
130
|
- lib/spoom/cli/run.rb
|
131
131
|
- lib/spoom/colors.rb
|
132
132
|
- lib/spoom/context.rb
|
133
|
+
- lib/spoom/context/bundle.rb
|
134
|
+
- lib/spoom/context/exec.rb
|
135
|
+
- lib/spoom/context/file_system.rb
|
136
|
+
- lib/spoom/context/git.rb
|
137
|
+
- lib/spoom/context/sorbet.rb
|
133
138
|
- lib/spoom/coverage.rb
|
134
139
|
- lib/spoom/coverage/d3.rb
|
135
140
|
- lib/spoom/coverage/d3/base.rb
|
@@ -139,7 +144,6 @@ files:
|
|
139
144
|
- lib/spoom/coverage/report.rb
|
140
145
|
- lib/spoom/coverage/snapshot.rb
|
141
146
|
- lib/spoom/file_tree.rb
|
142
|
-
- lib/spoom/git.rb
|
143
147
|
- lib/spoom/printer.rb
|
144
148
|
- lib/spoom/sorbet.rb
|
145
149
|
- lib/spoom/sorbet/config.rb
|
@@ -175,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
175
179
|
- !ruby/object:Gem::Version
|
176
180
|
version: '0'
|
177
181
|
requirements: []
|
178
|
-
rubygems_version: 3.
|
182
|
+
rubygems_version: 3.4.9
|
179
183
|
signing_key:
|
180
184
|
specification_version: 4
|
181
185
|
summary: Useful tools for Sorbet enthusiasts.
|
data/lib/spoom/git.rb
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "time"
|
5
|
-
|
6
|
-
module Spoom
|
7
|
-
# Execute git commands
|
8
|
-
module Git
|
9
|
-
class Commit < T::Struct
|
10
|
-
extend T::Sig
|
11
|
-
|
12
|
-
const :sha, String
|
13
|
-
const :time, Time
|
14
|
-
|
15
|
-
sig { returns(Integer) }
|
16
|
-
def timestamp
|
17
|
-
time.to_i
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
class << self
|
22
|
-
extend T::Sig
|
23
|
-
|
24
|
-
# Execute a `command`
|
25
|
-
sig { params(command: String, arg: String, path: String).returns(ExecResult) }
|
26
|
-
def exec(command, *arg, path: ".")
|
27
|
-
return ExecResult.new(
|
28
|
-
out: "",
|
29
|
-
err: "Error: `#{path}` is not a directory.",
|
30
|
-
status: false,
|
31
|
-
exit_code: 1,
|
32
|
-
) unless File.directory?(path)
|
33
|
-
|
34
|
-
T.unsafe(Open3).popen3(command, *arg, chdir: path) do |_, stdout, stderr, thread|
|
35
|
-
status = T.cast(thread.value, Process::Status)
|
36
|
-
ExecResult.new(
|
37
|
-
out: stdout.read,
|
38
|
-
err: stderr.read,
|
39
|
-
status: T.must(status.success?),
|
40
|
-
exit_code: T.must(status.exitstatus),
|
41
|
-
)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Git commands
|
46
|
-
|
47
|
-
sig { params(arg: String, path: String).returns(ExecResult) }
|
48
|
-
def checkout(*arg, path: ".")
|
49
|
-
exec("git checkout -q #{arg.join(" ")}", path: path)
|
50
|
-
end
|
51
|
-
|
52
|
-
sig { params(arg: String, path: String).returns(ExecResult) }
|
53
|
-
def diff(*arg, path: ".")
|
54
|
-
exec("git diff #{arg.join(" ")}", path: path)
|
55
|
-
end
|
56
|
-
|
57
|
-
sig { params(arg: String, path: String).returns(ExecResult) }
|
58
|
-
def log(*arg, path: ".")
|
59
|
-
exec("git log #{arg.join(" ")}", path: path)
|
60
|
-
end
|
61
|
-
|
62
|
-
sig { params(arg: String, path: String).returns(ExecResult) }
|
63
|
-
def show(*arg, path: ".")
|
64
|
-
exec("git show #{arg.join(" ")}", path: path)
|
65
|
-
end
|
66
|
-
|
67
|
-
sig { params(path: String).returns(T.nilable(String)) }
|
68
|
-
def current_branch(path: ".")
|
69
|
-
result = exec("git branch --show-current", path: path)
|
70
|
-
return nil unless result.status
|
71
|
-
|
72
|
-
result.out.strip
|
73
|
-
end
|
74
|
-
|
75
|
-
# Utils
|
76
|
-
|
77
|
-
# Get the last commit in the currently checked out branch
|
78
|
-
sig { params(path: String, short_sha: T::Boolean).returns(T.nilable(Commit)) }
|
79
|
-
def last_commit(path: ".", short_sha: true)
|
80
|
-
result = log("HEAD --format='%#{short_sha ? "h" : "H"} %at' -1", path: path)
|
81
|
-
return nil unless result.status
|
82
|
-
|
83
|
-
out = result.out.strip
|
84
|
-
return nil if out.empty?
|
85
|
-
|
86
|
-
parse_commit(out)
|
87
|
-
end
|
88
|
-
|
89
|
-
# Is there uncommited changes in `path`?
|
90
|
-
sig { params(path: String).returns(T::Boolean) }
|
91
|
-
def workdir_clean?(path: ".")
|
92
|
-
diff("HEAD", path: path).out.empty?
|
93
|
-
end
|
94
|
-
|
95
|
-
# Get the commit introducing the `sorbet/config` file
|
96
|
-
sig { params(path: String).returns(T.nilable(Commit)) }
|
97
|
-
def sorbet_intro_commit(path: ".")
|
98
|
-
result = log("--diff-filter=A --format='%h %at' -1 -- sorbet/config", path: path)
|
99
|
-
return nil unless result.status
|
100
|
-
|
101
|
-
out = result.out.strip
|
102
|
-
return nil if out.empty?
|
103
|
-
|
104
|
-
parse_commit(out)
|
105
|
-
end
|
106
|
-
|
107
|
-
# Get the commit removing the `sorbet/config` file
|
108
|
-
sig { params(path: String).returns(T.nilable(Commit)) }
|
109
|
-
def sorbet_removal_commit(path: ".")
|
110
|
-
result = log("--diff-filter=D --format='%h %at' -1 -- sorbet/config", path: path)
|
111
|
-
return nil unless result.status
|
112
|
-
|
113
|
-
out = result.out.strip
|
114
|
-
return nil if out.empty?
|
115
|
-
|
116
|
-
parse_commit(out)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Parse a line formated as `%h %at` into a `Commit`
|
120
|
-
sig { params(string: String).returns(T.nilable(Commit)) }
|
121
|
-
def parse_commit(string)
|
122
|
-
sha, epoch = string.split(" ", 2)
|
123
|
-
return nil unless sha && epoch
|
124
|
-
|
125
|
-
time = Time.strptime(epoch, "%s")
|
126
|
-
Commit.new(sha: sha, time: time)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|