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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -55
  3. data/lib/spoom/cli/deadcode.rb +172 -0
  4. data/lib/spoom/cli/helper.rb +20 -0
  5. data/lib/spoom/cli/srb/bump.rb +200 -0
  6. data/lib/spoom/cli/srb/coverage.rb +224 -0
  7. data/lib/spoom/cli/srb/lsp.rb +159 -0
  8. data/lib/spoom/cli/srb/tc.rb +150 -0
  9. data/lib/spoom/cli/srb.rb +27 -0
  10. data/lib/spoom/cli.rb +72 -32
  11. data/lib/spoom/context/git.rb +2 -2
  12. data/lib/spoom/context/sorbet.rb +2 -2
  13. data/lib/spoom/deadcode/definition.rb +11 -0
  14. data/lib/spoom/deadcode/indexer.rb +222 -224
  15. data/lib/spoom/deadcode/location.rb +2 -2
  16. data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -2
  17. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
  18. data/lib/spoom/deadcode/plugins/actionpack.rb +4 -6
  19. data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
  20. data/lib/spoom/deadcode/plugins/active_record.rb +9 -12
  21. data/lib/spoom/deadcode/plugins/active_support.rb +11 -0
  22. data/lib/spoom/deadcode/plugins/base.rb +1 -1
  23. data/lib/spoom/deadcode/plugins/graphql.rb +4 -4
  24. data/lib/spoom/deadcode/plugins/namespaces.rb +2 -4
  25. data/lib/spoom/deadcode/plugins/ruby.rb +8 -17
  26. data/lib/spoom/deadcode/plugins/sorbet.rb +4 -10
  27. data/lib/spoom/deadcode/plugins.rb +1 -0
  28. data/lib/spoom/deadcode/remover.rb +209 -174
  29. data/lib/spoom/deadcode/send.rb +9 -10
  30. data/lib/spoom/deadcode/visitor.rb +755 -0
  31. data/lib/spoom/deadcode.rb +40 -10
  32. data/lib/spoom/file_tree.rb +0 -16
  33. data/lib/spoom/sorbet/errors.rb +1 -1
  34. data/lib/spoom/sorbet/lsp/structures.rb +2 -2
  35. data/lib/spoom/version.rb +1 -1
  36. metadata +19 -15
  37. data/lib/spoom/cli/bump.rb +0 -198
  38. data/lib/spoom/cli/coverage.rb +0 -222
  39. data/lib/spoom/cli/lsp.rb +0 -168
  40. data/lib/spoom/cli/run.rb +0 -148
data/lib/spoom/cli/lsp.rb DELETED
@@ -1,168 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "shellwords"
5
-
6
- require_relative "../sorbet/lsp"
7
-
8
- module Spoom
9
- module Cli
10
- class LSP < Thor
11
- include Helper
12
-
13
- default_task :show
14
-
15
- desc "interactive", "Interactive LSP mode"
16
- def show
17
- context_requiring_sorbet!
18
-
19
- lsp = lsp_client
20
- # TODO: run interactive mode
21
- puts lsp
22
- end
23
-
24
- desc "list", "List all known symbols"
25
- # TODO: options, filter, limit, kind etc.. filter rbi
26
- def list
27
- run do |client|
28
- printer = symbol_printer
29
- Dir["**/*.rb"].each do |file|
30
- res = client.document_symbols(to_uri(file))
31
- next if res.empty?
32
-
33
- say("Symbols from `#{file}`:")
34
- printer.print_objects(res)
35
- end
36
- end
37
- end
38
-
39
- desc "hover", "Request hover informations"
40
- # TODO: options, filter, limit, kind etc.. filter rbi
41
- def hover(file, line, col)
42
- run do |client|
43
- res = client.hover(to_uri(file), line.to_i, col.to_i)
44
- say("Hovering `#{file}:#{line}:#{col}`:")
45
- if res
46
- symbol_printer.print_object(res)
47
- else
48
- say("<no data>")
49
- end
50
- end
51
- end
52
-
53
- desc "defs", "List definitions of a symbol"
54
- # TODO: options, filter, limit, kind etc.. filter rbi
55
- def defs(file, line, col)
56
- run do |client|
57
- res = client.definitions(to_uri(file), line.to_i, col.to_i)
58
- say("Definitions for `#{file}:#{line}:#{col}`:")
59
- symbol_printer.print_list(res)
60
- end
61
- end
62
-
63
- desc "find", "Find symbols matching a query"
64
- # TODO: options, filter, limit, kind etc.. filter rbi
65
- def find(query)
66
- run do |client|
67
- res = client.symbols(query).reject { |symbol| symbol.location.uri.start_with?("https") }
68
- say("Symbols matching `#{query}`:")
69
- symbol_printer.print_objects(res)
70
- end
71
- end
72
-
73
- desc "symbols", "List symbols from a file"
74
- # TODO: options, filter, limit, kind etc.. filter rbi
75
- def symbols(file)
76
- run do |client|
77
- res = client.document_symbols(to_uri(file))
78
- say("Symbols from `#{file}`:")
79
- symbol_printer.print_objects(res)
80
- end
81
- end
82
-
83
- desc "refs", "List references to a symbol"
84
- # TODO: options, filter, limit, kind etc.. filter rbi
85
- def refs(file, line, col)
86
- run do |client|
87
- res = client.references(to_uri(file), line.to_i, col.to_i)
88
- say("References to `#{file}:#{line}:#{col}`:")
89
- symbol_printer.print_list(res)
90
- end
91
- end
92
-
93
- desc "sigs", "List signatures for a symbol"
94
- # TODO: options, filter, limit, kind etc.. filter rbi
95
- def sigs(file, line, col)
96
- run do |client|
97
- res = client.signatures(to_uri(file), line.to_i, col.to_i)
98
- say("Signature for `#{file}:#{line}:#{col}`:")
99
- symbol_printer.print_list(res)
100
- end
101
- end
102
-
103
- desc "types", "Display type of a symbol"
104
- # TODO: options, filter, limit, kind etc.. filter rbi
105
- def types(file, line, col)
106
- run do |client|
107
- res = client.type_definitions(to_uri(file), line.to_i, col.to_i)
108
- say("Type for `#{file}:#{line}:#{col}`:")
109
- symbol_printer.print_list(res)
110
- end
111
- end
112
-
113
- no_commands do
114
- def lsp_client
115
- context_requiring_sorbet!
116
-
117
- path = exec_path
118
- client = Spoom::LSP::Client.new(
119
- Spoom::Sorbet::BIN_PATH,
120
- "--lsp",
121
- "--enable-all-experimental-lsp-features",
122
- "--disable-watchman",
123
- path: path,
124
- )
125
- client.open(File.expand_path(path))
126
- client
127
- end
128
-
129
- def symbol_printer
130
- Spoom::LSP::SymbolPrinter.new(
131
- indent_level: 2,
132
- colors: options[:color],
133
- prefix: "file://#{File.expand_path(exec_path)}",
134
- )
135
- end
136
-
137
- def run(&block)
138
- client = lsp_client
139
- block.call(client)
140
- rescue Spoom::LSP::Error::Diagnostics => err
141
- say_error("Sorbet returned typechecking errors for `#{symbol_printer.clean_uri(err.uri)}`")
142
- err.diagnostics.each do |d|
143
- say_error("#{d.message} (#{d.code})", status: " #{d.range}")
144
- end
145
- exit(1)
146
- rescue Spoom::LSP::Error::BadHeaders => err
147
- say_error("Sorbet didn't answer correctly (#{err.message})")
148
- exit(1)
149
- rescue Spoom::LSP::Error => err
150
- say_error(err.message)
151
- exit(1)
152
- ensure
153
- begin
154
- client&.close
155
- rescue
156
- # We can't do much if Sorbet refuse to close.
157
- # We kill the parent process and let the child be killed.
158
- exit(1)
159
- end
160
- end
161
-
162
- def to_uri(path)
163
- "file://" + File.join(File.expand_path(exec_path), path)
164
- end
165
- end
166
- end
167
- end
168
- end
data/lib/spoom/cli/run.rb DELETED
@@ -1,148 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- module Spoom
5
- module Cli
6
- class Run < Thor
7
- include Helper
8
-
9
- default_task :tc
10
-
11
- SORT_CODE = "code"
12
- SORT_LOC = "loc"
13
- SORT_ENUM = [SORT_CODE, SORT_LOC]
14
-
15
- DEFAULT_FORMAT = "%C - %F:%L: %M"
16
-
17
- desc "tc", "Run `srb tc`"
18
- option :limit, type: :numeric, aliases: :l, desc: "Limit displayed errors"
19
- option :code, type: :numeric, aliases: :c, desc: "Filter displayed errors by code"
20
- option :sort, type: :string, aliases: :s, desc: "Sort errors", enum: SORT_ENUM, default: SORT_LOC
21
- option :format, type: :string, aliases: :f, desc: "Format line output"
22
- option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
23
- option :count, type: :boolean, default: true, desc: "Show errors count"
24
- option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
25
- option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
26
- def tc(*paths_to_select)
27
- context = context_requiring_sorbet!
28
- limit = options[:limit]
29
- sort = options[:sort]
30
- code = options[:code]
31
- uniq = options[:uniq]
32
- format = options[:format]
33
- count = options[:count]
34
- sorbet = options[:sorbet]
35
-
36
- unless limit || code || sort
37
- result = T.unsafe(context).srb_tc(
38
- *options[:sorbet_options].split(" "),
39
- capture_err: false,
40
- sorbet_bin: sorbet,
41
- )
42
-
43
- say_error(result.err, status: nil, nl: false)
44
- exit(result.status)
45
- end
46
-
47
- error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
48
- result = T.unsafe(context).srb_tc(
49
- *options[:sorbet_options].split(" "),
50
- "--error-url-base=#{error_url_base}",
51
- capture_err: true,
52
- sorbet_bin: sorbet,
53
- )
54
-
55
- if result.status
56
- say_error(result.err, status: nil, nl: false)
57
- exit(0)
58
- end
59
-
60
- unless result.exit_code == 100
61
- # Sorbet will return exit code 100 if there are type checking errors.
62
- # If Sorbet returned something else, it means it didn't terminate normally.
63
- say_error(result.err, status: nil, nl: false)
64
- exit(1)
65
- end
66
-
67
- errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
68
- errors_count = errors.size
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
-
78
- errors = case sort
79
- when SORT_CODE
80
- Spoom::Sorbet::Errors.sort_errors_by_code(errors)
81
- when SORT_LOC
82
- errors.sort
83
- else
84
- errors # preserve natural sort
85
- end
86
-
87
- errors = T.must(errors.slice(0, limit)) if limit
88
-
89
- lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
90
- lines = lines.uniq if uniq
91
-
92
- lines.each do |line|
93
- say_error(line, status: nil)
94
- end
95
-
96
- if count
97
- if errors_count == errors.size
98
- say_error("Errors: #{errors_count}", status: nil)
99
- else
100
- say_error("Errors: #{errors.size} shown, #{errors_count} total", status: nil)
101
- end
102
- end
103
-
104
- exit(1)
105
- rescue Spoom::Sorbet::Error::Segfault => error
106
- say_error(<<~ERR, status: nil)
107
- #{red("!!! Sorbet exited with code #{error.result.exit_code} - SEGFAULT !!!")}
108
-
109
- This is most likely related to a bug in Sorbet.
110
- ERR
111
-
112
- exit(error.result.exit_code)
113
- rescue Spoom::Sorbet::Error::Killed => error
114
- say_error(<<~ERR, status: nil)
115
- #{red("!!! Sorbet exited with code #{error.result.exit_code} - KILLED !!!")}
116
- ERR
117
-
118
- exit(error.result.exit_code)
119
- end
120
-
121
- no_commands do
122
- def format_error(error, format)
123
- line = format
124
- line = line.gsub(/%C/, yellow(error.code.to_s))
125
- line = line.gsub(/%F/, error.file)
126
- line = line.gsub(/%L/, error.line.to_s)
127
- line = line.gsub(/%M/, colorize_message(error.message))
128
- line
129
- end
130
-
131
- def colorize_message(message)
132
- return message unless color?
133
-
134
- cyan = T.let(false, T::Boolean)
135
- word = StringIO.new
136
- message.chars.each do |c|
137
- if c == "`"
138
- cyan = !cyan
139
- next
140
- end
141
- word << (cyan ? cyan(c) : red(c))
142
- end
143
- word.string
144
- end
145
- end
146
- end
147
- end
148
- end