spoom 1.0.5 → 1.1.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 +0 -1
- data/README.md +45 -2
- data/lib/spoom.rb +20 -2
- data/lib/spoom/cli.rb +25 -14
- data/lib/spoom/cli/bump.rb +106 -13
- data/lib/spoom/cli/config.rb +3 -3
- data/lib/spoom/cli/coverage.rb +57 -42
- data/lib/spoom/cli/helper.rb +88 -9
- data/lib/spoom/cli/lsp.rb +20 -20
- data/lib/spoom/cli/run.rb +55 -25
- 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/git.rb +2 -1
- data/lib/spoom/printer.rb +0 -1
- data/lib/spoom/sorbet.rb +97 -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/test_helpers/project.rb +9 -0
- data/lib/spoom/version.rb +2 -2
- data/templates/card.erb +8 -0
- data/templates/card_snapshot.erb +22 -0
- data/templates/page.erb +50 -0
- metadata +9 -7
- data/lib/spoom/config.rb +0 -11
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/git.rb
CHANGED
@@ -14,11 +14,12 @@ module Spoom
|
|
14
14
|
return "", "Error: `#{path}` is not a directory.", false unless File.directory?(path)
|
15
15
|
opts = {}
|
16
16
|
opts[:chdir] = path
|
17
|
-
|
17
|
+
i, o, e, s = Open3.popen3(*T.unsafe([command, *T.unsafe(arg), opts]))
|
18
18
|
out = o.read.to_s
|
19
19
|
o.close
|
20
20
|
err = e.read.to_s
|
21
21
|
e.close
|
22
|
+
i.close
|
22
23
|
[out, err, T.cast(s.value, Process::Status).success?]
|
23
24
|
end
|
24
25
|
|
data/lib/spoom/printer.rb
CHANGED
data/lib/spoom/sorbet.rb
CHANGED
@@ -11,73 +11,112 @@ 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
|
+
"--no-config",
|
72
|
+
"--version",
|
73
|
+
*arg,
|
74
|
+
path: path,
|
75
|
+
capture_err: capture_err,
|
76
|
+
sorbet_bin: sorbet_bin
|
77
|
+
)
|
78
|
+
return nil unless res
|
79
|
+
out.split(" ")[2]
|
80
|
+
end
|
57
81
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
82
|
+
sig do
|
83
|
+
params(
|
84
|
+
arg: String,
|
85
|
+
path: String,
|
86
|
+
capture_err: T::Boolean,
|
87
|
+
sorbet_bin: T.nilable(String)
|
88
|
+
).returns(T.nilable(T::Hash[String, Integer]))
|
89
|
+
end
|
90
|
+
def srb_metrics(*arg, path: '.', capture_err: false, sorbet_bin: nil)
|
91
|
+
metrics_file = "metrics.tmp"
|
92
|
+
metrics_path = "#{path}/#{metrics_file}"
|
93
|
+
T.unsafe(self).srb_tc(
|
94
|
+
"--metrics-file",
|
95
|
+
metrics_file,
|
96
|
+
*arg,
|
97
|
+
path: path,
|
98
|
+
capture_err: capture_err,
|
99
|
+
sorbet_bin: sorbet_bin
|
100
|
+
)
|
101
|
+
if File.exist?(metrics_path)
|
102
|
+
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
103
|
+
File.delete(metrics_path)
|
104
|
+
return metrics
|
105
|
+
end
|
106
|
+
nil
|
67
107
|
end
|
68
|
-
nil
|
69
|
-
end
|
70
108
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
109
|
+
# Get `gem` version from the `Gemfile.lock` content
|
110
|
+
#
|
111
|
+
# Returns `nil` if `gem` cannot be found in the Gemfile.
|
112
|
+
sig { params(gem: String, path: String).returns(T.nilable(String)) }
|
113
|
+
def version_from_gemfile_lock(gem: 'sorbet', path: '.')
|
114
|
+
gemfile_path = "#{path}/Gemfile.lock"
|
115
|
+
return nil unless File.exist?(gemfile_path)
|
116
|
+
content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
|
117
|
+
return nil unless content
|
118
|
+
content[1]
|
119
|
+
end
|
81
120
|
end
|
82
121
|
end
|
83
122
|
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
|
@@ -76,6 +76,15 @@ module Spoom
|
|
76
76
|
Spoom::Git.exec("GIT_COMMITTER_DATE=\"#{date}\" git commit -m '#{message}' --date '#{date}'", path: path)
|
77
77
|
end
|
78
78
|
|
79
|
+
# Run `bundle install` in this project
|
80
|
+
sig { returns([T.nilable(String), T.nilable(String), T::Boolean]) }
|
81
|
+
def bundle_install
|
82
|
+
opts = {}
|
83
|
+
opts[:chdir] = path
|
84
|
+
out, err, status = Open3.capture3("bundle", "install", opts)
|
85
|
+
[out, err, status.success?]
|
86
|
+
end
|
87
|
+
|
79
88
|
# Run a command with `bundle exec` in this project
|
80
89
|
sig { params(cmd: String, args: String).returns([T.nilable(String), T.nilable(String), T::Boolean]) }
|
81
90
|
def bundle_exec(cmd, *args)
|
data/lib/spoom/version.rb
CHANGED
data/templates/card.erb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
<div class="card">
|
2
|
+
<% if title %>
|
3
|
+
<h5 class="card-header"><%= title %></h5>
|
4
|
+
<% end %>
|
5
|
+
<div class="card-body">
|
6
|
+
<div class="container-fluid">
|
7
|
+
<div class="row justify-content-md-center">
|
8
|
+
<div class="col-12 col-sm-4 col-xl-3">
|
9
|
+
<%= pie_sigils.html %>
|
10
|
+
</div>
|
11
|
+
<div class="d-none d-xl-block col-xl-1"></div>
|
12
|
+
<div class="col-12 col-sm-4 col-xl-3">
|
13
|
+
<%= pie_calls.html %>
|
14
|
+
</div>
|
15
|
+
<div class="d-none d-xl-block col-xl-1"></div>
|
16
|
+
<div class="col-12 col-sm-4 col-xl-3">
|
17
|
+
<%= pie_sigs.html %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
</div>
|
data/templates/page.erb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
7
|
+
|
8
|
+
<title><%= title %></title>
|
9
|
+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
|
10
|
+
|
11
|
+
<style>
|
12
|
+
.card {
|
13
|
+
margin: 30px 0 0 0;
|
14
|
+
}
|
15
|
+
|
16
|
+
.footer {
|
17
|
+
color: #6c757d;
|
18
|
+
text-align: center;
|
19
|
+
margin: 30px 0 0 0;
|
20
|
+
}
|
21
|
+
|
22
|
+
<%= header_style %>
|
23
|
+
</style>
|
24
|
+
</head>
|
25
|
+
<body>
|
26
|
+
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
27
|
+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
|
28
|
+
<script src="https://d3js.org/d3.v4.min.js"></script>
|
29
|
+
|
30
|
+
<script>
|
31
|
+
<%= header_script %>
|
32
|
+
</script>
|
33
|
+
|
34
|
+
<div class="px-5 py-4 container-fluid">
|
35
|
+
<div class="row justify-content-center">
|
36
|
+
<div class="col-xs-12 col-md-12 col-lg-9 col-xl-8">
|
37
|
+
<div class="header">
|
38
|
+
<%= header_html %>
|
39
|
+
</div>
|
40
|
+
<div class="body">
|
41
|
+
<%= body_html %>
|
42
|
+
</div>
|
43
|
+
<div class="footer">
|
44
|
+
<%= footer_html %>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</body>
|
50
|
+
</html>
|
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.1.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: 2021-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -70,16 +70,16 @@ dependencies:
|
|
70
70
|
name: sorbet
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.5.
|
75
|
+
version: 0.5.6347
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.5.
|
82
|
+
version: 0.5.6347
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: thor
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -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
|
@@ -152,6 +151,9 @@ files:
|
|
152
151
|
- lib/spoom/test_helpers/project.rb
|
153
152
|
- lib/spoom/timeline.rb
|
154
153
|
- lib/spoom/version.rb
|
154
|
+
- templates/card.erb
|
155
|
+
- templates/card_snapshot.erb
|
156
|
+
- templates/page.erb
|
155
157
|
homepage: https://github.com/Shopify/spoom
|
156
158
|
licenses:
|
157
159
|
- MIT
|