spoom 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +253 -1
- data/Rakefile +1 -0
- data/lib/spoom.rb +2 -1
- data/lib/spoom/cli.rb +42 -7
- data/lib/spoom/cli/bump.rb +59 -0
- data/lib/spoom/cli/config.rb +51 -0
- data/lib/spoom/cli/coverage.rb +191 -0
- data/lib/spoom/cli/helper.rb +70 -0
- data/lib/spoom/cli/lsp.rb +165 -0
- data/lib/spoom/cli/run.rb +79 -0
- data/lib/spoom/config.rb +1 -1
- data/lib/spoom/coverage.rb +73 -0
- data/lib/spoom/coverage/d3.rb +110 -0
- data/lib/spoom/coverage/d3/base.rb +50 -0
- data/lib/spoom/coverage/d3/circle_map.rb +195 -0
- data/lib/spoom/coverage/d3/pie.rb +175 -0
- data/lib/spoom/coverage/d3/timeline.rb +486 -0
- data/lib/spoom/coverage/report.rb +308 -0
- data/lib/spoom/coverage/snapshot.rb +132 -0
- data/lib/spoom/file_tree.rb +196 -0
- data/lib/spoom/git.rb +98 -0
- data/lib/spoom/printer.rb +81 -0
- data/lib/spoom/sorbet.rb +15 -2
- data/lib/spoom/sorbet/errors.rb +25 -15
- data/lib/spoom/sorbet/lsp.rb +4 -2
- data/lib/spoom/sorbet/lsp/structures.rb +108 -14
- data/lib/spoom/sorbet/metrics.rb +10 -79
- data/lib/spoom/sorbet/sigils.rb +98 -0
- data/lib/spoom/test_helpers/project.rb +103 -0
- data/lib/spoom/timeline.rb +53 -0
- data/lib/spoom/version.rb +1 -1
- metadata +25 -10
- data/lib/spoom/cli/commands/base.rb +0 -36
- data/lib/spoom/cli/commands/config.rb +0 -67
- data/lib/spoom/cli/commands/lsp.rb +0 -156
- data/lib/spoom/cli/commands/run.rb +0 -92
- data/lib/spoom/cli/symbol_printer.rb +0 -71
@@ -1,36 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'stringio'
|
5
|
-
|
6
|
-
module Spoom
|
7
|
-
module Cli
|
8
|
-
module Commands
|
9
|
-
class Base < Thor
|
10
|
-
no_commands do
|
11
|
-
def say_error(message, status = "Error")
|
12
|
-
status = set_color(status, :red)
|
13
|
-
|
14
|
-
buffer = StringIO.new
|
15
|
-
buffer << "#{status}: #{message}"
|
16
|
-
buffer << "\n" unless message.end_with?("\n")
|
17
|
-
|
18
|
-
$stderr.print(buffer.string)
|
19
|
-
$stderr.flush
|
20
|
-
end
|
21
|
-
|
22
|
-
def in_sorbet_project?
|
23
|
-
File.file?(Spoom::Config::SORBET_CONFIG)
|
24
|
-
end
|
25
|
-
|
26
|
-
def in_sorbet_project!
|
27
|
-
unless in_sorbet_project?
|
28
|
-
say_error("not in a Sorbet project (no sorbet/config)")
|
29
|
-
exit(1)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require_relative "base"
|
5
|
-
require_relative "../../sorbet/config"
|
6
|
-
|
7
|
-
module Spoom
|
8
|
-
module Cli
|
9
|
-
module Commands
|
10
|
-
class Config < Base
|
11
|
-
default_task :show
|
12
|
-
|
13
|
-
desc "show", "show Sorbet config"
|
14
|
-
def show
|
15
|
-
in_sorbet_project!
|
16
|
-
config = Spoom::Sorbet::Config.parse_file(Spoom::Config::SORBET_CONFIG)
|
17
|
-
|
18
|
-
say("Found Sorbet config at `#{Spoom::Config::SORBET_CONFIG}`.")
|
19
|
-
|
20
|
-
say("\nPaths typechecked:")
|
21
|
-
if config.paths.empty?
|
22
|
-
say(" * (default: .)")
|
23
|
-
else
|
24
|
-
config.paths.each do |path|
|
25
|
-
say(" * #{path}")
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
say("\nPaths ignored:")
|
30
|
-
if config.ignore.empty?
|
31
|
-
say(" * (default: none)")
|
32
|
-
else
|
33
|
-
config.ignore.each do |path|
|
34
|
-
say(" * #{path}")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
say("\nAllowed extensions:")
|
39
|
-
if config.allowed_extensions.empty?
|
40
|
-
say(" * .rb (default)")
|
41
|
-
say(" * .rbi (default)")
|
42
|
-
else
|
43
|
-
config.allowed_extensions.each do |ext|
|
44
|
-
say(" * #{ext}")
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
desc "files", "show files matching Sorbet config"
|
50
|
-
def files
|
51
|
-
in_sorbet_project!
|
52
|
-
config = Spoom::Sorbet::Config.parse_file(Spoom::Config::SORBET_CONFIG)
|
53
|
-
files = Spoom::Sorbet.srb_files(config)
|
54
|
-
|
55
|
-
say("Files matching `#{Spoom::Config::SORBET_CONFIG}`:")
|
56
|
-
if files.empty?
|
57
|
-
say(" NONE")
|
58
|
-
else
|
59
|
-
files.each do |path|
|
60
|
-
say(" * #{path}")
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,156 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'shellwords'
|
5
|
-
|
6
|
-
require_relative "base"
|
7
|
-
require_relative "../symbol_printer"
|
8
|
-
require_relative "../../sorbet/lsp"
|
9
|
-
|
10
|
-
module Spoom
|
11
|
-
module Cli
|
12
|
-
module Commands
|
13
|
-
class LSP < Base
|
14
|
-
default_task :show
|
15
|
-
|
16
|
-
desc "interactive", "interactive LSP mode"
|
17
|
-
def show
|
18
|
-
in_sorbet_project!
|
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
|
-
Dir["**/*.rb"].each do |file|
|
29
|
-
res = client.document_symbols(file.to_uri)
|
30
|
-
next if res.empty?
|
31
|
-
puts "Symbols from `#{file}`:"
|
32
|
-
SymbolPrinter.print_object(res, options["no_color"])
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
desc "hover", "request hover informations"
|
38
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
39
|
-
def hover(file, line, col)
|
40
|
-
run do |client|
|
41
|
-
res = client.hover(file.to_uri, line.to_i, col.to_i)
|
42
|
-
say "Hovering `#{file}:#{line}:#{col}`:"
|
43
|
-
if res
|
44
|
-
SymbolPrinter.print_object(res, options["no_color"])
|
45
|
-
else
|
46
|
-
puts "<no data>"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
desc "defs", "list definitions of a symbol"
|
52
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
53
|
-
def defs(file, line, col)
|
54
|
-
run do |client|
|
55
|
-
res = client.definitions(file.to_uri, line.to_i, col.to_i)
|
56
|
-
puts "Definitions for `#{file}:#{line}:#{col}`:"
|
57
|
-
SymbolPrinter.print_list(res, options["no_color"])
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
desc "find", "find symbols matching a query"
|
62
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
63
|
-
def find(query)
|
64
|
-
run do |client|
|
65
|
-
res = client.symbols(query)
|
66
|
-
puts "Symbols matching `#{query}`:"
|
67
|
-
printer = SymbolPrinter.new(2, options["no_color"])
|
68
|
-
res.each do |symbol|
|
69
|
-
next if symbol.location.uri.start_with?("https")
|
70
|
-
printer.visit(symbol)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
desc "symbols", "list symbols from a file"
|
76
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
77
|
-
def symbols(file)
|
78
|
-
run do |client|
|
79
|
-
res = client.document_symbols(file.to_uri)
|
80
|
-
puts "Symbols from `#{file}`:"
|
81
|
-
SymbolPrinter.print_object(res, options["no_color"])
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
desc "refs", "list references to a symbol"
|
86
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
87
|
-
def refs(file, line, col)
|
88
|
-
run do |client|
|
89
|
-
res = client.references(file.to_uri, line.to_i, col.to_i)
|
90
|
-
puts "References to `#{file}:#{line}:#{col}`:"
|
91
|
-
SymbolPrinter.print_list(res, options["no_color"])
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
desc "sigs", "list signatures for a symbol"
|
96
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
97
|
-
def sigs(file, line, col)
|
98
|
-
run do |client|
|
99
|
-
res = client.signatures(file.to_uri, line.to_i, col.to_i)
|
100
|
-
puts "Signature for `#{file}:#{line}:#{col}`:"
|
101
|
-
SymbolPrinter.print_list(res, options["no_color"])
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
desc "types", "display type of a symbol"
|
106
|
-
# TODO: options, filter, limit, kind etc.. filter rbi
|
107
|
-
def types(file, line, col)
|
108
|
-
run do |client|
|
109
|
-
res = client.type_definitions(file.to_uri, line.to_i, col.to_i)
|
110
|
-
say "Type for `#{file}:#{line}:#{col}`:"
|
111
|
-
SymbolPrinter.print_list(res, options["no_color"])
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
no_commands do
|
116
|
-
def lsp_client
|
117
|
-
in_sorbet_project!
|
118
|
-
client = Spoom::LSP::Client.new(
|
119
|
-
Spoom::Config::SORBET_PATH,
|
120
|
-
"--lsp",
|
121
|
-
"--enable-all-experimental-lsp-features",
|
122
|
-
"--disable-watchman",
|
123
|
-
)
|
124
|
-
client.open(Spoom::Config::WORKSPACE_PATH)
|
125
|
-
client
|
126
|
-
end
|
127
|
-
|
128
|
-
def run(&block)
|
129
|
-
client = lsp_client
|
130
|
-
block.call(client)
|
131
|
-
rescue Spoom::LSP::Error::Diagnostics => err
|
132
|
-
say_error("Sorbet returned typechecking errors for `#{err.uri.from_uri}`")
|
133
|
-
err.diagnostics.each do |d|
|
134
|
-
say_error("#{d.message} (#{d.code})", " #{d.range}")
|
135
|
-
end
|
136
|
-
exit(1)
|
137
|
-
rescue Spoom::LSP::Error::BadHeaders => err
|
138
|
-
say_error("Sorbet didn't answer correctly (#{err.message})")
|
139
|
-
exit(1)
|
140
|
-
rescue Spoom::LSP::Error => err
|
141
|
-
say_error(err.message)
|
142
|
-
exit(1)
|
143
|
-
ensure
|
144
|
-
begin
|
145
|
-
client&.close
|
146
|
-
rescue
|
147
|
-
# We can't do much if Sorbet refuse to close.
|
148
|
-
# We kill the parent process and let the child be killed.
|
149
|
-
exit(1)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
@@ -1,92 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require_relative "base"
|
5
|
-
|
6
|
-
module Spoom
|
7
|
-
module Cli
|
8
|
-
module Commands
|
9
|
-
class Run < Base
|
10
|
-
default_task :tc
|
11
|
-
|
12
|
-
desc "tc", "run srb tc"
|
13
|
-
option :limit, type: :numeric, aliases: :l
|
14
|
-
option :code, type: :numeric, aliases: :c
|
15
|
-
option :sort, type: :string, aliases: :s
|
16
|
-
def tc
|
17
|
-
in_sorbet_project!
|
18
|
-
|
19
|
-
limit = options[:limit]
|
20
|
-
sort = options[:sort]
|
21
|
-
code = options[:code]
|
22
|
-
colors = !options[:no_color]
|
23
|
-
|
24
|
-
unless limit || code || sort
|
25
|
-
return Spoom::Sorbet.srb_tc(capture_err: false).last
|
26
|
-
end
|
27
|
-
|
28
|
-
output, status = Spoom::Sorbet.srb_tc(capture_err: true)
|
29
|
-
if status
|
30
|
-
$stderr.print(output)
|
31
|
-
return 0
|
32
|
-
end
|
33
|
-
|
34
|
-
errors = Spoom::Sorbet::Errors::Parser.parse_string(output)
|
35
|
-
errors_count = errors.size
|
36
|
-
|
37
|
-
errors = sort == "code" ? errors.sort_by { |e| [e.code, e.file, e.line, e.message] } : errors.sort
|
38
|
-
errors = errors.select { |e| e.code == code } if code
|
39
|
-
errors = T.must(errors.slice(0, limit)) if limit
|
40
|
-
|
41
|
-
errors.each do |e|
|
42
|
-
code = colorize_code(e.code, colors)
|
43
|
-
message = colorize_message(e.message, colors)
|
44
|
-
$stderr.puts "#{code} - #{e.file}:#{e.line}: #{message}"
|
45
|
-
end
|
46
|
-
|
47
|
-
if errors_count == errors.size
|
48
|
-
$stderr.puts "Errors: #{errors_count}"
|
49
|
-
else
|
50
|
-
$stderr.puts "Errors: #{errors.size} shown, #{errors_count} total"
|
51
|
-
end
|
52
|
-
|
53
|
-
1
|
54
|
-
end
|
55
|
-
|
56
|
-
desc "metrics", "run srb tc and display metrics"
|
57
|
-
def metrics
|
58
|
-
in_sorbet_project!
|
59
|
-
|
60
|
-
metrics = Spoom::Sorbet.srb_metrics(capture_err: false)
|
61
|
-
if metrics
|
62
|
-
metrics.show
|
63
|
-
else
|
64
|
-
puts "no data"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
no_commands do
|
69
|
-
def colorize_code(code, colors = true)
|
70
|
-
return code.to_s unless colors
|
71
|
-
code.to_s.light_black
|
72
|
-
end
|
73
|
-
|
74
|
-
def colorize_message(message, colors = true)
|
75
|
-
return message unless colors
|
76
|
-
|
77
|
-
cyan = T.let(false, T::Boolean)
|
78
|
-
word = StringIO.new
|
79
|
-
message.chars.each do |c|
|
80
|
-
if c == '`'
|
81
|
-
cyan = !cyan
|
82
|
-
next
|
83
|
-
end
|
84
|
-
word << (cyan ? c.cyan : c.red)
|
85
|
-
end
|
86
|
-
word.string
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "colorize"
|
5
|
-
|
6
|
-
module Spoom
|
7
|
-
module Cli
|
8
|
-
class SymbolPrinter
|
9
|
-
attr_accessor :seen, :no_color
|
10
|
-
|
11
|
-
def self.print_list(list, no_color)
|
12
|
-
printer = SymbolPrinter.new(2, no_color)
|
13
|
-
list.each do |item|
|
14
|
-
printer.print(" * ")
|
15
|
-
printer.visit(item)
|
16
|
-
printer.printn
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.print_object(object, no_color)
|
21
|
-
printer = SymbolPrinter.new(2, no_color)
|
22
|
-
printer.visit(object)
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize(default_indent, no_color = false)
|
26
|
-
@seen = Set.new
|
27
|
-
@current_indent = default_indent
|
28
|
-
@no_color = no_color
|
29
|
-
String.disable_colorization = no_color
|
30
|
-
end
|
31
|
-
|
32
|
-
def indent
|
33
|
-
@current_indent += 2
|
34
|
-
end
|
35
|
-
|
36
|
-
def dedent
|
37
|
-
@current_indent -= 2
|
38
|
-
end
|
39
|
-
|
40
|
-
def print(string)
|
41
|
-
Kernel.print(string)
|
42
|
-
end
|
43
|
-
|
44
|
-
def printn
|
45
|
-
print("\n")
|
46
|
-
end
|
47
|
-
|
48
|
-
def printt
|
49
|
-
print(" " * @current_indent)
|
50
|
-
end
|
51
|
-
|
52
|
-
def visit(object)
|
53
|
-
if object.is_a?(Array)
|
54
|
-
object.each { |e| visit(e) }
|
55
|
-
else
|
56
|
-
object.accept_printer(self)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
class String
|
64
|
-
def to_uri
|
65
|
-
"file://" + File.join(Spoom::Config::WORKSPACE_PATH, self)
|
66
|
-
end
|
67
|
-
|
68
|
-
def from_uri
|
69
|
-
sub("file://#{Spoom::Config::WORKSPACE_PATH}", "")
|
70
|
-
end
|
71
|
-
end
|