spoom 1.1.11 → 1.1.12
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 -0
- data/README.md +6 -0
- data/Rakefile +1 -0
- data/lib/spoom/cli/bump.rb +11 -7
- data/lib/spoom/cli/coverage.rb +11 -7
- data/lib/spoom/cli/helper.rb +5 -4
- data/lib/spoom/cli/lsp.rb +2 -1
- data/lib/spoom/cli/run.rb +16 -6
- data/lib/spoom/cli.rb +6 -4
- data/lib/spoom/context.rb +219 -0
- data/lib/spoom/coverage/d3/base.rb +12 -8
- data/lib/spoom/coverage/d3/circle_map.rb +40 -37
- data/lib/spoom/coverage/d3/pie.rb +30 -26
- data/lib/spoom/coverage/d3/timeline.rb +86 -82
- data/lib/spoom/coverage/d3.rb +72 -70
- data/lib/spoom/coverage/report.rb +6 -6
- data/lib/spoom/coverage/snapshot.rb +40 -33
- data/lib/spoom/coverage.rb +84 -81
- data/lib/spoom/file_tree.rb +2 -0
- data/lib/spoom/git.rb +107 -97
- data/lib/spoom/printer.rb +4 -0
- data/lib/spoom/sorbet/errors.rb +29 -11
- data/lib/spoom/sorbet/lsp/base.rb +1 -1
- data/lib/spoom/sorbet/lsp/errors.rb +23 -17
- data/lib/spoom/sorbet/lsp/structures.rb +85 -54
- data/lib/spoom/sorbet/lsp.rb +71 -63
- data/lib/spoom/sorbet/metrics.rb +18 -16
- data/lib/spoom/sorbet/sigils.rb +59 -54
- data/lib/spoom/sorbet.rb +11 -8
- data/lib/spoom/timeline.rb +1 -0
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +43 -25
- metadata +20 -20
- data/lib/spoom/test_helpers/project.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed6d04e01efc1c6c8c00df77ad340f189a277aebf3afbd64edbd89f6145541f2
|
4
|
+
data.tar.gz: 1935557277bcfdb54b4ab0d389ab5a72fb5b2d00a6631f6c787dae9d83dac7c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 781d3bb6443c518fc1d4334e5c4ac17a09372ba9115f024bcdceb5cf15af4d4524d1e4cb465963ce5d23aaa07ebd5ab2114ebe81b4eea3fffe27942295e21b9f
|
7
|
+
data.tar.gz: 3427fdfe5cd483609fbbe6618de2f8a536f8bf0727a6865e44c2c4c9b74f9303c0fdcfdefd44bdd547d16e6766f0f09fa49c1e8cd991d0efcffaa37f3c9c7609
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -121,6 +121,12 @@ Hide the `Errors: X` at the end of the list:
|
|
121
121
|
$ spoom tc --no-count
|
122
122
|
```
|
123
123
|
|
124
|
+
List only the errors comming from specific directories or files:
|
125
|
+
|
126
|
+
```
|
127
|
+
$ spoom tc file1.rb path1/ path2/
|
128
|
+
```
|
129
|
+
|
124
130
|
#### Typing coverage
|
125
131
|
|
126
132
|
Show metrics about the project contents and the typing coverage:
|
data/Rakefile
CHANGED
data/lib/spoom/cli/bump.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "find"
|
5
|
+
require "open3"
|
6
6
|
|
7
7
|
module Spoom
|
8
8
|
module Cli
|
@@ -85,7 +85,6 @@ module Spoom
|
|
85
85
|
|
86
86
|
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
87
87
|
result = Sorbet.srb_tc(
|
88
|
-
"--no-error-sections",
|
89
88
|
"--error-url-base=#{error_url_base}",
|
90
89
|
path: exec_path,
|
91
90
|
capture_err: true,
|
@@ -108,17 +107,22 @@ module Spoom
|
|
108
107
|
|
109
108
|
errors = Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
|
110
109
|
|
111
|
-
|
112
|
-
|
110
|
+
all_files = errors.flat_map do |err|
|
111
|
+
[err.file, *err.files_from_error_sections]
|
112
|
+
end
|
113
|
+
|
114
|
+
files_with_errors = all_files.map do |file|
|
115
|
+
path = File.expand_path(file)
|
113
116
|
next unless path.start_with?(directory)
|
114
117
|
next unless File.file?(path)
|
115
118
|
next unless files_to_bump.include?(path)
|
119
|
+
|
116
120
|
path
|
117
121
|
end.compact.uniq
|
118
122
|
|
119
123
|
undo_changes(files_with_errors, from)
|
120
124
|
|
121
|
-
say("Found #{errors.length} type checking error#{
|
125
|
+
say("Found #{errors.length} type checking error#{"s" if errors.length > 1}") if options[:count_errors]
|
122
126
|
|
123
127
|
files_changed = files_to_bump - files_with_errors
|
124
128
|
print_changes(files_changed, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
@@ -134,7 +138,7 @@ module Spoom
|
|
134
138
|
end
|
135
139
|
message = StringIO.new
|
136
140
|
message << (dry ? "Can bump" : "Bumped")
|
137
|
-
message << " `#{files.size}` file#{
|
141
|
+
message << " `#{files.size}` file#{"s" if files.size > 1}"
|
138
142
|
message << " from `#{from}` to `#{to}`:"
|
139
143
|
say(message.string)
|
140
144
|
files.each do |file|
|
data/lib/spoom/cli/coverage.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require_relative
|
5
|
-
require_relative
|
4
|
+
require_relative "../coverage"
|
5
|
+
require_relative "../timeline"
|
6
6
|
|
7
7
|
module Spoom
|
8
8
|
module Cli
|
@@ -27,6 +27,7 @@ module Spoom
|
|
27
27
|
|
28
28
|
save_dir = options[:save]
|
29
29
|
return unless save_dir
|
30
|
+
|
30
31
|
FileUtils.mkdir_p(save_dir)
|
31
32
|
file = "#{save_dir}/#{snapshot.commit_sha || snapshot.timestamp}.json"
|
32
33
|
File.write(file, snapshot.to_json)
|
@@ -85,7 +86,7 @@ module Spoom
|
|
85
86
|
|
86
87
|
ticks.each_with_index do |sha, i|
|
87
88
|
date = Spoom::Git.commit_time(sha, path: path)
|
88
|
-
say("Analyzing commit `#{sha}` - #{date&.strftime(
|
89
|
+
say("Analyzing commit `#{sha}` - #{date&.strftime("%F")} (#{i + 1} / #{ticks.size})")
|
89
90
|
|
90
91
|
Spoom::Git.checkout(sha, path: path)
|
91
92
|
|
@@ -93,6 +94,7 @@ module Spoom
|
|
93
94
|
if options[:bundle_install]
|
94
95
|
Bundler.with_unbundled_env do
|
95
96
|
next unless bundle_install(path, sha)
|
97
|
+
|
96
98
|
snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
|
97
99
|
end
|
98
100
|
else
|
@@ -104,6 +106,7 @@ module Spoom
|
|
104
106
|
say("\n")
|
105
107
|
|
106
108
|
next unless save_dir
|
109
|
+
|
107
110
|
file = "#{save_dir}/#{sha}.json"
|
108
111
|
File.write(file, snapshot.to_json)
|
109
112
|
say(" Snapshot data saved under `#{file}`\n\n")
|
@@ -161,9 +164,9 @@ module Spoom
|
|
161
164
|
say_error("No report file to open `#{file}`")
|
162
165
|
say_error(<<~ERR, status: nil)
|
163
166
|
|
164
|
-
If you already generated a report under another name use #{blue(
|
167
|
+
If you already generated a report under another name use #{blue("spoom coverage open PATH")}.
|
165
168
|
|
166
|
-
To generate a report run #{blue(
|
169
|
+
To generate a report run #{blue("spoom coverage report")}.
|
167
170
|
ERR
|
168
171
|
exit(1)
|
169
172
|
end
|
@@ -174,6 +177,7 @@ module Spoom
|
|
174
177
|
no_commands do
|
175
178
|
def parse_time(string, option)
|
176
179
|
return nil unless string
|
180
|
+
|
177
181
|
Time.parse(string)
|
178
182
|
rescue ArgumentError
|
179
183
|
say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
|
@@ -196,9 +200,9 @@ module Spoom
|
|
196
200
|
say_error("No snapshot files found in `#{file}`")
|
197
201
|
say_error(<<~ERR, status: nil)
|
198
202
|
|
199
|
-
If you already generated snapshot files under another directory use #{blue(
|
203
|
+
If you already generated snapshot files under another directory use #{blue("spoom coverage report PATH")}.
|
200
204
|
|
201
|
-
To generate snapshot files run #{blue(
|
205
|
+
To generate snapshot files run #{blue("spoom coverage timeline --save")}.
|
202
206
|
ERR
|
203
207
|
end
|
204
208
|
end
|
data/lib/spoom/cli/helper.rb
CHANGED
@@ -60,8 +60,8 @@ module Spoom
|
|
60
60
|
unless in_sorbet_project?
|
61
61
|
say_error(
|
62
62
|
"not in a Sorbet project (`#{sorbet_config_file}` not found)\n\n" \
|
63
|
-
|
64
|
-
|
63
|
+
"When running spoom from another path than the project's root, " \
|
64
|
+
"use `--path PATH` to specify the path to the root."
|
65
65
|
)
|
66
66
|
Kernel.exit(1)
|
67
67
|
end
|
@@ -116,9 +116,9 @@ module Spoom
|
|
116
116
|
word = StringIO.new
|
117
117
|
in_ticks = T.let(false, T::Boolean)
|
118
118
|
string.chars.each do |c|
|
119
|
-
if c ==
|
119
|
+
if c == "`" && !in_ticks
|
120
120
|
in_ticks = true
|
121
|
-
elsif c ==
|
121
|
+
elsif c == "`" && in_ticks
|
122
122
|
in_ticks = false
|
123
123
|
res << colorize(word.string, HIGHLIGHT_COLOR)
|
124
124
|
word = StringIO.new
|
@@ -135,6 +135,7 @@ module Spoom
|
|
135
135
|
sig { params(string: String, color: Color).returns(String) }
|
136
136
|
def colorize(string, *color)
|
137
137
|
return string unless color?
|
138
|
+
|
138
139
|
T.unsafe(self).set_color(string, *color)
|
139
140
|
end
|
140
141
|
|
data/lib/spoom/cli/lsp.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
4
|
+
require "shellwords"
|
5
5
|
|
6
6
|
require_relative "../sorbet/lsp"
|
7
7
|
|
@@ -28,6 +28,7 @@ module Spoom
|
|
28
28
|
Dir["**/*.rb"].each do |file|
|
29
29
|
res = client.document_symbols(to_uri(file))
|
30
30
|
next if res.empty?
|
31
|
+
|
31
32
|
say("Symbols from `#{file}`:")
|
32
33
|
printer.print_objects(res)
|
33
34
|
end
|
data/lib/spoom/cli/run.rb
CHANGED
@@ -22,7 +22,8 @@ module Spoom
|
|
22
22
|
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
23
23
|
option :count, type: :boolean, default: true, desc: "Show errors count"
|
24
24
|
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
25
|
-
|
25
|
+
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
26
|
+
def tc(*paths_to_select)
|
26
27
|
in_sorbet_project!
|
27
28
|
|
28
29
|
path = exec_path
|
@@ -36,7 +37,7 @@ module Spoom
|
|
36
37
|
|
37
38
|
unless limit || code || sort
|
38
39
|
result = T.unsafe(Spoom::Sorbet).srb_tc(
|
39
|
-
*
|
40
|
+
*options[:sorbet_options].split(" "),
|
40
41
|
path: path,
|
41
42
|
capture_err: false,
|
42
43
|
sorbet_bin: sorbet
|
@@ -47,8 +48,10 @@ module Spoom
|
|
47
48
|
exit(result.status)
|
48
49
|
end
|
49
50
|
|
51
|
+
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
50
52
|
result = T.unsafe(Spoom::Sorbet).srb_tc(
|
51
|
-
*
|
53
|
+
*options[:sorbet_options].split(" "),
|
54
|
+
"--error-url-base=#{error_url_base}",
|
52
55
|
path: path,
|
53
56
|
capture_err: true,
|
54
57
|
sorbet_bin: sorbet
|
@@ -61,9 +64,17 @@ module Spoom
|
|
61
64
|
exit(0)
|
62
65
|
end
|
63
66
|
|
64
|
-
errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err)
|
67
|
+
errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
|
65
68
|
errors_count = errors.size
|
66
69
|
|
70
|
+
errors = errors.select { |e| e.code == code } if code
|
71
|
+
|
72
|
+
unless paths_to_select.empty?
|
73
|
+
errors.select! do |error|
|
74
|
+
paths_to_select.any? { |path_to_select| error.file&.start_with?(path_to_select) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
67
78
|
errors = case sort
|
68
79
|
when SORT_CODE
|
69
80
|
Spoom::Sorbet::Errors.sort_errors_by_code(errors)
|
@@ -73,7 +84,6 @@ module Spoom
|
|
73
84
|
errors # preserve natural sort
|
74
85
|
end
|
75
86
|
|
76
|
-
errors = errors.select { |e| e.code == code } if code
|
77
87
|
errors = T.must(errors.slice(0, limit)) if limit
|
78
88
|
|
79
89
|
lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
|
@@ -110,7 +120,7 @@ module Spoom
|
|
110
120
|
cyan = T.let(false, T::Boolean)
|
111
121
|
word = StringIO.new
|
112
122
|
message.chars.each do |c|
|
113
|
-
if c ==
|
123
|
+
if c == "`"
|
114
124
|
cyan = !cyan
|
115
125
|
next
|
116
126
|
end
|
data/lib/spoom/cli.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require "thor"
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "cli/helper"
|
7
7
|
|
8
8
|
require_relative "cli/bump"
|
9
9
|
require_relative "cli/config"
|
@@ -20,7 +20,7 @@ module Spoom
|
|
20
20
|
class_option :color, type: :boolean, default: true, desc: "Use colors"
|
21
21
|
class_option :path, type: :string, default: ".", aliases: :p, desc: "Run spoom in a specific path"
|
22
22
|
|
23
|
-
map T.unsafe(
|
23
|
+
map T.unsafe(["--version", "-v"] => :__print_version)
|
24
24
|
|
25
25
|
desc "bump", "Bump Sorbet sigils from `false` to `true` when no errors"
|
26
26
|
subcommand "bump", Spoom::Cli::Bump
|
@@ -71,8 +71,10 @@ module Spoom
|
|
71
71
|
|
72
72
|
# Utils
|
73
73
|
|
74
|
-
|
75
|
-
|
74
|
+
class << self
|
75
|
+
def exit_on_failure?
|
76
|
+
true
|
77
|
+
end
|
76
78
|
end
|
77
79
|
end
|
78
80
|
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "fileutils"
|
5
|
+
require "open3"
|
6
|
+
|
7
|
+
module Spoom
|
8
|
+
# An abstraction to a Ruby project context
|
9
|
+
#
|
10
|
+
# A context maps to a directory in the file system.
|
11
|
+
# It is used to manipulate files and run commands in the context of this directory.
|
12
|
+
class Context
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
# The absolute path to the directory this context is about
|
16
|
+
sig { returns(String) }
|
17
|
+
attr_reader :absolute_path
|
18
|
+
|
19
|
+
class << self
|
20
|
+
extend T::Sig
|
21
|
+
|
22
|
+
# Create a new context in the system's temporary directory
|
23
|
+
#
|
24
|
+
# `name` is used as prefix to the temporary directory name.
|
25
|
+
# The directory will be created if it doesn't exist.
|
26
|
+
sig { params(name: T.nilable(String)).returns(T.attached_class) }
|
27
|
+
def mktmp!(name = nil)
|
28
|
+
new(::Dir.mktmpdir(name))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a new context about `absolute_path`
|
33
|
+
#
|
34
|
+
# The directory will not be created if it doesn't exist.
|
35
|
+
# Call `#make!` to create it.
|
36
|
+
sig { params(absolute_path: String).void }
|
37
|
+
def initialize(absolute_path)
|
38
|
+
@absolute_path = T.let(::File.expand_path(absolute_path), String)
|
39
|
+
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
|
+
# Create the context directory at `absolute_path`
|
50
|
+
sig { void }
|
51
|
+
def mkdir!
|
52
|
+
FileUtils.rm_rf(@absolute_path)
|
53
|
+
FileUtils.mkdir_p(@absolute_path)
|
54
|
+
end
|
55
|
+
|
56
|
+
# List all files in this context matching `pattern`
|
57
|
+
sig { params(pattern: String).returns(T::Array[String]) }
|
58
|
+
def glob(pattern = "**/*")
|
59
|
+
Dir.glob(absolute_path_to(pattern)).map do |path|
|
60
|
+
Pathname.new(path).relative_path_from(@absolute_path).to_s
|
61
|
+
end.sort
|
62
|
+
end
|
63
|
+
|
64
|
+
# List all files at the top level of this context directory
|
65
|
+
sig { returns(T::Array[String]) }
|
66
|
+
def list
|
67
|
+
glob("*")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Does `relative_path` point to an existing file in this context directory?
|
71
|
+
sig { params(relative_path: String).returns(T::Boolean) }
|
72
|
+
def file?(relative_path)
|
73
|
+
File.file?(absolute_path_to(relative_path))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return the contents of the file at `relative_path` in this context directory
|
77
|
+
#
|
78
|
+
# Will raise if the file doesn't exist.
|
79
|
+
sig { params(relative_path: String).returns(String) }
|
80
|
+
def read(relative_path)
|
81
|
+
File.read(absolute_path_to(relative_path))
|
82
|
+
end
|
83
|
+
|
84
|
+
# Write `contents` in the file at `relative_path` in this context directory
|
85
|
+
#
|
86
|
+
# Append to the file if `append` is true.
|
87
|
+
sig { params(relative_path: String, contents: String, append: T::Boolean).void }
|
88
|
+
def write!(relative_path, contents = "", append: false)
|
89
|
+
absolute_path = absolute_path_to(relative_path)
|
90
|
+
FileUtils.mkdir_p(File.dirname(absolute_path))
|
91
|
+
File.write(absolute_path, contents, mode: append ? "a" : "w")
|
92
|
+
end
|
93
|
+
|
94
|
+
# Remove the path at `relative_path` (recursive + force) in this context directory
|
95
|
+
sig { params(relative_path: String).void }
|
96
|
+
def remove!(relative_path)
|
97
|
+
FileUtils.rm_rf(absolute_path_to(relative_path))
|
98
|
+
end
|
99
|
+
|
100
|
+
# Move the file or directory from `from_relative_path` to `to_relative_path`
|
101
|
+
sig { params(from_relative_path: String, to_relative_path: String).void }
|
102
|
+
def move!(from_relative_path, to_relative_path)
|
103
|
+
destination_path = absolute_path_to(to_relative_path)
|
104
|
+
FileUtils.mkdir_p(File.dirname(destination_path))
|
105
|
+
FileUtils.mv(absolute_path_to(from_relative_path), destination_path)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Delete this context and its content
|
109
|
+
#
|
110
|
+
# Warning: it will `rm -rf` the context directory on the file system.
|
111
|
+
sig { void }
|
112
|
+
def destroy!
|
113
|
+
FileUtils.rm_rf(@absolute_path)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Execution
|
117
|
+
|
118
|
+
# Run a command in this context directory
|
119
|
+
sig { params(command: String, capture_err: T::Boolean).returns(ExecResult) }
|
120
|
+
def exec(command, capture_err: true)
|
121
|
+
Bundler.with_unbundled_env do
|
122
|
+
opts = T.let({ chdir: @absolute_path }, T::Hash[Symbol, T.untyped])
|
123
|
+
|
124
|
+
if capture_err
|
125
|
+
out, err, status = Open3.capture3(command, opts)
|
126
|
+
ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
127
|
+
else
|
128
|
+
out, status = Open3.capture2(command, opts)
|
129
|
+
ExecResult.new(out: out, err: "", status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Bundle
|
135
|
+
|
136
|
+
# Read the `contents` of the Gemfile in this context directory
|
137
|
+
sig { returns(T.nilable(String)) }
|
138
|
+
def read_gemfile
|
139
|
+
read("Gemfile")
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set the `contents` of the Gemfile in this context directory
|
143
|
+
sig { params(contents: String, append: T::Boolean).void }
|
144
|
+
def write_gemfile!(contents, append: false)
|
145
|
+
write!("Gemfile", contents, append: append)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Run a command with `bundle` in this context directory
|
149
|
+
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
150
|
+
def bundle(command, version: nil)
|
151
|
+
command = "_#{version}_ #{command}" if version
|
152
|
+
exec("bundle #{command}")
|
153
|
+
end
|
154
|
+
|
155
|
+
# Run `bundle install` in this context directory
|
156
|
+
sig { params(version: T.nilable(String)).returns(ExecResult) }
|
157
|
+
def bundle_install!(version: nil)
|
158
|
+
bundle("install", version: version)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Run a command `bundle exec` in this context directory
|
162
|
+
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
163
|
+
def bundle_exec(command, version: nil)
|
164
|
+
bundle("exec #{command}", version: version)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Git
|
168
|
+
|
169
|
+
# Run a command prefixed by `git` in this context directory
|
170
|
+
sig { params(command: String).returns(ExecResult) }
|
171
|
+
def git(command)
|
172
|
+
exec("git #{command}")
|
173
|
+
end
|
174
|
+
|
175
|
+
# Run `git init` in this context directory
|
176
|
+
sig { params(branch: String).void }
|
177
|
+
def git_init!(branch: "main")
|
178
|
+
git("init -q -b #{branch}")
|
179
|
+
end
|
180
|
+
|
181
|
+
# Run `git checkout` in this context directory
|
182
|
+
sig { params(ref: String).returns(ExecResult) }
|
183
|
+
def git_checkout!(ref: "main")
|
184
|
+
git("checkout #{ref}")
|
185
|
+
end
|
186
|
+
|
187
|
+
# Get the current git branch in this context directory
|
188
|
+
sig { returns(T.nilable(String)) }
|
189
|
+
def git_current_branch
|
190
|
+
Spoom::Git.current_branch(path: @absolute_path)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Sorbet
|
194
|
+
|
195
|
+
# Run `bundle exec srb` in this context directory
|
196
|
+
sig { params(command: String).returns(ExecResult) }
|
197
|
+
def srb(command)
|
198
|
+
bundle_exec("srb #{command}")
|
199
|
+
end
|
200
|
+
|
201
|
+
# Read the contents of `sorbet/config` in this context directory
|
202
|
+
sig { returns(String) }
|
203
|
+
def read_sorbet_config
|
204
|
+
read(Spoom::Sorbet::CONFIG_PATH)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Set the `contents` of `sorbet/config` in this context directory
|
208
|
+
sig { params(contents: String, append: T::Boolean).void }
|
209
|
+
def write_sorbet_config!(contents, append: false)
|
210
|
+
write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
|
214
|
+
sig { params(relative_path: String).returns(T.nilable(String)) }
|
215
|
+
def read_file_strictness(relative_path)
|
216
|
+
Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -19,14 +19,18 @@ module Spoom
|
|
19
19
|
@data = data
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
class << self
|
23
|
+
extend T::Sig
|
24
|
+
|
25
|
+
sig { returns(String) }
|
26
|
+
def header_style
|
27
|
+
""
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { returns(String) }
|
31
|
+
def header_script
|
32
|
+
""
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
sig { returns(String) }
|
@@ -7,50 +7,52 @@ module Spoom
|
|
7
7
|
module Coverage
|
8
8
|
module D3
|
9
9
|
class CircleMap < Base
|
10
|
-
|
10
|
+
class << self
|
11
|
+
extend T::Sig
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
sig { returns(String) }
|
14
|
+
def header_style
|
15
|
+
<<~CSS
|
16
|
+
.node {
|
17
|
+
cursor: pointer;
|
18
|
+
}
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
.node:hover {
|
21
|
+
stroke: #333;
|
22
|
+
stroke-width: 1px;
|
23
|
+
}
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
.label.dir {
|
26
|
+
fill: #333;
|
27
|
+
}
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
.label.file {
|
30
|
+
font: 12px Arial, sans-serif;
|
31
|
+
}
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
.node.root, .node.file {
|
34
|
+
pointer-events: none;
|
35
|
+
}
|
36
|
+
CSS
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
39
|
+
sig { returns(String) }
|
40
|
+
def header_script
|
41
|
+
<<~JS
|
42
|
+
function treeHeight(root, height = 0) {
|
43
|
+
height += 1;
|
44
|
+
if (root.children && root.children.length > 0)
|
45
|
+
return Math.max(...root.children.map(child => treeHeight(child, height)));
|
46
|
+
else
|
47
|
+
return height;
|
48
|
+
}
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
function tooltipMap(d) {
|
51
|
+
moveTooltip(d)
|
52
|
+
.html("<b>" + d.data.name + "</b>")
|
53
|
+
}
|
54
|
+
JS
|
55
|
+
end
|
54
56
|
end
|
55
57
|
|
56
58
|
sig { override.returns(String) }
|
@@ -159,6 +161,7 @@ module Spoom
|
|
159
161
|
if node.children.empty?
|
160
162
|
return { name: node.name, strictness: tree_node_strictness(node) }
|
161
163
|
end
|
164
|
+
|
162
165
|
{
|
163
166
|
name: node.name,
|
164
167
|
children: node.children.values.map { |n| tree_node_to_json(n) },
|