spoom 1.2.4 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,150 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Cli
|
6
|
+
module Srb
|
7
|
+
class Tc < Thor
|
8
|
+
include Helper
|
9
|
+
|
10
|
+
default_task :tc
|
11
|
+
|
12
|
+
SORT_CODE = "code"
|
13
|
+
SORT_LOC = "loc"
|
14
|
+
SORT_ENUM = [SORT_CODE, SORT_LOC]
|
15
|
+
|
16
|
+
DEFAULT_FORMAT = "%C - %F:%L: %M"
|
17
|
+
|
18
|
+
desc "tc", "Run `srb tc`"
|
19
|
+
option :limit, type: :numeric, aliases: :l, desc: "Limit displayed errors"
|
20
|
+
option :code, type: :numeric, aliases: :c, desc: "Filter displayed errors by code"
|
21
|
+
option :sort, type: :string, aliases: :s, desc: "Sort errors", enum: SORT_ENUM, default: SORT_LOC
|
22
|
+
option :format, type: :string, aliases: :f, desc: "Format line output"
|
23
|
+
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
24
|
+
option :count, type: :boolean, default: true, desc: "Show errors count"
|
25
|
+
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
26
|
+
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
27
|
+
def tc(*paths_to_select)
|
28
|
+
context = context_requiring_sorbet!
|
29
|
+
limit = options[:limit]
|
30
|
+
sort = options[:sort]
|
31
|
+
code = options[:code]
|
32
|
+
uniq = options[:uniq]
|
33
|
+
format = options[:format]
|
34
|
+
count = options[:count]
|
35
|
+
sorbet = options[:sorbet]
|
36
|
+
|
37
|
+
unless limit || code || sort
|
38
|
+
result = T.unsafe(context).srb_tc(
|
39
|
+
*options[:sorbet_options].split(" "),
|
40
|
+
capture_err: false,
|
41
|
+
sorbet_bin: sorbet,
|
42
|
+
)
|
43
|
+
|
44
|
+
say_error(result.err, status: nil, nl: false)
|
45
|
+
exit(result.status)
|
46
|
+
end
|
47
|
+
|
48
|
+
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
49
|
+
result = T.unsafe(context).srb_tc(
|
50
|
+
*options[:sorbet_options].split(" "),
|
51
|
+
"--error-url-base=#{error_url_base}",
|
52
|
+
capture_err: true,
|
53
|
+
sorbet_bin: sorbet,
|
54
|
+
)
|
55
|
+
|
56
|
+
if result.status
|
57
|
+
say_error(result.err, status: nil, nl: false)
|
58
|
+
exit(0)
|
59
|
+
end
|
60
|
+
|
61
|
+
unless result.exit_code == 100
|
62
|
+
# Sorbet will return exit code 100 if there are type checking errors.
|
63
|
+
# If Sorbet returned something else, it means it didn't terminate normally.
|
64
|
+
say_error(result.err, status: nil, nl: false)
|
65
|
+
exit(1)
|
66
|
+
end
|
67
|
+
|
68
|
+
errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
|
69
|
+
errors_count = errors.size
|
70
|
+
|
71
|
+
errors = errors.select { |e| e.code == code } if code
|
72
|
+
|
73
|
+
unless paths_to_select.empty?
|
74
|
+
errors.select! do |error|
|
75
|
+
paths_to_select.any? { |path_to_select| error.file&.start_with?(path_to_select) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
errors = case sort
|
80
|
+
when SORT_CODE
|
81
|
+
Spoom::Sorbet::Errors.sort_errors_by_code(errors)
|
82
|
+
when SORT_LOC
|
83
|
+
errors.sort
|
84
|
+
else
|
85
|
+
errors # preserve natural sort
|
86
|
+
end
|
87
|
+
|
88
|
+
errors = T.must(errors.slice(0, limit)) if limit
|
89
|
+
|
90
|
+
lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
|
91
|
+
lines = lines.uniq if uniq
|
92
|
+
|
93
|
+
lines.each do |line|
|
94
|
+
say_error(line, status: nil)
|
95
|
+
end
|
96
|
+
|
97
|
+
if count
|
98
|
+
if errors_count == errors.size
|
99
|
+
say_error("Errors: #{errors_count}", status: nil)
|
100
|
+
else
|
101
|
+
say_error("Errors: #{errors.size} shown, #{errors_count} total", status: nil)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
exit(1)
|
106
|
+
rescue Spoom::Sorbet::Error::Segfault => error
|
107
|
+
say_error(<<~ERR, status: nil)
|
108
|
+
#{red("!!! Sorbet exited with code #{error.result.exit_code} - SEGFAULT !!!")}
|
109
|
+
|
110
|
+
This is most likely related to a bug in Sorbet.
|
111
|
+
ERR
|
112
|
+
|
113
|
+
exit(error.result.exit_code)
|
114
|
+
rescue Spoom::Sorbet::Error::Killed => error
|
115
|
+
say_error(<<~ERR, status: nil)
|
116
|
+
#{red("!!! Sorbet exited with code #{error.result.exit_code} - KILLED !!!")}
|
117
|
+
ERR
|
118
|
+
|
119
|
+
exit(error.result.exit_code)
|
120
|
+
end
|
121
|
+
|
122
|
+
no_commands do
|
123
|
+
def format_error(error, format)
|
124
|
+
line = format
|
125
|
+
line = line.gsub("%C", yellow(error.code.to_s))
|
126
|
+
line = line.gsub("%F", error.file)
|
127
|
+
line = line.gsub("%L", error.line.to_s)
|
128
|
+
line = line.gsub("%M", colorize_message(error.message))
|
129
|
+
line
|
130
|
+
end
|
131
|
+
|
132
|
+
def colorize_message(message)
|
133
|
+
return message unless color?
|
134
|
+
|
135
|
+
cyan = T.let(false, T::Boolean)
|
136
|
+
word = StringIO.new
|
137
|
+
message.chars.each do |c|
|
138
|
+
if c == "`"
|
139
|
+
cyan = !cyan
|
140
|
+
next
|
141
|
+
end
|
142
|
+
word << (cyan ? cyan(c) : red(c))
|
143
|
+
end
|
144
|
+
word.string
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "srb/bump"
|
5
|
+
require_relative "srb/coverage"
|
6
|
+
require_relative "srb/lsp"
|
7
|
+
require_relative "srb/tc"
|
8
|
+
|
9
|
+
module Spoom
|
10
|
+
module Cli
|
11
|
+
module Srb
|
12
|
+
class Main < Thor
|
13
|
+
desc "lsp", "Send LSP requests to Sorbet"
|
14
|
+
subcommand "lsp", Spoom::Cli::Srb::LSP
|
15
|
+
|
16
|
+
desc "coverage", "Collect metrics related to Sorbet coverage"
|
17
|
+
subcommand "coverage", Spoom::Cli::Srb::Coverage
|
18
|
+
|
19
|
+
desc "bump", "Change Sorbet sigils from one strictness to another when no errors"
|
20
|
+
subcommand "bump", Spoom::Cli::Srb::Bump
|
21
|
+
|
22
|
+
desc "tc", "Run typechecking with advanced options"
|
23
|
+
subcommand "tc", Spoom::Cli::Srb::Tc
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/spoom/cli.rb
CHANGED
@@ -4,12 +4,8 @@
|
|
4
4
|
require "thor"
|
5
5
|
|
6
6
|
require_relative "cli/helper"
|
7
|
-
|
8
|
-
require_relative "cli/
|
9
|
-
require_relative "cli/config"
|
10
|
-
require_relative "cli/lsp"
|
11
|
-
require_relative "cli/coverage"
|
12
|
-
require_relative "cli/run"
|
7
|
+
require_relative "cli/deadcode"
|
8
|
+
require_relative "cli/srb"
|
13
9
|
|
14
10
|
module Spoom
|
15
11
|
module Cli
|
@@ -22,39 +18,83 @@ module Spoom
|
|
22
18
|
|
23
19
|
map T.unsafe(["--version", "-v"] => :__print_version)
|
24
20
|
|
25
|
-
desc "
|
26
|
-
subcommand "
|
21
|
+
desc "srb", "Sorbet related commands"
|
22
|
+
subcommand "srb", Spoom::Cli::Srb::Main
|
27
23
|
|
28
|
-
desc "
|
29
|
-
|
24
|
+
desc "bump", "Bump Sorbet sigils from `false` to `true` when no errors"
|
25
|
+
option :from,
|
26
|
+
type: :string,
|
27
|
+
default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE,
|
28
|
+
desc: "Change only files from this strictness"
|
29
|
+
option :to,
|
30
|
+
type: :string,
|
31
|
+
default: Spoom::Sorbet::Sigils::STRICTNESS_TRUE,
|
32
|
+
desc: "Change files to this strictness"
|
33
|
+
option :force,
|
34
|
+
type: :boolean,
|
35
|
+
default: false,
|
36
|
+
aliases: :f,
|
37
|
+
desc: "Change strictness without type checking"
|
38
|
+
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
39
|
+
option :dry,
|
40
|
+
type: :boolean,
|
41
|
+
default: false,
|
42
|
+
aliases: :d,
|
43
|
+
desc: "Only display what would happen, do not actually change sigils"
|
44
|
+
option :only,
|
45
|
+
type: :string,
|
46
|
+
default: nil,
|
47
|
+
aliases: :o,
|
48
|
+
desc: "Only change specified list (one file by line)"
|
49
|
+
option :suggest_bump_command,
|
50
|
+
type: :string,
|
51
|
+
desc: "Command to suggest if files can be bumped"
|
52
|
+
option :count_errors,
|
53
|
+
type: :boolean,
|
54
|
+
default: false,
|
55
|
+
desc: "Count the number of errors if all files were bumped"
|
56
|
+
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
57
|
+
sig { params(directory: String).void }
|
58
|
+
def bump(directory = ".")
|
59
|
+
say_warning("This command is deprecated. Please use `spoom srb bump` instead.")
|
60
|
+
|
61
|
+
invoke(Cli::Srb::Bump, :bump, [directory], options)
|
62
|
+
end
|
30
63
|
|
31
64
|
desc "coverage", "Collect metrics related to Sorbet coverage"
|
32
|
-
|
65
|
+
def coverage(*args)
|
66
|
+
say_warning("This command is deprecated. Please use `spoom srb bump` instead.")
|
67
|
+
|
68
|
+
invoke(Cli::Srb::Coverage, args, options)
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "deadcode", "Analyze code to find deadcode"
|
72
|
+
subcommand "deadcode", Spoom::Cli::Deadcode
|
33
73
|
|
34
74
|
desc "lsp", "Send LSP requests to Sorbet"
|
35
|
-
|
75
|
+
def lsp(*args)
|
76
|
+
say_warning("This command is deprecated. Please use `spoom srb bump` instead.")
|
36
77
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
desc "files", "List all the files typechecked by Sorbet"
|
41
|
-
option :tree, type: :boolean, default: true, desc: "Display list as an indented tree"
|
42
|
-
option :rbi, type: :boolean, default: false, desc: "Show RBI files"
|
43
|
-
def files
|
44
|
-
context = context_requiring_sorbet!
|
45
|
-
|
46
|
-
files = context.srb_files(include_rbis: options[:rbi])
|
47
|
-
if files.empty?
|
48
|
-
say_error("No file matching `#{Sorbet::CONFIG_PATH}`")
|
49
|
-
exit(1)
|
50
|
-
end
|
78
|
+
invoke(Cli::Srb::LSP, args, options)
|
79
|
+
end
|
51
80
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
81
|
+
SORT_CODE = "code"
|
82
|
+
SORT_LOC = "loc"
|
83
|
+
SORT_ENUM = [SORT_CODE, SORT_LOC]
|
84
|
+
|
85
|
+
desc "tc", "Run Sorbet and parses its output"
|
86
|
+
option :limit, type: :numeric, aliases: :l, desc: "Limit displayed errors"
|
87
|
+
option :code, type: :numeric, aliases: :c, desc: "Filter displayed errors by code"
|
88
|
+
option :sort, type: :string, aliases: :s, desc: "Sort errors", enum: SORT_ENUM, default: SORT_LOC
|
89
|
+
option :format, type: :string, aliases: :f, desc: "Format line output"
|
90
|
+
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
91
|
+
option :count, type: :boolean, default: true, desc: "Show errors count"
|
92
|
+
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
93
|
+
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
94
|
+
def tc(*paths_to_select)
|
95
|
+
say_warning("This command is deprecated. Please use `spoom srb tc` instead.")
|
96
|
+
|
97
|
+
invoke(Cli::Srb::Tc, :tc, paths_to_select, options)
|
58
98
|
end
|
59
99
|
|
60
100
|
desc "--version", "Show version"
|
data/lib/spoom/context/git.rb
CHANGED
@@ -9,7 +9,7 @@ module Spoom
|
|
9
9
|
class << self
|
10
10
|
extend T::Sig
|
11
11
|
|
12
|
-
# Parse a line
|
12
|
+
# Parse a line formatted as `%h %at` into a `Commit`
|
13
13
|
sig { params(string: String).returns(T.nilable(Commit)) }
|
14
14
|
def parse_line(string)
|
15
15
|
sha, epoch = string.split(" ", 2)
|
@@ -127,7 +127,7 @@ module Spoom
|
|
127
127
|
git("show #{arg.join(" ")}")
|
128
128
|
end
|
129
129
|
|
130
|
-
# Is there
|
130
|
+
# Is there uncommitted changes in this context directory?
|
131
131
|
sig { params(path: String).returns(T::Boolean) }
|
132
132
|
def git_workdir_clean?(path: ".")
|
133
133
|
git_diff("HEAD").out.empty?
|
data/lib/spoom/context/sorbet.rb
CHANGED
@@ -75,13 +75,13 @@ module Spoom
|
|
75
75
|
# From Sorbet docs on `--ignore`:
|
76
76
|
# > Ignores input files that contain the given string in their paths (relative to the input path passed to
|
77
77
|
# > Sorbet). Strings beginning with / match against the prefix of these relative paths; others are substring
|
78
|
-
# >
|
78
|
+
# > matches. Matches must be against whole folder and file names, so `foo` matches `/foo/bar.rb` and
|
79
79
|
# > `/bar/foo/baz.rb` but not `/foo.rb` or `/foo2/bar.rb`.
|
80
80
|
string = if string.start_with?("/")
|
81
81
|
# Strings beginning with / match against the prefix of these relative paths
|
82
82
|
File.join(absolute_path, string)
|
83
83
|
else
|
84
|
-
# Others are substring
|
84
|
+
# Others are substring matches
|
85
85
|
File.join(absolute_path, "**", string)
|
86
86
|
end
|
87
87
|
# Matches must be against whole folder and file names
|
@@ -93,6 +93,17 @@ module Spoom
|
|
93
93
|
def ignored!
|
94
94
|
@status = Status::IGNORED
|
95
95
|
end
|
96
|
+
|
97
|
+
# Utils
|
98
|
+
|
99
|
+
sig { params(args: T.untyped).returns(String) }
|
100
|
+
def to_json(*args)
|
101
|
+
{
|
102
|
+
kind: kind,
|
103
|
+
name: name,
|
104
|
+
location: location.to_s,
|
105
|
+
}.to_json
|
106
|
+
end
|
96
107
|
end
|
97
108
|
end
|
98
109
|
end
|