has_parser?()
click to toggle source
def has_parser?
defined?(RubyVM::AbstractSyntaxTree)
end
parse_logging_options(opts)
click to toggle source
def parse_logging_options(opts)
opts.on("--log-level LEVEL", "Specify log level (defaults to `warn`)") do |level|
RBS.logger_level = level
end
opts.on("--log-output OUTPUT", "Specify the file to output log (defaults to stderr)") do |output|
io = File.open(output, "a") or raise
RBS.logger_output = io
end
opts
end
run(args)
click to toggle source
def run(args)
options = LibraryOptions.new
opts = OptionParser.new
opts.banner = <<~USAGE
Usage: rbs [options...] [command...]
Available commands:
Options:
USAGE
options.setup_library_options(opts)
parse_logging_options(opts)
opts.version = RBS::VERSION
opts.order!(args)
command = args.shift&.to_sym
case command
when :version
stdout.puts opts.ver
when *COMMANDS
__send__ :"run_#{command}", args, options
else
stdout.puts opts.help
end
end
run_ancestors(args, options)
click to toggle source
def run_ancestors(args, options)
kind = :instance
OptionParser.new do |opts|
opts.banner = <<EOU
Usage: rbs ancestors [options...] [type_name]
Show ancestors of the given class or module.
Examples:
$ rbs ancestors --instance String
$ rbs ancestors --singleton Array
Options:
EOU
opts.on("--instance", "Ancestors of instance of the given type_name (default)") { kind = :instance }
opts.on("--singleton", "Ancestors of singleton of the given type_name") { kind = :singleton }
end.order!(args)
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
builder = DefinitionBuilder::AncestorBuilder.new(env: env)
type_name = TypeName(args[0]).absolute!
if env.class_decls.key?(type_name)
ancestors = case kind
when :instance
builder.instance_ancestors(type_name)
when :singleton
builder.singleton_ancestors(type_name)
else
raise
end
ancestors.ancestors.each do |ancestor|
case ancestor
when Definition::Ancestor::Singleton
stdout.puts "singleton(#{ancestor.name})"
when Definition::Ancestor::Instance
if ancestor.args.empty?
stdout.puts ancestor.name.to_s
else
stdout.puts "#{ancestor.name}[#{ancestor.args.join(", ")}]"
end
end
end
else
stdout.puts "Cannot find class: #{type_name}"
end
end
run_ast(args, options)
click to toggle source
def run_ast(args, options)
OptionParser.new do |opts|
opts.banner = <<EOB
Usage: rbs ast [patterns...]
Print JSON AST of loaded environment.
You can specify patterns to filter declarations with the file names.
Examples:
$ rbs ast
$ rbs ast 'basic_object.rbs'
$ rbs -I ./sig ast ./sig
$ rbs -I ./sig ast '*/models/*.rbs'
EOB
end.order!(args)
patterns = args.map do |arg|
path = Pathname(arg)
if path.exist?
path
else
arg
end
end
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
decls = env.declarations.select do |decl|
loc = decl.location or raise
name = loc.buffer.name
patterns.empty? || patterns.any? do |pat|
case pat
when Pathname
Pathname(name).ascend.any? {|p| p == pat }
when String
name.end_with?(pat) || File.fnmatch(pat, name, File::FNM_EXTGLOB)
end
end
end
stdout.print JSON.generate(decls)
stdout.flush
end
run_constant(args, options)
click to toggle source
def run_constant(args, options)
context = nil
OptionParser.new do |opts|
opts.banner = <<EOU
Usage: rbs constant [options...] [name]
Resolve constant based on RBS.
Examples:
$ rbs constant ::Object
$ rbs constant UTF_8
$ rbs constant --context=::Encoding UTF_8
Options:
EOU
opts.on("--context CONTEXT", "Name of the module where the constant resolution starts") {|c| context = c }
end.order!(args)
unless args.size == 1
stdout.puts "Expected one argument."
return
end
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
builder = DefinitionBuilder.new(env: env)
table = ConstantTable.new(builder: builder)
namespace = context ? Namespace.parse(context).absolute! : Namespace.root
stdout.puts "Context: #{namespace}"
name = Namespace.parse(args[0]).to_type_name
stdout.puts "Constant name: #{name}"
constant = table.resolve_constant_reference(name, context: namespace.ascend.to_a)
if constant
stdout.puts " => #{constant.name}: #{constant.type}"
else
stdout.puts " => [no constant]"
end
end
run_list(args, options)
click to toggle source
def run_list(args, options)
list = Set[]
OptionParser.new do |opts|
opts.banner = <<EOB
Usage: rbs list [options...]
List classes, modules, and interfaces.
Examples:
$ rbs list
$ rbs list --class --module --interface
Options:
EOB
opts.on("--class", "List classes") { list << :class }
opts.on("--module", "List modules") { list << :module }
opts.on("--interface", "List interfaces") { list << :interface }
end.order!(args)
list.merge(_ = [:class, :module, :interface]) if list.empty?
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
if list.include?(:class) || list.include?(:module)
env.class_decls.each do |name, entry|
case entry
when Environment::ModuleEntry
if list.include?(:module)
stdout.puts "#{name} (module)"
end
when Environment::ClassEntry
if list.include?(:class)
stdout.puts "#{name} (class)"
end
end
end
end
if list.include?(:interface)
env.interface_decls.each do |name, entry|
stdout.puts "#{name} (interface)"
end
end
end
run_method(args, options)
click to toggle source
def run_method(args, options)
kind = :instance
OptionParser.new do |opts|
opts.banner = <<EOU
Usage: rbs method [options...] [type_name] [method_name]
Show the information of the method specified by type_name and method_name.
Examples:
$ rbs method --instance Kernel puts
$ rbs method --singleton String try_convert
Options:
EOU
opts.on("--instance", "Show an instance method (default)") { kind = :instance }
opts.on("--singleton", "Show a singleton method") { kind = :singleton }
end.order!(args)
unless args.size == 2
stdout.puts "Expected two arguments, but given #{args.size}."
return
end
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
builder = DefinitionBuilder.new(env: env)
type_name = TypeName(args[0]).absolute!
method_name = args[1].to_sym
unless env.class_decls.key?(type_name)
stdout.puts "Cannot find class: #{type_name}"
return
end
definition = case kind
when :instance
builder.build_instance(type_name)
when :singleton
builder.build_singleton(type_name)
else
raise
end
method = definition.methods[method_name]
unless method
stdout.puts "Cannot find method: #{method_name}"
return
end
stdout.puts "#{type_name}#{kind == :instance ? "#" : "."}#{method_name}"
stdout.puts " defined_in: #{method.defined_in}"
stdout.puts " implementation: #{method.implemented_in}"
stdout.puts " accessibility: #{method.accessibility}"
stdout.puts " types:"
separator = " "
for type in method.method_types
stdout.puts " #{separator} #{type}"
separator = "|"
end
end
run_methods(args, options)
click to toggle source
def run_methods(args, options)
kind = :instance
inherit = true
OptionParser.new do |opts|
opts.banner = <<EOU
Usage: rbs methods [options...] [type_name]
Show methods defined in the class or module.
Examples:
$ rbs methods --instance Kernel
$ rbs methods --singleton --no-inherit String
Options:
EOU
opts.on("--instance", "Show instance methods (default)") { kind = :instance }
opts.on("--singleton", "Show singleton methods") { kind = :singleton }
opts.on("--[no-]inherit", "Show methods defined in super class and mixed modules too") {|v| inherit = v }
end.order!(args)
unless args.size == 1
stdout.puts "Expected one argument."
return
end
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
builder = DefinitionBuilder.new(env: env)
type_name = TypeName(args[0]).absolute!
if env.class_decls.key?(type_name)
definition = case kind
when :instance
builder.build_instance(type_name)
when :singleton
builder.build_singleton(type_name)
else
raise
end
definition.methods.keys.sort.each do |name|
method = definition.methods[name]
if inherit || method.implemented_in == type_name
stdout.puts "#{name} (#{method.accessibility})"
end
end
else
stdout.puts "Cannot find class: #{type_name}"
end
end
run_parse(args, options)
click to toggle source
def run_parse(args, options)
OptionParser.new do |opts|
opts.banner = <<-EOB
Usage: rbs parse [files...]
Parse given RBS files and print syntax errors.
Examples:
$ rbs parse sig/app/models.rbs sig/app/controllers.rbs
EOB
end.parse!(args)
loader = options.loader()
syntax_error = false
args.each do |path|
path = Pathname(path)
loader.each_file(path, skip_hidden: false, immediate: true) do |file_path|
Parser.parse_signature(file_path.read)
rescue RBS::Parser::SyntaxError => ex
loc = ex.error_value.location
stdout.puts "#{file_path}:#{loc.start_line}:#{loc.start_column}: parse error on value: (#{ex.token_str})"
syntax_error = true
rescue RBS::Parser::SemanticsError => ex
loc = ex.location
stdout.puts "#{file_path}:#{loc.start_line}:#{loc.start_column}: #{ex.original_message}"
syntax_error = true
end
end
exit 1 if syntax_error
end
run_paths(args, options)
click to toggle source
def run_paths(args, options)
OptionParser.new do |opts|
opts.banner = <<EOU
Usage: rbs paths
Show paths to directories where the RBS files are loaded from.
Examples:
$ rbs paths
$ rbs -r set paths
EOU
end.parse!(args)
loader = options.loader()
kind_of = -> (path) {
case
when path.file?
"file"
when path.directory?
"dir"
when !path.exist?
"absent"
else
"unknown"
end
}
loader.each_dir do |source, dir|
case source
when :core
stdout.puts "#{dir} (#{kind_of[dir]}, core)"
when Pathname
stdout.puts "#{dir} (#{kind_of[dir]})"
when EnvironmentLoader::Library
stdout.puts "#{dir} (#{kind_of[dir]}, library, name=#{source.name})"
end
end
end
run_prototype(args, options)
click to toggle source
def run_prototype(args, options)
format = args.shift
case format
when "rbi", "rb"
decls = run_prototype_file(format, args)
when "runtime"
require_libs = []
relative_libs = []
merge = false
owners_included = []
OptionParser.new do |opts|
opts.banner = <<EOU
Usage: rbs prototype runtime [options...] [pattern...]
Generate RBS prototype based on runtime introspection.
It loads Ruby code specified in [options] and generates RBS prototypes for classes matches to [pattern].
Examples:
$ rbs prototype runtime String
$ rbs prototype runtime --require set Set
$ rbs prototype runtime -R lib/rbs RBS::*
Options:
EOU
opts.on("-r", "--require LIB", "Load library using `require`") do |lib|
require_libs << lib
end
opts.on("-R", "--require-relative LIB", "Load library using `require_relative`") do |lib|
relative_libs << lib
end
opts.on("--merge", "Merge generated prototype RBS with existing RBS") do
merge = true
end
opts.on("--method-owner CLASS", "Generate method prototypes if the owner of the method is [CLASS]") do |klass|
owners_included << klass
end
end.parse!(args)
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
require_libs.each do |lib|
require(lib)
end
relative_libs.each do |lib|
eval("require_relative(lib)", binding, "rbs")
end
decls = Prototype::Runtime.new(patterns: args, env: env, merge: merge, owners_included: owners_included).decls
else
stdout.puts <<EOU
Usage: rbs prototype [generator...] [args...]
Generate prototype of RBS files.
Supported generators are rb, rbi, runtime.
Examples:
$ rbs prototype rb foo.rb
$ rbs prototype rbi foo.rbi
$ rbs prototype runtime String
EOU
exit 1
end
if decls
writer = Writer.new(out: stdout)
writer.write decls
else
exit 1
end
end
run_prototype_file(format, args)
click to toggle source
def run_prototype_file(format, args)
availability = unless has_parser?
"\n** This command does not work on this interpreter (#{RUBY_ENGINE}) **\n"
end
opts = OptionParser.new
opts.banner = <<EOU
Usage: rbs prototype #{format} [files...]
#{availability}
Generate RBS prototype from source code.
It parses specified Ruby code and and generates RBS prototypes.
It only works on MRI because it parses Ruby code with `RubyVM::AbstractSyntaxTree`.
Examples:
$ rbs prototype rb lib/foo.rb
$ rbs prototype rbi sorbet/rbi/foo.rbi
EOU
opts.parse!(args)
unless has_parser?
stdout.puts "Not supported on this interpreter (#{RUBY_ENGINE})."
exit 1
end
if args.empty?
stdout.puts opts
return nil
end
parser = case format
when "rbi"
Prototype::RBI.new()
when "rb"
Prototype::RB.new()
end
args.each do |file|
parser.parse Pathname(file).read
end
parser.decls
end
run_test(args, options)
click to toggle source
def run_test(args, options)
unchecked_classes = []
targets = []
sample_size = nil
double_suite = nil
(opts = OptionParser.new do |opts|
opts.banner = <<EOB
Usage: rbs [rbs options...] test [test options...] COMMAND
Examples:
$ rbs test rake test
$ rbs --log-level=debug test --target SomeModule::* rspec
$ rbs test --target SomeModule::* --target AnotherModule::* --target SomeClass rake test
Options:
EOB
opts.on("--target TARGET", "Sets the runtime test target") do |target|
targets << target
end
opts.on("--sample-size SAMPLE_SIZE", "Sets the sample size") do |size|
sample_size = size
end
opts.on("--unchecked-class UNCHECKED_CLASS", "Sets the class that would not be checked") do |unchecked_class|
unchecked_classes << unchecked_class
end
opts.on("--double-suite DOUBLE_SUITE", "Sets the double suite in use (currently supported: rspec | minitest)") do |suite|
double_suite = suite
end
end).order!(args)
if args.length.zero?
stdout.puts opts.help
exit 1
end
env_hash = {
'RUBYOPT' => "#{ENV['RUBYOPT']} -rrbs/test/setup",
'RBS_TEST_OPT' => test_opt(options),
'RBS_TEST_LOGLEVEL' => RBS.logger_level,
'RBS_TEST_SAMPLE_SIZE' => sample_size,
'RBS_TEST_DOUBLE_SUITE' => double_suite,
'RBS_TEST_UNCHECKED_CLASSES' => (unchecked_classes.join(',') unless unchecked_classes.empty?),
'RBS_TEST_TARGET' => (targets.join(',') unless targets.empty?)
}
out, err, status = Open3.capture3(env_hash, *args)
stdout.print(out)
stderr.print(err)
status
end
run_validate(args, options)
click to toggle source
def run_validate(args, options)
OptionParser.new do |opts|
opts.banner = <<EOU
Usage: rbs validate
Validate RBS files. It ensures the type names in RBS files are present and the type applications have correct arity.
Examples:
$ rbs validate
EOU
opts.on("--silent") do
require "stringio"
@stdout = StringIO.new
end
end.parse!(args)
loader = options.loader()
env = Environment.from_loader(loader).resolve_type_names
builder = DefinitionBuilder.new(env: env)
validator = Validator.new(env: env, resolver: TypeNameResolver.from_env(env))
env.class_decls.each_key do |name|
stdout.puts "Validating class/module definition: `#{name}`..."
builder.build_instance(name).each_type do |type|
validator.validate_type type, context: [Namespace.root]
end
builder.build_singleton(name).each_type do |type|
validator.validate_type type, context: [Namespace.root]
end
end
env.interface_decls.each_key do |name|
stdout.puts "Validating interface: `#{name}`..."
builder.build_interface(name).each_type do |type|
validator.validate_type type, context: [Namespace.root]
end
end
env.constant_decls.each do |name, const|
stdout.puts "Validating constant: `#{name}`..."
validator.validate_type const.decl.type, context: const.context
builder.ensure_namespace!(name.namespace, location: const.decl.location)
end
env.global_decls.each do |name, global|
stdout.puts "Validating global: `#{name}`..."
validator.validate_type global.decl.type, context: [Namespace.root]
end
env.alias_decls.each do |name, decl|
stdout.puts "Validating alias: `#{name}`..."
builder.expand_alias(name).tap do |type|
validator.validate_type type, context: [Namespace.root]
end
end
end
run_vendor(args, options)
click to toggle source
def run_vendor(args, options)
clean = false
vendor_dir = Pathname("vendor/sigs")
OptionParser.new do |opts|
opts.banner = <<-EOB
Usage: rbs vendor [options...] [gems...]
Vendor signatures in the project directory.
This command ignores the RBS loading global options, `-r` and `-I`.
Examples:
$ rbs vendor
$ rbs vendor --vendor-dir=sig
$ rbs vendor --no-stdlib
Options:
EOB
opts.on("--[no-]clean", "Clean vendor directory (default: no)") do |v|
clean = v
end
opts.on("--vendor-dir [DIR]", "Specify the directory for vendored signatures (default: vendor/sigs)") do |path|
vendor_dir = Pathname(path)
end
end.parse!(args)
stdout.puts "Vendoring signatures to #{vendor_dir}..."
loader = options.loader()
args.each do |gem|
name, version = gem.split(/:/, 2)
next unless name
stdout.puts " Loading library: #{name}, version=#{version}..."
loader.add(library: name, version: version)
end
vendorer = Vendorer.new(vendor_dir: vendor_dir, loader: loader)
if clean
stdout.puts " Deleting #{vendor_dir}..."
vendorer.clean!
end
stdout.puts " Copying RBS files..."
vendorer.copy!
end
test_opt(options)
click to toggle source
def test_opt options
opts = []
opts.push(*options.repos.map {|dir| "--repo #{Shellwords.escape(dir)}"})
opts.push(*options.dirs.map {|dir| "-I #{Shellwords.escape(dir)}"})
opts.push(*options.libs.map {|lib| "-r#{Shellwords.escape(lib)}"})
opts.empty? ? nil : opts.join(" ")
end