Class: TypeProf::CLI::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/typeprof/cli/cli.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ CLI

Returns a new instance of CLI.



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/typeprof/cli/cli.rb', line 3

def initialize(argv)
  opt = OptionParser.new

  opt.banner = "Usage: #{ opt.program_name } [options] files_or_dirs..."

  core_options = {}
  lsp_options = {}
  cli_options = {}

  output = nil
  rbs_collection_path = nil
  initialize_config_file = false
  exclude_patterns = []

  opt.separator ""
  opt.separator "Options:"
  opt.on("-o OUTFILE", "Output to OUTFILE instead of stdout") {|v| output = v }
  opt.on("-q", "--quiet", "Quiet mode") do
    core_options[:display_indicator] = false
  end
  opt.on("-v", "--verbose", "Verbose mode") do
    core_options[:show_errors] = true
  end
  opt.on("--version", "Display typeprof version") { cli_options[:display_version] = true }
  opt.on("--collection PATH", "File path of collection configuration") { |v| rbs_collection_path = v }
  opt.on("--no-collection", "Ignore collection configuration") { rbs_collection_path = :no }
  opt.on("--exclude PATTERN", "Exclude files matching glob PATTERN (can be specified multiple times)") { |v| exclude_patterns << v }
  opt.on("--lsp", "LSP server mode") do |v|
    core_options[:display_indicator] = false
    cli_options[:lsp] = true
  end

  opt.separator ""
  opt.separator "Analysis output options:"
  opt.on("--[no-]show-typeprof-version", "Display TypeProf version in a header") {|v| core_options[:output_typeprof_version] = v }
  opt.on("--[no-]show-errors", "Display possible errors found during the analysis") {|v| core_options[:output_diagnostics] = v }
  opt.on("--[no-]show-parameter-names", "Display parameter names for methods") {|v| core_options[:output_parameter_names] = v }
  opt.on("--[no-]show-source-locations", "Display definition source locations for methods") {|v| core_options[:output_source_locations] = v }
  opt.on("--[no-]show-stats", "Display type inference statistics after analysis (for debugging purpose)") {|v| core_options[:output_stats] = v }

  opt.separator ""
  opt.separator "Advanced options:"
  opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| cli_options[:stackprof] = v.to_sym }
  opt.on("--init", 'Generate TypeProf configuration file') {|v| initialize_config_file = true}

  opt.separator ""
  opt.separator "LSP options:"
  opt.on("--port PORT", Integer, "Specify a port number to listen for requests on") {|v| lsp_options[:port] = v }
  opt.on("--stdio", "Use stdio for LSP transport") {|v| lsp_options[:stdio] = v }

  opt.parse!(argv)

  if initialize_config_file
    generate_config_file
    exit 0
  end

  if !cli_options[:lsp] && !lsp_options.empty?
    raise OptionParser::InvalidOption.new("lsp options with non-lsp mode")
  end

  @core_options = {
    rbs_collection: setup_rbs_collection(rbs_collection_path),
    display_indicator: $stderr.tty?,
    output_typeprof_version: true,
    output_errors: false,
    output_parameter_names: false,
    output_source_locations: false,
    output_stats: false,
    exclude_patterns: exclude_patterns,
  }.merge(core_options)

  @lsp_options = {
    port: 0,
    stdio: false,
  }.merge(lsp_options)

  @cli_options = {
    argv:,
    output: output ? open(output, "w") : $stdout.dup,
    display_version: false,
    stackprof: nil,
    lsp: false,
  }.merge(cli_options)

rescue OptionParser::InvalidOption, OptionParser::MissingArgument
  puts $!
  exit 1
end

Instance Attribute Details

#cli_optionsObject (readonly)

Returns the value of attribute cli_options.



113
114
115
# File 'lib/typeprof/cli/cli.rb', line 113

def cli_options
  @cli_options
end

#core_optionsObject (readonly)

Returns the value of attribute core_options.



113
114
115
# File 'lib/typeprof/cli/cli.rb', line 113

def core_options
  @core_options
end

#lsp_optionsObject (readonly)

Returns the value of attribute lsp_options.



113
114
115
# File 'lib/typeprof/cli/cli.rb', line 113

def lsp_options
  @lsp_options
end

Instance Method Details

#find_filesObject



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/typeprof/cli/cli.rb', line 155

def find_files
  files = []
  @cli_options[:argv].each do |path|
    if File.directory?(path)
      files.concat(Dir.glob("#{ path }/**/*.{rb,rbs}").select {|f| File.file?(f) })
    elsif File.file?(path)
      files << path
    else
      raise OptionParser::InvalidOption.new("no such file or directory -- #{ path }")
    end
  end

  if files.empty?
    exit if @cli_options[:display_version]
    raise OptionParser::InvalidOption.new("no input files")
  end

  files
end

#generate_config_fileObject



191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/typeprof/cli/cli.rb', line 191

def generate_config_file
  exist_dirs = ["app", "lib"].select { |dir| File.exist?(File.join(Dir.pwd, dir)) }
  File.write('typeprof.conf.jsonc', <<~JSONC, mode: "wx")
    {
      "typeprof_version": "experimental",
      "rbs_dir": "sig/",
      "analysis_unit_dirs": #{exist_dirs.inspect},
      // "exclude": ["**/templates/**/*.rb"],
      // "diagnostic_severity": "warning"
    }
  JSONC
end

#runObject



115
116
117
118
119
120
121
122
# File 'lib/typeprof/cli/cli.rb', line 115

def run

  if @cli_options[:lsp]
    run_lsp
  else
    run_cli
  end
end

#run_cliObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/typeprof/cli/cli.rb', line 135

def run_cli
  core = TypeProf::Core::Service.new(@core_options)

  puts "typeprof #{ TypeProf::VERSION }" if @cli_options[:display_version]

  files = find_files

  set_profiler do
    output = @cli_options[:output]

    core.batch(files, @cli_options[:output])

    output.close
  end

rescue OptionParser::InvalidOption, OptionParser::MissingArgument
  puts $!
  exit 1
end

#run_lspObject



124
125
126
127
128
129
130
131
132
133
# File 'lib/typeprof/cli/cli.rb', line 124

def run_lsp
  if @lsp_options[:stdio]
    TypeProf::LSP::Server.start_stdio(@core_options)
  else
    TypeProf::LSP::Server.start_socket(@core_options, @lsp_options[:port])
  end
rescue Exception
  puts $!.detailed_message(highlight: false).gsub(/^/, "---")
  raise
end

#set_profilerObject



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/typeprof/cli/cli.rb', line 175

def set_profiler
  if @cli_options[:stackprof]
    require "stackprof"
    out = "typeprof-stackprof-#{ @cli_options[:stackprof] }.dump"
    StackProf.start(mode: @cli_options[:stackprof], out: out, raw: true)
  end

  yield

ensure
  if @cli_options[:stackprof] && defined?(StackProf)
    StackProf.stop
    StackProf.results
  end
end

#setup_rbs_collection(path) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/typeprof/cli/cli.rb', line 93

def setup_rbs_collection(path)
  return nil if path == :no

  unless path
    path = RBS::Collection::Config::PATH.exist? ? RBS::Collection::Config::PATH.to_s : nil
    return nil unless path
  end

  if !File.readable?(path)
    raise OptionParser::InvalidOption.new("file not found: #{ path }")
  end

  lock_path = RBS::Collection::Config.to_lockfile_path(Pathname(path))
  if !File.readable?(lock_path)
    raise OptionParser::InvalidOption.new("file not found: #{ lock_path.to_s }; please run 'rbs collection install")
  end

  RBS::Collection::Config::Lockfile.from_lockfile(lockfile_path: lock_path, data: YAML.load_file(lock_path))
end