spoom 1.2.4 → 1.3.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/README.md +54 -55
- data/lib/spoom/cli/deadcode.rb +172 -0
- data/lib/spoom/cli/helper.rb +20 -0
- data/lib/spoom/cli/srb/bump.rb +200 -0
- data/lib/spoom/cli/srb/coverage.rb +224 -0
- data/lib/spoom/cli/srb/lsp.rb +159 -0
- data/lib/spoom/cli/srb/tc.rb +150 -0
- data/lib/spoom/cli/srb.rb +27 -0
- data/lib/spoom/cli.rb +72 -32
- data/lib/spoom/context/git.rb +2 -2
- data/lib/spoom/context/sorbet.rb +2 -2
- data/lib/spoom/deadcode/definition.rb +11 -0
- data/lib/spoom/deadcode/indexer.rb +222 -224
- data/lib/spoom/deadcode/location.rb +2 -2
- data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -2
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
- data/lib/spoom/deadcode/plugins/actionpack.rb +4 -6
- data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
- data/lib/spoom/deadcode/plugins/active_record.rb +9 -12
- data/lib/spoom/deadcode/plugins/active_support.rb +11 -0
- data/lib/spoom/deadcode/plugins/base.rb +1 -1
- data/lib/spoom/deadcode/plugins/graphql.rb +4 -4
- data/lib/spoom/deadcode/plugins/namespaces.rb +2 -4
- data/lib/spoom/deadcode/plugins/ruby.rb +8 -17
- data/lib/spoom/deadcode/plugins/sorbet.rb +4 -10
- data/lib/spoom/deadcode/plugins.rb +1 -0
- data/lib/spoom/deadcode/remover.rb +209 -174
- data/lib/spoom/deadcode/send.rb +9 -10
- data/lib/spoom/deadcode/visitor.rb +755 -0
- data/lib/spoom/deadcode.rb +40 -10
- data/lib/spoom/file_tree.rb +0 -16
- data/lib/spoom/sorbet/errors.rb +1 -1
- data/lib/spoom/sorbet/lsp/structures.rb +2 -2
- data/lib/spoom/version.rb +1 -1
- metadata +19 -15
- data/lib/spoom/cli/bump.rb +0 -198
- data/lib/spoom/cli/coverage.rb +0 -222
- data/lib/spoom/cli/lsp.rb +0 -168
- data/lib/spoom/cli/run.rb +0 -148
data/lib/spoom/deadcode.rb
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "erubi"
|
5
|
-
require "
|
5
|
+
require "prism"
|
6
6
|
|
7
|
+
require_relative "deadcode/visitor"
|
7
8
|
require_relative "deadcode/erb"
|
8
9
|
require_relative "deadcode/index"
|
9
10
|
require_relative "deadcode/indexer"
|
@@ -18,10 +19,15 @@ require_relative "deadcode/remover"
|
|
18
19
|
module Spoom
|
19
20
|
module Deadcode
|
20
21
|
class Error < Spoom::Error
|
21
|
-
extend T::Sig
|
22
22
|
extend T::Helpers
|
23
23
|
|
24
24
|
abstract!
|
25
|
+
end
|
26
|
+
|
27
|
+
class ParserError < Error; end
|
28
|
+
|
29
|
+
class IndexerError < Error
|
30
|
+
extend T::Sig
|
25
31
|
|
26
32
|
sig { params(message: String, parent: Exception).void }
|
27
33
|
def initialize(message, parent:)
|
@@ -30,23 +36,47 @@ module Spoom
|
|
30
36
|
end
|
31
37
|
end
|
32
38
|
|
33
|
-
class ParserError < Error; end
|
34
|
-
class IndexerError < Error; end
|
35
|
-
|
36
39
|
class << self
|
37
40
|
extend T::Sig
|
38
41
|
|
39
|
-
sig { params(
|
40
|
-
def
|
41
|
-
|
42
|
+
sig { params(ruby: String, file: String).returns(Prism::Node) }
|
43
|
+
def parse_ruby(ruby, file:)
|
44
|
+
result = Prism.parse(ruby)
|
45
|
+
unless result.success?
|
46
|
+
message = +"Error while parsing #{file}:\n"
|
47
|
+
|
48
|
+
result.errors.each do |e|
|
49
|
+
message << "- #{e.message} (at #{e.location.start_line}:#{e.location.start_column})\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
raise ParserError, message
|
53
|
+
end
|
54
|
+
|
55
|
+
result.value
|
56
|
+
end
|
57
|
+
|
58
|
+
sig do
|
59
|
+
params(
|
60
|
+
index: Index,
|
61
|
+
node: Prism::Node,
|
62
|
+
ruby: String,
|
63
|
+
file: String,
|
64
|
+
plugins: T::Array[Deadcode::Plugins::Base],
|
65
|
+
).void
|
66
|
+
end
|
67
|
+
def index_node(index, node, ruby, file:, plugins: [])
|
42
68
|
visitor = Spoom::Deadcode::Indexer.new(file, ruby, index, plugins: plugins)
|
43
69
|
visitor.visit(node)
|
44
|
-
rescue SyntaxTree::Parser::ParseError => e
|
45
|
-
raise ParserError.new("Error while parsing #{file} (#{e.message} at #{e.lineno}:#{e.column})", parent: e)
|
46
70
|
rescue => e
|
47
71
|
raise IndexerError.new("Error while indexing #{file} (#{e.message})", parent: e)
|
48
72
|
end
|
49
73
|
|
74
|
+
sig { params(index: Index, ruby: String, file: String, plugins: T::Array[Deadcode::Plugins::Base]).void }
|
75
|
+
def index_ruby(index, ruby, file:, plugins: [])
|
76
|
+
node = parse_ruby(ruby, file: file)
|
77
|
+
index_node(index, node, ruby, file: file, plugins: plugins)
|
78
|
+
end
|
79
|
+
|
50
80
|
sig { params(index: Index, erb: String, file: String, plugins: T::Array[Deadcode::Plugins::Base]).void }
|
51
81
|
def index_erb(index, erb, file:, plugins: [])
|
52
82
|
ruby = ERB.new(erb).src
|
data/lib/spoom/file_tree.rb
CHANGED
@@ -54,14 +54,6 @@ module Spoom
|
|
54
54
|
nodes.map(&:path)
|
55
55
|
end
|
56
56
|
|
57
|
-
# Return a map of strictnesses for each node in the tree
|
58
|
-
sig { params(context: Context).returns(T::Hash[Node, T.nilable(String)]) }
|
59
|
-
def nodes_strictnesses(context)
|
60
|
-
v = CollectStrictnesses.new(context)
|
61
|
-
v.visit_tree(self)
|
62
|
-
v.strictnesses
|
63
|
-
end
|
64
|
-
|
65
57
|
# Return a map of typing scores for each node in the tree
|
66
58
|
sig { params(context: Context).returns(T::Hash[Node, Float]) }
|
67
59
|
def nodes_strictness_scores(context)
|
@@ -82,14 +74,6 @@ module Spoom
|
|
82
74
|
printer.visit_tree(self)
|
83
75
|
end
|
84
76
|
|
85
|
-
sig { params(context: Context, out: T.any(IO, StringIO), colors: T::Boolean).void }
|
86
|
-
def print_with_strictnesses(context, out: $stdout, colors: true)
|
87
|
-
strictnesses = nodes_strictnesses(context)
|
88
|
-
|
89
|
-
printer = Printer.new(strictnesses, out: out, colors: colors)
|
90
|
-
printer.visit_tree(self)
|
91
|
-
end
|
92
|
-
|
93
77
|
# A node representing either a file or a directory inside a FileTree
|
94
78
|
class Node < T::Struct
|
95
79
|
extend T::Sig
|
data/lib/spoom/sorbet/errors.rb
CHANGED
@@ -76,7 +76,7 @@ module Spoom
|
|
76
76
|
^ # match beginning of line
|
77
77
|
(\S[^:]*) # capture filename as something that starts with a non-space character
|
78
78
|
# followed by anything that is not a colon character
|
79
|
-
: # match the filename - line number
|
79
|
+
: # match the filename - line number separator
|
80
80
|
(\d+) # capture the line number
|
81
81
|
:\s # match the line number - error message separator
|
82
82
|
(.*) # capture the error message
|
@@ -182,7 +182,7 @@ module Spoom
|
|
182
182
|
const :range, LSP::Range
|
183
183
|
const :code, Integer
|
184
184
|
const :message, String
|
185
|
-
const :
|
185
|
+
const :information, Object
|
186
186
|
|
187
187
|
class << self
|
188
188
|
extend T::Sig
|
@@ -193,7 +193,7 @@ module Spoom
|
|
193
193
|
range: Range.from_json(json["range"]),
|
194
194
|
code: json["code"].to_i,
|
195
195
|
message: json["message"],
|
196
|
-
|
196
|
+
information: json["relatedInformation"],
|
197
197
|
)
|
198
198
|
end
|
199
199
|
end
|
data/lib/spoom/version.rb
CHANGED
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.3.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: 2024-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 13.0
|
61
|
+
version: 13.1.0
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 13.0
|
68
|
+
version: 13.1.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: erubi
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,33 +81,33 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 1.10.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: prism
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
89
|
+
version: 0.19.0
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
96
|
+
version: 0.19.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: sorbet-static-and-runtime
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 0.5.10187
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
110
|
+
version: 0.5.10187
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: thor
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,12 +137,14 @@ files:
|
|
137
137
|
- lib/spoom.rb
|
138
138
|
- lib/spoom/backtrace_filter/minitest.rb
|
139
139
|
- lib/spoom/cli.rb
|
140
|
-
- lib/spoom/cli/bump.rb
|
141
140
|
- lib/spoom/cli/config.rb
|
142
|
-
- lib/spoom/cli/
|
141
|
+
- lib/spoom/cli/deadcode.rb
|
143
142
|
- lib/spoom/cli/helper.rb
|
144
|
-
- lib/spoom/cli/
|
145
|
-
- lib/spoom/cli/
|
143
|
+
- lib/spoom/cli/srb.rb
|
144
|
+
- lib/spoom/cli/srb/bump.rb
|
145
|
+
- lib/spoom/cli/srb/coverage.rb
|
146
|
+
- lib/spoom/cli/srb/lsp.rb
|
147
|
+
- lib/spoom/cli/srb/tc.rb
|
146
148
|
- lib/spoom/colors.rb
|
147
149
|
- lib/spoom/context.rb
|
148
150
|
- lib/spoom/context/bundle.rb
|
@@ -166,6 +168,7 @@ files:
|
|
166
168
|
- lib/spoom/deadcode/location.rb
|
167
169
|
- lib/spoom/deadcode/plugins.rb
|
168
170
|
- lib/spoom/deadcode/plugins/action_mailer.rb
|
171
|
+
- lib/spoom/deadcode/plugins/action_mailer_preview.rb
|
169
172
|
- lib/spoom/deadcode/plugins/actionpack.rb
|
170
173
|
- lib/spoom/deadcode/plugins/active_job.rb
|
171
174
|
- lib/spoom/deadcode/plugins/active_model.rb
|
@@ -185,6 +188,7 @@ files:
|
|
185
188
|
- lib/spoom/deadcode/reference.rb
|
186
189
|
- lib/spoom/deadcode/remover.rb
|
187
190
|
- lib/spoom/deadcode/send.rb
|
191
|
+
- lib/spoom/deadcode/visitor.rb
|
188
192
|
- lib/spoom/file_collector.rb
|
189
193
|
- lib/spoom/file_tree.rb
|
190
194
|
- lib/spoom/printer.rb
|
@@ -222,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
226
|
- !ruby/object:Gem::Version
|
223
227
|
version: '0'
|
224
228
|
requirements: []
|
225
|
-
rubygems_version: 3.
|
229
|
+
rubygems_version: 3.5.7
|
226
230
|
signing_key:
|
227
231
|
specification_version: 4
|
228
232
|
summary: Useful tools for Sorbet enthusiasts.
|
data/lib/spoom/cli/bump.rb
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "find"
|
5
|
-
require "open3"
|
6
|
-
|
7
|
-
module Spoom
|
8
|
-
module Cli
|
9
|
-
class Bump < Thor
|
10
|
-
extend T::Sig
|
11
|
-
include Helper
|
12
|
-
|
13
|
-
default_task :bump
|
14
|
-
|
15
|
-
desc "bump DIRECTORY", "Change Sorbet sigils from one strictness to another when no errors"
|
16
|
-
option :from,
|
17
|
-
type: :string,
|
18
|
-
default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE,
|
19
|
-
desc: "Change only files from this strictness"
|
20
|
-
option :to,
|
21
|
-
type: :string,
|
22
|
-
default: Spoom::Sorbet::Sigils::STRICTNESS_TRUE,
|
23
|
-
desc: "Change files to this strictness"
|
24
|
-
option :force,
|
25
|
-
type: :boolean,
|
26
|
-
default: false,
|
27
|
-
aliases: :f,
|
28
|
-
desc: "Change strictness without type checking"
|
29
|
-
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
30
|
-
option :dry,
|
31
|
-
type: :boolean,
|
32
|
-
default: false,
|
33
|
-
aliases: :d,
|
34
|
-
desc: "Only display what would happen, do not actually change sigils"
|
35
|
-
option :only,
|
36
|
-
type: :string,
|
37
|
-
default: nil,
|
38
|
-
aliases: :o,
|
39
|
-
desc: "Only change specified list (one file by line)"
|
40
|
-
option :suggest_bump_command,
|
41
|
-
type: :string,
|
42
|
-
desc: "Command to suggest if files can be bumped"
|
43
|
-
option :count_errors,
|
44
|
-
type: :boolean,
|
45
|
-
default: false,
|
46
|
-
desc: "Count the number of errors if all files were bumped"
|
47
|
-
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
48
|
-
sig { params(directory: String).void }
|
49
|
-
def bump(directory = ".")
|
50
|
-
context = context_requiring_sorbet!
|
51
|
-
from = options[:from]
|
52
|
-
to = options[:to]
|
53
|
-
force = options[:force]
|
54
|
-
dry = options[:dry]
|
55
|
-
only = options[:only]
|
56
|
-
cmd = options[:suggest_bump_command]
|
57
|
-
directory = File.expand_path(directory)
|
58
|
-
exec_path = File.expand_path(self.exec_path)
|
59
|
-
|
60
|
-
unless Sorbet::Sigils.valid_strictness?(from)
|
61
|
-
say_error("Invalid strictness `#{from}` for option `--from`")
|
62
|
-
exit(1)
|
63
|
-
end
|
64
|
-
|
65
|
-
unless Sorbet::Sigils.valid_strictness?(to)
|
66
|
-
say_error("Invalid strictness `#{to}` for option `--to`")
|
67
|
-
exit(1)
|
68
|
-
end
|
69
|
-
|
70
|
-
if options[:count_errors] && !dry
|
71
|
-
say_error("`--count-errors` can only be used with `--dry`")
|
72
|
-
exit(1)
|
73
|
-
end
|
74
|
-
|
75
|
-
say("Checking files...")
|
76
|
-
|
77
|
-
files_to_bump = context.srb_files_with_strictness(from, include_rbis: false)
|
78
|
-
.map { |file| File.expand_path(file, context.absolute_path) }
|
79
|
-
.select { |file| file.start_with?(directory) }
|
80
|
-
|
81
|
-
if only
|
82
|
-
list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
|
83
|
-
files_to_bump.select! { |file| list.include?(File.expand_path(file)) }
|
84
|
-
end
|
85
|
-
|
86
|
-
say("\n")
|
87
|
-
|
88
|
-
if files_to_bump.empty?
|
89
|
-
say("No files to bump from `#{from}` to `#{to}`")
|
90
|
-
exit(0)
|
91
|
-
end
|
92
|
-
|
93
|
-
Sorbet::Sigils.change_sigil_in_files(files_to_bump, to)
|
94
|
-
|
95
|
-
if force
|
96
|
-
print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
97
|
-
undo_changes(files_to_bump, from) if dry
|
98
|
-
exit(files_to_bump.empty?)
|
99
|
-
end
|
100
|
-
|
101
|
-
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
102
|
-
result = begin
|
103
|
-
T.unsafe(context).srb_tc(
|
104
|
-
*options[:sorbet_options].split(" "),
|
105
|
-
"--error-url-base=#{error_url_base}",
|
106
|
-
capture_err: true,
|
107
|
-
sorbet_bin: options[:sorbet],
|
108
|
-
)
|
109
|
-
rescue Spoom::Sorbet::Error::Segfault => error
|
110
|
-
say_error(<<~ERR, status: nil)
|
111
|
-
!!! Sorbet exited with code #{Spoom::Sorbet::SEGFAULT_CODE} - SEGFAULT !!!
|
112
|
-
|
113
|
-
This is most likely related to a bug in Sorbet.
|
114
|
-
It means one of the file bumped to `typed: #{to}` made Sorbet crash.
|
115
|
-
Run `spoom bump -f` locally followed by `bundle exec srb tc` to investigate the problem.
|
116
|
-
ERR
|
117
|
-
undo_changes(files_to_bump, from)
|
118
|
-
exit(error.result.exit_code)
|
119
|
-
rescue Spoom::Sorbet::Error::Killed => error
|
120
|
-
say_error(<<~ERR, status: nil)
|
121
|
-
!!! Sorbet exited with code #{Spoom::Sorbet::KILLED_CODE} - KILLED !!!
|
122
|
-
|
123
|
-
It means Sorbet was killed while executing. Changes to files have not been applied.
|
124
|
-
Re-run `spoom bump` to try again.
|
125
|
-
ERR
|
126
|
-
undo_changes(files_to_bump, from)
|
127
|
-
exit(error.result.exit_code)
|
128
|
-
end
|
129
|
-
|
130
|
-
if result.status
|
131
|
-
print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
132
|
-
undo_changes(files_to_bump, from) if dry
|
133
|
-
exit(files_to_bump.empty?)
|
134
|
-
end
|
135
|
-
|
136
|
-
unless result.exit_code == 100
|
137
|
-
# Sorbet will return exit code 100 if there are type checking errors.
|
138
|
-
# If Sorbet returned something else, it means it didn't terminate normally.
|
139
|
-
say_error(result.err, status: nil, nl: false)
|
140
|
-
undo_changes(files_to_bump, from)
|
141
|
-
exit(1)
|
142
|
-
end
|
143
|
-
|
144
|
-
errors = Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
|
145
|
-
|
146
|
-
all_files = errors.flat_map do |err|
|
147
|
-
[err.file, *err.files_from_error_sections]
|
148
|
-
end
|
149
|
-
|
150
|
-
files_with_errors = all_files.map do |file|
|
151
|
-
path = File.expand_path(file)
|
152
|
-
next unless path.start_with?(directory)
|
153
|
-
next unless File.file?(path)
|
154
|
-
next unless files_to_bump.include?(path)
|
155
|
-
|
156
|
-
path
|
157
|
-
end.compact.uniq
|
158
|
-
|
159
|
-
undo_changes(files_with_errors, from)
|
160
|
-
|
161
|
-
say("Found #{errors.length} type checking error#{"s" if errors.length > 1}") if options[:count_errors]
|
162
|
-
|
163
|
-
files_changed = files_to_bump - files_with_errors
|
164
|
-
print_changes(files_changed, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
165
|
-
undo_changes(files_to_bump, from) if dry
|
166
|
-
exit(files_changed.empty?)
|
167
|
-
end
|
168
|
-
|
169
|
-
no_commands do
|
170
|
-
def print_changes(files, command:, from: "false", to: "true", dry: false, path: File.expand_path("."))
|
171
|
-
files_count = files.size
|
172
|
-
if files_count.zero?
|
173
|
-
say("No files to bump from `#{from}` to `#{to}`")
|
174
|
-
return
|
175
|
-
end
|
176
|
-
message = StringIO.new
|
177
|
-
message << (dry ? "Can bump" : "Bumped")
|
178
|
-
message << " `#{files_count}` file#{"s" if files_count > 1}"
|
179
|
-
message << " from `#{from}` to `#{to}`:"
|
180
|
-
say(message.string)
|
181
|
-
files.each do |file|
|
182
|
-
file_path = Pathname.new(file).relative_path_from(path)
|
183
|
-
say(" + #{file_path}")
|
184
|
-
end
|
185
|
-
if dry && command
|
186
|
-
say("\nRun `#{command}` to bump #{files_count > 1 ? "them" : "it"}")
|
187
|
-
elsif dry
|
188
|
-
say("\nRun `spoom bump --from #{from} --to #{to}` locally then `commit the changes` and `push them`")
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def undo_changes(files, from_strictness)
|
193
|
-
Sorbet::Sigils.change_sigil_in_files(files, from_strictness)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
data/lib/spoom/cli/coverage.rb
DELETED
@@ -1,222 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require_relative "../coverage"
|
5
|
-
require_relative "../timeline"
|
6
|
-
|
7
|
-
module Spoom
|
8
|
-
module Cli
|
9
|
-
class Coverage < Thor
|
10
|
-
include Helper
|
11
|
-
|
12
|
-
DATA_DIR = "spoom_data"
|
13
|
-
|
14
|
-
default_task :snapshot
|
15
|
-
|
16
|
-
desc "snapshot", "Run srb tc and display metrics"
|
17
|
-
option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
|
18
|
-
option :rbi, type: :boolean, default: true, desc: "Include RBI files in metrics"
|
19
|
-
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
20
|
-
def snapshot
|
21
|
-
context = context_requiring_sorbet!
|
22
|
-
sorbet = options[:sorbet]
|
23
|
-
|
24
|
-
snapshot = Spoom::Coverage.snapshot(context, rbi: options[:rbi], sorbet_bin: sorbet)
|
25
|
-
snapshot.print
|
26
|
-
|
27
|
-
save_dir = options[:save]
|
28
|
-
return unless save_dir
|
29
|
-
|
30
|
-
FileUtils.mkdir_p(save_dir)
|
31
|
-
file = "#{save_dir}/#{snapshot.commit_sha || snapshot.timestamp}.json"
|
32
|
-
File.write(file, snapshot.to_json)
|
33
|
-
say("\nSnapshot data saved under `#{file}`")
|
34
|
-
end
|
35
|
-
|
36
|
-
desc "timeline", "Replay a project and collect metrics"
|
37
|
-
option :from, type: :string, desc: "From commit date"
|
38
|
-
option :to, type: :string, default: Time.now.strftime("%F"), desc: "To commit date"
|
39
|
-
option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
|
40
|
-
option :bundle_install, type: :boolean, desc: "Execute `bundle install` before collecting metrics"
|
41
|
-
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
42
|
-
def timeline
|
43
|
-
context = context_requiring_sorbet!
|
44
|
-
path = exec_path
|
45
|
-
sorbet = options[:sorbet]
|
46
|
-
|
47
|
-
ref_before = context.git_current_branch
|
48
|
-
ref_before = context.git_last_commit&.sha unless ref_before
|
49
|
-
unless ref_before
|
50
|
-
say_error("Not in a git repository")
|
51
|
-
say_error("\nSpoom needs to checkout into your previous commits to build the timeline.", status: nil)
|
52
|
-
exit(1)
|
53
|
-
end
|
54
|
-
|
55
|
-
unless context.git_workdir_clean?
|
56
|
-
say_error("Uncommited changes")
|
57
|
-
say_error(<<~ERR, status: nil)
|
58
|
-
|
59
|
-
Spoom needs to checkout into your previous commits to build the timeline."
|
60
|
-
|
61
|
-
Please `git commit` or `git stash` your changes then try again
|
62
|
-
ERR
|
63
|
-
exit(1)
|
64
|
-
end
|
65
|
-
|
66
|
-
save_dir = options[:save]
|
67
|
-
FileUtils.mkdir_p(save_dir) if save_dir
|
68
|
-
|
69
|
-
from = parse_time(options[:from], "--from")
|
70
|
-
to = parse_time(options[:to], "--to")
|
71
|
-
|
72
|
-
unless from
|
73
|
-
intro_commit = context.sorbet_intro_commit
|
74
|
-
intro_commit = T.must(intro_commit) # we know it's in there since in_sorbet_project!
|
75
|
-
from = intro_commit.time
|
76
|
-
end
|
77
|
-
|
78
|
-
timeline = Spoom::Timeline.new(context, from, to)
|
79
|
-
ticks = timeline.ticks
|
80
|
-
|
81
|
-
if ticks.empty?
|
82
|
-
say_error("No commits to replay, try different `--from` and `--to` options")
|
83
|
-
exit(1)
|
84
|
-
end
|
85
|
-
|
86
|
-
ticks.each_with_index do |commit, i|
|
87
|
-
say("Analyzing commit `#{commit.sha}` - #{commit.time.strftime("%F")} (#{i + 1} / #{ticks.size})")
|
88
|
-
|
89
|
-
context.git_checkout!(ref: commit.sha)
|
90
|
-
|
91
|
-
snapshot = T.let(nil, T.nilable(Spoom::Coverage::Snapshot))
|
92
|
-
if options[:bundle_install]
|
93
|
-
Bundler.with_unbundled_env do
|
94
|
-
next unless bundle_install(path, commit.sha)
|
95
|
-
|
96
|
-
snapshot = Spoom::Coverage.snapshot(context, sorbet_bin: sorbet)
|
97
|
-
end
|
98
|
-
else
|
99
|
-
snapshot = Spoom::Coverage.snapshot(context, sorbet_bin: sorbet)
|
100
|
-
end
|
101
|
-
next unless snapshot
|
102
|
-
|
103
|
-
snapshot.print(indent_level: 2)
|
104
|
-
say("\n")
|
105
|
-
|
106
|
-
next unless save_dir
|
107
|
-
|
108
|
-
file = "#{save_dir}/#{commit.sha}.json"
|
109
|
-
File.write(file, snapshot.to_json)
|
110
|
-
say(" Snapshot data saved under `#{file}`\n\n")
|
111
|
-
end
|
112
|
-
context.git_checkout!(ref: ref_before)
|
113
|
-
end
|
114
|
-
|
115
|
-
desc "report", "Produce a typing coverage report"
|
116
|
-
option :data, type: :string, default: DATA_DIR, desc: "Snapshots JSON data"
|
117
|
-
option :file,
|
118
|
-
type: :string,
|
119
|
-
default: "spoom_report.html",
|
120
|
-
aliases: :f,
|
121
|
-
desc: "Save report to file"
|
122
|
-
option :color_ignore,
|
123
|
-
type: :string,
|
124
|
-
default: Spoom::Coverage::D3::COLOR_IGNORE,
|
125
|
-
desc: "Color used for typed: ignore"
|
126
|
-
option :color_false,
|
127
|
-
type: :string,
|
128
|
-
default: Spoom::Coverage::D3::COLOR_FALSE,
|
129
|
-
desc: "Color used for typed: false"
|
130
|
-
option :color_true,
|
131
|
-
type: :string,
|
132
|
-
default: Spoom::Coverage::D3::COLOR_TRUE,
|
133
|
-
desc: "Color used for typed: true"
|
134
|
-
option :color_strict,
|
135
|
-
type: :string,
|
136
|
-
default: Spoom::Coverage::D3::COLOR_STRICT,
|
137
|
-
desc: "Color used for typed: strict"
|
138
|
-
option :color_strong,
|
139
|
-
type: :string,
|
140
|
-
default: Spoom::Coverage::D3::COLOR_STRONG,
|
141
|
-
desc: "Color used for typed: strong"
|
142
|
-
def report
|
143
|
-
context = context_requiring_sorbet!
|
144
|
-
|
145
|
-
data_dir = options[:data]
|
146
|
-
files = Dir.glob("#{data_dir}/*.json")
|
147
|
-
if files.empty?
|
148
|
-
message_no_data(data_dir)
|
149
|
-
exit(1)
|
150
|
-
end
|
151
|
-
|
152
|
-
snapshots = files.sort.map do |file|
|
153
|
-
json = File.read(file)
|
154
|
-
Spoom::Coverage::Snapshot.from_json(json)
|
155
|
-
end.filter(&:commit_timestamp).sort_by!(&:commit_timestamp)
|
156
|
-
|
157
|
-
palette = Spoom::Coverage::D3::ColorPalette.new(
|
158
|
-
ignore: options[:color_ignore],
|
159
|
-
false: options[:color_false],
|
160
|
-
true: options[:color_true],
|
161
|
-
strict: options[:color_strict],
|
162
|
-
strong: options[:color_strong],
|
163
|
-
)
|
164
|
-
|
165
|
-
report = Spoom::Coverage.report(context, snapshots, palette: palette)
|
166
|
-
file = options[:file]
|
167
|
-
File.write(file, report.html)
|
168
|
-
say("Report generated under `#{file}`")
|
169
|
-
say("\nUse `spoom coverage open` to open it.")
|
170
|
-
end
|
171
|
-
|
172
|
-
desc "open", "Open the typing coverage report"
|
173
|
-
def open(file = "spoom_report.html")
|
174
|
-
unless File.exist?(file)
|
175
|
-
say_error("No report file to open `#{file}`")
|
176
|
-
say_error(<<~ERR, status: nil)
|
177
|
-
|
178
|
-
If you already generated a report under another name use #{blue("spoom coverage open PATH")}.
|
179
|
-
|
180
|
-
To generate a report run #{blue("spoom coverage report")}.
|
181
|
-
ERR
|
182
|
-
exit(1)
|
183
|
-
end
|
184
|
-
|
185
|
-
exec("open #{file}")
|
186
|
-
end
|
187
|
-
|
188
|
-
no_commands do
|
189
|
-
def parse_time(string, option)
|
190
|
-
return unless string
|
191
|
-
|
192
|
-
Time.parse(string)
|
193
|
-
rescue ArgumentError
|
194
|
-
say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
|
195
|
-
exit(1)
|
196
|
-
end
|
197
|
-
|
198
|
-
def bundle_install(path, sha)
|
199
|
-
opts = {}
|
200
|
-
opts[:chdir] = path
|
201
|
-
out, status = Open3.capture2e("bundle install", opts)
|
202
|
-
unless status.success?
|
203
|
-
say_error("Can't run `bundle install` for commit `#{sha}`. Skipping snapshot")
|
204
|
-
say_error(out, status: nil)
|
205
|
-
return false
|
206
|
-
end
|
207
|
-
true
|
208
|
-
end
|
209
|
-
|
210
|
-
def message_no_data(file)
|
211
|
-
say_error("No snapshot files found in `#{file}`")
|
212
|
-
say_error(<<~ERR, status: nil)
|
213
|
-
|
214
|
-
If you already generated snapshot files under another directory use #{blue("spoom coverage report PATH")}.
|
215
|
-
|
216
|
-
To generate snapshot files run #{blue("spoom coverage timeline --save")}.
|
217
|
-
ERR
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|