Skip to content

Commit adf23a3

Browse files
committed
(Re)implement the CLI
Made it compatible with TypeProf v1 as far as reasonably possible
1 parent 73b4011 commit adf23a3

File tree

29 files changed

+505
-53
lines changed

29 files changed

+505
-53
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/coverage/
1+
/tmp/
22
dog_bench.stackprof.dump
33
dog_bench.pf2profile
44
dog_bench.json

bin/typeprof

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,4 @@
22

33
require_relative "../lib/typeprof"
44

5-
case ARGV[0]
6-
when "--version"
7-
puts "typeprof 0.30.0"
8-
when "--lsp"
9-
mode = ARGV[1]&.to_sym || :socket
10-
11-
core = TypeProf::Core::Service.new
12-
begin
13-
case mode
14-
when :socket
15-
TypeProf::LSP::Server.start_socket(core)
16-
when :stdio
17-
TypeProf::LSP::Server.start_stdio(core)
18-
else
19-
puts "lsp mode '#{mode}' is not supported. expected mode: socket, stdio"
20-
end
21-
rescue Exception
22-
puts $!.detailed_message(highlight: false)
23-
raise
24-
end
25-
else
26-
p ARGV
27-
end
5+
TypeProf::CLI::CLI.new(ARGV).run

lib/typeprof.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
require_relative "typeprof/diagnostic"
44
require_relative "typeprof/core"
55
require_relative "typeprof/lsp"
6+
require_relative "typeprof/cli"

lib/typeprof/cli.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require "optparse"
2+
require "pathname"
3+
4+
require_relative "cli/cli"

lib/typeprof/cli/cli.rb

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
require "io/console"
2+
3+
module TypeProf::CLI
4+
class CLI
5+
def initialize(argv)
6+
opt = OptionParser.new
7+
8+
opt.banner = "Usage: #{ opt.program_name } [options] files_or_dirs..."
9+
10+
core_options = {}
11+
lsp_options = {}
12+
cli_options = {}
13+
14+
output = nil
15+
rbs_collection_path = nil
16+
17+
opt.separator ""
18+
opt.separator "Options:"
19+
opt.on("-o OUTFILE", "Output to OUTFILE instead of stdout") {|v| output = v }
20+
opt.on("-q", "--quiet", "Quiet mode") do
21+
core_options[:display_indicator] = false
22+
end
23+
opt.on("-v", "--verbose", "Verbose mode") do
24+
core_options[:show_errors] = true
25+
end
26+
opt.on("--version", "Display typeprof version") { cli_options[:display_version] = true }
27+
opt.on("--collection PATH", "File path of collection configuration") { |v| rbs_collection_path = v }
28+
opt.on("--no-collection", "Ignore collection configuration") { rbs_collection_path = :no }
29+
opt.on("--lsp", "LSP server mode") do |v|
30+
core_options[:display_indicator] = false
31+
cli_options[:lsp] = true
32+
end
33+
34+
opt.separator ""
35+
opt.separator "Analysis output options:"
36+
opt.on("--[no-]show-typeprof-version", "Display TypeProf version in a header") {|v| core_options[:output_typeprof_version] = v }
37+
opt.on("--[no-]show-errors", "Display possible errors found during the analysis") {|v| core_options[:output_diagnostics] = v }
38+
opt.on("--[no-]show-parameter-names", "Display parameter names for methods") {|v| core_options[:output_parameter_names] = v }
39+
opt.on("--[no-]show-source-locations", "Display definition source locations for methods") {|v| core_options[:output_source_locations] = v }
40+
41+
opt.separator ""
42+
opt.separator "Advanced options:"
43+
opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| cli_options[:stackprof] = v.to_sym }
44+
45+
opt.separator ""
46+
opt.separator "LSP options:"
47+
opt.on("--port PORT", Integer, "Specify a port number to listen for requests on") {|v| lsp_options[:port] = v }
48+
opt.on("--stdio", "Use stdio for LSP transport") {|v| lsp_options[:stdio] = v }
49+
50+
opt.parse!(argv)
51+
52+
if cli_options[:lsp] && !lsp_options.empty?
53+
raise OptionParser::InvalidOption.new("lsp options with non-lsp mode")
54+
end
55+
56+
@core_options = {
57+
rbs_collection: setup_rbs_collection(rbs_collection_path),
58+
display_indicator: $stderr.tty?,
59+
output_typeprof_version: true,
60+
output_errors: false,
61+
output_parameter_names: false,
62+
output_source_locations: false,
63+
}.merge(core_options)
64+
65+
@lsp_options = {
66+
port: 0,
67+
stdio: false,
68+
}.merge(lsp_options)
69+
70+
@cli_options = {
71+
argv:,
72+
output: output ? open(output, "w") : $stdout.dup,
73+
display_version: false,
74+
stackprof: nil,
75+
lsp: false,
76+
}.merge(cli_options)
77+
78+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
79+
puts $!
80+
exit 1
81+
end
82+
83+
def setup_rbs_collection(path)
84+
return nil if path == :no
85+
86+
unless path
87+
path = RBS::Collection::Config::PATH.exist? ? RBS::Collection::Config::PATH.to_s : nil
88+
return nil unless path
89+
end
90+
91+
if !File.readable?(path)
92+
raise OptionParser::InvalidOption.new("file not found: #{ path }")
93+
end
94+
95+
lock_path = RBS::Collection::Config.to_lockfile_path(Pathname(path))
96+
if !File.readable?(lock_path)
97+
raise OptionParser::InvalidOption.new("file not found: #{ lock_path.to_s }; please run 'rbs collection install")
98+
end
99+
100+
RBS::Collection::Config::Lockfile.from_lockfile(lockfile_path: lock_path, data: YAML.load_file(lock_path))
101+
end
102+
103+
attr_reader :core_options, :lsp_options, :cli_options
104+
105+
def run
106+
core = TypeProf::Core::Service.new(@core_options)
107+
108+
if @cli_options[:lsp]
109+
run_lsp(core)
110+
else
111+
run_cli(core)
112+
end
113+
end
114+
115+
def run_lsp(core)
116+
if @lsp_options[:stdio]
117+
TypeProf::LSP::Server.start_stdio(core)
118+
else
119+
TypeProf::LSP::Server.start_socket(core)
120+
end
121+
rescue Exception
122+
puts $!.detailed_message(highlight: false).gsub(/^/, "---")
123+
raise
124+
end
125+
126+
def run_cli(core)
127+
puts "typeprof #{ TypeProf::VERSION }" if @cli_options[:display_version]
128+
129+
files = find_files
130+
131+
set_profiler do
132+
output = @cli_options[:output]
133+
134+
core.batch(files, @cli_options[:output])
135+
136+
output.close
137+
end
138+
139+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
140+
puts $!
141+
exit 1
142+
end
143+
144+
def find_files
145+
files = []
146+
@cli_options[:argv].each do |path|
147+
if File.directory?(path)
148+
files.concat(Dir.glob("#{ path }/**/*.{rb,rbs}"))
149+
elsif File.file?(path)
150+
files << path
151+
else
152+
raise OptionParser::InvalidOption.new("no such file or directory -- #{ path }")
153+
end
154+
end
155+
156+
if files.empty?
157+
exit if @cli_options[:display_version]
158+
raise OptionParser::InvalidOption.new("no input files")
159+
end
160+
161+
files
162+
end
163+
164+
def set_profiler
165+
if @cli_options[:stackprof]
166+
require "stackprof"
167+
out = "typeprof-stackprof-#{ @cli_options[:stackprof] }.dump"
168+
StackProf.start(mode: @cli_options[:stackprof], out: out, raw: true)
169+
end
170+
171+
yield
172+
173+
ensure
174+
if @cli_options[:stackprof] && defined?(StackProf)
175+
StackProf.stop
176+
StackProf.results
177+
end
178+
end
179+
end
180+
end

lib/typeprof/core/graph/box.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ def call(changes, genv, a_args, ret)
554554
end
555555
end
556556

557-
def show
557+
def show(output_parameter_names)
558558
block_show = []
559559
if @record_block.used
560560
blk_f_args = @record_block.f_args.map {|arg| arg.show }.join(", ")
@@ -585,6 +585,21 @@ def show
585585
if @f_args.rest_keywords
586586
args << "**#{ Type.strip_parens(@f_args.rest_keywords.show) }"
587587
end
588+
589+
if output_parameter_names && @node.is_a?(AST::DefNode)
590+
names = []
591+
names.concat(@node.req_positionals)
592+
names.concat(@node.opt_positionals)
593+
names.concat(@node.rest_positionals) if @node.rest_positionals
594+
names.concat(@node.post_positionals)
595+
names.concat(@node.req_keywords)
596+
names.concat(@node.opt_keywords)
597+
names.concat(@node.rest_keywords) if @node.rest_keywords
598+
args = args.zip(names).map do |arg, name|
599+
name ? "#{ arg } #{ name }" : arg
600+
end
601+
end
602+
588603
args = args.join(", ")
589604
s = args.empty? ? [] : ["(#{ args })"]
590605
s << "#{ block_show.sort.join(" | ") }" unless block_show.empty?

0 commit comments

Comments
 (0)