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
@@ -0,0 +1,121 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Git
|
6
|
+
class Commit < T::Struct
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
# Parse a line formated as `%h %at` into a `Commit`
|
13
|
+
sig { params(string: String).returns(T.nilable(Commit)) }
|
14
|
+
def parse_line(string)
|
15
|
+
sha, epoch = string.split(" ", 2)
|
16
|
+
return nil unless sha && epoch
|
17
|
+
|
18
|
+
time = Time.strptime(epoch, "%s")
|
19
|
+
Commit.new(sha: sha, time: time)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
const :sha, String
|
24
|
+
const :time, Time
|
25
|
+
|
26
|
+
sig { returns(Integer) }
|
27
|
+
def timestamp
|
28
|
+
time.to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Context
|
34
|
+
# Git features for a context
|
35
|
+
module Git
|
36
|
+
extend T::Sig
|
37
|
+
extend T::Helpers
|
38
|
+
|
39
|
+
requires_ancestor { Context }
|
40
|
+
|
41
|
+
# Run a command prefixed by `git` in this context directory
|
42
|
+
sig { params(command: String).returns(ExecResult) }
|
43
|
+
def git(command)
|
44
|
+
exec("git #{command}")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Run `git init` in this context directory
|
48
|
+
#
|
49
|
+
# Warning: passing a branch will run `git init -b <branch>` which is only available in git 2.28+.
|
50
|
+
# In older versions, use `git_init!` followed by `git("checkout -b <branch>")`.
|
51
|
+
sig { params(branch: T.nilable(String)).returns(ExecResult) }
|
52
|
+
def git_init!(branch: nil)
|
53
|
+
if branch
|
54
|
+
git("init -b #{branch}")
|
55
|
+
else
|
56
|
+
git("init")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Run `git checkout` in this context directory
|
61
|
+
sig { params(ref: String).returns(ExecResult) }
|
62
|
+
def git_checkout!(ref: "main")
|
63
|
+
git("checkout #{ref}")
|
64
|
+
end
|
65
|
+
|
66
|
+
# Run `git add . && git commit` in this context directory
|
67
|
+
sig { params(message: String, time: Time, allow_empty: T::Boolean).void }
|
68
|
+
def git_commit!(message: "message", time: Time.now.utc, allow_empty: false)
|
69
|
+
git("add --all")
|
70
|
+
|
71
|
+
args = ["-m '#{message}'", "--date '#{time}'"]
|
72
|
+
args << "--allow-empty" if allow_empty
|
73
|
+
|
74
|
+
exec("GIT_COMMITTER_DATE=\"#{time}\" git -c commit.gpgsign=false commit #{args.join(" ")}")
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get the current git branch in this context directory
|
78
|
+
sig { returns(T.nilable(String)) }
|
79
|
+
def git_current_branch
|
80
|
+
res = git("branch --show-current")
|
81
|
+
return nil unless res.status
|
82
|
+
|
83
|
+
res.out.strip
|
84
|
+
end
|
85
|
+
|
86
|
+
# Run `git diff` in this context directory
|
87
|
+
sig { params(arg: String).returns(ExecResult) }
|
88
|
+
def git_diff(*arg)
|
89
|
+
git("diff #{arg.join(" ")}")
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get the last commit in the currently checked out branch
|
93
|
+
sig { params(short_sha: T::Boolean).returns(T.nilable(Spoom::Git::Commit)) }
|
94
|
+
def git_last_commit(short_sha: true)
|
95
|
+
res = git_log("HEAD --format='%#{short_sha ? "h" : "H"} %at' -1")
|
96
|
+
return nil unless res.status
|
97
|
+
|
98
|
+
out = res.out.strip
|
99
|
+
return nil if out.empty?
|
100
|
+
|
101
|
+
Spoom::Git::Commit.parse_line(out)
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { params(arg: String).returns(ExecResult) }
|
105
|
+
def git_log(*arg)
|
106
|
+
git("log #{arg.join(" ")}")
|
107
|
+
end
|
108
|
+
|
109
|
+
sig { params(arg: String).returns(ExecResult) }
|
110
|
+
def git_show(*arg)
|
111
|
+
git("show #{arg.join(" ")}")
|
112
|
+
end
|
113
|
+
|
114
|
+
# Is there uncommited changes in this context directory?
|
115
|
+
sig { params(path: String).returns(T::Boolean) }
|
116
|
+
def git_workdir_clean?(path: ".")
|
117
|
+
git_diff("HEAD").out.empty?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -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,6 +3,14 @@
|
|
3
3
|
|
4
4
|
require "fileutils"
|
5
5
|
require "open3"
|
6
|
+
require "time"
|
7
|
+
require "tmpdir"
|
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"
|
6
14
|
|
7
15
|
module Spoom
|
8
16
|
# An abstraction to a Ruby project context
|
@@ -12,9 +20,11 @@ module Spoom
|
|
12
20
|
class Context
|
13
21
|
extend T::Sig
|
14
22
|
|
15
|
-
|
16
|
-
|
17
|
-
|
23
|
+
include Bundle
|
24
|
+
include Exec
|
25
|
+
include FileSystem
|
26
|
+
include Git
|
27
|
+
include Sorbet
|
18
28
|
|
19
29
|
class << self
|
20
30
|
extend T::Sig
|
@@ -29,6 +39,10 @@ module Spoom
|
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
42
|
+
# The absolute path to the directory this context is about
|
43
|
+
sig { returns(String) }
|
44
|
+
attr_reader :absolute_path
|
45
|
+
|
32
46
|
# Create a new context about `absolute_path`
|
33
47
|
#
|
34
48
|
# The directory will not be created if it doesn't exist.
|
@@ -37,195 +51,5 @@ module Spoom
|
|
37
51
|
def initialize(absolute_path)
|
38
52
|
@absolute_path = T.let(::File.expand_path(absolute_path), String)
|
39
53
|
end
|
40
|
-
|
41
|
-
# Returns the absolute path to `relative_path` in the context's directory
|
42
|
-
sig { params(relative_path: String).returns(String) }
|
43
|
-
def absolute_path_to(relative_path)
|
44
|
-
File.join(@absolute_path, relative_path)
|
45
|
-
end
|
46
|
-
|
47
|
-
# File System
|
48
|
-
|
49
|
-
# Does the context directory at `absolute_path` exist and is a directory?
|
50
|
-
sig { returns(T::Boolean) }
|
51
|
-
def exist?
|
52
|
-
File.directory?(@absolute_path)
|
53
|
-
end
|
54
|
-
|
55
|
-
# Create the context directory at `absolute_path`
|
56
|
-
sig { void }
|
57
|
-
def mkdir!
|
58
|
-
FileUtils.rm_rf(@absolute_path)
|
59
|
-
FileUtils.mkdir_p(@absolute_path)
|
60
|
-
end
|
61
|
-
|
62
|
-
# List all files in this context matching `pattern`
|
63
|
-
sig { params(pattern: String).returns(T::Array[String]) }
|
64
|
-
def glob(pattern = "**/*")
|
65
|
-
Dir.glob(absolute_path_to(pattern)).map do |path|
|
66
|
-
Pathname.new(path).relative_path_from(@absolute_path).to_s
|
67
|
-
end.sort
|
68
|
-
end
|
69
|
-
|
70
|
-
# List all files at the top level of this context directory
|
71
|
-
sig { returns(T::Array[String]) }
|
72
|
-
def list
|
73
|
-
glob("*")
|
74
|
-
end
|
75
|
-
|
76
|
-
# Does `relative_path` point to an existing file in this context directory?
|
77
|
-
sig { params(relative_path: String).returns(T::Boolean) }
|
78
|
-
def file?(relative_path)
|
79
|
-
File.file?(absolute_path_to(relative_path))
|
80
|
-
end
|
81
|
-
|
82
|
-
# Return the contents of the file at `relative_path` in this context directory
|
83
|
-
#
|
84
|
-
# Will raise if the file doesn't exist.
|
85
|
-
sig { params(relative_path: String).returns(String) }
|
86
|
-
def read(relative_path)
|
87
|
-
File.read(absolute_path_to(relative_path))
|
88
|
-
end
|
89
|
-
|
90
|
-
# Write `contents` in the file at `relative_path` in this context directory
|
91
|
-
#
|
92
|
-
# Append to the file if `append` is true.
|
93
|
-
sig { params(relative_path: String, contents: String, append: T::Boolean).void }
|
94
|
-
def write!(relative_path, contents = "", append: false)
|
95
|
-
absolute_path = absolute_path_to(relative_path)
|
96
|
-
FileUtils.mkdir_p(File.dirname(absolute_path))
|
97
|
-
File.write(absolute_path, contents, mode: append ? "a" : "w")
|
98
|
-
end
|
99
|
-
|
100
|
-
# Remove the path at `relative_path` (recursive + force) in this context directory
|
101
|
-
sig { params(relative_path: String).void }
|
102
|
-
def remove!(relative_path)
|
103
|
-
FileUtils.rm_rf(absolute_path_to(relative_path))
|
104
|
-
end
|
105
|
-
|
106
|
-
# Move the file or directory from `from_relative_path` to `to_relative_path`
|
107
|
-
sig { params(from_relative_path: String, to_relative_path: String).void }
|
108
|
-
def move!(from_relative_path, to_relative_path)
|
109
|
-
destination_path = absolute_path_to(to_relative_path)
|
110
|
-
FileUtils.mkdir_p(File.dirname(destination_path))
|
111
|
-
FileUtils.mv(absolute_path_to(from_relative_path), destination_path)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Delete this context and its content
|
115
|
-
#
|
116
|
-
# Warning: it will `rm -rf` the context directory on the file system.
|
117
|
-
sig { void }
|
118
|
-
def destroy!
|
119
|
-
FileUtils.rm_rf(@absolute_path)
|
120
|
-
end
|
121
|
-
|
122
|
-
# Execution
|
123
|
-
|
124
|
-
# Run a command in this context directory
|
125
|
-
sig { params(command: String, capture_err: T::Boolean).returns(ExecResult) }
|
126
|
-
def exec(command, capture_err: true)
|
127
|
-
Bundler.with_unbundled_env do
|
128
|
-
opts = T.let({ chdir: @absolute_path }, T::Hash[Symbol, T.untyped])
|
129
|
-
|
130
|
-
if capture_err
|
131
|
-
out, err, status = Open3.capture3(command, opts)
|
132
|
-
ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
133
|
-
else
|
134
|
-
out, status = Open3.capture2(command, opts)
|
135
|
-
ExecResult.new(out: out, err: "", status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Bundle
|
141
|
-
|
142
|
-
# Read the `contents` of the Gemfile in this context directory
|
143
|
-
sig { returns(T.nilable(String)) }
|
144
|
-
def read_gemfile
|
145
|
-
read("Gemfile")
|
146
|
-
end
|
147
|
-
|
148
|
-
# Set the `contents` of the Gemfile in this context directory
|
149
|
-
sig { params(contents: String, append: T::Boolean).void }
|
150
|
-
def write_gemfile!(contents, append: false)
|
151
|
-
write!("Gemfile", contents, append: append)
|
152
|
-
end
|
153
|
-
|
154
|
-
# Run a command with `bundle` in this context directory
|
155
|
-
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
156
|
-
def bundle(command, version: nil)
|
157
|
-
command = "_#{version}_ #{command}" if version
|
158
|
-
exec("bundle #{command}")
|
159
|
-
end
|
160
|
-
|
161
|
-
# Run `bundle install` in this context directory
|
162
|
-
sig { params(version: T.nilable(String)).returns(ExecResult) }
|
163
|
-
def bundle_install!(version: nil)
|
164
|
-
bundle("install", version: version)
|
165
|
-
end
|
166
|
-
|
167
|
-
# Run a command `bundle exec` in this context directory
|
168
|
-
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
169
|
-
def bundle_exec(command, version: nil)
|
170
|
-
bundle("exec #{command}", version: version)
|
171
|
-
end
|
172
|
-
|
173
|
-
# Git
|
174
|
-
|
175
|
-
# Run a command prefixed by `git` in this context directory
|
176
|
-
sig { params(command: String).returns(ExecResult) }
|
177
|
-
def git(command)
|
178
|
-
exec("git #{command}")
|
179
|
-
end
|
180
|
-
|
181
|
-
# Run `git init` in this context directory
|
182
|
-
sig { params(branch: String).void }
|
183
|
-
def git_init!(branch: "main")
|
184
|
-
git("init -q -b #{branch}")
|
185
|
-
end
|
186
|
-
|
187
|
-
# Run `git checkout` in this context directory
|
188
|
-
sig { params(ref: String).returns(ExecResult) }
|
189
|
-
def git_checkout!(ref: "main")
|
190
|
-
git("checkout #{ref}")
|
191
|
-
end
|
192
|
-
|
193
|
-
# Get the current git branch in this context directory
|
194
|
-
sig { returns(T.nilable(String)) }
|
195
|
-
def git_current_branch
|
196
|
-
Spoom::Git.current_branch(path: @absolute_path)
|
197
|
-
end
|
198
|
-
|
199
|
-
# Get the last commit in the currently checked out branch
|
200
|
-
sig { params(short_sha: T::Boolean).returns(T.nilable(Git::Commit)) }
|
201
|
-
def git_last_commit(short_sha: true)
|
202
|
-
Spoom::Git.last_commit(path: @absolute_path, short_sha: short_sha)
|
203
|
-
end
|
204
|
-
|
205
|
-
# Sorbet
|
206
|
-
|
207
|
-
# Run `bundle exec srb` in this context directory
|
208
|
-
sig { params(command: String).returns(ExecResult) }
|
209
|
-
def srb(command)
|
210
|
-
bundle_exec("srb #{command}")
|
211
|
-
end
|
212
|
-
|
213
|
-
# Read the contents of `sorbet/config` in this context directory
|
214
|
-
sig { returns(String) }
|
215
|
-
def read_sorbet_config
|
216
|
-
read(Spoom::Sorbet::CONFIG_PATH)
|
217
|
-
end
|
218
|
-
|
219
|
-
# Set the `contents` of `sorbet/config` in this context directory
|
220
|
-
sig { params(contents: String, append: T::Boolean).void }
|
221
|
-
def write_sorbet_config!(contents, append: false)
|
222
|
-
write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
|
223
|
-
end
|
224
|
-
|
225
|
-
# Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
|
226
|
-
sig { params(relative_path: String).returns(T.nilable(String)) }
|
227
|
-
def read_file_strictness(relative_path)
|
228
|
-
Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
|
229
|
-
end
|
230
54
|
end
|
231
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
|
|
@@ -63,8 +54,10 @@ module Spoom
|
|
63
54
|
|
64
55
|
if metrics_without_rbis
|
65
56
|
snapshot.methods_with_sig_excluding_rbis = metrics_without_rbis.fetch("types.sig.count", 0)
|
66
|
-
snapshot.methods_without_sig_excluding_rbis = metrics_without_rbis.fetch(
|
67
|
-
|
57
|
+
snapshot.methods_without_sig_excluding_rbis = metrics_without_rbis.fetch(
|
58
|
+
"types.input.methods.total",
|
59
|
+
0,
|
60
|
+
) - snapshot.methods_with_sig_excluding_rbis
|
68
61
|
end
|
69
62
|
|
70
63
|
Snapshot::STRICTNESSES.each do |strictness|
|
@@ -77,40 +70,34 @@ module Spoom
|
|
77
70
|
end
|
78
71
|
end
|
79
72
|
|
80
|
-
snapshot.version_static =
|
81
|
-
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")
|
82
75
|
|
83
|
-
files =
|
76
|
+
files = context.srb_files(with_config: new_config)
|
84
77
|
snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
|
85
78
|
|
86
79
|
snapshot
|
87
80
|
end
|
88
81
|
|
89
|
-
sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette
|
90
|
-
def report(snapshots, palette
|
91
|
-
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
|
92
85
|
|
93
86
|
Report.new(
|
94
|
-
project_name: File.basename(
|
87
|
+
project_name: File.basename(context.absolute_path),
|
95
88
|
palette: palette,
|
96
89
|
snapshots: snapshots,
|
97
|
-
sigils_tree: sigils_tree(
|
90
|
+
sigils_tree: sigils_tree(context),
|
98
91
|
sorbet_intro_commit: intro_commit&.sha,
|
99
92
|
sorbet_intro_date: intro_commit&.time,
|
100
93
|
)
|
101
94
|
end
|
102
95
|
|
103
|
-
sig { params(
|
104
|
-
def
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
sig { params(path: String).returns(FileTree) }
|
109
|
-
def sigils_tree(path: ".")
|
110
|
-
config = sorbet_config(path: path)
|
111
|
-
files = Sorbet.srb_files(config, path: path)
|
96
|
+
sig { params(context: Context).returns(FileTree) }
|
97
|
+
def sigils_tree(context)
|
98
|
+
files = context.srb_files
|
112
99
|
|
113
|
-
extensions =
|
100
|
+
extensions = context.sorbet_config.allowed_extensions
|
114
101
|
extensions = [".rb"] if extensions.empty?
|
115
102
|
extensions -= [".rbi"]
|
116
103
|
|
@@ -118,7 +105,7 @@ module Spoom
|
|
118
105
|
files.select! { |file| file =~ pattern }
|
119
106
|
files.reject! { |file| file =~ %r{/test/} }
|
120
107
|
|
121
|
-
FileTree.new(files, strip_prefix:
|
108
|
+
FileTree.new(files, strip_prefix: context.absolute_path)
|
122
109
|
end
|
123
110
|
end
|
124
111
|
end
|
data/lib/spoom/sorbet/errors.rb
CHANGED
@@ -18,13 +18,16 @@ module Spoom
|
|
18
18
|
class Parser
|
19
19
|
extend T::Sig
|
20
20
|
|
21
|
-
HEADER = T.let(
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
HEADER = T.let(
|
22
|
+
[
|
23
|
+
"👋 Hey there! Heads up that this is not a release build of sorbet.",
|
24
|
+
"Release builds are faster and more well-supported by the Sorbet team.",
|
25
|
+
"Check out the README to learn how to build Sorbet in release mode.",
|
26
|
+
"To forcibly silence this error, either pass --silence-dev-message,",
|
27
|
+
"or set SORBET_SILENCE_DEV_MESSAGE=1 in your shell environment.",
|
28
|
+
],
|
29
|
+
T::Array[String],
|
30
|
+
)
|
28
31
|
|
29
32
|
class << self
|
30
33
|
extend T::Sig
|