spoom 1.1.16 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/spoom/cli/bump.rb +3 -11
- data/lib/spoom/cli/config.rb +4 -3
- data/lib/spoom/cli/coverage.rb +14 -15
- data/lib/spoom/cli/helper.rb +11 -21
- data/lib/spoom/cli/lsp.rb +4 -2
- data/lib/spoom/cli/run.rb +3 -7
- 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 +16 -200
- data/lib/spoom/coverage.rb +20 -35
- data/lib/spoom/sorbet.rb +0 -119
- 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 -109
@@ -0,0 +1,136 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
class Context
|
6
|
+
# Sorbet features for a context
|
7
|
+
module Sorbet
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
requires_ancestor { Context }
|
12
|
+
|
13
|
+
# Run `bundle exec srb` in this context directory
|
14
|
+
sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(ExecResult) }
|
15
|
+
def srb(*arg, sorbet_bin: nil, capture_err: true)
|
16
|
+
res = if sorbet_bin
|
17
|
+
exec("#{sorbet_bin} #{arg.join(" ")}", capture_err: capture_err)
|
18
|
+
else
|
19
|
+
bundle_exec("srb #{arg.join(" ")}", capture_err: capture_err)
|
20
|
+
end
|
21
|
+
|
22
|
+
case res.exit_code
|
23
|
+
when Spoom::Sorbet::KILLED_CODE
|
24
|
+
raise Spoom::Sorbet::Error::Killed.new("Sorbet was killed.", res)
|
25
|
+
when Spoom::Sorbet::SEGFAULT_CODE
|
26
|
+
raise Spoom::Sorbet::Error::Segfault.new("Sorbet segfaulted.", res)
|
27
|
+
end
|
28
|
+
|
29
|
+
res
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(ExecResult) }
|
33
|
+
def srb_tc(*arg, sorbet_bin: nil, capture_err: true)
|
34
|
+
arg.prepend("tc") unless sorbet_bin
|
35
|
+
T.unsafe(self).srb(*arg, sorbet_bin: sorbet_bin, capture_err: capture_err)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig do
|
39
|
+
params(
|
40
|
+
arg: String,
|
41
|
+
sorbet_bin: T.nilable(String),
|
42
|
+
capture_err: T::Boolean,
|
43
|
+
).returns(T.nilable(T::Hash[String, Integer]))
|
44
|
+
end
|
45
|
+
def srb_metrics(*arg, sorbet_bin: nil, capture_err: true)
|
46
|
+
metrics_file = "metrics.tmp"
|
47
|
+
|
48
|
+
T.unsafe(self).srb_tc(
|
49
|
+
"--metrics-file",
|
50
|
+
metrics_file,
|
51
|
+
*arg,
|
52
|
+
sorbet_bin: sorbet_bin,
|
53
|
+
capture_err: capture_err,
|
54
|
+
)
|
55
|
+
return nil unless file?(metrics_file)
|
56
|
+
|
57
|
+
metrics_path = absolute_path_to(metrics_file)
|
58
|
+
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
59
|
+
remove!(metrics_file)
|
60
|
+
metrics
|
61
|
+
end
|
62
|
+
|
63
|
+
# List all files typechecked by Sorbet from its `config`
|
64
|
+
sig { params(with_config: T.nilable(Spoom::Sorbet::Config)).returns(T::Array[String]) }
|
65
|
+
def srb_files(with_config: nil)
|
66
|
+
config = with_config || sorbet_config
|
67
|
+
regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
|
68
|
+
exts = config.allowed_extensions.empty? ? [".rb", ".rbi"] : config.allowed_extensions
|
69
|
+
glob("**/*{#{exts.join(",")}}").reject do |f|
|
70
|
+
regs.any? { |re| re.match?(f) }
|
71
|
+
end.sort
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(T.nilable(String)) }
|
75
|
+
def srb_version(*arg, sorbet_bin: nil, capture_err: true)
|
76
|
+
res = T.unsafe(self).srb_tc("--no-config", "--version", *arg, sorbet_bin: sorbet_bin, capture_err: capture_err)
|
77
|
+
return nil unless res.status
|
78
|
+
|
79
|
+
res.out.split(" ")[2]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Does this context has a `sorbet/config` file?
|
83
|
+
sig { returns(T::Boolean) }
|
84
|
+
def has_sorbet_config?
|
85
|
+
file?(Spoom::Sorbet::CONFIG_PATH)
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { returns(Spoom::Sorbet::Config) }
|
89
|
+
def sorbet_config
|
90
|
+
Spoom::Sorbet::Config.parse_string(read_sorbet_config)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Read the contents of `sorbet/config` in this context directory
|
94
|
+
sig { returns(String) }
|
95
|
+
def read_sorbet_config
|
96
|
+
read(Spoom::Sorbet::CONFIG_PATH)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Set the `contents` of `sorbet/config` in this context directory
|
100
|
+
sig { params(contents: String, append: T::Boolean).void }
|
101
|
+
def write_sorbet_config!(contents, append: false)
|
102
|
+
write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
|
106
|
+
sig { params(relative_path: String).returns(T.nilable(String)) }
|
107
|
+
def read_file_strictness(relative_path)
|
108
|
+
Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Get the commit introducing the `sorbet/config` file
|
112
|
+
sig { returns(T.nilable(Spoom::Git::Commit)) }
|
113
|
+
def sorbet_intro_commit
|
114
|
+
res = git_log("--diff-filter=A --format='%h %at' -1 -- sorbet/config")
|
115
|
+
return nil unless res.status
|
116
|
+
|
117
|
+
out = res.out.strip
|
118
|
+
return nil if out.empty?
|
119
|
+
|
120
|
+
Spoom::Git::Commit.parse_line(out)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Get the commit removing the `sorbet/config` file
|
124
|
+
sig { returns(T.nilable(Spoom::Git::Commit)) }
|
125
|
+
def sorbet_removal_commit
|
126
|
+
res = git_log("--diff-filter=D --format='%h %at' -1 -- sorbet/config")
|
127
|
+
return nil unless res.status
|
128
|
+
|
129
|
+
out = res.out.strip
|
130
|
+
return nil if out.empty?
|
131
|
+
|
132
|
+
Spoom::Git::Commit.parse_line(out)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/spoom/context.rb
CHANGED
@@ -3,8 +3,15 @@
|
|
3
3
|
|
4
4
|
require "fileutils"
|
5
5
|
require "open3"
|
6
|
+
require "time"
|
6
7
|
require "tmpdir"
|
7
8
|
|
9
|
+
require_relative "context/bundle"
|
10
|
+
require_relative "context/exec"
|
11
|
+
require_relative "context/file_system"
|
12
|
+
require_relative "context/git"
|
13
|
+
require_relative "context/sorbet"
|
14
|
+
|
8
15
|
module Spoom
|
9
16
|
# An abstraction to a Ruby project context
|
10
17
|
#
|
@@ -13,9 +20,11 @@ module Spoom
|
|
13
20
|
class Context
|
14
21
|
extend T::Sig
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
23
|
+
include Bundle
|
24
|
+
include Exec
|
25
|
+
include FileSystem
|
26
|
+
include Git
|
27
|
+
include Sorbet
|
19
28
|
|
20
29
|
class << self
|
21
30
|
extend T::Sig
|
@@ -30,6 +39,10 @@ module Spoom
|
|
30
39
|
end
|
31
40
|
end
|
32
41
|
|
42
|
+
# The absolute path to the directory this context is about
|
43
|
+
sig { returns(String) }
|
44
|
+
attr_reader :absolute_path
|
45
|
+
|
33
46
|
# Create a new context about `absolute_path`
|
34
47
|
#
|
35
48
|
# The directory will not be created if it doesn't exist.
|
@@ -38,202 +51,5 @@ module Spoom
|
|
38
51
|
def initialize(absolute_path)
|
39
52
|
@absolute_path = T.let(::File.expand_path(absolute_path), String)
|
40
53
|
end
|
41
|
-
|
42
|
-
# Returns the absolute path to `relative_path` in the context's directory
|
43
|
-
sig { params(relative_path: String).returns(String) }
|
44
|
-
def absolute_path_to(relative_path)
|
45
|
-
File.join(@absolute_path, relative_path)
|
46
|
-
end
|
47
|
-
|
48
|
-
# File System
|
49
|
-
|
50
|
-
# Does the context directory at `absolute_path` exist and is a directory?
|
51
|
-
sig { returns(T::Boolean) }
|
52
|
-
def exist?
|
53
|
-
File.directory?(@absolute_path)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Create the context directory at `absolute_path`
|
57
|
-
sig { void }
|
58
|
-
def mkdir!
|
59
|
-
FileUtils.rm_rf(@absolute_path)
|
60
|
-
FileUtils.mkdir_p(@absolute_path)
|
61
|
-
end
|
62
|
-
|
63
|
-
# List all files in this context matching `pattern`
|
64
|
-
sig { params(pattern: String).returns(T::Array[String]) }
|
65
|
-
def glob(pattern = "**/*")
|
66
|
-
Dir.glob(absolute_path_to(pattern)).map do |path|
|
67
|
-
Pathname.new(path).relative_path_from(@absolute_path).to_s
|
68
|
-
end.sort
|
69
|
-
end
|
70
|
-
|
71
|
-
# List all files at the top level of this context directory
|
72
|
-
sig { returns(T::Array[String]) }
|
73
|
-
def list
|
74
|
-
glob("*")
|
75
|
-
end
|
76
|
-
|
77
|
-
# Does `relative_path` point to an existing file in this context directory?
|
78
|
-
sig { params(relative_path: String).returns(T::Boolean) }
|
79
|
-
def file?(relative_path)
|
80
|
-
File.file?(absolute_path_to(relative_path))
|
81
|
-
end
|
82
|
-
|
83
|
-
# Return the contents of the file at `relative_path` in this context directory
|
84
|
-
#
|
85
|
-
# Will raise if the file doesn't exist.
|
86
|
-
sig { params(relative_path: String).returns(String) }
|
87
|
-
def read(relative_path)
|
88
|
-
File.read(absolute_path_to(relative_path))
|
89
|
-
end
|
90
|
-
|
91
|
-
# Write `contents` in the file at `relative_path` in this context directory
|
92
|
-
#
|
93
|
-
# Append to the file if `append` is true.
|
94
|
-
sig { params(relative_path: String, contents: String, append: T::Boolean).void }
|
95
|
-
def write!(relative_path, contents = "", append: false)
|
96
|
-
absolute_path = absolute_path_to(relative_path)
|
97
|
-
FileUtils.mkdir_p(File.dirname(absolute_path))
|
98
|
-
File.write(absolute_path, contents, mode: append ? "a" : "w")
|
99
|
-
end
|
100
|
-
|
101
|
-
# Remove the path at `relative_path` (recursive + force) in this context directory
|
102
|
-
sig { params(relative_path: String).void }
|
103
|
-
def remove!(relative_path)
|
104
|
-
FileUtils.rm_rf(absolute_path_to(relative_path))
|
105
|
-
end
|
106
|
-
|
107
|
-
# Move the file or directory from `from_relative_path` to `to_relative_path`
|
108
|
-
sig { params(from_relative_path: String, to_relative_path: String).void }
|
109
|
-
def move!(from_relative_path, to_relative_path)
|
110
|
-
destination_path = absolute_path_to(to_relative_path)
|
111
|
-
FileUtils.mkdir_p(File.dirname(destination_path))
|
112
|
-
FileUtils.mv(absolute_path_to(from_relative_path), destination_path)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Delete this context and its content
|
116
|
-
#
|
117
|
-
# Warning: it will `rm -rf` the context directory on the file system.
|
118
|
-
sig { void }
|
119
|
-
def destroy!
|
120
|
-
FileUtils.rm_rf(@absolute_path)
|
121
|
-
end
|
122
|
-
|
123
|
-
# Execution
|
124
|
-
|
125
|
-
# Run a command in this context directory
|
126
|
-
sig { params(command: String, capture_err: T::Boolean).returns(ExecResult) }
|
127
|
-
def exec(command, capture_err: true)
|
128
|
-
Bundler.with_unbundled_env do
|
129
|
-
opts = T.let({ chdir: @absolute_path }, T::Hash[Symbol, T.untyped])
|
130
|
-
|
131
|
-
if capture_err
|
132
|
-
out, err, status = Open3.capture3(command, opts)
|
133
|
-
ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
134
|
-
else
|
135
|
-
out, status = Open3.capture2(command, opts)
|
136
|
-
ExecResult.new(out: out, err: nil, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
# Bundle
|
142
|
-
|
143
|
-
# Read the `contents` of the Gemfile in this context directory
|
144
|
-
sig { returns(T.nilable(String)) }
|
145
|
-
def read_gemfile
|
146
|
-
read("Gemfile")
|
147
|
-
end
|
148
|
-
|
149
|
-
# Set the `contents` of the Gemfile in this context directory
|
150
|
-
sig { params(contents: String, append: T::Boolean).void }
|
151
|
-
def write_gemfile!(contents, append: false)
|
152
|
-
write!("Gemfile", contents, append: append)
|
153
|
-
end
|
154
|
-
|
155
|
-
# Run a command with `bundle` in this context directory
|
156
|
-
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
157
|
-
def bundle(command, version: nil)
|
158
|
-
command = "_#{version}_ #{command}" if version
|
159
|
-
exec("bundle #{command}")
|
160
|
-
end
|
161
|
-
|
162
|
-
# Run `bundle install` in this context directory
|
163
|
-
sig { params(version: T.nilable(String)).returns(ExecResult) }
|
164
|
-
def bundle_install!(version: nil)
|
165
|
-
bundle("install", version: version)
|
166
|
-
end
|
167
|
-
|
168
|
-
# Run a command `bundle exec` in this context directory
|
169
|
-
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
170
|
-
def bundle_exec(command, version: nil)
|
171
|
-
bundle("exec #{command}", version: version)
|
172
|
-
end
|
173
|
-
|
174
|
-
# Git
|
175
|
-
|
176
|
-
# Run a command prefixed by `git` in this context directory
|
177
|
-
sig { params(command: String).returns(ExecResult) }
|
178
|
-
def git(command)
|
179
|
-
exec("git #{command}")
|
180
|
-
end
|
181
|
-
|
182
|
-
# Run `git init` in this context directory
|
183
|
-
#
|
184
|
-
# Warning: passing a branch will run `git init -b <branch>` which is only available in git 2.28+.
|
185
|
-
# In older versions, use `git_init!` followed by `git("checkout -b <branch>")`.
|
186
|
-
sig { params(branch: T.nilable(String)).returns(ExecResult) }
|
187
|
-
def git_init!(branch: nil)
|
188
|
-
if branch
|
189
|
-
git("init -b #{branch}")
|
190
|
-
else
|
191
|
-
git("init")
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
# Run `git checkout` in this context directory
|
196
|
-
sig { params(ref: String).returns(ExecResult) }
|
197
|
-
def git_checkout!(ref: "main")
|
198
|
-
git("checkout #{ref}")
|
199
|
-
end
|
200
|
-
|
201
|
-
# Get the current git branch in this context directory
|
202
|
-
sig { returns(T.nilable(String)) }
|
203
|
-
def git_current_branch
|
204
|
-
Spoom::Git.current_branch(path: @absolute_path)
|
205
|
-
end
|
206
|
-
|
207
|
-
# Get the last commit in the currently checked out branch
|
208
|
-
sig { params(short_sha: T::Boolean).returns(T.nilable(Git::Commit)) }
|
209
|
-
def git_last_commit(short_sha: true)
|
210
|
-
Spoom::Git.last_commit(path: @absolute_path, short_sha: short_sha)
|
211
|
-
end
|
212
|
-
|
213
|
-
# Sorbet
|
214
|
-
|
215
|
-
# Run `bundle exec srb` in this context directory
|
216
|
-
sig { params(command: String).returns(ExecResult) }
|
217
|
-
def srb(command)
|
218
|
-
bundle_exec("srb #{command}")
|
219
|
-
end
|
220
|
-
|
221
|
-
# Read the contents of `sorbet/config` in this context directory
|
222
|
-
sig { returns(String) }
|
223
|
-
def read_sorbet_config
|
224
|
-
read(Spoom::Sorbet::CONFIG_PATH)
|
225
|
-
end
|
226
|
-
|
227
|
-
# Set the `contents` of `sorbet/config` in this context directory
|
228
|
-
sig { params(contents: String, append: T::Boolean).void }
|
229
|
-
def write_sorbet_config!(contents, append: false)
|
230
|
-
write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
|
231
|
-
end
|
232
|
-
|
233
|
-
# Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
|
234
|
-
sig { params(relative_path: String).returns(T.nilable(String)) }
|
235
|
-
def read_file_strictness(relative_path)
|
236
|
-
Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
|
237
|
-
end
|
238
54
|
end
|
239
55
|
end
|
data/lib/spoom/coverage.rb
CHANGED
@@ -12,9 +12,9 @@ module Spoom
|
|
12
12
|
class << self
|
13
13
|
extend T::Sig
|
14
14
|
|
15
|
-
sig { params(
|
16
|
-
def snapshot(
|
17
|
-
config = sorbet_config
|
15
|
+
sig { params(context: Context, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
|
16
|
+
def snapshot(context, rbi: true, sorbet_bin: nil)
|
17
|
+
config = context.sorbet_config
|
18
18
|
config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
|
19
19
|
|
20
20
|
new_config = config.copy
|
@@ -27,25 +27,16 @@ module Spoom
|
|
27
27
|
new_config.options_string,
|
28
28
|
]
|
29
29
|
|
30
|
-
metrics =
|
31
|
-
|
32
|
-
path: path,
|
33
|
-
capture_err: true,
|
34
|
-
sorbet_bin: sorbet_bin,
|
35
|
-
)
|
30
|
+
metrics = context.srb_metrics(*flags, sorbet_bin: sorbet_bin)
|
31
|
+
|
36
32
|
# Collect extra information using a different configuration
|
37
33
|
flags << "--ignore sorbet/rbi/"
|
38
|
-
metrics_without_rbis =
|
39
|
-
*flags,
|
40
|
-
path: path,
|
41
|
-
capture_err: true,
|
42
|
-
sorbet_bin: sorbet_bin,
|
43
|
-
)
|
34
|
+
metrics_without_rbis = context.srb_metrics(*flags, sorbet_bin: sorbet_bin)
|
44
35
|
|
45
36
|
snapshot = Snapshot.new
|
46
37
|
return snapshot unless metrics
|
47
38
|
|
48
|
-
last_commit =
|
39
|
+
last_commit = context.git_last_commit
|
49
40
|
snapshot.commit_sha = last_commit&.sha
|
50
41
|
snapshot.commit_timestamp = last_commit&.timestamp
|
51
42
|
|
@@ -79,40 +70,34 @@ module Spoom
|
|
79
70
|
end
|
80
71
|
end
|
81
72
|
|
82
|
-
snapshot.version_static =
|
83
|
-
snapshot.version_runtime =
|
73
|
+
snapshot.version_static = context.gem_version_from_gemfile_lock("sorbet-static")
|
74
|
+
snapshot.version_runtime = context.gem_version_from_gemfile_lock("sorbet-runtime")
|
84
75
|
|
85
|
-
files =
|
76
|
+
files = context.srb_files(with_config: new_config)
|
86
77
|
snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
|
87
78
|
|
88
79
|
snapshot
|
89
80
|
end
|
90
81
|
|
91
|
-
sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette
|
92
|
-
def report(snapshots, palette
|
93
|
-
intro_commit =
|
82
|
+
sig { params(context: Context, snapshots: T::Array[Snapshot], palette: D3::ColorPalette).returns(Report) }
|
83
|
+
def report(context, snapshots, palette:)
|
84
|
+
intro_commit = context.sorbet_intro_commit
|
94
85
|
|
95
86
|
Report.new(
|
96
|
-
project_name: File.basename(
|
87
|
+
project_name: File.basename(context.absolute_path),
|
97
88
|
palette: palette,
|
98
89
|
snapshots: snapshots,
|
99
|
-
sigils_tree: sigils_tree(
|
90
|
+
sigils_tree: sigils_tree(context),
|
100
91
|
sorbet_intro_commit: intro_commit&.sha,
|
101
92
|
sorbet_intro_date: intro_commit&.time,
|
102
93
|
)
|
103
94
|
end
|
104
95
|
|
105
|
-
sig { params(
|
106
|
-
def
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
sig { params(path: String).returns(FileTree) }
|
111
|
-
def sigils_tree(path: ".")
|
112
|
-
config = sorbet_config(path: path)
|
113
|
-
files = Sorbet.srb_files(config, path: path)
|
96
|
+
sig { params(context: Context).returns(FileTree) }
|
97
|
+
def sigils_tree(context)
|
98
|
+
files = context.srb_files
|
114
99
|
|
115
|
-
extensions =
|
100
|
+
extensions = context.sorbet_config.allowed_extensions
|
116
101
|
extensions = [".rb"] if extensions.empty?
|
117
102
|
extensions -= [".rbi"]
|
118
103
|
|
@@ -120,7 +105,7 @@ module Spoom
|
|
120
105
|
files.select! { |file| file =~ pattern }
|
121
106
|
files.reject! { |file| file =~ %r{/test/} }
|
122
107
|
|
123
|
-
FileTree.new(files, strip_prefix:
|
108
|
+
FileTree.new(files, strip_prefix: context.absolute_path)
|
124
109
|
end
|
125
110
|
end
|
126
111
|
end
|
data/lib/spoom/sorbet.rb
CHANGED
@@ -39,124 +39,5 @@ module Spoom
|
|
39
39
|
|
40
40
|
KILLED_CODE = 137
|
41
41
|
SEGFAULT_CODE = 139
|
42
|
-
|
43
|
-
class << self
|
44
|
-
extend T::Sig
|
45
|
-
|
46
|
-
sig do
|
47
|
-
params(
|
48
|
-
arg: String,
|
49
|
-
path: String,
|
50
|
-
capture_err: T::Boolean,
|
51
|
-
sorbet_bin: T.nilable(String),
|
52
|
-
).returns(ExecResult)
|
53
|
-
end
|
54
|
-
def srb(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
55
|
-
if sorbet_bin
|
56
|
-
arg.prepend(sorbet_bin)
|
57
|
-
else
|
58
|
-
arg.prepend("bundle", "exec", "srb")
|
59
|
-
end
|
60
|
-
result = Spoom.exec(*T.unsafe(arg), path: path, capture_err: capture_err)
|
61
|
-
|
62
|
-
case result.exit_code
|
63
|
-
when KILLED_CODE
|
64
|
-
raise Error::Killed.new("Sorbet was killed.", result)
|
65
|
-
when SEGFAULT_CODE
|
66
|
-
raise Error::Segfault.new("Sorbet segfaulted.", result)
|
67
|
-
end
|
68
|
-
|
69
|
-
result
|
70
|
-
end
|
71
|
-
|
72
|
-
sig do
|
73
|
-
params(
|
74
|
-
arg: String,
|
75
|
-
path: String,
|
76
|
-
capture_err: T::Boolean,
|
77
|
-
sorbet_bin: T.nilable(String),
|
78
|
-
).returns(ExecResult)
|
79
|
-
end
|
80
|
-
def srb_tc(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
81
|
-
arg.prepend("tc") unless sorbet_bin
|
82
|
-
srb(*T.unsafe(arg), path: path, capture_err: capture_err, sorbet_bin: sorbet_bin)
|
83
|
-
end
|
84
|
-
|
85
|
-
# List all files typechecked by Sorbet from its `config`
|
86
|
-
sig { params(config: Config, path: String).returns(T::Array[String]) }
|
87
|
-
def srb_files(config, path: ".")
|
88
|
-
regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
|
89
|
-
exts = config.allowed_extensions.empty? ? [".rb", ".rbi"] : config.allowed_extensions
|
90
|
-
Dir.glob((Pathname.new(path) / "**/*{#{exts.join(",")}}").to_s).reject do |f|
|
91
|
-
regs.any? { |re| re.match?(f) }
|
92
|
-
end.sort
|
93
|
-
end
|
94
|
-
|
95
|
-
sig do
|
96
|
-
params(
|
97
|
-
arg: String,
|
98
|
-
path: String,
|
99
|
-
capture_err: T::Boolean,
|
100
|
-
sorbet_bin: T.nilable(String),
|
101
|
-
).returns(T.nilable(String))
|
102
|
-
end
|
103
|
-
def srb_version(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
104
|
-
result = T.let(
|
105
|
-
T.unsafe(self).srb_tc(
|
106
|
-
"--no-config",
|
107
|
-
"--version",
|
108
|
-
*arg,
|
109
|
-
path: path,
|
110
|
-
capture_err: capture_err,
|
111
|
-
sorbet_bin: sorbet_bin,
|
112
|
-
),
|
113
|
-
ExecResult,
|
114
|
-
)
|
115
|
-
return nil unless result.status
|
116
|
-
|
117
|
-
result.out.split(" ")[2]
|
118
|
-
end
|
119
|
-
|
120
|
-
sig do
|
121
|
-
params(
|
122
|
-
arg: String,
|
123
|
-
path: String,
|
124
|
-
capture_err: T::Boolean,
|
125
|
-
sorbet_bin: T.nilable(String),
|
126
|
-
).returns(T.nilable(T::Hash[String, Integer]))
|
127
|
-
end
|
128
|
-
def srb_metrics(*arg, path: ".", capture_err: false, sorbet_bin: nil)
|
129
|
-
metrics_file = "metrics.tmp"
|
130
|
-
metrics_path = "#{path}/#{metrics_file}"
|
131
|
-
T.unsafe(self).srb_tc(
|
132
|
-
"--metrics-file",
|
133
|
-
metrics_file,
|
134
|
-
*arg,
|
135
|
-
path: path,
|
136
|
-
capture_err: capture_err,
|
137
|
-
sorbet_bin: sorbet_bin,
|
138
|
-
)
|
139
|
-
if File.exist?(metrics_path)
|
140
|
-
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
141
|
-
File.delete(metrics_path)
|
142
|
-
return metrics
|
143
|
-
end
|
144
|
-
nil
|
145
|
-
end
|
146
|
-
|
147
|
-
# Get `gem` version from the `Gemfile.lock` content
|
148
|
-
#
|
149
|
-
# Returns `nil` if `gem` cannot be found in the Gemfile.
|
150
|
-
sig { params(gem: String, path: String).returns(T.nilable(String)) }
|
151
|
-
def version_from_gemfile_lock(gem: "sorbet", path: ".")
|
152
|
-
gemfile_path = "#{path}/Gemfile.lock"
|
153
|
-
return nil unless File.exist?(gemfile_path)
|
154
|
-
|
155
|
-
content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
|
156
|
-
return nil unless content
|
157
|
-
|
158
|
-
content[1]
|
159
|
-
end
|
160
|
-
end
|
161
42
|
end
|
162
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